[geary/wip/728002-webkit2] Reenable converting plain text URLs to links in HTML documents.



commit 37cd2551b86a368efd66a49e047c2ed0a1e8af9f
Author: Michael James Gratton <mike vee net>
Date:   Thu Jan 26 16:31:03 2017 +1100

    Reenable converting plain text URLs to links in HTML documents.
    
    * src/client/composer/composer-web-view.vala (ComposerWebView): Rename
      ::linkify_document since it really only applies to editor content. Make
      an asyc method so we can wait until its finished. Update call
      sites. Thunk call to JS.
    
    * src/client/web-process/util-composer.vala,
      src/client/web-process/util-webkit.vala: Remove unused code.
    
    * ui/composer-web-view.js (ComposerPageState):  Add ::linkifyContent
      method and ::linkify static method. Add unit tests.

 src/CMakeLists.txt                         |    1 -
 src/client/composer/composer-web-view.vala |   14 ++--
 src/client/composer/composer-widget.vala   |    7 +-
 src/client/web-process/util-composer.vala  |    5 -
 src/client/web-process/util-webkit.vala    |  124 ----------------------------
 test/js/composer-page-state-test.vala      |   43 ++++++++++-
 ui/composer-web-view.js                    |   60 +++++++++++++-
 7 files changed, 108 insertions(+), 146 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bb0ad05..d0a04e0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -413,7 +413,6 @@ client/util/util-webkit.vala
 set(WEB_PROCESS_SRC
 client/web-process/web-process-extension.vala
 client/web-process/util-composer.vala
-client/web-process/util-webkit.vala
 )
 
 set(CONSOLE_SRC
diff --git a/src/client/composer/composer-web-view.vala b/src/client/composer/composer-web-view.vala
index 5356696..8cc1c30 100644
--- a/src/client/composer/composer-web-view.vala
+++ b/src/client/composer/composer-web-view.vala
@@ -408,6 +408,13 @@ public class ComposerWebView : ClientWebView {
     }
 
     /**
+     * Converts plain text URLs in the editor content into links.
+     */
+    public async void linkify_content() throws Error {
+        yield run_javascript("geary.linkifyContent();", null);
+    }
+
+    /**
      * Returns the editor content as an HTML string.
      */
     public async string? get_html() throws Error {
@@ -491,13 +498,6 @@ public class ComposerWebView : ClientWebView {
         return flowed.str;
     }
 
-    /**
-     * ???
-     */
-    public void linkify_document() {
-        // XXX
-    }
-
     public override bool button_release_event(Gdk.EventButton event) {
         // WebView seems to unconditionally consume button events, so
         // to show a link popopver after the view has processed one,
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index cb9fc2e..0cb3e94 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -1260,16 +1260,15 @@ public class ComposerWidget : Gtk.EventBox {
     private async void on_send_async() {
         this.container.vanish();
         this.is_closing = true;
-        
-        this.editor.linkify_document();
-        
+
         // Perform send.
         try {
+            yield this.editor.linkify_content();
             yield this.account.send_email_async(yield get_composed_email());
         } catch (Error e) {
             GLib.message("Error sending email: %s", e.message);
         }
-        
+
         Geary.Nonblocking.Semaphore? semaphore = discard_draft();
         if (semaphore != null) {
             try {
diff --git a/src/client/web-process/util-composer.vala b/src/client/web-process/util-composer.vala
index ac340ff..6ef334c 100644
--- a/src/client/web-process/util-composer.vala
+++ b/src/client/web-process/util-composer.vala
@@ -19,11 +19,6 @@ namespace Util.Composer {
     private const string EDITING_DELETE_CONTAINER_ID = "WebKit-Editing-Delete-Container";
 
 
-    public void linkify_document(WebKit.WebPage page) {
-        Util.DOM.linkify_document(page.get_dom_document());
-    }
-
-
     /////////////////////// From WebEditorFixer ///////////////////////
 
     public bool on_should_insert_text(WebKit.WebPage page,
diff --git a/test/js/composer-page-state-test.vala b/test/js/composer-page-state-test.vala
index f792984..debb739 100644
--- a/test/js/composer-page-state-test.vala
+++ b/test/js/composer-page-state-test.vala
@@ -13,13 +13,15 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
         add_test("edit_context_link", edit_context_link);
         add_test("indent_line", indent_line);
         add_test("contains_attachment_keywords", contains_attachment_keywords);
+        add_test("linkify_content", linkify_content);
         add_test("get_html", get_html);
         add_test("get_text", get_text);
         add_test("get_text_with_quote", get_text_with_quote);
         add_test("get_text_with_nested_quote", get_text_with_nested_quote);
-        add_test("resolve_nesting", resolve_nesting);
+
         add_test("contains_keywords", contains_keywords);
         add_test("quote_lines", quote_lines);
+        add_test("resolve_nesting", resolve_nesting);
         add_test("replace_non_breaking_space", replace_non_breaking_space);
     }
 
@@ -109,6 +111,45 @@ some text
         }
     }
 
+    public void linkify_content() {
+        // XXX split these up into multiple tests
+        load_body_fixture("""
+http://example1.com
+
+<p>http://example2.com</p>
+
+<p>http://example3.com http://example4.com</p>
+
+<a href="blarg">http://example5.com</a>
+
+unknown://example6.com
+""");
+
+        string expected = """
+<a href="http://example1.com";>http://example1.com</a>
+
+<p><a href="http://example2.com";>http://example2.com</a></p>
+
+<p><a href="http://example3.com";>http://example3.com</a> <a 
href="http://example4.com";>http://example4.com</a></p>
+
+<a href="blarg">http://example5.com</a>
+
+unknown://example6.com
+<br><br>""";
+
+        try {
+            run_javascript("geary.linkifyContent();");
+            assert(WebKitUtil.to_string(run_javascript("geary.messageBody.innerHTML;")) ==
+                   expected);
+        } catch (Geary.JS.Error err) {
+            print("Geary.JS.Error: %s\n", err.message);
+            assert_not_reached();
+        } catch (Error err) {
+            print("WKError: %s\n", err.message);
+            assert_not_reached();
+        }
+    }
+
     public void get_html() {
         string html = "<p>para</p>";
         load_body_fixture(html);
diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js
index 8207f2e..da55842 100644
--- a/ui/composer-web-view.js
+++ b/ui/composer-web-view.js
@@ -14,12 +14,13 @@ let ComposerPageState = function() {
 };
 ComposerPageState.BODY_ID = "message-body";
 ComposerPageState.KEYWORD_SPLIT_REGEX = /[\s]+/g;
-ComposerPageState.QUOTE_START = "‘";
-ComposerPageState.QUOTE_END = "’";
-ComposerPageState.QUOTE_MARKER = "\x7f";
+ComposerPageState.QUOTE_START = "\x91";  // private use one
+ComposerPageState.QUOTE_END = "\x92";    // private use two
+ComposerPageState.QUOTE_MARKER = "\x7f"; // delete
+ComposerPageState.PROTOCOL_REGEX = 
/^(aim|apt|bitcoin|cvs|ed2k|ftp|file|finger|git|gtalk|http|https|irc|ircs|irc6|lastfm|ldap|ldaps|magnet|news|nntp|rsync|sftp|skype|smb|sms|svn|telnet|tftp|ssh|webcal|xmpp):/i;
 // Taken from Geary.HTML.URL_REGEX, without the inline modifier (?x)
 // at the start, which is unsupported in JS
-ComposerPageState.URL_REGEX = new 
RegExp("\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))",
 "i");
+ComposerPageState.URL_REGEX = new 
RegExp("\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))",
 "gi");
 
 ComposerPageState.prototype = {
     __proto__: PageState.prototype,
@@ -264,6 +265,9 @@ ComposerPageState.prototype = {
             }
         }
     },
+    linkifyContent: function() {
+        ComposerPageState.linkify(this.messageBody);
+    },
     getHtml: function() {
         return this.messageBody.innerHTML;
     },
@@ -403,6 +407,54 @@ ComposerPageState.htmlToQuotedText = function(root) {
     return ComposerPageState.replaceNonBreakingSpace(text);
 };
 
+// Linkifies "plain text" link
+ComposerPageState.linkify = function(node) {
+    if (node.nodeType == Node.TEXT_NODE) {
+        // Examine text node for something that looks like a URL
+        let input = node.nodeValue;
+        if (input != null) {
+            let output = input.replace(ComposerPageState.URL_REGEX, function(url) {
+                if (url.match(ComposerPageState.PROTOCOL_REGEX) != null) {
+                    url = "\x01" + url + "\x01";
+                }
+                return url;
+            });
+
+            if (input != output) {
+                // We got one!  Now split the text and swap in a new anchor.
+                let parent = node.parentNode;
+                let sibling = node.nextSibling;
+                for (let part of output.split("\x01")) {
+                    let newNode = null;
+                    if (part.match(ComposerPageState.URL_REGEX) != null) {
+                        newNode = document.createElement("A");
+                        newNode.href = part;
+                        newNode.innerText = part;
+                    } else {
+                        newNode = document.createTextNode(part);
+                    }
+                    parent.insertBefore(newNode, sibling);
+                }
+                parent.removeChild(node);
+            }
+        }
+    } else {
+        // Recurse
+        let child = node.firstChild;
+        while (child != null) {
+            // Save the child and get its next sibling early since if
+            // it does actually contain a URL, it will be removed from
+            // the tree
+            let target = child;
+            child = child.nextSibling;
+            // Don't attempt to linkify existing links
+            if (target.nodeName != "A") {
+                ComposerPageState.linkify(target);
+            }
+        }
+    }
+};
+
 ComposerPageState.resolveNesting = function(text, values) {
     let tokenregex = new RegExp(
         "(.?)" +


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