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.
This commit is contained in:
parent
b02059795f
commit
3f90f7785a
6 changed files with 181 additions and 187 deletions
|
|
@ -210,6 +210,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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -137,13 +212,6 @@ public class ComposerWebView : ClientWebView {
|
|||
return ""; // XXX
|
||||
}
|
||||
|
||||
/**
|
||||
* ???
|
||||
*/
|
||||
public void load_finished_and_realised() {
|
||||
// XXX
|
||||
}
|
||||
|
||||
/**
|
||||
* ???
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -151,52 +151,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;
|
||||
|
||||
|
|
@ -240,14 +194,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; internal set; }
|
||||
|
||||
public ComposeType compose_type { get; private set; default = ComposeType.NEW_MESSAGE; }
|
||||
|
|
@ -286,6 +232,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;
|
||||
|
|
@ -489,14 +436,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();
|
||||
|
||||
|
|
@ -522,8 +464,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);
|
||||
|
|
@ -850,10 +791,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();
|
||||
|
||||
|
|
@ -1089,55 +1026,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
|
||||
|
|
@ -2054,11 +1942,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -2256,11 +2140,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,
|
||||
|
|
@ -2335,14 +2250,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);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
69
ui/composer-web-view.js
Normal file
69
ui/composer-web-view.js
Normal file
|
|
@ -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();
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue