summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/content/widgets/calendar-invitation-panel.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/calendar/base/content/widgets/calendar-invitation-panel.js
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--comm/calendar/base/content/widgets/calendar-invitation-panel.js799
1 files changed, 799 insertions, 0 deletions
diff --git a/comm/calendar/base/content/widgets/calendar-invitation-panel.js b/comm/calendar/base/content/widgets/calendar-invitation-panel.js
new file mode 100644
index 0000000000..aa2be5e29f
--- /dev/null
+++ b/comm/calendar/base/content/widgets/calendar-invitation-panel.js
@@ -0,0 +1,799 @@
+/* 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/. */
+
+/* globals cal, openLinkExternally, MozXULElement, MozElements */
+
+"use strict";
+
+// Wrap in a block to prevent leaking to window scope.
+{
+ var { recurrenceRule2String } = ChromeUtils.import(
+ "resource:///modules/calendar/calRecurrenceUtils.jsm"
+ );
+
+ // calendar-invitation-panel.ftl is not globally loaded until now.
+ MozXULElement.insertFTLIfNeeded("calendar/calendar-invitation-panel.ftl");
+
+ const PROPERTY_REMOVED = -1;
+ const PROPERTY_UNCHANGED = 0;
+ const PROPERTY_ADDED = 1;
+ const PROPERTY_MODIFIED = 2;
+
+ /**
+ * InvitationPanel displays the details of an iTIP event invitation in an
+ * interactive panel.
+ */
+ class InvitationPanel extends HTMLElement {
+ static MODE_NEW = "New";
+ static MODE_ALREADY_PROCESSED = "Processed";
+ static MODE_UPDATE_MAJOR = "UpdateMajor";
+ static MODE_UPDATE_MINOR = "UpdateMinor";
+ static MODE_CANCELLED = "Cancelled";
+ static MODE_CANCELLED_NOT_FOUND = "CancelledNotFound";
+
+ /**
+ * Used to retrieve a property value from an event.
+ *
+ * @callback GetValue
+ * @param {calIEvent} event
+ * @returns {string}
+ */
+
+ /**
+ * A function used to make a property value visible in to the user.
+ *
+ * @callback PropertyShow
+ * @param {HTMLElement} node - The element responsible for displaying the
+ * value.
+ * @param {string} value - The value of property to display.
+ * @param {string} oldValue - The previous value of the property if the
+ * there is a prior copy of the event.
+ * @param {calIEvent} item - The event item the property belongs to.
+ * @param {string} oldItem - The prior version of the event if there is one.
+ */
+
+ /**
+ * @typedef {Object} InvitationPropertyDescriptor
+ * @property {string} id - The id of the HTMLElement that displays
+ * the property.
+ * @property {GetValue} getValue - Function used to retrieve the displayed
+ * value of the property from the item.
+ * @property {boolean?} isList - Indicates the value of the property is a
+ * list.
+ * @property {PropertyShow?} show - Function to use to display the property
+ * value if it is not a list.
+ */
+
+ /**
+ * A static list of objects used in determining how to display each of the
+ * properties.
+ *
+ * @type {PropertyDescriptor[]}
+ */
+ static propertyDescriptors = [
+ {
+ id: "when",
+ getValue(item) {
+ let tz = cal.dtz.defaultTimezone;
+ let startDate = item.startDate?.getInTimezone(tz) ?? null;
+ let endDate = item.endDate?.getInTimezone(tz) ?? null;
+ return `${startDate.icalString}-${endDate?.icalString}`;
+ },
+ show(intervalNode, newValue, oldValue, item) {
+ intervalNode.item = item;
+ },
+ },
+ {
+ id: "recurrence",
+ getValue(item) {
+ let parent = item.parentItem;
+ if (!parent.recurrenceInfo) {
+ return null;
+ }
+ return recurrenceRule2String(parent.recurrenceInfo, parent.recurrenceStartDate);
+ },
+ show(recurrence, value) {
+ recurrence.appendChild(document.createTextNode(value));
+ },
+ },
+ {
+ id: "location",
+ getValue(item) {
+ return item.getProperty("LOCATION");
+ },
+ show(location, value) {
+ location.appendChild(cal.view.textToHtmlDocumentFragment(value, document));
+ },
+ },
+ {
+ id: "summary",
+ getValue(item) {
+ return item.getAttendees();
+ },
+ show(summary, value) {
+ summary.attendees = value;
+ },
+ },
+ {
+ id: "attendees",
+ isList: true,
+ getValue(item) {
+ return item.getAttendees();
+ },
+ },
+ {
+ id: "attachments",
+ isList: true,
+ getValue(item) {
+ return item.getAttachments();
+ },
+ },
+ {
+ id: "description",
+ getValue(item) {
+ return item.descriptionText;
+ },
+ show(description, value) {
+ description.appendChild(cal.view.textToHtmlDocumentFragment(value, document));
+ },
+ },
+ ];
+
+ /**
+ * mode determines how the UI should display the received invitation. It
+ * must be set to one of the MODE_* constants, defaults to MODE_NEW.
+ *
+ * @type {string}
+ */
+ mode = InvitationPanel.MODE_NEW;
+
+ /**
+ * A previous copy of the event item if found on an existing calendar.
+ *
+ * @type {calIEvent?}
+ */
+ foundItem;
+
+ /**
+ * The event item to be displayed.
+ *
+ * @type {calIEvent?}
+ */
+ item;
+
+ constructor(id) {
+ super();
+ this.attachShadow({ mode: "open" });
+ document.l10n.connectRoot(this.shadowRoot);
+
+ let link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "chrome://calendar/skin/shared/widgets/calendar-invitation-panel.css";
+ this.shadowRoot.appendChild(link);
+ }
+
+ /**
+ * Compares two like property values, an old and a new one, to determine
+ * what type of change has been made (if any).
+ *
+ * @param {any} oldValue
+ * @param {any} newValue
+ * @returns {number} - One of the PROPERTY_* constants.
+ */
+ compare(oldValue, newValue) {
+ if (!oldValue && newValue) {
+ return PROPERTY_ADDED;
+ }
+ if (oldValue && !newValue) {
+ return PROPERTY_REMOVED;
+ }
+ return oldValue != newValue ? PROPERTY_MODIFIED : PROPERTY_UNCHANGED;
+ }
+
+ connectedCallback() {
+ if (this.item && this.mode) {
+ let template = document.getElementById(`calendarInvitationPanel`);
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
+
+ if (this.foundItem && this.foundItem.title != this.item.title) {
+ let indicator = this.shadowRoot.getElementById("titleChangeIndicator");
+ indicator.status = PROPERTY_MODIFIED;
+ indicator.hidden = false;
+ }
+ this.shadowRoot.getElementById("title").textContent = this.item.title;
+
+ let statusBar = this.shadowRoot.querySelector("calendar-invitation-panel-status-bar");
+ statusBar.status = this.mode;
+
+ this.shadowRoot.querySelector("calendar-minidate").date = this.item.startDate;
+
+ for (let prop of InvitationPanel.propertyDescriptors) {
+ let el = this.shadowRoot.getElementById(prop.id);
+ let value = prop.getValue(this.item);
+ let result = PROPERTY_UNCHANGED;
+
+ if (prop.isList) {
+ let oldValue = this.foundItem ? prop.getValue(this.foundItem) : [];
+ if (value.length || oldValue.length) {
+ el.oldValue = oldValue;
+ el.value = value;
+ el.closest(".calendar-invitation-row").hidden = false;
+ }
+ continue;
+ }
+
+ let oldValue = this.foundItem ? prop.getValue(this.foundItem) : null;
+ if (this.foundItem) {
+ result = this.compare(oldValue, value);
+ if (result) {
+ let indicator = this.shadowRoot.getElementById(`${prop.id}ChangeIndicator`);
+ if (indicator) {
+ indicator.type = result;
+ indicator.hidden = false;
+ }
+ }
+ }
+ if (value || oldValue) {
+ prop.show(el, value, oldValue, this.item, this.foundItem, result);
+ el.closest(".calendar-invitation-row").hidden = false;
+ }
+ }
+
+ if (
+ this.mode == InvitationPanel.MODE_NEW ||
+ this.mode == InvitationPanel.MODE_UPDATE_MAJOR
+ ) {
+ for (let button of this.shadowRoot.querySelectorAll("#actionButtons > button")) {
+ button.addEventListener("click", e =>
+ this.dispatchEvent(
+ new CustomEvent("calendar-invitation-panel-action", {
+ detail: { type: button.dataset.action },
+ })
+ )
+ );
+ }
+ this.shadowRoot.getElementById("footer").hidden = false;
+ }
+ }
+ }
+ }
+ customElements.define("calendar-invitation-panel", InvitationPanel);
+
+ /**
+ * Object used to describe relevant arguments to MozElements.NotificationBox.
+ * appendNotification().
+ * @type {Object} InvitationStatusBarDescriptor
+ * @property {string} label - An l10n id used used to generate the notification
+ * bar text.
+ * @property {number} priority - One of the notification box constants that
+ * indicate the priority of a notification.
+ * @property {object[]} buttons - An array of objects corresponding to the
+ * "buttons" argument of MozElements.NotificationBox.appendNotification().
+ * See that method for details.
+ */
+
+ /**
+ * InvitationStatusBar generates a notification bar that informs the user about
+ * the status of the received invitation and possible actions they may take.
+ */
+ class InvitationPanelStatusBar extends HTMLElement {
+ /**
+ * @type {NotificationBox}
+ */
+ get notificationBox() {
+ if (!this._notificationBox) {
+ this._notificationBox = new MozElements.NotificationBox(element => {
+ this.append(element);
+ });
+ }
+ return this._notificationBox;
+ }
+
+ /**
+ * Map-like object where each key is an InvitationPanel mode and the values
+ * are descriptors used to generate the notification bar for that mode.
+ *
+ * @type {Object.<string, InvitationStatusBarDescriptor>
+ */
+ notices = {
+ [InvitationPanel.MODE_NEW]: {
+ label: "calendar-invitation-panel-status-new",
+ buttons: [
+ {
+ "l10n-id": "calendar-invitation-panel-more-button",
+ callback: (notification, opts, button, event) =>
+ this._showMoreMenu(event, [
+ {
+ l10nId: "calendar-invitation-panel-menu-item-save-copy",
+ name: "save",
+ command: e =>
+ this.dispatchEvent(
+ new CustomEvent("calendar-invitation-panel-action", {
+ details: { type: "x-savecopy" },
+ bubbles: true,
+ composed: true,
+ })
+ ),
+ },
+ ]),
+ },
+ ],
+ },
+ [InvitationPanel.MODE_ALREADY_PROCESSED]: {
+ label: "calendar-invitation-panel-status-processed",
+ buttons: [
+ {
+ "l10n-id": "calendar-invitation-panel-view-button",
+ callback: () => {
+ this.dispatchEvent(
+ new CustomEvent("calendar-invitation-panel-action", {
+ detail: { type: "x-showdetails" },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ return true;
+ },
+ },
+ ],
+ },
+ [InvitationPanel.MODE_UPDATE_MINOR]: {
+ label: "calendar-invitation-panel-status-updateminor",
+ priority: this.notificationBox.PRIORITY_WARNING_LOW,
+ buttons: [
+ {
+ "l10n-id": "calendar-invitation-panel-update-button",
+ callback: () => {
+ this.dispatchEvent(
+ new CustomEvent("calendar-invitation-panel-action", {
+ detail: { type: "update" },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ return true;
+ },
+ },
+ ],
+ },
+ [InvitationPanel.MODE_UPDATE_MAJOR]: {
+ label: "calendar-invitation-panel-status-updatemajor",
+ priority: this.notificationBox.PRIORITY_WARNING_LOW,
+ },
+ [InvitationPanel.MODE_CANCELLED]: {
+ label: "calendar-invitation-panel-status-cancelled",
+ buttons: [{ "l10n-id": "calendar-invitation-panel-delete-button" }],
+ priority: this.notificationBox.PRIORITY_CRITICAL_LOW,
+ },
+ [InvitationPanel.MODE_CANCELLED_NOT_FOUND]: {
+ label: "calendar-invitation-panel-status-cancelled-notfound",
+ priority: this.notificationBox.PRIORITY_CRITICAL_LOW,
+ },
+ };
+
+ /**
+ * status corresponds to one of the MODE_* constants and will trigger
+ * rendering of the notification box.
+ *
+ * @type {string} status
+ */
+ set status(value) {
+ let opts = this.notices[value];
+ let priority = opts.priority || this.notificationBox.PRIORITY_INFO_LOW;
+ let buttons = opts.buttons || [];
+ let notification = this.notificationBox.appendNotification(
+ "invitationStatus",
+ {
+ label: { "l10n-id": opts.label },
+ priority,
+ },
+ buttons
+ );
+ notification.removeAttribute("dismissable");
+ }
+
+ _showMoreMenu(event, menuitems) {
+ let menu = document.getElementById("calendarInvitationPanelMoreMenu");
+ menu.replaceChildren();
+ for (let { type, l10nId, name, command } of menuitems) {
+ let menuitem = document.createXULElement("menuitem");
+ if (type) {
+ menuitem.type = type;
+ }
+ if (name) {
+ menuitem.name = name;
+ }
+ if (command) {
+ menuitem.addEventListener("command", command);
+ }
+ document.l10n.setAttributes(menuitem, l10nId);
+ menu.appendChild(menuitem);
+ }
+ menu.openPopup(event.originalTarget, "after_start", 0, 0, false, false, event);
+ return true;
+ }
+ }
+ customElements.define("calendar-invitation-panel-status-bar", InvitationPanelStatusBar);
+
+ /**
+ * InvitationInterval displays the formatted interval of the event. Formatting
+ * relies on cal.dtz.formatter.formatIntervalParts().
+ */
+ class InvitationInterval extends HTMLElement {
+ /**
+ * The item whose interval to show.
+ *
+ * @type {calIEvent}
+ */
+ set item(value) {
+ let [startDate, endDate] = cal.dtz.formatter.getItemDates(value);
+ let timezone = startDate.timezone.displayName;
+ let parts = cal.dtz.formatter.formatIntervalParts(startDate, endDate);
+ document.l10n.setAttributes(this, `calendar-invitation-interval-${parts.type}`, {
+ ...parts,
+ timezone,
+ });
+ }
+ }
+ customElements.define("calendar-invitation-interval", InvitationInterval);
+
+ const partStatOrder = ["ACCEPTED", "DECLINED", "TENTATIVE", "NEEDS-ACTION"];
+
+ /**
+ * InvitationPartStatSummary generates text indicating the aggregated
+ * participation status of each attendee in the event's attendees list.
+ */
+ class InvitationPartStatSummary extends HTMLElement {
+ constructor() {
+ super();
+ this.appendChild(
+ document.getElementById("calendarInvitationPartStatSummary").content.cloneNode(true)
+ );
+ }
+
+ /**
+ * Setting this property will trigger an update of the text displayed.
+ *
+ * @type {calIAttendee[]}
+ */
+ set attendees(attendees) {
+ let counts = {
+ ACCEPTED: 0,
+ DECLINED: 0,
+ TENTATIVE: 0,
+ "NEEDS-ACTION": 0,
+ TOTAL: attendees.length,
+ OTHER: 0,
+ };
+
+ for (let { participationStatus } of attendees) {
+ if (counts.hasOwnProperty(participationStatus)) {
+ counts[participationStatus]++;
+ } else {
+ counts.OTHER++;
+ }
+ }
+ document.l10n.setAttributes(
+ this.querySelector("#partStatTotal"),
+ "calendar-invitation-panel-partstat-total",
+ { count: counts.TOTAL }
+ );
+
+ let shownPartStats = partStatOrder.filter(partStat => counts[partStat]);
+ let breakdown = this.querySelector("#partStatBreakdown");
+ for (let partStat of shownPartStats) {
+ let span = document.createElement("span");
+ span.setAttribute("class", "calendar-invitation-panel-partstat-summary");
+
+ // calendar-invitation-panel-partstat-accepted
+ // calendar-invitation-panel-partstat-declined
+ // calendar-invitation-panel-partstat-tentative
+ // calendar-invitation-panel-partstat-needs-action
+ document.l10n.setAttributes(
+ span,
+ `calendar-invitation-panel-partstat-${partStat.toLowerCase()}`,
+ {
+ count: counts[partStat],
+ }
+ );
+ breakdown.appendChild(span);
+ }
+ }
+ }
+ customElements.define("calendar-invitation-partstat-summary", InvitationPartStatSummary);
+
+ /**
+ * BaseInvitationChangeList is a <ul> element that can visually show changes
+ * between elements of a list value.
+ *
+ * @template T
+ */
+ class BaseInvitationChangeList extends HTMLUListElement {
+ /**
+ * An array containing the old values to be compared against for changes.
+ *
+ * @type {T[]}
+ */
+ oldValue = [];
+
+ /**
+ * String indicating the type of list items to create. This is passed
+ * directly to the "is" argument of document.createElement().
+ *
+ * @abstract
+ */
+ listItem;
+
+ _createListItem(value, status) {
+ let li = document.createElement("li", { is: this.listItem });
+ li.changeStatus = status;
+ li.value = value;
+ return li;
+ }
+
+ /**
+ * Setting this property will trigger rendering of the list. If no prior
+ * values are detected, change indicators are not touched.
+ *
+ * @type {T[]}
+ */
+ set value(list) {
+ if (!this.oldValue.length) {
+ for (let value of list) {
+ this.append(this._createListItem(value));
+ }
+ return;
+ }
+ for (let [value, status] of this.getChanges(this.oldValue, list)) {
+ this.appendChild(this._createListItem(value, status));
+ }
+ }
+
+ /**
+ * Implemented by sub-classes to generate a list of changes for each element
+ * of the new list.
+ *
+ * @param {T[]} oldValue
+ * @param {T[]} newValue
+ * @return {[T, number][]}
+ */
+ getChanges(oldValue, newValue) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ }
+ }
+
+ /**
+ * BaseInvitationChangeListItem is the <li> element used for change lists.
+ *
+ * @template {T}
+ */
+ class BaseInvitationChangeListItem extends HTMLLIElement {
+ /**
+ * Indicates whether the item value has changed and should be displayed as
+ * such. Its value is one of the PROPERTY_* constants.
+ *
+ * @type {number}
+ */
+ changeStatus = PROPERTY_UNCHANGED;
+
+ /**
+ * Settings this property will render the list item including a change
+ * indicator if the changeStatus property != PROPERTY_UNCHANGED.
+ *
+ * @type {T}
+ */
+ set value(itemValue) {
+ this.build(itemValue);
+ if (this.changeStatus) {
+ let changeIndicator = document.createElement("calendar-invitation-change-indicator");
+ changeIndicator.type = this.changeStatus;
+ this.append(changeIndicator);
+ }
+ }
+
+ /**
+ * Implemented by sub-classes to build the <li> inner DOM structure.
+ *
+ * @param {T} value
+ * @abstract
+ */
+ build(value) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ }
+ }
+
+ /**
+ * InvitationAttendeeList displays a list of all the attendees on an event's
+ * attendee list.
+ */
+ class InvitationAttendeeList extends BaseInvitationChangeList {
+ listItem = "calendar-invitation-panel-attendee-list-item";
+
+ getChanges(oldValue, newValue) {
+ let diff = [];
+ for (let att of newValue) {
+ let oldAtt = oldValue.find(oldAtt => oldAtt.id == att.id);
+ if (!oldAtt) {
+ diff.push([att, PROPERTY_ADDED]); // New attendee.
+ } else if (oldAtt.participationStatus != att.participationStatus) {
+ diff.push([att, PROPERTY_MODIFIED]); // Participation status changed.
+ } else {
+ diff.push([att, PROPERTY_UNCHANGED]); // No change.
+ }
+ }
+
+ // Insert removed attendees into the diff.
+ for (let [idx, att] of oldValue.entries()) {
+ let found = newValue.find(newAtt => newAtt.id == att.id);
+ if (!found) {
+ diff.splice(idx, 0, [att, PROPERTY_REMOVED]);
+ }
+ }
+ return diff;
+ }
+ }
+ customElements.define("calendar-invitation-panel-attendee-list", InvitationAttendeeList, {
+ extends: "ul",
+ });
+
+ /**
+ * InvitationAttendeeListItem displays a single attendee from the attendee
+ * list.
+ */
+ class InvitationAttendeeListItem extends BaseInvitationChangeListItem {
+ build(value) {
+ let span = document.createElement("span");
+ if (this.changeStatus == PROPERTY_REMOVED) {
+ span.setAttribute("class", "removed");
+ }
+ span.textContent = value;
+ this.appendChild(span);
+ }
+ }
+ customElements.define(
+ "calendar-invitation-panel-attendee-list-item",
+ InvitationAttendeeListItem,
+ {
+ extends: "li",
+ }
+ );
+
+ /**
+ * InvitationAttachmentList displays a list of all attachments in the invitation
+ * that have URIs. Binary attachments are not supported.
+ */
+ class InvitationAttachmentList extends BaseInvitationChangeList {
+ listItem = "calendar-invitation-panel-attachment-list-item";
+
+ getChanges(oldValue, newValue) {
+ let diff = [];
+ for (let attch of newValue) {
+ if (!attch.uri) {
+ continue;
+ }
+ let oldAttch = oldValue.find(
+ oldAttch => oldAttch.uri && oldAttch.uri.spec == attch.uri.spec
+ );
+
+ if (!oldAttch) {
+ // New attachment.
+ diff.push([attch, PROPERTY_ADDED]);
+ continue;
+ }
+ if (
+ attch.hashId != oldAttch.hashId ||
+ attch.getParameter("FILENAME") != oldAttch.getParameter("FILENAME")
+ ) {
+ // Contents changed or renamed.
+ diff.push([attch, PROPERTY_MODIFIED]);
+ continue;
+ }
+ // No change.
+ diff.push([attch, PROPERTY_UNCHANGED]);
+ }
+
+ // Insert removed attachments into the diff.
+ for (let [idx, attch] of oldValue.entries()) {
+ if (!attch.uri) {
+ continue;
+ }
+ let found = newValue.find(newAtt => newAtt.uri && newAtt.uri.spec == attch.uri.spec);
+ if (!found) {
+ diff.splice(idx, 0, [attch, PROPERTY_REMOVED]);
+ }
+ }
+ return diff;
+ }
+ }
+ customElements.define("calendar-invitation-panel-attachment-list", InvitationAttachmentList, {
+ extends: "ul",
+ });
+
+ /**
+ * InvitationAttachmentListItem displays a link to an attachment attached to the
+ * event.
+ */
+ class InvitationAttachmentListItem extends BaseInvitationChangeListItem {
+ /**
+ * Indicates whether the attachment has changed and should be displayed as
+ * such. Its value is one of the PROPERTY_* constants.
+ *
+ * @type {number}
+ */
+ changeStatus = PROPERTY_UNCHANGED;
+
+ /**
+ * Sets up the attachment to be displayed as a link with appropriate icon.
+ * Links are opened externally.
+ *
+ * @param {calIAttachment}
+ */
+ build(value) {
+ let icon = document.createElement("img");
+ let iconSrc = value.uri.spec.length ? value.uri.spec : "dummy.html";
+ if (!value.uri.schemeIs("file")) {
+ // Using an uri directly, with e.g. a http scheme, wouldn't render any icon.
+ if (value.formatType) {
+ iconSrc = "goat?contentType=" + value.formatType;
+ } else {
+ // Let's try to auto-detect.
+ let parts = iconSrc.substr(value.uri.scheme.length + 2).split("/");
+ if (parts.length) {
+ iconSrc = parts[parts.length - 1];
+ }
+ }
+ }
+ icon.setAttribute("src", "moz-icon://" + iconSrc);
+ this.append(icon);
+
+ let title = value.getParameter("FILENAME") || value.uri.spec;
+ if (this.changeStatus == PROPERTY_REMOVED) {
+ let span = document.createElement("span");
+ span.setAttribute("class", "removed");
+ span.textContent = title;
+ this.append(span);
+ } else {
+ let link = document.createElement("a");
+ link.textContent = title;
+ link.setAttribute("href", value.uri.spec);
+ link.addEventListener("click", event => {
+ event.preventDefault();
+ openLinkExternally(event.target.href);
+ });
+ this.append(link);
+ }
+ }
+ }
+ customElements.define(
+ "calendar-invitation-panel-attachment-list-item",
+ InvitationAttachmentListItem,
+ {
+ extends: "li",
+ }
+ );
+
+ /**
+ * InvitationChangeIndicator is a visual indicator for indicating some piece
+ * of data has changed.
+ */
+ class InvitationChangeIndicator extends HTMLElement {
+ _typeMap = {
+ [PROPERTY_REMOVED]: "removed",
+ [PROPERTY_ADDED]: "added",
+ [PROPERTY_MODIFIED]: "modified",
+ };
+
+ /**
+ * One of the PROPERTY_* constants that indicates what kind of change we
+ * are indicating (add/modify/delete) etc.
+ *
+ * @type {number}
+ */
+ set type(value) {
+ let key = this._typeMap[value];
+ document.l10n.setAttributes(this, `calendar-invitation-change-indicator-${key}`);
+ }
+ }
+ customElements.define("calendar-invitation-change-indicator", InvitationChangeIndicator);
+}