Commit graph

16434 commits

Author SHA1 Message Date
Dan Stillman
d4507544af Advanced search: Reduce the condition row's control gap to 6px
Addresses #5978
2026-06-26 15:55:32 -04:00
Dan Stillman
98308c65b9 Advanced search: Drop the extra padding on the 'Find' line
Add a bit more inline padding to the whole pane to compensate, so the Find
line and conditions box aren't too close to the left edge.

Addresses #5978
2026-06-26 15:54:49 -04:00
Dan Stillman
f7e67f6643 Advanced search: Match the level-warning text size to the binding hint
Addresses #5978
2026-06-26 15:53:38 -04:00
Dan Stillman
cf7ee984ea Advanced search: Hide obsolete Child Note condition and migrate to Note
Addresses #5978
2026-06-26 15:53:07 -04:00
Dan Stillman
3875f411e5 Advanced search: Show binding hints and warnings on condition select
Not just on value entry, but add an exception for a new row created with
the + button

Addresses #5978
2026-06-26 15:52:34 -04:00
Dan Stillman
680452cc59 Advanced search: Update condition grouping icon and add ungroup button
Addresses #5978
2026-06-26 15:52:16 -04:00
Dan Stillman
3544848e5f Keep an edited search condition value when switching condition types
Addresses #5978
2026-06-26 15:50:58 -04:00
Dan Stillman
b56c153125 Fix Attachment Last Read search condition by matching at attachment level
Addresses #5978
2026-06-26 15:50:17 -04:00
Dan Stillman
2717aafedd Add annotation type, color, and author search conditions
Add annotationType, annotationColor, and annotationAuthor conditions,
each tagged `level: 'annotation'` so the cross-level search logic maps
and negates them correctly. The value fields are drop-down menus: the
annotation types, the reader's color palette, and the library's
existing annotation authors.

Ported from #5839. The PR kept a negated annotation condition (e.g.,
"Annotation Color" "is not" "yellow") from matching every non-annotation
item by checking whether the condition name contained "annotation". This
does the same using the condition's level, which the cross-level logic
already handles, so the existing annotationText and annotationComment
conditions are covered too.

Fixes #5837
2026-06-26 13:08:15 -04:00
Dan Stillman
cb2110e1c1 Move the annotation conditions into the main search menu
Addresses #5978
2026-06-26 12:48:48 -04:00
Dan Stillman
2239dd20a4 Update global schema version for search-condition-group gate (#5962)
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run
2026-06-25 16:14:38 -04:00
Dan Stillman
60807d552c Remove the search condition required flag (#5962)
The flag forced a condition to be ANDed even in "any" mode, but
condition groups now express that directly. It was never exposed in the
search UI and nothing seems to have been using it.

addCondition()/updateCondition() now throw if passed a truthy
`required`. Not dropping the column now to preserve DB compatibility.
2026-06-25 16:14:38 -04:00
Dan Stillman
ccf6f18643 Prefill Advanced Search from the quick search (#5962)
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.
2026-06-25 16:14:38 -04:00
Dan Stillman
786b85bf2d Expand Any Field in place so it nests correctly (#5962)
An Any Field condition expands into field/tag/note/creator conditions for
the term. Splice the expansion in right after the condition so it stays at
the same nesting depth, rather than appending it to the end of the
processing queue, which would emit it at the top level instead of within
its group. (Top-level Any Field is unaffected.)
2026-06-25 16:14:38 -04:00
Dan Stillman
0529c9a579 Match Any Field search condition to the All Fields & Tags quick search mode (#5962)
Add annotation text and comments to the conditions the Any Field
condition expands to, matching the fields covered by the All Fields &
Tags quick search mode, as the comment already says is intended. Key
detection and quoted-phrase splitting still differ, since those depend
on the quick search string parsing that Any Field doesn't do.
2026-06-25 16:14:38 -04:00
Dan Stillman
092295da22 Add result-level controls to the Advanced Search builder (#5962)
- 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
2026-06-25 16:14:38 -04:00
Dan Stillman
8b5a77a75e Support cross-level conditions and a result level in search (#5962)
Give a search a result level -- top-level item, attachment, note, or
annotation -- and map every condition to that level, so one search can
mix conditions that match at different levels of the item hierarchy
(e.g., a top-level item with a given author and a red annotation on one
of its PDFs). Each condition carries the level(s) it matches at: a match
is mapped up to an ancestor or down to a descendant, level-agnostic
conditions (tags) roll up to the result level, and fields that exist on
both items and attachments (title, url, accessDate) match natively at
either. The result level is stored as a `resultLevel` marker condition
alongside the join mode.

This also removes the temporary annotation-parent hacks, which the
general cross-level mapping replaces.
2026-06-25 16:14:38 -04:00
Dan Stillman
1341b6d5cc Style Advanced Search conditions as a divided, indented list (#5962) 2026-06-25 16:14:38 -04:00
Dan Stillman
cdc70d1280 Compose grouped full-text conditions in SQL (#5962)
A fulltextContent condition was evaluated as a global post-filter keyed
on the search's top-level join mode -- correct for a top-level
condition, but not for one inside a group, which must combine with its
siblings under the group's own join mode. The new condition grouping UI
allows fulltextContent to be placed within groups, and we need to do so
to prefill the advanced-search pane from an "Everything" quicksearch.

Materialize a grouped fulltextContent into an itemID set and emit it as an
ordinary itemID IN/NOT IN predicate, so combineConditions composes it
under the group's join mode. Top-level fulltextContent keeps the existing
post-filter unchanged.

This also removes the quicksearch full-text post-filter special case. A
quick search puts its full-text in per-word "any" groups, so the
_hasQuicksearch flag was needed to make the post-filter union those
matches rather than intersect them under the top-level "all" join. Now
that the grouped full-text is composed in SQL it never reaches the
post-filter, so the flag is gone.
2026-06-25 16:14:38 -04:00
Dan Stillman
5904f0e4bf Diff saved-search conditions as an ordered list (#5962)
With condition groups, the position of conditions and the pairing of
groupStart/groupEnd markers are meaningful, but conditions were diffed
as an unordered member set (compared by value, with additions appended).
A sync-conflict merge could reorder conditions or add/drop group
markers, corrupting the group structure.

Diff them as a single ordered unit instead, like creators. This changes
how concurrent edits to a search are reconciled: rather than merging the
two sides' conditions member by member, a conflicting edit now resolves
wholesale -- the remote condition list replaces the local one (searches
auto-merge to the remote version); a one-sided change still applies that
side's full list. Discarding one side of a rare simultaneous edit is
acceptable and avoids silently corrupting a grouped search's structure.

This was the only user of SearchConditions.equals(), so remove it.
2026-06-25 16:14:38 -04:00
Dan Stillman
d808988540 Add condition grouping UI to Advanced Search (#5962)
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.
2026-06-25 16:14:38 -04:00
Dan Stillman
c01def2a85 Support nested condition groups in saved searches (#5962)
Replace the flat anySQL/quicksearch-block assembly in _buildQuery with a
recursive tree of AND/OR groups, built and reduced by a new
Zotero.Search.combineConditions() helper. groupStart/groupEnd markers
delimit nested groups and a joinMode marker sets each group's mode, so a
saved search can combine conditions with arbitrary nesting and per-group
join modes. The per-condition SQL generation is unchanged.

For example, a search built as

    search.addCondition('joinMode', 'all');
    search.addCondition('title', 'contains', 'foo');
    search.addCondition('groupStart', 'true', '');
    search.addCondition('joinMode', 'any');
    search.addCondition('tag', 'is', 'x');
    search.addCondition('tag', 'is', 'y');
    search.addCondition('groupEnd', 'true', '');

means "title contains 'foo' AND (tag is 'x' OR tag is 'y')". The 'true'
operator on the group markers is an unused placeholder -- they carry no
value, but a condition's operator can't be empty.

The quick search (matching multiple words) and the Any Field condition
previously had their own special handling in the query builder; they now
use the same grouping as everything else, so that code is gone. Behavior
for existing non-grouped searches is unchanged; new tests cover nested
groups and combineConditions() directly.
2026-06-25 16:14:38 -04:00
Abe Jellinek
9a4317daa7 Preferences: Trim search term
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run
Fixes #5975
2026-06-25 09:35:47 -04:00
Dan Stillman
8f96a6d191 Enable Reset File Sync History for read-only libraries
https://forums.zotero.org/discussion/132375/switching-file-sync-settings-for-group
2026-06-25 09:34:47 -04:00
Dan Stillman
aab912e4ee Support Select All (Cmd/Ctrl-A) in the collection tree
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run
A plain select-all would mix uncombinable rows, so scope it to the
current selection's type: a library selection expands to all library
roots; a collection selection to every collection sharing a parent with
a selected one (so multi-level/multi-parent selections expand within
each branches); and a Recently Read selection to Recently Read in every
library. Other rows -- saved searches, Unfiled, Trash, etc. -- have no
useful expansion, so the tree's key handler leaves the selection
untouched instead of clearing it.
2026-06-24 14:23:08 -04:00
Dan Stillman
d7660f2f27
Add local-cache versioning for Read Aloud audio (#5967)
Version Read Aloud cache keys with the server-provided cacheVersion so
a version bump misses stale entries and re-fetches correct audio, prune
obsolete entries once per session, and skip caching responses sent with
Cache-Control: no-store.
2026-06-24 11:21:09 -04:00
Dan Stillman
962e1334c8 Inset item tree focus ring so its top isn't clipped
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run
Regressed by f4adb452ee, which dropped the items-tree body's top padding
to 0 for sticky section headers; the scroll container's overflow then
clipped the top of the first row's focus ring, which drew outside the
row box.
2026-06-23 09:35:25 -04:00
Tom Najdek
40cb1cfa84
Fix stale itemType in _previousData (#5964)
Some checks failed
CI / Build, Upload, Test (push) Has been cancelled
This fixes a problem where, on subsequent item type changes, a stale itemType is reported and the filename is not updated.
2026-06-19 16:34:23 -04:00
Dan Stillman
35317982f5 Make the sticky library header opaque to pointer events
The pinned sticky section header was opaque to paint but had
pointer-events: none, so clicks, drag-starts, and drops fell through to
the item occluded underneath it.

Make the opaque content capture pointer events instead. Clicks no-op
(header rows aren't selectable), drags don't start (header rows aren't
draggable), and drops on the header are rejected so they're a no-op
rather than acting on the list underneath.

Also drop the index > 0 exception when skipping non-selectable rows in
_onSelection(), so clicking a non-selectable row at the top (i.e., the
first library's header) is a true no-op instead of scrolling to the top.

Also skip non-selectable rows in handleActivate(), since the header can
now be double-clicked; without this it would try to open the library as
an item.

Fixes #5960
2026-06-19 16:07:53 -04:00
Dan Stillman
bdf79336f9 Account for sticky section header when scrolling a row into view
Arrow-key navigation scrolled the newly selected row flush with the top
of the view, leaving it hidden behind the pinned sticky section header.
Reserve a row's worth of space at the top so the row lands below the
pinned header.

Fixes #5959
2026-06-19 14:28:40 -04:00
Tom Najdek
e71bd89e0d Add support for undo/redo (#5823)
---------

Co-authored-by: Dan Stillman <dstillman@zotero.org>
2026-06-19 14:12:54 -04:00
Tom Najdek
c92148c3ec Add batch editing support for fields (#5823)
---------

Co-authored-by: Dan Stillman <dstillman@zotero.org>
2026-06-19 14:12:54 -04:00
Dan Stillman
8d7f5a39f9 Don't include trashed items in Advanced Search outside the trash
#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
2026-06-19 14:07:43 -04:00
Dan Stillman
ff094330f5 Don't show all trashed collections/searches during a search in trash
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run
When viewing the trash, trashed collections and saved searches were appended
to the items list unconditionally, so every advanced search (and quick search)
in the trash matched all of them. Now they're filtered by name during a quick
search and excluded entirely when an advanced search or tag filter is active,
since they can't match item-level conditions or tags.

Fixes #5957
2026-06-19 13:43:53 -04:00
Dan Stillman
4a318617d6 Don't crash on unbalanced {{endif}} in template engine
An {{endif}} without a matching {{if}} popped the base level off the
stack, leaving it empty and throwing "level is undefined" on the next
part. A malformed filename format template could therefore break file
attaching entirely.

https://forums.zotero.org/discussion/132296/pdfs-have-stopped-attaching-after-upgrade-to-zotero-9
2026-06-19 13:05:19 -04:00
Tom Najdek
6092b0335c
Fix auto-renaming not triggering when parent item type changes 2026-06-19 15:04:56 +02:00
Dan Stillman
35c8448582 Advanced search: Move focus to next "-" button when removing row via keyboard 2026-06-19 00:14:06 -04:00
Dan Stillman
e37a7d8f06 Advanced search: Don't run the search on Shift-Enter 2026-06-19 00:14:06 -04:00
Dan Stillman
2e79bbe402 Advanced search: Focus condition drop-down when adding row via keyboard 2026-06-19 00:14:05 -04:00
Dan Stillman
e38f8f78b0 Advanced search: Add find-as-you-type for all conditions
Closes #5953
2026-06-19 00:14:05 -04:00
Dan Stillman
90aa5d33a0 Fix first-column (Title) header label alignment after item tree refactor
The refactor dropped the 8px (COLUMN_PADDING / 2) offset that makes up
for the inline-start padding the first cell omits, so the header label
sat 8px too far left of the item titles.
2026-06-18 23:32:47 -04:00
Dan Stillman
f4adb452ee Keep the sticky items-list section header clear of the scrollbar
Pin the header inside the scrolling body as a zero-height position:
sticky element so its width tracks the body's content box and it lines
up with the rows without any JS geometry. Reserve a scrollbar gutter so
the macOS overlay scrollbar doesn't float over the content (the opaque
header must paint above the rows to occlude them, and so above the
scrollbar, so it can't be put under it). Drop the body's top padding for
the item tree so rows clip exactly where the header pins, and keep the
gap below the column header as a margin outside the scroll area.

Fixes #5958
2026-06-18 23:25:22 -04:00
Dan Stillman
08d875c7f7 Handle multiple-library item selections dropped on a collection or library
Route each item by its own library -- items already in the target
library are added directly (or skipped, for a library root), while
items from other libraries are copied in -- instead of attempting an
invalid cross-library insert. Disallow a move of such a selection
rather than silently copying.

Fixes #5961
2026-06-18 16:13:16 -04:00
Dan Stillman
6d1cd85211 Skip retraction banner during initial sync on a new computer 2026-06-18 14:51:44 -04:00
Dan Stillman
39954dc437 Remove dead collection assignment for window-opened notes (#5954)
The collections passed through Notes.open into the note window were never
read -- the note editor has no collections setter and EditorInstance.collection
is never assigned on this path -- so they had no effect. New notes are still
added to the selected collection(s) directly in newNote().
2026-06-18 14:10:25 -04:00
Dan Stillman
20fd5fe3e2 Move tag selector view settings popup handler out of XHTML (#5954) 2026-06-18 14:09:49 -04:00
Dan Stillman
618fcfa49d Use one combined tag query for a multiple-row selection (#5954) 2026-06-18 14:09:44 -04:00
Dan Stillman
1c76195a7c Show library-aware section headers in the item tree for multi-row selections (#5954)
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.
2026-06-18 14:09:36 -04:00
Dan Stillman
94c78759ab Add support for sticky section headers to VirtualizedTable (#5954)
When stickySectionHeaders is enabled, the header of the section at the
top of the view is pinned in an overlay that the rows scroll under,
pushed up as the next section's header arrives. Consumers identify
header rows via isSectionHeader. Row striping restarts at each section
header so every section's first row is the same shade.
stickySectionHeaders defaults to false, so existing tables are
unaffected.
2026-06-18 14:09:31 -04:00
Dan Stillman
b80fa996f5 Group the items list by library (#5954)
When the items list contains items from more than one library, group
them by library -- in collections-list order, independent of the active
sort -- with a section heading above each library's items.

Grouping is triggered automatically by an items list spanning more than
one library, not the kind of selection behind it, so any future source
of multi-library items would be separated the same way. Today the
cross-library collection selection is the only such source.
2026-06-18 14:09:17 -04:00