Commit 4e1b54b2f08f8c4b7d9624a20a9616e19e3e78ef

Updated email-mark-read-unread-mods
  
1INTRODUCTION
2This patch modifies how messages are marked as read or unread in the
3email application.
1This patch modifies how messages are marked as read or unread in the email application.
42
5WEBOS VERSION COMPATABILITY
6This patch is designed and tested for webOS version 1.2.1 only.
3This patch includes the following changes:
41) Messages are NOT marked as read when they are opened from the message list card (by default, this is what happens, which may not be useful if you just want to *look* at a message but don't want to have it be marked as read)
75
8DESCRIPTION
9This patch makes the following changes:
101) Messages are NOT marked as read when they are opened from the message
11list card. Normally, this is what happens, which may not be useful if you
12just want to review a message but don't want to have it be marked as read.
62) Updates the "Mark as Read"/"Mark as Unread" menu item label and function to work based the read/unread state of the message instead of defaulting to "Mark as Unread"
137
142) The "Mark as Read"/"Mark as Unread" menu item is properly coded to
15set the label based on the read/unread state of the message. Because of
16the default of marking a message as read when it is opened, the menu item
17was hard-coded to always display as "Mark as Unread". Per #1 above, if
18messages are not being marked as read when they are opened, this change
19is needed to ensure the menu renders correctly.
20
213) The message is marked as read when any of the following actions
22happen:
23 a) message is forwarded
24 b) message is replied to (regular reply or reply-all)
25 c) message is deleted
83) The message is marked as read when the message is forwarded, replied to, or deleted.
269Index: /usr/palm/applications/com.palm.app.email/app/controllers/message-assistant.js
2710===================================================================
2811--- .orig/usr/palm/applications/com.palm.app.email/app/controllers/message-assistant.js
2912+++ /usr/palm/applications/com.palm.app.email/app/controllers/message-assistant.js
30@@ -1,1741 +1,1756 @@
31-/* Copyright 2009 Palm, Inc. All rights reserved. */
32-
33-var MessageAssistant = Class.create({
34- initialize : function(targetEmail, folderId, focusStage, detailsObj) {
35- this.data = { id: targetEmail, senderDetails:{} }; // data.id
36- // This data is used to pre-render info in case the mailservice isn't able to respond quickly enough
37- if (detailsObj) {
38- this.data.prerenderData = true;
39- this.data.displayName = detailsObj.displayName;
40- this.data.summary = detailsObj.summary;
41- this.data.timeStamp = detailsObj.timeStamp;
42- this.data.flags = detailsObj.flags;
43- this.data.priority = detailsObj.priority;
44- }
45- this.folderId = folderId;
46- this.account = {};
47- this.gotFirstResponse = false;
48- this.bodyLeftOffset = 0;
49- this.transition = null;
50- this.waitingForMessageBodyTimeout = undefined;
51- this.attachmentDLProgress = {
52- lastUpdate:0,
53- progress:{},
54- subs:{},
55- clearSubscription: function(id) {
56- try {
57- this.subs[id].cancel();
58- delete (this.subs[id]);
59- delete (this.progress[id]);
60- } catch (e) {
61- Mojo.Log.logException(e, "clearSubscription");
62- }
63- }
64- };
65-
66- this.boundShowHideRecipients = this.showHideRecipients.bind(this);
67- this.boundGotoNextEmailNewer = this.gotoNextEmail.bind(this, 'newer');
68- this.boundGotoNextEmailOlder = this.gotoNextEmail.bind(this, 'older');
69- this.boundHandleInviteResponseAccept = this.handleInviteResponse.bind(this, 'accept');
70- this.boundHandleInviteResponseTentative = this.handleInviteResponse.bind(this, 'tentative');
71- this.boundHandleInviteResponseDecline = this.handleInviteResponse.bind(this, 'decline');
72- this.boundHandleInviteResponseRemove = this.handleInviteResponse.bind(this, 'remove');
73- this.boundHandleLinkClicked = this.handleLinkClicked.bind(this);
74- this.boundHandleInlineImageSaved = this.handleInlineImageSaved.bind(this);
75- this.boundHandleWebViewSingleTap = this.handleWebViewSingleTap.bind(this);
76-
77- if (focusStage === true) {
78- this.focusStageTimer = this.focusEmailStage.bind(this).delay(0.6);
79- }
80- },
81-
82- addAsScrollListener: function(event) {
83- event.scroller.addListener(this);
84- },
85-
86- moved: function() {
87- var scrollOffset = this.messageTarget.viewportOffset();
88- if (this.bodyLeftOffset === scrollOffset.left) {
89- return;
90- } else if (this.webview !== undefined) {
91- this.bodyLeftOffset = scrollOffset.left;
92- // Get the width of the browseradapter since that should tell the truth about its width
93- var webviewWidth = this.webview.down().getWidth();
94- //console.log("offsets: ov x=" + scrollOffset.left + ", w=" + this.emailHeaderBlock.getWidth() + ", wh=" + webviewWidth);
95-
96- if (scrollOffset.left > 0) {
97- scrollOffset.left = 0;
98- } else {
99- var rightExtent = (this.emailHeaderBlock.getWidth() - webviewWidth);
100- if (scrollOffset.left < rightExtent) {
101- scrollOffset.left = rightExtent;
102- }
103- }
104-
105- // move the header block & the attached pictures block so they appear to not scroll horizontally
106- var leftOffset = -scrollOffset.left + 'px';
107- this.emailHeaderBlock.setStyle({ 'left': leftOffset });
108- this.emailPicturesBlock.setStyle({ 'left': leftOffset });
109- }
110- },
111-
112- displayContactAvatarAndPresence: function(baseId, storageId, resp) {
113- var nameId = baseId + "-name";
114- var avatarId = baseId + "-photo";
115- var presenceId = baseId + "-presence";
116-
117- // If this person isn't in contact, give him ID=0. This is done in case the
118- // contact was deleted, in which case the old settings need to be cleared.
119- if (!resp.record) {
120- resp.record = { id:0 };
121- }
122-
123- if (baseId === "email-sender") {
124- this.data.fromID = resp.record.id;
125-
126-//<Reminder Info>
127- if (resp.record && resp.record.reminder) {
128- if (this.contactReminder === undefined) {
129- this.contactReminder = new ContactReminder();
130- }
131- this.contactReminder.displayReminder(resp);
132- }
133- } else {
134- this.controller.get(storageId).writeAttribute('contactid', resp.record.id);
135- }
136-
137- var nameElem = this.controller.get(nameId);
138- if (resp.record.displayText) {
139- nameElem.update(resp.record.displayText);
140- } else if (resp.record.firstName && resp.record.lastName) {
141- nameElem.update(resp.record.firstName + " " + resp.record.lastName);
142- } else if (resp.record.firstName) {
143- nameElem.update(resp.record.firstName);
144- } else if (resp.record.lastName) {
145- nameElem.update(resp.record.lastName);
146- }
147-
148- if (resp.record.pictureLocSquare) {
149- Mojo.Log.info("Displaying sender's picture ", resp.record.pictureLocSquare);
150- var imgElem = this.controller.get(avatarId);
151- imgElem.src = "file://" + resp.record.pictureLocSquare;
152- var imgFrame = imgElem.up('.from-photo');
153- if (imgFrame !== undefined) {
154- imgFrame.show();
155- }
156- imgElem.up().up().up().addClassName("has-avatar-icon");
157- }
158-
159- if (resp.record) {
160- if (resp.record.imAvailability !== undefined &&
161- resp.record.imAvailability !== null &&
162- resp.record.imAvailability !== IMName.NO_PRESENCE) {
163- Mojo.Log.info("imAvailability = ", resp.record.imAvailability);
164- var imPresence;
165- switch (resp.record.imAvailability) {
166- case IMName.BUSY:
167- imPresence = 'status-busy';
168- break;
169- case IMName.IDLE:
170- imPresence = 'status-idle';
171- break;
172- case IMName.ONLINE:
173- imPresence = 'status-available';
174- break;
175- default:
176- imPresence = 'status-offline';
177- }
178- var imgElem = this.controller.get(presenceId);
179- imgElem.addClassName(imPresence);
180- imgElem.show();
181- }
182- }
183- },
184-
185- folderAndAccountDetails: function(resp) {
186- // this gives us the following properties folder, account, login, protocol: EAS|IMAP|POP3}
187- this.account = resp;
188- // Add the email id to the account info because it will be used by the moveto scene
189- this.account.emailId = this.data.id;
190- var assistant = Mojo.Controller.getAppController().assistant;
191- assistant.notificationAssistant.clear(resp.account, resp.folder, this.data.id);
192- assistant.clearDebounce('m'+this.data.id);
193- },
194-
195- updateRecipientStatus: function() {
196- EmailRecipient.getDetails(this.controller, this.data.senderDetails.address, this.displayContactAvatarAndPresence.bind(this, 'email-sender', null));
197-
198- if (this.data.onlyRecipients !== undefined) {
199- var theThis = this;
200- this.data.onlyRecipients.each(function(r) {
201- EmailRecipient.getDetails(theThis.controller, r.address, theThis.displayContactAvatarAndPresence.bind(theThis, 'recip-'+r.id, r.id));
202- });
203- }
204- },
205-
206- handleSenderTap: function(event) {
207- this.showSenderContactDetails(event);
208- },
209-
210- showSenderContactDetails: function(event) {
211- if (this.data.fromID) {
212- EmailRecipient.launchContactDetails(this.controller, this.data.fromID);
213- } else {
214- var displayName = this.data.displayName;
215- // if the display name is the email address it isn't really the person's name
216- if (displayName === this.data.senderDetails.address) {
217- displayName = "";
218- }
219- EmailRecipient.addToContacts(this.controller, this.data.senderDetails.address, displayName);
220- }
221- },
222-
223- handleRecipientListSelect: function(event) {
224- var targetRow = this.controller.get(event.target);
225- if (!targetRow.hasClassName('email-recipient')) {
226- targetRow = targetRow.up('div.email-recipient');
227- }
228-
229- if (targetRow) {
230- var contactId = targetRow.readAttribute('contactid');
231- if (contactId && contactId > 0) {
232- EmailRecipient.launchContactDetails(this.controller, contactId);
233- } else {
234- var address = targetRow.readAttribute('address');
235- var displayName = targetRow.readAttribute('displayname');
236- displayName = displayName.escapeHTML();
237- EmailRecipient.addToContacts(this.controller, address, displayName);
238- }
239- }
240- },
241-
242- showHideRecipients: function(event) {
243- this.recipsDrawer.element.mojo.setOpenState(!this.recipsDrawer.element.mojo.getOpenState());
244- this.controller.get('email_recipients_compressed_list').toggle();
245- this.controller.get('email_recipients_compressed_count').toggle();
246- },
247-
248- messageDetailsUpdated: function(resp) {
249- if (this.gotFirstResponse === false) {
250- this.gotFirstResponse = true;
251- this.renderMessage(resp);
252- } else {
253- Mojo.Log.info("renderMessage: waiting for message body. isFullyLoaded=", resp.isFullyLoaded);
254- if (resp.isFullyLoaded == "true") {
255- if (this.waitingForMessageBodyTimeout !== undefined) {
256- clearTimeout(this.waitingForMessageBodyTimeout);
257- this.waitingForMessageBodyTimeout = undefined;
258- }
259- // Add the email body to the stored data model
260- this.data.textURI = resp.textURI;
261- this.data.text = resp.text;
262- this.renderMessageBody(resp);
263- }
264-
265- // POP doesn't know it has attachments until it has downloaded the full envelope
266- // so use the new attachment list if one there doesn't already exist
267- if ((!this.data.attachments || this.data.attachments.length === 0) &&
268- resp.attachments && EmailFlags.hasAttachment(resp.flags)) {
269- this.data.attachments = resp.attachments;
270- this.renderMessageAttachments(resp.attachments);
271- }
272- }
273- },
274-
275- // This is a special function that is only called when the scene is first launched
276- // and activate() occurs before the mailservice has time to respond with the full
277- // email data.
278- prerenderMessage: function(resp) {
279- this.data.prerenderData = false; // only want to prerender email once
280- Mojo.Log.info("prerenderMessage ", resp.id);
281-
282- if ((resp.flags & EmailFlags.flaggedBit) !== 0) {
283- resp.flagged = "starred";
284- }
285-
286- //convert the timestamp into a Date
287- var theDate = new Date(parseInt(resp.timeStamp));
288- resp.formattedDate = Mojo.Format.formatDate(theDate, {date:'medium', time:'short'});
289- resp.meridiem = "";
290-
291- var content = Mojo.View.render({template: 'message/message_from', object:resp});
292- this.controller.get('email_from').update(content);
293- //subject
294- resp.priority = Email.getPriorityClass(resp.priority);
295- content = Mojo.View.render({template: 'message/message_subject', object: resp});
296- this.controller.get('email_subject').update(content);
297- },
298-
299- renderMessage: function(resp) {
300- var content;
301- Mojo.Log.info("renderMessage ", resp.id);
302- this.data = resp;
303-
304- // Set the "read" flag if need be
305- if (!EmailFlags.isRead(resp.flags)) {
306- Email.setRead(resp.id, true);
307- }
308-
309- // Very first thing to do with recipients is fix them up for the address picker.
310- EmailRecipient.addAddressPickerFields(resp.recipients);
311-
312- var attachments = resp.attachments;
313-
314- if (resp.displayName) {
315- resp.displayName = resp.displayName.escapeHTML();
316- }
317-
318- if (this.data.summary == null || this.data.summary.length === 0) {
319- this.data.summary = $L("Untitled message");
320- } else {
321- resp.summary = resp.summary.escapeHTML();
322- }
323-
324- if ((resp.flags & EmailFlags.flaggedBit) !== 0) {
325- resp.flagged = "starred";
326- this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuClearFlag;
327- } else {
328- this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuSetFlag;
329- }
330-
331- //text -- if it is fully loaded then render it (need to check for string "true" since that's what's coming in json).
332- if (this.data.isFullyLoaded == "true") {
333- this.renderMessageBody(this.data);
334- } else {
335- this.waitForMessageBody();
336- }
337-
338- //convert the timestamp into a Date
339- //February 15, 2008 - 12:57 PM
340- var theDate = new Date(parseInt(resp.timeStamp));
341- resp.formattedDate = Mojo.Format.formatDate(theDate, {date:'medium', time:'short'});
342- resp.meridiem = ""; //theDate.toString($L('t'));
343-
344- content = Mojo.View.render({template: 'message/message_from', object:resp});
345- this.controller.get('email_from').update(content);
346- //subject
347- resp.priority = Email.getPriorityClass(resp.priority);
348- content = Mojo.View.render({template: 'message/message_subject', object: resp});
349- this.controller.get('email_subject').update(content);
350-
351- this.controller.get('previous_email').observe(Mojo.Event.tap, this.boundGotoNextEmailNewer);
352- this.controller.get('next_email').observe(Mojo.Event.tap, this.boundGotoNextEmailOlder);
353-
354- //recipients
355- this.renderMessageRecipients(resp);
356-
357- if (EmailFlags.isMeetingRequest(resp.flags)) {
358- var invite = new Object();
359- if (resp.whenStart) {
360- var startDate = new Date(parseInt(resp.whenStart));
361- var dateFormat = $L('EEE, MMM d');
362- var whenObject = {
363- date: Mojo.Format.formatDate(startDate, {date:dateFormat}),
364- startTime: Mojo.Format.formatDate(startDate, {time:'short'}),
365- endTime: Mojo.Format.formatDate(new Date(parseInt(resp.whenEnd)), {time:'short'})
366- };
367- invite.when = $L("#{date}, #{startTime} - #{endTime}").interpolate(whenObject);
368- } else {
369- // Service used to format the date. That doesn't work for too many reasons
370- invite.when = $L("#{when}").interpolate(resp); //TODO parse this and put in local date format
371- }
372- if (resp.busyStatus === 0) {
373- // Maybe change the style here
374- invite.conflict = $L("Conflicts with another event");
375- }
376- invite.where = $L("#{where}").interpolate(resp).escapeHTML();
377-
378- content = Mojo.View.render({template: 'message/meeting_invitation', object: invite});
379- this.controller.get('email-readview-invitations').update(content);
380-
381- this.controller.get('invite-accept').observe(Mojo.Event.tap, this.boundHandleInviteResponseAccept);
382- this.controller.get('invite-tentative').observe(Mojo.Event.tap, this.boundHandleInviteResponseTentative);
383- this.controller.get('invite-decline').observe(Mojo.Event.tap, this.boundHandleInviteResponseDecline);
384- }
385- else if (EmailFlags.isMeetingCancel(resp.flags)) {
386- content = Mojo.View.render({template: 'message/meeting_cancellation', object: {}});
387- this.controller.get('email-readview-invitations').update(content);
388- this.controller.get('invite-remove').observe(Mojo.Event.tap, this.boundHandleInviteResponseRemove);
389- }
390- else {
391- this.controller.get('email-readview-invitations').update("");
392- }
393-
394- // Attachments - some may be in the HTML body, so only show the attachments area if there
395- // is something in the attachments array
396- if (attachments && EmailFlags.hasAttachment(this.data.flags)) {
397- this.renderMessageAttachments(attachments);
398- } else {
399- // If no attachments, make sure old UI is cleaned out.
400- this.controller.get('email-readview-attachments-block').hide();
401- this.emailPicturesBlock.update("");
402- }
403-
404- // Ensure the whiteness is at least tall enough to fill the screen.
405- var contentContainer = this.controller.get('email-readview-content-container');
406- var po = contentContainer.positionedOffset();
407- var minHeight = (this.controller.window.innerHeight - po.top) + 'px';
408- contentContainer.setStyle({'min-height':minHeight})
409-
410- // Got the message details so say we're ready to render
411- if (this.delayActivate === true) {
412- this.delayActivate = false;
413- if (this.readyToActivateCallback !== undefined) {
414- this.readyToActivateCallback();
415- this.readyToActivateCallback = undefined;
416- }
417- }
418-
419- // Get details about the next and previous emails to know where the user can navigate.
420- // NOTE: this needs to be after message_subject is rendered
421- this.controller.serviceRequest(Email.identifier, {
422- method: 'getNextMessages',
423- parameters: {'message':this.data.id, 'folder': this.folderId },
424- onSuccess: this.handleNextMessagesResponse.bind(this),
425- onFailure: function(response) { Mojo.Log.error("getNextMessages failed "+Object.toJSON(response)); }
426- });
427-
428- if (this.transition !== null) {
429- this.transition.run();
430- this.transition.cleanup();
431- this.transition = null;
432- }
433- },
434-
435- renderMessageRecipients: function(resp) {
436- var recips = resp.recipients;
437- this.data.onlyRecipients = [];
438- this.data.senderDetails = {};
439- var content = null;
440- if (recips) {
441- var recipTypes;
442- if (EmailFlags.isMeetingType(resp.flags)) {
443- recipTypes = [ $L("From"), $L("Required"), $L("Optional"), $L("Bcc"), $L("From") ];
444- } else {
445- recipTypes = [ $L("From"), $L("To"), $L("Cc"), $L("Bcc"), $L("From") ];
446- }
447-
448- var theThis = this;
449- var prevRecp = {}; // This is used to determine when to display To/Cc/Bcc divider
450- recips.each(function(r) {
451- if(recipTypes[r.role] !== undefined) {
452- if (r.role === EmailRecipient.roleTo || r.role === EmailRecipient.roleCc || r.role === EmailRecipient.roleBcc) {
453- r.roleStr = recipTypes[r.role];
454- if (r.role != prevRecp.role) {
455- r.rowDisplayRole = "show";
456- r.hasDivider = "has-divider";
457- prevRecp.hideLast = "last";
458- }
459- theThis.data.onlyRecipients.push(r);
460- EmailRecipient.getDetails(theThis.controller, r.address, theThis.displayContactAvatarAndPresence.bind(theThis, 'recip-'+r.id, r.id));
461- prevRecp = r;
462- }
463- // roleReplyTo can overwrite the senderDetails because it takes precidence over roleFrom
464- else if (r.role === EmailRecipient.roleReplyTo) {
465- theThis.data.senderDetails = r;
466- }
467- // roleFrom should not overwrite the senderDetails because roleReplyTo takes precidence
468- else if (r.role === EmailRecipient.roleFrom && theThis.data.senderDetails.address === undefined) {
469- theThis.data.senderDetails = r;
470- }
471- }
472- });
473-
474- if (this.data.senderDetails.address !== undefined) {
475- // Get the contact ID for the sender of the email
476- EmailRecipient.getDetails(this.controller, this.data.senderDetails.address, this.displayContactAvatarAndPresence.bind(this, 'email-sender', null));
477- }
478-
479- if (this.data.onlyRecipients.length > 0) {
480- var recipElem = this.controller.get('email_recipients');
481- content = Mojo.View.render({
482- collection: this.data.onlyRecipients,
483- template: 'message/message_recips'
484- });
485- recipElem.update(content);
486-
487- var recipsSummary = { count:0, to:[], cc:[], bcc:[] };
488- this.data.onlyRecipients.each(function(r) {
489- if (r.role === EmailRecipient.roleTo) {
490- recipsSummary.to.push(r.displayName);
491- recipsSummary.count++;
492- } else if (r.role === EmailRecipient.roleCc) {
493- recipsSummary.cc.push(r.displayName);
494- recipsSummary.count++;
495- } else if (r.role === EmailRecipient.roleBcc) {
496- recipsSummary.bcc.push(r.displayName);
497- recipsSummary.count++;
498- }
499- });
500-
501- if (recipsSummary.count > 0) {
502- recipsSummary.displayTo = "none";
503- recipsSummary.displayCc = "none";
504- recipsSummary.displayBcc = "none";
505- if (recipsSummary.to.length > 0 && recipsSummary.cc.length > 0) {
506- recipsSummary.displayTo = "inline";
507- recipsSummary.displayCc = "inline";
508- recipsSummary.toList = recipsSummary.to.join(', ');
509- recipsSummary.ccList = recipsSummary.cc.join(', ');
510- } else if (recipsSummary.to.length > 0) {
511- recipsSummary.displayTo = "inline";
512- recipsSummary.toList = recipsSummary.to.join(', ');
513- } else if (recipsSummary.cc.length > 0) {
514- recipsSummary.displayCc = "inline";
515- recipsSummary.ccList = recipsSummary.cc.join(', ');
516- } else if (recipsSummary.bcc.length > 0) {
517- recipsSummary.displayBcc = "inline";
518- recipsSummary.bccList = recipsSummary.bcc.join(', ');
519- }
520-
521- if (recipsSummary.count === 1) {
522- recipsSummary.recipientCount = $L("1 recipient");
523- } else {
524- recipsSummary.recipientCount = $L("#{count} recipients").interpolate(recipsSummary);
525- }
526-
527- content = Mojo.View.render({template: 'message/message_recips_compressed', object: recipsSummary });
528- this.recipientController = this.controller.get('email_recipients_controller')
529- this.recipientController.update(content);
530- // Observe the row since that is the entire height and width of the tappable area
531- this.recipientController.up(".palm-row").observe(Mojo.Event.tap, this.boundShowHideRecipients, true);
532- }
533- }
534- }
535-
536- // If there were no recipients, then display the default "no recips"
537- if (content === null) {
538- Mojo.Log.info("displaying 'Only BCC recipients'");
539- var renderParams = {
540- template: 'message/message_recips_compressed',
541- object: {onlyBccRecipient: $L("Only BCC recipients"), displayTo: "none", displayCc: "none", displayBcc: "none" }
542- };
543- this.controller.get('email_recipients_controller').update(Mojo.View.render(renderParams));
544- }
545- },
546-
547- processTextProperty: function(data) {
548- // If it is an html email, it starts with "<!DOCTYPE" or "<html" some simple html tag
549- if (!data.isHtml) {
550- //console.log("******* NO MATCH *********")
551- // Plain text needs to replace the carriage returns with html line-break tags
552- data.text = data.text.escapeHTML().gsub("\n","<br>");
553- } else {
554- //console.log("******* MATCH *********")
555- data.text = data.text.stripScripts();
556- }
557- },
558-
559- renderMessageBody: function(data) {
560- if (data.isFullyLoaded != "true") {
561- Mojo.Log.error("renderMessageBody aborted because data.isFullyLoaded == ", data.isFullyLoaded)
562- return;
563- }
564-
565- this.hideNoBodyUI();
566-
567- if (data.textURI && this.webview.mojo.openURL) {
568- this.controller.get('email_text').hide();
569-
570- try {
571- Mojo.Log.breadcrumb("rendering URI " + data.textURI);
572- this.webview.mojo.setEnableJavaScript(false);
573- this.webview.mojo.openURL("file://" + data.textURI);
574- } catch (e) {
575- Mojo.Log.logException(e, 'renderMessageBody openUrl');
576- }
577-
578- // If the service didn't supply the text to use for replying to an email, request it now.
579- if (!data.text) {
580- var processTextProperty = this.processTextProperty;
581- this.controller.serviceRequest(Message.identifier, {
582- method: 'messageFileText',
583- parameters: { 'textURI':data.textURI },
584- onSuccess: function(resp) {
585- data.text = resp.text;
586- processTextProperty(data);
587- },
588- onFailure: function() { Mojo.Log.error("messageFileText failed for textURI", data.textURI) }
589- });
590- } else {
591- // Defer b/c this can be done after the email is rendered
592- this.processTextProperty.defer(data);
593- }
594- } else {
595- Mojo.Log.warn("WARNING: EMAIL using plain text");
596- var adapter = this.controller.get('email_body_text_outer');
597- if (adapter == null) {
598- adapter = this.controller.get('email_body_text');
599- }
600- adapter.hide();
601- var emailBody = this.controller.get('email_text');
602- // Strip out all scripts since they can do dangerous things
603- data.text = data.text.stripScripts();
604- // Since this is plain text, need to replace the carriage returns with html line-break tags
605- data.text = data.text.gsub("\n","<br>");
606- data.styles = "width:"+this.controller.window.innerWidth+"px;";
607- var content = Mojo.View.render({
608- template: 'message/message_text',
609- object: data
610- });
611- emailBody.update(content);
612- emailBody.show();
613- }
614-
615- this.webview.mojo.focus();
616- },
617-
618- renderMessageAttachments: function(attachments) {
619- // Show the div containing the attachments. For multiple attachments, also
620- // show the expand/collapse controller
621- this.controller.get('email-readview-attachments-block').show();
622-
623- var picturesList = [];
624- var names = undefined;
625- attachments.each(function(a) {
626- a.displayName = a.displayName.escapeHTML();
627- if (names === undefined) {
628- names = a.displayName.substring(0); // copy of the name
629- } else {
630- names += ", " + a.displayName;
631- }
632- // Icons
633- Attachments.processFileObject(a);
634- // Picture to be displayed
635- if (a.iconType == "type-image") {
636- var extension = a.extension.toLowerCase();
637- if (extension === ".jpg" || extension === ".jpeg" || extension === ".png") {
638- a.fixeduri = "file:///var/luna/data/extractfs/" + a.uri + ":0:0:320:240:3";
639- } else {
640- a.fixeduri = a.uri;
641- }
642- picturesList.push(a);
643- } else {
644- a.displayImage = "display:none";
645- }
646-
647- // Download %
648- if (a.uri) {
649- a.display = "display: none;";
650- a.download = "";
651- } else {
652- a.download = "show-download-icon";
653- a.display = "display: none;";
654- }
655- });
656-
657- // Only show the special "this file and N others" if there's more than 1 attachment
658- if (attachments.length > 1) {
659- // Setup the attachment compressed view
660- var content = Mojo.View.render({
661- object: { displayName:names, count:attachments.length },
662- template: 'message/message_attachments_compressed'
663- });
664- this.attachmentsShowElem.update(content);
665- }
666-
667- content = Mojo.View.render({
668- collection: attachments,
669- template: 'message/message_attachments'
670- });
671- this.controller.get('email-readview-attachments-list').update(content);
672-
673- // Attachment pictures
674- if (picturesList.length > 0) {
675- content = Mojo.View.render({
676- collection: picturesList,
677- template: 'message/message_pictures'
678- });
679- this.emailPicturesBlock.update(content);
680- } else {
681- this.emailPicturesBlock.update("");
682- }
683-
684- // Convert any downloaded audio attachments to AudioTag controls
685- attachments.each(function(a) {
686- if (a.uri !== undefined) {
687- this.setupAudioTag(a);
688- }
689- }.bind(this));
690-
691- //
692- // Now that the contents of the list are ready, compute heights and setup the "drawer"
693- //
694- this.attachmentList = this.controller.get('attachlist-list1');
695- this.attachmentList.setStyle({height:'auto'}); // make sure the height below is the full height
696- this.attachmentListHeight = parseInt(this.attachmentList.getHeight(), 10);
697-
698- // If the list contains only 1 item so it isn't considered as "shown"
699- this.attachmentListShown = (attachments.length > 1);
700- if (this.attachmentListShown) {
701- // Initially hide the attachments list
702- this.attachmentListShown = false;
703- this.attachmentList.setStyle({'height':'46px'});
704- this.attachmentList.hide();
705- this.attachmentsShowElem.show();
706- } else {
707- this.attachmentsShowElem.hide();
708- this.attachmentList.show();
709- }
710- },
711-
712- setupAudioTag: function(a) {
713- var success = false;
714- if (a.uri !== undefined && a.mimeType.startsWith("audio")) {
715- var audioElement = this.controller.get("progress_" + a.id);
716- try {
717- audioElement.show();
718- var audioTag = AudioTag.extendElement(audioElement, this.controller, a.uri);
719- audioTag.autoplay = false;
720- this.controller.get("adetails_" + a.id).hide();
721- success = true;
722- } catch (e) {
723- audioElement.hide();
724- }
725- }
726- return success;
727- },
728-
729- waitForMessageBody: function() {
730- // If the transport can't retrieve the message in a reasonable amount of time, error out
731- this.waitingForMessageBodyTimeout = this.waitMessageError.bind(this).delay(45);
732- this.showNoBodyUI();
733- },
734-
735- waitMessageError: function(resp) {
736- if (this.messageSubscription) {
737- this.messageSubscription.cancel();
738- this.messageSubscription = null;
739- }
740- Mojo.Log.error("waitMessageError", Object.toJSON(resp));
741-
742- // Waiting for message body means the message exists, but the body
743- // isn't yet on device. If the error occured before even waiting for
744- // the body, then popScene out to the previous scene because this
745- // email doesn't even exists anymore.
746- if (this.waitingForMessageBodyTimeout === undefined) {
747- // This should only occur if the email ID doesn't exist so go back to the previous scene
748- Mojo.Log.error("popping the message scene because message doesn't exists. ID=", this.data.id);
749- this.controller.stageController.popScene(resp);
750- } else {
751- clearTimeout(this.waitingForMessageBodyTimeout);
752- this.waitingForMessageBodyTimeout = undefined;
753- this.hideNoBodyUI();
754-
755- // Display error text
756- this.plainTextBody.update(Mojo.View.render({template: 'message/failed_to_download_err', object:{}}));
757- this.plainTextBody.show();
758- }
759- },
760-
761- showNoBodyUI: function() {
762- var nobodyDiv = this.controller.get('email_no_body')
763- nobodyDiv.show();
764- this.controller.instantiateChildWidgets(nobodyDiv);
765- this.controller.get('email_no_body_spinner').mojo.start();
766- },
767-
768- hideNoBodyUI: function() {
769- this.controller.get('email_no_body_spinner').mojo.stop();
770- this.controller.get('email_no_body').hide();
771- },
772-
773- handleToggleFavorites: function(event) {
774- // Flip the flagged bit
775- var value = true;
776- this.data.flags = this.data.flags ^ EmailFlags.flaggedBit;
777- if ((this.data.flags & EmailFlags.flaggedBit) == 0) {
778- this.controller.get('email_favorite').removeClassName("starred");
779- this.data.flagged = "";
780- this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuSetFlag;
781- value = false;
782- } else {
783- this.controller.get('email_favorite').addClassName("starred");
784- this.data.flagged = "starred";
785- this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuClearFlag;
786- }
787- //console.log("Setting flagged for " + this.data.id + " to " + value);
788- Email.setFlagged(this.data.id, value);
789- },
790-
791- handleAttachmentTapped: function(event) {
792- var targetRow = this.controller.get(event.target);
793- var listDiv = targetRow;
794- if (!listDiv.hasClassName('attachment-info')) {
795- listDiv = listDiv.up('div.attachment-info');
796- }
797-
798- if (listDiv) {
799- var attachmentUri = listDiv.getAttribute('x-uri');
800- var attachmentMimeType = listDiv.getAttribute('x-mimetype');
801- if (attachmentUri) {
802- if (attachmentMimeType.startsWith("audio")) {
803- // Do nothing because the AudioTag widget handles taps for audio files
804- } else if (attachmentMimeType.startsWith("image")) {
805- this.controller.stageController.pushScene('imageview', attachmentUri);
806- } else {
807- if (attachmentUri.startsWith("/")) {
808- attachmentUri = "file://" + attachmentUri;
809- }
810- //Message.launchAttachment(this.controller, attachmentUri, this.error.bind(this));
811- Message.getResourceType(this.controller, attachmentUri, attachmentMimeType, this.gotResourceType.bind(this), this.useInternalResourceHandler.bind(this, attachmentMimeType));
812- }
813- } else if (event.target.className === "download-cancel") {
814- this.stopAttachmentDownload(listDiv.id);
815- } else {
816- // Stop the timeout
817- if (this.hideTimeout) {
818- clearTimeout(this.hideTimeout);
819- this.hideTimeout = null;
820- }
821- this.startAttachmentDownload(listDiv.id);
822- }
823- }
824- },
825-
826- handleImageTapped: function(event) {
827- var imgElem = this.controller.get(event.target.id);
828- var uri = imgElem.getAttribute('x-uri');
829- this.controller.stageController.pushScene('imageview', uri);
830- },
831-
832- /*
833- * Callback function for getResourceType
834- */
835- gotResourceType: function(payload) {
836- //Check if this is launchable
837- if(payload.returnValue && payload.appIdByExtension) {
838- Message.launchAttachments(this.controller,payload.uri, payload.appIdByExtension, payload.mimeByExtension);
839- } else {
840- this.useInternalResourceHandler(payload.mimeByExtension, payload);
841- }
842- },
843-
844- useInternalResourceHandler: function(mimeType, response) {
845- var type = Attachments.getIconTypeFromMimeType(mimeType);
846- if (!type) {
847- try {
848- var extensionIndex = response.uri.lastIndexOf('.');
849- if (extensionIndex > 0) {
850- var extension = response.uri.substring(extensionIndex + 1).toLowerCase();
851- type = Attachments.getIconTypeFromExtension(extension);
852- }
853- } catch (e) {
854- Mojo.Log.logException(e, "MessageAssistant.useInternalResourceHandler");
855- }
856- }
857-
858- if (type === "type-image") {
859- this.controller.stageController.pushScene('imageview', response.uri);
860- } else {
861- Mojo.Log.error("Email - Attachment can't be opened!");
862- this.controller.showAlertDialog({
863- onChoose: function(value) {},
864- message: $L("Cannot find an application which can open this file."),
865- choices: [
866- {label:$L('OK'), value:'dismiss', type:'alert'}
867- ]
868- });
869- }
870- },
871-
872- startAttachmentDownload: function(id) {
873- Mojo.Log.info("start attachment download", id);
874- var progressbar = this.controller.get('progress_' + id);
875- if (progressbar.visible()) {
876- Mojo.Log.info("ignoring tap because attachment is already downloading");
877- } else {
878- this.attachmentDLProgress.subs[id] = Message.loadAttachment(id, this.attachmentDownloadProgress.bind(this), this.attachmentError.bind(this, id));
879-
880- // This makes the progressbar show up so the user gets immediate feedback.
881- this.controller.get('file_size_' + id).hide(); // showing the progress bar so hide the file size
882- this.controller.get('progress_' + id).show(); // ensures the progress bar is shown
883- // Set the initial progress to 0%
884- this.attachmentUpdateProgressbar(id, 0);
885- }
886- },
887-
888- stopAttachmentDownload: function(id) {
889- Mojo.Log.info("stop attachment download", id);
890- this.attachmentDLProgress.clearSubscription(id);
891- Message.cancelLoadAttachment(this.controller, id);
892-
893- // This makes the progressbar show up so the user gets immediate feedback.
894- this.controller.get('file_size_' + id).show();
895- this.controller.get('progress_' + id).hide();
896- // Set the initial progress to 0%
897- this.attachmentUpdateProgressbar(id, 0);
898- },
899-
900- handleInviteResponse: function(response, event) {
901- Mojo.Log.info("handleInviteResponse ", response);
902- var notificationAssistant = Mojo.Controller.getAppController().assistant.notificationAssistant;
903- notificationAssistant.handleNotification({ type:"general", message:$L("Sending invitation response") });
904- Email.inviteResponse(this.data, response);
905- this.controller.stageController.popScene();
906- },
907-
908- attachmentError: function(id, err) {
909- Mojo.Log.error("handleError ", err.errorText);
910- this.stopAttachmentDownload(id);
911- var that = this;
912- var errorText;
913- if (err.errorText && err.errorText.length > 0) {
914- errorText = $L("Error downloading file. Server reports: #{errorText}").interpolate(err).escapeHTML();
915- } else {
916- errorText = $L("Error while downloading file.");
917- }
918-
919- this.controller.showAlertDialog({
920- onChoose: function(value) {},
921- title: $L("Unable To Download File"),
922- message: errorText,
923- choices: [ {label:$L('OK'), value:'dismiss', type:'alert'} ]
924- });
925- },
926-
927- handleAttachmentDetails: function(attachment) {
928- Mojo.Log.info("handleAttachmentDetails ", attachment.id);
929-
930- // Update the uri for this attachment now that we got one.
931- this.data.attachments.each(function(a) {
932- if (a.id == attachment.id) {
933- a.uri = attachment.uri;
934- $break;
935- }
936- });
937-
938- // For some reason the id needs to be a string so using toString().
939- var elem = this.controller.get((attachment.id).toString());
940- var newUri = attachment.uri;
941- elem.writeAttribute('x-uri', newUri);
942- elem.writeAttribute('x-mimetype', attachment.mimeType);
943-
944- var audioSetup = this.setupAudioTag(attachment);
945- if (audioSetup) {
946- // The height of the inline audio player is different so need to recalc the list height
947- this.attachmentList.setStyle({height:'auto'}); // make sure the height below is the full height
948- this.attachmentListHeight = parseInt(this.attachmentList.getHeight(), 10);
949- } else {
950- var imgElem = this.controller.get('picture_'+attachment.id);
951- if (imgElem) {
952- Mojo.Log.info("set img src=", newUri);
953- imgElem.writeAttribute('x-uri', newUri);
954- // Following uri uses extractfs to scale the image to fit 320x240.
955- // This fixes problems with extremely large images causing the UI to chug.
956- Attachments.processFileObject(attachment);
957- var extension = attachment.extension.toLowerCase();
958- if (extension === ".jpg" || extension === ".jpeg" || extension === ".png") {
959- imgElem.src = "file:///var/luna/data/extractfs/"+newUri+":0:0:320:240:3";
960- } else {
961- imgElem.src = newUri;
962- }
963-
964- var thumbnail = elem.down('.readview-image-thumbnail');
965- if (thumbnail) {
966- thumbnail.src = "file:///var/luna/data/extractfs/"+newUri+":0:0:31:31:4";
967- thumbnail.show();
968- }
969- }
970- this.controller.get('download_icon_' + attachment.id).hide(); // remove the download arrow from the icon
971- this.controller.get('progress_' + attachment.id).hide(); // ensures the progress bar is no longer shown
972- this.controller.get('file_size_' + attachment.id).show(); // since progress bar is gone, show the file size
973- }
974- },
975-
976- attachmentDownloadProgress: function(info) {
977- // If the object doesn't contain a 'id' property, it isn't valid download progress
978- if (info.id) {
979- if (info.status == Message.ATTACHMENT_LOAD_COMPLETED_EVENT) {
980- Mojo.Log.info("attachmentDownloadProgress complete for id:", info.id);
981- this.attachmentDLProgress.clearSubscription(info.id);
982- this.attachmentUpdateProgressbar(info.id, 100);
983-
984- // Final step to fully download is to get details of this attachment, including the URI.
985- Message.getAttachmentDetails(this.controller, info.id, this.handleAttachmentDetails.bind(this), this.attachmentError.bind(this, info.id));
986- }
987- else if (info.status == Message.ATTACHMENT_LOAD_PROGRESS_EVENT) {
988- // The transports can be rather aggressive about sending progress notifications so
989- // defend against that by only updating the UI periodically
990- var now = new Date().getTime();
991- if (now < this.attachmentDLProgress.lastUpdate + 200) {
992- this.attachmentDLProgress.progress[info.id] = info.progress;
993- } else {
994- this.attachmentDLProgress.lastUpdate = now;
995- var that = this;
996- if (info.progress !== undefined) {
997- this.attachmentDLProgress.progress[info.id] = info.progress;
998- }
999- var progressObj = this.attachmentDLProgress.progress;
1000- Object.keys(progressObj).each(function(id) {
1001- var progress = progressObj[id];
1002- Mojo.Log.info("attachmentDownloadProgress id: ", id, ", progress: ", progress);
1003- that.attachmentUpdateProgressbar(id, progress);
1004- });
1005- }
1006- }
1007- }
1008- },
1009-
1010- attachmentUpdateProgressbar: function(id, percent) {
1011- var progressGroup = this.controller.get('progress_' + id);
1012- if (progressGroup) {
1013- var totalWidth = 2.48; // = 248 / 100%
1014- var progressWidth = Math.round(totalWidth * percent);
1015- progressGroup.down(0).setStyle({width:progressWidth+"px"});
1016- var backgrndWidth = Math.round(totalWidth * (100 - percent));
1017- progressGroup.down(1).setStyle({width:backgrndWidth+"px"});
1018- } else {
1019- Mojo.Log.error("Attachment ID ", id, " is invalid.");
1020- }
1021- },
1022-
1023- hideAttachmentList: function(event) {
1024- if (this.attachmentListShown) {
1025- var targetRow = this.controller.get(event.target);
1026- if (!targetRow.hasClassName('email-readview-attachments')) {
1027- targetRow = targetRow.up('div.email-readview-attachments');
1028- }
1029-
1030- // Only hide the attachments list if the mousedown target was outside the
1031- // list elements. Otherwise, reset the hide timer
1032- if (targetRow == null) {
1033- this.showHideAttachmentList();
1034- } else if (this.hideTimeout) {
1035- clearTimeout(this.hideTimeout);
1036- this.hideTimeout = setTimeout(this.showHideAttachmentList.bind(this), 15000);
1037- }
1038- }
1039- },
1040-
1041- showHideAttachmentList: function() {
1042- if (this.hideTimeout) {
1043- clearTimeout(this.hideTimeout);
1044- this.hideTimeout = null;
1045- }
1046-
1047- if (!this.attachmentListShown) {
1048- this.attachmentList.show();
1049- this.attachmentsShowElem.hide();
1050- }
1051-
1052- var options = {reverse:this.attachmentListShown,
1053- onComplete: this.animationComplete.bind(this),
1054- curve: 'over-easy',
1055- from: 46,
1056- to: this.attachmentListHeight,
1057- duration: 0.6};
1058- Mojo.Animation.animateStyle(this.attachmentList, 'height', 'bezier', options);
1059- },
1060-
1061- animationComplete: function(listElem, cancelled) {
1062- if (!cancelled) {
1063- this.attachmentListShown = !this.attachmentListShown;
1064- if (this.attachmentListShown) {
1065- this.attachmentsShowElem.hide();
1066- this.attachmentList.show();
1067-
1068- this.hideTimeout = setTimeout(this.showHideAttachmentList.bind(this), 15000);
1069- } else {
1070- this.attachmentList.hide();
1071- this.attachmentsShowElem.show();
1072- }
1073- }
1074- },
1075-
1076- /**
1077- * User clicked on a hyperlink.
1078- */
1079- handleLinkClicked: function(event) {
1080- Mojo.Log.info("handleLinkClicked %s", event.url);
1081- this.controller.serviceRequest('palm://com.palm.applicationManager',
1082- {
1083- method: 'open',
1084- parameters: {target: event.url}
1085- });
1086- },
1087-
1088- /**
1089- * WebView widget wants us to create a new page.
1090- */
1091- handleCreatePage: function(event) {
1092- Mojo.Log.info("handleCreatePage: %s", event.pageIdentifier);
1093- this.controller.serviceRequest('palm://com.palm.applicationManager',
1094- {
1095- method: 'open',
1096- parameters: {
1097- 'id': 'com.palm.app.browser',
1098- 'params': {scene: 'page', pageIdentifier: event.pageIdentifier}
1099- }
1100- });
1101- },
1102-
1103- /**
1104- * handle a menu command.
1105- */
1106- handleCommand: function(event) {
1107- if (event.type == Mojo.Event.command) {
1108- try {
1109- switch (event.command) {
1110- case 'reply':
1111- this.reply();
1112- break;
1113-
1114- case 'replyAll':
1115- this.replyAll();
1116- break;
1117-
1118- case 'forward':
1119- this.forward();
1120- break;
1121-
1122- case 'delete':
1123- this.deleteEmail();
1124- break;
1125-
1126- case 'move':
1127- this.controller.stageController.pushScene("moveto", this.account);
1128- break;
1129-
1130- case 'mark-unread':
1131- var currentLabel = this.markUnreadMenuItem.label;
1132- var markRead = (currentLabel == MessageAssistant.kAppMenuMarkRead);
1133- Email.setRead(this.data.id, markRead);
1134- if (markRead) {
1135- this.markUnreadMenuItem.label = MessageAssistant.kAppMenuMarkUnread;
1136- } else {
1137- this.markUnreadMenuItem.label = MessageAssistant.kAppMenuMarkRead;
1138- }
1139- break;
1140-
1141- case 'flag':
1142- this.handleToggleFavorites();
1143- break;
1144-
1145- case Mojo.Menu.prefsCmd:
1146- MenuController.showPrefs(this.controller.stageController);
1147- break;
1148-
1149- case Mojo.Menu.helpCmd:
1150- MenuController.showHelp();
1151- break;
1152- }
1153- }
1154- catch (e) {
1155- Mojo.Log.error("MessageAssistant.handleCommand: "+ e.message +" in "+ e.sourceURL +"("+ e.line +")" );
1156- }
1157- }
1158- // Enable prefs & help menu items
1159- else if (event.type == Mojo.Event.commandEnable &&
1160- (event.command == Mojo.Menu.prefsCmd || event.command == Mojo.Menu.helpCmd)) {
1161- event.stopPropagation();
1162- }
1163- },
1164-
1165- handleInlineImageSaved: function(event) {
1166- if (event.status) {
1167- var filepath = this.makeTitleFromUrl(event.filepath);
1168- var message = $L('Saving "#{path}"').interpolate({path: filepath});
1169- Mojo.Controller.appController.showBanner(
1170- {messageText: message},
1171- {banner: 'image', filename: event.filepath});
1172- }
1173- },
1174-
1175- makeTitleFromUrl: function(url) {
1176- if (url) {
1177- var result = url.match(/^.*\/([^\/]+)$/);
1178- if ((result !== null) && (result.length > 1)) {
1179- return result[1];
1180- }
1181- }
1182-
1183- return url;
1184- },
1185-
1186- handleWebViewSingleTap: function(event) {
1187- try {
1188- var tapPt = Element.viewportOffset(this.webview);
1189- tapPt.left = event.centerX - tapPt.left;
1190- tapPt.top = event.centerY - tapPt.top;
1191-
1192- //Mojo.Log.info("MessageAssistant.handleWebViewSingleTap(): event.altKey=%s, tapPt.left=%d, tapPt.top=%d", event.altKey, tapPt.left, tapPt.top);
1193- if (event.altKey) {
1194- var popupItems = [
1195- {label: $L('Open URL'), command:'openNew'},
1196- {label: $L('Share Link'), command:'shareUrl'},
1197- {label: $L('Copy URL'), command:'copyUrl'},
1198- {label: $L('Copy to Photos'), command:'copyToPhotos'},
1199- {label: $L('Share Image'), command:'shareImage'} //,
1200- //{label: $L('Set Wallpaper'), command:'setWallpaper'}
1201- ];
1202-
1203- var findItem = function(command) {
1204- var i;
1205- for (i = 0; i < popupItems.length; i++) {
1206- if (popupItems[i].command === command) {
1207- return popupItems[i];
1208- }
1209- }
1210- };
1211-
1212- var selectedCommand;
1213- var imageInfo;
1214-
1215- var saveImageCallback = function(succeeded, path) {
1216- if (succeeded) {
1217- switch (selectedCommand) {
1218- case 'shareImage':
1219- this.shareImage(imageInfo, path);
1220- break;
1221- case 'setWallpaper':
1222- this.setWallpaper(path);
1223- break;
1224- case 'copyToPhotos':
1225- this.showOkAlert($L('Image Saved'),
1226- $L('The image was successfully added to your photo album.'));
1227- break;
1228- }
1229- }
1230- else {
1231- this.showOkAlert($L('Error Saving Image'),
1232- $L('There was an error saving the selected image.'));
1233- }
1234- }.bind(this);
1235-
1236- var urlInfo = {};
1237- var popupSelectFunc = function(value) {
1238- selectedCommand = value;
1239-
1240- switch (value) {
1241- case 'openNew':
1242- this.newBrowserPage(urlInfo.url);
1243- break;
1244- case 'shareUrl':
1245- this.shareUrl(urlInfo.url, urlInfo.desc);
1246- break;
1247- case 'copyUrl':
1248- this.controller.stageController.setClipboard(urlInfo.url);
1249- break;
1250- case 'copyToPhotos':
1251- this.webview.mojo.saveImageAtPoint(tapPt.left, tapPt.top, "/media/internal", saveImageCallback);
1252- break;
1253- case 'shareImage':
1254- this.webview.mojo.saveImageAtPoint(tapPt.left, tapPt.top, "/tmp", saveImageCallback);
1255- break;
1256- case 'setWallpaper':
1257- this.webview.mojo.saveImageAtPoint(tapPt.left, tapPt.top, "/media/internal", saveImageCallback);
1258- break;
1259- }
1260- }.bind(this);
1261-
1262- var imageInfoResponse = function(response) {
1263- imageInfo = response;
1264- var usedItems = [];
1265-
1266- if (urlInfo.url) {
1267- usedItems.push( findItem('openNew') );
1268- usedItems.push( findItem('shareUrl') );
1269- usedItems.push( findItem('copyUrl') );
1270- }
1271-
1272- if (response.src) {
1273- usedItems.push( findItem('shareImage') );
1274- }
1275-
1276- if (this.supportedImageType(response.src, response.mimeType)) {
1277- usedItems.push( findItem('copyToPhotos') );
1278- //usedItems.push( findItem('setWallpaper') );
1279- }
1280-
1281- if (usedItems.length) {
1282- this.controller.popupSubmenu({ onChoose: popupSelectFunc, items: usedItems });
1283- }
1284- }.bind(this);
1285-
1286- var urlInspectResponse = function(response) {
1287- urlInfo = response || {};
1288- this.webview.mojo.getImageInfoAtPoint(tapPt.left, tapPt.top, imageInfoResponse);
1289- }.bind(this);
1290-
1291- this.webview.mojo.inspectUrlAtPoint(tapPt.left, tapPt.top, urlInspectResponse);
1292- }
1293- }
1294- catch (e) {
1295- Mojo.Log.logException(e);
1296- }
1297- },
1298-
1299- supportedImageType:function(url, mimeType) {
1300- switch (this.getImageType(url, mimeType)) {
1301- case 'jpeg':
1302- case 'png':
1303- case 'bmp': // We only support 24/32 bit BMP's and don't differentiate here
1304- // GIF not yet supported
1305- return true;
1306- default:
1307- return false;
1308- }
1309- },
1310-
1311- getImageType: function(url, mimeType) {
1312- url = url || '';
1313- mimeType = mimeType || '';
1314- var suffix = '';
1315- try {
1316- suffix = this.getResourceExtension(url);
1317- if (suffix === null) {
1318- suffix = '';
1319- }
1320- }
1321- catch (e) {
1322- Mojo.Log.logException(e);
1323- }
1324-
1325- suffix = suffix.toLowerCase();
1326- mimeType = mimeType.toLowerCase();
1327-
1328- if (suffix === 'jpg' || suffix === 'jpeg' || suffix === 'jpe' || mimeType === 'image/jpeg') {
1329- return 'jpeg';
1330- }
1331- else if (suffix === 'bmp' || mimeType === 'image/bmp') {
1332- return 'bmp';
1333- }
1334- else if (suffix === 'png' || mimeType === 'image/png') {
1335- return 'png';
1336- }
1337- else if (suffix === 'gif' || mimeType === 'image/gif') {
1338- return 'gif';
1339- }
1340- else {
1341- return 'unknown';
1342- }
1343- },
1344-
1345- getResourceExtension: function(url) {
1346- var p = new Poly9.URLParser(url);
1347-
1348- var matches = p.getPathname().match(/\.([^\.]*)$/i);
1349- if (matches) {
1350- return matches[1];
1351- }
1352- else {
1353- return null;
1354- }
1355- },
1356-
1357- newBrowserPage: function(url) {
1358- this.controller.serviceRequest('palm://com.palm.applicationManager',
1359- {
1360- method: 'open',
1361- parameters: {target: url}
1362- });
1363- },
1364-
1365- shareUrl: function(url, title) {
1366- if (url === undefined) {
1367- return;
1368- }
1369-
1370- if (!title) {
1371- try {
1372- title = $L("page at #{host}").interpolate({host: UrlUtil.getUrlHost(url)});
1373- }
1374- catch (e) {
1375- title = url;
1376- }
1377- }
1378-
1379- var text = $L("Here's a website I think you'll like: <a href='#{src}'>#{title}</a>").interpolate(
1380- {src: url, title: title});
1381- var parameters = {
1382- summary: $L('Check out this web page...'),
1383- text: text
1384- };
1385- var email = new Email();
1386- email.evalParams(parameters);
1387- AppAssistant.openComposeStage(email);
1388- },
1389-
1390- shareImage: function(imageInfoObj, pathToImage) {
1391- var title;
1392- if (imageInfoObj.title && imageInfoObj.title.length) {
1393- title = imageInfoObj.title;
1394- }
1395- else if (imageInfoObj.altText && imageInfoObj.altText.length) {
1396- title = imageInfoObj.altText;
1397- }
1398- else {
1399- var p = new Poly9.URLParser(imageInfoObj.src);
1400- title = $L('picture link');
1401- try {
1402- if (imageInfoObj.src !== 'data:') {
1403- title = $L("picture at #{host}").interpolate(
1404- {host: p.getHost()});
1405- }
1406- }
1407- catch (e) {}
1408- }
1409-
1410- var text = $L("Here's a picture I think you'll like: <a href='#{src}'>#{title}</a>").interpolate(
1411- {src: imageInfoObj.src, title: title});
1412-
1413- var parameters = {
1414- summary: $L('Check out this picture...'),
1415- text: text,
1416- attachments: [{fullPath: pathToImage}]
1417- };
1418- var email = new Email();
1419- email.evalParams(parameters);
1420- AppAssistant.openComposeStage(email);
1421- },
1422-
1423- setWallpaper: function(pathToImage) {
1424- var errorTitle = $L("Error Setting Wallpaper");
1425-
1426- var onSetSuccess = function(response) {
1427- this.showOkAlert($L("Wallpaper has been set"),
1428- $L("The image has been successfully set as your wallpaper."));
1429- }.bind(this);
1430-
1431- var onSetFailure = function(response) {
1432- this.showOkAlert(errorTitle, $L("Cannot set picture as current wallpaper."));
1433- }.bind(this);
1434-
1435- var onImportSuccess = function(response) {
1436- this.controller.serviceRequest('palm://com.palm.systemservice/',
1437- {
1438- method : 'setPreferences',
1439- parameters: {wallpaper: response.wallpaper},
1440- onSuccess: onSetSuccess,
1441- onFailure: onSetFailure
1442- });
1443- }.bind(this);
1444-
1445- var onImportFailure = function() {
1446- this.showOkAlert(errorTitle, $L("Cannot import picture into wallpaper database."));
1447- }.bind(this);
1448-
1449- this.controller.serviceRequest('palm://com.palm.systemservice/', {
1450- method : 'wallpaper/importWallpaper',
1451- parameters: {
1452- target: encodeURIComponent(pathToImage),
1453- scale: "1.0"
1454- },
1455- onSuccess: onImportSuccess,
1456- onFailure: onImportFailure
1457- });
1458- },
1459-
1460- showOkAlert: function(title, message)
1461- {
1462- this.controller.showAlertDialog({
1463- title: title, message: message,
1464- choices:[{label:$L('OK'), value:'1', type:'dismiss'}]
1465- });
1466- },
1467-
1468- handleNextMessagesResponse: function(response) {
1469- this.nextMessages = response;
1470-
1471- var prevEmail = this.controller.get('previous_email');
1472- if (!prevEmail) {
1473- Mojo.Log.error("previous_email element does not yet exist");
1474- } else {
1475- if (response.newer === undefined || response.newer.end) {
1476- // hide 'previous_email' because there's no more emails in that direction
1477- prevEmail.addClassName('disabled');
1478- } else {
1479- prevEmail.removeClassName('disabled');
1480- }
1481- }
1482-
1483- var nextEmail = this.controller.get('next_email');
1484- if (!nextEmail) {
1485- Mojo.Log.error("next_email element does not yet exist");
1486- } else {
1487- if (response.older === undefined || response.older.end) {
1488- // hide 'next_email' because there's no more emails in that direction
1489- nextEmail.addClassName('disabled');
1490- } else {
1491- nextEmail.removeClassName('disabled');
1492- }
1493- }
1494- },
1495-
1496- gotoNextEmail: function(direction) {
1497- if (this.nextMessages !== undefined && this.nextMessages[direction] !== undefined) {
1498- var details = this.nextMessages[direction];
1499- if (!details.end && details.id > 0) {
1500- if (this.transition !== null) {
1501- this.transition.cleanup();
1502- }
1503- this.transition = this.controller.prepareTransition(Mojo.Transition.crossFade, false);
1504- this.prepareForNewMessage(details);
1505- }
1506- }
1507- },
1508-
1509- prepareForNewMessage: function(details) {
1510- this.data = { id: details.id, senderDetails:{} }; // data.id
1511- this.account = {};
1512- this.gotFirstResponse = false;
1513- this.bodyLeftOffset = 0;
1514- // Clear out the old email body by loading a simple empty page
1515- this.webview.mojo.openURL(Mojo.appPath + "emptypage.html");
1516- this.plainTextBody.hide();
1517-
1518- // stop listening to all these
1519- this.controller.get('previous_email').stopObserving(Mojo.Event.tap, this.boundGotoNextEmailNewer);
1520- this.controller.get('next_email').stopObserving(Mojo.Event.tap, this.boundGotoNextEmailOlder);
1521- if (this.recipientController) {
1522- this.recipientController.up(".palm-row").stopObserving(Mojo.Event.tap, this.boundShowHideRecipients, true);
1523- }
1524- var elem;
1525- elem = this.controller.get('invite-accept');
1526- if (elem) {
1527- elem.stopObserving(Mojo.Event.tap, this.boundHandleInviteResponseAccept);
1528- }
1529- elem = this.controller.get('invite-tentative');
1530- if (elem) {
1531- elem.stopObserving(Mojo.Event.tap, this.boundHandleInviteResponseTentative);
1532- }
1533- this.controller.get('invite-decline');
1534- if (elem) {
1535- elem.stopObserving(Mojo.Event.tap, this.boundHandleInviteResponseDecline);
1536- }
1537-
1538- // reset the horizontal positioning of these two blocks.
1539- this.emailHeaderBlock.setStyle({ 'left': '0px' });
1540- this.emailPicturesBlock.setStyle({ 'left': '0px' });
1541-
1542- // Subscribe to the new message
1543- if (this.waitingForMessageBodyTimeout !== undefined) {
1544- clearTimeout(this.waitingForMessageBodyTimeout);
1545- this.waitingForMessageBodyTimeout = undefined;
1546- }
1547- if (this.messageSubscription) {
1548- this.messageSubscription.cancel();
1549- }
1550- this.messageSubscription = new Mojo.Service.Request(Message.identifier, {
1551- method: 'messageDetail',
1552- parameters: { 'message':this.data.id, subscribe:true },
1553- onSuccess: this.messageDetailsUpdated.bind(this),
1554- onFailure: this.waitMessageError.bind(this)
1555- });
1556-
1557- Message.getFolderAndAccount(this.controller, this.data.id, this.folderAndAccountDetails.bind(this));
1558- },
1559-
1560- /**
1561- * Called by the webview widget when setup is complete?
1562- */
1563- ready: function() {
1564- this.webview.mojo.addUrlRedirect("^file:.*", false, "", 0);
1565- this.webview.mojo.addUrlRedirect(".*", true, "", 0);
1566- },
1567-
1568- setupMessage: function() {
1569- // Setup the WebView for the body text
1570- // TODO autowidth to true?
1571- var emailStageController = Mojo.Controller.appController.getStageController("email");
1572- var attr = {minFontSize:18,
1573- cacheAdapter:true,
1574- fitWidth: false,
1575- virtualpagewidth: emailStageController.window.innerWidth,
1576- minimumpageheight: 32, // Start out very short in case the message body empty
1577- showClickedLink:true};
1578- this.controller.setupWidget('email_body_text', attr);
1579- this.controller.setupWidget('email_no_body_spinner', { spinnerSize: Mojo.Widget.spinnerSmall });
1580-
1581- this.webview = this.controller.get('email_body_text');
1582- this.webview.addEventListener(Mojo.Event.webViewUrlRedirect, this.boundHandleLinkClicked, false);
1583- this.webview.addEventListener(Mojo.Event.webViewMimeNotSupported, this.boundHandleLinkClicked, false);
1584- this.webview.addEventListener(Mojo.Event.webViewMimeHandoff, this.boundHandleLinkClicked, false);
1585- this.webview.addEventListener(Mojo.Event.webViewImageSaved, this.boundHandleInlineImageSaved, false);
1586- this.webview.addEventListener('singletap', this.boundHandleWebViewSingleTap, true);
1587-
1588- this.waitingForMessageBody = undefined;
1589- // subscribe to the message details because the transport may need to send updates in the case where the
1590- // email body and/or attachments isn't yet downloaded
1591- this.messageSubscription = new Mojo.Service.Request(Message.identifier, {
1592- method: 'messageDetail',
1593- parameters: { 'message':this.data.id, subscribe:true },
1594- onSuccess: this.messageDetailsUpdated.bind(this),
1595- onFailure: this.waitMessageError.bind(this)
1596- });
1597- },
1598-
1599- orientationChanged: function(orientation) {
1600- if (orientation === "left" || orientation === "right") {
1601- this.controller.sceneElement.addClassName('landscape');
1602- } else {
1603- this.controller.sceneElement.removeClassName('landscape');
1604- }
1605- },
1606-
1607- focusEmailStage: function() {
1608- if (this.focusStageTimer) {
1609- this.focusStageTimer = undefined;
1610- AppAssistant.focusEmailStage();
1611- }
1612- },
1613-
1614- // This is called from accountpreference assistant when the user removes the account
1615- accountDeletedNotification: function(accountId) {
1616- if (accountId === this.account.account) {
1617- Mojo.Log.warn("MessageAssistant is showing a deleted account, setting up for cleanup");
1618- this.popOnActivate = true;
1619- }
1620- },
1621-
1622- setup: function() {
1623- this.delayActivate = true;
1624- this.messageTarget = this.controller.get('readview-main');
1625- this.messageTarget.observe('mousedown', this.hideAttachmentList.bind(this), true);
1626- this.setupMessage();
1627-
1628- this.emailHeaderBlock = this.controller.get('email_header_block');
1629- this.plainTextBody = this.controller.get('email_text');
1630- this.emailPicturesBlock = this.controller.get('email_pictures_list');
1631-
1632- this.controller.get('email-readview-attachments-list').observe(Mojo.Event.tap, this.handleAttachmentTapped.bind(this));
1633- this.attachmentsShowElem = this.controller.get('show-hide-multi-attachments');
1634- this.attachmentsShowElem.observe(Mojo.Event.tap, this.showHideAttachmentList.bind(this));
1635-
1636- this.cmdMenuModel = {
1637- visible:true,
1638- items: [
1639- {label:$L('Reply'), icon:'reply', command:'reply'},
1640- {label:$L('Reply all'), icon:'reply-all', command:'replyAll'},
1641- {label:$L('Forward'), icon:'forward-email', command:'forward'},
1642- {label:$L('Delete'), icon:'delete', command:'delete'}
1643- ]};
1644- this.controller.setupWidget(Mojo.Menu.commandMenu, undefined, this.cmdMenuModel);
1645-
1646- this.markUnreadMenuItem = {label:MessageAssistant.kAppMenuMarkUnread, command:'mark-unread'};
1647- this.markSetFlagMenuItem = {label:MessageAssistant.kAppMenuSetFlag, command:'flag'};
1648- this.appMenuModel = {
1649- visible:true,
1650- items: [
1651- this.markUnreadMenuItem,
1652- this.markSetFlagMenuItem,
1653- {label:$L('Move to folder...'), command:'move'}
1654- ]};
1655- this.controller.setupWidget(Mojo.Menu.appMenu, undefined, this.appMenuModel);
1656-
1657- Message.getFolderAndAccount(this.controller, this.data.id, this.folderAndAccountDetails.bind(this));
1658-
1659- this.recipsDrawer = { openProperty: false };
1660- this.controller.setupWidget('email_recipients_drawer', {unstyled:true, modelProperty:'openProperty'}, this.recipsDrawer);
1661- this.recipsDrawer.element = this.controller.get('email_recipients_drawer');
1662-
1663- this.emailPicturesBlock.observe(Mojo.Event.tap, this.handleImageTapped.bind(this));
1664- // stop gesture events on pictures so they don't go to the webview widget
1665- this.emailPicturesBlock.observe('gesturestart', function(event) { event.stop(); }, false);
1666- this.emailPicturesBlock.observe('gesturechange', function(event) { event.stop(); }, false);
1667- this.emailPicturesBlock.observe('gestureend', function(event) { event.stop(); }, false);
1668-
1669- this.controller.get('email_from').observe(Mojo.Event.tap, this.handleSenderTap.bind(this));
1670- this.controller.get('email_recipients').observe(Mojo.Event.tap, this.handleRecipientListSelect.bind(this));
1671-
1672- this.boundUpdateRecipientStatus = this.updateRecipientStatus.bind(this);
1673- Mojo.Event.listen(this.controller.stageController.document, Mojo.Event.activate, this.boundUpdateRecipientStatus);
1674-
1675- Mojo.Event.listen(this.controller.getSceneScroller(), Mojo.Event.scrollStarting, this.addAsScrollListener.bind(this));
1676- },
1677-
1678- cleanup: function() {
1679- if (this.waitingForMessageBodyTimeout !== undefined) {
1680- clearTimeout(this.waitingForMessageBodyTimeout);
1681- this.waitingForMessageBodyTimeout = undefined;
1682- }
1683-
1684- if (this.focusStageTimer !== undefined) {
1685- clearTimeout(this.focusStageTimer);
1686- }
1687-
1688- this.webview.removeEventListener(Mojo.Event.webViewUrlRedirect, this.boundHandleLinkClicked, false);
1689- this.webview.removeEventListener(Mojo.Event.webViewMimeNotSupported, this.boundHandleLinkClicked, false);
1690- this.webview.removeEventListener(Mojo.Event.webViewMimeHandoff, this.boundHandleLinkClicked, false);
1691- this.webview.removeEventListener(Mojo.Event.webViewImageSaved, this.boundHandleInlineImageSaved, false);
1692- this.webview.removeEventListener('singletap', this.boundHandleWebViewSingleTap, true);
1693-
1694- if (this.messageSubscription) {
1695- this.messageSubscription.cancel();
1696- }
1697- Mojo.Event.stopListening(this.controller.stageController.document, Mojo.Event.activate, this.boundUpdateRecipientStatus);
1698- Message.closeMessage(this.controller, this.data.id);
1699- },
1700-
1701- aboutToActivate: function(callback) {
1702- if (this.delayActivate === true) {
1703- this.readyToActivateCallback = callback;
1704- } else {
1705- callback();
1706- }
1707- },
1708-
1709- activate: function() {
1710- // If the scene is invalid (usually because the underlying account was deleted),
1711- // just pop the scene and no more.
1712- if (this.popOnActivate === true) {
1713- this.controller.stageController.popScene();
1714- return;
1715- }
1716-
1717- // save the current scene controller physics parameters
1718- var scroller = this.controller.getSceneScroller();
1719- this.savedFlickSpeed = scroller.mojo.kFlickSpeed;
1720- this.savedFlickRatio = scroller.mojo.kFlickRatio;
1721- scroller.mojo.updatePhysicsParameters({flickSpeed: 0.12, flickRatio: 0.2});
1722-
1723- // If the scene is ready to activate and it needs to be focused, do that now.
1724- if (this.focusStageTimer !== undefined) {
1725- clearTimeout(this.focusStageTimer);
1726- this.focusEmailStage();
1727- }
1728-
1729- // prerenderData is only set if the data was supplied from the list assistant. If the
1730- // service already send a response, it will not include the prerenderData property.
1731- if (this.data.prerenderData === true) {
1732- this.prerenderMessage(this.data);
1733- }
1734- },
1735-
1736- deactivate: function() {
1737- // restore the current scene controller physics parameters
1738- var scroller = this.controller.getSceneScroller();
1739- scroller.mojo.updatePhysicsParameters({flickSpeed: this.savedFlickSpeed, flickRatio: this.savedFlickRatio});
1740- },
1741-
1742- reply: function() {
1743- var email = new Email();
1744- email.createReply(this.data, this.account.login);
1745- MenuController.showComposeView(email);
1746- },
1747-
1748- replyAll: function() {
1749- var email = new Email();
1750- email.createReplyAll(this.data, this.account.login);
1751- MenuController.showComposeView(email);
1752- },
1753-
1754- forward: function() {
1755- var email = new Email();
1756- email.createForward(this.data, this.account.login);
1757- MenuController.showComposeView(email);
1758- },
1759-
1760- deleteEmail: function() {
1761- Email.setDeleted(this.data.id, true);
1762- this.controller.stageController.popScene();
1763- }
1764-});
1765-
1766-MessageAssistant.kAppMenuMarkRead = $L('Mark as read');
1767-MessageAssistant.kAppMenuMarkUnread = $L('Mark as unread');
1768-MessageAssistant.kAppMenuSetFlag = $L('Set flag');
1769-MessageAssistant.kAppMenuClearFlag = $L('Clear flag');
1770-
1771-
1772+/* Copyright 2009 Palm, Inc. All rights reserved. */
1773+
1774+var MessageAssistant = Class.create({
1775+ initialize : function(targetEmail, folderId, focusStage, detailsObj) {
1776+ this.data = { id: targetEmail, senderDetails:{} }; // data.id
1777+ // This data is used to pre-render info in case the mailservice isn't able to respond quickly enough
1778+ if (detailsObj) {
1779+ this.data.prerenderData = true;
1780+ this.data.displayName = detailsObj.displayName;
1781+ this.data.summary = detailsObj.summary;
1782+ this.data.timeStamp = detailsObj.timeStamp;
1783+ this.data.flags = detailsObj.flags;
1784+ this.data.priority = detailsObj.priority;
1785+ }
1786+ this.folderId = folderId;
1787+ this.account = {};
1788+ this.gotFirstResponse = false;
1789+ this.bodyLeftOffset = 0;
1790+ this.transition = null;
1791+ this.waitingForMessageBodyTimeout = undefined;
1792+ this.attachmentDLProgress = {
1793+ lastUpdate:0,
1794+ progress:{},
1795+ subs:{},
1796+ clearSubscription: function(id) {
1797+ try {
1798+ this.subs[id].cancel();
1799+ delete (this.subs[id]);
1800+ delete (this.progress[id]);
1801+ } catch (e) {
1802+ Mojo.Log.logException(e, "clearSubscription");
1803+ }
1804+ }
1805+ };
1806+
1807+ this.boundShowHideRecipients = this.showHideRecipients.bind(this);
1808+ this.boundGotoNextEmailNewer = this.gotoNextEmail.bind(this, 'newer');
1809+ this.boundGotoNextEmailOlder = this.gotoNextEmail.bind(this, 'older');
1810+ this.boundHandleInviteResponseAccept = this.handleInviteResponse.bind(this, 'accept');
1811+ this.boundHandleInviteResponseTentative = this.handleInviteResponse.bind(this, 'tentative');
1812+ this.boundHandleInviteResponseDecline = this.handleInviteResponse.bind(this, 'decline');
1813+ this.boundHandleInviteResponseRemove = this.handleInviteResponse.bind(this, 'remove');
1814+ this.boundHandleLinkClicked = this.handleLinkClicked.bind(this);
1815+ this.boundHandleInlineImageSaved = this.handleInlineImageSaved.bind(this);
1816+ this.boundHandleWebViewSingleTap = this.handleWebViewSingleTap.bind(this);
1817+
1818+ if (focusStage === true) {
1819+ this.focusStageTimer = this.focusEmailStage.bind(this).delay(0.6);
1820+ }
1821+ },
1822+
1823+ addAsScrollListener: function(event) {
1824+ event.scroller.addListener(this);
1825+ },
1826+
1827+ moved: function() {
1828+ var scrollOffset = this.messageTarget.viewportOffset();
1829+ if (this.bodyLeftOffset === scrollOffset.left) {
1830+ return;
1831+ } else if (this.webview !== undefined) {
1832+ this.bodyLeftOffset = scrollOffset.left;
1833+ // Get the width of the browseradapter since that should tell the truth about its width
1834+ var webviewWidth = this.webview.down().getWidth();
1835+ //console.log("offsets: ov x=" + scrollOffset.left + ", w=" + this.emailHeaderBlock.getWidth() + ", wh=" + webviewWidth);
1836+
1837+ if (scrollOffset.left > 0) {
1838+ scrollOffset.left = 0;
1839+ } else {
1840+ var rightExtent = (this.emailHeaderBlock.getWidth() - webviewWidth);
1841+ if (scrollOffset.left < rightExtent) {
1842+ scrollOffset.left = rightExtent;
1843+ }
1844+ }
1845+
1846+ // move the header block & the attached pictures block so they appear to not scroll horizontally
1847+ var leftOffset = -scrollOffset.left + 'px';
1848+ this.emailHeaderBlock.setStyle({ 'left': leftOffset });
1849+ this.emailPicturesBlock.setStyle({ 'left': leftOffset });
1850+ }
1851+ },
1852+
1853+ displayContactAvatarAndPresence: function(baseId, storageId, resp) {
1854+ var nameId = baseId + "-name";
1855+ var avatarId = baseId + "-photo";
1856+ var presenceId = baseId + "-presence";
1857+
1858+ // If this person isn't in contact, give him ID=0. This is done in case the
1859+ // contact was deleted, in which case the old settings need to be cleared.
1860+ if (!resp.record) {
1861+ resp.record = { id:0 };
1862+ }
1863+
1864+ if (baseId === "email-sender") {
1865+ this.data.fromID = resp.record.id;
1866+
1867+//<Reminder Info>
1868+ if (resp.record && resp.record.reminder) {
1869+ if (this.contactReminder === undefined) {
1870+ this.contactReminder = new ContactReminder();
1871+ }
1872+ this.contactReminder.displayReminder(resp);
1873+ }
1874+ } else {
1875+ this.controller.get(storageId).writeAttribute('contactid', resp.record.id);
1876+ }
1877+
1878+ var nameElem = this.controller.get(nameId);
1879+ if (resp.record.displayText) {
1880+ nameElem.update(resp.record.displayText);
1881+ } else if (resp.record.firstName && resp.record.lastName) {
1882+ nameElem.update(resp.record.firstName + " " + resp.record.lastName);
1883+ } else if (resp.record.firstName) {
1884+ nameElem.update(resp.record.firstName);
1885+ } else if (resp.record.lastName) {
1886+ nameElem.update(resp.record.lastName);
1887+ }
1888+
1889+ if (resp.record.pictureLocSquare) {
1890+ Mojo.Log.info("Displaying sender's picture ", resp.record.pictureLocSquare);
1891+ var imgElem = this.controller.get(avatarId);
1892+ imgElem.src = "file://" + resp.record.pictureLocSquare;
1893+ var imgFrame = imgElem.up('.from-photo');
1894+ if (imgFrame !== undefined) {
1895+ imgFrame.show();
1896+ }
1897+ imgElem.up().up().up().addClassName("has-avatar-icon");
1898+ }
1899+
1900+ if (resp.record) {
1901+ if (resp.record.imAvailability !== undefined &&
1902+ resp.record.imAvailability !== null &&
1903+ resp.record.imAvailability !== IMName.NO_PRESENCE) {
1904+ Mojo.Log.info("imAvailability = ", resp.record.imAvailability);
1905+ var imPresence;
1906+ switch (resp.record.imAvailability) {
1907+ case IMName.BUSY:
1908+ imPresence = 'status-busy';
1909+ break;
1910+ case IMName.IDLE:
1911+ imPresence = 'status-idle';
1912+ break;
1913+ case IMName.ONLINE:
1914+ imPresence = 'status-available';
1915+ break;
1916+ default:
1917+ imPresence = 'status-offline';
1918+ }
1919+ var imgElem = this.controller.get(presenceId);
1920+ imgElem.addClassName(imPresence);
1921+ imgElem.show();
1922+ }
1923+ }
1924+ },
1925+
1926+ folderAndAccountDetails: function(resp) {
1927+ // this gives us the following properties folder, account, login, protocol: EAS|IMAP|POP3}
1928+ this.account = resp;
1929+ // Add the email id to the account info because it will be used by the moveto scene
1930+ this.account.emailId = this.data.id;
1931+ var assistant = Mojo.Controller.getAppController().assistant;
1932+ assistant.notificationAssistant.clear(resp.account, resp.folder, this.data.id);
1933+ assistant.clearDebounce('m'+this.data.id);
1934+ },
1935+
1936+ updateRecipientStatus: function() {
1937+ EmailRecipient.getDetails(this.controller, this.data.senderDetails.address, this.displayContactAvatarAndPresence.bind(this, 'email-sender', null));
1938+
1939+ if (this.data.onlyRecipients !== undefined) {
1940+ var theThis = this;
1941+ this.data.onlyRecipients.each(function(r) {
1942+ EmailRecipient.getDetails(theThis.controller, r.address, theThis.displayContactAvatarAndPresence.bind(theThis, 'recip-'+r.id, r.id));
1943+ });
1944+ }
1945+ },
1946+
1947+ handleSenderTap: function(event) {
1948+ this.showSenderContactDetails(event);
1949+ },
1950+
1951+ showSenderContactDetails: function(event) {
1952+ if (this.data.fromID) {
1953+ EmailRecipient.launchContactDetails(this.controller, this.data.fromID);
1954+ } else {
1955+ var displayName = this.data.displayName;
1956+ // if the display name is the email address it isn't really the person's name
1957+ if (displayName === this.data.senderDetails.address) {
1958+ displayName = "";
1959+ }
1960+ EmailRecipient.addToContacts(this.controller, this.data.senderDetails.address, displayName);
1961+ }
1962+ },
1963+
1964+ handleRecipientListSelect: function(event) {
1965+ var targetRow = this.controller.get(event.target);
1966+ if (!targetRow.hasClassName('email-recipient')) {
1967+ targetRow = targetRow.up('div.email-recipient');
1968+ }
1969+
1970+ if (targetRow) {
1971+ var contactId = targetRow.readAttribute('contactid');
1972+ if (contactId && contactId > 0) {
1973+ EmailRecipient.launchContactDetails(this.controller, contactId);
1974+ } else {
1975+ var address = targetRow.readAttribute('address');
1976+ var displayName = targetRow.readAttribute('displayname');
1977+ displayName = displayName.escapeHTML();
1978+ EmailRecipient.addToContacts(this.controller, address, displayName);
1979+ }
1980+ }
1981+ },
1982+
1983+ showHideRecipients: function(event) {
1984+ this.recipsDrawer.element.mojo.setOpenState(!this.recipsDrawer.element.mojo.getOpenState());
1985+ this.controller.get('email_recipients_compressed_list').toggle();
1986+ this.controller.get('email_recipients_compressed_count').toggle();
1987+ },
1988+
1989+ messageDetailsUpdated: function(resp) {
1990+ if (this.gotFirstResponse === false) {
1991+ this.gotFirstResponse = true;
1992+ this.renderMessage(resp);
1993+ } else {
1994+ Mojo.Log.info("renderMessage: waiting for message body. isFullyLoaded=", resp.isFullyLoaded);
1995+ if (resp.isFullyLoaded == "true") {
1996+ if (this.waitingForMessageBodyTimeout !== undefined) {
1997+ clearTimeout(this.waitingForMessageBodyTimeout);
1998+ this.waitingForMessageBodyTimeout = undefined;
1999+ }
2000+ // Add the email body to the stored data model
2001+ this.data.textURI = resp.textURI;
2002+ this.data.text = resp.text;
2003+ this.renderMessageBody(resp);
2004+ }
2005+
2006+ // POP doesn't know it has attachments until it has downloaded the full envelope
2007+ // so use the new attachment list if one there doesn't already exist
2008+ if ((!this.data.attachments || this.data.attachments.length === 0) &&
2009+ resp.attachments && EmailFlags.hasAttachment(resp.flags)) {
2010+ this.data.attachments = resp.attachments;
2011+ this.renderMessageAttachments(resp.attachments);
2012+ }
2013+ }
2014+ },
2015+
2016+ // This is a special function that is only called when the scene is first launched
2017+ // and activate() occurs before the mailservice has time to respond with the full
2018+ // email data.
2019+ prerenderMessage: function(resp) {
2020+ this.data.prerenderData = false; // only want to prerender email once
2021+ Mojo.Log.info("prerenderMessage ", resp.id);
2022+
2023+ if ((resp.flags & EmailFlags.flaggedBit) !== 0) {
2024+ resp.flagged = "starred";
2025+ }
2026+
2027+ //convert the timestamp into a Date
2028+ var theDate = new Date(parseInt(resp.timeStamp));
2029+ resp.formattedDate = Mojo.Format.formatDate(theDate, {date:'medium', time:'short'});
2030+ resp.meridiem = "";
2031+
2032+ var content = Mojo.View.render({template: 'message/message_from', object:resp});
2033+ this.controller.get('email_from').update(content);
2034+ //subject
2035+ resp.priority = Email.getPriorityClass(resp.priority);
2036+ content = Mojo.View.render({template: 'message/message_subject', object: resp});
2037+ this.controller.get('email_subject').update(content);
2038+ },
2039+
2040+ renderMessage: function(resp) {
2041+ var content;
2042+ Mojo.Log.info("renderMessage ", resp.id);
2043+ this.data = resp;
2044+
2045+ // Set the "read" flag if need be
13@@ -272,9 +272,16 @@ var MessageAssistant = Class.create({
14 this.data = resp;
15
16 // Set the "read" flag if need be
204617+ /*
2047+ if (!EmailFlags.isRead(resp.flags)) {
2048+ Email.setRead(resp.id, true);
2049+ }
18 if (!EmailFlags.isRead(resp.flags)) {
19 Email.setRead(resp.id, true);
20 }
205021+ */
205122+ if (EmailFlags.isRead(resp.flags)) {
205223+ this.markUnreadMenuItem.label = MessageAssistant.kAppMenuMarkUnread;
205324+ } else {
205425+ this.markUnreadMenuItem.label = MessageAssistant.kAppMenuMarkRead;
205526+ }
2056+
2057+ // Very first thing to do with recipients is fix them up for the address picker.
2058+ EmailRecipient.addAddressPickerFields(resp.recipients);
2059+
2060+ var attachments = resp.attachments;
2061+
2062+ if (resp.displayName) {
2063+ resp.displayName = resp.displayName.escapeHTML();
2064+ }
2065+
2066+ if (this.data.summary == null || this.data.summary.length === 0) {
2067+ this.data.summary = $L("Untitled message");
2068+ } else {
2069+ resp.summary = resp.summary.escapeHTML();
2070+ }
2071+
2072+ if ((resp.flags & EmailFlags.flaggedBit) !== 0) {
2073+ resp.flagged = "starred";
2074+ this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuClearFlag;
2075+ } else {
2076+ this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuSetFlag;
2077+ }
2078+
2079+ //text -- if it is fully loaded then render it (need to check for string "true" since that's what's coming in json).
2080+ if (this.data.isFullyLoaded == "true") {
2081+ this.renderMessageBody(this.data);
2082+ } else {
2083+ this.waitForMessageBody();
2084+ }
2085+
2086+ //convert the timestamp into a Date
2087+ //February 15, 2008 - 12:57 PM
2088+ var theDate = new Date(parseInt(resp.timeStamp));
2089+ resp.formattedDate = Mojo.Format.formatDate(theDate, {date:'medium', time:'short'});
2090+ resp.meridiem = ""; //theDate.toString($L('t'));
2091+
2092+ content = Mojo.View.render({template: 'message/message_from', object:resp});
2093+ this.controller.get('email_from').update(content);
2094+ //subject
2095+ resp.priority = Email.getPriorityClass(resp.priority);
2096+ content = Mojo.View.render({template: 'message/message_subject', object: resp});
2097+ this.controller.get('email_subject').update(content);
2098+
2099+ this.controller.get('previous_email').observe(Mojo.Event.tap, this.boundGotoNextEmailNewer);
2100+ this.controller.get('next_email').observe(Mojo.Event.tap, this.boundGotoNextEmailOlder);
2101+
2102+ //recipients
2103+ this.renderMessageRecipients(resp);
2104+
2105+ if (EmailFlags.isMeetingRequest(resp.flags)) {
2106+ var invite = new Object();
2107+ if (resp.whenStart) {
2108+ var startDate = new Date(parseInt(resp.whenStart));
2109+ var dateFormat = $L('EEE, MMM d');
2110+ var whenObject = {
2111+ date: Mojo.Format.formatDate(startDate, {date:dateFormat}),
2112+ startTime: Mojo.Format.formatDate(startDate, {time:'short'}),
2113+ endTime: Mojo.Format.formatDate(new Date(parseInt(resp.whenEnd)), {time:'short'})
2114+ };
2115+ invite.when = $L("#{date}, #{startTime} - #{endTime}").interpolate(whenObject);
2116+ } else {
2117+ // Service used to format the date. That doesn't work for too many reasons
2118+ invite.when = $L("#{when}").interpolate(resp); //TODO parse this and put in local date format
2119+ }
2120+ if (resp.busyStatus === 0) {
2121+ // Maybe change the style here
2122+ invite.conflict = $L("Conflicts with another event");
2123+ }
2124+ invite.where = $L("#{where}").interpolate(resp).escapeHTML();
2125+
2126+ content = Mojo.View.render({template: 'message/meeting_invitation', object: invite});
2127+ this.controller.get('email-readview-invitations').update(content);
2128+
2129+ this.controller.get('invite-accept').observe(Mojo.Event.tap, this.boundHandleInviteResponseAccept);
2130+ this.controller.get('invite-tentative').observe(Mojo.Event.tap, this.boundHandleInviteResponseTentative);
2131+ this.controller.get('invite-decline').observe(Mojo.Event.tap, this.boundHandleInviteResponseDecline);
2132+ }
2133+ else if (EmailFlags.isMeetingCancel(resp.flags)) {
2134+ content = Mojo.View.render({template: 'message/meeting_cancellation', object: {}});
2135+ this.controller.get('email-readview-invitations').update(content);
2136+ this.controller.get('invite-remove').observe(Mojo.Event.tap, this.boundHandleInviteResponseRemove);
2137+ }
2138+ else {
2139+ this.controller.get('email-readview-invitations').update("");
2140+ }
2141+
2142+ // Attachments - some may be in the HTML body, so only show the attachments area if there
2143+ // is something in the attachments array
2144+ if (attachments && EmailFlags.hasAttachment(this.data.flags)) {
2145+ this.renderMessageAttachments(attachments);
2146+ } else {
2147+ // If no attachments, make sure old UI is cleaned out.
2148+ this.controller.get('email-readview-attachments-block').hide();
2149+ this.emailPicturesBlock.update("");
2150+ }
2151+
2152+ // Ensure the whiteness is at least tall enough to fill the screen.
2153+ var contentContainer = this.controller.get('email-readview-content-container');
2154+ var po = contentContainer.positionedOffset();
2155+ var minHeight = (this.controller.window.innerHeight - po.top) + 'px';
2156+ contentContainer.setStyle({'min-height':minHeight})
2157+
2158+ // Got the message details so say we're ready to render
2159+ if (this.delayActivate === true) {
2160+ this.delayActivate = false;
2161+ if (this.readyToActivateCallback !== undefined) {
2162+ this.readyToActivateCallback();
2163+ this.readyToActivateCallback = undefined;
2164+ }
2165+ }
2166+
2167+ // Get details about the next and previous emails to know where the user can navigate.
2168+ // NOTE: this needs to be after message_subject is rendered
2169+ this.controller.serviceRequest(Email.identifier, {
2170+ method: 'getNextMessages',
2171+ parameters: {'message':this.data.id, 'folder': this.folderId },
2172+ onSuccess: this.handleNextMessagesResponse.bind(this),
2173+ onFailure: function(response) { Mojo.Log.error("getNextMessages failed "+Object.toJSON(response)); }
2174+ });
2175+
2176+ if (this.transition !== null) {
2177+ this.transition.run();
2178+ this.transition.cleanup();
2179+ this.transition = null;
2180+ }
2181+ },
2182+
2183+ renderMessageRecipients: function(resp) {
2184+ var recips = resp.recipients;
2185+ this.data.onlyRecipients = [];
2186+ this.data.senderDetails = {};
2187+ var content = null;
2188+ if (recips) {
2189+ var recipTypes;
2190+ if (EmailFlags.isMeetingType(resp.flags)) {
2191+ recipTypes = [ $L("From"), $L("Required"), $L("Optional"), $L("Bcc"), $L("From") ];
2192+ } else {
2193+ recipTypes = [ $L("From"), $L("To"), $L("Cc"), $L("Bcc"), $L("From") ];
2194+ }
2195+
2196+ var theThis = this;
2197+ var prevRecp = {}; // This is used to determine when to display To/Cc/Bcc divider
2198+ recips.each(function(r) {
2199+ if(recipTypes[r.role] !== undefined) {
2200+ if (r.role === EmailRecipient.roleTo || r.role === EmailRecipient.roleCc || r.role === EmailRecipient.roleBcc) {
2201+ r.roleStr = recipTypes[r.role];
2202+ if (r.role != prevRecp.role) {
2203+ r.rowDisplayRole = "show";
2204+ r.hasDivider = "has-divider";
2205+ prevRecp.hideLast = "last";
2206+ }
2207+ theThis.data.onlyRecipients.push(r);
2208+ EmailRecipient.getDetails(theThis.controller, r.address, theThis.displayContactAvatarAndPresence.bind(theThis, 'recip-'+r.id, r.id));
2209+ prevRecp = r;
2210+ }
2211+ // roleReplyTo can overwrite the senderDetails because it takes precidence over roleFrom
2212+ else if (r.role === EmailRecipient.roleReplyTo) {
2213+ theThis.data.senderDetails = r;
2214+ }
2215+ // roleFrom should not overwrite the senderDetails because roleReplyTo takes precidence
2216+ else if (r.role === EmailRecipient.roleFrom && theThis.data.senderDetails.address === undefined) {
2217+ theThis.data.senderDetails = r;
2218+ }
2219+ }
2220+ });
2221+
2222+ if (this.data.senderDetails.address !== undefined) {
2223+ // Get the contact ID for the sender of the email
2224+ EmailRecipient.getDetails(this.controller, this.data.senderDetails.address, this.displayContactAvatarAndPresence.bind(this, 'email-sender', null));
2225+ }
2226+
2227+ if (this.data.onlyRecipients.length > 0) {
2228+ var recipElem = this.controller.get('email_recipients');
2229+ content = Mojo.View.render({
2230+ collection: this.data.onlyRecipients,
2231+ template: 'message/message_recips'
2232+ });
2233+ recipElem.update(content);
2234+
2235+ var recipsSummary = { count:0, to:[], cc:[], bcc:[] };
2236+ this.data.onlyRecipients.each(function(r) {
2237+ if (r.role === EmailRecipient.roleTo) {
2238+ recipsSummary.to.push(r.displayName);
2239+ recipsSummary.count++;
2240+ } else if (r.role === EmailRecipient.roleCc) {
2241+ recipsSummary.cc.push(r.displayName);
2242+ recipsSummary.count++;
2243+ } else if (r.role === EmailRecipient.roleBcc) {
2244+ recipsSummary.bcc.push(r.displayName);
2245+ recipsSummary.count++;
2246+ }
2247+ });
2248+
2249+ if (recipsSummary.count > 0) {
2250+ recipsSummary.displayTo = "none";
2251+ recipsSummary.displayCc = "none";
2252+ recipsSummary.displayBcc = "none";
2253+ if (recipsSummary.to.length > 0 && recipsSummary.cc.length > 0) {
2254+ recipsSummary.displayTo = "inline";
2255+ recipsSummary.displayCc = "inline";
2256+ recipsSummary.toList = recipsSummary.to.join(', ');
2257+ recipsSummary.ccList = recipsSummary.cc.join(', ');
2258+ } else if (recipsSummary.to.length > 0) {
2259+ recipsSummary.displayTo = "inline";
2260+ recipsSummary.toList = recipsSummary.to.join(', ');
2261+ } else if (recipsSummary.cc.length > 0) {
2262+ recipsSummary.displayCc = "inline";
2263+ recipsSummary.ccList = recipsSummary.cc.join(', ');
2264+ } else if (recipsSummary.bcc.length > 0) {
2265+ recipsSummary.displayBcc = "inline";
2266+ recipsSummary.bccList = recipsSummary.bcc.join(', ');
2267+ }
2268+
2269+ if (recipsSummary.count === 1) {
2270+ recipsSummary.recipientCount = $L("1 recipient");
2271+ } else {
2272+ recipsSummary.recipientCount = $L("#{count} recipients").interpolate(recipsSummary);
2273+ }
2274+
2275+ content = Mojo.View.render({template: 'message/message_recips_compressed', object: recipsSummary });
2276+ this.recipientController = this.controller.get('email_recipients_controller')
2277+ this.recipientController.update(content);
2278+ // Observe the row since that is the entire height and width of the tappable area
2279+ this.recipientController.up(".palm-row").observe(Mojo.Event.tap, this.boundShowHideRecipients, true);
2280+ }
2281+ }
2282+ }
2283+
2284+ // If there were no recipients, then display the default "no recips"
2285+ if (content === null) {
2286+ Mojo.Log.info("displaying 'Only BCC recipients'");
2287+ var renderParams = {
2288+ template: 'message/message_recips_compressed',
2289+ object: {onlyBccRecipient: $L("Only BCC recipients"), displayTo: "none", displayCc: "none", displayBcc: "none" }
2290+ };
2291+ this.controller.get('email_recipients_controller').update(Mojo.View.render(renderParams));
2292+ }
2293+ },
2294+
2295+ processTextProperty: function(data) {
2296+ // If it is an html email, it starts with "<!DOCTYPE" or "<html" some simple html tag
2297+ if (!data.isHtml) {
2298+ //console.log("******* NO MATCH *********")
2299+ // Plain text needs to replace the carriage returns with html line-break tags
2300+ data.text = data.text.escapeHTML().gsub("\n","<br>");
2301+ } else {
2302+ //console.log("******* MATCH *********")
2303+ data.text = data.text.stripScripts();
2304+ }
2305+ },
2306+
2307+ renderMessageBody: function(data) {
2308+ if (data.isFullyLoaded != "true") {
2309+ Mojo.Log.error("renderMessageBody aborted because data.isFullyLoaded == ", data.isFullyLoaded)
2310+ return;
2311+ }
2312+
2313+ this.hideNoBodyUI();
2314+
2315+ if (data.textURI && this.webview.mojo.openURL) {
2316+ this.controller.get('email_text').hide();
2317+
2318+ try {
2319+ Mojo.Log.breadcrumb("rendering URI " + data.textURI);
2320+ this.webview.mojo.setEnableJavaScript(false);
2321+ this.webview.mojo.openURL("file://" + data.textURI);
2322+ } catch (e) {
2323+ Mojo.Log.logException(e, 'renderMessageBody openUrl');
2324+ }
2325+
2326+ // If the service didn't supply the text to use for replying to an email, request it now.
2327+ if (!data.text) {
2328+ var processTextProperty = this.processTextProperty;
2329+ this.controller.serviceRequest(Message.identifier, {
2330+ method: 'messageFileText',
2331+ parameters: { 'textURI':data.textURI },
2332+ onSuccess: function(resp) {
2333+ data.text = resp.text;
2334+ processTextProperty(data);
2335+ },
2336+ onFailure: function() { Mojo.Log.error("messageFileText failed for textURI", data.textURI) }
2337+ });
2338+ } else {
2339+ // Defer b/c this can be done after the email is rendered
2340+ this.processTextProperty.defer(data);
2341+ }
2342+ } else {
2343+ Mojo.Log.warn("WARNING: EMAIL using plain text");
2344+ var adapter = this.controller.get('email_body_text_outer');
2345+ if (adapter == null) {
2346+ adapter = this.controller.get('email_body_text');
2347+ }
2348+ adapter.hide();
2349+ var emailBody = this.controller.get('email_text');
2350+ // Strip out all scripts since they can do dangerous things
2351+ data.text = data.text.stripScripts();
2352+ // Since this is plain text, need to replace the carriage returns with html line-break tags
2353+ data.text = data.text.gsub("\n","<br>");
2354+ data.styles = "width:"+this.controller.window.innerWidth+"px;";
2355+ var content = Mojo.View.render({
2356+ template: 'message/message_text',
2357+ object: data
2358+ });
2359+ emailBody.update(content);
2360+ emailBody.show();
2361+ }
2362+
2363+ this.webview.mojo.focus();
2364+ },
2365+
2366+ renderMessageAttachments: function(attachments) {
2367+ // Show the div containing the attachments. For multiple attachments, also
2368+ // show the expand/collapse controller
2369+ this.controller.get('email-readview-attachments-block').show();
2370+
2371+ var picturesList = [];
2372+ var names = undefined;
2373+ attachments.each(function(a) {
2374+ a.displayName = a.displayName.escapeHTML();
2375+ if (names === undefined) {
2376+ names = a.displayName.substring(0); // copy of the name
2377+ } else {
2378+ names += ", " + a.displayName;
2379+ }
2380+ // Icons
2381+ Attachments.processFileObject(a);
2382+ // Picture to be displayed
2383+ if (a.iconType == "type-image") {
2384+ var extension = a.extension.toLowerCase();
2385+ if (extension === ".jpg" || extension === ".jpeg" || extension === ".png") {
2386+ a.fixeduri = "file:///var/luna/data/extractfs/" + a.uri + ":0:0:320:240:3";
2387+ } else {
2388+ a.fixeduri = a.uri;
2389+ }
2390+ picturesList.push(a);
2391+ } else {
2392+ a.displayImage = "display:none";
2393+ }
2394+
2395+ // Download %
2396+ if (a.uri) {
2397+ a.display = "display: none;";
2398+ a.download = "";
2399+ } else {
2400+ a.download = "show-download-icon";
2401+ a.display = "display: none;";
2402+ }
2403+ });
2404+
2405+ // Only show the special "this file and N others" if there's more than 1 attachment
2406+ if (attachments.length > 1) {
2407+ // Setup the attachment compressed view
2408+ var content = Mojo.View.render({
2409+ object: { displayName:names, count:attachments.length },
2410+ template: 'message/message_attachments_compressed'
2411+ });
2412+ this.attachmentsShowElem.update(content);
2413+ }
2414+
2415+ content = Mojo.View.render({
2416+ collection: attachments,
2417+ template: 'message/message_attachments'
2418+ });
2419+ this.controller.get('email-readview-attachments-list').update(content);
2420+
2421+ // Attachment pictures
2422+ if (picturesList.length > 0) {
2423+ content = Mojo.View.render({
2424+ collection: picturesList,
2425+ template: 'message/message_pictures'
2426+ });
2427+ this.emailPicturesBlock.update(content);
2428+ } else {
2429+ this.emailPicturesBlock.update("");
2430+ }
2431+
2432+ // Convert any downloaded audio attachments to AudioTag controls
2433+ attachments.each(function(a) {
2434+ if (a.uri !== undefined) {
2435+ this.setupAudioTag(a);
2436+ }
2437+ }.bind(this));
2438+
2439+ //
2440+ // Now that the contents of the list are ready, compute heights and setup the "drawer"
2441+ //
2442+ this.attachmentList = this.controller.get('attachlist-list1');
2443+ this.attachmentList.setStyle({height:'auto'}); // make sure the height below is the full height
2444+ this.attachmentListHeight = parseInt(this.attachmentList.getHeight(), 10);
2445+
2446+ // If the list contains only 1 item so it isn't considered as "shown"
2447+ this.attachmentListShown = (attachments.length > 1);
2448+ if (this.attachmentListShown) {
2449+ // Initially hide the attachments list
2450+ this.attachmentListShown = false;
2451+ this.attachmentList.setStyle({'height':'46px'});
2452+ this.attachmentList.hide();
2453+ this.attachmentsShowElem.show();
2454+ } else {
2455+ this.attachmentsShowElem.hide();
2456+ this.attachmentList.show();
2457+ }
2458+ },
2459+
2460+ setupAudioTag: function(a) {
2461+ var success = false;
2462+ if (a.uri !== undefined && a.mimeType.startsWith("audio")) {
2463+ var audioElement = this.controller.get("progress_" + a.id);
2464+ try {
2465+ audioElement.show();
2466+ var audioTag = AudioTag.extendElement(audioElement, this.controller, a.uri);
2467+ audioTag.autoplay = false;
2468+ this.controller.get("adetails_" + a.id).hide();
2469+ success = true;
2470+ } catch (e) {
2471+ audioElement.hide();
2472+ }
2473+ }
2474+ return success;
2475+ },
2476+
2477+ waitForMessageBody: function() {
2478+ // If the transport can't retrieve the message in a reasonable amount of time, error out
2479+ this.waitingForMessageBodyTimeout = this.waitMessageError.bind(this).delay(45);
2480+ this.showNoBodyUI();
2481+ },
2482+
2483+ waitMessageError: function(resp) {
2484+ if (this.messageSubscription) {
2485+ this.messageSubscription.cancel();
2486+ this.messageSubscription = null;
2487+ }
2488+ Mojo.Log.error("waitMessageError", Object.toJSON(resp));
2489+
2490+ // Waiting for message body means the message exists, but the body
2491+ // isn't yet on device. If the error occured before even waiting for
2492+ // the body, then popScene out to the previous scene because this
2493+ // email doesn't even exists anymore.
2494+ if (this.waitingForMessageBodyTimeout === undefined) {
2495+ // This should only occur if the email ID doesn't exist so go back to the previous scene
2496+ Mojo.Log.error("popping the message scene because message doesn't exists. ID=", this.data.id);
2497+ this.controller.stageController.popScene(resp);
2498+ } else {
2499+ clearTimeout(this.waitingForMessageBodyTimeout);
2500+ this.waitingForMessageBodyTimeout = undefined;
2501+ this.hideNoBodyUI();
2502+
2503+ // Display error text
2504+ this.plainTextBody.update(Mojo.View.render({template: 'message/failed_to_download_err', object:{}}));
2505+ this.plainTextBody.show();
2506+ }
2507+ },
2508+
2509+ showNoBodyUI: function() {
2510+ var nobodyDiv = this.controller.get('email_no_body')
2511+ nobodyDiv.show();
2512+ this.controller.instantiateChildWidgets(nobodyDiv);
2513+ this.controller.get('email_no_body_spinner').mojo.start();
2514+ },
2515+
2516+ hideNoBodyUI: function() {
2517+ this.controller.get('email_no_body_spinner').mojo.stop();
2518+ this.controller.get('email_no_body').hide();
2519+ },
2520+
2521+ handleToggleFavorites: function(event) {
2522+ // Flip the flagged bit
2523+ var value = true;
2524+ this.data.flags = this.data.flags ^ EmailFlags.flaggedBit;
2525+ if ((this.data.flags & EmailFlags.flaggedBit) == 0) {
2526+ this.controller.get('email_favorite').removeClassName("starred");
2527+ this.data.flagged = "";
2528+ this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuSetFlag;
2529+ value = false;
2530+ } else {
2531+ this.controller.get('email_favorite').addClassName("starred");
2532+ this.data.flagged = "starred";
2533+ this.markSetFlagMenuItem.label = MessageAssistant.kAppMenuClearFlag;
2534+ }
2535+ //console.log("Setting flagged for " + this.data.id + " to " + value);
2536+ Email.setFlagged(this.data.id, value);
2537+ },
2538+
2539+ handleAttachmentTapped: function(event) {
2540+ var targetRow = this.controller.get(event.target);
2541+ var listDiv = targetRow;
2542+ if (!listDiv.hasClassName('attachment-info')) {
2543+ listDiv = listDiv.up('div.attachment-info');
2544+ }
2545+
2546+ if (listDiv) {
2547+ var attachmentUri = listDiv.getAttribute('x-uri');
2548+ var attachmentMimeType = listDiv.getAttribute('x-mimetype');
2549+ if (attachmentUri) {
2550+ if (attachmentMimeType.startsWith("audio")) {
2551+ // Do nothing because the AudioTag widget handles taps for audio files
2552+ } else if (attachmentMimeType.startsWith("image")) {
2553+ this.controller.stageController.pushScene('imageview', attachmentUri);
2554+ } else {
2555+ if (attachmentUri.startsWith("/")) {
2556+ attachmentUri = "file://" + attachmentUri;
2557+ }
2558+ //Message.launchAttachment(this.controller, attachmentUri, this.error.bind(this));
2559+ Message.getResourceType(this.controller, attachmentUri, attachmentMimeType, this.gotResourceType.bind(this), this.useInternalResourceHandler.bind(this, attachmentMimeType));
2560+ }
2561+ } else if (event.target.className === "download-cancel") {
2562+ this.stopAttachmentDownload(listDiv.id);
2563+ } else {
2564+ // Stop the timeout
2565+ if (this.hideTimeout) {
2566+ clearTimeout(this.hideTimeout);
2567+ this.hideTimeout = null;
2568+ }
2569+ this.startAttachmentDownload(listDiv.id);
2570+ }
2571+ }
2572+ },
2573+
2574+ handleImageTapped: function(event) {
2575+ var imgElem = this.controller.get(event.target.id);
2576+ var uri = imgElem.getAttribute('x-uri');
2577+ this.controller.stageController.pushScene('imageview', uri);
2578+ },
2579+
2580+ /*
2581+ * Callback function for getResourceType
2582+ */
2583+ gotResourceType: function(payload) {
2584+ //Check if this is launchable
2585+ if(payload.returnValue && payload.appIdByExtension) {
2586+ Message.launchAttachments(this.controller,payload.uri, payload.appIdByExtension, payload.mimeByExtension);
2587+ } else {
2588+ this.useInternalResourceHandler(payload.mimeByExtension, payload);
2589+ }
2590+ },
2591+
2592+ useInternalResourceHandler: function(mimeType, response) {
2593+ var type = Attachments.getIconTypeFromMimeType(mimeType);
2594+ if (!type) {
2595+ try {
2596+ var extensionIndex = response.uri.lastIndexOf('.');
2597+ if (extensionIndex > 0) {
2598+ var extension = response.uri.substring(extensionIndex + 1).toLowerCase();
2599+ type = Attachments.getIconTypeFromExtension(extension);
2600+ }
2601+ } catch (e) {
2602+ Mojo.Log.logException(e, "MessageAssistant.useInternalResourceHandler");
2603+ }
2604+ }
2605+
2606+ if (type === "type-image") {
2607+ this.controller.stageController.pushScene('imageview', response.uri);
2608+ } else {
2609+ Mojo.Log.error("Email - Attachment can't be opened!");
2610+ this.controller.showAlertDialog({
2611+ onChoose: function(value) {},
2612+ message: $L("Cannot find an application which can open this file."),
2613+ choices: [
2614+ {label:$L('OK'), value:'dismiss', type:'alert'}
2615+ ]
2616+ });
2617+ }
2618+ },
2619+
2620+ startAttachmentDownload: function(id) {
2621+ Mojo.Log.info("start attachment download", id);
2622+ var progressbar = this.controller.get('progress_' + id);
2623+ if (progressbar.visible()) {
2624+ Mojo.Log.info("ignoring tap because attachment is already downloading");
2625+ } else {
2626+ this.attachmentDLProgress.subs[id] = Message.loadAttachment(id, this.attachmentDownloadProgress.bind(this), this.attachmentError.bind(this, id));
2627+
2628+ // This makes the progressbar show up so the user gets immediate feedback.
2629+ this.controller.get('file_size_' + id).hide(); // showing the progress bar so hide the file size
2630+ this.controller.get('progress_' + id).show(); // ensures the progress bar is shown
2631+ // Set the initial progress to 0%
2632+ this.attachmentUpdateProgressbar(id, 0);
2633+ }
2634+ },
2635+
2636+ stopAttachmentDownload: function(id) {
2637+ Mojo.Log.info("stop attachment download", id);
2638+ this.attachmentDLProgress.clearSubscription(id);
2639+ Message.cancelLoadAttachment(this.controller, id);
2640+
2641+ // This makes the progressbar show up so the user gets immediate feedback.
2642+ this.controller.get('file_size_' + id).show();
2643+ this.controller.get('progress_' + id).hide();
2644+ // Set the initial progress to 0%
2645+ this.attachmentUpdateProgressbar(id, 0);
2646+ },
2647+
2648+ handleInviteResponse: function(response, event) {
2649+ Mojo.Log.info("handleInviteResponse ", response);
2650+ var notificationAssistant = Mojo.Controller.getAppController().assistant.notificationAssistant;
2651+ notificationAssistant.handleNotification({ type:"general", message:$L("Sending invitation response") });
2652+ Email.inviteResponse(this.data, response);
2653+ this.controller.stageController.popScene();
2654+ },
2655+
2656+ attachmentError: function(id, err) {
2657+ Mojo.Log.error("handleError ", err.errorText);
2658+ this.stopAttachmentDownload(id);
2659+ var that = this;
2660+ var errorText;
2661+ if (err.errorText && err.errorText.length > 0) {
2662+ errorText = $L("Error downloading file. Server reports: #{errorText}").interpolate(err).escapeHTML();
2663+ } else {
2664+ errorText = $L("Error while downloading file.");
2665+ }
2666+
2667+ this.controller.showAlertDialog({
2668+ onChoose: function(value) {},
2669+ title: $L("Unable To Download File"),
2670+ message: errorText,
2671+ choices: [ {label:$L('OK'), value:'dismiss', type:'alert'} ]
2672+ });
2673+ },
2674+
2675+ handleAttachmentDetails: function(attachment) {
2676+ Mojo.Log.info("handleAttachmentDetails ", attachment.id);
2677+
2678+ // Update the uri for this attachment now that we got one.
2679+ this.data.attachments.each(function(a) {
2680+ if (a.id == attachment.id) {
2681+ a.uri = attachment.uri;
2682+ $break;
2683+ }
2684+ });
2685+
2686+ // For some reason the id needs to be a string so using toString().
2687+ var elem = this.controller.get((attachment.id).toString());
2688+ var newUri = attachment.uri;
2689+ elem.writeAttribute('x-uri', newUri);
2690+ elem.writeAttribute('x-mimetype', attachment.mimeType);
2691+
2692+ var audioSetup = this.setupAudioTag(attachment);
2693+ if (audioSetup) {
2694+ // The height of the inline audio player is different so need to recalc the list height
2695+ this.attachmentList.setStyle({height:'auto'}); // make sure the height below is the full height
2696+ this.attachmentListHeight = parseInt(this.attachmentList.getHeight(), 10);
2697+ } else {
2698+ var imgElem = this.controller.get('picture_'+attachment.id);
2699+ if (imgElem) {
2700+ Mojo.Log.info("set img src=", newUri);
2701+ imgElem.writeAttribute('x-uri', newUri);
2702+ // Following uri uses extractfs to scale the image to fit 320x240.
2703+ // This fixes problems with extremely large images causing the UI to chug.
2704+ Attachments.processFileObject(attachment);
2705+ var extension = attachment.extension.toLowerCase();
2706+ if (extension === ".jpg" || extension === ".jpeg" || extension === ".png") {
2707+ imgElem.src = "file:///var/luna/data/extractfs/"+newUri+":0:0:320:240:3";
2708+ } else {
2709+ imgElem.src = newUri;
2710+ }
2711+
2712+ var thumbnail = elem.down('.readview-image-thumbnail');
2713+ if (thumbnail) {
2714+ thumbnail.src = "file:///var/luna/data/extractfs/"+newUri+":0:0:31:31:4";
2715+ thumbnail.show();
2716+ }
2717+ }
2718+ this.controller.get('download_icon_' + attachment.id).hide(); // remove the download arrow from the icon
2719+ this.controller.get('progress_' + attachment.id).hide(); // ensures the progress bar is no longer shown
2720+ this.controller.get('file_size_' + attachment.id).show(); // since progress bar is gone, show the file size
2721+ }
2722+ },
2723+
2724+ attachmentDownloadProgress: function(info) {
2725+ // If the object doesn't contain a 'id' property, it isn't valid download progress
2726+ if (info.id) {
2727+ if (info.status == Message.ATTACHMENT_LOAD_COMPLETED_EVENT) {
2728+ Mojo.Log.info("attachmentDownloadProgress complete for id:", info.id);
2729+ this.attachmentDLProgress.clearSubscription(info.id);
2730+ this.attachmentUpdateProgressbar(info.id, 100);
2731+
2732+ // Final step to fully download is to get details of this attachment, including the URI.
2733+ Message.getAttachmentDetails(this.controller, info.id, this.handleAttachmentDetails.bind(this), this.attachmentError.bind(this, info.id));
2734+ }
2735+ else if (info.status == Message.ATTACHMENT_LOAD_PROGRESS_EVENT) {
2736+ // The transports can be rather aggressive about sending progress notifications so
2737+ // defend against that by only updating the UI periodically
2738+ var now = new Date().getTime();
2739+ if (now < this.attachmentDLProgress.lastUpdate + 200) {
2740+ this.attachmentDLProgress.progress[info.id] = info.progress;
2741+ } else {
2742+ this.attachmentDLProgress.lastUpdate = now;
2743+ var that = this;
2744+ if (info.progress !== undefined) {
2745+ this.attachmentDLProgress.progress[info.id] = info.progress;
2746+ }
2747+ var progressObj = this.attachmentDLProgress.progress;
2748+ Object.keys(progressObj).each(function(id) {
2749+ var progress = progressObj[id];
2750+ Mojo.Log.info("attachmentDownloadProgress id: ", id, ", progress: ", progress);
2751+ that.attachmentUpdateProgressbar(id, progress);
2752+ });
2753+ }
2754+ }
2755+ }
2756+ },
2757+
2758+ attachmentUpdateProgressbar: function(id, percent) {
2759+ var progressGroup = this.controller.get('progress_' + id);
2760+ if (progressGroup) {
2761+ var totalWidth = 2.48; // = 248 / 100%
2762+ var progressWidth = Math.round(totalWidth * percent);
2763+ progressGroup.down(0).setStyle({width:progressWidth+"px"});
2764+ var backgrndWidth = Math.round(totalWidth * (100 - percent));
2765+ progressGroup.down(1).setStyle({width:backgrndWidth+"px"});
2766+ } else {
2767+ Mojo.Log.error("Attachment ID ", id, " is invalid.");
2768+ }
2769+ },
2770+
2771+ hideAttachmentList: function(event) {
2772+ if (this.attachmentListShown) {
2773+ var targetRow = this.controller.get(event.target);
2774+ if (!targetRow.hasClassName('email-readview-attachments')) {
2775+ targetRow = targetRow.up('div.email-readview-attachments');
2776+ }
2777+
2778+ // Only hide the attachments list if the mousedown target was outside the
2779+ // list elements. Otherwise, reset the hide timer
2780+ if (targetRow == null) {
2781+ this.showHideAttachmentList();
2782+ } else if (this.hideTimeout) {
2783+ clearTimeout(this.hideTimeout);
2784+ this.hideTimeout = setTimeout(this.showHideAttachmentList.bind(this), 15000);
2785+ }
2786+ }
2787+ },
2788+
2789+ showHideAttachmentList: function() {
2790+ if (this.hideTimeout) {
2791+ clearTimeout(this.hideTimeout);
2792+ this.hideTimeout = null;
2793+ }
2794+
2795+ if (!this.attachmentListShown) {
2796+ this.attachmentList.show();
2797+ this.attachmentsShowElem.hide();
2798+ }
2799+
2800+ var options = {reverse:this.attachmentListShown,
2801+ onComplete: this.animationComplete.bind(this),
2802+ curve: 'over-easy',
2803+ from: 46,
2804+ to: this.attachmentListHeight,
2805+ duration: 0.6};
2806+ Mojo.Animation.animateStyle(this.attachmentList, 'height', 'bezier', options);
2807+ },
2808+
2809+ animationComplete: function(listElem, cancelled) {
2810+ if (!cancelled) {
2811+ this.attachmentListShown = !this.attachmentListShown;
2812+ if (this.attachmentListShown) {
2813+ this.attachmentsShowElem.hide();
2814+ this.attachmentList.show();
2815+
2816+ this.hideTimeout = setTimeout(this.showHideAttachmentList.bind(this), 15000);
2817+ } else {
2818+ this.attachmentList.hide();
2819+ this.attachmentsShowElem.show();
2820+ }
2821+ }
2822+ },
2823+
2824+ /**
2825+ * User clicked on a hyperlink.
2826+ */
2827+ handleLinkClicked: function(event) {
2828+ Mojo.Log.info("handleLinkClicked %s", event.url);
2829+ this.controller.serviceRequest('palm://com.palm.applicationManager',
2830+ {
2831+ method: 'open',
2832+ parameters: {target: event.url}
2833+ });
2834+ },
2835+
2836+ /**
2837+ * WebView widget wants us to create a new page.
2838+ */
2839+ handleCreatePage: function(event) {
2840+ Mojo.Log.info("handleCreatePage: %s", event.pageIdentifier);
2841+ this.controller.serviceRequest('palm://com.palm.applicationManager',
2842+ {
2843+ method: 'open',
2844+ parameters: {
2845+ 'id': 'com.palm.app.browser',
2846+ 'params': {scene: 'page', pageIdentifier: event.pageIdentifier}
2847+ }
2848+ });
2849+ },
2850+
2851+ /**
2852+ * handle a menu command.
2853+ */
2854+ handleCommand: function(event) {
2855+ if (event.type == Mojo.Event.command) {
2856+ try {
2857+ switch (event.command) {
2858+ case 'reply':
2859+ this.reply();
2860+ break;
2861+
2862+ case 'replyAll':
2863+ this.replyAll();
2864+ break;
2865+
2866+ case 'forward':
2867+ this.forward();
2868+ break;
2869+
2870+ case 'delete':
2871+ this.deleteEmail();
2872+ break;
2873+
2874+ case 'move':
2875+ this.controller.stageController.pushScene("moveto", this.account);
2876+ break;
2877+
2878+ case 'mark-unread':
2879+ var currentLabel = this.markUnreadMenuItem.label;
2880+ var markRead = (currentLabel == MessageAssistant.kAppMenuMarkRead);
2881+ Email.setRead(this.data.id, markRead);
2882+ if (markRead) {
2883+ this.markUnreadMenuItem.label = MessageAssistant.kAppMenuMarkUnread;
2884+ } else {
2885+ this.markUnreadMenuItem.label = MessageAssistant.kAppMenuMarkRead;
2886+ }
2887+ break;
2888+
2889+ case 'flag':
2890+ this.handleToggleFavorites();
2891+ break;
2892+
2893+ case Mojo.Menu.prefsCmd:
2894+ MenuController.showPrefs(this.controller.stageController);
2895+ break;
2896+
2897+ case Mojo.Menu.helpCmd:
2898+ MenuController.showHelp();
2899+ break;
2900+ }
2901+ }
2902+ catch (e) {
2903+ Mojo.Log.error("MessageAssistant.handleCommand: "+ e.message +" in "+ e.sourceURL +"("+ e.line +")" );
2904+ }
2905+ }
2906+ // Enable prefs & help menu items
2907+ else if (event.type == Mojo.Event.commandEnable &&
2908+ (event.command == Mojo.Menu.prefsCmd || event.command == Mojo.Menu.helpCmd)) {
2909+ event.stopPropagation();
2910+ }
2911+ },
2912+
2913+ handleInlineImageSaved: function(event) {
2914+ if (event.status) {
2915+ var filepath = this.makeTitleFromUrl(event.filepath);
2916+ var message = $L('Saving "#{path}"').interpolate({path: filepath});
2917+ Mojo.Controller.appController.showBanner(
2918+ {messageText: message},
2919+ {banner: 'image', filename: event.filepath});
2920+ }
2921+ },
2922+
2923+ makeTitleFromUrl: function(url) {
2924+ if (url) {
2925+ var result = url.match(/^.*\/([^\/]+)$/);
2926+ if ((result !== null) && (result.length > 1)) {
2927+ return result[1];
2928+ }
2929+ }
2930+
2931+ return url;
2932+ },
2933+
2934+ handleWebViewSingleTap: function(event) {
2935+ try {
2936+ var tapPt = Element.viewportOffset(this.webview);
2937+ tapPt.left = event.centerX - tapPt.left;
2938+ tapPt.top = event.centerY - tapPt.top;
2939+
2940+ //Mojo.Log.info("MessageAssistant.handleWebViewSingleTap(): event.altKey=%s, tapPt.left=%d, tapPt.top=%d", event.altKey, tapPt.left, tapPt.top);
2941+ if (event.altKey) {
2942+ var popupItems = [
2943+ {label: $L('Open URL'), command:'openNew'},
2944+ {label: $L('Share Link'), command:'shareUrl'},
2945+ {label: $L('Copy URL'), command:'copyUrl'},
2946+ {label: $L('Copy to Photos'), command:'copyToPhotos'},
2947+ {label: $L('Share Image'), command:'shareImage'} //,
2948+ //{label: $L('Set Wallpaper'), command:'setWallpaper'}
2949+ ];
2950+
2951+ var findItem = function(command) {
2952+ var i;
2953+ for (i = 0; i < popupItems.length; i++) {
2954+ if (popupItems[i].command === command) {
2955+ return popupItems[i];
2956+ }
2957+ }
2958+ };
2959+
2960+ var selectedCommand;
2961+ var imageInfo;
2962+
2963+ var saveImageCallback = function(succeeded, path) {
2964+ if (succeeded) {
2965+ switch (selectedCommand) {
2966+ case 'shareImage':
2967+ this.shareImage(imageInfo, path);
2968+ break;
2969+ case 'setWallpaper':
2970+ this.setWallpaper(path);
2971+ break;
2972+ case 'copyToPhotos':
2973+ this.showOkAlert($L('Image Saved'),
2974+ $L('The image was successfully added to your photo album.'));
2975+ break;
2976+ }
2977+ }
2978+ else {
2979+ this.showOkAlert($L('Error Saving Image'),
2980+ $L('There was an error saving the selected image.'));
2981+ }
2982+ }.bind(this);
2983+
2984+ var urlInfo = {};
2985+ var popupSelectFunc = function(value) {
2986+ selectedCommand = value;
2987+
2988+ switch (value) {
2989+ case 'openNew':
2990+ this.newBrowserPage(urlInfo.url);
2991+ break;
2992+ case 'shareUrl':
2993+ this.shareUrl(urlInfo.url, urlInfo.desc);
2994+ break;
2995+ case 'copyUrl':
2996+ this.controller.stageController.setClipboard(urlInfo.url);
2997+ break;
2998+ case 'copyToPhotos':
2999+ this.webview.mojo.saveImageAtPoint(tapPt.left, tapPt.top, "/media/internal", saveImageCallback);
3000+ break;
3001+ case 'shareImage':
3002+ this.webview.mojo.saveImageAtPoint(tapPt.left, tapPt.top, "/tmp", saveImageCallback);
3003+ break;
3004+ case 'setWallpaper':
3005+ this.webview.mojo.saveImageAtPoint(tapPt.left, tapPt.top, "/media/internal", saveImageCallback);
3006+ break;
3007+ }
3008+ }.bind(this);
3009+
3010+ var imageInfoResponse = function(response) {
3011+ imageInfo = response;
3012+ var usedItems = [];
3013+
3014+ if (urlInfo.url) {
3015+ usedItems.push( findItem('openNew') );
3016+ usedItems.push( findItem('shareUrl') );
3017+ usedItems.push( findItem('copyUrl') );
3018+ }
3019+
3020+ if (response.src) {
3021+ usedItems.push( findItem('shareImage') );
3022+ }
3023+
3024+ if (this.supportedImageType(response.src, response.mimeType)) {
3025+ usedItems.push( findItem('copyToPhotos') );
3026+ //usedItems.push( findItem('setWallpaper') );
3027+ }
3028+
3029+ if (usedItems.length) {
3030+ this.controller.popupSubmenu({ onChoose: popupSelectFunc, items: usedItems });
3031+ }
3032+ }.bind(this);
3033+
3034+ var urlInspectResponse = function(response) {
3035+ urlInfo = response || {};
3036+ this.webview.mojo.getImageInfoAtPoint(tapPt.left, tapPt.top, imageInfoResponse);
3037+ }.bind(this);
3038+
3039+ this.webview.mojo.inspectUrlAtPoint(tapPt.left, tapPt.top, urlInspectResponse);
3040+ }
3041+ }
3042+ catch (e) {
3043+ Mojo.Log.logException(e);
3044+ }
3045+ },
3046+
3047+ supportedImageType:function(url, mimeType) {
3048+ switch (this.getImageType(url, mimeType)) {
3049+ case 'jpeg':
3050+ case 'png':
3051+ case 'bmp': // We only support 24/32 bit BMP's and don't differentiate here
3052+ // GIF not yet supported
3053+ return true;
3054+ default:
3055+ return false;
3056+ }
3057+ },
3058+
3059+ getImageType: function(url, mimeType) {
3060+ url = url || '';
3061+ mimeType = mimeType || '';
3062+ var suffix = '';
3063+ try {
3064+ suffix = this.getResourceExtension(url);
3065+ if (suffix === null) {
3066+ suffix = '';
3067+ }
3068+ }
3069+ catch (e) {
3070+ Mojo.Log.logException(e);
3071+ }
3072+
3073+ suffix = suffix.toLowerCase();
3074+ mimeType = mimeType.toLowerCase();
3075+
3076+ if (suffix === 'jpg' || suffix === 'jpeg' || suffix === 'jpe' || mimeType === 'image/jpeg') {
3077+ return 'jpeg';
3078+ }
3079+ else if (suffix === 'bmp' || mimeType === 'image/bmp') {
3080+ return 'bmp';
3081+ }
3082+ else if (suffix === 'png' || mimeType === 'image/png') {
3083+ return 'png';
3084+ }
3085+ else if (suffix === 'gif' || mimeType === 'image/gif') {
3086+ return 'gif';
3087+ }
3088+ else {
3089+ return 'unknown';
3090+ }
3091+ },
3092+
3093+ getResourceExtension: function(url) {
3094+ var p = new Poly9.URLParser(url);
3095+
3096+ var matches = p.getPathname().match(/\.([^\.]*)$/i);
3097+ if (matches) {
3098+ return matches[1];
3099+ }
3100+ else {
3101+ return null;
3102+ }
3103+ },
3104+
3105+ newBrowserPage: function(url) {
3106+ this.controller.serviceRequest('palm://com.palm.applicationManager',
3107+ {
3108+ method: 'open',
3109+ parameters: {target: url}
3110+ });
3111+ },
3112+
3113+ shareUrl: function(url, title) {
3114+ if (url === undefined) {
3115+ return;
3116+ }
3117+
3118+ if (!title) {
3119+ try {
3120+ title = $L("page at #{host}").interpolate({host: UrlUtil.getUrlHost(url)});
3121+ }
3122+ catch (e) {
3123+ title = url;
3124+ }
3125+ }
3126+
3127+ var text = $L("Here's a website I think you'll like: <a href='#{src}'>#{title}</a>").interpolate(
3128+ {src: url, title: title});
3129+ var parameters = {
3130+ summary: $L('Check out this web page...'),
3131+ text: text
3132+ };
3133+ var email = new Email();
3134+ email.evalParams(parameters);
3135+ AppAssistant.openComposeStage(email);
3136+ },
3137+
3138+ shareImage: function(imageInfoObj, pathToImage) {
3139+ var title;
3140+ if (imageInfoObj.title && imageInfoObj.title.length) {
3141+ title = imageInfoObj.title;
3142+ }
3143+ else if (imageInfoObj.altText && imageInfoObj.altText.length) {
3144+ title = imageInfoObj.altText;
3145+ }
3146+ else {
3147+ var p = new Poly9.URLParser(imageInfoObj.src);
3148+ title = $L('picture link');
3149+ try {
3150+ if (imageInfoObj.src !== 'data:') {
3151+ title = $L("picture at #{host}").interpolate(
3152+ {host: p.getHost()});
3153+ }
3154+ }
3155+ catch (e) {}
3156+ }
3157+
3158+ var text = $L("Here's a picture I think you'll like: <a href='#{src}'>#{title}</a>").interpolate(
3159+ {src: imageInfoObj.src, title: title});
3160+
3161+ var parameters = {
3162+ summary: $L('Check out this picture...'),
3163+ text: text,
3164+ attachments: [{fullPath: pathToImage}]
3165+ };
3166+ var email = new Email();
3167+ email.evalParams(parameters);
3168+ AppAssistant.openComposeStage(email);
3169+ },
3170+
3171+ setWallpaper: function(pathToImage) {
3172+ var errorTitle = $L("Error Setting Wallpaper");
3173+
3174+ var onSetSuccess = function(response) {
3175+ this.showOkAlert($L("Wallpaper has been set"),
3176+ $L("The image has been successfully set as your wallpaper."));
3177+ }.bind(this);
3178+
3179+ var onSetFailure = function(response) {
3180+ this.showOkAlert(errorTitle, $L("Cannot set picture as current wallpaper."));
3181+ }.bind(this);
3182+
3183+ var onImportSuccess = function(response) {
3184+ this.controller.serviceRequest('palm://com.palm.systemservice/',
3185+ {
3186+ method : 'setPreferences',
3187+ parameters: {wallpaper: response.wallpaper},
3188+ onSuccess: onSetSuccess,
3189+ onFailure: onSetFailure
3190+ });
3191+ }.bind(this);
3192+
3193+ var onImportFailure = function() {
3194+ this.showOkAlert(errorTitle, $L("Cannot import picture into wallpaper database."));
3195+ }.bind(this);
3196+
3197+ this.controller.serviceRequest('palm://com.palm.systemservice/', {
3198+ method : 'wallpaper/importWallpaper',
3199+ parameters: {
3200+ target: encodeURIComponent(pathToImage),
3201+ scale: "1.0"
3202+ },
3203+ onSuccess: onImportSuccess,
3204+ onFailure: onImportFailure
3205+ });
3206+ },
3207+
3208+ showOkAlert: function(title, message)
3209+ {
3210+ this.controller.showAlertDialog({
3211+ title: title, message: message,
3212+ choices:[{label:$L('OK'), value:'1', type:'dismiss'}]
3213+ });
3214+ },
3215+
3216+ handleNextMessagesResponse: function(response) {
3217+ this.nextMessages = response;
3218+
3219+ var prevEmail = this.controller.get('previous_email');
3220+ if (!prevEmail) {
3221+ Mojo.Log.error("previous_email element does not yet exist");
3222+ } else {
3223+ if (response.newer === undefined || response.newer.end) {
3224+ // hide 'previous_email' because there's no more emails in that direction
3225+ prevEmail.addClassName('disabled');
3226+ } else {
3227+ prevEmail.removeClassName('disabled');
3228+ }
3229+ }
3230+
3231+ var nextEmail = this.controller.get('next_email');
3232+ if (!nextEmail) {
3233+ Mojo.Log.error("next_email element does not yet exist");
3234+ } else {
3235+ if (response.older === undefined || response.older.end) {
3236+ // hide 'next_email' because there's no more emails in that direction
3237+ nextEmail.addClassName('disabled');
3238+ } else {
3239+ nextEmail.removeClassName('disabled');
3240+ }
3241+ }
3242+ },
3243+
3244+ gotoNextEmail: function(direction) {
3245+ if (this.nextMessages !== undefined && this.nextMessages[direction] !== undefined) {
3246+ var details = this.nextMessages[direction];
3247+ if (!details.end && details.id > 0) {
3248+ if (this.transition !== null) {
3249+ this.transition.cleanup();
3250+ }
3251+ this.transition = this.controller.prepareTransition(Mojo.Transition.crossFade, false);
3252+ this.prepareForNewMessage(details);
3253+ }
3254+ }
3255+ },
3256+
3257+ prepareForNewMessage: function(details) {
3258+ this.data = { id: details.id, senderDetails:{} }; // data.id
3259+ this.account = {};
3260+ this.gotFirstResponse = false;
3261+ this.bodyLeftOffset = 0;
3262+ // Clear out the old email body by loading a simple empty page
3263+ this.webview.mojo.openURL(Mojo.appPath + "emptypage.html");
3264+ this.plainTextBody.hide();
3265+
3266+ // stop listening to all these
3267+ this.controller.get('previous_email').stopObserving(Mojo.Event.tap, this.boundGotoNextEmailNewer);
3268+ this.controller.get('next_email').stopObserving(Mojo.Event.tap, this.boundGotoNextEmailOlder);
3269+ if (this.recipientController) {
3270+ this.recipientController.up(".palm-row").stopObserving(Mojo.Event.tap, this.boundShowHideRecipients, true);
3271+ }
3272+ var elem;
3273+ elem = this.controller.get('invite-accept');
3274+ if (elem) {
3275+ elem.stopObserving(Mojo.Event.tap, this.boundHandleInviteResponseAccept);
3276+ }
3277+ elem = this.controller.get('invite-tentative');
3278+ if (elem) {
3279+ elem.stopObserving(Mojo.Event.tap, this.boundHandleInviteResponseTentative);
3280+ }
3281+ this.controller.get('invite-decline');
3282+ if (elem) {
3283+ elem.stopObserving(Mojo.Event.tap, this.boundHandleInviteResponseDecline);
3284+ }
3285+
3286+ // reset the horizontal positioning of these two blocks.
3287+ this.emailHeaderBlock.setStyle({ 'left': '0px' });
3288+ this.emailPicturesBlock.setStyle({ 'left': '0px' });
3289+
3290+ // Subscribe to the new message
3291+ if (this.waitingForMessageBodyTimeout !== undefined) {
3292+ clearTimeout(this.waitingForMessageBodyTimeout);
3293+ this.waitingForMessageBodyTimeout = undefined;
3294+ }
3295+ if (this.messageSubscription) {
3296+ this.messageSubscription.cancel();
3297+ }
3298+ this.messageSubscription = new Mojo.Service.Request(Message.identifier, {
3299+ method: 'messageDetail',
3300+ parameters: { 'message':this.data.id, subscribe:true },
3301+ onSuccess: this.messageDetailsUpdated.bind(this),
3302+ onFailure: this.waitMessageError.bind(this)
3303+ });
3304+
3305+ Message.getFolderAndAccount(this.controller, this.data.id, this.folderAndAccountDetails.bind(this));
3306+ },
3307+
3308+ /**
3309+ * Called by the webview widget when setup is complete?
3310+ */
3311+ ready: function() {
3312+ this.webview.mojo.addUrlRedirect("^file:.*", false, "", 0);
3313+ this.webview.mojo.addUrlRedirect(".*", true, "", 0);
3314+ },
3315+
3316+ setupMessage: function() {
3317+ // Setup the WebView for the body text
3318+ // TODO autowidth to true?
3319+ var emailStageController = Mojo.Controller.appController.getStageController("email");
3320+ var attr = {minFontSize:18,
3321+ cacheAdapter:true,
3322+ fitWidth: false,
3323+ virtualpagewidth: emailStageController.window.innerWidth,
3324+ minimumpageheight: 32, // Start out very short in case the message body empty
3325+ showClickedLink:true};
3326+ this.controller.setupWidget('email_body_text', attr);
3327+ this.controller.setupWidget('email_no_body_spinner', { spinnerSize: Mojo.Widget.spinnerSmall });
3328+
3329+ this.webview = this.controller.get('email_body_text');
3330+ this.webview.addEventListener(Mojo.Event.webViewUrlRedirect, this.boundHandleLinkClicked, false);
3331+ this.webview.addEventListener(Mojo.Event.webViewMimeNotSupported, this.boundHandleLinkClicked, false);
3332+ this.webview.addEventListener(Mojo.Event.webViewMimeHandoff, this.boundHandleLinkClicked, false);
3333+ this.webview.addEventListener(Mojo.Event.webViewImageSaved, this.boundHandleInlineImageSaved, false);
3334+ this.webview.addEventListener('singletap', this.boundHandleWebViewSingleTap, true);
3335+
3336+ this.waitingForMessageBody = undefined;
3337+ // subscribe to the message details because the transport may need to send updates in the case where the
3338+ // email body and/or attachments isn't yet downloaded
3339+ this.messageSubscription = new Mojo.Service.Request(Message.identifier, {
3340+ method: 'messageDetail',
3341+ parameters: { 'message':this.data.id, subscribe:true },
3342+ onSuccess: this.messageDetailsUpdated.bind(this),
3343+ onFailure: this.waitMessageError.bind(this)
3344+ });
3345+ },
3346+
3347+ orientationChanged: function(orientation) {
3348+ if (orientation === "left" || orientation === "right") {
3349+ this.controller.sceneElement.addClassName('landscape');
3350+ } else {
3351+ this.controller.sceneElement.removeClassName('landscape');
3352+ }
3353+ },
3354+
3355+ focusEmailStage: function() {
3356+ if (this.focusStageTimer) {
3357+ this.focusStageTimer = undefined;
3358+ AppAssistant.focusEmailStage();
3359+ }
3360+ },
3361+
3362+ // This is called from accountpreference assistant when the user removes the account
3363+ accountDeletedNotification: function(accountId) {
3364+ if (accountId === this.account.account) {
3365+ Mojo.Log.warn("MessageAssistant is showing a deleted account, setting up for cleanup");
3366+ this.popOnActivate = true;
3367+ }
3368+ },
3369+
3370+ setup: function() {
3371+ this.delayActivate = true;
3372+ this.messageTarget = this.controller.get('readview-main');
3373+ this.messageTarget.observe('mousedown', this.hideAttachmentList.bind(this), true);
3374+ this.setupMessage();
3375+
3376+ this.emailHeaderBlock = this.controller.get('email_header_block');
3377+ this.plainTextBody = this.controller.get('email_text');
3378+ this.emailPicturesBlock = this.controller.get('email_pictures_list');
3379+
3380+ this.controller.get('email-readview-attachments-list').observe(Mojo.Event.tap, this.handleAttachmentTapped.bind(this));
3381+ this.attachmentsShowElem = this.controller.get('show-hide-multi-attachments');
3382+ this.attachmentsShowElem.observe(Mojo.Event.tap, this.showHideAttachmentList.bind(this));
3383+
3384+ this.cmdMenuModel = {
3385+ visible:true,
3386+ items: [
3387+ {label:$L('Reply'), icon:'reply', command:'reply'},
3388+ {label:$L('Reply all'), icon:'reply-all', command:'replyAll'},
3389+ {label:$L('Forward'), icon:'forward-email', command:'forward'},
3390+ {label:$L('Delete'), icon:'delete', command:'delete'}
3391+ ]};
3392+ this.controller.setupWidget(Mojo.Menu.commandMenu, undefined, this.cmdMenuModel);
3393+
3394+ this.markUnreadMenuItem = {label:MessageAssistant.kAppMenuMarkUnread, command:'mark-unread'};
3395+ this.markSetFlagMenuItem = {label:MessageAssistant.kAppMenuSetFlag, command:'flag'};
3396+ this.appMenuModel = {
3397+ visible:true,
3398+ items: [
3399+ this.markUnreadMenuItem,
3400+ this.markSetFlagMenuItem,
3401+ {label:$L('Move to folder...'), command:'move'}
3402+ ]};
3403+ this.controller.setupWidget(Mojo.Menu.appMenu, undefined, this.appMenuModel);
3404+
3405+ Message.getFolderAndAccount(this.controller, this.data.id, this.folderAndAccountDetails.bind(this));
3406+
3407+ this.recipsDrawer = { openProperty: false };
3408+ this.controller.setupWidget('email_recipients_drawer', {unstyled:true, modelProperty:'openProperty'}, this.recipsDrawer);
3409+ this.recipsDrawer.element = this.controller.get('email_recipients_drawer');
3410+
3411+ this.emailPicturesBlock.observe(Mojo.Event.tap, this.handleImageTapped.bind(this));
3412+ // stop gesture events on pictures so they don't go to the webview widget
3413+ this.emailPicturesBlock.observe('gesturestart', function(event) { event.stop(); }, false);
3414+ this.emailPicturesBlock.observe('gesturechange', function(event) { event.stop(); }, false);
3415+ this.emailPicturesBlock.observe('gestureend', function(event) { event.stop(); }, false);
3416+
3417+ this.controller.get('email_from').observe(Mojo.Event.tap, this.handleSenderTap.bind(this));
3418+ this.controller.get('email_recipients').observe(Mojo.Event.tap, this.handleRecipientListSelect.bind(this));
3419+
3420+ this.boundUpdateRecipientStatus = this.updateRecipientStatus.bind(this);
3421+ Mojo.Event.listen(this.controller.stageController.document, Mojo.Event.activate, this.boundUpdateRecipientStatus);
3422+
3423+ Mojo.Event.listen(this.controller.getSceneScroller(), Mojo.Event.scrollStarting, this.addAsScrollListener.bind(this));
3424+ },
3425+
3426+ cleanup: function() {
3427+ if (this.waitingForMessageBodyTimeout !== undefined) {
3428+ clearTimeout(this.waitingForMessageBodyTimeout);
3429+ this.waitingForMessageBodyTimeout = undefined;
3430+ }
3431+
3432+ if (this.focusStageTimer !== undefined) {
3433+ clearTimeout(this.focusStageTimer);
3434+ }
3435+
3436+ this.webview.removeEventListener(Mojo.Event.webViewUrlRedirect, this.boundHandleLinkClicked, false);
3437+ this.webview.removeEventListener(Mojo.Event.webViewMimeNotSupported, this.boundHandleLinkClicked, false);
3438+ this.webview.removeEventListener(Mojo.Event.webViewMimeHandoff, this.boundHandleLinkClicked, false);
3439+ this.webview.removeEventListener(Mojo.Event.webViewImageSaved, this.boundHandleInlineImageSaved, false);
3440+ this.webview.removeEventListener('singletap', this.boundHandleWebViewSingleTap, true);
3441+
3442+ if (this.messageSubscription) {
3443+ this.messageSubscription.cancel();
3444+ }
3445+ Mojo.Event.stopListening(this.controller.stageController.document, Mojo.Event.activate, this.boundUpdateRecipientStatus);
3446+ Message.closeMessage(this.controller, this.data.id);
3447+ },
3448+
3449+ aboutToActivate: function(callback) {
3450+ if (this.delayActivate === true) {
3451+ this.readyToActivateCallback = callback;
3452+ } else {
3453+ callback();
3454+ }
3455+ },
3456+
3457+ activate: function() {
3458+ // If the scene is invalid (usually because the underlying account was deleted),
3459+ // just pop the scene and no more.
3460+ if (this.popOnActivate === true) {
3461+ this.controller.stageController.popScene();
3462+ return;
3463+ }
3464+
3465+ // save the current scene controller physics parameters
3466+ var scroller = this.controller.getSceneScroller();
3467+ this.savedFlickSpeed = scroller.mojo.kFlickSpeed;
3468+ this.savedFlickRatio = scroller.mojo.kFlickRatio;
3469+ scroller.mojo.updatePhysicsParameters({flickSpeed: 0.12, flickRatio: 0.2});
3470+
3471+ // If the scene is ready to activate and it needs to be focused, do that now.
3472+ if (this.focusStageTimer !== undefined) {
3473+ clearTimeout(this.focusStageTimer);
3474+ this.focusEmailStage();
3475+ }
3476+
3477+ // prerenderData is only set if the data was supplied from the list assistant. If the
3478+ // service already send a response, it will not include the prerenderData property.
3479+ if (this.data.prerenderData === true) {
3480+ this.prerenderMessage(this.data);
3481+ }
3482+ },
3483+
3484+ deactivate: function() {
3485+ // restore the current scene controller physics parameters
3486+ var scroller = this.controller.getSceneScroller();
3487+ scroller.mojo.updatePhysicsParameters({flickSpeed: this.savedFlickSpeed, flickRatio: this.savedFlickRatio});
3488+ },
3489+
3490+ reply: function() {
27
28 // Very first thing to do with recipients is fix them up for the address picker.
29 EmailRecipient.addAddressPickerFields(resp.recipients);
30@@ -1710,24 +1717,32 @@ var MessageAssistant = Class.create({
31 },
32
33 reply: function() {
349134+ Email.setRead(this.data.id, true);
349235+
3493+ var email = new Email();
3494+ email.createReply(this.data, this.account.login);
3495+ MenuController.showComposeView(email);
3496+ },
3497+
3498+ replyAll: function() {
36 var email = new Email();
37 email.createReply(this.data, this.account.login);
38 MenuController.showComposeView(email);
39 },
40
41 replyAll: function() {
349942+ Email.setRead(this.data.id, true);
350043+
3501+ var email = new Email();
3502+ email.createReplyAll(this.data, this.account.login);
3503+ MenuController.showComposeView(email);
3504+ },
3505+
3506+ forward: function() {
44 var email = new Email();
45 email.createReplyAll(this.data, this.account.login);
46 MenuController.showComposeView(email);
47 },
48
49 forward: function() {
350750+ Email.setRead(this.data.id, true);
350851+
3509+ var email = new Email();
3510+ email.createForward(this.data, this.account.login);
3511+ MenuController.showComposeView(email);
3512+ },
3513+
3514+ deleteEmail: function() {
52 var email = new Email();
53 email.createForward(this.data, this.account.login);
54 MenuController.showComposeView(email);
55 },
56
57 deleteEmail: function() {
351558+ Email.setRead(this.data.id, true);
351659+
3517+ Email.setDeleted(this.data.id, true);
3518+ this.controller.stageController.popScene();
3519+ }
3520+});
3521+
3522+MessageAssistant.kAppMenuMarkRead = $L('Mark as read');
3523+MessageAssistant.kAppMenuMarkUnread = $L('Mark as unread');
3524+MessageAssistant.kAppMenuSetFlag = $L('Set flag');
3525+MessageAssistant.kAppMenuClearFlag = $L('Clear flag');
3526+
3527+
60 Email.setDeleted(this.data.id, true);
61 this.controller.stageController.popScene();
62 }