From 2a69885b116a119caf18f67f01742f6de8c814d7 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 7 May 2015 18:18:48 -0400 Subject: [PATCH] Fix placement of saved searches in collections tree And unify row add/remove handling between collections tree and items tree --- .../zotero/xpcom/collectionTreeView.js | 201 +++++++----------- chrome/content/zotero/xpcom/itemTreeView.js | 152 ++++--------- .../content/zotero/xpcom/libraryTreeView.js | 75 +++++++ test/tests/collectionTreeViewTest.js | 31 +++ 4 files changed, 221 insertions(+), 238 deletions(-) diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index b0f978248..951daf0a9 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -136,7 +136,7 @@ Zotero.CollectionTreeView.prototype.refresh = Zotero.Promise.coroutine(function* // Record open states before refreshing if (this._rows) { for (var i=0, len=this._rows.length; i 0) { break; } } - // If we've found searches but then see something other than a search, stop - else if (inSearches) { + // If it's not a search and it's not a collection, stop + else if (!treeRow.isCollection()) { break; } } } this._addRow( - this._rows, - new Zotero.CollectionTreeRow('search', search), - level, + new Zotero.CollectionTreeRow('search', search, level), beforeRow ); } - this.rowCount++; - this._treebox.rowCountChanged(beforeRow, 1); - this._refreshRowMap(); return true; }); @@ -525,7 +521,7 @@ Zotero.CollectionTreeView.prototype.setHighlightedRows = Zotero.Promise.coroutin if (id[0] == 'C') { id = id.substr(1); yield this.expandToCollection(id); - row = this._collectionRowMap[id]; + row = this._rowMap["C" + id]; } if (row) { this._highlightedRows[row] = true; @@ -621,11 +617,6 @@ Zotero.CollectionTreeView.prototype.isContainer = function(row) return treeRow.isLibrary(true) || treeRow.isCollection() || treeRow.isPublications() || treeRow.isBucket(); } -Zotero.CollectionTreeView.prototype.isContainerOpen = function(row) -{ - return this._rows[row][1]; -} - /* * Returns true if the collection has no child collections */ @@ -653,11 +644,6 @@ Zotero.CollectionTreeView.prototype.isContainerEmpty = function(row) return true; } -Zotero.CollectionTreeView.prototype.getLevel = function(row) -{ - return this._rows[row][2]; -} - Zotero.CollectionTreeView.prototype.getParentIndex = function(row) { var thisLevel = this.getLevel(row); @@ -798,7 +784,7 @@ Zotero.CollectionTreeView.prototype.expandToCollection = Zotero.Promise.coroutin yield this.toggleOpenState(libraryRow); } - var row = this._collectionRowMap[collectionID]; + var row = this._rowMap["C" + collectionID]; if (row !== undefined) { return true; } @@ -809,7 +795,7 @@ Zotero.CollectionTreeView.prototype.expandToCollection = Zotero.Promise.coroutin col = yield Zotero.Collections.getAsync(parentID); } for each(var id in path) { - row = this._collectionRowMap[id]; + row = this._rowMap["C" + id]; if (!this.isContainerOpen(row)) { yield this.toggleOpenState(row); } @@ -957,8 +943,8 @@ Zotero.CollectionTreeView.prototype.getLastViewedRow = Zotero.Promise.coroutine( var select = 0; if (matches) { if (matches[1] == 'C') { - if (this._collectionRowMap[matches[2]]) { - select = this._collectionRowMap[matches[2]]; + if (this._rowMap["C" + matches[2]]) { + select = this._rowMap["C" + matches[2]]; } // Search recursively else { @@ -986,11 +972,11 @@ Zotero.CollectionTreeView.prototype.getLastViewedRow = Zotero.Promise.coroutine( lastCol = par; path.push(lastCol); } - while (!this._collectionRowMap[lastCol] && failsafe > 0) + while (!this._rowMap["C" + lastCol] && failsafe > 0) if (path.length) { for (var i=path.length-1; i>=0; i--) { var id = path[i]; - var row = this._collectionRowMap[id]; + var row = this._rowMap["C" + id]; if (!row) { var msg = "Collection not found in tree in " + "Zotero.CollectionTreeView.setTree()"; @@ -1000,8 +986,8 @@ Zotero.CollectionTreeView.prototype.getLastViewedRow = Zotero.Promise.coroutine( } if (!this.isContainerOpen(row)) { yield this.toggleOpenState(row); - if (this._collectionRowMap[matches[2]]) { - select = this._collectionRowMap[matches[2]]; + if (this._rowMap["C" + matches[2]]) { + select = this._rowMap["C" + matches[2]]; break; } } @@ -1081,8 +1067,8 @@ Zotero.CollectionTreeView.prototype.deleteSelection = Zotero.Promise.coroutine(f * Expand row based on last state, or manually from toggleOpenState() */ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(function* (rows, row, forceOpen) { - var treeRow = rows[row][0]; - var level = rows[row][2]; + var treeRow = rows[row]; + var level = rows[row].level; var isLibrary = treeRow.isLibrary(true); var isGroup = treeRow.isGroup(); var isCollection = treeRow.isCollection(); @@ -1120,7 +1106,7 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi if (!forceOpen && (this._containerState[treeRow.id] === false || (isCollection && !this._containerState[treeRow.id]))) { - rows[row][1] = false; + rows[row].isOpen = false; return 0; } @@ -1129,7 +1115,7 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi // If this isn't a manual open, set the initial state depending on whether // there are child nodes if (!forceOpen) { - rows[row][1] = startOpen; + rows[row].isOpen = startOpen; } if (!startOpen) { @@ -1140,17 +1126,15 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi // Add collections for (var i = 0, len = collections.length; i < len; i++) { - var newRow = this._addRow( + let beforeRow = row + 1 + newRows; + this._addRowToArray( rows, - new Zotero.CollectionTreeRow('collection', collections[i]), - level + 1, - row + 1 + newRows + new Zotero.CollectionTreeRow('collection', collections[i], level + 1), + beforeRow ); - - // Recursively expand child collections that should be open - newRows += yield this._expandRow(rows, newRow); - newRows++; + // Recursively expand child collections that should be open + newRows += yield this._expandRow(rows, beforeRow); } if (isCollection) { @@ -1159,14 +1143,22 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi // Add searches for (var i = 0, len = savedSearches.length; i < len; i++) { - this._addRow(rows, new Zotero.CollectionTreeRow('search', savedSearches[i]), level + 1, row + 1 + newRows); + this._addRowToArray( + rows, + new Zotero.CollectionTreeRow('search', savedSearches[i], level + 1), + row + 1 + newRows + ); newRows++; } // Duplicate items if (showDuplicates) { let d = new Zotero.Duplicates(libraryID); - this._addRow(rows, new Zotero.CollectionTreeRow('duplicates', d), level + 1, row + 1 + newRows); + this._addRowToArray( + rows, + new Zotero.CollectionTreeRow('duplicates', d, level + 1), + row + 1 + newRows + ); newRows++; } @@ -1177,7 +1169,11 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi s.name = Zotero.getString('pane.collections.unfiled'); s.addCondition('libraryID', 'is', libraryID); s.addCondition('unfiled', 'true'); - this._addRow(rows, new Zotero.CollectionTreeRow('unfiled', s), level + 1, row + 1 + newRows); + this._addRowToArray( + rows, + new Zotero.CollectionTreeRow('unfiled', s, level + 1), + row + 1 + newRows + ); newRows++; } @@ -1187,7 +1183,11 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi var ref = { libraryID: libraryID }; - this._addRow(rows, new Zotero.CollectionTreeRow('trash', ref), level + 1, row + 1 + newRows); + this._addRowToArray( + rows, + new Zotero.CollectionTreeRow('trash', ref, level + 1), + row + 1 + newRows + ); newRows++; } this._trashNotEmpty[libraryID] = !!deletedItems.length; @@ -1197,45 +1197,6 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi }); -/* - * Called by various view functions to show a row - */ -Zotero.CollectionTreeView.prototype._addRow = function (rows, treeRow, level, beforeRow) { - if (!level) { - level = 0; - } - - if (!beforeRow) { - beforeRow = rows.length; - } - - rows.splice(beforeRow, 0, [treeRow, false, level]); - - return beforeRow; -} - - -/* - * Called by view to hide specified row - */ -Zotero.CollectionTreeView.prototype._removeRow = function(row) -{ - this._rows.splice(row,1); - this.rowCount--; - if (this.selection.isSelected(row)) { - this.selection.toggleSelect(row); - } -} - - -/** - * Returns Zotero.CollectionTreeRow at row - */ -Zotero.CollectionTreeView.prototype.getRow = function (row) { - return this._rows[row][0]; -} - - /** * Returns libraryID or FALSE if not a library */ @@ -1295,16 +1256,8 @@ Zotero.CollectionTreeView.prototype.rememberSelection = Zotero.Promise.coroutine */ Zotero.CollectionTreeView.prototype._refreshRowMap = function() { this._rowMap = {}; - this._collectionRowMap = {}; for (let i = 0, len = this.rowCount; i < len; i++) { - let treeRow = this.getRow(i); - - this._rowMap[treeRow.id] = i; - - // Collections get special treatment for now - if (treeRow.isCollection()) { - this._collectionRowMap[treeRow.ref.id] = i; - } + this._rowMap[this.getRow(i).id] = i; } } @@ -2197,10 +2150,12 @@ Zotero.CollectionTreeCache = { }) }; -Zotero.CollectionTreeRow = function(type, ref) +Zotero.CollectionTreeRow = function(type, ref, level, isOpen) { this.type = type; this.ref = ref; + this.level = level || 0 + this.isOpen = isOpen || false; } diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js index 3209d22f7..2717cf740 100644 --- a/chrome/content/zotero/xpcom/itemTreeView.js +++ b/chrome/content/zotero/xpcom/itemTreeView.js @@ -48,7 +48,6 @@ Zotero.ItemTreeView = function (collectionTreeRow, sourcesOnly) { this._ownerDocument = null; this._needsSort = false; - this._rows = []; this._cellTextCache = {}; this._itemImages = {}; @@ -355,9 +354,8 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f this._addRowToArray( newRows, new Zotero.ItemTreeRow(item, 0, false), - added + 1 + added++ ); - added++; } newSearchItemIDs[item.id] = true; } @@ -369,9 +367,8 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f this._addRowToArray( newRows, new Zotero.ItemTreeRow(item, 0, false), - added + 1 + added++ ); - added++; } } @@ -414,7 +411,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio return; } - if (!this._itemRowMap) { + if (!this._rowMap) { Zotero.debug("Item row map didn't exist in itemTreeView.notify()"); return; } @@ -449,13 +446,13 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio if (extraData.column == 'title') { delete this._itemImages[id]; } - this._treebox.invalidateCell(this._itemRowMap[id], col); + this._treebox.invalidateCell(this._rowMap[id], col); } } else { for each(var id in ids) { delete this._itemImages[id]; - this._treebox.invalidateRow(this._itemRowMap[id]); + this._treebox.invalidateRow(this._rowMap[id]); } } } @@ -485,7 +482,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio } // If refreshing a single item, clear caches and then unselect and reselect row else if (savedSelection.length == 1 && savedSelection[0] == ids[0]) { - let row = this._itemRowMap[ids[0]]; + let row = this._rowMap[ids[0]]; delete this._cellTextCache[row]; this.selection.clearSelection(); @@ -541,7 +538,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio // On a delete in duplicates mode, just refresh rather than figuring // out what to remove if (collectionTreeRow.isDuplicates()) { - previousRow = this._itemRowMap[ids[0]]; + previousRow = this._rowMap[ids[0]]; yield this.refresh(); madeChanges = true; sort = true; @@ -561,7 +558,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio } // Row might already be gone (e.g. if this is a child and // 'modify' was sent to parent) - let row = this._itemRowMap[ids[i]]; + let row = this._rowMap[ids[i]]; if (push && row !== undefined) { // Don't remove child items from collections, because it's handled by 'modify' if (action == 'remove' && this.getParentIndex(row) != -1) { @@ -622,7 +619,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio let item = items[i]; let id = item.id; - let row = this._itemRowMap[id]; + let row = this._rowMap[id]; // Deleted items get a modify that we have to ignore when // not viewing the trash @@ -654,7 +651,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio } // If not moved from under one item to another, just resort the row, // which also invalidates it and refreshes it - else if (!(parentItemID && parentIndex != -1 && this._itemRowMap[parentItemID] != parentIndex)) { + else if (!(parentItemID && parentIndex != -1 && this._rowMap[parentItemID] != parentIndex)) { sort = id; } @@ -734,7 +731,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio && collectionTreeRow.ref.libraryID == item.libraryID) || (collectionTreeRow.isCollection() && item.inCollection(collectionTreeRow.ref.id))) // if we haven't already added it to our hash map - && this._itemRowMap[item.id] == null + && this._rowMap[item.id] == null // Regular item or standalone note/attachment && item.isTopLevelItem()) { let beforeRow = this.rowCount; @@ -824,7 +821,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio else if (action == 'modify' && ids.length == 1 && savedSelection.length == 1 && savedSelection[0] == ids[0]) { // If the item no longer matches the search term, clear the search - if (quicksearch && this._itemRowMap[ids[0]] == undefined) { + if (quicksearch && this._rowMap[ids[0]] == undefined) { Zotero.debug('Selected item no longer matches quicksearch -- clearing'); quicksearch.value = ''; quicksearch.doCommand(); @@ -847,7 +844,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio else { if (previousRow === false) { - previousRow = this._itemRowMap[ids[0]]; + previousRow = this._rowMap[ids[0]]; } if (sort) { @@ -1116,11 +1113,6 @@ Zotero.ItemTreeView.prototype.isContainer = function(row) return this.getRow(row).ref.isRegularItem(); } -Zotero.ItemTreeView.prototype.isContainerOpen = function(row) -{ - return this._rows[row].isOpen; -} - Zotero.ItemTreeView.prototype.isContainerEmpty = function(row) { if (this._sourcesOnly) { @@ -1135,11 +1127,6 @@ Zotero.ItemTreeView.prototype.isContainerEmpty = function(row) return item.numNotes(includeTrashed) === 0 && item.numAttachments(includeTrashed) == 0; } -Zotero.ItemTreeView.prototype.getLevel = function(row) -{ - return this.getRow(row).level; -} - // Gets the index of the row's container, or -1 if none (top-level) Zotero.ItemTreeView.prototype.getParentIndex = function(row) { @@ -1296,13 +1283,13 @@ Zotero.ItemTreeView.prototype.cycleHeader = Zotero.Promise.coroutine(function* ( this.selection.selectEventsSuppressed = true; var savedSelection = this.getSelectedItems(true); if (savedSelection.length == 1) { - var pos = this._itemRowMap[savedSelection[0]] - this._treebox.getFirstVisibleRow(); + var pos = this._rowMap[savedSelection[0]] - this._treebox.getFirstVisibleRow(); } yield this.sort(); yield this.rememberSelection(savedSelection); // If single row was selected, try to keep it in the same place if (savedSelection.length == 1) { - var newRow = this._itemRowMap[savedSelection[0]]; + var newRow = this._rowMap[savedSelection[0]]; // Calculate the last row that would give us a full view var fullTop = Math.max(0, this._rows.length - this._treebox.getPageLength()); // Calculate the row that would give us the same position @@ -1327,9 +1314,9 @@ Zotero.ItemTreeView.prototype.sort = Zotero.Promise.coroutine(function* (itemID) this._needsSort = false; // Single child item sort -- just toggle parent closed and open - if (itemID && this._itemRowMap[itemID] && - this.getRow(this._itemRowMap[itemID]).ref.parentKey) { - let parentIndex = this.getParentIndex(this._itemRowMap[itemID]); + if (itemID && this._rowMap[itemID] && + this.getRow(this._rowMap[itemID]).ref.parentKey) { + let parentIndex = this.getParentIndex(this._rowMap[itemID]); this._closeContainer(parentIndex); yield this.toggleOpenState(parentIndex); return; @@ -1537,7 +1524,7 @@ Zotero.ItemTreeView.prototype.sort = Zotero.Promise.coroutine(function* (itemID) // Single-row sort if (itemID) { - let row = this._itemRowMap[itemID]; + let row = this._rowMap[itemID]; for (let i=0, len=this._rows.length; i