From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mail/components/im/content/imContextMenu.js | 276 +++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 comm/mail/components/im/content/imContextMenu.js (limited to 'comm/mail/components/im/content/imContextMenu.js') diff --git a/comm/mail/components/im/content/imContextMenu.js b/comm/mail/components/im/content/imContextMenu.js new file mode 100644 index 0000000000..0d9ecf0763 --- /dev/null +++ b/comm/mail/components/im/content/imContextMenu.js @@ -0,0 +1,276 @@ +/* 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/. */ + +// This file is loaded in messenger.xhtml. +/* globals gatherTextUnder, goUpdateGlobalEditMenuItems, makeURLAbsolute, Services */ +/* import-globals-from ../../../base/content/widgets/browserPopups.js */ + +var gChatContextMenu = null; + +function imContextMenu(aXulMenu) { + this.target = null; + this.menu = null; + this.onLink = false; + this.onMailtoLink = false; + this.onSaveableLink = false; + this.link = false; + this.linkURL = ""; + this.linkURI = null; + this.linkProtocol = null; + this.isTextSelected = false; + this.isContentSelected = false; + this.shouldDisplay = true; + this.ellipsis = "\u2026"; + this.initedActions = false; + + try { + this.ellipsis = Services.prefs.getComplexValue( + "intl.ellipsis", + Ci.nsIPrefLocalizedString + ).data; + } catch (e) {} + + // Initialize new menu. + this.initMenu(aXulMenu); +} + +// Prototype for nsContextMenu "class." +imContextMenu.prototype = { + cleanup() { + nsContextMenu.contentData.browser.browsingContext.currentWindowGlobal + ?.getActor("ChatAction") + .reportHide(); + let elt = document.getElementById( + "context-sep-messageactions" + ).nextElementSibling; + // remove the action menuitems added last time we opened the popup + while (elt && elt.localName != "menuseparator") { + let tmp = elt.nextElementSibling; + elt.remove(); + elt = tmp; + } + }, + + /** + * Initialize context menu. Shows/hides relevant items. Message actions are + * handled separately in |initActions| if the actor gets them after this is + * called. + * + * @param {XULMenuPopupElement} aPopup - The popup to initialize on. + */ + initMenu(aPopup) { + this.menu = aPopup; + + // Get contextual info. + this.setTarget(); + + this.isTextSelected = this.isTextSelection(); + this.isContentSelected = this.isContentSelection(); + + // Initialize (disable/remove) menu items. + // Open/Save/Send link depends on whether we're in a link. + var shouldShow = this.onSaveableLink; + this.showItem("context-openlink", shouldShow); + this.showItem("context-sep-open", shouldShow); + this.showItem("context-savelink", shouldShow); + + // Copy depends on whether there is selected text. + // Enabling this context menu item is now done through the global + // command updating system + goUpdateGlobalEditMenuItems(); + + this.showItem("context-copy", this.isContentSelected); + this.showItem("context-selectall", !this.onLink || this.isContentSelected); + if (!this.initedActions) { + let actor = + nsContextMenu.contentData.browser.browsingContext.currentWindowGlobal?.getActor( + "ChatAction" + ); + if (actor?.actions) { + this.initActions(actor.actions); + } else { + this.showItem("context-sep-messageactions", false); + } + } + + // Copy email link depends on whether we're on an email link. + this.showItem("context-copyemail", this.onMailtoLink); + + // Copy link location depends on whether we're on a non-mailto link. + this.showItem("context-copylink", this.onLink && !this.onMailtoLink); + this.showItem( + "context-sep-copylink", + this.onLink && this.isContentSelected + ); + }, + + /** + * Adds the given message actions to the context menu. + * + * @param {Array} actions - Array containing the labels for the + * available actions. + */ + initActions(actions) { + this.showItem("context-sep-messageactions", actions.length > 0); + + // Display action menu items. + let sep = document.getElementById("context-sep-messageactions"); + for (let [index, label] of actions.entries()) { + let menuitem = document.createXULElement("menuitem"); + menuitem.setAttribute("label", label); + menuitem.addEventListener("command", () => { + nsContextMenu.contentData.browser.browsingContext.currentWindowGlobal + ?.getActor("ChatAction") + .sendAsyncMessage("ChatAction:Run", { index }); + }); + sep.parentNode.appendChild(menuitem); + } + this.initedActions = true; + }, + + // Set various context menu attributes based on the state of the world. + setTarget() { + // Initialize contextual info. + this.onLink = nsContextMenu.contentData.context.onLink; + this.linkURL = nsContextMenu.contentData.context.linkURL; + this.linkURI = this.getLinkURI(); + this.linkProtocol = nsContextMenu.contentData.context.linkProtocol; + this.linkText = nsContextMenu.contentData.context.linkTextStr; + this.onMailtoLink = nsContextMenu.contentData.context.onMailtoLink; + this.onSaveableLink = nsContextMenu.contentData.context.onSaveableLink; + }, + + // Open linked-to URL in a new window. + openLink(aURI) { + Cc["@mozilla.org/uriloader/external-protocol-service;1"] + .getService(Ci.nsIExternalProtocolService) + .loadURI(aURI || this.linkURI, nsContextMenu.contentData.principal); + }, + + // Generate email address and put it on clipboard. + copyEmail() { + // Copy the comma-separated list of email addresses only. + // There are other ways of embedding email addresses in a mailto: + // link, but such complex parsing is beyond us. + var url = this.linkURL; + var qmark = url.indexOf("?"); + var addresses; + + // 7 == length of "mailto:" + addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7); + + // Let's try to unescape it using a character set + // in case the address is not ASCII. + try { + var characterSet = this.target.ownerDocument.characterSet; + addresses = Services.textToSubURI.unEscapeURIForUI( + characterSet, + addresses + ); + } catch (ex) { + // Do nothing. + } + + var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( + Ci.nsIClipboardHelper + ); + clipboard.copyString(addresses); + }, + + // --------- + // Utilities + + // Show/hide one item (specified via name or the item element itself). + showItem(aItemOrId, aShow) { + var item = + aItemOrId.constructor == String + ? document.getElementById(aItemOrId) + : aItemOrId; + if (item) { + item.hidden = !aShow; + } + }, + + // Temporary workaround for DOM api not yet implemented by XUL nodes. + cloneNode(aItem) { + // Create another element like the one we're cloning. + var node = document.createXULElement(aItem.tagName); + + // Copy attributes from argument item to the new one. + var attrs = aItem.attributes; + for (var i = 0; i < attrs.length; i++) { + var attr = attrs.item(i); + node.setAttribute(attr.nodeName, attr.nodeValue); + } + + // Voila! + return node; + }, + + getLinkURI() { + try { + return Services.io.newURI(this.linkURL); + } catch (ex) { + // e.g. empty URL string + } + + return null; + }, + + // Get selected text. Only display the first 15 chars. + isTextSelection() { + // Get 16 characters, so that we can trim the selection if it's greater + // than 15 chars + var selectedText = getBrowserSelection(16); + + if (!selectedText) { + return false; + } + + if (selectedText.length > 15) { + selectedText = selectedText.substr(0, 15) + this.ellipsis; + } + + return true; + }, + + // Returns true if anything is selected. + isContentSelection() { + return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed; + }, +}; + +/** + * Gets the selected text in the active browser. Leading and trailing + * whitespace is removed, and consecutive whitespace is replaced by a single + * space. A maximum of 150 characters will be returned, regardless of the value + * of aCharLen. + * + * @param aCharLen + * The maximum number of characters to return. + */ +function getBrowserSelection(aCharLen) { + // selections of more than 150 characters aren't useful + const kMaxSelectionLen = 150; + const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen); + + var focusedWindow = document.commandDispatcher.focusedWindow; + var selection = focusedWindow.getSelection().toString(); + + if (selection) { + if (selection.length > charLen) { + // only use the first charLen important chars. see bug 221361 + var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}"); + pattern.test(selection); + selection = RegExp.lastMatch; + } + + selection = selection.trim().replace(/\s+/g, " "); + + if (selection.length > charLen) { + selection = selection.substr(0, charLen); + } + } + return selection; +} -- cgit v1.2.3