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;
The base model for the ExtensionManagerView. Keeps track of the extensions that are currently visible and manages sorting/filtering them. Must be disposed with dispose() when done. Events:
function ExtensionManagerViewModel() {
this._handleStatusChange = this._handleStatusChange.bind(this);
// Listen for extension status changes.
ExtensionManager
.on("statusChange." + this.source, this._handleStatusChange)
.on("registryUpdate." + this.source, this._handleStatusChange);
}
EventDispatcher.makeEventDispatcher(ExtensionManagerViewModel.prototype);
ExtensionManagerViewModel.prototype._entryMatchesQuery = function (entry, query) {
return query === "" ||
_searchFields.some(function (fieldSpec) {
var i, cur = entry;
for (i = 0; i < fieldSpec.length; i++) {
// Recurse downward through the specified fields to the leaf value.
cur = cur[fieldSpec[i]];
if (!cur) {
return false;
}
}
// If the leaf value is an array (like keywords), search each item, otherwise
// just search in the string.
if (Array.isArray(cur)) {
return cur.some(function (keyword) {
return keyword.toLowerCase().indexOf(query) !== -1;
});
} else if (fieldSpec[fieldSpec.length - 1] === "owner") {
// Special handling: ignore the authentication source when querying,
// since it's not useful to search on
var components = cur.split(":");
if (components[1].toLowerCase().indexOf(query) !== -1) {
return true;
}
} else if (cur.toLowerCase().indexOf(query) !== -1) {
return true;
}
});
};
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;
});
};
ExtensionManagerViewModel.prototype._getEntry = function (id) {
return null;
};
ExtensionManagerViewModel.prototype._handleStatusChange = function (e, id) {
this.trigger("change", id);
};
ExtensionManagerViewModel.prototype._setInitialFilter = function () {
// Initial filtered list is the same as the sorted list.
this.filterSet = _.clone(this.sortedFullSet);
this.trigger("filter");
};
ExtensionManagerViewModel.prototype._updateMessage = function () {
if (this._initializeFromSourcePromise && this._initializeFromSourcePromise.state() === "rejected") {
this.message = Strings.EXTENSION_MANAGER_ERROR_LOAD;
} else if (this.filterSet && this.filterSet.length === 0) {
this.message = this.sortedFullSet && this.sortedFullSet.length ? Strings.NO_EXTENSION_MATCHES : Strings.NO_EXTENSIONS;
} else {
this.message = null;
}
};
Unregisters listeners when we're done.
ExtensionManagerViewModel.prototype.dispose = function () {
ExtensionManager.off("." + this.source);
};
ExtensionManagerViewModel.prototype.filter = function (query, force) {
var self = this, initialList;
if (!force && this._lastQuery && query.indexOf(this._lastQuery) === 0) {
// This is the old query with some new letters added, so we know we can just
// search in the current filter set. (This is true even if query has spaces).
initialList = this.filterSet;
} else {
// This is a new query, so start with the full list.
initialList = this.sortedFullSet;
}
var keywords = query.toLowerCase().split(/\s+/);
// Takes 'extensionList' and returns a version filtered to only those that match 'keyword'
function filterForKeyword(extensionList, word) {
var filteredList = [];
extensionList.forEach(function (id) {
var entry = self._getEntry(id);
if (entry && self._entryMatchesQuery(entry, word)) {
filteredList.push(id);
}
});
return filteredList;
}
// "AND" the keywords together: successively filter down the result set by each keyword in turn
var i, currentList = initialList;
for (i = 0; i < keywords.length; i++) {
currentList = filterForKeyword(currentList, keywords[i]);
}
this._lastQuery = query;
this.filterSet = currentList;
this._updateMessage();
this.trigger("filter");
};
The model for the ExtensionManagerView that is responsible for handling registry-based extensions. This extends ExtensionManagerViewModel. Must be disposed with dispose() when done.
Events:
function RegistryViewModel() {
ExtensionManagerViewModel.call(this);
this.infoMessage = Strings.REGISTRY_SANITY_CHECK_WARNING;
}
RegistryViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype);
RegistryViewModel.prototype.constructor = RegistryViewModel;
RegistryViewModel.prototype._getEntry = function (id) {
var entry = this.extensions[id];
if (entry) {
return entry.registryInfo;
}
return entry;
};
Initializes the model from the remote extension registry.
RegistryViewModel.prototype._initializeFromSource = function () {
var self = this;
return ExtensionManager.downloadRegistry()
.done(function () {
self.extensions = ExtensionManager.extensions;
// Sort the registry by last published date and store the sorted list of IDs.
self._setSortedExtensionList(ExtensionManager.extensions, false);
self._setInitialFilter();
})
.fail(function () {
self.extensions = [];
self.sortedFullSet = [];
self.filterSet = [];
});
};
The model for the ExtensionManagerView that is responsible for handling previously-installed extensions. This extends ExtensionManagerViewModel. Must be disposed with dispose() when done.
Events:
function InstalledViewModel() {
ExtensionManagerViewModel.call(this);
// when registry is downloaded, sort extensions again - those with updates will be before others
var self = this;
ExtensionManager.on("registryDownload." + this.source, function () {
self._sortFullSet();
self._setInitialFilter();
});
}
InstalledViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype);
InstalledViewModel.prototype.constructor = InstalledViewModel;
InstalledViewModel.prototype._countUpdates = function () {
var self = this;
this.notifyCount = 0;
this.sortedFullSet.forEach(function (key) {
if (self.extensions[key].installInfo.updateCompatible && !ExtensionManager.isMarkedForUpdate(key)) {
self.notifyCount++;
}
});
};
InstalledViewModel.prototype._getEntry = function (id) {
var entry = this.extensions[id];
if (entry) {
return entry.installInfo;
}
return entry;
};
InstalledViewModel.prototype._handleStatusChange = function (e, id) {
var index = this.sortedFullSet.indexOf(id),
refilter = false;
if (index !== -1 && !this.extensions[id].installInfo) {
// This was in our set, but was uninstalled. Remove it.
this.sortedFullSet.splice(index, 1);
this._countUpdates(); // may also affect update count
refilter = true;
} else if (index === -1 && this.extensions[id].installInfo) {
// This was not in our set, but is now installed. Add it and resort.
this.sortedFullSet.push(id);
this._sortFullSet();
refilter = true;
}
if (refilter) {
this.filter(this._lastQuery || "", true);
}
if (this.extensions[id].installInfo) {
// If our count of available updates may have been affected, re-count
this._countUpdates();
}
ExtensionManagerViewModel.prototype._handleStatusChange.call(this, e, id);
};
Initializes the model from the set of locally installed extensions, sorted alphabetically by id (or name of the extension folder for legacy extensions).
InstalledViewModel.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();
this._countUpdates();
return new $.Deferred().resolve().promise();
};
InstalledViewModel.prototype._sortFullSet = function () {
var self = this;
this.sortedFullSet = this.sortedFullSet.sort(function (key1, key2) {
// before sorting by name, put first extensions that have updates
var ua1 = self.extensions[key1].installInfo.updateAvailable,
ua2 = self.extensions[key2].installInfo.updateAvailable;
if (ua1 && !ua2) {
return -1;
} else if (!ua1 && ua2) {
return 1;
}
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);
});
};
The model for the ExtensionManagerView that is responsible for handling registry-based theme extensions. This extends ExtensionManagerViewModel. Must be disposed with dispose() when done.
Events:
function ThemesViewModel() {
ExtensionManagerViewModel.call(this);
}
// Inheritance setup
ThemesViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype);
ThemesViewModel.prototype.constructor = ThemesViewModel;
ThemesViewModel.prototype._getEntry = function (id) {
var entry = this.extensions[id];
if (entry) {
return entry.registryInfo;
}
return entry;
};
exports.RegistryViewModel = RegistryViewModel;
exports.ThemesViewModel = ThemesViewModel;
exports.InstalledViewModel = InstalledViewModel;
exports.DefaultViewModel = DefaultViewModel;
});
Initializes the model from the remote extension registry.
ThemesViewModel.prototype._initializeFromSource = function () {
var self = this;
return ExtensionManager.downloadRegistry()
.done(function () {
self.extensions = ExtensionManager.extensions;
// Sort the registry by last published date and store the sorted list of IDs.
self._setSortedExtensionList(ExtensionManager.extensions, true);
self._setInitialFilter();
})
.fail(function () {
self.extensions = [];
self.sortedFullSet = [];
self.filterSet = [];
});
};