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 --- .../base/content/widgets/mouseoverPreviews.js | 439 +++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100644 comm/calendar/base/content/widgets/mouseoverPreviews.js (limited to 'comm/calendar/base/content/widgets/mouseoverPreviews.js') diff --git a/comm/calendar/base/content/widgets/mouseoverPreviews.js b/comm/calendar/base/content/widgets/mouseoverPreviews.js new file mode 100644 index 0000000000..38e5c1e24f --- /dev/null +++ b/comm/calendar/base/content/widgets/mouseoverPreviews.js @@ -0,0 +1,439 @@ +/* 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/. */ + +/** + * Code which generates event and task (todo) preview tooltips/titletips + * when the mouse hovers over either the event list, the task list, or + * an event or task box in one of the grid views. + * + * (Portions of this code were previously in calendar.js and unifinder.js, + * some of it duplicated.) + */ + +/* exported onMouseOverItem, showToolTip, getPreviewForItem, + getEventStatusString, getToDoStatusString */ + +/* import-globals-from ../calendar-ui-utils.js */ + +/** + * PUBLIC: This changes the mouseover preview based on the start and end dates + * of an occurrence of a (one-time or recurring) calEvent or calToDo. + * Used by all grid views. + */ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +/** + * PUBLIC: Displays a tooltip with details when hovering over an item in the views + * + * @param {DOMEvent} occurrenceBoxMouseEvent the triggering event + * @returns {boolean} true, if the tooltip is displayed + */ +function onMouseOverItem(occurrenceBoxMouseEvent) { + if ("occurrence" in occurrenceBoxMouseEvent.currentTarget) { + // occurrence of repeating event or todo + let occurrence = occurrenceBoxMouseEvent.currentTarget.occurrence; + const toolTip = document.getElementById("itemTooltip"); + return showToolTip(toolTip, occurrence); + } + return false; +} + +/** + * PUBLIC: Displays a tooltip for a given item + * + * @param {Node} aTooltip the node to hold the tooltip + * @param {CalIEvent|calIToDo} aItem the item to create the tooltip for + * @returns {boolean} true, if the tooltip is displayed + */ +function showToolTip(aToolTip, aItem) { + if (aItem) { + let holderBox = getPreviewForItem(aItem); + if (holderBox) { + while (aToolTip.lastChild) { + aToolTip.lastChild.remove(); + } + aToolTip.appendChild(holderBox); + return true; + } + } + return false; +} + +/** + * PUBLIC: Called when a user hovers over a todo element and the text for the + * mouse over is changed. + * + * @param {calIToDo} toDoItem - the item to create the preview for + * @param {boolean} aIsTooltip enabled if used for tooltip composition (default) + */ +function getPreviewForItem(aItem, aIsTooltip = true) { + if (aItem.isEvent()) { + return getPreviewForEvent(aItem, aIsTooltip); + } else if (aItem.isTodo()) { + return getPreviewForTask(aItem, aIsTooltip); + } + return null; +} + +/** + * PUBLIC: Returns the string for status (none), Tentative, Confirmed, or + * Cancelled for a given event + * + * @param {calIEvent} aEvent The event + * @returns {string} The string for the status property of the event + */ +function getEventStatusString(aEvent) { + switch (aEvent.status) { + // Event status value keywords are specified in RFC2445sec4.8.1.11 + case "TENTATIVE": + return cal.l10n.getCalString("statusTentative"); + case "CONFIRMED": + return cal.l10n.getCalString("statusConfirmed"); + case "CANCELLED": + return cal.l10n.getCalString("eventStatusCancelled"); + default: + return ""; + } +} + +/** + * PUBLIC: Returns the string for status (none), NeedsAction, InProcess, + * Cancelled, orCompleted for a given ToDo + * + * @param {calIToDo} aToDo The ToDo + * @returns {string} The string for the status property of the event + */ +function getToDoStatusString(aToDo) { + switch (aToDo.status) { + // Todo status keywords are specified in RFC2445sec4.8.1.11 + case "NEEDS-ACTION": + return cal.l10n.getCalString("statusNeedsAction"); + case "IN-PROCESS": + return cal.l10n.getCalString("statusInProcess"); + case "CANCELLED": + return cal.l10n.getCalString("todoStatusCancelled"); + case "COMPLETED": + return cal.l10n.getCalString("statusCompleted"); + default: + return ""; + } +} + +/** + * PRIVATE: Called when a user hovers over a todo element and the text for the + * mouse overis changed. + * + * @param {calIToDo} toDoItem - the item to create the preview for + * @param {boolean} aIsTooltip enabled if used for tooltip composition (default) + */ +function getPreviewForTask(toDoItem, aIsTooltip = true) { + if (toDoItem) { + const vbox = document.createXULElement("vbox"); + vbox.setAttribute("class", "tooltipBox"); + if (aIsTooltip) { + // tooltip appears above or below pointer, so may have as little as + // one half the screen height available (avoid top going off screen). + vbox.style.maxHeight = Math.floor(screen.height / 2); + } else { + vbox.setAttribute("flex", "1"); + } + boxInitializeHeaderTable(vbox); + + let hasHeader = false; + + if (toDoItem.title) { + boxAppendLabeledText(vbox, "tooltipTitle", toDoItem.title); + hasHeader = true; + } + + let location = toDoItem.getProperty("LOCATION"); + if (location) { + boxAppendLabeledText(vbox, "tooltipLocation", location); + hasHeader = true; + } + + // First try to get calendar name appearing in tooltip + if (toDoItem.calendar.name) { + let calendarNameString = toDoItem.calendar.name; + boxAppendLabeledText(vbox, "tooltipCalName", calendarNameString); + } + + if (toDoItem.entryDate && toDoItem.entryDate.isValid) { + boxAppendLabeledDateTime(vbox, "tooltipStart", toDoItem.entryDate); + hasHeader = true; + } + + if (toDoItem.dueDate && toDoItem.dueDate.isValid) { + boxAppendLabeledDateTime(vbox, "tooltipDue", toDoItem.dueDate); + hasHeader = true; + } + + if (toDoItem.priority && toDoItem.priority != 0) { + let priorityInteger = parseInt(toDoItem.priority, 10); + let priorityString; + + // These cut-offs should match calendar-event-dialog.js + if (priorityInteger >= 1 && priorityInteger <= 4) { + priorityString = cal.l10n.getCalString("highPriority"); + } else if (priorityInteger == 5) { + priorityString = cal.l10n.getCalString("normalPriority"); + } else { + priorityString = cal.l10n.getCalString("lowPriority"); + } + boxAppendLabeledText(vbox, "tooltipPriority", priorityString); + hasHeader = true; + } + + if (toDoItem.status && toDoItem.status != "NONE") { + let status = getToDoStatusString(toDoItem); + boxAppendLabeledText(vbox, "tooltipStatus", status); + hasHeader = true; + } + + if ( + toDoItem.status != null && + toDoItem.percentComplete != 0 && + toDoItem.percentComplete != 100 + ) { + boxAppendLabeledText(vbox, "tooltipPercent", String(toDoItem.percentComplete) + "%"); + hasHeader = true; + } else if (toDoItem.percentComplete == 100) { + if (toDoItem.completedDate == null) { + boxAppendLabeledText(vbox, "tooltipPercent", "100%"); + } else { + boxAppendLabeledDateTime(vbox, "tooltipCompleted", toDoItem.completedDate); + } + hasHeader = true; + } + + let description = toDoItem.descriptionText; + if (description) { + // display wrapped description lines like body of message below headers + if (hasHeader) { + boxAppendBodySeparator(vbox); + } + boxAppendBody(vbox, description, aIsTooltip); + } + + return vbox; + } + return null; +} + +/** + * PRIVATE: Called when mouse moves over a different, or when mouse moves over + * event in event list. The instStartDate is date of instance displayed at event + * box (recurring or multiday events may be displayed by more than one event box + * for different days), or null if should compute next instance from now. + * + * @param {calIEvent} aEvent - the item to create the preview for + * @param {boolean} aIsTooltip enabled if used for tooltip composition (default) + */ +function getPreviewForEvent(aEvent, aIsTooltip = true) { + let event = aEvent; + const vbox = document.createXULElement("vbox"); + vbox.setAttribute("class", "tooltipBox"); + if (aIsTooltip) { + // tooltip appears above or below pointer, so may have as little as + // one half the screen height available (avoid top going off screen). + vbox.maxHeight = Math.floor(screen.height / 2); + } else { + vbox.setAttribute("flex", "1"); + } + boxInitializeHeaderTable(vbox); + + if (event) { + if (event.title) { + boxAppendLabeledText(vbox, "tooltipTitle", aEvent.title); + } + + let location = event.getProperty("LOCATION"); + if (location) { + boxAppendLabeledText(vbox, "tooltipLocation", location); + } + if (!(event.startDate && event.endDate)) { + // Event may be recurrent event. If no displayed instance specified, + // use next instance, or previous instance if no next instance. + event = getCurrentNextOrPreviousRecurrence(event); + } + boxAppendLabeledDateTimeInterval(vbox, "tooltipDate", event); + + // First try to get calendar name appearing in tooltip + if (event.calendar.name) { + let calendarNameString = event.calendar.name; + boxAppendLabeledText(vbox, "tooltipCalName", calendarNameString); + } + + if (event.status && event.status != "NONE") { + let statusString = getEventStatusString(event); + boxAppendLabeledText(vbox, "tooltipStatus", statusString); + } + + if (event.organizer && event.getAttendees().length > 0) { + let organizer = event.organizer; + boxAppendLabeledText(vbox, "tooltipOrganizer", organizer); + } + + let description = event.descriptionText; + if (description) { + boxAppendBodySeparator(vbox); + // display wrapped description lines, like body of message below headers + boxAppendBody(vbox, description, aIsTooltip); + } + return vbox; + } + return null; +} + +/** + * PRIVATE: Append a separator, a thin space between header and body. + * + * @param {Node} vbox box to which to append separator. + */ +function boxAppendBodySeparator(vbox) { + const separator = document.createXULElement("separator"); + separator.setAttribute("class", "tooltipBodySeparator"); + vbox.appendChild(separator); +} + +/** + * PRIVATE: Append description to box for body text. Rendered as HTML. + * Indentation and line breaks are preserved. + * + * @param {Node} box - Box to which to append the body. + * @param {string} textString - Text of the body. + * @param {boolean} aIsTooltip - True for "tooltip" and false for "conflict-dialog" case. + */ +function boxAppendBody(box, textString, aIsTooltip) { + let type = aIsTooltip ? "description" : "vbox"; + let xulDescription = document.createXULElement(type); + xulDescription.setAttribute("class", "tooltipBody"); + if (!aIsTooltip) { + xulDescription.setAttribute("flex", "1"); + } + let docFragment = cal.view.textToHtmlDocumentFragment(textString, document); + xulDescription.appendChild(docFragment); + box.appendChild(xulDescription); +} + +/** + * PRIVATE: Use dateFormatter to format date and time, + * and to header table append a row containing localized Label: date. + * + * @param {Node} box The node to add the date label to + * @param {string} labelProperty The label + * @param {calIDateTime} date - The datetime object to format and add + */ +function boxAppendLabeledDateTime(box, labelProperty, date) { + date = date.getInTimezone(cal.dtz.defaultTimezone); + let formattedDateTime = cal.dtz.formatter.formatDateTime(date); + boxAppendLabeledText(box, labelProperty, formattedDateTime); +} + +/** + * PRIVATE: Use dateFormatter to format date and time interval, + * and to header table append a row containing localized Label: interval. + * + * @param box contains header table. + * @param labelProperty name of property for localized field label. + * @param item the event or task + */ +function boxAppendLabeledDateTimeInterval(box, labelProperty, item) { + let dateString = cal.dtz.formatter.formatItemInterval(item); + boxAppendLabeledText(box, labelProperty, dateString); +} + +/** + * PRIVATE: create empty 2-column table for header fields, and append it to box. + * + * @param {Node} box The node to create a column table for + */ +function boxInitializeHeaderTable(box) { + let table = document.createElementNS("http://www.w3.org/1999/xhtml", "table"); + table.setAttribute("class", "tooltipHeaderTable"); + box.appendChild(table); +} + +/** + * PRIVATE: To headers table, append a row containing Label: value, where label + * is localized text for labelProperty. + * + * @param box box containing headers table + * @param labelProperty name of property for localized name of header + * @param textString value of header field. + */ +function boxAppendLabeledText(box, labelProperty, textString) { + let labelText = cal.l10n.getCalString(labelProperty); + let table = box.querySelector("table"); + let row = document.createElementNS("http://www.w3.org/1999/xhtml", "tr"); + + row.appendChild(createTooltipHeaderLabel(labelText)); + row.appendChild(createTooltipHeaderDescription(textString)); + + table.appendChild(row); +} + +/** + * PRIVATE: Creates an element for field label (for header table) + * + * @param {string} text The text to display in the node + * @returns {Node} The node + */ +function createTooltipHeaderLabel(text) { + let labelCell = document.createElementNS("http://www.w3.org/1999/xhtml", "th"); + labelCell.setAttribute("class", "tooltipHeaderLabel"); + labelCell.textContent = text; + return labelCell; +} + +/** + * PRIVATE: Creates an element for field value (for header table) + * + * @param {string} text The text to display in the node + * @returns {Node} The node + */ +function createTooltipHeaderDescription(text) { + let descriptionCell = document.createElementNS("http://www.w3.org/1999/xhtml", "td"); + descriptionCell.setAttribute("class", "tooltipHeaderDescription"); + descriptionCell.textContent = text; + return descriptionCell; +} + +/** + * PRIVATE: If now is during an occurrence, return the occurrence. If now is + * before an occurrence, return the next occurrence or otherwise the previous + * occurrence. + * + * @param {calIEvent} calendarEvent The text to display in the node + * @returns {mixed} Returns a calIDateTime for the detected + * occurrence or calIEvent, if this is a + * non-recurring event + */ +function getCurrentNextOrPreviousRecurrence(calendarEvent) { + if (!calendarEvent.recurrenceInfo) { + return calendarEvent; + } + + let dur = calendarEvent.duration.clone(); + dur.isNegative = true; + + // To find current event when now is during event, look for occurrence + // starting duration ago. + let probeTime = cal.dtz.now(); + probeTime.addDuration(dur); + + let occ = calendarEvent.recurrenceInfo.getNextOccurrence(probeTime); + + if (!occ) { + let occs = calendarEvent.recurrenceInfo.getOccurrences( + calendarEvent.startDate, + probeTime, + 0, + {} + ); + occ = occs[occs.length - 1]; + } + return occ; +} -- cgit v1.2.3