A stored file's path is normally 'storage:<filename>', which is parsed
as a string, but some ancient libraries apparently have relative paths
('../.../foo.pdf') that fell through to PathUtils.filename() and threw
NS_ERROR_FILE_UNRECOGNIZED_PATH, breaking syncing.
Resolve stored-file leaves as strings regardless of prefix and reserve
PathUtils.filename() for linked files, whose paths are genuine absolute
paths.
https://forums.zotero.org/discussion/132861/zotero-sync-issue-id-1987012678
ee68452b treated any backslash as a directory separator, in both the
attachmentPath setter and the userdata 128 migration. But backslashes
are technically valid on Linux/macOS and appear in real filenames (due
to LaTeX in titles, etc.), so that would wrongly throw on or truncate
them. Only a forward slash (never valid in a filename) or a Windows
absolute path (drive-letter or UNC prefix) reliably indicates a leaked
directory path, so treat just those as paths and leave bare backslashes
alone. We should make sure we're running getValidFileName() everywhere
to avoid saving those.
Someone ended up (via a plugin, presumably) with stored-file attachments
with a full path after 'storage:', which broke file syncing. Throw when
setting a stored-file path containing a slash, and strip paths from
existing filenames in a schema update step. No particular reason to
think that the file with that basename will exist in the storage dir,
but at least it will be looking for the right file and not be totally
broken.
Separately, the dataserver will clean up filenames with full paths and
block going forward.
https://forums.zotero.org/discussion/132822/reference-sychronization-error
When the server returns an attachment without lastRead (because it was
cleared), fromJSON was not clearing the local value since it only
processes fields present in the JSON. Add explicit check after field
processing to clear lastRead when absent.
- Add columns as options in item tree, visible only in group libraries
- Fall back to createdByUserID for Modified By when lastModifiedByUserID
is not set
- Update lastModifiedByUserID on local saves when dateModified changes
- Fix backfill in _updateGroupItemUsers() to process all batches
- Update formatColumnName() to support Fluent string keys
Closes#233
Track when attachments are last opened or read, storing a `lastRead` Unix timestamp on the attachment. For user library items, `lastRead` syncs as an attachment property in item JSON. For group library items, it syncs via a per-user synced setting (like `lastPageIndex`).
- Add `lastRead` column to `itemAttachments`
- Add `AttachmentReadObserver` to update `lastRead` on file open and page change (throttled to 5 min for page changes)
- Add "Recently Read" virtual collection (items read in last 14 days, sorted by `lastRead` descending)
- Add `lastRead` search condition with date operators
- Add `lastRead` item tree column with new `dependsOnChildren` property for parent item aggregation
- Add `getItemLastRead()` to return max `lastRead` across child attachments
Also:
- Generalize collection tree SCSS to support universal (context-fill) icons alongside themed icons
---------
Co-authored-by: Dan Stillman <dstillman@zotero.org>
The firstCreator/sortCreator SQL now falls back through primary →
editor → director → contributor. This ensures Video Recording items
with only a Director creator show that creator in the items list.
Also refactors the repetitive SQL-building in _getFirstCreatorSQL()
and _getSortCreatorSQL() into helper functions.
Closes#5720
A test using `assert.eventually` was failing after the Bluebird removal
but worked with just `await`, and since there hasn't really been much
point to Chai as Promised since the introduction of `async`/`await` ages
ago, just remove the library instead of figuring out why.
- display the first continuous span of emojis in the primary cell of
the itemTree for non-colored tags.
- the emojis appear after the colored tags' circles (if any)
- to keep things consistent with itemTree, sort tags in the tagsBox in
the following order: colored tags first sorted by their position,
emoji tags after sorted alphabetically, followed by remaining tags sorted
alphabetically.
- revert change from 2401a34031
that only loads un-trashed collections in _loadCollections.
If an item only belongs to deleted collections, item._loaded.collections = true
from _loadCollections will never run, so an exception
will be thrown in item.toJSON() when syncing happens.
Instead, to address the problem of item.getCollections()
having stale data #4307, add 'includeTrashed' parameter to
item.getCollections() based on which item._collections
will be filtered. Fixes: #4346
- revert earlier, no more necessary, changes from a532cfb475
to not alter item._collections cache when collections are being trashed or restored.
Collection is removed from item._collections only when it is permanently
erased.
- removed unnecessary test checking for consistent item._collections
value before and after reload, since item._collections is no longer
modified
- fix encountered bug where a trashed child collection is not
unloaded if a parent collection is erased without being trashed first.
- tweaked Zotero.Search sql construction to count items
that only belong to trashed collections into 'unfiled'. Fixes: #4347
---------
Co-authored-by: Dan Stillman
Passing unformatted = true to Item#getField() now returns a bidi control
character-less result, and we use that in Reader#updateTitle() and
getFileBaseNameFromItem() to prevent bidi control characters from showing up in
filenames and window titles (the former everywhere, the latter on Windows only).
We also strip bidi control characters in getValidFileName() to be extra safe.
There were a few problems causing the incorrect behavior:
1. Rows were being removed only if they had no non-deleted children, which
wasn't the right check. We want to remove all rows with no *deleted*
children.
2. Children of the removed rows weren't being removed with them.
3. We weren't invalidating the tree (which _removeRows() doesn't do).
Also:
* Erase trashed annotation after getAnnotations() test
Because ItemTree#notify() doesn't yet correctly handle refresh events on
parent items that are themselves children (three-level nesting: item ->
attachment -> annotation), this test was causing a failure in
itemTreeTest.js.
Update `DataObject::isEditable()` to take an optional `op` argument to
test individual operations as opposed to general library editing.
Erasing objects now tests `erase`, and `Item::isEditable()` allows
`erase` for unowned group annotations while disallowing the default
`edit`.
It's still up to the reader to handle this appropriately in the UI and
not allow operations it shouldn't, but this enforces it in the data
layer.