12 |
13 | This program is free software: you can redistribute it and/or modify
14 | it under the terms of the GNU General Public License as published by
15 | the Free Software Foundation, either version 3 of the License, or
16 | (at your option) any later version.
17 |
18 | This program is distributed in the hope that it will be useful,
19 | but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 | GNU General Public License for more details.
22 |
23 | You should have received a copy of the GNU General Public License
24 | along with this program. If not, see .
25 | """
26 |
27 | import sys
28 | import argparse
29 | import random
30 | import string
31 |
32 | def generateFileContent(outputFile, dirCount, linkCount):
33 | f = open(outputFile, "w")
34 | f.write("\n")
35 | f.write("\n")
36 | f.write("Bookmarks\n")
37 | f.write("Bookmarks Menu
\n")
38 | f.write("\n")
39 | f.write("\n")
40 | f.write(generateBookmarks(dirCount, linkCount));
41 | f.write("
\n")
42 | f.close()
43 |
44 | def generateBookmarks(dirCount, linkCount):
45 | result = ""
46 | for d in range(0, dirCount):
47 | directoryName = generateName()
48 | result += " " \
49 | + directoryName \
50 | + "
\n" \
51 | + " \n"
52 | for b in range(0, linkCount):
53 | bookmarkName = generateName()
54 | result += "
- " \
57 | + bookmarkName \
58 | + "\n"
59 | result += "
\n"
60 | return result
61 |
62 | def generateName():
63 | return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(10))
64 |
65 | def main(argv):
66 | parser = argparse.ArgumentParser()
67 | parser.add_argument("outputfile", help="output filename")
68 | parser.add_argument("--dircount", type=int, help="number of directories to generate", default=10)
69 | parser.add_argument("--linkcount", type=int, help="number of bookmarks to generate per directory", default=10)
70 | args = parser.parse_args()
71 | generateFileContent(args.outputfile, args.dircount, args.linkcount)
72 |
73 | if __name__ == "__main__":
74 | main(sys.argv[1:])
75 |
--------------------------------------------------------------------------------
/locale/fr.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Title
3 | #
4 | Auto-Sort Bookmarks=Auto-Sort Bookmarks
5 | #
6 | # main.js (toolbar button)
7 | #
8 | sort_bookmarks=Trier les marque-pages
9 | #
10 | # Preferences (same order as package.json)
11 | #
12 | auto_sort_title=Tri automatique
13 | delay_title=Délai
14 | folder_delay_title=Délai pour les dossiers
15 | case_insensitive_title=Insensible à la casse
16 | exclude_folders_title=Dossiers à trier
17 | exclude_folders_label=Configurer...
18 | sort_by_title=Trier par
19 | sort_by_options.Name=Nom
20 | sort_by_options.URL=URL
21 | sort_by_options.Description=Description
22 | sort_by_options.Keyword=Mot-clé
23 | sort_by_options.Date Added=Date d’ajout
24 | sort_by_options.Last Modified=Dernière modification
25 | sort_by_options.Last visited=Dernière visite
26 | sort_by_options.Visited count=Nombre de visites
27 | sort_by_options.Reversed URL=URL renversée
28 | inverse_title=Inverser l’ordre
29 | then_sort_by_title=Puis trier par
30 | then_sort_by_options.None=Aucun
31 | then_sort_by_options.Name=Nom
32 | then_sort_by_options.URL=URL
33 | then_sort_by_options.Description=Description
34 | then_sort_by_options.Keyword=Mot-clé
35 | then_sort_by_options.Date Added=Date d’ajout
36 | then_sort_by_options.Last Modified=Dernière modification
37 | then_sort_by_options.Last visited=Dernière visite
38 | then_sort_by_options.Visited count=Nombre de visites
39 | then_sort_by_options.Reversed URL=URL renversée
40 | then_inverse_title=Inverser le deuxième ordre
41 | folder_sort_by_title=Trier les dossiers par
42 | folder_sort_by_options.None=Aucun
43 | folder_sort_by_options.Name=Nom
44 | folder_sort_by_options.Description=Description
45 | folder_sort_by_options.Date Added=Date d’ajout
46 | folder_sort_by_options.Last Modified=Dernière modification
47 | folder_inverse_title=Inverser l’ordre des dossiers
48 | folder_sort_order_title=Ordre de tri des dossiers
49 | livemark_sort_order_title=Ordre de tri des marque-pages dynamiques
50 | smart_bookmark_sort_order_title=Ordre de tri des marque-pages intelligents
51 | bookmark_sort_order_title=Ordre de tri des marque-pages
52 | #
53 | # confirmation.html
54 | #
55 | Do you want to enable the autosort?=Voulez-vous activer le tri automatique ?
56 | Yes=Oui
57 | No=Non
58 | Change options=Préférences
59 | #
60 | # configureFolders.html
61 | #
62 | Folders to Sort=Dossiers à trier
63 | Folders to Sort - Auto-Sort Bookmarks=Dossiers à trier - Auto-Sort Bookmarks
64 | Uncheck the folders to exclude when sorting.=Décochez les dossiers à exclure lors du tri.
65 | #
66 | # configure-folders.js
67 | #
68 | Recursive=Récursif
69 | The sub-folders are recursively excluded.=Les sous-dossiers sont exclus récursivement.
70 | Loading...=Chargement...
71 | #
72 | # Folders
73 | #
74 | menu_title=Menu des marque-pages
75 | toolbar_title=Barre personnelle
76 | unsorted_title=Marque-pages non classés
77 | #
78 | # Folders
79 | #
80 | Bookmarks Menu=Menu des marque-pages
81 | Bookmarks Toolbar=Barre personnelle
82 | Unsorted Bookmarks=Marque-pages non classés
83 |
--------------------------------------------------------------------------------
/locale/de.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Title
3 | #
4 | Auto-Sort Bookmarks=Auto-Sort Bookmarks
5 | #
6 | # main.js (toolbar button)
7 | #
8 | sort_bookmarks=Lesezeichen jetzt sortieren
9 | #
10 | # Preferences (same order as package.json)
11 | #
12 | auto_sort_title=Automatisch sortieren
13 | delay_title=Verzögerung
14 | folder_delay_title=Verzögerung für Ordner
15 | case_insensitive_title=Case Insensitive
16 | exclude_folders_title=Ordner zu Sortieren
17 | exclude_folders_label=Konfigurieren...
18 | sort_by_title=Zuerst sortieren nach
19 | sort_by_options.Name=Name
20 | sort_by_options.URL=URL
21 | sort_by_options.Description=Beschreibung
22 | sort_by_options.Date Added=Hinzugefügt
23 | sort_by_options.Keyword=Schlüsselwort
24 | sort_by_options.Last Modified=Zuletzt geändert
25 | sort_by_options.Last visited=Zuletzt besucht
26 | sort_by_options.Visited count=Meistbesucht
27 | sort_by_options.Reversed URL=Umgekehrt URL
28 | inverse_title=Reihenfolge umkehren
29 | then_sort_by_title=Anschließend sortieren nach
30 | then_sort_by_options.None=Keine Festlegung
31 | then_sort_by_options.Name=Name
32 | then_sort_by_options.URL=URL
33 | then_sort_by_options.Description=Beschreibung
34 | then_sort_by_options.Date Added=Hinzugefügt
35 | then_sort_by_options.Keyword=Schlüsselwort
36 | then_sort_by_options.Last Modified=Zuletzt geändert
37 | then_sort_by_options.Last visited=Zuletzt besucht
38 | then_sort_by_options.Visited count=Meistbesucht
39 | then_sort_by_options.Reversed URL=Umgekehrt URL
40 | then_inverse_title=Zweitkriterium=Reihenfolge umkehren
41 | folder_sort_by_title=Sortieren Ordner
42 | folder_sort_by_options.None=Keiner
43 | folder_sort_by_options.Name=Name
44 | folder_sort_by_options.Description=Beschreibung
45 | folder_sort_by_options.Date Added=Hinzugefügt
46 | folder_sort_by_options.Last Modified=Zuletzt geändert
47 | folder_inverse_title=Inverse Ordner bestellen
48 | folder_sort_order_title=Ordner
49 | livemark_sort_order_title=Dynamische Lesezeichen
50 | smart_bookmark_sort_order_title=Intelligente Lesezeichen
51 | bookmark_sort_order_title=Lesezeichen
52 | #
53 | # confirmation.html
54 | #
55 | Do you want to enable the autosort?=Möchten Sie das automatische Sortieren aktivieren?
56 | No=Nein
57 | Yes=Ja
58 | Change options=Einstellungen ändern
59 | #
60 | # configureFolders.html
61 | #
62 | Folders to Sort=Ordner zu Sortieren
63 | Folders to Sort - Auto-Sort Bookmarks=Ordner zu Sortieren - Auto-Sort Bookmarks
64 | Uncheck the folders to exclude when sorting.=Deaktivieren Sie die Ordner ausschließen, beim sortieren.
65 | #
66 | # configure-folders.js
67 | #
68 | Recursive=Rekursive
69 | The sub-folders are recursively excluded.=Die Unterordner werden rekursiv ausgeschlossen.
70 | Loading...=Laden...
71 | #
72 | # Folders
73 | #
74 | menu_title=Lesezeichen-Menü
75 | toolbar_title=Lesezeichen-Symbolleiste
76 | unsorted_title=Unsortierte Lesezeichen
77 | #
78 | # Folders
79 | #
80 | Bookmarks Menu=Lesezeichen-Menü
81 | Bookmarks Toolbar=Lesezeichen-Symbolleiste
82 | Unsorted Bookmarks=Unsortierte Lesezeichen
83 |
--------------------------------------------------------------------------------
/test/test-annotations.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | const {getDescription, isLivemark, isSmartBookmark} = require("lib/annotations");
21 | const {Bookmark, menuFolder} = require("lib/bookmarks");
22 | const {createBookmark, createFolder, createLivemark, createSeparator, createSmartBookmark, setDescription} = require("./utils");
23 |
24 | exports.testDescription = function (assert) {
25 | assert.strictEqual(getDescription(undefined), "");
26 |
27 | let item = createBookmark("Test title", "http://test.url/", menuFolder);
28 | assert.strictEqual(getDescription(item), "");
29 |
30 | setDescription(item, "Test description");
31 | assert.strictEqual(getDescription(item), "Test description");
32 |
33 | setDescription(item, "New description");
34 | assert.strictEqual(getDescription(item), "New description");
35 | };
36 |
37 | exports.testLivemark = function (assert) {
38 | let item = createBookmark("Test title", "http://test.url/", menuFolder);
39 | assert.strictEqual(isLivemark(item.id), false);
40 |
41 | item = createLivemark("Stack Overflow", "http://stackoverflow.com/feeds", menuFolder);
42 | assert.strictEqual(isLivemark(item.id), true);
43 |
44 | item = createFolder("Test Folder", menuFolder);
45 | assert.strictEqual(isLivemark(item.id), false);
46 |
47 | item = createSmartBookmark("Test Smart Bookmark", "MostVisited", "place:sort=8&maxResults=10", menuFolder);
48 | assert.strictEqual(isLivemark(item.id), false);
49 |
50 | item = createSeparator(menuFolder);
51 | assert.strictEqual(isLivemark(item.id), false);
52 | };
53 |
54 | exports.testSmartBookmark = function (assert) {
55 | let item = createBookmark("Test title", "http://test.url/", menuFolder);
56 | assert.strictEqual(isSmartBookmark(item.id), false);
57 |
58 | item = createLivemark("Stack Overflow", "http://stackoverflow.com/feeds", menuFolder);
59 | assert.strictEqual(isSmartBookmark(item.id), false);
60 |
61 | item = createFolder("Test Folder", menuFolder);
62 | assert.strictEqual(isSmartBookmark(item.id), false);
63 |
64 | item = createSmartBookmark("Test Smart Bookmark", "MostVisited", "place:sort=8&maxResults=10", menuFolder);
65 | assert.strictEqual(isSmartBookmark(item.id), true);
66 |
67 | item = createSeparator(menuFolder);
68 | assert.strictEqual(isSmartBookmark(item.id), false);
69 | };
70 |
71 | require("sdk/test").run(exports);
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [Auto-Sort Bookmarks](https://addons.mozilla.org/en-US/firefox/addon/auto-sort-bookmarks/)
2 | ==========================================================================================
3 |
4 | Pale Moon or Firefox (before version 57/Quantum) Plugin to Sort Bookmarks by Multiple Criteria.
5 |
6 | **Please backup your current bookmarks in case you do not like the new bookmarks order. Thus, you could restore them.**
7 |
8 | This extension provides a few options categorized within these categories:
9 | * Main Options
10 | * Exclude Folders
11 | * Sort Criteria
12 | * Sort Order
13 |
14 | These options work like this:
15 |
16 | **Main Options:**
17 |
18 | * **Auto-sort:** if this option is enabled, the bookmarks will be sorted when Firefox is opened, when this option is activated and when bookmarks are added, changed, moved or deleted.
19 | This means you cannot move any bookmarks in the same folder, unless it is moved over a separator.
20 | * **Delay:** allow to define a delay (in seconds) before automatically sorting bookmarks.
21 | * **Delay for Folders:** allow to define a delay (in seconds) before automatically sorting folders. This is to avoid that a new folder is sorted before you can choose it when adding a new bookmark.
22 | * **Case Insensitive:** if activated, the bookmarks will be sorted without considering the letter case.
23 |
24 | **Exclude Folders:**
25 |
26 | This button opens a new tab allowing you to exclude folders when sorting. If you uncheck the checkbox next to a folder, it wont be sorted, but the children folders will be sorted.
27 | If you want to exclude a folder recursively from being sorted, check the recursive checkbox.
28 |
29 | **Sort Criteria:**
30 |
31 | * **Sort By:** allow to specify the first sort criteria, that is to say, the order that will be used to sort the bookmarks. The choices are : name, url, description, keyword, date added, last modified, last visited, visited count and reversed base-URL.
32 | * **Inverse Order:** if this option is enabled, the order specified in "Sort By" will be reversed. So the order will be descending.
33 | * **Then Sort By:** allow to specify a second sort criteria (optional). For instance, if the first sort criteria is the name, it is possible to choose a second sort criteria to sort bookmarks with the same name.
34 | * **Inverse Second Order:** if this option is enabled, the order specified in "Then Sort By" will be reversed.
35 | * **Sort Folder By:** allow to specify a different sort criteria for folders. For instance, you might want to sort folders by name and other kinds of bookmarks by last visited.
36 | * **Inverse Folder Order:** if this option is enabled, the order speficied in "Sort Folder By" will be reversed.
37 |
38 | **Sort Order:**
39 |
40 | * **Folder Sort Order:** this option specify the order in which the folders are sorted. For instance, it is possible to sort folders before livemarks, smart bookmarks and bookmarks. To do this, the folder sort order should be lesser than the other sort orders.
41 | If the sort order of two types are at the same level, the bookmarks from these types will be sorted together.
42 | * **Livemark Sort Order:** this option specify the sort order of livemarks.
43 | * **Smart Bookmark Sort Order:** this option specify the sort order of smart bookmarks
44 | * **Bookmark Sort Order:** this option specify the bookmark sort order.
45 |
--------------------------------------------------------------------------------
/lib/configure-folders.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | const _ = require("sdk/l10n").get;
21 | const self = require("sdk/self");
22 | const data = self.data;
23 | const tabs = require("sdk/tabs");
24 | const {removeDoNotSortAnnotation, removeRecursiveAnnotation, setDoNotSortAnnotation, setRecursiveAnnotation} = require("lib/annotations");
25 | const {BookmarkManager, Folder, getChildrenFolders, getRootFolders} = require("lib/bookmarks");
26 | const bookmarkManager = new BookmarkManager({});
27 |
28 | /**
29 | * Show the page to configure the folders to exclude.
30 | */
31 | function showConfigureFoldersToExclude() {
32 | return function () {
33 | /**
34 | * Send children.
35 | * @param worker
36 | * @returns {Function}
37 | */
38 | function sendChildren(worker) {
39 | return function (parentID) {
40 | let children = getChildrenFolders(parentID);
41 | worker.port.emit("children", parentID, children);
42 | };
43 | }
44 |
45 | let worker;
46 |
47 | /**
48 | * Handle onRemove event.
49 | * @param item
50 | */
51 | function onRemove(item) {
52 | if (worker && item instanceof Folder) {
53 | worker.port.emit("remove-folder", item.id);
54 | }
55 | }
56 |
57 | bookmarkManager.on("remove", onRemove);
58 |
59 | tabs.open({
60 | url: data.url("configureFolders.html"),
61 | onOpen: function (tab) {
62 | tab.on("ready", function () {
63 | worker = tab.attach({
64 | contentScriptFile: data.url("configureFolders.js")
65 | });
66 |
67 | worker.port.on("sort-checkbox-change", function (folderID, activated) {
68 | if (activated) {
69 | removeDoNotSortAnnotation(folderID);
70 | }
71 | else {
72 | setDoNotSortAnnotation(folderID);
73 | }
74 | });
75 |
76 | worker.port.on("recursive-checkbox-change", function (folderID, activated) {
77 | if (activated) {
78 | setRecursiveAnnotation(folderID);
79 | }
80 | else {
81 | removeRecursiveAnnotation(folderID);
82 | }
83 | });
84 |
85 | worker.port.on("query-children", sendChildren(worker));
86 |
87 | const texts = {
88 | recursiveText: _("Recursive"),
89 | messageText: _("The sub-folders are recursively excluded."),
90 | loadingText: _("Loading..."),
91 | };
92 |
93 | worker.port.emit("init", getRootFolders(), data.url("add.png"), data.url("remove.png"), texts);
94 | });
95 | },
96 |
97 | onClose: function () {
98 | worker = null;
99 | bookmarkManager.removeListener("remove", onRemove);
100 | },
101 | });
102 | };
103 | }
104 |
105 | exports.showConfigureFoldersToExclude = showConfigureFoldersToExclude;
106 |
--------------------------------------------------------------------------------
/test/test-options.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | const {get, has, reset, set} = require("sdk/preferences/service");
21 | const self = require("sdk/self");
22 | const simplePrefs = require("sdk/simple-prefs");
23 | const prefs = simplePrefs.prefs;
24 | const {setPreferenceMaximum, setPreferenceMinimum} = require("lib/options");
25 | const {getOptionName} = require("./utils");
26 |
27 | /**
28 | * Set the value of the `name`d preference.
29 | * @param {string} name The preference name.
30 | * @return {boolean|string|int} The preference value.
31 | */
32 | function getOption(name) {
33 | return get(getOptionName(name));
34 | }
35 |
36 | /**
37 | * Set the `value` of the `name`d preference.
38 | * @param {string} name The preference name.
39 | * @param {boolean|string|int} value The preference value.
40 | */
41 | function setOption(name, value) {
42 | set(getOptionName(name), value);
43 | }
44 |
45 | /**
46 | * Test the setPreferenceMaximum() function.
47 | */
48 | exports.testPreferenceMaximum = function (assert) {
49 | setPreferenceMaximum("testMax", 15);
50 |
51 | setOption("testMax", 5);
52 | assert.strictEqual(getOption("testMax"), 5);
53 |
54 | setOption("testMax", 15);
55 | assert.strictEqual(getOption("testMax"), 15);
56 |
57 | setOption("testMax", 10);
58 | assert.strictEqual(getOption("testMax"), 10);
59 |
60 | setOption("testMax", 20);
61 | assert.strictEqual(getOption("testMax"), 15);
62 |
63 | setOption("testMax", 0);
64 | assert.strictEqual(getOption("testMax"), 0);
65 |
66 | setOption("testMax", 16);
67 | assert.strictEqual(getOption("testMax"), 15);
68 |
69 | setOption("testMax", -30);
70 | assert.strictEqual(getOption("testMax"), -30);
71 |
72 | setOption("testMax", 17);
73 | assert.strictEqual(getOption("testMax"), 15);
74 |
75 | reset(getOptionName("testMax"));
76 | };
77 |
78 | /**
79 | * Test the setPreferenceMaximum() and setPreferenceMinimum() functions on the same option.
80 | */
81 | exports.testPreferenceMaximumMinimum = function (assert) {
82 | setPreferenceMaximum("testMaxMin", 15);
83 | setPreferenceMinimum("testMaxMin", 2);
84 |
85 | setOption("testMaxMin", 5);
86 | assert.strictEqual(getOption("testMaxMin"), 5);
87 |
88 | setOption("testMaxMin", 15);
89 | assert.strictEqual(getOption("testMaxMin"), 15);
90 |
91 | setOption("testMaxMin", 10);
92 | assert.strictEqual(getOption("testMaxMin"), 10);
93 |
94 | setOption("testMaxMin", 20);
95 | assert.strictEqual(getOption("testMaxMin"), 15);
96 |
97 | setOption("testMaxMin", 0);
98 | assert.strictEqual(getOption("testMaxMin"), 2);
99 |
100 | setOption("testMaxMin", 16);
101 | assert.strictEqual(getOption("testMaxMin"), 15);
102 |
103 | setOption("testMaxMin", -30);
104 | assert.strictEqual(getOption("testMaxMin"), 2);
105 |
106 | setOption("testMaxMin", 17);
107 | assert.strictEqual(getOption("testMaxMin"), 15);
108 |
109 | setOption("testMaxMin", 2);
110 | assert.strictEqual(getOption("testMaxMin"), 2);
111 |
112 | setOption("testMaxMin", 22);
113 | assert.strictEqual(getOption("testMaxMin"), 15);
114 |
115 | setOption("testMaxMin", 1);
116 | assert.strictEqual(getOption("testMaxMin"), 2);
117 |
118 | setOption("testMaxMin", 30);
119 | assert.strictEqual(getOption("testMaxMin"), 15);
120 |
121 | setOption("testMaxMin", -2);
122 | assert.strictEqual(getOption("testMaxMin"), 2);
123 |
124 | setOption("testMaxMin", -1);
125 | assert.strictEqual(getOption("testMaxMin"), 2);
126 |
127 | reset(getOptionName("testMaxMin"));
128 | };
129 |
130 | /**
131 | * Test the setPreferenceMinimum() function.
132 | */
133 | exports.testPreferenceMinimum = function (assert) {
134 | setPreferenceMinimum("testMin", 2);
135 |
136 | setOption("testMin", 5);
137 | assert.strictEqual(getOption("testMin"), 5);
138 |
139 | setOption("testMin", 2);
140 | assert.strictEqual(getOption("testMin"), 2);
141 |
142 | setOption("testMin", 10);
143 | assert.strictEqual(getOption("testMin"), 10);
144 |
145 | setOption("testMin", 0);
146 | assert.strictEqual(getOption("testMin"), 2);
147 |
148 | setOption("testMin", 22);
149 | assert.strictEqual(getOption("testMin"), 22);
150 |
151 | setOption("testMin", 1);
152 | assert.strictEqual(getOption("testMin"), 2);
153 |
154 | setOption("testMin", 30);
155 | assert.strictEqual(getOption("testMin"), 30);
156 |
157 | setOption("testMin", -2);
158 | assert.strictEqual(getOption("testMin"), 2);
159 |
160 | reset(getOptionName("testMin"));
161 | };
162 |
163 | require("sdk/test").run(exports);
164 |
--------------------------------------------------------------------------------
/lib/annotations.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | const {Cc, Ci} = require("chrome");
21 | const annotationService = Cc["@mozilla.org/browser/annotation-service;1"].getService(Ci.nsIAnnotationService);
22 | const descriptionAnnotation = "bookmarkProperties/description";
23 | const livemarkAnnotation = "livemark/siteURI";
24 | const smartBookmarkAnnotation = "Places/SmartBookmark";
25 |
26 | /**
27 | * Get the item description.
28 | * @param {*} item The item.
29 | * @return {*} The item description.
30 | */
31 | function getDescription(item) {
32 | let description;
33 | try {
34 | description = annotationService.getItemAnnotation(item.id, descriptionAnnotation);
35 | }
36 | catch (exception) {
37 | description = "";
38 | }
39 |
40 | return description;
41 | }
42 |
43 | /**
44 | * Get an item annotation.
45 | * @param itemID The item ID.
46 | * @param name The item name.
47 | * @returns {*} The item annotation.
48 | */
49 | function getItemAnnotation(itemID, name) {
50 | let annotation;
51 | try {
52 | annotation = annotationService.getItemAnnotation(itemID, name);
53 | }
54 | catch (exception) {
55 | // Do nothing.
56 | }
57 |
58 | return annotation;
59 | }
60 |
61 | /**
62 | * Check if an item has a do not sort annotation.
63 | * @param itemID
64 | * @return {boolean}
65 | */
66 | function hasDoNotSortAnnotation(itemID) {
67 | let annotation = getItemAnnotation(itemID, "autosortbookmarks/donotsort");
68 | return annotation !== undefined;
69 | }
70 |
71 | /**
72 | * Check if an item has a recursive annotation.
73 | * @param itemID
74 | * @return {boolean}
75 | */
76 | function hasRecursiveAnnotation(itemID) {
77 | let annotation = getItemAnnotation(itemID, "autosortbookmarks/recursive");
78 | return annotation !== undefined;
79 | }
80 |
81 | /**
82 | * Check if an item is recursively excluded.
83 | * @param itemID
84 | * @return {boolean}
85 | */
86 | function isRecursivelyExcluded(itemID) {
87 | return hasDoNotSortAnnotation(itemID) && hasRecursiveAnnotation(itemID);
88 | }
89 |
90 | /**
91 | * Check whether `itemID` is a livemark.
92 | * @param {int} itemID The item ID.
93 | * @return {*} Whether the item is a livemark or not.
94 | */
95 | function isLivemark(itemID) {
96 | return annotationService.itemHasAnnotation(itemID, livemarkAnnotation);
97 | }
98 |
99 | /**
100 | * Check whether `itemID` is a smart bookmark.
101 | * @param {int} itemID The item ID.
102 | * @return {boolean} Whether the item is a smart bookmark or not.
103 | */
104 | function isSmartBookmark(itemID) {
105 | return annotationService.itemHasAnnotation(itemID, smartBookmarkAnnotation);
106 | }
107 |
108 | /**
109 | * Remove an item annotation.
110 | * @param itemID
111 | * @param name
112 | */
113 | function removeItemAnnotation(itemID, name) {
114 | annotationService.removeItemAnnotation(itemID, name);
115 | }
116 |
117 | /**
118 | * Remove the do not sort annotation on an item.
119 | * @param itemID
120 | */
121 | function removeDoNotSortAnnotation(itemID) {
122 | removeItemAnnotation(itemID, "autosortbookmarks/donotsort");
123 | }
124 |
125 | /**
126 | * Remove the recursive annotation on an item.
127 | * @param itemID
128 | */
129 | function removeRecursiveAnnotation(itemID) {
130 | removeItemAnnotation(itemID, "autosortbookmarks/recursive");
131 | }
132 |
133 | /**
134 | * Set an item annotation.
135 | * @param itemID
136 | * @param name
137 | * @param value
138 | */
139 | function setItemAnnotation(itemID, name, value) {
140 | const {Bookmark} = require("lib/bookmarks");
141 | if (Bookmark.exists(itemID)) {
142 | annotationService.setItemAnnotation(itemID, name, value, 0, annotationService.EXPIRE_NEVER);
143 | }
144 | }
145 |
146 | /**
147 | * Set the do not sort annotation on an item.
148 | * @param itemID
149 | */
150 | function setDoNotSortAnnotation(itemID) {
151 | setItemAnnotation(itemID, "autosortbookmarks/donotsort", true);
152 | }
153 |
154 | /**
155 | * Set the recursive annotation on an item.
156 | * @param itemID
157 | */
158 | function setRecursiveAnnotation(itemID) {
159 | setItemAnnotation(itemID, "autosortbookmarks/recursive", true);
160 | }
161 |
162 | exports.getDescription = getDescription;
163 | exports.getItemAnnotation = getItemAnnotation;
164 | exports.hasDoNotSortAnnotation = hasDoNotSortAnnotation;
165 | exports.hasRecursiveAnnotation = hasRecursiveAnnotation;
166 | exports.isRecursivelyExcluded = isRecursivelyExcluded;
167 | exports.isLivemark = isLivemark;
168 | exports.isSmartBookmark = isSmartBookmark;
169 | exports.removeItemAnnotation = removeItemAnnotation;
170 | exports.removeDoNotSortAnnotation = removeDoNotSortAnnotation;
171 | exports.removeRecursiveAnnotation = removeRecursiveAnnotation;
172 | exports.setItemAnnotation = setItemAnnotation;
173 | exports.setDoNotSortAnnotation = setDoNotSortAnnotation;
174 | exports.setRecursiveAnnotation = setRecursiveAnnotation;
175 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auto_sort_bookmarks",
3 | "description": "Sort bookmarks by multiple criteria",
4 | "version": "2.10.12",
5 | "author": "Antoni Boucher",
6 | "bugs": {
7 | "url": "https://github.com/eric-bixby/auto-sort-bookmarks-pm/issues"
8 | },
9 | "engines": {
10 | "firefox": ">=38.0a1",
11 | "{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}": ">=27.1.0b1"
12 | },
13 | "homepage": "https://github.com/eric-bixby/auto-sort-bookmarks-pm",
14 | "icon": "data/icon-48.png",
15 | "icon64": "data/icon-64.png",
16 | "id": "sortbookmarks@bouanto",
17 | "keywords": [
18 | "bookmarks",
19 | "sorter"
20 | ],
21 | "license": "GPL-3.0",
22 | "main": "lib/main.js",
23 | "permissions": {
24 | "multiprocess": true
25 | },
26 | "preferences": [
27 | {
28 | "name": "auto_sort",
29 | "title": "Auto-sort",
30 | "type": "bool",
31 | "value": false
32 | },
33 | {
34 | "name": "delay",
35 | "title": "Delay",
36 | "type": "integer",
37 | "value": 0
38 | },
39 | {
40 | "name": "folder_delay",
41 | "title": "Delay for Folders",
42 | "type": "integer",
43 | "value": 30
44 | },
45 | {
46 | "name": "case_insensitive",
47 | "title": "Case Insensitive",
48 | "type": "bool",
49 | "value": false
50 | },
51 | {
52 | "label": "Configure...",
53 | "name": "exclude_folders",
54 | "title": "Folders to Sort",
55 | "type": "control"
56 | },
57 | {
58 | "name": "sort_by",
59 | "title": "Sort By",
60 | "type": "menulist",
61 | "value": 0,
62 | "options": [
63 | {
64 | "value": "0",
65 | "label": "Name"
66 | },
67 | {
68 | "value": "1",
69 | "label": "URL"
70 | },
71 | {
72 | "value": "2",
73 | "label": "Description"
74 | },
75 | {
76 | "value": "3",
77 | "label": "Keyword"
78 | },
79 | {
80 | "value": "4",
81 | "label": "Date Added"
82 | },
83 | {
84 | "value": "5",
85 | "label": "Last Modified"
86 | },
87 | {
88 | "value": "6",
89 | "label": "Last visited"
90 | },
91 | {
92 | "value": "7",
93 | "label": "Visited count"
94 | },
95 | {
96 | "value": "8",
97 | "label": "Reversed URL"
98 | }
99 | ]
100 | },
101 | {
102 | "name": "inverse",
103 | "title": "Inverse Order",
104 | "type": "bool",
105 | "value": false
106 | },
107 | {
108 | "name": "then_sort_by",
109 | "title": "Then Sort By",
110 | "type": "menulist",
111 | "value": -1,
112 | "options": [
113 | {
114 | "value": "-1",
115 | "label": "None"
116 | },
117 | {
118 | "value": "0",
119 | "label": "Name"
120 | },
121 | {
122 | "value": "1",
123 | "label": "URL"
124 | },
125 | {
126 | "value": "2",
127 | "label": "Description"
128 | },
129 | {
130 | "value": "3",
131 | "label": "Keyword"
132 | },
133 | {
134 | "value": "4",
135 | "label": "Date Added"
136 | },
137 | {
138 | "value": "5",
139 | "label": "Last Modified"
140 | },
141 | {
142 | "value": "6",
143 | "label": "Last visited"
144 | },
145 | {
146 | "value": "7",
147 | "label": "Visited count"
148 | },
149 | {
150 | "value": "8",
151 | "label": "Reversed URL"
152 | }
153 | ]
154 | },
155 | {
156 | "name": "then_inverse",
157 | "title": "Inverse Second Order",
158 | "type": "bool",
159 | "value": false
160 | },
161 | {
162 | "name": "folder_sort_by",
163 | "title": "Sort Folder By",
164 | "type": "menulist",
165 | "value": 0,
166 | "options": [
167 | {
168 | "value": "-1",
169 | "label": "None"
170 | },
171 | {
172 | "value": "0",
173 | "label": "Name"
174 | },
175 | {
176 | "value": "2",
177 | "label": "Description"
178 | },
179 | {
180 | "value": "4",
181 | "label": "Date Added"
182 | },
183 | {
184 | "value": "5",
185 | "label": "Last Modified"
186 | }
187 | ]
188 | },
189 | {
190 | "name": "folder_inverse",
191 | "title": "Inverse Folder Order",
192 | "type": "bool",
193 | "value": false
194 | },
195 | {
196 | "name": "folder_sort_order",
197 | "title": "Folder Sort Order",
198 | "type": "integer",
199 | "value": 1
200 | },
201 | {
202 | "name": "livemark_sort_order",
203 | "title": "Livemark Sort Order",
204 | "type": "integer",
205 | "value": 2
206 | },
207 | {
208 | "name": "smart_bookmark_sort_order",
209 | "title": "Smart Bookmark Sort Order",
210 | "type": "integer",
211 | "value": 3
212 | },
213 | {
214 | "name": "bookmark_sort_order",
215 | "title": "Bookmark Sort Order",
216 | "type": "integer",
217 | "value": 4
218 | }
219 | ],
220 | "repository": {
221 | "type": "git",
222 | "url": "git+https://github.com/eric-bixby/auto-sort-bookmarks-pm.git"
223 | },
224 | "scripts": {
225 | "jpm-travis": "jpm --binary `which firefox` test -v"
226 | },
227 | "title": "Auto-Sort Bookmarks"
228 | }
229 |
--------------------------------------------------------------------------------
/data/configureFolders.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | let addIcon;
21 | let loadingText = "";
22 | let messageText = "";
23 | let recursiveText = "";
24 | let removeIcon;
25 | let fetching = new Set();
26 |
27 | /**
28 | * Send value.
29 | * @param type
30 | * @param folderID
31 | * @param checkbox
32 | * @param image
33 | * @returns {Function}
34 | */
35 | function sendValue(type, folderID, checkbox, image) {
36 | return function () {
37 | if (type === "recursive" && image.getAttribute("data-state") === "remove") {
38 | let children = document.querySelector("#folder-" + folderID);
39 | children.style.display = "block";
40 | }
41 | self.port.emit(type + "-checkbox-change", folderID, checkbox.checked);
42 | };
43 | }
44 |
45 | /**
46 | * Toggle children.
47 | * @param parentID
48 | * @param image
49 | * @param children
50 | * @param recursiveCheckbox
51 | * @returns {Function}
52 | */
53 | function toggleChildren(parentID, image, children, recursiveCheckbox) {
54 | return function () {
55 | if (!fetching.has(parentID)) {
56 | if (image.getAttribute("data-state") === "add") {
57 | image.src = removeIcon;
58 | image.setAttribute("data-state", "remove");
59 |
60 | if (!recursiveCheckbox.checked) {
61 | children.style.display = "block";
62 | children.textContent = loadingText;
63 | }
64 |
65 | fetching.add(parentID);
66 | setTimeout(function () {
67 | self.port.emit("query-children", parentID);
68 | }, 100);
69 | }
70 | else {
71 | image.src = addIcon;
72 | image.setAttribute("data-state", "add");
73 |
74 | if (children) {
75 | children.style.display = "none";
76 | }
77 | }
78 | }
79 | };
80 | }
81 |
82 | /**
83 | * Append folder.
84 | * @param folder
85 | * @param list
86 | */
87 | function appendFolder(folder, list) {
88 | let listItem = document.createElement("li");
89 |
90 | let recursiveCheckbox = document.createElement("input");
91 | let recursiveLabel = document.createElement("label");
92 |
93 | let children = document.createElement("ul");
94 | children.id = "folder-" + folder.id;
95 |
96 | let icon = document.createElement("img");
97 | icon.alt = "plus-minus";
98 | icon.src = addIcon;
99 | icon.setAttribute("data-state", "add");
100 | icon.addEventListener("click", toggleChildren(folder.id, icon, children, recursiveCheckbox), false);
101 | listItem.appendChild(icon);
102 |
103 | let label = document.createElement("label");
104 | label.textContent = folder.title;
105 | label.addEventListener("click", toggleChildren(folder.id, icon, children, recursiveCheckbox), false);
106 | listItem.appendChild(label);
107 |
108 | let checkbox = document.createElement("input");
109 | checkbox.type = "checkbox";
110 | checkbox.checked = !folder.excluded;
111 | checkbox.addEventListener("change", sendValue("sort", folder.id, checkbox), false);
112 | checkbox.addEventListener("change", function () {
113 | recursiveCheckbox.disabled = checkbox.checked;
114 | recursiveLabel.disabled = checkbox.checked;
115 | }, false);
116 |
117 | listItem.appendChild(checkbox);
118 |
119 | recursiveLabel.textContent = recursiveText;
120 | recursiveLabel.htmlFor = "recursive-" + folder.id;
121 | recursiveLabel.className = "recursive";
122 | recursiveLabel.disabled = checkbox.checked;
123 | listItem.appendChild(recursiveLabel);
124 |
125 | let message = document.createElement("p");
126 |
127 | recursiveCheckbox.type = "checkbox";
128 | recursiveCheckbox.id = "recursive-" + folder.id;
129 | recursiveCheckbox.checked = folder.recursivelyExcluded;
130 | recursiveCheckbox.disabled = checkbox.checked;
131 | recursiveCheckbox.className = "recursive-checkbox";
132 | recursiveCheckbox.addEventListener("change", sendValue("recursive", folder.id, recursiveCheckbox, icon), false);
133 | listItem.appendChild(recursiveCheckbox);
134 |
135 | message.textContent = messageText;
136 | listItem.appendChild(message);
137 |
138 | listItem.appendChild(children);
139 |
140 | list.appendChild(listItem);
141 | }
142 |
143 | /**
144 | * Append folders.
145 | * @param folders
146 | * @param list
147 | */
148 | function appendFolders(folders, list) {
149 | while (list.firstChild) {
150 | list.removeChild(list.firstChild);
151 | }
152 | for (let folder of folders) {
153 | appendFolder(folder, list);
154 | }
155 | }
156 |
157 | self.port.on("remove-folder", function (folderID) {
158 | let folder = document.querySelector("#folder-" + folderID);
159 | if (folder) {
160 | let parent = folder.parentNode;
161 | parent.parentNode.removeChild(parent);
162 | }
163 | });
164 |
165 | self.port.on("children", function (parentID, children) {
166 | let list = document.querySelector("#folder-" + parentID);
167 | appendFolders(children, list);
168 | fetching.delete(parentID);
169 | });
170 |
171 | self.port.on("init", function (folders, plusIcon, minusIcon, texts) {
172 | recursiveText = texts.recursiveText;
173 | messageText = texts.messageText;
174 | loadingText = texts.loadingText;
175 | addIcon = plusIcon;
176 | removeIcon = minusIcon;
177 |
178 | let rootFolders = document.querySelector("#rootFolders");
179 | if (rootFolders === null) {
180 | rootFolders = document.createElement("ul");
181 | rootFolders.id = "rootFolders";
182 | document.body.appendChild(rootFolders);
183 | }
184 |
185 | appendFolders(folders, rootFolders);
186 | });
187 |
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | const _ = require("sdk/l10n").get;
21 | const {has, set} = require("sdk/preferences/service");
22 | const self = require("sdk/self");
23 | const data = self.data;
24 | const simplePrefs = require("sdk/simple-prefs");
25 | const prefs = simplePrefs.prefs;
26 | const tabs = require("sdk/tabs");
27 | const {ActionButton} = require("sdk/ui/button/action");
28 | const windowUtils = require("sdk/window/utils");
29 | const {setDoNotSortAnnotation, setRecursiveAnnotation} = require("lib/annotations");
30 | const {BookmarkManager, menuFolder, toolbarFolder, unsortedFolder} = require("lib/bookmarks");
31 | const bookmarkManager = new BookmarkManager({});
32 | const {BookmarkSorter} = require("lib/bookmark-sorter");
33 | const bookmarkSorter = new BookmarkSorter();
34 | const {showConfigureFoldersToExclude} = require("lib/configure-folders");
35 | const {getOptionName, setPreferenceMaximum, setPreferenceMinimum} = require("lib/options");
36 | const sortCriterias = [
37 | "title",
38 | "url",
39 | "description",
40 | "keyword",
41 | "dateAdded",
42 | "lastModified",
43 | "lastVisited",
44 | "accessCount",
45 | "revurl"
46 | ];
47 |
48 | /**
49 | * On item added/changed/moved/removed/visited callback.
50 | * @param item
51 | * @param deleted
52 | * @param newFolder
53 | * @param annotationChange
54 | */
55 | function onChanged(item, deleted, newFolder, annotationChange) {
56 | bookmarkSorter.setChanged();
57 | }
58 |
59 | /**
60 | * Add the bookmark observer.
61 | */
62 | function addBookmarkObserver() {
63 | bookmarkManager.on("changed", onChanged);
64 | }
65 |
66 | /**
67 | * Sort all bookmarks.
68 | */
69 | function sortAllBookmarks() {
70 | bookmarkSorter.setChanged();
71 | }
72 |
73 | /**
74 | * Remove the bookmark observer.
75 | */
76 | function removeBookmarkObserver() {
77 | bookmarkManager.removeListener("changed", onChanged);
78 | }
79 |
80 | /**
81 | * Adjust the auto sorting feature.
82 | */
83 | function adjustAutoSort() {
84 | removeBookmarkObserver();
85 | if (prefs.auto_sort) {
86 | sortAllBookmarks();
87 | addBookmarkObserver();
88 | }
89 | }
90 |
91 | /**
92 | * Adjust the first run preference.
93 | */
94 | function adjustFirstRun() {
95 | let prefName = getOptionName("firstrun");
96 |
97 | if (has(prefName)) {
98 | set(prefName, false);
99 | }
100 | else {
101 | set(prefName, true);
102 | prefs.auto_sort = false;
103 | }
104 | }
105 |
106 | /**
107 | * Sort if the auto sort option is on.
108 | */
109 | function sortIfAuto() {
110 | if (prefs.auto_sort) {
111 | sortAllBookmarks();
112 | }
113 | }
114 |
115 | /**
116 | * Adjust the sort criteria of the bookmark sorter.
117 | */
118 | function adjustSortCriteria() {
119 | let differentFolderOrder = prefs.folder_sort_order !== prefs.livemark_sort_order && prefs.folder_sort_order !== prefs.smart_bookmark_sort_order && prefs.folder_sort_order !== prefs.bookmark_sort_order;
120 | bookmarkSorter.setCriteria(sortCriterias[prefs.sort_by], prefs.inverse,
121 | sortCriterias[parseInt(prefs.then_sort_by)] || undefined, prefs.then_inverse,
122 | sortCriterias[parseInt(prefs.folder_sort_by)], prefs.folder_inverse,
123 | differentFolderOrder, prefs.case_insensitive
124 | );
125 | sortIfAuto();
126 | }
127 |
128 | /**
129 | * Create the events.
130 | */
131 | function createEvents() {
132 | simplePrefs.on("auto_sort", function () {
133 | adjustAutoSort();
134 | });
135 |
136 | let preferences = ["folder_sort_order", "livemark_sort_order", "smart_bookmark_sort_order", "bookmark_sort_order"];
137 |
138 | for (let preference of preferences) {
139 | simplePrefs.on(preference, sortIfAuto);
140 | }
141 |
142 | simplePrefs.on("case_insensitive", adjustSortCriteria);
143 | simplePrefs.on("sort_by", adjustSortCriteria);
144 | simplePrefs.on("then_sort_by", adjustSortCriteria);
145 | simplePrefs.on("folder_sort_by", adjustSortCriteria);
146 | simplePrefs.on("inverse", adjustSortCriteria);
147 | simplePrefs.on("then_inverse", adjustSortCriteria);
148 | simplePrefs.on("folder_inverse", adjustSortCriteria);
149 | simplePrefs.on("folder_sort_order", adjustSortCriteria);
150 | simplePrefs.on("livemark_sort_order", adjustSortCriteria);
151 | simplePrefs.on("smart_bookmark_sort_order", adjustSortCriteria);
152 | simplePrefs.on("bookmark_sort_order", adjustSortCriteria);
153 |
154 | simplePrefs.on("exclude_folders", showConfigureFoldersToExclude(sortIfAuto));
155 | }
156 |
157 | /**
158 | * Show the addon options.
159 | */
160 | function showOptions() {
161 | windowUtils.getMostRecentBrowserWindow().BrowserOpenAddonsMgr("addons://detail/" + self.id + "/preferences");
162 | }
163 |
164 | /**
165 | * Show confirmation on install.
166 | */
167 | function showConfirmation() {
168 | tabs.open({
169 | url: data.url("confirmation.html"),
170 | onOpen: function (tab) {
171 | tab.on("ready", function () {
172 | let worker = tab.attach({
173 | contentScriptFile: data.url("confirmation.js")
174 | });
175 | worker.port.on("button-clicked", function (message) {
176 | switch (message) {
177 | case "button-yes":
178 | prefs.auto_sort = true;
179 | break;
180 | case "button-no":
181 | // Do nothing.
182 | break;
183 | case "button-change-options":
184 | showOptions();
185 | break;
186 | }
187 | tab.close();
188 | });
189 |
190 | worker.port.emit("show", data.url("icon-48.png"));
191 | });
192 | }
193 | });
194 | }
195 |
196 | /**
197 | * Create the widgets.
198 | * @param install
199 | */
200 | function createWidgets(install) {
201 | ActionButton({
202 | id: "sort-all",
203 | icon: {
204 | 16: "./icon-16.png",
205 | 32: "./icon-32.png",
206 | 64: "./icon-64.png"
207 | },
208 | label: _("sort_bookmarks"),
209 | onClick: sortAllBookmarks
210 | });
211 |
212 | if (install) {
213 | showConfirmation();
214 | }
215 | }
216 |
217 | /**
218 | * Migrate to set the default folder sort order and the default folders to exclude.
219 | * @param upgrade
220 | */
221 | function migrate(upgrade) {
222 | if (upgrade) {
223 | if (!prefs.migration) {
224 | prefs.migration = "2.7";
225 | }
226 |
227 | if (prefs.migration < "2.8") {
228 | if ([0, 2, 4, 5].indexOf(prefs.sort_by) >= 0) {
229 | prefs.folder_sort_by = prefs.sort_by;
230 | prefs.folder_inverse = prefs.inverse;
231 | }
232 |
233 | if (prefs.sort_menu === false) {
234 | setDoNotSortAnnotation(menuFolder.id);
235 | setRecursiveAnnotation(menuFolder.id);
236 | }
237 |
238 | if (prefs.sort_toolbar === false) {
239 | setDoNotSortAnnotation(toolbarFolder.id);
240 | setRecursiveAnnotation(toolbarFolder.id);
241 | }
242 |
243 | if (prefs.sort_unsorted === false) {
244 | setDoNotSortAnnotation(unsortedFolder.id);
245 | setRecursiveAnnotation(unsortedFolder.id);
246 | }
247 |
248 | delete prefs.sort_menu;
249 | delete prefs.sort_toolbar;
250 | delete prefs.sort_unsorted;
251 | }
252 |
253 | prefs.migration = self.version;
254 | }
255 | }
256 |
257 | /**
258 | * Set the minimum and maximum of integer preferences.
259 | */
260 | function setPreferenceMinimumMaximum() {
261 | let preferences = ["folder_sort_order", "livemark_sort_order", "smart_bookmark_sort_order", "bookmark_sort_order"];
262 | for (let preference of preferences) {
263 | setPreferenceMinimum(preference, 1);
264 | setPreferenceMaximum(preference, 4);
265 | }
266 |
267 | setPreferenceMinimum("folder_delay", 3);
268 | }
269 |
270 | /**
271 | * Auto-sort Bookmarks is a firefox extension that automatically sorts bookmarks.
272 | * @param options
273 | */
274 | function main(options) {
275 | migrate(options.loadReason === "upgrade");
276 | adjustFirstRun();
277 | createWidgets(options.loadReason === "install");
278 | adjustSortCriteria();
279 | adjustAutoSort();
280 | createEvents();
281 | setPreferenceMinimumMaximum();
282 | adjustFirstRun();
283 | }
284 |
285 | exports.main = main;
286 | exports.adjustAutoSort = adjustAutoSort;
287 | exports.adjustSortCriteria = adjustSortCriteria;
288 | exports.createEvents = createEvents;
289 | exports.setPreferenceMinimumMaximum = setPreferenceMinimumMaximum;
290 |
--------------------------------------------------------------------------------
/lib/bookmark-sorter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | const {Class} = require("sdk/core/heritage");
21 | const {isRecursivelyExcluded} = require("lib/annotations");
22 | const {Folder, menuFolder, toolbarFolder, unsortedFolder} = require("lib/bookmarks");
23 | const {setTimeout} = require("sdk/timers");
24 | const {Cc, Ci} = require("chrome");
25 | const bookmarkService = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Ci.nsINavBookmarksService);
26 |
27 | /**
28 | * Get start time.
29 | * @param name
30 | * @returns {number}
31 | */
32 | // function getStartTime(name) {
33 | // let now = new Date();
34 | // return now.getTime();
35 | // }
36 |
37 | /**
38 | * Get finish time.
39 | * @param name
40 | * @param startTime
41 | */
42 | // function getFinishTime(name, startTime) {
43 | // let now = new Date();
44 | // let elapsed = now.getTime() - startTime;
45 | // console.log(name + " (" + elapsed + "ms)");
46 | // }
47 |
48 | /**
49 | * Reverse the base of an URL to do a better sorting
50 | * @param str
51 | * @return {*}
52 | */
53 | function reverseBaseUrl(str) {
54 | if (!str) {
55 | return "";
56 | }
57 |
58 | // Used code generator: https://regex101.com/
59 | str = str.replace(/^\S+:\/\//, "");
60 | let re = /^[^\/]+$|^[^\/]+/;
61 |
62 | let m;
63 |
64 | if ((m = re.exec(str)) !== null) {
65 | if (m.index === re.lastIndex) {
66 | re.lastIndex++;
67 | }
68 |
69 | // Replace the found string by it's reversion
70 | str = str.replace(m[0], m[0].split(".").reverse().join("."));
71 | }
72 |
73 | return str;
74 | }
75 |
76 | /**
77 | * Bookmark sorter class.
78 | */
79 | let BookmarkSorter = new Class({
80 | /**
81 | * Indicates if sorting is in progress.
82 | */
83 | sorting: false,
84 |
85 | /**
86 | * Indicates if there was a change which means a sort is needed.
87 | */
88 | changed: false,
89 |
90 | /**
91 | * Delay for thread which checks for change.
92 | */
93 | delay: 3000,
94 |
95 | /**
96 | * Get a bookmark sorter.
97 | * @constructor
98 | */
99 | initialize: function () {
100 | this.sortIfChanged();
101 | },
102 |
103 | /**
104 | * Create a bookmark comparator.
105 | */
106 | createCompare: function () {
107 | let comparator;
108 |
109 | /**
110 | * Check for corrupted and order flags
111 | * @param bookmark1
112 | * @param bookmark2
113 | * @returns {number}
114 | */
115 | function checkCorruptedAndOrder(bookmark1, bookmark2) {
116 | if (bookmark1.corrupted) {
117 | if (bookmark2.corrupted) {
118 | return 0;
119 | }
120 |
121 | return 1;
122 | }
123 | else if (bookmark2.corrupted) {
124 | return -1;
125 | }
126 |
127 | if (bookmark1.order !== bookmark2.order) {
128 | return bookmark1.order - bookmark2.order;
129 | }
130 | }
131 |
132 | /**
133 | * Add reverse URLs
134 | * @param bookmark1
135 | * @param bookmark2
136 | * @param criteria
137 | */
138 | function addReverseUrls(bookmark1, bookmark2, criteria) {
139 | if (criteria === "revurl") {
140 | bookmark1.revurl = reverseBaseUrl(bookmark1.url);
141 | bookmark2.revurl = reverseBaseUrl(bookmark2.url);
142 | }
143 | }
144 |
145 | let compareOptions = {
146 | caseFirst: "upper",
147 | numeric: true,
148 | sensitivity: "case",
149 | };
150 |
151 | if (BookmarkSorter.prototype.caseInsensitive) {
152 | compareOptions.sensitivity = "base";
153 | }
154 |
155 | let firstComparator;
156 | if (["title", "url", "revurl", "description", "keyword"].indexOf(BookmarkSorter.prototype.firstSortCriteria) !== -1) {
157 | firstComparator = function (bookmark1, bookmark2) {
158 | addReverseUrls(bookmark1, bookmark2, BookmarkSorter.prototype.firstSortCriteria);
159 | return bookmark1[BookmarkSorter.prototype.firstSortCriteria].localeCompare(bookmark2[BookmarkSorter.prototype.firstSortCriteria], undefined, compareOptions) * BookmarkSorter.prototype.firstReverse;
160 | };
161 | }
162 | else {
163 | firstComparator = function (bookmark1, bookmark2) {
164 | return (bookmark1[BookmarkSorter.prototype.firstSortCriteria] - bookmark2[BookmarkSorter.prototype.firstSortCriteria]) * BookmarkSorter.prototype.firstReverse;
165 | };
166 | }
167 |
168 | let secondComparator;
169 | if (BookmarkSorter.prototype.secondSortCriteria !== undefined) {
170 | if (["title", "url", "revurl", "description", "keyword"].indexOf(BookmarkSorter.prototype.secondSortCriteria) !== -1) {
171 | secondComparator = function (bookmark1, bookmark2) {
172 | addReverseUrls(bookmark1, bookmark2, BookmarkSorter.prototype.secondSortCriteria);
173 | return bookmark1[BookmarkSorter.prototype.secondSortCriteria].localeCompare(bookmark2[BookmarkSorter.prototype.secondSortCriteria], undefined, compareOptions) * BookmarkSorter.prototype.secondReverse;
174 | };
175 | }
176 | else {
177 | secondComparator = function (bookmark1, bookmark2) {
178 | return (bookmark1[BookmarkSorter.prototype.secondSortCriteria] - bookmark2[BookmarkSorter.prototype.secondSortCriteria]) * BookmarkSorter.prototype.secondReverse;
179 | };
180 | }
181 | }
182 | else {
183 | secondComparator = function () {
184 | return 0;
185 | };
186 | }
187 |
188 | let itemComparator = function (bookmark1, bookmark2) {
189 | return firstComparator(bookmark1, bookmark2) || secondComparator(bookmark1, bookmark2);
190 | };
191 |
192 | if (BookmarkSorter.prototype.differentFolderOrder) {
193 | if (BookmarkSorter.prototype.folderSortCriteria !== undefined) {
194 | comparator = function (bookmark1, bookmark2) {
195 | if (bookmark1 instanceof Folder && bookmark2 instanceof Folder) {
196 | if (["title", "description"].indexOf(BookmarkSorter.prototype.folderSortCriteria) !== -1) {
197 | return bookmark1[BookmarkSorter.prototype.folderSortCriteria].localeCompare(bookmark2[BookmarkSorter.prototype.folderSortCriteria], undefined, compareOptions) * BookmarkSorter.prototype.folderReverse;
198 | }
199 |
200 | return (bookmark1[BookmarkSorter.prototype.folderSortCriteria] - bookmark2[BookmarkSorter.prototype.folderSortCriteria]) * BookmarkSorter.prototype.folderReverse;
201 | }
202 |
203 | return itemComparator(bookmark1, bookmark2);
204 | };
205 | }
206 | else {
207 | comparator = function (bookmark1, bookmark2) {
208 | if (bookmark1 instanceof Folder && bookmark2 instanceof Folder) {
209 | return 0;
210 | }
211 |
212 | return itemComparator(bookmark1, bookmark2);
213 | };
214 | }
215 | }
216 | else {
217 | comparator = itemComparator;
218 | }
219 |
220 | return function (bookmark1, bookmark2) {
221 | let result = checkCorruptedAndOrder(bookmark1, bookmark2);
222 | if (result === undefined) {
223 | return comparator(bookmark1, bookmark2);
224 | }
225 |
226 | return result;
227 | };
228 | },
229 |
230 | /**
231 | * Sort all bookmarks.
232 | */
233 | sortAllBookmarks: function () {
234 | let p1 = new Promise((resolve) => {
235 | let folders = [];
236 |
237 | if (!isRecursivelyExcluded(menuFolder.id)) {
238 | folders.push(menuFolder);
239 |
240 | for (let f of menuFolder.getFolders()) {
241 | folders.push(f);
242 | }
243 | }
244 |
245 | resolve(folders);
246 | });
247 |
248 | let p2 = new Promise((resolve) => {
249 | let folders = [];
250 |
251 | if (!isRecursivelyExcluded(toolbarFolder.id)) {
252 | folders.push(toolbarFolder);
253 |
254 | for (let f of toolbarFolder.getFolders()) {
255 | folders.push(f);
256 | }
257 | }
258 |
259 | resolve(folders);
260 | });
261 |
262 | let p3 = new Promise((resolve) => {
263 | let folders = [];
264 |
265 | if (!isRecursivelyExcluded(unsortedFolder.id)) {
266 | folders.push(unsortedFolder);
267 |
268 | for (let f of unsortedFolder.getFolders()) {
269 | folders.push(f);
270 | }
271 | }
272 |
273 | resolve(folders);
274 | });
275 |
276 | Promise.all([p1, p2, p3]).then(folders => {
277 | // Flatten array of arrays into array
278 | let merged = [].concat.apply([], folders);
279 | this.sortFolders(merged);
280 | });
281 | },
282 |
283 | /**
284 | * Set the sort criteria.
285 | * @param {string} firstSortCriteria The first sort criteria attribute.
286 | * @param {boolean} firstReverse Whether the first sort is reversed.
287 | * @param {string} secondReverse The second sort criteria attribute.
288 | * @param secondSortCriteria
289 | * @param folderSortCriteria
290 | * @param folderReverse
291 | * @param differentFolderOrder
292 | * @param caseInsensitive
293 | */
294 | setCriteria: function (firstSortCriteria, firstReverse, secondSortCriteria, secondReverse, folderSortCriteria, folderReverse, differentFolderOrder, caseInsensitive) {
295 | BookmarkSorter.prototype.firstReverse = firstReverse ? -1 : 1;
296 | BookmarkSorter.prototype.firstSortCriteria = firstSortCriteria;
297 | BookmarkSorter.prototype.secondReverse = secondReverse ? -1 : 1;
298 | BookmarkSorter.prototype.secondSortCriteria = secondSortCriteria;
299 | BookmarkSorter.prototype.folderReverse = folderReverse ? -1 : 1;
300 | BookmarkSorter.prototype.folderSortCriteria = folderSortCriteria;
301 | BookmarkSorter.prototype.differentFolderOrder = differentFolderOrder;
302 | BookmarkSorter.prototype.caseInsensitive = caseInsensitive;
303 | this.compare = this.createCompare();
304 | },
305 |
306 | /**
307 | * Sort and save a folder.
308 | * @param {Folder} folder The folder to sort and save.
309 | */
310 | sortAndSave: function (folder) {
311 | if (folder.canBeSorted()) {
312 | let self = this;
313 | self.sortFolder(folder);
314 | bookmarkService.runInBatchMode({
315 | runBatched() {
316 | folder.save();
317 | },
318 | }, null);
319 | }
320 | },
321 |
322 | /**
323 | * Sort the `folder` children.
324 | * @param {Folder} folder The folder to sort.
325 | */
326 | sortFolder: function (folder) {
327 | folder.getChildren();
328 |
329 | let delta = 0;
330 | let length;
331 |
332 | for (let i = 0; i < folder.children.length; ++i) {
333 | folder.children[i].sort(this.compare);
334 | length = folder.children[i].length;
335 | for (let j = 0; j < length; ++j) {
336 | folder.children[i][j].setIndex(j + delta);
337 | }
338 |
339 | delta += length + 1;
340 | }
341 | },
342 |
343 | /**
344 | * Sort the `folders`.
345 | * @param {Array.|Folder} folders The folders to sort.
346 | */
347 | sortFolders: function (folders) {
348 | folders = folders instanceof Folder ? [folders] : folders;
349 |
350 | let self = this;
351 | let promiseAry = [];
352 |
353 | for (let folder of folders) {
354 | let p = new Promise((resolve) => {
355 | // Not obvious but arg1 = folder
356 | setTimeout((function (arg1) {
357 | return function () {
358 | self.sortAndSave(arg1);
359 | resolve(true);
360 | };
361 | }(folder)), 0);
362 | });
363 | promiseAry.push(p);
364 | }
365 |
366 | Promise.all(promiseAry).then(bool => {
367 | if (bool) {
368 | self.sorting = false;
369 | self.changed = false;
370 | }
371 | });
372 | },
373 |
374 | /**
375 | * Set flag to trigger sorting.
376 | */
377 | setChanged: function () {
378 | this.changed = true;
379 | },
380 |
381 | /**
382 | * Perform sorting only if there was a change and not already sorting.
383 | */
384 | sortIfChanged: function () {
385 | if (this.changed && !this.sorting) {
386 | this.sorting = true;
387 | this.sortAllBookmarks();
388 | }
389 |
390 | let self = this;
391 |
392 | setTimeout(function () {
393 | self.sortIfChanged();
394 | }, this.delay);
395 | },
396 |
397 | });
398 |
399 | exports.BookmarkSorter = BookmarkSorter;
400 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014-2016 Boucher, Antoni
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | "use strict";
19 |
20 | const {Cc, Ci, Cu} = require("chrome");
21 | const {defer} = require("sdk/core/promise");
22 | const {MENU, TOOLBAR, UNSORTED} = require("sdk/places/bookmarks");
23 | const {reset} = require("sdk/preferences/service");
24 | const self = require("sdk/self");
25 | const windows = require("sdk/windows").browserWindows;
26 | const windowUtils = require("sdk/window/utils");
27 | const annotationService = Cc["@mozilla.org/browser/annotation-service;1"].getService(Ci.nsIAnnotationService);
28 | const asyncHistory = Cc["@mozilla.org/browser/history;1"].getService(Ci.mozIAsyncHistory);
29 | const bookmarkService = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Ci.nsINavBookmarksService);
30 | const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
31 | const {Bookmark, Folder, Livemark, menuFolder, Separator, SmartBookmark, toolbarFolder, unsortedFolder} = require("lib/bookmarks");
32 | const descriptionAnnotation = "bookmarkProperties/description";
33 | const livemarkFeedAnnotation = "livemark/feedURI";
34 | const livemarkReadOnlyAnnotation = "placesInternal/READ_ONLY";
35 | const livemarkSiteAnnotation = "livemark/siteURI";
36 | const smartBookmarkAnnotation = "Places/SmartBookmark";
37 | const {removeDoNotSortAnnotation, removeRecursiveAnnotation, setDoNotSortAnnotation, setRecursiveAnnotation} = require("lib/annotations");
38 |
39 | Cu.import("resource://gre/modules/XPCOMUtils.jsm");
40 | XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
41 |
42 | function ignore(folder) {
43 | setDoNotSortAnnotation(folder.id);
44 | setRecursiveAnnotation(folder.id);
45 | }
46 |
47 | function sort(folder) {
48 | removeDoNotSortAnnotation(folder.id);
49 | removeRecursiveAnnotation(folder.id);
50 | }
51 |
52 | function bookmarkToString(bookmark) {
53 | return bookmark.id + ". " + bookmark.title;
54 | }
55 |
56 | /**
57 | * Assert the equality of the bookmarks array/generator.
58 | * @param {Function|Array.