├── .gitignore ├── Changelog ├── LICENSE ├── Makefile ├── README.markdown ├── TODO ├── _locales ├── da │ └── messages.json ├── de │ └── messages.json ├── en │ └── messages.json ├── es │ └── messages.json ├── fr │ └── messages.json ├── hu │ └── messages.json ├── it │ └── messages.json ├── ja │ └── messages.json ├── nl │ └── messages.json ├── pl │ └── messages.json ├── pt │ └── messages.json ├── pt_BR │ └── messages.json ├── ru │ └── messages.json ├── sk │ └── messages.json ├── sr │ └── messages.json ├── sv │ └── messages.json ├── zh_CN │ └── messages.json └── zh_TW │ └── messages.json ├── api └── WindowListener │ ├── CHANGELOG.md │ ├── README.md │ ├── implementation.js │ └── schema.json ├── background.js ├── content ├── folderPane.js ├── scripts │ └── messenger.js ├── style │ ├── alphabetical.png │ ├── alphabetical2.png │ ├── down.png │ └── up.png ├── tbsortfolders.css ├── tbsortfolders.xhtml ├── tbsortfolders_91.xhtml └── ui.js ├── defaults └── preferences │ └── prefs.js ├── icon.png ├── locale ├── da │ ├── main.dtd │ └── ui.dtd ├── de │ ├── main.dtd │ └── ui.dtd ├── en-US │ ├── main.dtd │ └── ui.dtd ├── es-ES │ ├── main.dtd │ └── ui.dtd ├── fr │ ├── main.dtd │ └── ui.dtd ├── hu │ ├── main.dtd │ └── ui.dtd ├── it │ ├── main.dtd │ └── ui.dtd ├── ja │ ├── main.dtd │ └── ui.dtd ├── nb-NO │ ├── main.dtd │ └── ui.dtd ├── nl │ ├── main.dtd │ └── ui.dtd ├── pl │ ├── main.dtd │ └── ui.dtd ├── pt-BR │ ├── main.dtd │ └── ui.dtd ├── pt │ ├── main.dtd │ └── ui.dtd ├── ru-RU │ ├── main.dtd │ └── ui.dtd ├── sk │ ├── main.dtd │ └── ui.dtd ├── sr │ ├── main.dtd │ └── ui.dtd ├── sv-SE │ ├── main.dtd │ └── ui.dtd ├── zh-CN │ ├── main.dtd │ └── ui.dtd └── zh-TW │ ├── main.dtd │ └── ui.dtd ├── manifest.json ├── modules └── sort.jsm └── resources_past ├── default.png ├── icon.png └── sorted.png /.gitignore: -------------------------------------------------------------------------------- 1 | .vimsession 2 | *.xpi 3 | .vscode 4 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | -- V2.0.0 2 | 3 | Update version for tb68 4 | 5 | -- V1.3.0pre2 6 | 7 | Center preferences window. 8 | 9 | -- V1.3.0pre1 10 | 11 | Use manifest.json with "legacy" enabled to force overlay loading. 12 | Fixed Tb63 / Tb64 compatibilty issues. 13 | 14 | -- V1.2.0pre3 15 | 16 | Fixed the first account option "Local Folders". The "Local Folders" would drop to last if you added a new account afterwards. 17 | 18 | -- V1.2.0pre2 19 | 20 | Fixed the folder sort. Everything should be working again. 21 | 22 | -- V1.2.0pre2 23 | 24 | Fixed the account sort. Disabled the folder sort. 25 | 26 | -- v0.6 development series -- 27 | 28 | New bugfix with collapsing folders by default. Fix appearance of the tree. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ***** BEGIN LICENSE BLOCK ***** 2 | Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | 4 | The contents of this file are subject to the Mozilla Public License Version 5 | 1.1 (the "License"); you may not use this file except in compliance with 6 | the License. You may obtain a copy of the License at 7 | http://www.mozilla.org/MPL/ 8 | 9 | Software distributed under the License is distributed on an "AS IS" basis, 10 | WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | for the specific language governing rights and limitations under the 12 | License. 13 | 14 | The Original Code is GMail Conversation View 15 | 16 | The Initial Developer of the Original Code is 17 | Jonathan Protzenko. 18 | Portions created by the Initial Developer are Copyright (C) 2010 19 | the Initial Developer. All Rights Reserved. 20 | 21 | Contributor(s): 22 | 23 | Alternatively, the contents of this file may be used under the terms of 24 | either the GNU General Public License Version 2 or later (the "GPL"), or 25 | the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 26 | in which case the provisions of the GPL or the LGPL are applicable instead 27 | of those above. If you wish to allow use of your version of this file only 28 | under the terms of either the GPL or the LGPL, and not to allow others to 29 | use your version of this file under the terms of the MPL, indicate your 30 | decision by deleting the provisions above and replace them with the notice 31 | and other provisions required by the GPL or the LGPL. If you do not delete 32 | the provisions above, a recipient may use your version of this file under 33 | the terms of any one of the MPL, the GPL or the LGPL. 34 | 35 | ***** END LICENSE BLOCK ***** 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXCLUDES = $(addprefix --exclude , '*/*~' '*/.*.sw*' '*/.vimsession' '*/*.template' 'Makefile' '*/*.xcf' '*/*.xpi' 'resources_past/*' TODO) 2 | 3 | .PHONY: dist upload 4 | 5 | all: dist 6 | 7 | dist: 8 | rm -f tbsortfolders.xpi 9 | zip tbsortfolders.xpi $(EXCLUDES) -r * 10 | 11 | upload: dist 12 | scp tbsortfolders.xpi jonathan@protzenko.fr:~/Web/jonathan/manually-sort-folders/manually-sort-folders-$(DATE).xpi 13 | 14 | DATE = $(shell date +%Y%m%d%H%M) 15 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Manually Sort Folders 2 | ===================== 3 | 4 | Tested on: 5 | 6 | Windows 10 x64 with Thunderbird 68.0 (64-bit) 7 | Windows 10 x64 with Thunderbird 78.0 (64-bit) 8 | Windows 10 x64 with Thunderbird 91.13.0 (64-bit) 9 | Windows 10 x64 with Thunderbird 102.2.2 (64-bit) 10 | Linux with Thunderbird 102.0b7 11 | 12 | NOT tested: 13 | 14 | All other setups 15 | 16 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 17 | Warning: 18 | 19 | Please be careful using this updated plugin. It can kill your Thunderbird account config. 20 | [Make a backup of your Thunderbird profile](https://support.mozilla.org/en-US/kb/profiles-where-thunderbird-stores-user-data#w_backing-up-a-profile). 21 | 22 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 23 | 24 | 25 | This extension for Thunderbird 68+ adds a “Manually sort folders” entry into 26 | the Tools menu. Use it to, hum, manually sort your accounts and folders (that was obvious, 27 | wasn’t it?). 28 | 29 | I sometimes put a development version 30 | [online](https://jonathan.protzenko.fr/manually-sort-folders/), but over the 31 | past eight years, I’ve only updated this add-on twice, as it is very stable; so your 32 | best bet is to just stick with the publicly available version on 33 | [ATN](https://addons.thunderbird.net/addon/manually-sort-folders/). 34 | 35 | * Screenshots on [the wiki](https://github.com/protz/Manually-Sort-Folders/wiki) 36 | * Stable version on [ATN](https://addons.thunderbird.net/addon/manually-sort-folders/) 37 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Don't jump to the top if possible when clicking "refresh". 2 | - Don't display accounts that use the global inbox in the "Sort Accounts" tab 3 | - Add an option to apply the sorting logic to the "Favorites" folders (if 4 | possible simply). 5 | - Move the dialog to the "Preferences" dialog of the add-on (simpler) --> use 6 | nsIWindowMediator to access main window's variables such as gFolderTreeView 7 | - Enable drag&drop for sorting folders in the real folder pane [ETA: 2 weeks] 8 | -------------------------------------------------------------------------------- /_locales/da/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Giver dig mulighed for at ændre rækkefølgen af mapper i mapperuden." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Ermöglicht es Ihnen, die Reihenfolge der Ordner im Ordnerfenster zu ändern." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Allows you to change the order of folders in the folder pane." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Permite cambiar el orden de las carpetas en el panel de carpetas." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Permet de modifier l'ordre des dossiers dans le volet des dossiers." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/hu/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Lehetővé teszi a mappák sorrendjének megváltoztatását a mappapanelben." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Consente di modificare l'ordine delle cartelle nel riquadro delle cartelle." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "フォルダペインでのフォルダの並び順を変更することができます。" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Hiermee kunt u de volgorde van de mappen in het deelvenster mappen wijzigen." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Umożliwia zmianę kolejności folderów w okienku folderów." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/pt/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Permite-lhe alterar a ordem das pastas no painel de pastas." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Permite alterar a ordem das pastas no painel de pastas." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Позволяет изменить порядок папок в панели папок." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/sk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Umožňuje zmeniť poradie priečinkov na paneli priečinkov." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/sr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Омогућава вам да промените редослед фасцикли у окну фасцикли." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/sv/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "Låter dig ändra ordningen på mappar i mapprutan." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "允许你改变文件夹窗格中文件夹的顺序。" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Description of the extension", 4 | "message": "允許您更改文件夾窗格中文件夾的順序。" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /api/WindowListener/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version: 1.62 2 | ------------- 3 | - fix bug in fullyLoaded() 4 | 5 | Version: 1.61 6 | ------------- 7 | - adjusted to Thunderbird Supernova (Services is now in globalThis) 8 | 9 | Version: 1.60 10 | ------------- 11 | - explicitly set hasAddonManagerEventListeners flag to false on uninstall 12 | 13 | Version: 1.59 14 | ------------- 15 | - store hasAddonManagerEventListeners flag in add-on scope instead on the global 16 | window again, and clear it upon add-on removal 17 | 18 | Version: 1.58 19 | ------------- 20 | - hard fork WindowListener v1.57 implementation and continue to serve it for 21 | Thunderbird 111 and older 22 | - WindowListener v1.58 supports injection into nested browsers of the new 23 | mailTab front end of Thunderbird Supernova and allows "about:message" and 24 | "about:3pane" to be valid injection targets. More information can be found here: 25 | https://developer.thunderbird.net/thunderbird-development/codebase-overview/mail-front-end 26 | 27 | Version: 1.57 28 | ------------- 29 | - fix race condition which could prevent the AOM tab to be monkey patched correctly 30 | 31 | Version: 1.56 32 | ------------- 33 | - be precise on which revision the wrench symbol should be displayed, instead of 34 | the options button 35 | 36 | Version: 1.54 37 | ------------- 38 | - fix "ownerDoc.getElementById() is undefined" bug 39 | 40 | Version: 1.53 41 | ------------- 42 | - fix "tab.browser is undefined" bug 43 | 44 | Version: 1.52 45 | ------------- 46 | - clear cache only if add-on is uninstalled/updated, not on app shutdown 47 | 48 | Version: 1.51 49 | ------------- 50 | - use wrench button for options for TB78.10 51 | 52 | Version: 1.50 53 | ------------- 54 | - use built-in CSS rules to fix options button for dark themes (thanks to Thunder) 55 | - fix some occasions where options button was not added 56 | 57 | Version: 1.49 58 | ------------- 59 | - fixed missing eventListener for Beta + Daily 60 | 61 | Version: 1.48 62 | ------------- 63 | - moved notifyTools into its own NotifyTools API. 64 | 65 | Version: 1.39 66 | ------------- 67 | - fix for 68 68 | 69 | Version: 1.36 70 | ------------- 71 | - fix for beta 87 72 | 73 | Version: 1.35 74 | ------------- 75 | - add support for options button/menu in add-on manager and fix 68 double menu entry 76 | 77 | Version: 1.34 78 | ------------- 79 | - fix error in unload 80 | 81 | Version: 1.33 82 | ------------- 83 | - fix for e10s 84 | 85 | Version: 1.30 86 | ------------- 87 | - replace setCharPref by setStringPref to cope with UTF-8 encoding 88 | 89 | Version: 1.29 90 | ------------- 91 | - open options window centered 92 | 93 | Version: 1.28 94 | ------------- 95 | - do not crash on missing icon 96 | 97 | Version: 1.27 98 | ------------- 99 | - add openOptionsDialog() 100 | 101 | Version: 1.26 102 | ------------- 103 | - pass WL object to legacy preference window 104 | 105 | Version: 1.25 106 | ------------- 107 | - adding waitForMasterPassword 108 | 109 | Version: 1.24 110 | ------------- 111 | - automatically localize i18n locale strings in injectElements() 112 | 113 | Version: 1.22 114 | ------------- 115 | - to reduce confusions, only check built-in URLs as add-on URLs cannot 116 | be resolved if a temp installed add-on has bin zipped 117 | 118 | Version: 1.21 119 | ------------- 120 | - print debug messages only if add-ons are installed temporarily from 121 | the add-on debug page 122 | - add checks to registered windows and scripts, if they actually exists 123 | 124 | Version: 1.20 125 | ------------- 126 | - fix long delay before customize window opens 127 | - fix non working removal of palette items 128 | 129 | Version: 1.19 130 | ------------- 131 | - add support for ToolbarPalette 132 | 133 | Version: 1.18 134 | ------------- 135 | - execute shutdown script also during global app shutdown (fixed) 136 | 137 | Version: 1.17 138 | ------------- 139 | - execute shutdown script also during global app shutdown 140 | 141 | Version: 1.16 142 | ------------- 143 | - support for persist 144 | 145 | Version: 1.15 146 | ------------- 147 | - make (undocumented) startup() async 148 | 149 | Version: 1.14 150 | ------------- 151 | - support resource urls 152 | 153 | Version: 1.12 154 | ------------- 155 | - no longer allow to enforce custom "namespace" 156 | - no longer call it namespace but uniqueRandomID / scopeName 157 | - expose special objects as the global WL object 158 | - autoremove injected elements after onUnload has ben executed 159 | 160 | Version: 1.9 161 | ------------- 162 | - automatically remove all entries added by injectElements 163 | 164 | Version: 1.8 165 | ------------- 166 | - add injectElements 167 | 168 | Version: 1.7 169 | ------------- 170 | - add injectCSS 171 | - add optional enforced namespace 172 | 173 | Version: 1.6 174 | ------------- 175 | - added mutation observer to be able to inject into browser elements 176 | - use larger icons as fallback 177 | -------------------------------------------------------------------------------- /api/WindowListener/README.md: -------------------------------------------------------------------------------- 1 | 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). 2 | -------------------------------------------------------------------------------- /api/WindowListener/schema.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "namespace": "WindowListener", 4 | "functions": [ 5 | { 6 | "name": "registerDefaultPrefs", 7 | "type": "function", 8 | "parameters": [ 9 | { 10 | "name": "aPath", 11 | "type": "string", 12 | "description": "Relative path to the default file." 13 | } 14 | ] 15 | }, 16 | { 17 | "name": "registerOptionsPage", 18 | "type": "function", 19 | "parameters": [ 20 | { 21 | "name": "aPath", 22 | "type": "string", 23 | "description": "Path to the options page, which should be made accessible in the (legacy) Add-On Options menu." 24 | } 25 | ] 26 | }, 27 | { 28 | "name": "registerChromeUrl", 29 | "type": "function", 30 | "description": "Register folders which should be available as chrome:// urls (as defined in the legacy chrome.manifest)", 31 | "parameters": [ 32 | { 33 | "name": "data", 34 | "type": "array", 35 | "items": { 36 | "type": "array", 37 | "items": { 38 | "type": "string" 39 | } 40 | }, 41 | "description": "Array of manifest url definitions (content, locale, resource)" 42 | } 43 | ] 44 | }, 45 | { 46 | "name": "waitForMasterPassword", 47 | "type": "function", 48 | "async": true, 49 | "parameters": [] 50 | }, 51 | { 52 | "name": "openOptionsDialog", 53 | "type": "function", 54 | "parameters": [ 55 | { 56 | "name": "windowId", 57 | "type": "integer", 58 | "description": "Id of the window the dialog should be opened from." 59 | } 60 | ] 61 | }, 62 | { 63 | "name": "startListening", 64 | "type": "function", 65 | "async": true, 66 | "parameters": [] 67 | }, 68 | { 69 | "name": "registerWindow", 70 | "type": "function", 71 | "parameters": [ 72 | { 73 | "name": "windowHref", 74 | "type": "string", 75 | "description": "Url of the window, which should be listen for." 76 | }, 77 | { 78 | "name": "jsFile", 79 | "type": "string", 80 | "description": "Path to the JavaScript file, which should be loaded into the window." 81 | } 82 | ] 83 | }, 84 | { 85 | "name": "registerStartupScript", 86 | "type": "function", 87 | "parameters": [ 88 | { 89 | "name": "aPath", 90 | "type": "string", 91 | "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." 92 | } 93 | ] 94 | }, 95 | { 96 | "name": "registerShutdownScript", 97 | "type": "function", 98 | "parameters": [ 99 | { 100 | "name": "aPath", 101 | "type": "string", 102 | "description": "Path to a JavaScript file, which should be executed on add-on shutdown." 103 | } 104 | ] 105 | } 106 | ] 107 | } 108 | ] -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | browser.WindowListener.registerDefaultPrefs("defaults/preferences/prefs.js"); 2 | 3 | browser.WindowListener.registerChromeUrl([ 4 | ["content", "tbsortfolders", "content/"], 5 | ["resource", "tbsortfolders", "modules/"], 6 | ["locale", "tbsortfolders", "da", "locale/da/"], 7 | ["locale", "tbsortfolders", "de", "locale/de/"], 8 | ["locale", "tbsortfolders", "en-US", "locale/en-US/"], 9 | ["locale", "tbsortfolders", "es-ES", "locale/es-ES/"], 10 | ["locale", "tbsortfolders", "fr", "locale/fr/"], 11 | ["locale", "tbsortfolders", "it", "locale/it/"], 12 | ["locale", "tbsortfolders", "ja", "locale/ja/"], 13 | ["locale", "tbsortfolders", "nl", "locale/nl/"], 14 | ["locale", "tbsortfolders", "nb-NO", "locale/nb-NO/"], 15 | ["locale", "tbsortfolders", "pl", "locale/pl/"], 16 | ["locale", "tbsortfolders", "pt", "locale/pt/"], 17 | ["locale", "tbsortfolders", "pt-BR", "locale/pt-BR/"], 18 | ["locale", "tbsortfolders", "ru-RU", "locale/ru-RU/"], 19 | ["locale", "tbsortfolders", "sk", "locale/sk/"], 20 | ["locale", "tbsortfolders", "sr", "locale/sr/"], 21 | ["locale", "tbsortfolders", "sv-SE", "locale/sv-SE/"], 22 | ["locale", "tbsortfolders", "zh-CN", "locale/zh-CN/"], 23 | ["locale", "tbsortfolders", "zh-TW", "locale/zh-TW/"], 24 | ]); 25 | 26 | // For Thunderbird 78.0 and later 27 | browser.WindowListener.registerWindow( 28 | "chrome://messenger/content/messenger.xhtml", 29 | "chrome://tbsortfolders/content/scripts/messenger.js"); 30 | 31 | // For Thunderbird 68 32 | browser.WindowListener.registerWindow( 33 | "chrome://messenger/content/messenger.xul", 34 | "chrome://tbsortfolders/content/scripts/messenger.js"); 35 | 36 | browser.WindowListener.startListening(); 37 | -------------------------------------------------------------------------------- /content/folderPane.js: -------------------------------------------------------------------------------- 1 | 2 | (async function () { 3 | /* For folder sorting */ 4 | 5 | const Cc = Components.classes; 6 | const Ci = Components.interfaces; 7 | const Cu = Components.utils; 8 | 9 | Cu.import("resource://gre/modules/Log.jsm"); 10 | let tblog = Log.repository.getLogger("tbsortfolders.folderPane"); 11 | tblog.level = Log.Level.Debug; 12 | tblog.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); 13 | tblog.addAppender(new Log.DumpAppender(new Log.BasicFormatter())); 14 | 15 | var Services = globalThis.Services || ChromeUtils.import( 16 | "resource://gre/modules/Services.jsm" 17 | ).Services; 18 | 19 | Cu.import("resource://tbsortfolders/sort.jsm"); 20 | Cu.import("resource:///modules/MailUtils.jsm"); 21 | 22 | const ThunderbirdMajorVersion = Services.appinfo.version.split(".")[0]; 23 | 24 | tblog.debug("Init"); 25 | 26 | const tbsf_prefs = Services.prefs.getBranch("extensions.tbsortfolders@xulforum.org."); 27 | /* This array is populated either when the file is loaded or when the 28 | * preferences are updated. The keys are the account's pretty names and the 29 | * values are the sort functions associated to each account. */ 30 | var tbsf_prefs_functions; 31 | const mail_accountmanager_prefs = Services.prefs.getBranch("mail.accountmanager."); 32 | { 33 | let accounts = mail_accountmanager_prefs.getStringPref("accounts").split(","); 34 | tblog.debug("Accounts: "+accounts); 35 | } 36 | 37 | // var tb_accounts = mail_accountmanager_prefs.getStringPref("accounts"); 38 | // var tb_default_account = mail_accountmanager_prefs.getStringPref("defaultaccount"); 39 | // tblog.debug("TB Accounts: "+tb_accounts); 40 | // tblog.debug("TB Default account: "+tb_default_account); 41 | 42 | // let tbsf_accounts = null; 43 | // let tbsf_default_account = null; 44 | // try { 45 | // tbsf_accounts = tbsf_prefs.getStringPref("accounts"); 46 | // tbsf_default_account = tbsf_prefs.getStringPref("defaultaccount"); 47 | // tblog.debug("TBSF Accounts: "+tbsf_accounts); 48 | // tblog.debug("TBSF Default account: "+tbsf_default_account); 49 | // } catch (x) { 50 | // } 51 | 52 | tblog.debug("Add observer"); 53 | 54 | let mainWindow = Services.wm.getMostRecentWindow("mail:3pane"); 55 | let config = {attributes:true,attributeFilter:["maxpos"],childList:true,subtree:true}; 56 | var callback_foldertree = function (mutationList, observer) { 57 | tblog.debug("Observer activated"); 58 | 59 | // for (let mutation of mutationList) { 60 | // if (mutation.type == 'childList') { 61 | // tblog.debug("Childnode added"); 62 | // } else if (mutation.type == 'attributes') { 63 | // tblog.debug("The "+mutation.attributeName+" attribute changed"); 64 | // } 65 | // } 66 | 67 | let current_default_account = mail_accountmanager_prefs.getStringPref("defaultaccount"); 68 | let tbsf_default_account = tbsf_prefs.getStringPref("defaultaccount"); 69 | // tblog.debug("Current Default account: "+current_default_account); 70 | // tblog.debug("Stored Default account: "+tbsf_default_account); 71 | 72 | if (tbsf_default_account && current_default_account !== tbsf_default_account) 73 | mail_accountmanager_prefs.setStringPref("defaultaccount", tbsf_default_account); 74 | 75 | /* 76 | let current_tb_accounts = mail_accountmanager_prefs.getStringPref("accounts"); 77 | let tbsf_accounts = null; 78 | try { 79 | tbsf_accounts = tbsf_prefs.getStringPref("accounts"); 80 | } catch (x) { 81 | } 82 | tblog.debug("Current accounts: "+current_tb_accounts); 83 | tblog.debug("Stored accounts: "+tbsf_accounts); 84 | */ 85 | }; 86 | var observer_foldertree = new MutationObserver(callback_foldertree); 87 | observer_foldertree.observe(mainWindow.document.getElementById('folderTree'),config); 88 | 89 | // observer_foldertree.disconnect(); 90 | 91 | // Override function sortFolderItems(aFtvItems) of TB (in comm/mail/base/content/folderPane.js) 92 | sortFolderItems = function (aFtvItems) { 93 | if (!aFtvItems.length) 94 | return; 95 | 96 | //A sort function is associated to every account, so we get the account's name 97 | let parent = aFtvItems[0]._folder.parent; 98 | //In case we're asked to sort sub-folders, we walk up the tree up to the root 99 | //item which is the "fake folder" representing the account. 100 | while (parent.parent) parent = parent.parent; 101 | let parentName = parent.prettyName; 102 | 103 | let sort_function; 104 | if (tbsf_prefs_functions[parentName]) { 105 | //If we have a value for this account then tbsf_prefs_functions contains the 106 | //right sort function 107 | sort_function = tbsf_prefs_functions[parentName]; 108 | } else { 109 | //If we don't: use Tb's default 110 | sort_function = tbsf_sort_functions[0]; 111 | } 112 | aFtvItems.sort(sort_function); 113 | } 114 | 115 | function update_prefs_functions() { 116 | let tbsf_data = {}; 117 | try { 118 | tbsf_data = JSON.parse(tbsf_prefs.getStringPref("tbsf_data")); 119 | } catch (e) { 120 | } 121 | tbsf_prefs_functions = Object(); 122 | for (let vkey in tbsf_data) { 123 | let key = vkey; 124 | /* key[0] = 0 if the user asked for Tb's default sort function, 1 for 125 | alphabetical, 2 for custom sort 126 | key[1] = the data to pass to tbsf_sort_functions[2] if key[0] == 2 127 | */ 128 | if (tbsf_data[key][0] == 2) { 129 | //feed the manual sort function with the associated sort data 130 | tbsf_prefs_functions[key] = (a,b) => tbsf_sort_functions[2](tbsf_data[key][1], a, b); 131 | } else { 132 | //other functions don't need specific data 133 | tbsf_prefs_functions[key] = tbsf_sort_functions[tbsf_data[key][0]]; 134 | } 135 | } 136 | } 137 | 138 | update_prefs_functions(); 139 | 140 | let myPrefObserver = { 141 | register: function mpo_register () { 142 | tbsf_prefs.QueryInterface(Components.interfaces.nsIPrefBranch); 143 | tbsf_prefs.addObserver("", this, false); 144 | }, 145 | 146 | unregister: function mpo_unregister () { 147 | if (!tbsf_prefs) return; 148 | tbsf_prefs.removeObserver("", this); 149 | }, 150 | 151 | observe: function mpo_observe (aSubject, aTopic, aData) { 152 | if (aTopic != "nsPref:changed") 153 | return; 154 | switch (aData) { 155 | case "tbsf_data": 156 | update_prefs_functions(); 157 | break; 158 | } 159 | } 160 | }; 161 | myPrefObserver.register(); 162 | 163 | /* Startup folder */ 164 | let startup_folder = tbsf_prefs.getStringPref("startup_folder"); 165 | if (startup_folder) { 166 | tblog.debug("startup folder: "+startup_folder); 167 | } else { 168 | tblog.debug("No startup folder specified"); 169 | } 170 | 171 | if (startup_folder && ThunderbirdMajorVersion < 98) { 172 | /* 173 | On Thunderbird 97 and earlier, it was possible for add-ons to intervene in 174 | the startup behavior of Thunderbird. 175 | */ 176 | const oldRestoreTab = mailTabType.modes.folder.restoreTab; 177 | let inRestoreTab = false; 178 | mailTabType.modes.folder.restoreTab = function (x, y) { 179 | tblog.debug("restoreTab"); 180 | inRestoreTab = true; 181 | oldRestoreTab.call(this, x, y); 182 | inRestoreTab = false; 183 | }; 184 | const oldSelectFolder = gFolderTreeView.selectFolder; 185 | const change_folder = startup_folder; 186 | let firstRun = true; 187 | gFolderTreeView.selectFolder = function (x, y) { 188 | tblog.debug("selectFolder firstRun:"+firstRun.toString()+" inRestoreTab:"+inRestoreTab.toString()); 189 | if (firstRun && inRestoreTab) { 190 | const folder = MailUtils.getExistingFolder(change_folder); 191 | if (folder) { 192 | oldSelectFolder.call(this, folder, true); 193 | /* Ensures that the selected folder is on the screen. */ 194 | const selected = gFolderTreeView.getSelectedFolders()[0]; 195 | if (selected) { 196 | gFolderTreeView._treeElement.ensureRowIsVisible(gFolderTreeView.getIndexOfFolder(selected)); 197 | } 198 | } else { 199 | oldSelectFolder.call(this, x, y); 200 | } 201 | firstRun = false; 202 | } else { 203 | oldSelectFolder.call(this, x, y); 204 | } 205 | } 206 | tblog.debug("Overriding selectFolder"); 207 | startup_folder = null; 208 | } 209 | 210 | /* 211 | Refresh pane and select folder 212 | 213 | The start of this add-on may be too early to call gFolderTreeView._rebuild() 214 | and MailUtils.getExistingFolder(). 215 | 216 | So I structured a 10-times retry loop to counter any exceptions or failures 217 | that may occur due to that. 218 | */ 219 | async function selectFolder(win, startup_folder) { 220 | let tries = 0; 221 | let ms = 100; 222 | 223 | // Stop after 10 tries. Or use other break condition, like total time spend. 224 | while (tries < 10) { 225 | try { 226 | /* 227 | Refresh pane -- possible exception. 228 | */ 229 | win.gFolderTreeView._rebuild(); 230 | if (startup_folder) { 231 | /* 232 | Select folder 233 | Since Thunderbird 98, add-on startup has been delayed until 234 | Thunderbird is mostly done. So there is no way other than 235 | immediately selecting the folder. However, there is a report of a 236 | case where getExistingFolder returns null for the existing folder. 237 | */ 238 | let folder = MailUtils.getExistingFolder(startup_folder); 239 | if (folder && gFolderTreeView.selectFolder(folder, true)) { 240 | return true; 241 | } 242 | } else { 243 | return true; 244 | } 245 | } catch (e) { 246 | // Nothing. 247 | } 248 | tries++; 249 | await new Promise(resolve => setTimeout(resolve, ms)); 250 | } 251 | return false; 252 | } 253 | 254 | let selectPromises = []; 255 | for (let win of Services.wm.getEnumerator("mail:3pane")) { 256 | selectPromises.push(selectFolder(win, startup_folder)); 257 | } 258 | 259 | let results = await Promise.all(selectPromises); 260 | tblog.debug("Refreshing the pane" 261 | + (startup_folder ? " and selecting folder" : "") 262 | + ": " 263 | + (results ? "Success" : "Failure")); 264 | 265 | /* Ensures that the selected folder is on the screen. */ 266 | { 267 | const selected = gFolderTreeView.getSelectedFolders()[0]; 268 | if (selected) { 269 | gFolderTreeView._treeElement.ensureRowIsVisible(gFolderTreeView.getIndexOfFolder(selected)); 270 | } 271 | } 272 | 273 | tblog.debug("Init done"); 274 | 275 | })() 276 | -------------------------------------------------------------------------------- /content/scripts/messenger.js: -------------------------------------------------------------------------------- 1 | // Import any needed modules. 2 | var Services = globalThis.Services || ChromeUtils.import( 3 | "resource://gre/modules/Services.jsm" 4 | ).Services; 5 | const g_ThunderbirdMajorVersion = Services.appinfo.version.split(".")[0]; 6 | 7 | // Load an additional JavaScript file. 8 | Services.scriptloader.loadSubScript("chrome://tbsortfolders/content/folderPane.js", window, "UTF-8"); 9 | 10 | function onLoad(activatedWhileWindowOpen) { 11 | const tbsf_prefs = Services.prefs.getBranch("extensions.tbsortfolders@xulforum.org."); 12 | let xulname = 'tbsortfolders'; 13 | if (g_ThunderbirdMajorVersion >= 91) { 14 | xulname += '_91'; 15 | } 16 | let additionalElements = ` 17 | 18 | 22 | 23 | 24 | 25 | 29 | `; 30 | if (tbsf_prefs.getStringPref("hide_folder_icons")) { 31 | additionalElements += ` 32 | 33 | 34 | #folderTree > treechildren::-moz-tree-image { 35 | list-style-image: none; 36 | width: 0; 37 | height: 0; 38 | } 39 | 40 | `; 41 | } 42 | WL.injectElements(additionalElements, ["chrome://tbsortfolders/locale/main.dtd"]); 43 | } 44 | 45 | function onUnload(deactivatedWhileWindowOpen) { 46 | } 47 | -------------------------------------------------------------------------------- /content/style/alphabetical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/content/style/alphabetical.png -------------------------------------------------------------------------------- /content/style/alphabetical2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/content/style/alphabetical2.png -------------------------------------------------------------------------------- /content/style/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/content/style/down.png -------------------------------------------------------------------------------- /content/style/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/content/style/up.png -------------------------------------------------------------------------------- /content/tbsortfolders.css: -------------------------------------------------------------------------------- 1 | groupbox { 2 | margin: 5px; 3 | border: 2px solid var(--color-gray-20); 4 | padding: 0 0 5px 0; 5 | } 6 | groupbox caption { 7 | background: var(--color-gray-20); 8 | padding: 0.3em; 9 | margin: 0 0 5px 0; 10 | } 11 | treechildren::-moz-tree-image { 12 | -moz-context-properties: fill, fill-opacity, stroke; 13 | } 14 | -------------------------------------------------------------------------------- /content/tbsortfolders.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 |