Revision control

Copy as Markdown

Other Tools

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
var {FeedUtils} = ChromeUtils.import("resource:///modules/FeedUtils.jsm");
var { FolderUtils } = ChromeUtils.import("resource:///modules/FolderUtils.jsm");
var {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
var kClassicMailLayout = 0;
var kWideMailLayout = 1;
var kVerticalMailLayout = 2;
var kMouseButtonLeft = 0;
var kMouseButtonMiddle = 1;
var kMouseButtonRight = 2;
// Per message header flags to keep track of whether the user is allowing remote
// content for a particular message.
// if you change or add more values to these constants, be sure to modify
// the corresponding definitions in nsMsgContentPolicy.cpp
var kNoRemoteContentPolicy = 0;
var kBlockRemoteContent = 1;
var kAllowRemoteContent = 2;
var kIsAPhishMessage = 0;
var kNotAPhishMessage = 1;
var kMsgForwardAsAttachment = 0;
var gMessengerBundle;
var gOfflineManager;
// Timer to mark read, if the user has configured the app to mark a message as
// read if it is viewed for more than n seconds.
var gMarkViewedMessageAsReadTimer = null;
// The user preference, if HTML is not allowed. Assume, that the user could have
// set this to a value > 1 in their prefs.js or user.js, but that the value will
// not change during runtime other than through the MsgBody*() functions below.
var gDisallow_classes_no_html = 1;
// Disable the File | New | Account... menu item if the account preference is
// locked. Two other affected areas are the account central and the account
// manager dialogs.
function menu_new_init() {
let folders = GetSelectedMsgFolders();
if (folders.length != 1)
return;
let folder = folders[0];
if (!gMessengerBundle)
gMessengerBundle = document.getElementById("bundle_messenger");
if (Services.prefs.prefIsLocked("mail.disable_new_account_addition"))
document.getElementById("newAccountMenuItem")
.setAttribute("disabled", "true");
let isInbox = folder.isSpecialFolder(Ci.nsMsgFolderFlags.Inbox, false);
let showNew = folder.canCreateSubfolders ||
(isInbox && !(folder.flags & Ci.nsMsgFolderFlags.Virtual));
ShowMenuItem("menu_newFolder", showNew);
ShowMenuItem("menu_newVirtualFolder", showNew);
EnableMenuItem("menu_newFolder", folder.server.type != "imap" ||
!Services.io.offline);
if (showNew) {
// Change "New Folder..." menu according to the context.
let label = (folder.isServer || isInbox) ? "newFolderMenuItem" :
"newSubfolderMenuItem";
SetMenuItemLabel("menu_newFolder", gMessengerBundle.getString(label));
}
}
function goUpdateMailMenuItems(commandset) {
for (var i = 0; i < commandset.childNodes.length; i++) {
var commandID = commandset.childNodes[i].getAttribute("id");
if (commandID)
goUpdateCommand(commandID);
}
}
function file_init() {
document.commandDispatcher.updateCommands("create-menu-file");
}
function InitEditMessagesMenu() {
goSetMenuValue("cmd_delete", "valueDefault");
goSetAccessKey("cmd_delete", "valueDefaultAccessKey");
document.commandDispatcher.updateCommands("create-menu-edit");
// initialize the favorite Folder checkbox in the edit menu
let favoriteFolderMenu = document.getElementById("menu_favoriteFolder");
if (!favoriteFolderMenu.hasAttribute("disabled")) {
let folders = GetSelectedMsgFolders();
if (folders.length == 1 && !folders[0].isServer) {
let checked = folders[0].getFlag(Ci.nsMsgFolderFlags.Favorite);
// Adjust the checked state on the menu item.
favoriteFolderMenu.setAttribute("checked", checked);
favoriteFolderMenu.hidden = false;
} else {
favoriteFolderMenu.hidden = true;
}
}
}
function InitGoMessagesMenu() {
// deactivate the folders in the go menu if we don't have a folderpane
document.getElementById("goFolderMenu")
.setAttribute("disabled", IsFolderPaneCollapsed());
document.commandDispatcher.updateCommands("create-menu-go");
}
function view_init() {
if (!gMessengerBundle)
gMessengerBundle = document.getElementById("bundle_messenger");
var message_menuitem = document.getElementById("menu_showMessagePane");
if (message_menuitem && !message_menuitem.hidden) {
message_menuitem.setAttribute("checked", !IsMessagePaneCollapsed());
message_menuitem.setAttribute("disabled", gAccountCentralLoaded);
}
var threadpane_menuitem = document.getElementById("menu_showThreadPane");
if (threadpane_menuitem && !threadpane_menuitem.hidden) {
threadpane_menuitem.setAttribute("checked", !IsDisplayDeckCollapsed());
threadpane_menuitem.setAttribute("disabled", gAccountCentralLoaded);
}
var folderPane_menuitem = document.getElementById("menu_showFolderPane");
if (folderPane_menuitem && !folderPane_menuitem.hidden)
folderPane_menuitem.setAttribute("checked", !IsFolderPaneCollapsed());
document.getElementById("viewSortMenu").disabled = gAccountCentralLoaded;
document.getElementById("viewMessageViewMenu").disabled = gAccountCentralLoaded;
document.getElementById("viewMessagesMenu").disabled = gAccountCentralLoaded;
document.getElementById("charsetMenu").disabled = !gMessageDisplay.displayedMessage;
// Initialize the Message Body menuitem
let isFeed = gFolderDisplay &&
((gFolderDisplay.displayedFolder &&
gFolderDisplay.displayedFolder.server.type == "rss") ||
gFolderDisplay.selectedMessageIsFeed);
document.getElementById("viewBodyMenu").hidden = isFeed;
// Initialize the Show Feed Summary menu
let viewFeedSummary = document.getElementById("viewFeedSummary");
viewFeedSummary.hidden = !isFeed ||
document.documentElement.getAttribute("windowtype") != "mail:3pane";
let viewRssMenuItemIds = ["bodyFeedGlobalWebPage",
"bodyFeedGlobalSummary",
"bodyFeedPerFolderPref"];
let checked = FeedMessageHandler.onSelectPref;
for (let [index, id] of viewRssMenuItemIds.entries()) {
document.getElementById(id)
.setAttribute("checked", index == checked);
}
// Initialize the Display Attachments Inline menu.
var viewAttachmentInline = Services.prefs.getBoolPref("mail.inline_attachments");
document.getElementById("viewAttachmentsInlineMenuitem")
.setAttribute("checked", viewAttachmentInline);
document.commandDispatcher.updateCommands("create-menu-view");
}
function InitViewLayoutStyleMenu(event) {
var paneConfig = Services.prefs.getIntPref("mail.pane_config.dynamic");
var layoutStyleMenuitem = event.target.childNodes[paneConfig];
if (layoutStyleMenuitem)
layoutStyleMenuitem.setAttribute("checked", "true");
}
function setSortByMenuItemCheckState(id, value) {
var menuitem = document.getElementById(id);
if (menuitem) {
menuitem.setAttribute("checked", value);
}
}
function InitViewSortByMenu() {
var sortType = gDBView.sortType;
setSortByMenuItemCheckState("sortByDateMenuitem",
sortType == Ci.nsMsgViewSortType.byDate);
setSortByMenuItemCheckState("sortByReceivedMenuitem",
sortType == Ci.nsMsgViewSortType.byReceived);
setSortByMenuItemCheckState("sortByFlagMenuitem",
sortType == Ci.nsMsgViewSortType.byFlagged);
setSortByMenuItemCheckState("sortByOrderReceivedMenuitem",
sortType == Ci.nsMsgViewSortType.byId);
setSortByMenuItemCheckState("sortByPriorityMenuitem",
sortType == Ci.nsMsgViewSortType.byPriority);
setSortByMenuItemCheckState("sortBySizeMenuitem",
sortType == Ci.nsMsgViewSortType.bySize);
setSortByMenuItemCheckState("sortByStatusMenuitem",
sortType == Ci.nsMsgViewSortType.byStatus);
setSortByMenuItemCheckState("sortBySubjectMenuitem",
sortType == Ci.nsMsgViewSortType.bySubject);
setSortByMenuItemCheckState("sortByUnreadMenuitem",
sortType == Ci.nsMsgViewSortType.byUnread);
setSortByMenuItemCheckState("sortByTagsMenuitem",
sortType == Ci.nsMsgViewSortType.byTags);
setSortByMenuItemCheckState("sortByJunkStatusMenuitem",
sortType == Ci.nsMsgViewSortType.byJunkStatus);
setSortByMenuItemCheckState("sortByFromMenuitem",
sortType == Ci.nsMsgViewSortType.byAuthor);
setSortByMenuItemCheckState("sortByRecipientMenuitem",
sortType == Ci.nsMsgViewSortType.byRecipient);
setSortByMenuItemCheckState("sortByAttachmentsMenuitem",
sortType == Ci.nsMsgViewSortType.byAttachments);
var sortOrder = gDBView.sortOrder;
var sortTypeSupportsGrouping = (sortType == Ci.nsMsgViewSortType.byAuthor ||
sortType == Ci.nsMsgViewSortType.byDate ||
sortType == Ci.nsMsgViewSortType.byReceived ||
sortType == Ci.nsMsgViewSortType.byPriority ||
sortType == Ci.nsMsgViewSortType.bySubject ||
sortType == Ci.nsMsgViewSortType.byTags ||
sortType == Ci.nsMsgViewSortType.byRecipient ||
sortType == Ci.nsMsgViewSortType.byFlagged ||
sortType == Ci.nsMsgViewSortType.byAttachments);
setSortByMenuItemCheckState("sortAscending",
sortOrder == Ci.nsMsgViewSortOrder.ascending);
setSortByMenuItemCheckState("sortDescending",
sortOrder == Ci.nsMsgViewSortOrder.descending);
var grouped = ((gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort) != 0);
var threaded = ((gDBView.viewFlags & Ci.nsMsgViewFlagsType.kThreadedDisplay) != 0 && !grouped);
var sortThreadedMenuItem = document.getElementById("sortThreaded");
var sortUnthreadedMenuItem = document.getElementById("sortUnthreaded");
sortThreadedMenuItem.setAttribute("checked", threaded);
sortUnthreadedMenuItem.setAttribute("checked", !threaded && !grouped);
var groupBySortOrderMenuItem = document.getElementById("groupBySort");
groupBySortOrderMenuItem.setAttribute("disabled", !sortTypeSupportsGrouping);
groupBySortOrderMenuItem.setAttribute("checked", grouped);
}
function InitViewMessagesMenu() {
var viewFlags = gDBView ? gDBView.viewFlags : 0;
var viewType = gDBView ? gDBView.viewType : 0;
document.getElementById("viewAllMessagesMenuItem").setAttribute("checked",
(viewFlags & Ci.nsMsgViewFlagsType.kUnreadOnly) == 0 &&
(viewType == Ci.nsMsgViewType.eShowAllThreads));
document.getElementById("viewUnreadMessagesMenuItem").setAttribute("checked",
(viewFlags & Ci.nsMsgViewFlagsType.kUnreadOnly) != 0);
document.getElementById("viewThreadsWithUnreadMenuItem").setAttribute("checked",
viewType == Ci.nsMsgViewType.eShowThreadsWithUnread);
document.getElementById("viewWatchedThreadsWithUnreadMenuItem").setAttribute("checked",
viewType == Ci.nsMsgViewType.eShowWatchedThreadsWithUnread);
document.getElementById("viewIgnoredThreadsMenuItem").setAttribute("checked",
(viewFlags & Ci.nsMsgViewFlagsType.kShowIgnored) != 0);
}
function InitMessageMenu() {
var selectedMsg = gFolderDisplay.selectedMessage;
var isNews = gFolderDisplay.selectedMessageIsNews;
var isFeed = gFolderDisplay.selectedMessageIsFeed;
// We show Reply to Newsgroups only for news messages.
document.getElementById("replyNewsgroupMainMenu").hidden = !isNews;
// We show Reply to List only for list posts.
document.getElementById("replyListMainMenu").hidden = isNews || !IsListPost();
// For mail messages we say reply. For news we say ReplyToSender.
document.getElementById("replyMainMenu").hidden = isNews;
document.getElementById("replySenderMainMenu").hidden = !isNews;
// We show Reply to Sender and Newsgroup only for news messages.
document.getElementById("replySenderAndNewsgroupMainMenu").hidden = !isNews;
// For mail messages we say reply all. For news we say ReplyToAllRecipients.
document.getElementById("replyallMainMenu").hidden = isNews;
document.getElementById("replyAllRecipientsMainMenu").hidden = !isNews;
// We only show Ignore Thread and Watch Thread menu items for news.
document.getElementById("threadItemsSeparator").hidden = !isNews;
document.getElementById("killThread").hidden = !isNews;
document.getElementById("killSubthread").hidden = !isNews;
document.getElementById("watchThread").hidden = !isNews;
document.getElementById("menu_cancel").hidden = !isNews;
// Disable the Move and Copy menus if there are no messages selected.
// Disable the Move menu if we can't delete messages from the folder.
var msgFolder = GetLoadedMsgFolder();
var enableMenuItem = !isNews && selectedMsg &&
msgFolder && msgFolder.canDeleteMessages;
document.getElementById("moveMenu").disabled = !enableMenuItem;
// Also disable copy when no folder is loaded (like for .eml files).
var canCopy = selectedMsg && (!gMessageDisplay.isDummy ||
window.arguments[0].scheme == "file");
document.getElementById("copyMenu").disabled = !canCopy;
// Disable the Forward as/Tag menu items if no message is selected.
document.getElementById("forwardAsMenu").disabled = !selectedMsg;
document.getElementById("tagMenu").disabled = !selectedMsg;
// Show "Edit Draft Message" menus only in a drafts folder;
// otherwise hide them.
showCommandInSpecialFolder("cmd_editDraftMsg", Ci.nsMsgFolderFlags.Drafts);
// Show "New Message from Template" and "Edit Template" menus only in a
// templates folder; otherwise hide them.
showCommandInSpecialFolder("cmd_newMsgFromTemplate",
Ci.nsMsgFolderFlags.Templates);
showCommandInSpecialFolder("cmd_editTemplateMsg",
Ci.nsMsgFolderFlags.Templates);
// Initialize the Open Message menuitem
var winType = document.documentElement.getAttribute("windowtype");
if (winType == "mail:3pane")
document.getElementById("openMessageWindowMenuitem").hidden = isFeed;
// Initialize the Open Feed Message handler menu
let index = FeedMessageHandler.onOpenPref;
document.getElementById("menu_openFeedMessage")
.childNodes[index].setAttribute("checked", true);
let openRssMenu = document.getElementById("openFeedMessage");
openRssMenu.hidden = !isFeed;
if (winType != "mail:3pane")
openRssMenu.hidden = true;
// Disable the Mark menu when we're not in a folder.
document.getElementById("markMenu").disabled = !msgFolder;
document.commandDispatcher.updateCommands("create-menu-message");
}
/**
* Show folder-specific menu items only for messages in special folders, e.g.
* show 'cmd_editDraftMsg' in Drafts folder.
* show 'cmd_newMsgFromTemplate' in Templates folder.
*
* aCommandId the ID of a command to be shown in folders having aFolderFlag
* aFolderFlag the nsMsgFolderFlag that the folder must have to show the
* command
*/
function showCommandInSpecialFolder(aCommandId, aFolderFlag) {
let msg = gFolderDisplay.selectedMessage;
let folder = gFolderDisplay.displayedFolder;
// Check msg.folder exists as messages opened from a file have none.
let inSpecialFolder = (msg &&
msg.folder &&
msg.folder.isSpecialFolder(aFolderFlag, true)) ||
(folder && folder.getFlag(aFolderFlag));
document.getElementById(aCommandId).setAttribute("hidden", !inSpecialFolder);
return inSpecialFolder;
}
function InitViewHeadersMenu() {
var headerchoice =
Services.prefs.getIntPref("mail.show_headers",
Ci.nsMimeHeaderDisplayTypes.NormalHeaders);
document
.getElementById("cmd_viewAllHeader")
.setAttribute("checked",
headerchoice == Ci.nsMimeHeaderDisplayTypes.AllHeaders);
document
.getElementById("cmd_viewNormalHeader")
.setAttribute("checked",
headerchoice == Ci.nsMimeHeaderDisplayTypes.NormalHeaders);
document.commandDispatcher.updateCommands("create-menu-mark");
}
function InitViewBodyMenu() {
// Separate render prefs not implemented for feeds, bug 458606. Show the
// checked item for feeds as for the regular pref.
// let html_as = Services.prefs.getIntPref("rss.display.html_as");
// let prefer_plaintext = Services.prefs.getBoolPref("rss.display.prefer_plaintext");
// let disallow_classes = Services.prefs.getIntPref("rss.display.disallow_mime_handlers");
let html_as = Services.prefs.getIntPref("mailnews.display.html_as");
let prefer_plaintext = Services.prefs.getBoolPref("mailnews.display.prefer_plaintext");
let disallow_classes = Services.prefs.getIntPref("mailnews.display.disallow_mime_handlers");
let isFeed = gFolderDisplay.selectedMessageIsFeed;
const defaultIDs = ["bodyAllowHTML",
"bodySanitized",
"bodyAsPlaintext",
"bodyAllParts"];
const rssIDs = ["bodyFeedSummaryAllowHTML",
"bodyFeedSummarySanitized",
"bodyFeedSummaryAsPlaintext"];
let menuIDs = isFeed ? rssIDs : defaultIDs;
if (disallow_classes > 0)
gDisallow_classes_no_html = disallow_classes;
// else gDisallow_classes_no_html keeps its inital value (see top)
let AllowHTML_menuitem = document.getElementById(menuIDs[0]);
let Sanitized_menuitem = document.getElementById(menuIDs[1]);
let AsPlaintext_menuitem = document.getElementById(menuIDs[2]);
let AllBodyParts_menuitem;
if (!isFeed) {
AllBodyParts_menuitem = document.getElementById(menuIDs[3]);
AllBodyParts_menuitem.hidden =
!Services.prefs.getBoolPref("mailnews.display.show_all_body_parts_menu");
}
if (!prefer_plaintext && !html_as && !disallow_classes &&
AllowHTML_menuitem)
AllowHTML_menuitem.setAttribute("checked", true);
else if (!prefer_plaintext && html_as == 3 && disallow_classes > 0 &&
Sanitized_menuitem)
Sanitized_menuitem.setAttribute("checked", true);
else if (prefer_plaintext && html_as == 1 && disallow_classes > 0 &&
AsPlaintext_menuitem)
AsPlaintext_menuitem.setAttribute("checked", true);
else if (!prefer_plaintext && html_as == 4 && !disallow_classes &&
AllBodyParts_menuitem)
AllBodyParts_menuitem.setAttribute("checked", true);
// else (the user edited prefs/user.js) check none of the radio menu items
if (isFeed) {
AllowHTML_menuitem.hidden = !FeedMessageHandler.gShowSummary;
Sanitized_menuitem.hidden = !FeedMessageHandler.gShowSummary;
AsPlaintext_menuitem.hidden = !FeedMessageHandler.gShowSummary;
document.getElementById("viewFeedSummarySeparator").hidden = !FeedMessageHandler.gShowSummary;
}
}
function SetMenuItemLabel(menuItemId, customLabel) {
var menuItem = document.getElementById(menuItemId);
if (menuItem)
menuItem.setAttribute("label", customLabel);
}
function RemoveAllMessageTags() {
var selectedMessages = gFolderDisplay.selectedMessages;
if (!selectedMessages.length)
return;
var messages = [];
var tagArray = MailServices.tags.getAllTags();
var allKeys = "";
for (let j = 0; j < tagArray.length; ++j) {
if (j)
allKeys += " ";
allKeys += tagArray[j].key;
}
var prevHdrFolder = null;
// this crudely handles cross-folder virtual folders with selected messages
// that spans folders, by coalescing consecutive messages in the selection
// that happen to be in the same folder. nsMsgSearchDBView does this better,
// but nsIMsgDBView doesn't handle commands with arguments, and untag takes a
// key argument. Furthermore, we only delete legacy labels and known tags,
// keeping other keywords like (non)junk intact.
for (let i = 0; i < selectedMessages.length; ++i) {
var msgHdr = selectedMessages[i];
msgHdr.label = 0; // remove legacy label
if (prevHdrFolder != msgHdr.folder) {
if (prevHdrFolder)
prevHdrFolder.removeKeywordsFromMessages(messages, allKeys);
messages = [];
prevHdrFolder = msgHdr.folder;
}
messages.push(msgHdr);
}
if (prevHdrFolder)
prevHdrFolder.removeKeywordsFromMessages(messages, allKeys);
OnTagsChange();
}
function InitNewMsgMenu(aPopup) {
var identity = null;
var folder = GetFirstSelectedMsgFolder();
if (folder)
identity = getIdentityForServer(folder.server);
if (!identity) {
let defaultAccount = MailServices.accounts.defaultAccount;
if (defaultAccount)
identity = defaultAccount.defaultIdentity;
}
// If the identity is not found, use the mail.html_compose pref to
// determine the message compose type (HTML or PlainText).
var composeHTML = identity ? identity.composeHtml
: Services.prefs.getBoolPref("mail.html_compose");
const kIDs = {true: "button-newMsgHTML", false: "button-newMsgPlain"};
document.getElementById(kIDs[composeHTML]).setAttribute("default", "true");
document.getElementById(kIDs[!composeHTML]).removeAttribute("default");
}
function InitMessageReply(aPopup) {
var isNews = gFolderDisplay.selectedMessageIsNews;
// For mail messages we say reply. For news we say ReplyToSender.
// We show Reply to Newsgroups only for news messages.
aPopup.childNodes[0].hidden = isNews; // Reply
aPopup.childNodes[1].hidden = isNews || !IsListPost(); // Reply to List
aPopup.childNodes[2].hidden = !isNews; // Reply to Newsgroup
aPopup.childNodes[3].hidden = !isNews; // Reply to Sender Only
}
function InitMessageForward(aPopup) {
var forwardType = Services.prefs.getIntPref("mail.forward_message_mode");
if (forwardType != kMsgForwardAsAttachment) {
// forward inline is the first menuitem
aPopup.firstChild.setAttribute("default", "true");
aPopup.lastChild.removeAttribute("default");
} else {
// attachment is the last menuitem
aPopup.lastChild.setAttribute("default", "true");
aPopup.firstChild.removeAttribute("default");
}
}
function ToggleMessageTagKey(index) {
// toggle the tag state based upon that of the first selected message
var msgHdr = gFolderDisplay.selectedMessage;
if (!msgHdr)
return;
var tagArray = MailServices.tags.getAllTags();
for (var i = 0; i < tagArray.length; ++i) {
var key = tagArray[i].key;
if (!--index) {
// found the key, now toggle its state
var curKeys = msgHdr.getStringProperty("keywords");
if (msgHdr.label)
curKeys += " $label" + msgHdr.label;
var addKey = !(" " + curKeys + " ").includes(" " + key + " ");
ToggleMessageTag(key, addKey);
return;
}
}
}
function ToggleMessageTagMenu(target) {
var key = target.getAttribute("value");
var addKey = target.getAttribute("checked") == "true";
ToggleMessageTag(key, addKey);
}
function ToggleMessageTag(key, addKey) {
var messages = [];
var selectedMessages = gFolderDisplay.selectedMessages;
var toggler = addKey ? "addKeywordsToMessages" : "removeKeywordsFromMessages";
var prevHdrFolder = null;
// this crudely handles cross-folder virtual folders with selected messages
// that spans folders, by coalescing consecutive msgs in the selection
// that happen to be in the same folder. nsMsgSearchDBView does this
// better, but nsIMsgDBView doesn't handle commands with arguments,
// and (un)tag takes a key argument.
for (let i = 0; i < selectedMessages.length; ++i) {
var msgHdr = selectedMessages[i];
if (msgHdr.label) {
// Since we touch all these messages anyway, migrate the label now.
// If we don't, the thread tree won't always show the correct tag state,
// because resetting a label doesn't update the tree anymore...
msgHdr.folder.addKeywordsToMessages([msgHdr], "$label" + msgHdr.label);
msgHdr.label = 0; // remove legacy label
}
if (prevHdrFolder != msgHdr.folder) {
if (prevHdrFolder)
prevHdrFolder[toggler](messages, key);
messages = [];
prevHdrFolder = msgHdr.folder;
}
messages.push(msgHdr);
}
if (prevHdrFolder)
prevHdrFolder[toggler](messages, key);
OnTagsChange();
}
function SetMessageTagLabel(menuitem, index, name) {
// if a <key> is defined for this tag, use its key as the accesskey
// (the key for the tag at index n needs to have the id key_tag<n>)
var shortcutkey = document.getElementById("key_tag" + index);
var accesskey = shortcutkey ? shortcutkey.getAttribute("key") : "";
if (accesskey)
menuitem.setAttribute("accesskey", accesskey);
var label = gMessengerBundle.getFormattedString("mailnews.tags.format",
[accesskey, name]);
menuitem.setAttribute("label", label);
}
function InitMessageTags(menuPopup) {
var tagArray = MailServices.tags.getAllTags();
var tagCount = tagArray.length;
// remove any existing non-static entries...
var menuseparator = menuPopup.lastChild.previousSibling;
for (var i = menuPopup.childNodes.length; i > 4; --i)
menuseparator.previousSibling.remove();
// hide double menuseparator
menuseparator.previousSibling.hidden = !tagCount;
// create label and accesskey for the static remove item
var tagRemoveLabel = gMessengerBundle.getString("mailnews.tags.remove");
SetMessageTagLabel(menuPopup.firstChild, 0, tagRemoveLabel);
// now rebuild the list
var msgHdr = gFolderDisplay.selectedMessage;
var curKeys = msgHdr.getStringProperty("keywords");
if (msgHdr.label)
curKeys += " $label" + msgHdr.label;
for (var i = 0; i < tagCount; ++i) {
var taginfo = tagArray[i];
var removeKey = (" " + curKeys + " ").includes(" " + taginfo.key + " ");
if (taginfo.ordinal.includes("~AUTOTAG") && !removeKey)
continue;
// TODO we want to either remove or "check" the tags that already exist
var newMenuItem = document.createElement("menuitem");
SetMessageTagLabel(newMenuItem, i + 1, taginfo.tag);
newMenuItem.setAttribute("value", taginfo.key);
newMenuItem.setAttribute("type", "checkbox");
newMenuItem.setAttribute("checked", removeKey);
newMenuItem.setAttribute("oncommand", "ToggleMessageTagMenu(event.target);");
var color = taginfo.color;
if (color)
newMenuItem.setAttribute("class", "lc-" + color.substr(1));
menuPopup.insertBefore(newMenuItem, menuseparator);
}
}
function InitBackToolbarMenu(menuPopup) {
PopulateHistoryMenu(menuPopup, -1);
}
function InitForwardToolbarMenu(menuPopup) {
PopulateHistoryMenu(menuPopup, 1);
}
function PopulateHistoryMenu(menuPopup, navOffset) {
// remove existing entries
while (menuPopup.hasChildNodes())
menuPopup.lastChild.remove();
let startPos = messenger.navigatePos;
let historyArray = messenger.getNavigateHistory();
let maxPos = historyArray.length / 2; // Array consists of pairs.
if (GetLoadedMessage())
startPos += navOffset;
// starting from the current entry, march through history until we reach
// the array border or our menuitem limit
for (var i = startPos, itemCount = 0;
(i >= 0) && (i < maxPos) && (itemCount < 25);
i += navOffset, ++itemCount) {
var menuText = "";
let folder = MailUtils.getFolderForURI(historyArray[i * 2 + 1]);
if (!IsCurrentLoadedFolder(folder))
menuText += folder.prettyName + ": ";
var msgHdr = messenger.msgHdrFromURI(historyArray[i * 2]);
var subject = "";
if (msgHdr.flags & Ci.nsMsgMessageFlags.HasRe)
subject = "Re: ";
if (msgHdr.mime2DecodedSubject)
subject += msgHdr.mime2DecodedSubject;
if (subject)
menuText += subject + " - ";
menuText += msgHdr.mime2DecodedAuthor;
var newMenuItem = document.createElement("menuitem");
newMenuItem.setAttribute("label", menuText);
newMenuItem.setAttribute("value", i - startPos);
newMenuItem.folder = folder;
menuPopup.appendChild(newMenuItem);
}
}
function NavigateToUri(target) {
var historyIndex = target.getAttribute("value");
var msgUri = messenger.getMsgUriAtNavigatePos(historyIndex);
let msgHdrKey = messenger.msgHdrFromURI(msgUri).messageKey;
messenger.navigatePos += Number(historyIndex);
if (target.folder.URI == GetThreadPaneFolder().URI) {
gDBView.selectMsgByKey(msgHdrKey);
} else {
gStartMsgKey = msgHdrKey;
SelectMsgFolder(target.folder);
}
}
function InitMessageMark() {
document.getElementById("cmd_markAsFlagged")
.setAttribute("checked", SelectedMessagesAreFlagged());
document.commandDispatcher.updateCommands("create-menu-mark");
}
function UpdateJunkToolbarButton() {
var junkButtonDeck = document.getElementById("junk-deck");
// Wallpaper over Bug 491676 by using the attribute instead of the property.
junkButtonDeck.setAttribute("selectedIndex", SelectedMessagesAreJunk() ? 1 : 0);
}
function UpdateDeleteToolbarButton(aFolderPaneHasFocus) {
var deleteButtonDeck = document.getElementById("delete-deck");
var selectedIndex = 0;
// Never show "Undelete" in the 3-pane for folders, when delete would
// apply to the selected folder.
if (!aFolderPaneHasFocus && SelectedMessagesAreDeleted())
selectedIndex = 1;
// Wallpaper over Bug 491676 by using the attribute instead of the property.
deleteButtonDeck.setAttribute("selectedIndex", selectedIndex);
}
function UpdateDeleteCommand() {
var value = "value";
if (SelectedMessagesAreDeleted())
value += "IMAPDeleted";
if (GetNumSelectedMessages() < 2)
value += "Message";
else
value += "Messages";
goSetMenuValue("cmd_delete", value);
goSetAccessKey("cmd_delete", value + "AccessKey");
}
function SelectedMessagesAreDeleted() {
var firstSelectedMessage = gFolderDisplay.selectedMessage;
return firstSelectedMessage &&
(firstSelectedMessage.flags &
Ci.nsMsgMessageFlags.IMAPDeleted);
}
function SelectedMessagesAreJunk() {
var firstSelectedMessage = gFolderDisplay.selectedMessage;
if (!firstSelectedMessage)
return false;
var junkScore = firstSelectedMessage.getStringProperty("junkscore");
return (junkScore != "") && (junkScore != "0");
}
function SelectedMessagesAreRead() {
let messages = gFolderDisplay.selectedMessages;
if (messages.length == 0)
return undefined;
if (messages.every(function(msg) { return msg.isRead; }))
return true;
if (messages.every(function(msg) { return !msg.isRead; }))
return false;
return undefined;
}
function SelectedMessagesAreFlagged() {
var firstSelectedMessage = gFolderDisplay.selectedMessage;
return firstSelectedMessage && firstSelectedMessage.isFlagged;
}
function getMsgToolbarMenu_init() {
document.commandDispatcher.updateCommands("create-menu-getMsgToolbar");
}
function GetFirstSelectedMsgFolder() {
var selectedFolders = GetSelectedMsgFolders();
return (selectedFolders.length > 0) ? selectedFolders[0] : null;
}
function GetInboxFolder(server) {
try {
var rootMsgFolder = server.rootMsgFolder;
// Now find Inbox.
return rootMsgFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
} catch (ex) {
dump(ex + "\n");
}
return null;
}
function GetMessagesForInboxOnServer(server) {
var inboxFolder = GetInboxFolder(server);
// If the server doesn't support an inbox it could be an RSS server or
// some other server type, just use the root folder and the server
// implementation can figure out what to do.
if (!inboxFolder)
inboxFolder = server.rootFolder;
GetNewMsgs(server, inboxFolder);
}
function MsgGetMessage() {
// if offline, prompt for getting messages
if (DoGetNewMailWhenOffline())
GetFolderMessages();
}
function MsgGetMessagesForAllServers(defaultServer) {
MailTasksGetMessagesForAllServers(true, msgWindow, defaultServer);
}
/**
* Get messages for all those accounts which have the capability
* of getting messages and have session password available i.e.,
* curretnly logged in accounts.
* if offline, prompt for getting messages.
*/
function MsgGetMessagesForAllAuthenticatedAccounts() {
if (DoGetNewMailWhenOffline())
MailTasksGetMessagesForAllServers(false, msgWindow, null);
}
/**
* Get messages for the account selected from Menu dropdowns.
* if offline, prompt for getting messages.
*
* @param aFolder (optional) a folder in the account for which messages should
* be retrieved. If null, all accounts will be used.
*/
function MsgGetMessagesForAccount(aFolder) {
if (!aFolder) {
goDoCommand("cmd_getNewMessages");
return;
}
if (DoGetNewMailWhenOffline())
GetMessagesForInboxOnServer(aFolder.server);
}
// if offline, prompt for getNextNMessages
function MsgGetNextNMessages() {
if (DoGetNewMailWhenOffline()) {
var folder = GetFirstSelectedMsgFolder();
if (folder)
GetNextNMessages(folder);
}
}
function MsgDeleteMessage(aReallyDelete) {
// If the user deletes a message before its mark as read timer goes off,
// we should mark it as read (unless the user changed the pref). This
// ensures that we clear the biff indicator from the system tray when
// the user deletes the new message.
if (Services.prefs.getBoolPref("mailnews.ui.deleteMarksRead"))
MarkSelectedMessagesRead(true);
SetNextMessageAfterDelete();
// determine if we're using the IMAP delete model
var server = GetFirstSelectedMsgFolder().server;
const kIMAPDelete = Ci.nsMsgImapDeleteModels.IMAPDelete;
var imapDeleteModelUsed = server instanceof Ci.nsIImapIncomingServer &&
server.deleteModel == kIMAPDelete;
// execute deleteNoTrash only if IMAP delete model is not used
if (aReallyDelete && !imapDeleteModelUsed)
gDBView.doCommand(nsMsgViewCommandType.deleteNoTrash);
else
gDBView.doCommand(nsMsgViewCommandType.deleteMsg);
}
/**
* Copies the selected messages to the destination folder
* @param aDestFolder the destination folder
*/
function MsgCopyMessage(aDestFolder) {
if (gMessageDisplay.isDummy) {
let file = window.arguments[0].QueryInterface(Ci.nsIFileURL).file;
MailServices.copy.copyFileMessage(file, aDestFolder, null, false,
Ci.nsMsgMessageFlags.Read,
"", null, msgWindow);
} else {
gDBView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, aDestFolder);
}
}
/**
* Moves the selected messages to the destination folder
* @param aDestFolder the destination folder
*/
function MsgMoveMessage(aDestFolder) {
SetNextMessageAfterDelete();
gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, aDestFolder);
}
/**
* Calls the ComposeMessage function with the desired type and proper default
* based on the event that fired it.
*
* @param aCompType The nsIMsgCompType to pass to the function.
* @param aEvent (optional) The event that triggered the call.
* @param aFormat (optional) Override the message format.
*/
function ComposeMsgByType(aCompType, aEvent, aFormat) {
var format = aFormat || ((aEvent && aEvent.shiftKey) ? msgComposeFormat.OppositeOfDefault : msgComposeFormat.Default);
ComposeMessage(aCompType,
format,
GetFirstSelectedMsgFolder(),
gFolderDisplay ? gFolderDisplay.selectedMessageUris : null);
}
function MsgNewMessage(aEvent) {
var mode = aEvent && aEvent.target.getAttribute("mode");
ComposeMsgByType(msgComposeType.New, aEvent, mode && msgComposeFormat[mode]);
}
function MsgReplyMessage(aEvent) {
if (gFolderDisplay.selectedMessageIsNews)
MsgReplyGroup(aEvent);
else if (!gFolderDisplay.selectedMessageIsFeed)
MsgReplySender(aEvent);
}
function MsgReplyList(aEvent) {
ComposeMsgByType(msgComposeType.ReplyToList, aEvent);
}
function MsgReplyGroup(aEvent) {
ComposeMsgByType(msgComposeType.ReplyToGroup, aEvent);
}
function MsgReplySender(aEvent) {
ComposeMsgByType(msgComposeType.ReplyToSender, aEvent);
}
function MsgReplyToAllMessage(aEvent) {
var loadedFolder = GetLoadedMsgFolder();
var server = loadedFolder.server;
if (server && server.type == "nntp")
MsgReplyToSenderAndGroup(aEvent);
else
MsgReplyToAllRecipients(aEvent);
}
function MsgReplyToAllRecipients(aEvent) {
ComposeMsgByType(msgComposeType.ReplyAll, aEvent);
}
function MsgReplyToSenderAndGroup(aEvent) {
ComposeMsgByType(msgComposeType.ReplyToSenderAndGroup, aEvent);
}
// Message Archive function
function BatchMessageMover() {
this._batches = {};
this._currentKey = null;
this._dstFolderParent = null;
this._dstFolderName = null;
}
BatchMessageMover.prototype =
{
archiveMessages(aMsgHdrs) {
if (!aMsgHdrs.length)
return;
// We need to get the index of the message to select after archiving
// completes but reset the global variable to prevent the DBview from
// updating the selection; we'll do it manually at the end of
// processNextBatch.
SetNextMessageAfterDelete();
this.messageToSelectAfterWereDone = gNextMessageViewIndexAfterDelete;
gNextMessageViewIndexAfterDelete = -2;
for (let i = 0; i < aMsgHdrs.length; ++i) {
let msgHdr = aMsgHdrs[i];
let server = msgHdr.folder.server;
let msgDate = new Date(msgHdr.date / 1000); // convert date to JS date object
let msgYear = msgDate.getFullYear().toString();
let monthFolderName = msgYear + "-" + (msgDate.getMonth() + 1).toString().padStart(2, "0");
let archiveFolderUri;
let archiveGranularity;
let archiveKeepFolderStructure;
if (server.type == "rss") {
// RSS servers don't have an identity so we special case the archives URI.
archiveFolderUri = server.serverURI + "/Archives";
archiveGranularity =
Services.prefs.getIntPref("mail.identity.default.archive_granularity");
archiveKeepFolderStructure =
Services.prefs.getBoolPref("mail.identity.default.archive_keep_folder_structure");
} else {
let identity = GetIdentityForHeader(msgHdr,
Ci.nsIMsgCompType.ReplyAll);
archiveFolderUri = identity.archiveFolder;
archiveGranularity = identity.archiveGranularity;
archiveKeepFolderStructure = identity.archiveKeepFolderStructure;
}
let archiveFolder = MailUtils.getFolderForURI(archiveFolderUri, false);
let copyBatchKey = msgHdr.folder.URI + "\0" + monthFolderName;
if (!(copyBatchKey in this._batches))
this._batches[copyBatchKey] = [msgHdr.folder,
archiveFolderUri,
archiveGranularity,
archiveKeepFolderStructure,
msgYear,
monthFolderName];
this._batches[copyBatchKey].push(msgHdr);
}
MailServices.mfn.addListener(this, MailServices.mfn.folderAdded);
// Now we launch the code iterating over all message copies, one in turn.
this.processNextBatch();
},
processNextBatch() {
for (let key in this._batches) {
this._currentBatch = this._batches[key];
delete this._batches[key];
return this.filterBatch();
}
// all done
MailServices.mfn.removeListener(this);
// We're just going to select the message now.
let treeView = gDBView.QueryInterface(Ci.nsITreeView);
treeView.selection.select(this.messageToSelectAfterWereDone);
treeView.selectionChanged();
},
filterBatch() {
let batch = this._currentBatch;
// Apply filters to this batch.
let msgs = batch.slice(6);
let srcFolder = batch[0];
MailServices.filters.applyFilters(
Ci.nsMsgFilterType.Archive,
msgs, srcFolder, msgWindow, this);
// continues with onStopOperation
},
onStopOperation(aResult) {
if (!Components.isSuccessCode(aResult)) {
Cu.reportError("Archive filter failed: " + aResult);
// We don't want to effectively disable archiving because a filter
// failed, so we'll continue after reporting the error.
}
// Now do the default archive processing
this.continueBatch();
},
// continue processing of default archive operations
continueBatch() {
let batch = this._currentBatch;
let [srcFolder, archiveFolderUri, granularity, keepFolderStructure, msgYear, msgMonth] = batch;
let msgs = batch.slice(6);
let moveArray = [];
// Don't move any items that the filter moves or deleted
for (let item of msgs) {
if (srcFolder.msgDatabase.ContainsKey(item.messageKey) &&
!(srcFolder.getProcessingFlags(item.messageKey) &
Ci.nsMsgProcessingFlags.FilterToMove)) {
moveArray.push(item);
}
}
if (moveArray.length == 0)
return this.processNextBatch(); // continue processing
let archiveFolder = MailUtils.getFolderForURI(archiveFolderUri, false);
let dstFolder = archiveFolder;
// For folders on some servers (e.g. IMAP), we need to create the
// sub-folders asynchronously, so we chain the urls using the listener
// called back from createStorageIfMissing. For local,
// createStorageIfMissing is synchronous.
let isAsync = archiveFolder.server.protocolInfo.foldersCreatedAsync;
if (!archiveFolder.parent) {
archiveFolder.setFlag(Ci.nsMsgFolderFlags.Archive);
archiveFolder.createStorageIfMissing(this);
if (isAsync)
return; // continues with OnStopRunningUrl
}
if (!archiveFolder.canCreateSubfolders)
granularity = Ci.nsIMsgIdentity.singleArchiveFolder;
if (granularity >= Ci.nsIMsgIdentity.perYearArchiveFolders) {
archiveFolderUri += "/" + msgYear;
dstFolder = MailUtils.getFolderForURI(archiveFolderUri, false);
if (!dstFolder.parent) {
dstFolder.createStorageIfMissing(this);
if (isAsync)
return; // continues with OnStopRunningUrl
}
}
if (granularity >= Ci.nsIMsgIdentity.perMonthArchiveFolders) {
archiveFolderUri += "/" + msgMonth;
dstFolder = MailUtils.getFolderForURI(archiveFolderUri, false);
if (!dstFolder.parent) {
dstFolder.createStorageIfMissing(this);
if (isAsync)
return; // continues with OnStopRunningUrl
}
}
// Create the folder structure in Archives.
// For imap folders, we need to create the sub-folders asynchronously,
// so we chain the actions using the listener called back from
// createSubfolder. For local, createSubfolder is synchronous.
if (archiveFolder.canCreateSubfolders && keepFolderStructure) {
// Collect in-order list of folders of source folder structure,
// excluding top-level INBOX folder
let folderNames = [];
let rootFolder = srcFolder.server.rootFolder;
let inboxFolder = GetInboxFolder(srcFolder.server);
let folder = srcFolder;
while (folder != rootFolder && folder != inboxFolder) {
folderNames.unshift(folder.name);
folder = folder.parent;
}
// Determine Archive folder structure.
for (let i = 0; i < folderNames.length; ++i) {
let folderName = folderNames[i];
if (!dstFolder.containsChildNamed(folderName)) {
// Create Archive sub-folder (IMAP: async).
if (isAsync) {
this._dstFolderParent = dstFolder;
this._dstFolderName = folderName;
}
dstFolder.createSubfolder(folderName, msgWindow);
if (isAsync)
return; // continues with folderAdded
}
dstFolder = dstFolder.getChildNamed(folderName);
}
}
if (dstFolder != srcFolder) {
// Make sure the target folder is visible in the folder tree.
EnsureFolderIndex(gFolderTreeView, dstFolder);
let isNews = srcFolder.flags & Ci.nsMsgFolderFlags.Newsgroup;
// If the source folder doesn't support deleting messages, we
// make archive a copy, not a move.
MailServices.copy.copyMessages(srcFolder, moveArray, dstFolder,
srcFolder.canDeleteMessages && !isNews,
this, msgWindow, true);
return; // continues with OnStopCopy
}
return this.processNextBatch();
},
// This also implements nsIUrlListener, but we only care about the
// OnStopRunningUrl (createStorageIfMissing callback).
OnStartRunningUrl(aUrl) {
},
OnStopRunningUrl(aUrl, aExitCode) {
// This will always be a create folder url, afaik.
if (Components.isSuccessCode(aExitCode))
this.continueBatch();
else {
Cu.reportError("Archive failed to create folder: " + aExitCode);
this._batches = null;
this.processNextBatch(); // for cleanup and exit
}
},
// This also implements nsIMsgCopyServiceListener, but we only care
// about the OnStopCopy (copyMessages callback).
OnStartCopy() {
},
OnProgress(aProgress, aProgressMax) {
},
SetMessageKey(aKey) {
},
GetMessageId() {
},
OnStopCopy(aStatus) {
if (Components.isSuccessCode(aStatus)) {
return this.processNextBatch();
}
Cu.reportError("Archive failed to copy: " + aStatus);
this._batches = null;
this.processNextBatch(); // for cleanup and exit
},
// This also implements nsIMsgFolderListener, but we only care about the
// folderAdded (createSubfolder callback).
folderAdded(aFolder) {
// Check that this is the folder we're interested in.
if (aFolder.parent == this._dstFolderParent &&
aFolder.name == this._dstFolderName) {
this._dstFolderParent = null;
this._dstFolderName = null;
this.continueBatch();
}
},
QueryInterface(aIID) {
if (aIID.equals(Ci.nsIUrlListener) ||
aIID.equals(Ci.nsIMsgCopyServiceListener) ||
aIID.equals(Ci.nsIMsgFolderListener) ||
aIID.equals(Ci.nsIMsgOperationListener) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
function MsgArchiveSelectedMessages(aEvent) {
let batchMover = new BatchMessageMover();
batchMover.archiveMessages(gFolderDisplay.selectedMessages);
}
function MsgForwardMessage(event) {
var forwardType = Services.prefs.getIntPref("mail.forward_message_mode");
// mail.forward_message_mode could be 1, if the user migrated from 4.x
// 1 (forward as quoted) is obsolete, so we treat is as forward inline
// since that is more like forward as quoted then forward as attachment
if (forwardType == kMsgForwardAsAttachment)
MsgForwardAsAttachment(event);
else
MsgForwardAsInline(event);
}
function MsgForwardAsAttachment(event) {
ComposeMsgByType(msgComposeType.ForwardAsAttachment, event);
}
function MsgForwardAsInline(event) {
ComposeMsgByType(msgComposeType.ForwardInline, event);
}
function MsgEditMessageAsNew(aEvent) {
ComposeMsgByType(msgComposeType.EditAsNew, aEvent);
}
function MsgEditDraftMessage(aEvent) {
ComposeMsgByType(msgComposeType.Draft, aEvent);
}
function MsgNewMessageFromTemplate(aEvent) {
ComposeMsgByType(msgComposeType.Template, aEvent);
}
function MsgEditTemplateMessage(aEvent) {
ComposeMsgByType(msgComposeType.EditTemplate, aEvent);
}
function MsgComposeDraftMessage() {
ComposeMsgByType(msgComposeType.Draft, null, msgComposeFormat.Default);
}
function MsgCreateFilter() {
// retrieve Sender direct from selected message's headers
var msgHdr = gFolderDisplay.selectedMessage;
var emailAddress =
MailServices.headerParser.extractHeaderAddressMailboxes(msgHdr.author);
var accountKey = msgHdr.accountKey;
var folder;
if (accountKey.length > 0) {
var account = accountManager.getAccount(accountKey);
if (account) {
server = account.incomingServer;
if (server)
folder = server.rootFolder;
}
}
if (!folder)
folder = GetFirstSelectedMsgFolder();
if (emailAddress)
top.MsgFilters(emailAddress, folder);
}
function MsgSubscribe(folder) {
var preselectedFolder = folder || GetFirstSelectedMsgFolder();
if (preselectedFolder && preselectedFolder.server.type == "rss")
openSubscriptionsDialog(preselectedFolder); // open feed subscription dialog
else
Subscribe(preselectedFolder); // open imap/nntp subscription dialog
}
/**
* Show a confirmation dialog - check if the user really want to unsubscribe
* from the given newsgroup/s.
* @folders an array of newsgroup folders to unsubscribe from
* @return true if the user said it's ok to unsubscribe
*/
function ConfirmUnsubscribe(folders) {
if (!gMessengerBundle)
gMessengerBundle = document.getElementById("bundle_messenger");
let titleMsg = gMessengerBundle.getString("confirmUnsubscribeTitle");
let dialogMsg = (folders.length == 1) ?
gMessengerBundle.getFormattedString("confirmUnsubscribeText",
[folders[0].name], 1) :
gMessengerBundle.getString("confirmUnsubscribeManyText");
return Services.prompt.confirm(window, titleMsg, dialogMsg);
}
/**
* Unsubscribe from selected or passed in newsgroup/s.
* @param newsgroups (optional param) the newsgroup folders to unsubscribe from
*/
function MsgUnsubscribe(newsgroups) {
let folders = newsgroups || GetSelectedMsgFolders();
if (!ConfirmUnsubscribe(folders))
return;
for (let folder of folders) {
let subscribableServer =
folder.server.QueryInterface(Ci.nsISubscribableServer);
subscribableServer.unsubscribe(folder.name);
subscribableServer.commitSubscribeChanges();
}
}
function ToggleFavoriteFolderFlag() {
var folder = GetFirstSelectedMsgFolder();
folder.toggleFlag(Ci.nsMsgFolderFlags.Favorite);
}
function MsgSaveAsFile() {
SaveAsFile(gFolderDisplay.selectedMessageUris);
}
function MsgSaveAsTemplate() {
SaveAsTemplate(gFolderDisplay.selectedMessageUris);
}
function MsgOpenFromFile() {
var fp = Cc["@mozilla.org/filepicker;1"]
.createInstance(Ci.nsIFilePicker);
var filterLabel = gMessengerBundle.getString("EMLFiles");
var windowTitle = gMessengerBundle.getString("OpenEMLFiles");
fp.init(window, windowTitle, Ci.nsIFilePicker.modeOpen);
fp.appendFilter(filterLabel, "*.eml; *.msg");
// Default or last filter is "All Files".
fp.appendFilters(Ci.nsIFilePicker.filterAll);
fp.open(rv => {
if (rv != Ci.nsIFilePicker.returnOK || !fp.file) {
return;
}
let uri = fp.fileURL.QueryInterface(Ci.nsIURL);
uri.query = "type=application/x-message-display";
window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank",
"all,chrome,dialog=no,status,toolbar", uri);
});
}
function MsgOpenNewWindowForFolder(folderURI, msgKeyToSelect) {
let mailWindowService = Cc["@mozilla.org/messenger/windowservice;1"]
.getService(Ci.nsIMessengerWindowService);
if (!mailWindowService)
return;
if (folderURI) {
mailWindowService.openMessengerWindowWithUri("mail:3pane", folderURI,
msgKeyToSelect);
return;
}
// If there is a right-click happening, GetSelectedMsgFolders()
// will tell us about it (while the selection's currentIndex would reflect
// the node that was selected/displayed before the right-click.)
for (let folder of GetSelectedMsgFolders()) {
mailWindowService.openMessengerWindowWithUri("mail:3pane", folder.URI,
msgKeyToSelect);
}
}
function MsgOpenSelectedMessages() {
// Toggle message body (feed summary) and content-base url in message pane or
// load in browser, per pref, otherwise open summary or web page in new window
// or tab, per that pref.
if (gFolderDisplay.selectedMessageIsFeed) {
let msgHdr = gFolderDisplay.selectedMessage;
if (document.documentElement.getAttribute("windowtype") == "mail:3pane" &&
FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenToggleInMessagePane) {
let showSummary = FeedMessageHandler.shouldShowSummary(msgHdr, true);
FeedMessageHandler.setContent(msgHdr, showSummary);
FeedMessageHandler.onSelectPref =
showSummary ? FeedMessageHandler.kSelectOverrideSummary :
FeedMessageHandler.kSelectOverrideWebPage;
return;
}
if (FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenLoadInBrowser) {
setTimeout(FeedMessageHandler.loadWebPage, 20, msgHdr, {browser: true});
return;
}
}
var dbView = GetDBView();
var indices = GetSelectedIndices(dbView);
var numMessages = indices.length;
// This is a radio type button pref, currently with only 2 buttons.
// We need to keep the pref type as 'bool' for backwards compatibility
// with 4.x migrated prefs. For future radio button(s), please use another
// pref (either 'bool' or 'int' type) to describe it.
//
// mailnews.reuse_message_window values:
// false: open new standalone message window for each message
// true : reuse existing standalone message window for each message
if (Services.prefs.getBoolPref("mailnews.reuse_message_window") &&
numMessages == 1 &&
MsgOpenSelectedMessageInExistingWindow())
return;
var openWindowWarning = Services.prefs.getIntPref("mailnews.open_window_warning");
if ((openWindowWarning > 1) && (numMessages >= openWindowWarning)) {
InitPrompts();
if (!gMessengerBundle)
gMessengerBundle = document.getElementById("bundle_messenger");
var title = gMessengerBundle.getString("openWindowWarningTitle");
var text = PluralForm.get(numMessages,
gMessengerBundle.getString("openWindowWarningConfirmation"))
.replace("#1", numMessages);
if (!Services.prompt.confirm(window, title, text))
return;
}
for (var i = 0; i < numMessages; i++) {
MsgOpenNewWindowForMessage(dbView.getURIForViewIndex(indices[i]), dbView.getFolderForViewIndex(indices[i]).URI);
}
}
function MsgOpenSelectedMessageInExistingWindow() {
var windowID = Services.wm.getMostRecentWindow("mail:messageWindow");
if (!windowID)
return false;
try {
var messageURI = gDBView.URIForFirstSelectedMessage;
var msgHdr = gDBView.hdrForFirstSelectedMessage;
// Reset the window's message uri and folder uri vars, and
// update the command handlers to what's going to be used.
// This has to be done before the call to CreateView().
windowID.gCurrentMessageUri = messageURI;
windowID.gCurrentFolderUri = msgHdr.folder.URI;
windowID.UpdateMailToolbar("MsgOpenExistingWindowForMessage");
// even if the folder uri's match, we can't use the existing view
// (msgHdr.folder.URI == windowID.gCurrentFolderUri)
// the reason is quick search and mail views.
// see bug #187673
//
// for the sake of simplicity,
// let's always call CreateView(gDBView)
// which will clone gDBView
windowID.CreateView(gDBView);
windowID.OnLoadMessageWindowDelayed(false);
// bring existing window to front
windowID.focus();
return true;
} catch (ex) {
dump("reusing existing standalone message window failed: " + ex + "\n");
}
return false;
}
function MsgOpenSearch(aSearchStr, aEvent) {
// If you change /suite/navigator/navigator.js->BrowserSearch::loadSearch()
// make sure you make corresponding changes here.
var submission = Services.search.defaultEngine.getSubmission(aSearchStr);
if (!submission)
return;
var newTabPref = Services.prefs.getBoolPref("browser.search.opentabforcontextsearch");
var where = newTabPref ? aEvent && aEvent.shiftKey ? "tabshifted" : "tab" : "window";
openUILinkIn(submission.uri.spec, where, null, submission.postData);
}
function MsgOpenNewWindowForMessage(messageUri, folderUri) {
if (!messageUri)
messageUri = gFolderDisplay.selectedMessageUri;
if (!folderUri)
// Use GetSelectedMsgFolders() to find out which message to open
// instead of gDBView.getURIForViewIndex(currentIndex). This is
// required because on a right-click, the currentIndex value will be
// different from the actual row that is highlighted.
// GetSelectedMsgFolders() will return the message that is
// highlighted.
folderUri = GetSelectedMsgFolders()[0].URI;
// be sure to pass in the current view....
if (messageUri && folderUri) {
window.openDialog( "chrome://messenger/content/messageWindow.xul", "_blank", "all,chrome,dialog=no,status,toolbar", messageUri, folderUri, gDBView );
}
}
function CloseMailWindow() {
window.close();
}
function MsgJunk() {
MsgJunkMailInfo(true);
JunkSelectedMessages(!SelectedMessagesAreJunk());
}
/**
* Checks if the selected messages can be marked as read or unread
*
* @param read true if trying to mark messages as read, false otherwise
* @return true if the chosen operation can be performed
*/
function CanMarkMsgAsRead(read) {
return SelectedMessagesAreRead() != read;
}
/**
* Marks the selected messages as read or unread
*
* @param read true if trying to mark messages as read, false if marking unread,
* undefined if toggling the read status
*/
function MsgMarkMsgAsRead(read) {
if (read == undefined)
read = !SelectedMessagesAreRead();
MarkSelectedMessagesRead(read);
}
function MsgMarkAsFlagged() {
MarkSelectedMessagesFlagged(!SelectedMessagesAreFlagged());
}
function MsgMarkReadByDate() {
"chrome,modal,titlebar,centerscreen",
GetLoadedMsgFolder());
}
function MsgMarkAllRead() {
let folders = GetSelectedMsgFolders();
for (let folder of folders)
folder.markAllMessagesRead(msgWindow);
}
function MsgDownloadFlagged() {
gDBView.doCommand(nsMsgViewCommandType.downloadFlaggedForOffline);
}
function MsgDownloadSelected() {
gDBView.doCommand(nsMsgViewCommandType.downloadSelectedForOffline);
}
function MsgMarkThreadAsRead() {
ClearPendingReadTimer();
gDBView.doCommand(nsMsgViewCommandType.markThreadRead);
}
function MsgViewPageSource() {
ViewPageSource(gFolderDisplay.selectedMessageUris);
}
var gFindInstData;
function getFindInstData() {
if (!gFindInstData) {
gFindInstData = new nsFindInstData();
gFindInstData.browser = getMessageBrowser();
gFindInstData.rootSearchWindow = window.top.content;
gFindInstData.currentSearchWindow = window.top.content;
}
return gFindInstData;
}
function MsgFind() {
findInPage(getFindInstData());
}
function MsgFindAgain(reverse) {
findAgainInPage(getFindInstData(), reverse);
}
function MsgCanFindAgain() {
return canFindAgainInPage();
}
/**
* Go through each selected server and mark all its folders read.
*/
function MsgMarkAllFoldersRead() {
if (!Services.prompt.confirm(window,
gMessengerBundle.getString("confirmMarkAllFoldersReadTitle"),
gMessengerBundle.getString("confirmMarkAllFoldersReadMessage"))) {
return;
}
const selectedFolders = GetSelectedMsgFolders();
if (selectedFolders) {
const selectedServers = selectedFolders.filter(folder => folder.isServer);
selectedServers.forEach(function(server) {
for (let folder of server.rootFolder.descendants) {
folder.markAllMessagesRead(msgWindow);
}
});
}
}
function MsgFilters(emailAddress, folder) {
if (!folder)
folder = GetFirstSelectedMsgFolder();
var args;
if (emailAddress) {
// Prefill the filterEditor with the emailAddress.
args = {filterList: folder.getEditableFilterList(msgWindow), filterName: emailAddress};
"chrome, modal, resizable,centerscreen,dialog", args);
// If the user hits ok in the filterEditor dialog we set args.refresh=true
// there and we check this here in args to show filterList dialog.
// We also received the filter created via args.newFilter.
if ("refresh" in args && args.refresh) {
args = { refresh: true, folder, filter: args.newFilter };
MsgFilterList(args);
}
} else // just launch filterList dialog
{
args = { refresh: false, folder };
MsgFilterList(args);
}
}
function MsgApplyFilters() {
var preselectedFolder = GetFirstSelectedMsgFolder();
var curFilterList = preselectedFolder.getFilterList(msgWindow);
// create a new filter list and copy over the enabled filters to it.
// We do this instead of having the filter after the fact code ignore
// disabled filters because the Filter Dialog filter after the fact
// code would have to clone filters to allow disabled filters to run,
// and we don't support cloning filters currently.
var tempFilterList =
MailServices.filters.getTempFilterList(preselectedFolder);
var numFilters = curFilterList.filterCount;
// make sure the temp filter list uses the same log stream
tempFilterList.loggingEnabled = curFilterList.loggingEnabled;
tempFilterList.logStream = curFilterList.logStream;
var newFilterIndex = 0;
for (var i = 0; i < numFilters; i++) {
var curFilter = curFilterList.getFilterAt(i);
// only add enabled, UI visibile filters that are in the manual context
if (curFilter.enabled && !curFilter.temporary &&
(curFilter.filterType & Ci.nsMsgFilterType.Manual)) {
tempFilterList.insertFilterAt(newFilterIndex, curFilter);
newFilterIndex++;
}
}
MailServices.filters.applyFiltersToFolders(tempFilterList,
[preselectedFolder],
msgWindow);
}
function MsgApplyFiltersToSelection() {
var folder = gDBView.msgFolder;
var indices = GetSelectedIndices(gDBView);
if (indices && indices.length) {
var selectedMsgs = [];
for (var i = 0; i < indices.length; i++) {
try {
// Getting the URI will tell us if the item is real or a dummy header
var uri = gDBView.getURIForViewIndex(indices[i]);
if (uri) {
var msgHdr = folder.GetMessageHeader(gDBView.getKeyAt(indices[i]));
if (msgHdr)
selectedMsgs.push(msgHdr);
}
} catch (ex) {}
}
MailServices.filters.applyFilters(Ci.nsMsgFilterType.Manual, selectedMsgs,
folder, msgWindow);
}
}
function ChangeMailLayout(newLayout) {
Services.prefs.setIntPref("mail.pane_config.dynamic", newLayout);
}
function MsgViewAllHeaders() {
Services.prefs.setIntPref("mail.show_headers",
Ci.nsMimeHeaderDisplayTypes.AllHeaders);
}
function MsgViewNormalHeaders() {
Services.prefs.setIntPref("mail.show_headers",
Ci.nsMimeHeaderDisplayTypes.NormalHeaders);
}
function MsgBodyAllowHTML() {
ChangeMsgBodyDisplay(false, 0, 0);
}
function MsgBodySanitized() {
ChangeMsgBodyDisplay(false, 3, gDisallow_classes_no_html);
}
function MsgBodyAsPlaintext() {
ChangeMsgBodyDisplay(true, 1, gDisallow_classes_no_html);
}
function MsgBodyAllParts() {
ChangeMsgBodyDisplay(false, 4, 0);
}
function ChangeMsgBodyDisplay(plaintext, html, mime) {
Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", plaintext);
Services.prefs.setIntPref("mailnews.display.disallow_mime_handlers", mime);
Services.prefs.setIntPref("mailnews.display.html_as", html);
}
function MsgFeedBodyRenderPrefs(plaintext, html, mime) {
// Separate render prefs not implemented for feeds, bug 458606.
// Services.prefs.setBoolPref("rss.display.prefer_plaintext", plaintext);
// Services.prefs.setIntPref("rss.display.disallow_mime_handlers", mime);
// Services.prefs.setIntPref("rss.display.html_as", html)
Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", plaintext);
Services.prefs.setIntPref("mailnews.display.disallow_mime_handlers", mime);
Services.prefs.setIntPref("mailnews.display.html_as", html);
}
function ToggleInlineAttachment(target) {
var viewInline = !Services.prefs.getBoolPref("mail.inline_attachments");
Services.prefs.setBoolPref("mail.inline_attachments", viewInline);
target.setAttribute("checked", viewInline ? "true" : "false");
}
function MsgStop() {
StopUrls();
}
function MsgSendUnsentMsgs() {
// if offline, prompt for sendUnsentMessages
if (!Services.io.offline) {
SendUnsentMessages();
} else {
var option = PromptMessagesOffline("send");
if (option == 0) {
if (!gOfflineManager)
GetOfflineMgrService();
gOfflineManager.goOnline(false /* sendUnsentMessages */,
false /* playbackOfflineImapOperations */,
msgWindow);
SendUnsentMessages();
}
}
}
function PrintEnginePrintInternal(aDoPrintPreview, aMsgType) {
var messageList = gFolderDisplay.selectedMessageUris;
if (!messageList) {
dump("PrintEnginePrint(): No messages selected.\n");
return false;
}
"chrome,dialog=no,all,centerscreen",
messageList.length, messageList, statusFeedback,
aDoPrintPreview, aMsgType);
return true;
}
function PrintEnginePrint() {
return PrintEnginePrintInternal(false, Ci.nsIMsgPrintEngine.MNAB_PRINT_MSG);
}
function PrintEnginePrintPreview() {
return PrintEnginePrintInternal(true, Ci.nsIMsgPrintEngine.MNAB_PRINTPREVIEW_MSG);
}
// Kept for add-on compatibility.
function SelectFolder(folderUri) {
SelectMsgFolder(MailUtils.getFolderForURI(folderUri));
}
function IsMailFolderSelected() {
var selectedFolders = GetSelectedMsgFolders();
var folder = selectedFolders.length ? selectedFolders[0] : null;
return folder && folder.server.type != "nntp";
}
function IsGetNewMessagesEnabled() {
// users don't like it when the "Get Msgs" button is disabled
// so let's never do that.
// we'll just handle it as best we can in GetFolderMessages()
// when they click "Get Msgs" and
// Local Folders or a news server is selected
// see bugs #89404 and #111102
return true;
}
function IsGetNextNMessagesEnabled() {
var selectedFolders = GetSelectedMsgFolders();
var folder = selectedFolders.length ? selectedFolders[0] : null;
var menuItem = document.getElementById("menu_getnextnmsg");
if (folder && !folder.isServer &&
folder.server instanceof Ci.nsINntpIncomingServer) {
var menuLabel = PluralForm.get(folder.server.maxArticles,
gMessengerBundle.getString("getNextNewsMessages"))
.replace("#1", folder.server.maxArticles);
menuItem.setAttribute("label", menuLabel);
menuItem.removeAttribute("hidden");
return true;
}
menuItem.setAttribute("hidden", "true");
return false;
}
function SetUpToolbarButtons(uri) {
let deleteButton = document.getElementById("button-delete");
let replyAllButton = document.getElementById("button-replyall");
// Eventually, we might want to set up the toolbar differently for imap,
// pop, and news. For now, just tweak it based on if it is news or not.
let forNews = isNewsURI(uri);
deleteButton.hidden = forNews;
if (forNews) {
replyAllButton.setAttribute("type", "menu-button");
replyAllButton.setAttribute("tooltiptext",
replyAllButton.getAttribute("tooltiptextnews"));
} else {
replyAllButton.removeAttribute("type");
replyAllButton.setAttribute("tooltiptext",
replyAllButton.getAttribute("tooltiptextmail"));
}
}
function getMessageBrowser() {
return document.getElementById("messagepane");
}
// The zoom manager, view source and possibly some other functions still rely
// on the getBrowser function.
function getBrowser() {
return GetTabMail() ? GetTabMail().getBrowserForSelectedTab() :
getMessageBrowser();
}
function MsgSynchronizeOffline() {
"centerscreen,chrome,modal,titlebar,resizable",
{msgWindow});
}
function MsgOpenAttachment() {}
function MsgUpdateMsgCount() {}
function MsgImport() {}
function MsgSynchronize() {}
function MsgGetSelectedMsg() {}
function MsgGetFlaggedMsg() {}
function MsgSelectThread() {}
function MsgShowFolders() {}
function MsgShowLocationbar() {}
function MsgViewAttachInline() {}
function MsgWrapLongLines() {}
function MsgIncreaseFont() {}
function MsgDecreaseFont() {}
function MsgShowImages() {}
function MsgRefresh() {}
function MsgViewPageInfo() {}
function MsgFirstUnreadMessage() {}
function MsgFirstFlaggedMessage() {}
function MsgAddSenderToAddressBook() {}
function MsgAddAllToAddressBook() {}
function SpaceHit(event) {
var contentWindow = document.commandDispatcher.focusedWindow;
if (contentWindow.top == window)
contentWindow = content;
else if (document.commandDispatcher.focusedElement &&
!hrefAndLinkNodeForClickEvent(event))
return;
var rssiframe = content.document.getElementById("_mailrssiframe");
// If we are displaying an RSS article, we really want to scroll
// the nested iframe.
if (contentWindow == content && rssiframe)
contentWindow = rssiframe.contentWindow;
if (event && event.shiftKey) {
// if at the start of the message, go to the previous one
if (contentWindow.scrollY > 0)
contentWindow.scrollByPages(-1);
else if (Services.prefs.getBoolPref("mail.advance_on_spacebar"))
goDoCommand("cmd_previousUnreadMsg");
} else {
// if at the end of the message, go to the next one
if (contentWindow.scrollY < contentWindow.scrollMaxY)
contentWindow.scrollByPages(1);
else if (Services.prefs.getBoolPref("mail.advance_on_spacebar"))
goDoCommand("cmd_nextUnreadMsg");
}
}
function IsAccountOfflineEnabled() {
var selectedFolders = GetSelectedMsgFolders();
if (selectedFolders && (selectedFolders.length == 1))
return selectedFolders[0].supportsOffline;
return false;
}
function DoGetNewMailWhenOffline() {
if (!Services.io.offline)
return true;
if (PromptMessagesOffline("get") == 0) {
var sendUnsent = false;
if (this.CheckForUnsentMessages != undefined && CheckForUnsentMessages()) {
sendUnsent =
Services.prefs.getIntPref("offline.send.unsent_messages") == 1 ||
Services.prompt.confirmEx(
window,
gOfflinePromptsBundle.getString("sendMessagesOfflineWindowTitle"),
gOfflinePromptsBundle.getString("sendMessagesLabel2"),
Services.prompt.BUTTON_TITLE_IS_STRING *
(Services.prompt.BUTTON_POS_0 + Services.prompt.BUTTON_POS_1),
gOfflinePromptsBundle.getString("sendMessagesSendButtonLabel"),
gOfflinePromptsBundle.getString("sendMessagesNoSendButtonLabel"),
null, null, {value: false}) == 0;
}
if (!gOfflineManager)
GetOfflineMgrService();
gOfflineManager.goOnline(sendUnsent /* sendUnsentMessages */,
false /* playbackOfflineImapOperations */,
msgWindow);
return true;
}
return false;
}
// prompt for getting/sending messages when offline
function PromptMessagesOffline(aPrefix) {
InitPrompts();
var checkValue = {value: false};
return Services.prompt.confirmEx(
window,
gOfflinePromptsBundle.getString(aPrefix + "MessagesOfflineWindowTitle"),
gOfflinePromptsBundle.getString(aPrefix + "MessagesOfflineLabel"),
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
(Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1),
gOfflinePromptsBundle.getString(aPrefix + "MessagesOfflineGoButtonLabel"),
null, null, null, checkValue);
}
function GetDefaultAccountRootFolder() {
var account = accountManager.defaultAccount;
if (account) {
return account.incomingServer.rootMsgFolder;
}
return null;
}
/**
* Check for new messages for all selected folders, or for the default account
* in case no folders are selected.
*/
function GetFolderMessages() {
var selectedFolders = GetSelectedMsgFolders();
var defaultAccountRootFolder = GetDefaultAccountRootFolder();
var folders = (selectedFolders.length) ? selectedFolders
: [defaultAccountRootFolder];
if (!folders[0]) {
return;
}
for (let folder of folders) {
var serverType = folder.server.type;
if (folder.isServer && (serverType == "nntp")) {
// If we're doing "get msgs" on a news server,
// update unread counts on this server.
folder.server.performExpand(msgWindow);
} else if (serverType == "none") {
// If "Local Folders" is selected and the user does "Get Msgs" and
// LocalFolders is not deferred to, get new mail for the default account
//
// XXX TODO
// Should shift click get mail for all (authenticated) accounts?
// see bug #125885.
if (!folder.server.isDeferredTo) {
if (!defaultAccountRootFolder) {
continue;
}
GetNewMsgs(defaultAccountRootFolder.server, defaultAccountRootFolder);
} else {
GetNewMsgs(folder.server, folder);
}
} else {
GetNewMsgs(folder.server, folder);
}
}
}
/**
* Gets new messages for the given server, for the given folder.
* @param server which nsIMsgIncomingServer to check for new messages
* @param folder which nsIMsgFolder folder to check for new messages
*/
function GetNewMsgs(server, folder) {
// Note that for Global Inbox folder.server != server when we want to get
// messages for a specific account.
// Whenever we do get new messages, clear the old new messages.
folder.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NoMail;
folder.clearNewMessages();
server.getNewMessages(folder, msgWindow, null);
}
function SendUnsentMessages() {
let msgSendlater = Cc["@mozilla.org/messengercompose/sendlater;1"]
.getService(Ci.nsIMsgSendLater);
let allIdentities = MailServices.accounts.allIdentities;
for (let currentIdentity of allIdentities) {
let msgFolder = msgSendlater.getUnsentMessagesFolder(currentIdentity);
if (msgFolder) {
let numMessages = msgFolder.getTotalMessages(false /* include subfolders */);
if (numMessages > 0) {
msgSendlater.statusFeedback = statusFeedback;
msgSendlater.sendUnsentMessages(currentIdentity);
// Right now, all identities point to the same unsent messages
// folder, so to avoid sending multiple copies of the
// unsent messages, we only call messenger.SendUnsentMessages() once
// see bug #89150 for details
break;
}
}
}
}
function CommandUpdate_UndoRedo() {
EnableMenuItem("menu_undo", SetupUndoRedoCommand("cmd_undo"));
EnableMenuItem("menu_redo", SetupUndoRedoCommand("cmd_redo"));
}
function SetupUndoRedoCommand(command) {
// If we have selected a server, and are viewing account central
// there is no loaded folder.
var loadedFolder = GetLoadedMsgFolder();
if (!loadedFolder || !loadedFolder.server.canUndoDeleteOnServer)
return false;
var canUndoOrRedo = false;
var txnType = 0;
if (command == "cmd_undo") {
canUndoOrRedo = messenger.canUndo();
txnType = messenger.getUndoTransactionType();
} else {
canUndoOrRedo = messenger.canRedo();
txnType = messenger.getRedoTransactionType();
}
if (canUndoOrRedo) {
switch (txnType) {
default:
case Ci.nsIMessenger.eUnknown:
goSetMenuValue(command, "valueDefault");
break;
case Ci.nsIMessenger.eDeleteMsg:
goSetMenuValue(command, "valueDeleteMsg");
break;
case Ci.nsIMessenger.eMoveMsg:
goSetMenuValue(command, "valueMoveMsg");
break;
case Ci.nsIMessenger.eCopyMsg:
goSetMenuValue(command, "valueCopyMsg");
break;
case Ci.nsIMessenger.eMarkAllMsg:
goSetMenuValue(command, "valueUnmarkAllMsgs");
break;
}
} else {
goSetMenuValue(command, "valueDefault");
}
return canUndoOrRedo;
}
function HandleJunkStatusChanged(folder) {
// This might be the stand alone window, open to a message that was
// and attachment (or on disk), in which case, we want to ignore it.
var loadedMessage = GetLoadedMessage();
if (!loadedMessage ||
/type=application\/x-message-display/.test(loadedMessage) ||
!IsCurrentLoadedFolder(folder))
return;
// If multiple message are selected and we change the junk status
// we don't want to show the junk bar (since the message pane is blank).
var msgHdr = null;
if (GetNumSelectedMessages() == 1)
msgHdr = messenger.msgHdrFromURI(loadedMessage);
var junkBarWasDisplayed = gMessageNotificationBar.isShowingJunkNotification();
gMessageNotificationBar.setJunkMsg(msgHdr);
// Only reload message if junk bar display state has changed.
if (msgHdr && junkBarWasDisplayed != gMessageNotificationBar.isShowingJunkNotification()) {
// We may be forcing junk mail to be rendered with sanitized html.
// In that scenario, we want to reload the message if the status has just
// changed to not junk.
var sanitizeJunkMail = Services.prefs.getBoolPref("mail.spam.display.sanitize");
// Only bother doing this if we are modifying the html for junk mail...
if (sanitizeJunkMail) {
let junkScore = msgHdr.getStringProperty("junkscore");
let isJunk = (junkScore == Ci.nsIJunkMailPlugin.IS_SPAM_SCORE);
// If the current row isn't going to change, reload to show sanitized or
// unsanitized. Otherwise we wouldn't see the reloaded version anyway.
// XXX: need to special handle last message in view, for imap mark as deleted
// 1) When marking as non-junk, the msg would move back to the inbox.
// 2) When marking as junk, the msg will move or delete, if manualMark is set.
// 3) Marking as junk in the junk folder just changes the junk status.
if ((!isJunk && folder.isSpecialFolder(Ci.nsMsgFolderFlags.Inbox)) ||
(isJunk && !folder.server.spamSettings.manualMark) ||
(isJunk && folder.isSpecialFolder(Ci.nsMsgFolderFlags.Junk)))
ReloadMessage();
}
}
}
var gMessageNotificationBar =
{
get mStringBundle() {
delete this.mStringBundle;
return this.mStringBundle = document.getElementById("bundle_messenger");
},
get mBrandBundle() {
delete this.mBrandBundle;
return this.mBrandBundle = document.getElementById("bundle_brand");
},
get mMsgNotificationBar() {
delete this.mMsgNotificationBar;
return this.mMsgNotificationBar = document.getElementById("messagepanebox");
},
setJunkMsg(aMsgHdr) {
let isJunk = false;
if (aMsgHdr) {
let junkScore = aMsgHdr.getStringProperty("junkscore");
isJunk = ((junkScore != "") && (junkScore != "0"));
}
goUpdateCommand("button_junk");
if (isJunk) {
if (!this.isShowingJunkNotification()) {
let brandName = this.mBrandBundle.getString("brandShortName");
let junkBarMsg = this.mStringBundle.getFormattedString("junkBarMessage",
[brandName]);
let buttons = [{
label: this.mStringBundle.getString("junkBarInfoButton"),
accessKey: this.mStringBundle.getString("junkBarInfoButtonKey"),
popup: null,
callback() {
MsgJunkMailInfo(false);
return true;
}
},
{
label: this.mStringBundle.getString("junkBarButton"),
accessKey: this.mStringBundle.getString("junkBarButtonKey"),
popup: null,
callback() {
JunkSelectedMessages(false);
return true;
}
}];
this.mMsgNotificationBar.appendNotification(junkBarMsg, "junkContent",
null, this.mMsgNotificationBar.PRIORITY_WARNING_HIGH, buttons);
this.mMsgNotificationBar.collapsed = false;
}
}
},
remoteOrigins: null,
isShowingJunkNotification() {
return !!this.mMsgNotificationBar.getNotificationWithValue("junkContent");
},
setRemoteContentMsg(aMsgHdr, aContentURI, aCanOverride) {
// remoteOrigins is a Set of all blockable Origins.
if (!this.remoteOrigins)
this.remoteOrigins = new Set();
var origin = aContentURI.spec;
try {
origin = aContentURI.scheme + "://" + aContentURI.hostPort;
}
// No hostport so likely a special url. Try to use the whole url and see
// what comes of it.
catch (e) { }
this.remoteOrigins.add(origin);
if (this.mMsgNotificationBar.getNotificationWithValue("remoteContent"))
return;
var headerParser = MailServices.headerParser;
// update the allow remote content for sender string
var mailbox = headerParser.extractHeaderAddressMailboxes(aMsgHdr.author);
var emailAddress = mailbox || aMsgHdr.author;
var displayName = headerParser.extractFirstName(aMsgHdr.mime2DecodedAuthor);
var brandName = this.mBrandBundle.getString("brandShortName");
var remoteContentMsg = this.mStringBundle
.getFormattedString("remoteContentBarMessage",
[brandName]);
var buttons = [{
label: this.mStringBundle.getString("remoteContentPrefLabel"),
accessKey: this.mStringBundle.getString("remoteContentPrefAccesskey"),
popup: "remoteContentOptions"
}];
this.mMsgNotificationBar
.appendNotification(remoteContentMsg,
"remoteContent",
null,
this.mMsgNotificationBar.PRIORITY_WARNING_MEDIUM,
(aCanOverride ? buttons : []));
},
// aUrl is the nsIURI for the message currently loaded in the message pane
setPhishingMsg(aUrl) {
// if we've explicitly marked this message as not being an email scam, then don't
// bother checking it with the phishing detector.
var phishingMsg = false;
if (!checkMsgHdrPropertyIsNot("notAPhishMessage", kIsAPhishMessage))
phishingMsg = isMsgEmailScam(aUrl);
var oldNotif = this.mMsgNotificationBar.getNotificationWithValue("phishingContent");
if (phishingMsg) {
if (!oldNotif) {
let brandName = this.mBrandBundle.getString("brandShortName");
let phishingMsgNote = this.mStringBundle.getFormattedString("phishingBarMessage",
[brandName]);
let buttons = [{
label: this.mStringBundle.getString("phishingBarIgnoreButton"),
accessKey: this.mStringBundle.getString("phishingBarIgnoreButtonKey"),
popup: null,
callback() {
MsgIsNotAScam();
}
}];
this.mMsgNotificationBar.appendNotification(phishingMsgNote, "phishingContent",
null, this.mMsgNotificationBar.PRIORITY_CRITICAL_MEDIUM, buttons);
}
}
},
setMDNMsg(aMdnGenerator, aMsgHeader, aMimeHdr) {
this.mdnGenerator = aMdnGenerator;
// Return receipts can be RFC 3798 "Disposition-Notification-To",
// or non-standard "Return-Receipt-To".
var mdnHdr = aMimeHdr.extractHeader("Disposition-Notification-To", false) ||
aMimeHdr.extractHeader("Return-Receipt-To", false); // not
var fromHdr = aMimeHdr.extractHeader("From", false);
var mdnAddr = MailServices.headerParser
.extractHeaderAddressMailboxes(mdnHdr);
var fromAddr = MailServices.headerParser
.extractHeaderAddressMailboxes(fromHdr);
var authorName = MailServices.headerParser
.extractFirstName(aMsgHeader.mime2DecodedAuthor)
|| aMsgHeader.author;
var barMsg;
// If the return receipt doesn't go to the sender address, note that in the
// notification.
if (mdnAddr != fromAddr)
barMsg = this.mStringBundle.getFormattedString("mdnBarMessageAddressDiffers",
[authorName, mdnAddr]);
else
barMsg = this.mStringBundle.getFormattedString("mdnBarMessageNormal", [authorName]);
var oldNotif = this.mMsgNotificationBar.getNotificationWithValue("mdnContent");
if (!oldNotif) {
let buttons = [{
label: this.mStringBundle.getString("mdnBarSendReqButton"),
accessKey: this.mStringBundle.getString("mdnBarSendReqButtonKey"),
popup: null,
callback: SendMDNResponse
},
{
label: this.mStringBundle.getString("mdnBarIgnoreButton"),
accessKey: this.mStringBundle.getString("mdnBarIgnoreButtonKey"),
popup: null,
callback: IgnoreMDNResponse
}];
this.mMsgNotificationBar.appendNotification(barMsg, "mdnContent",
null, this.mMsgNotificationBar.PRIORITY_INFO_MEDIUM, buttons);
}
},
clearMsgNotifications() {
}
};
/**
* LoadMsgWithRemoteContent
* Reload the current message, allowing remote content
*/
function LoadMsgWithRemoteContent() {
// we want to get the msg hdr for the currently selected message
// change the "remoteContentBar" property on it
// then reload the message
setMsgHdrPropertyAndReload("remoteContentPolicy", kAllowRemoteContent);
window.content.focus();
}
/**
* Populate the remote content options for the current message.
*/
function onRemoteContentOptionsShowing(aEvent) {
var origins = [...gMessageNotificationBar.remoteOrigins];
var addresses = {};
MailServices.headerParser.parseHeadersWithArray(
gMessageDisplay.displayedMessage.author, addresses, {}, {});
var authorEmailAddress = addresses.value[0];
var emailURI = Services.io.newURI(
"chrome://messenger/content/email=" + authorEmailAddress);
var principal = Services.scriptSecurityManager
.createCodebasePrincipal(emailURI, {});
// Put author email first in the menu.
origins.unshift(principal.origin);
// Out with the old...
let childNodes = aEvent.target.querySelectorAll(".allow-remote-uri");
for (let child of childNodes)
child.remove();
var messengerBundle = gMessageNotificationBar.mStringBundle;
var separator = document.getElementById("remoteContentSettingsMenuSeparator")
// ... and in with the new.
for (let origin of origins) {
let menuitem = document.createElement("menuitem");
let host = origin.replace("chrome://messenger/content/email=", "");
let hostString = messengerBundle.getFormattedString("remoteContentAllow", [host]);
menuitem.setAttribute("label", hostString);
menuitem.setAttribute("value", origin);
menuitem.setAttribute("class", "allow-remote-uri");
aEvent.target.insertBefore(menuitem, separator);
}
}
/**
* Add privileges to display remote content for the given uri.
* @param aItem |Node| Item that was selected. The origin
* is extracted and converted to a uri and used to add
* permissions for the site.
*/
function allowRemoteContentForURI(aItem) {
var origin = aItem.getAttribute("value");
if (!origin)
return;
let uri = Services.io.newURI(origin);
Services.perms.add(uri, "image", Services.perms.ALLOW_ACTION);
ReloadMessage();
}
/**
* Displays fine-grained, per-site permissions for remote content.
*/
function editRemoteContentSettings() {
toDataManager("|permissions");
if (!Services.prefs.getBoolPref("browser.preferences.instantApply"))
ReloadMessage();
}
/**
* msgHdrForCurrentMessage
* Returns the msg hdr associated with the current loaded message.
*/
function msgHdrForCurrentMessage() {
var msgURI = GetLoadedMessage();
return (msgURI && !(/type=application\/x-message-display/.test(msgURI))) ? messenger.msgHdrFromURI(msgURI) : null;
}
function MsgIsNotAScam() {
// we want to get the msg hdr for the currently selected message
// change the "isPhishingMsg" property on it
// then reload the message
setMsgHdrPropertyAndReload("notAPhishMessage", kNotAPhishMessage);
}
function setMsgHdrPropertyAndReload(aProperty, aValue) {
// we want to get the msg hdr for the currently selected message
// change the appropiate property on it then reload the message
var msgHdr = msgHdrForCurrentMessage();
if (msgHdr) {
msgHdr.setUint32Property(aProperty, aValue);
ReloadMessage();
}
}
function checkMsgHdrPropertyIsNot(aProperty, aValue) {
// we want to get the msg hdr for the currently selected message,
// get the appropiate property on it and then test against value.
var msgHdr = msgHdrForCurrentMessage();
return (msgHdr && msgHdr.getUint32Property(aProperty) != aValue);
}
/**
* Mark a specified message as read.
* @param msgHdr header (nsIMsgDBHdr) of the message to mark as read
*/
function MarkMessageAsRead(msgHdr) {
ClearPendingReadTimer();
msgHdr.folder.markMessagesRead([msgHdr], true);
}
function ClearPendingReadTimer() {
if (gMarkViewedMessageAsReadTimer) {
clearTimeout(gMarkViewedMessageAsReadTimer);
gMarkViewedMessageAsReadTimer = null;
}
}
function OnMsgParsed(aUrl) {
gMessageNotificationBar.setPhishingMsg(aUrl);
// notify anyone (e.g., extensions) who's interested in when a message is loaded.
var msgURI = GetLoadedMessage();
Services.obs.notifyObservers(msgWindow.msgHeaderSink,
"MsgMsgDisplayed", msgURI);
// scale any overflowing images
var doc = getMessageBrowser().contentDocument;
var imgs = doc.getElementsByTagName("img");
for (var img of imgs) {
if (img.className == "moz-attached-image" &&
img.naturalWidth > doc.body.clientWidth) {
if (img.hasAttribute("shrinktofit"))
img.setAttribute("isshrunk", "true");
else
img.setAttribute("overflowing", "true");
}
}
}
function OnMsgLoaded(aUrl) {
if (!aUrl)
return;
// nsIMsgMailNewsUrl.folder throws an error when opening .eml files.
var folder;
try {
folder = aUrl.folder;
} catch (ex) {}
var msgURI = GetLoadedMessage();
if (!folder || !msgURI)
return;
// If we are in the middle of a delete or move operation, make sure that
// if the user clicks on another message then that message stays selected
// and the selection does not "snap back" to the message chosen by
// SetNextMessageAfterDelete() when the operation completes (bug 243532).
var wintype = document.documentElement.getAttribute("windowtype");
gNextMessageViewIndexAfterDelete = -2;
var msgHdr = msgHdrForCurrentMessage();
gMessageNotificationBar.setJunkMsg(msgHdr);
// Reset the blocked origins so we can populate it again for this message.
// Reset to null so it's only a Set if there's something in the Set.
gMessageNotificationBar.remoteOrigins = null;
var markReadAutoMode = Services.prefs.getBoolPref("mailnews.mark_message_read.auto");
// We just finished loading a message. If messages are to be marked as read
// automatically, set a timer to mark the message is read after n seconds
// where n can be configured by the user.
if (msgHdr && !msgHdr.isRead && markReadAutoMode) {
let markReadOnADelay = Services.prefs.getBoolPref("mailnews.mark_message_read.delay");
// Only use the timer if viewing using the 3-pane preview pane and the
// user has set the pref.
if (markReadOnADelay && wintype == "mail:3pane") // 3-pane window
{
ClearPendingReadTimer();
let markReadDelayTime = Services.prefs.getIntPref("mailnews.mark_message_read.delay.interval");
if (markReadDelayTime == 0)
MarkMessageAsRead(msgHdr);
else
gMarkViewedMessageAsReadTimer = setTimeout(MarkMessageAsRead,
markReadDelayTime * 1000,
msgHdr);
} else // standalone msg window
{
MarkMessageAsRead(msgHdr);
}
}
// See if MDN was requested but has not been sent.
HandleMDNResponse(aUrl);
}
/*
* This function handles all mdn response generation (ie, imap and pop).
* For pop the msg uid can be 0 (ie, 1st msg in a local folder) so no
* need to check uid here. No one seems to set mimeHeaders to null so
* no need to check it either.
*/
function HandleMDNResponse(aUrl) {
if (!aUrl)
return;
var msgFolder = aUrl.folder;
var msgHdr = gFolderDisplay.selectedMessage;
if (!msgFolder || !msgHdr || gFolderDisplay.selectedMessageIsNews)
return;
// if the message is marked as junk, do NOT attempt to process a return receipt
// in order to better protect the user
if (SelectedMessagesAreJunk())
return;
var mimeHdr;
try {
mimeHdr = aUrl.mimeHeaders;
} catch (ex) {
return;
}
// If we didn't get the message id when we downloaded the message header,
// we cons up an md5: message id. If we've done that, we'll try to extract
// the message id out of the mime headers for the whole message.
var msgId = msgHdr.messageId;
if (msgId.split(":")[0] == "md5") {
var mimeMsgId = mimeHdr.extractHeader("Message-Id", false);
if (mimeMsgId)
msgHdr.messageId = mimeMsgId;
}
// After a msg is downloaded it's already marked READ at this point so we must check if
// the msg has a "Disposition-Notification-To" header and no MDN report has been sent yet.
if (msgHdr.flags & Ci.nsMsgMessageFlags.MDNReportSent)
return;
var DNTHeader = mimeHdr.extractHeader("Disposition-Notification-To", false);
var oldDNTHeader = mimeHdr.extractHeader("Return-Receipt-To", false);
if (!DNTHeader && !oldDNTHeader)
return;
// Everything looks good so far, let's generate the MDN response.
var mdnGenerator = Cc["@mozilla.org/messenger-mdn/generator;1"]
.createInstance(Ci.nsIMsgMdnGenerator);
var askUser = mdnGenerator.process(Ci.nsIMsgMdnGenerator.eDisplayed,
msgWindow,
msgFolder,
msgHdr.messageKey,
mimeHdr,
false);
if (askUser)
gMessageNotificationBar.setMDNMsg(mdnGenerator, msgHdr, mimeHdr);
}
function SendMDNResponse() {
gMessageNotificationBar.mdnGenerator.userAgreed();
}
function IgnoreMDNResponse() {
gMessageNotificationBar.mdnGenerator.userDeclined();
}
/**
* Opens a search window with the given folder, or the displayed one if none is
* chosen.
*
* @param [aFolder] the folder to open the search window for, if different from
* the displayed one
*/
function MsgSearchMessages(aFolder) {
let folder = aFolder || gFolderDisplay.displayedFolder;
OpenOrFocusWindow({ folder }, "mailnews:search",
}
function MsgJunkMailInfo(aCheckFirstUse) {
if (aCheckFirstUse) {
if (!Services.prefs.getBoolPref("mailnews.ui.junk.firstuse"))
return;
Services.prefs.setBoolPref("mailnews.ui.junk.firstuse", false);
// Check to see if this is an existing profile where the user has started
// using the junk mail feature already.
if (MailServices.junk.userHasClassified)
return;
}
var desiredWindow = Services.wm.getMostRecentWindow("mailnews:junkmailinfo");
if (desiredWindow)
desiredWindow.focus();
else
window.openDialog("chrome://messenger/content/junkMailInfo.xul", "mailnews:junkmailinfo", "centerscreen,resizeable=no,titlebar,chrome,modal", null);
}
function MsgSearchAddresses() {
var args = { directory: null };
OpenOrFocusWindow(args, "mailnews:absearch", "chrome://messenger/content/ABSearchDialog.xul");
}
function MsgFilterList(args) {
OpenOrFocusWindow(args, "mailnews:filterlist", "chrome://messenger/content/FilterListDialog.xul");
}
function OpenOrFocusWindow(args, windowType, chromeURL) {
var desiredWindow = Services.wm.getMostRecentWindow(windowType);
if (desiredWindow) {
desiredWindow.focus();
if ("refresh" in args && args.refresh)
desiredWindow.refresh(args);
} else
window.openDialog(chromeURL, "", "chrome,resizable,status,centerscreen,dialog=no", args);
}
function getMailToolbox() {
return document.getElementById("mail-toolbox");
}
function MailToolboxCustomizeInit() {
toolboxCustomizeInit("mail-menubar");
}
function MailToolboxCustomizeDone(aToolboxChanged) {
toolboxCustomizeDone("mail-menubar", getMailToolbox(), aToolboxChanged);
// Make sure the folder location picker is initialized.
let folderContainer = document.getElementById("folder-location-container");
if (folderContainer &&
folderContainer.parentNode.localName != "toolbarpalette") {
FolderPaneSelectionChange();
}
}
function MailToolboxCustomizeChange(event) {
toolboxCustomizeChange(getMailToolbox(), event);
}