Show emails attached to main message; fix #6450

Attachments to those emails are not found yet.
This commit is contained in:
Robert Schroll 2013-04-24 18:45:31 -07:00 committed by Charles Lindsay
parent dbfce10d44
commit 2617f9949e
4 changed files with 227 additions and 132 deletions

View file

@ -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 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 = 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
<input type="button" value="%s" class="show_images" />""".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:
// <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>
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<Geary.RFC822.Message> 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.

View file

@ -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;

View file

@ -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<RFC822.MailboxAddress>? 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<RFC822.MailboxAddress>? 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<Geary.RFC822.Message> get_sub_messages() {
Gee.List<Geary.RFC822.Message> messages = new Gee.ArrayList<Geary.RFC822.Message>();
find_sub_messages(messages, message.get_mime_part());
return messages;
}
private void find_sub_messages(Gee.List<Geary.RFC822.Message> 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 {

View file

@ -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;