618 lines
20 KiB
JavaScript
618 lines
20 KiB
JavaScript
import React, { Component } from 'react';
|
||
import { Select, Icon, Upload, Button } from 'antd';
|
||
import axios from 'axios'
|
||
|
||
import 'antd/lib/select/style/index.css'
|
||
import TPMMDEditor from '../tpm/challengesnew/TPMMDEditor'
|
||
|
||
import { getUrl, getUploadActionUrl, appendFileSizeToUploadFileAll, appendFileSizeToUploadFile } from 'educoder'
|
||
const Option = Select.Option;
|
||
const $ = window.$;
|
||
|
||
let origin = getUrl();
|
||
|
||
// load
|
||
if (!window.postUpMsg) {
|
||
$.getScript(
|
||
`${origin}/javascripts/attachments.js`,
|
||
(data, textStatus, jqxhr) => {
|
||
|
||
});
|
||
}
|
||
|
||
|
||
const typeNameMap = {
|
||
'技术分享': 5,
|
||
'操作指南': 3,
|
||
'通知公告': 16,
|
||
}
|
||
export const typeNameMap2 = {
|
||
5: '技术分享',
|
||
3: '操作指南',
|
||
16: '通知公告',
|
||
}
|
||
const defaultType = '技术分享'
|
||
|
||
class MemoNew extends Component {
|
||
constructor(props) {
|
||
super(props)
|
||
this.mdRef = React.createRef();
|
||
|
||
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
|
||
})
|
||
|
||
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`)
|
||
}
|
||
; 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;
|
||
if (data.tag_list) {
|
||
document.title = "交流问答";
|
||
this.setState({
|
||
tag_list: data.tag_list
|
||
})
|
||
|
||
// 初始化 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
|
||
})
|
||
}
|
||
}).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();
|
||
}
|
||
|
||
|
||
|
||
|
||
}
|
||
|
||
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('/')
|
||
attachments.push(
|
||
<React.Fragment>
|
||
<span id={`attachments_10${index}`} className="attachment">
|
||
<label className="panel-form-label fl"> </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"></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>
|
||
|
||
<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>
|
||
|
||
</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="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 className="defalutSubmitbtn fl mr20"
|
||
onClick={() => { this.onCommit() }}>提交</a>
|
||
<a onClick={() => { this.onCancel() }} className="defalutCancelbtn fl">取消</a>
|
||
</div>
|
||
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default MemoNew;
|