Don't prompt to save an unchanged saved search

https://forums.zotero.org/discussion/133302/zotero-10-questions-about-search
This commit is contained in:
Dan Stillman 2026-08-20 15:36:55 -04:00
parent 0746c665a9
commit 0ee3e4052e
3 changed files with 87 additions and 3 deletions

View file

@ -140,6 +140,42 @@
this._search.addCondition('title', 'contains', '');
}
this._searchElem.search = this._search;
this._loadedState = this.type === 'saved' ? this._getState() : null;
}
/**
* The editor's current name and conditions, for comparison against the state the
* search was loaded with
*
* The search is serialized through the editor, since rendering an existing search
* can normalize it (e.g., folding a legacy 'noChildren' condition into the result
* level), and the normalized form is what a save would write.
*/
_getState() {
this._searchElem.updateSearch();
return {
name: this._nameField.value,
conditions: this._search.toJSON().conditions
};
}
/**
* Whether the name or conditions have been edited since the search was loaded,
* for type "saved"
*/
get hasChanges() {
if (this.type !== 'saved') {
throw new Error('hasChanges is unsupported for temporary search');
}
if (!this._loadedState) {
return false;
}
let state = this._getState();
return state.name !== this._loadedState.name
|| state.conditions.length !== this._loadedState.conditions.length
|| state.conditions.some((condition, i) => !Zotero.Searches.conditionEquals(
condition, this._loadedState.conditions[i]
));
}
_ensureSearch() {

View file

@ -1921,15 +1921,15 @@ var ZoteroPane = new function () {
/**
* Prompt to save changes in the open saved-search editor and close it
*
* If the search being edited was deleted or trashed, the editor is closed without
* a prompt, since there's nothing left to save the changes to.
* If the editor has no changes to save, or if the search being edited was deleted or
* trashed, leaving nothing to save the changes to, the editor is closed without a prompt.
*
* @return {Promise<Boolean>} - False if the user chose Cancel, leaving the editor open
*/
this._confirmCloseSavedSearchEditor = async function () {
let deck = document.getElementById('zotero-advanced-search-pane-deck');
let editedSearch = Zotero.Searches.get(deck.pane.editedSearchID);
if (!editedSearch || editedSearch.deleted) {
if (!editedSearch || editedSearch.deleted || !deck.pane.hasChanges) {
await deck.pane.cancel();
return true;
}

View file

@ -698,6 +698,49 @@ describe("Advanced Search", function () {
await selectLibrary(win);
});
it("should close the saved-search editor without prompting when nothing was changed", async function () {
var saved = await createDataObject('search', { name: "UnchangedEditing" });
await select(win, saved);
await zp.setSavedSearchEditorState('open');
assert.equal(deck.selectedSearchType, 'saved');
let stub = sinon.stub().returns(1); // Cancel
let promptService = win.Services.prompt;
win.Services.prompt = { confirmEx: stub };
try {
await selectLibrary(win);
assert.equal(stub.callCount, 0);
assert.equal(deck.state, 'closed');
}
finally {
win.Services.prompt = promptService;
}
await saved.eraseTx();
});
it("should prompt when only the saved search's name was changed", async function () {
var saved = await createDataObject('search', { name: "RenameWhileEditing" });
await select(win, saved);
await zp.setSavedSearchEditorState('open');
deck.pane.querySelector('#saved-search-name').value = "RenameWhileEditing 2";
let stub = sinon.stub().returns(0); // Save
let promptService = win.Services.prompt;
win.Services.prompt = { confirmEx: stub };
try {
await selectLibrary(win);
assert.equal(stub.callCount, 1);
assert.equal(saved.name, "RenameWhileEditing 2");
}
finally {
win.Services.prompt = promptService;
}
await saved.eraseTx();
});
it("should revert to the edited search without re-prompting when canceling", async function () {
var search1 = await createDataObject('search', { name: "CancelEditing1" });
var search2 = await createDataObject('search', { name: "CancelEditing2" });
@ -707,6 +750,11 @@ describe("Advanced Search", function () {
await zp.setSavedSearchEditorState('open');
assert.equal(deck.selectedSearchType, 'saved');
// Edit the editor's working copy, so that closing it prompts
var searchBox = deck.pane.querySelector('zoterosearch');
searchBox.querySelector('.conditions').firstChild.querySelector('#valuefield').value = 'edited';
searchBox.updateSearch();
// zoteroPane.js uses the pane window's Services, so stub there. Set it before
// touching the selection so no prompt can reach the real (modal) service.
let stub = sinon.stub().returns(1); // Cancel