summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/addrbook/content/vcard-edit/special-date.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/addrbook/content/vcard-edit/special-date.mjs')
-rw-r--r--comm/mail/components/addrbook/content/vcard-edit/special-date.mjs269
1 files changed, 269 insertions, 0 deletions
diff --git a/comm/mail/components/addrbook/content/vcard-edit/special-date.mjs b/comm/mail/components/addrbook/content/vcard-edit/special-date.mjs
new file mode 100644
index 0000000000..17c7df493b
--- /dev/null
+++ b/comm/mail/components/addrbook/content/vcard-edit/special-date.mjs
@@ -0,0 +1,269 @@
+/* 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/. */
+
+import { vCardIdGen } from "./id-gen.mjs";
+
+const lazy = {};
+ChromeUtils.defineModuleGetter(
+ lazy,
+ "VCardPropertyEntry",
+ "resource:///modules/VCardUtils.jsm"
+);
+
+const { ICAL } = ChromeUtils.import("resource:///modules/calendar/Ical.jsm");
+
+/**
+ * ANNIVERSARY and BDAY both have a cardinality of
+ * 1 ("Exactly one instance per vCard MAY be present.").
+ *
+ * For Anniversary we changed the cardinality to
+ * ("One or more instances per vCard MAY be present.")".
+ *
+ * @implements {VCardPropertyEntryView}
+ * @see RFC6350 ANNIVERSARY and BDAY
+ */
+export class VCardSpecialDateComponent extends HTMLElement {
+ /** @type {VCardPropertyEntry} */
+ vCardPropertyEntry;
+
+ /** @type {HTMLSelectElement} */
+ selectEl;
+ /** @type {HTMLInputElement} */
+ year;
+ /** @type {HTMLSelectElement} */
+ month;
+ /** @type {HTMLSelectElement} */
+ day;
+
+ /**
+ * Object containing the available days for each month.
+ *
+ * @type {object}
+ */
+ monthDays = {
+ 1: 31,
+ 2: 28,
+ 3: 31,
+ 4: 30,
+ 5: 31,
+ 6: 30,
+ 7: 31,
+ 8: 31,
+ 9: 30,
+ 10: 31,
+ 11: 30,
+ 12: 31,
+ };
+
+ static newAnniversaryVCardPropertyEntry() {
+ return new lazy.VCardPropertyEntry("anniversary", {}, "date", "");
+ }
+
+ static newBdayVCardPropertyEntry() {
+ return new lazy.VCardPropertyEntry("bday", {}, "date", "");
+ }
+
+ connectedCallback() {
+ if (this.hasConnected) {
+ return;
+ }
+ this.hasConnected = true;
+
+ let template = document.getElementById(
+ "template-vcard-edit-bday-anniversary"
+ );
+ let clonedTemplate = template.content.cloneNode(true);
+ this.appendChild(clonedTemplate);
+
+ this.selectEl = this.querySelector(".vcard-type-selection");
+ let selectId = vCardIdGen.next().value;
+ this.selectEl.id = selectId;
+ this.querySelector(".vcard-type-label").htmlFor = selectId;
+
+ this.selectEl.addEventListener("change", event => {
+ this.dispatchEvent(
+ VCardSpecialDateComponent.ChangeVCardPropertyEntryEvent(
+ event.target.value
+ )
+ );
+ });
+
+ this.month = this.querySelector("#month");
+ let monthId = vCardIdGen.next().value;
+ this.month.id = monthId;
+ this.querySelector('label[for="month"]').htmlFor = monthId;
+ this.month.addEventListener("change", () => {
+ this.fillDayOptions();
+ });
+
+ this.day = this.querySelector("#day");
+ let dayId = vCardIdGen.next().value;
+ this.day.id = dayId;
+ this.querySelector('label[for="day"]').htmlFor = dayId;
+
+ this.year = this.querySelector("#year");
+ let yearId = vCardIdGen.next().value;
+ this.year.id = yearId;
+ this.querySelector('label[for="year"]').htmlFor = yearId;
+ this.year.addEventListener("input", () => {
+ this.fillDayOptions();
+ });
+
+ document.l10n.formatValues([{ id: "vcard-date-year" }]).then(yearLabel => {
+ this.year.placeholder = yearLabel;
+ });
+
+ this.querySelector(".remove-property-button").addEventListener(
+ "click",
+ () => {
+ this.dispatchEvent(
+ new CustomEvent("vcard-remove-property", { bubbles: true })
+ );
+ this.remove();
+ }
+ );
+
+ this.fillMonthOptions();
+ this.fromVCardPropertyEntryToUI();
+ }
+
+ fromVCardPropertyEntryToUI() {
+ this.selectEl.value = this.vCardPropertyEntry.name;
+ if (this.vCardPropertyEntry.type === "text") {
+ // TODO: support of text type for special-date
+ this.hidden = true;
+ return;
+ }
+ // Default value is date-and-or-time.
+ let dateValue;
+ try {
+ dateValue = ICAL.VCardTime.fromDateAndOrTimeString(
+ this.vCardPropertyEntry.value || "",
+ "date-and-or-time"
+ );
+ } catch (ex) {
+ console.error(ex);
+ }
+ // Always set the month first since that controls the available days.
+ this.month.value = dateValue?.month || "";
+ this.fillDayOptions();
+ this.day.value = dateValue?.day || "";
+ this.year.value = dateValue?.year || "";
+ }
+
+ fromUIToVCardPropertyEntry() {
+ if (this.vCardPropertyEntry.type === "text") {
+ // TODO: support of text type for special-date
+ return;
+ }
+ // Default value is date-and-or-time.
+ let dateValue = new ICAL.VCardTime({}, null, "date");
+ // Set the properties directly instead of using the VCardTime
+ // constructor argument, which causes null values to become 0.
+ dateValue.year = this.year.value ? Number(this.year.value) : null;
+ dateValue.month = this.month.value ? Number(this.month.value) : null;
+ dateValue.day = this.day.value ? Number(this.day.value) : null;
+ this.vCardPropertyEntry.value = dateValue.toString();
+ }
+
+ valueIsEmpty() {
+ return !this.year.value && !this.month.value && !this.day.value;
+ }
+
+ /**
+ * @param {"bday" | "anniversary"} entryName
+ * @returns {CustomEvent}
+ */
+ static ChangeVCardPropertyEntryEvent(entryName) {
+ return new CustomEvent("vcard-bday-anniversary-change", {
+ detail: {
+ name: entryName,
+ },
+ bubbles: true,
+ });
+ }
+
+ /**
+ * Check if the specified year is a leap year in order to add or remove the
+ * extra day to February.
+ *
+ * @returns {boolean} True if the currently specified year is a leap year,
+ * or if no valid year value is available.
+ */
+ isLeapYear() {
+ // If the year is empty, we can't know if it's a leap year so must assume
+ // it is. Otherwise year-less dates can't show Feb 29.
+ if (!this.year.checkValidity() || this.year.value === "") {
+ return true;
+ }
+
+ let year = parseInt(this.year.value);
+ return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
+ }
+
+ fillMonthOptions() {
+ let formatter = Intl.DateTimeFormat(undefined, { month: "long" });
+ for (let m = 1; m <= 12; m++) {
+ let option = document.createElement("option");
+ option.setAttribute("value", m);
+ option.setAttribute("label", formatter.format(new Date(2000, m - 1, 2)));
+ this.month.appendChild(option);
+ }
+ }
+
+ /**
+ * Update the Day select element to reflect the available days of the selected
+ * month.
+ */
+ fillDayOptions() {
+ let prevDay = 0;
+ // Save the previously selected day if we have one.
+ if (this.day.childNodes.length > 1) {
+ prevDay = this.day.value;
+ }
+
+ // Always clear old options.
+ let defaultOption = document.createElement("option");
+ defaultOption.value = "";
+ document.l10n
+ .formatValues([{ id: "vcard-date-day" }])
+ .then(([dayLabel]) => {
+ defaultOption.textContent = dayLabel;
+ });
+ this.day.replaceChildren(defaultOption);
+
+ let monthValue = this.month.value || 1;
+ // Add a day to February if this is a leap year and we're in February.
+ if (monthValue === "2") {
+ this.monthDays["2"] = this.isLeapYear() ? 29 : 28;
+ }
+
+ let formatter = Intl.DateTimeFormat(undefined, { day: "numeric" });
+ for (let d = 1; d <= this.monthDays[monthValue]; d++) {
+ let option = document.createElement("option");
+ option.setAttribute("value", d);
+ option.setAttribute("label", formatter.format(new Date(2000, 0, d)));
+ this.day.appendChild(option);
+ }
+ // Reset the previously selected day, if it's available in the currently
+ // selected month.
+ this.day.value = prevDay <= this.monthDays[monthValue] ? prevDay : "";
+ }
+
+ /**
+ * @param {boolean} options.hasBday
+ */
+ birthdayAvailability(options) {
+ if (this.vCardPropertyEntry.name === "bday") {
+ return;
+ }
+ Array.from(this.selectEl.options).forEach(option => {
+ if (option.value === "bday") {
+ option.disabled = options.hasBday;
+ }
+ });
+ }
+}
+
+customElements.define("vcard-special-date", VCardSpecialDateComponent);