Commit graph

16530 commits

Author SHA1 Message Date
Dan Stillman
ea2ee7391d Force a regular backup before all DB upgrades, and drop versioned backups
Versioned backups (zotero.sqlite.<version>.bak) were kept until the
next userdata upgrade, potentially bloating the data directory by
gigabytes, while minor upgrades made no backup at all. Now any userdata
upgrade, integrity check, or global schema update forces a rotation
backup. Since versioned backups ignored backup.numBackups, setting that
to 0 now fully disables backups, including before upgrades.
2026-07-15 17:43:29 -04:00
Dan Stillman
02fb0e92ed Rework the Index Statistics preferences pane
Show indexing progress as a bar (items in the search index out of those
that will be) that resolves to "up to date", alongside
Indexed/Partial/Notes counts and a "File or full-text content not
available" count for attachments with no local file or full-text
content. While the pane is open, trigger indexing directly so it
advances as the user watches.

Remove the Rebuild Index and Clear Index buttons. Rebuild Index marked
all content unsynced and re-uploaded it, triggering a server reindex and
re-download on other devices, and the per-item reindex paths now cover
what needed it -- raising a length limit re-extracts only the affected
items, and unindexed items are indexed automatically. Clear Index wiped
the local index and extracted-text cache files, a destructive action
that shouldn't be exposed.
2026-07-15 15:36:40 -04:00
Dan Stillman
3ec407d861 Add full-text searching of note content
Note content is indexed into fulltext.sqlite, making note searches
accent- and case-insensitive and matching the note's plain text rather
than its HTML markup. To avoid re-indexing on every auto-save, a save
flags the note for background indexing, and searches match a flagged
note from its normalized text in memory until it's indexed.

Closes #378
2026-07-15 15:36:40 -04:00
Dan Stillman
5ade25f5f5 Drop the unused savedSearchConditions required column (#5962)
The condition `required` flag was removed in #5962, but the column was
kept so older clients could still read the database. The full-text
search changes bump the userdata compatibility version, locking out
those clients, so the column can now be dropped.
2026-07-15 15:36:40 -04:00
Dan Stillman
7c2a1d127d Add full-text content search via FTS5
Index attachment content into a contentless trigram FTS5 table in a
separate, attached fulltext.sqlite, normalized so matching is accent-
and case-insensitive. For content containing CJK characters, a companion
'ascii'-tokenized table holds bigrams so 1-2 character CJK queries, which
the trigram tokenizer can't match, still work. The extracted text still
lives in the .zotero-ft-cache files, so the index is fully derived and
rebuildable.

Use the FTS index for the fulltextContent condition, falling back to the
cached-text scan for queries too short to index, and point quick
search's content matching at the FTS index in place of the now-removed
word index. (One side effect: quick search now matches attachment
content by substring rather than by word.)

Already-extracted content is migrated into the index at startup, slowing
down on active usage. A background queue then extracts not-yet-indexed
attachments gradually when Zotero is idle. Attachments with no local
file or full-text content are recorded as missing. Content downloaded
via sync is processed into the index immediately when the sync finishes,
rather than waiting for idle like it did before, so it's searchable
immediately in on-demand file-download mode.

The index DB is tied to the main DB via the local user key and rebuilt
if they don't match (e.g., after a delete-and-resync). We compact it by
running FTS5's 'optimize' command once the indexing queue drains, and we
vacuum the attached database when necessary to reclaim disk space.

Closes #2038, #2044
Addresses #1595
2026-07-15 15:36:40 -04:00
Dan Stillman
e2536ba299 Add DBConnection hooks for auxiliary attached databases
Code that attaches its own database to the main connection needs to hook
into that connection's lifecycle. Add onIdle() to run maintenance (e.g.,
vacuuming) during the main database's idle maintenance, and
addCorruptionHandler() to handle a corruption error for the attached
database (after confirming that the main database is actually corrupt,
so a corrupt attached database doesn't trigger main-database recovery).
2026-07-15 15:36:40 -04:00
Dan Stillman
ae46954167 Add DBConnection.loadExtension()
Load a bundled SQLite extension (e.g., FTS5) by name. mozStorage
disables generic extension loading but allows specific bundled ones.
Extensions are registered per connection, so a loaded extension is
remembered and re-loaded automatically after a reconnect, before
onConnect() callbacks run.
2026-07-15 15:36:40 -04:00
Dan Stillman
da88c63231 Make the collection tree filter accent-insensitive
Addresses #1300
2026-07-15 15:36:40 -04:00
Dan Stillman
984c0a7f8c Make the tag selector filter accent-insensitive
Addresses #1300
2026-07-15 15:36:40 -04:00
Dan Stillman
3bd8d641b8 Make item search accent-insensitive
Search now ignores accents, so "seance" matches "séance" and vice versa.

Text is normalized with Unicode NFKD compatibility decomposition (which
also handles typographic ligatures, superscripts, full-width forms,
etc.) plus a small map for letters NFKD leaves alone (ø, œ, æ, ß, ...)
and the fraction slash, via Z.Utilities.Internal.normalizeForSearch().
The HTML tags we support in item fields are stripped, so markup isn't
matched (#81). Typographic quotes (#29, #1876) and dashes are folded to
ASCII.

Each searchable column gets a normalized shadow column --
itemDataValues.valueNormalized, tags.nameNormalized,
creators.firstNameNormalized/lastNameNormalized, and
itemAnnotations.textNormalized/commentNormalized -- populated at write
time and matched via COALESCE(normalized, raw) LIKE. NULL is stored when
normalizing only changes case, so plain-ASCII values are only stored
once. This covers the contains/doesNotContain/beginsWith operators in
both quick search and Advanced Search.

The new columns are local-only derived data and aren't synced. Older
clients will ignore them, so this doesn't break DB compatibility.
Existing rows are backfilled after the startup sync by
Zotero.Schema.populateNormalizedSearchColumns(), which should only take
a few seconds on most databases.

Closes #29, #81, #1300, #1876
2026-07-15 15:36:40 -04:00
Dan Stillman
d0f8d438b5 Clarify naming of the full-text sync content processor
Rename the sync-delivered full-text content processor's methods and
state to "sync content" (registerSyncContentProcessor,
processSyncedContent, indexSyncedContent, getSyncedContentCacheFile,
etc.), so it reads distinctly from the local index-building queues added
for content search. No behavior change.
2026-07-15 15:36:40 -04:00
Dan Stillman
cf09a0547c Regenerate SDT test pack fixture for pdf processor version 3
The document-worker bump raised SDT_PROCESSOR_VERSIONS.pdf from 2 to 3,
so the test's "valid" pack fixture (embedding version 2) was rejected as
stale-processor during generation, failing "should generate the pack when
missing."
2026-07-15 15:01:22 -04:00
Dan Stillman
c6595d6456 Update CSL locales 2026-07-15 13:08:36 -04:00
Dan Stillman
4f45db5009 Update translators submodule 2026-07-15 12:52:54 -04:00
Dan Stillman
933e207013 Update document-worker submodule 2026-07-15 12:52:00 -04:00
Dan Stillman
0ba89211ce Fix race between lastRead update and content-type fix on file open
Some checks are pending
CI / Test (shard 1) (push) Waiting to run
CI / Test (shard 2) (push) Waiting to run
CI / Test (shard 3) (push) Waiting to run
CI / Test (shard 4) (push) Waiting to run
CI / Utilities Tests (push) Waiting to run
CI / Build, Upload (push) Waiting to run
viewAttachment() fired the 'open' notification before launchFile() saved
a sniffed content type, so AttachmentReadObserver's concurrent lastRead
save could reload the item and discard the pending change, leaving the
attachment with a blank or incorrect content type.
2026-07-14 14:01:07 -04:00
Dan Stillman
bdea584a17 Update LibreOffice submodule 2026-07-14 13:39:07 -04:00
Dan Stillman
1a5e32a7b6 Update document-worker submodule 2026-07-14 11:05:43 -04:00
Dan Stillman
142f77b094 Update reader submodule (SDT) 2026-07-14 11:05:29 -04:00
Dan Stillman
6e35bd1497 Focus the pane window before the focus traversal tests
Some checks are pending
CI / Test (shard 1) (push) Waiting to run
CI / Test (shard 2) (push) Waiting to run
CI / Test (shard 3) (push) Waiting to run
CI / Test (shard 4) (push) Waiting to run
CI / Utilities Tests (push) Waiting to run
CI / Build, Upload (push) Waiting to run
The #focus() Tab/Shift-Tab tests wait on focus/blur events, which fire
only while the window is active. It intermittently loses activation in
CI's Xvfb, so call win.focus() in beforeEach when the window isn't
active.
2026-07-13 17:28:00 -04:00
nexdep
2c6185d8f1
Add Attachment Storage Type search condition (#5875)
---------

Co-authored-by: Dan Stillman <dstillman@zotero.org>
2026-07-13 15:57:37 -04:00
Dan Stillman
89880e3284 CI: Record which test drops the main window's activation
Temporary diagnostic for the intermittent shift-tab focus-test timeout.
A central afterEach in runtests.js tracks when
Services.focus.activeWindow stops being the main window; the shift-tab
test logs the last-active and first-inactive test in its timeout
message, to identify what leaves the pane inactive in CI.
2026-07-13 15:27:39 -04:00
Dan Stillman
5a8badae7b Revert "Activate window in focus traversal tests, fixing intermittent timeouts"
This reverts commit 03a610e696.

activate() foregrounds a window via the window manager, which CI's Xvfb
doesn't have, so activateZoteroPane() in beforeEach just hangs there
instead of fixing anything. Restore the temporary open-window logging to
try to catch what leaves the pane window inactive.
2026-07-13 15:15:13 -04:00
Dan Stillman
28867883a1 Don't let embedded note images match searches in the items list
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.
2026-07-13 14:32:40 -04:00
Dan Stillman
cdb9134537 Add "# of Notes", "# of Attachments", and "# of Annotations" search conditions
Notes and attachments are counted on regular items, and annotations on an
attachment or across a regular item's attachments; other rows are excluded
rather than always matching with a count of 0. Trashed children aren't
counted.
2026-07-13 14:18:59 -04:00
Dan Stillman
7cdd74bd2b Add "# of Tags" advanced search condition
Closes #158
2026-07-13 13:38:22 -04:00
Dan Stillman
24fe50cfd5 Add "is empty"/"is not empty" advanced search operators
Available for text, date, and number fields and creators. Previously only
possible via a doesNotContain hack with an empty value.
2026-07-13 13:38:22 -04:00
Dan Stillman
c181aac7f6 Citation dialog: Fix grouping of selected and open items
The front-most tab's item appeared under Selected Items instead of Open
Documents, and the library tab's actual selection wasn't shown. Selected
Items now always reflects the item tree selection, and an item that's both
selected and open stays under Open Documents (sorted after the front-most
tab) so that suggestions specific to open documents (e.g., the current page
number, planned in #6001) can apply to it. Notes open in tabs now appear
under Open Documents when adding a note.
2026-07-13 11:41:19 -04:00
Dan Stillman
582333397d Citation dialog: Allow typed locator after Cmd/Alt-Tab away and back
https://forums.zotero.org/discussion/132673/
2026-07-12 18:40:06 -04:00
Dan Stillman
5fe4c901ed Update stale downloadUpdate() usage in update dialog
Some checks failed
CI / Test (shard 1) (push) Has been cancelled
CI / Test (shard 2) (push) Has been cancelled
CI / Test (shard 3) (push) Has been cancelled
CI / Test (shard 4) (push) Has been cancelled
CI / Utilities Tests (push) Has been cancelled
CI / Build, Upload (push) Has been cancelled
downloadUpdate() is now async, takes a single argument, and returns a
result code rather than a state string, so the "failed" check could
never match.
2026-07-10 15:49:19 -04:00
Dan Stillman
8eb71a588d Fix download progress in update dialog
nsIProgressEventSink's onProgress()/onStatus() no longer take a context
argument.

Fixes #3130
2026-07-10 15:49:18 -04:00
Dan Stillman
dea2fb8491 Fix build_for_deploy dying silently on an empty incrementals file 2026-07-10 15:26:52 -04:00
Dan Stillman
6225931f93 Clear the quick search from every selected collection when prefilling
Opening the Advanced Search from the quick search cleared the search
text only on the first selected row, while the quick search had applied
it to all of them, so with a multi-collection selection the other
collections stayed filtered by the now-invisible text -- both while the
advanced search was open and after closing it.
2026-07-10 11:45:29 -04:00
Dan Stillman
540735785d Prompt before discarding saved-search edits via the Advanced Search toggle
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.
2026-07-10 11:45:29 -04:00
Dan Stillman
4188ef0ebc Don't drop an annotationAuthor value while its author menu is loading
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.
2026-07-10 11:45:29 -04:00
Dan Stillman
ccc7d0a5ad Fix focus target when a keyboard removal prunes a group
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.
2026-07-10 11:45:29 -04:00
Dan Stillman
3a34c6a18f Run a focused button's action on Enter in Advanced Search
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.
2026-07-10 11:45:29 -04:00
Dan Stillman
a6c3ad51ef Keep a group's same-entity binding visible when it no longer qualifies
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.
2026-07-10 11:45:28 -04:00
Dan Stillman
1e0cf49a16 Don't refresh the items list when clearing an unset advanced search
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.
2026-07-10 11:45:28 -04:00
Dan Stillman
cc28fad6da Roll a standalone attachment's annotations up to the attachment itself
_rollUpAnyToLevel() only followed an annotation's parent when the
attachment itself had a parent, so in a search for top-level items, a
tag on a standalone attachment's annotation matched nothing.
2026-07-10 11:45:28 -04:00
Dan Stillman
d95d8f294d Fix serialization of the result level seeded for a migrated childNote
The condition seeded by _loadConditions() used `mode: undefined`, but
toJSON() only omits the "/mode" suffix when the mode is exactly false
(what parseCondition() returns), so a saved search migrated from the
obsolete childNote condition serialized -- and synced -- the condition
as "resultLevel/undefined".
2026-07-10 11:45:28 -04:00
Dan Stillman
c43f028c96 Remove dead code left over from the Advanced Search dialog
onLibraryChange() has no callers since library changes re-render the
pane, and .active/doCommand() belonged to the removed dialog's search
box.
2026-07-10 11:45:28 -04:00
Dan Stillman
8daed576ba Make first top-level joinMode win in combineConditions()
_buildQuery() already uses the first top-level joinMode for the
full-text post-filter; with multiple top-level joinMode conditions (only
creatable via the API), combineConditions() used the last.
2026-07-10 11:45:28 -04:00
Dan Stillman
2c62010c4c Fix misplaced comma 2026-07-10 11:45:28 -04:00
Abe Jellinek
0a67f4967c Read Aloud: Update skip button tooltip text
#5998
2026-07-10 11:17:23 -04:00
Abe Jellinek
64165eabf6 ItemTree#selectItems(): Clear advanced search if item doesn't match
Some checks are pending
CI / Test (shard 4) (push) Waiting to run
CI / Utilities Tests (push) Waiting to run
CI / Test (shard 1) (push) Waiting to run
CI / Test (shard 2) (push) Waiting to run
CI / Test (shard 3) (push) Waiting to run
CI / Build, Upload (push) Waiting to run
Extends existing handling for tag selection and quicksearch to handle
advanced search as well.

Fixes #5996 (not just for zotero://select but for all selectItems()
callers)
2026-07-09 14:11:36 -04:00
Dan Stillman
243ef0e28f Fix historical comments from 606d8f19ba 2026-07-09 13:12:43 -04:00
Dan Stillman
606d8f19ba Advanced search: Indent subcollections in Collection condition menu
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/
2026-07-09 13:09:24 -04:00
Dan Stillman
03a610e696 Activate window in focus traversal tests, fixing intermittent timeouts
The #focus() Tab/Shift-Tab tests dispatch synthetic keydowns and wait on
a focusin event to sync with the collections search field's async
(setTimeout) focus. Gecko only fires focus/blur events while the window
is active, so when the pane window wasn't active -- it opens in the
background on macOS/Windows and can lose activation on Linux/CI -- the
focusin never fired and the test hung until Mocha's timeout. Activate
the window in beforeEach, as the item pane and tags box focus tests
already do.

Also removes the temporary open-window logging added in e1f19f34f5.
2026-07-09 11:00:24 -04:00
Dan Stillman
c3787495cc Don't show empty-library message for empty search with no groups
hasItems() also now short-circuits with LIMIT 1 rather than counting all rows

https://forums.zotero.org/discussion/132635/zotero-beta-empty-database-message-when-no-results
2026-07-09 10:38:43 -04:00