[evolution/wip/mcrha/webkit-jsc-api] Partially implement magic-links



commit cfe018f8a6496452e6e3ca2439bd356df06147d5
Author: Milan Crha <mcrha redhat com>
Date:   Wed Dec 4 19:06:17 2019 +0100

    Partially implement magic-links

 data/webkit/e-editor.js                            | 112 +++++++++++++
 data/webkit/e-selection.js                         |   4 +-
 data/webkit/e-undo-redo.js                         |   7 +-
 src/modules/webkit-editor/e-webkit-editor.c        | 177 ++++++++++++++++++++-
 .../web-extension/e-editor-web-extension.c         |  58 +++++++
 5 files changed, 352 insertions(+), 6 deletions(-)
---
diff --git a/data/webkit/e-editor.js b/data/webkit/e-editor.js
index d207486ba1..d1438788bd 100644
--- a/data/webkit/e-editor.js
+++ b/data/webkit/e-editor.js
@@ -21,6 +21,14 @@
    public functions start with upper-case letter. */
 
 var EvoEditor = {
+       // stephenhay from https://mathiasbynens.be/demo/url-regex
+       URL_PATTERN : "((?:(?:(?:" + "news|telnet|nntp|file|https?|s?ftp|webcal|localhost|ssh" + 
")\\:\\/\\/)|(?:www\\.|ftp\\.))[^\\s\\/\\$\\.\\?#].[^\\s]*+)",
+       // from camel-url-scanner.c
+       URL_INVALID_TRAILING_CHARS : ",.:;?!-|}])\">",
+       // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address
+       EMAIL_PATTERN : "[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}" +
+                       "[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*+",
+
        E_CONTENT_EDITOR_ALIGNMENT_NONE : -1,
        E_CONTENT_EDITOR_ALIGNMENT_LEFT : 0,
        E_CONTENT_EDITOR_ALIGNMENT_CENTER : 1,
@@ -57,6 +65,9 @@ var EvoEditor = {
 
        TEXT_INDENT_SIZE : 3, // in characters
        NORMAL_PARAGRAPH_WIDTH : 71,
+       MAGIC_LINKS : true,
+       MAGIC_SMILEYS : false,
+       UNICODE_SMILEYS : false,
 
        FORCE_NO : 0,
        FORCE_YES : 1,
@@ -2066,6 +2077,107 @@ EvoEditor.UpdateThemeStyleSheet = function(css)
        return EvoEditor.UpdateStyleSheet("x-evo-theme-sheet", css);
 }
 
+EvoEditor.MaybeReplaceTextAfterInput = function(inputEvent, isWordDelim)
+{
+       var isInsertParagraph = inputEvent.inputType == "insertParagraph";
+
+       if ((!isInsertParagraph && inputEvent.inputType != "insertText") ||
+           (!(EvoEditor.MAGIC_LINKS && (isWordDelim || isInsertParagraph)) &&
+           !EvoEditor.MAGIC_SMILEYS)) {
+               return;
+       }
+
+       var selection = document.getSelection();
+
+       if (!selection.isCollapsed || !selection.baseNode)
+               return;
+
+       var baseNode = selection.baseNode, parentElem;
+
+       if (baseNode.nodeType != baseNode.ELEMENT_NODE) {
+               parentElem = baseNode.parentElement;
+
+               if (!parentElem)
+                       return;
+       } else {
+               parentElem = baseNode;
+       }
+
+       if (isInsertParagraph) {
+               parentElem = parentElem.previousElementSibling;
+
+               if (!parentElem)
+                       return;
+
+               baseNode = parentElem.lastChild;
+
+               if (!baseNode || baseNode.nodeType != baseNode.TEXT_NODE)
+                       return;
+       }
+
+       if (baseNode.nodeValue == "")
+               return;
+
+       var canLinks;
+
+       canLinks = EvoEditor.MAGIC_LINKS && (isWordDelim || isInsertParagraph);
+
+       if (canLinks) {
+               var tmpNode;
+
+               for (tmpNode = baseNode; tmpNode && tmpNode.tagName != "BODY"; tmpNode = 
tmpNode.parentElement) {
+                       if (tmpNode.tagName == "A") {
+                               canLinks = false;
+                               break;
+                       }
+               }
+       }
+
+       var text = baseNode.nodeValue, selectionUpdater, covered = false;
+
+       selectionUpdater = EvoSelection.CreateUpdaterObject();
+
+       if (canLinks) {
+               var isEmail = text.search("@") >= 0, match;
+
+               // the replace call below replaces &nbsp; (0xA0) with regular space
+               match = EvoEditor.findPattern(text.replace(/ /g, " "), isEmail ? EvoEditor.EMAIL_PATTERN : 
EvoEditor.URL_PATTERN);
+               if (match) {
+                       var url = text.substring(match.start, match.end), node;
+
+                       EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "magicLink", 
baseNode.parentElement, baseNode.parentElement,
+                               EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML);
+
+                       try {
+                               covered = true;
+
+                               baseNode.splitText(match.end);
+                               baseNode.splitText(match.start);
+
+                               baseNode = baseNode.nextSibling;
+
+                               if (isEmail)
+                                       url = "mailto:"; + url;
+                               else if (url.startsWith("www."))
+                                       url = "http://"; + url;
+
+                               node = document.createElement("A");
+                               node.href = url;
+
+                               baseNode.parentElement.insertBefore(node, baseNode);
+                               node.appendChild(baseNode);
+                       } finally {
+                               EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "magicLink");
+                       }
+               }
+       }
+
+       if (!covered && EvoEditor.MAGIC_SMILEYS) {
+       }
+
+       selectionUpdater.restore();
+}
+
 document.onload = EvoEditor.initializeContent;
 
 document.onselectionchange = function() {
diff --git a/data/webkit/e-selection.js b/data/webkit/e-selection.js
index 5513e63f0c..bb880a11c9 100644
--- a/data/webkit/e-selection.js
+++ b/data/webkit/e-selection.js
@@ -382,11 +382,11 @@ EvoSelection.CreateUpdaterObject = function()
 
        obj.selectionBefore = EvoSelection.Store(document);
        obj.selectionBaseNode = document.getSelection().baseNode;
-       obj.selectionBaseOffset = document.getSelection().baseOffset;
+       obj.selectionBaseOffset = document.getSelection().baseOffset + 
EvoSelection.GetOverallTextOffset(obj.selectionBaseNode);
 
        if (!document.getSelection().isCollapsed) {
                obj.selectionExtentNode = document.getSelection().extentNode;
-               obj.selectionExtentOffset = document.getSelection().extentOffset;
+               obj.selectionExtentOffset = document.getSelection().extentOffset + 
EvoSelection.GetOverallTextOffset(obj.selectionExtentNode);
        }
 
        return obj;
diff --git a/data/webkit/e-undo-redo.js b/data/webkit/e-undo-redo.js
index 3f22f36d61..1024d419d6 100644
--- a/data/webkit/e-undo-redo.js
+++ b/data/webkit/e-undo-redo.js
@@ -452,14 +452,17 @@ EvoUndoRedo.before_input_cb = function(inputEvent)
 
 EvoUndoRedo.input_cb = function(inputEvent)
 {
+       var isWordDelim = EvoUndoRedo.isWordDelimEvent(inputEvent);
+
        if (EvoUndoRedo.disabled) {
                EvoEditor.EmitContentChanged();
+               EvoEditor.MaybeReplaceTextAfterInput(inputEvent, isWordDelim);
                return;
        }
 
        var opType = inputEvent.inputType;
 
-       if (EvoUndoRedo.isWordDelimEvent(inputEvent))
+       if (isWordDelim)
                opType += "::WordDelim";
 
        if (EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_EVENT, opType)) {
@@ -477,6 +480,8 @@ EvoUndoRedo.input_cb = function(inputEvent)
 
        if (opType == "insertFromDrop")
                EvoUndoRedo.dropTarget = null;
+
+       EvoEditor.MaybeReplaceTextAfterInput(inputEvent, isWordDelim);
 }
 
 EvoUndoRedo.drop_cb = function(event)
diff --git a/src/modules/webkit-editor/e-webkit-editor.c b/src/modules/webkit-editor/e-webkit-editor.c
index 4f9c5a4bb1..145d582e71 100644
--- a/src/modules/webkit-editor/e-webkit-editor.c
+++ b/src/modules/webkit-editor/e-webkit-editor.c
@@ -65,7 +65,10 @@ enum {
        PROP_SUPERSCRIPT,
        PROP_UNDERLINE,
 
-       PROP_NORMAL_PARAGRAPH_WIDTH
+       PROP_NORMAL_PARAGRAPH_WIDTH,
+       PROP_MAGIC_LINKS,
+       PROP_MAGIC_SMILEYS,
+       PROP_UNICODE_SMILEYS
 };
 
 struct _EWebKitEditorPrivate {
@@ -112,6 +115,9 @@ struct _EWebKitEditorPrivate {
 
        guint font_size;
        gint normal_paragraph_width;
+       gboolean magic_links;
+       gboolean magic_smileys;
+       gboolean unicode_smileys;
 
        EContentEditorBlockFormat block_format;
        EContentEditorAlignment alignment;
@@ -5145,6 +5151,81 @@ webkit_editor_get_normal_paragraph_width (EWebKitEditor *wk_editor)
        return wk_editor->priv->normal_paragraph_width;
 }
 
+static void
+webkit_editor_set_magic_links (EWebKitEditor *wk_editor,
+                              gboolean value)
+{
+       g_return_if_fail (E_IS_WEBKIT_EDITOR (wk_editor));
+
+       if ((wk_editor->priv->magic_links ? 1 : 0) != (value ? 1 : 0)) {
+               wk_editor->priv->magic_links = value;
+
+               e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (wk_editor), wk_editor->priv->cancellable,
+                       "EvoEditor.MAGIC_LINKS = %x;",
+                       value);
+
+               g_object_notify (G_OBJECT (wk_editor), "magic-links");
+       }
+}
+
+static gboolean
+webkit_editor_get_magic_links (EWebKitEditor *wk_editor)
+{
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), FALSE);
+
+       return wk_editor->priv->magic_links;
+}
+
+static void
+webkit_editor_set_magic_smileys (EWebKitEditor *wk_editor,
+                                gboolean value)
+{
+       g_return_if_fail (E_IS_WEBKIT_EDITOR (wk_editor));
+
+       if ((wk_editor->priv->magic_smileys ? 1 : 0) != (value ? 1 : 0)) {
+               wk_editor->priv->magic_smileys = value;
+
+               e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (wk_editor), wk_editor->priv->cancellable,
+                       "EvoEditor.MAGIC_SMILEYS = %x;",
+                       value);
+
+               g_object_notify (G_OBJECT (wk_editor), "magic-smileys");
+       }
+}
+
+static gboolean
+webkit_editor_get_magic_smileys (EWebKitEditor *wk_editor)
+{
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), FALSE);
+
+       return wk_editor->priv->magic_smileys;
+}
+
+static void
+webkit_editor_set_unicode_smileys (EWebKitEditor *wk_editor,
+                                  gboolean value)
+{
+       g_return_if_fail (E_IS_WEBKIT_EDITOR (wk_editor));
+
+       if ((wk_editor->priv->unicode_smileys ? 1 : 0) != (value ? 1 : 0)) {
+               wk_editor->priv->unicode_smileys = value;
+
+               e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (wk_editor), wk_editor->priv->cancellable,
+                       "EvoEditor.UNICODE_SMILEYS = %x;",
+                       value);
+
+               g_object_notify (G_OBJECT (wk_editor), "unicide-smileys");
+       }
+}
+
+static gboolean
+webkit_editor_get_unicode_smileys (EWebKitEditor *wk_editor)
+{
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), FALSE);
+
+       return wk_editor->priv->unicode_smileys;
+}
+
 static void
 e_webkit_editor_initialize_web_extensions_cb (WebKitWebContext *web_context,
                                              gpointer user_data)
@@ -5214,10 +5295,27 @@ webkit_editor_constructed (GObject *object)
        webkit_settings_set_enable_developer_extras (web_settings, e_util_get_webkit_developer_mode_enabled 
());
 
        settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
        g_settings_bind (
                settings, "composer-word-wrap-length",
                wk_editor, "normal-paragraph-width",
                G_SETTINGS_BIND_GET);
+
+       g_settings_bind (
+               settings, "composer-magic-links",
+               wk_editor, "magic-links",
+               G_SETTINGS_BIND_GET);
+
+       g_settings_bind (
+               settings, "composer-magic-smileys",
+               wk_editor, "magic-smileys",
+               G_SETTINGS_BIND_GET);
+
+       g_settings_bind (
+               settings, "composer-unicode-smileys",
+               wk_editor, "unicode-smileys",
+               G_SETTINGS_BIND_GET);
+
        g_object_unref (settings);
 
        e_webkit_editor_load_data (wk_editor, "");
@@ -5406,6 +5504,24 @@ webkit_editor_set_property (GObject *object,
                                g_value_get_int (value));
                        return;
 
+               case PROP_MAGIC_LINKS:
+                       webkit_editor_set_magic_links (
+                               E_WEBKIT_EDITOR (object),
+                               g_value_get_boolean (value));
+                       return;
+
+               case PROP_MAGIC_SMILEYS:
+                       webkit_editor_set_magic_smileys (
+                               E_WEBKIT_EDITOR (object),
+                               g_value_get_boolean (value));
+                       return;
+
+               case PROP_UNICODE_SMILEYS:
+                       webkit_editor_set_unicode_smileys (
+                               E_WEBKIT_EDITOR (object),
+                               g_value_get_boolean (value));
+                       return;
+
                case PROP_ALIGNMENT:
                        webkit_editor_set_alignment (
                                E_WEBKIT_EDITOR (object),
@@ -5584,6 +5700,21 @@ webkit_editor_get_property (GObject *object,
                                webkit_editor_get_normal_paragraph_width (E_WEBKIT_EDITOR (object)));
                        return;
 
+               case PROP_MAGIC_LINKS:
+                       g_value_set_boolean (value,
+                               webkit_editor_get_magic_links (E_WEBKIT_EDITOR (object)));
+                       return;
+
+               case PROP_MAGIC_SMILEYS:
+                       g_value_set_boolean (value,
+                               webkit_editor_get_magic_smileys (E_WEBKIT_EDITOR (object)));
+                       return;
+
+               case PROP_UNICODE_SMILEYS:
+                       g_value_set_boolean (value,
+                               webkit_editor_get_unicode_smileys (E_WEBKIT_EDITOR (object)));
+                       return;
+
                case PROP_ALIGNMENT:
                        g_value_set_enum (
                                value,
@@ -6452,7 +6583,43 @@ e_webkit_editor_class_init (EWebKitEditorClass *class)
                        NULL,
                        G_MININT32,
                        G_MAXINT32,
-                       71, /* Should be the same as e-editor.js:EvoEditor.NORMAL_PARAGRAPH_WIDTH */
+                       71, /* Should be the same as e-editor.js:EvoEditor.NORMAL_PARAGRAPH_WIDTH and in the 
init()*/
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_MAGIC_LINKS,
+               g_param_spec_boolean (
+                       "magic-links",
+                       NULL,
+                       NULL,
+                       TRUE, /* Should be the same as e-editor.js:EvoEditor.MAGIC_LINKS and in the init() */
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_MAGIC_SMILEYS,
+               g_param_spec_boolean (
+                       "magic-smileys",
+                       NULL,
+                       NULL,
+                       FALSE, /* Should be the same as e-editor.js:EvoEditor.MAGIC_SMILEYS and in the init() 
*/
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_UNICODE_SMILEYS,
+               g_param_spec_boolean (
+                       "unicode-smileys",
+                       NULL,
+                       NULL,
+                       FALSE, /* Should be the same as e-editor.js:EvoEditor.UNICODE_SMILEYS and in the 
init() */
                        G_PARAM_READWRITE |
                        G_PARAM_CONSTRUCT |
                        G_PARAM_STATIC_STRINGS));
@@ -6474,6 +6641,11 @@ e_webkit_editor_init (EWebKitEditor *wk_editor)
        wk_editor->priv->old_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
(GDestroyNotify) g_variant_unref);
        wk_editor->priv->visually_wrap_long_lines = FALSE;
 
+       wk_editor->priv->normal_paragraph_width = 71;
+       wk_editor->priv->magic_links = TRUE;
+       wk_editor->priv->magic_smileys = FALSE;
+       wk_editor->priv->unicode_smileys = FALSE;
+
        g_signal_connect (
                wk_editor, "load-changed",
                G_CALLBACK (webkit_editor_load_changed_cb), NULL);
@@ -6578,7 +6750,6 @@ e_webkit_editor_init (EWebKitEditor *wk_editor)
 
        wk_editor->priv->start_bottom = E_THREE_STATE_INCONSISTENT;
        wk_editor->priv->top_signature = E_THREE_STATE_INCONSISTENT;
-       wk_editor->priv->normal_paragraph_width = 71; /* Should be the same as 
e-editor.js:EvoEditor.NORMAL_PARAGRAPH_WIDTH */
 }
 
 static void
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 dbcb9c9dc5..497e53ee01 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
@@ -164,6 +164,48 @@ load_javascript_file (JSCContext *jsc_context,
        g_free (content);
 }
 
+/* Returns 'null', when no match for the 'pattern' in 'text' found, otherwise
+   returns an 'object { start : nnn, end : nnn };' with the first longest pattern match. */
+static JSCValue *
+evo_editor_jsc_find_pattern (const gchar *text,
+                            const gchar *pattern,
+                            JSCContext *jsc_context)
+{
+       JSCValue *object = NULL;
+       GRegex *regex;
+
+       if (!text || !*text || !pattern || !*pattern)
+               return jsc_value_new_null (jsc_context);
+
+       regex = g_regex_new (pattern, 0, 0, NULL);
+       if (regex) {
+               GMatchInfo *match_info = NULL;
+               gint start = -1, end = -1;
+
+               if (g_regex_match_all (regex, text, G_REGEX_MATCH_NOTEMPTY, &match_info) &&
+                   g_match_info_fetch_pos (match_info, 0, &start, &end) &&
+                   start >= 0 && end >= 0) {
+                       JSCValue *number;
+
+                       object = jsc_value_new_object (jsc_context, NULL, NULL);
+
+                       number = jsc_value_new_number (jsc_context, start);
+                       jsc_value_object_set_property (object, "start", number);
+                       g_clear_object (&number);
+
+                       number = jsc_value_new_number (jsc_context, end);
+                       jsc_value_object_set_property (object, "end", number);
+                       g_clear_object (&number);
+               }
+
+               if (match_info)
+                       g_match_info_free (match_info);
+               g_regex_unref (regex);
+       }
+
+       return object ? object : jsc_value_new_null (jsc_context);
+}
+
 static void
 window_object_cleared_cb (WebKitScriptWorld *world,
                          WebKitWebPage *page,
@@ -171,6 +213,7 @@ window_object_cleared_cb (WebKitScriptWorld *world,
                          gpointer user_data)
 {
        JSCContext *jsc_context;
+       JSCValue *jsc_editor;
 
        /* Load the javascript files only to the main frame, not to the subframes */
        if (!webkit_frame_is_main_frame (frame))
@@ -184,6 +227,21 @@ window_object_cleared_cb (WebKitScriptWorld *world,
        load_javascript_file (jsc_context, "e-undo-redo.js");
        load_javascript_file (jsc_context, "e-editor.js");
 
+       jsc_editor = jsc_context_get_value (jsc_context, "EvoEditor");
+
+       if (jsc_editor) {
+               JSCValue *jsc_function;
+
+               jsc_function = jsc_value_new_function (jsc_context, "findPattern",
+                       G_CALLBACK (evo_editor_jsc_find_pattern), g_object_ref (jsc_context), g_object_unref,
+                       JSC_TYPE_VALUE, 2, G_TYPE_STRING, G_TYPE_STRING);
+
+               jsc_value_object_set_property (jsc_editor, "findPattern", jsc_function);
+
+               g_clear_object (&jsc_function);
+               g_clear_object (&jsc_editor);
+       }
+
        g_clear_object (&jsc_context);
 }
 


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