diff --git a/src/client/composer/composer-web-view.vala b/src/client/composer/composer-web-view.vala
index 6e210c10..20593a98 100644
--- a/src/client/composer/composer-web-view.vala
+++ b/src/client/composer/composer-web-view.vala
@@ -385,6 +385,21 @@ public class ComposerWebView : ClientWebView {
this.run_javascript.begin("geary.deleteQuotedMessage();", null);
}
+ /**
+ * Determines if the editor content contains an attachment keyword.
+ */
+ public async bool contains_attachment_keywords(string keyword_spec, string subject) {
+ try {
+ return WebKitUtil.to_bool(
+ yield run_javascript("geary.containsAttachmentKeyword(\"%s\", \"%s\");"
+ .printf(keyword_spec, subject), null)
+ );
+ } catch (Error err) {
+ debug("Error checking or attchment keywords: %s", err.message);
+ return false;
+ }
+ }
+
/**
* Returns the editor content as an HTML string.
*/
@@ -483,13 +498,6 @@ public class ComposerWebView : ClientWebView {
// XXX
}
- /**
- * ???
- */
- public string get_block_quote_representation() {
- return ""; // 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 f91e8387..7a44bcd5 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -160,14 +160,12 @@ public class ComposerWidget : Gtk.EventBox {
private const string URI_LIST_MIME_TYPE = "text/uri-list";
private const string FILE_URI_PREFIX = "file://";
- public const string ATTACHMENT_KEYWORDS_SUFFIX = ".doc|.pdf|.xls|.ppt|.rtf|.pps";
-
- // A list of keywords, separated by pipe ("|") characters, that suggest an attachment; since
- // this is full-word checking, include all variants of each word. No spaces are allowed.
- public const string ATTACHMENT_KEYWORDS_LOCALIZED = _("attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|enclosing|encloses|enclosure|enclosures");
-
- private delegate bool CompareStringFunc(string key, string token);
-
+ // Translators: This is list of keywords, separated by pipe ("|")
+ // characters, that suggest an attachment; since this is full-word
+ // checking, include all variants of each word. No spaces are
+ // allowed.
+ private const string ATTACHMENT_KEYWORDS_LOCALIZED = _("attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|enclosing|encloses|enclosure|enclosures");
+
public Geary.Account account { get; private set; }
public Geary.RFC822.MailboxAddresses from { get; private set; }
@@ -1218,82 +1216,6 @@ public class ComposerWidget : Gtk.EventBox {
return check_send_on_return(event) && base.key_press_event(event);
}
- // compares all keys to all tokens according to user-supplied comparison function
- // Returns true if found
- private bool search_tokens(string[] keys, string[] tokens, CompareStringFunc cmp_func,
- out string? found_key, out string? found_token) {
- foreach (string key in keys) {
- foreach (string token in tokens) {
- if (cmp_func(key, token)) {
- found_key = key;
- found_token = token;
-
- return true;
- }
- }
- }
-
- found_key = null;
- found_token = null;
-
- return false;
- }
-
- private bool email_contains_attachment_keywords() {
- // Filter out all content contained in block quotes
- string filtered = @"$subject\n";
- filtered += this.editor.get_block_quote_representation();
-
- Regex url_regex = null;
- try {
- // Prepare to ignore urls later
- url_regex = new Regex(Geary.HTML.URL_REGEX, RegexCompileFlags.CASELESS);
- } catch (Error error) {
- debug("Error building regex in keyword checker: %s", error.message);
- }
-
- string[] suffix_keys = ATTACHMENT_KEYWORDS_SUFFIX.casefold().split("|");
- string[] full_word_keys = ATTACHMENT_KEYWORDS_LOCALIZED.casefold().split("|");
-
- foreach (string line in filtered.split("\n")) {
- // Stop looking once we hit forwarded content
- if (line.has_prefix("--")) {
- break;
- }
-
- // casefold line, strip start and ending whitespace, then tokenize by whitespace
- string folded = line.casefold().strip();
- string[] tokens = folded.split_set(" \t");
-
- // search for full-word matches
- string? found_key, found_token;
- bool found = search_tokens(full_word_keys, tokens, (key, token) => {
- return key == token;
- }, out found_key, out found_token);
-
- // if not found, search for suffix matches
- if (!found) {
- found = search_tokens(suffix_keys, tokens, (key, token) => {
- return token.has_suffix(key);
- }, out found_key, out found_token);
- }
-
- if (found) {
- try {
- // Make sure the match isn't coming from a url
- if (found_key in url_regex.replace(folded, -1, 0, "")) {
- return true;
- }
- } catch (Error error) {
- debug("Regex replacement error in keyword checker: %s", error.message);
- return true;
- }
- }
- }
-
- return false;
- }
-
private async bool should_send() {
bool has_subject = !Geary.String.is_empty(subject.strip());
bool has_attachment = this.attached_files.size > 0;
@@ -1312,7 +1234,9 @@ public class ComposerWidget : Gtk.EventBox {
confirmation = _("Send message with an empty subject?");
} else if (!has_body && !has_attachment) {
confirmation = _("Send message with an empty body?");
- } else if (!has_attachment && email_contains_attachment_keywords()) {
+ } else if (!has_attachment &&
+ yield this.editor.contains_attachment_keywords(
+ ATTACHMENT_KEYWORDS_LOCALIZED, this.subject)) {
confirmation = _("Send message without an attachment?");
}
if (confirmation != null) {
diff --git a/src/client/web-process/util-composer.vala b/src/client/web-process/util-composer.vala
index 28554341..ac340ff0 100644
--- a/src/client/web-process/util-composer.vala
+++ b/src/client/web-process/util-composer.vala
@@ -19,10 +19,6 @@ namespace Util.Composer {
private const string EDITING_DELETE_CONTAINER_ID = "WebKit-Editing-Delete-Container";
- public string get_block_quote_representation(WebKit.WebPage page) {
- return Util.DOM.get_text_representation(page.get_dom_document(), "blockquote");
- }
-
public void linkify_document(WebKit.WebPage page) {
Util.DOM.linkify_document(page.get_dom_document());
}
diff --git a/src/client/web-process/util-webkit.vala b/src/client/web-process/util-webkit.vala
index 1f2c9230..ea202bf2 100644
--- a/src/client/web-process/util-webkit.vala
+++ b/src/client/web-process/util-webkit.vala
@@ -8,91 +8,6 @@
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):";
namespace Util.DOM {
- public WebKit.DOM.HTMLElement? select(WebKit.DOM.Node node, string selector) {
- try {
- if (node is WebKit.DOM.Document) {
- return (node as WebKit.DOM.Document).query_selector(selector) as WebKit.DOM.HTMLElement;
- } else {
- return (node as WebKit.DOM.Element).query_selector(selector) as WebKit.DOM.HTMLElement;
- }
- } catch (Error error) {
- debug("Error selecting element %s: %s", selector, error.message);
- return null;
- }
- }
-
- public WebKit.DOM.HTMLElement? clone_node(WebKit.DOM.Node node, bool deep = true) {
- WebKit.DOM.HTMLElement? clone = null;
- try {
- clone = node.clone_node(deep) as WebKit.DOM.HTMLElement;
- } catch (Error err) {
- debug("Error selecting cloning node: %s", err.message);
- }
- return clone;
- }
-
- // Returns the text contained in the DOM document, after ignoring tags of type "exclude"
- // and padding newlines where appropriate. Used to scan for attachment keywords.
- public string get_text_representation(WebKit.DOM.Document doc, string exclude) {
- WebKit.DOM.HTMLElement? copy = Util.DOM.clone_node(doc.get_body());
- if (copy == null) {
- return "";
- }
-
- // Keep deleting the next excluded element until there are none left
- while (true) {
- WebKit.DOM.HTMLElement? current = Util.DOM.select(copy, exclude);
- if (current == null) {
- break;
- }
-
- WebKit.DOM.Node parent = current.get_parent_node();
- try {
- parent.remove_child(current);
- } catch (Error error) {
- debug("Error removing blockquotes: %s", error.message);
- break;
- }
- }
-
- WebKit.DOM.NodeList node_list;
- try {
- node_list = copy.query_selector_all("br");
- } catch (Error error) {
- debug("Error finding
s: %s", error.message);
- return copy.get_inner_text();
- }
-
- // Replace
tags with newlines
- for (int i = 0; i < node_list.length; ++i) {
- WebKit.DOM.Node br = node_list.item(i);
- WebKit.DOM.Node parent = br.get_parent_node();
- try {
- parent.replace_child(doc.create_text_node("\n"), br);
- } catch (Error error) {
- debug("Error replacing
: %s", error.message);
- }
- }
-
- try {
- node_list = copy.query_selector_all("div");
- } catch (Error error) {
- debug("Error finding
inner quote+ +
some text
+ +some text + +--outerquote text
+"""); + try { + assert(WebKitUtil.to_bool(run_javascript( + @"geary.containsAttachmentKeyword(\"some\", \"subject text\");" + ))); + assert(WebKitUtil.to_bool(run_javascript( + @"geary.containsAttachmentKeyword(\"subject\", \"subject text\");" + ))); + assert(!WebKitUtil.to_bool(run_javascript( + @"geary.containsAttachmentKeyword(\"innerquote\", \"subject text\");" + ))); + assert(!WebKitUtil.to_bool(run_javascript( + @"geary.containsAttachmentKeyword(\"sig\", \"subject text\");" + ))); + assert(!WebKitUtil.to_bool(run_javascript( + @"geary.containsAttachmentKeyword(\"outerquote\", \"subject text\");" + ))); + } 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 = "
para
"; load_body_fixture(html); @@ -111,6 +150,59 @@ class ComposerPageStateTest : ClientWebViewTestCase