Просмотр исходного кода

impl Emoji AutoComplete with emojione

Yuki Takei 8 лет назад
Родитель
Сommit
447c21e631
1 измененных файлов с 77 добавлено и 15 удалено
  1. 77 15
      resource/js/components/PageEditor/Editor.js

+ 77 - 15
resource/js/components/PageEditor/Editor.js

@@ -1,6 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+import emojione from 'emojione';
+import emojiStrategy from 'emojione/emoji_strategy.json';
 import * as codemirror from 'codemirror';
 
 import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
@@ -26,11 +28,14 @@ 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.emojiComplete = this.emojiComplete.bind(this);
+    this.autoCompleteEmoji = this.autoCompleteEmoji.bind(this);
+
+    this.initEmojiImageMap()
   }
 
   componentDidMount() {
@@ -40,6 +45,16 @@ 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;
   }
@@ -64,18 +79,13 @@ export default class Editor extends React.Component {
     editor.setCursor({line: line-1});   // leave 'ch' field as null/undefined to indicate the end of line
   }
 
-  // sample data
-  getEmojiList() {
-    return ['apple:', 'abc:', 'axz:', 'bee:', 'beam:', 'bleach:']
-  }
-
-  emojiComplete() {
+  autoCompleteEmoji() {
     const cm = this.getCodeMirror();
 
+    // see https://codemirror.net/doc/manual.html#addon_show-hint
     cm.showHint({
       completeSingle: false,
       hint: () => {
-        const emojiList = this.getEmojiList();
         let cur = cm.getCursor(), token = cm.getTokenAt(cur);
         let start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
         let ch = cur.ch, line = cur.line;
@@ -83,13 +93,11 @@ export default class Editor extends React.Component {
         while (ch-- > -1) {
           let t = cm.getTokenAt({ch, line}).string;
           if (t === ':') {
-            let filteredList = emojiList.filter((item) => {
-              return item.indexOf(currentWord) == 0 ? true : false
-            });
-            if (filteredList.length >= 1) {
+            const shortnames = this.searchEmojiShortnames(currentWord);
+            if (shortnames.length >= 1) {
               return {
-                list: filteredList,
-                from: codemirror.Pos(line, ch),
+                list: this.generateEmojiRenderer(shortnames),
+                from: codemirror.Pos(line, ch-1),
                 to: codemirror.Pos(line, end)
               };
             }
@@ -100,6 +108,58 @@ export default class Editor extends React.Component {
     });
   }
 
+  /**
+   * see https://codemirror.net/doc/manual.html#addon_show-hint
+   * @param {any}
+   */
+  generateEmojiRenderer(emojiShortnames) {
+    return emojiShortnames.map((shortname) => {
+      return {
+        text: shortname,
+        render: (element) => {
+          element.innerHTML = `${this.emojiShortnameImageMap[shortname]} ${shortname}`;
+        }
+      }
+    });
+  }
+
+  /**
+   * transplanted from https://github.com/emojione/emojione/blob/master/examples/OTHER.md
+   * @param {string} term
+   * @returns {string[]} a list of shortname
+   */
+  searchEmojiShortnames(term) {
+    var results = [];
+    var results2 = [];
+    var results3 = [];
+    for (let unicode in emojiStrategy) {
+      const data = emojiStrategy[unicode];
+      if (data.shortname.indexOf(term) > -1) {
+        results.push(data.shortname);
+      }
+      else {
+        if((data.aliases != null) && (data.aliases.indexOf(term) > -1)) {
+          results2.push(data.shortname);
+        }
+        else if ((data.keywords != null) && (data.keywords.indexOf(term) > -1)) {
+          results3.push(data.shortname);
+        }
+      }
+    };
+
+    if (term.length >= 3) {
+        results.sort(function(a,b) { return (a.length > b.length); });
+        results2.sort(function(a,b) { return (a.length > b.length); });
+        results3.sort();
+    }
+    var newResults = results.concat(results2).concat(results3);
+
+    // limit 10
+    newResults = newResults.slice(0, 10);
+
+    return newResults;
+  }
+
   /**
    * dispatch onSave event
    */
@@ -142,10 +202,12 @@ export default class Editor extends React.Component {
           }
         }}
         onChange={(editor, data, value) => {
-          this.emojiComplete(editor);
           if (this.props.onChange != null) {
             this.props.onChange(value);
           }
+
+          // Emoji AutoComplete
+          this.autoCompleteEmoji(editor);
         }}
       />
     )