From 6ce6e8a1009159bd21378d70610f222c07ebac80 Mon Sep 17 00:00:00 2001 From: Martin Zagora Date: Thu, 2 Mar 2017 11:52:15 +1100 Subject: [PATCH 1/7] add a tab with default extensions to extension manager --- src/extensibility/ExtensionManagerDialog.js | 1 + src/extensibility/ExtensionManagerView.js | 7 +- .../ExtensionManagerViewModel.js | 66 +++++++++++++++++++ src/htmlContent/extension-manager-dialog.html | 16 ++++- src/nls/root/strings.js | 1 + 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/extensibility/ExtensionManagerDialog.js b/src/extensibility/ExtensionManagerDialog.js index 855b91211d4..c299cea9d37 100644 --- a/src/extensibility/ExtensionManagerDialog.js +++ b/src/extensibility/ExtensionManagerDialog.js @@ -321,6 +321,7 @@ define(function (require, exports, module) { } models.push(new ExtensionManagerViewModel.InstalledViewModel()); + models.push(new ExtensionManagerViewModel.DefaultViewModel()); function updateSearchDisabled() { var model = models[_activeTabIndex], diff --git a/src/extensibility/ExtensionManagerView.js b/src/extensibility/ExtensionManagerView.js index 2a981ff6c68..ad480bf9557 100644 --- a/src/extensibility/ExtensionManagerView.js +++ b/src/extensibility/ExtensionManagerView.js @@ -336,10 +336,9 @@ define(function (require, exports, module) { context.removalAllowed = this.model.source === "installed" && !context.failedToStart && !hasPendingAction; - context.disablingAllowed = this.model.source === "installed" && - !context.disabled && !hasPendingAction; - context.enablingAllowed = this.model.source === "installed" && - context.disabled && !hasPendingAction; + var isDefaultOrInstalled = this.model.source === "default" || this.model.source === "installed"; + context.disablingAllowed = isDefaultOrInstalled && !context.disabled && !hasPendingAction; + context.enablingAllowed = isDefaultOrInstalled && context.disabled && !hasPendingAction; // Copy over helper functions that we share with the registry app. ["lastVersionDate", "authorInfo"].forEach(function (helper) { diff --git a/src/extensibility/ExtensionManagerViewModel.js b/src/extensibility/ExtensionManagerViewModel.js index cb86d1327ee..73577e8fa90 100644 --- a/src/extensibility/ExtensionManagerViewModel.js +++ b/src/extensibility/ExtensionManagerViewModel.js @@ -80,6 +80,12 @@ define(function (require, exports, module) { */ ExtensionManagerViewModel.prototype.SOURCE_INSTALLED = "installed"; + /** + * @type {string} + * Constant indicating that this model/view should initialize from the list of default bundled extensions. + */ + ExtensionManagerViewModel.prototype.SOURCE_DEFAULT = "default"; + /** * @type {Object} * The current set of extensions managed by this model. Same as ExtensionManager.extensions. @@ -509,6 +515,65 @@ define(function (require, exports, module) { return entry; }; + /** + * Model for displaying default extensions that come bundled with Brackets + */ + function DefaultViewModel() { + ExtensionManagerViewModel.call(this); + } + + // Inheritance setup + DefaultViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); + DefaultViewModel.prototype.constructor = DefaultViewModel; + + /** + * Add SOURCE_DEFAULT to DefaultViewModel + */ + DefaultViewModel.prototype.source = ExtensionManagerViewModel.prototype.SOURCE_DEFAULT; + + /** + * Initializes the model from the set of default extensions, sorted alphabetically by id + * @return {$.Promise} a promise that's resolved when we're done initializing. + */ + DefaultViewModel.prototype._initializeFromSource = function () { + var self = this; + this.extensions = ExtensionManager.extensions; + this.sortedFullSet = Object.keys(this.extensions) + .filter(function (key) { + return self.extensions[key].installInfo && + self.extensions[key].installInfo.locationType === ExtensionManager.LOCATION_DEFAULT; + }); + this._sortFullSet(); + this._setInitialFilter(); + return new $.Deferred().resolve().promise(); + }; + + /** + * @private + * Re-sorts the current full set + */ + DefaultViewModel.prototype._sortFullSet = function () { + var self = this; + this.sortedFullSet = this.sortedFullSet.sort(function (key1, key2) { + var metadata1 = self.extensions[key1].installInfo.metadata, + metadata2 = self.extensions[key2].installInfo.metadata, + id1 = (metadata1.title || metadata1.name).toLocaleLowerCase(), + id2 = (metadata2.title || metadata2.name).toLocaleLowerCase(); + return id1.localeCompare(id2); + }); + }; + + /** + * @private + * Finds the default extension metadata by id. If there is no default extension matching the given id, + * this returns `null`. + * @param {string} id of the theme extension + * @return {Object?} extension metadata or null if there's no matching extension + */ + DefaultViewModel.prototype._getEntry = function (id) { + return this.extensions[id] ? this.extensions[id].installInfo : null; + }; + /** * The model for the ExtensionManagerView that is responsible for handling registry-based theme extensions. * This extends ExtensionManagerViewModel. @@ -573,4 +638,5 @@ define(function (require, exports, module) { exports.RegistryViewModel = RegistryViewModel; exports.ThemesViewModel = ThemesViewModel; exports.InstalledViewModel = InstalledViewModel; + exports.DefaultViewModel = DefaultViewModel; }); \ No newline at end of file diff --git a/src/htmlContent/extension-manager-dialog.html b/src/htmlContent/extension-manager-dialog.html index 53ab9562bff..8533f0c92de 100644 --- a/src/htmlContent/extension-manager-dialog.html +++ b/src/htmlContent/extension-manager-dialog.html @@ -6,7 +6,21 @@

  • {{Strings.EXTENSIONS_THEMES_TITLE}}
  • {{/showRegistry}} -

  • {{Strings.EXTENSIONS_INSTALLED_TITLE}}
  • +
  • + + + +
    + {{Strings.EXTENSIONS_INSTALLED_TITLE}} +
    +
  • +
  • + + +
    + {{Strings.EXTENSIONS_DEFAULT_TITLE}} +
    +
  • {{Strings.EXTENSIONS_SORT_BY}} diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 6e15cfaf9f8..f65fc6307f7 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -578,6 +578,7 @@ define({ "NO_EXTENSION_MATCHES" : "No extensions match your search.", "REGISTRY_SANITY_CHECK_WARNING" : "NOTE: These extensions may come from different authors than {APP_NAME} itself. Extensions are not reviewed and have full local privileges. Be cautious when installing extensions from an unknown source.", "EXTENSIONS_INSTALLED_TITLE" : "Installed", + "EXTENSIONS_DEFAULT_TITLE" : "Default", "EXTENSIONS_AVAILABLE_TITLE" : "Available", "EXTENSIONS_THEMES_TITLE" : "Themes", "EXTENSIONS_UPDATES_TITLE" : "Updates", From 7d54c14248c5c499a365462f719ccd0446bc3e9a Mon Sep 17 00:00:00 2001 From: Martin Zagora Date: Fri, 3 Mar 2017 09:29:54 +1100 Subject: [PATCH 2/7] implement getDefaultExtensionPath function --- src/utils/ExtensionLoader.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/utils/ExtensionLoader.js b/src/utils/ExtensionLoader.js index d84c41b8d0e..636e5fdf513 100644 --- a/src/utils/ExtensionLoader.js +++ b/src/utils/ExtensionLoader.js @@ -71,6 +71,13 @@ define(function (require, exports, module) { globalPaths[key] = PathUtils.makePathAbsolute(srcPath + "/" + globalPaths[key]); }); + /** + * Returns the full path to the default extensions directory. + */ + function getDefaultExtensionPath() { + return FileUtils.getNativeBracketsDirectoryPath() + "/extensions/default"; + } + /** * Returns the full path of the default user extensions directory. This is in the users * application support directory, which is typically @@ -405,7 +412,11 @@ define(function (require, exports, module) { if (params.get("reloadWithoutUserExts") === "true") { paths = ["default"]; } else { - paths = ["default", "dev", getUserExtensionPath()]; + paths = [ + getDefaultExtensionPath(), + "dev", + getUserExtensionPath() + ]; } } @@ -453,6 +464,7 @@ define(function (require, exports, module) { // public API exports.init = init; + exports.getDefaultExtensionPath = getDefaultExtensionPath; exports.getUserExtensionPath = getUserExtensionPath; exports.getRequireContextForExtension = getRequireContextForExtension; exports.loadExtension = loadExtension; From c06fe29faff67a5dacb76fe715a09b8190cb8d05 Mon Sep 17 00:00:00 2001 From: Martin Zagora Date: Fri, 3 Mar 2017 10:30:46 +1100 Subject: [PATCH 3/7] enable and disable default extensions through a preference --- src/extensibility/Package.js | 56 ++++++++++++++++++++++++++++++------ src/nls/root/strings.js | 1 + 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/extensibility/Package.js b/src/extensibility/Package.js index 721ef3a2d05..d49647748a1 100644 --- a/src/extensibility/Package.js +++ b/src/extensibility/Package.js @@ -43,6 +43,11 @@ define(function (require, exports, module) { description: Strings.DESCRIPTION_PROXY }); + var PREF_EXTENSIONS_DEFAULT_DISABLED = "extensions.default.disabled"; + PreferencesManager.definePreference(PREF_EXTENSIONS_DEFAULT_DISABLED, "array", [], { + description: Strings.DESCRIPTION_DISABLED_DEFAULT_EXTENSIONS + }); + var Errors = { ERROR_LOADING: "ERROR_LOADING", MALFORMED_URL: "MALFORMED_URL", @@ -436,6 +441,23 @@ define(function (require, exports, module) { }); } + /** + * This function manages the PREF_EXTENSIONS_DEFAULT_DISABLED preference + * holding an array of default extension paths that should not be loaded + * on Brackets startup + */ + function toggleDefaultExtension(path, enabled) { + var arr = PreferencesManager.get(PREF_EXTENSIONS_DEFAULT_DISABLED); + if (!Array.isArray(arr)) { arr = []; } + var io = arr.indexOf(path); + if (enabled === true && io !== -1) { + arr.splice(io, 1); + } else if (enabled === false && io === -1) { + arr.push(path); + } + PreferencesManager.set(PREF_EXTENSIONS_DEFAULT_DISABLED, arr); + } + /** * Disables the extension at the given path. * @@ -446,12 +468,19 @@ define(function (require, exports, module) { function disable(path) { var result = new $.Deferred(), file = FileSystem.getFileForPath(path + "/.disabled"); + + var defaultExtensionPath = ExtensionLoader.getDefaultExtensionPath(); + if (file.fullPath.indexOf(defaultExtensionPath) === 0) { + toggleDefaultExtension(path, false); + result.resolve(); + return result.promise(); + } + file.write("", function (err) { if (err) { - result.reject(err); - } else { - result.resolve(); + return result.reject(err); } + result.resolve(); }); return result.promise(); } @@ -466,14 +495,25 @@ define(function (require, exports, module) { function enable(path) { var result = new $.Deferred(), file = FileSystem.getFileForPath(path + "/.disabled"); - file.unlink(function (err) { - if (err) { - result.reject(err); - return; - } + + function afterEnable() { ExtensionLoader.loadExtension(FileUtils.getBaseName(path), { baseUrl: path }, "main") .done(result.resolve) .fail(result.reject); + } + + var defaultExtensionPath = ExtensionLoader.getDefaultExtensionPath(); + if (file.fullPath.indexOf(defaultExtensionPath) === 0) { + toggleDefaultExtension(path, true); + afterEnable(); + return result.promise(); + } + + file.unlink(function (err) { + if (err) { + return result.reject(err); + } + afterEnable(); }); return result.promise(); } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index f65fc6307f7..8f1e88232ba 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -702,6 +702,7 @@ define({ "DESCRIPTION_CODE_FOLDING_MIN_FOLD_SIZE" : "Minimum lines before a collapsible section icon appears", "DESCRIPTION_CODE_FOLDING_SAVE_FOLD_STATES" : "true to remember collapsed sections if you close and reopen a file or project", "DESCRIPTION_CODE_FOLDING_MAKE_SELECTIONS_FOLDABLE": "true to enable code folding on selected text in the editor", + "DESCRIPTION_DISABLED_DEFAULT_EXTENSIONS" : "Default extensions that are disabled", "DESCRIPTION_ATTR_HINTS" : "Enable/disable HTML attribute hints", "DESCRIPTION_CSS_PROP_HINTS" : "Enable/disable CSS/LESS/SCSS property hints", "DESCRIPTION_JS_HINTS" : "Enable/disable JavaScript code hints", From 63ffc99ccfd7f4bdb6372e1dea53e6f73bca0ff9 Mon Sep 17 00:00:00 2001 From: Martin Zagora Date: Fri, 3 Mar 2017 10:32:09 +1100 Subject: [PATCH 4/7] use the preference when attempting to load an extension --- src/utils/ExtensionUtils.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/utils/ExtensionUtils.js b/src/utils/ExtensionUtils.js index c5c4c4d6a5f..b4ba8231c5d 100644 --- a/src/utils/ExtensionUtils.js +++ b/src/utils/ExtensionUtils.js @@ -29,10 +29,11 @@ define(function (require, exports, module) { "use strict"; - var Async = require("utils/Async"), - FileSystem = require("filesystem/FileSystem"), - FileUtils = require("file/FileUtils"), - PathUtils = require("thirdparty/path-utils/path-utils"); + var Async = require("utils/Async"), + FileSystem = require("filesystem/FileSystem"), + FileUtils = require("file/FileUtils"), + PathUtils = require("thirdparty/path-utils/path-utils"), + PreferencesManager = require("preferences/PreferencesManager"); /** * Appends a