From 5732d23e98abaab27ed0a74cd5776b6f8e0d53f6 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Mon, 7 Oct 2019 23:42:49 +1100 Subject: [PATCH] ui/composer-web-view.js: Add element blacklist to htmlToText Update ComposerPageState.htmlToText() to allow ignoring certain elements if needed. Update tests to test the function directly rather than going through ComposerPageState.toText(). --- test/js/composer-page-state-test.vala | 123 +++++++++++++++++--------- ui/composer-web-view.js | 31 ++++--- 2 files changed, 100 insertions(+), 54 deletions(-) diff --git a/test/js/composer-page-state-test.vala b/test/js/composer-page-state-test.vala index 8c1fcf08..bfe4978e 100644 --- a/test/js/composer-page-state-test.vala +++ b/test/js/composer-page-state-test.vala @@ -13,6 +13,10 @@ class ComposerPageStateTest : ClientWebViewTestCase { public ComposerPageStateTest() { base("ComposerPageStateTest"); + add_test("html_to_text", html_to_text); + add_test("html_to_text_with_quote", html_to_text_with_quote); + add_test("html_to_text_with_nested_quote", html_to_text_with_nested_quote); + add_test("html_to_text_with_blacklist", html_to_text_with_blacklist); add_test("edit_context_font", edit_context_font); add_test("edit_context_link", edit_context_link); add_test("indent_line", indent_line); @@ -20,9 +24,6 @@ class ComposerPageStateTest : ClientWebViewTestCase { add_test("clean_content", clean_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("contains_keywords", contains_keywords); add_test("replace_non_breaking_space", replace_non_breaking_space); @@ -33,6 +34,84 @@ class ComposerPageStateTest : ClientWebViewTestCase { } } + public void html_to_text() throws Error { + load_body_fixture("

para

"); + try { + assert( + Util.JS.to_string( + run_javascript( + @"ComposerPageState.htmlToText(window.document.body);" + ).get_js_value() + ) == "para\n\n\n\n" + ); + } catch (Util.JS.Error err) { + print("Util.JS.Error: %s\n", err.message); + assert_not_reached(); + } catch (Error err) { + print("WKError: %s\n", err.message); + assert_not_reached(); + } + } + + public void html_to_text_with_quote() throws Error { + unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER; + load_body_fixture("

pre

quote

post

"); + try { + assert( + Util.JS.to_string( + run_javascript( + "ComposerPageState.htmlToText(window.document.body);" + ).get_js_value() + ) == @"pre\n\n$(q_marker)quote\n$(q_marker)\npost\n\n\n\n" + ); + } catch (Util.JS.Error err) { + print("Util.JS.Error: %s", err.message); + assert_not_reached(); + } catch (Error err) { + print("WKError: %s", err.message); + assert_not_reached(); + } + } + + public void html_to_text_with_nested_quote() throws Error { + unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER; + load_body_fixture("

pre

quote1

quote2

post

"); + try { + assert( + Util.JS.to_string( + run_javascript( + "ComposerPageState.htmlToText(window.document.body)" + ).get_js_value() + ) == @"pre\n\n$(q_marker)quote1\n$(q_marker)\n$(q_marker)$(q_marker)quote2\n$(q_marker)$(q_marker)\npost\n\n\n\n" + ); + } catch (Util.JS.Error err) { + print("Util.JS.Error: %s\n", err.message); + assert_not_reached(); + } catch (Error err) { + print("WKError: %s\n", err.message); + assert_not_reached(); + } + } + + public void html_to_text_with_blacklist() throws Error { + load_body_fixture("

pre

quote1

quote2

post

"); + try { + assert( + Util.JS.to_string( + run_javascript( + "ComposerPageState.htmlToText(window.document.body, [\"blockquote\"])" + ).get_js_value() + ) == @"pre\n\npost\n\n\n\n" + ); + } catch (Util.JS.Error err) { + print("Util.JS.Error: %s\n", err.message); + assert_not_reached(); + } catch (Error err) { + print("WKError: %s\n", err.message); + assert_not_reached(); + } + } + public void edit_context_link() throws Error { string html = "para"; load_body_fixture(html); @@ -222,44 +301,6 @@ unknown://example6.com } } - public void get_text_with_quote() throws Error { - unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER; - load_body_fixture("

pre

quote

post

"); - try { - assert( - Util.JS.to_string( - run_javascript(@"window.geary.getText();") - .get_js_value() - ) == @"pre\n\n$(q_marker)quote\n$(q_marker)\npost\n\n\n\n" - ); - } catch (Util.JS.Error err) { - print("Util.JS.Error: %s", err.message); - assert_not_reached(); - } catch (Error err) { - print("WKError: %s", err.message); - assert_not_reached(); - } - } - - public void get_text_with_nested_quote() throws Error { - unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER; - load_body_fixture("

pre

quote1

quote2

post

"); - try { - assert( - Util.JS.to_string( - run_javascript(@"window.geary.getText();") - .get_js_value() - ) == @"pre\n\n$(q_marker)quote1\n$(q_marker)\n$(q_marker)$(q_marker)quote2\n$(q_marker)$(q_marker)\npost\n\n\n\n" - ); - } catch (Util.JS.Error err) { - print("Util.JS.Error: %s\n", err.message); - assert_not_reached(); - } catch (Error err) { - print("WKError: %s\n", err.message); - assert_not_reached(); - } - } - public void contains_keywords() throws Error { load_body_fixture(); string complete_keys = """new Set(["keyword1", "keyword2"])"""; diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js index a848bb1a..e56b48e2 100644 --- a/ui/composer-web-view.js +++ b/ui/composer-web-view.js @@ -460,11 +460,16 @@ ComposerPageState.cleanPart = function(part, removeIfEmpty) { * `ComposerPageState.QUOTE_MARKER`, where the number of markers indicates * the depth of nesting of the quote. */ -ComposerPageState.htmlToText = function(root) { +ComposerPageState.htmlToText = function(root, blacklist = []) { let parentStyle = window.getComputedStyle(root); let text = ""; for (let node of (root.childNodes || [])) { + let nodeName = node.nodeName.toLowerCase(); + if (blacklist.includes(nodeName)) { + continue; + } + let isBlock = ( node instanceof Element && window.getComputedStyle(node).display == "block" @@ -476,7 +481,7 @@ ComposerPageState.htmlToText = function(root) { text += "\n"; } } - switch (node.nodeName.toLowerCase()) { + switch (nodeName) { case "#text": let nodeText = node.nodeValue; switch (parentStyle.whiteSpace) { @@ -500,24 +505,24 @@ ComposerPageState.htmlToText = function(root) { break; case "a": if (node.closest("body.plain")) { - text += ComposerPageState.htmlToText(node); + text += ComposerPageState.htmlToText(node, blacklist); } else if (node.textContent == node.href) { text += "<" + node.href + ">"; } else { - text += ComposerPageState.htmlToText(node); + text += ComposerPageState.htmlToText(node, blacklist); text += " <" + node.href + ">"; } break; case "b": case "strong": if (node.closest("body.plain")) { - text += ComposerPageState.htmlToText(node); + text += ComposerPageState.htmlToText(node, blacklist); } else { - text += "*" + ComposerPageState.htmlToText(node) + "*"; + text += "*" + ComposerPageState.htmlToText(node, blacklist) + "*"; } break; case "blockquote": - let bqText = ComposerPageState.htmlToText(node); + let bqText = ComposerPageState.htmlToText(node, blacklist); // If there is a newline at the end of the quote, remove it // After this switch we ensure that there is a newline after the quote bqText = bqText.replace(/\n$/, ""); @@ -532,23 +537,23 @@ ComposerPageState.htmlToText = function(root) { case "i": case "em": if (node.closest("body.plain")) { - text += ComposerPageState.htmlToText(node); + text += ComposerPageState.htmlToText(node, blacklist); } else { - text += "/" + ComposerPageState.htmlToText(node) + "/"; + text += "/" + ComposerPageState.htmlToText(node, blacklist) + "/"; } break; case "u": if (node.closest("body.plain")) { - text += ComposerPageState.htmlToText(node); + text += ComposerPageState.htmlToText(node, blacklist); } else { - text += "_" + ComposerPageState.htmlToText(node) + "_"; + text += "_" + ComposerPageState.htmlToText(node, blacklist) + "_"; } break; case "#comment": - case "style": + case "style": break; default: - text += ComposerPageState.htmlToText(node); + text += ComposerPageState.htmlToText(node, blacklist); break; } if (isBlock) {