├── .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 |
23 | `;
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 |
11 |
12 |
13 |
14 | &accountsort.noaccountsetupyet;
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | &accountssort.description;
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | &accountsort.localfolders;
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | &gb.move;
45 |
46 |
50 |
54 |
55 |
56 |
57 |
58 | &gb.first;
59 | &gbfirst.default;
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | &gb.extra;
68 |
69 | &accountsort.warning;
70 |
71 |
72 | &accountsort.restartwarning;
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | &sortfolders.description;
85 |
86 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
106 |
107 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | &gb.extra;
117 | &sortmethod.default.description;
118 |
119 |
120 | &gb.extra;
121 | &sortmethod.alphabetical.description;
122 |
123 |
124 |
125 | &gb.movefolder;
126 |
127 |
131 |
135 |
136 |
137 |
138 |
139 | &gb.sort_siblings_by;
140 |
141 |
145 |
146 |
147 |
148 |
149 |
150 | &gb.extra;
151 | &sortmethod.custom.description;
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | &extra.description;
165 |
166 |
167 | &extra.startup;
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
178 |
182 |
183 |
184 | &extra.startupfolder.notice;
185 |
186 |
187 | &extra.misc;
188 |
189 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
--------------------------------------------------------------------------------
/content/tbsortfolders_91.xhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 | &accountsort.noaccountsetupyet;
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | &accountssort.description;
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | &gb.move;
37 |
38 |
42 |
46 |
47 |
48 |
49 |
50 | &gb.sort_by;
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 | &gb.extra;
62 |
63 | &accountsort.note;
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | &sortfolders.description;
75 |
76 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
96 |
97 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | &gb.extra;
107 | &sortmethod.default.description;
108 |
109 |
110 | &gb.extra;
111 | &sortmethod.alphabetical.description;
112 |
113 |
114 |
115 | &gb.movefolder;
116 |
117 |
121 |
125 |
126 |
127 |
128 |
129 | &gb.sort_siblings_by;
130 |
131 |
135 |
136 |
137 |
138 |
139 |
140 | &gb.extra;
141 | &sortmethod.custom.description;
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | &extra.description;
155 |
156 |
157 | &extra.startup;
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
168 |
172 |
173 |
174 | &extra.startupfolder.notice;
175 |
176 |
177 | &extra.misc;
178 |
179 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/content/ui.js:
--------------------------------------------------------------------------------
1 | const Cc = Components.classes;
2 | const Ci = Components.interfaces;
3 | const Cu = Components.utils;
4 |
5 | // Logging using the latest from https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Log.jsm
6 | // Assuming this is 68+ only.
7 | Cu.import("resource://gre/modules/Log.jsm");
8 | let tblog = Log.repository.getLogger("tbsortfolders.ui");
9 | tblog.level = Log.Level.Debug;
10 | tblog.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
11 | tblog.addAppender(new Log.DumpAppender(new Log.BasicFormatter()));
12 |
13 | var Services = globalThis.Services || ChromeUtils.import(
14 | "resource://gre/modules/Services.jsm"
15 | ).Services;
16 | const g_ThunderbirdMajorVersion = Services.appinfo.version.split(".")[0];
17 |
18 | Cu.import("resource://tbsortfolders/sort.jsm");
19 | Cu.import("resource:///modules/MailUtils.jsm");
20 |
21 | if (g_ThunderbirdMajorVersion >= 91) {
22 | Cu.import("resource:///modules/MailServices.jsm"); // for reorderAccounts
23 | }
24 |
25 | try {
26 | Cu.import("resource:///modules/iteratorUtils.jsm"); // for fixIterator
27 | } catch(e) {
28 | /* fixIterator was removed together with iteratorutils.jsm on Thunderbird 91.
29 | * That's no longer needed.
30 | */
31 | }
32 |
33 | var g_accounts = Object();
34 | const tbsf_prefs = Services.prefs.getBranch("extensions.tbsortfolders@xulforum.org.");
35 | var tbsf_data = {};
36 | var current_account = null;
37 |
38 | const mail_accountmanager_prefs = Services.prefs.getBranch("mail.accountmanager.");
39 | const mail_account_prefs = Services.prefs.getBranch("mail.account.");
40 | const mail_server_prefs = Services.prefs.getBranch("mail.server.");
41 |
42 | /* Most of the functions below are for *folder* sorting */
43 |
44 | function assert(v, s) {
45 | if (!v) {
46 | tblog.error("Assertion failure "+s);
47 | throw "Assertion failure";
48 | }
49 | }
50 |
51 | function dump_tree(node, prefix) {
52 | if (prefix === undefined) prefix = "";
53 | tblog.debug("Dump tree: "+prefix+node.tagName);
54 | for (let i = 0; i < node.children.length; i++)
55 | dump_tree(node.children[i], prefix+" ");
56 | }
57 |
58 | function item_key(tree_item) {
59 | //return tree_item.querySelector("treerow > treecell").getAttribute("value");
60 | for (let i = 0; i < tree_item.children.length; ++i)
61 | if (tree_item.children[i].tagName == "treerow")
62 | return tree_item.children[i].firstChild.getAttribute("value");
63 | tblog.error("TBSortFolders: severe error, no item key for "+tree_item);
64 | }
65 |
66 | function item_label(tree_item) {
67 | //return tree_item.querySelector("treerow > treecell").getAttribute("label");
68 | for (let i = 0; i < tree_item.children.length; ++i)
69 | if (tree_item.children[i].tagName == "treerow")
70 | return tree_item.children[i].firstChild.getAttribute("label");
71 | tblog.error("TBSortFolders: severe error, no item label for "+tree_item);
72 | }
73 |
74 | let ftvItems = {};
75 |
76 | function rebuild_tree(full, collapse) {
77 | tblog.debug("rebuild_tree("+full+");");
78 | let dfs = 0;
79 | /* Cache these expensive calls. They're called for each comparison :( */
80 | let myFtvItem = function(tree_item) {
81 | if (!ftvItems[tree_item.id]) {
82 | let text = item_label(tree_item);
83 | let folder = MailUtils.getExistingFolder(tree_item.id);
84 | folder.QueryInterface(Ci.nsIMsgFolder);
85 | ftvItems[tree_item.id] = { _folder: folder, text: text };
86 | }
87 | return ftvItems[tree_item.id];
88 | }
89 | let sort_function;
90 | let replace_data = false;
91 | let sort_method = tbsf_data[current_account][0];
92 | if (sort_method == 0) {
93 | tblog.debug("Sort method 0");
94 | sort_function = (c1, c2) => tbsf_sort_functions[0](myFtvItem(c1), myFtvItem(c2));
95 | } else if (sort_method == 1) {
96 | tblog.debug("Sort method 1");
97 | sort_function = (c1, c2) => tbsf_sort_functions[1](myFtvItem(c1), myFtvItem(c2));
98 | } else if (sort_method == 2) {
99 | tblog.debug("Sort method 2");
100 | sort_function =
101 | (c1, c2) => tbsf_sort_functions[2](tbsf_data[current_account][1], myFtvItem(c1), myFtvItem(c2));
102 | replace_data = true;
103 | } else if (sort_method == 3) {
104 | tblog.debug("Sort method 3");
105 | sort_function = (c1, c2) => tbsf_sort_functions[3](myFtvItem(c1), myFtvItem(c2));
106 | }
107 | let fresh_data = {};
108 | let my_sort = function(a_tree_items, indent) {
109 | let tree_items = Array();
110 | //tree_items = a_tree_items;
111 |
112 | tblog.debug(indent+a_tree_items.length+" nodes passed");
113 | for (let i = 0; i < a_tree_items.length; ++i)
114 | tree_items.push(a_tree_items[i]);
115 | tblog.debug(indent+tree_items.length+" folders to examine before sort");
116 | tree_items.sort(sort_function);
117 |
118 | tblog.debug(indent+tree_items.length+" folders to examine");
119 | for (let i = 0; i < tree_items.length; ++i) {
120 | dfs++;
121 | //let data = tbsf_data[current_account][1];
122 | /*if (data[item_key(tree_items[i])] !== undefined)
123 | assert(data[item_key(tree_items[i])] == dfs, "dfs "+dfs+" data "+data[item_key(tree_items[i])]);
124 | else*/
125 | /* We need to do this: in case a folder has been deleted in the middle of
126 | the DFS, the sort keys are not contiguous anymore. However, we wish to
127 | maintain the invariant that is commented out above (the assert). The
128 | invariant above (the assert) is broken if a folder has been deleted in the
129 | meanwhile so we make sure it is enforced with the line below. It only
130 | changes something in case a folder has been deleted/added since we last
131 | walked the folder tree.
132 |
133 | It is to be remarked that when a folder has been added, it is sorted
134 | \emph{at the end} of the list (see special case and comments in
135 | folderPane.js) so the test above gives true (it's undefined) and we set
136 | the right sort keys. */
137 | fresh_data[item_key(tree_items[i])] = dfs;
138 | if (full) {
139 | tblog.debug(indent+"### Rebuilding "+dfs+" is "+item_key(tree_items[i]));
140 | }
141 |
142 | //let n_tree_items = tree_items[i].querySelectorAll("[thisnode] > treechildren > treeitem");
143 | let n_tree_items = [];
144 | for (let j = 0; j < tree_items[i].children.length; ++j)
145 | if (tree_items[i].children[j].tagName == "treechildren")
146 | n_tree_items = tree_items[i].children[j].children;
147 | if (n_tree_items.length) {
148 | my_sort(n_tree_items, indent+" ");
149 | if (collapse
150 | && tree_items[i].getAttribute("container") == "true"
151 | && tree_items[i].getAttribute("open") == "true")
152 | tree_items[i].setAttribute("open", "false");
153 | }
154 | }
155 |
156 | if (full) {
157 | //dummy, slow insertion algorithm (but only used when the folder list is
158 | //initially built)
159 | for (let i = 0; i < tree_items.length; ++i)
160 | tree_items[i].parentNode.appendChild(tree_items[i].parentNode.removeChild(tree_items[i]));
161 | } else {
162 | //cleverer one: we know we're only swapping two items
163 | let i = 0;
164 | while (i < tree_items.length && tree_items[0].parentNode.children[i] == tree_items[i])
165 | i++;
166 | //we found a difference between what we want and the state of the UI: swap
167 | //current item with the next
168 | if (i < tree_items.length - 1) {
169 | let parent = tree_items[0].parentNode;
170 | parent.insertBefore(parent.removeChild(parent.children[i+1]), parent.children[i]);
171 | }
172 | }
173 | }
174 |
175 | let children = document.querySelectorAll("#foldersTree > treechildren > treeitem");
176 | my_sort(children, "");
177 | if (replace_data)
178 | tbsf_data[current_account][1] = fresh_data; //this "fresh" array allows us to get rid of old folder's keys
179 | }
180 |
181 | function decode_special(flags) {
182 | if (flags & 0x00000100) {
183 | return 'Trash';
184 | } else if (flags & 0x00000200) {
185 | return 'Sent';
186 | } else if (flags & 0x00000400) {
187 | return 'Drafts';
188 | } else if (flags & 0x00000800) {
189 | return 'Outbox';
190 | } else if (flags & 0x00001000) {
191 | return 'Inbox';
192 | } else if (flags & 0x00004000) {
193 | return 'Archives';
194 | } else if (flags & 0x00400000) {
195 | return 'Templates';
196 | } else if (flags & 0x40000000) {
197 | return 'Junk';
198 | } else if (flags & 0x80000000) {
199 | return 'Favorite';
200 | } else {
201 | return 'none';
202 | }
203 | }
204 |
205 | function build_folder_tree(account) {
206 | // Clear folder tree
207 | let treechildren = document.getElementById("treeChildren");
208 | while (treechildren.firstChild) {
209 | treechildren.removeChild(treechildren.firstChild);
210 | }
211 |
212 | // Fill folder tree
213 | if (account.incomingServer.rootFolder.hasSubFolders) {
214 | // tblog.debug("Root Folder keys: "+Object.keys(account.incomingServer.rootFolder));
215 | walk_folder(account.incomingServer.rootFolder,treechildren,0);
216 | }
217 | }
218 |
219 | function walk_folder(folder,treechildren,depth) {
220 | let subFolders = folder.subFolders;
221 | if (Array.isArray(subFolders)) {
222 | // Thunderbird 91 and later
223 | subFolders.forEach(s => {
224 | let folder = s.QueryInterface(Components.interfaces.nsIMsgFolder);
225 | walk_folder_append(folder,treechildren,depth);
226 | });
227 | } else {
228 | // before Thunderbird 91
229 | while (typeof subFolders.hasMoreElements === 'function' && subFolders.hasMoreElements()) {
230 | let folder = subFolders.getNext().QueryInterface(Components.interfaces.nsIMsgFolder);
231 | walk_folder_append(folder,treechildren,depth);
232 | }
233 | }
234 | }
235 |
236 | function walk_folder_append(folder,treechildren,depth) {
237 | let indent = ' '.repeat(2*depth);
238 | tblog.debug("Folder: "+indent+folder.prettyName);
239 | tblog.debug("Folder URI: "+indent+folder.URI);
240 | tblog.debug("Folder flags: "+indent+folder.flags);
241 |
242 | let special_name = decode_special(folder.flags);
243 | tblog.debug("Special name: "+special_name);
244 |
245 | let treeitem = document.createXULElement('treeitem');
246 | treeitem.setAttribute('id',folder.URI);
247 | let treerow = document.createXULElement('treerow');
248 | let treecell = document.createXULElement('treecell');
249 | treecell.setAttribute('label',folder.prettyName);
250 | treecell.setAttribute('value',folder.URI);
251 | treecell.setAttribute('properties','specialFolder-'+special_name);
252 | treerow.appendChild(treecell);
253 | treeitem.appendChild(treerow)
254 |
255 | if (folder.hasSubFolders) {
256 |
257 | treeitem.setAttribute('container','true');
258 | treeitem.setAttribute('open','true');
259 | let treechildrensub = document.createXULElement('treechildren');
260 |
261 | walk_folder(folder,treechildrensub,depth+1);
262 |
263 | treeitem.appendChild(treechildrensub)
264 | }
265 |
266 | treechildren.appendChild(treeitem);
267 | }
268 |
269 |
270 | function on_load() {
271 | try {
272 | tblog.debug("on_load");
273 |
274 | document.getElementsByTagName('window')[0].maxHeight = screen.availHeight - 32;
275 |
276 | let width = tbsf_prefs.getStringPref("width");
277 | let height = tbsf_prefs.getStringPref("height");
278 | if (width < 600) {
279 | width = 600;
280 | }
281 | if (height < 400) {
282 | height = 400;
283 | }
284 | window.resizeTo(width, height);
285 |
286 | let json = tbsf_prefs.getStringPref("tbsf_data");
287 | try {
288 | tbsf_data = JSON.parse(json);
289 | } catch (e) {
290 | }
291 |
292 | let account_manager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
293 | let name_initial = '';
294 | let name;
295 | let accounts_menu = document.getElementById("accounts_menu");
296 | let accounts = [];
297 | if (Array.isArray(account_manager.accounts)) {
298 | accounts = account_manager.accounts;
299 | } else {
300 | if (typeof fixIterator === 'function') {
301 | for (let x of fixIterator(account_manager.accounts, Ci.nsIMsgAccount)) {
302 | accounts.push(x);
303 | }
304 | }
305 | }
306 | tblog.debug("Total accounts: "+accounts.length);
307 | if (!accounts.length) {
308 | document.querySelector("tabbox").style.display = "none";
309 | document.getElementById("err_no_accounts").style.display = "";
310 | return;
311 | }
312 | for (let account of accounts) {
313 | // tblog.debug("Account keys: "+Object.keys(account));
314 | //fill the menulist with the right elements
315 | if (!account.incomingServer)
316 | continue;
317 | tblog.debug("Account: "+account.incomingServer.rootFolder.prettyName);
318 | let name = account.incomingServer.rootFolder.prettyName;
319 | let it = document.createXULElement("menuitem");
320 | it.setAttribute("label", name);
321 | accounts_menu.appendChild(it);
322 |
323 | //register the account for future use, create the right data structure in
324 | //the data
325 | g_accounts[name] = account;
326 | if (!tbsf_data[name]) tbsf_data[name] = Array();
327 | if (!name_initial) name_initial = name;
328 | }
329 | document.getElementById("accounts_menu").parentNode.setAttribute("label", name_initial);
330 |
331 |
332 | tblog.debug("Accounts: "+account_manager.accounts.length);
333 | // tblog.debug("Account Manager keys: "+Object.keys(account_manager));
334 | // try {
335 | // let tb_accounts = mail_accountmanager_prefs.getStringPref("accounts");
336 | // let tbsf_accounts = tbsf_prefs.getStringPref("accounts");
337 |
338 | // tblog.debug("TB Accounts: "+tb_accounts);
339 | // tblog.debug("TBSF Accounts: "+tbsf_accounts);
340 |
341 | // let tb_default_account = mail_accountmanager_prefs.getStringPref("defaultaccount");
342 | // let tbsf_default_account = tbsf_prefs.getStringPref("defaultaccount");
343 |
344 | // tblog.debug("TB Default account: "+tb_default_account);
345 | // tblog.debug("TBSF Default account: "+tbsf_default_account);
346 | // } catch (x) {
347 | // }
348 |
349 |
350 | on_account_changed();
351 |
352 | accounts_on_load();
353 | extra_on_load();
354 | } catch (e) {
355 | tblog.debug(e);
356 | throw e;
357 | }
358 | }
359 |
360 | function renumber(treeItem, start) {
361 | tbsf_data[current_account][1][item_key(treeItem)] = start++;
362 | let children = []; // = treeItem.querySelectorAll("treechildren > treeitem"); but only starting from the root
363 | for (let j = 0; j < treeItem.children.length; ++j)
364 | if (treeItem.children[j].tagName == "treechildren")
365 | children = treeItem.children[j].children;
366 |
367 | for (let i = 0; i < children.length; ++i)
368 | start = renumber(children[i], start);
369 | return start;
370 | }
371 |
372 | function move_up(tree_item) {
373 | let tree = document.getElementById("foldersTree");
374 | let uri = item_key(tree_item);
375 | tblog.debug("URI: "+uri);
376 | if (tree_item.previousSibling) {
377 | let previous_item = tree_item.previousSibling;
378 | let previous_uri = item_key(previous_item);
379 | let data = tbsf_data[current_account][1];
380 | renumber(previous_item, renumber(tree_item, data[previous_uri]));
381 | rebuild_tree();
382 | //tree.builder.rebuild();
383 | } else {
384 | tblog.debug("This is unexpected");
385 | }
386 | /*for (let i = 0; i < 10; ++i) {
387 | let tree_item = tree.view.getItemAtIndex(i);
388 | let k = item_key(tree_item);
389 | tblog.debug(tbsf_data[current_account][1][k]+" ");
390 | } tblog.debug("");*/
391 | }
392 |
393 | function on_move_up() {
394 | tblog.debug("on_move_up");
395 | const tree = document.getElementById("foldersTree");
396 | if (tree.currentIndex < 0) return;
397 | const tree_item = tree.view.getItemAtIndex(tree.currentIndex);
398 | if (tree_item.previousSibling) {
399 | move_up(tree_item);
400 | tree.view.selection.select(tree.view.getIndexOfItem(tree_item));
401 | }
402 | }
403 |
404 | function on_move_down() {
405 | tblog.debug("on_move_down");
406 | const tree = document.getElementById("foldersTree");
407 | if (tree.currentIndex < 0) return;
408 | const tree_item = tree.view.getItemAtIndex(tree.currentIndex);
409 | if (tree_item.nextSibling) {
410 | move_up(tree_item.nextSibling);
411 | tree.view.selection.select(tree.view.getIndexOfItem(tree_item));
412 | }
413 | }
414 |
415 | function on_alphabetical() {
416 | tblog.debug("on_alphabetical");
417 | const tree = document.getElementById("foldersTree");
418 | if (tree.currentIndex < 0) return;
419 | const tree_item = tree.view.getItemAtIndex(tree.currentIndex);
420 | const uri = item_key(tree_item);
421 | const parent = tree_item.parentNode;
422 | if (!parent) return;
423 | const compare_function = document.getElementById("sort_folder_name_case_sensitive").checked ?
424 | ((a, b) => a > b) : ((a, b) => a.toLowerCase() > b.toLowerCase());
425 | let number = tbsf_data[current_account][1][item_key(parent.firstChild)];
426 | let siblingArray = [];
427 | for (let i = 0; i < parent.children.length; i++) {
428 | siblingArray.push(parent.children[i]);
429 | }
430 | siblingArray.sort((a, b) => compare_function(item_label(a), item_label(b)));
431 | siblingArray.forEach(item => {
432 | parent.appendChild(item);
433 | number = renumber(item, number);
434 | });
435 | rebuild_tree(true, false);
436 | tree.view.selection.select(tree.view.getIndexOfItem(tree_item));
437 | }
438 |
439 | function get_sort_method_for_account(account) {
440 | if (tbsf_data[account] && tbsf_data[account][0] !== undefined)
441 | return tbsf_data[account][0];
442 | else
443 | return 0;
444 | }
445 |
446 | function update_tree() {
447 | let account = g_accounts[current_account];
448 | let root_folder = account.incomingServer.rootFolder; // nsIMsgFolder
449 | let tree = document.getElementById("foldersTree");
450 | tree.setAttribute("ref", root_folder.URI);
451 |
452 | build_folder_tree(account);
453 | }
454 |
455 | function on_account_changed() {
456 | //update the UI
457 | let new_account = document.getElementById("accounts_menu").parentNode.getAttribute("label");
458 | if (new_account != current_account) {
459 | current_account = new_account;
460 | let sort_method = get_sort_method_for_account(current_account);
461 | document.getElementById("sort_method").value = sort_method;
462 | update_tree();
463 | on_sort_method_changed();
464 | }
465 | }
466 |
467 | function on_sort_method_changed() {
468 | let sort_method = document.getElementById("sort_method").getAttribute("value");
469 | tbsf_data[current_account][0] = sort_method;
470 | if (sort_method == 2) {
471 | document.getElementById("default_sort_box").style.display = "none";
472 | document.getElementById("alphabetical_sort_box").style.display = "none";
473 | document.getElementById("manual_sort_box").style.display = "";
474 | if (!tbsf_data[current_account][1])
475 | tbsf_data[current_account][1] = {};
476 | } else if (sort_method == 1 || sort_method == 3) {
477 | document.getElementById("default_sort_box").style.display = "none";
478 | document.getElementById("alphabetical_sort_box").style.display = "";
479 | document.getElementById("manual_sort_box").style.display = "none";
480 | } else if (sort_method == 0) {
481 | document.getElementById("default_sort_box").style.display = "";
482 | document.getElementById("alphabetical_sort_box").style.display = "none";
483 | document.getElementById("manual_sort_box").style.display = "none";
484 | }
485 | tbsf_prefs.setStringPref("tbsf_data", JSON.stringify(tbsf_data));
486 | rebuild_tree(true, true);
487 | }
488 |
489 | function on_close() {
490 | on_refresh();
491 | window.close();
492 | }
493 |
494 | function on_refresh() {
495 | tbsf_prefs.setStringPref("tbsf_data", JSON.stringify(tbsf_data));
496 | for (let win of Services.wm.getEnumerator("mail:3pane")) {
497 | win.gFolderTreeView._rebuild();
498 | }
499 | tbsf_prefs.setStringPref("width", window.outerWidth);
500 | tbsf_prefs.setStringPref("height", window.outerHeight);
501 | }
502 |
503 | window.addEventListener("unload", on_refresh, false);
504 |
505 |
506 | /* The functions below are for *account* sorting */
507 |
508 | var g_other_accounts = [];
509 | var g_active_list = null;
510 |
511 | function accounts_on_load() {
512 | let accounts = mail_accountmanager_prefs.getStringPref("accounts").split(",");
513 | let defaultaccount = mail_accountmanager_prefs.getStringPref("defaultaccount");
514 | if (g_ThunderbirdMajorVersion < 91) {
515 | accounts = accounts.filter((x) => x != defaultaccount);
516 | accounts = [defaultaccount].concat(accounts);
517 | }
518 | accounts = accounts.filter((a) => mail_account_prefs.getPrefType(a+".server") === mail_account_prefs.PREF_STRING); // Avoid error occurs in following getStringPref .
519 | let servers = accounts.map((a) => mail_account_prefs.getStringPref(a+".server"));
520 | let types = servers.map((s) => mail_server_prefs.getStringPref(s+".type"));
521 | let names = servers.map(function (s) {
522 | try {
523 | return mail_server_prefs.getStringPref(s+".name");
524 | } catch (e) {
525 | return mail_server_prefs.getStringPref(s+".hostname");
526 | } });
527 |
528 | let mail_accounts = [];
529 | let news_accounts = [];
530 | let other_accounts = [];
531 | let add_li = function (list, [account, server, type, name]) {
532 | let li = document.createXULElement("richlistitem");
533 | let desc = document.createXULElement("description");
534 | let txt = document.createTextNode(name);
535 | desc.appendChild(txt);
536 | li.appendChild(desc);
537 | li.value = account;
538 | list.appendChild(li);
539 | };
540 | let news_account_found = false;
541 | if (g_ThunderbirdMajorVersion < 91) {
542 | /* Because of Thunderbird bug 244347, we had to treat regular accounts
543 | * (email and RSS), news accounts, and others (local folders fall into
544 | * this category) differently.
545 | */
546 | for (let i = 0; i < accounts.length; ++i) {
547 | switch (types[i]) {
548 | case "imap":
549 | case "pop3":
550 | case "exquilla":
551 | case "movemail":
552 | case "rss":
553 | mail_accounts.unshift([accounts[i], servers[i], types[i], names[i]]);
554 | add_li(document.getElementById("accounts_list"), mail_accounts[0]);
555 | document.getElementById("default_account").firstChild.setAttribute("disabled", false);
556 | /* We're not setting the "first account in the list" value in the UI
557 | * because it defaults to "first rss or mail account in the list */
558 | break;
559 | case "nntp":
560 | news_account_found = true;
561 | news_accounts.unshift([accounts[i], servers[i], types[i], names[i]]);
562 | let mi = document.createXULElement("menuitem");
563 | mi.setAttribute("value", accounts[i]);
564 | mi.setAttribute("label", names[i]);
565 | document.getElementById("default_account").appendChild(mi);
566 | add_li(document.getElementById("news_accounts_list"), news_accounts[0]);
567 | /* Set the "first account in the list value in the UI */
568 | if (defaultaccount == accounts[i])
569 | mi.parentNode.parentNode.value = accounts[i];
570 | break;
571 | default:
572 | let hidden = false;
573 | try {
574 | let hidden_pref = mail_server_prefs.getStringPref(servers[i]+".hidden");
575 | hidden = hidden_pref;
576 | } catch (e) {
577 | }
578 | if (!hidden) {
579 | let mi = document.createXULElement("menuitem");
580 | mi.setAttribute("value", accounts[i]);
581 | mi.setAttribute("label", names[i]);
582 | document.getElementById("default_account").appendChild(mi);
583 | /* Set the "first account in the list" value in the UI */
584 | if (defaultaccount == accounts[i])
585 | mi.parentNode.parentNode.value = accounts[i];
586 | }
587 | other_accounts.unshift([accounts[i], servers[i], types[i], names[i]]);
588 | }
589 | }
590 | g_other_accounts = other_accounts;
591 | if (news_account_found) {
592 | document.getElementById("news_accounts_list").style.display = "";
593 | }
594 | } else {
595 | /* Since Thunderbird 91, all accounts are treated equally. */
596 | let accounts_list = document.getElementById("accounts_list");
597 | for (let i = 0; i < accounts.length; ++i) {
598 | mail_accounts.unshift([accounts[i], servers[i], types[i], names[i]]);
599 | add_li(accounts_list, mail_accounts[0]);
600 | }
601 | }
602 | g_active_list = document.getElementById("accounts_list");
603 |
604 | /* Avoid Thunderbird bug(?) */
605 | if (g_active_list.selectedIndex < 0 && g_active_list.getItemAtIndex(0)) {
606 | g_active_list.selectedIndex = 0;
607 | }
608 |
609 | /* Don't understand why this was needed here. */
610 | //update_accounts_prefs();
611 | }
612 |
613 | function update_accounts_prefs() {
614 | let accounts = document.getElementById("accounts_list");
615 | let new_pref = null;
616 | let first_mail_account = null;
617 | for (let i = 0; i < accounts.children.length; ++i) {
618 | let child = accounts.children[i];
619 | if (!first_mail_account)
620 | first_mail_account = child.value;
621 | new_pref = new_pref ? (new_pref + "," + child.value) : child.value;
622 | }
623 | for (let i = 0; i < g_other_accounts.length; ++i) {
624 | let [account, server, type, name] = g_other_accounts[i];
625 | new_pref = new_pref ? (new_pref + "," + account) : account;
626 | }
627 | if (g_ThunderbirdMajorVersion < 91) {
628 | let news_accounts = document.getElementById("news_accounts_list");
629 | for (let i = 0; i < news_accounts.children.length; ++i) {
630 | let child = news_accounts.children[i];
631 | new_pref = new_pref ? (new_pref + "," + child.value) : child.value;
632 | }
633 | }
634 |
635 | mail_accountmanager_prefs.setStringPref("accounts",new_pref);
636 | //tbsf_prefs.setStringPref("accounts",new_pref);
637 | tblog.debug("Sorted accounts: "+new_pref);
638 |
639 | if (g_ThunderbirdMajorVersion < 91) {
640 | let default_account = document.getElementById("default_account").parentNode.value;
641 | if (default_account == "-1") {
642 | mail_accountmanager_prefs.setStringPref("defaultaccount",first_mail_account);
643 | tbsf_prefs.setStringPref("defaultaccount",first_mail_account);
644 | tblog.debug("Default account: "+first_mail_account);
645 | } else {
646 | mail_accountmanager_prefs.setStringPref("defaultaccount",default_account);
647 | tbsf_prefs.setStringPref("defaultaccount",default_account);
648 | tblog.debug("Default account: "+default_account);
649 | }
650 | }
651 | }
652 |
653 | function account_reordered() {
654 |
655 | if (g_ThunderbirdMajorVersion < 91) {
656 | return; // The following operations are impossible before Thunderbird 91. Therefore, the only way to reflect the account reordering we have done in prefs is to restart Thunderbird.
657 | }
658 |
659 | // Send reordered account list to MailServices.
660 | let accountIds = [];
661 | for (let account of document.getElementById("accounts_list").children) {
662 | accountIds.push(account.value);
663 | }
664 | MailServices.accounts.reorderAccounts(accountIds);
665 |
666 | // Notify the UI to rebuild the account tree.
667 | for (let win of Services.wm.getEnumerator("mail:3pane")) {
668 | win.gFolderTreeView._rebuild();
669 | let tabmail = win.document.getElementById("tabmail");
670 | for (let tabInfo of tabmail.tabInfo) {
671 | let tab = tabmail.getTabForBrowser(tabInfo.browser);
672 | if (tab && tab.urlbar && tab.urlbar.value == "about:accountsettings") {
673 | tab.browser.reload();
674 | return;
675 | }
676 | }
677 | }
678 | }
679 |
680 | function account_move_up(index, listbox) {
681 | let item = listbox.getItemAtIndex(index);
682 | if (!item)
683 | return false;
684 |
685 | let previous_item = item.previousSibling;
686 | if (!previous_item)
687 | return false;
688 |
689 | let parent = item.parentNode;
690 | parent.insertBefore(parent.removeChild(item), previous_item);
691 |
692 | return true;
693 | }
694 |
695 | function on_account_move_up() {
696 | tblog.debug("on_account_move_up");
697 | if (!g_active_list) return;
698 |
699 | let listbox = g_active_list;
700 | let i = listbox.selectedIndex;
701 |
702 | tblog.debug("index: "+i);
703 |
704 | if (i < 0) return;
705 | if (account_move_up(i, listbox))
706 | listbox.selectedIndex = i-1;
707 | update_accounts_prefs();
708 | account_reordered();
709 | }
710 |
711 | function on_account_move_down() {
712 | tblog.debug("on_account_move_down");
713 | if (!g_active_list) return;
714 |
715 | let listbox = g_active_list;
716 | let i = listbox.selectedIndex;
717 |
718 | tblog.debug("index: "+i);
719 |
720 | if (i < 0) return;
721 | if (account_move_up(i+1, listbox))
722 | listbox.selectedIndex = i+1;
723 | update_accounts_prefs();
724 | account_reordered();
725 | }
726 |
727 | function on_account_alphabetical() {
728 | tblog.debug("on_account_alphabetical");
729 | if (!g_active_list) return;
730 |
731 | let tmpAry = new Array();
732 | for (let i = 0; i < g_active_list.children.length; i++) {
733 | tmpAry[i] = new Array();
734 | tmpAry[i][0] = g_active_list.children[i].textContent;
735 | tmpAry[i][1] = g_active_list.children[i].value;
736 | }
737 | if (document.getElementById("sort_account_name_case_sensitive").checked) {
738 | tmpAry.sort();
739 | } else {
740 | tmpAry.sort((a, b) => a[0].toLowerCase() > b[0].toLowerCase());
741 | }
742 | while (g_active_list.children.length > 0) {
743 | g_active_list.children[0].remove();
744 | }
745 | for (let i = 0; i < tmpAry.length; i++) {
746 | let li = document.createXULElement("richlistitem");
747 | let desc = document.createXULElement("description");
748 | let txt = document.createTextNode(tmpAry[i][0]);
749 | desc.appendChild(txt);
750 | li.appendChild(desc);
751 | li.value = tmpAry[i][1];
752 | g_active_list.appendChild(li);
753 | }
754 | g_active_list.selectedIndex = 0;
755 | update_accounts_prefs();
756 | account_reordered();
757 | }
758 |
759 | function on_account_restart() {
760 | let mainWindow = Services.wm.getMostRecentWindow("mail:3pane");
761 | mainWindow.setTimeout(function () { Services.startup.quit(Services.startup.eForceQuit|Services.startup.eRestart); },1000);
762 | window.close();
763 | }
764 |
765 | function on_accounts_list_click() {
766 | if (g_ThunderbirdMajorVersion < 91) {
767 | g_active_list = document.getElementById("accounts_list");
768 | document.getElementById("news_accounts_list").clearSelection();
769 | }
770 | }
771 |
772 | function on_news_accounts_list_click() {
773 | if (g_ThunderbirdMajorVersion < 91) {
774 | g_active_list = document.getElementById("news_accounts_list");
775 | document.getElementById("accounts_list").clearSelection();
776 | }
777 | }
778 |
779 | /* These are UI functions for the "Extra settings" tab */
780 |
781 | /* Borrowed from http://mxr.mozilla.org/comm-central/source/mailnews/base/prefs/content/am-copies.js */
782 | function on_pick_folder(aEvent) {
783 | let folder = aEvent.target._folder;
784 | let picker = document.getElementById("startupFolder");
785 | picker.folder = folder;
786 | picker.setAttribute("label", folder.prettyName+' ('+folder.URI+')');
787 | tbsf_prefs.setStringPref("startup_folder", folder.URI);
788 | }
789 |
790 | function extra_on_load() {
791 | let startup_folder = tbsf_prefs.getStringPref("startup_folder");
792 | let picker = document.getElementById("startupFolder");
793 | let folder;
794 | if (startup_folder)
795 | folder = MailUtils.getExistingFolder(startup_folder);
796 | if (folder) {
797 | picker.folder = folder;
798 | picker.setAttribute("label", folder.prettyName+' ('+folder.URI+')');
799 | } else {
800 | let menu = document.getElementById("startup_folder_method");
801 | menu.value = "0";
802 | picker.disabled = true;
803 | picker.style.display = "none";
804 | }
805 | on_startup_folder_method_changed();
806 |
807 | document.getElementById("hideFolderIcons").checked = Boolean(tbsf_prefs.getStringPref("hide_folder_icons"));
808 | }
809 |
810 | function on_startup_folder_method_changed() {
811 | const method = document.getElementById("startup_folder_method").getAttribute("value");
812 | const picker = document.getElementById("startupFolder");
813 | let notice = false;
814 | if (method == "1") {
815 | picker.style.display = "";
816 | picker.disabled = false;
817 | if (picker.folder)
818 | tbsf_prefs.setStringPref("startup_folder", picker.folder.URI);
819 | notice = (g_ThunderbirdMajorVersion < 98);
820 | } else {
821 | picker.style.display = "none";
822 | picker.disabled = true;
823 | tbsf_prefs.setStringPref("startup_folder", "");
824 | }
825 | document.getElementById("startup_folder_notice").style.display = notice ? "" : "none";
826 |
827 | }
828 |
829 | function on_hide_folder_icons_changed() {
830 | tbsf_prefs.setStringPref("hide_folder_icons", document.getElementById("hideFolderIcons").checked ? "True" : "");
831 | }
832 |
--------------------------------------------------------------------------------
/defaults/preferences/prefs.js:
--------------------------------------------------------------------------------
1 | pref("extensions.tbsortfolders@xulforum.org.tbsf_data", "{}");
2 | pref("extensions.tbsortfolders@xulforum.org.startup_folder", "");
3 | pref("extensions.tbsortfolders@xulforum.org.defaultaccount", "");
4 | pref("extensions.tbsortfolders@xulforum.org.width", "0");
5 | pref("extensions.tbsortfolders@xulforum.org.height", "0");
6 | pref("extensions.tbsortfolders@xulforum.org.hide_folder_icons", "");
7 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/icon.png
--------------------------------------------------------------------------------
/locale/da/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/da/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/de/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/de/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/en-US/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/en-US/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/es-ES/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/es-ES/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/fr/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/fr/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/hu/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/hu/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/it/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/it/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/ja/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/ja/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/nb-NO/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/nb-NO/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/nl/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/nl/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/pl/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/pl/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/pt-BR/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/pt-BR/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/pt/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/pt/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/ru-RU/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/ru-RU/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/sk/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/sk/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/sr/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/sr/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/sv-SE/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/sv-SE/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/zh-CN/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/zh-CN/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/locale/zh-TW/main.dtd:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/locale/zh-TW/ui.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Manually sort folders",
4 | "description": "__MSG_extensionDescription__",
5 | "version": "2.3.0",
6 | "author": "Jonathan Protzenko and Itagaki Fumihiko",
7 | "homepage_url": "https://github.com/protz/Manually-Sort-Folders/wiki",
8 | "applications": {
9 | "gecko": {
10 | "id": "tbsortfolders@xulforum.org",
11 | "strict_min_version": "68.0",
12 | "strict_max_version": "103.0"
13 | }
14 | },
15 | "icons": {
16 | "32": "icon.png"
17 | },
18 | "background": {
19 | "scripts": [
20 | "background.js"
21 | ]
22 | },
23 | "experiment_apis": {
24 | "WindowListener": {
25 | "schema": "api/WindowListener/schema.json",
26 | "parent": {
27 | "scopes": ["addon_parent"],
28 | "paths": [["WindowListener"]],
29 | "script": "api/WindowListener/implementation.js"
30 | }
31 | }
32 | },
33 | "default_locale": "en"
34 | }
35 |
--------------------------------------------------------------------------------
/modules/sort.jsm:
--------------------------------------------------------------------------------
1 | var EXPORTED_SYMBOLS = ["tbsf_sort_functions", "getFolderFromUri"]
2 | var Cc = Components.classes;
3 | var Ci = Components.interfaces;
4 | var Cu = Components.utils;
5 |
6 | /* This array doesn't change, it just contains the stubs. The third function is
7 | * not complete, it is instanciated with the right data (i.e. the custom sort
8 | * keys) for each account before being added to tbsf_prefs_functions. */
9 | var tbsf_sort_functions = [
10 | //Thunderbird's default (copied from the original folderPane.js)
11 | function(a, b) {
12 | let sortKey = a._folder.compareSortKeys(b._folder);
13 | if (sortKey)
14 | return sortKey;
15 | return a.text.toLowerCase() > b.text.toLowerCase();
16 | },
17 |
18 | //Strictly alphabetical (Ascending)
19 | function(a, b) {
20 | return a.text.toLowerCase() > b.text.toLowerCase();
21 | },
22 |
23 | //Custom
24 | function (data, a, b) {
25 | /* If a folder was added and we don't have a custom key for it, we just put
26 | * it at the end. Such folders are sorted with "alphabetical" sort function.
27 | * This is what all those four cases below are about. */
28 | let k1 = a._folder.URI;
29 | let k2 = b._folder.URI;
30 | //dump(k1+" "+k2+"\n");
31 | if (!data[k1] && data[k2])
32 | return 1;
33 | else if (data[k1] && !data[k2])
34 | return -1;
35 | else if (!data[k1] && !data[k2])
36 | return (a.text.toLowerCase() > b.text.toLowerCase())*2 - 1;
37 | else
38 | return data[k1] - data[k2];
39 | },
40 |
41 | //Strictly alphabetical (Descending)
42 | function(a, b) {
43 | return a.text.toLowerCase() < b.text.toLowerCase();
44 | }
45 | ]
46 |
--------------------------------------------------------------------------------
/resources_past/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/resources_past/default.png
--------------------------------------------------------------------------------
/resources_past/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/resources_past/icon.png
--------------------------------------------------------------------------------
/resources_past/sorted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protz/Manually-Sort-Folders/87b7e72a4843c65188707b04be395146fbf8876b/resources_past/sorted.png
--------------------------------------------------------------------------------