summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/im/content/imContextMenu.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/im/content/imContextMenu.js')
-rw-r--r--comm/mail/components/im/content/imContextMenu.js276
1 files changed, 276 insertions, 0 deletions
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<string>} 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;
+}