Commit graph

1986 commits

Author SHA1 Message Date
Dan Stillman
dfd745c5d5 Log test failures that will be retried
The reporter only showed the final failure, so if a retry failed differently
(e.g., from a stub left wrapped by the first failure), the original error was
lost.
2026-07-08 10:50:32 -04:00
Dan Stillman
1fe9cad5f8 citationDialogTest: Initialize styles in before()
Zotero.Cite.getLocatorString() requires Zotero.Styles.init() to have run.
The full test run relied on an earlier test file (bibliographyTest) having
initialized styles.
2026-07-08 10:50:32 -04:00
Dan Stillman
bd25413213 runtests.sh: Add -p option to run a shard of the test files (e.g., -p 2/4)
The sorted file list is split into contiguous chunks of roughly equal total
file size, using size as a stand-in for run time, so new test files are
included automatically and slow test files are spread across shards rather
than landing in the same shard by chance. Contiguous chunks preserve the
alphabetical run order of a full run, so files keep the same preceding files
as in a full run except at chunk starts, and a shard can be reproduced locally
by passing its first and last files to -s and -e.
2026-07-08 10:50:32 -04:00
Dan Stillman
094d5cc2b6
Update staged builds in place for faster dev builds (#5994)
After a full build, dir_build saves a manifest of build/ files to
staging/.build-manifest. On subsequent runs, if all changed files are
ones that build.sh copies into omni.ja unmodified (chrome/, components/,
resource/, and test/ when tests are staged), zip just those files into
the staged omni.ja instead of rebuilding, taking rebuilds from ~15
seconds to ~0.3 seconds on an M1 Mac. Files are prescreened by size and
mtime so that only changed files need to be hashed. Zotero .ftl files
are also updated at their localization/<locale>/ paths, and test files
are also copied to the staged tests/ directory.

Any other change triggers an automatic full rebuild: files transformed
by build.sh (defaults/, chrome.manifest, version, translators/, styles/,
mozilla .ftl files, CSL locales), removed files, changes to build inputs
in app/ (detected via a size/mtime fingerprint, with xulrunner runtimes
covered by the hash-* files written by fetch_xulrunner), or requesting
tests or devtools that the staged build doesn't include.

Other changes:

- dir_build no longer takes -q and always skips omni.ja compression and
  optimization, which only matter for distribution builds made via
  build.sh. Use -f (dir_build or build_and_run) to force a full rebuild.
- build_and_run now always rebuilds. -r is deprecated, and -n skips the
  rebuild and just launches the app.
- build_and_run no longer passes -purgecaches. Startup caches are
  invalidated automatically when the BuildID changes, which now happens
  whenever omni.ja is modified (including via add_omni_file), so
  relaunching an unchanged build can use the startup cache.
- build_and_run and runtests.sh invoke js-build directly instead of via
  'npm run', which saves ~270ms of npm overhead per build.
- The Word integration dylib is now ad-hoc-signed by dir_build, and only
  on full rebuilds, since incremental updates don't invalidate the
  existing signature. This also covers test builds, which were never
  signed before.
- dir_build removes broken symlinks left in build/ when source files are
  deleted, which previously broke rsync in prepare_build.
2026-07-08 10:07:58 -04:00
Dan Stillman
cddf0ea237 Update citeproc.js to include Juris-M/citeproc-js#281
Fixes HTML appearing in citations after an error during citation preview

https://forums.zotero.org/discussion/132539/
2026-07-03 15:53:12 -04:00
Dan Stillman
e416ea2dfa Fix repeated save prompt after trashing the saved search being edited
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.
2026-07-02 12:44:07 -04:00
Dan Stillman
2f364f2fab Allow creating a saved search with collections or searches selected
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
2026-07-02 12:38:52 -04:00
Martynas Bagdonas
af704dc119 Update document-worker submodule and report SDT progress 2026-07-02 15:04:31 +03:00
Dan Stillman
26c30d8ecd Allow browsing an attachment's annotations in search results
With "Hide Non-Matching Annotations" enabled, an attachment displayed as
empty and non-expandable if no annotations matched the search, so
searching by any non-annotation condition made it impossible to expand
attachments to browse their annotations. Now only hide the non-matching
annotations when the attachment actually has a matching one.

https://forums.zotero.org/discussion/132519/beta-advanced-search-cannot-expand-annotations-of-search-results
2026-07-01 22:13:38 -04:00
Dan Stillman
163c27ca79 Keep Advanced Search button visible when the item pane is wide
When the item pane is dragged wide, the items pane is squeezed and the
quick search wrapper kept its intrinsic width and overflowed, pushing the
trailing Advanced Search button out under the item pane. Let the wrapper
shrink so the button stays within the pane.

Fixes #5982
2026-06-30 15:57:06 -04:00
Dan Stillman
13bd8c9d8d
Group attachment and annotation conditions into Advanced Search submenus (#5981) 2026-06-30 14:20:47 -04:00
Tom Najdek
7f30b0e47d
Respect skipDateModifiedUpdate of the operation when undoing it, fix #5971 2026-06-30 18:23:23 +02:00
Dan Stillman
36d025f6a9 Update item count on every items-view refresh
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run
The 'items in this view' count failed to update when the result set
changed without a selection change (e.g., quick search, tag filter, or
sort with nothing selected). The listener that refreshes the item pane
on refresh was registered in 8277277948 to fire only once, on initial
load, so later refreshes never recomputed the count.

This is all a regression from the items tree refactor (cbbff600a6),
which stopped running itemSelected() on every refresh.
2026-06-29 15:12:22 -04:00
Tom Najdek
2006cdac6d
Improve the behavior of read-only fields in batch edit mode, fix #5968
- Fix a bug where focusing on a read-only field with a common value across items would clear the field and display a "Multiple" placeholder instead
- Fix read-only date fields always showing "Multiple" in batch edit mode, even when values are genuinely different
- Read-only fields are now focusable in batch edit mode
2026-06-29 17:17:33 +02:00
Dan Stillman
02b19d0cde Match multi-level search condition against any ancestor level for descendant results
Some checks failed
CI / Build, Upload, Test (push) Has been cancelled
A field that exists at more than one level (e.g., Title, on both
top-level items and attachments) targeting a descendant result level
only matched the closest ancestor, so searching annotations by Title
found nothing, since it checked the parent attachment's title rather
than the top-level item's. Map down from each ancestor level and union
them, testing the predicate once so its bound parameters aren't
duplicated.

Addresses #5978
2026-06-26 16:53:52 -04:00
Dan Stillman
340ba92513 Show "Search subcollections" only with a Collection condition
The option only affects Collection conditions, so hide it -- and don't
emit the 'recursive' condition -- when the search has none.
2026-06-26 16:29:34 -04:00
Dan Stillman
6c7c661fb1 Advanced search: Separate Saved Search from the Collection condition
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.
2026-06-26 16:24:49 -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
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
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
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
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
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
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
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
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
Dan Stillman
15c2c95470 Support multiple-collection selection (#5954)
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.
2026-06-18 14:05:55 -04:00