|
@@ -1,4 +1,4 @@
|
|
|
-import React from 'react';
|
|
|
|
|
|
|
+import React, { useCallback } from 'react';
|
|
|
|
|
|
|
|
import { createValidator } from '@growi/codemirror-textlint';
|
|
import { createValidator } from '@growi/codemirror-textlint';
|
|
|
import { commands } from 'codemirror';
|
|
import { commands } from 'codemirror';
|
|
@@ -7,16 +7,18 @@ import * as loadCssSync from 'load-css-file';
|
|
|
import PropTypes from 'prop-types';
|
|
import PropTypes from 'prop-types';
|
|
|
import { Button } from 'reactstrap';
|
|
import { Button } from 'reactstrap';
|
|
|
import * as loadScript from 'simple-load-script';
|
|
import * as loadScript from 'simple-load-script';
|
|
|
|
|
+import { throttle, debounce } from 'throttle-debounce';
|
|
|
import urljoin from 'url-join';
|
|
import urljoin from 'url-join';
|
|
|
|
|
|
|
|
import InterceptorManager from '~/services/interceptor-manager';
|
|
import InterceptorManager from '~/services/interceptor-manager';
|
|
|
|
|
+import { useDrawioModal } from '~/stores/modal';
|
|
|
import loggerFactory from '~/utils/logger';
|
|
import loggerFactory from '~/utils/logger';
|
|
|
|
|
|
|
|
import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
|
|
import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
|
|
|
|
|
|
|
|
import AbstractEditor from './AbstractEditor';
|
|
import AbstractEditor from './AbstractEditor';
|
|
|
import CommentMentionHelper from './CommentMentionHelper';
|
|
import CommentMentionHelper from './CommentMentionHelper';
|
|
|
-// import DrawioModal from './DrawioModal';
|
|
|
|
|
|
|
+import { DrawioModal } from './DrawioModal';
|
|
|
import EditorIcon from './EditorIcon';
|
|
import EditorIcon from './EditorIcon';
|
|
|
import EmojiPicker from './EmojiPicker';
|
|
import EmojiPicker from './EmojiPicker';
|
|
|
import EmojiPickerHelper from './EmojiPickerHelper';
|
|
import EmojiPickerHelper from './EmojiPickerHelper';
|
|
@@ -105,7 +107,9 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
isCheatsheetModalShown: false,
|
|
isCheatsheetModalShown: false,
|
|
|
additionalClassSet: new Set(),
|
|
additionalClassSet: new Set(),
|
|
|
isEmojiPickerShown: false,
|
|
isEmojiPickerShown: false,
|
|
|
- emojiSearchText: null,
|
|
|
|
|
|
|
+ emojiSearchText: '',
|
|
|
|
|
+ startPosWithEmojiPickerModeTurnedOn: null,
|
|
|
|
|
+ isEmojiPickerMode: false,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
this.cm = React.createRef();
|
|
this.cm = React.createRef();
|
|
@@ -131,7 +135,16 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
this.pasteHandler = this.pasteHandler.bind(this);
|
|
this.pasteHandler = this.pasteHandler.bind(this);
|
|
|
this.cursorHandler = this.cursorHandler.bind(this);
|
|
this.cursorHandler = this.cursorHandler.bind(this);
|
|
|
this.changeHandler = this.changeHandler.bind(this);
|
|
this.changeHandler = this.changeHandler.bind(this);
|
|
|
- this.keyUpHandler = this.keyUpHandler.bind(this);
|
|
|
|
|
|
|
+ this.turnOnEmojiPickerMode = this.turnOnEmojiPickerMode.bind(this);
|
|
|
|
|
+ this.turnOffEmojiPickerMode = this.turnOffEmojiPickerMode.bind(this);
|
|
|
|
|
+ this.windowClickHandler = this.windowClickHandler.bind(this);
|
|
|
|
|
+ this.keyDownHandler = this.keyDownHandler.bind(this);
|
|
|
|
|
+ this.keyDownHandlerForEmojiPicker = this.keyDownHandlerForEmojiPicker.bind(this);
|
|
|
|
|
+ this.keyDownHandlerForEmojiPickerThrottled = throttle(400, this.keyDownHandlerForEmojiPicker);
|
|
|
|
|
+ this.showEmojiPicker = this.showEmojiPicker.bind(this);
|
|
|
|
|
+ this.keyPressHandlerForEmojiPicker = this.keyPressHandlerForEmojiPicker.bind(this);
|
|
|
|
|
+ this.keyPressHandlerForEmojiPickerThrottled = debounce(50, throttle(200, this.keyPressHandlerForEmojiPicker));
|
|
|
|
|
+ this.keyPressHandler = this.keyPressHandler.bind(this);
|
|
|
|
|
|
|
|
this.updateCheatsheetStates = this.updateCheatsheetStates.bind(this);
|
|
this.updateCheatsheetStates = this.updateCheatsheetStates.bind(this);
|
|
|
|
|
|
|
@@ -142,11 +155,9 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
|
|
this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
|
|
|
this.showLinkEditHandler = this.showLinkEditHandler.bind(this);
|
|
this.showLinkEditHandler = this.showLinkEditHandler.bind(this);
|
|
|
this.showHandsonTableHandler = this.showHandsonTableHandler.bind(this);
|
|
this.showHandsonTableHandler = this.showHandsonTableHandler.bind(this);
|
|
|
- this.showDrawioHandler = this.showDrawioHandler.bind(this);
|
|
|
|
|
|
|
|
|
|
this.foldDrawioSection = this.foldDrawioSection.bind(this);
|
|
this.foldDrawioSection = this.foldDrawioSection.bind(this);
|
|
|
this.onSaveForDrawio = this.onSaveForDrawio.bind(this);
|
|
this.onSaveForDrawio = this.onSaveForDrawio.bind(this);
|
|
|
- this.checkWhetherEmojiPickerShouldBeShown = this.checkWhetherEmojiPickerShouldBeShown.bind(this);
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -180,6 +191,13 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
}
|
|
}
|
|
|
this.emojiPickerHelper = new EmojiPickerHelper(this.getCodeMirror());
|
|
this.emojiPickerHelper = new EmojiPickerHelper(this.getCodeMirror());
|
|
|
|
|
|
|
|
|
|
+ // HACKME: Find a better way to handle onClick for Editor
|
|
|
|
|
+ document.addEventListener('click', this.windowClickHandler);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ componentWillUnmount() {
|
|
|
|
|
+ // HACKME: Find a better way to handle onClick for Editor
|
|
|
|
|
+ document.removeEventListener('click', this.windowClickHandler);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
componentWillReceiveProps(nextProps) {
|
|
@@ -583,12 +601,84 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- keyUpHandler(editor, event) {
|
|
|
|
|
- if (event.key !== 'Backspace') {
|
|
|
|
|
- this.checkWhetherEmojiPickerShouldBeShown();
|
|
|
|
|
|
|
+ turnOnEmojiPickerMode(pos) {
|
|
|
|
|
+ this.setState({
|
|
|
|
|
+ isEmojiPickerMode: true,
|
|
|
|
|
+ startPosWithEmojiPickerModeTurnedOn: pos,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ turnOffEmojiPickerMode() {
|
|
|
|
|
+ this.setState({
|
|
|
|
|
+ isEmojiPickerMode: false,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ showEmojiPicker(initialSearchingText) {
|
|
|
|
|
+ // show emoji picker with a stored word
|
|
|
|
|
+ this.setState({
|
|
|
|
|
+ isEmojiPickerShown: true,
|
|
|
|
|
+ emojiSearchText: initialSearchingText ?? '',
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const resetStartPos = initialSearchingText == null;
|
|
|
|
|
+ if (resetStartPos) {
|
|
|
|
|
+ this.setState({ startPosWithEmojiPickerModeTurnedOn: null });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.turnOffEmojiPickerMode();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ keyPressHandlerForEmojiPicker(editor, event) {
|
|
|
|
|
+ const char = event.key;
|
|
|
|
|
+ const isEmojiPickerMode = this.state.isEmojiPickerMode;
|
|
|
|
|
+
|
|
|
|
|
+ // evaluate whether emoji picker mode to be turned on
|
|
|
|
|
+ if (!isEmojiPickerMode) {
|
|
|
|
|
+ const startPos = this.emojiPickerHelper.shouldModeTurnOn(char);
|
|
|
|
|
+ if (startPos == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.turnOnEmojiPickerMode(startPos);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // evaluate whether EmojiPicker to be opened
|
|
|
|
|
+ const startPos = this.state.startPosWithEmojiPickerModeTurnedOn;
|
|
|
|
|
+ if (this.emojiPickerHelper.shouldOpen(startPos)) {
|
|
|
|
|
+ const initialSearchingText = this.emojiPickerHelper.getInitialSearchingText(startPos);
|
|
|
|
|
+ this.showEmojiPicker(initialSearchingText);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.turnOffEmojiPickerMode();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ keyPressHandler(editor, event) {
|
|
|
|
|
+ this.keyPressHandlerForEmojiPickerThrottled(editor, event);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ keyDownHandlerForEmojiPicker(editor, event) {
|
|
|
|
|
+ const key = event.key;
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.state.isEmojiPickerMode) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'BackSpace'].includes(key)) {
|
|
|
|
|
+ this.turnOffEmojiPickerMode();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ keyDownHandler(editor, event) {
|
|
|
|
|
+ this.keyDownHandlerForEmojiPickerThrottled(editor, event);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ windowClickHandler() {
|
|
|
|
|
+ this.turnOffEmojiPickerMode();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* CodeMirror paste event handler
|
|
* CodeMirror paste event handler
|
|
|
* see: https://codemirror.net/doc/manual.html#events
|
|
* see: https://codemirror.net/doc/manual.html#events
|
|
@@ -610,26 +700,6 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * Show emoji picker component when emoji pattern (`:` + searchWord ) found
|
|
|
|
|
- * eg `:a`, `:ap`
|
|
|
|
|
- */
|
|
|
|
|
- checkWhetherEmojiPickerShouldBeShown() {
|
|
|
|
|
- const searchWord = this.emojiPickerHelper.getEmoji();
|
|
|
|
|
-
|
|
|
|
|
- if (searchWord == null) {
|
|
|
|
|
- this.setState({ isEmojiPickerShown: false });
|
|
|
|
|
- this.setState({ emojiSearchText: null });
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- this.setState({ emojiSearchText: searchWord });
|
|
|
|
|
- // Show emoji picker after user stop typing
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- this.setState({ isEmojiPickerShown: true });
|
|
|
|
|
- }, 700);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* update states which related to cheatsheet
|
|
* update states which related to cheatsheet
|
|
|
* @param {boolean} isGfmModeTmp (use state.isGfmMode if null is set)
|
|
* @param {boolean} isGfmModeTmp (use state.isGfmMode if null is set)
|
|
@@ -709,7 +779,8 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
<div className="text-left">
|
|
<div className="text-left">
|
|
|
<div className="mb-2 d-none d-md-block">
|
|
<div className="mb-2 d-none d-md-block">
|
|
|
<EmojiPicker
|
|
<EmojiPicker
|
|
|
- onClose={() => this.setState({ isEmojiPickerShown: false, emojiSearchText: null })}
|
|
|
|
|
|
|
+ onClose={() => this.setState({ isEmojiPickerShown: false })}
|
|
|
|
|
+ onSelected={emoji => this.emojiPickerHelper.addEmoji(emoji, this.state.startPosWithEmojiPickerModeTurnedOn)}
|
|
|
emojiSearchText={emojiSearchText}
|
|
emojiSearchText={emojiSearchText}
|
|
|
emojiPickerHelper={this.emojiPickerHelper}
|
|
emojiPickerHelper={this.emojiPickerHelper}
|
|
|
isOpen={this.state.isEmojiPickerShown}
|
|
isOpen={this.state.isEmojiPickerShown}
|
|
@@ -799,10 +870,6 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
// this.handsontableModal.current.show(mtu.getMarkdownTable(this.getCodeMirror()));
|
|
// this.handsontableModal.current.show(mtu.getMarkdownTable(this.getCodeMirror()));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- showDrawioHandler() {
|
|
|
|
|
- // this.drawioModal.current.show(mdu.getMarkdownDrawioMxfile(this.getCodeMirror()));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
// fold draw.io section (::: drawio ~ :::)
|
|
// fold draw.io section (::: drawio ~ :::)
|
|
|
foldDrawioSection() {
|
|
foldDrawioSection() {
|
|
@@ -954,7 +1021,7 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
color={null}
|
|
color={null}
|
|
|
bssize="small"
|
|
bssize="small"
|
|
|
title="draw.io"
|
|
title="draw.io"
|
|
|
- onClick={this.showDrawioHandler}
|
|
|
|
|
|
|
+ onClick={() => this.props.onClickDrawioBtn(mdu.getMarkdownDrawioMxfile(this.getCodeMirror()))}
|
|
|
>
|
|
>
|
|
|
<EditorIcon icon="Drawio" />
|
|
<EditorIcon icon="Drawio" />
|
|
|
</Button>,
|
|
</Button>,
|
|
@@ -963,7 +1030,7 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
color={null}
|
|
color={null}
|
|
|
bssize="small"
|
|
bssize="small"
|
|
|
title="Emoji"
|
|
title="Emoji"
|
|
|
- onClick={() => this.setState({ isEmojiPickerShown: true })}
|
|
|
|
|
|
|
+ onClick={() => this.showEmojiPicker()}
|
|
|
>
|
|
>
|
|
|
<EditorIcon icon="Emoji" />
|
|
<EditorIcon icon="Emoji" />
|
|
|
</Button>,
|
|
</Button>,
|
|
@@ -1043,7 +1110,8 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
this.props.onDragEnter(event);
|
|
this.props.onDragEnter(event);
|
|
|
}
|
|
}
|
|
|
}}
|
|
}}
|
|
|
- onKeyUp={this.keyUpHandler}
|
|
|
|
|
|
|
+ onKeyPress={this.keyPressHandler}
|
|
|
|
|
+ onKeyDown={this.keyDownHandler}
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
{ this.renderLoadingKeymapOverlay() }
|
|
{ this.renderLoadingKeymapOverlay() }
|
|
@@ -1059,17 +1127,11 @@ class CodeMirrorEditor extends AbstractEditor {
|
|
|
ref={this.linkEditModal}
|
|
ref={this.linkEditModal}
|
|
|
onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
|
|
onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
|
|
|
/>
|
|
/>
|
|
|
- {/*
|
|
|
|
|
- <HandsontableModal
|
|
|
|
|
|
|
+ {/* <HandsontableModal
|
|
|
ref={this.handsontableModal}
|
|
ref={this.handsontableModal}
|
|
|
onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
|
|
onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
|
|
|
autoFormatMarkdownTable={this.props.editorSettings.autoFormatMarkdownTable}
|
|
autoFormatMarkdownTable={this.props.editorSettings.autoFormatMarkdownTable}
|
|
|
/> */}
|
|
/> */}
|
|
|
- {/* <DrawioModal
|
|
|
|
|
- ref={this.drawioModal}
|
|
|
|
|
- onSave={this.onSaveForDrawio}
|
|
|
|
|
- /> */}
|
|
|
|
|
-
|
|
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -1088,4 +1150,17 @@ CodeMirrorEditor.defaultProps = {
|
|
|
lineNumbers: true,
|
|
lineNumbers: true,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export default CodeMirrorEditor;
|
|
|
|
|
|
|
+
|
|
|
|
|
+const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
|
|
|
|
|
+ const { open: openDrawioModal } = useDrawioModal();
|
|
|
|
|
+
|
|
|
|
|
+ const openDrawioModalHandler = useCallback((drawioMxFile) => {
|
|
|
|
|
+ openDrawioModal(drawioMxFile);
|
|
|
|
|
+ }, [openDrawioModal]);
|
|
|
|
|
+
|
|
|
|
|
+ return <CodeMirrorEditor ref={ref} onClickDrawioBtn={openDrawioModalHandler} {...props} />;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+CodeMirrorEditorFc.displayName = 'CodeMirrorEditorFc';
|
|
|
|
|
+
|
|
|
|
|
+export default CodeMirrorEditorFc;
|