Răsfoiți Sursa

refactor and add EmojiAutoCompleteHelper

Yuki Takei 8 ani în urmă
părinte
comite
2c513cacf8

+ 2 - 126
resource/js/components/PageEditor/Editor.js

@@ -21,6 +21,7 @@ require('codemirror/mode/gfm/gfm');
 require('codemirror/theme/eclipse.css');
 
 import pasteHelper from './PasteHelper';
+import emojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
 
 
 export default class Editor extends React.Component {
@@ -35,14 +36,10 @@ export default class Editor extends React.Component {
       value: this.props.value,
     };
 
-    this.initEmojiImageMap = this.initEmojiImageMap.bind(this);
     this.getCodeMirror = this.getCodeMirror.bind(this);
     this.setCaretLine = this.setCaretLine.bind(this);
     this.forceToFocus = this.forceToFocus.bind(this);
     this.dispatchSave = this.dispatchSave.bind(this);
-    this.autoCompleteEmoji = this.autoCompleteEmoji.bind(this);
-
-    this.initEmojiImageMap()
   }
 
   componentDidMount() {
@@ -52,16 +49,6 @@ export default class Editor extends React.Component {
     codemirror.commands.save = this.dispatchSave;
   }
 
-  initEmojiImageMap() {
-    this.emojiShortnameImageMap = {};
-    for (let unicode in emojiStrategy) {
-      const data = emojiStrategy[unicode];
-      const shortname = data.shortname;
-      // add image tag
-      this.emojiShortnameImageMap[shortname] = emojione.shortnameToImage(shortname);
-    }
-  }
-
   getCodeMirror() {
     return this.refs.cm.editor;
   }
@@ -86,117 +73,6 @@ export default class Editor extends React.Component {
     editor.setCursor({line: line-1});   // leave 'ch' field as null/undefined to indicate the end of line
   }
 
-  /**
-   * try to find emoji terms and show hint
-   */
-  autoCompleteEmoji() {
-    const cm = this.getCodeMirror();
-
-    // see https://regex101.com/r/gy3i03/1
-    const pattern = /:[^:\s]+/
-
-    const currentPos = cm.getCursor();
-    // find previous ':shortname'
-    const sc = cm.getSearchCursor(pattern, currentPos, { multiline: false });
-    if (sc.findPrevious()) {
-      const isInputtingEmoji = (currentPos.line === sc.to().line && currentPos.ch === sc.to().ch);
-      // return if it isn't inputting emoji
-      if (!isInputtingEmoji) {
-        return;
-      }
-    }
-    else {
-      return;
-    }
-
-    // see https://codemirror.net/doc/manual.html#addon_show-hint
-    cm.showHint({
-      completeSingle: false,
-      // closeOnUnfocus: false,  // for debug
-      hint: () => {
-        const matched = cm.getDoc().getRange(sc.from(), sc.to());
-        const term = matched.replace(':', '');  // remove ':' in the head
-
-        // get a list of shortnames
-        const shortnames = this.searchEmojiShortnames(term);
-        if (shortnames.length >= 1) {
-          return {
-            list: this.generateEmojiRenderer(shortnames),
-            from: sc.from(),
-            to: sc.to(),
-          };
-        }
-      },
-    });
-  }
-
-  /**
-   * see https://codemirror.net/doc/manual.html#addon_show-hint
-   * @param {any}
-   */
-  generateEmojiRenderer(emojiShortnames) {
-    return emojiShortnames.map((shortname) => {
-      return {
-        text: shortname,
-        className: 'crowi-emoji-autocomplete',
-        render: (element) => {
-          element.innerHTML =
-            `<div class="img-container">${this.emojiShortnameImageMap[shortname]}</div>` +
-            `<span class="shortname-container">${shortname}</span>`;
-        }
-      }
-    });
-  }
-
-  /**
-   * transplanted from https://github.com/emojione/emojione/blob/master/examples/OTHER.md
-   * @param {string} term
-   * @returns {string[]} a list of shortname
-   */
-  searchEmojiShortnames(term) {
-    const maxLength = 12;
-
-    let results1 = [], results2 = [], results3 = [], results4 = [];
-    const countLen1 = () => { results1.length; }
-    const countLen2 = () => { countLen1() + results2.length; }
-    const countLen3 = () => { countLen2() + results3.length; }
-    const countLen4 = () => { countLen3() + results4.length; }
-    // TODO performance tune
-    // when total length of all results is less than `maxLength`
-    for (let unicode in emojiStrategy) {
-      const data = emojiStrategy[unicode];
-
-      if (maxLength <= countLen1()) { break; }
-      // prefix match to shortname
-      else if (data.shortname.indexOf(`:${term}`) > -1) {
-        results1.push(data.shortname);
-        continue;
-      }
-      else if (maxLength <= countLen2()) { continue; }
-      // partial match to shortname
-      else if (data.shortname.indexOf(term) > -1) {
-        results2.push(data.shortname);
-        continue;
-      }
-      else if (maxLength <= countLen3()) { continue; }
-      // partial match to elements of aliases
-      else if ((data.aliases != null) && data.aliases.find(elem => elem.indexOf(term) > -1)) {
-        results3.push(data.shortname);
-        continue;
-      }
-      else if (maxLength <= countLen4()) { continue; }
-      // partial match to elements of keywords
-      else if ((data.keywords != null) && data.keywords.find(elem => elem.indexOf(term) > -1)) {
-        results4.push(data.shortname);
-      }
-    };
-
-    let results = results1.concat(results2).concat(results3).concat(results4);
-    results = results.slice(0, maxLength);
-
-    return results;
-  }
-
   /**
    * dispatch onSave event
    */
@@ -247,7 +123,7 @@ export default class Editor extends React.Component {
           }
 
           // Emoji AutoComplete
-          this.autoCompleteEmoji(editor);
+          emojiAutoCompleteHelper.showHint(editor);
         }}
       />
     )

+ 138 - 0
resource/js/components/PageEditor/EmojiAutoCompleteHelper.js

@@ -0,0 +1,138 @@
+import emojione from 'emojione';
+import emojiStrategy from 'emojione/emoji_strategy.json';
+
+class EmojiAutoCompleteHelper {
+
+  constructor() {
+    this.initEmojiImageMap = this.initEmojiImageMap.bind(this);
+    this.showHint = this.showHint.bind(this);
+
+    this.initEmojiImageMap()
+  }
+
+  initEmojiImageMap() {
+    this.emojiShortnameImageMap = {};
+    for (let unicode in emojiStrategy) {
+      const data = emojiStrategy[unicode];
+      const shortname = data.shortname;
+      // add image tag
+      this.emojiShortnameImageMap[shortname] = emojione.shortnameToImage(shortname);
+    }
+  }
+
+  /**
+   * try to find emoji terms and show hint
+   * @param {any} editor An editor instance of CodeMirror
+   */
+  showHint(editor) {
+    // see https://regex101.com/r/gy3i03/1
+    const pattern = /:[^:\s]+/
+
+    const currentPos = editor.getCursor();
+    // find previous ':shortname'
+    const sc = editor.getSearchCursor(pattern, currentPos, { multiline: false });
+    if (sc.findPrevious()) {
+      const isInputtingEmoji = (currentPos.line === sc.to().line && currentPos.ch === sc.to().ch);
+      // return if it isn't inputting emoji
+      if (!isInputtingEmoji) {
+        return;
+      }
+    }
+    else {
+      return;
+    }
+
+    // see https://codemirror.net/doc/manual.html#addon_show-hint
+    editor.showHint({
+      completeSingle: false,
+      // closeOnUnfocus: false,  // for debug
+      hint: () => {
+        const matched = editor.getDoc().getRange(sc.from(), sc.to());
+        const term = matched.replace(':', '');  // remove ':' in the head
+
+        // get a list of shortnames
+        const shortnames = this.searchEmojiShortnames(term);
+        if (shortnames.length >= 1) {
+          return {
+            list: this.generateEmojiRenderer(shortnames),
+            from: sc.from(),
+            to: sc.to(),
+          };
+        }
+      },
+    });
+  }
+
+  /**
+   * see https://codemirror.net/doc/manual.html#addon_show-hint
+   * @param {string[]} emojiShortnames a list of shortname
+   */
+  generateEmojiRenderer(emojiShortnames) {
+    return emojiShortnames.map((shortname) => {
+      return {
+        text: shortname,
+        className: 'crowi-emoji-autocomplete',
+        render: (element) => {
+          element.innerHTML =
+            `<div class="img-container">${this.emojiShortnameImageMap[shortname]}</div>` +
+            `<span class="shortname-container">${shortname}</span>`;
+        }
+      }
+    });
+  }
+
+  /**
+   * transplanted from https://github.com/emojione/emojione/blob/master/examples/OTHER.md
+   * @param {string} term
+   * @returns {string[]} a list of shortname
+   */
+  searchEmojiShortnames(term) {
+    const maxLength = 12;
+
+    let results1 = [], results2 = [], results3 = [], results4 = [];
+    const countLen1 = () => { results1.length; }
+    const countLen2 = () => { countLen1() + results2.length; }
+    const countLen3 = () => { countLen2() + results3.length; }
+    const countLen4 = () => { countLen3() + results4.length; }
+    // TODO performance tune
+    // when total length of all results is less than `maxLength`
+    for (let unicode in emojiStrategy) {
+      const data = emojiStrategy[unicode];
+
+      if (maxLength <= countLen1()) { break; }
+      // prefix match to shortname
+      else if (data.shortname.indexOf(`:${term}`) > -1) {
+        results1.push(data.shortname);
+        continue;
+      }
+      else if (maxLength <= countLen2()) { continue; }
+      // partial match to shortname
+      else if (data.shortname.indexOf(term) > -1) {
+        results2.push(data.shortname);
+        continue;
+      }
+      else if (maxLength <= countLen3()) { continue; }
+      // partial match to elements of aliases
+      else if ((data.aliases != null) && data.aliases.find(elem => elem.indexOf(term) > -1)) {
+        results3.push(data.shortname);
+        continue;
+      }
+      else if (maxLength <= countLen4()) { continue; }
+      // partial match to elements of keywords
+      else if ((data.keywords != null) && data.keywords.find(elem => elem.indexOf(term) > -1)) {
+        results4.push(data.shortname);
+      }
+    };
+
+    let results = results1.concat(results2).concat(results3).concat(results4);
+    results = results.slice(0, maxLength);
+
+    return results;
+  }
+
+}
+
+// singleton pattern
+const instance = new EmojiAutoCompleteHelper();
+Object.freeze(instance);
+export default instance;