Zotero.SearchQuery turns a query like `by:smith after:2020 tag:"to read"
crispr` or `creator is smith and (tag is foo or bar)` into a
Zotero.Search, matching whatever text is left over using the current
search mode. Anything that doesn't look like a clause is free text, so a
DOI or a title with a colon in it is matched literally.
The search box syntax-highlights the parts of recognized conditions and
offers autocomplete for condition names, for the values of conditions
that have a fixed set of them (like item type), and for tags and
creators from the selected libraries.
the all-creators mode (fieldmode 2) concatenated
firstname || ' ' || lastname, so a two-field creator without a first
name produced ' last', which advanced search fields inserted with the
space.
Binding is meaningful for a condition that matches at every level -- a
tag bound to an attachment means the tag is on the attachment -- but a
group carrying one lost the binding as soon as the search was
serialized, so "items with an attachment tagged foo" couldn't be built.
Bug 2008041 made them html-style boolean attributes, so presence alone
means true and setAttribute('disabled', false) now disables the element.
Switch the sites that can be passed a falsy value to toggleAttribute(),
read them with hasAttribute(), and drop a disabled="false" from markup.
Sites that only ever pass true are left alone.
Multi-collection selection left .collectionTreeRow and similar in place
to reduce breakage, but that would just leave plugins and other callers
potentially broken when multiple rows were selected. All singular
getters now throw and say what to use instead. getSelectedLibraryIDs()
was added to replace getSelectedLibraryID().
An image pasted into a note is stored as a hidden attachment, so a
matching search condition (e.g., "# of Tags" is 0) would pull the
image's parent item into the results with no visible match.
With the saved-search editor open, Cmd/Ctrl-Shift-F replaced it with the
temporary Advanced Search pane and the search field's close button
closed it, in both cases discarding the editor's working copy with no
way to recover the edits. Move the collection-switch save prompt into a
shared helper and show it before replacing or closing the editor.
Choosing Save on a collection switch now continues the switch directly
instead of relying on the save's modify notification to re-sync the view.
onConditionSelected() populates the Author value menu asynchronously,
and until it finished, the row's default textbox was still the visible
value control, so serializing the row (running or saving the search)
replaced the loaded author with an empty value. Serialize the stored
value while the menu is pending, and invalidate the population if a
newer condition selection takes over meanwhile.
removeRow() remembered the removed row's index within its own group,
but when the removal emptied the group and pruned it, the index was
applied to the ancestor's children, focusing an unrelated row. Track the
pruned group's slot in its parent instead.
The pane-level Enter handler ran the default action (save in the saved
editor, submit in the temporary pane) regardless of the event target, so
Enter with focus on the Cancel button saved and closed the editor, and
Enter on Clear ran the search. Click the focused button instead, which
the platform otherwise only does for Space.
The "in the same annotation" binding menu was hidden as soon as a group
no longer had two conditions at the bound level, but the group's stored
binding was still saved and applied. After switching one of two bound
annotation conditions to another type, the group looked like a plain
"Match all of the following" group but still required a single
annotation to satisfy all of its conditions, and no control was left to
see or clear the binding.
Show the menu whenever the group is bound, drop the binding only once no
condition at its level remains, and refresh the menus before
collectGroup() in updateSearch() so the first save after such an edit
doesn't serialize stale state.
setAdvancedSearch() invalidated the row unconditionally, unlike
setSearch()/setTags(), so merely opening or closing the Advanced Search
pane (which applies a null filter) re-queried and rebuilt the items
list. A passed search is still always applied, since its conditions may
have been edited in place, and setFilter() still waits for a refresh
already in flight so that callers see the settled view.
Instead of just prefixing the labels with "-", indent the whole row,
including the icon. This looks better and fixes FAYT on subcollection
names.
https://forums.zotero.org/discussion/132561/
Deleting a search in the collections pane moves it to the trash rather
than erasing it, so the check that closes the saved-search editor when
the edited search is deleted didn't catch it. The save-changes prompt
then appeared on the next selection change, and canceling couldn't
restore the removed row, so the prompt reappeared on every selection
until choosing Don't Save. Treat a trashed search like a deleted one
and close the editor without prompting.
Previously a search could be saved only at a library root. Now the Save
Search button is enabled when collections and/or saved searches within
a single editable library are selected, and the selection is added to
the saved search as collection/savedSearch conditions -- an 'any' group
of them when more than one row is selected. If the search's own join
mode is 'any', its existing conditions move into an 'any' group of
their own so the scope conditions apply to every result instead of
joining the OR. A 'recursive' condition is included per the
recursiveCollections pref.
https://forums.zotero.org/discussion/132528/beta-cannot-create-a-saved-search-from-a-collection
Saved searches were folded into the "Collection" condition's value menu
in 2016 (9c52ebdf8b), for reasons I can't totally remember. Give Saved
Search its own condition again.
Opening Advanced Search from a non-empty quick search reproduces the
current quick search mode as editable conditions, one per word (or quoted
phrase) joined with "all":
- Title/Creator/Year: a single Title, Creator, Year condition per word,
with the result level set to item (the mode matches only top-level items)
- All Fields & Tags: a single Any Field condition per word
- Everything: Any Field + Full Text Content as an "any" group per word
(relies on grouped full-text composing correctly in SQL)
The Title/Creator/Year mode needs a condition to map to, so add a "Title,
Creator, Year" search condition that expands to the same field set as the
quick search mode (title, publication title, short title, court, year,
citation key, creator), mirroring how Any Field matches All Fields & Tags.
Like Any Field, it expands at query-build time, so the saved search stores
a single condition and its sub-fields don't need their own entries in the
condition menu.
- Reword the header as one sentence with a result-level menu ("Find
[attachments] matching [all] of the following:")
- Provide a per-group menu to bind the group's descendant conditions to
the same attachment, note, or annotation (e.g., one annotation that is
both red and contains a given word, not two different ones)
- Show a hint that offers to group ungrouped sibling conditions (e.g.,
two annotation conditions at the top level, to bind them to one
annotation)
- Show a warning when conditions can't combine at the chosen result
level (e.g., an annotation condition with a note result level)
- Remove the two legacy checkboxes:
- "Show top-level items" becomes result level = top-level item and is
migrated on save
- "Include parent and child items", which has no result-level
equivalent, keeps working, stays editable, and round-trips on
searches that already have it, but it isn't offered on new searches
and is removed on save if unchecked
Render the search as a tree of groups: a root group plus nested
search-condition-group elements, each with its own join-mode menu and a
remove control. Each condition row gets a "( )" button that wraps it in
a new group in place, so further conditions can be added to combine with
it under a separate join mode. Switch the builder to rebuild-from-tree --
the DOM is the source of truth and the search's flat conditions (with
groupStart/joinMode/groupEnd markers) are regenerated on each edit, so
the old conditionID-as-index tracking is gone.
#5658 added includeDeleted to the Advanced Search outside the trash, so
trashed items kept matching: trashing a result removed its row, but
re-running the search brought it back. Exclude deleted items by default,
the same as a quick search, and only include them when viewing the trash,
where the scope returns only deleted items.
Fixes#5956
When multiple collection-list rows are selected, group the combined
items by library under sticky headers (e.g., "My Library", "Group X (2
collections selected)"), or show a single summary header for a multi-row
selection within one library, with blank spacer rows separating
libraries.
Allow selecting multiple collections, saved searches, or library roots in the
collection tree -- within a library or across libraries -- and show the union
of their items. The selection is threaded through the pane as an array
(getCollectionTreeRows(), changeCollectionTreeRows(), etc.); the item pane, tag
selector, reports, and export operate on all selected rows.
Adding items (new items and notes, drag-and-drop, the attachment dialog,
import, Add by Identifier) targets every selected collection. Only rows that can
share an items view may be combined: collections, saved searches, and library
roots mix freely, and multiple Recently Read rows can be combined across
libraries, but other special views (Trash, Duplicates, etc.) and rows from
different visibility groups can't be shown together, so a selection mixing them
keeps only the focused row. In-window advanced search runs across all selected
collections.
Advanced search value autocomplete is now scoped to the searched library, fixing
a long-standing TODO where suggestions were drawn from all libraries regardless
of the search scope; for a cross-library selection it spans the selected
libraries.
For a cross-library selection, the tag selector shows the union of tags
(colored tags only when a single library is in scope, since colors are
per-library), and deleting a tag spans all selected libraries while
rename/color/split are disabled. If advanced search is open, the
collection and saved-search conditions are omitted, since each is scoped
to a single library.
The temporary advanced search saved silently under an auto-generated
"Untitled" name. Prompt for a name instead (defaulting to that name),
and label its button "Save Search…" to signal the dialog. The saved-search
editor's button, which saves changes directly, is now just "Save".
https://github.com/zotero/zotero/pull/5658#issuecomment-4696398750
Deleting the saved search being edited triggered onCollectionSelected's
save-changes prompt, which doesn't make sense for a search that's gone.
Detect the deleted search and close the editor without prompting.
The remove button on the sole remaining condition is now enabled once a
value is entered, and clicking it replaces the condition with the default
empty one shown when the pane is first opened, instead of staying
disabled.
- Wait for the itemsView to be initialized in the onload listener of the
Advanced Search window
- moved waitForTagSelector in a few tests to avoid it resolving too
early. For some tests these tweaks are always required, for others -
tests would pass half the time. Again, not clear why it's necessary,
potentially some flakiness from react 18?
A XUL one for the current use in Advanced Search and an HTML one for
future uses. Sets the value to libraryID and adds data attributes for
editable/filesEditable on the HTML one.
Changes `libraryTreeView::addEventListener('load')` and similar to
`libraryTreeView::onLoad.addListener(listener, once)`, etc. `once` is an
optional boolean that, when true, causes the listener to fire once and
then be removed. This is implicit for 'load'.
'load' maintains its special behavior of running immediately if the
treeview has already been loaded.
Also adds `waitForLoad()` and `waitForSelect()` functions that return
promises on event completion, since most uses of those events were just
resolving deferreds.