1 // Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
7 const Mail = Module("mail", {
8 init: function init() {
9 // used for asynchronously selecting messages after wrapping folders
10 this._selectMessageKeys = [];
11 this._selectMessageCount = 1;
12 this._selectMessageReverse = false;
14 this._mailSession = Cc["@mozilla.org/messenger/services/session;1"].getService(Ci.nsIMsgMailSession);
15 this._notifyFlags = Ci.nsIFolderListener.intPropertyChanged | Ci.nsIFolderListener.event;
16 this._mailSession.AddFolderListener(this._folderListener, this._notifyFlags);
20 OnItemAdded: function (parentItem, item) {},
21 OnItemRemoved: function (parentItem, item) {},
22 OnItemPropertyChanged: function (item, property, oldValue, newValue) {},
23 OnItemIntPropertyChanged: function (item, property, oldValue, newValue) {},
24 OnItemBoolPropertyChanged: function (item, property, oldValue, newValue) {},
25 OnItemUnicharPropertyChanged: function (item, property, oldValue, newValue) {},
26 OnItemPropertyFlagChanged: function (item, property, oldFlag, newFlag) {},
28 OnItemEvent: function (folder, event) {
29 let eventType = event.toString();
30 if (eventType == "FolderLoaded") {
32 let msgFolder = folder.QueryInterface(Ci.nsIMsgFolder);
33 autocommands.trigger("FolderLoaded", { url: msgFolder });
35 // Jump to a message when requested
37 if (mail._selectMessageKeys.length > 0) {
38 for (let j = 0; j < mail._selectMessageKeys.length; j++)
39 indices.push([gDBView.findIndexFromKey(mail._selectMessageKeys[j], true), mail._selectMessageKeys[j]]);
42 let index = mail._selectMessageCount - 1;
43 if (mail._selectMessageReverse)
44 index = mail._selectMessageKeys.length - 1 - index;
46 gDBView.selectMsgByKey(indices[index][1]);
47 mail._selectMessageKeys = [];
51 /*else if (eventType == "ImapHdrDownloaded") {}
52 else if (eventType == "DeleteOrMoveMsgCompleted") {}
53 else if (eventType == "DeleteOrMoveMsgFailed") {}
54 else if (eventType == "AboutToCompact") {}
55 else if (eventType == "CompactCompleted") {}
56 else if (eventType == "RenameCompleted") {}
57 else if (eventType == "JunkStatusChanged") {}*/
61 _getCurrentFolderIndex: function () {
62 // for some reason, the index is interpreted as a string, therefore the parseInt
63 return parseInt(gFolderTreeView.getIndexOfFolder(gFolderTreeView.getSelectedFolders()[0]));
66 _getRSSUrl: function () {
67 return gDBView.hdrForFirstSelectedMessage.messageId.replace(/(#.*)?@.*$/, "");
70 _moveOrCopy: function (copy, destinationFolder, operateOnThread) {
71 let folders = mail.getFolders(destinationFolder);
72 if (folders.length == 0)
73 return void dactyl.echoerr("Exxx: No matching folder for " + destinationFolder);
74 else if (folders.length > 1)
75 return dactyl.echoerr("Exxx: More than one match for " + destinationFolder);
77 let count = gDBView.selection.count;
79 return void dactyl.beep();
81 (copy ? MsgCopyMessage : MsgMoveMessage)(folders[0]);
82 util.timeout(function () {
83 dactyl.echomsg(count + " message(s) " + (copy ? "copied" : "moved") + " to " + folders[0].prettyName, 1);
87 _parentIndex: function (index) {
89 let tree = GetThreadTree();
92 let tmp = tree.view.getParentIndex(parent);
101 // does not wrap yet, intentional?
102 _selectUnreadFolder: function (backwards, count) {
103 count = Math.max(1, count);
104 let direction = backwards ? -1 : 1;
105 let c = this._getCurrentFolderIndex();
108 while (count > 0 && (c + i) < gFolderTreeView.rowCount && (c + i) >= 0) {
109 let resource = gFolderTreeView._rowMap[c + i]._folder;
110 if (!resource.isServer && resource.getNumUnread(false)) {
116 if (!folder || count > 0)
119 gFolderTreeView.selection.timedSelect(c + folder, 500);
122 _escapeRecipient: function (recipient) {
124 recipient = recipient.replace(/"/g, "");
125 return "\"" + recipient + "\"";
128 get currentAccount() this.currentFolder.rootFolder,
130 get currentFolder() gFolderTreeView.getSelectedFolders()[0],
132 /** @property {nsISmtpServer[]} The list of configured SMTP servers. */
134 let servers = services.smtp.smtpServers;
137 while (servers.hasMoreElements()) {
138 let server = servers.getNext();
139 if (server instanceof Ci.nsISmtpServer)
146 composeNewMail: function (args) {
147 let params = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance(Ci.nsIMsgComposeParams);
148 params.composeFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(Ci.nsIMsgCompFields);
151 if (args.originalMsg)
152 params.originalMsgURI = args.originalMsg;
154 params.composeFields.to = args.to;
156 params.composeFields.cc = args.cc;
158 params.composeFields.bcc = args.bcc;
160 params.composeFields.newsgroups = args.newsgroups;
162 params.composeFields.subject = args.subject;
164 params.composeFields.body = args.body;
166 if (args.attachments) {
167 while (args.attachments.length > 0) {
168 let url = args.attachments.pop();
169 let file = io.getFile(url);
171 return void dactyl.echoerr("Exxx: Could not attach file `" + url + "'", commandline.FORCE_SINGLELINE);
173 attachment = Cc["@mozilla.org/messengercompose/attachment;1"].createInstance(Ci.nsIMsgAttachment);
174 attachment.url = "file://" + file.path;
175 params.composeFields.addAttachment(attachment);
180 params.type = Ci.nsIMsgCompType.New;
182 const msgComposeService = Cc["@mozilla.org/messengercompose;1"].getService();
183 msgComposeService = msgComposeService.QueryInterface(Ci.nsIMsgComposeService);
184 msgComposeService.OpenComposeWindowWithParams(null, params);
187 // returns an array of nsIMsgFolder objects
188 getFolders: function (filter, includeServers, includeMsgFolders) {
193 filter = filter.toLowerCase();
195 if (includeServers === undefined)
196 includeServers = false;
197 if (includeMsgFolders === undefined)
198 includeMsgFolders = true;
200 for (let i = 0; i < gFolderTreeView.rowCount; i++) {
201 let resource = gFolderTreeView._rowMap[i]._folder;
202 if ((resource.isServer && !includeServers) || (!resource.isServer && !includeMsgFolders))
205 let folderString = resource.server.prettyName + ": " + resource.name;
207 if (resource.prettiestName.toLowerCase().indexOf(filter) >= 0)
208 folders.push(resource);
209 else if (folderString.toLowerCase().indexOf(filter) >= 0)
210 folders.push(resource);
215 getNewMessages: function (currentAccountOnly) {
216 if (currentAccountOnly)
217 MsgGetMessagesForAccount();
219 GetMessagesForAllAuthenticatedAccounts();
222 getStatistics: function (currentAccountOnly) {
223 let accounts = currentAccountOnly ? [this.currentAccount]
224 : this.getFolders("", true, false);
226 let unreadCount = 0, totalCount = 0, newCount = 0;
227 for (let i = 0; i < accounts.length; i++) {
228 let account = accounts[i];
229 unreadCount += account.getNumUnread(true); // true == deep (includes subfolders)
230 totalCount += account.getTotalMessages(true);
231 newCount += account.getNumUnread(true);
234 return { numUnread: unreadCount, numTotal: totalCount, numNew: newCount };
237 collapseThread: function () {
238 let tree = GetThreadTree();
240 let parent = this._parentIndex(tree.currentIndex);
241 if (tree.changeOpenState(parent, false)) {
242 tree.view.selection.select(parent);
243 tree.treeBoxObject.ensureRowIsVisible(parent);
250 expandThread: function () {
251 let tree = GetThreadTree();
253 let row = tree.currentIndex;
254 if (row >= 0 && tree.changeOpenState(row, true))
261 * General-purpose method to find messages.
263 * @param {function(nsIMsgDBHdr):boolean} validatorFunc Return
264 * true/false whether msg should be selected or not.
265 * @param {boolean} canWrap When true, wraps around folders.
266 * @param {boolean} openThreads Should we open closed threads?
267 * @param {boolean} reverse Change direction of searching.
269 selectMessage: function (validatorFunc, canWrap, openThreads, reverse, count) {
270 function currentIndex() {
271 let index = gDBView.selection.currentIndex;
277 function closedThread(index) {
278 if (!(gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay))
281 index = (typeof index == "number") ? index : currentIndex();
282 return !gDBView.isContainerOpen(index) && !gDBView.isContainerEmpty(index);
285 if (typeof validatorFunc != "function")
288 if (typeof count != "number" || count < 1)
291 // first try to find in current folder
293 for (let i = currentIndex() + (reverse ? -1 : (openThreads && closedThread() ? 0 : 1));
294 reverse ? (i >= 0) : (i < gDBView.rowCount);
295 reverse ? i-- : i++) {
296 let key = gDBView.getKeyAt(i);
297 let msg = gDBView.db.GetMsgHdrForKey(key);
300 if (openThreads && closedThread(i)) {
301 let thread = gDBView.db.GetThreadContainingMsgHdr(msg);
302 let originalCount = count;
304 for (let j = (i == currentIndex() && !reverse) ? 1 : (reverse ? thread.numChildren - 1 : 0);
305 reverse ? (j >= 0) : (j < thread.numChildren);
306 reverse ? j-- : j++) {
307 msg = thread.getChildAt(j);
308 if (validatorFunc(msg) && --count == 0) {
309 // this hack is needed to get the correct message, because getChildAt() does not
310 // necessarily return the messages in the order they are displayed
311 gDBView.selection.timedSelect(i, GetThreadTree()._selectDelay || 500);
312 GetThreadTree().treeBoxObject.ensureRowIsVisible(i);
314 GetThreadTree().changeOpenState(i, true);
315 this.selectMessage(validatorFunc, false, false, false, originalCount);
321 else { // simple non-threaded message
322 if (validatorFunc(msg) && --count == 0) {
323 gDBView.selection.timedSelect(i, GetThreadTree()._selectDelay || 500);
324 GetThreadTree().treeBoxObject.ensureRowIsVisible(i);
331 // then in other folders
333 this._selectMessageReverse = reverse;
335 let folders = this.getFolders("", true, true);
336 let ci = this._getCurrentFolderIndex();
337 for (let i = 1; i < folders.length; i++) {
338 let index = (i + ci) % folders.length;
340 index = folders.length - 1 - index;
342 let folder = folders[index];
346 this._selectMessageCount = count;
347 this._selectMessageKeys = [];
349 // sometimes folder.getMessages can fail with an exception
350 // TODO: find out why, and solve the problem
352 var msgs = folder.messages;
355 msgs = folder.getMessages(msgWindow); // for older thunderbirds
356 dactyl.dump("WARNING: " + folder.prettyName + " failed to getMessages, trying old API");
360 while (msgs.hasMoreElements()) {
361 let msg = msgs.getNext().QueryInterface(Ci.nsIMsgDBHdr);
362 if (validatorFunc(msg)) {
364 this._selectMessageKeys.push(msg.messageKey);
369 // SelectFolder is asynchronous, message is selected in this._folderListener
370 SelectFolder(folder.URI);
376 // TODO: finally for the "rest" of the current folder
381 setHTML: function (value) {
382 let values = [[true, 1, gDisallow_classes_no_html], // plaintext
383 [false, 0, 0], // HTML
384 [false, 3, gDisallow_classes_no_html]]; // sanitized/simple HTML
386 if (typeof value != "number" || value < 0 || value > 2)
389 gPrefBranch.setBoolPref("mailnews.display.prefer_plaintext", values[value][0]);
390 gPrefBranch.setIntPref("mailnews.display.html_as", values[value][1]);
391 gPrefBranch.setIntPref("mailnews.display.disallow_mime_handlers", values[value][2]);
396 commands: function initCommands(dactyl, modules, window) {
397 commands.add(["go[to]"],
400 let count = Math.max(0, args.count - 1);
401 let arg = args.literalArg || "Inbox";
403 let folder = mail.getFolders(arg, true, true)[count];
405 dactyl.echoerr("Exxx: Folder \"" + arg + "\" does not exist");
406 else if (dactyl.forceNewTab)
407 MsgOpenNewTabForFolder(folder.URI);
409 SelectFolder(folder.URI);
413 completer: function (context) completion.mailFolder(context),
418 commands.add(["m[ail]"],
419 "Write a new message",
422 mailargs.to = args.join(", ");
423 mailargs.subject = args["-subject"];
424 mailargs.bcc = args["-bcc"] || [];
425 mailargs.cc = args["-cc"] || [];
426 mailargs.body = args["-text"];
427 mailargs.attachments = args["-attachment"] || [];
429 let addresses = args;
431 addresses = addresses.concat(mailargs.bcc);
433 addresses = addresses.concat(mailargs.cc);
435 // TODO: is there a better way to check for validity?
436 if (addresses.some(function (recipient) !(/\S@\S+\.\S/.test(recipient))))
437 return void dactyl.echoerr("Exxx: Invalid e-mail address");
439 mail.composeNewMail(mailargs);
442 // TODO: completers, validators - whole shebang. Do people actually use this? --djk
444 { names: ["-subject", "-s"], type: CommandOption.STRING, description: "Subject line"},
445 { names: ["-attachment", "-a"], type: CommandOption.LIST, description: "List of attachments"},
446 { names: ["-bcc", "-b"], type: CommandOption.LIST, description: "Blind Carbon Copy addresses"},
447 { names: ["-cc", "-c"], type: CommandOption.LIST, description: "Carbon Copy addresses"},
448 { names: ["-text", "-t"], type: CommandOption.STRING, description: "Message body"}
452 commands.add(["copy[to]"],
453 "Copy selected messages",
454 function (args) { mail._moveOrCopy(true, args.literalArg); },
457 completer: function (context) completion.mailFolder(context),
461 commands.add(["move[to]"],
462 "Move selected messages",
463 function (args) { mail._moveOrCopy(false, args.literalArg); },
466 completer: function (context) completion.mailFolder(context),
470 commands.add(["empty[trash]"],
471 "Empty trash of the current account",
472 function () { window.goDoCommand("cmd_emptyTrash"); },
475 commands.add(["get[messages]"],
476 "Check for new messages",
477 function (args) mail.getNewMessages(!args.bang),
483 completion: function initCompletion(dactyl, modules, window) {
484 completion.mailFolder = function mailFolder(context) {
485 let folders = mail.getFolders(context.filter);
486 context.anchored = false;
487 context.quote = false;
488 context.completions = folders.map(function (folder)
489 [folder.server.prettyName + ": " + folder.name,
490 "Unread: " + folder.getNumUnread(false)]);
493 mappings: function initMappings(dactyl, modules, window) {
494 var myModes = config.mailModes;
496 mappings.add(myModes, ["<Return>", "i"],
497 "Inspect (focus) message",
498 function () { content.focus(); });
500 mappings.add(myModes, ["I"],
501 "Open the message in new tab",
503 if (gDBView && gDBView.selection.count < 1)
504 return void dactyl.beep();
506 MsgOpenNewTabForMessage();
509 mappings.add(myModes, ["<Space>"],
510 "Scroll message or select next unread one",
511 function () Events.PASS);
513 mappings.add(myModes, ["t"],
515 function () { gDBView.ExpandAndSelectThreadByIndex(GetThreadTree().currentIndex, false); });
517 mappings.add(myModes, ["d", "<Del>"],
518 "Move mail to Trash folder",
519 function () { window.goDoCommand("cmd_delete"); });
521 mappings.add(myModes, ["j", "<Right>"],
522 "Select next message",
523 function (args) { mail.selectMessage(function (msg) true, false, false, false, args.count); },
526 mappings.add(myModes, ["gj"],
527 "Select next message, including closed threads",
528 function (args) { mail.selectMessage(function (msg) true, false, true, false, args.count); },
531 mappings.add(myModes, ["J", "<Tab>"],
532 "Select next unread message",
533 function (args) { mail.selectMessage(function (msg) !msg.isRead, true, true, false, args.count); },
536 mappings.add(myModes, ["k", "<Left>"],
537 "Select previous message",
538 function (args) { mail.selectMessage(function (msg) true, false, false, true, args.count); },
541 mappings.add(myModes, ["gk"],
542 "Select previous message",
543 function (args) { mail.selectMessage(function (msg) true, false, true, true, args.count); },
546 mappings.add(myModes, ["K"],
547 "Select previous unread message",
548 function (args) { mail.selectMessage(function (msg) !msg.isRead, true, true, true, args.count); },
551 mappings.add(myModes, ["*"],
552 "Select next message from the same sender",
554 let author = gDBView.hdrForFirstSelectedMessage.mime2DecodedAuthor.toLowerCase();
555 mail.selectMessage(function (msg) msg.mime2DecodedAuthor.toLowerCase().indexOf(author) == 0, true, true, false, args.count);
559 mappings.add(myModes, ["#"],
560 "Select previous message from the same sender",
562 let author = gDBView.hdrForFirstSelectedMessage.mime2DecodedAuthor.toLowerCase();
563 mail.selectMessage(function (msg) msg.mime2DecodedAuthor.toLowerCase().indexOf(author) == 0, true, true, true, args.count);
568 mappings.add(myModes, ["m"],
569 "Compose a new message",
570 function () { CommandExMode().open("mail -subject="); });
572 mappings.add(myModes, ["M"],
573 "Compose a new message to the sender of selected mail",
575 let to = mail._escapeRecipient(gDBView.hdrForFirstSelectedMessage.mime2DecodedAuthor);
576 CommandExMode().open("mail " + to + " -subject=");
579 mappings.add(myModes, ["r"],
581 function () { window.goDoCommand("cmd_reply"); });
583 mappings.add(myModes, ["R"],
585 function () { window.goDoCommand("cmd_replyall"); });
587 mappings.add(myModes, ["f"],
589 function () { window.goDoCommand("cmd_forward"); });
591 mappings.add(myModes, ["F"],
592 "Forward message inline",
593 function () { window.goDoCommand("cmd_forwardInline"); });
596 mappings.add(myModes, ["<Down>"],
597 "Scroll message down",
598 function (args) { buffer.scrollLines(Math.max(args.count, 1)); },
601 mappings.add(myModes, ["<Up>"],
603 function (args) { buffer.scrollLines(-Math.max(args.count, 1)); },
606 mappings.add([modes.MESSAGE], ["<Left>"],
607 "Select previous message",
608 function (args) { mail.selectMessage(function (msg) true, false, false, true, args.count); },
611 mappings.add([modes.MESSAGE], ["<Right>"],
612 "Select next message",
613 function (args) { mail.selectMessage(function (msg) true, false, false, false, args.count); },
617 mappings.add(myModes, ["u"],
620 if (messenger.canUndo())
621 messenger.undo(msgWindow);
625 mappings.add(myModes, ["<C-r>"],
628 if (messenger.canRedo())
629 messenger.redo(msgWindow);
635 mappings.add(myModes, ["gm"],
637 function () { mail.getNewMessages(); });
639 mappings.add(myModes, ["gM"],
640 "Get new messages for current account only",
641 function () { mail.getNewMessages(true); });
644 mappings.add(myModes, ["c"],
646 function () { CommandExMode().open("goto "); });
648 mappings.add(myModes, ["s"],
649 "Move selected messages",
650 function () { CommandExMode().open("moveto "); });
652 mappings.add(myModes, ["S"],
653 "Copy selected messages",
654 function () { CommandExMode().open("copyto "); });
656 mappings.add(myModes, ["<C-s>"],
658 function () { mail._moveOrCopy(false, options["archivefolder"]); });
660 mappings.add(myModes, ["]s"],
661 "Select next starred message",
662 function (args) { mail.selectMessage(function (msg) msg.isFlagged, true, true, false, args.count); },
665 mappings.add(myModes, ["[s"],
666 "Select previous starred message",
667 function (args) { mail.selectMessage(function (msg) msg.isFlagged, true, true, true, args.count); },
670 mappings.add(myModes, ["]a"],
671 "Select next message with an attachment",
672 function (args) { mail.selectMessage(function (msg) gDBView.db.HasAttachments(msg.messageKey), true, true, false, args.count); },
675 mappings.add(myModes, ["[a"],
676 "Select previous message with an attachment",
677 function (args) { mail.selectMessage(function (msg) gDBView.db.HasAttachments(msg.messageKey), true, true, true, args.count); },
681 mappings.add(myModes, ["gi"],
684 let folder = mail.getFolders("Inbox", false, true)[(args.count > 0) ? (args.count - 1) : 0];
686 SelectFolder(folder.URI);
692 mappings.add(myModes, ["<C-n>"],
693 "Select next folder",
695 let newPos = mail._getCurrentFolderIndex() + Math.max(1, args.count);
696 if (newPos >= gFolderTreeView.rowCount) {
697 newPos = newPos % gFolderTreeView.rowCount;
698 commandline.echo(_("finder.atBottom"), commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
700 gFolderTreeView.selection.timedSelect(newPos, 500);
704 mappings.add(myModes, ["<C-N>"],
705 "Go to next mailbox with unread messages",
707 mail._selectUnreadFolder(false, args.count);
711 mappings.add(myModes, ["<C-p>"],
712 "Select previous folder",
714 let newPos = mail._getCurrentFolderIndex() - Math.max(1, args.count);
716 newPos = (newPos % gFolderTreeView.rowCount) + gFolderTreeView.rowCount;
717 commandline.echo(_("finder.atTop"), commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
719 gFolderTreeView.selection.timedSelect(newPos, 500);
723 mappings.add(myModes, ["<C-P>"],
724 "Go to previous mailbox with unread messages",
726 mail._selectUnreadFolder(true, args.count);
731 mappings.add(myModes, ["za"],
732 "Toggle thread collapsed/expanded",
733 function () { if (!mail.expandThread()) mail.collapseThread(); });
735 mappings.add(myModes, ["zc"],
737 function () { mail.collapseThread(); });
739 mappings.add(myModes, ["zo"],
741 function () { mail.expandThread(); });
743 mappings.add(myModes, ["zr", "zR"],
744 "Expand all threads",
745 function () { window.goDoCommand("cmd_expandAllThreads"); });
747 mappings.add(myModes, ["zm", "zM"],
748 "Collapse all threads",
749 function () { window.goDoCommand("cmd_collapseAllThreads"); });
751 mappings.add(myModes, ["<C-i>"],
753 function ({ count }) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.forward, true); },
756 mappings.add(myModes, ["<C-o>"],
758 function ({ count }) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.back, true); },
761 mappings.add(myModes, ["gg"],
762 "Select first message",
763 function ({ count }) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.firstMessage, true); },
766 mappings.add(myModes, ["G"],
767 "Select last message",
768 function ({ count }) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.lastMessage, false); },
772 mappings.add(myModes, ["l"],
775 if (!GetSelectedMessages())
776 return void dactyl.beep();
779 case "r": MsgMarkMsgAsRead(); break;
780 case "s": MsgMarkAsFlagged(); break;
781 case "i": ToggleMessageTagKey(1); break; // Important
782 case "w": ToggleMessageTagKey(2); break; // Work
783 case "p": ToggleMessageTagKey(3); break; // Personal
784 case "t": ToggleMessageTagKey(4); break; // TODO
785 case "l": ToggleMessageTagKey(5); break; // Later
786 default: dactyl.beep();
793 // TODO: change binding?
794 mappings.add(myModes, ["T"],
795 "Mark current folder as read",
797 if (mail.currentFolder.isServer)
798 return dactyl.beep();
800 mail.currentFolder.markAllMessagesRead(msgWindow);
803 mappings.add(myModes, ["<C-t>"],
804 "Mark all messages as read",
806 mail.getFolders("", false).forEach(function (folder) { folder.markAllMessagesRead(msgWindow); });
810 mappings.add(myModes, ["h"],
811 "Toggle displayed headers",
813 let value = gPrefBranch.getIntPref("mail.show_headers", 2);
814 gPrefBranch.setIntPref("mail.show_headers", value == 2 ? 1 : 2);
818 mappings.add(myModes, ["x"],
819 "Toggle HTML message display",
821 let wantHtml = (gPrefBranch.getIntPref("mailnews.display.html_as", 1) == 1);
822 mail.setHTML(wantHtml ? 1 : 0);
826 mappings.add(myModes, ["Y"],
830 let subject = gDBView.hdrForFirstSelectedMessage.mime2DecodedSubject;
831 dactyl.clipboardWrite(subject, true);
833 catch (e) { dactyl.beep(); }
836 mappings.add(myModes, ["y"],
837 "Yank sender or feed URL",
840 if (mail.currentAccount.server.type == "rss")
841 dactyl.clipboardWrite(mail._getRSSUrl(), true);
843 dactyl.clipboardWrite(gDBView.hdrForFirstSelectedMessage.mime2DecodedAuthor, true);
845 catch (e) { dactyl.beep(); }
848 // RSS specific mappings
849 mappings.add(myModes, ["p"],
850 "Open RSS message in browser",
853 if (mail.currentAccount.server.type == "rss")
854 messenger.launchExternalURL(mail._getRSSUrl());
855 // TODO: what to do for non-rss message?
862 services: function initServices(dactyl, modules, window) {
863 services.add("smtp", "@mozilla.org/messengercompose/smtp;1", Ci.nsISmtpService);
866 modes: function initModes(dactyl, modules, window) {
867 modes.addMode("MESSAGE", {
869 description: "Active the message is focused",
870 bases: [modes.COMMAND]
873 options: function initOptions(dactyl, modules, window) {
874 // FIXME: why does this default to "Archive", I don't have one? The default
875 // value won't validate now. mst please fix. --djk
876 options.add(["archivefolder"],
877 "Set the archive folder",
880 completer: function (context) completion.mailFolder(context)
883 // TODO: generate the possible values dynamically from the menu
884 options.add(["layout"],
885 "Set the layout of the mail window",
888 setter: function (value) {
890 case "classic": ChangeMailLayout(0); break;
891 case "wide": ChangeMailLayout(1); break;
892 case "vertical": ChangeMailLayout(2); break;
893 // case "inherit" just does nothing
898 completer: function (context) [
899 ["inherit", "Default View"], // FIXME: correct description?
900 ["classic", "Classic View"],
901 ["wide", "Wide View"],
902 ["vertical", "Vertical View"]
906 options.add(["smtpserver", "smtp"],
907 "Set the default SMTP server",
908 "string", services.smtp.defaultServer.key, // TODO: how should we handle these persistent external defaults - "inherit" or null?
910 getter: function () services.smtp.defaultServer.key,
911 setter: function (value) {
912 let server = mail.smtpServers.filter(function (s) s.key == value)[0];
913 services.smtp.defaultServer = server;
916 completer: function (context) [[s.key, s.serverURI] for ([, s] in Iterator(mail.smtpServers))]
919 /*options.add(["threads"],
920 "Use threading to group messages",
923 setter: function (value) {
935 // vim: set fdm=marker sw=4 ts=4 et: