|
@@ -1,6 +1,8 @@
|
|
|
import React from 'react';
|
|
import React from 'react';
|
|
|
import PropTypes from 'prop-types';
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
|
|
|
|
|
+import Modal from 'react-bootstrap/es/Modal';
|
|
|
|
|
+
|
|
|
import AbstractEditor from './AbstractEditor';
|
|
import AbstractEditor from './AbstractEditor';
|
|
|
|
|
|
|
|
import urljoin from 'url-join';
|
|
import urljoin from 'url-join';
|
|
@@ -19,6 +21,7 @@ window.CodeMirror = require('codemirror');
|
|
|
|
|
|
|
|
|
|
|
|
|
import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
|
|
import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
|
|
|
|
|
+require('codemirror/addon/display/placeholder');
|
|
|
require('codemirror/addon/edit/matchbrackets');
|
|
require('codemirror/addon/edit/matchbrackets');
|
|
|
require('codemirror/addon/edit/matchtags');
|
|
require('codemirror/addon/edit/matchtags');
|
|
|
require('codemirror/addon/edit/closetag');
|
|
require('codemirror/addon/edit/closetag');
|
|
@@ -58,6 +61,9 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
isGfmMode: this.props.isGfmMode,
|
|
isGfmMode: this.props.isGfmMode,
|
|
|
isEnabledEmojiAutoComplete: false,
|
|
isEnabledEmojiAutoComplete: false,
|
|
|
isLoadingKeymap: false,
|
|
isLoadingKeymap: false,
|
|
|
|
|
+ isSimpleCheatsheetShown: this.props.isGfmMode && this.props.value.length === 0,
|
|
|
|
|
+ isCheatsheetModalButtonShown: this.props.isGfmMode && this.props.value.length > 0,
|
|
|
|
|
+ isCheatsheetModalShown: false,
|
|
|
additionalClassSet: new Set(),
|
|
additionalClassSet: new Set(),
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -77,8 +83,12 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
this.scrollCursorIntoViewHandler = this.scrollCursorIntoViewHandler.bind(this);
|
|
this.scrollCursorIntoViewHandler = this.scrollCursorIntoViewHandler.bind(this);
|
|
|
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.updateCheatsheetStates = this.updateCheatsheetStates.bind(this);
|
|
|
|
|
|
|
|
this.renderLoadingKeymapOverlay = this.renderLoadingKeymapOverlay.bind(this);
|
|
this.renderLoadingKeymapOverlay = this.renderLoadingKeymapOverlay.bind(this);
|
|
|
|
|
+ this.renderCheatsheetModalButton = this.renderCheatsheetModalButton.bind(this);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
init() {
|
|
init() {
|
|
@@ -152,13 +162,13 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
*/
|
|
*/
|
|
|
setGfmMode(bool) {
|
|
setGfmMode(bool) {
|
|
|
// update state
|
|
// update state
|
|
|
- const additionalClassSet = this.state.additionalClassSet;
|
|
|
|
|
this.setState({
|
|
this.setState({
|
|
|
isGfmMode: bool,
|
|
isGfmMode: bool,
|
|
|
isEnabledEmojiAutoComplete: bool,
|
|
isEnabledEmojiAutoComplete: bool,
|
|
|
- additionalClassSet,
|
|
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ this.updateCheatsheetStates(bool, null);
|
|
|
|
|
+
|
|
|
// update CodeMirror option
|
|
// update CodeMirror option
|
|
|
const mode = bool ? 'gfm' : undefined;
|
|
const mode = bool ? 'gfm' : undefined;
|
|
|
this.getCodeMirror().setOption('mode', mode);
|
|
this.getCodeMirror().setOption('mode', mode);
|
|
@@ -412,6 +422,19 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ changeHandler(editor, data, value) {
|
|
|
|
|
+ if (this.props.onChange != null) {
|
|
|
|
|
+ this.props.onChange(value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.updateCheatsheetStates(null, value);
|
|
|
|
|
+
|
|
|
|
|
+ // Emoji AutoComplete
|
|
|
|
|
+ if (this.state.isEnabledEmojiAutoComplete) {
|
|
|
|
|
+ this.emojiAutoCompleteHelper.showHint(editor);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* CodeMirror paste event handler
|
|
* CodeMirror paste event handler
|
|
|
* see: https://codemirror.net/doc/manual.html#events
|
|
* see: https://codemirror.net/doc/manual.html#events
|
|
@@ -431,7 +454,26 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * update states which related to cheatsheet
|
|
|
|
|
+ * @param {boolean} isGfmMode (use state.isGfmMode if null is set)
|
|
|
|
|
+ * @param {string} value (get value from codemirror if null is set)
|
|
|
|
|
+ */
|
|
|
|
|
+ updateCheatsheetStates(isGfmMode, value) {
|
|
|
|
|
+ if (isGfmMode == null) {
|
|
|
|
|
+ isGfmMode = this.state.isGfmMode;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value == null) {
|
|
|
|
|
+ value = this.getCodeMirror().getDoc().getValue();
|
|
|
|
|
+ }
|
|
|
|
|
+ // update isSimpleCheatsheetShown, isCheatsheetModalButtonShown
|
|
|
|
|
+ const isSimpleCheatsheetShown = isGfmMode && value.length === 0;
|
|
|
|
|
+ const isCheatsheetModalButtonShown = isGfmMode && value.length > 0;
|
|
|
|
|
+ this.setState({ isSimpleCheatsheetShown, isCheatsheetModalButtonShown });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
renderLoadingKeymapOverlay() {
|
|
renderLoadingKeymapOverlay() {
|
|
|
|
|
+ // centering
|
|
|
const style = {
|
|
const style = {
|
|
|
top: 0,
|
|
top: 0,
|
|
|
right: 0,
|
|
right: 0,
|
|
@@ -448,6 +490,161 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
: '';
|
|
: '';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ renderSimpleCheatsheet() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="panel panel-default gfm-cheatsheet mb-0">
|
|
|
|
|
+ <div className="panel-heading"><i className="icon-fw icon-question"/>Markdown Help</div>
|
|
|
|
|
+ <div className="panel-body small p-b-0">
|
|
|
|
|
+ <div className="row">
|
|
|
|
|
+ <div className="col-xs-6">
|
|
|
|
|
+ <p>
|
|
|
|
|
+ # 見出し1<br />
|
|
|
|
|
+ ## 見出し2
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p><i>*斜体*</i> <b>**強調**</b></p>
|
|
|
|
|
+ <p>
|
|
|
|
|
+ [リンク](http://..)<br />
|
|
|
|
|
+ [/ページ名/子ページ名]
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p>
|
|
|
|
|
+ ```javascript:index.js<br />
|
|
|
|
|
+ writeCode();<br />
|
|
|
|
|
+ ```
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="col-xs-6">
|
|
|
|
|
+ <p>
|
|
|
|
|
+ - リスト 1<br />
|
|
|
|
|
+ - リスト 1_1<br />
|
|
|
|
|
+ - リスト 2<br />
|
|
|
|
|
+ 1. 番号付きリスト 1
|
|
|
|
|
+ 1. 番号付きリスト 2
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <hr />
|
|
|
|
|
+ <p>行末にスペース2つ[ ][ ]<br />で改行</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ renderCheatsheetModalBody() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="row small">
|
|
|
|
|
+ <div className="col-sm-6">
|
|
|
|
|
+ <h4>Header</h4>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li><code># </code>見出し1</li>
|
|
|
|
|
+ <li><code>## </code>見出し2</li>
|
|
|
|
|
+ <li><code>### </code>見出し3</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>Block</h4>
|
|
|
|
|
+ <p className="mb-1"><code>[空白行]</code>を挟むことで段落になります</p>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>text</li>
|
|
|
|
|
+ <li></li>
|
|
|
|
|
+ <li>text</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>Line breaks</h4>
|
|
|
|
|
+ <p className="mb-1">段落中、<code>[space][space]</code>(スペース2つ) で改行されます</p>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>text<code> </code><code> </code></li>
|
|
|
|
|
+ <li>text</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>Typography</h4>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li><i>*イタリック*</i></li>
|
|
|
|
|
+ <li><b>**ボールド**</b></li>
|
|
|
|
|
+ <li><i><b>***イタリックボールド***</b></i></li>
|
|
|
|
|
+ <li>~~取り消し線~~ => <s>striked text</s></li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>Link</h4>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>[Google](https://www.google.co.jp/)</li>
|
|
|
|
|
+ <li>[/Page1/ChildPage1]</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>コードハイライト</h4>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>```javascript:index.js</li>
|
|
|
|
|
+ <li>writeCode();</li>
|
|
|
|
|
+ <li>```</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="col-sm-6">
|
|
|
|
|
+ <h4>リスト</h4>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>- リスト 1</li>
|
|
|
|
|
+ <li> - リスト 1_1</li>
|
|
|
|
|
+ <li>- リスト 2</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>1. 番号付きリスト 1</li>
|
|
|
|
|
+ <li>1. 番号付きリスト 2</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>- [ ] タスク(チェックなし)</li>
|
|
|
|
|
+ <li>- [x] タスク(チェック付き)</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>引用</h4>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>> 複数行の引用文を</li>
|
|
|
|
|
+ <li>> 書くことができます</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li>>> 多重引用</li>
|
|
|
|
|
+ <li>>>> 多重引用</li>
|
|
|
|
|
+ <li>>>>> 多重引用</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>Table</h4>
|
|
|
|
|
+ <ul className="hljs text-center">
|
|
|
|
|
+ <li>| 左寄せ | 中央寄せ | 右寄せ |</li>
|
|
|
|
|
+ <li>|:-----------|:----------:|-----------:|</li>
|
|
|
|
|
+ <li>|column 1 | column 2 | column 3|</li>
|
|
|
|
|
+ <li>|column 1 | column 2 | column 3|</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <h4>Images</h4>
|
|
|
|
|
+ <p className="mb-1"><code> </code> で<span className="text-info"><img></span>タグを挿入できます</p>
|
|
|
|
|
+ <ul className="hljs">
|
|
|
|
|
+ <li></li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+
|
|
|
|
|
+ <hr />
|
|
|
|
|
+ <a href="/Sandbox" className="btn btn-info btn-block" target="_blank">
|
|
|
|
|
+ <i className="icon-share-alt"/> Sandbox を開く
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ renderCheatsheetModalButton() {
|
|
|
|
|
+ const showCheatsheetModal = () => {
|
|
|
|
|
+ this.setState({isCheatsheetModalShown: true});
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const hideCheatsheetModal = () => {
|
|
|
|
|
+ this.setState({isCheatsheetModalShown: false});
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <React.Fragment>
|
|
|
|
|
+ <Modal className="modal-gfm-cheatsheet" show={this.state.isCheatsheetModalShown} onHide={() => { hideCheatsheetModal() }}>
|
|
|
|
|
+ <Modal.Header closeButton>
|
|
|
|
|
+ <Modal.Title><i className="icon-fw icon-question"/>Markdown Help</Modal.Title>
|
|
|
|
|
+ </Modal.Header>
|
|
|
|
|
+ <Modal.Body className="pt-1">
|
|
|
|
|
+ { this.renderCheatsheetModalBody() }
|
|
|
|
|
+ </Modal.Body>
|
|
|
|
|
+ </Modal>
|
|
|
|
|
+
|
|
|
|
|
+ <a className="gfm-cheatsheet-modal-link text-muted small" onClick={() => { showCheatsheetModal() }}>
|
|
|
|
|
+ <i className="icon-question" /> Markdown
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </React.Fragment>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
render() {
|
|
render() {
|
|
|
const mode = this.state.isGfmMode ? 'gfm' : undefined;
|
|
const mode = this.state.isGfmMode ? 'gfm' : undefined;
|
|
|
const defaultEditorOptions = {
|
|
const defaultEditorOptions = {
|
|
@@ -457,6 +654,8 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
const additionalClasses = Array.from(this.state.additionalClassSet).join(' ');
|
|
const additionalClasses = Array.from(this.state.additionalClassSet).join(' ');
|
|
|
const editorOptions = Object.assign(defaultEditorOptions, this.props.editorOptions || {});
|
|
const editorOptions = Object.assign(defaultEditorOptions, this.props.editorOptions || {});
|
|
|
|
|
|
|
|
|
|
+ const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plane Text..';
|
|
|
|
|
+
|
|
|
return <React.Fragment>
|
|
return <React.Fragment>
|
|
|
<ReactCodeMirror
|
|
<ReactCodeMirror
|
|
|
ref="cm"
|
|
ref="cm"
|
|
@@ -478,6 +677,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
lineWrapping: true,
|
|
lineWrapping: true,
|
|
|
autoRefresh: {force: true}, // force option is enabled by autorefresh.ext.js -- Yuki Takei
|
|
autoRefresh: {force: true}, // force option is enabled by autorefresh.ext.js -- Yuki Takei
|
|
|
autoCloseTags: true,
|
|
autoCloseTags: true,
|
|
|
|
|
+ placeholder: placeholder,
|
|
|
matchBrackets: true,
|
|
matchBrackets: true,
|
|
|
matchTags: {bothTags: true},
|
|
matchTags: {bothTags: true},
|
|
|
// folding
|
|
// folding
|
|
@@ -506,16 +706,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
this.props.onScroll(data);
|
|
this.props.onScroll(data);
|
|
|
}
|
|
}
|
|
|
}}
|
|
}}
|
|
|
- onChange={(editor, data, value) => {
|
|
|
|
|
- if (this.props.onChange != null) {
|
|
|
|
|
- this.props.onChange(value);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Emoji AutoComplete
|
|
|
|
|
- if (this.state.isEnabledEmojiAutoComplete) {
|
|
|
|
|
- this.emojiAutoCompleteHelper.showHint(editor);
|
|
|
|
|
- }
|
|
|
|
|
- }}
|
|
|
|
|
|
|
+ onChange={this.changeHandler}
|
|
|
onDragEnter={(editor, event) => {
|
|
onDragEnter={(editor, event) => {
|
|
|
if (this.props.onDragEnter != null) {
|
|
if (this.props.onDragEnter != null) {
|
|
|
this.props.onDragEnter(event);
|
|
this.props.onDragEnter(event);
|
|
@@ -524,6 +715,12 @@ export default class CodeMirrorEditor extends AbstractEditor {
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
{ this.renderLoadingKeymapOverlay() }
|
|
{ this.renderLoadingKeymapOverlay() }
|
|
|
|
|
+
|
|
|
|
|
+ <div className="overlay overlay-gfm-cheatsheet mt-1 p-3 pt-3">
|
|
|
|
|
+ { this.state.isSimpleCheatsheetShown && this.renderSimpleCheatsheet() }
|
|
|
|
|
+ { this.state.isCheatsheetModalButtonShown && this.renderCheatsheetModalButton() }
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
</React.Fragment>;
|
|
</React.Fragment>;
|
|
|
}
|
|
}
|
|
|
|
|
|