- 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.
The candidate scan counted items whose Extra content parsed as fields
but couldn't actually be migrated (e.g., fields already set on the
item), so the window could appear on every global schema update without
changing anything.
Also use a dedicated headline instead of "Upgrading database…", and show
the window only if the migration is still running after 500 ms.
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
The connector is now a Safari web extension, so the bundled appex is a
static wrapper stub whose placeholder resources are replaced at build
time with a connector build via $SAFARI_EXT_RESOURCES. The stub only
needs to be rebuilt when the native wrapper project changes, not for
connector releases.
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.
These conditions expanded into an OR-group across their underlying
fields, so a "does not contain"/"is not" operator matched almost every
item: any item missing one of the fields satisfied that field's negated
condition. Use an AND-group for negative operators, so the value must be
absent from every field.
https://forums.zotero.org/discussion/132835/
Selecting all libraries pulled in feed rows, which belong to a different
visibility group and so can't be shown together, collapsing the
selection back to a single row. Constrain the all-libraries scope to the
current selection's visibility group. Also preventDefault() the shortcut
so it no longer falls through to the cmd_selectAll key command, which
threw NS_ERROR_FAILURE.
https://forums.zotero.org/discussion/132843/beta-cannot-select-all-libraries
The download-rename test used a backslash as its sample invalid
character, which now throws in the attachmentFilename setter. Use a
colon instead, which still tests download-time filtering.
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
Accented stop-words weren't matched by the existing unaccented
entries (e.g., "fur"), so automatic journal abbreviation kept and
capitalized them: "Jahrbuch für Heimatkunde" became "Jahrb. Für
Heimatkunde" instead of "Jahrb. Heimatkunde".
https://groups.google.com/g/zotero-dev/c/uP18QEKe2JU/m/AsoGWxd9AwAJ