summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/content/item-editing/calendar-item-panel.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/calendar/base/content/item-editing/calendar-item-panel.js')
-rw-r--r--comm/calendar/base/content/item-editing/calendar-item-panel.js1143
1 files changed, 1143 insertions, 0 deletions
diff --git a/comm/calendar/base/content/item-editing/calendar-item-panel.js b/comm/calendar/base/content/item-editing/calendar-item-panel.js
new file mode 100644
index 0000000000..f2550d509a
--- /dev/null
+++ b/comm/calendar/base/content/item-editing/calendar-item-panel.js
@@ -0,0 +1,1143 @@
+/* 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/. */
+
+/* exported onLoadCalendarItemPanel, onCancel, onCommandSave,
+ * onCommandDeleteItem, editAttendees, editPrivacy, editPriority,
+ * editStatus, editShowTimeAs, updateShowTimeAs, editToDoStatus,
+ * postponeTask, toggleTimezoneLinks, attachURL,
+ * onCommandViewToolbar, onCommandCustomize, attachFileByAccountKey,
+ * onUnloadCalendarItemPanel, openNewEvent, openNewTask,
+ * openNewMessage
+ */
+
+/* import-globals-from ../../../../mail/base/content/globalOverlay.js */
+/* import-globals-from ../dialogs/calendar-dialog-utils.js */
+/* import-globals-from ../calendar-ui-utils.js */
+
+// XXX Need to determine which of these we really need here.
+var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
+
+var gTabmail;
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ // gTabmail is null if we are in a dialog window and not in a tab.
+ gTabmail = document.getElementById("tabmail") || null;
+
+ if (!gTabmail) {
+ // In a dialog window the following menu item functions need to be
+ // defined. In a tab they are defined elsewhere. To prevent errors in
+ // the log they are defined here (before the onLoad function is called).
+ /**
+ * Update menu items that rely on focus.
+ */
+ window.goUpdateGlobalEditMenuItems = () => {
+ goUpdateCommand("cmd_undo");
+ goUpdateCommand("cmd_redo");
+ goUpdateCommand("cmd_cut");
+ goUpdateCommand("cmd_copy");
+ goUpdateCommand("cmd_paste");
+ goUpdateCommand("cmd_selectAll");
+ };
+ /**
+ * Update menu items that rely on the current selection.
+ */
+ window.goUpdateSelectEditMenuItems = () => {
+ goUpdateCommand("cmd_cut");
+ goUpdateCommand("cmd_copy");
+ goUpdateCommand("cmd_delete");
+ goUpdateCommand("cmd_selectAll");
+ };
+ /**
+ * Update menu items that relate to undo/redo.
+ */
+ window.goUpdateUndoEditMenuItems = () => {
+ goUpdateCommand("cmd_undo");
+ goUpdateCommand("cmd_redo");
+ };
+ /**
+ * Update menu items that depend on clipboard contents.
+ */
+ window.goUpdatePasteMenuItems = () => {
+ goUpdateCommand("cmd_paste");
+ };
+ }
+ },
+ { once: true }
+);
+
+// Stores the ids of the iframes of currently open event/task tabs, used
+// when window is closed to prompt for saving changes.
+var gItemTabIds = [];
+var gItemTabIdsCopy;
+
+// gConfig is used when switching tabs to restore the state of
+// toolbar, statusbar, and menubar for the current tab.
+var gConfig = {
+ isEvent: null,
+ privacy: null,
+ hasPrivacy: null,
+ calendarType: null,
+ privacyValues: null,
+ priority: null,
+ hasPriority: null,
+ status: null,
+ percentComplete: null,
+ showTimeAs: null,
+ // whether commands are enabled or disabled
+ attendeesCommand: null, // cmd_attendees
+ attachUrlCommand: null, // cmd_attach_url
+ timezonesEnabled: false, // cmd_timezone
+};
+
+/**
+ * Receive an asynchronous message from the iframe.
+ *
+ * @param {MessageEvent} aEvent - Contains the message being received
+ */
+function receiveMessage(aEvent) {
+ if (aEvent.origin !== "chrome://calendar") {
+ return;
+ }
+ switch (aEvent.data.command) {
+ case "initializeItemMenu":
+ initializeItemMenu(aEvent.data.label, aEvent.data.accessKey);
+ break;
+ case "cancelDialog":
+ document.querySelector("dialog").cancelDialog();
+ break;
+ case "closeWindowOrTab":
+ closeWindowOrTab(aEvent.data.iframeId);
+ break;
+ case "showCmdStatusNone":
+ document.getElementById("cmd_status_none").removeAttribute("hidden");
+ break;
+ case "updateTitle":
+ updateTitle(aEvent.data.prefix, aEvent.data.title);
+ break;
+ case "updateConfigState":
+ updateItemTabState(aEvent.data.argument);
+ Object.assign(gConfig, aEvent.data.argument);
+ break;
+ case "enableAcceptCommand":
+ enableAcceptCommand(aEvent.data.argument);
+ break;
+ case "replyToClosingWindowWithTabs":
+ handleWindowClose(aEvent.data.response);
+ break;
+ case "removeDisableAndCollapseOnReadonly":
+ removeDisableAndCollapseOnReadonly();
+ break;
+ case "setElementAttribute": {
+ let arg = aEvent.data.argument;
+ document.getElementById(arg.id)[arg.attribute] = arg.value;
+ break;
+ }
+ case "loadCloudProviders": {
+ loadCloudProviders(aEvent.data.items);
+ break;
+ }
+ case "updateSaveControls": {
+ updateSaveControls(aEvent.data.argument.sendNotSave);
+ break;
+ }
+ }
+}
+
+window.addEventListener("message", receiveMessage);
+
+/**
+ * Send an asynchronous message to an iframe. Additional properties of
+ * aMessage are generally arguments that will be passed to the function
+ * named in aMessage.command. If aIframeId is omitted, the message will
+ * be sent to the iframe of the current tab.
+ *
+ * @param {object} aMessage - Contains the message being sent
+ * @param {string} aMessage.command - The name of a function to call
+ * @param {string} aIframeId - (optional) id of an iframe to send the message to
+ */
+function sendMessage(aMessage, aIframeId) {
+ let iframeId = gTabmail
+ ? aIframeId || gTabmail.currentTabInfo.iframe.id
+ : "calendar-item-panel-iframe";
+ let iframe = document.getElementById(iframeId);
+ iframe.contentWindow.postMessage(aMessage, "*");
+}
+
+/**
+ * When the user closes the window, this function handles prompting them
+ * to save any unsaved changes for any open item tabs, before closing the
+ * window, or not if 'cancel' was clicked. Requires sending and receiving
+ * async messages from the iframes of all open item tabs.
+ *
+ * @param {boolean} aResponse - The response from the tab's iframe
+ */
+function handleWindowClose(aResponse) {
+ if (!aResponse) {
+ // Cancel was clicked, just leave the window open. We're done.
+ } else if (gItemTabIdsCopy.length > 0) {
+ // There are more unsaved changes in tabs to prompt the user about.
+ let nextId = gItemTabIdsCopy.shift();
+ sendMessage({ command: "closingWindowWithTabs", id: nextId }, nextId);
+ } else {
+ // Close the window, there are no more unsaved changes in tabs.
+ window.removeEventListener("close", windowCloseListener);
+ window.close();
+ }
+}
+
+/**
+ * Listener function for window close. We prevent the window from
+ * closing, then for each open tab we prompt the user to save any
+ * unsaved changes with handleWindowClose.
+ *
+ * @param {object} aEvent - The window close event
+ */
+function windowCloseListener(aEvent) {
+ aEvent.preventDefault();
+ gItemTabIdsCopy = gItemTabIds.slice();
+ handleWindowClose(true);
+}
+
+/**
+ * Load handler for the outer parent context that contains the iframe.
+ *
+ * @param {string} aIframeId - (optional) Id of the iframe in this tab
+ * @param {string} aUrl - (optional) The url to load in the iframe
+ */
+function onLoadCalendarItemPanel(aIframeId, aUrl) {
+ let iframe;
+ let iframeSrc;
+ let dialog = document.querySelector("dialog");
+
+ if (!gTabmail) {
+ gTabmail = document.getElementById("tabmail") || null;
+ // This should not happen.
+ if (gTabmail) {
+ console.warn(
+ "gTabmail was undefined on document load and is defined now, that should not happen."
+ );
+ }
+ }
+ if (gTabmail) {
+ // tab case
+ let iframeId = aIframeId || gTabmail.currentTabInfo.iframe.id;
+ iframe = document.getElementById(iframeId);
+ iframeSrc = aUrl;
+
+ // Add a listener to detect close events, prompt user about saving changes.
+ window.addEventListener("close", windowCloseListener);
+ } else {
+ // window dialog case
+ iframe = document.createXULElement("iframe");
+ iframeSrc = "chrome://calendar/content/calendar-item-iframe.xhtml";
+
+ iframe.setAttribute("id", "calendar-item-panel-iframe");
+ iframe.setAttribute("flex", "1");
+
+ // Note: iframe.contentWindow is undefined before the iframe is inserted here.
+ dialog.insertBefore(iframe, document.getElementById("status-bar"));
+
+ iframe.contentWindow.addEventListener(
+ "load",
+ () => {
+ // Push setting dimensions to the end of the event queue.
+ setTimeout(() => {
+ let body = iframe.contentDocument.body;
+ // Make sure the body does not exceed its content's size.
+ body.style.width = "fit-content";
+ body.style.height = "fit-content";
+ let { scrollHeight, scrollWidth } = body;
+ iframe.style.minHeight = `${scrollHeight}px`;
+ iframe.style.minWidth = `${scrollWidth}px`;
+ // Reset the body.
+ body.style.width = null;
+ body.style.height = null;
+ });
+ },
+ { once: true }
+ );
+
+ // Move the args so they are positioned relative to the iframe,
+ // for the window dialog just as they are for the tab.
+ // XXX Should we delete the arguments here in the parent context
+ // so they are only accessible in one place?
+ iframe.contentWindow.arguments = [window.arguments[0]];
+
+ // hide the ok and cancel dialog buttons
+ let accept = dialog.getButton("accept");
+ let cancel = dialog.getButton("cancel");
+ accept.setAttribute("collapsed", "true");
+ cancel.setAttribute("collapsed", "true");
+ cancel.parentNode.setAttribute("collapsed", "true");
+
+ document.addEventListener("dialogaccept", event => {
+ let itemTitle = iframe.contentDocument.documentElement.querySelector("#item-title");
+ // Prevent dialog from saving if title is empty.
+ if (!itemTitle.value) {
+ event.preventDefault();
+ return;
+ }
+ sendMessage({ command: "onAccept" });
+ event.preventDefault();
+ });
+
+ document.addEventListener("dialogcancel", event => {
+ sendMessage({ command: "onCancel" });
+ event.preventDefault();
+ });
+
+ // set toolbar icon color for light or dark themes
+ if (typeof window.ToolbarIconColor !== "undefined") {
+ window.ToolbarIconColor.init();
+ }
+ }
+
+ // event or task
+ let calendarItem = iframe.contentWindow.arguments[0].calendarEvent;
+ gConfig.isEvent = calendarItem.isEvent();
+
+ // for tasks in a window dialog, set the dialog id for CSS selection.
+ if (!gTabmail) {
+ if (gConfig.isEvent) {
+ setDialogId(dialog, "calendar-event-dialog");
+ } else {
+ setDialogId(dialog, "calendar-task-dialog");
+ }
+ }
+
+ // timezones enabled
+ gConfig.timezonesEnabled = getTimezoneCommandState();
+ iframe.contentWindow.gTimezonesEnabled = gConfig.timezonesEnabled;
+
+ // set the iframe src, which loads the iframe's contents
+ iframe.setAttribute("src", iframeSrc);
+}
+
+/**
+ * Unload handler for the outer parent context that contains the iframe.
+ * Currently only called for windows and not tabs.
+ */
+function onUnloadCalendarItemPanel() {
+ if (!gTabmail) {
+ // window dialog case
+ if (typeof window.ToolbarIconColor !== "undefined") {
+ window.ToolbarIconColor.uninit();
+ }
+ }
+}
+
+/**
+ * Updates the UI. Called when a user makes a change and when an
+ * event/task tab is shown. When a tab is shown aArg contains the gConfig
+ * data for that event/task. We pass the full tab state object to the
+ * update functions and they just use the properties they need from it.
+ *
+ * @param {object} aArg - Its properties hold data about the event/task
+ */
+function updateItemTabState(aArg) {
+ const lookup = {
+ privacy: updatePrivacy,
+ priority: updatePriority,
+ status: updateStatus,
+ showTimeAs: updateShowTimeAs,
+ percentComplete: updateMarkCompletedMenuItem,
+ attendeesCommand: updateAttendeesCommand,
+ attachUrlCommand: updateAttachment,
+ timezonesEnabled: updateTimezoneCommand,
+ };
+ for (let key of Object.keys(aArg)) {
+ let procedure = lookup[key];
+ if (procedure) {
+ procedure(aArg);
+ }
+ }
+}
+
+/**
+ * When in a window, set Item-Menu label to Event or Task.
+ *
+ * @param {string} aLabel - The new name for the menu
+ * @param {string} aAccessKey - The access key for the menu
+ */
+function initializeItemMenu(aLabel, aAccessKey) {
+ let menuItem = document.getElementById("item-menu");
+ menuItem.setAttribute("label", aLabel);
+ menuItem.setAttribute("accesskey", aAccessKey);
+}
+
+/**
+ * Handler for when tab is cancelled. (calendar.item.editInTab = true)
+ *
+ * @param {string} aIframeId - The id of the iframe
+ */
+function onCancel(aIframeId) {
+ sendMessage({ command: "onCancel", iframeId: aIframeId }, aIframeId);
+ // We return false to prevent closing of a window until we
+ // can ask the user about saving any unsaved changes.
+ return false;
+}
+
+/**
+ * Closes tab or window. Called after prompting to save any unsaved changes.
+ *
+ * @param {string} aIframeId - The id of the iframe
+ */
+function closeWindowOrTab(iframeId) {
+ if (gTabmail) {
+ if (iframeId) {
+ // Find the tab associated with this iframeId, and close it.
+ let myTabInfo = gTabmail.tabInfo.filter(x => "iframe" in x && x.iframe.id == iframeId)[0];
+ myTabInfo.allowTabClose = true;
+ gTabmail.closeTab(myTabInfo);
+ } else {
+ gTabmail.currentTabInfo.allowTabClose = true;
+ gTabmail.removeCurrentTab();
+ }
+ } else {
+ window.close();
+ }
+}
+
+/**
+ * Handler for saving the event or task.
+ *
+ * @param {boolean} aIsClosing - Is the tab or window closing
+ */
+function onCommandSave(aIsClosing) {
+ sendMessage({ command: "onCommandSave", isClosing: aIsClosing });
+}
+
+/**
+ * Handler for deleting the event or task.
+ */
+function onCommandDeleteItem() {
+ sendMessage({ command: "onCommandDeleteItem" });
+}
+
+/**
+ * Disable the saving options according to the item title.
+ *
+ * @param {boolean} disabled - True if the save options needs to be disabled else false.
+ */
+function disableSaving(disabled) {
+ let cmdSave = document.getElementById("cmd_save");
+ if (cmdSave) {
+ cmdSave.setAttribute("disabled", disabled);
+ }
+ let cmdAccept = document.getElementById("cmd_accept");
+ if (cmdAccept) {
+ cmdAccept.setAttribute("disabled", disabled);
+ }
+}
+
+/**
+ * Update the title of the tab or window.
+ *
+ * @param {string} prefix - The prefix string according to the item.
+ * @param {string} title - The item title.
+ */
+function updateTitle(prefix, title) {
+ disableSaving(!title);
+ let newTitle = prefix + ": " + title;
+ if (gTabmail) {
+ gTabmail.currentTabInfo.title = newTitle;
+ gTabmail.setTabTitle(gTabmail.currentTabInfo);
+ } else {
+ document.title = newTitle;
+ }
+}
+
+/**
+ * Open a new event.
+ */
+function openNewEvent() {
+ sendMessage({ command: "openNewEvent" });
+}
+
+/**
+ * Open a new task.
+ */
+function openNewTask() {
+ sendMessage({ command: "openNewTask" });
+}
+
+/**
+ * Open a new Thunderbird compose window.
+ */
+function openNewMessage() {
+ MailServices.compose.OpenComposeWindow(
+ null,
+ null,
+ null,
+ Ci.nsIMsgCompType.New,
+ Ci.nsIMsgCompFormat.Default,
+ null,
+ null,
+ null
+ );
+}
+
+/**
+ * Handler for edit attendees command.
+ */
+function editAttendees() {
+ sendMessage({ command: "editAttendees" });
+}
+
+/**
+ * Sends a message to set the gConfig values in the iframe.
+ *
+ * @param {object} aArg - Container
+ * @param {string} aArg.privacy - (optional) New privacy value
+ * @param {short} aArg.priority - (optional) New priority value
+ * @param {string} aArg.status - (optional) New status value
+ * @param {string} aArg.showTimeAs - (optional) New showTimeAs / transparency value
+ */
+function editConfigState(aArg) {
+ sendMessage({ command: "editConfigState", argument: aArg });
+}
+
+/**
+ * Handler for changing privacy. aEvent is used for the popup menu
+ * event-privacy-menupopup in the Privacy toolbar button.
+ *
+ * @param {Node} aTarget Has the new privacy in its "value" attribute
+ * @param {XULCommandEvent} aEvent - (optional) the UI element selection event
+ */
+function editPrivacy(aTarget, aEvent) {
+ if (aEvent) {
+ aEvent.stopPropagation();
+ }
+ // "privacy" is indeed the correct attribute to use here
+ let newPrivacy = aTarget.getAttribute("privacy");
+ editConfigState({ privacy: newPrivacy });
+}
+
+/**
+ * Updates the UI according to the privacy setting and the selected
+ * calendar. If the selected calendar does not support privacy or only
+ * certain values, these are removed from the UI. This function should
+ * be called any time that privacy setting is updated.
+ *
+ * @param {object} aArg Contains privacy properties
+ * @param {string} aArg.privacy The new privacy value
+ * @param {boolean} aArg.hasPrivacy Whether privacy is supported
+ * @param {string} aArg.calendarType The type of calendar
+ * @param {string[]} aArg.privacyValues The possible privacy values
+ */
+function updatePrivacy(aArg) {
+ if (aArg.hasPrivacy) {
+ // Update privacy capabilities (toolbar)
+ let menupopup = document.getElementById("event-privacy-menupopup");
+ if (menupopup) {
+ // Only update the toolbar if the button is actually there
+ for (let node of menupopup.children) {
+ let currentProvider = node.getAttribute("provider");
+ if (node.hasAttribute("privacy")) {
+ let currentPrivacyValue = node.getAttribute("privacy");
+ // Collapsed state
+
+ // Hide the toolbar if the value is unsupported or is for a
+ // specific provider and doesn't belong to the current provider.
+ if (
+ !aArg.privacyValues.includes(currentPrivacyValue) ||
+ (currentProvider && currentProvider != aArg.calendarType)
+ ) {
+ node.setAttribute("collapsed", "true");
+ } else {
+ node.removeAttribute("collapsed");
+ }
+
+ // Checked state
+ if (aArg.privacy == currentPrivacyValue) {
+ node.setAttribute("checked", "true");
+ } else {
+ node.removeAttribute("checked");
+ }
+ }
+ }
+ }
+
+ // Update privacy capabilities (menu) but only if we are not in a tab.
+ if (!gTabmail) {
+ menupopup = document.getElementById("options-privacy-menupopup");
+ for (let node of menupopup.children) {
+ let currentProvider = node.getAttribute("provider");
+ if (node.hasAttribute("privacy")) {
+ let currentPrivacyValue = node.getAttribute("privacy");
+ // Collapsed state
+
+ // Hide the menu if the value is unsupported or is for a
+ // specific provider and doesn't belong to the current provider.
+ if (
+ !aArg.privacyValues.includes(currentPrivacyValue) ||
+ (currentProvider && currentProvider != aArg.calendarType)
+ ) {
+ node.setAttribute("collapsed", "true");
+ } else {
+ node.removeAttribute("collapsed");
+ }
+
+ // Checked state
+ if (aArg.privacy == currentPrivacyValue) {
+ node.setAttribute("checked", "true");
+ } else {
+ node.removeAttribute("checked");
+ }
+ }
+ }
+ }
+
+ // Update privacy capabilities (statusbar)
+ let privacyPanel = document.getElementById("status-privacy");
+ let hasAnyPrivacyValue = false;
+ for (let node of privacyPanel.children) {
+ let currentProvider = node.getAttribute("provider");
+ if (node.hasAttribute("privacy")) {
+ let currentPrivacyValue = node.getAttribute("privacy");
+
+ // Hide the panel if the value is unsupported or is for a
+ // specific provider and doesn't belong to the current provider,
+ // or is not the items privacy value
+ if (
+ !aArg.privacyValues.includes(currentPrivacyValue) ||
+ (currentProvider && currentProvider != aArg.calendarType) ||
+ aArg.privacy != currentPrivacyValue
+ ) {
+ node.setAttribute("collapsed", "true");
+ } else {
+ node.removeAttribute("collapsed");
+ hasAnyPrivacyValue = true;
+ }
+ }
+ }
+
+ // Don't show the status panel if no valid privacy value is selected
+ if (hasAnyPrivacyValue) {
+ privacyPanel.removeAttribute("collapsed");
+ } else {
+ privacyPanel.setAttribute("collapsed", "true");
+ }
+ } else {
+ // aArg.hasPrivacy is false
+ document.getElementById("button-privacy").disabled = true;
+ document.getElementById("status-privacy").collapsed = true;
+ // in the tab case the menu item does not exist
+ let privacyMenuItem = document.getElementById("options-privacy-menu");
+ if (privacyMenuItem) {
+ document.getElementById("options-privacy-menu").disabled = true;
+ }
+ }
+}
+
+/**
+ * Handler to change the priority.
+ *
+ * @param {Node} aTarget - Has the new priority in its "value" attribute
+ */
+function editPriority(aTarget) {
+ let newPriority = parseInt(aTarget.getAttribute("value"), 10);
+ editConfigState({ priority: newPriority });
+}
+
+/**
+ * Updates the dialog controls related to priority.
+ *
+ * @param {object} aArg Contains priority properties
+ * @param {string} aArg.priority The new priority value
+ * @param {boolean} aArg.hasPriority - Whether priority is supported
+ */
+function updatePriority(aArg) {
+ // Set up capabilities
+ if (document.getElementById("button-priority")) {
+ document.getElementById("button-priority").disabled = !aArg.hasPriority;
+ }
+ if (!gTabmail && document.getElementById("options-priority-menu")) {
+ document.getElementById("options-priority-menu").disabled = !aArg.hasPriority;
+ }
+ document.getElementById("status-priority").collapsed = !aArg.hasPriority;
+
+ if (aArg.hasPriority) {
+ let priorityLevel = "none";
+ if (aArg.priority >= 1 && aArg.priority <= 4) {
+ priorityLevel = "high";
+ } else if (aArg.priority == 5) {
+ priorityLevel = "normal";
+ } else if (aArg.priority >= 6 && aArg.priority <= 9) {
+ priorityLevel = "low";
+ }
+
+ let priorityNone = document.getElementById("cmd_priority_none");
+ let priorityLow = document.getElementById("cmd_priority_low");
+ let priorityNormal = document.getElementById("cmd_priority_normal");
+ let priorityHigh = document.getElementById("cmd_priority_high");
+
+ priorityNone.setAttribute("checked", priorityLevel == "none" ? "true" : "false");
+ priorityLow.setAttribute("checked", priorityLevel == "low" ? "true" : "false");
+ priorityNormal.setAttribute("checked", priorityLevel == "normal" ? "true" : "false");
+ priorityHigh.setAttribute("checked", priorityLevel == "high" ? "true" : "false");
+
+ // Status bar panel
+ let priorityPanel = document.getElementById("status-priority");
+ let image = priorityPanel.querySelector("img");
+ if (priorityLevel === "none") {
+ // If the priority is none, don't show the status bar panel
+ priorityPanel.setAttribute("collapsed", "true");
+ image.removeAttribute("data-l10n-id");
+ image.setAttribute("alt", "");
+ image.removeAttribute("src");
+ } else {
+ priorityPanel.removeAttribute("collapsed");
+ image.setAttribute("alt", cal.l10n.getString("calendar", `${priorityLevel}Priority`));
+ image.setAttribute(
+ "src",
+ `chrome://calendar/skin/shared/statusbar-priority-${priorityLevel}.svg`
+ );
+ }
+ }
+}
+
+/**
+ * Handler for changing the status.
+ *
+ * @param {Node} aTarget - Has the new status in its "value" attribute
+ */
+function editStatus(aTarget) {
+ let newStatus = aTarget.getAttribute("value");
+ editConfigState({ status: newStatus });
+}
+
+/**
+ * Update the dialog controls related to status.
+ *
+ * @param {object} aArg - Contains the new status value
+ * @param {string} aArg.status - The new status value
+ */
+function updateStatus(aArg) {
+ const statusLabels = [
+ "status-status-tentative-label",
+ "status-status-confirmed-label",
+ "status-status-cancelled-label",
+ ];
+ const commands = [
+ "cmd_status_none",
+ "cmd_status_tentative",
+ "cmd_status_confirmed",
+ "cmd_status_cancelled",
+ ];
+ let found = false;
+ document.getElementById("status-status").collapsed = true;
+ commands.forEach((aElement, aIndex, aArray) => {
+ let node = document.getElementById(aElement);
+ let matches = node.getAttribute("value") == aArg.status;
+ found = found || matches;
+
+ node.setAttribute("checked", matches ? "true" : "false");
+
+ if (aIndex > 0) {
+ statusLabels[aIndex - 1].hidden = !matches;
+ if (matches) {
+ document.getElementById("status-status").collapsed = false;
+ }
+ }
+ });
+ if (!found) {
+ // The current Status value is invalid. Change the status to
+ // "not specified" and update the status again.
+ sendMessage({ command: "editStatus", value: "NONE" });
+ }
+}
+
+/**
+ * Handler for changing the transparency.
+ *
+ * @param {Node} aTarget - Has the new transparency in its "value" attribute
+ */
+function editShowTimeAs(aTarget) {
+ let newValue = aTarget.getAttribute("value");
+ editConfigState({ showTimeAs: newValue });
+}
+
+/**
+ * Update the dialog controls related to transparency.
+ *
+ * @param {object} aArg - Contains the new transparency value
+ * @param {string} aArg.showTimeAs - The new transparency value
+ */
+function updateShowTimeAs(aArg) {
+ let showAsBusy = document.getElementById("cmd_showtimeas_busy");
+ let showAsFree = document.getElementById("cmd_showtimeas_free");
+
+ showAsBusy.setAttribute("checked", aArg.showTimeAs == "OPAQUE" ? "true" : "false");
+ showAsFree.setAttribute("checked", aArg.showTimeAs == "TRANSPARENT" ? "true" : "false");
+
+ document.getElementById("status-freebusy").collapsed =
+ aArg.showTimeAs != "OPAQUE" && aArg.showTimeAs != "TRANSPARENT";
+ document.getElementById("status-freebusy-free-label").hidden = aArg.showTimeAs == "OPAQUE";
+ document.getElementById("status-freebusy-busy-label").hidden = aArg.showTimeAs == "TRANSPARENT";
+}
+
+/**
+ * Change the task percent complete (and thus task status).
+ *
+ * @param {short} aPercentComplete - The new percent complete value
+ */
+function editToDoStatus(aPercentComplete) {
+ sendMessage({ command: "editToDoStatus", value: aPercentComplete });
+}
+
+/**
+ * Check or uncheck the "Mark updated" menu item in "Events and Tasks"
+ * menu based on the percent complete value.
+ *
+ * @param {object} aArg - Container
+ * @param {short} aArg.percentComplete - The percent complete value
+ */
+function updateMarkCompletedMenuItem(aArg) {
+ // Command only for tab case, function only to be executed in dialog windows.
+ if (gTabmail) {
+ let completedCommand = document.getElementById("calendar_toggle_completed_command");
+ let isCompleted = aArg.percentComplete == 100;
+ completedCommand.setAttribute("checked", isCompleted);
+ }
+}
+
+/**
+ * Postpone the task's start date/time and due date/time. ISO 8601
+ * format: "PT1H", "P1D", and "P1W" are 1 hour, 1 day, and 1 week. (We
+ * use this format intentionally instead of a calIDuration object because
+ * those objects cannot be serialized for message passing with iframes.)
+ *
+ * @param {string} aDuration - A duration in ISO 8601 format
+ */
+function postponeTask(aDuration) {
+ sendMessage({ command: "postponeTask", value: aDuration });
+}
+
+/**
+ * Get the timezone button state.
+ *
+ * @returns {boolean} True is active/checked and false is inactive/unchecked
+ */
+function getTimezoneCommandState() {
+ let cmdTimezone = document.getElementById("cmd_timezone");
+ return cmdTimezone.getAttribute("checked") == "true";
+}
+
+/**
+ * Set the timezone button state. Used to keep the toolbar button in
+ * sync when switching tabs.
+ *
+ * @param {object} aArg - Contains timezones property
+ * @param {boolean} aArg.timezonesEnabled - Are timezones enabled?
+ */
+function updateTimezoneCommand(aArg) {
+ let cmdTimezone = document.getElementById("cmd_timezone");
+ cmdTimezone.setAttribute("checked", aArg.timezonesEnabled);
+ gConfig.timezonesEnabled = aArg.timezonesEnabled;
+}
+
+/**
+ * Toggles the command that allows enabling the timezone links in the dialog.
+ */
+function toggleTimezoneLinks() {
+ let cmdTimezone = document.getElementById("cmd_timezone");
+ let currentState = getTimezoneCommandState();
+ cmdTimezone.setAttribute("checked", currentState ? "false" : "true");
+ gConfig.timezonesEnabled = !currentState;
+ sendMessage({ command: "toggleTimezoneLinks", checked: !currentState });
+}
+
+/**
+ * Prompts the user to attach an url to this item.
+ */
+function attachURL() {
+ sendMessage({ command: "attachURL" });
+}
+
+/**
+ * Updates dialog controls related to item attachments.
+ *
+ * @param {object} aArg Container
+ * @param {boolean} aArg.attachUrlCommand - Enable the attach url command?
+ */
+function updateAttachment(aArg) {
+ document.getElementById("cmd_attach_url").setAttribute("disabled", !aArg.attachUrlCommand);
+}
+
+/**
+ * Updates attendees command enabled/disabled state.
+ *
+ * @param {object} aArg Container
+ * @param {boolean} aArg.attendeesCommand - Enable the attendees command?
+ */
+function updateAttendeesCommand(aArg) {
+ document.getElementById("cmd_attendees").setAttribute("disabled", !aArg.attendeesCommand);
+}
+
+/**
+ * Enables/disables the commands cmd_accept and cmd_save related to the
+ * save operation.
+ *
+ * @param {boolean} aEnable - Enable the commands?
+ */
+function enableAcceptCommand(aEnable) {
+ document.getElementById("cmd_accept").setAttribute("disabled", !aEnable);
+ document.getElementById("cmd_save").setAttribute("disabled", !aEnable);
+}
+
+/**
+ * Enable and un-collapse all elements that are disable-on-readonly and
+ * collapse-on-readonly.
+ */
+function removeDisableAndCollapseOnReadonly() {
+ let enableElements = document.getElementsByAttribute("disable-on-readonly", "true");
+ for (let element of enableElements) {
+ element.removeAttribute("disabled");
+ }
+ let collapseElements = document.getElementsByAttribute("collapse-on-readonly", "true");
+ for (let element of collapseElements) {
+ element.removeAttribute("collapsed");
+ }
+}
+
+/**
+ * Handler to toggle toolbar visibility.
+ *
+ * @param {string} aToolbarId - The id of the toolbar node to toggle
+ * @param {string} aMenuitemId - The corresponding menuitem in the view menu
+ */
+function onCommandViewToolbar(aToolbarId, aMenuItemId) {
+ let toolbar = document.getElementById(aToolbarId);
+ let menuItem = document.getElementById(aMenuItemId);
+
+ if (!toolbar || !menuItem) {
+ return;
+ }
+
+ let toolbarCollapsed = toolbar.collapsed;
+
+ // toggle the checkbox
+ menuItem.setAttribute("checked", toolbarCollapsed);
+
+ // toggle visibility of the toolbar
+ toolbar.collapsed = !toolbarCollapsed;
+
+ Services.xulStore.persist(toolbar, "collapsed");
+ Services.xulStore.persist(menuItem, "checked");
+}
+
+/**
+ * Called after the customize toolbar dialog has been closed by the
+ * user. We need to restore the state of all buttons and commands of
+ * all customizable toolbars.
+ *
+ * @param {boolean} aToolboxChanged - When true the toolbox has changed
+ */
+function dialogToolboxCustomizeDone(aToolboxChanged) {
+ // Re-enable menu items (disabled during toolbar customization).
+ let menubarId = gTabmail ? "mail-menubar" : "event-menubar";
+ let menubar = document.getElementById(menubarId);
+ for (let menuitem of menubar.children) {
+ menuitem.removeAttribute("disabled");
+ }
+
+ // make sure our toolbar buttons have the correct enabled state restored to them...
+ document.commandDispatcher.updateCommands("itemCommands");
+
+ // Enable the toolbar context menu items
+ document.getElementById("cmd_customize").removeAttribute("disabled");
+
+ // Update privacy items to make sure the toolbarbutton's menupopup is set
+ // correctly
+ updatePrivacy(gConfig);
+}
+
+/**
+ * Handler to start the customize toolbar dialog for the event dialog's toolbar.
+ */
+function onCommandCustomize() {
+ // install the callback that handles what needs to be
+ // done after a toolbar has been customized.
+ let toolboxId = "event-toolbox";
+
+ let toolbox = document.getElementById(toolboxId);
+ toolbox.customizeDone = dialogToolboxCustomizeDone;
+
+ // Disable menu items during toolbar customization.
+ let menubarId = gTabmail ? "mail-menubar" : "event-menubar";
+ let menubar = document.getElementById(menubarId);
+ for (let menuitem of menubar.children) {
+ menuitem.setAttribute("disabled", true);
+ }
+
+ // Disable the toolbar context menu items
+ document.getElementById("cmd_customize").setAttribute("disabled", "true");
+
+ let wintype = document.documentElement.getAttribute("windowtype");
+ wintype = wintype.replace(/:/g, "");
+
+ window.openDialog(
+ "chrome://messenger/content/customizeToolbar.xhtml",
+ "CustomizeToolbar" + wintype,
+ "chrome,all,dependent",
+ document.getElementById(toolboxId), // toolbox dom node
+ false, // is mode toolbar yes/no?
+ null, // callback function
+ "dialog"
+ ); // name of this mode
+}
+
+/**
+ * Add menu items to the UI for attaching files using a cloud provider.
+ *
+ * @param {object[]} aItemObjects - Array of objects that each contain
+ * data to create a menuitem
+ */
+function loadCloudProviders(aItemObjects) {
+ /**
+ * Deletes any existing menu items in aParentNode that have a
+ * cloudProviderAccountKey attribute.
+ *
+ * @param {Node} aParentNode - A menupopup containing menu items
+ */
+ function deleteAlreadyExisting(aParentNode) {
+ for (let node of aParentNode.children) {
+ if (node.cloudProviderAccountKey) {
+ aParentNode.removeChild(node);
+ }
+ }
+ }
+
+ // Delete any existing menu items with a cloudProviderAccountKey,
+ // needed for the tab case to prevent duplicate menu items, and
+ // helps keep the menu items current.
+ let toolbarPopup = document.getElementById("button-attach-menupopup");
+ if (toolbarPopup) {
+ deleteAlreadyExisting(toolbarPopup);
+ }
+ let optionsPopup = document.getElementById("options-attachments-menupopup");
+ if (optionsPopup) {
+ deleteAlreadyExisting(optionsPopup);
+ }
+
+ for (let itemObject of aItemObjects) {
+ // Create a menu item.
+ let item = document.createXULElement("menuitem");
+ item.setAttribute("label", itemObject.label);
+ item.setAttribute("observes", "cmd_attach_cloud");
+ item.setAttribute(
+ "oncommand",
+ "attachFileByAccountKey(event.target.cloudProviderAccountKey); event.stopPropagation();"
+ );
+
+ if (itemObject.class) {
+ item.setAttribute("class", itemObject.class);
+ item.setAttribute("image", itemObject.image);
+ }
+
+ // Add the menu item to the UI.
+ if (toolbarPopup) {
+ toolbarPopup.appendChild(item.cloneNode(true)).cloudProviderAccountKey =
+ itemObject.cloudProviderAccountKey;
+ }
+ if (optionsPopup) {
+ // This one doesn't need to clone, just use the item itself.
+ optionsPopup.appendChild(item).cloudProviderAccountKey = itemObject.cloudProviderAccountKey;
+ }
+ }
+}
+
+/**
+ * Send a message to attach a file using a given cloud provider,
+ * to be identified by the cloud provider's accountKey.
+ *
+ * @param {string} aAccountKey - The accountKey for a cloud provider
+ */
+function attachFileByAccountKey(aAccountKey) {
+ sendMessage({ command: "attachFileByAccountKey", accountKey: aAccountKey });
+}
+
+/**
+ * Updates the save controls depending on whether the event has attendees
+ *
+ * @param {boolean} aSendNotSave
+ */
+function updateSaveControls(aSendNotSave) {
+ if (window.calItemSaveControls && window.calItemSaveControls.state == aSendNotSave) {
+ return;
+ }
+
+ let saveBtn = document.getElementById("button-save");
+ let saveandcloseBtn = document.getElementById("button-saveandclose");
+ let saveMenu =
+ document.getElementById("item-save-menuitem") ||
+ document.getElementById("calendar-save-menuitem");
+ let saveandcloseMenu =
+ document.getElementById("item-saveandclose-menuitem") ||
+ document.getElementById("calendar-save-and-close-menuitem");
+
+ // we store the initial label and tooltip values to be able to reset later
+ if (!window.calItemSaveControls) {
+ window.calItemSaveControls = {
+ state: false,
+ saveMenu: { label: saveMenu.label },
+ saveandcloseMenu: { label: saveandcloseMenu.label },
+ saveBtn: null,
+ saveandcloseBtn: null,
+ };
+ // we need to check for each button whether it exists since toolbarbuttons
+ // can be removed by customizing
+ if (saveBtn) {
+ window.window.calItemSaveControls.saveBtn = {
+ label: saveBtn.label,
+ tooltiptext: saveBtn.tooltip,
+ };
+ }
+ if (saveandcloseBtn) {
+ window.window.calItemSaveControls.saveandcloseBtn = {
+ label: saveandcloseBtn.label,
+ tooltiptext: saveandcloseBtn.tooltip,
+ };
+ }
+ }
+
+ // we update labels and tooltips but leave accesskeys as they are
+ window.calItemSaveControls.state = aSendNotSave;
+ if (aSendNotSave) {
+ if (saveBtn) {
+ saveBtn.label = cal.l10n.getString("calendar-event-dialog", "saveandsendButtonLabel");
+ saveBtn.tooltiptext = cal.l10n.getString("calendar-event-dialog", "saveandsendButtonTooltip");
+ saveBtn.setAttribute("mode", "send");
+ }
+ if (saveandcloseBtn) {
+ saveandcloseBtn.label = cal.l10n.getString(
+ "calendar-event-dialog",
+ "sendandcloseButtonLabel"
+ );
+ saveandcloseBtn.tooltiptext = cal.l10n.getString(
+ "calendar-event-dialog",
+ "sendandcloseButtonTooltip"
+ );
+ saveandcloseBtn.setAttribute("mode", "send");
+ }
+ saveMenu.label = cal.l10n.getString("calendar-event-dialog", "saveandsendMenuLabel");
+ saveandcloseMenu.label = cal.l10n.getString("calendar-event-dialog", "sendandcloseMenuLabel");
+ } else {
+ if (saveBtn) {
+ saveBtn.label = window.calItemSaveControls.saveBtn.label;
+ saveBtn.tooltiptext = window.calItemSaveControls.saveBtn.tooltip;
+ saveBtn.removeAttribute("mode");
+ }
+ if (saveandcloseBtn) {
+ saveandcloseBtn.label = window.calItemSaveControls.saveandcloseBtn.label;
+ saveandcloseBtn.tooltiptext = window.calItemSaveControls.saveandcloseBtn.tooltip;
+ saveandcloseBtn.removeAttribute("mode");
+ }
+ saveMenu.label = window.calItemSaveControls.saveMenu.label;
+ saveandcloseMenu.label = window.calItemSaveControls.saveandcloseMenu.label;
+ }
+}