From 0f0badea27430c308ad03496f9d9999293c7f04c Mon Sep 17 00:00:00 2001 From: Bogdan Abaev Date: Thu, 11 Jan 2024 00:19:27 -0500 Subject: [PATCH] restore collection scroll position after filter Before filtering starts, save the selected row, scroll position, and which rows were collapsed before filering, and restore them once the filtering is over if a row was focused during filtering. --- chrome/content/zotero/collectionTree.jsx | 53 +++++++++++++++++++++--- chrome/content/zotero/zoteroPane.js | 1 + 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/chrome/content/zotero/collectionTree.jsx b/chrome/content/zotero/collectionTree.jsx index 3116aee434..df4cafff14 100644 --- a/chrome/content/zotero/collectionTree.jsx +++ b/chrome/content/zotero/collectionTree.jsx @@ -90,6 +90,9 @@ var CollectionTree = class CollectionTree extends LibraryTree { this._filter = ""; this._filterResultsCache = {}; + this._filterInitialScrollPosition = null; + this._filterInitialCollapsedRows = []; + this._treeWasFocused = false; this._hiddenFocusedRow = null; this.onLoad = this.createEventBinding('load', true, true); @@ -2370,6 +2373,14 @@ var CollectionTree = class CollectionTree extends LibraryTree { } + // Record if the collection tree has been focused. this._treeWasFocused is set to false when + // filtering starts. When the filter is cleared, if this._treeWasFocused is still false, + // no selection/focusing was done on the collectionTree, so we should reset previous scroll + // position. Otherwise, we should scroll to place the selected row in the middle. + recordCollectionTreeFocus = () => { + this._treeWasFocused = true; + }; + /** * Set collection filter and refresh collectionTree to only include * rows that match the filter. Rows that do not match the filter but have children that do @@ -2379,10 +2390,19 @@ var CollectionTree = class CollectionTree extends LibraryTree { * @param {String} filterText - Text that rows have to contain to match the filter */ async setFilter(filterText) { + let collectionTable = document.getElementById("collection-tree").firstElementChild; + let isEmpty = this._isFilterEmpty(); + let willBeEmpty = filterText.length == 0; this._filter = filterText.toLowerCase(); let currentRow = this.getRow(this.selection.focused) || this._hiddenFocusedRow; let currentRowDisplayed = currentRow && this._includedInTree(currentRow.ref); - let scrollToSelected = filterText.length == 0; + let shouldRestoreScrollPosition = willBeEmpty && !isEmpty && !this._treeWasFocused; + // Save the initial scroll position, selected row and which rows were collapsed before the filtering starts + if (!willBeEmpty && isEmpty) { + this._filterInitialScrollPosition = collectionTable.scrollTop; + this._filterInitialCollapsedRows = this._rows.filter(r => !r.isOpen).map(r => r.id); + this._treeWasFocused = false; + } // If current row does not match any filters, it'll be hidden, so clear selection if (!currentRowDisplayed) { this.selection.clearSelection(); @@ -2396,7 +2416,7 @@ var CollectionTree = class CollectionTree extends LibraryTree { } // Re-select previously selected row else { - await this.selectByID(currentRow.id, scrollToSelected); + await this.selectByID(currentRow.id, !shouldRestoreScrollPosition); } } @@ -2413,11 +2433,34 @@ var CollectionTree = class CollectionTree extends LibraryTree { } } } - // If the selected collection is not scrolled to, scroll to the very top - if (!scrollToSelected) { - let collectionTable = document.getElementById("collection-tree").firstElementChild; + // If the filter has been cleared and the selection has not changed, restore the initial scroll position + if (shouldRestoreScrollPosition) { + // For the initial scroll position to make sense, collapse rows that were initially collapsed + for (let rowID of this._filterInitialCollapsedRows) { + let index = this.getRowIndexByID(rowID); + if (index && this._rows[index].isOpen) { + this.toggleOpenState(index); + } + } + this._filterInitialCollapsedRows = []; + collectionTable.scrollTop = this._filterInitialScrollPosition; + this._filterInitialScrollPosition = null; + } + // During filtering, scroll to the very top + else if (!willBeEmpty) { collectionTable.scrollTop = 0; } + // If the filtering is cleared and the selection has changed, scroll to have the + // newly selected row in the middle + else if (willBeEmpty && !isEmpty) { + let selectedRow = collectionTable.querySelector(".row.selected"); + let rowRect = selectedRow.getBoundingClientRect(); + let tableRect = collectionTable.getBoundingClientRect(); + let rowMiddle = rowRect.top + rowRect.height / 2; + let tableMiddle = tableRect.top + tableRect.height / 2; + let scrollPosition = collectionTable.scrollTop + rowMiddle - tableMiddle; + collectionTable.scrollTop = scrollPosition; + } } diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 249f0e6ca1..867c61af12 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -1530,6 +1530,7 @@ var ZoteroPane = new function() onContextMenu: (...args) => ZoteroPane.onCollectionsContextMenuOpen(...args), dragAndDrop: true }); + collectionsTree.firstChild.addEventListener("focus", ZoteroPane.collectionsView.recordCollectionTreeFocus); } catch (e) { Zotero.logError(e);