summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/content/widgets/customizable-toolbar.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/content/widgets/customizable-toolbar.js')
-rw-r--r--comm/mail/base/content/widgets/customizable-toolbar.js319
1 files changed, 319 insertions, 0 deletions
diff --git a/comm/mail/base/content/widgets/customizable-toolbar.js b/comm/mail/base/content/widgets/customizable-toolbar.js
new file mode 100644
index 0000000000..350e814716
--- /dev/null
+++ b/comm/mail/base/content/widgets/customizable-toolbar.js
@@ -0,0 +1,319 @@
+/* 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/. */
+
+"use strict";
+
+/* globals MozXULElement */
+
+// Wrap in a block to prevent leaking to window scope.
+{
+ /**
+ * Extends the built-in `toolbar` element to allow it to be customized.
+ *
+ * @augments {MozXULElement}
+ */
+ class CustomizableToolbar extends MozXULElement {
+ connectedCallback() {
+ if (this.delayConnectedCallback() || this._hasConnected) {
+ return;
+ }
+ this._hasConnected = true;
+
+ this._toolbox = null;
+ this._newElementCount = 0;
+
+ // Search for the toolbox palette in the toolbar binding because
+ // toolbars are constructed first.
+ let toolbox = this.toolbox;
+ if (!toolbox) {
+ return;
+ }
+
+ if (!toolbox.palette) {
+ // Look to see if there is a toolbarpalette.
+ let node = toolbox.firstElementChild;
+ while (node) {
+ if (node.localName == "toolbarpalette") {
+ break;
+ }
+ node = node.nextElementSibling;
+ }
+
+ if (!node) {
+ return;
+ }
+
+ // Hold on to the palette but remove it from the document.
+ toolbox.palette = node;
+ toolbox.removeChild(node);
+ }
+
+ // Build up our contents from the palette.
+ let currentSet =
+ this.getAttribute("currentset") || this.getAttribute("defaultset");
+
+ if (currentSet) {
+ this.currentSet = currentSet;
+ }
+ }
+
+ /**
+ * Get the toolbox element connected to this toolbar.
+ *
+ * @returns {Element?} The toolbox element or null.
+ */
+ get toolbox() {
+ if (this._toolbox) {
+ return this._toolbox;
+ }
+
+ let toolboxId = this.getAttribute("toolboxid");
+ if (toolboxId) {
+ let toolbox = document.getElementById(toolboxId);
+ if (!toolbox) {
+ let tbName = this.hasAttribute("toolbarname")
+ ? ` (${this.getAttribute("toolbarname")})`
+ : "";
+
+ throw new Error(
+ `toolbar ID ${this.id}${tbName}: toolboxid attribute '${toolboxId}' points to a toolbox that doesn't exist`
+ );
+ }
+ this._toolbox = toolbox;
+ return this._toolbox;
+ }
+
+ this._toolbox =
+ this.parentNode && this.parentNode.localName == "toolbox"
+ ? this.parentNode
+ : null;
+
+ return this._toolbox;
+ }
+
+ /**
+ * Sets the current set of items in the toolbar.
+ *
+ * @param {string} val - Comma-separated list of IDs or "__empty".
+ * @returns {string} Comma-separated list of IDs or "__empty".
+ */
+ set currentSet(val) {
+ if (val == this.currentSet) {
+ return;
+ }
+
+ // Build a cache of items in the toolbarpalette.
+ let palette = this.toolbox ? this.toolbox.palette : null;
+ let paletteChildren = palette ? palette.children : [];
+
+ let paletteItems = {};
+
+ for (let item of paletteChildren) {
+ paletteItems[item.id] = item;
+ }
+
+ let ids = val == "__empty" ? [] : val.split(",");
+ let children = this.children;
+ let nodeidx = 0;
+ let added = {};
+
+ // Iterate over the ids to use on the toolbar.
+ for (let id of ids) {
+ // Iterate over the existing nodes on the toolbar. nodeidx is the
+ // spot where we want to insert items.
+ let found = false;
+ for (let i = nodeidx; i < children.length; i++) {
+ let curNode = children[i];
+ if (this._idFromNode(curNode) == id) {
+ // The node already exists. If i equals nodeidx, we haven't
+ // iterated yet, so the item is already in the right position.
+ // Otherwise, insert it here.
+ if (i != nodeidx) {
+ this.insertBefore(curNode, children[nodeidx]);
+ }
+
+ added[curNode.id] = true;
+ nodeidx++;
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ // Move on to the next id.
+ continue;
+ }
+
+ // The node isn't already on the toolbar, so add a new one.
+ let nodeToAdd = paletteItems[id] || this._getToolbarItem(id);
+ if (nodeToAdd && !(nodeToAdd.id in added)) {
+ added[nodeToAdd.id] = true;
+ this.insertBefore(nodeToAdd, children[nodeidx] || null);
+ nodeToAdd.setAttribute("removable", "true");
+ nodeidx++;
+ }
+ }
+
+ // Remove any leftover removable nodes.
+ for (let i = children.length - 1; i >= nodeidx; i--) {
+ let curNode = children[i];
+
+ let curNodeId = this._idFromNode(curNode);
+ // Skip over fixed items.
+ if (curNodeId && curNode.getAttribute("removable") == "true") {
+ if (palette) {
+ palette.appendChild(curNode);
+ } else {
+ this.removeChild(curNode);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the current set of items in the toolbar.
+ *
+ * @returns {string} Comma-separated list of IDs or "__empty".
+ */
+ get currentSet() {
+ let node = this.firstElementChild;
+ let currentSet = [];
+ while (node) {
+ let id = this._idFromNode(node);
+ if (id) {
+ currentSet.push(id);
+ }
+ node = node.nextElementSibling;
+ }
+
+ return currentSet.join(",") || "__empty";
+ }
+
+ /**
+ * Return the ID for a given toolbar item node, with special handling for
+ * some cases.
+ *
+ * @param {Element} node - Return the ID of this node.
+ * @returns {string} The ID of the node.
+ */
+ _idFromNode(node) {
+ if (node.getAttribute("skipintoolbarset") == "true") {
+ return "";
+ }
+ const specialItems = {
+ toolbarseparator: "separator",
+ toolbarspring: "spring",
+ toolbarspacer: "spacer",
+ };
+ return specialItems[node.localName] || node.id;
+ }
+
+ /**
+ * Returns a toolbar item based on the given ID.
+ *
+ * @param {string} id - The ID for the new toolbar item.
+ * @returns {Element?} The toolbar item corresponding to the ID, or null.
+ */
+ _getToolbarItem(id) {
+ // Handle special cases.
+ if (["separator", "spring", "spacer"].includes(id)) {
+ let newItem = document.createXULElement("toolbar" + id);
+ // Due to timers resolution Date.now() can be the same for
+ // elements created in small timeframes. So ids are
+ // differentiated through a unique count suffix.
+ newItem.id = id + Date.now() + ++this._newElementCount;
+ if (id == "spring") {
+ newItem.flex = 1;
+ }
+ return newItem;
+ }
+
+ let toolbox = this.toolbox;
+ if (!toolbox) {
+ return null;
+ }
+
+ // Look for an item with the same id, as the item may be
+ // in a different toolbar.
+ let item = document.getElementById(id);
+ if (
+ item &&
+ item.parentNode &&
+ item.parentNode.localName == "toolbar" &&
+ item.parentNode.toolbox == toolbox
+ ) {
+ return item;
+ }
+
+ if (toolbox.palette) {
+ // Attempt to locate an item with a matching ID within the palette.
+ let paletteItem = toolbox.palette.firstElementChild;
+ while (paletteItem) {
+ if (paletteItem.id == id) {
+ return paletteItem;
+ }
+ paletteItem = paletteItem.nextElementSibling;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Insert an item into the toolbar.
+ *
+ * @param {string} id - The ID of the item to insert.
+ * @param {Element?} beforeElt - Optional element to insert the item before.
+ * @param {Element?} wrapper - Optional wrapper element.
+ * @returns {Element} The inserted item.
+ */
+ insertItem(id, beforeElt, wrapper) {
+ let newItem = this._getToolbarItem(id);
+ if (!newItem) {
+ return null;
+ }
+
+ let insertItem = newItem;
+ // Make sure added items are removable.
+ newItem.setAttribute("removable", "true");
+
+ // Wrap the item in another node if so inclined.
+ if (wrapper) {
+ wrapper.appendChild(newItem);
+ insertItem = wrapper;
+ }
+
+ // Insert the palette item into the toolbar.
+ if (beforeElt) {
+ this.insertBefore(insertItem, beforeElt);
+ } else {
+ this.appendChild(insertItem);
+ }
+ return newItem;
+ }
+
+ /**
+ * Determine whether the current set of toolbar items has custom
+ * interactive items or not.
+ *
+ * @param {string} currentSet - Comma-separated list of IDs or "__empty".
+ * @returns {boolean} Whether the current set has custom interactive items.
+ */
+ hasCustomInteractiveItems(currentSet) {
+ if (currentSet == "__empty") {
+ return false;
+ }
+
+ let defaultOrNoninteractive = (this.getAttribute("defaultset") || "")
+ .split(",")
+ .concat(["separator", "spacer", "spring"]);
+
+ return currentSet
+ .split(",")
+ .some(item => !defaultOrNoninteractive.includes(item));
+ }
+ }
+
+ customElements.define("customizable-toolbar", CustomizableToolbar, {
+ extends: "toolbar",
+ });
+}