[geary/wip/728002-webkit2: 82/105] Reimplement loading and cleaning message into WK2 composer web view.



commit 596275397a3eb5a913dbb4fe6070531ef79fb281
Author: Michael James Gratton <mike vee net>
Date:   Wed Nov 30 22:56:01 2016 +1100

    Reimplement loading and cleaning message into WK2 composer web view.
    
    * src/client/application/geary-controller.vala (GearyController::open_async):
      Load ComposerWebView resources.
    
    * src/client/composer/composer-web-view.vala (ComposerWebView): Move
      HTML/CSS template here from ComposerWidget. Load composer-web-view.js
      on app init and add it to the web view's user content manager.
      (ComposerWebView::load_html): Overridden to require HTML body and
      signature, assemble complete HTML as appropriate before chaining up to
      the default impl.
      (ComposerWebView::load_finished_and_realised): Remove redundant method.
    
    * src/client/composer/composer-widget.vala (ComposerWidget):
      Remove 'message' prop since it is unused and onerous. Cache current
      account's signature as a field so it can be passed through to the
      editor as needed. Port on_link_clicked to composer-web-view.js.
    
    * src/client/web-process/util-composer.vala: Remove function ported to JS
      in composer-web-view.js
    
    * ui/CMakeLists.txt: Include new ComposerWebView JS resource.
    
    * ui/composer-web-view.js: Port composer HTML sanitisation methods to JS,
      add to a custom subclass of PageState. Instantiate it and hook it up to
      onload.

 src/client/application/geary-controller.vala |    1 +
 src/client/composer/composer-web-view.vala   |   82 ++++++++++++-
 src/client/composer/composer-widget.vala     |  165 ++++++--------------------
 src/client/web-process/util-composer.vala    |   50 --------
 ui/CMakeLists.txt                            |    1 +
 ui/composer-web-view.js                      |   69 +++++++++++
 6 files changed, 181 insertions(+), 187 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 6ec675e..0c7148b 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -210,6 +210,7 @@ public class GearyController : Geary.BaseObject {
         // Load web view resources
         try {
             ClientWebView.load_scripts(this.application);
+            ComposerWebView.load_resources(this.application);
             ConversationWebView.load_resources(this.application);
         } catch (Error err) {
             error("Error loading web resources: %s", err.message);
diff --git a/src/client/composer/composer-web-view.vala b/src/client/composer/composer-web-view.vala
index ddab2c8..e3d0663 100644
--- a/src/client/composer/composer-web-view.vala
+++ b/src/client/composer/composer-web-view.vala
@@ -12,6 +12,61 @@
 public class ComposerWebView : ClientWebView {
 
 
+    private const string HTML_BODY = """
+        <html><head><title></title>
+        <style>
+        body {
+            margin: 0px !important;
+            padding: 0 !important;
+            background-color: white !important;
+            font-size: medium !important;
+        }
+        body.plain, body.plain * {
+            font-family: monospace !important;
+            font-weight: normal;
+            font-style: normal;
+            font-size: medium !important;
+            color: black;
+            text-decoration: none;
+        }
+        body.plain a {
+            cursor: text;
+        }
+        #message-body {
+            box-sizing: border-box;
+            padding: 10px;
+            outline: 0px solid transparent;
+            min-height: 100%;
+        }
+        blockquote {
+            margin-top: 0px;
+            margin-bottom: 0px;
+            margin-left: 10px;
+            margin-right: 10px;
+            padding-left: 5px;
+            padding-right: 5px;
+            background-color: white;
+            border: 0;
+            border-left: 3px #aaa solid;
+        }
+        pre {
+            white-space: pre-wrap;
+            margin: 0;
+        }
+        </style>
+        </head><body>
+        <div id="message-body" contenteditable="true" dir="auto">%s</div>
+        </body></html>""";
+    private const string CURSOR = "<span id=\"cursormarker\"></span>";
+
+    private static WebKit.UserScript? app_script = null;
+
+    public static void load_resources(GearyApplication app)
+        throws Error {
+        ComposerWebView.app_script =
+            ClientWebView.load_app_script(app, "composer-web-view.js");
+    }
+
     private bool is_shift_down = false;
 
 
@@ -19,6 +74,9 @@ public class ComposerWebView : ClientWebView {
 
 
     public ComposerWebView() {
+        base();
+        this.user_content_manager.add_script(ComposerWebView.app_script);
+
         get_editor_state().notify["typing-attributes"].connect(() => {
                 text_attributes_changed(get_editor_state().typing_attributes);
             });
@@ -27,6 +85,23 @@ public class ComposerWebView : ClientWebView {
         this.key_press_event.connect(on_key_press_event);
     }
 
+    /**
+     * Loads a message HTML body into the view.
+     */
+    public new void load_html(string? body, string? signature, bool top_posting) {
+        string html = "";
+        signature = signature ?? "";
+
+        if (body == null)
+            html = CURSOR + "<br /><br />" + signature;
+        else if (top_posting)
+            html = CURSOR + "<br /><br />" + signature + body;
+        else
+            html = body + CURSOR + "<br /><br />" + signature;
+
+        base.load_html(HTML_BODY.printf(html), null);
+    }
+
     public bool can_undo() {
         // can_execute_editing_command.begin(
         //     WebKit.EDITING_COMMAND_UNDO,
@@ -140,13 +215,6 @@ public class ComposerWebView : ClientWebView {
     /**
      * ???
      */
-    public void load_finished_and_realised() {
-        // XXX
-    }
-
-    /**
-     * ???
-     */
     public bool handle_key_press(Gdk.EventKey event) {
         // XXX
         return false;
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 6682372..cf0409c 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -151,52 +151,6 @@ public class ComposerWidget : Gtk.EventBox {
 
     private const string URI_LIST_MIME_TYPE = "text/uri-list";
     private const string FILE_URI_PREFIX = "file://";
-    private const string HTML_BODY = """
-        <html><head><title></title>
-        <style>
-        body {
-            margin: 0px !important;
-            padding: 0 !important;
-            background-color: white !important;
-            font-size: medium !important;
-        }
-        body.plain, body.plain * {
-            font-family: monospace !important;
-            font-weight: normal;
-            font-style: normal;
-            font-size: medium !important;
-            color: black;
-            text-decoration: none;
-        }
-        body.plain a {
-            cursor: text;
-        }
-        #message-body {
-            box-sizing: border-box;
-            padding: 10px;
-            outline: 0px solid transparent;
-            min-height: 100%;
-        }
-        blockquote {
-            margin-top: 0px;
-            margin-bottom: 0px;
-            margin-left: 10px;
-            margin-right: 10px;
-            padding-left: 5px;
-            padding-right: 5px;
-            background-color: white;
-            border: 0;
-            border-left: 3px #aaa solid;
-        }
-        pre {
-            white-space: pre-wrap;
-            margin: 0;
-        }
-        </style>
-        </head><body>
-        <div id="message-body" contenteditable="true" dir="auto"></div>
-        </body></html>""";
-    private const string CURSOR = "<span id=\"cursormarker\"></span>";
     
     private const int DRAFT_TIMEOUT_SEC = 10;
     
@@ -240,14 +194,6 @@ public class ComposerWidget : Gtk.EventBox {
         set { this.subject_entry.set_text(value); }
     }
 
-    public string message {
-        owned get { return get_html(); }
-        set {
-            this.body_html = value;
-            this.editor.load_html(HTML_BODY, null);
-        }
-    }
-
     public ComposerState state { get; internal set; }
 
     public ComposeType compose_type { get; private set; default = ComposeType.NEW_MESSAGE; }
@@ -286,6 +232,7 @@ public class ComposerWidget : Gtk.EventBox {
     private ContactListStore? contact_list_store = null;
 
     private string? body_html = null;
+    private string? signature_html = null;
 
     [GtkChild]
     private Gtk.Box composer_container;
@@ -489,14 +436,9 @@ public class ComposerWidget : Gtk.EventBox {
         }
 
         update_from_field();
+        update_signature();
         update_pending_attachments(this.pending_include, true);
 
-        // only add signature if the option is actually set and if this is not a draft
-        if (this.account.information.use_email_signature && !is_referred_draft)
-            add_signature_and_cursor();
-        else
-            set_cursor();
-
         // Add actions once every element has been initialized and added
         initialize_actions();
 
@@ -522,8 +464,7 @@ public class ComposerWidget : Gtk.EventBox {
         this.editor.key_press_event.connect(on_editor_key_press);
         //this.editor.user_changed_contents.connect(reset_draft_timer);
 
-        // only do this after setting body_html
-        this.editor.load_html(HTML_BODY, null);
+        this.editor.load_html(this.body_html, this.signature_html, this.top_posting);
 
         GearyApplication.instance.config.settings.changed[Configuration.SPELL_CHECK_KEY].connect(
             on_spell_check_changed);
@@ -850,10 +791,6 @@ public class ComposerWidget : Gtk.EventBox {
         // This is safe to call even when this connection hasn't been made.
         realize.disconnect(on_load_finished_and_realized);
 
-        if (!Geary.String.is_empty(this.body_html)) {
-            this.editor.load_finished_and_realised();
-        }
-
         on_spell_check_changed();
         update_actions();
 
@@ -1089,55 +1026,6 @@ public class ComposerWidget : Gtk.EventBox {
         referred_ids.add(referred.id);
     }
     
-    private void add_signature_and_cursor() {
-        string? signature = null;
-        
-        // If use signature is enabled but no contents are on settings then we'll use ~/.signature, if any
-        // otherwise use whatever the user has input in settings dialog
-        if (this.account.information.use_email_signature
-            && Geary.String.is_empty_or_whitespace(this.account.information.email_signature)) {
-            File signature_file = File.new_for_path(Environment.get_home_dir()).get_child(".signature");
-            if (!signature_file.query_exists()) {
-                set_cursor();
-                return;
-            }
-            
-            try {
-                FileUtils.get_contents(signature_file.get_path(), out signature);
-                if (Geary.String.is_empty_or_whitespace(signature)) {
-                    set_cursor();
-                    return;
-                }
-                signature = Geary.HTML.smart_escape(signature, false);
-            } catch (Error error) {
-                debug("Error reading signature file %s: %s", signature_file.get_path(), error.message);
-                set_cursor();
-                return;
-            }
-        } else {
-            signature = account.information.email_signature;
-            if (Geary.String.is_empty_or_whitespace(signature)) {
-                set_cursor();
-                return;
-            }
-            signature = Geary.HTML.smart_escape(signature, true);
-        }
-
-        if (this.body_html == null)
-            this.body_html = CURSOR + "<br /><br />" + signature;
-        else if (top_posting)
-            this.body_html = CURSOR + "<br /><br />" + signature + this.body_html;
-        else
-            this.body_html = this.body_html + CURSOR + "<br /><br />" + signature;
-    }
-
-    private void set_cursor() {
-        if (top_posting)
-            this.body_html = CURSOR + this.body_html;
-        else
-            this.body_html = this.body_html + CURSOR;
-    }
-
     private bool can_save() {
         return this.draft_manager != null
             && this.draft_manager.is_open
@@ -2054,11 +1942,7 @@ public class ComposerWidget : Gtk.EventBox {
             this.can_delete_quote = false;
             if (event.keyval == Gdk.Key.BackSpace) {
                 this.body_html = null;
-                if (this.account.information.use_email_signature)
-                    add_signature_and_cursor();
-                else
-                    set_cursor();
-                this.editor.load_html(HTML_BODY, null);
+                this.editor.load_html(this.body_html, this.signature_html, this.top_posting);
                 return true;
             }
         }
@@ -2256,11 +2140,42 @@ public class ComposerWidget : Gtk.EventBox {
             return false;
 
         this.account = new_account;
+        update_signature();
         load_entry_completions.begin();
 
         return true;
     }
 
+    private void update_signature() {
+        string? account_sig = null;
+
+        if (this.account.information.use_email_signature) {
+            account_sig = account.information.email_signature;
+            if (Geary.String.is_empty_or_whitespace(account_sig)) {
+                // No signature is specified in the settings, so use
+                // ~/.signature
+
+                // XXX This loading should be async, but that needs to
+                // be factored into how the signature HTML is passed
+                // to the editor.
+                File signature_file = File.new_for_path(Environment.get_home_dir()).get_child(".signature");
+                if (signature_file.query_exists()) {
+                    try {
+                        FileUtils.get_contents(signature_file.get_path(), out account_sig);
+                    } catch (Error error) {
+                        debug("Error reading signature file %s: %s", signature_file.get_path(), 
error.message);
+                    }
+                }
+            }
+
+            account_sig = (!Geary.String.is_empty_or_whitespace(account_sig))
+                ? Geary.HTML.smart_escape(account_sig, true)
+                : null;
+        }
+
+        this.signature_html = account_sig;
+    }
+
     private void on_text_attributes_changed(uint mask) {
         this.actions.change_action_state(
             ACTION_BOLD,
@@ -2335,14 +2250,4 @@ public class ComposerWidget : Gtk.EventBox {
         link_dialog("http://";);
     }
 
-    // private static void on_link_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
-    //     ComposerWidget composer) {
-    //     try {
-    //         composer.editor.get_dom_document().get_default_view().get_selection().
-    //             select_all_children(element);
-    //     } catch (Error e) {
-    //         debug("Error selecting link: %s", e.message);
-    //     }
-    // }
-
 }
diff --git a/src/client/web-process/util-composer.vala b/src/client/web-process/util-composer.vala
index 0f83dc8..93442b1 100644
--- a/src/client/web-process/util-composer.vala
+++ b/src/client/web-process/util-composer.vala
@@ -21,56 +21,6 @@ namespace Util.Composer {
     private const string EDITING_DELETE_CONTAINER_ID = "WebKit-Editing-Delete-Container";
 
 
-    public void on_load_finished_and_realized(WebKit.WebPage page, string body_html) {
-        WebKit.DOM.Document document = page.get_dom_document();
-        WebKit.DOM.HTMLElement? body = document.get_element_by_id(BODY_ID) as WebKit.DOM.HTMLElement;
-        assert(body != null);
-
-        try {
-            body.set_inner_html(body_html);
-        } catch (Error e) {
-            debug("Failed to load prefilled body: %s", e.message);
-        }
-
-        protect_blockquote_styles(page);
-
-        // Focus within the HTML document
-        body.focus();
-
-        // Set cursor at appropriate position
-        try {
-            WebKit.DOM.Element? cursor = document.get_element_by_id("cursormarker");
-            if (cursor != null) {
-                WebKit.DOM.Range range = document.create_range();
-                range.select_node_contents(cursor);
-                range.collapse(false);
-                // WebKit.DOM.DOMSelection selection = document.default_page.get_selection();
-                // selection.remove_all_ranges();
-                // selection.add_range(range);
-                // cursor.parent_element.remove_child(cursor);
-            }
-        } catch (Error error) {
-            debug("Error setting cursor at end of text: %s", error.message);
-        }
-
-        //Util.DOM.bind_event(view, "a", "click", (Callback) on_link_clicked, this);
-    }
-
-    private void protect_blockquote_styles(WebKit.WebPage page) {
-        // We will search for an remove a particular styling when we quote text.  If that style
-        // exists in the quoted text, we alter it slightly so we don't mess with it later.
-        try {
-            WebKit.DOM.NodeList node_list = page.get_dom_document().query_selector_all(
-                "blockquote[style=\"margin: 0 0 0 40px; border: none; padding: 0px;\"]");
-            for (int i = 0; i < node_list.length; ++i) {
-                ((WebKit.DOM.Element) node_list.item(i)).set_attribute("style",
-                    "margin: 0 0 0 40px; padding: 0px; border:none;");
-            }
-        } catch (Error error) {
-            debug("Error protecting blockquotes: %s", error.message);
-        }
-    }
-
     public void insert_quote(WebKit.WebPage page, string quote) {
         WebKit.DOM.Document document = page.get_dom_document();
         document.exec_command("insertHTML", false, quote);
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 65d416c..f51229f 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -10,6 +10,7 @@ set(RESOURCE_LIST
   STRIPBLANKS "composer-headerbar.ui"
   STRIPBLANKS "composer-menus.ui"
   STRIPBLANKS "composer-widget.ui"
+              "composer-web-view.js"
   STRIPBLANKS "conversation-email.ui"
   STRIPBLANKS "conversation-email-attachment-view.ui"
   STRIPBLANKS "conversation-email-menus.ui"
diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js
new file mode 100644
index 0000000..7c902ef
--- /dev/null
+++ b/ui/composer-web-view.js
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2016 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Application logic for ComposerWebView.
+ */
+var ComposerPageState = function() {
+    this.init.apply(this, arguments);
+};
+ComposerPageState.prototype = {
+    __proto__: PageState.prototype,
+    init: function() {
+        PageState.prototype.init.apply(this, []);
+    },
+    loaded: function() {
+        // Search for and remove a particular styling when we quote
+        // text. If that style exists in the quoted text, we alter it
+        // slightly so we don't mess with it later.
+        var nodeList = document.querySelectorAll(
+            "blockquote[style=\"margin: 0 0 0 40px; border: none; padding: 0px;\"]");
+        for (var i = 0; i < nodeList.length; ++i) {
+            nodeList.item(i).setAttribute(
+                "style",
+                "margin: 0 0 0 40px; padding: 0px; border:none;"
+            );
+        }
+
+        // Focus within the HTML document
+        document.body.focus();
+
+        // Set cursor at appropriate position
+        var cursor = document.getElementById("cursormarker");
+        if (cursor != null) {
+            var range = document.createRange();
+            range.selectNodeContents(cursor);
+            range.collapse(false);
+
+            var selection = window.getSelection();
+            selection.removeAllRanges();
+            selection.addRange(range);
+            cursor.parentNode.removeChild(cursor);
+        }
+
+        // Chain up here so we continue to a preferred size update
+        // after munging the HTML above.
+        PageState.prototype.loaded.apply(this, []);
+
+        //Util.DOM.bind_event(view, "a", "click", (Callback) on_link_clicked, this);
+    }
+    // private static void on_link_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
+    //     ComposerWidget composer) {
+    //     try {
+    //         composer.editor.get_dom_document().get_default_view().get_selection().
+    //             select_all_children(element);
+    //     } catch (Error e) {
+    //         debug("Error selecting link: %s", e.message);
+    //     }
+    // }
+};
+
+var geary = new ComposerPageState();
+window.onload = function() {
+    geary.loaded();
+};


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