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.
(cherry picked from commit 9da57a9fe3)
The row label builder skipped values that weren't strings, so a numeric
cell -- a note count, or a pre-1000 year (which is parsed with
parseInt()) -- announced only its column
name.
Also switch to formatColumnName() while we're here, so row labels
resolve the column name the same way the header does.
(cherry picked from commit b83342d8e0)
The guard tested the newly clicked column's settings entry while the
delete targeted the previously sorted column's, so the old direction
survived when the new column had no entry, and clicking threw when the
old column had none.
(cherry picked from commit 2f90893e59)
The gap after the icon came from whitespace baked into the legacy
cross.png, so it disappeared when the icon switched to the
edge-to-edge cross.svg and the title abutted the icon.
While trying to [fix](https://github.com/NixOS/nixpkgs/pull/519431) another issue in the Zotero package for nixpkgs, I discovered a new bug. When setting a non-`source` update channel, the build fails on MacOS with x86 architectures.
The build fails with:
```
source not found twice in ChannelPrefs
source
stringWithCString:encoding:
```
Which definitely means it is caused by the `strings` command [here](https://github.com/zotero/zotero/blob/main/app/mac/set-channel-prefs-channel#L29).
When manually executing the strings command on the file, I can repeat getting only one `source` result on x86 mac, while getting two on arm64 or linux. After digging around, I tried with the flag `-arch all`, and both `source` values are found on x86 mac. Full command: `strings -n 3 -arch all $binary`.
Closes#5974.
Additional fixes for broken item tree behaviour when multiple items are
selected, and changing focus with ctrl/cmd-arrow keys.
Aligned Collection Tree/Virtualized Tree collapse/expand behaviour when
multiple containers are selected, one of them is focused, and arrow key
left-right is pressed, to the behaviour in Item Tree - now all of them
are collapsed/expanded.
---------
Co-authored-by: Dan Stillman <dstillman@zotero.org>
The PMC lookup in #5906 was added before the OA lookup, but the OA
lookup often returns a PMC URL of its own (including, at least now, for
the DOI given in the PR), with a direct file URL rather than a page to
scrape, so try it first and fall back to the PMCID-derived URL only when
it turns up nothing.
Since #5906, the 'oa' resolver list can start with a plain URL object
for items with a PMCID, but _getOpenAccessPDFURLs() and
saveOpenAccessAttachment() called resolvers[0](), which threw for those
items -- skipping the lookup silently in the first case and failing the
save in the second.
readAsText() already reads the charset from the blob's type when no
encoding is passed, so pulling it out first had no effect. The regex
was also missing a quantifier and captured a single character, which
readAsText() discarded as an unrecognized label.
Show File triggered a file 'open' notification, so revealing a file
moved its item to the top of Recently Read. Use a separate 'reveal'
event, which file syncing still observes to queue a modification check.
https://forums.zotero.org/discussion/133006/
- Fix multiple potential scenarios causing a template engine crash
- Add support for specifying string literals in the template engine
- Validate `if/else/elseif` order and matching clause closures
- Validate to ensure every `{{` is properly closed with a matching `}}`
- When a template is invalid, display a warning, do not offer batch-renaming tools, do not update synced setting
- When a template is invalid, prompt the user to fix or reset the template when
Closes#5965
Menu plugins reading it acted on one arbitrary row of the selection.
Reading it now throws and names collectionTreeRows, which the context
has already provided since multi-collection selection landed.
The context now copies property descriptors rather than values, since
copying values would evaluate the throwing collectionTreeRow getter for
every menu.
Collections and saved searches can be selected together, and the
search's items don't need to be in any of the collections, which would
result in search-only items being moved to the trash.
The view-wide branches (trash, duplicates, feeds, Recently Read) read
the first selected row, which multi-collection selection preserved with
a getter rather than updating. setCollectionTreeRows() now derives the
kind of view the selection adds up to, throwing if the rows disagree,
and those branches test it.
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().
Collections and saved searches can be selected together, so a
collection-item change called getDescendents() on rows that don't have
it and compared search IDs against collection IDs.
Recently Read rows can span libraries, but read attachments were marked
as matches for the first row's library only, leaving the rest as grayed
context rows.
Code walking the items list assumed every row was an object, so the
headers and spacers shown whenever more than one row is selected got
picked up as items: getSortedItems() passed them to export and report
generation, restoring from the trash called item methods on them, and
the item pane counted them in "N items in this view".
The requireServerIDOnWrite flag was added to the base endpoint class but
never set on the upload receiver, so uploads sent without the header
were still rejected with 428.
The cache-existence check didn't await getCacheObject(), so the
promise was always truthy and no cache object was ever saved for
objects the server reported as unchanged. Without a cached version,
later remote changes to those objects produced avoidable conflicts.
lastRead conflicts were auto-resolved only when a cached version of
the object was available, so differing read times could still trigger
the conflict resolution window.
The citation dialog's + column now has a tooltip that reflects how
many items a click will add based on the current selection, e.g.,
"Add 3 Items to Citation".
Clicking the + button on a row that is part of a multi-item selection
now adds all selected items to the citation, matching the existing
list-mode behavior. Clicking + on an unselected row still adds only
that item.
With focus moving back to the input after a multi-item add, a typed
number was applied as a page locator to every just-added item. It now
starts a search instead. Single-item adds still enable the typed-locator
shortcut.
_scrollItemTreeToRow() parsed the row index from the wrong rowID
segment, so it threw on every call, and adding items from the items list
never returned focus to the input as intended.
Every local API response includes a stable per-database server ID so
that clients can partition cached data, especially local object
versions, by Zotero instance. Write requests must include the ID, and
when it's provided on any request it must match the current server.
---------
Co-authored-by: Dan Stillman <dstillman@zotero.org>
Add support for item, collection, and search writes (POST/PUT/PATCH/
DELETE), tag deletion, full-text writes, and the three-phase file
upload flow. Writes require a local API key granted by the user via a
confirmation dialog (POST /api/local/authorize) and follow the web
API's concurrency model, with If-Unmodified-Since-Version and
per-object version preconditions checked against local versions.
---------
Co-authored-by: Dan Stillman <dstillman@zotero.org>
Add a clientVersion column for items, collections, searches, and
libraries, incremented once per library per transaction on every
object save or deletion. The local API reports these versions instead
of synced versions -- in object JSON, format=versions, since=
filtering, and Last-Modified-Version -- since synced versions don't
reflect local changes and are 0 for unsynced objects. Group metadata
responses keep reporting the synced group version, which has no local
counterpart.
---------
Co-authored-by: Dan Stillman <dstillman@zotero.org>
Developer ID-signed Safari web extensions load only in Safari 18.4 and
later -- older Safari blocks them at the code-signing layer -- so macOS
11 and 12 users, whose Safari versions top out at 16.6 and 17.6, lost
the connector with the web extension conversion.
Embed the prebuilt legacy App Extension ($SAFARI_APP_EXTENSION, set by
the deploy scripts) at Contents/PlugIns/ZoteroSafariExtension.appex with
the historical bundle identifier, and move the web extension to
Contents/PlugIns/ZoteroSafariWebExtension.appex with a new
.SafariWebExtension identifier. The web extension's
SFSafariAppExtensionBundleIdentifiersToReplace key causes Safari
versions that can load it to hide the App Extension and migrate its
enabled state, while older Safari shows only the App Extension.
Also remove the removed-files entries for the App Extension's
resources, which would otherwise delete them on update.
https://forums.zotero.org/discussion/132925/
hdiutil makehybrid gives every file on the image non-empty Finder info
(it sets an icon location), which appears as a com.apple.FinderInfo
extended attribute. codesign --verify --strict rejects FinderInfo as
detritus, and it can cause Safari to fail to load the web extension.
(Reported by a user with Safari 17.6 on macOS 12. No other reports from
the beta, so maybe not universal.) The Safari App Extension apparently
wasn't affected, since it shipped with the same attributes for years.
Convert the hybrid image to a read-write image, mount it, strip the
attributes from its files, and compress from that. The volume header is
untouched, so the open-folder flag that makes Finder open the volume
window on mount is preserved.
Only fresh installs from the DMG were affected. The updater writes fresh
files without the attributes, so copies updated in place were clean.
(Notably, this is the reverse of the post-update extension breakage for
which the standard advice has always been to delete Zotero.app and
redownload. Following that advice is exactly what resulted in a broken
copy here.)
https://forums.zotero.org/discussion/132925/
The toolkit's default menuitem padding is em-based, so it balloons
with the UI font size in menulist dropdowns (e.g., the item type
menu), which inherit that font, forcing long lists to scroll. macOS
uses a fixed value on all menus and is unaffected. Match that on
Windows and Linux, slightly roomier, so every menu stays compact
regardless of font size.
https://forums.zotero.org/discussion/132797/request-for-compact-view-in-dropdown-menus
allowJavaScript: false didn't disable scripts for a system-principal
document, such as a blob: URL created from chrome code, which runs
scripts regardless of the allowJavascript flag. Sandbox the document
instead, forcing a null principal and blocking script execution.
The retraction checker fired a separate 'refresh' notification for
every matched item on each check, including already-known retractions
that hadn't changed. At startup this produced a burst of events that
forced observers like the item tree to rebuild repeatedly. Batch each
operation's changes into a single notification, and notify only for
items whose retraction state actually changed.