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/preferences/preferences.js | |
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/preferences/preferences.js')
-rw-r--r-- | comm/mail/components/preferences/preferences.js | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/comm/mail/components/preferences/preferences.js b/comm/mail/components/preferences/preferences.js new file mode 100644 index 0000000000..1a123527ca --- /dev/null +++ b/comm/mail/components/preferences/preferences.js @@ -0,0 +1,453 @@ +/* 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-globals-from ../../../../toolkit/content/preferencesBindings.js */ +/* import-globals-from general.js */ +/* import-globals-from compose.js */ +/* import-globals-from downloads.js */ +/* import-globals-from privacy.js */ +/* import-globals-from chat.js */ +/* import-globals-from sync.js */ +/* import-globals-from findInPage.js */ +/* globals gCalendarPane */ + +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { ExtensionSupport } = ChromeUtils.import( + "resource:///modules/ExtensionSupport.jsm" +); +var { calendarDeactivator } = ChromeUtils.import( + "resource:///modules/calendar/calCalendarDeactivator.jsm" +); +var { UIDensity } = ChromeUtils.import("resource:///modules/UIDensity.jsm"); +var { UIFontSize } = ChromeUtils.import("resource:///modules/UIFontSize.jsm"); + +var paneDeck = document.getElementById("paneDeck"); +var defaultPane = "paneGeneral"; + +ChromeUtils.defineESModuleGetters(this, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(this, "gSubDialog", function () { + const { SubDialogManager } = ChromeUtils.importESModule( + "resource://gre/modules/SubDialog.sys.mjs" + ); + return new SubDialogManager({ + dialogStack: document.getElementById("dialogStack"), + dialogTemplate: document.getElementById("dialogTemplate"), + dialogOptions: { + styleSheets: [ + "chrome://messenger/skin/preferences/dialog.css", + "chrome://messenger/skin/preferences/preferences.css", + ], + resizeCallback: ({ title, frame }) => { + UIFontSize.registerWindow(frame.contentWindow); + + // Search within main document and highlight matched keyword. + gSearchResultsPane.searchWithinNode(title, gSearchResultsPane.query); + + // Search within sub-dialog document and highlight matched keyword. + gSearchResultsPane.searchWithinNode( + frame.contentDocument.firstElementChild, + gSearchResultsPane.query + ); + + // Creating tooltips for all the instances found + for (let node of gSearchResultsPane.listSearchTooltips) { + if (!node.tooltipNode) { + gSearchResultsPane.createSearchTooltip( + node, + gSearchResultsPane.query + ); + } + } + + // Resize the dialog to fit the content with edited font size. + requestAnimationFrame(() => { + let dialogs = frame.ownerGlobal.gSubDialog._dialogs; + let dialog = dialogs.find( + d => d._frame.contentDocument == frame.contentDocument + ); + if (dialog) { + UIFontSize.resizeSubDialog(dialog); + } + }); + }, + }, + }); +}); + +document.addEventListener("DOMContentLoaded", init, { once: true }); + +var gCategoryInits = new Map(); +var gLastCategory = { category: undefined, subcategory: undefined }; + +function init_category_if_required(category) { + let categoryInfo = gCategoryInits.get(category); + if (!categoryInfo) { + throw new Error( + "Unknown in-content prefs category! Can't init " + category + ); + } + if (categoryInfo.inited) { + return null; + } + return categoryInfo.init(); +} + +function register_module(categoryName, categoryObject) { + gCategoryInits.set(categoryName, { + inited: false, + async init() { + let template = document.getElementById(categoryName); + if (template) { + // Replace the template element with the nodes inside of it. + let frag = template.content; + await document.l10n.translateFragment(frag); + + // Actually insert them into the DOM. + document.l10n.pauseObserving(); + template.replaceWith(frag); + document.l10n.resumeObserving(); + + // Asks Preferences to update the attribute value of the entire + // document again (this can be simplified if we could separate the + // preferences of each pane.) + Preferences.queueUpdateOfAllElements(); + } + categoryObject.init(); + this.inited = true; + }, + }); +} + +function init() { + register_module("paneGeneral", gGeneralPane); + register_module("paneCompose", gComposePane); + register_module("panePrivacy", gPrivacyPane); + register_module("paneCalendar", gCalendarPane); + if (AppConstants.NIGHTLY_BUILD) { + register_module("paneSync", gSyncPane); + } + register_module("paneSearchResults", gSearchResultsPane); + if (Services.prefs.getBoolPref("mail.chat.enabled")) { + register_module("paneChat", gChatPane); + } else { + // Remove the pane from the DOM so it doesn't get incorrectly included in + // the search results. + document.getElementById("paneChat").remove(); + } + + // If no calendar is currently enabled remove it from the DOM so it doesn't + // get incorrectly included in the search results. + if (!calendarDeactivator.isCalendarActivated) { + document.getElementById("paneCalendar").remove(); + document.getElementById("category-calendar").remove(); + } + gSearchResultsPane.init(); + + let categories = document.getElementById("categories"); + categories.addEventListener("select", event => gotoPref(event.target.value)); + + document.documentElement.addEventListener("keydown", event => { + if (event.key == "Tab") { + categories.setAttribute("keyboard-navigation", "true"); + } else if ((event.ctrlKey || event.metaKey) && event.key == "f") { + document.getElementById("searchInput").focus(); + event.preventDefault(); + } + }); + + categories.addEventListener("mousedown", function () { + this.removeAttribute("keyboard-navigation"); + }); + + window.addEventListener("hashchange", onHashChange); + let lastSelected = Services.xulStore.getValue( + "about:preferences", + "paneDeck", + "lastSelected" + ); + gotoPref(lastSelected); + + UIDensity.registerWindow(window); + UIFontSize.registerWindow(window); +} + +function onHashChange() { + gotoPref(); +} + +async function gotoPref(aCategory) { + let categories = document.getElementById("categories"); + const kDefaultCategoryInternalName = "paneGeneral"; + const kDefaultCategory = "general"; + let hash = document.location.hash; + + let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName; + let breakIndex = category.indexOf("-"); + // Subcategories allow for selecting smaller sections of the preferences + // until proper search support is enabled (bug 1353954). + let subcategory = breakIndex != -1 && category.substring(breakIndex + 1); + if (subcategory) { + category = category.substring(0, breakIndex); + } + category = friendlyPrefCategoryNameToInternalName(category); + if (category != "paneSearchResults") { + gSearchResultsPane.query = null; + gSearchResultsPane.searchInput.value = ""; + gSearchResultsPane.getFindSelection(window).removeAllRanges(); + gSearchResultsPane.removeAllSearchTooltips(); + gSearchResultsPane.removeAllSearchMenuitemIndicators(); + } else if (!gSearchResultsPane.searchInput.value) { + // Something tried to send us to the search results pane without + // a query string. Default to the General pane instead. + category = kDefaultCategoryInternalName; + document.location.hash = kDefaultCategory; + gSearchResultsPane.query = null; + } + + // Updating the hash (below) or changing the selected category + // will re-enter gotoPref. + if (gLastCategory.category == category && !subcategory) { + return; + } + + let item; + if (category != "paneSearchResults") { + // Hide second level headers in normal view + for (let element of document.querySelectorAll(".search-header")) { + element.hidden = true; + } + + item = categories.querySelector(".category[value=" + category + "]"); + if (!item) { + category = kDefaultCategoryInternalName; + item = categories.querySelector(".category[value=" + category + "]"); + } + } + + if ( + gLastCategory.category || + category != kDefaultCategoryInternalName || + subcategory + ) { + let friendlyName = internalPrefCategoryNameToFriendlyName(category); + document.location.hash = friendlyName; + } + // Need to set the gLastCategory before setting categories.selectedItem since + // the categories 'select' event will re-enter the gotoPref codepath. + gLastCategory.category = category; + gLastCategory.subcategory = subcategory; + if (item) { + categories.selectedItem = item; + } else { + categories.clearSelection(); + } + window.history.replaceState(category, document.title); + + try { + await init_category_if_required(category); + } catch (ex) { + console.error( + new Error( + "Error initializing preference category " + category + ": " + ex + ) + ); + throw ex; + } + + // Bail out of this goToPref if the category + // or subcategory changed during async operation. + if ( + gLastCategory.category !== category || + gLastCategory.subcategory !== subcategory + ) { + return; + } + + search(category, "data-category"); + + let mainContent = document.querySelector(".main-content"); + mainContent.scrollTop = 0; + + spotlight(subcategory, category); + + document.dispatchEvent(new CustomEvent("paneSelected", { bubbles: true })); + document.getElementById("preferencesContainer").scrollTo(0, 0); + document.getElementById("paneDeck").setAttribute("lastSelected", category); + Services.xulStore.setValue( + "about:preferences", + "paneDeck", + "lastSelected", + category + ); +} + +function friendlyPrefCategoryNameToInternalName(aName) { + if (aName.startsWith("pane")) { + return aName; + } + return "pane" + aName.substring(0, 1).toUpperCase() + aName.substr(1); +} + +// This function is duplicated inside of utilityOverlay.js's openPreferences. +function internalPrefCategoryNameToFriendlyName(aName) { + return (aName || "").replace(/^pane./, function (toReplace) { + return toReplace[4].toLowerCase(); + }); +} + +function search(aQuery, aAttribute) { + let paneDeck = document.getElementById("paneDeck"); + let elements = paneDeck.children; + for (let element of elements) { + // If the "data-hidden-from-search" is "true", the + // element will not get considered during search. + if ( + element.getAttribute("data-hidden-from-search") != "true" || + element.getAttribute("data-subpanel") == "true" + ) { + let attributeValue = element.getAttribute(aAttribute); + if (attributeValue == aQuery) { + element.hidden = false; + } else { + element.hidden = true; + } + } else if ( + element.getAttribute("data-hidden-from-search") == "true" && + !element.hidden + ) { + element.hidden = true; + } + element.classList.remove("visually-hidden"); + } + + let keysets = paneDeck.getElementsByTagName("keyset"); + for (let element of keysets) { + let attributeValue = element.getAttribute(aAttribute); + if (attributeValue == aQuery) { + element.removeAttribute("disabled"); + } else { + element.setAttribute("disabled", true); + } + } +} + +async function spotlight(subcategory, category) { + let highlightedElements = document.querySelectorAll(".spotlight"); + if (highlightedElements.length) { + for (let element of highlightedElements) { + element.classList.remove("spotlight"); + } + } + if (subcategory) { + scrollAndHighlight(subcategory, category); + } +} + +async function scrollAndHighlight(subcategory, category) { + let element = document.querySelector(`[data-subcategory="${subcategory}"]`); + if (!element) { + return; + } + let header = getClosestDisplayedHeader(element); + + scrollContentTo(header); + element.classList.add("spotlight"); +} + +/** + * If there is no visible second level header it will return first level header, + * otherwise return second level header. + * + * @returns {Element} The closest displayed header. + */ +function getClosestDisplayedHeader(element) { + let header = element.closest("groupbox"); + let searchHeader = header.querySelector(".search-header"); + if ( + searchHeader && + searchHeader.hidden && + header.previousElementSibling.classList.contains("subcategory") + ) { + header = header.previousElementSibling; + } + return header; +} + +function scrollContentTo(element) { + const STICKY_CONTAINER_HEIGHT = + document.querySelector(".sticky-container").clientHeight; + let mainContent = document.querySelector(".main-content"); + let top = element.getBoundingClientRect().top - STICKY_CONTAINER_HEIGHT; + mainContent.scroll({ + top, + behavior: "smooth", + }); +} + +/** + * Selects the specified preferences pane + * + * @param paneID ID of prefpane to select + * @param scrollPaneTo ID of the element to scroll into view + * @param otherArgs.subdialog ID of button to activate, opening a subdialog + */ +function selectPrefPane(paneID, scrollPaneTo, otherArgs) { + if (paneID) { + if (gLastCategory.category != paneID) { + gotoPref(paneID); + } + if (scrollPaneTo) { + showTab(scrollPaneTo, otherArgs ? otherArgs.subdialog : undefined); + } + } +} + +/** + * Select the specified tab + * + * @param scrollPaneTo ID of the element to scroll into view + * @param subdialogID ID of button to activate, opening a subdialog + */ +function showTab(scrollPaneTo, subdialogID) { + setTimeout(function () { + let scrollTarget = document.getElementById(scrollPaneTo); + if (scrollTarget.closest("groupbox")) { + scrollTarget = scrollTarget.closest("groupbox"); + } + scrollTarget.scrollIntoView(); + if (subdialogID) { + document.getElementById(subdialogID).click(); + } + }); +} + +/** + * Filter the lastFallbackLocale from availableLocales if it doesn't have all + * of the needed strings. + * + * When the lastFallbackLocale isn't the defaultLocale, then by default only + * fluent strings are included. To fully use that locale you need the langpack + * to be installed, so if it isn't installed remove it from availableLocales. + */ +async function getAvailableLocales() { + let { availableLocales, defaultLocale, lastFallbackLocale } = Services.locale; + // If defaultLocale isn't lastFallbackLocale, then we still need the langpack + // for lastFallbackLocale for it to be useful. + if (defaultLocale != lastFallbackLocale) { + let lastFallbackId = `langpack-${lastFallbackLocale}@thunderbird.mozilla.org`; + let lastFallbackInstalled = await AddonManager.getAddonByID(lastFallbackId); + if (!lastFallbackInstalled) { + return availableLocales.filter(locale => locale != lastFallbackLocale); + } + } + return availableLocales; +} |