diff --git a/_locales/da/messages.json b/_locales/da/messages.json index 281f71e..5c4b26e 100644 --- a/_locales/da/messages.json +++ b/_locales/da/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Lokale mapper" + }, + "accountsort.noaccountsetupyet": { + "message": "Du har endnu ikke oprettet nogen konto!" + }, + "accountsort.note": { + "message": "Du kan ændre rækkefølgen af konti i dette panel. (På Thunderbird 91 og nyere kan du også gøre det i brugergrænsefladen Kontoindstillinger.)" + }, + "accountsort.restartwarning": { + "message": "Du skal genstarte Thunderbird før ændringerne træder i kraft." + }, + "accountsort.warning": { + "message": "Almindelige mailkonti, Unix Movemail-konti så vel som feed-konti skal komme først. Lokale mapper kommer derefter og sidst kommer kontiene med nyhedsgrupper. Du kan tilsidesætte dette ved at vælge enten Lokale mapper eller en af kontiene for nyhedsgrupper som standard, og på den måde sætte den øverst på listen." + }, + "accountssort.description": { + "message": "Dette panel lader dig sortere kontiene i mappeoversigten. Brug næste faneblad hvis du vil sortere mapper inde i en konto." + }, + "button.account_name": { + "message": "Kontoens navn" + }, + "button.close": { + "message": "Luk" + }, + "button.move_down": { + "message": "Flyt ned" + }, + "button.move_up": { + "message": "Flyt op" + }, + "button.refresh": { + "message": "Opdater mappeoversigten" + }, + "button.restart": { + "message": "Genstart Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Forskel på store og små bogstaver" + }, "extensionDescription": { "description": "Description of the extension", "message": "Giver dig mulighed for at ændre rækkefølgen af mapper i mapperuden." + }, + "extra.description": { + "message": "Ved hjælp af forskellige mappe-relaterede indstillinger, kan du bruge dette faneblad til, at tilpasse Thunderbird yderligere." + }, + "extra.hide_folder_icons": { + "message": "Skjul mappeikoner (Skal genstarte denne tilføjelse)" + }, + "extra.misc": { + "message": "Div." + }, + "extra.nofolder": { + "message": "Ingen mappe valgt" + }, + "extra.startup": { + "message": "Startmappe" + }, + "extra.startupfolder": { + "message": "Som standard, start med denne mappe åben" + }, + "extra.startupfolder.notice": { + "message": "* Denne funktion fungerer muligvis ikke pålideligt i Thunderbird 97 og tidligere versioner." + }, + "extra.usecustom": { + "message": "Anvend brugervalgt mappe" + }, + "extra.uselast": { + "message": "Anvend senest åbnede mappe" + }, + "extra.usethisfolder": { + "message": "Anvend denne mappe" + }, + "gb.extra": { + "message": "Yderligere information" + }, + "gb.first": { + "message": "Første konto i mappepanelet" + }, + "gb.move": { + "message": "Flyt valgte konto" + }, + "gb.movefolder": { + "message": "Flyt valgte mappe" + }, + "gb.sort_by": { + "message": "Sorter efter" + }, + "gb.sort_siblings_by": { + "message": "Sortere inden for det valgte niveau efter" + }, + "gbfirst.default": { + "message": "Dette er også standardkontoen." + }, + "general.title": { + "message": "Sorter mapper manuelt" + }, + "mi.firstmail": { + "message": "Første feed eller mailkonto i listen" + }, + "sortfolders.description": { + "message": "Dette panel lader dig sortere hver kontos mapper. Vælg først en konto og så en sorteringsmetode." + }, + "sortmethod.alphabetical.ascending": { + "message": "Brug almindelig sammenligning af strenge (Stigende rækkefølge)" + }, + "sortmethod.alphabetical.descending": { + "message": "Brug almindelig sammenligning af strenge (Nedadgående rækkefølge)" + }, + "sortmethod.alphabetical.description": { + "message": "Dette er en standard sorteringsfunktion, der ikke bruger andet end den mest almindelige sammenligningsfunktion." + }, + "sortmethod.custom": { + "message": "Brug en manuel defineret sorteringsfunktion" + }, + "sortmethod.custom.description": { + "message": "Brug knapperne ovenover til manuelt at sortere dine mapper. Hvis en ny mappe bliver tilføjet, bliver den anbragt i slutningen af listen. Du kan komme tilbage hertil senere hvis det ikke passer dig." + }, + "sortmethod.default": { + "message": "Brug Thunderbirds standard" + }, + "sortmethod.default.description": { + "message": "Dette er Thunderbirds standard sorteringsfunktion." + }, + "sortmethod.samplelegend": { + "message": "(Eksempel på mappeoversigt)" + }, + "tab.accounts": { + "message": "Sorter konti" + }, + "tab.extra": { + "message": "Yderligere indstillinger" + }, + "tab.folders": { + "message": "Sorter mapper" + }, + "tbsf.menuentry.label": { + "message": "Manually sort folders" + }, + "tc.foldername": { + "message": "Mappenavn" } -} +} \ No newline at end of file diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 2e2e954..6e0f53e 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Lokale Ordner" + }, + "accountsort.noaccountsetupyet": { + "message": "Es wurde noch kein Konto eingerichtet!" + }, + "accountsort.note": { + "message": "Sie können die Reihenfolge der Konten in diesem Bereich ändern. (Sie können dies auch in den Kontoeinstellungen von Thunderbird 91 und höher tun)." + }, + "accountsort.restartwarning": { + "message": "Zum Übernehmen der Änderungen muss Thunderbird neu gestartet werden." + }, + "accountsort.warning": { + "message": "Standardmäßig erscheinen E-Mail-Konten, Unix-Movemail-Konten sowie Blog- & News-Feed-Konten an oberster Position, danach folgen die "Lokalen Ordner" und als letztes Newsgruppen-Konten. Dies kann geändert werden, indem das Konto "Lokale Ordner" oder eins der Newsgruppen-Konten als Standardkonto ausgewählt wird, wodurch es an die oberste Position gesetzt wird." + }, + "accountssort.description": { + "message": "Diese Ansicht erlaubt, die Konten in der Konten-/Ordneransicht zu sortieren. Im nächsten Tab können die Ordner innerhalb eines Kontos sortiert werden." + }, + "button.account_name": { + "message": "Kontoname" + }, + "button.close": { + "message": "Schließen" + }, + "button.move_down": { + "message": "Nach unten" + }, + "button.move_up": { + "message": "Nach oben" + }, + "button.refresh": { + "message": "Konten-/Ordnerliste aktualisieren" + }, + "button.restart": { + "message": "Thunderbird neu starten" + }, + "checkbox.case_sensitive": { + "message": "Groß-/Kleinschreibung beachten" + }, "extensionDescription": { "description": "Description of the extension", "message": "Ermöglicht es Ihnen, die Reihenfolge der Ordner im Ordnerfenster zu ändern." + }, + "extra.description": { + "message": "Tab für verschiedene ordnerbezogene Einstellungen" + }, + "extra.hide_folder_icons": { + "message": "Ordnersymbole ausblenden (Dieses Add-on muss neu gestartet werden)" + }, + "extra.misc": { + "message": "Sonstiges" + }, + "extra.nofolder": { + "message": "Kein Ordner ausgewählt" + }, + "extra.startup": { + "message": "Startordner" + }, + "extra.startupfolder": { + "message": "Beim Start von Thunderbird folgenden Ordner öffnen:" + }, + "extra.startupfolder.notice": { + "message": "* Diese Funktion funktioniert möglicherweise nicht zuverlässig in Thunderbird 97 und früheren Versionen." + }, + "extra.usecustom": { + "message": "Benutzerdefinierten Ordner verwenden" + }, + "extra.uselast": { + "message": "Zuletzt geöffneten Ordner verwenden" + }, + "extra.usethisfolder": { + "message": "Diesen Ordner verwenden" + }, + "gb.extra": { + "message": "Weitere Informationen" + }, + "gb.first": { + "message": "Erstes Konto in der Konten-/Ordneransicht" + }, + "gb.move": { + "message": "Ausgewähltes Konto verschieben" + }, + "gb.movefolder": { + "message": "Ausgewählten Ordner verschieben" + }, + "gb.sort_by": { + "message": "Sortieren nach" + }, + "gb.sort_siblings_by": { + "message": "Sortieren innerhalb der ausgewählten Ebene nach" + }, + "gbfirst.default": { + "message": "Standardkonto (siehe Beschreibung darunter):" + }, + "general.title": { + "message": "Konten- & Ordnerreihenfolge ändern" + }, + "mi.firstmail": { + "message": "Erstes E-Mail- oder Blogs & News-Feeds-Konto in der Liste" + }, + "sortfolders.description": { + "message": "Diese Ansicht erlaubt das Sortieren der Ordner eines jeden Kontos. Wählen Sie erst ein Konto aus und anschließend die Sortiermethode." + }, + "sortmethod.alphabetical.ascending": { + "message": "Einfacher Vergleich des Ordnernamens (Aufsteigende Reihenfolge)" + }, + "sortmethod.alphabetical.descending": { + "message": "Einfacher Vergleich des Ordnernamens (Absteigende Reihenfolge)" + }, + "sortmethod.alphabetical.description": { + "message": "Dies ist eine übliche Sortierfunktion, die auf einfachem Zeichenvergleich der Namen basiert." + }, + "sortmethod.custom": { + "message": "Benutzerdefinierte Sortierfunktion" + }, + "sortmethod.custom.description": { + "message": "Die Ordner lassen sich über die obigen Schaltflächen verschieben. Später erstellte Ordner werden am Ende der Liste hinzugefügt. Die Veränderungen können später hier rückgängig gemacht werden." + }, + "sortmethod.default": { + "message": "Thunderbirds eigene Sortiermethode" + }, + "sortmethod.default.description": { + "message": "Dies ist Thunderbirds eigene Sortiermethode, die besondere Unicode-Zeichen nicht berücksichtigt." + }, + "sortmethod.samplelegend": { + "message": "(Beispielhafte Konten-/Ordneransicht)" + }, + "tab.accounts": { + "message": "Konten sortieren" + }, + "tab.extra": { + "message": "Erweitert" + }, + "tab.folders": { + "message": "Ordner sortieren" + }, + "tbsf.menuentry.label": { + "message": "Konten- & Ordnerreihenfolge ändern" + }, + "tc.foldername": { + "message": "Ordnername" } } diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 7ba4f8f..c872512 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Local Folders" + }, + "accountsort.noaccountsetupyet": { + "message": "You have no account setup yet!" + }, + "accountsort.note": { + "message": "You can change the order of accounts in this panel. (On Thunderbird 91 and later, you can also do it in the Account Settings UI.)" + }, + "accountsort.restartwarning": { + "message": "You must restart Thunderbird for the changes to take effect." + }, + "accountsort.warning": { + "message": "Per default, regular mail accounts, Unix Movemail accounts as well as RSS accounts come first, "Local Folders" are next, and at the bottom of the list are the News accounts. You can override this by choosing either "Local Folders" or one of the Newsgroup accounts as default, hence putting it on top of the list." + }, + "accountssort.description": { + "message": "This panel allows you to order the accounts in the folder pane. Use next tab if you wish to sort folders inside an account." + }, + "button.account_name": { + "message": "Account name" + }, + "button.close": { + "message": "Close" + }, + "button.move_down": { + "message": "Move down" + }, + "button.move_up": { + "message": "Move up" + }, + "button.refresh": { + "message": "Refresh folder pane" + }, + "button.restart": { + "message": "Restart Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Case sensitive" + }, "extensionDescription": { "description": "Description of the extension", "message": "Allows you to change the order of folders in the folder pane." + }, + "extra.description": { + "message": "Use this tab to further customize Thunderbird using various folder-related settings" + }, + "extra.hide_folder_icons": { + "message": "Hide folder icons (Need to restart this add-on)" + }, + "extra.misc": { + "message": "Misc." + }, + "extra.nofolder": { + "message": "No folder selected" + }, + "extra.startup": { + "message": "Startup folder" + }, + "extra.startupfolder": { + "message": "By default, start with the following folder open" + }, + "extra.startupfolder.notice": { + "message": "* This feature may not work reliably on Thunderbird 97 and earlier versions." + }, + "extra.usecustom": { + "message": "Use a custom folder" + }, + "extra.uselast": { + "message": "Use last opened folder" + }, + "extra.usethisfolder": { + "message": "Use this folder" + }, + "gb.extra": { + "message": "Additional information" + }, + "gb.first": { + "message": "First account in the folder pane" + }, + "gb.move": { + "message": "Move selected account" + }, + "gb.movefolder": { + "message": "Move selected folder" + }, + "gb.sort_by": { + "message": "Sort by" + }, + "gb.sort_siblings_by": { + "message": "Sort within the selected level by" + }, + "gbfirst.default": { + "message": "This is also the default account." + }, + "general.title": { + "message": "Manually sort folders" + }, + "mi.firstmail": { + "message": "First RSS or Mail account in the list" + }, + "sortfolders.description": { + "message": "This panel allows you to sort the folders of each account. First select an account, then a sort method." + }, + "sortmethod.alphabetical.ascending": { + "message": "Use plain string compare (Ascending order)" + }, + "sortmethod.alphabetical.descending": { + "message": "Use plain string compare (Descending order)" + }, + "sortmethod.alphabetical.description": { + "message": "This is a standard sort function that uses nothing but the most basic string comparison function." + }, + "sortmethod.custom": { + "message": "Use a manually defined sort function" + }, + "sortmethod.custom.description": { + "message": "Use the buttons above to manually sort your folders. If a new folder is added, it will be put at the end of the list. You can come back here later if that doesn't suit you." + }, + "sortmethod.default": { + "message": "Use Thunderbird's default" + }, + "sortmethod.default.description": { + "message": "This is Thunderbird's default sort function." + }, + "sortmethod.samplelegend": { + "message": "(Sample folder pane above)" + }, + "tab.accounts": { + "message": "Sort accounts" + }, + "tab.extra": { + "message": "Extra settings" + }, + "tab.folders": { + "message": "Sort folders" + }, + "tbsf.menuentry.label": { + "message": "Manually sort folders" + }, + "tc.foldername": { + "message": "Folder Name" } -} +} \ No newline at end of file diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 1422af2..412a2ea 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Carpetas Locales" + }, + "accountsort.noaccountsetupyet": { + "message": "Aún no tiene un cuenta configurada." + }, + "accountsort.note": { + "message": "Puede cambiar el orden de las cuentas en este panel. (También puedes hacerlo en la interfaz de usuario de configuración de cuentas en Thunderbird 91 y posteriores)." + }, + "accountsort.restartwarning": { + "message": "Debe reiniciar Thunderbird para que los cambios tengan efecto." + }, + "accountsort.warning": { + "message": "Cuentas de correo normales, cuentas Unix Movemail y las cuentas RSS deben ir primero. Las carpetas locales son las siguientes, y al final de la lista las cuentas de Noticias. Puede modificar este orden eligiendo como cuenta predeterminada las Carpetas Locales o una de las cuentas de Noticias, pasando esta al comienzo de la lista." + }, + "accountssort.description": { + "message": "Este panel le permite ordenar las cuentas del panel de carpetas. Usar próxima pestaña si desea ordenar las carpetas dentro de una cuenta." + }, + "button.account_name": { + "message": "Nombre de la cuenta" + }, + "button.close": { + "message": "Cerrar" + }, + "button.move_down": { + "message": "Abajo" + }, + "button.move_up": { + "message": "Arriba" + }, + "button.refresh": { + "message": "Actualizar panel de carpetas" + }, + "button.restart": { + "message": "Reiniciar Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Mayúsculas/minúsculas sí importan" + }, "extensionDescription": { "description": "Description of the extension", "message": "Permite cambiar el orden de las carpetas en el panel de carpetas." + }, + "extra.description": { + "message": "Usar esta pestaña para personalizar Thunderbird usando varias opciones relacionadas con carpetas" + }, + "extra.hide_folder_icons": { + "message": "Ocultar iconos de carpetas (Es necesario reiniciar este complemento)" + }, + "extra.misc": { + "message": "Varios" + }, + "extra.nofolder": { + "message": "Carpeta no seleccionada" + }, + "extra.startup": { + "message": "Carpeta de inicio" + }, + "extra.startupfolder": { + "message": "Comenzar predeterminadamente con la siguiente carpeta abierta" + }, + "extra.startupfolder.notice": { + "message": "* Es posible que esta función no funcione de forma fiable en Thunderbird 97 y versiones anteriores." + }, + "extra.usecustom": { + "message": "Usar una carpeta personalizada" + }, + "extra.uselast": { + "message": "Usar la última carpeta abierta" + }, + "extra.usethisfolder": { + "message": "Usar esta carpeta" + }, + "gb.extra": { + "message": "Información adicional" + }, + "gb.first": { + "message": "Primera cuenta en el panel de carpetas" + }, + "gb.move": { + "message": "Mover cuenta seleccionada" + }, + "gb.movefolder": { + "message": "Mover carpeta seleccionada" + }, + "gb.sort_by": { + "message": "Ordenar por" + }, + "gb.sort_siblings_by": { + "message": "Ordenar dentro del nivel seleccionado por" + }, + "gbfirst.default": { + "message": "Esta también es la cuenta predeterminada." + }, + "general.title": { + "message": "Manually sort folders" + }, + "mi.firstmail": { + "message": "Primer RDD o cuenta de correo en la lista" + }, + "sortfolders.description": { + "message": "Este panel permite ordenar las carpetas de cada cuenta. Primero seleccione una cuenta y después elija un método de ordenación." + }, + "sortmethod.alphabetical.ascending": { + "message": "Usar comparación de cadena normal (Orden ascendente)" + }, + "sortmethod.alphabetical.descending": { + "message": "Usar comparación de cadena normal (Orden descendente)" + }, + "sortmethod.alphabetical.description": { + "message": "Esta es una función estándar que utiliza sólo la función más básica de comparación de cadenas." + }, + "sortmethod.custom": { + "message": "Usar función de ordenación definida manualmente" + }, + "sortmethod.custom.description": { + "message": "Use los botones de arriba para ordenar manualmente sus carpetas. Si se añade una nueva carpeta, se pondrá al final de la lista. Usted puede volver aquí más adelante si esto no le conviene." + }, + "sortmethod.default": { + "message": "Usar predeterminado de Thunderbird" + }, + "sortmethod.default.description": { + "message": "Esta es la función de ordenación predeterminada de Thunderbird." + }, + "sortmethod.samplelegend": { + "message": "(Panel de carpetas del ejemplo anterior)" + }, + "tab.accounts": { + "message": "Ordenar cuentas" + }, + "tab.extra": { + "message": "Opciones extra" + }, + "tab.folders": { + "message": "Ordenar carpetas" + }, + "tbsf.menuentry.label": { + "message": "Manually sort folders" + }, + "tc.foldername": { + "message": "Nombre de carpeta" } -} +} \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 651aba8..d66d9ae 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Dossiers locaux" + }, + "accountsort.noaccountsetupyet": { + "message": "Vous n'avez pas encore configuré de compte !" + }, + "accountsort.note": { + "message": "Vous pouvez modifier l'ordre des comptes dans ce panneau. (Sur Thunderbird 91 et plus, vous pouvez également le faire dans l'interface utilisateur des paramètres de compte)." + }, + "accountsort.restartwarning": { + "message": "Il est nécessaire de redémarrer Thunderbird pour que les changements soient pris en compte." + }, + "accountsort.warning": { + "message": "Les comptes de courrier, MoveMail ou RSS viennent en premier, suivis par les dossiers locaux, puis par les comptes de News. Les dossiers locaux ou l'un des comptes de news peuvent apparaître en premier dans la liste grâce à l'option ci-dessus." + }, + "accountssort.description": { + "message": "Cet onglet permet de choisir l'ordre dans lequel s'affichent les comptes dans le panneau latéral. Pour trier les dossiers d'un compte donné, veuillez vous référer à l'onglet suivant." + }, + "button.account_name": { + "message": "Nom du compte" + }, + "button.close": { + "message": "Fermer" + }, + "button.move_down": { + "message": "Descendre" + }, + "button.move_up": { + "message": "Monter" + }, + "button.refresh": { + "message": "Rafraîchir la liste des dossiers" + }, + "button.restart": { + "message": "Redémarrer Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Différences majuscules/minuscule" + }, "extensionDescription": { "description": "Description of the extension", "message": "Permet de modifier l'ordre des dossiers dans le volet des dossiers." + }, + "extra.description": { + "message": "Cet onglet vous permet de personnaliser Thunderbird à l'aide de divers réglages concernant les dossiers." + }, + "extra.hide_folder_icons": { + "message": "Masquer les icônes des dossiers (Il faut redémarrer cet add-on)" + }, + "extra.misc": { + "message": "Divers." + }, + "extra.nofolder": { + "message": "Pas de dossier sélectionné" + }, + "extra.startup": { + "message": "Dossier à afficher au démarrage de Thunderbird." + }, + "extra.startupfolder": { + "message": "Par défaut, afficher le dossier suivant au démarrage de Thunderbird" + }, + "extra.startupfolder.notice": { + "message": "* Cette fonction peut ne pas fonctionner de manière fiable dans Thunderbird 97 et les versions antérieures." + }, + "extra.usecustom": { + "message": "Choisir un dossier particulier" + }, + "extra.uselast": { + "message": "Utiliser le dernier dossier ouvert" + }, + "extra.usethisfolder": { + "message": "Utiliser ce dossier" + }, + "gb.extra": { + "message": "Quelques précisions" + }, + "gb.first": { + "message": "Compte à afficher en premier" + }, + "gb.move": { + "message": "Déplacer le compte sélectionné" + }, + "gb.movefolder": { + "message": "Déplacer le dossier sélectionné" + }, + "gb.sort_by": { + "message": "Trier par" + }, + "gb.sort_siblings_by": { + "message": "Trier dans le niveau sélectionné par" + }, + "gbfirst.default": { + "message": "Ce compte sera également le compte par défaut." + }, + "general.title": { + "message": "Trier les dossiers manuellement" + }, + "mi.firstmail": { + "message": "Premier compte courrier/RSS dans la liste" + }, + "sortfolders.description": { + "message": "Cet onglet permet de trier les dossiers d'un compte donné." + }, + "sortmethod.alphabetical.ascending": { + "message": "Trier alphabétiquement (Ordre ascendant)" + }, + "sortmethod.alphabetical.descending": { + "message": "Trier alphabétiquement (Ordre décroissant)" + }, + "sortmethod.alphabetical.description": { + "message": "Il s'agit d'une fonction de tri standard qui n'utilise rien d'autre que la fonction de comparaison de chaînes la plus élémentaire." + }, + "sortmethod.custom": { + "message": "Choisir à la main l'ordre de tri" + }, + "sortmethod.custom.description": { + "message": "Utilisez les boutons pour ordonner les dossiers comme vous le souhaitez. Si un dossier est ajouté ultérieurement, il apparaîtra en fin de liste. Dans l'hypothèse où cela ne vous convient pas, revenez ici pour choisir un emplacement pour le nouveau dossier." + }, + "sortmethod.default": { + "message": "Utiliser la méthode par défaut de Thunderbird" + }, + "sortmethod.default.description": { + "message": "La fonction de tri par défaut de Thunderbird." + }, + "sortmethod.samplelegend": { + "message": "(Exemple de résultat ci-dessus)" + }, + "tab.accounts": { + "message": "Trier les comptes" + }, + "tab.extra": { + "message": "Avancé" + }, + "tab.folders": { + "message": "Trier les dossiers" + }, + "tbsf.menuentry.label": { + "message": "Trier les dossiers manuellement" + }, + "tc.foldername": { + "message": "Dossier" } -} +} \ No newline at end of file diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index 469ce9f..28d2388 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Helyi mappák" + }, + "accountsort.noaccountsetupyet": { + "message": "Még nincs fiókja beállítva!" + }, + "accountsort.note": { + "message": "Ezen a panelen megváltoztathatja a számlák sorrendjét. (A Thunderbird 91 és újabb verziókban ezt a fiókbeállítások felhasználói felületen is megteheti.)" + }, + "accountsort.restartwarning": { + "message": "A változások érvénybe lépéséhez újra kell indítania a Thunderbirdöt." + }, + "accountsort.warning": { + "message": "A szokásos e-mail fiókok, Unix Movemail fiókok és az RSS-fiókok jönnek először. A Helyi mappák a következőek, a lista alján pedig a Hírforrások szerepelnek. Ezt felülbírálhatja azzal, hogy a Helyi mappákat vagy egy Hírforrást választja alapértelmezettként, így azt téve a lista tetejére." + }, + "accountssort.description": { + "message": "Ezzel a panellel sorba rendezheti a fiókokat a mappa panelen. Használja a következő lapot, ha a mappákat akarja sorba rendezni egy fiókon belül." + }, + "button.account_name": { + "message": "Fiók neve" + }, + "button.close": { + "message": "Bezárás" + }, + "button.move_down": { + "message": "Mozgatás le" + }, + "button.move_up": { + "message": "Mozgatás fel" + }, + "button.refresh": { + "message": "Mappa panel frissítése" + }, + "button.restart": { + "message": "A Thunderbird újraindítása" + }, + "checkbox.case_sensitive": { + "message": "Nagybetű/kisbetű érzékeny" + }, "extensionDescription": { "description": "Description of the extension", "message": "Lehetővé teszi a mappák sorrendjének megváltoztatását a mappapanelben." + }, + "extra.description": { + "message": "Használja ezt a lapot a Thunderbird különböző mappákkal kapcsolatos beállításokkal történő testreszabásához" + }, + "extra.hide_folder_icons": { + "message": "Mappaikonok elrejtése (újra kell indítani ezt a bővítményt)" + }, + "extra.misc": { + "message": "Egyéb" + }, + "extra.nofolder": { + "message": "Nincs mappa kiválasztva" + }, + "extra.startup": { + "message": "Kezdeti mappa" + }, + "extra.startupfolder": { + "message": "Alapértelmezetten indítson a következő mappa megnyitásával" + }, + "extra.startupfolder.notice": { + "message": "* Ez a funkció a Thunderbird 97 és korábbi verziókban nem feltétlenül működik megbízhatóan." + }, + "extra.usecustom": { + "message": "Egyéni mappa használata" + }, + "extra.uselast": { + "message": "A legutóbb megnyitott mappa megnyitása" + }, + "extra.usethisfolder": { + "message": "Ezen mappa használata" + }, + "gb.extra": { + "message": "További információk" + }, + "gb.first": { + "message": "Első fiók a mappa panelen" + }, + "gb.move": { + "message": "A kiválasztott fiók mozgatása" + }, + "gb.movefolder": { + "message": "A kiválasztott mappa mozgatása" + }, + "gb.sort_by": { + "message": "Rendezés" + }, + "gb.sort_siblings_by": { + "message": "Rendezés a kiválasztott szinten belül" + }, + "gbfirst.default": { + "message": "Ez az alapértelmezett fiók." + }, + "general.title": { + "message": "Mappák kézi rendezése" + }, + "mi.firstmail": { + "message": "Első RSS vagy levelezőfiók a listában" + }, + "sortfolders.description": { + "message": "Ezzel a panellel minden fiók mappáit sorba rendezheti. Először válassza ki a fiókat, majd a rendezési módszert." + }, + "sortmethod.alphabetical.ascending": { + "message": "Egyszerű összehasonlítás használata (Felmenő sorrend)" + }, + "sortmethod.alphabetical.descending": { + "message": "Egyszerű összehasonlítás használata (Lefelé haladó sorrend)" + }, + "sortmethod.alphabetical.description": { + "message": "Ez az alapértelmezett rendezési függvény, amely nem csinál mást, mint egyszerűen összehasonlítja a karakterláncokat." + }, + "sortmethod.custom": { + "message": "Egyéni rendezési függvény használata" + }, + "sortmethod.custom.description": { + "message": "Használja a fenti gombokat a mappák kézi rendezéséhez. Ha egy új mappa kerül hozzáadásra, akkor a lista végére kerül. Bármikor visszatérhet ide, ha a sorrend nem felel meg Önnek." + }, + "sortmethod.default": { + "message": "Alapértelmezés használata" + }, + "sortmethod.default.description": { + "message": "Ez a Thunderbird alapértelmezett rendezési függvénye." + }, + "sortmethod.samplelegend": { + "message": "(Minta mappa panel fent)" + }, + "tab.accounts": { + "message": "Fiókok rendezése" + }, + "tab.extra": { + "message": "További beállítások" + }, + "tab.folders": { + "message": "Mappák rendezése" + }, + "tbsf.menuentry.label": { + "message": "Mappák kézi rendezése" + }, + "tc.foldername": { + "message": "Mappanév" } -} +} \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 4da2d20..a90670f 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Cartelle locali" + }, + "accountsort.noaccountsetupyet": { + "message": "Nessun account impostato" + }, + "accountsort.note": { + "message": "In questo pannello è possibile modificare l'ordine degli account. (Su Thunderbird 91 e successivi, è possibile farlo anche nell'interfaccia utente delle Impostazioni account)." + }, + "accountsort.restartwarning": { + "message": "Riavviare Thunderbird per rendere effettive le modifiche" + }, + "accountsort.warning": { + "message": "Solitamente vengono elencati per primi gli account di posta, account Movemail ed account RSS. In seguito vengono elencate le cartelle locali, ed infine in fondo alla lista sono presenti gli account di gruppi di discussione. È possibile ignorare tale ordine predefinito degli account selezionando le cartelle locali oppure uno degli account di gruppi di discussione impostandolo come predefinito e quindi spostandolo in cima alla lista" + }, + "accountssort.description": { + "message": "È possibile ordinare gli account nel pannello delle cartelle. (Per ordinare le cartelle dei singoli account selezionare 'Ordinamento cartelle')" + }, + "button.account_name": { + "message": "Nome del account" + }, + "button.close": { + "message": "Chiudi" + }, + "button.move_down": { + "message": "Sposta giù" + }, + "button.move_up": { + "message": "Sposta su" + }, + "button.refresh": { + "message": "Aggiorna il pannello delle cartelle" + }, + "button.restart": { + "message": "Riavvia Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Sensibile alle maiuscole e alle minuscole" + }, "extensionDescription": { "description": "Description of the extension", "message": "Consente di modificare l'ordine delle cartelle nel riquadro delle cartelle." + }, + "extra.description": { + "message": "È possibile personalizzare ulteriormente Thunderbird selezionando una cartella da aprire all'avvio" + }, + "extra.hide_folder_icons": { + "message": "Nascondi le icone delle cartelle (è necessario riavviare questo componente aggiuntivo)" + }, + "extra.misc": { + "message": "Varie" + }, + "extra.nofolder": { + "message": "(nessuna cartella selezionata)" + }, + "extra.startup": { + "message": "Cartella da aprire all'avvio" + }, + "extra.startupfolder": { + "message": "Cartella da aprire all'avvio di Thunderbird" + }, + "extra.startupfolder.notice": { + "message": "* Questa funzione potrebbe non funzionare in modo affidabile in Thunderbird 97 e versioni precedenti." + }, + "extra.usecustom": { + "message": "Utilizza una cartella da selezionare" + }, + "extra.uselast": { + "message": "Utilizza l'ultima cartella aperta" + }, + "extra.usethisfolder": { + "message": "Utilizza questa cartella" + }, + "gb.extra": { + "message": "Informazioni complementari" + }, + "gb.first": { + "message": "Primo account nel pannello delle cartelle" + }, + "gb.move": { + "message": "Spostamento dell'account selezionato" + }, + "gb.movefolder": { + "message": "Spostamento della cartella selezionata" + }, + "gb.sort_by": { + "message": "Ordinamento per" + }, + "gb.sort_siblings_by": { + "message": "Ordinamento all'interno del livello selezionato per" + }, + "gbfirst.default": { + "message": "(Solitamente è anche l'account predefinito)" + }, + "general.title": { + "message": "Impostazioni di Manually sort folders" + }, + "mi.firstmail": { + "message": "Primo account RSS o di posta nella lista" + }, + "sortfolders.description": { + "message": "È possibile ordinare le cartelle di ogni account. Selezionare un account ed in seguito un metodo di ordinamento" + }, + "sortmethod.alphabetical.ascending": { + "message": "Ordine alfabetico (Ordine ascendente)" + }, + "sortmethod.alphabetical.descending": { + "message": "Ordine alfabetico (Ordine decrescente)" + }, + "sortmethod.alphabetical.description": { + "message": "È stato selezionato l'ordine alfabetico, che si basa semplicemente sull'ordine alfabetico" + }, + "sortmethod.custom": { + "message": "Ordine manuale" + }, + "sortmethod.custom.description": { + "message": "È stato selezionato l'ordine manuale, che permette di spostare su/giù le cartelle dell'account tramite i relativi tasti 'Sposta su' e 'Sposta giù'. Se viene aggiunta una nuova cartella, questa viene inserita in fondo alla lista. Sarà possibile in seguito spostarla" + }, + "sortmethod.default": { + "message": "Ordine predefinito di Thunderbird" + }, + "sortmethod.default.description": { + "message": "È stato selezionato l'ordine predefinito di Thunderbird" + }, + "sortmethod.samplelegend": { + "message": "(Esempio del pannello delle cartelle)" + }, + "tab.accounts": { + "message": "Ordinamento account" + }, + "tab.extra": { + "message": "Varie" + }, + "tab.folders": { + "message": "Ordinamento cartelle" + }, + "tbsf.menuentry.label": { + "message": "Impostazioni di Manually sort folders…" + }, + "tc.foldername": { + "message": "Nome della cartella" } -} +} \ No newline at end of file diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 74d4259..47c847b 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "ローカルフォルダ" + }, + "accountsort.noaccountsetupyet": { + "message": "アカウント設定がまだありません!" + }, + "accountsort.note": { + "message": "このパネルでアカウントの順序を変更することができます。(Thunderbird 91以降ではアカウント設定UIでも行えます。)" + }, + "accountsort.restartwarning": { + "message": "変更を反映させるためには Thunderbird を再起動してください" + }, + "accountsort.warning": { + "message": "通常のメールアカウントは RSS アカウントと同様に Unix Movemail アカウントの最上部に置かれます。 その次にローカルフォルダ、そしてリスト最後はブログとニュースアカウントとなります。 デフォルトとしてローカルフォルダかブログとニュースグループアカウントのどちらかを選んでリスト先頭に配置することによってこれを変えることができます。" + }, + "accountssort.description": { + "message": "このパネルではフォルダペインのアカウントの順序を変更することができます。各アカウント内のフォルダを並べ替えるには次のタブ [フォルダの並べ替え] を使用してください。" + }, + "button.account_name": { + "message": "アカウント名" + }, + "button.close": { + "message": "閉じる" + }, + "button.move_down": { + "message": "下へ" + }, + "button.move_up": { + "message": "上へ" + }, + "button.refresh": { + "message": "フォルダペインの更新" + }, + "button.restart": { + "message": "Thunderbird の再起動" + }, + "checkbox.case_sensitive": { + "message": "大文字と小文字を区別" + }, "extensionDescription": { "description": "Description of the extension", "message": "フォルダペインでのフォルダの並び順を変更することができます。" + }, + "extra.description": { + "message": "このタブでは様々なフォルダ関連の設定で Thunderbird をさらにカスタマイズできます。" + }, + "extra.hide_folder_icons": { + "message": "フォルダーアイコンを非表示にする (このアドオンの再始動が必要)" + }, + "extra.misc": { + "message": "その他" + }, + "extra.nofolder": { + "message": "フォルダの選択なし" + }, + "extra.startup": { + "message": "起動フォルダ" + }, + "extra.startupfolder": { + "message": "以下のフォルダが開いた状態で起動する" + }, + "extra.startupfolder.notice": { + "message": "* Thunderbird 97以前では、この機能が確実に働かない場合があります。" + }, + "extra.usecustom": { + "message": "カスタムフォルダ" + }, + "extra.uselast": { + "message": "最後に開いたフォルダ" + }, + "extra.usethisfolder": { + "message": "現在のフォルダ" + }, + "gb.extra": { + "message": "追加情報" + }, + "gb.first": { + "message": "フォルダペイン最上部のアカウント設定" + }, + "gb.move": { + "message": "選択したアカウントの移動" + }, + "gb.movefolder": { + "message": "選択したフォルダの移動" + }, + "gb.sort_by": { + "message": "並べ替え" + }, + "gb.sort_siblings_by": { + "message": "選択した階層の並べ替え" + }, + "gbfirst.default": { + "message": "これはデフォルトアカウントです" + }, + "general.title": { + "message": "Manually sort folders 設定" + }, + "mi.firstmail": { + "message": "フォルダペイン最上部を RSS またはメールアカウントにする" + }, + "sortfolders.description": { + "message": "このパネルでは各アカウントのフォルダを並べ替えることができます。まずアカウントを選び、次に並べ替え方法を選んでください。" + }, + "sortmethod.alphabetical.ascending": { + "message": "名前で並べ替え (昇順)" + }, + "sortmethod.alphabetical.descending": { + "message": "名前で並べ替え (降順)" + }, + "sortmethod.alphabetical.description": { + "message": "これは単純な文字列比較による並べ替えです。" + }, + "sortmethod.custom": { + "message": "手動で並べ替え" + }, + "sortmethod.custom.description": { + "message": "上のボタンを使用してフォルダを並べ替えてください。" + }, + "sortmethod.default": { + "message": "Thunderbird のデフォルトの順序" + }, + "sortmethod.default.description": { + "message": "これは Thunderbird のデフォルトの順序です。" + }, + "sortmethod.samplelegend": { + "message": "(サンプルフォルダ欄)" + }, + "tab.accounts": { + "message": "アカウントの並べ替え" + }, + "tab.extra": { + "message": "追加設定" + }, + "tab.folders": { + "message": "フォルダの並べ替え" + }, + "tbsf.menuentry.label": { + "message": "フォルダの並べ替え" + }, + "tc.foldername": { + "message": "フォルダ名" } -} +} \ No newline at end of file diff --git a/_locales/nb_NO/messages.json b/_locales/nb_NO/messages.json new file mode 100644 index 0000000..b44cbfb --- /dev/null +++ b/_locales/nb_NO/messages.json @@ -0,0 +1,144 @@ +{ + "accountsort.localfolders": { + "message": "Lokale mapper" + }, + "accountsort.noaccountsetupyet": { + "message": "Du har ikke satt opp noen kontoer enda!" + }, + "accountsort.note": { + "message": "Du kan endre rekkefølgen på kontoene i dette panelet. (På Thunderbird 91 og nyere kan du også gjøre det i brukergrensesnittet for kontoinnstillinger.)" + }, + "accountsort.restartwarning": { + "message": "Du må starte Thunderbird på nytt for at endringene skal vises." + }, + "accountsort.warning": { + "message": "Normale e-postkontoer, Unix Movemail-kontoer, samt RSS-kontoer må komme først. Lokale mapper kommer så, og nederst på listen er Nyhetsgrupper. Du kan overstyre dette enten ved å velge Lokale mapper eller en av Nyhetsgruppene som standard og dermed flytte den øverst på listen." + }, + "accountssort.description": { + "message": "I denne fanen kan du sortere kontoene i mappevisningen. Velg neste fane hvis du ønsker å sortere mappene i en konto." + }, + "button.account_name": { + "message": "Kontonavn" + }, + "button.close": { + "message": "Lukk" + }, + "button.move_down": { + "message": "Flytt ned" + }, + "button.move_up": { + "message": "Flytt opp" + }, + "button.refresh": { + "message": "Oppdater" + }, + "button.restart": { + "message": "Start Thunderbird på nytt" + }, + "checkbox.case_sensitive": { + "message": "Skiller mellom store og små bokstaver" + }, + "extensionDescription": { + "description": "Beskrivelse av utvidelsen", + "message": "Lar deg endre rekkefølgen på mapper i mapperuten." + }, + "extra.description": { + "message": "I denne fanen kan du finne flere innstillinger relatert til mapper i Thunderbird." + }, + "extra.hide_folder_icons": { + "message": "Skjul mappeikoner (Må starte dette tillegget på nytt)" + }, + "extra.misc": { + "message": "Diverse" + }, + "extra.nofolder": { + "message": "Ingen mappe er valgt" + }, + "extra.startup": { + "message": "Oppstartsmappe" + }, + "extra.startupfolder": { + "message": "Start Thunderbird med følgende mappe åpen" + }, + "extra.startupfolder.notice": { + "message": "* Denne funksjonen fungerer kanskje ikke pålitelig i Thunderbird 97 og tidligere versjoner." + }, + "extra.usecustom": { + "message": "Bruk en forhåndsvalgt mappe" + }, + "extra.uselast": { + "message": "Bruk siste åpnede mappe" + }, + "extra.usethisfolder": { + "message": "Bruk denne mappen" + }, + "gb.extra": { + "message": "Tilleggsinformasjon" + }, + "gb.first": { + "message": "Første konto i mappevisning" + }, + "gb.move": { + "message": "Flytt valgt konto" + }, + "gb.movefolder": { + "message": "Flytt valgt mappe" + }, + "gb.sort_by": { + "message": "Sorter etter" + }, + "gb.sort_siblings_by": { + "message": "Sorter innenfor det valgte nivået etter" + }, + "gbfirst.default": { + "message": "Dette er standard kontoen." + }, + "general.title": { + "message": "Sorter mapper manuelt" + }, + "mi.firstmail": { + "message": "Første RRS eller E-postkonto i lista" + }, + "sortfolders.description": { + "message": "I denne fanen kan du sortere mappene for hver konto. Velg først en konto, så en sorteringsmetode." + }, + "sortmethod.alphabetical.ascending": { + "message": "Sorter tekst alfabetisk (Ascending order)" + }, + "sortmethod.alphabetical.descending": { + "message": "Sorter tekst alfabetisk (Descending order)" + }, + "sortmethod.alphabetical.description": { + "message": "Dette valget sorterer mappene alfabetisk." + }, + "sortmethod.custom": { + "message": "Egendefinert sortering" + }, + "sortmethod.custom.description": { + "message": "Bruk knappene over til å manuelt sortere dine mapper. Når du legger til en ny mappe, vil denne havne sist på listen. Dette kan du selvfølgelig endre senere i denne menyen." + }, + "sortmethod.default": { + "message": "Forhåndsvalg av Thunderbird" + }, + "sortmethod.default.description": { + "message": "Dette er den forhåndsvalgte sorteringsmetoden til Thunderbird." + }, + "sortmethod.samplelegend": { + "message": "(Eksempelmappe visning over)" + }, + "tab.accounts": { + "message": "Sorter kontoer" + }, + "tab.extra": { + "message": "Flere instillinger" + }, + "tab.folders": { + "message": "Sorter mapper" + }, + "tbsf.menuentry.label": { + "message": "Sorter mapper manuelt" + }, + "tc.foldername": { + "message": "Mappenavn" + } +} \ No newline at end of file diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index a286f05..fa6495f 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Lokale mappen" + }, + "accountsort.noaccountsetupyet": { + "message": "U hebt nog geen account ingesteld!" + }, + "accountsort.note": { + "message": "U kunt de volgorde van de accounts in dit paneel wijzigen. (Op Thunderbird 91 en later, kunt u dit ook doen in de Account Settings UI)." + }, + "accountsort.restartwarning": { + "message": "U dient Thunderbird te herstarten om de wijzigingen door te voeren." + }, + "accountsort.warning": { + "message": "Regulier mailaccounts, Unix Movemailaccounts en RSS-accounts dienen eerst te komen. Hierna volgt Lokale mappen, en onderaan de lijst staan de Nieuwsaccounts. U kunt dit wijzigen door Lokale mappen of één van de Nieuwsaccounts als standaard in te stellen, waardoor deze bovenaan de lijst wordt geplaatst." + }, + "accountssort.description": { + "message": "Met dit venster kunt u de accounts ordenen in het mappenvenster. Gebruik het volgende tabblad als u de mappen binnen een account wilt sorteren." + }, + "button.account_name": { + "message": "Accountnaam" + }, + "button.close": { + "message": "Sluiten" + }, + "button.move_down": { + "message": "Omlaag" + }, + "button.move_up": { + "message": "Omhoog" + }, + "button.refresh": { + "message": "Mappenvenster verversen" + }, + "button.restart": { + "message": "Thunderbird herstarten" + }, + "checkbox.case_sensitive": { + "message": "Hoofdlettergevoelig" + }, "extensionDescription": { "description": "Description of the extension", "message": "Hiermee kunt u de volgorde van de mappen in het deelvenster mappen wijzigen." + }, + "extra.description": { + "message": "Gebruik dit tabblad voor verdere aanpassingen van Thunderbird middels diverse mapgerelateerde instellingen" + }, + "extra.hide_folder_icons": { + "message": "Mappictogrammen verbergen (U moet deze add-on opnieuw opstarten)" + }, + "extra.misc": { + "message": "Diversen" + }, + "extra.nofolder": { + "message": "Geen map geselecteerd" + }, + "extra.startup": { + "message": "Opstartmap" + }, + "extra.startupfolder": { + "message": "Standaard starten met de volgende map geopend" + }, + "extra.startupfolder.notice": { + "message": "* Deze functie werkt mogelijk niet betrouwbaar in Thunderbird 97 en eerdere versies." + }, + "extra.usecustom": { + "message": "Aangepaste map gebruiken" + }, + "extra.uselast": { + "message": "Laatst geopende map gebruiken" + }, + "extra.usethisfolder": { + "message": "Deze map gebruiken" + }, + "gb.extra": { + "message": "Aanvullende informatie" + }, + "gb.first": { + "message": "Eerste account in het mappenvenster" + }, + "gb.move": { + "message": "Geselecteerd account verplaatsen" + }, + "gb.movefolder": { + "message": "Geselecteerde map verplaatsen" + }, + "gb.sort_by": { + "message": "Sorteren op" + }, + "gb.sort_siblings_by": { + "message": "Sorteren binnen het geselecteerde niveau op" + }, + "gbfirst.default": { + "message": "Dit is ook het standaardaccount." + }, + "general.title": { + "message": "Mappen handmatig sorteren" + }, + "mi.firstmail": { + "message": "Eerste RSS- of mailaccount in de lijst" + }, + "sortfolders.description": { + "message": "Met dit venster kunt u de mappen van elke account sorteren. Selecteer eerst een account en daarna een sorteermethode." + }, + "sortmethod.alphabetical.ascending": { + "message": "Vergelijken op platte tekst (Oplopende volgorde)" + }, + "sortmethod.alphabetical.descending": { + "message": "Vergelijken op platte tekst (Aflopende volgorde)" + }, + "sortmethod.alphabetical.description": { + "message": "Dit is een standaardsorteerfunctie die niets anders gebruikt dan de meest basale tekstvergelijkingsfunctie." + }, + "sortmethod.custom": { + "message": "Handmatig ingestelde sorteerfunctie gebruiken" + }, + "sortmethod.custom.description": { + "message": "Gebruik bovenstaande knoppen om uw mappen handmatig te sorteren. Als een nieuwe map wordt toegevoegd, wordt deze aan het einde van de lijst geplaatst. U kunt later hier terugkomen als u dat anders zou willen." + }, + "sortmethod.default": { + "message": "Standaard van Thunderbird gebruiken" + }, + "sortmethod.default.description": { + "message": "Dit is de standaardsorteerfunctie van Thunderbird." + }, + "sortmethod.samplelegend": { + "message": "(Voorbeeld mappenvenster boven)" + }, + "tab.accounts": { + "message": "Accounts sorteren" + }, + "tab.extra": { + "message": "Extra instellingen" + }, + "tab.folders": { + "message": "Mappen sorteren" + }, + "tbsf.menuentry.label": { + "message": "Mappen handmatig sorteren" + }, + "tc.foldername": { + "message": "Mapnaam" } -} +} \ No newline at end of file diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 146ee4c..cd4b47a 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Foldery lokalne" + }, + "accountsort.noaccountsetupyet": { + "message": "Nie masz jeszcze skonfigurowanego konta!" + }, + "accountsort.note": { + "message": "W tym panelu możesz zmienić kolejność kont. (W Thunderbirdzie 91 i nowszych możesz to zrobić także w interfejsie użytkownika Ustawienia konta)." + }, + "accountsort.restartwarning": { + "message": "Aby zmiany zostały zastosowane, należy ponownie uruchomić Thunderbirda." + }, + "accountsort.warning": { + "message": "Standardowe konta pocztowe, konta Unix Movemail, a także konta RSS muszą być na początku listy. Za nimi powinny znaleźć się Foldery lokalne i na końcu konta grup dyskusyjnych. Można zmienić to, wybierając Foldery lokalne lub jedno z kont grup dyskusyjnych jako domyślne i umieścić go na szczycie listy." + }, + "accountssort.description": { + "message": "Tutaj można określić kolejność kont w panelu folderów. Przejdź do karty „Sortuj foldery”, by posortować foldery wewnątrz konta." + }, + "button.account_name": { + "message": "Nazwa konta" + }, + "button.close": { + "message": "Zamknij" + }, + "button.move_down": { + "message": "W dół" + }, + "button.move_up": { + "message": "W górę" + }, + "button.refresh": { + "message": "Odśwież panel folderów" + }, + "button.restart": { + "message": "Uruchom ponownie Thunderbirda" + }, + "checkbox.case_sensitive": { + "message": "Rozróżnianie wielkości liter" + }, "extensionDescription": { "description": "Description of the extension", "message": "Umożliwia zmianę kolejności folderów w okienku folderów." + }, + "extra.description": { + "message": "Użyj tej karty, by dodatkowo dostosować Thunderbirda za pomocą różnych powiązanych z folderami ustawień" + }, + "extra.hide_folder_icons": { + "message": "Ukryj ikony folderów (Wymaga ponownego uruchomienia tego dodatku)" + }, + "extra.misc": { + "message": "Misc." + }, + "extra.nofolder": { + "message": "Nie wybrano folderu" + }, + "extra.startup": { + "message": "Folder początkowy" + }, + "extra.startupfolder": { + "message": "Rozpoczynaj z następującym otwartym folderem" + }, + "extra.startupfolder.notice": { + "message": "* Ta funkcja może nie działać niezawodnie w programie Thunderbird 97 i wcześniejszych wersjach." + }, + "extra.usecustom": { + "message": "użyj własny folder" + }, + "extra.uselast": { + "message": "Użyj ostatnio otwierany folder" + }, + "extra.usethisfolder": { + "message": "Użyj ten folder" + }, + "gb.extra": { + "message": "Dodatkowe informacje" + }, + "gb.first": { + "message": "Pierwsze konto w panelu folderów" + }, + "gb.move": { + "message": "Przenieś wybrane konto" + }, + "gb.movefolder": { + "message": "Przenieś wybrany folder" + }, + "gb.sort_by": { + "message": "Sortuj według" + }, + "gb.sort_siblings_by": { + "message": "Sortowanie w obrębie wybranego poziomu według" + }, + "gbfirst.default": { + "message": "To jest także domyślne konto." + }, + "general.title": { + "message": "Sortuj foldery ręcznie" + }, + "mi.firstmail": { + "message": "Pierwszy kanał RSS lub konto pocztowe na liście" + }, + "sortfolders.description": { + "message": "Tutaj można ręcznie posortować foldery każdego konta. Wybierz konto i metodę sortowania." + }, + "sortmethod.alphabetical.ascending": { + "message": "Używaj prostego porównania znaków (Kolejność rosnąca)" + }, + "sortmethod.alphabetical.descending": { + "message": "Używaj prostego porównania znaków (Kolejność malejąca)" + }, + "sortmethod.alphabetical.description": { + "message": "To jest standardowa funkcja sortowania używająca tylko podstawowych funkcji porównywania ciągów znaków." + }, + "sortmethod.custom": { + "message": "Używaj funkcji zdefiniowanych ręcznie" + }, + "sortmethod.custom.description": { + "message": "Użyj przycisków znajdujących się powyżej, by ręcznie posortować foldery. Nowo dodawane foldery będą umieszczane na końcu listy. W każdej chwili możesz tutaj wrócić i dowolnie zmieniać kolejność." + }, + "sortmethod.default": { + "message": "Używaj domyślnego Thunderbirda" + }, + "sortmethod.default.description": { + "message": "To jest domyślna funkcja sortowania używana przez Thunderbirda." + }, + "sortmethod.samplelegend": { + "message": "(Przykładowy panel folderów)" + }, + "tab.accounts": { + "message": "Sortuj konta" + }, + "tab.extra": { + "message": "Ustawienia dodatkowe" + }, + "tab.folders": { + "message": "Sortuj foldery" + }, + "tbsf.menuentry.label": { + "message": "Sortuj foldery ręcznie" + }, + "tc.foldername": { + "message": "Nazwa folderu" } -} +} \ No newline at end of file diff --git a/_locales/pt/messages.json b/_locales/pt/messages.json index 195b93c..f5837aa 100644 --- a/_locales/pt/messages.json +++ b/_locales/pt/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Pastas locais" + }, + "accountsort.noaccountsetupyet": { + "message": "Ainda não tem uma conta configurada!" + }, + "accountsort.note": { + "message": "Pode alterar a ordem das contas neste painel. (No Thunderbird 91 e mais tarde, pode também fazê-lo na UI Account Settings)." + }, + "accountsort.restartwarning": { + "message": "Tem de reiniciar o Thunderbird para as alterações terem efeito." + }, + "accountsort.warning": { + "message": "As contas normais, contas Unix Movemail assim como contas RSS têm de ser as primeiras. As pastas locais são as seguintes e no por último a lista estão as novas contas. Pode substituir isto escolhendo ou as Pastas locais ou uma das contas de grupos de notícias como pré-definidas colocando-a no topo da lista." + }, + "accountssort.description": { + "message": "Este painel permite-lhe ordenar as contas no painel das pastas. Use o separador seguinte se desejar ordenar as pastas dentro da conta." + }, + "button.account_name": { + "message": "Nome da conta" + }, + "button.close": { + "message": "Fechar" + }, + "button.move_down": { + "message": "Mover para baixo" + }, + "button.move_up": { + "message": "Mover para cima" + }, + "button.refresh": { + "message": "Actualizar painel da pasta" + }, + "button.restart": { + "message": "Reiniciar Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Sensível a maiúsculas e minúsculas" + }, "extensionDescription": { "description": "Description of the extension", "message": "Permite-lhe alterar a ordem das pastas no painel de pastas." + }, + "extra.description": { + "message": "Use este separador para personalizar ainda mais o Thunderbird usando várias definições relacionadas com pastas" + }, + "extra.hide_folder_icons": { + "message": "Ocultar ícones de pastas (Necessidade de reiniciar este suplemento)" + }, + "extra.misc": { + "message": "Diversos" + }, + "extra.nofolder": { + "message": "nenhuma pasta seleccionada" + }, + "extra.startup": { + "message": "Pasta inicial" + }, + "extra.startupfolder": { + "message": "pro defeito, começar a seguinte pasta aberta" + }, + "extra.startupfolder.notice": { + "message": "* Esta característica pode não funcionar de forma fiável nas versões Thunderbird 97 e anteriores." + }, + "extra.usecustom": { + "message": "Usar uma pasta personalizada" + }, + "extra.uselast": { + "message": "Usar última pasta aberta" + }, + "extra.usethisfolder": { + "message": "Usar esta pasta" + }, + "gb.extra": { + "message": "Informação adicional" + }, + "gb.first": { + "message": "Primeira conta no painel de pastas" + }, + "gb.move": { + "message": "Mover a conta seleccionada" + }, + "gb.movefolder": { + "message": "Mover pasta seleccionada" + }, + "gb.sort_by": { + "message": "Ordenar por" + }, + "gb.sort_siblings_by": { + "message": "Ordenar dentro do nível selecionado por" + }, + "gbfirst.default": { + "message": "Esta é também a conta pré-definida." + }, + "general.title": { + "message": "Ordenar pastas manualmente" + }, + "mi.firstmail": { + "message": "Primeiro RSS ou conta de correio na lista" + }, + "sortfolders.description": { + "message": "Este painel permite-lhe ordenar as pastas de cada conta. Primeiro seleccione a conta e depois o método para ordenar." + }, + "sortmethod.alphabetical.ascending": { + "message": "Usar uma comparação simples (Ordem ascendente)" + }, + "sortmethod.alphabetical.descending": { + "message": "Usar uma comparação simples (Ordem decrescente)" + }, + "sortmethod.alphabetical.description": { + "message": "Esta é uma função de ordenação standard que usa apenas a função mais básica de comparação." + }, + "sortmethod.custom": { + "message": "Usar uma função de ordenação definida manualmente" + }, + "sortmethod.custom.description": { + "message": "Use estes botões para ordenar as pastas manualmente. se uma nova pasta for adicionada, será colocada no fim da lista. Pode voltar aqui mais tarde se isso não lhe convier." + }, + "sortmethod.default": { + "message": "Usar a pré-definida do Thunderbird" + }, + "sortmethod.default.description": { + "message": "Esta é a funcionalidade de ordenação por defeito do Thunderbird." + }, + "sortmethod.samplelegend": { + "message": "(Painel da pasta de amostra acima)" + }, + "tab.accounts": { + "message": "Ordenar contas" + }, + "tab.extra": { + "message": "Definições extra" + }, + "tab.folders": { + "message": "Ordenar pastas" + }, + "tbsf.menuentry.label": { + "message": "Ordenar pastas manualmente" + }, + "tc.foldername": { + "message": "Nome da pasta" } -} +} \ No newline at end of file diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json index 6f4fe60..96eeafe 100644 --- a/_locales/pt_BR/messages.json +++ b/_locales/pt_BR/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Pastas Locais" + }, + "accountsort.noaccountsetupyet": { + "message": "Você não tem nenhuma configuração de conta ainda!" + }, + "accountsort.note": { + "message": "Você pode alterar a ordem das contas neste painel. (No Thunderbird 91 e posteriores, você também pode fazê-lo na UI de Configuração de Contas)." + }, + "accountsort.restartwarning": { + "message": "Você deve reiniciar o Thunderbird para que as mudanças tenham efeito." + }, + "accountsort.warning": { + "message": "Contas de correio regular, do Unix Movemail, bem como contas de RSS deve vir em primeiro lugar. As pastas locais são as seguintes e, por último, as contas Notícias. Você pode substituir isto escolhendo pastas locais ou uma das contas de Grupos de Notícias como padrão, portanto, colocando-o no topo da lista." + }, + "accountssort.description": { + "message": "Este painel permite ordenar as contas no painel de pastas. Use a próxima aba, se você desejar ordenar as pastas dentro de uma conta." + }, + "button.account_name": { + "message": "Nome da conta" + }, + "button.close": { + "message": "Fechar" + }, + "button.move_down": { + "message": "Mover para baixo" + }, + "button.move_up": { + "message": "Mover para cima" + }, + "button.refresh": { + "message": "Recarregar o painel de pastas" + }, + "button.restart": { + "message": "Reiniciar o Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Sensível a maiúsculas e minúsculas" + }, "extensionDescription": { "description": "Description of the extension", "message": "Permite alterar a ordem das pastas no painel de pastas." + }, + "extra.description": { + "message": "Use essa aba para personalizar ainda mais o Thunderbird usando várias configurações de pastas" + }, + "extra.hide_folder_icons": { + "message": "Ocultar ícones de pastas (Necessidade de reiniciar este suplemento)" + }, + "extra.misc": { + "message": "Diversos" + }, + "extra.nofolder": { + "message": "Nenhuma pasta selecionada" + }, + "extra.startup": { + "message": "Pasta de início" + }, + "extra.startupfolder": { + "message": "Por padrão, comece abrindo a seguinte pasta" + }, + "extra.startupfolder.notice": { + "message": "* Este recurso pode não funcionar de forma confiável nas versões 97 e anteriores do Thunderbird." + }, + "extra.usecustom": { + "message": "Use uma pasta personalizada" + }, + "extra.uselast": { + "message": "Use a última pasta aberta" + }, + "extra.usethisfolder": { + "message": "Use a presente pasta" + }, + "gb.extra": { + "message": "Informação adicional" + }, + "gb.first": { + "message": "Primeira conta no painel de pastas" + }, + "gb.move": { + "message": "Mover a conta selecionada" + }, + "gb.movefolder": { + "message": "Mover a pasta selecionada" + }, + "gb.sort_by": { + "message": "Ordenar por" + }, + "gb.sort_siblings_by": { + "message": "Ordenar dentro do nível selecionado por" + }, + "gbfirst.default": { + "message": "Esta também é a conta padrão." + }, + "general.title": { + "message": "Manually sort folders (Organiza pastas manualmente)" + }, + "mi.firstmail": { + "message": "Primeiro RSS ou conta de correio eletrônico da lista" + }, + "sortfolders.description": { + "message": "Este painel permite que você classifique as pastas de cada conta. Primeiro, selecione uma conta, então um método de classificação." + }, + "sortmethod.alphabetical.ascending": { + "message": "Classifique considerando todo o teor da linha (Ordem ascendente)" + }, + "sortmethod.alphabetical.descending": { + "message": "Classifique considerando todo o teor da linha (Ordem decrescente)" + }, + "sortmethod.alphabetical.description": { + "message": "Esta é uma função de classificação padrão que utiliza apenas a função de comparação de linha mais simples." + }, + "sortmethod.custom": { + "message": "Use uma função de classificação definida manualmente" + }, + "sortmethod.custom.description": { + "message": "Use os botões acima para ordenar manualmente suas pastas. Se uma nova pasta é adicionada, ela será colocada no final da lista. Você pode voltar aqui mais tarde, se isso não lhe agradar." + }, + "sortmethod.default": { + "message": "Use os valores padrões do Thunderbird" + }, + "sortmethod.default.description": { + "message": "Esta é a função padrão de classificação do Thunderbird." + }, + "sortmethod.samplelegend": { + "message": "(Painel de pasta exemplo acima)" + }, + "tab.accounts": { + "message": "Organiza contas" + }, + "tab.extra": { + "message": "Configurações extras" + }, + "tab.folders": { + "message": "Organiza pastas" + }, + "tbsf.menuentry.label": { + "message": "Manually sort folders (Organiza pastas manualmente)" + }, + "tc.foldername": { + "message": "Nome da Pasta" } -} +} \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 5f5246d..6d983c8 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Локальные папки" + }, + "accountsort.noaccountsetupyet": { + "message": "У вас нет ни одной учётной записи!" + }, + "accountsort.note": { + "message": "Вы можете изменить порядок учетных записей на этой панели. (В Thunderbird 91 и более поздних версиях это также можно сделать в пользовательском интерфейсе Настройки учетных записей)." + }, + "accountsort.restartwarning": { + "message": "Чтобы изменения вступили в силу необходимо перезапустить Thunderbird." + }, + "accountsort.warning": { + "message": "Регулярные почтовые учётные записи, учётные записи Unix Movemail, в том числе, учётные записи RSS располагаются первыми. Затем идут «Локальные папки» и, внизу списка, учётные записи новостей. Вы можете изменить это задав «Локальные папки» или одну из учётных записей новостей как значение по умолчанию, переместив её таким образом на самый верх списка." + }, + "accountssort.description": { + "message": "Эта панель позволит вам упорядочить учётные записи в панели папок. Если вы желаете отсортировать папки в учётной записи — используйте следующую вкладку." + }, + "button.account_name": { + "message": "Имя учётных" + }, + "button.close": { + "message": "Закрыть" + }, + "button.move_down": { + "message": "Ниже" + }, + "button.move_up": { + "message": "Выше" + }, + "button.refresh": { + "message": "Обновить панель папок" + }, + "button.restart": { + "message": "Перезапустить Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Чувствительность к регистру" + }, "extensionDescription": { "description": "Description of the extension", "message": "Позволяет изменить порядок папок в панели папок." + }, + "extra.description": { + "message": "С помощью этой вкладки вы сможете настроить различные параметры Thunderbird, связанные с папками" + }, + "extra.hide_folder_icons": { + "message": "Скрыть значки папок (необходимо перезапустить это дополнение)" + }, + "extra.misc": { + "message": "Разное" + }, + "extra.nofolder": { + "message": "папка не выбрана" + }, + "extra.startup": { + "message": "Папка запуска" + }, + "extra.startupfolder": { + "message": "По умолчанию, открывать при запуске" + }, + "extra.startupfolder.notice": { + "message": "* Эта функция может работать ненадежно в Thunderbird 97 и более ранних версиях." + }, + "extra.usecustom": { + "message": "папку по вашему выбору" + }, + "extra.uselast": { + "message": "последнюю открытую папку" + }, + "extra.usethisfolder": { + "message": "выбрать эту папку" + }, + "gb.extra": { + "message": "Дополнительная информация" + }, + "gb.first": { + "message": "Первая учётная запись в панели папок" + }, + "gb.move": { + "message": "Переместить выбранную учётную запись" + }, + "gb.movefolder": { + "message": "Переместить выбранную папку" + }, + "gb.sort_by": { + "message": "Сортировать по" + }, + "gb.sort_siblings_by": { + "message": "Сортировка в пределах выбранного уровня по" + }, + "gbfirst.default": { + "message": "Она же заданная по умолчанию учётная запись." + }, + "general.title": { + "message": "Сортировка папок вручную" + }, + "mi.firstmail": { + "message": "Первая учётная запись RSS или почты в списке" + }, + "sortfolders.description": { + "message": "Эта панель позволит вам отсортировать папки любой учётной записи. Сначала выберите учётную запись, а затем — метод сортировки." + }, + "sortmethod.alphabetical.ascending": { + "message": "Использовать сравнение обычных строк (По возрастанию)" + }, + "sortmethod.alphabetical.descending": { + "message": "Использовать сравнение обычных строк (По убыванию)" + }, + "sortmethod.alphabetical.description": { + "message": "Обычная функция сортировки, которая использует самую простую функцию сравнения строк." + }, + "sortmethod.custom": { + "message": "Использовать ручную сортировку" + }, + "sortmethod.custom.description": { + "message": "Используя расположенные выше кнопки самостоятельно рассортируйте ваши папки. При добавлении новой папки она будет помещена в конец списка. Вы можете вернуться сюда позже, если это вас не устраивает." + }, + "sortmethod.default": { + "message": "Использовать стандартный метод Thunderbird" + }, + "sortmethod.default.description": { + "message": "Функция сортировки, используемая в Thunderbird по умолчанию." + }, + "sortmethod.samplelegend": { + "message": "(Образец панели папок)" + }, + "tab.accounts": { + "message": "Сортировка учётных записей" + }, + "tab.extra": { + "message": "Дополнительные настройки" + }, + "tab.folders": { + "message": "Сортировка папок" + }, + "tbsf.menuentry.label": { + "message": "Сортировка папок вручную" + }, + "tc.foldername": { + "message": "Имя папки" } -} +} \ No newline at end of file diff --git a/_locales/sk/messages.json b/_locales/sk/messages.json index b9a5765..cabf29d 100644 --- a/_locales/sk/messages.json +++ b/_locales/sk/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Lokálne priečinky" + }, + "accountsort.noaccountsetupyet": { + "message": "Nemáte zatiaľ žiadny účet!" + }, + "accountsort.note": { + "message": "Na tomto paneli môžete zmeniť poradie účtov. (V Thunderbirde 91 a novšom to môžete urobiť aj v používateľskom rozhraní Nastavenia účtu.)" + }, + "accountsort.restartwarning": { + "message": "Zmeny sa prejavia po reštarte Thunderbird-u." + }, + "accountsort.warning": { + "message": "Regulárne mailové účty, Unix Movemail účty, taktiež RSS účty musia ísť prvé. Lokálne priečinky sú ďalšie a naspodu zoznamu sú nové účty. Toto môžete zmeniť, buď vybratím Lokálnych priečinkov alebo jedným z účtov a umiestnením ho na vrch zoznamu." + }, + "accountssort.description": { + "message": "Tento panel Vám umožní zoradiť účty v zozname priečinkov. Použite ďalšiu záložku, ak chcete zoradiť priečinky vo vnútri účtu." + }, + "button.account_name": { + "message": "Názov účtu" + }, + "button.close": { + "message": "Zavrieť" + }, + "button.move_down": { + "message": "Posunúť dolu" + }, + "button.move_up": { + "message": "Posunúť hore" + }, + "button.refresh": { + "message": "Aktualizovať zoznam priečinkov" + }, + "button.restart": { + "message": "Reštartovať Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Rozlišovať malé a veľké písmená" + }, "extensionDescription": { "description": "Description of the extension", "message": "Umožňuje zmeniť poradie priečinkov na paneli priečinkov." + }, + "extra.description": { + "message": "Použite túto záložku pre jemnejšie úpravy nastavení priečinkov Thunderbirdu" + }, + "extra.hide_folder_icons": { + "message": "Skryť ikony priečinkov (Je potrebné reštartovať tento doplnok)" + }, + "extra.misc": { + "message": "Rôzne" + }, + "extra.nofolder": { + "message": "Nevybratý žiadny priečinok" + }, + "extra.startup": { + "message": "Štartovací priečinok" + }, + "extra.startupfolder": { + "message": "Začať otvorením tohto priečinku" + }, + "extra.startupfolder.notice": { + "message": "* Táto funkcia nemusí spoľahlivo fungovať v programe Thunderbird 97 a starších verziách." + }, + "extra.usecustom": { + "message": "Použiť vlastný priečinok" + }, + "extra.uselast": { + "message": "Použiť naposledy otvorený priečinok" + }, + "extra.usethisfolder": { + "message": "Použiť tento priečinok" + }, + "gb.extra": { + "message": "Rozšírené informácie" + }, + "gb.first": { + "message": "Prvý účet v zozname priečinkov" + }, + "gb.move": { + "message": "Posunutie vybratého účtu" + }, + "gb.movefolder": { + "message": "Presunúť vybratý priečinok" + }, + "gb.sort_by": { + "message": "Zoradiť podľa" + }, + "gb.sort_siblings_by": { + "message": "Triedenie vo vybranej úrovni podľa" + }, + "gbfirst.default": { + "message": "Toto je taktiež aj predvolený účet." + }, + "general.title": { + "message": "Ručné zoradenie priečinkov" + }, + "mi.firstmail": { + "message": "Prvý RSS alebo poštový účet v zozname" + }, + "sortfolders.description": { + "message": "Tento panel Vám umožní zoradiť priečinky každého účtu. Najprv vyberte účet a potom metódu zoradenia." + }, + "sortmethod.alphabetical.ascending": { + "message": "Použiť zoradenie jednoduchým porovnaním reťazcov (Vzostupné poradie)" + }, + "sortmethod.alphabetical.descending": { + "message": "Použiť zoradenie jednoduchým porovnaním reťazcov (Zostupné poradie)" + }, + "sortmethod.alphabetical.description": { + "message": "Toto je štandardná metóda zoradenia, ktorá používa jednoduché porovnanie reťazcov." + }, + "sortmethod.custom": { + "message": "Použiť ručne definované zoradenie" + }, + "sortmethod.custom.description": { + "message": "Použite tlačidlá 'Posunúť hore' a 'Posunúť dolu' pre ručné zoradenie priečinkov. Ak pridáte nový priečinok, umiestní sa na koniec zoznamu. Umiestnenie priečinka môžete zmeniť neskôr." + }, + "sortmethod.default": { + "message": "Použiť predvolenú metódu zoradenia z Thunderbird-u" + }, + "sortmethod.default.description": { + "message": "Toto je predvolená metóda zoradenia Thunderbird-u." + }, + "sortmethod.samplelegend": { + "message": "(Vzorový zoznam priečinkov)" + }, + "tab.accounts": { + "message": "Zoradenie účtov" + }, + "tab.extra": { + "message": "Extra nastavenia" + }, + "tab.folders": { + "message": "Zoradenie priečinkov" + }, + "tbsf.menuentry.label": { + "message": "Ručné zoradenie priečinkov" + }, + "tc.foldername": { + "message": "Názov priečinku" } -} +} \ No newline at end of file diff --git a/_locales/sr/messages.json b/_locales/sr/messages.json index 591c994..4799663 100644 --- a/_locales/sr/messages.json +++ b/_locales/sr/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Локале фасцикле" + }, + "accountsort.noaccountsetupyet": { + "message": "Још увек нема подешених налога!" + }, + "accountsort.note": { + "message": "У овом панелу можете променити редослед налога. (На Тхундербирд-у 91 и новијим верзијама, то такође можете да урадите у корисничком интерфејсу за подешавања налога.)" + }, + "accountsort.restartwarning": { + "message": "Морате да поново покренете Тандерберд да би измене биле примењене." + }, + "accountsort.warning": { + "message": "Обични налози за е-пошту, Unix Movemail налози, као и RSS налози морају да буду на почетку. Следе локалне фасцикле, а на дну су налози за новости. Ово можете да замените ако поставите локалне фасцикле или неки ог налога за новости као подразумевану, што ће их стаити на врх списка." + }, + "accountssort.description": { + "message": "Ова табла вам омогућава да поређате налоге у табли фасцикли. Користите следећи језичак ако желите да поређате фасцикле унутар њих." + }, + "button.account_name": { + "message": "Назив налога" + }, + "button.close": { + "message": "Затвори" + }, + "button.move_down": { + "message": "Помери надоле" + }, + "button.move_up": { + "message": "Помери нагоре" + }, + "button.refresh": { + "message": "Освежи таблу фасцикли" + }, + "button.restart": { + "message": "Поново покрени Тандерберд" + }, + "checkbox.case_sensitive": { + "message": "Велика и мала слова" + }, "extensionDescription": { "description": "Description of the extension", "message": "Омогућава вам да промените редослед фасцикли у окну фасцикли." + }, + "extra.description": { + "message": "Користите овај језичак за прилагођавање Тандерберда коришћењем разних подешавања везаним за фасцикле" + }, + "extra.hide_folder_icons": { + "message": "Сакриј иконе директоријума (треба поново покренути овај додатак)" + }, + "extra.misc": { + "message": "Мисц." + }, + "extra.nofolder": { + "message": "Није одабрана фасцикла" + }, + "extra.startup": { + "message": "Почетна фасцикла" + }, + "extra.startupfolder": { + "message": "Подразумевано, почни са отвореном следећом фасциклом" + }, + "extra.startupfolder.notice": { + "message": "* Ова функција можда неће радити поуздано на Тхундербирд-у 97 и старијим верзијама." + }, + "extra.usecustom": { + "message": "Користи прилагођену фасциклу" + }, + "extra.uselast": { + "message": "Користи последњу отворену фасциклу" + }, + "extra.usethisfolder": { + "message": "Користи ову фасциклу" + }, + "gb.extra": { + "message": "Додатне информације" + }, + "gb.first": { + "message": "Први налог у табли фасцикли" + }, + "gb.move": { + "message": "Премести одабрани налог" + }, + "gb.movefolder": { + "message": "Премести одабрану фасциклу" + }, + "gb.sort_by": { + "message": "Сортирај по" + }, + "gb.sort_siblings_by": { + "message": "Сортирај у оквиру изабраног нивоа по" + }, + "gbfirst.default": { + "message": "Ово је и подразумевани налог." + }, + "general.title": { + "message": "Ручно поређај фасцикле" + }, + "mi.firstmail": { + "message": "Први RSS налог или налог за е-пошту на списку" + }, + "sortfolders.description": { + "message": "Ова табла вам омогућава да поређате фасцикле сваког налога. Прво одаберите налог, а онда начин ређања." + }, + "sortmethod.alphabetical.ascending": { + "message": "Користи поређење чистих ниски (Узлазни ред)" + }, + "sortmethod.alphabetical.descending": { + "message": "Користи поређење чистих ниски (Силазним ред)" + }, + "sortmethod.alphabetical.description": { + "message": "Ово је стандардна функција ређања, која користи најједноставнији начин поређења." + }, + "sortmethod.custom": { + "message": "користи ручно одређену функцију ређања" + }, + "sortmethod.custom.description": { + "message": "Користите дугмад изнад да бисте ручно поређали фасцикле. Ако додате нову фасциклу, биће додана на дно списка. Можете да се вратите овамо назад, ако вам то не одговара." + }, + "sortmethod.default": { + "message": "користи подразумевано Тандербердово" + }, + "sortmethod.default.description": { + "message": "Ово је подразумевана Тандербердова функција ређања." + }, + "sortmethod.samplelegend": { + "message": "(пример табле фасцикле изнад)" + }, + "tab.accounts": { + "message": "Поређај налоге" + }, + "tab.extra": { + "message": "Додатна подешавања" + }, + "tab.folders": { + "message": "Поређај фасцикле" + }, + "tbsf.menuentry.label": { + "message": "Ручно поређај фасцикле" + }, + "tc.foldername": { + "message": "Назив фасцикле" } -} +} \ No newline at end of file diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index 8535235..da1be63 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "Lokala mappar" + }, + "accountsort.noaccountsetupyet": { + "message": "Du har inte konfigurerat något konto ännu!" + }, + "accountsort.note": { + "message": "Du kan ändra ordningen på kontona i den här panelen. (I Thunderbird 91 och senare kan du också göra det i kontoinställningsgränssnittet.)" + }, + "accountsort.restartwarning": { + "message": "Du måste starta om Thunderbird för att ändringarna skall verkställas." + }, + "accountsort.warning": { + "message": "Vanliga e-postkonton, Unix Movemail-konton och RSS-konton måste komma först. Sedan följer "Lokala mappar" och nederst i listan finns diskussionsgruppskontona. Du kan åsidosätta detta genom att välja antingen "Lokala mappar" eller ett av diskussionsgruppskontona som standardkonto, vilket placerar det överst i listan." + }, + "accountssort.description": { + "message": "Denna panel låter dig sortera dina konton i mappanelen. Gå till nästa flik om du vill sortera mappar inom ett konto." + }, + "button.account_name": { + "message": "Kontonamn" + }, + "button.close": { + "message": "Stäng" + }, + "button.move_down": { + "message": "Flytta nedåt" + }, + "button.move_up": { + "message": "Flytta uppåt" + }, + "button.refresh": { + "message": "Uppdatera mappanelen" + }, + "button.restart": { + "message": "Starta om Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "Skiftlägeskänslig" + }, "extensionDescription": { "description": "Description of the extension", "message": "Låter dig ändra ordningen på mappar i mapprutan." + }, + "extra.description": { + "message": "Använd denna flik för att ytterligare anpassa Thunderbird m.h.a. olika mapprelaterade inställningar" + }, + "extra.hide_folder_icons": { + "message": "Dölj mappikoner (Du måste starta om det här tillägget)" + }, + "extra.misc": { + "message": "Övrigt" + }, + "extra.nofolder": { + "message": "Ingen mapp har markerats" + }, + "extra.startup": { + "message": "Startmapp" + }, + "extra.startupfolder": { + "message": "Som standard, starta med följande mapp öppnad" + }, + "extra.startupfolder.notice": { + "message": "* Den här funktionen kanske inte fungerar tillförlitligt i Thunderbird 97 och tidigare versioner." + }, + "extra.usecustom": { + "message": "Använd en egenvald mapp" + }, + "extra.uselast": { + "message": "Använd senast öppnade mapp" + }, + "extra.usethisfolder": { + "message": "Använd denna mapp" + }, + "gb.extra": { + "message": "Ytterligare information" + }, + "gb.first": { + "message": "Första konto i mappanelen" + }, + "gb.move": { + "message": "Flytta markerat konto" + }, + "gb.movefolder": { + "message": "Flytta markerad mapp" + }, + "gb.sort_by": { + "message": "Sortera efter" + }, + "gb.sort_siblings_by": { + "message": "Sortera inom den valda nivån efter" + }, + "gbfirst.default": { + "message": "Det här är även standardkontot." + }, + "general.title": { + "message": "Sortera mappar manuellt" + }, + "mi.firstmail": { + "message": "Första RSS- eller e-postkonto i listan" + }, + "sortfolders.description": { + "message": "Denna panel låter dig sortera varje enskilt kontos mappar. Välj först ett konto och sedan en sorteringsmetod." + }, + "sortmethod.alphabetical.ascending": { + "message": "Använd vanlig strängjämförelse (Stigande ordning)" + }, + "sortmethod.alphabetical.descending": { + "message": "Använd vanlig strängjämförelse (Nedåtgående ordning)" + }, + "sortmethod.alphabetical.description": { + "message": "Det här är en standardsorteringsfunktion som endast använder den mest basala strängjämförelsefunktionen." + }, + "sortmethod.custom": { + "message": "Använd en manuellt definierad sorteringsfunktion" + }, + "sortmethod.custom.description": { + "message": "Använd knapparna ovan för att sortera dina mappar manuellt. Om en ny mapp läggs till, så kommer den att placeras sist i listan. Du kan komma tillbaka hit senare om du ändrar dig." + }, + "sortmethod.default": { + "message": "Använd Thunderbirds standard" + }, + "sortmethod.default.description": { + "message": "Det här är Thunderbirds standardsorteringsfunktion." + }, + "sortmethod.samplelegend": { + "message": "(Välj ur mappanelen ovan)" + }, + "tab.accounts": { + "message": "Sortera konton" + }, + "tab.extra": { + "message": "Övrigt" + }, + "tab.folders": { + "message": "Sortera mappar" + }, + "tbsf.menuentry.label": { + "message": "Sortera mappar manuellt" + }, + "tc.foldername": { + "message": "Mappnamn" } -} +} \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index af1a417..b8dfa1d 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "本地文件夹" + }, + "accountsort.noaccountsetupyet": { + "message": "你还没有设置账户呢!" + }, + "accountsort.note": { + "message": "您可以在此面板中更改帐户的顺序。 (在 Thunderbird 91 及更高版本上,您也可以在帐户设置 UI 中执行此操作。)" + }, + "accountsort.restartwarning": { + "message": "你必须重启Thunderbird使改变生效" + }, + "accountsort.warning": { + "message": "普通邮件账户,Unix Movemail账户和RSS账户必须要首先考虑。本地文件夹其次,列表的最下面是新账户。你可以选择本地文件夹或者新闻组账户作为默认,因此把它放到列表最上面以废除这个账户。" + }, + "accountssort.description": { + "message": "此面板允许你对文件夹窗格中的账户排序。使用下一个标签,如果你想对某一个账户中的文件夹排序。" + }, + "button.account_name": { + "message": "帐户名" + }, + "button.close": { + "message": "关闭" + }, + "button.move_down": { + "message": "向下移动" + }, + "button.move_up": { + "message": "向上移动" + }, + "button.refresh": { + "message": "刷新文件夹窗格" + }, + "button.restart": { + "message": "重启Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "区分大小写" + }, "extensionDescription": { "description": "Description of the extension", "message": "允许你改变文件夹窗格中文件夹的顺序。" + }, + "extra.description": { + "message": "使用这个标签用各种不同的文件夹相关设置进一步定制Thunderbird" + }, + "extra.hide_folder_icons": { + "message": "隐藏文件夹图标(需要重启此插件)" + }, + "extra.misc": { + "message": "杂项" + }, + "extra.nofolder": { + "message": "没有选择文件夹" + }, + "extra.startup": { + "message": "起始文件夹" + }, + "extra.startupfolder": { + "message": "默认,以以下打开的文件夹开始" + }, + "extra.startupfolder.notice": { + "message": "* 此功能可能无法在 Thunderbird 97 及更早版本上可靠运行。" + }, + "extra.usecustom": { + "message": "使用自定义文件夹" + }, + "extra.uselast": { + "message": "使用上次打开的文件夹" + }, + "extra.usethisfolder": { + "message": "使用这个文件夹" + }, + "gb.extra": { + "message": "更多信息" + }, + "gb.first": { + "message": "文件夹窗格中的第一个账户" + }, + "gb.move": { + "message": "移动选定的账户" + }, + "gb.movefolder": { + "message": "移动选定的文件夹" + }, + "gb.sort_by": { + "message": "排序方式" + }, + "gb.sort_siblings_by": { + "message": "在所选级别内排序" + }, + "gbfirst.default": { + "message": "这也是默认账户。" + }, + "general.title": { + "message": "手动对文件夹排序" + }, + "mi.firstmail": { + "message": "列表中的第一个RSS订阅或电子邮件账户" + }, + "sortfolders.description": { + "message": "这个面板使你对每个账户的文件夹进行排序。首先选择一个账户,然后选择排序方式。" + }, + "sortmethod.alphabetical.ascending": { + "message": "采用纯字符串对比 (升序)" + }, + "sortmethod.alphabetical.descending": { + "message": "采用纯字符串对比 (降序)" + }, + "sortmethod.alphabetical.description": { + "message": "这是一个标准的排序功能,使用只是最基本的字符串比较函数。" + }, + "sortmethod.custom": { + "message": "使用一个自定义的排序功能" + }, + "sortmethod.custom.description": { + "message": "使用以上按钮对你的文件夹手动排序。如果添加一个新的文件夹,它将被放在列表的最后面。你之后还可以返回如果那样不适合你。" + }, + "sortmethod.default": { + "message": "使用Thunderbird的默认设置" + }, + "sortmethod.default.description": { + "message": "这是Thunderbird的默认排序分类功能。" + }, + "sortmethod.samplelegend": { + "message": "(以上示例文件夹窗格)" + }, + "tab.accounts": { + "message": "对账户排序" + }, + "tab.extra": { + "message": "其它设置" + }, + "tab.folders": { + "message": "对文件夹排序" + }, + "tbsf.menuentry.label": { + "message": "手动对文件夹排序" + }, + "tc.foldername": { + "message": "文件夹名" } -} +} \ No newline at end of file diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 6b431a9..0b9878b 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -1,6 +1,144 @@ { + "accountsort.localfolders": { + "message": "本機資料夾" + }, + "accountsort.noaccountsetupyet": { + "message": "你還未設置一個帳號!" + }, + "accountsort.note": { + "message": "您可以在此面板中更改帳戶的順序。 (在 Thunderbird 91 及更高版本上,您也可以在帳戶設置 UI 中執行此操作。)" + }, + "accountsort.restartwarning": { + "message": "你必須重新啟動Thunderbird以讓設定值生效。" + }, + "accountsort.warning": { + "message": "一般信件帳號、Unix Movemail帳號與RSS帳號會在最前面。接下來是本機帳號,在清單最下面的是新聞帳號。你能改變這樣的順序,只要將本機資料夾或任一新聞帳號移到清單中的最上面,便能設定成預設帳號。" + }, + "accountssort.description": { + "message": "這個分頁允許你來調整資料夾窗格中的帳號。使用下一個分頁來排序帳號中的資料夾。" + }, + "button.account_name": { + "message": "帳號名稱" + }, + "button.close": { + "message": "關閉" + }, + "button.move_down": { + "message": "下移" + }, + "button.move_up": { + "message": "上移" + }, + "button.refresh": { + "message": "重新整理資料夾窗格" + }, + "button.restart": { + "message": "重新啟動Thunderbird" + }, + "checkbox.case_sensitive": { + "message": "區分大小寫" + }, "extensionDescription": { "description": "Description of the extension", "message": "允許您更改文件夾窗格中文件夾的順序。" + }, + "extra.description": { + "message": "使用此分頁來進一步調整Thunderbird的資料夾相關設定" + }, + "extra.hide_folder_icons": { + "message": "隱藏文件夾圖標(需要重啟這個插件)" + }, + "extra.misc": { + "message": "雜項" + }, + "extra.nofolder": { + "message": "沒有資料夾被選擇" + }, + "extra.startup": { + "message": "初始資料夾" + }, + "extra.startupfolder": { + "message": "啟動時預設以下列的資料夾開啟" + }, + "extra.startupfolder.notice": { + "message": "* 此功能可能無法在 Thunderbird 97 及更早版本上可靠運行。" + }, + "extra.usecustom": { + "message": "使用自訂的資料夾" + }, + "extra.uselast": { + "message": "使用最後開啟的資料夾" + }, + "extra.usethisfolder": { + "message": "使用這個資料夾" + }, + "gb.extra": { + "message": "額外資訊" + }, + "gb.first": { + "message": "資料夾窗格中第一個帳號" + }, + "gb.move": { + "message": "移動所選的帳號" + }, + "gb.movefolder": { + "message": "移動所選的資料夾" + }, + "gb.sort_by": { + "message": "排序方式" + }, + "gb.sort_siblings_by": { + "message": "在選定級別內排序" + }, + "gbfirst.default": { + "message": "這也是預設的帳號。" + }, + "general.title": { + "message": "手動排序資料夾" + }, + "mi.firstmail": { + "message": "在清單中第一個RSS或信件帳號" + }, + "sortfolders.description": { + "message": "這個分頁允許你來排列每個帳號的資料夾。先選擇一個帳號,然後再選擇一個排序方法。" + }, + "sortmethod.alphabetical.ascending": { + "message": "依資料夾名稱的字母順序排序 (升序)" + }, + "sortmethod.alphabetical.descending": { + "message": "依資料夾名稱的字母順序排序 (降序)" + }, + "sortmethod.alphabetical.description": { + "message": "這是一個標準的排序方法,它僅僅利用最基礎的字母排序方法。" + }, + "sortmethod.custom": { + "message": "使用自行定義的排序功能" + }, + "sortmethod.custom.description": { + "message": "使用上面的按鈕來手動排序你的資料夾。如果加入一個新資料夾,它將放在清單的最後面。如果你不滿意,你可以稍後回來這邊調整。" + }, + "sortmethod.default": { + "message": "使用Thunderbird的預設設定" + }, + "sortmethod.default.description": { + "message": "這是Thunderbird的預設排序方法。" + }, + "sortmethod.samplelegend": { + "message": "移動選擇的資料夾" + }, + "tab.accounts": { + "message": "排序帳號" + }, + "tab.extra": { + "message": "額外的設定" + }, + "tab.folders": { + "message": "排序資料夾" + }, + "tbsf.menuentry.label": { + "message": "Manually sort folders 選項" + }, + "tc.foldername": { + "message": "資料夾名稱" } -} +} \ No newline at end of file diff --git a/api/CustomFolderSort/implementation.js b/api/CustomFolderSort/implementation.js new file mode 100644 index 0000000..fecb8e0 --- /dev/null +++ b/api/CustomFolderSort/implementation.js @@ -0,0 +1,182 @@ +var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +function getAbout3PaneWindow(nativeTab) { + if (nativeTab.mode && nativeTab.mode.name == "mail3PaneTab") { + return nativeTab.chromeBrowser.contentWindow + } + return null; +} + +function reloadFolders(folderPane) { + for (let mode of Object.values(folderPane._modes)) { + if (!mode.active) { + continue; + } + mode.containerList.replaceChildren(); + folderPane._initMode(mode); + } +}; + +function folderURIToPath(accountId, uri) { + let server = MailServices.accounts.getAccount(accountId).incomingServer; + let rootURI = server.rootFolder.URI; + if (rootURI == uri) { + return "/"; + } + // The .URI property of an IMAP folder doesn't have %-encoded characters, but + // may include literal % chars. Services.io.newURI(uri) applies encodeURI to + // the returned filePath, but will not encode any literal % chars, which will + // cause decodeURIComponent to fail (bug 1707408). + if (server.type == "imap") { + return uri.substring(rootURI.length); + } + let path = Services.io.newURI(uri).filePath; + return path.split("/").map(decodeURIComponent).join("/"); +} + +function getFolderId(accountId, path) { + let data = `${accountId}:${path}`; + return data; + + let arr = new TextEncoder().encode(data); + let str = ""; + for (let i = 0; i < arr.length; i += 65536) { + str += String.fromCharCode.apply(null, arr.subarray(i, i + 65536)); + } + return btoa(str); +} + +async function install(window, preferences) { + for (let i = 0; i < 20; i++) { + if (window.folderPane && window.folderPane._initialized) { + break; + } + await new Promise(r => window.setTimeout(r, 125)) + } + if (!window.folderPane || window.folderPane._manualSortFolderBackupAddSubFolders) { + return; + } + + window.folderPane._manualSortFolderBackupAddSubFolders = window.folderPane._addSubFolders; + window.folderPane._addSubFolders = function (parentFolder, parentRow, modeName, filterFunction) { + console.log("ManualSortFolders: about:3pane is patched"); + + let subFolders = parentFolder.subFolders; + if (!subFolders.length) { + return; + } + + for (let i = 0; i < subFolders.length; i++) { + let folder = subFolders[i]; + if (this._isGmailFolder(folder)) { + subFolders.splice(i, 1, ...folder.subFolders); + } + } + + let server = parentFolder.server; + let accountId = MailServices.accounts.FindAccountForServer(server).key; + let sortType = preferences.accountSettings.has(accountId) + ? preferences.accountSettings.get(accountId).type + : null + + switch (sortType) { + case "2": // custom + { + let sortData = new Map(); + // Add custom sortKey + for (let i = 0; i < subFolders.length; i++) { + let folder = subFolders[i]; + let path = folderURIToPath(accountId, folder.URI); + + if (preferences.folderSort.has(getFolderId(accountId, path))) { + sortData.set(folder.URI, preferences.folderSort.get(getFolderId(accountId, path))); + } else { + sortData.set(folder.URI, `_${i}`); + } + } + // Custom sort + subFolders.sort((a, b) => sortData.get(a.URI) > sortData.get(b.URI)); + } + break; + + case "1": + subFolders.sort((a, b) => a.prettyName > b.prettyName) + break; + + case "3": + subFolders.sort((a, b) => a.prettyName < b.prettyName) + break; + + default: + subFolders.sort((a, b) => a.compareSortKeys(b)); + } + + for (let folder of subFolders) { + if (typeof filterFunction == "function" && !filterFunction(folder)) { + continue; + } + let folderRow = this._createFolderRow(modeName, folder); + this._addSubFolders(folder, folderRow, modeName, filterFunction); + parentRow.childList.appendChild(folderRow); + } + } + + console.log("ManualSortFolders: patched _addSubFolders(), Reload") + reloadFolders(window.folderPane); +} + +async function uninstall(window) { + for (let i = 0; i < 20; i++) { + if (window.folderPane && window.folderPane._initialized) { + break; + } + await new Promise(r => window.setTimeout(r, 125)) + } + if (!window.folderPane || !window.folderPane._manualSortFolderBackupAddSubFolders) { + return; + } + + window.folderPane._addSubFolders = window.folderPane._manualSortFolderBackupAddSubFolders; + delete window.folderPane._manualSortFolderBackupAddSubFolders; + + console.log("ManualSortFolders: restored _addSubFolders(), Reload") + reloadFolders(window.folderPane); +} + +var CustomFolderSort = class extends ExtensionCommon.ExtensionAPI { + getAPI(context) { + return { + CustomFolderSort: { + async patch(tabId, preferences) { + let { nativeTab } = context.extension.tabManager.get(tabId); + let about3PaneWindow = getAbout3PaneWindow(nativeTab); + if (about3PaneWindow) { + install(about3PaneWindow, preferences); + } + }, + async update(tabId, preferences) { + let { nativeTab } = context.extension.tabManager.get(tabId); + let about3PaneWindow = getAbout3PaneWindow(nativeTab); + if (about3PaneWindow) { + uninstall(about3PaneWindow); + install(about3PaneWindow, preferences); + } + }, + }, + }; + } + + onShutdown(isAppShutdown) { + if (isAppShutdown) return; + + for (let window of Services.wm.getEnumerator("mail:3pane")) { + for (let nativeTab of window.gTabmail.tabInfo) { + let about3PaneWindow = getAbout3PaneWindow(nativeTab); + if (about3PaneWindow) { + uninstall(about3PaneWindow); + } + } + } + } +}; diff --git a/api/CustomFolderSort/schema.json b/api/CustomFolderSort/schema.json new file mode 100644 index 0000000..7b9a32b --- /dev/null +++ b/api/CustomFolderSort/schema.json @@ -0,0 +1,37 @@ +[ + { + "namespace": "CustomFolderSort", + "functions": [ + { + "name": "patch", + "type": "function", + "async": true, + "parameters": [ + { + "type": "integer", + "name": "tabId" + }, + { + "type": "any", + "name": "preferences" + } + ] + }, + { + "name": "update", + "type": "function", + "async": true, + "parameters": [ + { + "type": "integer", + "name": "tabId" + }, + { + "type": "any", + "name": "preferences" + } + ] + } + ] + } +] diff --git a/api/LegacyPrefs/README.md b/api/LegacyPrefs/README.md new file mode 100644 index 0000000..452d2cc --- /dev/null +++ b/api/LegacyPrefs/README.md @@ -0,0 +1,60 @@ +## Objective + +Use this API to access Thunderbird's system preferences or to migrate your own preferences from the Thunderbird preference system to the local storage of your MailExtension. + +## Usage + +Add the [LegacyPrefs API](https://github.com/thundernest/addon-developer-support/tree/master/auxiliary-apis/LegacyPrefs) to your add-on. Your `manifest.json` needs an entry like this: + +``` + "experiment_apis": { + "LegacyPrefs": { + "schema": "api/LegacyPrefs/schema.json", + "parent": { + "scopes": ["addon_parent"], + "paths": [["LegacyPrefs"]], + "script": "api/LegacyPrefs/implementation.js" + } + } + }, +``` + +## API Functions + +This API provides the following functions: + +### async getPref(aName, [aFallback]) + +Returns the value for the ``aName`` preference. If it is not defined or has no default value assigned, ``aFallback`` will be returned (which defaults to ``null``). + +### async getUserPref(aName) + +Returns the user defined value for the ``aName`` preference. This will ignore any defined default value and will only return an explicitly set value, which differs from the default. Otherwise it will return ``null``. + +### clearUserPref(aName) + +Clears the user defined value for preference ``aName``. Subsequent calls to ``getUserPref(aName)`` will return ``null``. + +### async setPref(aName, aValue) + +Set the ``aName`` preference to the given value. Will return false and log an error to the console, if the type of ``aValue`` does not match the type of the preference. + +## API Events + +This API provides the following events: + +### onChanged.addListener(listener, branch) + +Register a listener which is notified each time a value in the specified branch is changed. The listener returns the name and the new value of the changed preference. + +Example: + +``` +browser.LegacyPrefs.onChanged.addListener(async (name, value) => { + console.log(`Changed value in "mailnews.": ${name} = ${value}`); +}, "mailnews."); +``` + +--- + +A detailed example using the LegacyPref API to migrate add-on preferences to the local storage can be found in [/scripts/preferences/](https://github.com/thundernest/addon-developer-support/tree/master/scripts/preferences). diff --git a/api/LegacyPrefs/implementation.js b/api/LegacyPrefs/implementation.js new file mode 100644 index 0000000..1837228 --- /dev/null +++ b/api/LegacyPrefs/implementation.js @@ -0,0 +1,213 @@ +/* + * This file is provided by the addon-developer-support repository at + * https://github.com/thundernest/addon-developer-support + * + * Version 1.10 + * - adjusted to Thunderbird Supernova (Services is now in globalThis) + * + * Version 1.9 + * - fixed fallback issue reported by Axel Grude + * + * Version 1.8 + * - reworked onChanged event to allow registering multiple branches + * + * Version 1.7 + * - add onChanged event + * + * Version 1.6 + * - add setDefaultPref() + * + * Version 1.5 + * - replace set/getCharPref by set/getStringPref to fix encoding issue + * + * Version 1.4 + * - setPref() function returns true if the value could be set, otherwise false + * + * Version 1.3 + * - add setPref() function + * + * Version 1.2 + * - add getPref() function + * + * Author: John Bieling (john@thunderbird.net) + * + * 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/. + */ + +var { ExtensionCommon } = ChromeUtils.import( + "resource://gre/modules/ExtensionCommon.jsm" +); +var { ExtensionUtils } = ChromeUtils.import( + "resource://gre/modules/ExtensionUtils.jsm" +); +var { ExtensionError } = ExtensionUtils; + +var Services = globalThis.Services || + ChromeUtils.import("resource://gre/modules/Services.jsm").Services; + + +var LegacyPrefs = class extends ExtensionCommon.ExtensionAPI { + getAPI(context) { + + class LegacyPrefsManager { + constructor() { + this.observedBranches = new Map(); + this.QueryInterface = ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]) + } + + addObservedBranch(branch, fire) { + return this.observedBranches.set(branch, fire); + } + + hasObservedBranch(branch) { + return this.observedBranches.has(branch); + } + + removeObservedBranch(branch) { + return this.observedBranches.delete(branch); + } + + async observe(aSubject, aTopic, aData) { + if (aTopic == "nsPref:changed") { + let branch = [...this.observedBranches.keys()] + .reduce( + (p, c) => aData.startsWith(c) && (!p || c.length > p.length) ? c : p, + null + ); + if (branch) { + let name = aData.substr(branch.length); + let value = await this.getLegacyPref(aData); + let fire = this.observedBranches.get(branch); + fire(name, value); + } + } + } + + async getLegacyPref( + aName, + aFallback = null, + userPrefOnly = true + ) { + let prefType = Services.prefs.getPrefType(aName); + if (prefType == Services.prefs.PREF_INVALID) { + return aFallback; + } + + let value = aFallback; + if (!userPrefOnly || Services.prefs.prefHasUserValue(aName)) { + switch (prefType) { + case Services.prefs.PREF_STRING: + value = Services.prefs.getStringPref(aName, aFallback); + break; + + case Services.prefs.PREF_INT: + value = Services.prefs.getIntPref(aName, aFallback); + break; + + case Services.prefs.PREF_BOOL: + value = Services.prefs.getBoolPref(aName, aFallback); + break; + + default: + console.error( + `Legacy preference <${aName}> has an unknown type of <${prefType}>.` + ); + } + } + return value; + } + } + + let legacyPrefsManager = new LegacyPrefsManager(); + + return { + LegacyPrefs: { + onChanged: new ExtensionCommon.EventManager({ + context, + name: "LegacyPrefs.onChanged", + register: (fire, branch) => { + if (legacyPrefsManager.hasObservedBranch(branch)) { + throw new ExtensionError(`Cannot add more than one listener for branch "${branch}".`) + } + legacyPrefsManager.addObservedBranch(branch, fire.sync); + Services.prefs + .getBranch(null) + .addObserver(branch, legacyPrefsManager); + return () => { + Services.prefs + .getBranch(null) + .removeObserver(branch, legacyPrefsManager); + legacyPrefsManager.removeObservedBranch(branch); + }; + }, + }).api(), + + // only returns something, if a user pref value is set + getUserPref: async function (aName) { + return await legacyPrefsManager.getLegacyPref(aName); + }, + + // returns the default value, if no user defined value exists, + // and returns the fallback value, if the preference does not exist + getPref: async function (aName, aFallback = null) { + return await legacyPrefsManager.getLegacyPref(aName, aFallback, false); + }, + + clearUserPref: function (aName) { + Services.prefs.clearUserPref(aName); + }, + + // sets a pref + setPref: async function (aName, aValue) { + let prefType = Services.prefs.getPrefType(aName); + if (prefType == Services.prefs.PREF_INVALID) { + console.error( + `Unknown legacy preference <${aName}>, forgot to declare a default?.` + ); + return false; + } + + switch (prefType) { + case Services.prefs.PREF_STRING: + Services.prefs.setStringPref(aName, aValue); + return true; + break; + + case Services.prefs.PREF_INT: + Services.prefs.setIntPref(aName, aValue); + return true; + break; + + case Services.prefs.PREF_BOOL: + Services.prefs.setBoolPref(aName, aValue); + return true; + break; + + default: + console.error( + `Legacy preference <${aName}> has an unknown type of <${prefType}>.` + ); + } + return false; + }, + + setDefaultPref: async function (aName, aValue) { + let defaults = Services.prefs.getDefaultBranch(""); + switch (typeof aValue) { + case "string": + return defaults.setStringPref(aName, aValue); + case "number": + return defaults.setIntPref(aName, aValue); + case "boolean": + return defaults.setBoolPref(aName, aValue); + } + }, + }, + }; + } +}; diff --git a/api/LegacyPrefs/schema.json b/api/LegacyPrefs/schema.json new file mode 100644 index 0000000..cbd001e --- /dev/null +++ b/api/LegacyPrefs/schema.json @@ -0,0 +1,134 @@ +[ + { + "namespace": "LegacyPrefs", + "events": [ + { + "name": "onChanged", + "type": "function", + "description": "Fired when a preference has been changed.", + "parameters": [ + { + "name": "name", + "type": "string", + "description": "Name of the preference." + }, + { + "name": "value", + "type": "any", + "description": "Value of the preference." + } + ], + "extraParameters": [ + { + "name": "branch", + "description": "The branch to observe.", + "type": "string" + } + ] + } + ], + "functions": [ + { + "name": "getUserPref", + "type": "function", + "async": true, + "description": "Gets a user value from the legacy pref system.", + "parameters": [ + { + "name": "aName", + "type": "string", + "description": "Name of the preference." + } + ] + }, + { + "name": "getPref", + "type": "function", + "async": true, + "description": "Gets a value from the legacy pref system.", + "parameters": [ + { + "name": "aName", + "type": "string", + "description": "Name of the preference." + }, + { + "name": "aFallback", + "type": "any", + "description": "Value to be returned, if the requested preference does not exist.", + "optional": true, + "default": null + } + ] + }, + { + "name": "setPref", + "type": "function", + "async": true, + "description": "Sets a value for an existing pref of the legacy pref system.", + "parameters": [ + { + "name": "aName", + "type": "string", + "description": "Name of the preference." + }, + { + "name": "aValue", + "choices": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "boolean" + } + ], + "description": "Value to be set." + } + ] + }, + { + "name": "setDefaultPref", + "type": "function", + "async": true, + "description": "Defines the default value for pref of the legacy pref system. This defines the type of a pref and is needed for new prefs.", + "parameters": [ + { + "name": "aName", + "type": "string", + "description": "Name of the preference." + }, + { + "name": "aValue", + "choices": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "boolean" + } + ], + "description": "Default value to be set." + } + ] + }, + { + "name": "clearUserPref", + "type": "function", + "description": "Removes a user value from the legacy pref system.", + "parameters": [ + { + "name": "aName", + "type": "string", + "description": "Name of the preference." + } + ] + } + ] + } +] diff --git a/api/WindowListener/CHANGELOG.md b/api/WindowListener/CHANGELOG.md deleted file mode 100644 index 2d93253..0000000 --- a/api/WindowListener/CHANGELOG.md +++ /dev/null @@ -1,176 +0,0 @@ -Version: 1.62 -------------- -- fix bug in fullyLoaded() - -Version: 1.61 -------------- -- adjusted to Thunderbird Supernova (Services is now in globalThis) - -Version: 1.60 -------------- -- explicitly set hasAddonManagerEventListeners flag to false on uninstall - -Version: 1.59 -------------- -- store hasAddonManagerEventListeners flag in add-on scope instead on the global - window again, and clear it upon add-on removal - -Version: 1.58 -------------- -- hard fork WindowListener v1.57 implementation and continue to serve it for - Thunderbird 111 and older -- WindowListener v1.58 supports injection into nested browsers of the new - mailTab front end of Thunderbird Supernova and allows "about:message" and - "about:3pane" to be valid injection targets. More information can be found here: - https://developer.thunderbird.net/thunderbird-development/codebase-overview/mail-front-end - -Version: 1.57 -------------- -- fix race condition which could prevent the AOM tab to be monkey patched correctly - -Version: 1.56 -------------- -- be precise on which revision the wrench symbol should be displayed, instead of - the options button - -Version: 1.54 -------------- -- fix "ownerDoc.getElementById() is undefined" bug - -Version: 1.53 -------------- -- fix "tab.browser is undefined" bug - -Version: 1.52 -------------- -- clear cache only if add-on is uninstalled/updated, not on app shutdown - -Version: 1.51 -------------- -- use wrench button for options for TB78.10 - -Version: 1.50 -------------- -- use built-in CSS rules to fix options button for dark themes (thanks to Thunder) -- fix some occasions where options button was not added - -Version: 1.49 -------------- -- fixed missing eventListener for Beta + Daily - -Version: 1.48 -------------- -- moved notifyTools into its own NotifyTools API. - -Version: 1.39 -------------- -- fix for 68 - -Version: 1.36 -------------- -- fix for beta 87 - -Version: 1.35 -------------- -- add support for options button/menu in add-on manager and fix 68 double menu entry - -Version: 1.34 -------------- -- fix error in unload - -Version: 1.33 -------------- -- fix for e10s - -Version: 1.30 -------------- -- replace setCharPref by setStringPref to cope with UTF-8 encoding - -Version: 1.29 -------------- -- open options window centered - -Version: 1.28 -------------- -- do not crash on missing icon - -Version: 1.27 -------------- -- add openOptionsDialog() - -Version: 1.26 -------------- -- pass WL object to legacy preference window - -Version: 1.25 -------------- -- adding waitForMasterPassword - -Version: 1.24 -------------- -- automatically localize i18n locale strings in injectElements() - -Version: 1.22 -------------- -- to reduce confusions, only check built-in URLs as add-on URLs cannot - be resolved if a temp installed add-on has bin zipped - -Version: 1.21 -------------- -- print debug messages only if add-ons are installed temporarily from - the add-on debug page -- add checks to registered windows and scripts, if they actually exists - -Version: 1.20 -------------- -- fix long delay before customize window opens -- fix non working removal of palette items - -Version: 1.19 -------------- -- add support for ToolbarPalette - -Version: 1.18 -------------- -- execute shutdown script also during global app shutdown (fixed) - -Version: 1.17 -------------- -- execute shutdown script also during global app shutdown - -Version: 1.16 -------------- -- support for persist - -Version: 1.15 -------------- -- make (undocumented) startup() async - -Version: 1.14 -------------- -- support resource urls - -Version: 1.12 -------------- -- no longer allow to enforce custom "namespace" -- no longer call it namespace but uniqueRandomID / scopeName -- expose special objects as the global WL object -- autoremove injected elements after onUnload has ben executed - -Version: 1.9 -------------- -- automatically remove all entries added by injectElements - -Version: 1.8 -------------- -- add injectElements - -Version: 1.7 -------------- -- add injectCSS -- add optional enforced namespace - -Version: 1.6 -------------- -- added mutation observer to be able to inject into browser elements -- use larger icons as fallback diff --git a/api/WindowListener/README.md b/api/WindowListener/README.md deleted file mode 100644 index 0996ba8..0000000 --- a/api/WindowListener/README.md +++ /dev/null @@ -1 +0,0 @@ -Usage description can be found in the [wiki](https://github.com/thundernest/addon-developer-support/wiki/Using-the-WindowListener-API-to-convert-a-Legacy-Overlay-WebExtension-into-a-MailExtension-for-Thunderbird-78). diff --git a/api/WindowListener/implementation.js b/api/WindowListener/implementation.js deleted file mode 100644 index cc89d1a..0000000 --- a/api/WindowListener/implementation.js +++ /dev/null @@ -1,2170 +0,0 @@ -/* - * This file is provided by the addon-developer-support repository at - * https://github.com/thundernest/addon-developer-support - * - * Version 1.62 - * - * Author: John Bieling (john@thunderbird.net) - * - * 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 some things we need. -var { ExtensionCommon } = ChromeUtils.import( - "resource://gre/modules/ExtensionCommon.jsm" -); -var { ExtensionSupport } = ChromeUtils.import( - "resource:///modules/ExtensionSupport.jsm" -); -var Services = globalThis.Services || - ChromeUtils.import("resource://gre/modules/Services.jsm").Services; - -function getThunderbirdVersion() { - let parts = Services.appinfo.version.split("."); - return { - major: parseInt(parts[0]), - minor: parseInt(parts[1]), - } -} - -var WindowListener_102 = class extends ExtensionCommon.ExtensionAPI { - log(msg) { - if (this.debug) console.log("WindowListener API: " + msg); - } - - getCards(e) { - // This gets triggered by real events but also manually by providing the outer window. - // The event is attached to the outer browser, get the inner one. - let doc; - - // 78,86, and 87+ need special handholding. *Yeah*. - if (getThunderbirdVersion().major < 86) { - let ownerDoc = e.document || e.target.ownerDocument; - doc = ownerDoc.getElementById("html-view-browser").contentDocument; - } else if (getThunderbirdVersion().major < 87) { - let ownerDoc = e.document || e.target; - doc = ownerDoc.getElementById("html-view-browser").contentDocument; - } else { - doc = e.document || e.target; - } - return doc.querySelectorAll("addon-card"); - } - - // Add pref entry to 68 - add68PrefsEntry(event) { - let id = this.menu_addonPrefs_id + "_" + this.uniqueRandomID; - - // Get the best size of the icon (16px or bigger) - let iconSizes = this.extension.manifest.icons - ? Object.keys(this.extension.manifest.icons) - : []; - iconSizes.sort((a, b) => a - b); - let bestSize = iconSizes.filter((e) => parseInt(e) >= 16).shift(); - let icon = bestSize ? this.extension.manifest.icons[bestSize] : ""; - - let name = this.extension.manifest.name; - let entry = icon - ? event.target.ownerGlobal.MozXULElement.parseXULToFragment( - `` - ) - : event.target.ownerGlobal.MozXULElement.parseXULToFragment( - `` - ); - - event.target.appendChild(entry); - let noPrefsElem = event.target.querySelector('[disabled="true"]'); - // using collapse could be undone by core, so we use display none - // noPrefsElem.setAttribute("collapsed", "true"); - noPrefsElem.style.display = "none"; - event.target.ownerGlobal.document - .getElementById(id) - .addEventListener("command", this); - } - - // Event handler for the addon manager, to update the state of the options button. - handleEvent(e) { - switch (e.type) { - // 68 add-on options menu showing - case "popupshowing": - { - this.add68PrefsEntry(e); - } - break; - - // 78/88 add-on options menu/button click - case "click": - { - e.preventDefault(); - e.stopPropagation(); - let WL = {}; - WL.extension = this.extension; - WL.messenger = this.getMessenger(this.context); - let w = Services.wm.getMostRecentWindow("mail:3pane"); - w.openDialog( - this.pathToOptionsPage, - "AddonOptions", - "chrome,resizable,centerscreen", - WL - ); - } - break; - - // 68 add-on options menu command - case "command": - { - let WL = {}; - WL.extension = this.extension; - WL.messenger = this.getMessenger(this.context); - e.target.ownerGlobal.openDialog( - this.pathToOptionsPage, - "AddonOptions", - "chrome,resizable,centerscreen", - WL - ); - } - break; - - // update, ViewChanged and manual call for add-on manager options overlay - default: { - let cards = this.getCards(e); - for (let card of cards) { - // Setup either the options entry in the menu or the button - //window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", WL)}); - if (card.addon.id == this.extension.id) { - let optionsMenu = - (getThunderbirdVersion().major > 78 && getThunderbirdVersion().major < 88) || - (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor < 10) || - (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor == 10 && getThunderbirdVersion().revision < 2); - if (optionsMenu) { - // Options menu in 78.0-78.10 and 79-87 - let addonOptionsLegacyEntry = card.querySelector( - ".extension-options-legacy" - ); - if (card.addon.isActive && !addonOptionsLegacyEntry) { - let addonOptionsEntry = card.querySelector( - "addon-options panel-list panel-item[action='preferences']" - ); - addonOptionsLegacyEntry = card.ownerDocument.createElement( - "panel-item" - ); - addonOptionsLegacyEntry.setAttribute( - "data-l10n-id", - "preferences-addon-button" - ); - addonOptionsLegacyEntry.classList.add( - "extension-options-legacy" - ); - addonOptionsEntry.parentNode.insertBefore( - addonOptionsLegacyEntry, - addonOptionsEntry - ); - card - .querySelector(".extension-options-legacy") - .addEventListener("click", this); - } else if (!card.addon.isActive && addonOptionsLegacyEntry) { - addonOptionsLegacyEntry.remove(); - } - } else { - // Add-on button in 88 - let addonOptionsButton = card.querySelector( - ".windowlistener-options-button" - ); - if (card.addon.isActive && !addonOptionsButton) { - addonOptionsButton = card.ownerDocument.createElement("button"); - addonOptionsButton.classList.add("windowlistener-options-button"); - addonOptionsButton.classList.add("extension-options-button"); - card.optionsButton.parentNode.insertBefore( - addonOptionsButton, - card.optionsButton - ); - card - .querySelector(".windowlistener-options-button") - .addEventListener("click", this); - } else if (!card.addon.isActive && addonOptionsButton) { - addonOptionsButton.remove(); - } - } - } - } - } - } - } - - // Some tab/add-on-manager related functions - getTabMail(window) { - return window.document.getElementById("tabmail"); - } - - // returns the outer browser, not the nested browser of the add-on manager - // events must be attached to the outer browser - getAddonManagerFromTab(tab) { - if (tab.browser && tab.mode.name == "contentTab") { - let win = tab.browser.contentWindow; - if (win && win.location.href == "about:addons") { - return win; - } - } - } - - getAddonManagerFromWindow(window) { - let tabMail = this.getTabMail(window); - for (let tab of tabMail.tabInfo) { - let managerWindow = this.getAddonManagerFromTab(tab); - if (managerWindow) { - return managerWindow; - } - } - } - - async getAddonManagerFromWindowWaitForLoad(window) { - let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane"); - - let tabMail = this.getTabMail(window); - for (let tab of tabMail.tabInfo) { - if (tab.browser && tab.mode.name == "contentTab") { - // Instead of registering a load observer, wait until its loaded. Not nice, - // but gets aroud a lot of edge cases. - while(!tab.pageLoaded) { - await new Promise(r => setTimeout(r, 150)); - } - let managerWindow = this.getAddonManagerFromTab(tab); - if (managerWindow) { - return managerWindow; - } - } - } - } - - setupAddonManager(managerWindow, forceLoad = false) { - if (!managerWindow) { - return; - } - if (!( - managerWindow && - managerWindow[this.uniqueRandomID] && - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners - )) { - managerWindow.document.addEventListener("ViewChanged", this); - managerWindow.document.addEventListener("update", this); - managerWindow.document.addEventListener("view-loaded", this); - managerWindow[this.uniqueRandomID] = {}; - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true; - } - if (forceLoad) this.handleEvent(managerWindow); - } - - getMessenger(context) { - let apis = ["storage", "runtime", "extension", "i18n"]; - - function getStorage() { - let localstorage = null; - try { - localstorage = context.apiCan.findAPIPath("storage"); - localstorage.local.get = (...args) => - localstorage.local.callMethodInParentProcess("get", args); - localstorage.local.set = (...args) => - localstorage.local.callMethodInParentProcess("set", args); - localstorage.local.remove = (...args) => - localstorage.local.callMethodInParentProcess("remove", args); - localstorage.local.clear = (...args) => - localstorage.local.callMethodInParentProcess("clear", args); - } catch (e) { - console.info("Storage permission is missing"); - } - return localstorage; - } - - let messenger = {}; - for (let api of apis) { - switch (api) { - case "storage": - XPCOMUtils.defineLazyGetter(messenger, "storage", () => getStorage()); - break; - - default: - XPCOMUtils.defineLazyGetter(messenger, api, () => - context.apiCan.findAPIPath(api) - ); - } - } - return messenger; - } - - error(msg) { - if (this.debug) console.error("WindowListener API: " + msg); - } - - // async sleep function using Promise - async sleep(delay) { - let timer = Components.classes["@mozilla.org/timer;1"].createInstance( - Components.interfaces.nsITimer - ); - return new Promise(function (resolve, reject) { - let event = { - notify: function (timer) { - resolve(); - }, - }; - timer.initWithCallback( - event, - delay, - Components.interfaces.nsITimer.TYPE_ONE_SHOT - ); - }); - } - - getAPI(context) { - // Track if this is the background/main context - if (context.viewType != "background") - throw new Error( - "The WindowListener API may only be called from the background page." - ); - - this.context = context; - - this.uniqueRandomID = "AddOnNS" + context.extension.instanceId; - this.menu_addonPrefs_id = "addonPrefs"; - - this.registeredWindows = {}; - this.pathToStartupScript = null; - this.pathToShutdownScript = null; - this.pathToOptionsPage = null; - this.chromeHandle = null; - this.chromeData = null; - this.resourceData = null; - this.openWindows = []; - this.debug = context.extension.addonData.temporarilyInstalled; - - const aomStartup = Cc[ - "@mozilla.org/addons/addon-manager-startup;1" - ].getService(Ci.amIAddonManagerStartup); - const resProto = Cc[ - "@mozilla.org/network/protocol;1?name=resource" - ].getService(Ci.nsISubstitutingProtocolHandler); - - let self = this; - - // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager. - this.tabMonitor = { - onTabTitleChanged(tab) { }, - onTabClosing(tab) { }, - onTabPersist(tab) { }, - onTabRestored(tab) { }, - onTabSwitched(aNewTab, aOldTab) { }, - async onTabOpened(tab) { - if (tab.browser && tab.mode.name == "contentTab") { - let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane"); - // Instead of registering a load observer, wait until its loaded. Not nice, - // but gets aroud a lot of edge cases. - while(!tab.pageLoaded) { - await new Promise(r => setTimeout(r, 150)); - } - self.setupAddonManager(self.getAddonManagerFromTab(tab)); - } - }, - }; - - return { - WindowListener: { - async waitForMasterPassword() { - // Wait until master password has been entered (if needed) - while (!Services.logins.isLoggedIn) { - self.log("Waiting for master password."); - await self.sleep(1000); - } - self.log("Master password has been entered."); - }, - - aDocumentExistsAt(uriString) { - self.log( - "Checking if document at <" + - uriString + - "> used in registration actually exists." - ); - try { - let uriObject = Services.io.newURI(uriString); - let content = Cu.readUTF8URI(uriObject); - } catch (e) { - Components.utils.reportError(e); - return false; - } - return true; - }, - - registerOptionsPage(optionsUrl) { - self.pathToOptionsPage = optionsUrl.startsWith("chrome://") - ? optionsUrl - : context.extension.rootURI.resolve(optionsUrl); - }, - - registerDefaultPrefs(defaultUrl) { - let url = context.extension.rootURI.resolve(defaultUrl); - - let prefsObj = {}; - prefsObj.Services = globalThis.Services|| - ChromeUtils.import("resource://gre/modules/Services.jsm").Services; - prefsObj.pref = function (aName, aDefault) { - let defaults = Services.prefs.getDefaultBranch(""); - switch (typeof aDefault) { - case "string": - return defaults.setStringPref(aName, aDefault); - - case "number": - return defaults.setIntPref(aName, aDefault); - - case "boolean": - return defaults.setBoolPref(aName, aDefault); - - default: - throw new Error( - "Preference <" + - aName + - "> has an unsupported type <" + - typeof aDefault + - ">. Allowed are string, number and boolean." - ); - } - }; - Services.scriptloader.loadSubScript(url, prefsObj, "UTF-8"); - }, - - registerChromeUrl(data) { - let chromeData = []; - let resourceData = []; - for (let entry of data) { - if (entry[0] == "resource") resourceData.push(entry); - else chromeData.push(entry); - } - - if (chromeData.length > 0) { - const manifestURI = Services.io.newURI( - "manifest.json", - null, - context.extension.rootURI - ); - self.chromeHandle = aomStartup.registerChrome( - manifestURI, - chromeData - ); - } - - for (let res of resourceData) { - // [ "resource", "shortname" , "path" ] - let uri = Services.io.newURI( - res[2], - null, - context.extension.rootURI - ); - resProto.setSubstitutionWithFlags( - res[1], - uri, - resProto.ALLOW_CONTENT_ACCESS - ); - } - - self.chromeData = chromeData; - self.resourceData = resourceData; - }, - - registerWindow(windowHref, jsFile) { - if (self.debug && !this.aDocumentExistsAt(windowHref)) { - self.error( - "Attempt to register an injector script for non-existent window: " + - windowHref - ); - return; - } - - if (!self.registeredWindows.hasOwnProperty(windowHref)) { - // path to JS file can either be chrome:// URL or a relative URL - let path = jsFile.startsWith("chrome://") - ? jsFile - : context.extension.rootURI.resolve(jsFile); - - self.registeredWindows[windowHref] = path; - } else { - self.error( - "Window <" + windowHref + "> has already been registered" - ); - } - }, - - registerStartupScript(aPath) { - self.pathToStartupScript = aPath.startsWith("chrome://") - ? aPath - : context.extension.rootURI.resolve(aPath); - }, - - registerShutdownScript(aPath) { - self.pathToShutdownScript = aPath.startsWith("chrome://") - ? aPath - : context.extension.rootURI.resolve(aPath); - }, - - openOptionsDialog(windowId) { - let window = context.extension.windowManager.get(windowId, context) - .window; - let WL = {}; - WL.extension = self.extension; - WL.messenger = self.getMessenger(self.context); - window.openDialog( - self.pathToOptionsPage, - "AddonOptions", - "chrome,resizable,centerscreen", - WL - ); - }, - - async startListening() { - // load the registered startup script, if one has been registered - // (mail3:pane may not have been fully loaded yet) - if (self.pathToStartupScript) { - let startupJS = {}; - startupJS.WL = {}; - startupJS.WL.extension = self.extension; - startupJS.WL.messenger = self.getMessenger(self.context); - try { - if (self.pathToStartupScript) { - Services.scriptloader.loadSubScript( - self.pathToStartupScript, - startupJS, - "UTF-8" - ); - // delay startup until startup has been finished - self.log( - "Waiting for async startup() in <" + - self.pathToStartupScript + - "> to finish." - ); - if (startupJS.startup) { - await startupJS.startup(); - self.log( - "startup() in <" + self.pathToStartupScript + "> finished" - ); - } else { - self.log( - "No startup() in <" + self.pathToStartupScript + "> found." - ); - } - } - } catch (e) { - Components.utils.reportError(e); - } - } - - let urls = Object.keys(self.registeredWindows); - if (urls.length > 0) { - // Before registering the window listener, check which windows are already open - self.openWindows = []; - for (let window of Services.wm.getEnumerator(null)) { - self.openWindows.push(window); - } - - // Register window listener for all pre-registered windows - ExtensionSupport.registerWindowListener( - "injectListener_" + self.uniqueRandomID, - { - // React on all windows and manually reduce to the registered - // windows, so we can do special actions when the main - // messenger window is opened. - //chromeURLs: Object.keys(self.registeredWindows), - async onLoadWindow(window) { - // Create add-on scope - window[self.uniqueRandomID] = {}; - - // Special action #1: If this is the main messenger window - if ( - window.location.href == - "chrome://messenger/content/messenger.xul" || - window.location.href == - "chrome://messenger/content/messenger.xhtml" - ) { - if (self.pathToOptionsPage) { - if (getThunderbirdVersion().major < 78) { - let element_addonPrefs = window.document.getElementById( - self.menu_addonPrefs_id - ); - element_addonPrefs.addEventListener( - "popupshowing", - self - ); - } else { - // Add a tabmonitor, to be able to setup the options button/menu in the add-on manager. - self - .getTabMail(window) - .registerTabMonitor(self.tabMonitor); - window[self.uniqueRandomID].hasTabMonitor = true; - // Setup the options button/menu in the add-on manager, if it is already open. - let managerWindow = await self.getAddonManagerFromWindowWaitForLoad(window); - self.setupAddonManager(managerWindow, true); - } - } - } - - // Special action #2: If this page contains browser elements - let browserElements = window.document.getElementsByTagName( - "browser" - ); - if (browserElements.length > 0) { - //register a MutationObserver - window[ - self.uniqueRandomID - ]._mObserver = new window.MutationObserver(function ( - mutations - ) { - mutations.forEach(async function (mutation) { - if ( - mutation.attributeName == "src" && - self.registeredWindows.hasOwnProperty( - mutation.target.getAttribute("src") - ) - ) { - // When the MutationObserver callsback, the window is still showing "about:black" and it is going - // to unload and then load the new page. Any eventListener attached to the window will be removed - // so we cannot listen for the load event. We have to poll manually to learn when loading has finished. - // On my system it takes 70ms. - let loaded = false; - for (let i = 0; i < 100 && !loaded; i++) { - await self.sleep(100); - let targetWindow = - mutation.target.contentWindow.wrappedJSObject; - if ( - targetWindow && - targetWindow.location.href == - mutation.target.getAttribute("src") && - targetWindow.document.readyState == "complete" - ) { - loaded = true; - break; - } - } - if (loaded) { - let targetWindow = - mutation.target.contentWindow.wrappedJSObject; - // Create add-on scope - targetWindow[self.uniqueRandomID] = {}; - // Inject with isAddonActivation = false - self._loadIntoWindow(targetWindow, false); - } - } - }); - }); - - for (let element of browserElements) { - if ( - self.registeredWindows.hasOwnProperty( - element.getAttribute("src") - ) - ) { - let targetWindow = - element.contentWindow.wrappedJSObject; - // Create add-on scope - targetWindow[self.uniqueRandomID] = {}; - // Inject with isAddonActivation = true - self._loadIntoWindow(targetWindow, true); - } else { - // Window/Browser is not yet fully loaded, postpone injection via MutationObserver - window[self.uniqueRandomID]._mObserver.observe( - element, - { - attributes: true, - childList: false, - characterData: false, - } - ); - } - } - } - - // Load JS into window - self._loadIntoWindow( - window, - self.openWindows.includes(window) - ); - }, - - onUnloadWindow(window) { - // Remove JS from window, window is being closed, addon is not shut down - self._unloadFromWindow(window, false); - }, - } - ); - } else { - self.error("Failed to start listening, no windows registered"); - } - }, - }, - }; - } - - _loadIntoWindow(window, isAddonActivation) { - if ( - window.hasOwnProperty(this.uniqueRandomID) && - this.registeredWindows.hasOwnProperty(window.location.href) - ) { - try { - let uniqueRandomID = this.uniqueRandomID; - let extension = this.extension; - - // Add reference to window to add-on scope - window[this.uniqueRandomID].window = window; - window[this.uniqueRandomID].document = window.document; - - // Keep track of toolbarpalettes we are injecting into - window[this.uniqueRandomID]._toolbarpalettes = {}; - - //Create WLDATA object - window[this.uniqueRandomID].WL = {}; - window[this.uniqueRandomID].WL.scopeName = this.uniqueRandomID; - - // Add helper function to inject CSS to WLDATA object - window[this.uniqueRandomID].WL.injectCSS = function (cssFile) { - let element; - let v = parseInt(Services.appinfo.version.split(".").shift()); - - // using createElementNS in TB78 delays the insert process and hides any security violation errors - if (v > 68) { - element = window.document.createElement("link"); - } else { - let ns = window.document.documentElement.lookupNamespaceURI("html"); - element = window.document.createElementNS(ns, "link"); - } - - element.setAttribute("wlapi_autoinjected", uniqueRandomID); - element.setAttribute("rel", "stylesheet"); - element.setAttribute("href", cssFile); - return window.document.documentElement.appendChild(element); - }; - - // Add helper function to inject XUL to WLDATA object - window[this.uniqueRandomID].WL.injectElements = function ( - xulString, - dtdFiles = [], - debug = false - ) { - let toolbarsToResolve = []; - - function checkElements(stringOfIDs) { - let arrayOfIDs = stringOfIDs.split(",").map((e) => e.trim()); - for (let id of arrayOfIDs) { - let element = window.document.getElementById(id); - if (element) { - return element; - } - } - return null; - } - - function localize(entity) { - let msg = entity.slice("__MSG_".length, -2); - return extension.localeData.localizeMessage(msg); - } - - function injectChildren(elements, container) { - if (debug) console.log(elements); - - for (let i = 0; i < elements.length; i++) { - // take care of persists - const uri = window.document.documentURI; - for (const persistentNode of elements[i].querySelectorAll( - "[persist]" - )) { - for (const persistentAttribute of persistentNode - .getAttribute("persist") - .trim() - .split(" ")) { - if ( - Services.xulStore.hasValue( - uri, - persistentNode.id, - persistentAttribute - ) - ) { - persistentNode.setAttribute( - persistentAttribute, - Services.xulStore.getValue( - uri, - persistentNode.id, - persistentAttribute - ) - ); - } - } - } - - if ( - elements[i].hasAttribute("insertafter") && - checkElements(elements[i].getAttribute("insertafter")) - ) { - let insertAfterElement = checkElements( - elements[i].getAttribute("insertafter") - ); - - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - ": insertafter " + - insertAfterElement.id - ); - if ( - debug && - elements[i].id && - window.document.getElementById(elements[i].id) - ) { - console.error( - "The id <" + - elements[i].id + - "> of the injected element already exists in the document!" - ); - } - elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID); - insertAfterElement.parentNode.insertBefore( - elements[i], - insertAfterElement.nextSibling - ); - } else if ( - elements[i].hasAttribute("insertbefore") && - checkElements(elements[i].getAttribute("insertbefore")) - ) { - let insertBeforeElement = checkElements( - elements[i].getAttribute("insertbefore") - ); - - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - ": insertbefore " + - insertBeforeElement.id - ); - if ( - debug && - elements[i].id && - window.document.getElementById(elements[i].id) - ) { - console.error( - "The id <" + - elements[i].id + - "> of the injected element already exists in the document!" - ); - } - elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID); - insertBeforeElement.parentNode.insertBefore( - elements[i], - insertBeforeElement - ); - } else if ( - elements[i].id && - window.document.getElementById(elements[i].id) - ) { - // existing container match, dive into recursivly - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - " is an existing container, injecting into " + - elements[i].id - ); - injectChildren( - Array.from(elements[i].children), - window.document.getElementById(elements[i].id) - ); - } else if (elements[i].localName === "toolbarpalette") { - // These vanish from the document but still exist via the palette property - if (debug) console.log(elements[i].id + " is a toolbarpalette"); - let boxes = [ - ...window.document.getElementsByTagName("toolbox"), - ]; - let box = boxes.find( - (box) => box.palette && box.palette.id === elements[i].id - ); - let palette = box ? box.palette : null; - - if (!palette) { - if (debug) - console.log( - `The palette for ${elements[i].id} could not be found, deferring to later` - ); - continue; - } - - if (debug) - console.log(`The toolbox for ${elements[i].id} is ${box.id}`); - - toolbarsToResolve.push(...box.querySelectorAll("toolbar")); - toolbarsToResolve.push( - ...window.document.querySelectorAll( - `toolbar[toolboxid="${box.id}"]` - ) - ); - for (let child of elements[i].children) { - child.setAttribute("wlapi_autoinjected", uniqueRandomID); - } - window[uniqueRandomID]._toolbarpalettes[palette.id] = palette; - injectChildren(Array.from(elements[i].children), palette); - } else { - // append element to the current container - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - ": append to " + - container.id - ); - elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID); - container.appendChild(elements[i]); - } - } - } - - if (debug) console.log("Injecting into root document:"); - let localizedXulString = xulString.replace( - /__MSG_(.*?)__/g, - localize - ); - injectChildren( - Array.from( - window.MozXULElement.parseXULToFragment( - localizedXulString, - dtdFiles - ).children - ), - window.document.documentElement - ); - - for (let bar of toolbarsToResolve) { - let currentset = Services.xulStore.getValue( - window.location, - bar.id, - "currentset" - ); - if (currentset) { - bar.currentSet = currentset; - } else if (bar.getAttribute("defaultset")) { - bar.currentSet = bar.getAttribute("defaultset"); - } - } - }; - - // Add extension object to WLDATA object - window[this.uniqueRandomID].WL.extension = this.extension; - // Add messenger object to WLDATA object - window[this.uniqueRandomID].WL.messenger = this.getMessenger( - this.context - ); - // Load script into add-on scope - Services.scriptloader.loadSubScript( - this.registeredWindows[window.location.href], - window[this.uniqueRandomID], - "UTF-8" - ); - window[this.uniqueRandomID].onLoad(isAddonActivation); - } catch (e) { - Components.utils.reportError(e); - } - } - } - - _unloadFromWindow(window, isAddonDeactivation) { - // unload any contained browser elements - if ( - window.hasOwnProperty(this.uniqueRandomID) && - window[this.uniqueRandomID].hasOwnProperty("_mObserver") - ) { - window[this.uniqueRandomID]._mObserver.disconnect(); - let browserElements = window.document.getElementsByTagName("browser"); - for (let element of browserElements) { - if (element.contentWindow) { - this._unloadFromWindow( - element.contentWindow.wrappedJSObject, - isAddonDeactivation - ); - } - } - } - - if ( - window.hasOwnProperty(this.uniqueRandomID) && - this.registeredWindows.hasOwnProperty(window.location.href) - ) { - // Remove this window from the list of open windows - this.openWindows = this.openWindows.filter((e) => e != window); - - if (window[this.uniqueRandomID].onUnload) { - try { - // Call onUnload() - window[this.uniqueRandomID].onUnload(isAddonDeactivation); - } catch (e) { - Components.utils.reportError(e); - } - } - - // Remove all auto injected objects - let elements = Array.from( - window.document.querySelectorAll( - '[wlapi_autoinjected="' + this.uniqueRandomID + '"]' - ) - ); - for (let element of elements) { - element.remove(); - } - - // Remove all autoinjected toolbarpalette items - for (const palette of Object.values( - window[this.uniqueRandomID]._toolbarpalettes - )) { - let elements = Array.from( - palette.querySelectorAll( - '[wlapi_autoinjected="' + this.uniqueRandomID + '"]' - ) - ); - for (let element of elements) { - element.remove(); - } - } - } - - // Remove add-on scope, if it exists - if (window.hasOwnProperty(this.uniqueRandomID)) { - delete window[this.uniqueRandomID]; - } - } - - onShutdown(isAppShutdown) { - if (isAppShutdown) { - return; // the application gets unloaded anyway - } - - // Unload from all still open windows - let urls = Object.keys(this.registeredWindows); - if (urls.length > 0) { - for (let window of Services.wm.getEnumerator(null)) { - //remove our entry in the add-on options menu - if ( - this.pathToOptionsPage && - (window.location.href == "chrome://messenger/content/messenger.xul" || - window.location.href == - "chrome://messenger/content/messenger.xhtml") - ) { - if (getThunderbirdVersion().major < 78) { - let element_addonPrefs = window.document.getElementById( - this.menu_addonPrefs_id - ); - element_addonPrefs.removeEventListener("popupshowing", this); - // Remove our entry. - let entry = window.document.getElementById( - this.menu_addonPrefs_id + "_" + this.uniqueRandomID - ); - if (entry) entry.remove(); - // Do we have to unhide the noPrefsElement? - if (element_addonPrefs.children.length == 1) { - let noPrefsElem = element_addonPrefs.querySelector( - '[disabled="true"]' - ); - noPrefsElem.style.display = "inline"; - } - } else { - // Remove event listener for addon manager view changes - let managerWindow = this.getAddonManagerFromWindow(window); - if ( - managerWindow && - managerWindow[this.uniqueRandomID] && - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners - ) { - managerWindow.document.removeEventListener("ViewChanged", this); - managerWindow.document.removeEventListener("view-loaded", this); - managerWindow.document.removeEventListener("update", this); - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false; - - let cards = this.getCards(managerWindow); - if (getThunderbirdVersion().major < 88) { - // Remove options menu in 78-87 - for (let card of cards) { - let addonOptionsLegacyEntry = card.querySelector( - ".extension-options-legacy" - ); - if (addonOptionsLegacyEntry) addonOptionsLegacyEntry.remove(); - } - } else { - // Remove options button in 88 - for (let card of cards) { - if (card.addon.id == this.extension.id) { - let addonOptionsButton = card.querySelector( - ".windowlistener-options-button" - ); - if (addonOptionsButton) addonOptionsButton.remove(); - break; - } - } - } - } - - // Remove tabmonitor - if (window[this.uniqueRandomID].hasTabMonitor) { - this.getTabMail(window).unregisterTabMonitor(this.tabMonitor); - window[this.uniqueRandomID].hasTabMonitor = false; - } - } - } - - // if it is app shutdown, it is not just an add-on deactivation - this._unloadFromWindow(window, !isAppShutdown); - } - // Stop listening for new windows. - ExtensionSupport.unregisterWindowListener( - "injectListener_" + this.uniqueRandomID - ); - } - - // Load registered shutdown script - let shutdownJS = {}; - shutdownJS.extension = this.extension; - try { - if (this.pathToShutdownScript) - Services.scriptloader.loadSubScript( - this.pathToShutdownScript, - shutdownJS, - "UTF-8" - ); - } catch (e) { - Components.utils.reportError(e); - } - - // Extract all registered chrome content urls - let chromeUrls = []; - if (this.chromeData) { - for (let chromeEntry of this.chromeData) { - if (chromeEntry[0].toLowerCase().trim() == "content") { - chromeUrls.push("chrome://" + chromeEntry[1] + "/"); - } - } - } - - // Unload JSMs of this add-on - const rootURI = this.extension.rootURI.spec; - for (let module of Cu.loadedModules) { - if ( - module.startsWith(rootURI) || - (module.startsWith("chrome://") && - chromeUrls.find((s) => module.startsWith(s))) - ) { - this.log("Unloading: " + module); - Cu.unload(module); - } - } - - // Flush all caches - Services.obs.notifyObservers(null, "startupcache-invalidate"); - this.registeredWindows = {}; - - if (this.resourceData) { - const resProto = Cc[ - "@mozilla.org/network/protocol;1?name=resource" - ].getService(Ci.nsISubstitutingProtocolHandler); - for (let res of this.resourceData) { - // [ "resource", "shortname" , "path" ] - resProto.setSubstitution(res[1], null); - } - } - - if (this.chromeHandle) { - this.chromeHandle.destruct(); - this.chromeHandle = null; - } - } -}; - -var WindowListener_115 = class extends ExtensionCommon.ExtensionAPI { - log(msg) { - if (this.debug) console.log("WindowListener API: " + msg); - } - - getCards(e) { - // This gets triggered by real events but also manually by providing the outer window. - // The event is attached to the outer browser, get the inner one. - let doc = e.document || e.target; - return doc.querySelectorAll("addon-card"); - } - - - // Event handler for the addon manager, to update the state of the options button. - handleEvent(e) { - switch (e.type) { - case "click": { - e.preventDefault(); - e.stopPropagation(); - let WL = {}; - WL.extension = this.extension; - WL.messenger = this.getMessenger(this.context); - let w = Services.wm.getMostRecentWindow("mail:3pane"); - w.openDialog( - this.pathToOptionsPage, - "AddonOptions", - "chrome,resizable,centerscreen", - WL - ); - } - break; - - // update, ViewChanged and manual call for add-on manager options overlay - default: { - let cards = this.getCards(e); - for (let card of cards) { - // Setup either the options entry in the menu or the button - if (card.addon.id == this.extension.id) { - // Add-on button - let addonOptionsButton = card.querySelector( - ".windowlistener-options-button" - ); - if (card.addon.isActive && !addonOptionsButton) { - let origAddonOptionsButton = card.querySelector(".extension-options-button") - origAddonOptionsButton.setAttribute("hidden", "true"); - - addonOptionsButton = card.ownerDocument.createElement("button"); - addonOptionsButton.classList.add("windowlistener-options-button"); - addonOptionsButton.classList.add("extension-options-button"); - card.optionsButton.parentNode.insertBefore( - addonOptionsButton, - card.optionsButton - ); - card - .querySelector(".windowlistener-options-button") - .addEventListener("click", this); - } else if (!card.addon.isActive && addonOptionsButton) { - addonOptionsButton.remove(); - } - } - } - } - } - } - - // Some tab/add-on-manager related functions - getTabMail(window) { - return window.document.getElementById("tabmail"); - } - - // returns the outer browser, not the nested browser of the add-on manager - // events must be attached to the outer browser - getAddonManagerFromTab(tab) { - if (tab.browser) { - let win = tab.browser.contentWindow; - if (win && win.location.href == "about:addons") { - return win; - } - } - } - - getAddonManagerFromWindow(window) { - let tabMail = this.getTabMail(window); - for (let tab of tabMail.tabInfo) { - let win = this.getAddonManagerFromTab(tab); - if (win) { - return win; - } - } - } - - setupAddonManager(managerWindow, forceLoad = false) { - if (!managerWindow) { - return; - } - if (!this.pathToOptionsPage) { - return; - } - if ( - managerWindow && - managerWindow[this.uniqueRandomID] && - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners - ) { - return; - } - - managerWindow.document.addEventListener("ViewChanged", this); - managerWindow.document.addEventListener("update", this); - managerWindow.document.addEventListener("view-loaded", this); - managerWindow[this.uniqueRandomID] = {}; - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true; - if (forceLoad) { - this.handleEvent(managerWindow); - } - } - - getMessenger(context) { - let apis = ["storage", "runtime", "extension", "i18n"]; - - function getStorage() { - let localstorage = null; - try { - localstorage = context.apiCan.findAPIPath("storage"); - localstorage.local.get = (...args) => - localstorage.local.callMethodInParentProcess("get", args); - localstorage.local.set = (...args) => - localstorage.local.callMethodInParentProcess("set", args); - localstorage.local.remove = (...args) => - localstorage.local.callMethodInParentProcess("remove", args); - localstorage.local.clear = (...args) => - localstorage.local.callMethodInParentProcess("clear", args); - } catch (e) { - console.info("Storage permission is missing"); - } - return localstorage; - } - - let messenger = {}; - for (let api of apis) { - switch (api) { - case "storage": - XPCOMUtils.defineLazyGetter(messenger, "storage", () => getStorage()); - break; - - default: - XPCOMUtils.defineLazyGetter(messenger, api, () => - context.apiCan.findAPIPath(api) - ); - } - } - return messenger; - } - - error(msg) { - if (this.debug) console.error("WindowListener API: " + msg); - } - - // async sleep function using Promise - async sleep(delay) { - let timer = Components.classes["@mozilla.org/timer;1"].createInstance( - Components.interfaces.nsITimer - ); - return new Promise(function (resolve, reject) { - let event = { - notify: function (timer) { - resolve(); - }, - }; - timer.initWithCallback( - event, - delay, - Components.interfaces.nsITimer.TYPE_ONE_SHOT - ); - }); - } - - getAPI(context) { - // Track if this is the background/main context - if (context.viewType != "background") - throw new Error( - "The WindowListener API may only be called from the background page." - ); - - this.context = context; - - this.uniqueRandomID = "AddOnNS" + context.extension.instanceId; - this.menu_addonPrefs_id = "addonPrefs"; - - this.registeredWindows = {}; - this.pathToStartupScript = null; - this.pathToShutdownScript = null; - this.pathToOptionsPage = null; - this.chromeHandle = null; - this.chromeData = null; - this.resourceData = null; - this.openWindows = []; - this.debug = context.extension.addonData.temporarilyInstalled; - - const aomStartup = Cc[ - "@mozilla.org/addons/addon-manager-startup;1" - ].getService(Ci.amIAddonManagerStartup); - const resProto = Cc[ - "@mozilla.org/network/protocol;1?name=resource" - ].getService(Ci.nsISubstitutingProtocolHandler); - - let self = this; - - // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager. - this.tabMonitor = { - onTabTitleChanged(aTab) { }, - onTabClosing(aTab) { }, - onTabPersist(aTab) { }, - onTabRestored(aTab) { }, - onTabSwitched(aNewTab, aOldTab) { - //self.setupAddonManager(self.getAddonManagerFromTab(aNewTab)); - }, - async onTabOpened(aTab) { - if (aTab.browser) { - if (!aTab.pageLoaded) { - // await a location change if browser is not loaded yet - await new Promise((resolve) => { - let reporterListener = { - QueryInterface: ChromeUtils.generateQI([ - "nsIWebProgressListener", - "nsISupportsWeakReference", - ]), - onStateChange() { }, - onProgressChange() { }, - onLocationChange( - /* in nsIWebProgress*/ aWebProgress, - /* in nsIRequest*/ aRequest, - /* in nsIURI*/ aLocation - ) { - aTab.browser.removeProgressListener(reporterListener); - resolve(); - }, - onStatusChange() { }, - onSecurityChange() { }, - onContentBlockingEvent() { }, - }; - aTab.browser.addProgressListener(reporterListener); - }); - } - self.setupAddonManager(self.getAddonManagerFromTab(aTab)); - self._loadIntoWindow(aTab.browser.contentWindow, false); - } - - if (aTab.chromeBrowser) { - self._loadIntoWindow(aTab.chromeBrowser.contentWindow, false); - } - }, - }; - - return { - WindowListener: { - async waitForMasterPassword() { - // Wait until master password has been entered (if needed) - while (!Services.logins.isLoggedIn) { - self.log("Waiting for master password."); - await self.sleep(1000); - } - self.log("Master password has been entered."); - }, - - aDocumentExistsAt(uriString) { - // No sane way yet to detect if about:urls exists, maybe lookup the definition? - if (uriString.startsWith("about:")) { - return true; - } - - self.log( - "Checking if document at <" + - uriString + - "> used in registration actually exists." - ); - try { - let uriObject = Services.io.newURI(uriString); - let content = Cu.readUTF8URI(uriObject); - } catch (e) { - Components.utils.reportError(e); - return false; - } - return true; - }, - - registerOptionsPage(optionsUrl) { - self.pathToOptionsPage = optionsUrl.startsWith("chrome://") - ? optionsUrl - : context.extension.rootURI.resolve(optionsUrl); - }, - - registerDefaultPrefs(defaultUrl) { - let url = context.extension.rootURI.resolve(defaultUrl); - - let prefsObj = {}; - prefsObj.Services = globalThis.Services|| - ChromeUtils.import("resource://gre/modules/Services.jsm").Services; - prefsObj.pref = function (aName, aDefault) { - let defaults = Services.prefs.getDefaultBranch(""); - switch (typeof aDefault) { - case "string": - return defaults.setStringPref(aName, aDefault); - - case "number": - return defaults.setIntPref(aName, aDefault); - - case "boolean": - return defaults.setBoolPref(aName, aDefault); - - default: - throw new Error( - "Preference <" + - aName + - "> has an unsupported type <" + - typeof aDefault + - ">. Allowed are string, number and boolean." - ); - } - }; - Services.scriptloader.loadSubScript(url, prefsObj, "UTF-8"); - }, - - registerChromeUrl(data) { - let chromeData = []; - let resourceData = []; - for (let entry of data) { - if (entry[0] == "resource") resourceData.push(entry); - else chromeData.push(entry); - } - - if (chromeData.length > 0) { - const manifestURI = Services.io.newURI( - "manifest.json", - null, - context.extension.rootURI - ); - self.chromeHandle = aomStartup.registerChrome( - manifestURI, - chromeData - ); - } - - for (let res of resourceData) { - // [ "resource", "shortname" , "path" ] - let uri = Services.io.newURI( - res[2], - null, - context.extension.rootURI - ); - resProto.setSubstitutionWithFlags( - res[1], - uri, - resProto.ALLOW_CONTENT_ACCESS - ); - } - - self.chromeData = chromeData; - self.resourceData = resourceData; - }, - - registerWindow(windowHref, jsFile) { - if (self.debug && !this.aDocumentExistsAt(windowHref)) { - self.error( - "Attempt to register an injector script for non-existent window: " + - windowHref - ); - return; - } - - if (!self.registeredWindows.hasOwnProperty(windowHref)) { - // path to JS file can either be chrome:// URL or a relative URL - let path = jsFile.startsWith("chrome://") - ? jsFile - : context.extension.rootURI.resolve(jsFile); - - self.registeredWindows[windowHref] = path; - } else { - self.error( - "Window <" + windowHref + "> has already been registered" - ); - } - }, - - registerStartupScript(aPath) { - self.pathToStartupScript = aPath.startsWith("chrome://") - ? aPath - : context.extension.rootURI.resolve(aPath); - }, - - registerShutdownScript(aPath) { - self.pathToShutdownScript = aPath.startsWith("chrome://") - ? aPath - : context.extension.rootURI.resolve(aPath); - }, - - openOptionsDialog(windowId) { - let window = context.extension.windowManager.get(windowId, context) - .window; - let WL = {}; - WL.extension = self.extension; - WL.messenger = self.getMessenger(self.context); - window.openDialog( - self.pathToOptionsPage, - "AddonOptions", - "chrome,resizable,centerscreen", - WL - ); - }, - - async startListening() { - // load the registered startup script, if one has been registered - // (mail3:pane may not have been fully loaded yet) - if (self.pathToStartupScript) { - let startupJS = {}; - startupJS.WL = {}; - startupJS.WL.extension = self.extension; - startupJS.WL.messenger = self.getMessenger(self.context); - try { - if (self.pathToStartupScript) { - Services.scriptloader.loadSubScript( - self.pathToStartupScript, - startupJS, - "UTF-8" - ); - // delay startup until startup has been finished - self.log( - "Waiting for async startup() in <" + - self.pathToStartupScript + - "> to finish." - ); - if (startupJS.startup) { - await startupJS.startup(); - self.log( - "startup() in <" + self.pathToStartupScript + "> finished" - ); - } else { - self.log( - "No startup() in <" + self.pathToStartupScript + "> found." - ); - } - } - } catch (e) { - Components.utils.reportError(e); - } - } - - let urls = Object.keys(self.registeredWindows); - if (urls.length > 0) { - // Before registering the window listener, check which windows are already open - self.openWindows = []; - for (let window of Services.wm.getEnumerator(null)) { - self.openWindows.push(window); - } - - // Register window listener for all pre-registered windows - ExtensionSupport.registerWindowListener( - "injectListener_" + self.uniqueRandomID, - { - // React on all windows and manually reduce to the registered - // windows, so we can do special actions when the main - // messenger window is opened. - //chromeURLs: Object.keys(self.registeredWindows), - async onLoadWindow(window) { - // Load JS into window - await self._loadIntoWindow( - window, - self.openWindows.includes(window) - ); - }, - - onUnloadWindow(window) { - // Remove JS from window, window is being closed, addon is not shut down - self._unloadFromWindow(window, false); - }, - } - ); - } else { - self.error("Failed to start listening, no windows registered"); - } - }, - }, - }; - } - - _loadIntoNestedBrowsers(window, isAddonActivation) { - let elements = []; - elements = elements.concat(...window.document.getElementsByTagName("browser")); - elements = elements.concat(...window.document.getElementsByTagName("xul:browser")); - for (let element of elements) { - this._loadIntoWindow(element.contentWindow, isAddonActivation); - } - - } - - async _loadIntoWindow(window, isAddonActivation) { - const fullyLoaded = async window => { - for (let i = 0; i < 20; i++) { - await this.sleep(50); - if ( - window && - window.location.href != "about:blank" && - window.document.readyState == "complete" - ) { - return; - } - } - throw new Error("Window ignored"); - } - - try { - await fullyLoaded(window); - } catch(ex) { - return; - } - - if (!window || window.hasOwnProperty(this.uniqueRandomID)) { - return; - } - - // Special action if this is the main messenger window. - if (window.location.href == "chrome://messenger/content/messenger.xhtml") { - // Add a tab monitor. The tabMonitor checks newly opened tabs and injects us. - this.getTabMail(window).registerTabMonitor(this.tabMonitor); - window[this.uniqueRandomID] = {}; - window[this.uniqueRandomID].hasTabMonitor = true; - - // Setup the options button/menu in the add-on manager, if it is already open. - this.setupAddonManager(this.getAddonManagerFromWindow(window), true); - } - - // Load into nested browsers - this._loadIntoNestedBrowsers(window, isAddonActivation); - - if (this.registeredWindows.hasOwnProperty(window.location.href)) { - if (!window.hasOwnProperty(this.uniqueRandomID)) { - window[this.uniqueRandomID] = {}; - } - - try { - let uniqueRandomID = this.uniqueRandomID; - let extension = this.extension; - - // Add reference to window to add-on scope - window[this.uniqueRandomID].window = window; - window[this.uniqueRandomID].document = window.document; - - // Keep track of toolbarpalettes we are injecting into - window[this.uniqueRandomID]._toolbarpalettes = {}; - - //Create WLDATA object - window[this.uniqueRandomID].WL = {}; - window[this.uniqueRandomID].WL.scopeName = this.uniqueRandomID; - - // Add helper function to inject CSS to WLDATA object - window[this.uniqueRandomID].WL.injectCSS = function (cssFile) { - let element; - let v = parseInt(Services.appinfo.version.split(".").shift()); - - // using createElementNS in TB78 delays the insert process and hides any security violation errors - if (v > 68) { - element = window.document.createElement("link"); - } else { - let ns = window.document.documentElement.lookupNamespaceURI("html"); - element = window.document.createElementNS(ns, "link"); - } - - element.setAttribute("wlapi_autoinjected", uniqueRandomID); - element.setAttribute("rel", "stylesheet"); - element.setAttribute("href", cssFile); - return window.document.documentElement.appendChild(element); - }; - - // Add helper function to inject XUL to WLDATA object - window[this.uniqueRandomID].WL.injectElements = function ( - xulString, - dtdFiles = [], - debug = false - ) { - let toolbarsToResolve = []; - - function checkElements(stringOfIDs) { - let arrayOfIDs = stringOfIDs.split(",").map((e) => e.trim()); - for (let id of arrayOfIDs) { - let element = window.document.getElementById(id); - if (element) { - return element; - } - } - return null; - } - - function localize(entity) { - let msg = entity.slice("__MSG_".length, -2); - return extension.localeData.localizeMessage(msg); - } - - function injectChildren(elements, container) { - if (debug) console.log(elements); - - for (let i = 0; i < elements.length; i++) { - // take care of persists - const uri = window.document.documentURI; - for (const persistentNode of elements[i].querySelectorAll( - "[persist]" - )) { - for (const persistentAttribute of persistentNode - .getAttribute("persist") - .trim() - .split(" ")) { - if ( - Services.xulStore.hasValue( - uri, - persistentNode.id, - persistentAttribute - ) - ) { - persistentNode.setAttribute( - persistentAttribute, - Services.xulStore.getValue( - uri, - persistentNode.id, - persistentAttribute - ) - ); - } - } - } - - if ( - elements[i].hasAttribute("insertafter") && - checkElements(elements[i].getAttribute("insertafter")) - ) { - let insertAfterElement = checkElements( - elements[i].getAttribute("insertafter") - ); - - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - ": insertafter " + - insertAfterElement.id - ); - if ( - debug && - elements[i].id && - window.document.getElementById(elements[i].id) - ) { - console.error( - "The id <" + - elements[i].id + - "> of the injected element already exists in the document!" - ); - } - elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID); - insertAfterElement.parentNode.insertBefore( - elements[i], - insertAfterElement.nextSibling - ); - } else if ( - elements[i].hasAttribute("insertbefore") && - checkElements(elements[i].getAttribute("insertbefore")) - ) { - let insertBeforeElement = checkElements( - elements[i].getAttribute("insertbefore") - ); - - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - ": insertbefore " + - insertBeforeElement.id - ); - if ( - debug && - elements[i].id && - window.document.getElementById(elements[i].id) - ) { - console.error( - "The id <" + - elements[i].id + - "> of the injected element already exists in the document!" - ); - } - elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID); - insertBeforeElement.parentNode.insertBefore( - elements[i], - insertBeforeElement - ); - } else if ( - elements[i].id && - window.document.getElementById(elements[i].id) - ) { - // existing container match, dive into recursivly - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - " is an existing container, injecting into " + - elements[i].id - ); - injectChildren( - Array.from(elements[i].children), - window.document.getElementById(elements[i].id) - ); - } else if (elements[i].localName === "toolbarpalette") { - // These vanish from the document but still exist via the palette property - if (debug) console.log(elements[i].id + " is a toolbarpalette"); - let boxes = [ - ...window.document.getElementsByTagName("toolbox"), - ]; - let box = boxes.find( - (box) => box.palette && box.palette.id === elements[i].id - ); - let palette = box ? box.palette : null; - - if (!palette) { - if (debug) - console.log( - `The palette for ${elements[i].id} could not be found, deferring to later` - ); - continue; - } - - if (debug) - console.log(`The toolbox for ${elements[i].id} is ${box.id}`); - - toolbarsToResolve.push(...box.querySelectorAll("toolbar")); - toolbarsToResolve.push( - ...window.document.querySelectorAll( - `toolbar[toolboxid="${box.id}"]` - ) - ); - for (let child of elements[i].children) { - child.setAttribute("wlapi_autoinjected", uniqueRandomID); - } - window[uniqueRandomID]._toolbarpalettes[palette.id] = palette; - injectChildren(Array.from(elements[i].children), palette); - } else { - // append element to the current container - if (debug) - console.log( - elements[i].tagName + - "#" + - elements[i].id + - ": append to " + - container.id - ); - elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID); - container.appendChild(elements[i]); - } - } - } - - if (debug) console.log("Injecting into root document:"); - let localizedXulString = xulString.replace( - /__MSG_(.*?)__/g, - localize - ); - injectChildren( - Array.from( - window.MozXULElement.parseXULToFragment( - localizedXulString, - dtdFiles - ).children - ), - window.document.documentElement - ); - - for (let bar of toolbarsToResolve) { - let currentset = Services.xulStore.getValue( - window.location, - bar.id, - "currentset" - ); - if (currentset) { - bar.currentSet = currentset; - } else if (bar.getAttribute("defaultset")) { - bar.currentSet = bar.getAttribute("defaultset"); - } - } - }; - - // Add extension object to WLDATA object - window[this.uniqueRandomID].WL.extension = this.extension; - // Add messenger object to WLDATA object - window[this.uniqueRandomID].WL.messenger = this.getMessenger( - this.context - ); - // Load script into add-on scope - Services.scriptloader.loadSubScript( - this.registeredWindows[window.location.href], - window[this.uniqueRandomID], - "UTF-8" - ); - window[this.uniqueRandomID].onLoad(isAddonActivation); - } catch (e) { - Components.utils.reportError(e); - } - } - } - - _unloadFromWindow(window, isAddonDeactivation) { - // Unload any contained browser elements. - let elements = []; - elements = elements.concat(...window.document.getElementsByTagName("browser")); - elements = elements.concat(...window.document.getElementsByTagName("xul:browser")); - for (let element of elements) { - if (element.contentWindow) { - this._unloadFromWindow( - element.contentWindow, - isAddonDeactivation - ); - } - } - - if ( - window.hasOwnProperty(this.uniqueRandomID) && - this.registeredWindows.hasOwnProperty(window.location.href) - ) { - // Remove this window from the list of open windows - this.openWindows = this.openWindows.filter((e) => e != window); - - if (window[this.uniqueRandomID].onUnload) { - try { - // Call onUnload() - window[this.uniqueRandomID].onUnload(isAddonDeactivation); - } catch (e) { - Components.utils.reportError(e); - } - } - - // Remove all auto injected objects - let elements = Array.from( - window.document.querySelectorAll( - '[wlapi_autoinjected="' + this.uniqueRandomID + '"]' - ) - ); - for (let element of elements) { - element.remove(); - } - - // Remove all autoinjected toolbarpalette items - for (const palette of Object.values( - window[this.uniqueRandomID]._toolbarpalettes - )) { - let elements = Array.from( - palette.querySelectorAll( - '[wlapi_autoinjected="' + this.uniqueRandomID + '"]' - ) - ); - for (let element of elements) { - element.remove(); - } - } - } - - // Remove add-on scope, if it exists - if (window.hasOwnProperty(this.uniqueRandomID)) { - delete window[this.uniqueRandomID]; - } - } - - onShutdown(isAppShutdown) { - if (isAppShutdown) { - return; // the application gets unloaded anyway - } - - // Unload from all still open windows - let urls = Object.keys(this.registeredWindows); - if (urls.length > 0) { - for (let window of Services.wm.getEnumerator(null)) { - //remove our entry in the add-on options menu - if (window.location.href == "chrome://messenger/content/messenger.xhtml") { - // Remove event listener for addon manager view changes - let managerWindow = this.getAddonManagerFromWindow(window); - if ( - managerWindow && - managerWindow[this.uniqueRandomID] && - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners - ) { - managerWindow.document.removeEventListener("ViewChanged", this); - managerWindow.document.removeEventListener("view-loaded", this); - managerWindow.document.removeEventListener("update", this); - managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false; - - let buttons = managerWindow.document.getElementsByClassName("extension-options-button"); - for (let button of buttons) { - button.removeAttribute("hidden"); - } - let cards = this.getCards(managerWindow); - // Remove options button in 88+ - for (let card of cards) { - if (card.addon.id == this.extension.id) { - let origAddonOptionsButton = card.querySelector(".extension-options-button") - origAddonOptionsButton.removeAttribute("hidden"); - - let addonOptionsButton = card.querySelector( - ".windowlistener-options-button" - ); - if (addonOptionsButton) addonOptionsButton.remove(); - break; - } - } - } - - // Remove tabmonitor - if (window[this.uniqueRandomID].hasTabMonitor) { - this.getTabMail(window).unregisterTabMonitor(this.tabMonitor); - window[this.uniqueRandomID].hasTabMonitor = false; - } - } - - // if it is app shutdown, it is not just an add-on deactivation - this._unloadFromWindow(window, !isAppShutdown); - } - // Stop listening for new windows. - ExtensionSupport.unregisterWindowListener( - "injectListener_" + this.uniqueRandomID - ); - } - - // Load registered shutdown script - let shutdownJS = {}; - shutdownJS.extension = this.extension; - try { - if (this.pathToShutdownScript) - Services.scriptloader.loadSubScript( - this.pathToShutdownScript, - shutdownJS, - "UTF-8" - ); - } catch (e) { - Components.utils.reportError(e); - } - - // Extract all registered chrome content urls - let chromeUrls = []; - if (this.chromeData) { - for (let chromeEntry of this.chromeData) { - if (chromeEntry[0].toLowerCase().trim() == "content") { - chromeUrls.push("chrome://" + chromeEntry[1] + "/"); - } - } - } - - // Unload JSMs of this add-on - const rootURI = this.extension.rootURI.spec; - for (let module of Cu.loadedModules) { - if ( - module.startsWith(rootURI) || - (module.startsWith("chrome://") && - chromeUrls.find((s) => module.startsWith(s))) - ) { - this.log("Unloading: " + module); - Cu.unload(module); - } - } - - // Flush all caches - Services.obs.notifyObservers(null, "startupcache-invalidate"); - this.registeredWindows = {}; - - if (this.resourceData) { - const resProto = Cc[ - "@mozilla.org/network/protocol;1?name=resource" - ].getService(Ci.nsISubstitutingProtocolHandler); - for (let res of this.resourceData) { - // [ "resource", "shortname" , "path" ] - resProto.setSubstitution(res[1], null); - } - } - - if (this.chromeHandle) { - this.chromeHandle.destruct(); - this.chromeHandle = null; - } - } -}; - -var WindowListener = getThunderbirdVersion().major < 111 - ? WindowListener_102 - : WindowListener_115; diff --git a/api/WindowListener/schema.json b/api/WindowListener/schema.json deleted file mode 100644 index 5d3bbb2..0000000 --- a/api/WindowListener/schema.json +++ /dev/null @@ -1,108 +0,0 @@ -[ - { - "namespace": "WindowListener", - "functions": [ - { - "name": "registerDefaultPrefs", - "type": "function", - "parameters": [ - { - "name": "aPath", - "type": "string", - "description": "Relative path to the default file." - } - ] - }, - { - "name": "registerOptionsPage", - "type": "function", - "parameters": [ - { - "name": "aPath", - "type": "string", - "description": "Path to the options page, which should be made accessible in the (legacy) Add-On Options menu." - } - ] - }, - { - "name": "registerChromeUrl", - "type": "function", - "description": "Register folders which should be available as chrome:// urls (as defined in the legacy chrome.manifest)", - "parameters": [ - { - "name": "data", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "string" - } - }, - "description": "Array of manifest url definitions (content, locale, resource)" - } - ] - }, - { - "name": "waitForMasterPassword", - "type": "function", - "async": true, - "parameters": [] - }, - { - "name": "openOptionsDialog", - "type": "function", - "parameters": [ - { - "name": "windowId", - "type": "integer", - "description": "Id of the window the dialog should be opened from." - } - ] - }, - { - "name": "startListening", - "type": "function", - "async": true, - "parameters": [] - }, - { - "name": "registerWindow", - "type": "function", - "parameters": [ - { - "name": "windowHref", - "type": "string", - "description": "Url of the window, which should be listen for." - }, - { - "name": "jsFile", - "type": "string", - "description": "Path to the JavaScript file, which should be loaded into the window." - } - ] - }, - { - "name": "registerStartupScript", - "type": "function", - "parameters": [ - { - "name": "aPath", - "type": "string", - "description": "Path to a JavaScript file, which should be executed on add-on startup. The script will be executed after the main application window has been sucessfully loaded." - } - ] - }, - { - "name": "registerShutdownScript", - "type": "function", - "parameters": [ - { - "name": "aPath", - "type": "string", - "description": "Path to a JavaScript file, which should be executed on add-on shutdown." - } - ] - } - ] - } -] \ No newline at end of file diff --git a/background.js b/background.js index a828a58..9c9f77f 100644 --- a/background.js +++ b/background.js @@ -1,36 +1,59 @@ -browser.WindowListener.registerDefaultPrefs("defaults/preferences/prefs.js"); +const PREF_PREFIX = "extensions.tbsortfolders@xulforum.org."; +// They were all defined as strings. +const PREF_DEFAULTS = { + "tbsf_data": "{}", + "startup_folder": "", + "defaultaccount": "", + "width": "0", + "height": "0", + "hide_folder_icons": "", +} -browser.WindowListener.registerChromeUrl([ - ["content", "tbsortfolders", "content/"], - ["resource", "tbsortfolders", "modules/"], - ["locale", "tbsortfolders", "da", "locale/da/"], - ["locale", "tbsortfolders", "de", "locale/de/"], - ["locale", "tbsortfolders", "en-US", "locale/en-US/"], - ["locale", "tbsortfolders", "es-ES", "locale/es-ES/"], - ["locale", "tbsortfolders", "fr", "locale/fr/"], - ["locale", "tbsortfolders", "it", "locale/it/"], - ["locale", "tbsortfolders", "ja", "locale/ja/"], - ["locale", "tbsortfolders", "nl", "locale/nl/"], - ["locale", "tbsortfolders", "nb-NO", "locale/nb-NO/"], - ["locale", "tbsortfolders", "pl", "locale/pl/"], - ["locale", "tbsortfolders", "pt", "locale/pt/"], - ["locale", "tbsortfolders", "pt-BR", "locale/pt-BR/"], - ["locale", "tbsortfolders", "ru-RU", "locale/ru-RU/"], - ["locale", "tbsortfolders", "sk", "locale/sk/"], - ["locale", "tbsortfolders", "sr", "locale/sr/"], - ["locale", "tbsortfolders", "sv-SE", "locale/sv-SE/"], - ["locale", "tbsortfolders", "zh-CN", "locale/zh-CN/"], - ["locale", "tbsortfolders", "zh-TW", "locale/zh-TW/"], -]); +browser.runtime.onStartup.addListener(async () => { + let preferences = await browser.storage.local.get({ + startupFolder: "" + }) + if (preferences.startupFolder) { + // Extract accountId and path. + let data = preferences.startupFolder.split(":"); + let accountId = data.shift(); + let path = data.join(":"); -// For Thunderbird 78.0 and later -browser.WindowListener.registerWindow( - "chrome://messenger/content/messenger.xhtml", - "chrome://tbsortfolders/content/scripts/messenger.js"); + let mailTabs = await browser.mailTabs.query({}); + for (let mailTab of mailTabs) { + browser.mailTabs.update(mailTab.id, {displayedFolder: {accountId, path}}); + } + } +}) -// For Thunderbird 68 -browser.WindowListener.registerWindow( - "chrome://messenger/content/messenger.xul", - "chrome://tbsortfolders/content/scripts/messenger.js"); +async function init() { + // TODO: Migrate old LegacyPrefs to local storage. + let prefs = {}; + for (let [name, value] of Object.entries(PREF_DEFAULTS)) { + await browser.LegacyPrefs.setDefaultPref(`${PREF_PREFIX}${name}`, value); + prefs[name] = await browser.LegacyPrefs.getPref(`${PREF_PREFIX}${name}`); + } + + browser.tabs.onCreated.addListener(async tab => { + let preferences = await browser.storage.local.get({ + accountSettings: new Map(), + folderSort: new Map() + }) + browser.CustomFolderSort.patch(tab.id, preferences); + }); + + // Handle all already open tabs. + async function handlerAlreadyOpenTabs() { + let tabs = (await browser.tabs.query({})).filter(t => ["mail"].includes(t.type)); + let preferences = await browser.storage.local.get({ + accountSettings: new Map(), + folderSort: new Map() + }) + for (let tab of tabs) { + browser.CustomFolderSort.patch(tab.id, preferences); + } + } + handlerAlreadyOpenTabs(); +} +init(); -browser.WindowListener.startListening(); diff --git a/content/folderPane.js b/content/folderPane.js deleted file mode 100644 index 1105835..0000000 --- a/content/folderPane.js +++ /dev/null @@ -1,275 +0,0 @@ - -(async function () { - /* For folder sorting */ - - const Cc = Components.classes; - const Ci = Components.interfaces; - const Cu = Components.utils; - - Cu.import("resource://gre/modules/Log.jsm"); - let tblog = Log.repository.getLogger("tbsortfolders.folderPane"); - tblog.level = Log.Level.Debug; - tblog.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); - tblog.addAppender(new Log.DumpAppender(new Log.BasicFormatter())); - - var Services = globalThis.Services || ChromeUtils.import( - "resource://gre/modules/Services.jsm" - ).Services; - - Cu.import("resource://tbsortfolders/sort.jsm"); - Cu.import("resource:///modules/MailUtils.jsm"); - - const ThunderbirdMajorVersion = Services.appinfo.version.split(".")[0]; - - tblog.debug("Init"); - - const tbsf_prefs = Services.prefs.getBranch("extensions.tbsortfolders@xulforum.org."); - /* This array is populated either when the file is loaded or when the - * preferences are updated. The keys are the account's pretty names and the - * values are the sort functions associated to each account. */ - var tbsf_prefs_functions; - const mail_accountmanager_prefs = Services.prefs.getBranch("mail.accountmanager."); - { - let accounts = mail_accountmanager_prefs.getStringPref("accounts").split(","); - tblog.debug("Accounts: "+accounts); - } - -// var tb_accounts = mail_accountmanager_prefs.getStringPref("accounts"); -// var tb_default_account = mail_accountmanager_prefs.getStringPref("defaultaccount"); -// tblog.debug("TB Accounts: "+tb_accounts); -// tblog.debug("TB Default account: "+tb_default_account); - -// let tbsf_accounts = null; -// let tbsf_default_account = null; -// try { -// tbsf_accounts = tbsf_prefs.getStringPref("accounts"); -// tbsf_default_account = tbsf_prefs.getStringPref("defaultaccount"); -// tblog.debug("TBSF Accounts: "+tbsf_accounts); -// tblog.debug("TBSF Default account: "+tbsf_default_account); -// } catch (x) { -// } - - tblog.debug("Add observer"); - - let mainWindow = Services.wm.getMostRecentWindow("mail:3pane"); - let config = {attributes:true,attributeFilter:["maxpos"],childList:true,subtree:true}; - var callback_foldertree = function (mutationList, observer) { - tblog.debug("Observer activated"); - -// for (let mutation of mutationList) { -// if (mutation.type == 'childList') { -// tblog.debug("Childnode added"); -// } else if (mutation.type == 'attributes') { -// tblog.debug("The "+mutation.attributeName+" attribute changed"); -// } -// } - - let current_default_account = mail_accountmanager_prefs.getStringPref("defaultaccount"); - let tbsf_default_account = tbsf_prefs.getStringPref("defaultaccount"); -// tblog.debug("Current Default account: "+current_default_account); -// tblog.debug("Stored Default account: "+tbsf_default_account); - - if (tbsf_default_account && current_default_account !== tbsf_default_account) - mail_accountmanager_prefs.setStringPref("defaultaccount", tbsf_default_account); - -/* - let current_tb_accounts = mail_accountmanager_prefs.getStringPref("accounts"); - let tbsf_accounts = null; - try { - tbsf_accounts = tbsf_prefs.getStringPref("accounts"); - } catch (x) { - } - tblog.debug("Current accounts: "+current_tb_accounts); - tblog.debug("Stored accounts: "+tbsf_accounts); -*/ - }; - var observer_foldertree = new MutationObserver(callback_foldertree); - observer_foldertree.observe(mainWindow.document.getElementById('folderTree'),config); - -// observer_foldertree.disconnect(); - - // Override function sortFolderItems(aFtvItems) of TB (in comm/mail/base/content/folderPane.js) - sortFolderItems = function (aFtvItems) { - if (!aFtvItems.length) - return; - - //A sort function is associated to every account, so we get the account's name - let parent = aFtvItems[0]._folder.parent; - //In case we're asked to sort sub-folders, we walk up the tree up to the root - //item which is the "fake folder" representing the account. - while (parent.parent) parent = parent.parent; - let parentName = parent.prettyName; - - let sort_function; - if (tbsf_prefs_functions[parentName]) { - //If we have a value for this account then tbsf_prefs_functions contains the - //right sort function - sort_function = tbsf_prefs_functions[parentName]; - } else { - //If we don't: use Tb's default - sort_function = tbsf_sort_functions[0]; - } - aFtvItems.sort(sort_function); - } - - function update_prefs_functions() { - let tbsf_data = {}; - try { - tbsf_data = JSON.parse(tbsf_prefs.getStringPref("tbsf_data")); - } catch (e) { - } - tbsf_prefs_functions = Object(); - for (let vkey in tbsf_data) { - let key = vkey; - /* key[0] = 0 if the user asked for Tb's default sort function, 1 for - alphabetical, 2 for custom sort - key[1] = the data to pass to tbsf_sort_functions[2] if key[0] == 2 - */ - if (tbsf_data[key][0] == 2) { - //feed the manual sort function with the associated sort data - tbsf_prefs_functions[key] = (a,b) => tbsf_sort_functions[2](tbsf_data[key][1], a, b); - } else { - //other functions don't need specific data - tbsf_prefs_functions[key] = tbsf_sort_functions[tbsf_data[key][0]]; - } - } - } - - update_prefs_functions(); - - let myPrefObserver = { - register: function mpo_register () { - tbsf_prefs.QueryInterface(Components.interfaces.nsIPrefBranch); - tbsf_prefs.addObserver("", this, false); - }, - - unregister: function mpo_unregister () { - if (!tbsf_prefs) return; - tbsf_prefs.removeObserver("", this); - }, - - observe: function mpo_observe (aSubject, aTopic, aData) { - if (aTopic != "nsPref:changed") - return; - switch (aData) { - case "tbsf_data": - update_prefs_functions(); - break; - } - } - }; - myPrefObserver.register(); - - /* Startup folder */ - let startup_folder = tbsf_prefs.getStringPref("startup_folder"); - if (startup_folder) { - tblog.debug("startup folder: "+startup_folder); - } else { - tblog.debug("No startup folder specified"); - } - - if (startup_folder && ThunderbirdMajorVersion < 98) { - /* - On Thunderbird 97 and earlier, it was possible for add-ons to intervene in - the startup behavior of Thunderbird. - */ - const oldRestoreTab = mailTabType.modes.folder.restoreTab; - let inRestoreTab = false; - mailTabType.modes.folder.restoreTab = function (x, y) { - tblog.debug("restoreTab"); - inRestoreTab = true; - oldRestoreTab.call(this, x, y); - inRestoreTab = false; - }; - const oldSelectFolder = gFolderTreeView.selectFolder; - const change_folder = startup_folder; - let firstRun = true; - gFolderTreeView.selectFolder = function (x, y) { - tblog.debug("selectFolder firstRun:"+firstRun.toString()+" inRestoreTab:"+inRestoreTab.toString()); - if (firstRun && inRestoreTab) { - const folder = MailUtils.getExistingFolder(change_folder); - if (folder) { - oldSelectFolder.call(this, folder, true); - /* Ensures that the selected folder is on the screen. */ - const selected = gFolderTreeView.getSelectedFolders()[0]; - if (selected) { - gFolderTreeView._treeElement.ensureRowIsVisible(gFolderTreeView.getIndexOfFolder(selected)); - } - } else { - oldSelectFolder.call(this, x, y); - } - firstRun = false; - } else { - oldSelectFolder.call(this, x, y); - } - } - tblog.debug("Overriding selectFolder"); - startup_folder = null; - } - - /* - Refresh pane and select folder - - The start of this add-on may be too early to call gFolderTreeView._rebuild() - and MailUtils.getExistingFolder(). - - So I structured a 10-times retry loop to counter any exceptions or failures - that may occur due to that. - */ - async function selectFolder(win, startup_folder) { - let tries = 0; - let ms = 100; - - // Stop after 10 tries. Or use other break condition, like total time spend. - while (tries < 10) { - try { - /* - Refresh pane -- possible exception. - */ - win.gFolderTreeView._rebuild(); - if (startup_folder) { - /* - Select folder - Since Thunderbird 98, add-on startup has been delayed until - Thunderbird is mostly done. So there is no way other than - immediately selecting the folder. However, there is a report of a - case where getExistingFolder returns null for the existing folder. - */ - let folder = MailUtils.getExistingFolder(startup_folder); - if (folder && gFolderTreeView.selectFolder(folder, true)) { - return true; - } - } else { - return true; - } - } catch (e) { - // Nothing. - } - tries++; - await new Promise(resolve => setTimeout(resolve, ms)); - } - return false; - } - - let selectPromises = []; - for (let win of Services.wm.getEnumerator("mail:3pane")) { - selectPromises.push(selectFolder(win, startup_folder)); - } - - let results = await Promise.all(selectPromises); - tblog.debug("Refreshing the pane" - + (startup_folder ? " and selecting folder" : "") - + ": " - + (results ? "Success" : "Failure")); - - /* Ensures that the selected folder is on the screen. */ - { - const selected = gFolderTreeView.getSelectedFolders()[0]; - if (selected) { - gFolderTreeView._treeElement.ensureRowIsVisible(gFolderTreeView.getIndexOfFolder(selected)); - } - } - - tblog.debug("Init done"); - -})() diff --git a/content/scripts/messenger.js b/content/scripts/messenger.js deleted file mode 100644 index c8eca84..0000000 --- a/content/scripts/messenger.js +++ /dev/null @@ -1,46 +0,0 @@ -// Import any needed modules. -var Services = globalThis.Services || ChromeUtils.import( - "resource://gre/modules/Services.jsm" -).Services; -const g_ThunderbirdMajorVersion = Services.appinfo.version.split(".")[0]; - -// Load an additional JavaScript file. -Services.scriptloader.loadSubScript("chrome://tbsortfolders/content/folderPane.js", window, "UTF-8"); - -function onLoad(activatedWhileWindowOpen) { - const tbsf_prefs = Services.prefs.getBranch("extensions.tbsortfolders@xulforum.org."); - let xulname = 'tbsortfolders'; - if (g_ThunderbirdMajorVersion >= 91) { - xulname += '_91'; - } - let additionalElements = ` - - - - - - - `; - if (tbsf_prefs.getStringPref("hide_folder_icons")) { - additionalElements += ` - - - #folderTree > treechildren::-moz-tree-image { - list-style-image: none; - width: 0; - height: 0; - } - - `; - } - WL.injectElements(additionalElements, ["chrome://tbsortfolders/locale/main.dtd"]); -} - -function onUnload(deactivatedWhileWindowOpen) { -} diff --git a/content/tbsortfolders.css b/content/tbsortfolders.css deleted file mode 100644 index 5ea014e..0000000 --- a/content/tbsortfolders.css +++ /dev/null @@ -1,13 +0,0 @@ -groupbox { - margin: 5px; - border: 2px solid var(--color-gray-20); - padding: 0 0 5px 0; -} -groupbox caption { - background: var(--color-gray-20); - padding: 0.3em; - margin: 0 0 5px 0; -} -treechildren::-moz-tree-image { - -moz-context-properties: fill, fill-opacity, stroke; -} diff --git a/content/tbsortfolders.xhtml b/content/tbsortfolders.xhtml deleted file mode 100644 index 0c42543..0000000 --- a/content/tbsortfolders.xhtml +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - + + + + +

__MSG_general.title__

+ + + + +
+
    + + + +
+ +
+ + + +
+
__MSG_sortfolders.description__
+
+ + +
+
+
    +
+
+
+ __MSG_gb.extra__ +
__MSG_sortmethod.default.description__
+
+
+ __MSG_gb.extra__ +
__MSG_sortmethod.alphabetical.description__
+
+
+
+ __MSG_gb.movefolder__ +
+ +
+ +
+
+
+ __MSG_gb.sort_siblings_by__ +
+ +
+
+ + +
+
+
+
+ __MSG_gb.extra__ +
__MSG_sortmethod.custom.description__
+
+
+
+
+
+ + + +
+
+
+ + +
+ + + diff --git a/options/options.js b/options/options.js new file mode 100644 index 0000000..b484313 --- /dev/null +++ b/options/options.js @@ -0,0 +1,331 @@ +var preferences; + +function getFolderId(folder) { + let data = `${folder.accountId}:${folder.path}`; + return data; + + let arr = new TextEncoder().encode(data); + let str = ""; + for (let i = 0; i < arr.length; i += 65536) { + str += String.fromCharCode.apply(null, arr.subarray(i, i + 65536)); + } + return btoa(str); +} + + +/** + * M-C has still not enabled "svg.context-properties.content.enabled" by default. + * With that enabled, WebExtensions could use the icons as follows: + * + * #foldersTree { + * list-style-image: var(--folder-pane-draft); + * fill: color-mix(in srgb, var(--color-purple-60) 20%, transparent); + * fill-opacity: color-mix(in srgb, var(--color-purple-60) 20%, transparent); + * stroke: var(--color-purple-60); + * -moz-context-properties:fill-opacity, fill, stroke; + * } + * + * As a workaround, we load the SVG as an OBJECT and manipulate the attributes. + * An alternative would be to modify the SVG. + */ +function createFolderEntry(folder) { + let type = folder.type; + if (!type) type = "folder" + else if (type == "drafts") type = "draft"; + + function clickHandler(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + + let tree = document.getElementById("foldersTree"); + let selectEntry = tree.querySelector('[aria-selected="true"]'); + if (selectEntry) { + selectEntry.setAttribute('aria-selected', false); + } + e.target.closest("li").setAttribute('aria-selected', true); + } + + let color = getComputedStyle(document.documentElement).getPropertyValue(`--folder-color-${type}`); + let icon = `/images/folder-${type}.svg`; + + let li = document.createElement("li"); + let hbox = document.createElement("div"); + hbox.classList.add("hbox"); + + let object = document.createElement("object"); + object.addEventListener("load", (e) => { + let svg = e.target.getSVGDocument(); + let paths = svg.querySelectorAll("path"); + paths[0].setAttribute("fill", `color-mix(in srgb, ${color} 20%, transparent)`); //fill + paths[1].setAttribute("fill", color); //stroke + }) + object.addEventListener("click", clickHandler) + object.setAttribute("type", "image/svg+xml"); + object.setAttribute("data", icon); + hbox.appendChild(object); + + let div = document.createElement("div"); + div.textContent = folder.name; + hbox.appendChild(div); + hbox.addEventListener("click", clickHandler) + + li.appendChild(hbox); + + li.dataset.folderName = folder.name; + li.dataset.sortKey = preferences.folderSort.get(getFolderId(folder)); + li.dataset.path = folder.path; + li.dataset.accountId = folder.accountId; + + return li; +} +// (Re-)load the sorted folders into the foldersTree element, according to the +// selected folder and sort type. +async function loadSortedFolderEntries() { + let tree = document.getElementById("foldersTree"); + let accountId = document.getElementById("folder_accounts_list").value; + let account = await browser.accounts.get(accountId, true); + let type = document.getElementById("folder_sort_method").value; + let folders = []; + + // First, add folders with a sortKey already. + let largestIdx = 0; + for (let folder of account.folders) { + let folderId = getFolderId(folder); + if (preferences.folderSort.has(folderId)) { + largestIdx = Math.max(largestIdx, preferences.folderSort.get(folderId)); + folders.push(createFolderEntry(folder)); + } + } + // Second, add folders without a sortKey and define one. + for (let folder of account.folders) { + let folderId = getFolderId(folder); + if (!preferences.folderSort.has(folderId)) { + preferences.folderSort.set(folderId, ++largestIdx); + folders.push(createFolderEntry(folder)); + } + } + + // Clear current list. + while (tree.firstChild) { + tree.removeChild(tree.lastChild); + } + + // Hide/Show information based on current sort type. + let sortMap = { + "default_sort_box": ["0"], + "alphabetical_sort_box": ["1", "3"], + "manual_sort_box": ["2"] + }; + for (let [id, values] of Object.entries(sortMap)) { + document.getElementById(id).style.display = values.includes(type) ? "block" : "none"; + } + + // Sort. + switch (type) { + case "1": // asc + folders.sort((a, b) => b.dataset.folderName < a.dataset.folderName) + break; + case "3": // desc + folders.sort((a, b) => b.dataset.folderName > a.dataset.folderName) + break; + case "2": // custom + folders.sort((a, b) => parseInt(b.dataset.sortKey) < parseInt(a.dataset.sortKey)) + break; + } + + // Add sorted entries to DOM. + for (let elem of folders) { + tree.appendChild(elem); + } + +} + +function tablistClickHandler(elem) { + let target = elem.target; + + if (target.parentNode.id != 'tablist') return false; + + let selectedTab = document.querySelector('[aria-selected="true"]'); + selectedTab.setAttribute('aria-selected', false); + target.setAttribute('aria-selected', true); + + let panels = document.querySelector('[aria-hidden="false"]'); + panels.setAttribute('aria-hidden', true); + + let panelId = target.getAttribute('aria-controls'), + panel = document.getElementById(panelId); + panel.setAttribute('aria-hidden', false); +} + +function on_extra_hide_folder_icons_changed(e) { + preferences.hideFolderIcon = document.getElementById("extra_hide_folder_icons").checked; +} +function on_extra_pick_startup_folder(e) { + preferences.startupFolder = document.getElementById("extra_startup_folder").value; +} + +function on_folder_account_changed() { + let accountId = document.getElementById("folder_accounts_list").value; + + if (!preferences.accountSettings.has(accountId)) { + preferences.accountSettings.set(accountId, { + type: "0", + }) + }; + + document.getElementById("folder_sort_method").value = preferences.accountSettings.get(accountId).type; + loadSortedFolderEntries(); +} +function on_folder_sort_method_changed() { + let accountId = document.getElementById("folder_accounts_list").value; + preferences.accountSettings.get(accountId).type = document.getElementById("folder_sort_method").value; + loadSortedFolderEntries(); +} +// Move the actual DOM elements, update sort state. +function on_folder_move_up(e) { + let tree = document.getElementById("foldersTree"); + let selectEntry = tree.querySelector('[aria-selected="true"]'); + if (selectEntry.previousSibling) { + let key1 = selectEntry.dataset.sortKey; + let key2 = selectEntry.previousSibling.dataset.sortKey; + let path1 = selectEntry.dataset.path + let path2 = selectEntry.previousSibling.dataset.path; + + // Update DOM elements. + selectEntry.dataset.sortKey = key2; + selectEntry.previousSibling.dataset.sortKey = key1; + tree.insertBefore(selectEntry, selectEntry.previousSibling); + + // Update sort state. + let accountId = selectEntry.dataset.accountId; + preferences.folderSort.set(getFolderId({ accountId, path: path1 }), parseInt(key2)); + preferences.folderSort.set(getFolderId({ accountId, path: path2 }), parseInt(key1)); + } +} +// Move the actual DOM elements, update sort state. +function on_folder_move_down(e) { + let tree = document.getElementById("foldersTree"); + let selectEntry = tree.querySelector('[aria-selected="true"]'); + if (selectEntry.nextSibling) { + let key1 = selectEntry.dataset.sortKey; + let key2 = selectEntry.nextSibling.dataset.sortKey; + let path1 = selectEntry.dataset.path + let path2 = selectEntry.nextSibling.dataset.path; + + // Update DOM elements. + selectEntry.dataset.sortKey = key2; + selectEntry.nextSibling.dataset.sortKey = key1; + tree.insertBefore(selectEntry.nextSibling, selectEntry) + + // Update sort state. + let accountId = selectEntry.dataset.accountId; + preferences.folderSort.set(getFolderId({ accountId, path: path1 }), parseInt(key2)); + preferences.folderSort.set(getFolderId({ accountId, path: path2 }), parseInt(key1)); + } +} +// Update sort state, redraw folders. +async function on_folder_sort_alpha(e) { + let accountId = document.getElementById("folder_accounts_list").value; + let { folders } = await browser.accounts.get(accountId, true); + + let sensitivity = document.getElementById("sort_folder_name_case_sensitive").checked + ? "case" + : "base"; + folders.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity })); + + // Save sorted list as custom sort. + let i = 0; + for (let folder of folders) { + preferences.folderSort.set(getFolderId(folder), i++); + } + loadSortedFolderEntries(); +} + +async function save() { + await browser.storage.local.set(preferences); + document.getElementById("save_message").style.display = "block"; + + // Update all open tabs! + let tabs = (await browser.tabs.query({})).filter(t => ["mail"].includes(t.type)); + for (let tab of tabs) { + await browser.CustomFolderSort.update(tab.id, preferences); + } + + await new Promise(r => window.setTimeout(r, 1000)); + document.getElementById("save_message").style.display = "none"; +} + +async function load() { + i18n.updateDocument(); + + preferences = await browser.storage.local.get({ + accountSettings: new Map(), + folderSort: new Map(), + hideFolderIcon: false, + startupFolder: "" + }) + + const elementEventMap = { + tablist: { type: "click", callback: tablistClickHandler }, + save_button: { type: "click", callback: save }, + //account_up_button: { type: "click", callback: on_account_move_up }, + //account_down_button: { type: "click", callback: on_account_move_down }, + //account_alpha_button:{ type: "click", callback: on_account_sort_alpha }, + + folder_accounts_list: { type: "change", callback: on_folder_account_changed }, + folder_sort_method: { type: "change", callback: on_folder_sort_method_changed }, + folder_up_button: { type: "click", callback: on_folder_move_up }, + folder_down_button: { type: "click", callback: on_folder_move_down }, + folder_alpha_button: { type: "click", callback: on_folder_sort_alpha }, + + extra_hide_folder_icons: { type: "click", callback: on_extra_hide_folder_icons_changed }, + extra_startup_folder: { type: "change", callback: on_extra_pick_startup_folder }, + } + + for (let [elementId, eventData] of Object.entries(elementEventMap)) { + document.getElementById(elementId).addEventListener(eventData.type, eventData.callback); + } + + // Load accounts into various dropdowns. + let accounts = await browser.accounts.list(); + for (let account of accounts) { + // Fill folder_accounts_list select. + let option = document.createElement('option'); + option.label = account.name; + option.value = account.id; + document.getElementById("folder_accounts_list").appendChild(option.cloneNode(true)); + document.getElementById("accounts_list").appendChild(option); + + if (!preferences.accountSettings.has(account.id)) { + preferences.accountSettings.set(account.id, { + type: "0", + }) + }; + + // Fill extra_startup_folder select. + if (account.folders && account.folders.length > 0) { + let optgroup = document.createElement('optgroup'); + optgroup.label = account.name; + document.getElementById("extra_startup_folder").appendChild(optgroup); + account.folders.forEach(f => { + let option = document.createElement('option'); + option.label = f.name; + option.value = getFolderId(f); + document.getElementById("extra_startup_folder").appendChild(option); + }) + } + } + + // Load hide icons pref. + document.getElementById("extra_hide_folder_icons").checked = preferences.hideFolderIcon; + + // Load startupFolder. + if (preferences.startupFolder) { + document.getElementById("extra_startup_folder").value = preferences.startupFolder; + } + + // Load the sorted folder entries. + on_folder_account_changed(); +} + +window.addEventListener("load", load);