[evolution/wip/mcrha/webkit-jsc-api] Implement and use EvoEditor.SetFontName()



commit 65a28ff08b28ed08023f80941bbbedcd5d5b2e2e
Author: Milan Crha <mcrha redhat com>
Date:   Mon Nov 25 18:43:28 2019 +0100

    Implement and use EvoEditor.SetFontName()

 data/webkit/e-editor.js                     | 205 ++++++++++++++++++++++------
 data/webkit/e-selection.js                  |  82 +++++++++++
 data/webkit/e-undo-redo.js                  |  38 ++++++
 src/modules/webkit-editor/e-webkit-editor.c |  18 ++-
 4 files changed, 297 insertions(+), 46 deletions(-)
---
diff --git a/data/webkit/e-editor.js b/data/webkit/e-editor.js
index 55a3b0c2c8..7c615d219c 100644
--- a/data/webkit/e-editor.js
+++ b/data/webkit/e-editor.js
@@ -68,6 +68,7 @@ var EvoEditor = {
        mode : 1, // one of the MODE constants
        storedSelection : null,
        inheritThemeColors : false,
+       checkInheritFontsOnChange : false,
        forceFormatStateUpdate : false,
        formattingState : {
                mode : -1,
@@ -892,28 +893,16 @@ EvoEditor.SetBlockFormat = function(format)
                createParent : null,
                firstLI : true,
                targetElement : null,
-               selectionBaseNode : null,
-               selectionBaseOffset : -1,
-               selectionExtentNode : null,
-               selectionExtentOffset : -1,
+               selectionUpdater : null,
 
                flat : true,
                onlyBlockElements : true,
 
                exec : function(parent, element) {
-                       var newElement, changeBase = false, changeExtent = false;
+                       var newElement;
 
-                       if (traversar.selectionBaseNode) {
-                               changeBase = element === traversar.selectionBaseNode ||
-                                       (traversar.selectionBaseNode.noteType == 
traversar.selectionBaseNode.TEXT_NODE &&
-                                        traversar.selectionBaseNode.parentElement === element);
-                       }
-
-                       if (traversar.selectionExtentNode) {
-                               changeExtent = element === traversar.selectionExtentNode ||
-                                       (traversar.selectionExtentNode.noteType == 
traversar.selectionExtentNode.TEXT_NODE &&
-                                        traversar.selectionExtentNode.parentElement === element);
-                       }
+                       if (traversar.selectionUpdater)
+                               traversar.selectionUpdater.beforeRemove(element);
 
                        if (traversar.firstLI) {
                                if (traversar.createParent) {
@@ -925,16 +914,15 @@ EvoEditor.SetBlockFormat = function(format)
 
                        newElement = EvoEditor.renameElement(element, traversar.toSet.tagName, 
traversar.toSet.attributes, traversar.targetElement);
 
-                       if (changeBase)
-                               traversar.selectionBaseNode = newElement;
-
-                       if (changeExtent)
-                               traversar.selectionExtentNode = newElement;
+                       if (traversar.selectionUpdater)
+                               traversar.selectionUpdater.afterRemove(newElement);
 
                        return true;
                }
        };
 
+       traversar.selectionUpdater = EvoSelection.CreateUpdaterObject();
+
        var affected = EvoEditor.ClaimAffectedContent(null, null, 
EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE);
 
        switch (format) {
@@ -985,36 +973,13 @@ EvoEditor.SetBlockFormat = function(format)
                throw "EvoEditor.SetBlockFormat: Unknown block format value: '" + format + "'";
        }
 
-       var selectionBefore = EvoSelection.Store(document);
-
        EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setBlockFormat", null, null,
                EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML);
 
-       traversar.selectionBaseNode = document.getSelection().baseNode;
-       traversar.selectionBaseOffset = document.getSelection().baseOffset;
-       if (!document.getSelection().isCollapsed) {
-               traversar.selectionExtentNode = document.getSelection().extentNode;
-               traversar.selectionExtentOffset = document.getSelection().extentOffset;
-       }
-
        try {
                EvoEditor.ForeachChildInAffectedContent(affected, traversar);
 
-               if (traversar.selectionBaseNode && traversar.selectionBaseNode.parentElement) {
-                       var selection = {
-                               baseElem : EvoSelection.GetChildPath(document.body, 
traversar.selectionBaseNode),
-                               baseOffset : traversar.selectionBaseOffset
-                       };
-
-                       if (traversar.selectionExtentNode) {
-                               selection.extentElem = EvoSelection.GetChildPath(document.body, 
traversar.selectionExtentNode);
-                               selection.extentOffset = traversar.selectionExtentOffset;
-                       }
-
-                       EvoSelection.Restore(document, selection);
-               } else {
-                       EvoSelection.Restore(document, selectionBefore);
-               }
+               traversar.selectionUpdater.restore();
        } finally {
                EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setBlockFormat");
                EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE);
@@ -1316,6 +1281,151 @@ EvoEditor.SetMode = function(mode)
        }
 }
 
+EvoEditor.applyFontReset = function(record, isUndo)
+{
+       if (record.changes) {
+               var ii;
+
+               if (isUndo) {
+                       for (ii = record.changes.length - 1; ii >= 0; ii--) {
+                               var change = record.changes[ii];
+                               var parent = EvoSelection.FindElementByPath(document.body, change.parentPath);
+
+                               if (!parent) {
+                                       throw "EvoEditor.applyFontReset: Cannot find node at path " + 
change.path;
+                               }
+
+                               parent.innerHTML = change.htmlBefore;
+                       }
+               } else {
+                       for (ii = 0; ii < record.changes.length; ii++) {
+                               var change = record.changes[ii];
+                               var parent = EvoSelection.FindElementByPath(document.body, change.parentPath);
+
+                               if (!parent) {
+                                       throw "EvoEditor.applyFontReset: Cannot find node at path " + 
change.path;
+                               }
+
+                               parent.innerHTML = change.htmlAfter;
+                       }
+               }
+       }
+}
+
+EvoEditor.replaceInheritFonts = function(undoRedoRecord, selectionUpdater)
+{
+       var nodes, ii;
+
+       nodes = document.querySelectorAll("font[face=inherit]");
+
+       for (ii = nodes.length - 1; ii >= 0; ii--) {
+               var node = nodes.item(ii);
+
+               if (!node || (!undoRedoRecord && !document.getSelection().containsNode(node, true)))
+                       continue;
+
+               var parent, change = null;
+
+               parent = node.parentElement;
+
+               if (undoRedoRecord) {
+                       if (!undoRedoRecord.changes)
+                               undoRedoRecord.changes = [];
+
+                       change = {
+                               parentPath : EvoSelection.GetChildPath(document.body, parent),
+                               htmlBefore : parent.innerHTML,
+                               htmlAfter : ""
+                       };
+
+                       undoRedoRecord.changes[undoRedoRecord.changes.length] = change;
+               }
+
+               if (node.attributes.length == 1) {
+                       var child;
+
+                       while (node.firstChild) {
+                               var child = node.firstChild;
+
+                               selectionUpdater.beforeRemove(child);
+
+                               parent.insertBefore(child, node);
+
+                               selectionUpdater.afterRemove(child);
+                       }
+
+                       parent.removeChild(node);
+               } else {
+                       node.removeAttribute("face");
+               }
+
+               if (change)
+                       change.htmlAfter = parent.innerHTML;
+       }
+
+       if (undoRedoRecord && undoRedoRecord.changes)
+               undoRedoRecord.apply = EvoEditor.applyFontReset;
+}
+
+EvoEditor.maybeReplaceInheritFonts = function()
+{
+       if (document.querySelectorAll("font[face=inherit]").length <= 0)
+               return;
+
+       var record, selectionUpdater;
+
+       selectionUpdater = EvoSelection.CreateUpdaterObject();
+
+       record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "UnsetFontName", null, null, 
EvoEditor.CLAIM_CONTENT_FLAG_NONE);
+       try {
+               EvoEditor.replaceInheritFonts(record, selectionUpdater);
+
+               selectionUpdater.restore();
+       } finally {
+               EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "UnsetFontName");
+
+               if (record)
+                       EvoUndoRedo.GroupTopRecords(2);
+       }
+}
+
+EvoEditor.SetFontName = function(name)
+{
+       if (!name || name == "")
+               name = "inherit";
+
+       var record, selectionUpdater = EvoSelection.CreateUpdaterObject();
+
+       record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "SetFontName", null, null,
+               EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML);
+       try {
+               document.execCommand("FontName", false, name);
+
+               if (document.getSelection().isCollapsed) {
+                       if (name == "inherit")
+                               EvoEditor.checkInheritFontsOnChange = true;
+
+                       /* Format change on collapsed selection is not applied immediately */
+                       if (record)
+                               record.ignore = true;
+               } else if (name == "inherit") {
+                       var subrecord;
+
+                       subrecord = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "SetFontName", 
null, null, EvoEditor.CLAIM_CONTENT_FLAG_NONE);
+                       try {
+                               EvoEditor.replaceInheritFonts(subrecord, selectionUpdater);
+                               selectionUpdater.restore();
+                       } finally {
+                               EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "SetFontName");
+                       }
+               }
+       } finally {
+               EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "SetFontName");
+               EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE);
+               EvoEditor.EmitContentChanged();
+       }
+}
+
 EvoEditor.convertHtmlToSend = function()
 {
        var html, bgcolor, text, link, vlink;
@@ -1554,6 +1664,11 @@ EvoEditor.UpdateThemeStyleSheet = function(css)
 document.onload = EvoEditor.initializeContent;
 
 document.onselectionchange = function() {
+       if (EvoEditor.checkInheritFontsOnChange) {
+               EvoEditor.checkInheritFontsOnChange = false;
+               EvoEditor.maybeReplaceInheritFonts();
+       }
+
        EvoEditor.maybeUpdateFormattingState(EvoEditor.forceFormatStateUpdate ? EvoEditor.FORCE_YES : 
EvoEditor.FORCE_MAYBE);
        EvoEditor.forceFormatStateUpdate = false;
 };
diff --git a/data/webkit/e-selection.js b/data/webkit/e-selection.js
index e56e45fcf6..5513e63f0c 100644
--- a/data/webkit/e-selection.js
+++ b/data/webkit/e-selection.js
@@ -309,3 +309,85 @@ EvoSelection.FromString = function(str)
 
        return selection;
 }
+
+/* The so-called updater object has several methods to work with when removing
+   elements from the structure, which tries to preserve selection in the new
+   document structure. The methods are:
+
+   beforeRemove(node) - called before going to remove the 'node'
+   afterRemove(newNode) - with what the 'node' from beforeRemove() had been replaced
+   restore() - called at the end, to restore the selection
+ */
+EvoSelection.CreateUpdaterObject = function()
+{
+       var obj = {
+               selectionBefore : null,
+               selectionBaseNode : null,
+               selectionBaseOffset : -1,
+               selectionExtentNode : null,
+               selectionExtentOffset : -1,
+               changeBase : false,
+               changeExtent : false,
+
+               beforeRemove : function(node) {
+                       this.changeBase = false;
+                       this.changeExtent = false;
+
+                       if (this.selectionBaseNode) {
+                               this.changeBase = node === this.selectionBaseNode ||
+                                       (this.selectionBaseNode.noteType == this.selectionBaseNode.TEXT_NODE 
&&
+                                        this.selectionBaseNode.parentElement === node);
+                       }
+
+                       if (this.selectionExtentNode) {
+                               this.changeExtent = node === this.selectionExtentNode ||
+                                       (this.selectionExtentNode.noteType == 
this.selectionExtentNode.TEXT_NODE &&
+                                        this.selectionExtentNode.parentElement === node);
+                       }
+               },
+
+               afterRemove : function(newNode) {
+                       if (this.changeBase) {
+                               this.selectionBaseNode = newNode;
+                               this.selectionBaseOffset += EvoSelection.GetOverallTextOffset(newNode);
+                       }
+
+                       if (this.changeExtent) {
+                               this.selectionExtentNode = newNode;
+                               this.selectionExtentOffset += EvoSelection.GetOverallTextOffset(newNode);
+                       }
+
+                       this.changeBase = false;
+                       this.changeExtent = false;
+               },
+
+               restore : function() {
+                       if (this.selectionBaseNode && this.selectionBaseNode.parentElement) {
+                               var selection = {
+                                       baseElem : EvoSelection.GetChildPath(document.body, 
this.selectionBaseNode),
+                                       baseOffset : this.selectionBaseOffset
+                               };
+
+                               if (this.selectionExtentNode) {
+                                       selection.extentElem = EvoSelection.GetChildPath(document.body, 
this.selectionExtentNode);
+                                       selection.extentOffset = this.selectionExtentOffset;
+                               }
+
+                               EvoSelection.Restore(document, selection);
+                       } else {
+                               EvoSelection.Restore(document, this.selectionBefore);
+                       }
+               }
+       };
+
+       obj.selectionBefore = EvoSelection.Store(document);
+       obj.selectionBaseNode = document.getSelection().baseNode;
+       obj.selectionBaseOffset = document.getSelection().baseOffset;
+
+       if (!document.getSelection().isCollapsed) {
+               obj.selectionExtentNode = document.getSelection().extentNode;
+               obj.selectionExtentOffset = document.getSelection().extentOffset;
+       }
+
+       return obj;
+}
diff --git a/data/webkit/e-undo-redo.js b/data/webkit/e-undo-redo.js
index 454f865b3a..3a18649762 100644
--- a/data/webkit/e-undo-redo.js
+++ b/data/webkit/e-undo-redo.js
@@ -751,4 +751,42 @@ EvoUndoRedo.Clear = function()
        EvoUndoRedo.stack.clear();
 }
 
+EvoUndoRedo.GroupTopRecords = function(nRecords, opType)
+{
+       if (EvoUndoRedo.ongoingRecordings.length)
+               throw "EvoUndoRedo.GroupTopRecords: Cannot be called when there are ongoing recordings";
+
+       var group = {};
+
+       group.kind = EvoUndoRedo.RECORD_KIND_GROUP;
+       group.opType = opType;
+       group.selectionBefore = null;
+       group.selectionAfter = null;
+       group.records = [];
+
+       while (nRecords >= 1) {
+               nRecords--;
+
+               var record = EvoUndoRedo.stack.undo();
+
+               if (!record)
+                       break;
+
+               group.records[group.records.length] = record;
+               group.selectionBefore = record.selectionBefore;
+
+               if (!group.selectionAfter)
+                       group.selectionAfter = record.selectionAfter;
+
+               if (!group.opType)
+                       group.opType = record.opType + "::Grouped";
+       }
+
+       if (group.records.length) {
+               group.records = group.records.reverse();
+
+               EvoUndoRedo.stack.push(group);
+       }
+}
+
 EvoUndoRedo.Attach();
diff --git a/src/modules/webkit-editor/e-webkit-editor.c b/src/modules/webkit-editor/e-webkit-editor.c
index 2207c3e690..f2510283d7 100644
--- a/src/modules/webkit-editor/e-webkit-editor.c
+++ b/src/modules/webkit-editor/e-webkit-editor.c
@@ -602,6 +602,20 @@ formatting_changed_cb (WebKitUserContentManager *manager,
        }
        g_clear_object (&jsc_value);
 
+       changed = FALSE;
+       jsc_value = jsc_value_object_get_property (jsc_params, "fontFamily");
+       if (jsc_value && jsc_value_is_string (jsc_value)) {
+               gchar *value = jsc_value_to_string (jsc_value);
+
+               if (g_strcmp0 (value, wk_editor->priv->font_name) != 0) {
+                       update_style_flag (E_WEBKIT_EDITOR_STYLE_IS_MONOSPACE, g_strcmp0 (value, "monospace") 
== 0);
+                       changed = TRUE;
+               }
+
+               g_free (value);
+       }
+       g_clear_object (&jsc_value);
+
        wk_editor->priv->temporary_style_flags = wk_editor->priv->style_flags;
 
        #undef update_style_flag
@@ -3862,7 +3876,9 @@ webkit_editor_set_style_flag (EWebKitEditor *wk_editor,
                webkit_web_view_execute_editing_command (WEBKIT_WEB_VIEW (wk_editor), "Strikethrough");
                break;
        case E_WEBKIT_EDITOR_STYLE_IS_MONOSPACE:
-               webkit_web_view_execute_editing_command_with_argument (WEBKIT_WEB_VIEW (wk_editor), 
"FontName", "monospace");
+               e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (wk_editor), wk_editor->priv->cancellable,
+                       "EvoEditor.SetFontName(%s);",
+                       do_set ? "monospace" : "");
                break;
        case E_WEBKIT_EDITOR_STYLE_IS_SUBSCRIPT:
                webkit_web_view_execute_editing_command (WEBKIT_WEB_VIEW (wk_editor), "Subscript");


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