forgeplus-react/src/exchange/Post/MemoNew.js

812 lines
29 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 axios from 'axios';
import { Select } from 'antd';
import 'antd/lib/select/style/index.css'
import 'antd/lib/date-picker/style/index.css'
import zhCN from 'antd/lib/date-picker/locale/zh_CN';
import './MemoNew.css'
import './post.css'
import '../exchange.css'
import ImageUpload from './ImageUpload'
const Option = Select.Option;
const $ = window.$;
const isDevServer = window.location.port === "3007";
let origin = window.location.origin;
let path = "/editormd/lib/" // origin + '/react/build/js/editormd/lib/'
if (isDevServer) {
origin = 'http://localhost:3000'
path = 'http://localhost:3000/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", '|', "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>"
},
//这个配置在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(){
// 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-2);
editorName.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
callback && callback()
}
});
return editorName;
}
class MemoNew extends Component {
constructor(props) {
super(props)
this.state = {
memoSubject: '',
memoContent: '',
memoType: undefined,
memoTime: undefined,
memoRepertoire: '',
memoLanguage: [],
fileList: [],
memoSubjectLength:undefined,
repertoires: [],
currentSelectRepertoiresIndex: -1,
repertoiresTagMap: {},
// 主题板块
smallOption:undefined,
bigPlateId:undefined,
smallPlateId:undefined,
}
}
onCommit() {
const { memoSubject , memoLanguage, currentMemoId, attachmentData , bigPlateId , smallPlateId} = this.state;
const { showSnackbar } = this.props;
this.setState({
memoSubjectLength : memoSubject.length
})
if (!memoSubject) {
showSnackbar('请先输入标题')
return
}
let mdVal;
try {
mdVal = this.taskpass_editormd.getValue()
} catch (e) {
showSnackbar('编辑器还未加载完毕,请稍后')
return
}
if (!mdVal) {
showSnackbar('请输入话题内容')
return
}
if (!bigPlateId) {
showSnackbar('请选择一级板块');
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 = {};
let lastIndex = 0;
$('#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(6)').val()
const attachment_id = $(item).find('input:nth-child(7)').val()
attachmentsMap[index] = {
filename,
token,
attachment_id
}
lastIndex = index;
})
if (attachmentData) {
const { response } = attachmentData
lastIndex++;
attachmentsMap[lastIndex] = {
filename: attachmentData.name,
token: '',
attachment_id: response.attachment_id
}
}
if (currentMemoId) {
this.updateMemo(attachmentsMap)
} else {
this.newMemo(attachmentsMap)
}
}
onCancel() {
const { currentMemoId } = this.state;
if (currentMemoId) { // 编辑
this.props.history.push(`/forums/${currentMemoId}`)
} else { // 新建
this.props.history.push(`/forums`)
}
}
onOkTime(value, dateString){
this.setState({
memoTime:dateString
})
}
updateMemo(attachmentsMap) {
const { memoSubject , currentMemoId, content, attachmentData , smallPlateId , bigPlateId} = this.state;
const mdVal = this.taskpass_editormd.getValue()
console.log('isContentEdit: ', mdVal === content);
const newMemoUrl = `/memos/${currentMemoId}/update`
const params = {
content_changed: this.contentChanged,
memo: {
subject: memoSubject ,
content: mdVal,
},
forum_id:bigPlateId,
attachments: attachmentsMap,
children_forum_id:smallPlateId
}
if (attachmentData) {
const attachment_id = attachmentData.response.attachment_id
if (attachment_id) {
params.attachment_id = attachment_id;
}
}
axios.post(newMemoUrl, params)
.then((response) => {
const { status, message } = response.data;
if (status === 0) {
window.$("html,body").animate({"scrollTop":0})
this.props.history.push(`/forums/${currentMemoId}`)
} else {
if (message.indexOf("Couldn't find Attachment with") !== -1) {
this.props.showSnackbar('附件不存在或正在被删除中,请稍后再试。。。')
} else {
this.props.showSnackbar(message)
}
}
}).catch((error) => {
console.log(error)
})
}
newMemo(attachmentsMap) {
const { memoSubject , attachmentData , smallPlateId , bigPlateId } = this.state;
const mdVal = this.taskpass_editormd.getValue()
const newMemoUrl = `/memos/create`
const params = {
memo: {
subject: memoSubject ,
content: mdVal
},
forum_id:bigPlateId,
attachments: attachmentsMap,
children_forum_id:smallPlateId,
}
if (attachmentData) {
const attachment_id = attachmentData.response.attachment_id
if (attachment_id) {
params.attachment_id = attachment_id;
// params.filename = attachmentData.name // 服务端处理 服务端使用默认的origin_filename
}
}
axios.post(newMemoUrl, params).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 {
if (message.indexOf("Couldn't find Attachment with") !== -1) {
this.props.showSnackbar('附件不存在或正在被删除中,请稍后再试。。。')
} else {
this.props.showNotification(message)
}
}
}).catch((error) => {
console.log(error)
})
}
onMemoDelete(memo) {
const deleteUrl = `/memos/${memo.id}`;
// 获取memo list
axios.delete(deleteUrl, {
withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showSnackbar('删除成功');
this.props.history.push(`/forums`)
}
}).catch((error) => {
console.log(error)
})
}
componentWillUnmount() {
$('body>#root').off('onMemoDelete')
}
componentDidMount(){
$('body>#root').on('onMemoDelete', (event) => {
// const val = $('body>#root').data('onMemoDelete')
const val = window.onMemoDelete ;
this.onMemoDelete( JSON.parse(decodeURIComponent(val)) )
})
const newMemoUrl = `/memos/new`
axios.get(newMemoUrl,{
// withCredentials: true,
})
.then((response) => {
const data = response.data;
if ( data.current_user ) {
this.setState({
memo_tag: data.memo_type,
memoTime:""
})
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" />`) )
}
}).catch((error) => {
console.log(error)
})
// 如果是编辑
const { match } = this.props
const memoId = match.params.memoId;
if (memoId) {
const memoUrl = `/memos/${match.params.memoId}/edit`;
axios.get(memoUrl).then((response) => {
const current_user = response.data.current_user;
if (current_user) {
const { content, forum_section, id, subject , attachments_url, memo_image_info,
memo_type, published_at , children_forum_section} = response.data;
let date;
let date_value;
this.initMD(content, memoId);
if(published_at){
date = new Date(published_at);
date_value=date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
}
const newState = {
currentMemoId: id,
attachments_url,
memoSubject: subject,
memo_tag: memo_type,
memoLanguage: forum_section.forum_id,
memoTime:date_value,
content,
bigPlateId: forum_section.forum_id,
smallPlateId: children_forum_section.children_forum_id
}
// 编辑,初始化小板块的下拉选项(如果大板块有值)
this.getChildPlate(memo_type,forum_section && forum_section.forum_id);
if (memo_image_info && memo_image_info.id) {
newState.fileList = [{
uid: memo_image_info.id,
name: memo_image_info.filename,
status: 'done',
url: memo_image_info.url,
}]
newState.attachmentData = { // 初始化
name: memo_image_info.filename,
response: {
attachment_id: memo_image_info.id,
}
}
}
this.setState({...newState})
// 加载完后滚动条滚动
window.$("html,body").animate({"scrollTop":0})
// this.props.initForumState({
// current_user,
// tag_list
// })
}
}).catch((error) => {
console.log(error)
})
} else {
this.initMD();
}
}
initMD(initValue, memoId) {
this.contentChanged = false;
const placeholder = "";
// amp;
// 编辑时要传memoId
const imageUrl = `/upload_with_markdown?container_id=${memoId?memoId:''}&container_type=Memo`;
// 创建editorMd
setTimeout(()=>{
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;
}, 300)
}
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: []
});
};
onTagChange(value) {
if (value && value.length > 3) {
this.props.showSnackbar(`最多选择3个技术标签`)
return;
}
this.setState({
memoLanguage: value
})
}
onTypeChange(value) {
this.setState({
memoType: value
})
}
onMemoNameChange(e) {
this.setState({
memoSubject: e.target.value
})
}
renderMemoType() {
const { memo_type } = this.state;
if (!memo_type || memo_type.length === 0) {
return ''
}
const result = []
// memo_type.map((item, index) => <Option value={item.id} key={index} >{item.name}</Option> )
memo_type.forEach((item, index) => {
result.push(<Option value={item.id} key={index} >{item.name}</Option>)
})
return result;
}
renderTag() {
let { memo_tag } = this.state;
if (!memo_tag || memo_tag.length === 0) {
return ''
}
const result = []
memo_tag.forEach((item, index) => {
result.push(<Option value={item.id+''} key={index} >{item.name}</Option>)
})
return result;
}
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">
<i className="fa fa-folder mr5 color-light-grey newLeftgrey" aria-hidden="true"></i>
<input type="text" className="upload_filename readonly hidden" name="attachments[2][filename]" readonly="readonly"
style={{border:'none', width:'220px',whiteSpace: 'nowrap', textOverflow:'ellipsis',fontFamily: 'Consolas'}}
size="8" value={item.filename}></input>
<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="fa fa-trash-o mr5"></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;
}
_findById(id, arg_items) {
if (!arg_items) {
return undefined
}
const items = arg_items;
for(let i = 0; i < items.length; i++) {
if (id === items[i].id) {
return i;
}
}
}
// 是否是技术动态吧
_isTechShare() {
const { memoType, memo_type } = this.state
const index = this._findById(memoType, memo_type)
return !!index && memo_type[index] && memo_type[index].name.indexOf('技术问答') !== -1
}
onImageUploadDone = (data) => {
this.setState({
attachmentData: data
})
}
onImageUploadRemove = () => {
const { attachment_id } = this.state.attachmentData.response;
const deleteUrl = `/attachments/${attachment_id}.js?attachment_id=1`;
// 获取memo list
axios.delete(deleteUrl, {
withCredentials: true,
})
.then((response) => {
const data = response.data
if (data) {
this.setState({
attachmentData: undefined
})
this.props.showSnackbar('删除成功');
}
}).catch((error) => {
console.log(error)
})
}
// 切换大板块
changeLargeOption=(value)=>{
const { memo_tag } = this.state;
this.getChildPlate(memo_tag,value);
}
getChildPlate =(memo_tag,value)=>{
let listFilter = memo_tag && memo_tag.length > 0 && memo_tag.filter((item=> item.id === value))[0];
let list = listFilter && listFilter.children_tags;
this.setState({
smallOption:list && list.map(item=>{
return(
<Option value={item.id}>{item.title}</Option>
)
}),
bigPlateId:value,
smallPlateId: list && list.length > 0 ? (list[0].id || undefined) : undefined
})
}
// 切换二级板块
changeSmallOption=(value)=>{
this.setState({
smallPlateId:value
})
}
render() {
const { match } = this.props
const {
memoSubject ,
attachments_url,
currentMemoId,
fileList,
memoSubjectLength ,
memo_tag,
smallOption,
bigPlateId,
smallPlateId
} = this.state;
const memoId = match.params.memoId;
console.log("bigPlateId",bigPlateId);
return (
<div className="educontent-min postNewform">
<p className="font-22 color-grey3 mt30 mb10">{ memoId ? '编辑' : '新建'}</p>
<div className="edu-back-white mb10 clearfix" id="memoSubject" style={{position:'relative'}}>
<div className="padding30">
<div className="df">
<span className="mr20 new_label"><span className="color-orange">*</span></span>
<div className="flex1">
<input type="text" className="input-100-45" maxlength="50"
value={memoSubject} onChange={(val)=>this.onMemoNameChange(val)} placeholder="请输入发布标题最大限制50字符">
</input>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl" style={memoSubjectLength==0?{display:'block'}:{display:'none'}}>
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
{/* 发布时间 */}
{/* <div className="df ecSelectbox">
<span className="mr30 color-orange pt10"></span>
<div className="flex1 mr20">
<DatePicker
disabledDate={disabledDate}
showTime
format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择发布时间"
locale={zhCN}
placeholder={"请选择时间"}
onChange={(value, dateString)=>this.onOkTime(value, dateString)}
/>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>必填项
</span>
</div>
</div> */}
{/* <p className="color-grey-6 font-16 mb30">内容</p> */}
<div className="df mt20">
<span className="mr20 new_label"><span className="color-orange">*</span></span>
<div className="flex1">
<div className="break_word new_li" id="memoMD">
<textarea style={{'display':'none'}}></textarea>
</div>
</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" style={{marginLeft:"70px"}}>
<span id={`attachments_fields`} className="attachments_fields newforgeattachment" 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>
{/* 请求status 422 X-CSRF-Token: eVo38laEF880o3cwZ/0F9kH01q4jMkriuVRemIBq06Y= */}
<div className="df uploadBtn uploadBtnclick mb20" >
<a className="fl" onClick={()=>window.$('#_file').click()}>
<span style={{color: '#21B351', fontSize: "14px", marginLeft: "62px"}}>上传附件</span>
<span style={{color: '#CDCDCD', fontSize: "14px"}}>(单个文件50M以内)</span>
</a>
</div>
{/* 选择了技术问答才显示树标签 */}
{/* this._isTechShare() &&
<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': '500px', 'overflow': 'auto'}}
mode="multiple"
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="df ecSelectbox mb20">
<span className="mr20 new_label"><span className="color-orange">*</span></span>
<div className="flex1">
{/* <Select className="ecSelect"
value={memoType}
placeholder="请选择技术标签"
onChange={(val)=>this.onTypeChange(val)}>
{ this.renderMemoType()}
</Select> */}
{/* <Select
mode="multiple"
className="ecSelect"
value={memoLanguage}
placeholder="请选择技术标签"
onChange={(e) => this.onTagChange(e)}
dropdownStyle={{'maxHeight': '500px', 'overflow': 'auto'}}
>
{this.renderTag()}
</Select> */}
<Select className="selectItem" value={bigPlateId} onChange={this.changeLargeOption}>
{
memo_tag && memo_tag.length > 0 && memo_tag.map((item,key)=>{
return(
<Option value={item.id} key={key}>{item.name}</Option>
)
})
}
</Select>
<Select className="selectItem" value={smallPlateId} onChange={this.changeSmallOption}>
{ smallOption }
</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 className="df">
<span className="mr20 new_label">上传封面</span>
<div className="flex1 mr20">
<ImageUpload
fileList={fileList}
currentMemoId={currentMemoId}
onImageUploadRemove={this.onImageUploadRemove}
onImageUploadDone={this.onImageUploadDone}
></ImageUpload>
</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 mb50 edu-txt-center">
<span className="inline">
<a onClick={()=>{ this.onCancel() }} className="defalutCancelbtn mr20 fl newdefalutCancelbtn">返回</a>
<a className="defalutSubmitbtn fl newdefalutSubmitbtn" onClick={()=>{this.onCommit()}}>提交</a>
</span>
</div>
</div>
);
}
}
const children = [];
for (let i = 10; i < 36; i++) {
children.push(<Option key={i.toString(36) + i}>{i.toString(36) + i}</Option>);
}
export default MemoNew;