summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/content/mailContext.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/content/mailContext.js')
-rw-r--r--comm/mail/base/content/mailContext.js822
1 files changed, 822 insertions, 0 deletions
diff --git a/comm/mail/base/content/mailContext.js b/comm/mail/base/content/mailContext.js
new file mode 100644
index 0000000000..7b2a050b1e
--- /dev/null
+++ b/comm/mail/base/content/mailContext.js
@@ -0,0 +1,822 @@
+/* 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/. */
+
+// mailCommon.js
+/* globals commandController */
+
+// about:3pane and about:message must BOTH provide these:
+
+/* globals goDoCommand */ // globalOverlay.js
+/* globals gDBView, gFolder, gViewWrapper, messengerBundle */
+
+/* globals gEncryptedURIService */ // mailCommon.js
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ calendarDeactivator:
+ "resource:///modules/calendar/calCalendarDeactivator.jsm",
+ EnigmailURIs: "chrome://openpgp/content/modules/uris.jsm",
+ MailUtils: "resource:///modules/MailUtils.jsm",
+ PhishingDetector: "resource:///modules/PhishingDetector.jsm",
+ TagUtils: "resource:///modules/TagUtils.jsm",
+});
+
+/**
+ * Called by ContextMenuParent if this window is about:3pane, or is
+ * about:message but not contained by about:3pane.
+ *
+ * @returns {boolean} true if this function opened the context menu
+ */
+function openContextMenu({ data, target }, browser) {
+ if (window.browsingContext.parent != window.browsingContext.top) {
+ // Not sure how we'd get here, but let's not continue if we do.
+ return false;
+ }
+
+ if (browser.getAttribute("context") != "mailContext") {
+ return false;
+ }
+
+ mailContextMenu.setAsMessagePaneContextMenu(data, target.browsingContext);
+ let screenX = data.context.screenXDevPx / window.devicePixelRatio;
+ let screenY = data.context.screenYDevPx / window.devicePixelRatio;
+ let popup = document.getElementById("mailContext");
+ popup.openPopupAtScreen(screenX, screenY, true);
+
+ return true;
+}
+
+var mailContextMenu = {
+ /**
+ * @type {XULPopupElement}
+ */
+ _menupopup: null,
+
+ // Commands handled by commandController.
+ _commands: {
+ "mailContext-editDraftMsg": "cmd_editDraftMsg",
+ "mailContext-newMsgFromTemplate": "cmd_newMsgFromTemplate",
+ "mailContext-editTemplateMsg": "cmd_editTemplateMsg",
+ "mailContext-openConversation": "cmd_openConversation",
+ "mailContext-replyNewsgroup": "cmd_replyGroup",
+ "mailContext-replySender": "cmd_replySender",
+ "mailContext-replyAll": "cmd_replyall",
+ "mailContext-replyList": "cmd_replylist",
+ "mailContext-forward": "cmd_forward",
+ "mailContext-forwardAsInline": "cmd_forwardInline",
+ "mailContext-forwardAsAttachment": "cmd_forwardAttachment",
+ "mailContext-multiForwardAsAttachment": "cmd_forwardAttachment",
+ "mailContext-redirect": "cmd_redirect",
+ "mailContext-cancel": "cmd_cancel",
+ "mailContext-editAsNew": "cmd_editAsNew",
+ "mailContext-addNewTag": "cmd_addTag",
+ "mailContext-manageTags": "cmd_manageTags",
+ "mailContext-tagRemoveAll": "cmd_removeTags",
+ "mailContext-markReadByDate": "cmd_markReadByDate",
+ "mailContext-markFlagged": "cmd_markAsFlagged",
+ "mailContext-archive": "cmd_archive",
+ "mailContext-moveToFolderAgain": "cmd_moveToFolderAgain",
+ "mailContext-decryptToFolder": "cmd_copyDecryptedTo",
+ "mailContext-delete": "cmd_deleteMessage",
+ "mailContext-ignoreThread": "cmd_killThread",
+ "mailContext-ignoreSubthread": "cmd_killSubthread",
+ "mailContext-watchThread": "cmd_watchThread",
+ "mailContext-saveAs": "cmd_saveAsFile",
+ "mailContext-print": "cmd_print",
+ "mailContext-downloadSelected": "cmd_downloadSelected",
+ },
+
+ // More commands handled by commandController, except these ones get
+ // disabled instead of hidden.
+ _alwaysVisibleCommands: {
+ "mailContext-markRead": "cmd_markAsRead",
+ "mailContext-markUnread": "cmd_markAsUnread",
+ "mailContext-markThreadAsRead": "cmd_markThreadAsRead",
+ "mailContext-markAllRead": "cmd_markAllRead",
+ "mailContext-markAsJunk": "cmd_markAsJunk",
+ "mailContext-markAsNotJunk": "cmd_markAsNotJunk",
+ "mailContext-recalculateJunkScore": "cmd_recalculateJunkScore",
+ },
+
+ /**
+ * If we have overridden the selection for the context menu.
+ *
+ * @see `setOverrideSelection`
+ * @type {boolean}
+ */
+ _selectionIsOverridden: false,
+
+ init() {
+ this._menupopup = document.getElementById("mailContext");
+ this._menupopup.addEventListener("popupshowing", this);
+ this._menupopup.addEventListener("popuphidden", this);
+ this._menupopup.addEventListener("command", this);
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "popupshowing":
+ this.onPopupShowing(event);
+ break;
+ case "popuphidden":
+ this.onPopupHidden(event);
+ break;
+ case "command":
+ this.onCommand(event);
+ break;
+ }
+ },
+
+ onPopupShowing(event) {
+ if (event.target == this._menupopup) {
+ this.fillMailContextMenu(event);
+ }
+ },
+
+ onPopupHidden(event) {
+ if (event.target == this._menupopup) {
+ this.clearOverrideSelection();
+ }
+ },
+
+ onCommand(event) {
+ this.onMailContextMenuCommand(event);
+ },
+
+ /**
+ * Override the selection that this context menu should operate on. The
+ * effect lasts until `clearOverrideSelection` is called by `onPopupHidden`.
+ *
+ * @param {integer} index - The index of the row to use as selection.
+ */
+ setOverrideSelection(index) {
+ this._selectionIsOverridden = true;
+ window.threadPane.saveSelection();
+ window.threadTree._selection.selectEventsSuppressed = true;
+ window.threadTree._selection.select(index);
+ },
+
+ /**
+ * Has the real selection been overridden by a right-click on a message that
+ * wasn't selected?
+ *
+ * @type {boolean}
+ */
+ get selectionIsOverridden() {
+ return this._selectionIsOverridden;
+ },
+
+ /**
+ * Clear the overriding selection, and go back to the previous selection.
+ */
+ clearOverrideSelection() {
+ if (!window.threadTree) {
+ return;
+ }
+ if (this._selectionIsOverridden) {
+ window.threadTree._selection.selectEventsSuppressed = true;
+ window.threadPane.restoreSelection({ notify: false });
+ this._selectionIsOverridden = false;
+ window.threadTree.invalidate();
+ }
+ window.threadTree
+ .querySelector(".context-menu-target")
+ ?.classList.remove("context-menu-target");
+ window.threadTree._selection.selectEventsSuppressed = false;
+ window.threadTree.table.body.focus();
+ },
+
+ setAsThreadPaneContextMenu() {
+ delete this.browsingContext;
+ delete this.context;
+ delete this.selectionInfo;
+ this.inThreadTree = true;
+
+ for (let id of [
+ "mailContext-openInBrowser",
+ "mailContext-openLinkInBrowser",
+ "mailContext-copylink",
+ "mailContext-savelink",
+ "mailContext-reportPhishingURL",
+ "mailContext-addemail",
+ "mailContext-composeemailto",
+ "mailContext-copyemail",
+ "mailContext-copyimage",
+ "mailContext-saveimage",
+ "mailContext-copy",
+ "mailContext-selectall",
+ "mailContext-searchTheWeb",
+ ]) {
+ document.getElementById(id).hidden = true;
+ }
+ },
+
+ setAsMessagePaneContextMenu({ context, selectionInfo }, browsingContext) {
+ function showItem(id, show) {
+ let item = document.getElementById(id);
+ if (item) {
+ item.hidden = !show;
+ }
+ }
+
+ delete this.inThreadTree;
+ this.browsingContext = browsingContext;
+ this.context = context;
+ this.selectionInfo = selectionInfo;
+
+ // showItem("mailContext-openInBrowser", false);
+ showItem(
+ "mailContext-openLinkInBrowser",
+ context.onLink && !context.onMailtoLink
+ );
+ showItem("mailContext-copylink", context.onLink && !context.onMailtoLink);
+ showItem("mailContext-savelink", context.onLink);
+ showItem(
+ "mailContext-reportPhishingURL",
+ context.onLink && !context.onMailtoLink
+ );
+ showItem("mailContext-addemail", context.onMailtoLink);
+ showItem("mailContext-composeemailto", context.onMailtoLink);
+ showItem("mailContext-copyemail", context.onMailtoLink);
+ showItem("mailContext-copyimage", context.onImage);
+ showItem("mailContext-saveimage", context.onLoadedImage);
+ showItem(
+ "mailContext-copy",
+ selectionInfo && !selectionInfo.docSelectionIsCollapsed
+ );
+ showItem("mailContext-selectall", true);
+ showItem(
+ "mailContext-searchTheWeb",
+ selectionInfo && !selectionInfo.docSelectionIsCollapsed
+ );
+
+ let searchTheWeb = document.getElementById("mailContext-searchTheWeb");
+ if (!searchTheWeb.hidden) {
+ let key = "openSearch.label";
+ let abbrSelection;
+ if (selectionInfo.text.length > 15) {
+ key += ".truncated";
+ abbrSelection = selectionInfo.text.slice(0, 15);
+ } else {
+ abbrSelection = selectionInfo.text;
+ }
+
+ searchTheWeb.label = messengerBundle.formatStringFromName(key, [
+ Services.search.defaultEngine.name,
+ abbrSelection,
+ ]);
+ }
+ },
+
+ fillMailContextMenu(event) {
+ function showItem(id, show) {
+ let item = document.getElementById(id);
+ if (item) {
+ item.hidden = !show;
+ }
+ }
+
+ function enableItem(id, enabled) {
+ let item = document.getElementById(id);
+ item.disabled = !enabled;
+ }
+
+ function checkItem(id, checked) {
+ let item = document.getElementById(id);
+ if (item) {
+ // Convert truthy/falsy to boolean before string.
+ item.setAttribute("checked", !!checked);
+ }
+ }
+
+ function setSingleSelection(id, show = true) {
+ showItem(id, numSelectedMessages == 1 && show);
+ enableItem(id, numSelectedMessages == 1);
+ }
+
+ // Hide things that don't work yet.
+ for (let id of [
+ "mailContext-openInBrowser",
+ "mailContext-recalculateJunkScore",
+ ]) {
+ showItem(id, false);
+ }
+
+ let onSpecialItem =
+ this.context?.isContentSelected ||
+ this.context?.onCanvas ||
+ this.context?.onLink ||
+ this.context?.onImage ||
+ this.context?.onAudio ||
+ this.context?.onVideo ||
+ this.context?.onTextInput;
+
+ for (let id of ["mailContext-tags", "mailContext-mark"]) {
+ showItem(id, !onSpecialItem);
+ }
+
+ // Ask commandController about the commands it controls.
+ for (let [id, command] of Object.entries(this._commands)) {
+ showItem(
+ id,
+ !onSpecialItem && commandController.isCommandEnabled(command)
+ );
+ }
+ for (let [id, command] of Object.entries(this._alwaysVisibleCommands)) {
+ showItem(id, !onSpecialItem);
+ enableItem(id, commandController.isCommandEnabled(command));
+ }
+
+ let inAbout3Pane = !!window.threadTree;
+ let inThreadTree = !!this.inThreadTree;
+
+ let message =
+ gFolder || gViewWrapper.isSynthetic
+ ? gDBView?.hdrForFirstSelectedMessage
+ : top.messenger.msgHdrFromURI(window.gMessageURI);
+ let folder = message?.folder;
+ let isDummyMessage = !gViewWrapper.isSynthetic && !folder;
+
+ let numSelectedMessages = isDummyMessage ? 1 : gDBView.numSelected;
+ let isNewsgroup = folder?.isSpecialFolder(
+ Ci.nsMsgFolderFlags.Newsgroup,
+ true
+ );
+ let canMove =
+ numSelectedMessages >= 1 && !isNewsgroup && folder?.canDeleteMessages;
+ let canCopy = numSelectedMessages >= 1;
+
+ setSingleSelection("mailContext-openNewTab", inThreadTree);
+ setSingleSelection("mailContext-openNewWindow", inThreadTree);
+ setSingleSelection(
+ "mailContext-openContainingFolder",
+ (!isDummyMessage && !inAbout3Pane) || gViewWrapper.isSynthetic
+ );
+ setSingleSelection("mailContext-forward", !onSpecialItem);
+ setSingleSelection("mailContext-forwardAsMenu", !onSpecialItem);
+ showItem(
+ "mailContext-multiForwardAsAttachment",
+ numSelectedMessages > 1 &&
+ commandController.isCommandEnabled("cmd_forwardAttachment")
+ );
+
+ if (isDummyMessage) {
+ showItem("mailContext-tags", false);
+ } else {
+ showItem("mailContext-tags", true);
+ this._initMessageTags();
+ }
+
+ showItem("mailContext-mark", !isDummyMessage);
+ checkItem("mailContext-markFlagged", message?.isFlagged);
+
+ setSingleSelection("mailContext-copyMessageUrl", !!isNewsgroup);
+ // Disable move if we can't delete message(s) from this folder.
+ showItem("mailContext-moveMenu", canMove && !onSpecialItem);
+ showItem("mailContext-copyMenu", canCopy && !onSpecialItem);
+
+ top.initMoveToFolderAgainMenu(
+ document.getElementById("mailContext-moveToFolderAgain")
+ );
+
+ // Show only if a message is actively selected in the DOM.
+ // extractFromEmail can't work on dummy messages.
+ showItem(
+ "mailContext-calendar-convert-menu",
+ numSelectedMessages == 1 &&
+ !isDummyMessage &&
+ calendarDeactivator.isCalendarActivated
+ );
+
+ document.l10n.setAttributes(
+ document.getElementById("mailContext-delete"),
+ message.flags & Ci.nsMsgMessageFlags.IMAPDeleted
+ ? "mail-context-undelete-messages"
+ : "mail-context-delete-messages",
+ {
+ count: numSelectedMessages,
+ }
+ );
+
+ checkItem(
+ "mailContext-ignoreThread",
+ folder?.msgDatabase.isIgnored(message?.messageKey)
+ );
+ checkItem(
+ "mailContext-ignoreSubthread",
+ folder && message.flags & Ci.nsMsgMessageFlags.Ignored
+ );
+ checkItem(
+ "mailContext-watchThread",
+ folder?.msgDatabase.isWatched(message?.messageKey)
+ );
+
+ showItem(
+ "mailContext-downloadSelected",
+ window.threadTree && numSelectedMessages > 1
+ );
+
+ let lastItem;
+ for (let child of document.getElementById("mailContext").children) {
+ if (child.localName == "menuseparator") {
+ child.hidden = !lastItem || lastItem.localName == "menuseparator";
+ }
+ if (!child.hidden) {
+ lastItem = child;
+ }
+ }
+ if (lastItem.localName == "menuseparator") {
+ lastItem.hidden = true;
+ }
+
+ // The rest of this block sends menu information to WebExtensions.
+
+ let selectionInfo = this.selectionInfo;
+ let isContentSelected = selectionInfo
+ ? !selectionInfo.docSelectionIsCollapsed
+ : false;
+ let textSelected = selectionInfo ? selectionInfo.text : "";
+ let isTextSelected = !!textSelected.length;
+
+ let tabmail = top.document.getElementById("tabmail");
+ let subject = {
+ menu: event.target,
+ tab: tabmail ? tabmail.currentTabInfo : top,
+ isContentSelected,
+ isTextSelected,
+ onTextInput: this.context?.onTextInput,
+ onLink: this.context?.onLink,
+ onImage: this.context?.onImage,
+ onEditable: this.context?.onEditable,
+ srcUrl: this.context?.mediaURL,
+ linkText: this.context?.linkTextStr,
+ linkUrl: this.context?.linkURL,
+ selectionText: isTextSelected ? selectionInfo.fullText : undefined,
+ pageUrl: this.browsingContext?.currentURI?.spec,
+ };
+
+ if (inThreadTree) {
+ subject.displayedFolder = folder;
+ subject.selectedMessages = gDBView.getSelectedMsgHdrs();
+ }
+
+ subject.context = subject;
+ subject.wrappedJSObject = subject;
+
+ Services.obs.notifyObservers(subject, "on-prepare-contextmenu");
+ Services.obs.notifyObservers(subject, "on-build-contextmenu");
+ },
+
+ onMailContextMenuCommand(event) {
+ // If commandController handles this command, ask it to do so.
+ if (event.target.id in this._commands) {
+ commandController.doCommand(this._commands[event.target.id], event);
+ return;
+ }
+ if (event.target.id in this._alwaysVisibleCommands) {
+ commandController.doCommand(
+ this._alwaysVisibleCommands[event.target.id],
+ event
+ );
+ return;
+ }
+
+ switch (event.target.id) {
+ // Links
+ // case "mailContext-openInBrowser":
+ // this._openInBrowser();
+ // break;
+ case "mailContext-openLinkInBrowser":
+ // Only called in about:message.
+ top.openLinkExternally(this.context.linkURL);
+ break;
+ case "mailContext-copylink":
+ goDoCommand("cmd_copyLink");
+ break;
+ case "mailContext-savelink":
+ top.saveURL(
+ this.context.linkURL, // URL
+ null, // originalURL
+ this.context.linkTextStr, // fileName
+ null, // filePickerTitleKey
+ true, // shouldBypassCache
+ false, // skipPrompt
+ null, // referrerInfo
+ null, // cookieJarSettings
+ this.browsingContext.window.document, // sourceDocument
+ null, // isContentWindowPrivate,
+ Services.scriptSecurityManager.getSystemPrincipal() // principal
+ );
+ break;
+ case "mailContext-reportPhishingURL":
+ PhishingDetector.reportPhishingURL(this.context.linkURL);
+ break;
+ case "mailContext-addemail":
+ top.addEmail(this.context.linkURL);
+ break;
+ case "mailContext-composeemailto":
+ top.composeEmailTo(
+ this.context.linkURL,
+ gFolder
+ ? MailServices.accounts.getFirstIdentityForServer(gFolder.server)
+ : null
+ );
+ break;
+ case "mailContext-copyemail": {
+ let addresses = top.getEmail(this.context.linkURL);
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(addresses);
+ break;
+ }
+
+ // Images
+ case "mailContext-copyimage":
+ goDoCommand("cmd_copyImageContents");
+ break;
+ case "mailContext-saveimage":
+ top.saveURL(
+ this.context.imageInfo.currentSrc, // URL
+ null, // originalURL
+ this.context.linkTextStr, // fileName
+ "SaveImageTitle", // filePickerTitleKey
+ true, // shouldBypassCache
+ false, // skipPrompt
+ null, // referrerInfo
+ null, // cookieJarSettings
+ this.browsingContext.window?.document, // sourceDocument
+ null, // isContentWindowPrivate,
+ Services.scriptSecurityManager.getSystemPrincipal() // principal
+ );
+ break;
+
+ // Edit
+ case "mailContext-copy":
+ goDoCommand("cmd_copy");
+ break;
+ case "mailContext-selectall":
+ goDoCommand("cmd_selectAll");
+ break;
+
+ // Search
+ case "mailContext-searchTheWeb":
+ top.openWebSearch(this.selectionInfo.text);
+ break;
+
+ // Open messages
+ case "mailContext-openNewTab":
+ top.OpenMessageInNewTab(gDBView.hdrForFirstSelectedMessage, {
+ event,
+ viewWrapper: gViewWrapper,
+ });
+ break;
+ case "mailContext-openNewWindow":
+ top.MsgOpenNewWindowForMessage(
+ gDBView.hdrForFirstSelectedMessage,
+ gViewWrapper
+ );
+ break;
+ case "mailContext-openContainingFolder":
+ MailUtils.displayMessageInFolderTab(gDBView.hdrForFirstSelectedMessage);
+ break;
+
+ // Move/copy/archive/convert/delete
+ // (Move and Copy sub-menus are handled in the default case.)
+ case "mailContext-copyMessageUrl": {
+ let message = gDBView.hdrForFirstSelectedMessage;
+ let server = message?.folder?.server;
+
+ if (!server) {
+ return;
+ }
+
+ // TODO let backend construct URL and return as attribute
+ let url =
+ server.socketType == Ci.nsMsgSocketType.SSL ? "snews://" : "news://";
+ url += server.hostName + ":" + server.port + "/" + message.messageId;
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(url);
+ break;
+ }
+
+ // Calendar Convert sub-menu
+ case "mailContext-calendar-convert-event-menuitem":
+ top.calendarExtract.extractFromEmail(
+ gDBView.hdrForFirstSelectedMessage,
+ true
+ );
+ break;
+ case "mailContext-calendar-convert-task-menuitem":
+ top.calendarExtract.extractFromEmail(
+ gDBView.hdrForFirstSelectedMessage,
+ false
+ );
+ break;
+
+ // Save/print/download
+ default: {
+ if (
+ document.getElementById("mailContext-moveMenu").contains(event.target)
+ ) {
+ commandController.doCommand("cmd_moveMessage", event.target._folder);
+ } else if (
+ document.getElementById("mailContext-copyMenu").contains(event.target)
+ ) {
+ commandController.doCommand("cmd_copyMessage", event.target._folder);
+ } else if (
+ document
+ .getElementById("mailContext-decryptToFolder")
+ .contains(event.target)
+ ) {
+ commandController.doCommand(
+ "cmd_copyDecryptedTo",
+ event.target._folder
+ );
+ }
+ break;
+ }
+ }
+ },
+
+ // Tags sub-menu
+
+ /**
+ * Refresh the contents of the tag popup menu/panel.
+ * Used for example for appmenu/Message/Tag panel.
+ *
+ * @param {Element} parent - Parent element that will contain the menu items.
+ * @param {string} [elementName] - Type of menu item, e.g. "menuitem", "toolbarbutton".
+ * @param {string} [classes] - Classes to set on the menu items.
+ */
+ _initMessageTags() {
+ let parent = document.getElementById("mailContext-tagpopup");
+ // Remove any existing non-static items (clear tags list before rebuilding it).
+ // There is a separator element above the dynamically added tag elements, so
+ // remove dynamically added elements below the separator.
+ while (parent.lastElementChild.localName == "menuitem") {
+ parent.lastElementChild.remove();
+ }
+
+ // Create label and accesskey for the static "remove all tags" item.
+ let removeItem = document.getElementById("mailContext-tagRemoveAll");
+ removeItem.label = messengerBundle.GetStringFromName(
+ "mailnews.tags.remove"
+ );
+
+ // Rebuild the list.
+ let message = gDBView.hdrForFirstSelectedMessage;
+ let currentTags = message
+ ? message.getStringProperty("keywords").split(" ")
+ : [];
+ let index = 1;
+
+ for (let tagInfo of MailServices.tags.getAllTags()) {
+ let msgHasTag = currentTags.includes(tagInfo.key);
+ if (tagInfo.ordinal.includes("~AUTOTAG") && !msgHasTag) {
+ return;
+ }
+
+ let item = document.createXULElement("menuitem");
+ item.accessKey = index < 10 ? index : "";
+ item.label = messengerBundle.formatStringFromName(
+ "mailnews.tags.format",
+ [item.accessKey, tagInfo.tag]
+ );
+ item.setAttribute("type", "checkbox");
+ if (msgHasTag) {
+ item.setAttribute("checked", "true");
+ }
+ item.value = tagInfo.key;
+ item.addEventListener("command", event =>
+ this._toggleMessageTag(
+ tagInfo.key,
+ item.getAttribute("checked") == "true"
+ )
+ );
+ if (tagInfo.color) {
+ item.style.color = tagInfo.color;
+ }
+ parent.appendChild(item);
+
+ index++;
+ }
+ },
+
+ removeAllMessageTags() {
+ let selectedMessages = gDBView.getSelectedMsgHdrs();
+ if (!selectedMessages.length) {
+ return;
+ }
+
+ let messages = [];
+ let allKeys = MailServices.tags
+ .getAllTags()
+ .map(t => t.key)
+ .join(" ");
+ let 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 known tags,
+ // keeping other keywords like (non)junk intact.
+ for (let i = 0; i < selectedMessages.length; ++i) {
+ let msgHdr = selectedMessages[i];
+ if (prevHdrFolder != msgHdr.folder) {
+ if (prevHdrFolder) {
+ prevHdrFolder.removeKeywordsFromMessages(messages, allKeys);
+ }
+ messages = [];
+ prevHdrFolder = msgHdr.folder;
+ }
+ messages.push(msgHdr);
+ }
+ if (prevHdrFolder) {
+ prevHdrFolder.removeKeywordsFromMessages(messages, allKeys);
+ }
+ },
+
+ _toggleMessageTag(key, addKey) {
+ let messages = [];
+ let selectedMessages = gDBView.getSelectedMsgHdrs();
+ let toggler = addKey
+ ? "addKeywordsToMessages"
+ : "removeKeywordsFromMessages";
+ let 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) {
+ let msgHdr = selectedMessages[i];
+ if (prevHdrFolder != msgHdr.folder) {
+ if (prevHdrFolder) {
+ prevHdrFolder[toggler](messages, key);
+ }
+ messages = [];
+ prevHdrFolder = msgHdr.folder;
+ }
+ messages.push(msgHdr);
+ }
+ if (prevHdrFolder) {
+ prevHdrFolder[toggler](messages, key);
+ }
+ },
+
+ /**
+ * Toggle the state of a message tag on the selected messages (based on the
+ * state of the first selected message, like for starring).
+ *
+ * @param {number} keyNumber - The number (1 through 9) associated with the tag.
+ */
+ _toggleMessageTagKey(keyNumber) {
+ let msgHdr = gDBView.hdrForFirstSelectedMessage;
+ if (!msgHdr) {
+ return;
+ }
+
+ let tagArray = MailServices.tags.getAllTags();
+ if (keyNumber > tagArray.length) {
+ return;
+ }
+
+ let key = tagArray[keyNumber - 1].key;
+ let curKeys = msgHdr.getStringProperty("keywords").split(" ");
+ if (msgHdr.label) {
+ curKeys.push("$label" + msgHdr.label);
+ }
+ let addKey = !curKeys.includes(key);
+
+ this._toggleMessageTag(key, addKey);
+ },
+
+ addTag() {
+ top.openDialog(
+ "chrome://messenger/content/newTagDialog.xhtml",
+ "",
+ "chrome,titlebar,modal,centerscreen",
+ {
+ result: "",
+ okCallback: (name, color) => {
+ MailServices.tags.addTag(name, color, "");
+ let key = MailServices.tags.getKeyForTag(name);
+ TagUtils.addTagToAllDocumentSheets(key, color);
+
+ this._toggleMessageTag(key, true);
+ return true;
+ },
+ }
+ );
+ },
+};