import dayjs from "dayjs"; import shortid from "shortid"; import PropTypes from "prop-types"; import classnames from "classnames"; import React, { Fragment } from "react"; import intl from "react-intl-universal"; import { Icon, Button, Popover, Input, message } from "antd"; import Emoji from "./Emoji"; import Upload from "./Upload"; import Comment from "../../Comment"; import { isMobile } from "./../../utils"; import { OSS_LINK } from "../../constant"; import { isFunction } from "../../helper"; import { OSS_ENDPOINT, OSS_BUCKET, DRIVER_LICENSE_PATH, ERROR_DEFAULT } from "../../constant"; import "./index.css"; const { TextArea } = Input; const client = oss => { return new window.OSS.Wrapper({ accessKeyId: oss.access_key_id, accessKeySecret: oss.access_key_secret, stsToken: oss.security_token, endpoint: OSS_ENDPOINT, //常量,你可以自己定义 bucket: OSS_BUCKET }); }; const uploadPath = (path, file) => { return `${path}/${dayjs().format("YYYYMMDD")}/${shortid.generate()}.${ file.type.split("/")[1] }`; }; const UploadToOss = (oss, path, file) => { const url = uploadPath(path, file); return new Promise((resolve, reject) => { client(oss) .multipartUpload(url, file) .then(data => { resolve(data); }) .catch(error => { reject(error); }); }); }; class Editor extends React.Component { constructor(props) { super(props); this.state = { showUpload: false, value: props.value || "", // 编辑器里面的值 fileList: props.fileList || [], // 图片列表 fileMap: {}, // 已经上传的图片路径和 uid 的映射 { uid: path } uploadVisible: false // 上传图片弹窗是否可视 }; this.handleChange = this.handleChange.bind(this); this.handleClickEmoji = this.handleClickEmoji.bind(this); this.handleChangeFileList = this.handleChangeFileList.bind(this); this.handleShowUpload = this.handleShowUpload.bind(this); this.handleUpload = this.handleUpload.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.handlePaste = this.handlePaste.bind(this); this.resetState = this.resetState.bind(this); this.handleEmojiScroll = this.handleEmojiScroll.bind(this); this.handlePressEnter = this.handlePressEnter.bind(this); this.invokeFileListChange = this.invokeFileListChange.bind(this); } componentDidMount() { const { app, onRef, uploadDefaultShow } = this.props; if ( app.currentUser && (app.currentUser.user_id > 0 || app.currentUser.id > 0) ) { app.sOssSts(); } if (isFunction(onRef)) { onRef(this); } if (uploadDefaultShow) { const uploadFileList = this.props.fileList || this.state.fileList; if (uploadFileList.length > 0) { this.setState({ uploadVisible: true, }); } } } handleEmojiScroll(e) { if (!this.emoji) { return; } e.preventDefault(); if (e.deltaY > 0) { this.emoji.next(); } else if (e.deltaY < 0) { this.emoji.prev(); } } /** * 编辑器的值改变事件 * 将最新的值存储到 state 中 * @param {string} value 输入的值 */ handleChange(value) { this.setState({ value }); if (this.props.onChange) { this.props.onChange(value); } } /** * 点击 emoji 的事件 * 点击后,需要将改 emoji 插入到编辑器中 * 插入的值为 [emoji chinese name] * 参数 emoji 即为 emoji chinese name * @param {string}} emoji emoji 的中文,如 微笑 */ handleClickEmoji(emoji) { let value = this.props.value || this.state.value; value += `[${emoji}]`; this.handleChange(value); } /** * 监听文件列表改变事件 * @param {Array} fileList 文件列表 */ handleChangeFileList(fileList) { let list = fileList; if (fileList.length > this.props.maxUpload) { list = fileList.slice(0, this.props.maxUpload); } this.invokeFileListChange(list); } /** * 控制上传 Popover 的显示和隐藏 * @param {boolean} showUpload 是否显示上传的 Popover */ handleShowUpload(showUpload) { if (typeof showUpload === "boolean") { this.setState({ showUpload: showUpload }); } else { this.setState({ showUpload: !this.state.showUpload }); } } /** * 上传文件 * @param {object} param 文件对象 */ handleUpload({ uid, path }) { const { fileMap } = this.state; let { fileList } = this.state; fileMap[uid] = path; fileList = fileList.map(item => { if (item.uid === uid) { item.thumbUrl = OSS_LINK + path; } return item; }); this.setState({ fileMap }); this.invokeFileListChange(fileList); } /** * **统一处理fileList的修改** * 1. upload * 2. paste * PS: 移动端需要做额外操作 * -- evo 20200223 */ invokeFileListChange(fileList) { const { limitOne, handleChangeFileList } = this.props; handleChangeFileList(fileList); this.setState({ fileList }); if (limitOne && isMobile) { const file = fileList[0]; if ( file && file.status === "done" && !file.thumbUrl.includes("data:image") ) { this.setState({ uploadVisible: false }); } } } /** * 粘贴回调 */ handlePaste(e) { if (this.state.fileList.length >= this.props.maxUpload) { return; } const items = e.clipboardData && e.clipboardData.items; let file = null; if (items && items.length) { for (let i = 0; i < items.length; i++) { if (items[i].type.indexOf("image") !== -1) { file = items[i].getAsFile(); break; } } if (file === null) return; } this.setState({ uploadVisible: true }); let reader = new FileReader(); reader.readAsDataURL(file); reader.onloadend = () => { // DRIVER_LICENSE_PATH oss 的存储路径位置 UploadToOss(this.props.app.oss, DRIVER_LICENSE_PATH, file) .then(data => { const fileList = this.state.fileList.concat({ url: OSS_LINK + data.name, thumbUrl: OSS_LINK + data.name, type: file.type, uid: new Date().valueOf() }); this.invokeFileListChange(fileList); }) .catch(e => { const msg = e.message || ERROR_DEFAULT; if (this.props.showError) { message.error(msg); } if (this.props.onError) { this.props.onError(msg, { response: e.response }); } }); }; } /** * 提交编辑器内容 * 提交功能,交给父组件来实现 * 需要父组件传入 onSubmit */ handleSubmit() { const { maxLength } = this.props; let { value, fileMap, fileList } = this.state; if (value.length > maxLength) { // message.error(`字数不得超过${maxLength}字`); message.error(intl.get("editor.maxLength", { maxLength })); return; } const files = []; if (fileList.length) { fileList.forEach(item => { if (item.url) { files.push(item.url); return; } if (!fileMap[item.uid]) { return; } files.push(`${OSS_LINK}${fileMap[item.uid]}`); }); } if (this.props.beforeSubmit) { Promise.resolve(this.props.beforeSubmit({ text: value, files })).then( res => { if (!(res === false)) { this.props.onSubmit({ text: value, files }, (res, action) => { this.resetState(); if (action === "comment" && this.props.onCommentSuccess) { this.props.onCommentSuccess(res); } }); } } ); } else { this.props.onSubmit({ text: value, files }, (res, action) => { this.resetState(); if (action === "comment" && this.props.onCommentSuccess) { this.props.onCommentSuccess(res); } }); } } resetState() { this.handleChange(""); this.handleChangeFileList([]); this.setState({ showUpload: false, value: "", fileList: [], fileMap: {} }); } checkDisabledSubmit() { const { btnDisabled, value, fileList } = this.props; if (btnDisabled) { return true; } if (value && value !== "") { return false; } if (this.state.value && this.state.value !== "") { return false; } if (fileList && fileList.length > 0) { return false; } if (this.state.fileList.length > 0) { return false; } return true; } /** * **处理Enter事件** * 1. `allowEnterSubmit`为true时enter触发submit事件 * 2. `e.preventDefault`为了防止enter事件后仍触发换行 * 3. enter事件开启后,仍可以用`shift + enter`触发换行 * -- evo 20200222 */ handlePressEnter(e) { if (this.props.allowEnterSubmit) { if (!e.shiftKey) { e.preventDefault(); this.handleSubmit(); } } } render() { const { value, // placeholder, rows, showEmoji, showUpload, multiple, emojiPopoverPlacement, uploadPopoverPlacement, uploadOverlayClassName, fileList, maxUpload, // btnSubmitText, btnLoading, button, emojiToolIcon, imageToolIcon, maxLength, autoFocus, app } = this.props; let placeholder = this.props.placeholder || intl.get("editor.placeholder"); let btnSubmitText = this.props.btnSubmitText || intl.get("editor.SubmitBtn"); const handleSubmit = this.handleSubmit; const disabledSubmit = this.checkDisabledSubmit(); const inputValue = value || this.state.value; const uploadFileList = fileList || this.state.fileList; const isLogin = app.currentUser && (app.currentUser.user_id > 0 || app.currentUser.id > 0); return (
{isLogin ? (
maxLength })} >