diff --git a/src/extensibility/ExtensionManager.js b/src/extensibility/ExtensionManager.js
index b6cc2d8ef01..5997d482abc 100644
--- a/src/extensibility/ExtensionManager.js
+++ b/src/extensibility/ExtensionManager.js
@@ -108,6 +108,9 @@ define(function (require, exports, module) {
_idsToDisable = {};
PreferencesManager.stateManager.definePreference(FOLDER_AUTOINSTALL, "object", undefined);
+ PreferencesManager.definePreference("extensions.sort", "string", "publishedDate", {
+ description: Strings.SORT_EXTENSION_METHOD
+ });
/**
* @private
diff --git a/src/extensibility/ExtensionManagerDialog.js b/src/extensibility/ExtensionManagerDialog.js
index acfd61873fb..855b91211d4 100644
--- a/src/extensibility/ExtensionManagerDialog.js
+++ b/src/extensibility/ExtensionManagerDialog.js
@@ -41,7 +41,8 @@ define(function (require, exports, module) {
KeyEvent = require("utils/KeyEvent"),
ExtensionManager = require("extensibility/ExtensionManager"),
ExtensionManagerView = require("extensibility/ExtensionManagerView").ExtensionManagerView,
- ExtensionManagerViewModel = require("extensibility/ExtensionManagerViewModel");
+ ExtensionManagerViewModel = require("extensibility/ExtensionManagerViewModel"),
+ PreferencesManager = require("preferences/PreferencesManager");
var dialogTemplate = require("text!htmlContent/extension-manager-dialog.html");
@@ -372,6 +373,11 @@ define(function (require, exports, module) {
if (models[_activeTabIndex]) {
$modalDlg.scrollTop(models[_activeTabIndex].scrollPos || 0);
clearSearch();
+ if (_activeTabIndex === 2) {
+ $(".ext-sort-group").hide();
+ } else {
+ $(".ext-sort-group").show();
+ }
}
}
@@ -460,6 +466,18 @@ define(function (require, exports, module) {
$modalDlg.scrollTop(0);
});
}).on("click", ".search-clear", clearSearch);
+
+ // Sort the extension list based on the current selected sorting criteria
+ $dlg.on("change", ".sort-extensions", function (e) {
+ var sortBy = $(this).val();
+ PreferencesManager.set("extensions.sort", sortBy);
+ models.forEach(function (model, index) {
+ if (index <= 1) {
+ model._setSortedExtensionList(ExtensionManager.extensions, index === 1);
+ views[index].filter($(".search").val());
+ }
+ });
+ });
// Disable the search field when there are no items in the model
models.forEach(function (model, index) {
@@ -480,6 +498,11 @@ define(function (require, exports, module) {
} else { // Otherwise show the first tab
$dlg.find(".nav-tabs a:first").tab("show");
}
+ if ($activeTab.hasClass("installed")) {
+ $(".ext-sort-group").hide();
+ } else {
+ $(".ext-sort-group").show();
+ }
});
// Handle the 'Install from URL' button.
diff --git a/src/extensibility/ExtensionManagerView.js b/src/extensibility/ExtensionManagerView.js
index 0bac5cdde1a..2a981ff6c68 100644
--- a/src/extensibility/ExtensionManagerView.js
+++ b/src/extensibility/ExtensionManagerView.js
@@ -36,7 +36,8 @@ define(function (require, exports, module) {
LanguageManager = require("language/LanguageManager"),
Mustache = require("thirdparty/mustache/mustache"),
PathUtils = require("thirdparty/path-utils/path-utils"),
- itemTemplate = require("text!htmlContent/extension-manager-view-item.html");
+ itemTemplate = require("text!htmlContent/extension-manager-view-item.html"),
+ PreferencesManager = require("preferences/PreferencesManager");
/**
@@ -71,6 +72,7 @@ define(function (require, exports, module) {
this._$infoMessage = $("
")
.appendTo(this.$el).html(this.model.infoMessage);
this._$table = $("").appendTo(this.$el);
+ $(".sort-extensions").val(PreferencesManager.get("extensions.sort"));
this.model.initialize().done(function () {
self._setupEventHandlers();
@@ -248,6 +250,7 @@ define(function (require, exports, module) {
var installWarningBase = context.requiresNewer ? Strings.EXTENSION_LATEST_INCOMPATIBLE_NEWER : Strings.EXTENSION_LATEST_INCOMPATIBLE_OLDER;
context.installWarning = StringUtils.format(installWarningBase, entry.registryInfo.versions[entry.registryInfo.versions.length - 1].version, latestVerCompatInfo.compatibleVersion);
}
+ context.downloadCount = entry.registryInfo.totalDownloads;
} else {
// We should only get here when viewing the Installed tab and some extensions don't exist in the registry
// (or registry is offline). These flags *should* always be ignored in that scenario, but just in case...
diff --git a/src/extensibility/ExtensionManagerViewModel.js b/src/extensibility/ExtensionManagerViewModel.js
index 90d30fe512e..cb86d1327ee 100644
--- a/src/extensibility/ExtensionManagerViewModel.js
+++ b/src/extensibility/ExtensionManagerViewModel.js
@@ -28,10 +28,11 @@ define(function (require, exports, module) {
var _ = require("thirdparty/lodash");
- var ExtensionManager = require("extensibility/ExtensionManager"),
- registry_utils = require("extensibility/registry_utils"),
- EventDispatcher = require("utils/EventDispatcher"),
- Strings = require("strings");
+ var ExtensionManager = require("extensibility/ExtensionManager"),
+ registry_utils = require("extensibility/registry_utils"),
+ EventDispatcher = require("utils/EventDispatcher"),
+ Strings = require("strings"),
+ PreferencesManager = require("preferences/PreferencesManager");
/**
* @private
@@ -292,6 +293,20 @@ define(function (require, exports, module) {
});
};
+ ExtensionManagerViewModel.prototype._setSortedExtensionList = function (extensions, isTheme) {
+ this.filterSet = this.sortedFullSet = registry_utils.sortRegistry(extensions, "registryInfo", PreferencesManager.get("extensions.sort"))
+ .filter(function (entry) {
+ if (!isTheme) {
+ return entry.registryInfo && !entry.registryInfo.metadata.theme;
+ } else {
+ return entry.registryInfo && entry.registryInfo.metadata.theme;
+ }
+ })
+ .map(function (entry) {
+ return entry.registryInfo.metadata.name;
+ });
+ };
+
/**
* The model for the ExtensionManagerView that is responsible for handling registry-based extensions.
* This extends ExtensionManagerViewModel.
@@ -329,13 +344,7 @@ define(function (require, exports, module) {
self.extensions = ExtensionManager.extensions;
// Sort the registry by last published date and store the sorted list of IDs.
- self.sortedFullSet = registry_utils.sortRegistry(self.extensions, "registryInfo")
- .filter(function (entry) {
- return entry.registryInfo !== undefined && entry.registryInfo.metadata.theme === undefined;
- })
- .map(function (entry) {
- return entry.registryInfo.metadata.name;
- });
+ self._setSortedExtensionList(ExtensionManager.extensions, false);
self._setInitialFilter();
})
.fail(function () {
@@ -536,13 +545,7 @@ define(function (require, exports, module) {
self.extensions = ExtensionManager.extensions;
// Sort the registry by last published date and store the sorted list of IDs.
- self.sortedFullSet = registry_utils.sortRegistry(self.extensions, "registryInfo")
- .filter(function (entry) {
- return entry.registryInfo !== undefined && entry.registryInfo.metadata.theme;
- })
- .map(function (entry) {
- return entry.registryInfo.metadata.name;
- });
+ self._setSortedExtensionList(ExtensionManager.extensions, true);
self._setInitialFilter();
})
.fail(function () {
@@ -570,4 +573,4 @@ define(function (require, exports, module) {
exports.RegistryViewModel = RegistryViewModel;
exports.ThemesViewModel = ThemesViewModel;
exports.InstalledViewModel = InstalledViewModel;
-});
+});
\ No newline at end of file
diff --git a/src/extensibility/registry_utils.js b/src/extensibility/registry_utils.js
index 57be44c6e29..3ee9b76b6df 100644
--- a/src/extensibility/registry_utils.js
+++ b/src/extensibility/registry_utils.js
@@ -120,7 +120,7 @@ define(function (require, exports, module) {
* we should look at the top level of the object.
* @return {Array} Sorted array of registry entries.
*/
- exports.sortRegistry = function (registry, subkey) {
+ exports.sortRegistry = function (registry, subkey, sortBy) {
function getPublishTime(entry) {
if (entry.versions) {
return new Date(entry.versions[entry.versions.length - 1].published).getTime();
@@ -136,8 +136,16 @@ define(function (require, exports, module) {
sortedEntries.push(registry[key]);
});
sortedEntries.sort(function (entry1, entry2) {
- return getPublishTime((subkey && entry2[subkey]) || entry2) -
- getPublishTime((subkey && entry1[subkey]) || entry1);
+ if (sortBy !== "publishedDate") {
+ if (entry1.registryInfo && entry2.registryInfo) {
+ return entry2.registryInfo.totalDownloads - entry1.registryInfo.totalDownloads;
+ } else {
+ return Number.NEGATIVE_INFINITY;
+ }
+ } else {
+ return getPublishTime((subkey && entry2[subkey]) || entry2) -
+ getPublishTime((subkey && entry1[subkey]) || entry1);
+ }
});
return sortedEntries;
diff --git a/src/htmlContent/extension-manager-dialog.html b/src/htmlContent/extension-manager-dialog.html
index ebc3162bb60..53ab9562bff 100644
--- a/src/htmlContent/extension-manager-dialog.html
+++ b/src/htmlContent/extension-manager-dialog.html
@@ -9,6 +9,11 @@
{{Strings.EXTENSIONS_INSTALLED_TITLE}}
+ {{Strings.EXTENSIONS_SORT_BY}}
+
diff --git a/src/htmlContent/extension-manager-view-item.html b/src/htmlContent/extension-manager-view-item.html
index bb3782e5ed1..6b15d72bce4 100644
--- a/src/htmlContent/extension-manager-view-item.html
+++ b/src/htmlContent/extension-manager-view-item.html
@@ -8,6 +8,9 @@
{{#hasVersionInfo}}
— {{lastVersionDate}}
{{/hasVersionInfo}}
+ {{#downloadCount}}
+ {{downloadCount}}
+ {{/downloadCount}}
{{#showInstallButton}}
@@ -99,4 +102,4 @@
{{/isInstalled}}
|
-
+
\ No newline at end of file
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 21adacbbcab..6e15cfaf9f8 100644
--- a/src/nls/root/strings.js
+++ b/src/nls/root/strings.js
@@ -503,6 +503,7 @@ define({
"INSTALL_CANCELED" : "Installation canceled.",
"VIEW_COMPLETE_DESCRIPTION" : "View complete description",
"VIEW_TRUNCATED_DESCRIPTION" : "View truncated description",
+ "SORT_EXTENSION_METHOD" : "Sort Extensions using downloadCount or publishedDate",
// These must match the error codes in ExtensionsDomain.Errors.* :
"INVALID_ZIP_FILE" : "The downloaded content is not a valid zip file.",
"MISSING_PACKAGE_JSON" : "The package has no package.json file.",
@@ -580,6 +581,9 @@ define({
"EXTENSIONS_AVAILABLE_TITLE" : "Available",
"EXTENSIONS_THEMES_TITLE" : "Themes",
"EXTENSIONS_UPDATES_TITLE" : "Updates",
+ "EXTENSIONS_SORT_BY" : "Sort By",
+ "EXTENSIONS_LAST_UPDATED" : "Last Updated",
+ "EXTENSIONS_DOWNLOADS" : "Downloads",
"INLINE_EDITOR_NO_MATCHES" : "No matches available.",
"INLINE_EDITOR_HIDDEN_MATCHES" : "All matches are collapsed. Expand the files listed at right to view matches.",
diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less
index 7bed48f66ae..0fd283bc034 100644
--- a/src/styles/brackets_patterns_override.less
+++ b/src/styles/brackets_patterns_override.less
@@ -1183,6 +1183,17 @@ a[href^="http"] {
opacity: 0.3;
}
}
+ .sort-extensions-title {
+ float: left;
+ margin-right: 5px;
+ margin-top: 5px;
+ }
+ .sort-extensions {
+ float: left;
+ margin-right: 10px;
+ width: auto;
+ padding-right: 18px;
+ }
}
.modal-body {
height: 400px;
diff --git a/src/styles/images/download-icon.svg b/src/styles/images/download-icon.svg
new file mode 100644
index 00000000000..74927919666
--- /dev/null
+++ b/src/styles/images/download-icon.svg
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/test/spec/ExtensionManager-test-files/mockRegistry.json b/test/spec/ExtensionManager-test-files/mockRegistry.json
index 259639f7071..b61ec791c10 100644
--- a/test/spec/ExtensionManager-test-files/mockRegistry.json
+++ b/test/spec/ExtensionManager-test-files/mockRegistry.json
@@ -20,7 +20,8 @@
"published": "2013-04-10T18:21:27.058Z",
"brackets": ">0.20.0"
}
- ]
+ ],
+ "totalDownloads": 10
},
"long-desc-extension": {
"metadata": {
@@ -42,7 +43,8 @@
"version": "1.0.0",
"published": "2013-04-10T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 184
},
"mock-extension-1": {
"metadata": {
@@ -65,7 +67,8 @@
"version": "1.0.0",
"published": "2013-04-11T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 190
},
"mock-extension-2": {
"metadata": {
@@ -79,7 +82,8 @@
"version": "1.0.0",
"published": "2013-04-11T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 88
},
"mock-extension-3": {
"metadata": {
@@ -93,7 +97,8 @@
"version": "1.0.0",
"published": "2013-04-11T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 34
},
"mock-extension-4": {
"metadata": {
@@ -107,7 +112,8 @@
"version": "1.0.0",
"published": "2013-04-11T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 20
},
"malicious-script-extension": {
"metadata": {
@@ -122,7 +128,8 @@
"version": "1.0.0",
"published": "2013-04-10T18:26:24.956Z"
}
- ]
+ ],
+ "totalDownloads": 781
},
"select-parent": {
"metadata": {
@@ -147,7 +154,8 @@
"published": "2013-04-10T18:26:47.580Z",
"brackets": ">0.22.0"
}
- ]
+ ],
+ "totalDownloads": 101
},
"basic-valid-extension": {
"metadata": {
@@ -165,7 +173,8 @@
"version": "3.0.0",
"published": "2013-04-10T18:29:11.907Z"
}
- ]
+ ],
+ "totalDownloads": 130
},
"everyscrub": {
"metadata": {
@@ -179,6 +188,7 @@
"version": "0.23.0",
"published": "2013-04-11T17:22:38.033Z"
}
- ]
+ ],
+ "totalDownloads": 101
}
}
\ No newline at end of file
diff --git a/test/spec/ExtensionManager-test-files/mockRegistryForSearch.json b/test/spec/ExtensionManager-test-files/mockRegistryForSearch.json
index a58761d98e8..2e64ad07643 100644
--- a/test/spec/ExtensionManager-test-files/mockRegistryForSearch.json
+++ b/test/spec/ExtensionManager-test-files/mockRegistryForSearch.json
@@ -19,7 +19,8 @@
"published": "2013-04-10T18:21:27.058Z",
"brackets": ">0.20.0"
}
- ]
+ ],
+ "totalDownloads": 10
},
"item-2": {
"metadata": {
@@ -36,7 +37,8 @@
"version": "1.0.0",
"published": "2013-04-10T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 5
},
"item-3": {
"metadata": {
@@ -53,7 +55,8 @@
"version": "1.0.0",
"published": "2013-04-10T13:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 11
},
"item-4": {
"metadata": {
@@ -71,7 +74,8 @@
"version": "1.0.0",
"published": "2013-04-10T15:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 12
},
"item-5": {
"metadata": {
@@ -89,7 +93,8 @@
"version": "1.0.0",
"published": "2013-04-16T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 2
},
"item-6": {
"metadata": {
@@ -111,6 +116,7 @@
"version": "1.0.0",
"published": "2013-04-15T18:26:20.553Z"
}
- ]
+ ],
+ "totalDownloads": 100
}
}
\ No newline at end of file
diff --git a/test/spec/ExtensionManager-test-files/mockRegistryThemes.json b/test/spec/ExtensionManager-test-files/mockRegistryThemes.json
index 6a62a6636af..35386535b2a 100644
--- a/test/spec/ExtensionManager-test-files/mockRegistryThemes.json
+++ b/test/spec/ExtensionManager-test-files/mockRegistryThemes.json
@@ -188,7 +188,8 @@
"version": "0.0.1",
"published": "2014-08-31T04:05:47.778Z"
}
- ]
+ ],
+ "totalDownloads": 80
},
"theme-2": {
"metadata": {
@@ -205,6 +206,7 @@
"version": "1.0.1",
"published": "2014-07-31T04:05:47.778Z"
}
- ]
+ ],
+ "totalDownloads": 99
}
}
diff --git a/test/spec/ExtensionManager-test.js b/test/spec/ExtensionManager-test.js
index 52bc8df2e8b..13682a7cee3 100644
--- a/test/spec/ExtensionManager-test.js
+++ b/test/spec/ExtensionManager-test.js
@@ -48,6 +48,7 @@ define(function (require, exports, module) {
Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
LocalizationUtils = require("utils/LocalizationUtils"),
+ PreferencesManager = require("preferences/PreferencesManager"),
mockRegistryText = require("text!spec/ExtensionManager-test-files/mockRegistry.json"),
mockRegistryThemesText = require("text!spec/ExtensionManager-test-files/mockRegistryThemes.json"),
mockRegistryForSearch = require("text!spec/ExtensionManager-test-files/mockRegistryForSearch.json"),
@@ -734,7 +735,15 @@ define(function (require, exports, module) {
expect(model.extensions).toEqual(ExtensionManager.extensions);
});
+ it("should start with the full set sorted in reverse download count order", function () {
+ PreferencesManager.set("extensions.sort", "downloadCount");
+ model._setSortedExtensionList(ExtensionManager.extensions, false);
+ expect(model.filterSet).toEqual(["item-6", "item-4", "item-3", "find-uniq1-in-name", "item-2", "item-5"]);
+ });
+
it("should start with the full set sorted in reverse publish date order", function () {
+ PreferencesManager.set("extensions.sort", "publishedDate");
+ model._setSortedExtensionList(ExtensionManager.extensions, false);
expect(model.filterSet).toEqual(["item-5", "item-6", "item-2", "find-uniq1-in-name", "item-4", "item-3"]);
});
@@ -829,6 +838,12 @@ define(function (require, exports, module) {
it("should start with the full set sorted in reverse publish date order", function () {
expect(model.filterSet).toEqual(["theme-1", "theme-2"]);
});
+
+ it("should start with the full set sorted in reverse download count order", function () {
+ PreferencesManager.set("extensions.sort", "downloadCount");
+ model._setSortedExtensionList(ExtensionManager.extensions, true);
+ expect(model.filterSet).toEqual(["theme-2", "theme-1"]);
+ });
});
@@ -1282,7 +1297,8 @@ define(function (require, exports, module) {
// Simple fields
[item.metadata.version,
- item.metadata.author && item.metadata.author.name]
+ item.metadata.author && item.metadata.author.name,
+ item.totalDownloads]
.forEach(function (value) {
if (value) {
expect(view).toHaveText(value);