From 2617f9949ec610ca877e8be39ec8bcbff4d9ef98 Mon Sep 17 00:00:00 2001 From: Robert Schroll Date: Wed, 24 Apr 2013 18:45:31 -0700 Subject: [PATCH] Show emails attached to main message; fix #6450 Attachments to those emails are not found yet. --- src/client/views/conversation-viewer.vala | 255 +++++++++++--------- src/client/views/conversation-web-view.vala | 2 +- src/engine/rfc822/rfc822-message.vala | 77 ++++-- theming/message-viewer.css | 25 +- 4 files changed, 227 insertions(+), 132 deletions(-) diff --git a/src/client/views/conversation-viewer.vala b/src/client/views/conversation-viewer.vala index 366df6e9..cb7cf295 100644 --- a/src/client/views/conversation-viewer.vala +++ b/src/client/views/conversation-viewer.vala @@ -149,7 +149,6 @@ public class ConversationViewer : Gtk.Box { return; string message_id = get_div_id(email.id); - string header = ""; WebKit.DOM.Node insert_before = web_view.container.get_last_child(); @@ -158,36 +157,12 @@ public class ConversationViewer : Gtk.Box { 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_message = Util.DOM.clone_select(web_view.get_dom_document(), "#email_template"); + div_message = make_email_div(); 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"); } @@ -196,93 +171,35 @@ public class ConversationViewer : Gtk.Box { return; } - email_to_element.set(email.id, div_message); - insert_header_address(ref header, _("From:"), email.from != null ? email.from : email.sender, - true); - - if (email.to != null) - 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 = ""; bool remote_images = false; try { - body_text = email.get_message().get_body(true); - body_text = insert_html_markup(body_text, email, out remote_images); - } catch (Error err) { - debug("Could not get message text. %s", err.message); + set_message_html(email.get_message(), div_message, out remote_images); + } catch (Error error) { + warning("Error getting message from email: %s", error.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); - - if (remote_images) { - WebKit.DOM.HTMLElement remote_images_bar = - Util.DOM.select(div_email_container, ".remote_images"); + + if (remote_images) { + WebKit.DOM.HTMLElement remote_images_bar = + Util.DOM.select(div_message, ".remote_images"); + try { ((WebKit.DOM.Element) remote_images_bar).get_class_list().add("show"); remote_images_bar.set_inner_html("""%s %s """.printf( remote_images_bar.get_inner_html(), _("This message contains remote images."), _("Show Images"))); + } catch (Error error) { + warning("Error showing remote images bar: %s", error.message); } - - } 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); @@ -304,7 +221,7 @@ public class ConversationViewer : Gtk.Box { 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:not(:only-of-type) .header_container", "click", (Callback) on_body_toggle_clicked, this); + bind_event(web_view, ".email:not(:only-of-type) .header_container, .email .email .header_container","click", (Callback) on_body_toggle_clicked, this); bind_event(web_view, ".email .compressed_note", "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); @@ -312,6 +229,120 @@ public class ConversationViewer : Gtk.Box { bind_event(web_view, ".remote_images .close_show_images", "click", (Callback) on_close_show_images, this); } + private WebKit.DOM.HTMLElement make_email_div() { + // The HTML is like this: + //
+ //
+ // + //
+ return Util.DOM.clone_select(web_view.get_dom_document(), "#email_template"); + } + + private void set_message_html(Geary.RFC822.Message message, WebKit.DOM.HTMLElement div_message, + out bool remote_images) { + string header = ""; + WebKit.DOM.HTMLElement div_email_container = Util.DOM.select(div_message, "div.email_container"); + + insert_header_address(ref header, _("From:"), message.from, true); + + if (message.to != null) + insert_header_address(ref header, _("To:"), message.to); + + if (message.cc != null) + insert_header_address(ref header, _("Cc:"), message.cc); + + if (message.bcc != null) + insert_header_address(ref header, _("Bcc:"), message.bcc); + + if (message.subject != null) + insert_header(ref header, _("Subject:"), message.subject.value); + + if (message.date != null) + insert_header_date(ref header, _("Date:"), message.date.value, true); + + // Add the avatar. + Geary.RFC822.MailboxAddress? primary = message.sender; + 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 = message.get_preview(); + 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 = ""; + remote_images = false; + try { + body_text = message.get_body(true); + body_text = insert_html_markup(body_text, message, out remote_images); + } catch (Error err) { + debug("Could not get message text. %s", err.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); + } + + // Look for any attached emails + Gee.List sub_messages = message.get_sub_messages(); + foreach (Geary.RFC822.Message sub_message in sub_messages) { + WebKit.DOM.HTMLElement div_sub_message = make_email_div(); + bool sub_remote_images = false; + try { + div_sub_message.set_attribute("id", ""); + div_sub_message.get_class_list().add("read"); + div_sub_message.get_class_list().add("hide"); + div_message.append_child(div_sub_message); + set_message_html(sub_message, div_sub_message, out sub_remote_images); + remote_images = remote_images || sub_remote_images; + } catch (Error error) { + debug("Error adding message: %s", error.message); + } + } + } + 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) { @@ -644,18 +675,22 @@ public class ConversationViewer : Gtk.Box { private void show_images_email(WebKit.DOM.Element email_element) { // TODO: Remember that these images have been shown. try { - WebKit.DOM.Element? body = email_element.query_selector(".body"); - if (body == null) - return; - - WebKit.DOM.NodeList nodes = body.query_selector_all("img"); - for (ulong i = 0; i < nodes.length; i++) { - WebKit.DOM.Element? element = nodes.item(i) as WebKit.DOM.Element; - if (element == null || !element.has_attribute("src")) + WebKit.DOM.NodeList body_nodes = email_element.query_selector_all(".body"); + for (ulong j = 0; j < body_nodes.length; j++) { + WebKit.DOM.Element? body = body_nodes.item(j) as WebKit.DOM.Element; + if (body == null) continue; - string src = element.get_attribute("src"); - element.set_attribute("src", web_view.allow_prefix + src); + WebKit.DOM.NodeList nodes = body.query_selector_all("img"); + for (ulong i = 0; i < nodes.length; i++) { + WebKit.DOM.Element? element = nodes.item(i) as WebKit.DOM.Element; + if (element == null || !element.has_attribute("src")) + continue; + + string src = element.get_attribute("src"); + if (!web_view.is_always_loaded(src)) + element.set_attribute("src", web_view.allow_prefix + src); + } } WebKit.DOM.Element? remote_images = email_element.query_selector(".remote_images"); @@ -892,7 +927,7 @@ public class ConversationViewer : Gtk.Box { } } - private string insert_html_markup(string text, Geary.Email email, out bool remote_images) { + private string insert_html_markup(string text, Geary.RFC822.Message message, out bool remote_images) { remote_images = false; try { // Create a workspace for manipulating the HTML. @@ -948,7 +983,7 @@ public class ConversationViewer : Gtk.Box { } 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); + message.get_content_by_mime_id(mime_id); uint8[] image_data = image_content.get_array(); // Get the content type. diff --git a/src/client/views/conversation-web-view.vala b/src/client/views/conversation-web-view.vala index 2aeda9ff..3909dcb8 100644 --- a/src/client/views/conversation-web-view.vala +++ b/src/client/views/conversation-web-view.vala @@ -104,7 +104,7 @@ public class ConversationWebView : WebKit.WebView { } } - private bool is_always_loaded(string? uri) { + public bool is_always_loaded(string? uri) { if (uri == null) return true; diff --git a/src/engine/rfc822/rfc822-message.vala b/src/engine/rfc822/rfc822-message.vala index 83493cbc..cda44e4b 100644 --- a/src/engine/rfc822/rfc822-message.vala +++ b/src/engine/rfc822/rfc822-message.vala @@ -22,6 +22,7 @@ public class Geary.RFC822.Message : BaseObject { public RFC822.MessageIDList? references { get; private set; default = null; } public RFC822.Subject? subject { get; private set; default = null; } public string? mailer { get; private set; default = null; } + public Geary.RFC822.Date? date { get; private set; default = null; } private GMime.Message message; @@ -36,6 +37,11 @@ public class Geary.RFC822.Message : BaseObject { stock_from_gmime(); } + public Message.from_gmime_message(GMime.Message message) { + this.message = message; + stock_from_gmime(); + } + public Message.from_string(string full_email) throws RFC822Error { this(new Geary.RFC822.Full(new Geary.Memory.StringBuffer(full_email))); } @@ -162,7 +168,8 @@ public class Geary.RFC822.Message : BaseObject { sender = email.sender; message.set_sender(email.message.get_sender()); - message.set_date_as_string(email.message.get_date_as_string()); + date = email.date; + message.set_date_as_string(email.date.to_string()); // Optional headers. if (email.to != null) { @@ -248,43 +255,43 @@ public class Geary.RFC822.Message : BaseObject { email.set_message_header(new Geary.RFC822.Header(new Geary.Memory.StringBuffer( message.get_headers()))); - email.set_send_date(new Geary.RFC822.Date(message.get_date_as_string())); + email.set_send_date(date); email.set_originators(from, new Geary.RFC822.MailboxAddresses.single(sender), null); email.set_receivers(to, cc, bcc); email.set_full_references(null, in_reply_to, references); email.set_message_subject(subject); email.set_message_body(new Geary.RFC822.Text(new Geary.Memory.StringBuffer( message.get_body().to_string()))); - email.set_message_preview(new Geary.RFC822.PreviewText.from_string( - preview_from_email(email))); + email.set_message_preview(new Geary.RFC822.PreviewText.from_string(get_preview())); return email; } // Takes an e-mail object with a body and generates a preview. If there is no body // or the body is the empty string, the empty string will be returned. - // - // Note that this is intended for outgoing messages, and as such we rely on the text - // section existing. - private string preview_from_email(Geary.Email email) { + public string get_preview() { + string? preview = null; try { - return Geary.String.safe_byte_substring(email.get_message(). - get_first_mime_part_of_content_type("text/plain").to_string(). - chug(), Geary.Email.MAX_PREVIEW_BYTES); + preview = get_text_body(false); } catch (Error e) { - debug("Could not generate outbox preview: %s", e.message); - - // fall through + try { + preview = Geary.HTML.remove_html_tags(get_html_body()); + } catch (Error error) { + debug("Could not generate message preview: %s\n and: %s", e.message, error.message); + } } - return ""; + return Geary.String.safe_byte_substring((preview ?? "").chug(), + Geary.Email.MAX_PREVIEW_BYTES); } private void stock_from_gmime() { - from = new RFC822.MailboxAddresses.from_rfc822_string(message.get_sender()); - - // sender is defined as first From address, from better or worse - sender = (from.size != 0) ? from[0] : null; + string? message_sender = message.get_sender(); + if (message_sender != null) { + from = new RFC822.MailboxAddresses.from_rfc822_string(message_sender); + // sender is defined as first From address, from better or worse + sender = (from.size != 0) ? from[0] : null; + } Gee.List? converted = convert_gmime_address_list( message.get_recipients(GMime.RecipientType.TO)); @@ -310,6 +317,14 @@ public class Geary.RFC822.Message : BaseObject { if (!String.is_empty(message.get_header(HEADER_MAILER))) mailer = message.get_header(HEADER_MAILER); + + if (!String.is_empty(message.get_date_as_string())) { + try { + date = new Geary.RFC822.Date(message.get_date_as_string()); + } catch (Error error) { + debug("Could not get date from message: %s", error.message); + } + } } private Gee.List? convert_gmime_address_list(InternetAddressList? addrlist, @@ -485,6 +500,30 @@ public class Geary.RFC822.Message : BaseObject { attachments.add(root as GMime.Part); } } + + public Gee.List get_sub_messages() { + Gee.List messages = new Gee.ArrayList(); + find_sub_messages(messages, message.get_mime_part()); + return messages; + } + + private void find_sub_messages(Gee.List messages, GMime.Object root) { + // If this is a multipart container, check each of its children. + GMime.Multipart? multipart = root as GMime.Multipart; + if (multipart != null) { + int count = multipart.get_count(); + for (int i = 0; i < count; ++i) { + find_sub_messages(messages, multipart.get_part(i)); + } + return; + } + + GMime.MessagePart? messagepart = root as GMime.MessagePart; + if (messagepart != null) { + GMime.Message sub_message = messagepart.get_message(); + messages.add(new Geary.RFC822.Message.from_gmime_message(sub_message)); + } + } private Geary.Memory.AbstractBuffer mime_part_to_memory_buffer(GMime.Part part, bool to_utf8 = false, bool to_html = false) throws RFC822Error { diff --git a/theming/message-viewer.css b/theming/message-viewer.css index f97cca20..b05b25a8 100644 --- a/theming/message-viewer.css +++ b/theming/message-viewer.css @@ -200,10 +200,15 @@ hr { } .email.hide .body, .email.hide > .attachment_container, - .email:not(.hide) .header_container .preview { + .email:not(.hide) .header_container .preview, + .email.hide .email { display: none; } - .email:not(:only-of-type) .header_container { + .email.hide .header_container .preview { + display: block; + } + .email:not(:only-of-type) .header_container, + .email .email .header_container { cursor: pointer; } .email:not(.hide) .header .field .value { @@ -263,6 +268,18 @@ hr { cursor: hand; } +.email .email { + box-shadow: none; + margin-top: 0; + border: none; + border-top: 1px rgba(0,0,0,0.4) solid; +} +.email .email .email_container .menu, +.email .email .email_container .starred, +.email .email .email_container .unstarred { + display: none; +} + .email:not(.attachment) .attachment.icon { display: none; } @@ -280,6 +297,10 @@ hr { background-color: white; margin: 0 16px 5px; } +.email > .email + .attachment_container .top_border{ + height: auto; + margin: 0; +} .email > .attachment_container > .attachment { margin: 10px 10px 0 10px; padding: 2px;