init project

This commit is contained in:
Jasder
2020-03-09 00:40:16 +08:00
commit 2937b2a94d
6549 changed files with 7215173 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
/*Nav START*/
._forum_tab {
padding-bottom: 26px;
}
.discuss-tab {
height: 90px;
}
.discuss-tab ._forum_tab a.navItem {
line-height: 2;
}
.discuss-tab ._forum_tab a.navItem:hover {
color: #4CACFF !important;
border-bottom: none !important;
}
.discuss-tab ._forum_tab a.navItem.active {
border-bottom: none !important;
border: 1px solid #4CACFF !important;
color: #4CACFF !important;
border-radius:24px;
}
.discuss-tab a.returnBtnA:hover {
border-bottom: none!important;
}
/*Nav END*/
#forum_list {
display: flex;
flex-direction: column;
}
.rc-pagination {
padding: 30px 20px;
background: #FAFAFA;
margin: 0 auto;
width: fit-content;
}
/*分页*/
.ec-pagination .rc-pagination-item{
border-radius: 2px;
width: 30px;
height: 32px;
line-height: 32px;
}
.ec-pagination a{outline: none;}
.ec-pagination .rc-pagination-jump-next{
height: 32px;
line-height: 32px;
}
.ec-pagination .rc-pagination-item:hover{
border: 1px solid #4cacff;
color: #4cacff;
}
.ec-pagination .rc-pagination-item-active{
background-color: #4CACFF;
}
.ec-pagination .rc-pagination-prev, .ec-pagination .rc-pagination-next{display: none}
.ec-pagination .rc-pagination-options-quick-jumper input{
height: 32px;
border-radius: 2px;
}
.ec-pagination .rc-pagination-options-quick-jumper{
height: 34px;
line-height: 34px;
margin-left: 0px;
}

View File

@@ -0,0 +1,266 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import Loading from '../../Loading'
import Loadable from 'react-loadable';
import classNames from 'classnames'
import MemoTechShare from './MemoTechShare'
// import MemoGuide from './MemoGuide'
// import MemoNewest from './MemoNewest'
// import MemoHottest from './MemoHottest'
import MemoDetail from './MemoDetail'
import MemoNew from './MemoNew'
import MemoMyPublish from './MemoMyPublish'
import MemoShixun from './shixun/MemoShixun'
import { TPMIndexHOC } from '../tpm/TPMIndexHOC'
import RightMyPublish from './RightMyPublish'
import UserSection from './UserSection'
import RightHotLabel from './RightHotLabel'
import RightHotQuestion from './RightHotQuestion'
import RightMemoLabel from './RightMemoLabel'
import RecommendShixun from './RecommendShixun'
import ForumsNavTab from './ForumsNavTab'
import axios from 'axios'
import 'rc-select/assets/index.css';
import './ForumsIndex.css'
import './RightSection.css'
import { SnackbarHOC, getUrl } from 'educoder'
import { CNotificationHOC } from '../courses/common/CNotificationHOC'
let _url_origin = getUrl()
const $ = window.$
$('head').append( $('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/stylesheets/css/edu-admin.css?6`) );
$('head').append( $('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/stylesheets/css/edu-forum.css?1525440977`) );
$('head').append( $('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/stylesheets/educoder/magic-check.css?1525440977`) );
setTimeout(()=>{
// 附件上传滚动条 \public\stylesheets\jquery\jquery-ui-1.9.2.css
$('head').append( $('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/stylesheets/jquery/jquery-ui-1.9.2.css`) );
}, 1000)
class ForumsIndex extends Component {
constructor(props) {
super(props)
this.state = {
searchValue: '',
enterKeyFlag: false,
showSearchValue: false,
selectedHotLabelIndex: -1,
}
}
setSearchValue = (searchValue, enterKeyFlag) => {
if (enterKeyFlag === true) {
this.setState({
selectedHotLabelIndex: -1
})
}
this.setState({
searchValue,
showSearchValue: (enterKeyFlag && searchValue) ? true : false,
enterKeyFlag: enterKeyFlag === true ? !this.state.enterKeyFlag : this.state.enterKeyFlag
})
}
setHotLabelIndex = (index, callback) => {
const newState = {
selectedHotLabelIndex: index,
}
if (index != -1) {
newState.searchValue = ''
newState.showSearchValue = false
}
this.setState({
...newState
}, callback)
}
initForumState(data) {
this.setState({...data})
}
componentDidMount() {
window.document.title = '交流问答'
}
componentWillReceiveProps(newProps, newContext) {
}
render() {
const { match, history, resLoading } = this.props
const { memo } = this.state;
const techSharePath = `${match.path}/categories/:memoType`
const guidePath = `${match.path}/categories/:memoType`
const hottestPath = `${match.path}/categories/:memoType` // ?order=hottest
const newestPath = `${match.path}/categories/:memoType` // ?order=newest
const shixunDiscussPath = `/forums/categories/shixun_discuss`
const locationPath = history.location.pathname
const isWidth100 = (locationPath.indexOf('forums/new') !== -1
|| locationPath.indexOf('/edit') !== -1) ? true : false
const pathArray = locationPath.split('/');
const isMemoDetail = (!isWidth100 &&
pathArray.length === 3 && !isNaN(parseInt(pathArray[2])) ) ? true : false
const isGuide = locationPath.indexOf('/forums/categories/3') !== -1
return (
<div className="newMain clearfix">
<div className="educontent mt30 clearfix">
{/* 左边栏 component={TechShare}
<ForumsNavTab {...this.props}></ForumsNavTab> */}
<div className={classNames('fl', { with75: !isWidth100}, { width100: isWidth100}) }>
<Switch>
<Route path={`/forums/categories/my_published`} render={
(props) => (<MemoMyPublish {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
setSearchValue={this.setSearchValue}
setHotLabelIndex={this.setHotLabelIndex}
/>)
}></Route>
<Route path={`${shixunDiscussPath}`} render={
(props) => (<MemoShixun {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
setSearchValue={this.setSearchValue}
setHotLabelIndex={this.setHotLabelIndex}
/>)
}></Route>
<Route path={`${techSharePath}`} render={
(props) => (<MemoTechShare {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
setSearchValue={this.setSearchValue}
setHotLabelIndex={this.setHotLabelIndex}
/>)
}></Route>
{/*
<Route path={`${guidePath}`} render={
(props) => (<MemoGuide {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)} />)
}></Route>
<Route path={`${hottestPath}`} render={
(props) => (<MemoHottest {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)} />)
}></Route>
<Route path={`${newestPath}`} render={
(props) => (<MemoNewest {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)} />)
}></Route> */}
{/* :forumTypeId/ */}
<Route path={`/forums/new`} render={
(props) => (<MemoNew {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
/>)
}></Route>
<Route path={`/forums/:memoId/edit`} render={
(props) => (<MemoNew {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
/>)
}></Route>
<Route path={`${match.path}/:memoId`} render={
(props) => (<MemoDetail {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
/>)
}></Route>
<Redirect from={`${match.url}`} to={`/forums/categories/all?order=newest`} />
</Switch>
</div>
{/* 右边栏 */}
{ !isWidth100 && <div className="with25 fl">
<div className="ml20">
{isMemoDetail ?
<React.Fragment>
<UserSection {...this.props} {...this.state} initForumState={(data)=>this.initForumState(data)} ></UserSection>
{/*todo 新增RightMemoLabel 和 推荐实训RecommendShixun */}
{ memo && memo.tag && <RightMemoLabel {...this.props} {...this.state}></RightMemoLabel> }
<RecommendShixun {...this.props} {...this.state}></RecommendShixun>
</React.Fragment>
:
<React.Fragment>
<RightMyPublish {...this.props} {...this.state} setSearchValue={this.setSearchValue}></RightMyPublish>
{ !isGuide && <RightHotLabel {...this.props} {...this.state} ></RightHotLabel> }
<RightHotQuestion {...this.props} {...this.state} ></RightHotQuestion>
<RecommendShixun {...this.props} {...this.state}></RecommendShixun>
</React.Fragment>
}
</div>
</div>
}
</div>
</div>
);
}
}
export default CNotificationHOC() (SnackbarHOC() ( TPMIndexHOC ( ForumsIndex ) ));
/*
:
列表所有:
http://localhost:3000/forums/categories/all
:
详情:
:
http://localhost:3000/forums/5
:
http://localhost:3000/forums/new
:
http://localhost:3000/forums/categories/my_published
*/

View File

@@ -0,0 +1,106 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
class ForumsNavTab extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onNavClick(active) {
// TODO 为什么事件发不过去
// https://github.com/facebook/react/issues/3249#issuecomment-177750141
// window.$(window).trigger('setSearchValue', '', true);
this.props.setSearchValue('')
if (!active) {
this.props.initForumState({
selectedHotLabelIndex: -1,
})
}
}
render() {
const { match, history, currentPage } = this.props
const techSharePath = `/forums/categories/5`
const guidePath = `/forums/categories/3`
const guidePaths = `/forums/categories/16`
const hottestPath = `/forums/categories/all?order=hottest` // ?order=hottest
const newestPath = `/forums/categories/all?order=newest` // ?order=newest
const shixunDiscussPath = `/forums/categories/shixun_discuss`
const locationPath = history.location.pathname + history.location.search
/*
<ul>
<li className={classNames({'selected': locationPath.indexOf(techSharePath) === 0 })}>
<Link to={`${techSharePath}`} >techShare</Link>
</li>
<li className={classNames({'selected': locationPath.indexOf(guidePath) === 0 })}>
<Link to={`${guidePath}`}>guide</Link>
</li>
</ul>
*/
return (
<div className="discuss-tab pl20 bor-bottom-greyE clearfix pr edu-back-white">
<p className="_forum_tab clearfix">
{/*<a href="/forums" className="fl font-16 ptl5-10 block mr20 active">
<span className="fl">技术分享</span>
<span className="forum_filtrate_span2 forum_filtrate_span2_bg mt10 ml10 fl">219</span>
</a>*/}
<Link to={`${newestPath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf('order=newest') !== -1 })}
onClick={()=>this.onNavClick(locationPath.indexOf('order=newest') !== -1)}
>
<span className="fl">最新回复</span>
</Link>
<Link to={`${hottestPath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf('order=hottest') !== -1 })}
onClick={()=>this.onNavClick(locationPath.indexOf('order=hottest') !== -1)}
>
<span className="fl">热门话题</span>
</Link>
<Link to={`${shixunDiscussPath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf('shixun_discuss') !== -1 })}
onClick={()=>this.onNavClick(locationPath.indexOf('shixun_discuss') !== -1)}
>
<span className="fl">实训回复</span>
</Link>
<Link to={`${techSharePath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf(techSharePath) === 0 })}
onClick={()=>this.onNavClick(locationPath.indexOf(techSharePath) === 0)}
>
<span className="fl">技术分享</span>
</Link>
<Link to={`${guidePath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf(guidePath) === 0 })}
onClick={()=>this.onNavClick(locationPath.indexOf(guidePath) === 0)}
>
<span className="fl">操作指南</span>
</Link>
<Link to={`${guidePaths}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf(guidePaths) === 0 })}
onClick={()=>this.onNavClick(locationPath.indexOf(guidePaths) === 0)}
>
<span className="fl">通知公告</span>
</Link>
{/*<a href="/forums?type=discuss" className="fl font-16 ptl5-10 block mr20">
<span className="fl">实训交流</span>
<span className="forum_filtrate_span2 mt10 ml10 fl">1391</span>
</a>*/}
</p>
</div>
);
}
}
export default ForumsNavTab;

View File

@@ -0,0 +1,861 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import axios from 'axios'
import moment from 'moment'
import Comments from '../comment/Comments'
import update from 'immutability-helper'
// import Tooltip from 'material-ui/Tooltip';
import RewardDialog from '../common/RewardDialog';
import {ImageLayerOfCommentHOC} from '../page/layers/ImageLayerOfCommentHOC'
import MemoDetailKEEditor from './MemoDetailKEEditor'
import MemoDetailMDEditor from './MemoDetailMDEditor'
import { bytesToSize, CBreadcrumb ,htmlEncode} from 'educoder'
import { Tooltip } from 'antd'
// import CBreadcrumb from '../courses/common/CBreadcrumb'
import { typeNameMap2 } from './MemoNew'
import CaseDetail from "../moop_cases/CaseDetail";
const $ = window.$
function urlStringify(params) {
let noParams = true;
let paramsUrl = '';
for (let key in params) {
noParams = false;
paramsUrl += `${key}=${params[key]}&`
}
if (noParams) {
return '';
}
paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1);
return paramsUrl;
}
class MemoDetail extends Component {
constructor(props) {
super(props)
this.state = {
memoLoading: true,
hasMoreComments: false,
pageCount: 2,
goldRewardDialogOpen: false
}
}
componentDidMount() {
// window.$("html,body").animate({"scrollTop":0})
const { match } = this.props
const memoUrl = `/memos/${match.params.memoId}.json`;
this.setState({
memoLoading: true
})
axios.get(memoUrl,{
// withCredentials: true,
})
.then((response) => {
const memo = response.data.memo
if (response.data.status === -1) {
setTimeout(() => {
this.props.showNotification('帖子不存在!')
}, 300)
this.props.history.push(`/forums`)
return;
} else if (memo) {
// this.setState({...response.data})
const { memo_replies, memo } = response.data;
let hasMoreComments = false;
if (memo_replies && memo_replies.length === 10 && memo.replies_count > 10) {
// 遍历一遍,计算下是否还有评论未加载
let totalCount = 10;
memo_replies.forEach(item=>{
totalCount += item.children.length
})
if (totalCount < memo.replies_count) {
hasMoreComments = true;
}
}
this.setState({
hasMoreComments,
pageCount: 2,
comments: memo_replies
})
delete response.data.memo_replies;
// reset
response.data.memo.praise_count = response.data.memo.memo_praise_count
this.props.initForumState(response.data)
// const user = response.data.current_user;
// user.tidding_count = response.data.tidding_count;
// this.props.initCommonState(user)
}
this.setState({
memoLoading: false
})
}).catch((error) => {
console.log(error)
})
$('body>#root').on('onMemoDelete', (event) => {
// const val = $('body>#root').data('onMemoDelete')
const val = window.onMemoDelete ;
this.onMemoDelete( JSON.parse(decodeURIComponent(val)) )
})
}
componentWillUnmount() {
$('body>#root').off('onMemoDelete')
}
onMemoDelete(memo) {
const deleteUrl = `/memos/${memo.id}.json`;
// 获取memo list
axios.delete(deleteUrl, {
// withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showNotification('删除成功');
this.props.history.push(`/forums`)
} else if (status === -1) {
this.props.showNotification('帖子已被删除');
this.props.history.push(`/forums`)
}
}).catch((error) => {
console.log(error)
})
}
componentDidUpdate(prevProps, prevState, snapshot) {
// if (this.props.memo && this.props.memo.content
// && (!prevProps.memo || prevProps.memo.content != this.props.memo.content) ) {
if (this.props.memo && this.props.memo.content && prevState.memoLoading === true && this.state.memoLoading === false) {
// md渲染content等xhr执行完即memoLoading变化memo.content更新后初始化md
if (this.props.memo.is_md) {
setTimeout(()=>{
var shixunDescr = window.editormd.markdownToHTML("memo_content_editorMd", {
htmlDecode: "style,script,iframe", // you can filter tags decode
taskList: true,
tex: true, // 默认不解析
flowChart: true, // 默认不解析
sequenceDiagram: true // 默认不解析
});
}, 200)
}
}
}
clickPraise(){
const { memo } = this.props;
const url = `/discusses/${memo.id}/plus.json`;
console.log(url)
axios.post(url, {
container_type: 'Memo',
type: 1 // "踩0赞1"
},
{
// withCredentials: true
}
).then((response) => {
const newMemo = Object.assign({}, this.props.memo)
newMemo.praise_count = response.data.praise_count
newMemo.user_praise = !newMemo.user_praise
this.props.initForumState({memo : newMemo })
}).catch((error) => {
console.log(error)
})
}
renderAttachment() {
const { memo, attachments_list } = this.props;
const attachments = []
attachments_list.forEach((item, index) => {
const ar = item.url.split('/')
const fileName = item.title
let filesize = item.filesize
attachments.push(
<div className="color-grey df" key={index} style={{ lineHeight: '17px'}}>
<a className="color-grey ">
<i className="font-14 color-green iconfont icon-fujian mr8" aria-hidden="true"></i>
</a>
<a href={item.url} title={fileName.length > 30 ? fileName : ''}
className="mr12 color9B9B overflowHidden1" length="58" style={{maxWidth: '480px'}}>
{fileName}
</a>
<span className="color656565 mt2 color-grey-6 font-12 mr8">{filesize}</span>
</div>
)
})
return attachments;
}
// ------------------------------------------------------------------------------------------- comments START
// ------------------------------------------------------------------------------------------- comments START
_getUser() {
const { current_user } = this.props;
current_user.user_url = `/users/${current_user.login}`;
return current_user;
}
_findById(id, arg_comments) {
const comments = arg_comments;
for(let i = 0; i < comments.length; i++) {
if (id === comments[i].id) {
return i;
}
}
}
replyComment = (commentContent, id, editor) => {
const { showNotification } = this.props;
if (!commentContent || commentContent.length === 0) {
showNotification('必须填写内容!')
return;
}
if (this.props.memo.id === id ) { // 回复帖子
this.createNewComment(commentContent, id, editor);
return;
}
// /${id}
const url = `/memos/reply.json`;
const { comments } = this.state;
const user = this._getUser();
/*
移除末尾的空行
.replace(/(\n<p>\n\t<br \/>\n<\/p>)*$/g,'');
*/
if (commentContent) {
commentContent = commentContent.replace(/(\n<p>\n\t<br \/>\n<\/p>)*$/g,'');
}
commentContent=htmlEncode(commentContent)
axios.post(url, {
parent_id: id,
content: commentContent
},
{
// withCredentials: true
}
).then((response) => {
response.data.memo = response.data
if (response.data.memo) {
let newDiscuss = response.data.memo;
var commentIndex = this._findById(id, comments);
let comment = comments[commentIndex];
if (!comment.children ) {
comment.children = []
}
// TODO userName iamge_url
comment.children.push( {
"can_delete": true,
"content": commentContent,
"image_url": user.image_url,
"username": user.username,
"user_login": user.login,
"id": newDiscuss.id,
// "position": newDiscuss.position,
"time": "1分钟前",
"praise_count": 0,
"user_id": newDiscuss.author_id,
})
comments[commentIndex] = comment
// ke
editor.html && editor.html('')
// md
if (editor.setValue) {
editor.setValue('')
const $ = window.$
var view_selector = `.commentItemMDEditorView_${id}`
$(view_selector).hide();
}
this.setState({
// runTesting: false,
comments: comments
}, ()=>{
// keditor代码美化
editor.html && window.prettyPrint()
})
const newMemo2 = Object.assign({}, this.props.memo);
newMemo2.replies_count = newMemo2.replies_count + 1;
this.props.initForumState({
memo: newMemo2
})
}
}).catch((error) => {
console.log(error)
})
}
deleteComment = (parrentComment, childCommentId) => {
let deleteCommentId = parrentComment.id
if (childCommentId) {
deleteCommentId = childCommentId;
}
const url = `/memos/${deleteCommentId}.json`
let comments = this.state.comments;
axios.delete(url,
{
// withCredentials: true
}
).then((response) => {
// TODO 删除成功或失败
if (response.data && response.data.status === 0) {
const commentIndex = this._findById(parrentComment.id, comments);
// https://stackoverflow.com/questions/29527385/removing-element-from-array-in-component-state
if (!childCommentId) {
this.setState((prevState) => ({
comments: update(prevState.comments, {$splice: [[commentIndex, 1]]})
}))
// if (this.state.comments.length <= 5) {
// this.fetchComment()
// }
} else {
let childCommentIndex = this._findById(childCommentId, comments[commentIndex].children);
comments[commentIndex].children = update(comments[commentIndex].children, {$splice: [[childCommentIndex, 1]]})
this.setState({ comments })
}
const newMemo = Object.assign({}, this.props.memo);
newMemo.replies_count = newMemo.replies_count - 1;
this.props.initForumState({
memo: newMemo
})
}
}).catch((error) => {
console.log(error)
})
}
// 评论点赞
commentPraise = (discussId) => {
const { comments } = this.state;
const commentIndex = this._findById(discussId, comments);
const url = `/discusses/${discussId}/plus.json`
axios.post(url, {
// id: discussId,
// container_id: challenge.id,
container_type: 'Memo', //Discuss
type: comments[commentIndex].user_praise === true ? 0 : 1, // "踩0赞1"
},
{
// withCredentials: true
}
).then((response) => {
if (response.data.praise_count === 0 || response.data.praise_count) {
comments[commentIndex].user_praise = !comments[commentIndex].user_praise;
comments[commentIndex].praise_count = response.data.praise_count;
this.setState({
comments
})
}
}).catch((error) => {
console.log(error)
})
}
rewardCode = (parrentComment, childComment, amount) => {
const { showNotification } = this.props;
const { comments } = this.state;
let handleComment = parrentComment
if (childComment) {
handleComment = childComment;
}
let handleCommentId = handleComment.id;
const url = `/discusses/${handleCommentId}/reward_code.json`
axios.post(url, {
id: handleCommentId,
container_type: 'Memo',
score: amount,
user_id: handleComment.user_id
},
{
// withCredentials: true
}
).then((response) => {
if (response.data && response.data.code) {
const commentIndex = this._findById(parrentComment.id, comments);
if (childComment) {
const childCommentIndex = this._findById(handleComment.id, parrentComment.children);
const newChildComment = Object.assign({}, childComment);
newChildComment.reward = response.data.code
parrentComment.children[childCommentIndex] = newChildComment
comments[commentIndex] = parrentComment;
this.setState({
comments
})
} else {
comments[commentIndex].reward = response.data.code;
this.setState({
comments
})
}
}
}).catch((error) => {
console.log(error)
showNotification('奖励失败,请联系系统管理员!')
})
}
hiddenComment = (item, childCommentId) => {
const id = item.id
const { showNotification } = this.props;
const user = this._getUser();
const url = `/memos/${id}/hidden.json`
const { comments } = this.state;
const commentIndex = this._findById(id, comments);
const comment = comments[commentIndex];
axios.post(url, {
hidden: !comment.hidden ? "1" : "0"
},
{
// withCredentials: true
}
).then((response) => {
if (response.data.status === -1) {
showNotification(response.data.message)
return;
}
if (response.data.status === 0) {
if (!childCommentId) {
comment.hidden = !comment.hidden;
this.setState({
comments: comments
})
} else { // TODO 目前子回复没hidden字段
let childCommentIndex = this._findById(childCommentId, comments[commentIndex].children);
const childComment = comments[commentIndex].children[childCommentIndex]
childComment.hidden = !childComment.hidden;
this.setState({ comments })
}
}
// {"message":"Couldn't find Discuss with id=911","status":-1}
}).catch((error) => {
console.log(error)
})
}
createNewComment = (commentContent, id, editor) => {
let content = commentContent;
const { memo } = this.props;
if(content != undefined){
content = content.replace(/(\n<p>\n\t<br \/>\n<\/p>)*$/g,'');
var beforeImage = content.split("<img");
var afterImage = content.split("/>");
if(beforeImage[0] == "" && afterImage[1] == ""){
window.notice_box('不支持纯图片评论<br/>请在评论中增加文字信息');
return;
}
}
// /${memo.id}
const url = `/memos/reply.json`;
let { comments } = this.state;
const user = this._getUser();
content=htmlEncode(content)
axios.post(url, {
parent_id: memo.id,
content: content
},
{
// withCredentials: true
}
).then((response) => {
if (response.data.status === -1) {
console.error('服务端异常')
return;
}
if (response.data) {
response.data.memo = response.data
const newMemo = response.data.memo;
// ke
editor.html && editor.html('');
editor.afterBlur && editor.afterBlur()
// md
editor.setValue && editor.setValue('')
if (!comments) {
comments = [];
}
comments.unshift( {
"can_delete": true,
"admin": user.admin,
"content": content,
"image_url": user.image_url,
"username": user.username,
"user_login": user.login,
"id": newMemo.id,
"reward": null,
"hidden": newMemo.hidden,
"user_praise": false,
"time": "1分钟前",
"praise_count": 0,
"user_id": user.user_id,
})
this.setState({
comments
})
const newMemo2 = Object.assign({}, this.props.memo);
newMemo2.replies_count = newMemo2.replies_count + 1;
this.props.initForumState({
memo: newMemo2
})
}
}).catch((error) => {
console.log(error)
})
}
moreMemos = () => {
let { comments, pageCount } = this.state;
let { memo } = this.props;
const user = this._getUser();
const url = `/memos/${memo.id}/more_reply.json?page=${pageCount}`;
axios.get(url, {
},
{
// withCredentials: true
}
).then((response) => {
if (response.data.status === -1) {
console.error('服务端异常')
return;
}
let { memo_replies } = response.data;
if (!memo_replies || memo_replies.length === 0) {
this.setState({
hasMoreComments: false
})
return;
}
if (response.data.memos_count) {
const newComments = comments.concat(memo_replies);
const hasMoreComments = memo_replies.length === 10
this.setState({
comments: newComments,
hasMoreComments,
pageCount: pageCount+1
})
}
}).catch((error) => {
console.log(error)
})
}
// ------------------------------------------------------------------------------------------- comments END
// ------------------------------------------------------------------------------------------- comments END
// 置顶
setTop(memo) {
const params = {
sticky: memo.sticky ? 0 : 1,
}
if (this.state.p_s_order) {
params.order = this.state.p_s_order;
}
if (this.state.p_forum_id) {
params.forum_id = this.state.p_forum_id;
}
let paramsUrl = urlStringify(params)
const set_top_or_down_Url = `/memos/${memo.id}/sticky_or_cancel.json?${paramsUrl}`;
// 获取memo list
axios.post(set_top_or_down_Url, {
// withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showNotification( memo.sticky ? '取消置顶成功' : '置顶成功');
memo.sticky = memo.sticky ? false : true
this.setState({
memo: Object.assign({}, memo)
})
}
}).catch((error) => {
console.log(error)
})
}
// --------------------------------------------------------------------------------------------帖子獎勵
rewardCodeMemo = (inputVal) => {
console.log(inputVal)
const { memo, author_info } = this.props;
const newMemo = Object.assign({}, memo);
const _reward = parseInt(inputVal)
const url = `/discusses/${memo.id}/reward_code.json`
axios.post(url, {
id: memo.id,
container_type: 'Memo',
score: _reward,
user_id: author_info.user_id
}, {
// withCredentials: true,
})
.then((response) => {
const { code } = response.data;
if (code > 0) {
newMemo.reward = code
this.props.initForumState({
memo: newMemo
})
this.props.showNotification( '奖励成功' );
} else {
this.props.showNotification( '奖励失败,请联系系统管理员!' );
}
}).catch((error) => {
console.log(error)
})
}
setRewardDialogVisible = (visible) => {
this.setState({
goldRewardDialogOpen: visible
})
}
showRewardDialog = () => {
this.setState({
goldRewardDialogOpen: true
})
}
// --------------------------------------------------------------------------------------------帖子獎勵 END
showCommentInput = () => {
debugger
if (window.__useKindEditor === true) {
this.refs.editor.showEditor();
} else {
this.refs.editor.showEditor();
}
}
render() {
const { match, history } = this.props
const { memo, recommend_shixun, current_user,author_info } = this.props;
const { comments, hasMoreComments, goldRewardDialogOpen } = this.state;
document.title = memo&&memo.subject!=undefined?memo&&memo.subject:"交流问答";
if (!memo || this.state.memoLoading) {
return <div className="edu-back-white" id="forum_index_list"></div>
}
let _current_user = {}
if (current_user) {
_current_user = current_user
}
(_current_user.user_url = `/users/${_current_user.login}`);
memo.isDetailPage = true;
// TODO 图片上传地址
return (
<React.Fragment>
<CBreadcrumb items={[
{ to: `/forums/categories/${memo.forum_id}`, name: typeNameMap2[memo.forum_id]},
{ name: '详情' },
]}
separator={' / '}
></CBreadcrumb>
<div className="edu-back-white memoDetail" id="forum_index_list"> {/* fl with100 */}
<style>{`
.memoDetail .commentsbtn {
margin-top: 6px;
}
`}</style>
<RewardDialog goldRewardDialogOpen={goldRewardDialogOpen}
setRewardDialogVisible={this.setRewardDialogVisible}
rewardCode={this.rewardCodeMemo}
{...this.props}
></RewardDialog>
<div className="clearfix">
<div id="forum_list" className="forum_table mh650">
<div className="padding40-30 bor-bottom-greyE">
<div className="font-16 mb5 cdefault clearfix pr pr35" style={{display: 'flex', alignItems: 'center'}}>
{/* overflowHidden1 */}
<span className="noteDetailTitle " style={{maxWidth: '634px'}}>{memo.subject}</span>
{ memo.sticky && <span className="btn-cir btn-cir-red ml10 "
style={{ height: '20px', alignSelf: 'flex-start', marginTop: '10px' }}
>置顶</span>}
{ !!memo.reward &&
<Tooltip title={`获得平台奖励金币:${memo.reward}`}>
<span className="color-orange font-14 ml15"
style={{ height: '20px', alignSelf: 'flex-start', marginTop: '1px' }}
>
<i className="iconfont icon-gift mr5"></i>
<span style={{ 'vertical-align': 'sub' }}>{memo.reward}</span>
</span>
</Tooltip>
}
<div style={{ flex: 1, alignSelf: 'flex-start' }}>
{ _current_user && (_current_user.admin === true || _current_user.user_id === author_info.user_id) &&
<div className="edu-position-hidebox" style={{position: 'absolute', right: '12px',top:'4px'}}>
<a href="javascript:void(0);"><i className="fa fa-bars font-16"></i></a>
<ul className="edu-position-hide undis">
{ _current_user.admin === true &&
( memo.sticky === true ?
<li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>取消置顶</a></li>
:
<li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>&nbsp;&nbsp;</a></li> )
}
<li><Link to={`/forums/${memo.id}/edit`}>&nbsp;&nbsp;</Link></li>
<li>
<a href="javascript:void(0)" onClick={() =>
window.delete_confirm_box_2_react(`onMemoDelete`, '您确定要删除吗?' , memo)}>
&nbsp;&nbsp;</a>
</li>
</ul>
</div>
}
{/* <Link className={`task-hide fr return_btn color-grey-6 mt2 ${ _current_user && (_current_user.admin === true
|| _current_user.user_id === author_info.user_id) ? '': 'no_mr'} `} to="/forums"
style={{ marginRight: '10px'}}
>
返回
</Link> */}
</div>
</div>
<div className="color-grey-9 clearfix">
<span className="fl">{moment(memo.time).fromNow()} 发布</span>
<div className="fr detailIcons">
<style>{`
.detailIcons i{
vertical-align: sub;
}
`}</style>
{ _current_user.admin && <Tooltip title={ "帖子奖励" }>
<span className="noteDetailNum rightline cdefault" style={{padding: '0 4px', cursor: 'pointer'}}>
<i className="iconfont icon-jiangli mr5" onClick={this.showRewardDialog}></i>
</span>
</Tooltip> }
<span className={`noteDetailNum ${!!memo.replies_count ? 'rightline' : ''} cdefault`}>
<i className="iconfont icon-liulanyan mr5"></i>{memo.viewed_count}
</span>
{ !!memo.replies_count &&
<Tooltip title={ "写评论" }>
<a href="javascript:void(0)" className="noteDetailNum">
<i className="iconfont icon-huifu1 mr5" onClick={this.showCommentInput}></i>{memo.replies_count}
</a>
</Tooltip>
}
</div>
</div>
</div>
<div className="padding40 memoContent new_li">
{ !memo.is_md ?
<div dangerouslySetInnerHTML={{__html: memo.content}}></div> :
<div id="memo_content_editorMd" className="new_li">
<textarea style={{'display': 'none'}}>
{memo.content}
</textarea>
</div>
}
</div>
<div className="padding40 bor-bottom-greyE" style={{ paddingTop: '0px'}}>
<div className="mt10 mb20">
<p className={`noteDetailPoint ${memo.user_praise ? 'Pointed' : ''} `} onClick={()=>{this.clickPraise()}} >
<i className="iconfont icon-dianzan"></i><br/>
<span>{memo.praise_count}</span>
</p>
</div>
{ this.props.attachments_list &&
<div>
{this.renderAttachment()}
</div>
}
</div>
{ window.__useKindEditor === true ?
<MemoDetailKEEditor ref="editor" memo={memo} {...this.props}></MemoDetailKEEditor>
:
<MemoDetailMDEditor ref="editor" memo={memo} {...this.props}></MemoDetailMDEditor>
}
{/* onClick={ this.createNewComment } */}
<div className="padding40 bor-bottom-greyE memoReplies commentsDelegateParent"
style={{ display: (comments && !!comments.length) ? 'block' : 'none' }}>
<div className="replies_count">
<span className="labal">全部回复</span>
<span className="count">{memo.replies_count}</span>
</div>
<Comments comments={comments} user={_current_user}
replyComment={this.replyComment}
deleteComment={this.deleteComment}
commentPraise={this.commentPraise}
rewardCode={this.rewardCode}
hiddenComment={this.hiddenComment}
></Comments>
{ hasMoreComments ?
<div className="memoMore" style={{ cursor: 'default' }}>
<a onClick={this.moreMemos}>查看更多评论</a>
<div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div>
</div>
:
<div className="memoMore">
<div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div>
</div>}
</div>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default ImageLayerOfCommentHOC() (MemoDetail);

View File

@@ -0,0 +1,25 @@
.editorMD ol, .editorMD ul, .editorMD li {
list-style-type: decimal;
}
/*md编辑器 resizeBar*/
.editor__resize {
position: absolute;
width: 120px;
height: 4px;
left: 50%;
transform: translateX(-50%);
margin-top: 2px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
cursor: row-resize;
text-indent: 110%;
white-space: nowrap;
overflow: hidden;
text-transform: capitalize;
box-sizing: border-box;
/*transform: translateX(-22%);*/
}

View File

@@ -0,0 +1,66 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
const $ = window.$;
class MemoDetailKEEditor extends Component {
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.memo && (!prevProps.memo || this.props.memo.id != prevProps.memo.id)) {
this.keEditor = window.sd_create_editor_from_data(this.props.memo.id, null, "100%", "Memo");
window._kk = this.keEditor
}
}
componentDidMount() {
this.keEditor = window.sd_create_editor_from_data(this.props.memo.id, null, "100%", "Memo");
window._kk = this.keEditor
}
showEditor() {
$("html, body").animate({ scrollTop: $('#commentInput').offset().top - 100 }, 1000, () => {
if (this.keEditor) {
const FF = !(window.mozInnerScreenX == null);
if (FF) {
this.keEditor.focus()
} else {
this.keEditor.edit.win.document.body.focus()
}
}
});
}
render() {
const { match, history, memo } = this.props
if (!memo) {
return <div></div>
}
return (
<div nhname={`new_message_${memo.id}`} className="" style={{ paddingTop: '20px', paddingBottom: '20px' }}
id="commentInput">
<form acceptCharset="UTF-8" action="/discusses?challenge_id=118&dis_id=61&dis_type=Shixun"
style={{ flexDirection: 'column', width: '94%', marginLeft: '3%'}}
className="df" data-remote="true" id="new_comment_form" method="post">
<div nhname={`toolbar_container_${memo.id}`}></div>
<textarea id={`comment_news_${memo.id}`}
nhname={`new_message_textarea_${memo.id}`} name="content" className="none">
</textarea>
</form>
<a id={`new_message_submit_btn_${memo.id}`} href="javascript:void(0)"
style={{display: 'none'}} className="commentsbtn task-btn task-btn-blue fr">
发送
</a>
</div>
);
}
}
export default ( MemoDetailKEEditor );

View File

@@ -0,0 +1,219 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { getUploadActionUrl } from 'educoder'
import './MemoDetailEditor.css'
require('codemirror/lib/codemirror.css');
const $ = window.$;
class MemoDetailMDEditor extends Component {
constructor(props) {
super(props)
this.state = {
isInited: this.props.usingMockInput ? false : true,
isError: false,
errorMsg: ''
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.memo && (!prevProps.memo || this.props.memo.id != prevProps.memo.id)) {
// this.keEditor = window.sd_create_editor_from_data(this.props.memo.id, null, "100%", "Memo");
// window._kk = this.keEditor
}
}
initMDEditor = () => {
// 因为props.memo不存在时本组件不会被加载这里直接在didMount里初始化即可
const placeholder = '我要回复...'
// const imageUrl = `/upload_with_markdown?container_id=${this.props.memo.id}&container_type=Memo`;
const imageUrl = `${getUploadActionUrl()}`;
if (this.isMDInited) {
return;
}
this.isMDInited = true
// 执行太快了,样式不正常
window.__tt = 400;
setTimeout(() => {
console.log('create_editorMD_4comment')
var commentMDEditor = window.create_editorMD_4comment("memo_comment_editorMd", '', this.props.height || 240, placeholder, imageUrl, () => {
// commentMDEditor.focus()
this.initDrag()
commentMDEditor.cm.on("change", (_cm, changeObj) => {
this.setState({
isError: false,
errorMsg: ''
})
})
// commentMDEditor.cm.focus()
}, {
watch: false,
dialogLockScreen: false,
});
this.commentMDEditor = commentMDEditor;
window.commentMDEditor = commentMDEditor;
}, window.__tt)
}
componentDidMount() {
!this.props.usingMockInput && this.initMDEditor()
}
initDrag = () => {
window.initMDEditorDragResize(".editor__resize", this.commentMDEditor)
}
onCommit = () => {
if(this.props.checkIfLogin()===false){
this.props.showLoginDialog()
return
}
if(this.props.checkIfProfileCompleted()===false){
this.props.showaccountprofileDialog()
return
}
const content = this.commentMDEditor.getValue();
// this.props.showError ==
if (this.props.showError == true) {
if (!content || content.trim() == "") {
this.setState({
isError: true,
errorMsg: '不能为空'
})
return;
} else if (content.length > 2000) {
this.setState({
isError: true,
errorMsg: '不能超过2000个字符'
})
return;
}
this.setState({
isError: false,
errorMsg: ''
})
}
if (this.props.replyComment) {
this.props.replyComment(content, this.props.memo.id, this.commentMDEditor)
} else {
window.$(document).trigger("onReply", { commentContent: content
, id: this.props.memo.id, editor: this.commentMDEditor } );
}
}
showEditor() {
$("html, body").animate({ scrollTop: $('.commentInput:visible').offset().top - 100 }, 1000, () => {
if (this.commentMDEditor) {
this.commentMDEditor.cm.focus()
} else {
this.onMockInputClick()
}
});
}
close = () => {
this.setState({isInited: false})
}
onMockInputClick = () => {
this.setState({isInited: true})
if (!this.isMDInited) {
this.initMDEditor()
} else {
setTimeout(() => {
this.commentMDEditor && this.commentMDEditor.cm.focus()
}, 10)
}
}
render() {
const { match, history, memo, placeholder, className, imageExpand } = this.props
const { isInited, errorMsg } = this.state
if (!memo) {
return <div></div>
}
return (
<React.Fragment>
<style>{`
.mockInputWrapper {
display: flex;
padding: 20px 30px 20px 30px;
border-bottom: 1px solid #EEEEEE;
}
.mockInputWrapper input {
flex:1;
padding-left: 10px;
height: 40px;
background: rgb(246,246,246);
margin-right: 20px;
}
.mockInputWrapper a.commentsbtn {
height: 40px;
display: inline-block;
margin-top: 0px !important;
vertical-align: text-top;
padding-top: 6px;
width: 60px;
margin-right: 0px !important;
}
.commentInput {
}
.commentInput .editormd{
width:100%!important;
}
`}</style>
<div style={{ display: isInited ? 'none' : '', borderBottom: `${this.props.commentsLength == 0 ? 'none' : '1px solid #EEEEEE'}`}}
className={`mockInputWrapper commentInput ${className}`} >
<input onClick={this.onMockInputClick} placeholder={placeholder || '我要回复'}></input>
<a href="javascript:void(0)"
onClick={this.onMockInputClick} className="commentsbtn task-btn task-btn-blue">
{this.props.buttonText || '发送'}
</a>
</div>
<style>
{/*
先注释了影响到了md的拖拽
{
`
.commentInputs{
height: 250px;
}
`
} */}
</style>
<div nhname={`new_message_${memo.id}`}
className={`commentInput commentInputs ${className} ${imageExpand && 'editormd-image-click-expand' }`}
style={{ padding: '30px',boxSizing:"border-box", display: isInited ? '' : 'none', paddingBottom: '40px' }} >
<div id="memo_comment_editorMd" className="editorMD" style={{ marginBottom: '0px'
, border: errorMsg ? '1px solid red' : '1px solid #ddd'}}>
<textarea style={{'display': 'none'}}>
</textarea>
</div>
<div className="editor__resize" href="javascript:void(0);">调整高度</div>
{ errorMsg && <span className="fl" style={{color: 'red', marginTop: '6px',
marginLeft: '4px'}}>{errorMsg}</span> }
<div style={{height: "16px"}}>
<a id={`new_message_submit_btn_${memo.id}`} href="javascript:void(0)"
onClick={this.onCommit} className="commentsbtn task-btn task-btn-blue fr">
{this.props.buttonText || '发送'}
</a>
</div>
</div>
</React.Fragment>
);
}
}
export default ( MemoDetailMDEditor );

View File

@@ -0,0 +1,173 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { getUploadActionUrl } from 'educoder'
import './MemoDetailEditor.css'
require('codemirror/lib/codemirror.css');
const $ = window.$;
///作业回答 专用
class MemoDetailMDEditortwo extends Component {
constructor(props) {
super(props)
this.state = {
isInited: this.props.usingMockInput ? false : true,
isError: false,
errorMsg: ''
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.memo && (!prevProps.memo || this.props.memo.id != prevProps.memo.id)) {
// this.keEditor = window.sd_create_editor_from_data(this.props.memo.id, null, "100%", "Memo");
// window._kk = this.keEditor
}
}
initMDEditor = () => {
// 因为props.memo不存在时本组件不会被加载这里直接在didMount里初始化即可
const placeholder = '我要回复...'
// const imageUrl = `/upload_with_markdown?container_id=${this.props.memo.id}&container_type=Memo`;
const imageUrl = `${getUploadActionUrl()}`;
// 执行太快了,样式不正常
window.__tt = 400;
setTimeout(() => {
var commentMDEditor = window.create_editorMD_4comment("memo_comment_editorMd", '', this.props.height || 240, placeholder, imageUrl, () => {
commentMDEditor.focus();
this.initDrag();
commentMDEditor.cm.on("change", (_cm, changeObj) => {
this.setState({
isError: false,
errorMsg: ''
})
})
}, {
watch: false,
dialogLockScreen: false,
});
this.commentMDEditor = commentMDEditor;
window.commentMDEditor = commentMDEditor;
}, window.__tt)
};
componentDidMount() {
!this.props.usingMockInput && this.initMDEditor()
}
initDrag = () => {
window.initMDEditorDragResize(".editor__resize", this.commentMDEditor)
}
onCommit = () => {
const content = this.commentMDEditor.getValue();
// this.props.showError ==
if (this.props.showError == true) {
if (!content || content.trim() == "") {
this.setState({
isError: true,
errorMsg: '不能为空'
})
return;
} else if (content.length > 2000) {
this.setState({
isError: true,
errorMsg: '不能超过2000个字符'
})
return;
}
this.setState({
isError: false,
errorMsg: ''
})
}
window.$(document).trigger("onReply", { commentContent: content
, id: this.props.memo.id, editor: this.commentMDEditor } );
}
showEditor() {
$("html, body").animate({ scrollTop: $('#commentInput').offset().top - 100 }, 1000, () => {
if (this.commentMDEditor) {
this.commentMDEditor.cm.focus()
} else {
$('#commentInput input')[0].click()
}
});
}
onMockInputClick = () => {
this.setState({isInited: true})
this.initMDEditor()
}
render() {
const { match, history, memo, placeholder } = this.props
const { isInited, errorMsg } = this.state
if (!memo) {
return <div></div>
}
return (
<React.Fragment>
<style>{`
.mockInputWrapper {
display: flex;
padding: 30px;
}
.mockInputWrapper input {
flex:1;
padding-left: 10px;
height: 40px;
background: rgb(246,246,246);
margin-right: 20px;
}
.mockInputWrapper a.commentsbtn {
height: 40px;
display: inline-block;
margin-top: 0px !important;
vertical-align: text-top;
padding-top: 6px;
width: 60px;
margin-right: 0px !important;
}
#commentInput .editormd{
width:100%!important;
}
`}</style>
<div style={{ display: isInited ? 'none' : ''}} className="mockInputWrapper" id="commentInput">
<input onClick={this.onMockInputClick} placeholder={placeholder || '我要回复'}></input>
<a href="javascript:void(0)"
onClick={this.onMockInputClick} className="commentsbtn task-btn task-btn-blue">
发送
</a>
</div>
<div nhname={`new_message_${memo.id}`} className=""
style={{ padding: '30px',boxSizing:"border-box", display: isInited ? '' : 'none' }} id="commentInput">
<div id="memo_comment_editorMd" className="editorMD" style={{ marginBottom: '0px'
, border: errorMsg ? '1px solid red' : '1px solid #ddd'}}>
<textarea style={{'display': 'none'}}>
</textarea>
</div>
<div className="editor__resize" href="javascript:void(0);">调整高度</div>
{ errorMsg && <input className="fl" style={{color: 'red', marginTop: '6px',
marginLeft: '4px'}}>{errorMsg}</input> }
<a id={`new_message_submit_btn_${memo.id}`} href="javascript:void(0)"
onClick={this.onCommit} className="commentsbtn task-btn-blue task-btn fr ">
发送
</a>
</div>
</React.Fragment>
);
}
}
export default ( MemoDetailMDEditortwo );

View File

@@ -0,0 +1,60 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import {getImageUrl, toPath} from 'educoder';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
import { queryString, ThemeContext } from 'educoder'
class MemoList extends Component {
render() {
const { match, history, currentPage, memo_count ,memo_list, renderMemoList, onPaginationChange } = this.props
let theme = this.context;
return (
<React.Fragment>
<div id="forum_list" className="forum_table">
<style>{`
.forum_table_item .item_name:hover {
color: ${theme.foreground_select}
}
`}</style>
<div className="mh650 edu-back-white">
{!memo_list || memo_list.length === 0 ?
<div className="edu-tab-con-box clearfix edu-txt-center">
<img className="edu-nodata-img mb20" src={getImageUrl("images/educoder/nodata.png")}/>
<p className="edu-nodata-p mb30">暂时还没有相关数据哦</p>
</div>
: renderMemoList()
}
</div>
</div>
{ !!memo_count && memo_count > 15 &&
<div style={{ width: '100%', background: '#FAFAFA'}}>
<Pagination className={'ec-pagination'}
onChange={(pageNum, pageSize) => onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/>
</div> }
</React.Fragment>
);
}
}
MemoList.contextType = ThemeContext;
export default ( MemoList );

View File

@@ -0,0 +1,73 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
// import queryString from 'query-string'
import { queryString } from 'educoder'
import MemoList from './MemoList'
class MemoMyPublish extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
componentDidMount() {
}
componentWillUnmount() {
}
componentWillReceiveProps(newProps, newContext) {
}
renderMemoList() {
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count, memo_list } = this.props
return (
<React.Fragment>
<div className="discuss-tab bor-bottom-greyE clearfix pr boxsizing">
<p className="_forum_tab pl20 pr20 clearfix boxsizing" style={{fontSize:'18px', color:'rgba(5,16,26,1)'}}>
我的发布
<Link className="returnBtnA fr mr10" to={`/forums`}><span className="color-grey-9 font-16">返回</span></Link>
</p>
</div>
<MemoList {...this.props} renderMemoList={() => this.renderMemoList()}
onPaginationChange={ (pageNum, pageSize) => this.props.onPaginationChange(pageNum, pageSize) }
>
</MemoList>
</React.Fragment>
);
}
}
export default postPaginationHOC({ isMyPublish: true }) ( MemoMyPublish );

View File

@@ -0,0 +1,856 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { Select,Icon, Upload, Button } from 'antd';
// demo http://react-component.github.io/upload/examples/simple.html
// import Upload from 'rc-upload';
import axios from 'axios'
import 'antd/lib/select/style/index.css'
import TPMMDEditor from '../tpm/challengesnew/TPMMDEditor'
import { getUrl, getUploadActionUrl, appendFileSizeToUploadFileAll, appendFileSizeToUploadFile } from 'educoder'
import CaseDetail from "../moop_cases/CaseDetail";
const Option = Select.Option;
const $ = window.$;
let origin = getUrl();
let path = getUrl("/editormd/lib/")
// load
if (!window.postUpMsg) {
$.getScript(
`${origin}/javascripts/attachments.js`,
(data, textStatus, jqxhr) => {
});
}
// editorMD to create
/**
*
* @param id 渲染DOM的id
* @param width 宽度
* @param high 高度
* @param placeholder
* @param imageUrl 上传图片的url
* @returns {*} 返回一个editorMD实例
*/
function create_editorMD(id, width, high, placeholder, imageUrl, callback){
var editorName = window.editormd(id, {
width : width,
height : high,
syncScrolling : "single",
//你的lib目录的路径我这边用JSP做测试的
path : path , // "/editormd/lib/"
tex : true,
tocm : true,
emoji : true,
taskList : true,
codeFold : true,
searchReplace : true,
htmlDecode : "style,script,iframe",
sequenceDiagram : true,
autoFocus: false,
toolbarIcons : function() {
// Or return editormd.toolbarModes[name]; // full, simple, mini
// Using "||" set icons align right.
return ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "testIcon", "testIcon1", '|', "image", "table"
// , '|', "underlineIcon"
, "|", "watch", "clear" ]
},
toolbarCustomIcons : {
testIcon : "<a type=\"inline\" class=\"latex\" ><div class='zbg'></div></a>",
testIcon1 : "<a type=\"latex\" class=\"latex\" ><div class='zbg_latex'></div></a>",
// underlineIcon例子
// underlineIcon : "<a type=\"underline\" class=\"underline\" ><div class='underlineIcon'></div></a>"
},
//这个配置在simple.html中并没有但是为了能够提交表单使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中方便post提交表单。
saveHTMLToTextarea : true,
// 用于增加自定义工具栏的功能可以直接插入HTML标签不使用默认的元素创建图标
dialogMaskOpacity : 0.6,
placeholder: placeholder,
imageUpload : true,
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL : imageUrl,//url
onload: function(){
// underlineIcon例子
// $('#'+ id + " .underline").bind('click', function() {
// var __Cursor = editorName.cm.getDoc().getCursor();
// editorName.appendMarkdown('__')
// editorName.cm.setCursor(__Cursor.line, __Cursor.ch + 2);
// });
// this.previewing();
$("#"+ id +" [type=\"latex\"]").bind("click", function(){
editorName.cm.replaceSelection("```latex");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("```");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line-1, 0);
});
$("#"+ id +" [type=\"inline\"]").bind("click", function(){
editorName.cm.replaceSelection("`$$$$`");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line, __Cursor.ch-3);
editorName.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
window.md_elocalStorage(editorName, `memoNew_${id}`, "memoNew");
callback && callback()
}
});
return editorName;
}
const typeNameMap = {
'技术分享': 5,
'操作指南': 3,
'通知公告':16,
}
export const typeNameMap2 = {
5: '技术分享',
3: '操作指南',
16: '通知公告',
}
const defaultType = '技术分享'
const languageSeparator = '/'
class MemoNew extends Component {
constructor(props) {
super(props)
this.mdRef = React.createRef();
// https://testbdweb.trustie.net/uploads.js?attachment_id=1&filename=jqui.js
// https://ant.design/components/upload-cn/
this.uploaderProps = {
action: '/uploads.js',
data: { attachment_id: 1 }, // , filename: 2
headers: {
Authorization: 'authorization-text',
},
multiple: true,
beforeUpload(file) {
// console.log('beforeUpload', file.name);
},
onStart: (file) => {
console.log('onStart', file.name);
// this.refs.inner.abort(file);
},
onSuccess(file) {
console.log('onSuccess', file);
},
onProgress(step, file) {
console.log('onProgress', Math.round(step.percent), file.name);
},
onError(err) {
console.log('onError', err);
},
};
this.state = {
memoSubject: '',
memoContent: '',
memoType: typeNameMap[defaultType],
memoRepertoire: '',
memoLanguage: [],
repertoires: [],
currentSelectRepertoiresIndex: -1,
repertoiresTagMap: {},
fileList: [],
forums:[{id:5,name:"技术分享"},{id:3,name:"技术指南"},{id:16,name:"通知公告"}],
}
}
onCommit() {
const { memoSubject, memoRepertoire, memoLanguage, currentMemoId, memoType } = this.state;
const { showNotification } = this.props;
if (!memoSubject) {
showNotification('请先输入话题名称')
return
}
let mdVal;
try {
mdVal = this.mdRef.current.getValue()
} catch (e) {
showNotification('编辑器还未加载完毕,请稍后')
return
}
if (!mdVal) {
showNotification('请先输入话题内容')
return
}
// !memoRepertoire ||
if (memoType === 5 && ( !memoLanguage || memoLanguage.length === 0 )) {
showNotification('请先选择技术标签')
return
}
/*
<meta content="authenticity_token" name="csrf-param" />
<meta content="G7peAyb1T37RvdwxnVUKmTXuL8T7FaBze5mK0j6MCKs=" name="csrf-token" />
http://localhost:3000/attachments/download/185790/Git-2.17.1.2-32-bit.exe
https://www.educoder.net/attachments/205112.js?attachment_id=1
*/
// collect attachments
const $ = window.$;
const attachmentsMap = {};
const attachmentIds = this.state.fileList.map(item => {
return item.response ? item.response.id : item.id
})
// $('#attachments_fields .attachment').each(( index, item ) => {
// const filename = $(item).find('.upload_filename').val();
// // $($('#attachments_fields .attachment')[0]).find('input:nth-child(6)').val()
// const token = $(item).find('input:nth-child(7)').val()
// const attachment_id = parseInt($(item).children().last().val())
// attachmentsMap[index] = {
// filename,
// token,
// attachment_id
// }
// attachmentIds.push(attachment_id)
// })
if (currentMemoId) {
this.updateMemo(attachmentIds)
} else {
this.newMemo(attachmentIds)
}
}
onCancel() {
const { currentMemoId, memoType } = this.state;
if (currentMemoId) { // 编辑
this.props.history.push(`/forums/${currentMemoId}`)
} else { // 新建
this.props.history.push(`/forums`)
}
// debugger;this.props.history.goBack()
}
updateMemo(attachmentsMap) {
const { memoSubject, memoRepertoire, memoLanguage, memoType, currentMemoId, content } = this.state;
const mdVal = this.mdRef.current.getValue()
console.log('isContentEdit: ', mdVal === content);
const newMemoUrl = `/memos/${currentMemoId}.json`
axios.put(newMemoUrl, {
content_changed: this.contentChanged,
tags: memoLanguage,
// memo:{
subject: memoSubject ,
content: mdVal,
forum_id: memoType,
repertoire_name: memoRepertoire,
// language: memoLanguage.join(languageSeparator),
//
// },
attachment_ids: attachmentsMap
}, {
// withCredentials: true,
})
.then((response) => {
const { status, message, memo_id } = response.data;
if (status === 0) {
window.$("html,body").animate({"scrollTop":0})
this.props.history.push(`/forums/${currentMemoId}`)
} else {
this.props.showNotification(message)
}
}).catch((error) => {
console.log(error)
})
}
newMemo(attachmentsMap) {
const { memoSubject, memoRepertoire, memoLanguage, memoType } = this.state;
const mdVal = this.mdRef.current.getValue()
const newMemoUrl = `/memos.json`
axios.post(newMemoUrl, {
tags: memoLanguage,
// memo:{
subject: memoSubject ,
content: mdVal,
forum_id: memoType,
// repertoire_name: memoRepertoire,
// },
attachment_ids: attachmentsMap
}, {
// withCredentials: true,
})
.then((response) => {
const { status, message, memo_id } = response.data;
if (status === 0) {
window.$("html,body").animate({"scrollTop":0})
this.props.history.push(`/forums/${memo_id}`)
} else {
this.props.showNotification(message)
}
}).catch((error) => {
console.log(error)
})
}
componentDidMount() {
const newMemoUrl = `/memos/new.json`
axios.get(newMemoUrl,{
// withCredentials: true,
})
.then((response) => {
const data = response.data;
const repertoires = [];
const repertoiresTagMap = {}
if ( data.tag_list ) {
document.title = "交流问答";
// data.tag_list.forEach((item, index)=>{
// const tagArray = [];
// item.tag.forEach( (tag, index) => {
// tagArray.push(tag.name)
// })
// repertoires.push(item.rep.repertoire.name)
// repertoiresTagMap[item.rep.repertoire.name] = tagArray
// })
this.setState({
tag_list: data.tag_list
// repertoires,
// repertoiresTagMap
})
// const user = response.data.current_user;
// user.tidding_count = response.data.tidding_count;
// this.props.initCommonState(user)
// 初始化 csrf meta
const $ = window.$
$('head').append( $('<meta content="authenticity_token" name="csrf-param" />') )
$('head').append( $(`<meta content="${response.data.csrf_token}" name="csrf-token" />`) )
}
if(data.forums){
this.setState({
forums: data.forums===undefined||data.forums===null||data.forums.length===0? this.state.forums:data.forums
// repertoires,
// repertoiresTagMap
})
}
}).catch((error) => {
console.log(error)
})
// 如果是编辑
const { match } = this.props
const memoId = match.params.memoId;
if (memoId) {
const memoUrl = `/memos/${match.params.memoId}/edit.json`;
axios.get(memoUrl,{
// withCredentials: true,
})
.then((response) => {
const tag_list = response.data.tag_list
if (tag_list) {
// this.setState({...response.data})
document.title = response.data.subject;
const { content, forum_id, id, repertoire_name, subject,
current_user, tag_list, attachments_url, memo_tags, attachments } = response.data;
this.initMD(content);
// this.onRepertoiresChange(repertoire_name)
// tag -> memo_tags
const tag = memo_tags;
let memoLanguage = []
if (tag) {
memoLanguage = tag.map((item, index) => {
return item.id + ""
})
}
const fileList = attachments.map(item => {
return {
id: item.id,
uid: item.id,
name: appendFileSizeToUploadFile(item),
url: item.url,
filesize: item.filesize,
status: 'done'
}
})
this.setState({
fileList,
currentMemoId: memoId,
memoSubject: subject,
memoType: forum_id,
memoRepertoire: repertoire_name,
memoLanguage,
attachments_url,
content
// repertoires: [],
// currentSelectRepertoiresIndex: -1,
}, ()=> {
// 解决有时候编辑时内容不显示的问题
setTimeout(() => {
this.mdRef.current && this.mdRef.current.setValue(content || '')
}, 2000)
$('.upload_filename').each((index, item) => {
var width = window._textWidth($(item), '14px');
console.log(width)
$(item).css('width', width + 20)
})
})
// 加载完后滚动条滚动
window.$("html,body").animate({"scrollTop":0})
this.props.initForumState({
// current_user,
tag_list
})
}
}).catch((error) => {
console.log(error)
})
} else {
this.initMD();
}
}
initMD(initValue) {
return;
this.contentChanged = false;
const placeholder = "";
// amp;
// 编辑时要传memoId
// const imageUrl = `/upload_with_markdown?container_id=&container_type=Memo`;
const imageUrl = `${getUploadActionUrl()}`;
// 创建editorMd
const taskpass_editormd = create_editorMD("memoMD", '100%', 400, placeholder, imageUrl, () => {
setTimeout(()=>{
taskpass_editormd.resize()
taskpass_editormd.cm && taskpass_editormd.cm.refresh()
}, 500)
if (initValue) {
taskpass_editormd.setValue(initValue)
}
taskpass_editormd.cm.on("change", (_cm, changeObj) =>{
console.log('....contentChanged')
this.contentChanged = true;
})
});
this.taskpass_editormd = taskpass_editormd;
window.taskpass_editormd = taskpass_editormd;
}
renderOptions(array) {
const elementArray = [];
array.forEach(( item, index ) => {
elementArray.push(
<Option key={index} value={item}>{item}</Option>
)
})
return elementArray
}
onRepertoiresChange(value) {
const index = this.state.repertoires.indexOf(value)
this.setState({
currentSelectRepertoiresIndex: index,
memoRepertoire: value,
memoLanguage: ''
});
};
renderTag() {
const { tag_list } = this.state;
if (!tag_list || tag_list.length === 0) {
return ''
}
const result = []
tag_list.forEach((item, index) => {
result.push(<Option value={item.id+''} key={index} >{item.name}</Option>)
})
return result;
}
onTagChange(value) {
if (value && value.length > 3) {
this.props.showNotification(`最多选择3个技术标签`)
return;
}
this.setState({
memoLanguage: value
})
}
onTypeChange(value) {
this.setState({
memoType: typeNameMap[value]
})
}
onMemoNameChange(e) {
this.setState({
memoSubject: e.target.value
})
}
renderAttachment() {
const { attachments_url } = this.state;
const attachments = []
attachments_url.forEach((item, index) => {
const ar = item.url.split('/')
const fileName = ar[ar.length - 1]
/*
<p className="clearfix" key={index} >
<a href={item.url} className="color-green clearfix notefileDownload">
<span className="fl">{fileName}</span><i className="iconfont icon-xiazai color-green ml5 fl"></i>
</a>
</p>
*/
// ?attachment_id=2
/*
<span id="attachments_fields" className="attachments_fields" xmlns="http://www.w3.org/1999/html">
</span>
*/
attachments.push(
<React.Fragment>
<span id={`attachments_10${index}`} className="attachment">
<label className="panel-form-label fl">&nbsp;</label>
<i className="iconfont icon-fujian ml20mr20Color" aria-hidden="true"></i>
<input type="text" className="upload_filename readonly hidden" name="attachments[2][filename]" readonly="readonly"
style={{border:'none',whiteSpace: 'nowrap', textOverflow:'ellipsis',fontFamily: 'Consolas'
, color: '#676767', marginLeft: '20px', verticalAlign: 'middle'}}
size="8" value={item.filename}></input>
<font className="mr20 ml20mr20Color" style={{marginLeft:'10px', verticalAlign: 'middle'}}>{window.conver_size(item.id)}</font>
<a href={`/attachments/${item.id}.js?attachment_id=10${index}`} className="remove-upload"
style={{verticalAlign: 'top', display: 'inlineBlock'}} data-remote="true"
data-method="delete">
<i className="iconfont ml20mr20Color">&#xe61c;</i>
</a>
<div className="div_attachments" name="div_attachments_xx"></div>
{/**/}<input type="hidden" name="attachments[xx][token]"
value="185811.24305bb2c4912f715629aa3615cdbabc"></input>
<input type="hidden" name="attachments[xx][attachment_id]" value={item.id}></input>
</span>
<div className="cl"></div>
</React.Fragment>
)
})
return attachments;
}
handleChange = (info) => {
if (info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
let fileList = info.fileList;
this.setState({
fileList: appendFileSizeToUploadFileAll(fileList)
});
}
}
onAttachmentRemove = (file) => {
if(!file.percent || file.percent == 100){
this.props.confirm({
// title: '确定要删除这个附件吗?',
content: '是否确认删除?',
okText: '确定',
cancelText: '取消',
// content: 'Some descriptions',
onOk: () => {
this.deleteAttachment(file)
},
onCancel() {
console.log('Cancel');
},
});
return false;
}
}
deleteAttachment = (file) => {
// 初次上传不能直接取uid
const url = `/attachments/${file.response ? file.response.id : file.uid}.json`
axios.delete(url, {
})
.then((response) => {
if (response.data) {
const { status } = response.data;
if (status == 0) {
console.log('--- success')
this.setState((state) => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
});
}
}
})
.catch(function (error) {
console.log(error);
});
}
render() {
const { match, history,forums } = this.props
const {
// repertoires, repertoiresTagMap, currentSelectRepertoiresIndex, memoRepertoire,
tag_list,
memoSubject, memoType,
memoLanguage, attachments_url, fileList } = this.state;
const memoId = match.params.memoId;
const uploadProps = {
width: 600,
fileList,
multiple: true,
// https://github.com/ant-design/ant-design/issues/15505
// showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。
// showUploadList: false,
action: `${getUploadActionUrl()}`,
onChange: this.handleChange,
onRemove: this.onAttachmentRemove,
beforeUpload: (file) => {
console.log('beforeUpload', file.name);
const isLt150M = file.size / 1024 / 1024 < 150;
if (!isLt150M) {
this.props.showNotification('文件大小必须小于150MB!');
}
return isLt150M;
},
};
return (
<div >
<p className="clearfix mb10 undefined cBreadcrumb"><a className="btn colorgrey fl hovercolorblue"
href="/forums">交流问答</a><span
className="color-grey-9 fl ml3 mr3"> / </span><span></span></p>
<div className="pt20 pl20 pr20 pb20 bor-bottom-greyE clearfix" style={{background: '#fff'}}>
<span className="fl font-16">{ memoId ? '编辑话题' : '发布话题'}</span>
{/*<a href="/shixuns/mf98sgi3/settings" className="ring-green mt7 fr" id="edit_setting"
data-remote="true" data-tip-down="编辑"><img src="/images/educoder/icon/edit.svg" className="fl ml2 mt3">
</a>*/}
</div>
<div className="edu-back-white mb10 clearfix" id="memoSubject">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">话题名称</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<input type="text" className="input-100-45 greyInput" maxlength="50"
value={memoSubject} onChange={(val)=>this.onMemoNameChange(val)} placeholder="">
</input>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div>
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">内容</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<TPMMDEditor ref={this.mdRef} placeholder={''} watch={false}
mdID={'memoMD'} initValue={this.state.content} className="memoMD">
</TPMMDEditor>
{/* <div className="flex1 break_word show_content_grey new_li" id="memoMD">
<textarea style={{'display':'none'}}></textarea>
</div> */}
<p id="e_tip_memoNew" className="edu-txt-right color-grey-cd font-12"></p>
<p id="e_tips_memoNew" className="edu-txt-right color-grey-cd font-12"></p>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
{/* <form className="newForm">
<span id={`attachments_fields`} className="attachments_fields"
xmlns="http://www.w3.org/1999/html">
{ attachments_url && !!attachments_url.length &&
this.renderAttachment()
}
</span>
<span className="add_attachment">
<input className="file_selector" data-are-you-sure="您确定要删除吗?"
data-delete-all-files="您确定要删除所有文件吗" data-description-placeholder="可选的描述"
data-field-is-public="公开" data-file-count="个文件已上传"
data-lebel-file-uploding="个文件正在上传" data-max-concurrent-uploads="2"
data-max-file-size-message="该文件无法上传。超过文件大小限制 (50 MB)建议上传到百度云等其他共享工具里然后在txt文档里给出链接以及共享密码并上传"
data-max-file-size="52428800" data-upload-path="/uploads.js" id="_file"
multiple="multiple" name="attachments[dummy][file]"
onChange={()=>{debugger;window.addInputFiles( window.$('.file_selector')[0] ) }}
style={{'display':'none'}} type="file">
</input>
</span>
</form>*/}
<style>{`
.memo_upload.upload_1 {
margin-left: 36px;
}
.memo_upload.upload_1 .ant-upload-list {
margin-left: 30px;
}
.memo_upload.upload_1 .ant-upload-list-item-info .anticon-paper-clip {
top: 4px;
}
`}</style>
{/*<Upload {...this.uploaderProps} ref="inner"><a>开始上传</a></Upload>*/}
<Upload {...uploadProps} className="upload_1 memo_upload" >
<Button className="uploadBtn">
<Icon type="upload" /> 上传附件
</Button>
(单个文件150M以内)
</Upload>
{/* 请求status 422 */}
{/* <Icon type="upload" ></Icon> */}
{/* <div className="df uploadBtn">
<a href="javascript:void(0);" className="fl" onClick={()=>window.$('#_file').click()}
data-tip-down="请选择文件上传">
<i className="fa fa-upload mr5 color-blue"></i>
<span className="color-blue"> 上传附件
</span><span style={{color: '#CDCDCD', fontSize: "14px"}}>(单个文件50M以内)</span>
</a>
</div> */}
</div>
</div>
{/* TODOTODO 这里重复的html代码太多如果有其他页面有类似需求需要封装*/}
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">话题类型</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<Select className="ecSelect" value={typeNameMap2[memoType]}
onChange={(val)=>this.onTypeChange(val)}>
<Option value="技术分享">技术分享</Option>
<Option value="操作指南">操作指南</Option>
<Option value="通知公告">通知公告</Option>
</Select>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div>
{/* memoType === typeNameMap['技术分享'] &&
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">技术标签</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="mr20">
<Select className="ecSelect" placeholder="请选择大标签" onChange={(e) => this.onRepertoiresChange(e)}
value={memoRepertoire}
>
{this.renderOptions(repertoires)}
</Select>
</div>
<div className="flex1 mr20">
<Select className="ecSelect" placeholder="请选择小标签"
onChange={(e) => this.onTagChange(e)} value={memoLanguage}
dropdownStyle={{'maxHeight': '300px', 'overflow': 'auto'}} >
{ currentSelectRepertoiresIndex >= 0
&& this.renderOptions(repertoiresTagMap[repertoires[currentSelectRepertoiresIndex]])}
</Select>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>必填项
</span>
</div>
</div>
</div>
</div> */}
{ memoType === typeNameMap['技术分享'] &&
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">技术标签</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<Select
className="ecSelect"
value={memoLanguage}
placeholder="请选择技术标签"
onChange={(e) => this.onTagChange(e)}
dropdownStyle={{'maxHeight': '300px', 'overflow': 'auto'}}
mode="multiple"
filterOption={(inputValue, option) => { return option.props.children.toLocaleLowerCase().indexOf(inputValue.toLocaleLowerCase()) != -1 } }
tokenSeparators={[';']} >
{this.renderTag()}
</Select>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div> }
<div className="clearfix mt30">
<a href="javascript:void(0)" className="defalutSubmitbtn fl mr20"
onClick={()=>{this.onCommit()}}>提交</a>
<a onClick={()=>{ this.onCancel() }} className="defalutCancelbtn fl">取消</a>
</div>
</div>
);
}
}
export default MemoNew;

View File

@@ -0,0 +1,65 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
class MemoGuide extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} memo={item} user={user} index={index} {...this.props}></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<div id="forum_list" className="forum_table mh650">
{this.renderMemoList()}
{ !!memo_count && <Pagination onChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/> }
</div>
</React.Fragment>
);
}
}
export default postPaginationHOC( MemoGuide );

View File

@@ -0,0 +1,84 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
class MemoHottest extends Component {
constructor(props) {
super(props)
this.handleLocationChange = this.handleLocationChange.bind(this);
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
componentDidMount() {
// this.handleLocationChange(this.props.history.location);
this.unlisten = this.props.history.listen(this.handleLocationChange);
}
componentWillUnmount() {
this.unlisten();
}
handleLocationChange(location) {
// your staff here
console.log(`- - - location: '${location.pathname}'`);
if (location.pathname && location.pathname.indexOf('/forums/categories/all') != -1
&& this.locationSearch != location.search) {
this.props.fetchMemos();
}
this.locationSearch = location.search;
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} memo={item} user={user} index={index} {...this.props}></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<div id="forum_list" className="forum_table mh650">
{this.renderMemoList()}
{ !!memo_count && <Pagination onChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/> }
</div>
</React.Fragment>
);
}
}
export default postPaginationHOC( MemoHottest );

View File

@@ -0,0 +1,63 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
class MemoNewest extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} memo={item} user={user} index={index} {...this.props}></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<div id="forum_list" className="forum_table mh650">
{this.renderMemoList()}
{ !!memo_count && <Pagination onChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/> }
</div>
</React.Fragment>
);
}
}
export default postPaginationHOC( MemoNewest );

View File

@@ -0,0 +1,116 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
// import queryString from 'query-string'
import { queryString } from 'educoder'
import MemoList from './MemoList'
class MemoTechShare extends Component {
constructor(props) {
super(props)
this.handleLocationChange = this.handleLocationChange.bind(this);
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
componentDidMount() {
// this.handleLocationChange(this.props.history.location);
// this.unlisten = this.props.history.listen(this.handleLocationChange);
}
componentWillUnmount() {
// this.unlisten();
}
componentDidUpdate(prevProps) {
if(this.props.match.params.memoType !== prevProps.match.params.memoType) {
// do something
console.log(`memoType changed`)
this.props.fetchMemos();
}
}
componentWillReceiveProps(newProps, newContext) {
if (newProps.match.url === this.props.match.url) {
const oldParsed = queryString.parse(this.props.location.search);
const newParsed = queryString.parse(newProps.location.search);
if (!newParsed.page && oldParsed.page ||
(oldParsed.order && newParsed.order && oldParsed.order != newParsed.order)) {
this.props.fetchMemos();
}
// console.log('componentWillReceiveProps...')
}
}
handleLocationChange(location) {
console.log(`- - - location: '${location.pathname}'`);
if (location.pathname) {
if (location.pathname.indexOf('/forums/categories/all') != -1
&& this.props.location.search && this.props.location.search.indexOf('order=') != -1
&& location.search.indexOf('order=') != -1) {
const oldParsed = queryString.parse(this.props.location.search);
const newParsed = queryString.parse(location.search);
if (oldParsed.order != newParsed.order) { // 只有在热门和最新间跳转时,才需要处理
this.props.fetchMemos();
}
}
}
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} user={user} index={index} {...this.props}
// setTop={(memo)=>this.setTop(memo)}
// setDown={(memo)=>this.setDown(memo)} memo={item}
// ></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count ,memo_list } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<MemoList {...this.props} renderMemoList={() => this.renderMemoList()}
onPaginationChange={ (pageNum, pageSize) => this.props.onPaginationChange(pageNum, pageSize) }
>
</MemoList>
</React.Fragment>
);
}
}
export default postPaginationHOC() ( MemoTechShare );

View File

@@ -0,0 +1,224 @@
/*MemoDetail --------------------------------- START */
.educontent {
margin-bottom: 20px;
}
/* 左侧区域最小高度*/
#forum_index_list {
min-height: 400px;
position: relative;
}
#forum_index_list .forum_table .forum_table_item {
background: #fff;
}
.noMemosTip {
position: absolute;
right: 10px;
top: 58px;
z-index: 999;
}
#forum_list {
background: #f9f9f9;
}
#forum_list .return_btn {
line-height: 38px;
/* margin-right: 15px; */
font-size: 14px;
cursor: pointer;
}
#forum_list .return_btn.no_mr {
margin-right: -24px !important;
}
div#forum_list>div {
background: #fff;
}
.memoContent img {
max-width: 815px;
}
.memoReplies {
position: relative;
margin-top: 8px;
}
.memoReplies .-fit {
position: static;
}
.replies_count {
margin-left: 12px;
}
.replies_count .label {
color: #666666;
}
.replies_count .count {
color: #999999;
margin-left: 10px;
}
.memoMore {
padding-top: 10px;
height: 50px;
line-height: 50px;
text-align: center;
color: rgba(69,155,230,1);
cursor: pointer;
position: relative;
}
.memoMore .writeCommentBtn{
position: absolute;
right: 0px;
color: #666666;
top: 15px;
}
.memoMore .writeCommentBtn:hover {
color: #4DACFF;
}
/*使用md編輯器用为子回复时宽度会变*/
.panel-comment_item .comment_orig_content {
width: 705px;
}
.iconfont.icon-xiazai {
font-size: 22px!important;
margin-right: 6px;
}
/* MemoDetail --------------------------------- END */
/* PostItem --------------------------------- START */
.forum_table_item {
padding-left: 20px;
}
/* 置顶 */
.forum_table_item .btn-top {
border-radius: 11px;
padding: 0px 6px;
background: #FF4343;
}
/* 管理员操作 */
.edu-position-hide {
position: absolute;
top: 15px;
left: -20px;
box-shadow: 0px 2px 8px rgba(146, 153, 169, 0.5);
background: #fff;
z-index: 1001;
padding: 5px 0;
z-index: 999999;
}
.edu-position-hide li a:hover {
background: #4CACFF;
color: #fff;
}
.edu-position-hidebox>a:link{
color: #4CACFF;
}
.edu-position-hidebox:hover .edu-position-hide {
display: block;
}
.edu-position-hide li a {
display: inline-block;
height: 30px;
width: 100px;
line-height: 30px;
text-align: center;
font-size: 12px!important;
}
/* PostItem --------------------------------- END */
/* MemoNew --------------------------------- START */
#attachments_fields div.ui-progressbar {
width: 120px;
height: 10px;
margin: 2px 0 -2px 8px;
display: inline-block;
}
.ui-widget-header {
border: 1px solid #4CACFF;
background: #4CACFF;
}
.iconfont.icon-fujian {
color: #29BD8B
}
/* rc-select样式覆写*/
.ecSelect {
width: 300px;
}
.ecSelect .rc-select-selection {
height: 40px;
}
.ecSelect .rc-select-search--inline .rc-select-search__field {
padding-top: 6px;
}
.ecSelect .rc-select-selection--single .rc-select-selection-selected-value
, .ecSelect .rc-select-selection__placeholder {
top: 6px;
}
.ecSelect .rc-select-arrow {
top: 6px;
}
.defalutCancelbtn {
cursor: pointer;
}
.defalutSubmitbtnysl{
display: block;border: 1px solid #4CACFF;background-color: #4CACFF;color: #fff!important;width: 120px;text-align: center;line-height: 40px;border-radius: 2px;
width: 130px;
height: 40px;
background: rgba(76,172,255,1);
border-radius: 4px;
font-size: 16px;
font-family: MicrosoftYaHei;
font-weight: 400;
color: rgba(255,255,255,1);
}
#attachments_fields {
margin-left: -77px;
display: flex;
flex-direction: column;
}
.uploadBtn {
/* margin-left: 46px; */
}
#memoMD.show_content_grey {
padding: 0;
}
.newForm .attachments_fields {
/*margin-left: -39px !important*/
}
#attachments_fields div.ui-progressbar {
width: 120px;
height: 10px;
margin: 2px 0 -2px 8px;
display: inline-block;
}
.ui-progressbar-value.ui-widget-header {
border: 1px solid #4CACFF;
background: #4CACFF;
}
/* MemoNew --------------------------------- END */
/*RightMyPublish*/
.publishMemoSection {
padding-bottom: 0px !important;
}
.advertisement {
margin-top: 10px;
height: 155px;
}
.advertisement img{
width: 100%;
}
/* MyPublish*/
.returnBtn {
font-size:16px;
color:rgba(153,153,153,1);
float: right;
margin-right: 50px;
position: relative;
bottom: 12px;
}

View File

@@ -0,0 +1,110 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { getImageUrl, toPath, ThemeContext } from 'educoder';
import moment from 'moment';
import { Tooltip } from 'antd'
class PostItem extends Component {
_toTenThousand(num) {
if (num > 10000) {
return ( (num - 500)/ 10000 ).toFixed(1) + '万'
}
return num
}
render() {
const { match, history, currentPage, memo, user, setTop, setDown } = this.props
return (
<div className="forum_table_line pl20">
<div className="forum_table_item" id={`memo_detail_${memo.id}`}>
<a href={`/users/${memo.login}`} className="fr mr15">
<img alt="用户头像" className="bor-radius-all mt3" height="50" src={getImageUrl(`images/`+memo.image_url)} width="50" />
</a>
<div className="fl pr" style={{flex: 1}}>
<p className="font-16 clearfix" style={{ lineHeight: 2 }}>
{/* target="_blank" */}
<a href={`/forums/${memo.id}`} target="_blank" title={memo.subject && memo.subject.length > 46 ? memo.subject : ''}
className="clearfix task-hide item_name fl" style={{maxWidth: '600px'}} >
{memo.subject}
</a>
{ memo.sticky && <span className="btn-top btn-cir-orange mt6 ml5 fl">置顶</span> }
{ memo.reward &&
<Tooltip title={`获得平台奖励金币:${memo.reward}`}>
<span className=" ml10 fl color-orange03 fl" >
<i className="iconfont icon-gift font-16 mr5 fl"></i><span className="fl mt3 font-14">{memo.reward}</span>
</span>
</Tooltip>
}
</p>
<div className="clearfix mt5 color-grey-9">
<span className="fl">{memo.user_name}</span>
{/*最后回复todo{memo.username}
memo.language && memo.language != 'other' && <span className="fl language-cir-orange mr10 mt3 ml6">{memo.language}</span>
*/}
{/* <span className="fl ml50">{moment(memo.updated_at).fromNow()}</span> */}
{memo.tag && memo.tag.length ? <span className="fl ml50">来自 {memo.tag.join('/')}</span> : ''}
{/*<span className="fl language-cir-orange mr10 mt3">C++</span>*/}
<p className="font-12 fr mr8 color-grey-6" style={{ marginTop: '4px' }}>
{/* data-tip-down="回复数" <i className="fa fa-comments-o mr5"></i>{memo.replies_count}
<i className="fa fa-thumbs-o-up mr5"></i>{memo.praise_count}*/}
{memo.replies_count ?
<span className="mr10 ml10 fl edu-txt-right" style={{cursor: 'default'}} >
{memo.replies_count} 回复
</span> :''}
{memo.praise_count ?
<span className="mr10 ml10 fl edu-txt-right" style={{cursor: 'default'}} >
{memo.praise_count}
</span> :''}
{memo.viewed_count ?
<span className="mr10 ml10 fl edu-txt-right" style={{cursor: 'default',minWidth:'55px'}}>
{this._toTenThousand(memo.viewed_count)} 浏览
</span> :''}
</p>
</div>
{ user && (user.admin === true || user.login === memo.login) &&
<div className="edu-position-hidebox" style={{position: 'absolute', right: '18px',top:'0px'}}>
<a href="javascript:void(0);"><i className="fa fa-bars font-16"></i></a>
<ul className="edu-position-hide undis">
{ user.admin === true &&
( memo.sticky === true ?
<li><a href="javascript:void(0);" onClick={() => setDown(memo)}>取消置顶</a></li>
:
<li><a href="javascript:void(0);" onClick={() => setTop(memo)}>&nbsp;&nbsp;</a></li> )
}
<li><Link to={`/forums/${memo.id}/edit`}>&nbsp;&nbsp;</Link></li>
<li>
<a href="javascript:void(0)" onClick={() =>
window.delete_confirm_box_2_react(`onMemoDelete`, '您确定要删除吗?' , memo)}>
&nbsp;&nbsp;</a>
</li>
</ul>
</div>
}
</div>
</div>
</div>
);
}
}
PostItem.contextType = ThemeContext;
export default PostItem

View File

@@ -0,0 +1,352 @@
import React from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'
import update from 'immutability-helper'
import PostItem from './PostItem'
import { CircularProgress } from 'material-ui/Progress';
import { queryString } from 'educoder'
import { on, off, updatePageParams } from 'educoder'
import './Post.css'
const $ = window.$
function urlStringify(params) {
let noParams = true;
let paramsUrl = '';
for (let key in params) {
noParams = false;
paramsUrl += `${key}=${params[key]}&`
}
if (noParams) {
return '';
}
paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1);
return paramsUrl;
}
export function postPaginationHOC(options = {}) {
// options.isMyPublish
return function wrap(WrappedComponent) {
return class II extends React.Component {
constructor(props) {
super(props)
this.state = {
currentPage: 1,
loadingMemos: true
}
}
componentDidMount() {
$('body>#root').on('onMemoDelete', (event) => {
// const val = $('body>#root').data('onMemoDelete')
const val = window.onMemoDelete ;
this.onMemoDelete( JSON.parse(decodeURIComponent(val)) )
})
window.$('#shixun_search_input').val('')
this.props.setSearchValue('')
this.fetchMemos(null, '')
const that = this;
$(window).on('popstate', (e) => {
var state = e.originalEvent.state;
console.log('popstate', state)
if (state !== null) {
let currentPage = that.state.currentPage;;
// // 浏览器地址改变了
const search = that.props.history.location.search
const parsed = queryString.parse(search);
if (parsed.page != currentPage) {
currentPage = parseInt( parsed.page || 1)
// that.setSearchValue('')
that.fetchMemos(currentPage)
this.setState({
currentPage,
})
}
}
});
// RightMyPublish组件发过来的消息
// $(window).on('setSearchValue', (event, val, noFetch)=>{
// })
on('hotTagClick', (event, tagName, index) => {
this.props.setHotLabelIndex(tagName.selectedHotLabelIndex, () => {
this.fetchMemos(1, undefined)
})
})
}
componentWillReceiveProps(newProps, newContext) {
if (newProps.enterKeyFlag !== this.props.enterKeyFlag) {
const childPath = this.props.match.path.split('/:')[0]
// 加入一个浏览地址
const _search = this.props.location.search;
if (_search) {
const parsed = queryString.parse(_search);
if (parsed.page != 1) {
parsed.page = 1;
this.props.history.push(`${this.props.match.url}?${queryString.stringify(parsed)}`)
}
}
this.fetchMemos(1, newProps.searchValue, newProps.selectedHotLabelIndex) // 搜索框模糊搜索,重置为第一页
}
}
componentWillUnmount() {
// 要移除掉不然到了MemoDetail页面可能会有2个onMemoDelete监听
$('body>#root').off('onMemoDelete')
$(window).off('setSearchValue')
$(window).off('popstate')
off('hotTagClick')
}
fetchMemos(arg_currentPage, arg_searchValue, arg_selectedHotLabelIndex) {
let { match, history } = this.props
let searchValue = arg_searchValue != undefined ? arg_searchValue : this.props.searchValue
// 根据参数初始化页数
const memoType = match.params.memoType;
const urlArray = match.url.split('/')
const lastPath = urlArray[2]
// 1 问题反馈
// 3 操作指南 5 技术分享
const memoTypeMap = {
'guide': 3,
'techShare': 5
}
const orderTypeMap = {
'hottest': 'replies_count',
'newest': 'updated_at', // 'created_at',
}
const _search = this.props.history.location.search;
const parsed = queryString.parse(_search);
let currentPage = parseInt( arg_currentPage ? arg_currentPage : (parsed.page || 1) )
const params = {
// replies_count最热 created_at 最新
// s_order: 'replies_count',
page: currentPage,
// forum: // forum_id
// user_id
}
if (searchValue) {
params.search = searchValue.trim()
}
let orderType = ''
if (memoType==='all') {
orderType = parsed.order || 'hottest';
params.order = orderTypeMap[orderType]
} else if (options.isMyPublish) {
params.user_id = -1
} else if (memoType) {
params.forum = memoType
if (memoType == 5) { // 讨论区的技术分享tab按照创建时间倒序
params.order = 'created_at'
}
}
let { selectedHotLabelIndex, hot_tags } = this.props;
selectedHotLabelIndex = arg_selectedHotLabelIndex ? arg_selectedHotLabelIndex : selectedHotLabelIndex
if (selectedHotLabelIndex !== -1 && hot_tags[selectedHotLabelIndex]) {
params.tag_repertoire_id = hot_tags[selectedHotLabelIndex].tag_repertoire_id // encodeURIComponent()
}
let paramsUrl = queryString.stringify(params)
const memosUrl = '/memos.json?' + paramsUrl // /${challenge.identifier}/star
this.setState({
currentPage,
loadingMemos: true,
orderType: orderType
})
// 获取memo list
axios.get(memosUrl,{
// withCredentials: true,
})
.then((response) => {
const memo_count = response.data.memo_count
if (memo_count>=0) {
const maxPage = Math.ceil( memo_count / 15 )
// page超出显示最后一页
if (maxPage != 0 && maxPage < currentPage) {
this.fetchMemos(maxPage);
return;
}
// const user = response.data.current_user;
// user.tidding_count = response.data.tidding_count;
// this.props.initCommonState(user)
this.props.initForumState(response.data)
this.setState({
p_forum_id: params.forum,
p_s_order: params.s_order,
loadingMemos: false
})
}
}).catch((error) => {
console.log(error)
})
}
onCurrentPageChange(pageNum) {
this.setState({
currentPage: pageNum
})
}
onPaginationChange(pageNum, pageSize) {
window.$("html,body").animate({"scrollTop":0})
updatePageParams(pageNum, this.props)
// 加入一个浏览地址
// const params = {
// page: pageNum
// }
// if (this.state.orderType) {
// params.order = this.state.orderType;
// }
// this.props.history.push(`${url}?${queryString.stringify(parsed)}`)
this.fetchMemos(pageNum);
// this.setState({
// currentPage: pageNum
// })
}
// 置顶
setTop(memo) {
const params = {
sticky: memo.sticky ? 0 : 1,
}
if (this.state.p_s_order) {
params.order = this.state.p_s_order;
}
if (this.state.p_forum_id) {
params.forum_id = this.state.p_forum_id;
}
let paramsUrl = urlStringify(params)
const set_top_or_down_Url = `/memos/${memo.id}/sticky_or_cancel.json?${paramsUrl}`;
// 获取memo list
axios.post(set_top_or_down_Url, {
// withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.fetchMemos(1, '')
// const { memo_list } = response.data;
// this.props.initForumState({ memo_list })
// 刷新列表
// TODO 服务端直接返回第一页列表
// this.props.history.replace('/')
}
}).catch((error) => {
console.log(error)
})
}
// 取消置顶
setDown(memo) {
this.setTop(memo);
}
onMemoDelete(memo) {
const deleteUrl = `/memos/${memo.id}.json`;
// 获取memo list
axios.delete(deleteUrl, {
// withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showNotification('删除成功');
// 刷新列表
this.fetchMemos();
}
}).catch((error) => {
console.log(error)
})
}
// item渲染
//
renderMemoList() {
const { memo_list, user } = this.props;
if (!memo_list) {
return ''
}
return memo_list.map( (item, index) => {
return (
<PostItem key={item.id} user={user} index={index} {...this.props}
setTop={(memo)=>this.setTop(memo)}
setDown={(memo)=>this.setDown(memo)}
memo={item}
></PostItem>
)
})
}
render() {
const { loadingMemos } = this.state;
const { memo_list, searchValue, showSearchValue, memo_count, selectedHotLabelIndex, hot_tags} = this.props;
// 规则: 搜索框输入了值 或者 选择了热门标签的时候显示该提示
const _showSearchValue = showSearchValue || selectedHotLabelIndex != -1
let _searchValue;
if (showSearchValue) {
_searchValue = searchValue
} else if (selectedHotLabelIndex != -1){
_searchValue = hot_tags[selectedHotLabelIndex].name || hot_tags[selectedHotLabelIndex]
}
return (
<div className="edu-back-white" id="forum_index_list"> {/* fl with100 */}
<div className="clearfix">
{ _showSearchValue &&
<div className="noMemosTip" style={{display: loadingMemos ? 'none' : 'block'}}>
<span className="fr pr20" id="search_result">
共找到&nbsp;
<span className="color-orange03">{memo_count}</span>"
<span className="color-orange03">{_searchValue}</span>"
</span>
</div> }
<CircularProgress size={40} thickness={3}
style={{ marginLeft: 'auto', marginRight: 'auto', paddingTop: '20%'
, display: loadingMemos ? 'block': 'none' }}/>
{ !loadingMemos &&
<WrappedComponent {...this.props} {...this.state}
onPaginationChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
onCurrentPageChange={(pageNum, pageSize) => this.onCurrentPageChange(pageNum, pageSize)}
renderMemoList={() => this.renderMemoList()}
fetchMemos={(arg1, arg2) => this.fetchMemos(arg1, arg2)}
></WrappedComponent>
}
</div>
</div>
)
}
}
}
}

View File

@@ -0,0 +1,62 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
class RecommendShixun extends Component {
constructor(props) {
super(props)
this.state = {
}
}
showRecommandShixun(){
const { recommend_shixuns } = this.props;
if (!recommend_shixuns) {
return '';
}
const result = [];
recommend_shixuns.forEach((shixun, index) => {
const _shixun = shixun
result.push(
<div className="recomments clearfix df" key={index}>
<a href={`/shixuns/${_shixun.identifier}/challenges`} style={{height:'76px'}} target="_blank">
<img alt={`${_shixun.id}`} style={{maxHeight:'76px'}} src={`/${_shixun.image_url}`} width="100" >
</img>
</a>
<div className="ml10 flex1">
<a href={`/shixuns/${_shixun.identifier}/challenges`} target="_blank" title={_shixun.name && _shixun.name.length > 9 ? _shixun.name : ''}
className="color-grey-6 task-hide mb10 recomment-name" style={{maxWidth:'147px'}}>
{_shixun.name}
</a>
<p className="color-grey-9">{_shixun.myshixuns_count} 人学习</p>
</div>
</div>
)
})
return result;
}
render() {
const { match, history, currentPage } = this.props
// 参考 TPMShixunDiscuss.js 推荐实训, 页面路径http://localhost:3007/shixuns/uznmbg54/shixun_discuss
return (
<div className="padding10">
<p className="mb20 font-16 clearfix" style={{ lineHeight: 2 }}>推荐实训</p>
<div className="recommend-list">
{this.showRecommandShixun()}
</div>
</div>
);
}
}
export default RecommendShixun;

View File

@@ -0,0 +1,56 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { trigger } from 'educoder'
class RightHotLabel extends Component {
constructor(props) {
super(props)
this.state = {
}
}
renderTags() {
const { hot_tags, selectedHotLabelIndex } = this.props;
if (!hot_tags) {
return ''
}
const result = hot_tags.map((item, index) => {
let params = {}
if (typeof item === 'string') {
params.name = item;
} else {
params = item;
}
params.selectedHotLabelIndex = index;
return (
<a href="javascript:void(0)" onClick={() => trigger('hotTagClick', params)} key={index}
className={classNames({"selected": selectedHotLabelIndex === index})}>
{item.name || item}
</a> )
})
return result
}
render() {
const { match, history, currentPage, selectedHotLabelIndex } = this.props
return (
<div className="clearfix padding40-20 edu-back-white mt10">
<p className="font-16">热门标签</p>
<div className="mt30 HotLabelList clearfix" id="HotLabelList">
{this.renderTags()}
</div>
</div>
);
}
}
export default RightHotLabel;

View File

@@ -0,0 +1,53 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
class RightHotQuestion extends Component {
constructor(props) {
super(props)
this.state = {
}
}
//
renderHotMemos() {
const { hot_memos } = this.props;
if (!hot_memos) {
return ''
}
return hot_memos.map((item, index) => {
return <div className="hotQuestionItem" key={index}>
<Link to={`/forums/${item.id}`} className="color-grey-6 task-hide mb5 questiontName"
title={ item.subject && item.subject.length > 15 ? item.subject : '' }
>
{item.subject}
</Link>
<p className="clearfix font-12 color-grey-9">
<span className="fl">{item.replies_count} 回答</span>
{ !!item.tag && item.tag.length ? <span className="fr">来自 {item.tag.join('/')}</span> : ''}
</p>
</div>
})
}
render() {
const { match, history, currentPage } = this.props
return (
<div className="clearfix padding40-20 edu-back-white mt10">
<p className="font-16">热门问题</p>
<div className="mt10 hotQuestionList clearfix" id="hotQuestionList">
{this.renderHotMemos()}
</div>
</div>
);
}
}
export default RightHotQuestion;

View File

@@ -0,0 +1,47 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { trigger } from 'educoder'
class MemoLabel extends Component {
constructor(props) {
super(props)
this.state = {
}
}
renderTags() {
const { memo } = this.props;
const arrays = memo.tag.map((item, index) => {
return <a href="javascript:void(0)" key={index}>{item}</a>
})
return arrays
}
render() {
const { match, history, currentPage, memo } = this.props
if (!memo || !memo.tag || memo.tag.length === 0) {
return ''
}
return (
<div className="clearfix padding30-20 edu-back-white mt10">
<p className="font-16">话题标签</p>
<div className="mt30 HotLabelList clearfix" id="HotLabelList">
{this.renderTags()}
</div>
</div>
);
}
}
export default MemoLabel;

View File

@@ -0,0 +1,75 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { getImageUrl, toPath, LinkAfterLogin } from 'educoder'
import match_adImg from '../../images/ad/match_ad.jpg'
const $ = window.$
class RightMyPublish extends Component {
constructor(props) {
super(props)
this.state = {
}
}
handleKeyPress = (event) => {
if(event.type !== 'keypress' || event.key == 'Enter'){
this.props.setSearchValue( this.props.searchValue, true);
// $(window).trigger('setSearchValue', $('#shixun_search_input').val())
}
}
handleInput = (event) => {
this.props.setSearchValue(event.target.value);
}
render() {
const { match, history, currentPage, my_memos_count, setSearchValue, searchValue } = this.props
return (
<React.Fragment>
<div className="clearfix edu-back-white padding40-20 publishMemoSection">
{/*<div className="searchFor h40 mt15 mb5 ml20">
<div className="searchCon fl">
<input type="text" className="searchinput" name="search" value="" placeholder="请输入帖子标题的关键字进行搜索">
</input>
<span className="search_close" onclick="colse_searchbox();" data-tip-down="清除">×</span>
</div>
<i className="fa fa-search mr5 fl color-dark-grey search_icon"
onClick="$('#search_memos').submit();" style={{margin:'8px'}} data-tip-down="搜索"></i>
</div>*/}
<div className="search-new">
<input type="text" className="search-new-input fl" placeholder="搜索您想了解的话题" id="shixun_search_input"
onKeyPress={this.handleKeyPress} onChange={ this.handleInput } value={searchValue}
>
</input>
<span className="search-span"></span>
<img src={getImageUrl("images/educoder/icon/search.svg")} className="fl mt5"
onClick={ this.handleKeyPress }>
</img>
</div>
<LinkAfterLogin {...this.props} to={'/forums/new'} className="sendMyQuestion edu-default-btn edu-blueback-btn edu-txt-center font-16 mb30">发布话题</LinkAfterLogin>
{/*<p className="edu-txt-center font-16">
<span>我的发布</span><br/>
<Link to={`/forums/categories/my_published`} className="color-blue">{my_memos_count}</Link>
</p>*/}
</div>
<div className="clearfix edu-back-white advertisement" >
<a href="/competitions" target="_blank"><img src={match_adImg}></img></a>
</div>
</React.Fragment>
);
}
}
export default RightMyPublish;

View File

@@ -0,0 +1,61 @@
/* 右侧搜索区域*/
.searchFor .searchCon {
width: 215px;
}
.search-new {
width:237px!important;
height: 30px;
margin-bottom: 30px;
/*margin-right: 35px;*/
}
.search-newysl {
height: 30px;
margin-bottom: 30px;
}
.search-newyslw{
width:237px!important;
}
.search-new-input {
padding-left: 16px;
height: 30px;
}
.search-span {
border-radius: 17px;
}
.search-new img {
right: 10px;
}
/* 右侧 热门标签 */
.HotLabelList a{display: block;float: left;padding: 0px 9px;height: 28px;line-height: 28px;border-radius: 14px;background-color: #f5f5f5;color: #666;margin-right: 10px;margin-bottom: 9px;}
.HotLabelList a.selected {
background: #4CACFF;
color: #fff;
}
/* 右侧 热门问题 */
.hotQuestionItem{padding:20px 0px;border-bottom: 1px solid #eee;}
.questiontName{max-width: 100%;display: block;}
/* 用户信息-UserSection*/
.user_default_btn {width: 114px;}
.userPrivateName{line-height: 25px;margin-bottom: 9px;}
.userPrivatePost{line-height: 20px;}
.noteDetailTitle{line-height: 38px;font-size: 24px;font-weight: normal;text-align:justify }
.noteDetailNum{float: left;padding:0px 12px;position: relative;color: #999!important;height: 28px;line-height: 26px;}
.noteDetailNum.rightline:after{position: absolute;content: '';right: 0px;width: 1px;background-color: #EAEAEA;height: 8px;top:10px;}
/*帖子详情点赞*/
.noteDetailPoint{width: 100px;height: 70px;background-color: #4cacff;border-radius: 35px;color: #FFFFff;text-align: center;margin: 0px auto;box-sizing: border-box;padding: 2px 0px;cursor: pointer; line-height: 22px;
padding-top: 12px;}
.Pointed{background-color:#f0f0f0;color: #b3b3b3; cursor: default}
.notefileDownload{height: 25px;line-height: 22px;}

View File

@@ -0,0 +1,87 @@
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import axios from 'axios'
class UserSection extends Component {
constructor(props) {
super(props)
this.state = {
}
}
/*点击关注或者取消关注*/
AboutFocus(){
const { author_info } = this.props
/*http://localhost:3000/api/v1/users/155/watch?object_id=156&object_type=user*/
// const focusUrl = `/api/v1/users/${author_info.user_id}/${this.props.author_info.watched ? 'unwatch' : 'watch'}?object_id=${author_info.user_id}&object_type=user`
// axios.get(focusUrl,{
// })
// .then((response) => {
// const status = response.data.status;
// console.log(status);
// if(status == 1){
// const new_author_info = Object.assign({}, this.props.author_info)
// new_author_info.watched = !new_author_info.watched
// this.props.initForumState({author_info: new_author_info})
// }
// }).catch((error) => {
// console.log(error)
// })
let url=`/users/${author_info.user_id}/watch.json`;
// 取消关注
if(author_info.watched){
axios.delete(url).then((result)=>{
if(result){
const new_author_info = Object.assign({}, this.props.author_info)
new_author_info.watched = !new_author_info.watched
this.props.initForumState({author_info: new_author_info})
}
}).catch((error)=>{
console.log(error)
})
}else{
// 关注
axios.post(url).then((result)=>{
if(result){
const new_author_info = Object.assign({}, this.props.author_info)
new_author_info.watched = !new_author_info.watched
this.props.initForumState({author_info: new_author_info})
}
}).catch((error)=>{
console.log(error);
})
}
}
render() {
const { match, history, author_info , current_user } = this.props
if (!author_info || !current_user) {
return <div className="edu-back-white" id="forum_index_list"></div>
}
return (
<div className="edu-back-white padding40-20 edu-txt-center">
<a href={`/users/${author_info.login}`} target="_blank"><img src={`/images/${author_info.image_url}`} width="90" height="90" className="radius mb5"/></a>
<p className="font-20 userPrivateName">{author_info.username}</p>
<p className="color-grey-9 userPrivatePost">{author_info.identity}</p>
{ author_info.user_id !== current_user.user_id &&
<p className="clearfix mt30">
<a href="javascript:void(0)" className="fl font-16 mr10 user_default_btn edu-blueback-btn" onClick={()=>{this.AboutFocus()}}>{ author_info.watched == true ? "取消关注" : "关注" }</a>
<a href={`/messages/${current_user.login}/message_detail?target_ids=${author_info.user_id}`} className="fr font-16 user_default_btn user_private_btn" target="_blank">私信</a>
</p> }
</div>
);
}
}
export default UserSection;

View File

@@ -0,0 +1,21 @@
.sxReturnItem{
width: 100%;
padding-left:20px;
box-sizing: border-box;
}
.ItemLine{
border-bottom: 1px solid #EBEBEB;
padding:30px 0px;
}
.ItemLineHeadPhoto{
height:48px;
width: 48px;
float: left;
margin-top: 4px;
}
.shixunReply{
max-width: 604px;
}
.sxReturnItem:last-child .ItemLine{
border-bottom: none;
}

View File

@@ -0,0 +1,226 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import ShiXunPostItem from './ShiXunPostItem'
import ForumsNavTab from '../ForumsNavTab'
import { CircularProgress } from 'material-ui/Progress';
import { on, off } from 'educoder'
import './MemoShixun.css'
// import queryString from 'query-string'
import { queryString, updatePageParams } from 'educoder'
import MemoList from '../MemoList'
import axios from 'axios'
const $ = window.$;
class MemoShixun extends Component {
constructor(props) {
super(props)
this.state = {
currentPage: 1,
loadingMemos: true
}
}
onPaginationChange(pageNum, pageSize) {
window.$("html,body").animate({"scrollTop":0})
updatePageParams(pageNum, this.props)
this.fetchShixunMemos(pageNum);
}
componentWillReceiveProps(newProps, newContext) {
if (newProps.enterKeyFlag !== this.props.enterKeyFlag) {
// const childPath = this.props.match.path.split('/:')[0]
// // 加入一个浏览地址
// const _search = this.props.location.search;
// if (_search) {
// const parsed = queryString.parse(_search);
// if (parsed.page != 1) {
// parsed.page = 1;
// this.props.history.push(`${this.props.match.url}?${queryString.stringify(parsed)}`)
// }
// }
this.fetchShixunMemos(1, newProps.searchValue, newProps.selectedHotLabelIndex) // 搜索框模糊搜索,重置为第一页
}
}
fetchShixunMemos(arg_currentPage, arg_searchValue, arg_selectedHotLabelIndex) {
/*
page = params[:page].to_i
offset = page * 15
search = params[:search]
tag = params[:tag_repertoire_id]
*/
const _search = this.props.history.location.search;
const parsed = queryString.parse(_search);
let currentPage = parseInt( arg_currentPage ? arg_currentPage : (parsed.page || 1) )
const paramsObject = {
page: currentPage // - 1 从1开始
}
let searchValue = arg_searchValue != undefined ? arg_searchValue : this.props.searchValue
if (searchValue) {
paramsObject.search = searchValue
}
let { selectedHotLabelIndex, hot_tags } = this.props;
selectedHotLabelIndex = arg_selectedHotLabelIndex ? arg_selectedHotLabelIndex : selectedHotLabelIndex
if (selectedHotLabelIndex !== -1 && hot_tags[selectedHotLabelIndex]) {
paramsObject.tag_repertoire_id = hot_tags[selectedHotLabelIndex].id
}
const stringifid = queryString.stringify(paramsObject);
const url = `/discusses/forum_discusses.json?${stringifid}` // /${challenge.identifier}/star
// 获取memo list
this.setState({
currentPage: currentPage ,
loadingMemos: true
})
axios.get(url,{
// withCredentials: true,
})
.then((response) => {
if (response.data) {
// const user = response.data.current_user;
// user.tidding_count = response.data.tidding_count;
// this.props.initCommonState(user)
this.props.initForumState(response.data)
// const { hot_tags } = response.data;
// if (hot_tags && hot_tags.length) {
// this.tagNameIdMap = {}
// hot_tags.forEach( (item, index) => {
// this.tagNameIdMap[item.name] = item.id
// })
// }
this.setState({
// p_forum_id: params.forum,
// p_s_order: params.s_order,
loadingMemos: false
})
}
}).catch((error) => {
console.log(error)
})
}
componentDidMount() {
this.fetchShixunMemos();
on('hotTagClick', (event, tagName) => {
this.props.setHotLabelIndex(tagName.selectedHotLabelIndex, () => {
this.fetchShixunMemos(1, undefined)
})
})
$(window).on('popstate', (e) => {
var state = e.originalEvent.state;
console.log('popstate', state)
if (state !== null) {
let currentPage = this.state.currentPage;;
// // 浏览器地址改变了
const search = this.props.history.location.search
const parsed = queryString.parse(search);
if (parsed.page != currentPage) {
currentPage = parseInt( parsed.page || 1)
// this.setSearchValue('')
this.fetchShixunMemos(currentPage)
this.setState({
currentPage,
})
}
}
});
}
componentWillUnmount() {
off('hotTagClick')
$(window).off('popstate')
}
renderMemoList() {
const { memo_list, user } = this.props;
if (!memo_list) {
return ''
}
/*
<PostItem key={item.id} user={user} index={index} {...this.props}
setTop={(memo)=>this.setTop(memo)}
setDown={(memo)=>this.setDown(memo)}
memo={item}
></PostItem>
*/
return memo_list.map( (item, index) => {
return (
<ShiXunPostItem key={item.id} user={user} index={index} {...this.props} memo={item}></ShiXunPostItem>
)
})
}
render() {
const { match, history , memo_count ,memo_list, showSearchValue, searchValue
, selectedHotLabelIndex, hot_tags } = this.props;
const { currentPage, loadingMemos } = this.state;
// 规则: 搜索框输入了值 或者 选择了热门标签的时候显示该提示
const _showSearchValue = showSearchValue || selectedHotLabelIndex != -1
let _searchValue;
if (showSearchValue) {
_searchValue = searchValue
} else if (selectedHotLabelIndex != -1){
_searchValue = hot_tags[selectedHotLabelIndex].name || hot_tags[selectedHotLabelIndex]
}
return (
<div className="edu-back-white" id="forum_index_list"> {/* fl with100 */}
<div className="clearfix">
{ _showSearchValue &&
<div className="noMemosTip" style={{display: loadingMemos ? 'none' : 'block'}}>
<span className="fr pr20" id="search_result">
共找到&nbsp;
<span className="color-orange03">{memo_count}</span>"
<span className="color-orange03">{_searchValue}</span>"
</span>
</div> }
<CircularProgress size={40} thickness={3}
style={{ marginLeft: 'auto', marginRight: 'auto', paddingTop: '20%'
, display: loadingMemos ? 'block': 'none' }}/>
{ !loadingMemos &&
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<MemoList {...this.props}
renderMemoList={() => this.renderMemoList()}
onPaginationChange={ (pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize) }
{...this.state}
>
</MemoList>
</React.Fragment>
}
</div>
</div>
);
}
}
export default MemoShixun;

View File

@@ -0,0 +1,69 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import moment from 'moment'
import './MemoShixun.css'
class ShiXunPostItem extends Component
{
constructor(props)
{
super(props)
this.state = {
}
}
render() {
const { memo } = this.props;
let tagStr = ''
if (memo.shixun_tag && memo.shixun_tag.length) {
memo.shixun_tag.forEach( tag => {
tagStr += ` ${tag}`
})
}
return (
<React.Fragment>
<div className="sxReturnItem">
<div className="ItemLine clearfix df">
<a href={`/users/${memo.login}`} className="ItemLineHeadPhoto">
<img src={`/images/${memo.image_url}`}
width="48px" height="48px" className="radius"/>
</a>
<div className="flex1 ml10 pr20">
<div className="clearfix" style={{ height: '32px' }}>
<p className="shixunReply task-hide font-16 fl">
<Link to={`${memo.tpm_url}`} title={memo.subject} target="_blank">{memo.subject}</Link>
</p>
{ memo.reward &&
<span className="color-orange ml20 fl" data-tip-down={`获得平台奖励金币:${memo.reward}`}
style={{ lineHeight: '20px' }}
>
<i className="iconfont icon-gift font-16 mr5 fl"></i><span className="fl mt3">{memo.reward}</span>
</span>
}
</div>
<p className="clearfix mt5">
<span className="fl color-grey-9">{memo.username}</span>
{/* <span className="fl color-grey-9 ml40">{moment(memo.updated_at).fromNow()}</span> */}
{ !!tagStr && <span className="fl color-grey-9 ml40">来自 {tagStr}</span>}
{ !!memo.praises_count && <span className="fr color-grey-6 ml20 font-12">{memo.praises_count} </span>}
{ !!memo.replies_count && <span className="fr color-grey-6 font-12">{memo.replies_count} 回复</span>}
</p>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default ShiXunPostItem