www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit 455eb68c20e0c93a600b3962736b956cc32ecf29
parent ca56fbbb2bb5cc6a7d45bf730bfe165260a06b26
Author: Dan Stillman <dstillman@zotero.org>
Date:   Mon, 25 Jul 2011 21:27:32 +0000

Persist open/close state of source list (libraries, collections, etc.)


Diffstat:
Mchrome/content/zotero/xpcom/collectionTreeView.js | 401++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mchrome/content/zotero/xpcom/data/collection.js | 5+++--
Mchrome/content/zotero/xpcom/data/group.js | 17++++++++++++++++-
Mchrome/content/zotero/xpcom/search.js | 7+++++++
Mchrome/content/zotero/zoteroPane.js | 20+++++++++++++-------
5 files changed, 282 insertions(+), 168 deletions(-)

diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -36,11 +36,15 @@ */ Zotero.CollectionTreeView = function() { - this._treebox = null; this.itemToSelect = null; + this.hideSources = []; + + this._treebox = null; this._highlightedRows = {}; this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'bucket']); - this.hideSources = []; + this._containerState = {}; + this._duplicatesLibraries = []; + this._unfiledLibraries = []; } /* @@ -71,7 +75,15 @@ Zotero.CollectionTreeView.prototype.setTree = function(treebox) } }, false); - this.refresh(); + try { + this.refresh(); + } + // Tree errors don't get caught by default + catch (e) { + Zotero.debug(e); + Components.utils.reportError(e); + throw (e); + } this.selection.currentColumn = this._treebox.columns.getFirstColumn(); @@ -107,86 +119,46 @@ Zotero.CollectionTreeView.prototype.refresh = function() this._dataItems = []; this.rowCount = 0; + try { + this._containerState = JSON.parse(Zotero.Prefs.get("sourceList.persist")); + } + catch (e) { + this._containerState = {}; + } + if (this.hideSources.indexOf('duplicates') == -1) { try { - var duplicateLibraries = Zotero.Prefs.get('duplicateLibraries').split(','); + this._duplicateLibraries = Zotero.Prefs.get('duplicateLibraries').split(','); } catch (e) { // Add to personal library by default Zotero.Prefs.set('duplicateLibraries', '0'); - duplicateLibraries = ['0']; + this._duplicateLibraries = ['0']; } } try { - var unfiledLibraries = Zotero.Prefs.get('unfiledLibraries').split(','); + this._unfiledLibraries = Zotero.Prefs.get('unfiledLibraries').split(','); } catch (e) { // Add to personal library by default Zotero.Prefs.set('unfiledLibraries', '0'); - unfiledLibraries = ['0']; + this._unfiledLibraries = ['0']; } var self = this; var library = { id: null, - libraryID: null, - expand: function () { - var newRows = 0; - - var collections = Zotero.getCollections(); - for (var i=0; i<collections.length; i++) { - // Skip group collections - if (collections[i].libraryID) { - continue; - } - self._showItem(new Zotero.ItemGroup('collection', collections[i]), 1, newRows+1); - newRows++; - } - - var savedSearches = Zotero.Searches.getAll(); - if (savedSearches) { - for (var i=0; i<savedSearches.length; i++) { - self._showItem(new Zotero.ItemGroup('search', savedSearches[i]), 1, newRows+1); - newRows++; - } - } - - // Duplicate items - if (self.hideSources.indexOf('duplicates') == -1 && duplicateLibraries.indexOf('0') != -1) { - var d = new Zotero.Duplicates(0); - self._showItem(new Zotero.ItemGroup('duplicates', d), 1, newRows+1); - newRows++; - } - - // Unfiled items - if (unfiledLibraries.indexOf('0') != -1) { - var s = new Zotero.Search; - s.name = Zotero.getString('pane.collections.unfiled'); - s.addCondition('libraryID', 'is', null); - s.addCondition('unfiled', 'true'); - self._showItem(new Zotero.ItemGroup('unfiled', s), 1, newRows+1); - newRows++; - } - - if (self.hideSources.indexOf('trash') == -1) { - var deletedItems = Zotero.Items.getDeleted(); - if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) { - self._showItem(new Zotero.ItemGroup('trash', false), 1, newRows+1); - newRows++; - } - self.trashNotEmpty = !!deletedItems; - } - - return newRows; - } + libraryID: null }; - this._showItem(new Zotero.ItemGroup('library', library), 0, 1, 1); // itemgroup ref, level, beforeRow, startOpen - library.expand(); + + // itemgroup, level, beforeRow, startOpen + this._showRow(new Zotero.ItemGroup('library', library), 0, 1); + this._expandRow(0); var groups = Zotero.Groups.getAll(); if (groups.length) { - this._showItem(new Zotero.ItemGroup('separator', false)); + this._showRow(new Zotero.ItemGroup('separator', false)); var header = { id: "group-libraries-header", label: "Group Libraries", // TODO: localize @@ -195,71 +167,36 @@ Zotero.CollectionTreeView.prototype.refresh = function() var groups = Zotero.Groups.getAll(); } - for (var i=0; i<groups.length; i++) { - var startOpen = groups[i].hasCollections(); - - self._showItem(new Zotero.ItemGroup('group', groups[i]), 1, null, startOpen); - - var newRows = 0; - - // Add group collections - var collections = groups[i].getCollections(); - for (var j=0; j<collections.length; j++) { - self._showItem(new Zotero.ItemGroup('collection', collections[j]), 2); - newRows++; - } - - // Add group searches - var savedSearches = Zotero.Searches.getAll(groups[i].libraryID); - if (savedSearches) { - for (var j=0; j<savedSearches.length; j++) { - self._showItem(new Zotero.ItemGroup('search', savedSearches[j]), 2); - newRows++; - } - } - - // Duplicate items - if (self.hideSources.indexOf('duplicates') == -1 - && duplicateLibraries.indexOf(groups[i].libraryID + '') != -1) { - var d = new Zotero.Duplicates(groups[i].libraryID); - self._showItem(new Zotero.ItemGroup('duplicates', d), 2); - newRows++; - } - - // Unfiled items - if (unfiledLibraries.indexOf(groups[i].libraryID + '') != -1) { - var s = new Zotero.Search; - s.libraryID = groups[i].libraryID; - s.name = Zotero.getString('pane.collections.unfiled'); - s.addCondition('libraryID', 'is', groups[i].libraryID); - s.addCondition('unfiled', 'true'); - self._showItem(new Zotero.ItemGroup('unfiled', s), 2); - newRows++; - } + for (var i = 0, len = groups.length; i < len; i++) { + var row = self._showRow(new Zotero.ItemGroup('group', groups[i]), 1); + self._expandRow(row); } } - }; - this._showItem(new Zotero.ItemGroup('header', header), null, null, true); - header.expand(groups); + } + var row = this._showRow(new Zotero.ItemGroup('header', header)); + if (this._containerState.HG) { + this._dataItems[row][1] = true; + header.expand(groups); + } } var shares = Zotero.Zeroconf.instances; if (shares.length) { - this._showItem(new Zotero.ItemGroup('separator', false)); + this._showRow(new Zotero.ItemGroup('separator', false)); for each(var share in shares) { - this._showItem(new Zotero.ItemGroup('share', share)); + this._showRow(new Zotero.ItemGroup('share', share)); } } if (this.hideSources.indexOf('commons') == -1 && Zotero.Commons.enabled) { - this._showItem(new Zotero.ItemGroup('separator', false)); + this._showRow(new Zotero.ItemGroup('separator', false)); var header = { id: "commons-header", label: "Commons", // TODO: localize expand: function (buckets) { var show = function (buckets) { for each(var bucket in buckets) { - self._showItem(new Zotero.ItemGroup('bucket', bucket), 1); + self._showRow(new Zotero.ItemGroup('bucket', bucket), 1); } } if (buckets) { @@ -270,7 +207,7 @@ Zotero.CollectionTreeView.prototype.refresh = function() } } }; - this._showItem(new Zotero.ItemGroup('header', header), null, null, commonsExpand); + this._showRow(new Zotero.ItemGroup('header', header), null, null, commonsExpand); if (commonsExpand) { header.expand(); } @@ -292,6 +229,7 @@ Zotero.CollectionTreeView.prototype.refresh = function() } } + /* * Redisplay everything */ @@ -566,7 +504,12 @@ Zotero.CollectionTreeView.prototype.isContainerEmpty = function(row) return true; } if (itemGroup.isGroup()) { - return !itemGroup.ref.hasCollections(); + var libraryID = itemGroup.ref.libraryID; + + return !itemGroup.ref.hasCollections() + && !itemGroup.ref.hasSearches() + && this._duplicateLibraries.indexOf(libraryID + '') == -1 + && this._unfiledLibraries.indexOf(libraryID + '') == -1; } if (itemGroup.isCollection()) { return !itemGroup.ref.hasChildCollections(); @@ -622,36 +565,8 @@ Zotero.CollectionTreeView.prototype.toggleOpenState = function(row) if (itemGroup.type == 'header') { itemGroup.ref.expand(); } - else if(itemGroup.type == 'bucket') { - } - else { - if (itemGroup.isLibrary()) { - count = itemGroup.ref.expand(); - } - else { - if (itemGroup.isGroup()) { - var collections = itemGroup.ref.getCollections(); - } - else { - var collections = Zotero.getCollections(itemGroup.ref.id); - } - // Add child collections - for (var i=0; i<collections.length; i++) { - this._showItem(new Zotero.ItemGroup('collection', collections[i]), thisLevel + 1, row + count + 1); - count++; - } - - // Add group searches - if (itemGroup.isGroup()) { - var savedSearches = Zotero.Searches.getAll(itemGroup.ref.libraryID); - if (savedSearches) { - for (var i=0; i<savedSearches.length; i++) { - this._showItem(new Zotero.ItemGroup('search', savedSearches[i]), thisLevel + 1, row + count + 1); - count; - } - } - } - } + else if (itemGroup.isLibrary(true) || itemGroup.isCollection()) { + this._expandRow(row, true); } } this._dataItems[row][1] = !this._dataItems[row][1]; //toggle container open value @@ -660,6 +575,7 @@ Zotero.CollectionTreeView.prototype.toggleOpenState = function(row) this._treebox.invalidateRow(row); this._treebox.endUpdateBatch(); this._refreshHashMap(); + this._rememberOpenStates(); } @@ -755,8 +671,16 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) { } // Find library - for (var i=0, rows=this.rowCount; i<rows; i++) { + for (var i = 0; i < this.rowCount; i++) { var itemGroup = this._getItemAtRow(i); + + // If group header is closed, open it + if (itemGroup.isHeader() && itemGroup.ref.id == 'group-libraries-header' + && !this.isContainerOpen(i)) { + this.toggleOpenState(i); + continue; + } + if (itemGroup.ref && itemGroup.ref.libraryID == libraryID) { this.selection.select(i); return true; @@ -772,7 +696,7 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) { */ Zotero.CollectionTreeView.prototype.getLastViewedRow = function () { var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder'); - var matches = lastViewedFolder.match(/^([A-Z])([0-9]+)?$/); + var matches = lastViewedFolder.match(/^([A-Z])([G0-9]+)?$/); var select = 0; if (matches) { if (matches[1] == 'C') { @@ -895,14 +819,118 @@ Zotero.CollectionTreeView.prototype.deleteSelection = function() } } + +/** + * Expand row based on last state, or manually from toggleOpenState() + */ +Zotero.CollectionTreeView.prototype._expandRow = function (row, forceOpen) { + var itemGroup = this._getItemAtRow(row); + var isGroup = itemGroup.isGroup(); + var isCollection = itemGroup.isCollection(); + var level = this.getLevel(row) + 1; + var libraryID = itemGroup.ref.libraryID; + var intLibraryID = libraryID ? libraryID : 0; + + if (isGroup) { + var group = Zotero.Groups.getByLibraryID(libraryID); + var collections = group.getCollections(); + var showTrash = false; + } + else { + var collections = Zotero.getCollections(itemGroup.ref.id); + var showTrash = this.hideSources.indexOf('trash') == -1; + } + + var savedSearches = Zotero.Searches.getAll(libraryID); + var showDuplicates = this.hideSources.indexOf('duplicates') == -1 + && this._duplicateLibraries.indexOf(intLibraryID + '') != -1; + var showUnfiled = this._unfiledLibraries.indexOf(intLibraryID + '') != -1; + + // If not a manual open and either the library is set to be hidden + // or this is a collection that isn't explicitly opened, + // set the initial state to closed + if (!forceOpen && + (this._containerState[itemGroup.id] === false + || (isCollection && !this._containerState[itemGroup.id]))) { + this._dataItems[row][1] = false; + return 0; + } + + var startOpen = !!(collections.length || savedSearches.length || showDuplicates || showUnfiled); + + // If this isn't a manual open, set the initial state depending on whether + // there are child nodes + if (!forceOpen) { + this._dataItems[row][1] = startOpen; + } + + if (!startOpen) { + return 0; + } + + var newRows = 0; + + // Add collections + for (var i = 0, len = collections.length; i < len; i++) { + // In personal library root, skip group collections + if (!isGroup && !isCollection && collections[i].libraryID) { + continue; + } + var newRow = this._showRow(new Zotero.ItemGroup('collection', collections[i]), level, row + 1 + newRows); + + // Recursively expand child collections that should be open + newRows += this._expandRow(newRow); + + newRows++; + } + + if (isCollection) { + return newRows; + } + + // Add searches + for (var i = 0, len = savedSearches.length; i < len; i++) { + this._showRow(new Zotero.ItemGroup('search', savedSearches[i]), level, row + 1 + newRows); + newRows++; + } + + // Duplicate items + if (showDuplicates) { + var d = new Zotero.Duplicates(isGroup ? group.libraryID : 0); + this._showRow(new Zotero.ItemGroup('duplicates', d), level, row + 1 + newRows); + newRows++; + } + + // Unfiled items + if (showUnfiled) { + var s = new Zotero.Search; + if (isGroup) { + s.libraryID = group.libraryID; + } + s.name = Zotero.getString('pane.collections.unfiled'); + s.addCondition('libraryID', 'is', isGroup ? group.libraryID : null); + s.addCondition('unfiled', 'true'); + this._showRow(new Zotero.ItemGroup('unfiled', s), level, row + 1 + newRows); + newRows++; + } + + if (showTrash) { + var deletedItems = Zotero.Items.getDeleted(); + if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) { + this._showRow(new Zotero.ItemGroup('trash', false), level, row + 1 + newRows); + newRows++; + } + this.trashNotEmpty = !!deletedItems; + } + + return newRows; +} + + /* * Called by various view functions to show a row - * - * itemGroup: reference to the ItemGroup - * level: the indent level of the row - * beforeRow: row index to insert new row before */ -Zotero.CollectionTreeView.prototype._showItem = function(itemGroup, level, beforeRow, startOpen) +Zotero.CollectionTreeView.prototype._showRow = function(itemGroup, level, beforeRow) { if (!level) { level = 0; @@ -912,14 +940,13 @@ Zotero.CollectionTreeView.prototype._showItem = function(itemGroup, level, befor beforeRow = this._dataItems.length; } - if (!startOpen) { - startOpen = false; - } - - this._dataItems.splice(beforeRow, 0, [itemGroup, startOpen, level]); + this._dataItems.splice(beforeRow, 0, [itemGroup, false, level]); this.rowCount++; + + return beforeRow; } + /* * Called by view to hide specified row */ @@ -928,6 +955,7 @@ Zotero.CollectionTreeView.prototype._hideItem = function(row) this._dataItems.splice(row,1); this.rowCount--; } + /* * Returns Zotero.ItemGroup at row */ @@ -936,6 +964,7 @@ Zotero.CollectionTreeView.prototype._getItemAtRow = function(row) return this._dataItems[row][0]; } + /* * Saves the ids of the currently selected item for later */ @@ -965,8 +994,8 @@ Zotero.CollectionTreeView.prototype.rememberSelection = function(selection) this.selection.select(this._rowMap[selection]); } } - - + + Zotero.CollectionTreeView.prototype.getSelectedCollection = function(asID) { if (this.selection && this.selection.count > 0 @@ -980,7 +1009,6 @@ Zotero.CollectionTreeView.prototype.getSelectedCollection = function(asID) { } - /** * Creates mapping of item group ids to tree rows */ @@ -1000,6 +1028,58 @@ Zotero.CollectionTreeView.prototype._refreshHashMap = function() } } + +Zotero.CollectionTreeView.prototype._rememberOpenStates = function () { + var state = this._containerState; + + // Every so often, remove obsolete rows + if (Math.random() < 1/20) { + Zotero.debug("Purging sourceList.persist"); + for (var id in state) { + var m = id.match(/^C([0-9]+)$/); + if (m) { + if (!Zotero.Collections.get(m[1])) { + delete state[id]; + } + continue; + } + + var m = id.match(/^G([0-9]+)$/); + if (m) { + if (!Zotero.Groups.get(m[1])) { + delete state[id]; + } + continue; + } + } + } + + for (var i = 0, len = this.rowCount; i < len; i++) { + if (!this.isContainer(i)) { + continue; + } + + var itemGroup = this._getItemAtRow(i); + if (!itemGroup.id) { + continue; + } + + var open = this.isContainerOpen(i); + + // Collections default to closed + if (!open && itemGroup.isCollection()) { + delete state[itemGroup.id]; + continue; + } + + state[itemGroup.id] = open; + } + + this._containerState = state; + Zotero.Prefs.set("sourceList.persist", JSON.stringify(state)); +} + + //////////////////////////////////////////////////////////////////////////////// /// /// Command Controller: @@ -1646,12 +1726,17 @@ Zotero.ItemGroup.prototype.__defineGetter__('id', function () { case 'trash': return 'T'; + case 'header': + if (this.ref.id == 'group-libraries-header') { + return 'HG'; + } + break; + case 'group': return 'G' + this.ref.id; - - default: - return ''; } + + return ''; }); Zotero.ItemGroup.prototype.isLibrary = function (includeGlobal) diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js @@ -587,11 +587,12 @@ Zotero.Collection.prototype.save = function () { //Zotero.Notifier.trigger('add', 'collection-item', this.id + '-' + itemID); } - if (this._changed.libraryID) { + if (isNew && this.libraryID) { var groupID = Zotero.Libraries.getGroupIDFromLibraryID(this.libraryID); var group = Zotero.Groups.get(groupID); - group.clearCollectionsCache(); + group.clearCollectionCache(); } + Zotero.DB.commitTransaction(); } catch (e) { diff --git a/chrome/content/zotero/xpcom/data/group.js b/chrome/content/zotero/xpcom/data/group.js @@ -44,6 +44,7 @@ Zotero.Group.prototype._init = function () { this._loaded = false; this._changed = false; this._hasCollections = null; + this._hasSearches = null; } @@ -139,6 +140,7 @@ Zotero.Group.prototype.loadFromRow = function(row) { this._loaded = true; this._changed = false; this._hasCollections = null; + this._hasSearches = null; this._id = row.groupID; this._libraryID = row.libraryID; @@ -174,10 +176,23 @@ Zotero.Group.prototype.hasCollections = function () { } -Zotero.Group.prototype.clearCollectionsCache = function () { +Zotero.Group.prototype.hasSearches = function () { + if (this._hasSearches !== null) { + return this._hasSearches; + } + + this._hasSearches = !!Zotero.Searches.getAll(this.id).length; + return this._hasSearches; +} + + +Zotero.Group.prototype.clearCollectionCache = function () { this._hasCollections = null; } +Zotero.Group.prototype.clearSearchCache = function () { + this._hasSearches = null; +} /** * Returns collections of this group diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js @@ -343,6 +343,13 @@ Zotero.Search.prototype.save = function(fixGaps) { Zotero.DB.query(sql, sqlParams); } + + if (isNew && this.libraryID) { + var groupID = Zotero.Libraries.getGroupIDFromLibraryID(this.libraryID); + var group = Zotero.Groups.get(groupID); + group.clearSearchCache(); + } + Zotero.DB.commitTransaction(); } catch (e) { diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js @@ -840,15 +840,21 @@ var ZoteroPane = new function() Zotero.Prefs.set(prefKey, newids.join()); - if (show) { - Zotero.Prefs.set('lastViewedFolder', lastViewedFolderID); - } - this.collectionsView.refresh(); + // If group is closed, open it + this.collectionsView.selectLibrary(libraryID); + row = this.collectionsView.selection.currentIndex; + if (!this.collectionsView.isContainerOpen(row)) { + this.collectionsView.toggleOpenState(row); + } + // Select new row - var row = this.collectionsView.getLastViewedRow(); - this.collectionsView.selection.select(row); + if (show) { + Zotero.Prefs.set('lastViewedFolder', lastViewedFolderID); + var row = this.collectionsView.getLastViewedRow(); + this.collectionsView.selection.select(row); + } } @@ -2100,7 +2106,7 @@ var ZoteroPane = new function() // Library else { - show = [m.newCollection, m.newSavedSearch, m.showDuplicates, m.showUnfiled, m.sep2, m.exportFile]; + show = [m.newCollection, m.newSavedSearch, m.sep1, m.showDuplicates, m.showUnfiled, m.sep2, m.exportFile]; } // Disable some actions if user doesn't have write access