[geary/wip/728002-webkit2] Initial pass at implementing Format=Flowed formatting in JS for WK2.



commit 7977e8b5b8df3c321e87687d1a8786873564c354
Author: Michael James Gratton <mike vee net>
Date:   Mon Dec 5 10:07:23 2016 +1100

    Initial pass at implementing Format=Flowed formatting in JS for WK2.
    
    May not actually be working, needs testing, may eat your replies, etc.

 src/client/web-process/util-webkit.vala |  126 ---------------------------
 ui/composer-web-view.js                 |  144 ++++++++++++++++++++++++++++++-
 2 files changed, 143 insertions(+), 127 deletions(-)
---
diff --git a/src/client/web-process/util-webkit.vala b/src/client/web-process/util-webkit.vala
index 5437a7f..4b82677 100644
--- a/src/client/web-process/util-webkit.vala
+++ b/src/client/web-process/util-webkit.vala
@@ -7,10 +7,6 @@
 // Regex to determine if a URL has a known protocol.
 public const string 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):";
 
-// Private use unicode characters are used for quote tokens
-public const string QUOTE_START = "‘";
-public const string QUOTE_END = "’";
-
 namespace Util.DOM {
     public WebKit.DOM.HTMLElement? select(WebKit.DOM.Node node, string selector) {
         try {
@@ -298,126 +294,4 @@ namespace Util.DOM {
         return outtext;
     }
 
-    /**
-     * Convert a HTML DOM tree to RFC 3676 format=flowed text.
-     *
-     * This will modify/reset the DOM.
-     */
-    public string html_to_flowed_text(WebKit.DOM.HTMLElement el) {
-        string saved_doc = el.get_inner_html();
-        WebKit.DOM.NodeList blockquotes;
-        try {
-            blockquotes = el.query_selector_all("blockquote");
-        } catch (Error error) {
-            debug("Error selecting blockquotes: %s", error.message);
-            return "";
-        }
-
-        int nbq = (int) blockquotes.length;
-        string[] bqtexts = new string[nbq];
-
-        // Get text of blockquotes and pull them out of DOM.  They are replaced with tokens deliminated
-        // with the characters QUOTE_START and QUOTE_END (from a unicode private use block).  We need to
-        // get the text while they're  still in the DOM to get newlines at appropriate places.  We go
-        // through the list of blockquotes from the end so that we get the innermost ones first.
-        for (int i = nbq - 1; i >= 0; i--) {
-            WebKit.DOM.HTMLElement bq = (WebKit.DOM.HTMLElement) blockquotes.item(i);
-            bqtexts[i] = bq.get_inner_text();
-            if (bqtexts[i].length > 0 && bqtexts[i].substring(-1, 1) == "\n")
-                bqtexts[i] = bqtexts[i].slice(0, -1);
-            else
-                debug("Did not find expected newline at end of quote.");
-
-            try {
-                bq.set_inner_text(@"$QUOTE_START$i$QUOTE_END");
-            } catch (Error error) {
-                debug("Error manipulating DOM: %s", error.message);
-            }
-        }
-
-        // Reassemble plain text out of parts, replace non-breaking space with regular space
-        string doctext = resolve_nesting(el.get_inner_text(), bqtexts).replace("\xc2\xa0", " ");
-
-        // Reassemble DOM
-        try {
-            el.set_inner_html(saved_doc);
-        } catch (Error error) {
-            debug("Error resetting DOM: %s", error.message);
-        }
-
-        // Wrap, space stuff, quote
-        string[] lines = doctext.split("\n");
-        GLib.StringBuilder flowed = new GLib.StringBuilder.sized(doctext.length);
-        foreach (string line in lines) {
-            // Strip trailing whitespace, so it doesn't look like a flowed line.  But the
-            // signature separator "-- " is special, so leave that alone.
-            if (line != "-- ")
-                line = line.chomp();
-            int quote_level = 0;
-            while (line[quote_level] == Geary.RFC822.Utils.QUOTE_MARKER)
-                quote_level += 1;
-            line = line[quote_level:line.length];
-            string prefix = quote_level > 0 ? string.nfill(quote_level, '>') + " " : "";
-            int max_len = 72 - prefix.length;
-
-            do {
-                int start_ind = 0;
-                if (quote_level == 0 &&
-                    (line.has_prefix(">") || line.has_prefix("From"))) {
-                    line = " " + line;
-                    start_ind = 1;
-                }
-
-                int cut_ind = line.length;
-                if (cut_ind > max_len) {
-                    string beg = line[0:max_len];
-                    cut_ind = beg.last_index_of(" ", start_ind) + 1;
-                    if (cut_ind == 0) {
-                        cut_ind = line.index_of(" ", start_ind) + 1;
-                        if (cut_ind == 0)
-                            cut_ind = line.length;
-                        if (cut_ind > 998 - prefix.length)
-                            cut_ind = 998 - prefix.length;
-                    }
-                }
-                flowed.append(prefix + line[0:cut_ind] + "\n");
-                line = line[cut_ind:line.length];
-            } while (line.length > 0);
-        }
-
-        return flowed.str;
-    }
-
-    public string quote_lines(string text) {
-        string[] lines = text.split("\n");
-        for (int i=0; i<lines.length; i++)
-            lines[i] = @"$(Geary.RFC822.Utils.QUOTE_MARKER)" + lines[i];
-        return string.joinv("\n", lines);
-    }
-
-    public string resolve_nesting(string text, string[] values) {
-        try {
-            GLib.Regex tokenregex = new GLib.Regex(@"(.?)$QUOTE_START([0-9]*)$QUOTE_END(?=(.?))");
-            return tokenregex.replace_eval(text, -1, 0, 0, (info, res) => {
-                int key = int.parse(info.fetch(2));
-                string prev_char = info.fetch(1), next_char = info.fetch(3), insert_next = "";
-                // Make sure there's a newline before and after the quote.
-                if (prev_char != "" && prev_char != "\n")
-                    prev_char = prev_char + "\n";
-                if (next_char != "" && next_char != "\n")
-                    insert_next = "\n";
-                if (key >= 0 && key < values.length) {
-                    res.append(prev_char + quote_lines(resolve_nesting(values[key], values)) + insert_next);
-                } else {
-                    debug("Regex error in denesting blockquotes: Invalid key");
-                    res.append("");
-                }
-                return false;
-            });
-        } catch (Error error) {
-            debug("Regex error in denesting blockquotes: %s", error.message);
-            return "";
-        }
-    }
-
 }
diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js
index 0089132..8fa0dee 100644
--- a/ui/composer-web-view.js
+++ b/ui/composer-web-view.js
@@ -13,6 +13,9 @@ var ComposerPageState = function() {
     this.init.apply(this, arguments);
 };
 ComposerPageState.BODY_ID = "message-body";
+ComposerPageState.QUOTE_START = "‘";
+ComposerPageState.QUOTE_END = "’";
+ComposerPageState.QUOTE_MARKER = "\x7f";
 
 ComposerPageState.prototype = {
     __proto__: PageState.prototype,
@@ -63,7 +66,9 @@ ComposerPageState.prototype = {
         return document.getElementById(ComposerPageState.BODY_ID).innerHTML;
     },
     getText: function() {
-        return document.getElementById(ComposerPageState.BODY_ID).innerText;
+        return ComposerPageState.htmlToFlowedText(
+            document.getElementById(ComposerPageState.BODY_ID)
+        );
     },
     setRichText: function(enabled) {
         if (enabled) {
@@ -87,6 +92,143 @@ ComposerPageState.prototype = {
     }
 };
 
+/**
+ * Convert a HTML DOM tree to RFC 3676 format=flowed text.
+ *
+ * This will modify/reset the DOM.
+ */
+ComposerPageState.htmlToFlowedText = function(root) {
+    var savedDoc = root.innerHTML;
+    var blockquotes = root.querySelectorAll("blockquote");
+    var nbq = blockquotes.length;
+    var bqtexts = new Array(nbq);
+
+    // Get text of blockquotes and pull them out of DOM.  They are
+    // replaced with tokens deliminated with the characters
+    // QUOTE_START and QUOTE_END (from a unicode private use block).
+    // We need to get the text while they're still in the DOM to get
+    // newlines at appropriate places.  We go through the list of
+    // blockquotes from the end so that we get the innermost ones
+    // first.
+    for (let i = nbq - 1; i >= 0; i--) {
+        let bq = blockquotes.item(i);
+        let text = bq.innerText;
+        console.log("Line: " + text);
+        if (text.substr(-1, 1) == "\n") {
+            text = text.slice(0, -1);
+            console.log("  found expected newline at end of quote!");
+        } else {
+            console.log(
+                "  no newline at end of quote: " +
+                    text.length > 0
+                    ? "0x" + text.codePointAt(text.length - 1).toString(16)
+                    : "empty line"
+            );
+        }
+        bqtexts[i] = text;
+
+        bq.innerText = (
+            ComposerPageState.QUOTE_START
+                + i.toString()
+                + ComposerPageState.QUOTE_END
+        );
+    }
+
+    // Reassemble plain text out of parts, replace non-breaking
+    // space with regular space
+    var doctext = ComposerPageState.resolveNesting(
+        root.innerText, bqtexts
+    ).replace("\xc2\xa0", " ");
+
+    // Reassemble DOM
+    root.innerHTML = savedDoc;
+
+    // Wrap, space stuff, quote
+    var lines = doctext.split("\n");
+    flowed = [];
+    for (let line of lines) {
+        // Strip trailing whitespace, so it doesn't look like a flowed
+        // line.  But the signature separator "-- " is special, so
+        // leave that alone.
+        if (line != "-- ") {
+            line = line.trimRight();
+        }
+        let quoteLevel = 0;
+        while (line[quoteLevel] == ComposerPageState.QUOTE_MARKER) {
+            quoteLevel += 1;
+        }
+        line = line.substr(quoteLevel, line.length);
+        let prefix = quoteLevel > 0 ? '>'.repeat(quoteLevel) + " " : "";
+        let maxLen = 72 - prefix.length;
+
+        do {
+            let startInd = 0;
+            if (quoteLevel == 0 &&
+                (line.startsWith(">") || line.startsWith("From"))) {
+                line = " " + line;
+                startInd = 1;
+            }
+
+            let cutInd = line.length;
+            if (cutInd > maxLen) {
+                let beg = line.substr(0, maxLen);
+                cutInd = beg.lastIndexOf(" ", startInd) + 1;
+                if (cutInd == 0) {
+                    cutInd = line.indexOf(" ", startInd) + 1;
+                    if (cutInd == 0) {
+                        cutInd = line.length;
+                    }
+                    if (cutInd > 998 - prefix.length) {
+                        cutInd = 998 - prefix.length;
+                    }
+                }
+            }
+            flowed.push(prefix + line.substr(0, cutInd) + "\n");
+            line = line.substr(cutInd, line.length);
+        } while (line.length > 0);
+    }
+
+    return flowed.join("");
+};
+
+ComposerPageState.resolveNesting = function(text, values) {
+    let tokenregex = new RegExp(
+        "(.?)" +
+            ComposerPageState.QUOTE_START +
+            "([0-9]*)" +
+            ComposerPageState.QUOTE_END +
+            "(?=(.?))"
+    );
+    return text.replace(tokenregex, function(match, p1, p2, p3, offset, str) {
+        let key = new Number(p2);
+        let prevChar = p1;
+        let nextChar = p3;
+        let insertNext = "";
+        // Make sure there's a newline before and after the quote.
+        if (prevChar != "" && prevChar != "\n")
+            prevChar = prevChar + "\n";
+        if (nextChar != "" && nextChar != "\n")
+            insertNext = "\n";
+
+        let value = "";
+        if (key >= 0 && key < values.length) {
+            let nested = ComposerPageState.resolveNesting(values[key], values);
+            value = prevChar + ComposerPageState.quoteLines(nested) + insertNext;
+        } else {
+            console.log("Regex error in denesting blockquotes: Invalid key");
+        }
+        return value;
+    });
+};
+
+ComposerPageState.quoteLines = function(text) {
+    let lines = text.split("\n");
+    for (let i = 0; i < lines.length; i++)
+        lines[i] = ComposerPageState.QUOTE_MARKER + lines[i];
+    return lines.join("\n");
+};
+
+
 var geary = new ComposerPageState();
 window.onload = function() {
     geary.loaded();


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