diff --git a/chrome/content/zotero/advancedSearch.js b/chrome/content/zotero/advancedSearch.js
deleted file mode 100644
index b28c441147..0000000000
--- a/chrome/content/zotero/advancedSearch.js
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-
-import CollectionViewItemTree from 'zotero/collectionViewItemTree';
-import { COLUMNS } from 'zotero/itemTreeColumns';
-
-
-var ZoteroAdvancedSearch = new function () {
- this.onLoad = onLoad;
- this.search = search;
- this.clear = clear;
- this.onItemActivate = onItemActivate;
-
- this.itemsView = false;
- this._loadedDeferred = Zotero.Promise.defer();
-
- var _searchBox;
- var _libraryID;
- var _searchCounter = 0;
-
- async function onLoad() {
- _searchBox = document.getElementById('zotero-search-box');
-
- // Set font size from pref
- var sbc = document.getElementById('zotero-search-box-container');
- Zotero.UIProperties.registerRoot(sbc);
-
- _searchBox.onLibraryChange = this.onLibraryChange;
- var io = window.arguments[0];
-
- io.dataIn.search.loadPrimaryData()
- .then(function () {
- _searchBox.search = io.dataIn.search;
- });
-
- var elem = document.getElementById('zotero-items-tree');
- const columns = COLUMNS.map((column) => {
- column = Object.assign({}, column);
- column.hidden = !['title', 'firstCreator', 'year', 'hasAttachment'].includes(column.dataKey);
- return column;
- });
- this.itemsView = await CollectionViewItemTree.init(elem, {
- id: "advanced-search",
- dragAndDrop: true,
- columnPicker: true,
- onActivate: this.onItemActivate.bind(this),
- columns,
- });
-
- await this.itemsView.changeCollectionTreeRow({
- id: 'advanced-search-' + _searchCounter++,
- ref: _searchBox.search,
- visibilityGroup: 'default',
- isSearchMode: () => true,
- getItems: async () => [],
- isLibrary: () => false,
- isCollection: () => false,
- isSearch: () => true,
- isPublications: () => false,
- isDuplicates: () => false,
- isFeed: () => false,
- isFeeds: () => false,
- isFeedsOrFeed: () => false,
- isRecentlyRead: () => false,
- isSortable: () => true,
- isShare: () => false,
- isTrash: () => false,
- isSearch: () => true
- });
-
- // Focus the first field in the window
- Services.focus.moveFocus(window, null, Services.focus.MOVEFOCUS_FORWARD, 0);
- this._loadedDeferred.resolve();
- }
-
- this.onUnload = function () {
- this.itemsView.unregister();
- }
-
- function search() {
- _searchBox.updateSearch();
- _searchBox.active = true;
-
- return this.itemsView.changeCollectionTreeRow({
- id: 'advanced-search-' + _searchCounter++,
- ref: _searchBox.search,
- visibilityGroup: 'default',
- isSearchMode: () => true,
- isSearch: () => true,
- getItems: async function () {
- await Zotero.Libraries.get(_libraryID).waitForDataLoad('item');
-
- var search = _searchBox.search.clone();
- search.libraryID = _libraryID;
- var ids = await search.search();
- return Zotero.Items.get(ids);
- }
- });
- }
-
-
- function clear() {
- this.itemsView.changeCollectionTreeRow(null);
-
- var s = new Zotero.Search();
- // Don't clear the selected library
- s.libraryID = _searchBox.search.libraryID;
- s.addCondition('title', 'contains', '');
- _searchBox.search = s;
- _searchBox.active = false;
- }
-
-
- this.save = async function () {
- _searchBox.updateSearch();
-
- var promptService = Services.prompt;
-
- var libraryID = _searchBox.search.libraryID;
-
- var searches = await Zotero.Searches.getAll(libraryID);
- var prefix = Zotero.getString('pane.collections.untitled');
- var name = Zotero.Utilities.Internal.getNextName(
- prefix,
- searches.map(s => s.name).filter(n => n.startsWith(prefix))
- );
-
- name = { value: name };
- var result = promptService.prompt(window,
- Zotero.getString('pane.collections.newSavedSeach'),
- Zotero.getString('pane.collections.savedSearchName'), name, "", {});
-
- if (!result) {
- return;
- }
-
- if (!name.value) {
- name.value = 'untitled';
- }
-
- var s = _searchBox.search.clone();
- s.name = name.value;
- await s.saveTx();
-
- window.close();
- };
-
-
- this.onLibraryChange = function (libraryID) {
- _libraryID = libraryID;
- var library = Zotero.Libraries.get(libraryID);
- var isEditable = library.editable && library.libraryType != 'publications';
- document.getElementById('zotero-search-save').disabled = !isEditable;
- }
-
-
- function onItemActivate(event, items)
- {
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
-
- var lastWin = wm.getMostRecentWindow("navigator:browser");
-
- if (!lastWin) {
- return;
- }
-
- lastWin.ZoteroPane.selectItems(items.map(item => item.id));
- lastWin.focus();
- }
-}
diff --git a/chrome/content/zotero/advancedSearch.xhtml b/chrome/content/zotero/advancedSearch.xhtml
deleted file mode 100644
index ac90733b78..0000000000
--- a/chrome/content/zotero/advancedSearch.xhtml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
- %zoteroDTD;
-
- %searchboxDTD;
-]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/chrome/content/zotero/collectionViewItemTree.jsx b/chrome/content/zotero/collectionViewItemTree.jsx
index 7ed1ed2031..231dc9f387 100644
--- a/chrome/content/zotero/collectionViewItemTree.jsx
+++ b/chrome/content/zotero/collectionViewItemTree.jsx
@@ -152,6 +152,9 @@ class CollectionViewItemTreeRowProvider extends ItemTreeRowProvider {
case 'citation-search':
changed = this.collectionTreeRow.setSearch(data, 'fields');
break;
+ case 'advanced-search':
+ changed = this.collectionTreeRow.setAdvancedSearch(data);
+ break;
case 'tags':
changed = this.collectionTreeRow.setTags(data);
break;
diff --git a/chrome/content/zotero/customElements.js b/chrome/content/zotero/customElements.js
index 9e9f0e0b64..2522d987f4 100644
--- a/chrome/content/zotero/customElements.js
+++ b/chrome/content/zotero/customElements.js
@@ -33,6 +33,8 @@ Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemTreeMe
{
// https://searchfox.org/mozilla-central/rev/8e885f04a0a4ff6d64ea59741c10d9b8e45d9ff8/toolkit/content/customElements.js#826-832
for (let [tag, script] of [
+ ['advanced-search-deck', 'chrome://zotero/content/elements/advancedSearchDeck.js'],
+ ['advanced-search-pane', 'chrome://zotero/content/elements/advancedSearchPane.js'],
['attachment-box', 'chrome://zotero/content/elements/attachmentBox.js'],
['attachment-preview', 'chrome://zotero/content/elements/attachmentPreview.js'],
['attachment-preview-box', 'chrome://zotero/content/elements/attachmentPreviewBox.js'],
diff --git a/chrome/content/zotero/elements/advancedSearchDeck.js b/chrome/content/zotero/elements/advancedSearchDeck.js
new file mode 100644
index 0000000000..56faf28012
--- /dev/null
+++ b/chrome/content/zotero/elements/advancedSearchDeck.js
@@ -0,0 +1,83 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2025 Corporation for Digital Scholarship
+ Vienna, Virginia, USA
+ https://www.zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+"use strict";
+
+{
+ class AdvancedSearchDeck extends XULElementBase {
+ content = MozXULElement.parseXULToFragment(`
+
+
+
+
+ `);
+
+ init() {
+ this.hidden = true;
+ this._state = 'closed';
+
+ this.deck = this.firstChild;
+ }
+
+ get state() {
+ return this._state;
+ }
+
+ set state(state) {
+ switch (state) {
+ case 'open':
+ this.hidden = false;
+ break;
+ case 'collapsed':
+ case 'closed':
+ this.hidden = true;
+ break;
+ default:
+ throw new Error('Invalid state: ' + state);
+ }
+ this._state = state;
+ }
+
+ get selectedSearchType() {
+ return this.deck.selectedIndex === 0 ? 'temporary' : 'saved';
+ }
+
+ set selectedSearchType(selectedSearchType) {
+ switch (selectedSearchType) {
+ case 'temporary':
+ this.deck.selectedIndex = 0;
+ break;
+ case 'saved':
+ this.deck.selectedIndex = 1;
+ break;
+ }
+ }
+
+ get pane() {
+ return this.deck.selectedPanel;
+ }
+ }
+ customElements.define("advanced-search-deck", AdvancedSearchDeck);
+}
diff --git a/chrome/content/zotero/elements/advancedSearchPane.js b/chrome/content/zotero/elements/advancedSearchPane.js
new file mode 100644
index 0000000000..2dc9a5abd1
--- /dev/null
+++ b/chrome/content/zotero/elements/advancedSearchPane.js
@@ -0,0 +1,209 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2025 Corporation for Digital Scholarship
+ Vienna, Virginia, USA
+ https://www.zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+"use strict";
+
+{
+ class AdvancedSearchPane extends XULElementBase {
+ content = MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+
+ `);
+
+ _active = false;
+
+ init() {
+ this._nameField = this.querySelector('#saved-search-name');
+ this._searchElem = this.querySelector('zoterosearch');
+ this._cancelButton = this.querySelector('.cancel-button');
+ this._searchButton = this.querySelector('.search-button');
+ this._clearButton = this.querySelector('.clear-button');
+ this._saveButton = this.querySelector('.save-button');
+
+ this._searchElem.addEventListener('input', () => this._searchElem.updateSearch());
+ this._searchElem.addEventListener('command', () => this._searchElem.updateSearch());
+
+ this._cancelButton.addEventListener('command', () => this.cancel());
+ this._searchButton.addEventListener('command', () => this.submit());
+ this._clearButton.addEventListener('command', () => this.clear());
+ this._saveButton.addEventListener('command', () => this.save());
+
+ if (!['temporary', 'saved'].includes(this.type)) {
+ throw new Error(`Invalid type: ${this.type}`);
+ }
+
+ if (this.type === 'saved') {
+ this._saveButton.setAttribute('default', 'true');
+ }
+
+ this.addEventListener('keydown', this._handleKeyDown);
+ }
+
+ _handleKeyDown = async (event) => {
+ this._searchElem.updateSearch();
+ if (event.key === 'Enter') {
+ if (this.type === 'temporary') {
+ await this.submit();
+ }
+ else {
+ await this.save();
+ }
+ }
+ };
+
+ /**
+ * @returns {'temporary' | 'saved'}
+ */
+ get type() {
+ return this.getAttribute('type');
+ }
+
+ get search() {
+ return this._search;
+ }
+
+ /**
+ * Whether the search has been submitted and should filter the items list
+ */
+ get active() {
+ return this._active;
+ }
+
+ set search(search) {
+ this._active = false;
+ if (this.type === 'saved') {
+ if (!search?.id) {
+ throw new Error('Cannot edit unsaved search');
+ }
+ this._searchID = search.id;
+ this._search = search.clone();
+ this._nameField.value = search.name;
+ }
+ else if (search) {
+ this._search = search.clone();
+ }
+ else {
+ this._search = new Zotero.Search();
+ this._search.addCondition('title', 'contains', '');
+ }
+ this._searchElem.search = this._search;
+ }
+
+ _ensureSearch() {
+ if (!this._search && this.type === 'temporary') {
+ this.search = null;
+ }
+ }
+
+ refresh() {
+ this._ensureSearch();
+ let libraryID = ZoteroPane.getSelectedLibraryID();
+ // Keep the previous library when the selected row doesn't have one (e.g., Feeds)
+ if (libraryID) {
+ this._search.libraryID = libraryID;
+ }
+ this._searchElem.search = this._search;
+ // There would be no way to scope a saved search to anything but the library root,
+ // so disable the option to save.
+ // Somewhat unfortunate - revisit when/if we support nested condition sets in the UI.
+ this._saveButton.disabled = this.type === 'temporary' && !ZoteroPane.getCollectionTreeRow().isLibrary();
+ }
+
+ async cancel() {
+ await ZoteroPane.setSavedSearchEditorState('closed');
+ }
+
+ async submit() {
+ if (this.type === 'saved') {
+ throw new Error('submit() is unsupported for saved search');
+ }
+
+ this._searchElem.updateSearch();
+ this._active = true;
+ await ZoteroPane.itemsView.setFilter('advanced-search', this._search);
+ ZoteroPane.itemsView.focus();
+ }
+
+ async clear() {
+ if (this.type === 'saved') {
+ throw new Error('clear() is unsupported for saved search');
+ }
+
+ this.search = null;
+ await ZoteroPane.itemsView.setFilter('advanced-search', null);
+ }
+
+ async save() {
+ this._searchElem.updateSearch();
+
+ if (this.type === 'saved') {
+ let search = Zotero.Searches.get(this._searchID);
+ if (!search) {
+ throw new Error('Missing search');
+ }
+ search.fromJSON(this._search.toJSON());
+ search.name = this._nameField.value;
+ await search.saveTx();
+ await ZoteroPane.setSavedSearchEditorState('closed');
+ Zotero_Tabs.rename('zotero-pane', search.name);
+ return;
+ }
+
+ let collectionTreeRow = ZoteroPane.getCollectionTreeRow();
+ if (!collectionTreeRow.isLibrary()) {
+ throw new Error('Can only save in library root');
+ }
+ this._ensureSearch();
+
+ let libraryID = collectionTreeRow.ref.libraryID;
+ let searches = await Zotero.Searches.getAll(libraryID);
+ let prefix = Zotero.getString('pane.collections.untitled');
+ let name = Zotero.Utilities.Internal.getNextName(
+ prefix,
+ searches.map(s => s.name).filter(n => n.startsWith(prefix))
+ );
+
+ let search = this._search.clone(libraryID);
+ search.name = name;
+ await search.saveTx();
+
+ await ZoteroPane.setAdvancedSearchState('closed');
+ }
+
+ focus(options) {
+ this._searchElem.querySelector('#conditionsmenu').focus(options);
+ }
+ }
+ customElements.define("advanced-search-pane", AdvancedSearchPane);
+}
diff --git a/chrome/content/zotero/elements/quickSearchTextbox.js b/chrome/content/zotero/elements/quickSearchTextbox.js
index 58ce7e3727..a501346a70 100644
--- a/chrome/content/zotero/elements/quickSearchTextbox.js
+++ b/chrome/content/zotero/elements/quickSearchTextbox.js
@@ -33,8 +33,15 @@
this.searchTextbox = null;
MozXULElement.insertFTLIfNeeded("zotero.ftl");
this.content = MozXULElement.parseXULToFragment(`
-
-
+
+
+
+
+
+
+
+
+
`, ['chrome://zotero/locale/zotero.dtd']);
}
@@ -95,6 +102,30 @@
wrapper.appendChild(dropmarkerHost);
wrapper.appendChild(searchBox);
+ // Add Advanced Search button at the end of the field in main window
+ if (document.documentElement.getAttribute('windowtype') === 'navigator:browser') {
+ let advancedButton = document.createXULElement('toolbarbutton');
+ advancedButton.id = 'zotero-tb-search-advanced-button';
+ advancedButton.tabIndex = 0;
+ document.l10n.setAttributes(advancedButton, 'quicksearch-advanced-search-button');
+ advancedButton.addEventListener('command', (event) => {
+ // Don't trigger a quick search via the oncommand handler
+ event.stopPropagation();
+ ZoteroPane.toggleAdvancedSearchState('open');
+ });
+ wrapper.appendChild(advancedButton);
+ }
+
+ this.deck = this.firstElementChild;
+
+ this.querySelector('.advanced-collapse-button').addEventListener('command', (event) => {
+ event.stopPropagation();
+ ZoteroPane.toggleAdvancedSearchState('collapsed');
+ });
+ this.querySelector('.advanced-close-button').addEventListener('command', (event) => {
+ event.stopPropagation();
+ ZoteroPane.toggleAdvancedSearchState('closed');
+ });
// If Alt-Up/Down, show popup
this.addEventListener('keypress', (event) => {
@@ -132,20 +163,13 @@
popup.append(item);
}
- // Add Advanced Search menu item in main window
- if (document.documentElement.getAttribute('windowtype') === 'navigator:browser') {
- let separator = document.createXULElement('menuseparator');
- popup.append(separator);
- let advancedSearchOption = document.createXULElement('menuitem');
- advancedSearchOption.label = Zotero.getString("zotero.toolbar.advancedSearch");
- advancedSearchOption.addEventListener("command", () => {
- ZoteroPane.openAdvancedSearchWindow();
- });
- popup.append(advancedSearchOption);
- }
-
return this._searchModePopup = popup;
}
+
+ onCollectionSelected() {
+ this.searchTextbox.value = '';
+ this.updateMode();
+ }
updateMode() {
let mode = Zotero.Prefs.get("search.quicksearch-mode");
@@ -158,6 +182,29 @@
this.searchModePopup.querySelector(`menuitem[value="${mode}"]`)
.setAttribute('checked', 'true');
document.l10n.setAttributes(this.searchTextbox.inputField, "quicksearch-input", { placeholder: this._searchModes[mode] });
+
+ let advancedSearchDeck = document.getElementById('zotero-advanced-search-pane-deck');
+ if (advancedSearchDeck) {
+ let state = advancedSearchDeck.state;
+ let selectedSearchType = advancedSearchDeck.selectedSearchType;
+
+ document.l10n.setAttributes(
+ this.querySelector('#advanced-search-label'),
+ selectedSearchType === 'temporary' ? 'advanced-search' : 'edit-saved-search',
+ );
+ this.deck.selectedIndex = state === 'closed' ? 0 : 1;
+ this.querySelector('#advanced-search-indicator').dataset.collapsed = state === 'collapsed';
+ this.querySelector('.advanced-collapse-button').hidden = selectedSearchType === 'saved';
+ }
+ }
+
+ focus(options) {
+ if (this.deck.selectedIndex === 0) {
+ this._searchModePopup.flattenedTreeParentNode.focus(options);
+ }
+ else {
+ Services.focus.moveFocus(window, this.deck.selectedPanel, Services.focus.MOVEFOCUS_FORWARD, 0);
+ }
}
_id(id) {
diff --git a/chrome/content/zotero/elements/zoteroSearch.js b/chrome/content/zotero/elements/zoteroSearch.js
index 0150c160a5..afd364a1b5 100644
--- a/chrome/content/zotero/elements/zoteroSearch.js
+++ b/chrome/content/zotero/elements/zoteroSearch.js
@@ -37,35 +37,24 @@
class ZoteroSearch extends SearchElementBase {
content = MozXULElement.parseXULToFragment(`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
`, ['chrome://zotero/locale/zotero.dtd', 'chrome://zotero/locale/searchbox.dtd']);
get search() {
@@ -75,18 +64,12 @@
set search(val) {
this.searchRef = val;
- var libraryMenu = this.querySelector('#libraryMenu');
- var libraries = Zotero.Libraries.getAll();
- Zotero.Utilities.Internal.buildLibraryMenu(
- libraryMenu, libraries, this.searchRef.libraryID
- );
- if (this.searchRef.id) {
- libraryMenu.disabled = true;
- }
- this.updateLibrary();
-
this.querySelector('#joinModeMenu').removeAttribute('condition');
this.querySelector('#joinModeMenu').value = 'all';
+
+ this.querySelector('#recursiveCheckbox').checked = false;
+ this.querySelector('#noChildrenCheckbox').checked = false;
+ this.querySelector('#includeParentsAndChildrenCheckbox').checked = false;
var conditionsBox = this.querySelector('#conditions');
while (conditionsBox.hasChildNodes()) {
@@ -118,6 +101,10 @@
}
}
}
+
+ init() {
+ this.addEventListener('keypress', event => this.handleKeyPress(event));
+ }
addCondition(ref) {
var conditionsBox = this.querySelector('#conditions');
@@ -167,20 +154,6 @@
}
}
- updateLibrary() {
- var menu = this.querySelector('#libraryMenu');
- var libraryID = parseInt(menu.selectedItem.value);
-
- if (this.onLibraryChange) {
- this.onLibraryChange(libraryID);
- }
- if (!this.searchRef.id) {
- this.searchRef.libraryID = libraryID;
- }
-
- [...this.querySelector('#conditions').childNodes].forEach(x => x.onLibraryChange());
- }
-
updateJoinMode() {
var menu = this.querySelector('#joinModeMenu');
if (menu.hasAttribute('condition')) this.search.updateCondition(menu.getAttribute('condition'), 'joinMode', menu.value, null);
@@ -586,6 +559,11 @@
if (this.value) {
valueMenu.value = this.value;
+ // If the value isn't in the menu (e.g., a collection from another
+ // library after a library change), fall back to the first item
+ if (!valueMenu.selectedItem) {
+ valueMenu.selectedIndex = 0;
+ }
}
}
@@ -747,8 +725,6 @@
onRemoveClicked() {
if (this.parent) {
- window.resizeBy(0, -1 * this.getBoundingClientRect().height);
- window.dispatchEvent(new CustomEvent('resize'));
this.parent.removeCondition(this.conditionID);
}
}
@@ -764,7 +740,6 @@
)
);
this.parent.addCondition(ref);
- window.resizeBy(0, this.getBoundingClientRect().height);
}
}
diff --git a/chrome/content/zotero/libraryTree.js b/chrome/content/zotero/libraryTree.js
index 5c682ba20c..f9499270a1 100644
--- a/chrome/content/zotero/libraryTree.js
+++ b/chrome/content/zotero/libraryTree.js
@@ -214,15 +214,15 @@ var LibraryTree = class LibraryTree extends React.Component {
this.tree && this.tree.scrollToRow(index);
}
- _updateHeight = () => {
+ updateHeight = () => {
this.forceUpdate(() => {
if (this.tree) {
this.tree.rerender();
}
});
- }
+ };
- updateHeight = Zotero.Utilities.debounce(this._updateHeight, 200);
+ updateHeightDebounced = Zotero.Utilities.debounce(this.updateHeight, 200);
updateFontSize() {
this.tree.updateFontSize();
diff --git a/chrome/content/zotero/searchDialog.js b/chrome/content/zotero/searchDialog.js
deleted file mode 100644
index 62cb4f1734..0000000000
--- a/chrome/content/zotero/searchDialog.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-"use strict";
-
-var itemsView;
-var collectionsView;
-var io;
-
-function doLoad()
-{
- // Set font size from pref
- var sbc = document.getElementById('zotero-search-box-container');
- Zotero.UIProperties.registerRoot(sbc);
-
- io = window.arguments[0];
-
- var searchBox = document.getElementById('search-box');
- searchBox.groups = io.dataIn.groups;
- searchBox.search = io.dataIn.search;
- let searchName = document.getElementById('search-name');
- searchName.value = io.dataIn.name;
- searchName.select();
-
- document.addEventListener('dialogaccept', doAccept);
-}
-
-function doUnload()
-{
-
-}
-
-function doAccept()
-{
- document.getElementById('search-box').search.name = document.getElementById('search-name').value;
- try {
- let searchBox = document.getElementById('search-box');
- searchBox.updateSearch();
- io.dataOut = {
- json: searchBox.search.toJSON()
- };
- }
- catch (e) {
- Zotero.debug(e, 1);
- Components.utils.reportError(e);
- throw (e);
- }
-}
diff --git a/chrome/content/zotero/searchDialog.xhtml b/chrome/content/zotero/searchDialog.xhtml
deleted file mode 100644
index eed0e8195b..0000000000
--- a/chrome/content/zotero/searchDialog.xhtml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/chrome/content/zotero/xpcom/collectionTreeRow.js b/chrome/content/zotero/xpcom/collectionTreeRow.js
index 38ded96839..86d3cb656f 100644
--- a/chrome/content/zotero/xpcom/collectionTreeRow.js
+++ b/chrome/content/zotero/xpcom/collectionTreeRow.js
@@ -464,7 +464,8 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = async function () {
}
s2.setScope(s, includeScopeChildren);
- if (this.searchText) {
+ // Add Quick Search unless advanced search is enabled
+ if (this.searchText && !this.advancedSearch) {
let cond = 'quicksearch-'
+ (this.searchMode || Zotero.Prefs.get('search.quicksearch-mode'));
s2.addCondition(cond, 'contains', this.searchText);
@@ -476,8 +477,37 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = async function () {
}
}
- this._cachedSearch = s2;
- return s2;
+ let s3;
+ if (this.advancedSearch) {
+ if (this.advancedSearch.libraryID === null) {
+ // A library-less search (Feeds pseudo-library) can't be clone()d
+ s3 = new Zotero.Search();
+ s3.fromJSON(this.advancedSearch.toJSON());
+ }
+ else {
+ s3 = this.advancedSearch.clone();
+ }
+ // Show matches in the trash too. includeDeleted has to be set on the
+ // scope searches as well, since a search's scope defines the superset
+ // of possible results. Special condition - unaffected by joinMode.
+ if (!this.isTrash()) {
+ if (s == this.ref) {
+ // Don't modify a saved search object
+ s = s.clone(this.ref.libraryID);
+ s2.setScope(s, includeScopeChildren);
+ }
+ s.addCondition('includeDeleted', 'true');
+ s2.addCondition('includeDeleted', 'true');
+ }
+ s3.addCondition('includeDeleted', 'true');
+ s3.setScope(s2, includeScopeChildren);
+ }
+ else {
+ s3 = s2;
+ }
+
+ this._cachedSearch = s3;
+ return s3;
};
Zotero.CollectionTreeRow.prototype.getChildTags = function () {
@@ -537,6 +567,23 @@ Zotero.CollectionTreeRow.prototype.setSearch = function (searchText, mode = null
return true;
}
+Zotero.CollectionTreeRow.prototype.setAdvancedSearch = function (advancedSearch) {
+ this.clearCache();
+ if (!advancedSearch) {
+ this.advancedSearch = undefined;
+ }
+ else if (this.ref.libraryID === undefined) {
+ // Feeds pseudo-library -- leave the library unset so that the search
+ // spans all feed libraries
+ this.advancedSearch = new Zotero.Search();
+ this.advancedSearch.fromJSON(advancedSearch.toJSON());
+ }
+ else {
+ this.advancedSearch = advancedSearch.clone(this.ref.libraryID);
+ }
+ return true;
+};
+
Zotero.CollectionTreeRow.prototype.setTags = function (tags) {
let oldTags = this.tags instanceof Set ? this.tags : new Set(this.tags || []);
let newTags = tags instanceof Set ? new Set(tags) : new Set(tags || []);
@@ -570,8 +617,8 @@ Zotero.CollectionTreeRow.prototype.isSearchMode = function () {
return true;
}
- // Quicksearch
- if (this.searchText != '') {
+ // Search filters
+ if (this.advancedSearch || this.searchText != '') {
return true;
}
diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js
index 8110841918..edde327cd7 100644
--- a/chrome/content/zotero/xpcom/data/search.js
+++ b/chrome/content/zotero/xpcom/data/search.js
@@ -597,8 +597,9 @@ Zotero.Search.prototype.search = async function (asTempTable) {
// Run a subsearch to define the superset of possible results
if (this._scope) {
- // If subsearch has post-search filter, run and insert ids into temp table
- if (this._scope.hasPostSearchFilter()) {
+ // If subsearch has post-search filter or a recursive scope,
+ // run and insert ids into temp table
+ if (this._scope.hasPostSearchFilter() || this._scope._scope) {
var ids = await this._scope.search();
if (!ids) {
return [];
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index 943686cb0a..37d7f866fd 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -364,7 +364,7 @@ var ZoteroPane = new function () {
'zotero-tb-add': {
ArrowNext: () => document.getElementById("zotero-tb-lookup"),
ArrowPrevious: () => null,
- Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
+ Tab: () => document.getElementById("zotero-tb-search").focus(),
ShiftTab: () => {
if (collectionsPane.getAttribute("collapsed")) {
return document.getElementById('zotero-tb-sync');
@@ -378,7 +378,7 @@ var ZoteroPane = new function () {
'zotero-tb-lookup': {
ArrowNext: () => document.getElementById("zotero-tb-attachment-add"),
ArrowPrevious: () => document.getElementById("zotero-tb-add"),
- Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
+ Tab: () => document.getElementById("zotero-tb-search").focus(),
ShiftTab: () => document.getElementById('zotero-tb-collections-search').click(),
Enter: () => Zotero_Lookup.showPanel(event.target),
' ': () => Zotero_Lookup.showPanel(event.target)
@@ -386,13 +386,13 @@ var ZoteroPane = new function () {
'zotero-tb-attachment-add': {
ArrowNext: () => document.getElementById("zotero-tb-note-add"),
ArrowPrevious: () => document.getElementById("zotero-tb-lookup"),
- Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
+ Tab: () => document.getElementById("zotero-tb-search").focus(),
ShiftTab: () => document.getElementById('zotero-tb-collections-search').click()
},
'zotero-tb-note-add': {
ArrowNext: () => null,
ArrowPrevious: () => document.getElementById("zotero-tb-attachment-add"),
- Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
+ Tab: () => document.getElementById("zotero-tb-search").focus(),
ShiftTab: () => document.getElementById('zotero-tb-collections-search').click()
},
'zotero-tb-search-dropmarker': {
@@ -403,9 +403,7 @@ var ZoteroPane = new function () {
},
'zotero-tb-search-textbox': {
Tab: () => document.getElementById("zotero-tb-toggle-item-pane-stacked"),
- ShiftTab: () => {
- document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus();
- }
+ ShiftTab: () => document.getElementById("zotero-tb-search").focus()
},
'zotero-tb-toggle-item-pane-stacked': {
Tab: () => itemTree.querySelector(".virtualized-table"),
@@ -1554,59 +1552,9 @@ var ZoteroPane = new function () {
this.loadURI(Zotero.Groups.addGroupURL);
}
-
- this.newSearch = async function () {
- if (Zotero.DB.inTransaction()) {
- await Zotero.DB.waitForTransaction();
- }
-
- var libraryID = this.getSelectedLibraryID();
-
- var s = new Zotero.Search();
- s.libraryID = libraryID;
- s.addCondition('title', 'contains', '');
-
- var searches = await Zotero.Searches.getAll(libraryID)
- var prefix = Zotero.getString('pane.collections.untitled');
- var name = Zotero.Utilities.Internal.getNextName(
- prefix,
- searches.map(s => s.name).filter(n => n.startsWith(prefix))
- );
-
- var io = { dataIn: { search: s, name }, dataOut: null };
- window.openDialog('chrome://zotero/content/searchDialog.xhtml','','chrome,modal,centerscreen',io);
- if (!io.dataOut) {
- return false;
- }
- s.fromJSON(io.dataOut.json);
- await s.saveTx();
- return s.id;
- };
-
this.setVirtual = function (libraryID, type, show, select) {
return this.collectionsView.toggleVirtualCollection(libraryID, type, show, select);
};
-
- this.openAdvancedSearchWindow = function () {
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var enumerator = wm.getEnumerator('zotero:search');
- while (enumerator.hasMoreElements()) {
- var win = enumerator.getNext();
- }
-
- if (win) {
- win.focus();
- return;
- }
-
- var s = new Zotero.Search();
- s.libraryID = this.getSelectedLibraryID();
- s.addCondition('title', 'contains', '');
-
- var io = {dataIn: {search: s}, dataOut: null};
- window.openDialog('chrome://zotero/content/advancedSearch.xhtml', '', 'chrome,dialog=no,centerscreen', io);
- };
this.initItemsTree = async function () {
try {
@@ -1688,7 +1636,7 @@ var ZoteroPane = new function () {
this.tagSelector.handleResize();
}
if (this.collectionsView) {
- this.collectionsView.updateHeight();
+ this.collectionsView.updateHeightDebounced();
}
}, 100);
@@ -1756,7 +1704,6 @@ var ZoteroPane = new function () {
}
};
-
this.onCollectionSelected = Zotero.serial(async function () {
var collectionTreeRow = this.getCollectionTreeRow();
if (!collectionTreeRow) {
@@ -1772,13 +1719,43 @@ var ZoteroPane = new function () {
return;
}
+ let advancedSearchDeck = document.getElementById('zotero-advanced-search-pane-deck');
+ if (this.itemsView.collectionTreeRow?.isSearch()
+ && advancedSearchDeck.state === 'open'
+ && advancedSearchDeck.selectedSearchType === 'saved') {
+ let result = Services.prompt.confirmEx(window,
+ Zotero.getString('saved-search-close-confirmation-title'),
+ Zotero.getString('saved-search-close-confirmation-body'),
+ Ci.nsIPromptService.BUTTON_POS_0_DEFAULT
+ | Ci.nsIPrompt.BUTTON_TITLE_SAVE * Ci.nsIPrompt.BUTTON_POS_0
+ | Ci.nsIPrompt.BUTTON_TITLE_CANCEL * Ci.nsIPrompt.BUTTON_POS_1
+ | Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE * Ci.nsIPrompt.BUTTON_POS_2,
+ null, null, null,
+ null, {});
+ switch (result) {
+ case 0:
+ await advancedSearchDeck.pane.save();
+ return;
+ case 1:
+ this.collectionsView.selection.selectEventsSuppressed = true;
+ try {
+ await this.collectionsView.selectByID(this.itemsView.collectionTreeRow.id);
+ }
+ finally {
+ this.collectionsView.selection.selectEventsSuppressed = false;
+ }
+ return;
+ case 2:
+ await advancedSearchDeck.pane.cancel();
+ break;
+ }
+ }
+
// Rename tab
Zotero_Tabs.rename('zotero-pane', collectionTreeRow.getName());
- let type = Zotero.Libraries.get(collectionTreeRow.ref.libraryID).libraryType;
-
// Clear quick search and tag selector when switching views
- document.getElementById('zotero-tb-search-textbox').value = "";
+ document.getElementById('zotero-tb-search').onCollectionSelected();
if (ZoteroPane.tagSelector) {
ZoteroPane.tagSelector.clearTagSelection();
}
@@ -1788,6 +1765,7 @@ var ZoteroPane = new function () {
collectionTreeRow.setTags(ZoteroPane.tagSelector.getTagSelection());
}
+ this._refreshAdvancedSearchPane(collectionTreeRow);
this._updateEnabledActionsForRow(collectionTreeRow);
// If item data not yet loaded for library, load it now.
@@ -1815,6 +1793,125 @@ var ZoteroPane = new function () {
Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);
});
+
+
+ /**
+ * @param {Zotero.CollectionTreeRow} [collectionTreeRow] - During collection selection,
+ * the newly selected row, which isn't in the items view yet
+ */
+ this._refreshAdvancedSearchPane = function (collectionTreeRow) {
+ let deck = document.getElementById('zotero-advanced-search-pane-deck');
+
+ deck.pane.refresh();
+
+ let search = deck.state === 'closed' || deck.selectedSearchType !== 'temporary' || !deck.pane.active
+ ? null
+ : deck.pane.search;
+ if (collectionTreeRow) {
+ collectionTreeRow.setAdvancedSearch(search);
+ return undefined;
+ }
+ // Apply via the items view, whose row can be a different object from the
+ // collection tree's current row
+ return this.itemsView.setFilter('advanced-search', search);
+ };
+
+
+ /**
+ * @param {'open' | 'collapsed' | 'closed'} state
+ */
+ this.setAdvancedSearchState = async function (state) {
+ let deck = document.getElementById('zotero-advanced-search-pane-deck');
+ let oldState = deck.state;
+ deck.selectedSearchType = 'temporary';
+ deck.state = state;
+
+ let advancedSearchPane = deck.pane;
+
+ document.getElementById('zotero-tb-search').updateMode();
+ let refreshPromise;
+ if (state === 'open' && oldState === 'collapsed'
+ || state === 'collapsed' && oldState === 'open') {
+ // State change only causes visual refresh - update the tree height
+ this.itemsView.updateHeight();
+ }
+ else {
+ // State change changes displayed items - refresh the tree
+ refreshPromise = this._refreshAdvancedSearchPane();
+ }
+
+ // Update the pane state synchronously, so that a state change initiated
+ // while the refresh below is pending doesn't see a stale search
+ if (state === 'closed') {
+ advancedSearchPane.search = null;
+ }
+ else if (state === 'open') {
+ Zotero_Tabs.select('zotero-pane');
+ advancedSearchPane.focus();
+ }
+
+ await refreshPromise;
+ };
+
+
+ /**
+ * @param {'open' | 'collapsed' | 'closed'} state
+ */
+ this.toggleAdvancedSearchState = async function (state) {
+ let deck = document.getElementById('zotero-advanced-search-pane-deck');
+ if (state === deck.state && deck.selectedSearchType !== 'saved') {
+ // If we're trying to open the pane, and it's already open but not focused,
+ // focus it
+ if (state === 'open' && !deck.pane.matches(':focus-within')) {
+ deck.pane.focus();
+ return;
+ }
+
+ // Flip the state
+ switch (state) {
+ case 'open':
+ state = 'closed';
+ break;
+ case 'collapsed':
+ case 'closed':
+ state = 'open';
+ break;
+ }
+ }
+ await this.setAdvancedSearchState(state);
+ };
+
+
+ /**
+ * @param {'open' | 'closed'} state
+ */
+ this.setSavedSearchEditorState = async function (state) {
+ let collectionTreeRow = this.getCollectionTreeRow();
+ if (state === 'open' && !collectionTreeRow.isSearch()) {
+ throw new Error('Cannot show saved search editor outside search row');
+ }
+
+ let deck = document.getElementById('zotero-advanced-search-pane-deck');
+ deck.selectedSearchType = 'saved';
+ deck.state = state;
+
+ if (state === 'open') {
+ deck.pane.search = collectionTreeRow.ref;
+ }
+
+ document.getElementById('zotero-tb-search').updateMode();
+ let refreshPromise = this._refreshAdvancedSearchPane();
+ if (state === 'open') {
+ deck.pane.focus();
+ }
+ await refreshPromise;
+ };
+
+
+ this.openAdvancedSearchWindow = function () {
+ Zotero.debug(`ZoteroPane.openAdvancedSearchWindow() is deprecated -- use ZoteroPane.toggleAdvancedSearchState() instead`);
+ this.toggleAdvancedSearchState('open');
+ };
/**
@@ -1828,7 +1925,6 @@ var ZoteroPane = new function () {
"menu_noteAdd",
"cmd_zotero_newCollection",
- "cmd_zotero_newSavedSearch",
"cmd_zotero_import",
"cmd_zotero_importFromClipboard",
@@ -2580,28 +2676,7 @@ var ZoteroPane = new function () {
this.collectionsView.startEditing(row);
}
else {
- let s = row.ref.clone();
- let groups = [];
- // Promises don't work in the modal dialog, so get the group name here, if
- // applicable, and pass it in. We only need the group that this search belongs
- // to, if any, since the library drop-down is disabled for saved searches.
- if (Zotero.Libraries.get(s.libraryID).libraryType == 'group') {
- groups.push(Zotero.Groups.getByLibraryID(s.libraryID));
- }
- var io = {
- dataIn: {
- search: s,
- name: row.getName(),
- groups: groups
- },
- dataOut: null
- };
- window.openDialog('chrome://zotero/content/searchDialog.xhtml','','chrome,modal,centerscreen',io);
- if (io.dataOut) {
- row.ref.fromJSON(io.dataOut.json);
- await row.ref.saveTx();
- Zotero_Tabs.rename("zotero-pane", row.ref.name);
- }
+ this.setSavedSearchEditorState('open');
}
}
};
@@ -3274,10 +3349,6 @@ var ZoteroPane = new function () {
id: "newCollection",
command: "cmd_zotero_newCollection"
},
- {
- id: "newSavedSearch",
- command: "cmd_zotero_newSavedSearch"
- },
{
id: "newSubcollection",
oncommand: () => {
@@ -3564,8 +3635,7 @@ var ZoteroPane = new function () {
show.push(
'sync',
'sep1',
- 'newCollection',
- 'newSavedSearch'
+ 'newCollection'
);
}
// Only show "Show Duplicates", "Show Unfiled Items", and "Show Retracted" if rows are hidden
@@ -6784,7 +6854,7 @@ var ZoteroPane = new function () {
if (ZoteroPane.itemsView) {
// Need to immediately rerender the items here without any debouncing
// since tree height will have changed
- ZoteroPane.itemsView._updateHeight();
+ ZoteroPane.itemsView.updateHeight();
}
ZoteroContextPane.update();
Zotero_Tabs.updateSidebarLayout();
@@ -6970,7 +7040,7 @@ var ZoteroPane = new function () {
var collectionsPaneWidth = collectionsPane.getBoundingClientRect().width;
tagSelector.style.maxWidth = collectionsPaneWidth + 'px';
if (ZoteroPane.itemsView) {
- ZoteroPane.itemsView.updateHeight();
+ ZoteroPane.itemsView.updateHeightDebounced();
}
this.handleTagSelectorResize();
diff --git a/chrome/content/zotero/zoteroPane.xhtml b/chrome/content/zotero/zoteroPane.xhtml
index a15a042f6e..9f4d1cf0c2 100644
--- a/chrome/content/zotero/zoteroPane.xhtml
+++ b/chrome/content/zotero/zoteroPane.xhtml
@@ -102,7 +102,7 @@
-
+
@@ -116,7 +116,6 @@
-
@@ -428,7 +427,7 @@
key="key_find" accesskey="&findCmd.accesskey;"
oncommand="ZoteroStandalone.currentReader.toggleFindPopup({ open: true });"/>
@@ -936,7 +935,6 @@
-
@@ -1371,6 +1369,7 @@
+
diff --git a/chrome/locale/en-US/zotero/searchbox.dtd b/chrome/locale/en-US/zotero/searchbox.dtd
index f01c0d13bc..3c1deb7ff4 100644
--- a/chrome/locale/en-US/zotero/searchbox.dtd
+++ b/chrome/locale/en-US/zotero/searchbox.dtd
@@ -1,7 +1,5 @@
-
-
@@ -19,7 +17,3 @@
-
-
-
-
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
index e07c5fd99c..bde238d75e 100644
--- a/chrome/locale/en-US/zotero/zotero.dtd
+++ b/chrome/locale/en-US/zotero/zotero.dtd
@@ -82,7 +82,6 @@
-
@@ -93,7 +92,6 @@
-
diff --git a/chrome/locale/en-US/zotero/zotero.ftl b/chrome/locale/en-US/zotero/zotero.ftl
index cb5a9df4ee..9b177bf6c1 100644
--- a/chrome/locale/en-US/zotero/zotero.ftl
+++ b/chrome/locale/en-US/zotero/zotero.ftl
@@ -48,11 +48,15 @@ general-view-troubleshooting-instructions = View Troubleshooting Instructions
general-go-back = Go Back
general-accept = Accept
general-cancel = Cancel
+cancel-button =
+ .label = { general-cancel }
general-show-in-library = Show in Library
general-restartApp = Restart { -app-name }
general-restartInTroubleshootingMode = Restart in Troubleshooting Mode
general-save = Save
general-clear = Clear
+clear-button =
+ .label = { general-clear }
general-update = Update
general-back = Back
general-edit = Edit
@@ -193,8 +197,9 @@ item-menu-remove-from-recently-read =
.label = Remove from { recently-read }…
collections-menu-rename-collection =
.label = Rename Collection
+edit-saved-search = Edit Saved Search
collections-menu-edit-saved-search =
- .label = Edit Saved Search
+ .label = { edit-saved-search }
collections-menu-move-collection =
.label = Move To
collections-menu-copy-collection =
@@ -725,6 +730,13 @@ quicksearch-input =
.placeholder = { $placeholder }
.aria-description = { $placeholder }
+advanced-search = Advanced Search
+menuitem-advanced-search =
+ .label = { advanced-search }
+quicksearch-advanced-search-button =
+ .tooltiptext = { advanced-search }
+ .aria-label = { advanced-search }
+
item-pane-header-view-as =
.label = View As
item-pane-header-none =
@@ -926,3 +938,11 @@ os-keystore-migrate-failed =
[windows] { -app-name } couldn’t encrypt your stored credentials. Your credentials remain stored unencrypted on disk. Restart { -app-name } and try again.
*[other] { -app-name } couldn’t access your { -os-name } keyring to encrypt your stored credentials. Your credentials remain stored unencrypted on disk. Make sure a keyring service is running and restart { -app-name }.
}
+
+search-button =
+ .label = Search
+save-search-button =
+ .label = Save Search
+
+saved-search-close-confirmation-title = Editing Saved Search
+saved-search-close-confirmation-body = Do you want to save changes you made to this saved search?
diff --git a/chrome/skin/default/zotero/20/universal/maximize.svg b/chrome/skin/default/zotero/20/universal/maximize.svg
new file mode 100644
index 0000000000..ea46e4bfd4
--- /dev/null
+++ b/chrome/skin/default/zotero/20/universal/maximize.svg
@@ -0,0 +1,3 @@
+
diff --git a/chrome/skin/default/zotero/20/universal/minimize.svg b/chrome/skin/default/zotero/20/universal/minimize.svg
new file mode 100644
index 0000000000..e4b6fa2700
--- /dev/null
+++ b/chrome/skin/default/zotero/20/universal/minimize.svg
@@ -0,0 +1,3 @@
+
diff --git a/scss/_zotero.scss b/scss/_zotero.scss
index e027b26b82..c3f572158f 100644
--- a/scss/_zotero.scss
+++ b/scss/_zotero.scss
@@ -21,7 +21,6 @@
// Components
// --------------------------------------------------
-@import "components/advancedSearch";
@import "components/annotation";
@import "components/autosuggest";
@import "components/banners";
@@ -79,6 +78,7 @@
// Elements
// --------------------------------------------------
+@import "elements/advancedSearchPane";
@import "elements/attachmentBox";
@import "elements/attachmentPreview";
@import "elements/attachmentPreviewBox";
diff --git a/scss/components/_advancedSearch.scss b/scss/components/_advancedSearch.scss
deleted file mode 100644
index 8efd0026d5..0000000000
--- a/scss/components/_advancedSearch.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-#zotero-advanced-search-dialog {
- @include macOS-normalize-controls;
-
- min-height: 500px;
- min-width: 700px;
-
- #zotero-search-box-container {
- max-height: 100vh;
- }
-
- #zotero-search-box-controls {
- padding: 3px;
- }
-
- #recursiveCheckbox, #noChildrenCheckbox, #includeParentsAndChildrenCheckbox {
- margin: 3px 6px;
- }
-
- #zotero-search-buttons {
- margin: 3px 0;
-
- button {
- margin-inline-start: 6px;
- }
- }
-}
diff --git a/scss/components/_menu.scss b/scss/components/_menu.scss
index 2771d641ac..1174afa48d 100644
--- a/scss/components/_menu.scss
+++ b/scss/components/_menu.scss
@@ -27,7 +27,6 @@ $menu-icons: (
library-lookup: "library-lookup",
new-feed: "feed",
note: "note",
- new-saved-search: "saved-search",
show-duplicates: "duplicate",
show-unfiled: "unfiled",
show-retracted: "retracted",
diff --git a/scss/components/_toolbar.scss b/scss/components/_toolbar.scss
index 81b36a20a2..ed84dd0183 100644
--- a/scss/components/_toolbar.scss
+++ b/scss/components/_toolbar.scss
@@ -1,8 +1,10 @@
+$toolbar-padding-inline: 8px;
+
.toolbar {
height: $height-toolbar !important; /* Hard-code this to fix toolbar icon compression on Linux */
min-height: $height-toolbar; /* Needed to prevent squashing by stretched tag selector */
margin: 0;
- padding: 0px 8px 0px 8px;
+ padding: 0 $toolbar-padding-inline;
min-width: 1px;
}
diff --git a/scss/elements/_advancedSearchPane.scss b/scss/elements/_advancedSearchPane.scss
new file mode 100644
index 0000000000..92dd44f069
--- /dev/null
+++ b/scss/elements/_advancedSearchPane.scss
@@ -0,0 +1,54 @@
+advanced-search-deck {
+ flex-direction: column;
+
+ > advanced-search-pane:not(.deck-selected) {
+ display: none;
+ }
+}
+
+advanced-search-pane {
+ @include inactive-opacity;
+
+ flex-direction: column;
+ // Inline padding is further down the tree so focus rings work with overflow: auto
+ padding-block: 8px;
+ gap: 8px;
+ background: var(--material-toolbar);
+
+ .saved-search-name-row {
+ align-items: center;
+ }
+
+ .advanced-search-buttons {
+ padding-inline: 8px;
+ gap: 8px;
+
+ button {
+ min-width: 100px;
+ }
+ }
+
+ &[type="temporary"] {
+ .saved-search-name-row {
+ display: none;
+ }
+
+ .advanced-search-buttons {
+ .cancel-button {
+ display: none;
+ }
+ }
+ }
+
+ &[type="saved"] {
+ .advanced-search-buttons {
+ .search-button {
+ display: none;
+ }
+
+ .clear-button {
+ display: none;
+ }
+ }
+ }
+}
diff --git a/scss/elements/_quickSearchTextbox.scss b/scss/elements/_quickSearchTextbox.scss
index b4e3dc07cc..87b02f8b00 100644
--- a/scss/elements/_quickSearchTextbox.scss
+++ b/scss/elements/_quickSearchTextbox.scss
@@ -28,6 +28,10 @@ quick-search-textbox {
}
}
+#zotero-tb-search {
+ height: 100%;
+}
+
#zotero-tb-search-dropmarker {
position: relative;
// 6px padding + 16px icon width + 2px padding + 8px dropmarker width + 4px padding
@@ -70,6 +74,51 @@ quick-search-textbox {
height: 28px;
z-index: 1;
width: 100%;
+
+ // Keep input text and the clear icon out from under the Advanced Search button
+ &:has(~ #zotero-tb-search-advanced-button) {
+ padding-inline-end: 30px;
+ }
+}
+
+// Match the specificity of the #zotero-items-toolbar toolbarbutton rules
+#zotero-tb-search #zotero-tb-search-advanced-button {
+ position: relative;
+ width: 28px;
+ height: 28px;
+ min-width: 0;
+ margin: 0;
+ margin-inline-start: -30px;
+ margin-inline-end: 2px;
+ padding: 0;
+ z-index: 2;
+ @include svgicon-menu("filter", "universal", "16");
+ color: var(--fill-secondary);
+
+ @include focus-ring;
+
+ // Show the hover effect on a chip around the icon, inset within the field,
+ // rather than on the whole button
+ &:hover, &:active {
+ background-color: transparent;
+ }
+
+ .toolbarbutton-icon {
+ width: 24px;
+ height: 24px;
+ // The glyph is weighted slightly to the right within the SVG, so shift it
+ // a pixel left to optically center it (physical sides -- doesn't flip in RTL)
+ padding: 5px 6px 5px 4px;
+ border-radius: 4px;
+ }
+
+ &:hover .toolbarbutton-icon {
+ background-color: var(--fill-quinary);
+ }
+
+ &:active .toolbarbutton-icon {
+ background-color: var(--fill-quarternary);
+ }
}
#zotero-tb-search-spinner {
@@ -79,6 +128,59 @@ quick-search-textbox {
visibility: hidden;
}
-#search-wrapper {
+#search-deck {
width: 100%;
-}
\ No newline at end of file
+}
+
+#search-wrapper {
+ align-items: center;
+}
+
+#advanced-search-indicator {
+ gap: 8px;
+ align-items: center;
+
+ // Cover the inline-end toolbar padding and the existing bottom border
+ margin-inline-end: -$toolbar-padding-inline;
+ padding-inline: $toolbar-padding-inline;
+ margin-bottom: -1px;
+
+ background: var(--material-toolbar);
+ border-inline-start: var(--material-border);
+
+ // Prevent text from shifting when border shows after panel is collapsed
+ border-block: var(--material-border-transparent);
+
+ // This is styled like it's part of the Advanced Search panel, which can't drag the window,
+ // so match that behavior
+ -moz-window-dragging: no-drag;
+
+ & > label {
+ flex: 1;
+ padding-inline: 4px;
+ font-weight: 700;
+ }
+
+ .zotero-clicky {
+ width: 28px;
+ height: 28px;
+ margin: 0;
+ padding: 4px;
+ }
+
+ .advanced-collapse-button {
+ @include svgicon-menu("minimize", "universal", "20");
+ }
+
+ .advanced-close-button {
+ @include svgicon-menu("x", "universal", "20");
+ }
+
+ &[data-collapsed="true"] {
+ border-bottom: var(--material-border);
+
+ .advanced-collapse-button {
+ @include svgicon-menu("maximize", "universal", "20");
+ }
+ }
+}
diff --git a/scss/elements/_zoteroSearch.scss b/scss/elements/_zoteroSearch.scss
index 9085b3ed6e..8e1a0be213 100644
--- a/scss/elements/_zoteroSearch.scss
+++ b/scss/elements/_zoteroSearch.scss
@@ -1,15 +1,31 @@
zoterosearch {
- #search-box > hbox, #search-box > groupbox {
- margin: 0 6px;
- }
+ flex-direction: column;
+ padding-inline: 8px;
+ gap: 8px;
+ overflow: auto;
+ max-height: 33vh;
+ @include macOS-normalize-controls;
groupbox {
- margin-top: 0;
- padding-top: 0;
+ gap: 8px;
& > #conditions {
- -moz-appearance: groupbox;
- padding: 6px;
+ min-width: fit-content; // Grow with content, don't overflow
+ padding: 8px;
+ gap: 8px;
+ background: var(--fill-senary);
+ border: var(--material-border-quinary);
+ border-radius: 6px;
+ }
+ }
+
+ #search-option-checkboxes {
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 8px;
+
+ checkbox {
+ margin: 0;
}
}
@@ -23,10 +39,6 @@ zoterosearch {
padding-left: 0 !important;
}
- checkbox {
- margin-right: .5em;
- }
-
input {
margin-block: 0;
}
diff --git a/test/tests/advancedSearchTest.js b/test/tests/advancedSearchTest.js
index 455409d4a1..b995f7768f 100644
--- a/test/tests/advancedSearchTest.js
+++ b/test/tests/advancedSearchTest.js
@@ -1,7 +1,7 @@
"use strict";
describe("Advanced Search", function () {
- var win, zp;
+ var win, zp, deck;
before(function* () {
yield resetDB({
@@ -10,56 +10,170 @@ describe("Advanced Search", function () {
});
win = yield loadZoteroPane();
zp = win.ZoteroPane;
+ deck = win.document.getElementById('zotero-advanced-search-pane-deck');
});
after(function () {
win.close();
});
- it("should perform a search", function* () {
- var item = yield createDataObject('item', { setTitle: true });
+ it("should perform a search", async function () {
+ var item = await createDataObject('item', { setTitle: true });
+ var otherItem = await createDataObject('item', { setTitle: true });
+
+ await zp.toggleAdvancedSearchState('open');
+ var pane = deck.pane;
+
+ // Opening the pane shouldn't filter the items list
+ await zp.itemsView.waitForLoad();
+ assert.equal(zp.itemsView.rowCount, 2);
- var promise = waitForWindow('chrome://zotero/content/advancedSearch.xhtml');
- zp.openAdvancedSearchWindow();
- var searchWin = yield promise;
- yield searchWin.ZoteroAdvancedSearch._loadedDeferred.promise;
// Add condition
- var searchBox = searchWin.document.getElementById('zotero-search-box');
-
var s = new Zotero.Search();
- s.addCondition('title', 'is', item.getField('title'))
- searchBox.search = s;
+ s.libraryID = item.libraryID;
+ s.addCondition('title', 'is', item.getField('title'));
+ pane.search = s;
// Run search and wait for results
- var o = searchWin.ZoteroAdvancedSearch;
- var iv = o.itemsView;
- yield iv.waitForLoad();
- yield o.search();
- yield iv.waitForLoad();
+ var iv = zp.itemsView;
+ await pane.submit();
+ await iv.waitForLoad();
// Check results
assert.equal(iv.rowCount, 1);
var index = iv.getRowIndexByID(item.id);
assert.isNumber(index);
- searchWin.close();
+ // Closing should restore the unfiltered view
+ await zp.setAdvancedSearchState('closed');
+ assert.equal(iv.rowCount, 2);
- yield item.eraseTx();
+ await item.eraseTx();
+ await otherItem.eraseTx();
+ });
+
+ it("should show results in trash", async function () {
+ var item = await createDataObject('item', { setTitle: true });
+ item.deleted = true;
+ await item.saveTx();
+
+ await zp.toggleAdvancedSearchState('open');
+ var pane = deck.pane;
+
+ var s = new Zotero.Search();
+ s.libraryID = item.libraryID;
+ s.addCondition('title', 'is', item.getField('title'));
+ pane.search = s;
+
+ var iv = zp.itemsView;
+ await pane.submit();
+ await iv.waitForLoad();
+
+ assert.isNumber(iv.getRowIndexByID(item.id));
+
+ await zp.setAdvancedSearchState('closed');
+
+ await item.eraseTx();
+ });
+
+ it("shouldn't reapply previous search when reopened while closing", async function () {
+ var item = await createDataObject('item', { setTitle: true });
+
+ await zp.toggleAdvancedSearchState('open');
+ var s = new Zotero.Search();
+ s.libraryID = item.libraryID;
+ s.addCondition('title', 'is', 'nomatch');
+ deck.pane.search = s;
+ await deck.pane.submit();
+ await zp.itemsView.waitForLoad();
+ assert.equal(zp.itemsView.rowCount, 0);
+
+ // Close and immediately reopen, without waiting, as with UI clicks
+ zp.toggleAdvancedSearchState('closed');
+ await zp.toggleAdvancedSearchState('open');
+ await zp.itemsView.waitForLoad();
+
+ // The previous search shouldn't have been reapplied
+ assert.isNumber(zp.itemsView.getRowIndexByID(item.id));
+
+ await zp.setAdvancedSearchState('closed');
+ await item.eraseTx();
+ });
+
+ it("should scope results to the selected saved search", async function () {
+ var inBoth = await createDataObject('item', { title: "foo bar" });
+ var inSavedOnly = await createDataObject('item', { title: "foo baz" });
+ var inAdvancedOnly = await createDataObject('item', { title: "bar qux" });
+
+ var saved = new Zotero.Search();
+ saved.libraryID = Zotero.Libraries.userLibraryID;
+ saved.name = "Scope Test";
+ saved.addCondition('title', 'contains', 'foo');
+ await saved.saveTx();
+ await select(win, saved);
+
+ await zp.toggleAdvancedSearchState('open');
+ var s = new Zotero.Search();
+ s.libraryID = saved.libraryID;
+ s.addCondition('title', 'contains', 'bar');
+ deck.pane.search = s;
+
+ var iv = zp.itemsView;
+ await deck.pane.submit();
+ await iv.waitForLoad();
+
+ // Only the item matching both the saved search and the advanced search
+ assert.equal(iv.rowCount, 1);
+ assert.isNumber(iv.getRowIndexByID(inBoth.id));
+
+ // The saved search itself shouldn't have been modified
+ assert.lengthOf(Object.keys(saved.getConditions()), 1);
+
+ await zp.setAdvancedSearchState('closed');
+
+ await Zotero.Items.erase([inBoth.id, inSavedOnly.id, inAdvancedOnly.id]);
+ await saved.eraseTx();
+ });
+
+ it("should search across feeds in Feeds view", async function () {
+ let feed = await createFeed();
+ let feedItem = await createDataObject('feedItem', { libraryID: feed.libraryID, setTitle: true }, { skipSelect: true });
+ let otherFeedItem = await createDataObject('feedItem', { libraryID: feed.libraryID, setTitle: true }, { skipSelect: true });
+
+ await zp.collectionsView.selectFeeds();
+ await waitForItemsLoad(win);
+
+ await zp.toggleAdvancedSearchState('open');
+ var s = new Zotero.Search();
+ s.libraryID = Zotero.Libraries.userLibraryID;
+ s.addCondition('title', 'is', feedItem.getField('title'));
+ deck.pane.search = s;
+
+ var iv = zp.itemsView;
+ await deck.pane.submit();
+ await iv.waitForLoad();
+
+ assert.equal(iv.rowCount, 1);
+ assert.isNumber(iv.getRowIndexByID(feedItem.id));
+
+ await zp.setAdvancedSearchState('closed');
+ await selectLibrary(win);
+
+ await feed.eraseTx();
});
describe("Conditions", function () {
- var searchWin, searchBox, conditions;
+ var pane, searchBox, conditions;
- before(function* () {
- var promise = waitForWindow('chrome://zotero/content/advancedSearch.xhtml');
- zp.openAdvancedSearchWindow();
- searchWin = yield promise;
- searchBox = searchWin.document.getElementById('zotero-search-box');
+ before(async function () {
+ await zp.toggleAdvancedSearchState('open');
+ pane = deck.pane;
+ searchBox = pane.querySelector('zoterosearch');
conditions = searchBox.querySelector('#conditions');
});
- after(function () {
- searchWin.close();
+ after(async function () {
+ await zp.setAdvancedSearchState('closed');
});
describe("Collection", function () {
@@ -73,8 +187,9 @@ describe("Advanced Search", function () {
// Add condition
var s = new Zotero.Search();
+ s.libraryID = Zotero.Libraries.userLibraryID;
s.addCondition('title', 'is', '');
- searchBox.search = s;
+ pane.search = s;
var searchCondition = conditions.firstChild;
var conditionsMenu = searchCondition.querySelector('#conditionsmenu');
@@ -113,8 +228,9 @@ describe("Advanced Search", function () {
var search = await createDataObject('search', { name: "A" });
var s = new Zotero.Search();
+ s.libraryID = Zotero.Libraries.userLibraryID;
s.addCondition('savedSearch', 'is', search.key);
- searchBox.search = s;
+ pane.search = s;
var searchCondition = conditions.firstChild;
var conditionsMenu = searchCondition.querySelector('#conditionsmenu');
@@ -132,8 +248,9 @@ describe("Advanced Search", function () {
var search = await createDataObject('search', { name: "B" });
var s = new Zotero.Search();
+ s.libraryID = Zotero.Libraries.userLibraryID;
s.addCondition('title', 'is', '');
- searchBox.search = s;
+ pane.search = s;
var searchCondition = conditions.firstChild;
var conditionsMenu = searchCondition.querySelector('#conditionsmenu');
@@ -174,8 +291,9 @@ describe("Advanced Search", function () {
var search2 = await createDataObject('search', { name: "D", libraryID: groupLibraryID });
var s = new Zotero.Search();
+ s.libraryID = Zotero.Libraries.userLibraryID;
s.addCondition('title', 'is', '');
- searchBox.search = s;
+ pane.search = s;
var searchCondition = conditions.firstChild;
var conditionsMenu = searchCondition.querySelector('#conditionsmenu');
@@ -198,18 +316,13 @@ describe("Advanced Search", function () {
}
assert.equal(valueMenu.value, "S" + search1.key);
- var libraryMenu = searchWin.document.getElementById('libraryMenu');
- for (let i = 0; i < libraryMenu.itemCount; i++) {
- let menuitem = libraryMenu.getItemAtIndex(i);
- // Switch to group library
- if (menuitem.value == groupLibraryID) {
- menuitem.click();
- break;
- }
- }
+ // Switch to the group library in the collection tree, which changes
+ // the search library and re-renders the conditions
+ await selectLibrary(win, groupLibraryID);
var values = [];
- valueMenu = searchCondition.querySelector('#valuemenu')
+ searchCondition = conditions.firstChild;
+ valueMenu = searchCondition.querySelector('#valuemenu');
assert.equal(valueMenu.value, "C" + collection2.key);
for (let i = 0; i < valueMenu.itemCount; i++) {
let menuitem = valueMenu.getItemAtIndex(i);
@@ -220,6 +333,8 @@ describe("Advanced Search", function () {
assert.include(values, "C" + collection2.key);
assert.include(values, "S" + search2.key);
+ await selectLibrary(win);
+
await Zotero.Collections.erase([collection1.id, collection2.id]);
await Zotero.Searches.erase([search1.id, search2.id]);
});
diff --git a/test/tests/collectionViewItemTreeTest.js b/test/tests/collectionViewItemTreeTest.js
index f3b9182314..7b8132f6a8 100644
--- a/test/tests/collectionViewItemTreeTest.js
+++ b/test/tests/collectionViewItemTreeTest.js
@@ -2783,47 +2783,6 @@ describe("CollectionViewItemTree", function () {
assert.include(text, toplevelItemTwo.getDisplayTitle());
});
});
-
- describe('Advanced Search', function () {
- describe('#notify', function () {
- it('should resolve the returned promise when an item is selected', async function() {
- var item = await createDataObject('item', { setTitle: true });
- var promise = waitForWindow('chrome://zotero/content/advancedSearch.xhtml');
- zp.openAdvancedSearchWindow();
- var searchWin = await promise;
- await searchWin.ZoteroAdvancedSearch._loadedDeferred.promise;
- // Add condition
- var searchBox = searchWin.document.getElementById('zotero-search-box');
-
- var s = new Zotero.Search();
- s.addCondition('title', 'is', item.getField('title'))
- searchBox.search = s;
-
- // Run search and wait for results
- var o = searchWin.ZoteroAdvancedSearch;
- var iv = o.itemsView;
- await iv.waitForLoad();
- await o.search();
- await iv.waitForLoad();
-
- // Check results
- assert.equal(iv.rowCount, 1);
-
- // Make sure an item is selected (otherwise notify resolves fine)
- await iv.selectItem(item.id);
- assert.equal(iv.selection.count, 1);
-
- let notifySpy = sinon.spy(iv, 'notify');
- await createDataObject('item');
- assert.isTrue(notifySpy.calledOnce);
- await notifySpy.returnValues[0];
- notifySpy.restore();
-
- searchWin.close();
- await item.eraseTx();
- });
- });
- });
describe("Search error handling", function () {
var rowProvider;
diff --git a/test/tests/searchTest.js b/test/tests/searchTest.js
index 4dc950174e..c920f4d4d4 100644
--- a/test/tests/searchTest.js
+++ b/test/tests/searchTest.js
@@ -236,6 +236,9 @@ describe("Zotero.Search", function () {
matches = await s.search();
// Result should be the same
assert.sameMembers(matches, [itemOne.id, itemTwo.id]);
+
+ await itemOne.eraseTx();
+ await itemTwo.eraseTx();
});
});
diff --git a/test/tests/zoteroPaneTest.js b/test/tests/zoteroPaneTest.js
index b4fe9b147e..8c89a0559b 100644
--- a/test/tests/zoteroPaneTest.js
+++ b/test/tests/zoteroPaneTest.js
@@ -117,30 +117,22 @@ describe("ZoteroPane", function () {
});
});
- describe("#newSearch()", function () {
+ describe("Advanced Search", function () {
it("should create a saved search", async function () {
- var promise = waitForDialog(
- // TODO: Test changing a condition
- function (dialog) {},
- 'accept',
- 'chrome://zotero/content/searchDialog.xhtml'
- );
- var id = await zp.newSearch();
- await promise;
- var search = Zotero.Searches.get(id);
- assert.ok(search);
- assert.isTrue(search.name.startsWith(Zotero.getString('pane.collections.untitled')));
- });
-
- it("should handle clicking Cancel in the search window", async function () {
- var promise = waitForDialog(
- function (dialog) {},
- 'cancel',
- 'chrome://zotero/content/searchDialog.xhtml'
- );
- var id = await zp.newSearch();
- await promise;
- assert.isFalse(id);
+ await selectLibrary(win);
+ await zp.toggleAdvancedSearchState('open');
+ var deck = doc.getElementById('zotero-advanced-search-pane-deck');
+
+ var searchIDs = (await Zotero.Searches.getAll(userLibraryID)).map(s => s.id);
+ await deck.pane.save();
+
+ var newSearches = (await Zotero.Searches.getAll(userLibraryID))
+ .filter(s => !searchIDs.includes(s.id));
+ assert.lengthOf(newSearches, 1);
+ assert.isTrue(newSearches[0].name.startsWith(Zotero.getString('pane.collections.untitled')));
+ assert.equal(deck.state, 'closed');
+
+ await newSearches[0].eraseTx();
});
});
@@ -1007,19 +999,26 @@ describe("ZoteroPane", function () {
});
describe("#editSelectedCollection()", function () {
+ async function editSearchAddCondition(search) {
+ await select(win, search);
+ await zp.editSelectedCollection();
+
+ var deck = doc.getElementById('zotero-advanced-search-pane-deck');
+ assert.equal(deck.state, 'open');
+ assert.equal(deck.selectedSearchType, 'saved');
+
+ var pane = deck.pane;
+ var searchBox = pane.querySelector('zoterosearch');
+ var c = searchBox.search.getCondition(
+ searchBox.search.addCondition("title", "contains", "foo")
+ );
+ searchBox.addCondition(c);
+ await pane.save();
+ }
+
it("should edit a saved search", async function () {
var search = await createDataObject('search');
- await select(win, search);
- var promise = waitForWindow('chrome://zotero/content/searchDialog.xhtml', function (win) {
- let searchBox = win.document.getElementById('search-box');
- var c = searchBox.search.getCondition(
- searchBox.search.addCondition("title", "contains", "foo")
- );
- searchBox.addCondition(c);
- win.document.querySelector('dialog').acceptDialog();
- });
- await zp.editSelectedCollection();
- await promise;
+ await editSearchAddCondition(search);
var conditions = search.getConditions();
assert.lengthOf(Object.keys(conditions), 3);
});
@@ -1027,17 +1026,7 @@ describe("ZoteroPane", function () {
it("should edit a saved search in a group", async function () {
var group = await getGroup();
var search = await createDataObject('search', { libraryID: group.libraryID });
- await select(win, search);
- var promise = waitForWindow('chrome://zotero/content/searchDialog.xhtml', function (win) {
- let searchBox = win.document.getElementById('search-box');
- var c = searchBox.search.getCondition(
- searchBox.search.addCondition("title", "contains", "foo")
- );
- searchBox.addCondition(c);
- win.document.querySelector('dialog').acceptDialog();
- });
- await zp.editSelectedCollection();
- await promise;
+ await editSearchAddCondition(search);
var conditions = search.getConditions();
assert.lengthOf(Object.keys(conditions), 3);
});