forgeplus-react/src/modules/forums/MemoNew.js

618 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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">&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>
<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;