diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/components/addrbook/content/vcard-edit/special-date.mjs | |
parent | Initial commit. (diff) | |
download | thunderbird-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 'comm/mail/components/addrbook/content/vcard-edit/special-date.mjs')
-rw-r--r-- | comm/mail/components/addrbook/content/vcard-edit/special-date.mjs | 269 |
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); |