Activated by --inspector, this pop ups a WebKit HTML/CSS inspector that's quite useful for development and debugging.
1219 lines
51 KiB
Vala
1219 lines
51 KiB
Vala
/* Copyright 2011-2012 Yorba Foundation
|
|
*
|
|
* This software is licensed under the GNU Lesser General Public License
|
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
*/
|
|
|
|
public class ConversationViewer : Gtk.Box {
|
|
public const Geary.Email.Field REQUIRED_FIELDS =
|
|
Geary.Email.Field.HEADER
|
|
| Geary.Email.Field.BODY
|
|
| Geary.Email.Field.ORIGINATORS
|
|
| Geary.Email.Field.RECEIVERS
|
|
| Geary.Email.Field.SUBJECT
|
|
| Geary.Email.Field.DATE
|
|
| Geary.Email.Field.FLAGS
|
|
| Geary.Email.Field.PREVIEW;
|
|
|
|
private const int ATTACHMENT_PREVIEW_SIZE = 50;
|
|
private const string MESSAGE_CONTAINER_ID = "message_container";
|
|
private const string SELECTION_COUNTER_ID = "multiple_messages";
|
|
|
|
// Fired when the user clicks a link.
|
|
public signal void link_selected(string link);
|
|
|
|
// Fired when the user clicks "reply" in the message menu.
|
|
public signal void reply_to_message(Geary.Email message);
|
|
|
|
// Fired when the user clicks "reply all" in the message menu.
|
|
public signal void reply_all_message(Geary.Email message);
|
|
|
|
// Fired when the user clicks "forward" in the message menu.
|
|
public signal void forward_message(Geary.Email message);
|
|
|
|
// Fired when the user marks a message.
|
|
public signal void mark_message(Geary.Email message, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove);
|
|
|
|
// Fired when the user opens an attachment.
|
|
public signal void open_attachment(Geary.Attachment attachment);
|
|
|
|
// Fired when the user wants to save one or more attachments.
|
|
public signal void save_attachments(Gee.List<Geary.Attachment> attachment);
|
|
|
|
// List of emails in this view.
|
|
public Gee.TreeSet<Geary.Email> messages { get; private set; default =
|
|
new Gee.TreeSet<Geary.Email>((CompareFunc<Geary.Email>) Geary.Email.compare_date_ascending); }
|
|
|
|
// The HTML viewer to view the emails.
|
|
public ConversationWebView web_view { get; private set; }
|
|
|
|
// The Info Bar to be shown when an external image is blocked.
|
|
public Gtk.InfoBar external_images_info_bar { get; private set; }
|
|
|
|
// Label for displaying overlay messages.
|
|
private Gtk.Label message_overlay_label;
|
|
|
|
// Maps emails to their corresponding elements.
|
|
private Gee.HashMap<Geary.EmailIdentifier, WebKit.DOM.HTMLElement> email_to_element = new
|
|
Gee.HashMap<Geary.EmailIdentifier, WebKit.DOM.HTMLElement>(Geary.Hashable.hash_func,
|
|
Geary.Equalable.equal_func);
|
|
|
|
private string? hover_url = null;
|
|
private Gtk.Menu? context_menu = null;
|
|
private Gtk.Menu? message_menu = null;
|
|
private Gtk.Menu? attachment_menu = null;
|
|
private weak Geary.Folder? current_folder = null;
|
|
private Geary.AccountInformation? current_account_information = null;
|
|
|
|
public ConversationViewer() {
|
|
Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
|
|
|
|
external_images_info_bar = new Gtk.InfoBar.with_buttons(
|
|
_("_Show Images"), Gtk.ResponseType.OK, _("_Cancel"), Gtk.ResponseType.CANCEL);
|
|
external_images_info_bar.no_show_all = true;
|
|
external_images_info_bar.response.connect(on_external_images_info_bar_response);
|
|
external_images_info_bar.message_type = Gtk.MessageType.WARNING;
|
|
Gtk.Box? external_images_info_bar_content_area =
|
|
external_images_info_bar.get_content_area() as Gtk.Box;
|
|
if (external_images_info_bar_content_area != null) {
|
|
Gtk.Label label = new Gtk.Label(_("This message contains images. Do you want to show them?"));
|
|
label.set_line_wrap(true);
|
|
external_images_info_bar_content_area.add(label);
|
|
label.show_all();
|
|
}
|
|
pack_start(external_images_info_bar, false, false);
|
|
|
|
web_view = new ConversationWebView();
|
|
|
|
web_view.hovering_over_link.connect(on_hovering_over_link);
|
|
web_view.realize.connect( () => { web_view.get_vadjustment().value_changed.connect(mark_read); });
|
|
web_view.size_allocate.connect(mark_read);
|
|
|
|
web_view.image_load_requested.connect(on_image_load_requested);
|
|
web_view.link_selected.connect((link) => { link_selected(link); });
|
|
|
|
Gtk.ScrolledWindow conversation_viewer_scrolled = new Gtk.ScrolledWindow(null, null);
|
|
conversation_viewer_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
|
|
conversation_viewer_scrolled.add(web_view);
|
|
|
|
Gtk.Overlay message_overlay = new Gtk.Overlay();
|
|
message_overlay.add(conversation_viewer_scrolled);
|
|
|
|
message_overlay_label = new Gtk.Label(null);
|
|
message_overlay_label.ellipsize = Pango.EllipsizeMode.MIDDLE;
|
|
message_overlay_label.halign = Gtk.Align.START;
|
|
message_overlay_label.valign = Gtk.Align.END;
|
|
message_overlay.add_overlay(message_overlay_label);
|
|
|
|
pack_start(message_overlay);
|
|
}
|
|
|
|
private void on_image_load_requested() {
|
|
external_images_info_bar.show();
|
|
}
|
|
|
|
private void on_external_images_info_bar_response(Gtk.InfoBar sender, int response_id) {
|
|
web_view.apply_load_external_images(response_id == Gtk.ResponseType.OK);
|
|
sender.hide();
|
|
}
|
|
|
|
public Geary.Email? get_last_message() {
|
|
return messages.is_empty ? null : messages.last();
|
|
}
|
|
|
|
// Removes all displayed e-mails from the view.
|
|
public void clear(Geary.Folder? new_folder, Geary.AccountInformation? account_information) {
|
|
// Remove all messages from DOM.
|
|
try {
|
|
foreach (WebKit.DOM.HTMLElement element in email_to_element.values) {
|
|
if (element.get_parent_element() != null)
|
|
element.get_parent_element().remove_child(element);
|
|
}
|
|
} catch (Error e) {
|
|
debug("Error clearing message viewer: %s", e.message);
|
|
}
|
|
email_to_element.clear();
|
|
messages.clear();
|
|
|
|
current_folder = new_folder;
|
|
current_account_information = account_information;
|
|
}
|
|
|
|
// Converts an email ID into HTML ID used by the <div> for the email.
|
|
private string get_div_id(Geary.EmailIdentifier id) {
|
|
return "message_%s".printf(id.to_string());
|
|
}
|
|
|
|
public void show_multiple_selected(uint selected_count) {
|
|
// Remove any messages and hide the message container, then show the counter.
|
|
clear(current_folder, current_account_information);
|
|
try {
|
|
web_view.hide_element_by_id(MESSAGE_CONTAINER_ID);
|
|
web_view.show_element_by_id(SELECTION_COUNTER_ID);
|
|
|
|
// Update the counter's count.
|
|
WebKit.DOM.HTMLElement counter =
|
|
web_view.get_dom_document().get_element_by_id("selection_counter") as WebKit.DOM.HTMLElement;
|
|
if (selected_count == 0) {
|
|
counter.set_inner_html(_("No conversations selected."));
|
|
} else {
|
|
counter.set_inner_html(_("%u conversations selected.").printf(selected_count));
|
|
}
|
|
} catch (Error e) {
|
|
debug("Error updating counter: %s", e.message);
|
|
}
|
|
}
|
|
|
|
public void add_message(Geary.Email email) {
|
|
web_view.apply_load_external_images(false);
|
|
|
|
// Make sure the message container is showing and the multi-message counter hidden.
|
|
try {
|
|
web_view.show_element_by_id(MESSAGE_CONTAINER_ID);
|
|
web_view.hide_element_by_id(SELECTION_COUNTER_ID);
|
|
} catch (Error e) {
|
|
debug("Error showing/hiding containers: %s", e.message);
|
|
}
|
|
|
|
if (messages.contains(email))
|
|
return;
|
|
|
|
string message_id = get_div_id(email.id);
|
|
string header = "";
|
|
|
|
WebKit.DOM.Node insert_before = web_view.container.get_last_child();
|
|
|
|
messages.add(email);
|
|
Geary.Email? higher = messages.higher(email);
|
|
if (higher != null)
|
|
insert_before = web_view.get_dom_document().get_element_by_id(get_div_id(higher.id));
|
|
|
|
WebKit.DOM.HTMLElement div_email_container;
|
|
WebKit.DOM.HTMLElement div_message;
|
|
try {
|
|
// The HTML is like this:
|
|
// <div id="$MESSAGE_ID" class="email">
|
|
// <div class="geary_spacer"></div>
|
|
// <div class="email_container">
|
|
// <div class="button_bar">
|
|
// <div class="starred button"><img class="icon" /></div>
|
|
// <div class="unstarred button"><img class="icon" /></div>
|
|
// <div class="menu button"><img class="icon" /></div>
|
|
// </div>
|
|
// <table>$HEADER</table>
|
|
// <span>
|
|
// $EMAIL_BODY
|
|
//
|
|
// <div class="signature">$SIGNATURE</div>
|
|
//
|
|
// <div class="quote_container controllable">
|
|
// <div class="shower">[show]</div>
|
|
// <div class="hider">[hide]</div>
|
|
// <div class="quote">$QUOTE</div>
|
|
// </div>
|
|
// </span>
|
|
// </div>
|
|
// </div>
|
|
div_message = Util.DOM.clone_select(web_view.get_dom_document(), "#email_template");
|
|
div_message.set_attribute("id", message_id);
|
|
web_view.container.insert_before(div_message, insert_before);
|
|
div_email_container = Util.DOM.select(div_message, "div.email_container");
|
|
if (email.is_unread() == Geary.Trillian.FALSE) {
|
|
div_message.get_class_list().add("hide");
|
|
}
|
|
} catch (Error setup_error) {
|
|
warning("Error setting up webkit: %s", setup_error.message);
|
|
|
|
return;
|
|
}
|
|
|
|
email_to_element.set(email.id, div_message);
|
|
|
|
insert_header_address(ref header, _("From:"), email.from != null ? email.from : email.sender,
|
|
true);
|
|
|
|
// Only include to string if it's not just this account.
|
|
// TODO: multiple accounts.
|
|
if (email.to != null && current_account_information != null) {
|
|
if (!(email.to.get_all().size == 1 && email.to.get_all().get(0).address == current_account_information.email))
|
|
insert_header_address(ref header, _("To:"), email.to);
|
|
}
|
|
|
|
if (email.cc != null) {
|
|
insert_header_address(ref header, _("Cc:"), email.cc);
|
|
}
|
|
|
|
if (email.bcc != null) {
|
|
insert_header_address(ref header, _("Bcc:"), email.bcc);
|
|
}
|
|
|
|
if (email.subject != null)
|
|
insert_header(ref header, _("Subject:"), email.get_subject_as_string());
|
|
|
|
if (email.date != null)
|
|
insert_header_date(ref header, _("Date:"), email.date.value, true);
|
|
|
|
// Add the avatar.
|
|
Geary.RFC822.MailboxAddress? primary = email.get_primary_originator();
|
|
if (primary != null) {
|
|
try {
|
|
WebKit.DOM.HTMLImageElement icon = Util.DOM.select(div_message, ".avatar")
|
|
as WebKit.DOM.HTMLImageElement;
|
|
icon.set_attribute("src",
|
|
Gravatar.get_image_uri(primary, Gravatar.Default.MYSTERY_MAN, 48));
|
|
} catch (Error error) {
|
|
debug("Failed to inject avatar URL: %s", error.message);
|
|
}
|
|
}
|
|
|
|
// Insert the preview text.
|
|
try {
|
|
WebKit.DOM.HTMLElement preview =
|
|
Util.DOM.select(div_message, ".header_container .preview");
|
|
string preview_str = email.get_preview_as_string();
|
|
if (preview_str.length == Geary.Email.MAX_PREVIEW_BYTES) {
|
|
preview_str += "…";
|
|
}
|
|
preview.set_inner_text(Geary.String.reduce_whitespace(preview_str));
|
|
} catch (Error error) {
|
|
debug("Failed to add preview text: %s", error.message);
|
|
}
|
|
|
|
string body_text = "";
|
|
try {
|
|
body_text = email.get_message().get_first_mime_part_of_content_type("text/html").to_string();
|
|
body_text = insert_html_markup(body_text, email);
|
|
} catch (Error err) {
|
|
try {
|
|
body_text = linkify_and_escape_plain_text(email.get_message().
|
|
get_first_mime_part_of_content_type("text/plain").to_string());
|
|
body_text = insert_plain_text_markup(body_text);
|
|
} catch (Error err2) {
|
|
debug("Could not get message text. %s", err2.message);
|
|
}
|
|
}
|
|
|
|
// Graft header and email body into the email container.
|
|
try {
|
|
WebKit.DOM.HTMLElement table_header =
|
|
Util.DOM.select(div_email_container, ".header_container .header");
|
|
table_header.set_inner_html(header);
|
|
|
|
WebKit.DOM.HTMLElement span_body = Util.DOM.select(div_email_container, ".body");
|
|
span_body.set_inner_html(body_text);
|
|
|
|
} catch (Error html_error) {
|
|
warning("Error setting HTML for message: %s", html_error.message);
|
|
}
|
|
|
|
// Set attachment icon and add the attachments container if we have any attachments.
|
|
set_attachment_icon(div_message, email.attachments.size > 0);
|
|
if (email.attachments.size > 0) {
|
|
insert_attachments(div_message, email.attachments);
|
|
}
|
|
|
|
// Add classes according to the state of the email.
|
|
update_flags(email);
|
|
|
|
// Add animation class after other classes set, to avoid initial animation.
|
|
Idle.add(() => {
|
|
try {
|
|
div_message.get_class_list().add("animate");
|
|
} catch (Error error) {
|
|
debug("Could not enable animation class: %s", error.message);
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// Attach to the click events for hiding/showing quotes, opening the menu, and so forth.
|
|
bind_event(web_view, ".email", "contextmenu", (Callback) on_context_menu, this);
|
|
bind_event(web_view, ".quote_container > .hider", "click", (Callback) on_hide_quote_clicked);
|
|
bind_event(web_view, ".quote_container > .shower", "click", (Callback) on_show_quote_clicked);
|
|
bind_event(web_view, ".email_container .menu", "click", (Callback) on_menu_clicked, this);
|
|
bind_event(web_view, ".email_container .starred", "click", (Callback) on_unstar_clicked, this);
|
|
bind_event(web_view, ".email_container .unstarred", "click", (Callback) on_star_clicked, this);
|
|
bind_event(web_view, ".header .field .value", "click", (Callback) on_value_clicked, this);
|
|
bind_event(web_view, ".email .header_container", "click", (Callback) on_body_toggle_clicked, this);
|
|
bind_event(web_view, ".attachment_container .attachment", "click", (Callback) on_attachment_clicked, this);
|
|
bind_event(web_view, ".attachment_container .attachment", "contextmenu", (Callback) on_attachment_menu, this);
|
|
}
|
|
|
|
public void unhide_last_email() {
|
|
WebKit.DOM.HTMLElement last_email = (WebKit.DOM.HTMLElement) web_view.container.get_last_child().previous_sibling;
|
|
if (last_email != null) {
|
|
WebKit.DOM.DOMTokenList class_list = last_email.get_class_list();
|
|
try {
|
|
class_list.remove("hide");
|
|
} catch (Error error) {
|
|
// Expected, if not hidden
|
|
}
|
|
}
|
|
}
|
|
|
|
public void show_first_visible_email() {
|
|
// Select the first email element that does not have "hide" in its
|
|
// class list. The constraint on the id is required because there
|
|
// are some elements (like #selection_counter) that have the "email"
|
|
// class, but that are not messages.
|
|
WebKit.DOM.HTMLElement first_visible_email =
|
|
Util.DOM.select(web_view.get_dom_document(), ".email[id^=message_]:not(.hide)");
|
|
if (first_visible_email != null) {
|
|
// Select the sibling which, if it exists, is the previous hidden
|
|
// message. This is a way Geary has to say "hey, this conversation
|
|
// is not new: there are some messages above".
|
|
WebKit.DOM.HTMLElement first_visible_email_sibling =
|
|
(WebKit.DOM.HTMLElement) first_visible_email.previous_sibling;
|
|
if (first_visible_email_sibling != null) {
|
|
web_view.scroll_to_element(first_visible_email_sibling);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Geary.Email? get_email_from_element(WebKit.DOM.Element element) {
|
|
// First get the email container.
|
|
WebKit.DOM.Element? email_element = null;
|
|
try {
|
|
if (element.webkit_matches_selector(".email")) {
|
|
email_element = element;
|
|
} else {
|
|
email_element = closest_ancestor(element, ".email");
|
|
}
|
|
} catch (Error error) {
|
|
debug("Failed to find div.email from element: %s", error.message);
|
|
return null;
|
|
}
|
|
|
|
if (email_element == null)
|
|
return null;
|
|
|
|
// Next find the ID in the email-to-element map.
|
|
Geary.EmailIdentifier? email_id = null;
|
|
foreach (var entry in email_to_element.entries) {
|
|
if (entry.value == email_element) {
|
|
email_id = entry.key;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (email_id == null)
|
|
return null;
|
|
|
|
// Now lookup the email in our messages set.
|
|
foreach (Geary.Email message in messages) {
|
|
if (message.id == email_id)
|
|
return message;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private Geary.Attachment? get_attachment_from_element(WebKit.DOM.Element element) {
|
|
Geary.Email? email = get_email_from_element(element);
|
|
if (email == null)
|
|
return null;
|
|
|
|
try {
|
|
return email.get_attachment(int64.parse(element.get_attribute("data-attachment-id")));
|
|
} catch (Geary.EngineError err) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void set_attachment_icon(WebKit.DOM.HTMLElement container, bool show) {
|
|
try {
|
|
WebKit.DOM.DOMTokenList class_list = container.get_class_list();
|
|
Util.DOM.toggle_class(class_list, "attachment", show);
|
|
} catch (Error e) {
|
|
warning("Failed to set attachment icon: %s", e.message);
|
|
}
|
|
}
|
|
|
|
public void update_flags(Geary.Email email) {
|
|
// Nothing to do if we aren't displaying this email.
|
|
if (!email_to_element.has_key(email.id)) {
|
|
return;
|
|
}
|
|
|
|
Geary.EmailFlags flags = email.email_flags;
|
|
|
|
// Update the flags in our message set.
|
|
foreach (Geary.Email message in messages) {
|
|
if (message.id.equals(email.id)) {
|
|
message.set_flags(flags);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get the email div and update its state.
|
|
WebKit.DOM.HTMLElement container = email_to_element.get(email.id);
|
|
try {
|
|
WebKit.DOM.DOMTokenList class_list = container.get_class_list();
|
|
Util.DOM.toggle_class(class_list, "read", !flags.is_unread());
|
|
Util.DOM.toggle_class(class_list, "starred", flags.is_flagged());
|
|
} catch (Error e) {
|
|
warning("Failed to set classes on .email: %s", e.message);
|
|
}
|
|
}
|
|
|
|
private static void on_context_menu(WebKit.DOM.Element clicked_element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
Geary.Email email = conversation_viewer.get_email_from_element(clicked_element);
|
|
if (email != null)
|
|
conversation_viewer.show_context_menu(email, clicked_element);
|
|
}
|
|
|
|
private void show_context_menu(Geary.Email email, WebKit.DOM.Element clicked_element) {
|
|
context_menu = build_context_menu(email, clicked_element);
|
|
context_menu.show_all();
|
|
context_menu.popup(null, null, null, 0, 0);
|
|
}
|
|
|
|
private Gtk.Menu build_context_menu(Geary.Email email, WebKit.DOM.Element clicked_element) {
|
|
Gtk.Menu menu = new Gtk.Menu();
|
|
|
|
if (web_view.can_copy_clipboard()) {
|
|
// Add a menu item for copying the current selection.
|
|
Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("_Copy"));
|
|
item.activate.connect(on_copy_text);
|
|
menu.append(item);
|
|
}
|
|
|
|
if (hover_url != null) {
|
|
// Add a menu item for copying the link.
|
|
Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("Copy _Link"));
|
|
item.activate.connect(on_copy_link);
|
|
menu.append(item);
|
|
|
|
if (Geary.RFC822.MailboxAddress.is_valid_address(hover_url)) {
|
|
// Add a menu item for copying the address.
|
|
item = new Gtk.MenuItem.with_mnemonic(_("Copy _Email Address"));
|
|
item.activate.connect(on_copy_email_address);
|
|
menu.append(item);
|
|
}
|
|
}
|
|
|
|
// Select all.
|
|
Gtk.MenuItem select_all_item = new Gtk.MenuItem.with_mnemonic(_("Select _All"));
|
|
select_all_item.activate.connect(on_select_all);
|
|
menu.append(select_all_item);
|
|
|
|
// Inspect.
|
|
if (Args.inspector) {
|
|
Gtk.MenuItem inspect_item = new Gtk.MenuItem.with_mnemonic(_("_Inspect"));
|
|
inspect_item.activate.connect(() => {web_view.web_inspector.inspect_node(clicked_element);});
|
|
menu.append(inspect_item);
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
private static void on_hide_quote_clicked(WebKit.DOM.Element element) {
|
|
try {
|
|
WebKit.DOM.Element parent = element.get_parent_element();
|
|
parent.set_attribute("class", "quote_container controllable hide");
|
|
} catch (Error error) {
|
|
warning("Error hiding quote: %s", error.message);
|
|
}
|
|
}
|
|
|
|
private static void on_show_quote_clicked(WebKit.DOM.Element element) {
|
|
try {
|
|
WebKit.DOM.Element parent = element.get_parent_element();
|
|
parent.set_attribute("class", "quote_container controllable show");
|
|
} catch (Error error) {
|
|
warning("Error hiding quote: %s", error.message);
|
|
}
|
|
}
|
|
|
|
private static void on_menu_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
event.stop_propagation();
|
|
Geary.Email email = conversation_viewer.get_email_from_element(element);
|
|
if (email != null)
|
|
conversation_viewer.show_message_menu(email);
|
|
}
|
|
|
|
private static void on_unstar_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
event.stop_propagation();
|
|
Geary.Email? email = conversation_viewer.get_email_from_element(element);
|
|
if (email != null)
|
|
conversation_viewer.unflag_message(email);
|
|
}
|
|
|
|
private static void on_star_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
event.stop_propagation();
|
|
Geary.Email? email = conversation_viewer.get_email_from_element(element);
|
|
if (email != null)
|
|
conversation_viewer.flag_message(email);
|
|
}
|
|
|
|
private static void on_value_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
if (!conversation_viewer.is_hidden_email(element))
|
|
event.stop_propagation(); // Don't allow toggle
|
|
}
|
|
|
|
private bool is_hidden_email(WebKit.DOM.Element element) {
|
|
try {
|
|
WebKit.DOM.HTMLElement? email_element = closest_ancestor(element, ".email");
|
|
if (email_element == null)
|
|
return false;
|
|
|
|
WebKit.DOM.DOMTokenList class_list = email_element.get_class_list();
|
|
return class_list.contains("hide");
|
|
} catch (Error error) {
|
|
warning("Error getting hidden status: %s", error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void on_body_toggle_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
conversation_viewer.on_body_toggle_clicked_self(element);
|
|
}
|
|
|
|
private void on_body_toggle_clicked_self(WebKit.DOM.Element element) {
|
|
try {
|
|
WebKit.DOM.HTMLElement? email_element = closest_ancestor(element, ".email");
|
|
if (email_element == null)
|
|
return;
|
|
|
|
WebKit.DOM.DOMTokenList class_list = email_element.get_class_list();
|
|
if (class_list.contains("hide"))
|
|
class_list.remove("hide");
|
|
else
|
|
class_list.add("hide");
|
|
} catch (Error error) {
|
|
warning("Error toggling message: %s", error.message);
|
|
}
|
|
|
|
mark_read();
|
|
}
|
|
|
|
private static void on_attachment_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
conversation_viewer.on_attachment_clicked_self(element);
|
|
}
|
|
|
|
private void on_attachment_clicked_self(WebKit.DOM.Element element) {
|
|
int64 attachment_id = int64.parse(element.get_attribute("data-attachment-id"));
|
|
Geary.Email? email = get_email_from_element(element);
|
|
if (email == null)
|
|
return;
|
|
|
|
Geary.Attachment? attachment = null;
|
|
try {
|
|
attachment = email.get_attachment(attachment_id);
|
|
} catch (Error error) {
|
|
warning("Error opening attachment: %s", error.message);
|
|
}
|
|
|
|
if (attachment != null)
|
|
open_attachment(attachment);
|
|
}
|
|
|
|
private static void on_attachment_menu(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
|
ConversationViewer conversation_viewer) {
|
|
event.stop_propagation();
|
|
Geary.Email? email = conversation_viewer.get_email_from_element(element);
|
|
Geary.Attachment? attachment = conversation_viewer.get_attachment_from_element(element);
|
|
if (email != null && attachment != null)
|
|
conversation_viewer.show_attachment_menu(email, attachment);
|
|
}
|
|
|
|
private void on_message_menu_selection_done() {
|
|
message_menu = null;
|
|
}
|
|
|
|
private void on_attachment_menu_selection_done() {
|
|
attachment_menu = null;
|
|
}
|
|
|
|
private void save_attachment(Geary.Attachment attachment) {
|
|
Gee.List<Geary.Attachment> attachments = new Gee.ArrayList<Geary.Attachment>();
|
|
attachments.add(attachment);
|
|
save_attachments(attachments);
|
|
}
|
|
|
|
private void on_mark_read_message(Geary.Email message) {
|
|
Geary.EmailFlags flags = new Geary.EmailFlags();
|
|
flags.add(Geary.EmailFlags.UNREAD);
|
|
mark_message(message, null, flags);
|
|
mark_manual_read(message.id);
|
|
}
|
|
|
|
private void on_mark_unread_message(Geary.Email message) {
|
|
Geary.EmailFlags flags = new Geary.EmailFlags();
|
|
flags.add(Geary.EmailFlags.UNREAD);
|
|
mark_message(message, flags, null);
|
|
mark_manual_read(message.id);
|
|
}
|
|
|
|
// Use this when an email has been marked read through manual (user) intervention
|
|
public void mark_manual_read(Geary.EmailIdentifier id) {
|
|
if (email_to_element.has_key(id)) {
|
|
try {
|
|
email_to_element.get(id).get_class_list().add("manual_read");
|
|
} catch (Error error) {
|
|
debug("Adding manual_read class failed: %s", error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void on_print_message(Geary.Email message) {
|
|
try {
|
|
email_to_element.get(message.id).get_class_list().add("print");
|
|
web_view.get_main_frame().print();
|
|
email_to_element.get(message.id).get_class_list().remove("print");
|
|
} catch (GLib.Error error) {
|
|
debug("Hiding elements for printing failed: %s", error.message);
|
|
}
|
|
}
|
|
|
|
private void flag_message(Geary.Email email) {
|
|
Geary.EmailFlags flags = new Geary.EmailFlags();
|
|
flags.add(Geary.EmailFlags.FLAGGED);
|
|
mark_message(email, flags, null);
|
|
}
|
|
|
|
private void unflag_message(Geary.Email email) {
|
|
Geary.EmailFlags flags = new Geary.EmailFlags();
|
|
flags.add(Geary.EmailFlags.FLAGGED);
|
|
mark_message(email, null, flags);
|
|
}
|
|
|
|
private void show_attachment_menu(Geary.Email email, Geary.Attachment attachment) {
|
|
attachment_menu = build_attachment_menu(email, attachment);
|
|
attachment_menu.show_all();
|
|
attachment_menu.popup(null, null, null, 0, 0);
|
|
}
|
|
|
|
private Gtk.Menu build_attachment_menu(Geary.Email email, Geary.Attachment attachment) {
|
|
Gtk.Menu menu = new Gtk.Menu();
|
|
menu.selection_done.connect(on_attachment_menu_selection_done);
|
|
|
|
Gtk.MenuItem save_attachment_item = new Gtk.MenuItem.with_mnemonic(_("_Save As..."));
|
|
save_attachment_item.activate.connect(() => save_attachment(attachment));
|
|
menu.append(save_attachment_item);
|
|
|
|
if (email.attachments.size > 1) {
|
|
Gtk.MenuItem save_all_item = new Gtk.MenuItem.with_mnemonic(_("Save All A_ttachments..."));
|
|
save_all_item.activate.connect(() => save_attachments(email.attachments));
|
|
menu.append(save_all_item);
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
private void show_message_menu(Geary.Email email) {
|
|
message_menu = build_message_menu(email);
|
|
message_menu.show_all();
|
|
message_menu.popup(null, null, null, 0, 0);
|
|
}
|
|
|
|
private Gtk.Menu build_message_menu(Geary.Email email) {
|
|
Gtk.Menu menu = new Gtk.Menu();
|
|
menu.selection_done.connect(on_message_menu_selection_done);
|
|
|
|
if (email.attachments.size > 0) {
|
|
string mnemonic = ngettext("Save A_ttachment...", "Save All A_ttachments...",
|
|
email.attachments.size);
|
|
Gtk.MenuItem save_all_item = new Gtk.MenuItem.with_mnemonic(mnemonic);
|
|
save_all_item.activate.connect(() => save_attachments(email.attachments));
|
|
menu.append(save_all_item);
|
|
menu.append(new Gtk.SeparatorMenuItem());
|
|
}
|
|
|
|
// Reply to a message.
|
|
Gtk.MenuItem reply_item = new Gtk.MenuItem.with_mnemonic(_("_Reply"));
|
|
reply_item.activate.connect(() => reply_to_message(email));
|
|
menu.append(reply_item);
|
|
|
|
// Reply to all on a message.
|
|
Gtk.MenuItem reply_all_item = new Gtk.MenuItem.with_mnemonic(_("Reply to _All"));
|
|
reply_all_item.activate.connect(() => reply_all_message(email));
|
|
menu.append(reply_all_item);
|
|
|
|
// Forward a message.
|
|
Gtk.MenuItem forward_item = new Gtk.MenuItem.with_mnemonic(_("_Forward"));
|
|
forward_item.activate.connect(() => forward_message(email));
|
|
menu.append(forward_item);
|
|
|
|
// Separator.
|
|
menu.append(new Gtk.SeparatorMenuItem());
|
|
|
|
// Mark as read/unread.
|
|
if (current_folder is Geary.FolderSupportsMark) {
|
|
if (email.is_unread().to_boolean(false)) {
|
|
Gtk.MenuItem mark_read_item = new Gtk.MenuItem.with_mnemonic(_("_Mark as Read"));
|
|
mark_read_item.activate.connect(() => on_mark_read_message(email));
|
|
menu.append(mark_read_item);
|
|
} else {
|
|
Gtk.MenuItem mark_unread_item = new Gtk.MenuItem.with_mnemonic(_("_Mark as Unread"));
|
|
mark_unread_item.activate.connect(() => on_mark_unread_message(email));
|
|
menu.append(mark_unread_item);
|
|
}
|
|
}
|
|
|
|
// Print a message.
|
|
Gtk.MenuItem print_item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.PRINT, null);
|
|
print_item.activate.connect(() => on_print_message(email));
|
|
menu.append(print_item);
|
|
|
|
// Separator.
|
|
menu.append(new Gtk.SeparatorMenuItem());
|
|
|
|
// View original message source.
|
|
Gtk.MenuItem view_source_item = new Gtk.MenuItem.with_mnemonic(_("_View Source"));
|
|
view_source_item.activate.connect(() => on_view_source(email));
|
|
menu.append(view_source_item);
|
|
|
|
return menu;
|
|
}
|
|
|
|
private WebKit.DOM.HTMLDivElement create_quote_container() throws Error {
|
|
WebKit.DOM.HTMLDivElement quote_container = web_view.create_div();
|
|
quote_container.set_attribute("class", "quote_container");
|
|
quote_container.set_inner_html("%s%s%s".printf("<div class=\"shower\">[show]</div>",
|
|
"<div class=\"hider\">[hide]</div>", "<div class=\"quote\"></div>"));
|
|
return quote_container;
|
|
}
|
|
|
|
private string[] split_message_and_signature(string text) {
|
|
try {
|
|
Regex signature_regex = new Regex("\\R--\\s*\\R", RegexCompileFlags.MULTILINE);
|
|
return signature_regex.split_full(text, -1, 0, 0, 2);
|
|
} catch (RegexError e) {
|
|
debug("Regex error searching for signature: %s", e.message);
|
|
return new string[0];
|
|
}
|
|
}
|
|
|
|
private string set_up_quotes(string text) {
|
|
try {
|
|
// Extract any quote containers from the signature block and make them controllable.
|
|
WebKit.DOM.HTMLElement container = web_view.create_div();
|
|
container.set_inner_html(text);
|
|
WebKit.DOM.NodeList quote_list = container.query_selector_all(".signature .quote_container");
|
|
for (int i = 0; i < quote_list.length; ++i) {
|
|
WebKit.DOM.Element quote = quote_list.item(i) as WebKit.DOM.Element;
|
|
quote.set_attribute("class", "quote_container controllable hide");
|
|
container.append_child(quote);
|
|
}
|
|
|
|
// If there is only one quote container in the message, set it up as controllable.
|
|
quote_list = container.query_selector_all(".quote_container");
|
|
if (quote_list.length == 1) {
|
|
((WebKit.DOM.Element) quote_list.item(0)).set_attribute("class",
|
|
"quote_container controllable hide");
|
|
}
|
|
return container.get_inner_html();
|
|
} catch (Error error) {
|
|
debug("Error adjusting final quote block: %s", error.message);
|
|
return text;
|
|
}
|
|
}
|
|
|
|
private string insert_plain_text_markup(string text) {
|
|
// Plain text signature and quote:
|
|
// --
|
|
// Nate
|
|
//
|
|
// 2012/3/14 Nate Lillich <nate@yorba.org>#015
|
|
// >
|
|
// >
|
|
//
|
|
// Wrap all quotes in hide/show controllers.
|
|
string message = "";
|
|
try {
|
|
WebKit.DOM.HTMLElement container = web_view.create_div();
|
|
int offset = 0;
|
|
while (offset < text.length) {
|
|
// Find the beginning of a quote block.
|
|
int quote_start = text.index_of(">") == 0 && message.length == 0 ? 0 :
|
|
text.index_of("\n>", offset);
|
|
if (quote_start == -1) {
|
|
break;
|
|
} else if (text.get(quote_start) == '\n') {
|
|
// Don't include the newline.
|
|
++quote_start;
|
|
}
|
|
|
|
// Find the end of the quote block.
|
|
int quote_end = quote_start;
|
|
do {
|
|
quote_end = text.index_of("\n", quote_end + 1);
|
|
} while (quote_end != -1 && quote_end == text.index_of("\n>", quote_end));
|
|
if (quote_end == -1) {
|
|
quote_end = text.length;
|
|
}
|
|
|
|
// Copy the stuff before the quote, then the wrapped quote.
|
|
WebKit.DOM.Element quote_container = create_quote_container();
|
|
Util.DOM.select(quote_container, ".quote").set_inner_html(
|
|
decorate_quotes(text.substring(quote_start, quote_end - quote_start)));
|
|
container.append_child(quote_container);
|
|
if (quote_start > offset) {
|
|
message += text.substring(offset, quote_start - offset);
|
|
}
|
|
message += container.get_inner_html();
|
|
offset = quote_end;
|
|
container.set_inner_html("");
|
|
}
|
|
|
|
// Append everything that's left.
|
|
if (offset != text.length) {
|
|
message += text.substring(offset);
|
|
}
|
|
} catch (Error error) {
|
|
debug("Error wrapping plaintext quotes: %s", error.message);
|
|
return text;
|
|
}
|
|
|
|
// Find the signature marker (--) at the beginning of a line.
|
|
string[] message_chunks = split_message_and_signature(message);
|
|
string signature = "";
|
|
if (message_chunks.length == 2) {
|
|
signature = "<div class=\"signature\">%s</div>".printf(
|
|
message.substring(message_chunks[0].length).strip());
|
|
message = "<div>%s</div>".printf(message_chunks[0]);
|
|
}
|
|
return "<pre>" + set_up_quotes(message + signature) + "</pre>";
|
|
}
|
|
|
|
private string insert_html_markup(string text, Geary.Email email) {
|
|
try {
|
|
// Create a workspace for manipulating the HTML.
|
|
WebKit.DOM.HTMLElement container = web_view.create_div();
|
|
container.set_inner_html(text);
|
|
|
|
// Some HTML messages like to wrap themselves in full, proper html, head, and body tags.
|
|
// If we have that here, lets remove it since we are sticking it in our own document.
|
|
WebKit.DOM.HTMLElement? body = Util.DOM.select(container, "body");
|
|
if (body != null) {
|
|
container.set_inner_html(body.get_inner_html());
|
|
}
|
|
|
|
// Get all the top level block quotes and stick them into a hide/show controller.
|
|
WebKit.DOM.NodeList blockquote_list = container.query_selector_all("blockquote");
|
|
for (int i = 0; i < blockquote_list.length; ++i) {
|
|
// Get the nodes we need.
|
|
WebKit.DOM.Node blockquote_node = blockquote_list.item(i);
|
|
WebKit.DOM.Node? next_sibling = blockquote_node.get_next_sibling();
|
|
WebKit.DOM.Node parent = blockquote_node.get_parent_node();
|
|
|
|
// Make sure this is a top level blockquote.
|
|
if (node_is_child_of(blockquote_node, "BLOCKQUOTE")) {
|
|
continue;
|
|
}
|
|
|
|
// parent
|
|
// quote_container
|
|
// blockquote
|
|
// sibling
|
|
WebKit.DOM.Element quote_container = create_quote_container();
|
|
Util.DOM.select(quote_container, ".quote").append_child(blockquote_node);
|
|
if (next_sibling == null) {
|
|
parent.append_child(quote_container);
|
|
} else {
|
|
parent.insert_before(quote_container, next_sibling);
|
|
}
|
|
}
|
|
|
|
// Now look for the signature.
|
|
wrap_html_signature(ref container);
|
|
|
|
// Then look for all <img> tags. Inline images are replaced with
|
|
// data URLs, while external images are added to
|
|
// external_images_uri (to be used later by is_image()).
|
|
Gee.ArrayList<string> external_images_uri = new Gee.ArrayList<string>();
|
|
WebKit.DOM.NodeList inline_list = container.query_selector_all("img");
|
|
for (ulong i = 0; i < inline_list.length; ++i) {
|
|
// Get the MIME content for the image.
|
|
WebKit.DOM.HTMLImageElement img = (WebKit.DOM.HTMLImageElement) inline_list.item(i);
|
|
string? src = img.get_attribute("src");
|
|
if (Geary.String.is_empty(src)) {
|
|
continue;
|
|
} else if (src.has_prefix("cid:")) {
|
|
string mime_id = src.substring(4);
|
|
Geary.Memory.AbstractBuffer image_content =
|
|
email.get_message().get_content_by_mime_id(mime_id);
|
|
uint8[] image_data = image_content.get_array();
|
|
|
|
// Get the content type.
|
|
bool uncertain_content_type;
|
|
string mimetype = ContentType.get_mime_type(ContentType.guess(null, image_data,
|
|
out uncertain_content_type));
|
|
|
|
// Then set the source to a data url.
|
|
web_view.set_data_url(img, mimetype, image_data);
|
|
} else if (!src.has_prefix("data:")) {
|
|
external_images_uri.add(src);
|
|
if (!web_view.load_external_images)
|
|
external_images_info_bar.show();
|
|
}
|
|
}
|
|
|
|
web_view.set_external_images_uris(external_images_uri);
|
|
|
|
// Now return the whole message.
|
|
return set_up_quotes(container.get_inner_html());
|
|
} catch (Error e) {
|
|
debug("Error modifying HTML message: %s", e.message);
|
|
return text;
|
|
}
|
|
}
|
|
|
|
private void wrap_html_signature(ref WebKit.DOM.HTMLElement container) throws Error {
|
|
// Most HTML signatures fall into one of these designs which are handled by this method:
|
|
//
|
|
// 1. GMail: <div>-- </div>$SIGNATURE
|
|
// 2. GMail Alternate: <div><span>-- </span></div>$SIGNATURE
|
|
// 3. Thunderbird: <div>-- <br>$SIGNATURE</div>
|
|
//
|
|
WebKit.DOM.NodeList div_list = container.query_selector_all("div,span,p");
|
|
int i = 0;
|
|
Regex sig_regex = new Regex("^--\\s*$");
|
|
Regex alternate_sig_regex = new Regex("^--\\s*(?:<br|\\R)");
|
|
for (; i < div_list.length; ++i) {
|
|
// Get the div and check that it starts a signature block and is not inside a quote.
|
|
WebKit.DOM.HTMLElement div = div_list.item(i) as WebKit.DOM.HTMLElement;
|
|
string inner_html = div.get_inner_html();
|
|
if ((sig_regex.match(inner_html) || alternate_sig_regex.match(inner_html)) &&
|
|
!node_is_child_of(div, "BLOCKQUOTE")) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have a signature, move it and all of its following siblings that are not quotes
|
|
// inside a signature div.
|
|
if (i == div_list.length) {
|
|
return;
|
|
}
|
|
WebKit.DOM.Element elem = div_list.item(i) as WebKit.DOM.Element;
|
|
WebKit.DOM.HTMLElement signature_container = web_view.create_div();
|
|
signature_container.set_attribute("class", "signature");
|
|
do {
|
|
// Get its sibling _before_ we move it into the signature div.
|
|
WebKit.DOM.Element? sibling = elem.get_next_element_sibling() as WebKit.DOM.Element;
|
|
if (!elem.get_attribute("class").contains("quote_container")) {
|
|
signature_container.append_child(elem);
|
|
}
|
|
elem = sibling;
|
|
} while (elem != null);
|
|
container.append_child(signature_container);
|
|
}
|
|
|
|
public void remove_message(Geary.Email email) {
|
|
if (!messages.contains(email))
|
|
return;
|
|
|
|
WebKit.DOM.HTMLElement element = email_to_element.get(email.id);
|
|
email_to_element.unset(email.id);
|
|
|
|
try {
|
|
if (element.get_parent_element() != null)
|
|
element.get_parent_element().remove_child(element);
|
|
} catch (Error err) {
|
|
debug("Could not remove message: %s", err.message);
|
|
}
|
|
}
|
|
|
|
private string create_header_row(string title, string value, bool important) {
|
|
return """
|
|
<div class="field %s">
|
|
<div class="title">%s</div>
|
|
<div class="value">%s</div>
|
|
</div>""".printf(important ? "important" : "", title, value);
|
|
}
|
|
|
|
// Appends a header field to header_text
|
|
private void insert_header(ref string header_text, string _title, string? _value,
|
|
bool important = false) {
|
|
if (Geary.String.is_empty(_value))
|
|
return;
|
|
|
|
header_text += create_header_row(Geary.HTML.escape_markup(_title),
|
|
Geary.HTML.escape_markup(_value), important);
|
|
}
|
|
|
|
private void insert_header_date(ref string header_text, string _title, DateTime _value,
|
|
bool important = false){
|
|
|
|
Date.ClockFormat clock_format = GearyApplication.instance.config.clock_format;
|
|
string title = Geary.HTML.escape_markup(_title);
|
|
string value = """
|
|
<span class="hidden_only">%s</span>
|
|
<span class="not_hidden_only">%s</span>
|
|
""".printf(Date.pretty_print(_value, clock_format),
|
|
Date.pretty_print_verbose(_value, clock_format));
|
|
header_text += create_header_row(title, value, important);
|
|
}
|
|
|
|
// Appends email address fields to the header.
|
|
private void insert_header_address(ref string header_text, string title,
|
|
Geary.RFC822.MailboxAddresses? addresses, bool important = false) {
|
|
if (addresses == null)
|
|
return;
|
|
|
|
int i = 0;
|
|
string value = "";
|
|
Gee.List<Geary.RFC822.MailboxAddress> list = addresses.get_all();
|
|
foreach (Geary.RFC822.MailboxAddress a in list) {
|
|
value += "<a href='mailto:%s'>".printf(a.address);
|
|
if (a.name != null) {
|
|
value += "<span class='address_name'>%s</span> ".printf(a.name);
|
|
value += "<span class='address_value'>%s</span>".printf(a.address);
|
|
} else {
|
|
value += "<span class='address_name'>%s</span>".printf(a.address);
|
|
}
|
|
value += "</a>";
|
|
|
|
if (++i < list.size)
|
|
value += ", ";
|
|
}
|
|
|
|
header_text += create_header_row(Geary.HTML.escape_markup(title), value, important);
|
|
}
|
|
|
|
private void insert_attachments(WebKit.DOM.HTMLElement email_container,
|
|
Gee.List<Geary.Attachment> attachments) {
|
|
|
|
// <div class="attachment_container">
|
|
// <div class="top_border"></div>
|
|
// <table class="attachment" data-attachment-id="">
|
|
// <tr>
|
|
// <td class="preview">
|
|
// <img src="" />
|
|
// </td>
|
|
// <td class="info">
|
|
// <div class="filename"></div>
|
|
// <div class="filesize"></div>
|
|
// </td>
|
|
// </tr>
|
|
// </table>
|
|
// </div>
|
|
|
|
try {
|
|
// Prepare the dom for our attachments.
|
|
WebKit.DOM.Document document = web_view.get_dom_document();
|
|
WebKit.DOM.HTMLElement attachment_container =
|
|
Util.DOM.clone_select(document, "#attachment_template");
|
|
WebKit.DOM.HTMLElement attachment_template =
|
|
Util.DOM.select(attachment_container, ".attachment");
|
|
attachment_container.remove_attribute("id");
|
|
attachment_container.remove_child(attachment_template);
|
|
|
|
// Create an attachment table for each attachment.
|
|
foreach (Geary.Attachment attachment in attachments) {
|
|
// Generate the attachment table.
|
|
WebKit.DOM.HTMLElement attachment_table = Util.DOM.clone_node(attachment_template);
|
|
string filename = Geary.String.is_empty_or_whitespace(attachment.filename) ?
|
|
_("none") : attachment.filename;
|
|
Util.DOM.select(attachment_table, ".info .filename")
|
|
.set_inner_text(filename);
|
|
Util.DOM.select(attachment_table, ".info .filesize")
|
|
.set_inner_text(Files.get_filesize_as_string(attachment.filesize));
|
|
attachment_table.set_attribute("data-attachment-id", "%s".printf(attachment.id.to_string()));
|
|
|
|
// Set the image preview and insert it into the container.
|
|
WebKit.DOM.HTMLImageElement img =
|
|
Util.DOM.select(attachment_table, ".preview img") as WebKit.DOM.HTMLImageElement;
|
|
web_view.set_image_src(img, attachment.mime_type, attachment.filepath, ATTACHMENT_PREVIEW_SIZE);
|
|
attachment_container.append_child(attachment_table);
|
|
}
|
|
|
|
// Append the attachments to the email.
|
|
email_container.append_child(attachment_container);
|
|
} catch (Error error) {
|
|
debug("Failed to insert attachments: %s", error.message);
|
|
}
|
|
}
|
|
|
|
public void scroll_reset() {
|
|
web_view.scroll_reset();
|
|
}
|
|
|
|
private void on_hovering_over_link(string? title, string? url) {
|
|
// Copy the link the user is hovering over. Note that when the user mouses-out,
|
|
// this signal is called again with null for both parameters.
|
|
hover_url = url;
|
|
message_overlay_label.label = hover_url;
|
|
}
|
|
|
|
private void on_copy_text() {
|
|
web_view.copy_clipboard();
|
|
}
|
|
|
|
private void on_copy_link() {
|
|
// Put the current link in clipboard.
|
|
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
|
clipboard.set_text(hover_url, -1);
|
|
clipboard.store();
|
|
}
|
|
|
|
private void on_copy_email_address() {
|
|
// Put the current email address in clipboard.
|
|
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
|
if (hover_url.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME))
|
|
clipboard.set_text(hover_url.substring(Geary.ComposedEmail.MAILTO_SCHEME.length, -1), -1);
|
|
else
|
|
clipboard.set_text(hover_url, -1);
|
|
clipboard.store();
|
|
}
|
|
|
|
private void on_select_all() {
|
|
web_view.select_all();
|
|
}
|
|
|
|
private void on_view_source(Geary.Email message) {
|
|
string source = message.header.buffer.to_string() + message.body.buffer.to_string();
|
|
|
|
try {
|
|
string temporary_filename;
|
|
int temporary_handle = FileUtils.open_tmp("geary-message-XXXXXX.txt",
|
|
out temporary_filename);
|
|
FileUtils.set_contents(temporary_filename, source);
|
|
FileUtils.close(temporary_handle);
|
|
string temporary_uri = Filename.to_uri(temporary_filename, null);
|
|
Gtk.show_uri(web_view.get_screen(), temporary_uri, Gdk.CURRENT_TIME);
|
|
} catch (Error error) {
|
|
ErrorDialog dialog = new ErrorDialog(GearyApplication.instance.get_main_window(),
|
|
_("Failed to open default text editor."), error.message);
|
|
dialog.run();
|
|
}
|
|
}
|
|
|
|
public void mark_read() {
|
|
Gee.List<Geary.EmailIdentifier> ids = new Gee.ArrayList<Geary.EmailIdentifier>();
|
|
WebKit.DOM.Document document = web_view.get_dom_document();
|
|
long scroll_top = document.body.scroll_top;
|
|
long scroll_height = document.document_element.scroll_height;
|
|
|
|
foreach (Geary.Email message in messages) {
|
|
try {
|
|
if (message.email_flags.is_unread()) {
|
|
WebKit.DOM.HTMLElement element = email_to_element.get(message.id);
|
|
WebKit.DOM.HTMLElement body = (WebKit.DOM.HTMLElement) element.get_elements_by_class_name("body").item(0);
|
|
if (!element.get_class_list().contains("manual_read") &&
|
|
body.offset_top + body.offset_height > scroll_top &&
|
|
body.offset_top + 28 < scroll_top + scroll_height) { // 28 = 15 padding + 13 first line of text
|
|
ids.add(message.id);
|
|
}
|
|
}
|
|
} catch (Error error) {
|
|
debug("Problem checking email class: %s", error.message);
|
|
}
|
|
}
|
|
|
|
Geary.FolderSupportsMark? supports_mark = current_folder as Geary.FolderSupportsMark;
|
|
if (supports_mark != null & ids.size > 0) {
|
|
Geary.EmailFlags flags = new Geary.EmailFlags();
|
|
flags.add(Geary.EmailFlags.UNREAD);
|
|
supports_mark.mark_email_async.begin(ids, null, flags, null);
|
|
}
|
|
}
|
|
}
|
|
|