as the description of a urlbar result. * * @param {string | [string, string]} dateStr * A string in the format "YYYY-MM-DD" representing a single * day or a tuple of strings representing start and end days. * @param {string} name * The name of the event. * @returns {?object} * A l10n object or null if the event is in the past. */ #formatDateCountdown(dateStr, name) { if (Array.isArray(dateStr)) { let daysUntilStart = this.#getDaysUntil(dateStr[0]); let daysUntilEnd = this.#getDaysUntil(dateStr[1]); if (daysUntilEnd < 0) { throw new Error("Date lies in the past."); } if (daysUntilStart > 0) { return { id: "urlbar-result-dates-countdown-range", args: { daysUntilStart, name }, }; } if (daysUntilEnd > 0) { return { id: "urlbar-result-dates-ongoing", args: { daysUntilEnd, name }, }; } return { id: "urlbar-result-dates-ends-today", args: { name }, }; } let daysUntil = this.#getDaysUntil(dateStr); if (daysUntil < 0) { throw new Error("Date lies in the past."); } if (daysUntil > 0) { return { id: "urlbar-result-dates-countdown", args: { daysUntilStart: daysUntil, name }, }; } return { id: "urlbar-result-dates-today", args: { name }, }; } /** * Returns the number of days until the specified date. * * @param {string} dateStr * A string in the format "YYYY-MM-DD". * @returns {number} * The time until the input date in days. */ #getDaysUntil(dateStr) { let now = new Date(); now.setHours(0, 0, 0, 0); let date = new Date(dateStr + "T00:00"); let msUntil = date.getTime() - now.getTime(); // Round to account for potential DST. return Math.round(msUntil / MS_PER_DAY); } /** * Creates a urlbar result from an important dates payload. * * @param {UrlbarQueryContext} queryContext * The query context. * @param {DatePayload} payload * The important dates payload. * @returns {?UrlbarResult} * A urlbar result or null if all instances are in the past. */ #makeDateResult(queryContext, payload) { let eventDateOrRange = payload.dates.find( // Find first entry where the end date is in the future. // This is always the upcoming occurrence since dates is ordered by time. d => this.#getDaysUntil(Array.isArray(d) ? d[1] : d) >= 0 ); if (!eventDateOrRange) { // All instances of the event are in the past. return null; } let daysUntilStart = this.#getDaysUntil( Array.isArray(eventDateOrRange) ? eventDateOrRange[0] : eventDateOrRange ); let description, descriptionL10n; if (daysUntilStart > SHOW_COUNTDOWN_THRESHOLD_DAYS) { description = payload.name; } else { descriptionL10n = { ...this.#formatDateCountdown(eventDateOrRange, payload.name), }; } let dateString = this.#formatDateOrRange(eventDateOrRange); return new lazy.UrlbarResult({ type: lazy.UrlbarUtils.RESULT_TYPE.SEARCH, source: lazy.UrlbarUtils.RESULT_SOURCE.SEARCH, isBestMatch: true, hideRowLabel: true, richSuggestionIconSize: 24, payload: { title: dateString, description, engine: lazy.UrlbarSearchUtils.getDefaultEngine(queryContext.isPrivate) .name, descriptionL10n, query: payload.name, lowerCaseSuggestion: payload.name.toLowerCase(), icon: "chrome://browser/skin/calendar-24.svg", helpUrl: lazy.QuickSuggest.HELP_URL, isManageable: true, isBlockable: true, }, highlights: { title: lazy.UrlbarUtils.HIGHLIGHT.ALL, }, }); } onEngagement(_queryContext, controller, details, _searchString) { switch (details.selType) { case "manage": // "manage" is handled by UrlbarInput, no need to do anything here. break; case "dismiss": { let { result } = details; lazy.QuickSuggest.dismissResult(result); result.acknowledgeDismissalL10n = { id: "firefox-suggest-dismissal-acknowledgment-one", }; controller.removeResult(result); break; } } } } PK