From 0273795f5db86c416a2e40978c5b853a3fae26c8 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Mon, 26 Aug 2019 16:10:26 +0200
Subject: [PATCH] add transform step to composer to auto-replace emoticons with
 emoji

---
 .../views/rooms/BasicMessageComposer.js       | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js
index e4179d9c3b..780f39f9e7 100644
--- a/src/components/views/rooms/BasicMessageComposer.js
+++ b/src/components/views/rooms/BasicMessageComposer.js
@@ -25,6 +25,11 @@ import {autoCompleteCreator} from '../../../editor/parts';
 import {renderModel} from '../../../editor/render';
 import {Room} from 'matrix-js-sdk';
 import TypingStore from "../../../stores/TypingStore";
+import EMOJIBASE from 'emojibase-data/en/compact.json';
+import SettingsStore from "../../../settings/SettingsStore";
+import EMOTICON_REGEX from 'emojibase-regex/emoticon';
+
+const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
 
 const IS_MAC = navigator.platform.indexOf("Mac") !== -1;
 
@@ -70,6 +75,28 @@ export default class BasicMessageEditor extends React.Component {
         this._modifiedFlag = false;
     }
 
+    _replaceEmoticon = (caret, inputType, diff) => {
+        const {model} = this.props;
+        const range = model.startRange(caret);
+        // expand range max 8 characters backwards from caret
+        let n = 8;
+        range.expandBackwardsWhile((index, offset) => {
+            const part = model.parts[index];
+            n -= 1;
+            return n >= 0 && (part.type === "plain" || part.type === "pill-candidate");
+        });
+        const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
+        if (emoticonMatch) {
+            const query = emoticonMatch[1].toLowerCase().replace("-", "");
+            const data = EMOJIBASE.find(e => e.emoticon ? e.emoticon.toLowerCase() === query : false);
+            if (data) {
+                // + 1 because index is reported without preceding space
+                range.moveStart(emoticonMatch.index + 1);
+                return range.replace([this.props.model.partCreator.plain(data.unicode + " ")]);
+            }
+        }
+    }
+
     _updateEditorState = (caret, inputType, diff) => {
         renderModel(this._editorRef, this.props.model);
         if (caret) {
@@ -262,6 +289,9 @@ export default class BasicMessageEditor extends React.Component {
     componentDidMount() {
         const model = this.props.model;
         model.setUpdateCallback(this._updateEditorState);
+        if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
+            model.setTransformCallback(this._replaceEmoticon);
+        }
         const partCreator = model.partCreator;
         // TODO: does this allow us to get rid of EditorStateTransfer?
         // not really, but we could not serialize the parts, and just change the autoCompleter