[geary/wip/728002-webkit2] Reimplement loading and cleaning message into WK2 composer web view.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/728002-webkit2] Reimplement loading and cleaning message into WK2 composer web view.
- Date: Thu, 1 Dec 2016 23:38:04 +0000 (UTC)
commit 9791910735b7069b4b41ca48b6c849211d06e1c4
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 2b24398..c5059c2 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -211,6 +211,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 333efbb..6979f30 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -150,52 +150,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;
@@ -239,14 +193,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; set; }
public ComposeType compose_type { get; private set; default = ComposeType.NEW_MESSAGE; }
@@ -283,6 +229,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;
@@ -484,14 +431,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();
@@ -517,8 +459,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);
@@ -834,10 +775,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();
@@ -1073,55 +1010,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
@@ -2042,11 +1930,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;
}
}
@@ -2244,11 +2128,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,
@@ -2323,14 +2238,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 28a8588..cc5bbd5 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]