[evolution/wip/mcrha/webkit-jsc-api] Implement "Magic smileys"



commit fd53cf02728efe024c87b3d5bba8346c86f34ed3
Author: Milan Crha <mcrha redhat com>
Date:   Fri Jan 31 14:21:40 2020 +0100

    Implement "Magic smileys"

 data/webkit/e-editor.js                            | 222 ++++++++++++++++++---
 .../web-extension/e-editor-web-extension.c         |  64 ++++++
 2 files changed, 254 insertions(+), 32 deletions(-)
---
diff --git a/data/webkit/e-editor.js b/data/webkit/e-editor.js
index f278477e9a..fc0badc89d 100644
--- a/data/webkit/e-editor.js
+++ b/data/webkit/e-editor.js
@@ -2243,6 +2243,119 @@ EvoEditor.UpdateThemeStyleSheet = function(css)
        return EvoEditor.UpdateStyleSheet("x-evo-theme-sheet", css);
 }
 
+EvoEditor.findSmileys = function(text, unicodeSmileys)
+{
+       /* Based on original use_pictograms() from GtkHTML */
+       var emoticons_chars = [
+               /*  0 */  "D",  "O",  ")",  "(",  "|",  "/",  "P",  "Q",  "*",  "!",
+               /* 10 */  "S", null,  ":",  "-", null,  ":", null,  ":",  "-",  null,
+               /* 20 */  ":", null,  ":",  ";",  "=",  "-", "\"", null,  ":",  ";",
+               /* 30 */  "B", "\"",  "|", null,  ":",  "-",  "'", null,  ":",  "X",
+               /* 40 */ null,  ":", null,  ":",  "-", null,  ":", null,  ":",  "-",
+               /* 50 */ null,  ":", null,  ":",  "-", null,  ":", null,  ":",  "-",
+               /* 60 */ null,  ":", null,  ":", null,  ":",  "-", null,  ":", null,
+               /* 70 */  ":",  "-", null,  ":", null,  ":",  "-", null,  ":", null ];
+       var emoticons_states = [
+               /*  0 */  12,  17,  22,  34,  43,  48,  53,  58,  65,  70,
+               /* 10 */  75,   0, -15,  15,   0, -15,   0, -17,  20,   0,
+               /* 20 */ -17,   0, -14, -20, -14,  28,  63,   0, -14, -20,
+               /* 30 */  -3,  63, -18,   0, -12,  38,  41,   0, -12,  -2,
+               /* 40 */   0,  -4,   0, -10,  46,   0, -10,   0, -19,  51,
+               /* 50 */   0, -19,   0, -11,  56,   0, -11,   0, -13,  61,
+               /* 60 */   0, -13,   0,  -6,   0,  68,  -7,   0,  -7,   0,
+               /* 70 */ -16,  73,   0, -16,   0, -21,  78,   0, -21,   0 ];
+       var emoticons_icon_names = [
+               "face-angel",
+               "face-angry",
+               "face-cool",
+               "face-crying",
+               "face-devilish",
+               "face-embarrassed",
+               "face-kiss",
+               "face-laugh",           /* not used */
+               "face-monkey",          /* not used */
+               "face-plain",
+               "face-raspberry",
+               "face-sad",
+               "face-sick",
+               "face-smile",
+               "face-smile-big",
+               "face-smirk",
+               "face-surprise",
+               "face-tired",
+               "face-uncertain",
+               "face-wink",
+               "face-worried"
+       ];
+       var res = null, pos, state, start, uc;
+
+       start = text.length - 1;
+
+       if (start < 1)
+               return res;
+
+       pos = start;
+       while (pos >= 0) {
+               state = 0;
+               while (pos >= 0) {
+                       uc = text[pos];
+                       var relative = 0;
+                       while (emoticons_chars[state + relative] != null) {
+                               if (emoticons_chars[state + relative] == uc) {
+                                       break;
+                               }
+                               relative++;
+                       }
+                       state = emoticons_states[state + relative];
+                       /* 0 .. not found, -n .. found n-th */
+                       if (state <= 0)
+                               break;
+                       pos--;
+               }
+
+               /* Special case needed to recognize angel and devilish. */
+               if (pos > 0 && state == -14) {
+                       uc = text[pos - 1];
+                       if (uc == 'O') {
+                               state = -1;
+                               pos--;
+                       } else if (uc == '>') {
+                               state = -5;
+                               pos--;
+                       }
+               }
+
+               if (state < 0) {
+                       if (pos > 0) {
+                               uc = text[pos - 1];
+
+                               if (uc != ' ') {
+                                       return res;
+                               }
+                       }
+
+                       var obj = EvoEditor.lookupEmoticon(emoticons_icon_names[- state - 1], unicodeSmileys);
+
+                       if (obj) {
+                               obj.start = pos;
+                               obj.end = start + 1;
+
+                               if (!res)
+                                       res = [];
+
+                               res[res.length] = obj;
+                       }
+
+                       pos--;
+                       start = pos;
+               } else {
+                       break;
+               }
+       }
+
+       return res;
+}
+
 EvoEditor.AfterInputEvent = function(inputEvent, isWordDelim)
 {
        var isInsertParagraph = inputEvent.inputType == "insertParagraph";
@@ -2313,6 +2426,34 @@ EvoEditor.AfterInputEvent = function(inputEvent, isWordDelim)
 
        var text = baseNode.nodeValue, covered = false;
 
+       var replaceMatchWithNode = function(opType, match, newNode, canEmit) {
+               EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType, baseNode.parentElement, 
baseNode.parentElement,
+                       EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML);
+
+               try {
+                       var offset = selection.baseOffset, updateSelection = selection.baseNode === baseNode, 
newBaseNode;
+
+                       baseNode.splitText(match.end);
+                       newBaseNode = baseNode.nextSibling;
+                       baseNode.splitText(match.start);
+
+                       baseNode = baseNode.nextSibling;
+
+                       baseNode.parentElement.insertBefore(newNode, baseNode);
+                       baseNode.parentElement.removeChild(baseNode);
+
+                       if (updateSelection && newBaseNode && offset - match.end >= 0)
+                               selection.setPosition(newBaseNode, offset - match.end);
+               } finally {
+                       EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType);
+
+                       if (canEmit) {
+                               EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE);
+                               EvoEditor.EmitContentChanged();
+                       }
+               }
+       }
+
        if (canLinks) {
                var isEmail = text.search("@") >= 0, match;
 
@@ -2371,44 +2512,58 @@ EvoEditor.AfterInputEvent = function(inputEvent, isWordDelim)
                        }
 
                        if (url.length > 0) {
-                               EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "magicLink", 
baseNode.parentElement, baseNode.parentElement,
-                                       EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML);
+                               covered = true;
 
-                               try {
-                                       var offset = selection.baseOffset, updateSelection = 
selection.baseNode === baseNode, newBaseNode;
+                               if (isEmail)
+                                       url = "mailto:"; + url;
+                               else if (url.startsWith("www."))
+                                       url = "https://"; + url;
+
+                               node = document.createElement("A");
+                               node.href = url;
+
+                               replaceMatchWithNode("magicLink", match, node, true);
+                       }
+               }
+       }
 
-                                       covered = true;
+       if (!covered && EvoEditor.MAGIC_SMILEYS) {
+               var matches;
 
-                                       baseNode.splitText(match.end);
-                                       newBaseNode = baseNode.nextSibling;
-                                       baseNode.splitText(match.start);
+               // the replace call below replaces &nbsp; (0xA0) with regular space
+               matches = EvoEditor.findSmileys(text.replace(/ /g, " "), EvoEditor.UNICODE_SMILEYS);
+               if (matches) {
+                       var sz = matches.length, node, tmpElement = null;
 
-                                       baseNode = baseNode.nextSibling;
+                       if (sz > 1)
+                               EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "magicSmiley");
 
-                                       if (isEmail)
-                                               url = "mailto:"; + url;
-                                       else if (url.startsWith("www."))
-                                               url = "http://"; + url;
+                       try {
+                               // they are ordered from the end already
+                               for (ii = 0; ii < sz; ii++) {
+                                       var match = matches[ii];
 
-                                       node = document.createElement("A");
-                                       node.href = url;
+                                       if (!match.imageUri || EvoEditor.UNICODE_SMILEYS || EvoEditor.mode != 
EvoEditor.MODE_HTML) {
+                                               node = document.createTextNode(match.text);
+                                       } else {
+                                               if (!tmpElement)
+                                                       tmpElement = document.createElement("SPAN");
 
-                                       baseNode.parentElement.insertBefore(node, baseNode);
-                                       node.appendChild(baseNode);
+                                               tmpElement.innerHTML = 
EvoEditor.createEmoticonHTML(match.text, match.imageUri, match.width, match.height);
+                                               node = tmpElement.firstChild;
+                                       }
 
-                                       if (updateSelection && newBaseNode && offset - match.end >= 0)
-                                               selection.setPosition(newBaseNode, offset - match.end);
-                               } finally {
-                                       EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "magicLink");
+                                       replaceMatchWithNode("magicSmiley", match, node, sz == 1);
+                               }
+                       } finally {
+                               if (sz > 1) {
+                                       EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "magicSmiley");
                                        EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE);
                                        EvoEditor.EmitContentChanged();
                                }
                        }
                }
        }
-
-       if (!covered && EvoEditor.MAGIC_SMILEYS) {
-       }
 }
 
 EvoEditor.getParentElement = function(tagName, fromNode, canClimbUp)
@@ -3515,16 +3670,19 @@ EvoEditor.MoveSelectionToPoint = function(xx, yy, cancel_if_not_collapsed)
        }
 }
 
+EvoEditor.createEmoticonHTML = function(text, imageUri, width, height)
+{
+       if (imageUri && EvoEditor.mode == EvoEditor.MODE_HTML && !EvoEditor.UNICODE_SMILEYS)
+               return "<img src=\"" + imageUri + "\" alt=\"" +
+                       text.replace(/\&/g, "&amp;").replace(/\"/g, "&quot;").replace(/\'/g, "&apos;") +
+                       "\" width=\"" + width + "px\" height=\"" + height + "px\">";
+
+       return text;
+}
+
 EvoEditor.InsertEmoticon = function(text, imageUri, width, height)
 {
-       if (imageUri) {
-               EvoEditor.InsertHTML("InsertEmoticon",
-                       "<img src=\"" + imageUri + "\" alt=\"" +
-                               text.replace(/\&/g, "&amp;").replace(/\"/g, "&quot;").replace(/\'/g, 
"&apos;") +
-                       "\" width=\"" + width + "px\" height=\"" + height + "px\">");
-       } else {
-               EvoEditor.InsertHTML("InsertEmoticon", text);
-       }
+       EvoEditor.InsertHTML("InsertEmoticon", EvoEditor.createEmoticonHTML(text, imageUri, width, height));
 }
 
 EvoEditor.GetCurrentSignatureUid = function()
diff --git a/src/modules/webkit-editor/web-extension/e-editor-web-extension.c 
b/src/modules/webkit-editor/web-extension/e-editor-web-extension.c
index 560a00d24c..2def8ee041 100644
--- a/src/modules/webkit-editor/web-extension/e-editor-web-extension.c
+++ b/src/modules/webkit-editor/web-extension/e-editor-web-extension.c
@@ -215,6 +215,60 @@ evo_editor_jsc_find_pattern (const gchar *text,
        return object ? object : jsc_value_new_null (jsc_context);
 }
 
+/* Returns 'null' or an object { text : string, imageUri : string, width : nnn, height : nnn }
+   where only the 'text' is required, describing an emoticon. */
+static JSCValue *
+evo_editor_jsc_lookup_emoticon (const gchar *iconName,
+                               gboolean use_unicode_smileys,
+                               JSCContext *jsc_context)
+{
+       JSCValue *object = NULL;
+
+       if (iconName && *iconName) {
+               const EEmoticon *emoticon;
+
+               emoticon = e_emoticon_chooser_lookup_emoticon (iconName);
+
+               if (emoticon) {
+                       JSCValue *value;
+
+                       object = jsc_value_new_object (jsc_context, NULL, NULL);
+
+                       if (use_unicode_smileys) {
+                               value = jsc_value_new_string (jsc_context, emoticon->unicode_character);
+                               jsc_value_object_set_property (object, "text", value);
+                               g_clear_object (&value);
+                       } else {
+                               gchar *image_uri;
+
+                               value = jsc_value_new_string (jsc_context, emoticon->text_face);
+                               jsc_value_object_set_property (object, "text", value);
+                               g_clear_object (&value);
+
+                               image_uri = e_emoticon_get_uri ((EEmoticon *) emoticon);
+
+                               if (image_uri) {
+                                       value = jsc_value_new_string (jsc_context, image_uri);
+                                       jsc_value_object_set_property (object, "imageUri", value);
+                                       g_clear_object (&value);
+
+                                       value = jsc_value_new_number (jsc_context, 16);
+                                       jsc_value_object_set_property (object, "width", value);
+                                       g_clear_object (&value);
+
+                                       value = jsc_value_new_number (jsc_context, 16);
+                                       jsc_value_object_set_property (object, "height", value);
+                                       g_clear_object (&value);
+
+                                       g_free (image_uri);
+                               }
+                       }
+               }
+       }
+
+       return object ? object : jsc_value_new_null (jsc_context);
+}
+
 static void
 evo_editor_jsc_set_spell_check_languages (const gchar *langs,
                                          GWeakRef *wkrf_extension)
@@ -312,6 +366,16 @@ window_object_cleared_cb (WebKitScriptWorld *world,
 
                g_clear_object (&jsc_function);
 
+               /* EvoEditor.lookupEmoticon(iconName, useUnicodeSmileys) */
+               func_name = "lookupEmoticon";
+               jsc_function = jsc_value_new_function (jsc_context, func_name,
+                       G_CALLBACK (evo_editor_jsc_lookup_emoticon), g_object_ref (jsc_context), 
g_object_unref,
+                       JSC_TYPE_VALUE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+               jsc_value_object_set_property (jsc_editor, func_name, jsc_function);
+
+               g_clear_object (&jsc_function);
+
                /* EvoEditor.SetSpellCheckLanguages(langs) */
                func_name = "SetSpellCheckLanguages";
                jsc_function = jsc_value_new_function (jsc_context, func_name,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]