/* 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 taskDetailsView, sendMailToOrganizer, taskViewCopyLink */ /* import-globals-from ../../../mail/base/content/mailCore.js */ /* import-globals-from item-editing/calendar-item-editing.js */ /* import-globals-from ../src/calApplicationUtils.js */ /* import-globals-from calendar-ui-utils.js */ var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); var { recurrenceRule2String } = ChromeUtils.import( "resource:///modules/calendar/calRecurrenceUtils.jsm" ); var { AppConstants } = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); var taskDetailsView = { /** * Task Details Events * * XXXberend Please document this function, possibly also consolidate since * its the only function in taskDetailsView. */ onSelect(event) { function displayElement(id, flag) { document.getElementById(id).hidden = !flag; return flag; } let dateFormatter = cal.dtz.formatter; let item = document.getElementById("calendar-task-tree").currentTask; if ( displayElement("calendar-task-details-container", item != null) && displayElement("calendar-task-view-splitter", item != null) ) { document.getElementById("calendar-task-details-title-row").toggleAttribute("hidden", false); document.getElementById("calendar-task-details-title").textContent = item.title ? item.title.replace(/\n/g, " ") : ""; let organizer = item.organizer; if ( !document .getElementById("calendar-task-details-organizer-row") .toggleAttribute("hidden", !organizer) ) { let name = organizer.commonName; if (!name || name.length <= 0) { if (organizer.id && organizer.id.length) { name = organizer.id; let re = new RegExp("^mailto:(.*)", "i"); let matches = re.exec(name); if (matches) { name = matches[1]; } } } if ( !document .getElementById("calendar-task-details-organizer-row") .toggleAttribute("hidden", !name) ) { document.getElementById("calendar-task-details-organizer").textContent = name; } } let priority = 0; if (item.calendar.getProperty("capabilities.priority.supported")) { priority = parseInt(item.priority, 10); } document .getElementById("calendar-task-details-priority-row") .toggleAttribute("hidden", priority == 0); displayElement("calendar-task-details-priority-low", priority >= 6 && priority <= 9); displayElement("calendar-task-details-priority-normal", priority == 5); displayElement("calendar-task-details-priority-high", priority >= 1 && priority <= 4); let status = item.getProperty("STATUS"); if ( !document .getElementById("calendar-task-details-status-row") .toggleAttribute("hidden", !status) ) { let statusDetails = document.getElementById("calendar-task-details-status"); switch (status) { case "NEEDS-ACTION": { statusDetails.textContent = cal.l10n.getCalString("taskDetailsStatusNeedsAction"); break; } case "IN-PROCESS": { let percent = 0; let property = item.getProperty("PERCENT-COMPLETE"); if (property != null) { percent = parseInt(property, 10); } statusDetails.textContent = cal.l10n.getCalString("taskDetailsStatusInProgress", [ percent, ]); break; } case "COMPLETED": { if (item.completedDate) { let completedDate = item.completedDate.getInTimezone(cal.dtz.defaultTimezone); statusDetails.textContent = cal.l10n.getCalString("taskDetailsStatusCompletedOn", [ dateFormatter.formatDateTime(completedDate), ]); } break; } case "CANCELLED": { statusDetails.textContent = cal.l10n.getCalString("taskDetailsStatusCancelled"); break; } default: { document .getElementById("calendar-task-details-status-row") .toggleAttribute("hidden", true); break; } } } let categories = item.getCategories(); if ( !document .getElementById("calendar-task-details-category-row") .toggleAttribute("hidden", categories.length == 0) ) { document.getElementById("calendar-task-details-category").textContent = categories.join(", "); } let taskStartDate = item[cal.dtz.startDateProp(item)]; if (taskStartDate) { document.getElementById("task-start-date").textContent = cal.dtz.getStringForDateTime(taskStartDate); } document.getElementById("task-start-row").toggleAttribute("hidden", !taskStartDate); let taskDueDate = item[cal.dtz.endDateProp(item)]; if (taskDueDate) { document.getElementById("task-due-date").textContent = cal.dtz.getStringForDateTime(taskDueDate); } document.getElementById("task-due-row").toggleAttribute("hidden", !taskDueDate); let parentItem = item; if (parentItem.parentItem != parentItem) { // XXXdbo Didn't we want to get rid of these checks? parentItem = parentItem.parentItem; } let recurrenceInfo = parentItem.recurrenceInfo; let recurStart = parentItem.recurrenceStartDate; if ( !document .getElementById("calendar-task-details-repeat-row") .toggleAttribute("hidden", !recurrenceInfo || !recurStart) ) { let kDefaultTimezone = cal.dtz.defaultTimezone; let startDate = recurStart.getInTimezone(kDefaultTimezone); let endDate = item.dueDate ? item.dueDate.getInTimezone(kDefaultTimezone) : null; let detailsString = recurrenceRule2String( recurrenceInfo, startDate, endDate, startDate.isDate ); if (detailsString) { let rpv = document.getElementById("calendar-task-details-repeat"); rpv.textContent = detailsString.split("\n").join(" "); } } let iframe = document.getElementById("calendar-task-details-description"); let docFragment = cal.view.textToHtmlDocumentFragment( item.descriptionText, iframe.contentDocument, item.descriptionHTML ); // Make any links open in the user's default browser, not in Thunderbird. for (let anchor of docFragment.querySelectorAll("a")) { anchor.addEventListener("click", function (event) { event.preventDefault(); if (event.isTrusted) { launchBrowser(anchor.getAttribute("href"), event); } }); } iframe.contentDocument.body.replaceChildren(docFragment); let link = iframe.contentDocument.createElement("link"); link.rel = "stylesheet"; link.href = "chrome://messenger/skin/shared/editorContent.css"; iframe.contentDocument.head.replaceChildren(link); let attachmentRows = document.getElementById("calendar-task-details-attachment-rows"); while (attachmentRows.lastChild) { attachmentRows.lastChild.remove(); } let attachments = item.getAttachments(); if (displayElement("calendar-task-details-attachment-row", attachments.length > 0)) { displayElement("calendar-task-details-attachment-rows", true); for (let attachment of attachments) { let url = attachment.calIAttachment.uri.spec; let urlLabel = document.createXULElement("label"); urlLabel.setAttribute("class", "text-link"); urlLabel.setAttribute("value", url); urlLabel.setAttribute("tooltiptext", url); urlLabel.setAttribute("crop", "end"); urlLabel.setAttribute("onclick", "if (event.button != 2) launchBrowser(this.value);"); urlLabel.setAttribute("context", "taskview-link-context-menu"); attachmentRows.appendChild(urlLabel); } } } }, loadCategories() { let categoryPopup = document.getElementById("task-actions-category-popup"); let item = document.getElementById("calendar-task-tree").currentTask; let itemCategories = item.getCategories(); let categoryList = cal.category.fromPrefs(); for (let cat of itemCategories) { if (!categoryList.includes(cat)) { categoryList.push(cat); } } cal.l10n.sortArrayByLocaleCollator(categoryList); let maxCount = item.calendar.getProperty("capabilities.categories.maxCount"); while (categoryPopup.childElementCount > 2) { categoryPopup.lastChild.remove(); } if (maxCount == 1) { let menuitem = document.createXULElement("menuitem"); menuitem.setAttribute("class", "menuitem-iconic"); menuitem.setAttribute("label", cal.l10n.getCalString("None")); menuitem.setAttribute("type", "radio"); if (itemCategories.length === 0) { menuitem.setAttribute("checked", "true"); } categoryPopup.appendChild(menuitem); } for (let cat of categoryList) { let menuitem = document.createXULElement("menuitem"); menuitem.setAttribute("class", "menuitem-iconic calendar-category"); menuitem.setAttribute("label", cat); menuitem.setAttribute("value", cat); menuitem.setAttribute("type", maxCount === null || maxCount > 1 ? "checkbox" : "radio"); if (itemCategories.includes(cat)) { menuitem.setAttribute("checked", "true"); } categoryPopup.appendChild(menuitem); } }, saveCategories(event) { let categoryPopup = document.getElementById("task-actions-category-popup"); let item = document.getElementById("calendar-task-tree").currentTask; let oldCategories = item.getCategories(); let categories = Array.from( categoryPopup.querySelectorAll("menuitem.calendar-category[checked]"), menuitem => menuitem.value ); let unchanged = oldCategories.length == categories.length; for (let i = 0; unchanged && i < categories.length; i++) { unchanged = oldCategories[i] == categories[i]; } if (!unchanged) { let newItem = item.clone(); newItem.setCategories(categories); doTransaction("modify", newItem, newItem.calendar, item, null); return false; } return true; }, categoryTextboxKeypress(event) { let category = event.target.value; let categoryPopup = document.getElementById("task-actions-category-popup"); switch (event.key) { case " ": { // The menu popup seems to eat this keypress. let start = event.target.selectionStart; event.target.value = category.substring(0, start) + " " + category.substring(event.target.selectionEnd); event.target.selectionStart = event.target.selectionEnd = start + 1; return; } case "Tab": case "ArrowDown": case "ArrowUp": { event.target.blur(); event.preventDefault(); let key = event.key == "ArrowUp" ? "ArrowUp" : "ArrowDown"; categoryPopup.dispatchEvent(new KeyboardEvent("keydown", { key })); categoryPopup.dispatchEvent(new KeyboardEvent("keyup", { key })); return; } case "Escape": if (category) { event.target.value = ""; } else { categoryPopup.hidePopup(); } event.preventDefault(); return; case "Enter": category = category.trim(); if (category != "") { break; } return; default: { return; } } event.preventDefault(); let categoryList = categoryPopup.querySelectorAll("menuitem.calendar-category"); let categories = Array.from(categoryList, cat => cat.getAttribute("value")); let modified = false; let newIndex = categories.indexOf(category); if (newIndex > -1) { if (categoryList[newIndex].getAttribute("checked") != "true") { categoryList[newIndex].setAttribute("checked", "true"); modified = true; } } else { const localeCollator = new Intl.Collator(); let compare = localeCollator.compare; newIndex = cal.data.binaryInsert(categories, category, compare, true); let item = document.getElementById("calendar-task-tree").currentTask; let maxCount = item.calendar.getProperty("capabilities.categories.maxCount"); let menuitem = document.createXULElement("menuitem"); menuitem.setAttribute("class", "menuitem-iconic calendar-category"); menuitem.setAttribute("label", category); menuitem.setAttribute("value", category); menuitem.setAttribute("type", maxCount === null || maxCount > 1 ? "checkbox" : "radio"); menuitem.setAttribute("checked", true); categoryPopup.insertBefore(menuitem, categoryList[newIndex]); modified = true; } if (modified) { categoryList = categoryPopup.querySelectorAll("menuitem.calendar-category[checked]"); categories = Array.from(categoryList, cat => cat.getAttribute("value")); let item = document.getElementById("calendar-task-tree").currentTask; let newItem = item.clone(); newItem.setCategories(categories); doTransaction("modify", newItem, newItem.calendar, item, null); } event.target.value = ""; }, }; /** * Updates the currently applied filter for the task view and refreshes the task * tree. * * @param {string} [filter] - The filter name to set. */ function taskViewUpdate(filter) { if (!filter) { let taskFilterGroup = document.getElementById("task-tree-filtergroup"); filter = taskFilterGroup.value || "all"; } let tree = document.getElementById("calendar-task-tree"); let oldFilter = tree.getAttribute("filterValue"); if (filter != oldFilter) { tree.setAttribute("filterValue", filter); document .querySelectorAll( `menuitem[command="calendar_task_filter_command"][type="radio"], toolbarbutton[command="calendar_task_filter_command"][type="radio"]` ) .forEach(item => { if (item.getAttribute("value") == filter) { item.setAttribute("checked", "true"); } else { item.removeAttribute("checked"); } }); let radio = document.querySelector( `radio[command="calendar_task_filter_command"][value="${filter}"]` ); if (radio) { radio.radioGroup.selectedItem = radio; } } tree.updateFilter(filter); } /** * Prepares a dialog to send an email to the organizer of the currently selected * task in the task view. * * XXX We already have a function with this name in the event dialog. Either * consolidate or make name more clear. */ function sendMailToOrganizer() { let item = document.getElementById("calendar-task-tree").currentTask; if (item != null) { let organizer = item.organizer; let email = cal.email.getAttendeeEmail(organizer, true); let emailSubject = cal.l10n.getString("calendar-event-dialog", "emailSubjectReply", [ item.title, ]); let identity = item.calendar.getProperty("imip.identity"); cal.email.sendTo(email, emailSubject, null, identity); } } // Install event listeners for the display deck change and connect task tree to filter field function taskViewOnLoad() { let calendarDisplayBox = document.getElementById("calendarDisplayBox"); let tree = document.getElementById("calendar-task-tree"); if (calendarDisplayBox && tree) { tree.textFilterField = "task-text-filter-field"; // setup the platform-dependent placeholder for the text filter field let textFilter = document.getElementById("task-text-filter-field"); if (textFilter) { let base = textFilter.getAttribute("emptytextbase"); let keyLabel = textFilter.getAttribute( AppConstants.platform == "macosx" ? "keyLabelMac" : "keyLabelNonMac" ); textFilter.setAttribute("placeholder", base.replace("#1", keyLabel)); textFilter.value = ""; } taskViewUpdate(); } // Setup customizeDone handler for the task action toolbox. let toolbox = document.getElementById("task-actions-toolbox"); toolbox.customizeDone = function (aEvent) { MailToolboxCustomizeDone(aEvent, "CustomizeTaskActionsToolbar"); }; Services.obs.notifyObservers(window, "calendar-taskview-startup-done"); } /** * Copy the value of the given link node to the clipboard * * @param linkNode The node containing the value to copy to the clipboard */ function taskViewCopyLink(linkNode) { if (linkNode) { let linkAddress = linkNode.value; let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); clipboard.copyString(linkAddress); } }