import React from 'react'; import PropTypes from 'prop-types'; import { Subscribe } from 'unstated'; import { Modal, ModalHeader, ModalBody, } from 'reactstrap'; import Dropzone from 'react-dropzone'; import EditorContainer from '~/client/services/EditorContainer'; import Cheatsheet from './Cheatsheet'; import AbstractEditor from './AbstractEditor'; import CodeMirrorEditor from './CodeMirrorEditor'; import TextAreaEditor from './TextAreaEditor'; import pasteHelper from './PasteHelper'; export default class Editor extends AbstractEditor { constructor(props) { super(props); this.state = { isComponentDidMount: false, dropzoneActive: false, isUploading: false, isCheatsheetModalShown: false, }; this.getEditorSubstance = this.getEditorSubstance.bind(this); this.pasteFilesHandler = this.pasteFilesHandler.bind(this); this.dragEnterHandler = this.dragEnterHandler.bind(this); this.dragLeaveHandler = this.dragLeaveHandler.bind(this); this.dropHandler = this.dropHandler.bind(this); this.showMarkdownHelp = this.showMarkdownHelp.bind(this); this.getAcceptableType = this.getAcceptableType.bind(this); this.getDropzoneClassName = this.getDropzoneClassName.bind(this); this.renderDropzoneOverlay = this.renderDropzoneOverlay.bind(this); } componentDidMount() { this.setState({ isComponentDidMount: true }); } getEditorSubstance() { return this.props.isMobile ? this.taEditor : this.cmEditor; } /** * @inheritDoc */ forceToFocus() { this.getEditorSubstance().forceToFocus(); } /** * @inheritDoc */ setValue(newValue) { this.getEditorSubstance().setValue(newValue); } /** * @inheritDoc */ setGfmMode(bool) { this.getEditorSubstance().setGfmMode(bool); } /** * @inheritDoc */ setCaretLine(line) { this.getEditorSubstance().setCaretLine(line); } /** * @inheritDoc */ setScrollTopByLine(line) { this.getEditorSubstance().setScrollTopByLine(line); } /** * @inheritDoc */ insertText(text) { this.getEditorSubstance().insertText(text); } /** * remove overlay and set isUploading to false */ terminateUploadingState() { this.setState({ dropzoneActive: false, isUploading: false, }); } /** * dispatch onUpload event */ dispatchUpload(files) { if (this.props.onUpload != null) { this.props.onUpload(files); } } /** * get acceptable(uploadable) file type */ getAcceptableType() { let accept = 'null'; // reject all if (this.props.isUploadable) { if (!this.props.isUploadableFile) { accept = 'image/*'; // image only } else { accept = ''; // allow all } } return accept; } pasteFilesHandler(event) { const items = event.clipboardData.items || event.clipboardData.files || []; // abort if length is not 1 if (items.length < 1) { return; } for (let i = 0; i < items.length; i++) { try { const file = items[i].getAsFile(); // check file type (the same process as Dropzone) if (file != null && pasteHelper.isAcceptableType(file, this.getAcceptableType())) { this.dispatchUpload(file); this.setState({ isUploading: true }); } } catch (e) { this.logger.error(e); } } } dragEnterHandler(event) { const dataTransfer = event.dataTransfer; // do nothing if contents is not files if (!dataTransfer.types.includes('Files')) { return; } this.setState({ dropzoneActive: true }); } dragLeaveHandler() { this.setState({ dropzoneActive: false }); } dropHandler(accepted, rejected) { // rejected if (accepted.length !== 1) { // length should be 0 or 1 because `multiple={false}` is set this.setState({ dropzoneActive: false }); return; } const file = accepted[0]; this.dispatchUpload(file); this.setState({ isUploading: true }); } showMarkdownHelp() { this.setState({ isCheatsheetModalShown: true }); } getDropzoneClassName(isDragAccept, isDragReject) { let className = 'dropzone'; if (!this.props.isUploadable) { className += ' dropzone-unuploadable'; } else { className += ' dropzone-uploadable'; if (this.props.isUploadableFile) { className += ' dropzone-uploadablefile'; } } // uploading if (this.state.isUploading) { className += ' dropzone-uploading'; } if (isDragAccept) { className += ' dropzone-accepted'; } if (isDragReject) { className += ' dropzone-rejected'; } return className; } renderDropzoneOverlay() { return (
{this.state.isUploading && (
Uploading...
) } {!this.state.isUploading && }
); } renderNavbar() { return (
); } getNavbarItems() { // set navbar items(react elements) here that are common in CodeMirrorEditor or TextAreaEditor const navbarItems = []; // concat common items and items specific to CodeMirrorEditor or TextAreaEditor return navbarItems.concat(this.getEditorSubstance().getNavbarItems()); } renderCheatsheetModal() { const hideCheatsheetModal = () => { this.setState({ isCheatsheetModalShown: false }); }; return ( Markdown help ); } render() { const flexContainer = { height: '100%', display: 'flex', flexDirection: 'column', }; const isMobile = this.props.isMobile; return (
{ this.dropzone = c }} accept={this.getAcceptableType()} noClick noKeyboard multiple={false} onDragLeave={this.dragLeaveHandler} onDrop={this.dropHandler} > {({ getRootProps, getInputProps, isDragAccept, isDragReject, }) => { return (
{ this.state.dropzoneActive && this.renderDropzoneOverlay() } { this.state.isComponentDidMount && this.renderNavbar() } {/* for PC */} { !isMobile && ( { editorContainer => ( // eslint-disable-next-line arrow-body-style { this.cmEditor = c }} indentSize={editorContainer.state.indentSize} editorOptions={editorContainer.state.editorOptions} onPasteFiles={this.pasteFilesHandler} onDragEnter={this.dragEnterHandler} onMarkdownHelpButtonClicked={this.showMarkdownHelp} {...this.props} /> )} )} {/* for mobile */} { isMobile && ( { this.taEditor = c }} onPasteFiles={this.pasteFilesHandler} onDragEnter={this.dragEnterHandler} {...this.props} /> )}
); }}
{ this.props.isUploadable && ( ) } { this.renderCheatsheetModal() }
); } } Editor.propTypes = Object.assign({ noCdn: PropTypes.bool, isMobile: PropTypes.bool, isUploadable: PropTypes.bool, isUploadableFile: PropTypes.bool, emojiStrategy: PropTypes.object, onChange: PropTypes.func, onUpload: PropTypes.func, }, AbstractEditor.propTypes);