[geary/wip/728002-webkit2: 44/96] Implement getting message selection for quoting and selection in WK2.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/728002-webkit2: 44/96] Implement getting message selection for quoting and selection in WK2.
- Date: Sat, 14 Jan 2017 12:15:31 +0000 (UTC)
commit f1a8410099f8f50cfb90dfdbc6f1feb0b76c1728
Author: Michael James Gratton <mike vee net>
Date: Thu Dec 1 12:06:44 2016 +1100
Implement getting message selection for quoting and selection in WK2.
* src/client/conversation-viewer/conversation-web-view.vala
(ConversationWebView): Remove has_selection method since we are
using the signal to specify if a selection was found or not. Update
call sites to use that.
(ConversationWebView::get_selection_for_find,
ConversationWebView::get_selection_for_quoting): Implement using calls
to web process JS methods.
* src/client/application/geary-controller.vala
(GearyController::create_reply_forward_widget): If we have a possible
message view to quote from, handle constructing the compser widget
asynchronously, when we know if we have a quote or not.
* src/client/conversation-viewer/conversation-viewer.vala:
(ConversationViewer::on_find_mode_changed): Handle getting text
selection for finds asynchonously.
* src/client/components/client-web-view.vala
(ClientWebView::get_string_result): New helper for getting string
values from JS calls.
* src/client/conversation-viewer/conversation-email.vala
(ConversationEmail::get_selection_for_quoting,
ConversationEmail::get_selection_for_find): Handxle errors when
obtaining selections from a message view.
* src/client/conversation-viewer/conversation-message.vala: Remove
methods that were simply passed through to the web view anyway. Update
call sies.
* src/client/web-process/util-conversation.vala: Port all remaining
functions to JS, remove.
* bindings/vapi/javascriptcore-4.0.vapi: Add methods needed to get
strings out of WebKit.JavascriptResult instances.
* ui/conversation-web-view.js: Implement selection functions in JS, minor
cleanup.
bindings/vapi/javascriptcore-4.0.vapi | 20 +++++-
src/CMakeLists.txt | 1 -
src/client/application/geary-controller.vala | 10 ++-
src/client/components/client-web-view.vala | 17 ++++
.../conversation-viewer/conversation-email.vala | 41 ++++++----
.../conversation-viewer/conversation-message.vala | 11 ---
.../conversation-viewer/conversation-viewer.vala | 12 ++-
.../conversation-viewer/conversation-web-view.vala | 15 ++--
src/client/web-process/util-conversation.vala | 78 --------------------
ui/conversation-web-view.js | 59 ++++++++++++++-
10 files changed, 137 insertions(+), 127 deletions(-)
---
diff --git a/bindings/vapi/javascriptcore-4.0.vapi b/bindings/vapi/javascriptcore-4.0.vapi
index 027d781..f17c5d1 100644
--- a/bindings/vapi/javascriptcore-4.0.vapi
+++ b/bindings/vapi/javascriptcore-4.0.vapi
@@ -19,6 +19,9 @@ namespace JS {
[CCode (cname = "JSValueToNumber")]
public double to_number(JS.Value value, out JS.Value exception);
+ [CCode (cname = "JSValueToStringCopy")]
+ public String to_string_copy(JS.Value value, out JS.Value exception);
+
[CCode (cname = "JSGlobalContextRelease")]
public bool release();
}
@@ -68,6 +71,21 @@ namespace JS {
[CCode (cname = "JSStringCreateWithUTF8CString")]
public String.create_with_utf8_cstring(string str);
-
+
+ [CCode (cname = "JSStringGetLength")]
+ public int String.get_length();
+
+ [CCode (cname = "JSStringGetMaximumUTF8CStringSize")]
+ public int String.get_maximum_utf8_cstring_size();
+
+ [CCode (cname = "JSStringGetUTF8CString")]
+ public void String.get_utf8_cstring(string* buffer, int bufferSize);
+
+ [CCode (cname = "JSStringRetain")]
+ public void String.retain();
+
+ [CCode (cname = "JSStringRelease")]
+ public void String.release();
+
}
}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 46c23b3..40651c1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -408,7 +408,6 @@ client/util/util-migrate.vala
set(WEB_PROCESS_SRC
client/web-process/web-process-extension.vala
client/web-process/util-composer.vala
-client/web-process/util-conversation.vala
client/web-process/util-webkit.vala
)
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 0c7148b..d221c47 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2278,11 +2278,15 @@ public class GearyController : Geary.BaseObject {
email_view = list_view.get_reply_target();
}
}
- string? quote = null;
+
if (email_view != null) {
- quote = email_view.get_selection_for_quoting();
+ email_view.get_selection_for_quoting.begin((obj, res) => {
+ string? quote = email_view.get_selection_for_quoting.end(res);
+ create_compose_widget(compose_type, email_view.email, quote);
+ });
+ } else {
+ create_compose_widget(compose_type, email_view.email, null);
}
- create_compose_widget(compose_type, email_view.email, quote);
}
private void create_compose_widget(ComposerWidget.ComposeType compose_type,
diff --git a/src/client/components/client-web-view.vala b/src/client/components/client-web-view.vala
index 2a49b8e..ff05cc7 100644
--- a/src/client/components/client-web-view.vala
+++ b/src/client/components/client-web-view.vala
@@ -100,6 +100,23 @@ public class ClientWebView : WebKit.WebView {
}
JS.Value? err = null;
return (int) context.to_number(value, out err);
+ // XXX check err
+ // XXX unref result?
+ }
+
+ protected static string get_string_result(WebKit.JavascriptResult result)
+ throws JSError {
+ JS.GlobalContext context = result.get_global_context();
+ JS.Value js_str_value = result.get_value();
+ JS.Value? err = null;
+ JS.String js_str = context.to_string_copy(js_str_value, out err);
+ // XXX check err
+ int len = js_str.get_maximum_utf8_cstring_size();
+ string value = string.nfill(len, 0);
+ js_str.get_utf8_cstring(value, len);
+ js_str.release();
+ debug("Got string: %s", value);
+ return value;
// XXX unref result?
}
diff --git a/src/client/conversation-viewer/conversation-email.vala
b/src/client/conversation-viewer/conversation-email.vala
index 30bc0bc..b1dafd7 100644
--- a/src/client/conversation-viewer/conversation-email.vala
+++ b/src/client/conversation-viewer/conversation-email.vala
@@ -575,19 +575,33 @@ public class ConversationEmail : Gtk.Box {
/**
* Returns user-selected body HTML from a message, if any.
*/
- public string? get_selection_for_quoting() {
- return (this.body_selection_message != null)
- ? this.body_selection_message.get_selection_for_quoting()
- : null;
+ public async string? get_selection_for_quoting() {
+ string? selection = null;
+ if (this.body_selection_message != null) {
+ try {
+ selection =
+ yield this.body_selection_message.web_view.get_selection_for_quoting();
+ } catch (Error err) {
+ debug("Failed to get selection for quoting: %s", err.message);
+ }
+ }
+ return selection;
}
/**
* Returns user-selected body text from a message, if any.
*/
- public string? get_selection_for_find() {
- return (this.body_selection_message != null)
- ? this.body_selection_message.get_selection_for_find()
- : null;
+ public async string? get_selection_for_find() {
+ string? selection = null;
+ if (this.body_selection_message != null) {
+ try {
+ selection =
+ yield this.body_selection_message.web_view.get_selection_for_find();
+ } catch (Error err) {
+ debug("Failed to get selection for find: %s", err.message);
+ }
+ }
+ return selection;
}
/**
@@ -630,8 +644,9 @@ public class ConversationEmail : Gtk.Box {
this.message_bodies_loaded = true;
}
});
- view.web_view.selection_changed.connect(() => {
- on_message_selection_changed(view);
+ view.web_view.selection_changed.connect((has_selection) => {
+ this.body_selection_message = has_selection ? view : null;
+ body_selection_changed(has_selection);
});
}
@@ -766,12 +781,6 @@ public class ConversationEmail : Gtk.Box {
contact_store.mark_contacts_async.begin(contact_list, flags, null);
}
- private void on_message_selection_changed(ConversationMessage view) {
- bool has_selection = view.web_view.has_selection();
- this.body_selection_message = has_selection ? view : null;
- body_selection_changed(has_selection);
- }
-
[GtkCallback]
private void on_attachments_child_activated(Gtk.FlowBox view,
Gtk.FlowBoxChild child) {
diff --git a/src/client/conversation-viewer/conversation-message.vala
b/src/client/conversation-viewer/conversation-message.vala
index 7c567d4..0b3e65f 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -514,17 +514,6 @@ public class ConversationMessage : Gtk.Grid {
web_view.get_find_controller().search_finish();
}
- internal string? get_selection_for_quoting() {
- return this.web_view.get_selection_for_quoting();
- }
-
- /**
- * Returns the current selection as a string, suitable for find.
- */
- internal string? get_selection_for_find() {
- return this.web_view.get_selection_for_find();
- }
-
private SimpleAction add_action(string name, bool enabled, VariantType? type = null) {
SimpleAction action = new SimpleAction(name, type);
action.set_enabled(enabled);
diff --git a/src/client/conversation-viewer/conversation-viewer.vala
b/src/client/conversation-viewer/conversation-viewer.vala
index 7ad8c45..aba2442 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -294,11 +294,13 @@ public class ConversationViewer : Gtk.Stack {
ConversationEmail? email_view =
this.current_list.get_selection_view();
if (email_view != null) {
- string text = email_view.get_selection_for_find();
- if (text != null) {
- this.conversation_find_entry.set_text(text);
- this.conversation_find_entry.select_region(0, -1);
- }
+ email_view.get_selection_for_find.begin((obj, res) => {
+ string text = email_view.get_selection_for_find.end(res);
+ if (text != null) {
+ this.conversation_find_entry.set_text(text);
+ this.conversation_find_entry.select_region(0, -1);
+ }
+ });
}
} else {
// Find was disabled
diff --git a/src/client/conversation-viewer/conversation-web-view.vala
b/src/client/conversation-viewer/conversation-web-view.vala
index bbfc951..0a84c18 100644
--- a/src/client/conversation-viewer/conversation-web-view.vala
+++ b/src/client/conversation-viewer/conversation-web-view.vala
@@ -39,23 +39,20 @@ public class ConversationWebView : ClientWebView {
load_html(html, null);
}
- public bool has_selection() {
- bool has_selection = false; // XXX set me
- return has_selection;
- }
-
/**
* Returns the current selection, for prefill as find text.
*/
- public string get_selection_for_find() {
- return ""; // XXX
+ public async string get_selection_for_find() throws Error{
+ WebKit.JavascriptResult result = yield this.run_javascript("geary.getSelectionForFind();", null);
+ return get_string_result(result);
}
/**
* Returns the current selection, for quoting in a message.
*/
- public string get_selection_for_quoting() {
- return ""; // XXX
+ public async string get_selection_for_quoting() throws Error {
+ WebKit.JavascriptResult result = yield this.run_javascript("geary.getSelectionForQuoting();", null);
+ return get_string_result(result);
}
/**
diff --git a/ui/conversation-web-view.js b/ui/conversation-web-view.js
index 15db7fb..38e70af 100644
--- a/ui/conversation-web-view.js
+++ b/ui/conversation-web-view.js
@@ -1,4 +1,5 @@
/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
* Copyright 2016 Michael Gratton <mike vee net>
*
* This software is licensed under the GNU Lesser General Public License
@@ -20,8 +21,8 @@ ConversationPageState.prototype = {
this.updateDirection();
this.createControllableQuotes();
this.wrapSignature();
- // Call after so we continue to a preferred size update after
- // munging the HTML above.
+ // Chain up here so we continue to a preferred size update
+ // after munging the HTML above.
PageState.prototype.loaded.apply(this, []);
},
/**
@@ -49,7 +50,9 @@ ConversationPageState.prototype = {
// top level blockquote
if (!ConversationPageState.isDescendantOf(blockquote, "BLOCKQUOTE")) {
var quoteContainer = document.createElement("DIV");
- quoteContainer.classList.add("geary-quote-container");
+ quoteContainer.classList.add(
+ ConversationPageState.QUOTE_CONTAINER_CLASS
+ );
// Only make it controllable if the quote is tall enough
if (blockquote.offsetHeight > 50) {
@@ -116,9 +119,59 @@ ConversationPageState.prototype = {
} while (elem != null);
parent.appendChild(signatureContainer);
}
+ },
+ getSelectionForQuoting: function() {
+ var quote = null;
+ var selection = window.getSelection();
+ if (!selection.isCollapsed) {
+ var range = selection.getRangeAt(0);
+ var dummy = document.createElement("DIV");
+ var includeDummy = false;
+ var ancestor = range.commonAncestorContainer;
+ if (ancestor.nodeType != Node.ELEMENT_NODE) {
+ ancestor = ancestor.parentNode;
+ // If the selection is part of a plain text message,
+ // we have to stick it in an appropriately styled div,
+ // so that new lines are preserved.
+ if (ConversationPageState.isDescendantOf(ancestor, ".plaintext")) {
+ dummy.classList.add("plaintext");
+ dummy.setAttribute("style", "white-space: pre-wrap;");
+ includeDummy = true;
+ }
+ dummy.appendChild(range.cloneContents());
+
+ // Remove the chrome we put around quotes, leaving
+ // only the blockquote element.
+ var quotes = dummy.querySelectorAll(
+ "." + ConversationPageState.QUOTE_CONTAINER_CLASS
+ );
+ for (var i = 0; i < quotes.length; i++) {
+ var div = quotes.item(i);
+ var blockquote = div.querySelector("blockquote");
+ div.parentElement().replaceChild(blockquote, div);
+ }
+
+ quote = includeDummy ? dummy.outerHTML : dummy.innerHTML;
+ }
+ }
+ return quote;
+ },
+ getSelectionForFind: function() {
+ var value = null;
+ var selection = window.getSelection();
+
+ if (selection.rangeCount > 0) {
+ var range = selection.getRangeAt(0);
+ value = range.toString().trim();
+ if (value == "") {
+ value = null;
+ }
+ }
+ return value;
}
};
+ConversationPageState.QUOTE_CONTAINER_CLASS = "geary-quote-container";
ConversationPageState.isDescendantOf = function(node, ancestorTag) {
var ancestor = node.parentNode;
while (ancestor != null) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]