Compare commits

...

436 commits
main ... 6.0.33

Author SHA1 Message Date
Dan Stillman
cddd989159 Update version 2024-02-24 13:51:45 -05:00
Dan Stillman
d6418c443f Update version 2024-02-21 17:17:58 -05:00
Dan Stillman
ffb1cc0c94 Update version 2024-02-18 06:18:45 -05:00
Dan Stillman
dd98c5b2b1 Update translators and styles 2023-11-02 17:40:03 -04:00
Dan Stillman
d05fd69fc2 Block JS and media loading in reports 2023-11-02 17:40:03 -04:00
Dan Stillman
0633f0191b Update version 2023-11-02 17:40:03 -04:00
Dan Stillman
72850b49dc Update version 2023-10-23 03:23:23 -04:00
Adomas Venčkauskas
32e50d89be Fix http word integration word path unicode issues. 2023-09-27 12:19:58 +03:00
Dan Stillman
9c9936a88a Update version 2023-09-06 15:54:38 -04:00
Dan Stillman
16ea4da9b2 Don't show underline or text annotations in Zotero 6 2023-09-05 00:00:18 -04:00
Dan Stillman
9e4986abf5 Update pdf-worker submodule 2023-09-04 21:57:19 -04:00
Adomas Venčkauskas
a8a691fddb Fix the integration template check for unknown agents
(cherry picked from commit 9668270017)
2023-09-04 03:09:16 -04:00
Dan Stillman
e19ab9b30b Don't require unused date field in retractions list
(cherry picked from commit b4c3c0c7f9)
2023-09-03 18:53:15 -04:00
Abe Jellinek
8f3f3c9b86 Fix ScienceDirect PDF downloads
ScienceDirect sometimes puts the `name` directive at the end of the Content-Type
header instead of in Content-Disposition. That isn't strictly spec-approved, but
there are other directives (`charset` and `boundary`) that can also be appended
to Content-Type per the spec. We want to strip them before looking for handlers.

https://forums.zotero.org/discussion/105194/sciencedirect-pdf-downloads-not-working-zotero-7

(cherry picked from commit a4c3f5267b)
2023-08-30 05:46:31 -04:00
Dan Stillman
117bc45ed4 Look up PubMed IDs in batches of 200
https://forums.zotero.org/discussion/comment/440245/#Comment_440245

(cherry picked from commit af91173734)
2023-08-30 05:46:29 -04:00
Dan Stillman
4db311eb46 Serialize lookup requests
These were switched to parallel in 86b77cc45, resulting in blocks:

https://forums.zotero.org/discussion/106658/limited-amount-of-articles-that-can-be-added-through-pmid

(cherry picked from commit fcc68d6d80)
2023-08-30 05:46:26 -04:00
Dan Stillman
fb148497ba Add Zotero.getMainWindows()
(cherry picked from commit 07309d7c25)
2023-08-30 05:34:39 -04:00
Dan Stillman
6f506bbc3d Make annotation sortIndex more flexible
(cherry picked from commit af892e5cbd)
2023-08-30 05:23:18 -04:00
Martynas Bagdonas
78865ed2ef Add support for text annotations
(cherry picked from commit 89adf56eed)
2023-08-30 05:21:48 -04:00
Martynas Bagdonas
0ad55a6a70 Add support for underline annotations
(cherry picked from commit cb14034c46)
2023-08-30 05:21:43 -04:00
Dan Stillman
4f7583e1c0 Disable attachmentContentType check to prevent sync errors
(cherry picked from commit 81ec8287f8)
2023-08-30 05:21:12 -04:00
Adomas Venčkauskas
48c7c60bf1 Include information about saved snapshots in /saveSnapshot 2023-07-12 10:32:08 +03:00
Dan Stillman
266060dc48
Merge pull request #3171 from adomasven/mac-word-http-6.0
Add support for mac word transaction initialization via http for Zotero 6.0
2023-07-06 01:44:59 -04:00
Adomas Venčkauskas
9515912830 Update template version check 2023-06-16 10:18:42 +03:00
Adomas Venčkauskas
078626be82 Add support for macWord transaction initialization via HTTP 2023-06-16 10:17:53 +03:00
Dan Stillman
4b61954e52 Upload deployment ZIP from 6.0 branch 2023-05-21 18:49:26 -04:00
Abe Jellinek
0d6678a735 Disable window dragging inside toolbar controls
And enable in the context pane toolbar extension.
2023-05-17 16:54:01 +03:00
Abe Jellinek
2b9455670f Update pdf-reader
Switch to fx63-compatible branch with draggable toolbar.
2023-05-16 16:32:14 +03:00
Abe Jellinek
1b3caadd20 Allow window dragging from PDF reader toolbar
Fixes #2713
2023-05-16 16:21:48 +03:00
Abe Jellinek
c9381f307c Accept non-integer attachmentLastPageIndex 2023-05-12 22:30:01 +03:00
Dan Stillman
c9c7805cdf Remove connectorTypeSchemaData.js and update build_typeSchemaData.js
We now use zoteroTypeSchemaData.js in the connector, so update the
build script to generate that.
2023-05-06 02:01:36 -04:00
Dan Stillman
3e12f3f20b Revert to earlier translate submodule before Z7 HiddenBrowser changes 2023-05-01 02:06:31 -04:00
Dan Stillman
2f0d41c0cb Ignore numeric values for Type: lines in Extra
https://forums.zotero.org/discussion/comment/433334/#Comment_433334
2023-04-23 17:07:57 -04:00
Dan Stillman
00e63f0f8a Lint: Fix some missing spaces that make eslint behave annoyingly 2023-04-23 17:07:57 -04:00
Abe Jellinek
8096e22f40 Scaffold: Improve perceived translation speed
When translators do a lot of logging.

https://github.com/zotero/translators/pull/3022#issuecomment-1517291971
2023-04-21 13:45:54 -04:00
Dan Stillman
e59e6cdcd8 Update version 2023-04-21 07:18:50 -04:00
Dan Stillman
fcc6cc17a9 Better logging for changes to deleted property 2023-04-21 06:52:11 -04:00
Dan Stillman
8992efc869 Fix potential sync error after Replace Online Library in group
https://forums.zotero.org/discussion/104431/syncing-problem

Replace Online Library can upload annotations created by others in a
group library, so if the upload resulted in a local write, "Cannot edit
item in library" was thrown, since annotations by others aren't
writable. This should've only been a problem if the uploaded data was
actually modified by the server, but we were also checking whether
objects were editable before checking if they had actually changed, so
it would happen for any upload of another person's annotation.

This fixes the order of checks when saving objects and makes an
edit-check exception for saving uploaded data for group annotations.
2023-04-21 06:52:08 -04:00
Dan Stillman
01ebeb1834 Update utilities and translate submodules 2023-04-20 04:37:38 -04:00
Dan Stillman
bed256318a Update connectorTypeSchemaData and test data for Dataset Number removal 2023-04-20 03:36:33 -04:00
Dan Stillman
860e901240 Fix redundant Number on Dataset item type 2023-04-20 03:24:26 -04:00
Dan Stillman
9ce8855fa4 Update citeproc-js to 1.4.63
73bc1b44bc
2023-04-19 17:27:37 -04:00
Dan Stillman
ecbee6ee26 Update version 2023-04-19 16:53:26 -04:00
Dan Stillman
fe8308c9dd Update version 2023-04-19 04:27:33 -04:00
Adomas Venčkauskas
90589606c2 Fix classic citation dialog collection tree not working (regression b213b8f1)
Report https://forums.zotero.org/discussion/104460/zotero-6-0-24-classic-add-citation-dialog-doesnt-show-collections-content
2023-04-19 11:25:29 +03:00
Dan Stillman
77e0c71c74 Update version 2023-04-18 17:45:39 -04:00
Dan Stillman
ac2c001291 Update locales from Transifex 2023-04-18 17:45:39 -04:00
Dan Stillman
c641b28a9f Update translators 2023-04-18 16:45:18 -04:00
Adomas Venčkauskas
31fc418a97 Add a test for feed update scroll issues. See b213b8f1 2023-04-17 12:29:41 +03:00
Adomas Venčkauskas
b213b8f15d Fix feed update refreshing, scrolling item and collection trees
Closes #2950
2023-04-14 15:22:22 +03:00
Dan Stillman
7e6f7ac4b3 CI: Stop using --legacy-peer-deps
No longer necessary after react-virtualized update (#3055)
2023-04-13 00:56:23 -04:00
Dan Stillman
5327616376 Update react-virtualized to 9.22.4
Fixes #3055 for now
2023-04-13 00:53:53 -04:00
Dan Stillman
9a7486e53c Prevent setting search .name to empty value
Prevents bug in zotero-citation plugin (at least on macOS) from creating
a search that breaks syncing

We were already checking for a missing name in `saveTx()`, but the
plugin is saving the same search twice in rapid succession, the second
time without a name, and the second attempt clears the search object's
name value after the first save's `_initSave()` check and before its SQL
write. The second save fails, but the first save goes through without a
name, resulting in a sync error.

https://forums.zotero.org/discussion/104274/id-1702002152-cannot-sync
https://github.com/MuiseDestiny/zotero-citation/issues/31
2023-04-11 23:48:07 -04:00
Dan Stillman
1d6cae08d2 Add '#' to default annotation color
And add test
2023-04-09 17:03:50 -04:00
Dan Stillman
ce16a28f4a Set a default annotation color if not assigned
https://forums.zotero.org/discussion/104249/sync-issue-on-ipados
2023-04-09 17:00:08 -04:00
Dan Stillman
400396be25 CI: Update actions/cache version 2023-04-07 04:28:29 -04:00
Dan Stillman
41c0d9346e CI: Add --openssl-legacy-provider for Webpack 2023-04-07 03:41:51 -04:00
Dan Stillman
543bfdabd3 CI: Use npm i --legacy-peer-deps for react-virtualized 2023-04-07 03:18:25 -04:00
Dan Stillman
dcfce382d9 Update GitHub Actions action versions and Node version 2023-04-07 01:41:28 -04:00
Martynas Bagdonas
5ad5598089 Don't show HTML and Markdown options for collection export
Fixes #3033
2023-04-05 09:15:36 +02:00
Dan Stillman
a66db9b52b Update connectorTypeSchemaData.js for Dataset and Standard 2023-04-03 01:10:03 -04:00
Dan Stillman
49b08df0dd Update test sample data for authority/legislativeBody base mapping 2023-04-03 00:43:55 -04:00
Dan Stillman
2e4fed4000 Update global schema and utilities 2023-04-02 19:12:17 -04:00
Dan Stillman
db99d4cfe1 Update utilities submodule 2023-04-02 19:04:15 -04:00
Dan Stillman
85c35a2270 Update global schema 2023-04-02 18:41:30 -04:00
Dan Stillman
3cb5837bc6 Update styles and translators 2023-04-02 03:56:16 -04:00
Dan Stillman
7b614962c6 Update locales from Transifex and merge new English strings 2023-04-02 03:52:13 -04:00
Dan Stillman
c8109fef18 Restore "Authority" as localized string
We still use base-only fields as search conditions, and schema.json
consumers might want to do so as well, so it makes sense to include
them.

Also remove base-only localized strings from `searchConditions` that are
already included in `itemFields.`
2023-04-01 17:13:28 -04:00
Dan Stillman
1497524bab Remove localized string for "Authority"
This is only a base field and doesn't need to be localized.

Added by mistake in 42a4649420
2023-03-23 04:18:28 -04:00
Dan Stillman
cd88d1f661 Update CSL locales 2023-03-23 01:37:29 -04:00
Dan Stillman
23ebf277fe Write out path when generating item type/field data for tests
It writes to the build folder, so it's easy to think it's not working.
2023-03-23 01:37:29 -04:00
Dan Stillman
42a4649420 Add Dataset and Standard item types
zotero/zotero-bits#22
zotero/zotero-bits#52
2023-03-23 01:37:29 -04:00
Dan Stillman
778fba60cb Limit Everything full-text-word subsearch to current library
This fixes an error using Everything if another library hasn't yet been
loaded and should speed up Everything searches for people with multiple
libraries.

This is sort of an awkward fix, because it doesn't fix the underlying
problem of `addCondition('libraryID', ...)` not working the same as
`.libraryID =` for some search conditions that perform subsearches. But
supporting `addCondition('libraryID', ...)` for those would get
complicated, because there could technically be multiple such
conditions, and applying those to a subsearch that used `AND` would get
messy. So let's just fix the problem at hand.

Fixes #3032
2023-03-21 16:11:59 -04:00
Dan Stillman
bba09658e1 Update CSL locales 2023-03-21 15:56:47 -04:00
Dan Stillman
b298a48cda Better way of skipping migration of Place and Date from Extra
Or at least a way that we already have built-in and that only applies to
the call in `Zotero.Item::migrateExtraFields()`. This doesn't
distinguish between CSL fields (`publisher-place`, `event-place`,
`issued`) and actual Zotero field, and we really only need to skip
the former, but it's fine.

Follow-up to 8a1dd3637, related to #3030
2023-03-21 06:29:50 -04:00
Dan Stillman
8a1dd3637c Temporarily disable extracting Issued from Extra
Until we can parse ranges in the Date field. See #3030.

citeproc-js will still use it (which is the point).
2023-03-21 05:55:08 -04:00
Adomas Ven
e024449732
Sorts first-author last name matches first when year present. (#3025)
Closes #3024
2023-03-18 20:52:54 -04:00
Abe Jellinek
14bb46f434
Scaffold: Support the detectedItemType test field (#3023) 2023-03-13 17:50:41 -04:00
Dan Stillman
bc07bf803c Update locales from Transifex 2023-03-12 16:44:56 -04:00
Dan Stillman
367dfdf969 Update translators 2023-03-12 16:15:16 -04:00
Tom Najdek
c57c3c470e
Mendeley importer: Fix issue with empty tags (#3018)
Also adds a test for this particular case and for importing tags in
general.
2023-03-09 16:13:19 -05:00
Tom Najdek
a1d06066b2
Mendeley importer: Fix issue with empty creators (#3016)
It does not appear to be possible to create a creator with no values in
Mendeley, however we got reports of these causing the imports to fail.
This tweak makes the importer more resilient by discarding empty/invalid
creators.
2023-03-07 19:40:42 -05:00
Dan Stillman
1f45c44b86 Fix no such column: IA.authorName error from early 6.0 beta upgrade
Fixes #3013
2023-03-04 22:19:01 -05:00
Andreas Grapentin
8ac77bf9f0
modified standalone keyset to include the quitApplication keybinding (#3010)
modified unix keyset to include the quitApplication keybinding, modified windows keyset to use the correct keybinding
2023-03-04 22:16:05 -05:00
Adomas Venčkauskas
59ba212cf8 Use referrer from connector when saving snapshots
Closes #3006
2023-02-28 13:38:05 +02:00
Dan Stillman
935c67bc01 Match duplicates with same last name but different field modes
https://forums.zotero.org/discussion/103307/duplicates-not-being-recognised-as-duplicates
2023-02-28 00:35:13 -05:00
Dan Stillman
adf6f9f2fb Use clearer variable in item box
https://groups.google.com/g/zotero-dev/c/TvOfXVxwwkA/m/SdpU1QD7AAAJ
2023-02-25 01:39:22 -05:00
Abe Jellinek
bdadd363d3 Scaffold web template: Serialize multiples
Fixes #2980
2023-02-23 16:37:16 -05:00
Dan Stillman
c3cde7784d Update CSL locales 2023-02-22 22:16:05 -05:00
Dan Stillman
d7a8b2bb87 Update version 2023-02-22 22:16:05 -05:00
Dan Stillman
4ae4d98417 Update styles and translators 2023-02-22 16:43:00 -05:00
Dan Stillman
a9f1526bfc Update locales from Transifex 2023-02-22 16:42:43 -05:00
Dan Stillman
f868f4f713 Update locales from Transifex 2023-02-21 16:40:36 -05:00
Dan Stillman
f3c0c01fe1 Fix startup error if DB corrupt and no backup files
Fixes #2996
2023-02-18 03:49:56 -05:00
Martynas Bagdonas
0bd39ef42e Fix annotation image rendering caused issues:
- Don't save unmodified annotation item when rendering annotation image
- Don't render annotation image multiple times
2023-02-17 10:31:28 +00:00
Dan Stillman
a78920b16f Merge new English strings 2023-02-15 20:08:40 -05:00
James Eschrich
d866a10a2b
Made toolbar accessible to keyboard (#2853) 2023-02-13 23:28:53 -05:00
Adomas Ven
0f9024dc98
Remove firefox-zotero IPC pipe and switching code (#2990) 2023-02-13 19:18:40 -05:00
Dan Stillman
d8f7626dd4 Update note-editor 2023-02-10 17:03:26 -05:00
Martynas Bagdonas
c8da33d19c Tweak note-editor text and highlight color icons 2023-02-10 11:32:57 +00:00
Martynas Bagdonas
0f63b2e654 Change note-editor text color icon 2023-02-10 09:47:31 +00:00
Martynas Bagdonas
19be7b9b70 Add missing text color string 2023-02-10 09:45:57 +00:00
Dan Stillman
52a1981747 Update locales from Transifex and merge new English strings 2023-02-10 00:36:38 -05:00
Martynas Bagdonas
19e1b231de Add more colors to pdf-reader, note-editor and introduce text color tool 2023-02-09 16:15:12 +00:00
Martynas Bagdonas
b956d9b429 Add Filename option for PDF tab title pref 2023-02-06 17:44:01 +00:00
Dan Stillman
962cc23d1a Update Retraction Watch retraction reasons 2023-02-05 21:50:29 -05:00
Dan Stillman
bbf72bfbcf Fix some lint errors in Zotero.Retractions 2023-02-05 21:50:29 -05:00
Dan Stillman
b924720a2d Detect retractions for DOIs with capital letters
https://forums.zotero.org/discussion/102745/retraction-watch-not-working
2023-02-05 21:50:29 -05:00
Dan Stillman
34bb51f58a Update version 2023-02-04 16:46:18 -05:00
Dan Stillman
3aae80ca7d Merge new English strings 2023-02-03 00:55:33 -05:00
Dan Stillman
ce60ce90f6 Add PDF reader tab title pref to General pane 2023-02-03 00:43:18 -05:00
Dan Stillman
6d0e5959e6 Rename PDF tab title pref to include reader
In case we have a separate pref for notes or something else
2023-02-03 00:43:18 -05:00
Martynas Bagdonas
a5fb64f295
Add a hidden pref for tab title with support for Creator-Year-Title option (#2985) 2023-02-02 17:35:58 -05:00
Dan Stillman
87fbf3c7fb Add English strings from connector 2023-01-26 17:07:10 -05:00
Dan Stillman
c0bc2bafed Update version 2023-01-25 18:53:48 -05:00
Dan Stillman
886500812e Update translators 2023-01-25 18:53:28 -05:00
Dan Stillman
4a06053d72 Update locales from Transifex and merge new English strings 2023-01-25 18:52:47 -05:00
Adomas Ven
0ffe3122c2
Refactor l10n code. Remove use of react-intl. (#2975)
Strings from .dtd files are now accessible from Zotero.getMessage
as the eventual move to Fluent would remove their distinction.
2023-01-17 15:59:19 -05:00
Martynas Bagdonas
1f721feab7 Increase active tabs count from 2 to 3 on Windows and <=8 GB systems 2023-01-17 19:35:38 +02:00
YFdyh000
9f53b48984
Show fieldLocalizedName in Scaffold - List All Fields (#2965) 2023-01-17 10:38:43 -05:00
Dan Stillman
9dad14c244 Update version 2023-01-13 20:21:22 -05:00
Dan Stillman
6374aea1c8 Update translators 2023-01-12 21:34:42 -05:00
Dan Stillman
be6eee5412 Update locales from Transifex 2023-01-12 21:33:09 -05:00
Dan Stillman
5919f0e77a Fix DOI field context menu not appearing after field edit
Context menu wouldn't appear again until the item box was refreshed
2023-01-12 20:35:22 -05:00
Dan Stillman
f006995676 Item box: Use addEventListener() 2023-01-12 20:35:20 -05:00
Dan Stillman
665473b108 Add protection against immediate retries in delayGenerator 2023-01-11 02:32:44 -05:00
Dan Stillman
21777af980 Fix incorrect retries if multiple HTTP.request() calls get 500s
https://forums.zotero.org/discussion/comment/425814/#Comment_425814

The passed intervals array was modified, so after a request() got a 500,
subsequent calls would start with longer delays and eventually start
retrying immediately.
2023-01-11 02:32:39 -05:00
YFdyh000
4fbb89a588
stop opening while right click on attachment's url (#2962) 2023-01-09 19:36:05 -05:00
Dan Stillman
ff38ff5b9d Fix broken creator autocomplete if place becomes a base field
Well this was a wild one to debug.

Creator fields were only initialized for autocomplete due to a series of
>10-year-old bugs:

1) In `showEditor()`, `Zotero.ItemFields.isAutocompleteField(fieldName)`
   was called for creator fields, which would pass, e.g.,
   `creator-0-lastName`.

2) In `isAutocompleteField()`, `ItemFields.getName()` would normalize
   `creator-0-lastName` to `false`, since it's not a valid field.

3) `isAutocompleteField()` listed `place` as a base field despite its
   not having any mapped fields, so when `getTypeFieldsFromBase()` was
   called on it, the return value would be `false`, which would be added
   to the list of autocomplete fields, which would mean that the
   normalized field of `false` from `creator-0-lastName` would match,
   which would mean that `isAutocompleteField('creator-0-lastName')`
   would always return true...as long as `place` never gained a mapped
   field.

Except `isAutocompleteField()` wasn't supposed to be the test for
initializing autocomplete for creator fields anyway -- `fieldName ==
'creator'` was. But `fieldName` is something like `creator-0-lastName`,
not `creator`, which meant that that test always failed, which meant
that if `place` did gain a mapped field, both tests would fail, which
would cause the creator field not to be initialized for autocomplete,
which would cause it to break as soon as you started to type into it.

This fixes that.
2023-01-09 06:15:33 -05:00
Dan Stillman
9d2c7d44f8 Additional logging for "Invalid sync state undefined"
https://forums.zotero.org/discussion/102186/invalid-sync-state-undefined
2023-01-07 15:25:21 -05:00
Abe Jellinek
3678639273
LABD file relinking: Process using Unix paths (#2961) 2023-01-05 15:07:10 -05:00
Martynas Bagdonas
52a079044d Limit Windows version to 2 PDF tabs in memory
Fixes zotero/zotero#2955
2023-01-02 12:51:14 +02:00
Dan Stillman
7e61be9799 Disable /connector/request tests from 86c56951df
Until we restore the new header handling
2022-12-25 04:01:35 -05:00
Dan Stillman
0862e1e1bc Revert "Zotero.HTTP.request(): Process headers case insensitively"
This reverts commit f6dd47dd1f.

I shouldn't have cherry-picked this from fx102.
2022-12-24 03:55:15 -05:00
Abe Jellinek
f6dd47dd1f Zotero.HTTP.request(): Process headers case insensitively
Using the Headers class from the Fetch API.

Before, the added test would fail: `_requestInternal()`, not finding a header
named `Content-Type` (case sensitive), would set it to
`application/x-www-form-urlencoded`. XMLHttpRequest, upon being given both
`content-type`: `application/json`) and `Content-Type`:
`application/x-www-form-urlencoded`, would helpfully merge the two, producing
`content-type`: `application/json, application/x-www-form-urlencoded`. That's
obviously not the correct behavior.
2022-12-23 21:42:36 -05:00
Abe Jellinek
86c56951df Add /connector/request endpoint 2022-12-23 21:42:36 -05:00
Dan Stillman
51762e1c41 Update translators 2022-12-23 00:41:43 -05:00
Dan Stillman
5697f12310 Update locales from Transifex 2022-12-23 00:40:26 -05:00
Dan Stillman
4184dc18d2 Add mechanism for setting preferences remotely
Limited to a hard-coded list

Initially limited to `extensions.zotero.import.mendeleyUseOAuth`,
to switch the Mendeley importer from direct login to OAuth
2022-12-23 00:20:43 -05:00
Dan Stillman
da392518ed Allow 204 response to OPTIONS request from WebDAV server
https://forums.zotero.org/discussion/101824/possible-to-allow-http-204-response-for-options-request-in-webdav-server-check

The specs used to (seemingly inadvertently) disallow 204 for OPTIONS,
but they've been clarified to make it clear that it's allowed [1], and MDN
now even shows it as an example [2].

[1] https://stackoverflow.com/a/57455110
[2] https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
2022-12-17 04:02:20 -07:00
Dan Stillman
1a83b472e8 Merge new English strings 2022-12-17 02:35:08 -07:00
Dan Stillman
7262465109 Mendeley citation relinking tweaks
- Show "More information" link next to relink option
- Automatically check relink option when coming from integration prompt
- Change done message to "[x] items were relinked" instead of "0 items
  were imported"
2022-12-17 04:31:55 -05:00
Tom Najdek
91c385a67a Add option to Mendeley importer to relink items
New option only appears if importer version is < 1 or not present.
It will:
    * Skip fetching collections and attachments
    * Skip any new items
    * Update relations on existing items
2022-12-17 04:31:55 -05:00
Dan Stillman
48ad7c0ab7 Mendeley import prompt tweaks
- Tweak message text
- Add "More Information" button
2022-12-17 03:58:16 -05:00
Adomas Venčkauskas
2082b68dd3 Add a prompt to import Mendeley DB if mendeley citation found in doc 2022-12-17 03:58:16 -05:00
Adomas Venčkauskas
dcd6cdbcb3 Add a nicer interface for prompts 2022-12-17 03:58:16 -05:00
Adomas Venčkauskas
d27fc31ef0 Fix a potential bug in integration plugin when field codes are corrupt 2022-12-14 16:20:44 +02:00
Dan Stillman
95e46ac8a2 Store Mendeley importer version number in database 2022-12-13 23:38:53 -07:00
Dan Stillman
41b0e64109 "installed" → "enabled" in ZotFile error message for Mendeley import 2022-12-13 23:38:53 -07:00
Martynas Bagdonas
77eb325b26 Update note-editor and pdf-worker submodules 2022-12-12 12:56:53 +02:00
Dan Stillman
a9dc25decc Update translators and styles 2022-12-12 00:27:04 -07:00
Dan Stillman
df3856f735 Update locales from Transifex 2022-12-12 00:27:04 -07:00
Abe Jellinek
6a0ecd243d v-t: Fix freeze when type-to-find wraps and first row is selected 2022-12-06 14:00:54 +01:00
Dan Stillman
1422d84bd6 Add "support questions" to CONTRIBUTING.md 2022-12-01 05:09:56 -05:00
Adomas Venčkauskas
3d8bbb3e55 Link Mendeley citations in documents to imported items. Closes #2622 2022-11-29 16:27:36 +02:00
Dan Stillman
f33cf1cde3 Don't show items with annotated attachments after moving to trash
https://forums.zotero.org/discussion/100775/deleted-items-keep-reappearing-in-my-library

Regression from 20c6fe67
2022-11-28 04:30:50 -05:00
Adomas Venčkauskas
deb5d1cd27 Avoid locator detection on paste in citation dialog. Closes #2856
Locators will get detected if there is additional typing or editing
after the paste
2022-11-22 15:57:07 +02:00
Adomas Venčkauskas
29006c556d Fix quick format locator regexp to not match numbers without space.
Closes #2915
2022-11-22 15:57:05 +02:00
Adomas Venčkauskas
2db90511f9 Import URL from XPCOM code instead of relying on window for Proxy code
Also fix other failing proxy tests. Closes #2928
2022-11-21 11:51:18 +02:00
Adomas Venčkauskas
8135895313 Fix proxy test 2022-11-21 11:28:52 +02:00
Adomas Venčkauskas
dfdc66a41c Proxy code: ensure URL constructor in non-window JS scope
Closes #2924
2022-11-21 10:24:11 +02:00
Dan Stillman
1106685f2d Fix Item.fromJSON() failure after deb17d450e 2022-11-19 05:26:04 -05:00
Dan Stillman
deb17d450e Don't allow note on embedded-image attachments
https://github.com/windingwind/zotero-better-notes/issues/212
2022-11-19 04:55:32 -05:00
Dan Stillman
89bc094690 Fix response content type in Find Available PDF test 2022-11-16 18:35:31 -05:00
Dan Stillman
1a1c08890f Use clearer name for Find Available PDF tests 2022-11-16 18:35:31 -05:00
Dan Stillman
a48e0f2437 Handle relative PDF links when using custom PDF resolver 2022-11-16 18:35:31 -05:00
Tom Najdek
ac4308b3e7
Fix focus stealing in item box #2885 (#2895) 2022-11-16 15:55:24 -05:00
Adomas Venčkauskas
c3c0b363c1 Make sure URL constructor is available in non-window environment 2022-11-15 15:23:24 +02:00
Adomas Venčkauskas
ae14d433f9 Don't throw when getting potential proxies with invalid URL 2022-11-15 13:53:25 +02:00
Adomas Venčkauskas
b91d85f288 Deproxify homepage URLs without trailing slash. Closes #2884 2022-11-15 13:23:04 +02:00
Dan Stillman
26847c672f Fix clearing of active quick search after annotation change
https://forums.zotero.org/discussion/90727/search-results-disappear
2022-11-14 22:56:11 -05:00
Dan Stillman
a505c94751 Update locales from Transifex and merge new English strings 2022-11-14 16:46:59 -05:00
Dan Stillman
52176dc30a
Merge pull request #2871 from tnajdek/mendeley-import-direct-auth
Mendeley Import: Switch to direct login/password auth in order to obtain Mendeley Desktop item id
2022-11-14 02:23:04 -05:00
Tom Najdek
0e12126436
Block Mendeley import if ZotFile installed 2022-11-13 12:47:29 +01:00
Tom Najdek
d131fe3dc9
Mendeley Import: Auth using direct login
* Importer will now ask user for a login and password via form and will perform sign-in directly
  using credentials rather than oauth
* Signing in this way enables importer to obtain desktop document ID which is now stored for each
  item
* It's possible to switch back to the old method (ouath) by setting `import.mendeleyUseOAuth` pref
  to `true`.
* New option to only import new items. This options only appears if database contains previously
  imported items.
* Importer will now update mendeleyDB:documentUUID on existing items to match value used in Mendeley
  Desktop if available
* Importer will no longer create collections when no new items are imported
* Importer will only report number of new items imported on re-import
* Importer will now preserve dateAdded on re-import

Co-authored-by: Dan Stillman <dstillman@zotero.org>
2022-11-13 12:47:29 +01:00
Abe Jellinek
c2941738e6 Add "Any Field" advanced search condition (#2902) 2022-11-11 12:17:56 -05:00
Adomas Venčkauskas
0aae101196 Improve cookie-sandbox behavior when getting attachments via WBP
Previously cookies only got attached on the initial request but not on
any redirect and subsequent request. This may have been the cause for
many reports of import failures behind proxies in the past.
2022-11-11 12:55:55 +02:00
Dan Stillman
02098a9a34 Update version 2022-11-11 01:47:09 -05:00
Adomas Venčkauskas
378d8180e8 Adjust tests to not use the dotsToHyphens proxy option 2022-11-10 12:46:40 +02:00
Adomas Venčkauskas
e3d13826c8 Fix proxied urls not unproxifying (regression 1116b05f)
Technically this code has been wrong for a long time, but it only
manifest now. It seems that the equivalent code in translate module
was fixed when that code was extracted into a separate repo
2022-11-10 12:46:36 +02:00
Adomas Venčkauskas
6fc5b037d9 Update translate submodule 2022-11-10 12:28:06 +02:00
Adomas Venčkauskas
6a4a329ec6 Always replace dots to hyphens for https proxy schemes 2022-11-10 12:25:40 +02:00
Dan Stillman
656acf3908 Update version 2022-11-09 04:17:47 -05:00
Adomas Venčkauskas
1920a5efd7 Prevent citation merging from being always enabled after refreshing the doc 2022-11-09 11:07:45 +02:00
PChemGuy
13ab3de1a1
Fixed double-quoted string literals in SQL files (#2892)
Replaced double quotes around string literals with single quotes.
2022-11-04 01:21:27 -04:00
Abe Jellinek
47610d0faa Scaffold: Autocomplete translator IDs in setTranslator() calls 2022-11-01 16:38:01 -04:00
Abe Jellinek
42e042c6c6 Scaffold: Fix copying JSON test data
Previously only copied '[object Object]'.
2022-11-01 16:05:54 -04:00
Dan Stillman
b9a5fd9f78 Fix error copying from note if note Quick Copy format is "HTML"
In this case, Note Markdown wouldn't be preloaded, but it's always used
for copying from the note editor, and since the note editor uses
`noWait`, this would result in "Code promise is not resolved in noWait
mode".
2022-11-01 03:57:50 -04:00
Dan Stillman
cbb524d257 Fix Quick Copy drag after reiniting translators
Zotero.Translators.reinit() didn't reinit Quick Copy, so if an export
translator was selected it wouldn't be preloaded again and dragging
would fail with "Code promise is not resolved in noWait mode".
2022-11-01 03:57:50 -04:00
Dan Stillman
93ea818bf2 Update version 2022-11-01 03:57:50 -04:00
Dan Stillman
d2243cc339 Update translators 2022-10-31 19:11:56 -04:00
Dan Stillman
f06267e4cd Update styles and translators 2022-10-31 17:40:00 -04:00
Dan Stillman
624101832e Update locales from Transifex 2022-10-31 17:34:24 -04:00
Martynas Bagdonas
6bc1551f7f Increase wait time for pdf-reader iframe initialization in tests
This is necessary until a proper initialization promise is added to pdf-reader.
2022-10-31 18:03:42 +02:00
Dan Stillman
a8a0cabcc4 Fix HiDPI icon for webpages 2022-10-31 05:43:17 -04:00
Dan Stillman
9d80c54d5d Update repotime 2022-10-31 05:18:06 -04:00
Adomas Venčkauskas
1c8554d527 Make colored tagging via keyboard layout independent
Report https://forums.zotero.org/discussion/100717/tag-number-shortcut-with-mac-raccourci-clavier-pour-marqueurs-avec-un-mac
2022-10-31 09:45:56 +02:00
Dan Stillman
5cea183ae3 Avoid infinite/excessive loops in Find Available PDF
https://forums.zotero.org/discussion/100634/potential-infinite-loop-when-trying-to-find-available-pdf

Closes #2883
2022-10-30 04:53:09 -04:00
Dan Stillman
92434dbb78 Update Mac Word automation settings instructions for Ventura 2022-10-30 04:53:08 -04:00
Dan Stillman
2c5a91682e Fix comment for Zotero.Attachments.getPDFResolvers() 2022-10-29 04:43:23 -04:00
Dan Stillman
3cc54eaf95 Retry only twice on error in Find Available PDF
Not three times
2022-10-29 04:43:23 -04:00
Dan Stillman
7f91a0c1a8 Fix hang on 500 from site during Find Available PDF
Find Available PDF has its own domain-based retry logic that predated
automatic 5xx retries in Zotero.HTTP, so disable the latter and fix some
bugs in the former.

Fixes #2700
2022-10-29 04:43:23 -04:00
Adomas Venčkauskas
77fdc8e40c Truncate long citation text in citation dialog. Closes #2879 2022-10-28 15:09:35 +03:00
Dan Stillman
c5a769285b Update translators 2022-10-26 06:55:49 -04:00
Dan Stillman
fe09afa0a6 Restore cell.csl for style tests
Removed in 4e1754b960
2022-10-26 05:14:47 -04:00
Dan Stillman
ab2bec936e Reset accidental change from 7fcc1d35 2022-10-26 04:38:19 -04:00
Adomas Venčkauskas
af66712c77 Update SingleFile. Closes #2880 2022-10-25 15:57:54 +03:00
Adomas Venčkauskas
7db16843b3 Add an integration test for citation merging 2022-10-25 14:55:19 +03:00
Adomas Venčkauskas
a4eafd0307 Fix wrong given name disambiguation after editing author for citeproc-js
Closes #2870
2022-10-25 14:55:16 +03:00
Adomas Venčkauskas
4e1754b960 Change integration test citation style to APA and some refactoring 2022-10-25 14:55:16 +03:00
Dan Stillman
238c7ce410 Update locales from Transifex and merge new English strings 2022-10-24 21:13:24 -04:00
Dan Stillman
8a15e5abe0 Don't localize "Markdown" in Quick Copy settings 2022-10-24 19:53:42 -04:00
Dan Stillman
159c112257 Add Zotero.Utilities.Internal.stringWithColon() for localized colons
Generally just a colon, but, e.g., French adds a space before it. We'll
want to replace uses of `punctuation.colon` with this.
2022-10-24 19:49:10 -04:00
Dan Stillman
70f9462913 Merge pull request #2676 from mrtcode/note-html-export-links
Add "Include Zotero Links" option for note export and QuickCopy
2022-10-22 16:47:18 -04:00
Dan Stillman
4b5e2eae1b Update translators 2022-10-22 16:42:24 -04:00
Dan Stillman
c4876fd5ae Adjust layout of Quick Copy note format options
Also:

- Hide format options until translators are loaded (since otherwise
  the checkboxes don't have labels and the layout is messed up)
- Remove unnecessary "zotero-" prefix from the new elements
2022-10-22 16:41:48 -04:00
Dan Stillman
7fcc1d35a4 Reset note Quick Copy pref on upgrade if set to Markdown + Rich Text
The default JSON pref value used spaces, but changing the drop-down
resulted in a version without spaces, so simply changing the default to
include the new link options wouldn't change all existing installs and
the "Include App Links" checkbox wouldn't show as enabled for Markdown.
This adds a pref migration step to reset the pref to the new default if
it's set to Markdown + Rich Text, regardless of whitespace differences.
2022-10-22 16:41:48 -04:00
Martynas Bagdonas
d0d3ed731d Add "Include Zotero Links" option for note export and QuickCopy
Fixes #2349
2022-10-22 16:41:48 -04:00
Dan Stillman
f2bfaeb7a9 Fix error message for invalid field in Item.setField()
The error message was showing a field instead of an item type.

https://forums.zotero.org/discussion/100598/error-1533410729-impossible-to-correct-date-field-in-items-added-with-zotero-conector-ver-6-0-15
2022-10-22 05:28:02 -04:00
Adomas Ven
2fa67bae64
Adds support for merging adjacent citations (#2875) 2022-10-21 03:17:35 -04:00
Dan Stillman
a2db01c633 Merge new English strings 2022-10-18 00:00:46 -04:00
Dan Stillman
2b9e9c174f Don't match all attachments with annotations for "not" search conditions
Fixes #2867
2022-10-17 23:59:29 -04:00
Dan Stillman
aa0ef7012c PDF reader: Hide "Rotate Page 180°" menu option for now
Rotating will speed up with Zotero 7, so maybe we can do without this.
2022-10-17 23:59:07 -04:00
Dan Stillman
73306ee5fb PDF reader: Add "Page" to rotate menu options for clarity 2022-10-17 23:58:52 -04:00
Martynas Bagdonas
d8d6054927 Add rotation options to Edit menu to rotate the current PDF page 2022-10-17 12:26:47 +03:00
Dan Stillman
410ebc288b Update locales from Transifex 2022-10-14 17:17:48 -04:00
Martynas Bagdonas
55095ba354 Reduce loaded tabs limit if the system has 8 GB or less memory 2022-10-14 11:13:02 +03:00
Dan Stillman
3ba6343668 Merge new English strings 2022-10-12 05:04:49 -04:00
Dan Stillman
d37aa37629 Update note-editor and pdf-reader submodules 2022-10-12 05:03:09 -04:00
Dan Stillman
3582405f55 Default to UTF-8 when indexing text file without detected charset
https://forums.zotero.org/discussion/94637/indexing-of-markdown-plain-text-attachments-with-md-extension
2022-10-12 05:01:10 -04:00
Martynas Bagdonas
78eb857e73 Add annotation update test to pdf-reader tests 2022-10-11 18:07:03 +03:00
Abe Jellinek
618e71b5f1 Fix last editpane tab being misaligned vertically (#2864) 2022-10-11 10:49:30 -04:00
Martynas Bagdonas
7bada8ce0c Tweak pdf-reader annotation creation test 2022-10-10 17:09:30 +03:00
Martynas Bagdonas
af436d34a8 Add pdf-reader annotation creation test 2022-10-10 16:53:42 +03:00
Dan Stillman
e45becce2a Tweak text in LABD auto-relinking dialogs (#2374, #2092) 2022-10-08 02:29:39 -04:00
Abe Jellinek
fb4f7b7932 Automatically relink attachments from LABD (#2374)
Fixes #2092
2022-10-08 02:29:31 -04:00
Dan Stillman
fd8aa4433c Change connector server response to "Zotero is running"
People are confused by "Zotero Connector Server is Available". I'm not
sure if this will be any better, but not mentioning the connector might
help -- that seems to be part of the problem.

We could display a longer message with a link to
https://www.zotero.org/support/kb/connector_zotero_unavailable, but this
is the /ping page that gets hit constantly, so it seems like we should
keep it short (though probably it doesn't make a difference).
2022-10-07 01:31:59 -04:00
Martynas Bagdonas
29dd0cf5dc Don't allow to rotate or delete pages for read-only PDF 2022-10-06 18:00:32 +03:00
Martynas Bagdonas
28329aa785 Fix annotation image saving to cache for read-only annotations 2022-10-06 17:32:51 +03:00
Martynas Bagdonas
c5d418fc16 Fix annotation saving regression 2022-10-06 16:43:58 +03:00
Martynas Bagdonas
e9c6c76e79 Properly handle errors when trying to open too large (>2GB-1) PDF file 2022-10-06 10:57:04 +03:00
Martynas Bagdonas
f13966894f Update pdf-reader submodule 2022-10-06 09:37:01 +03:00
Martynas Bagdonas
b7cc1d549b Fix pref observer handler unregistering 2022-10-05 14:09:39 +03:00
Dan Stillman
9a3f8dbc94 Update locales from Transifex 2022-10-05 02:45:05 -04:00
Martynas Bagdonas
b594ad0b0a Allow importing annotations in group libraries 2022-10-04 14:22:54 +03:00
Martynas Bagdonas
4dd1851140 Don't allow pdf-reader to set annotation author name
Fixes the issue when your own created annotation becomes grayish and indicates that it was modified by another user.
2022-10-04 13:59:16 +03:00
Martynas Bagdonas
006dd20d5b Re-render annotation images when rotating a page
Fixes #2824
2022-10-03 17:03:32 +03:00
Adomas Venčkauskas
55afe7c08d Fix item list row text not read when using non-contiguous kb selection
Closes #2830
2022-10-03 15:20:38 +03:00
Dan Stillman
9fadfac7ee Add 15 and 16 as note font size options in menu
https://forums.zotero.org/discussion/100079/notebook-fonts
2022-10-03 06:43:28 -04:00
Martynas Bagdonas
38ae3a2600 Remove PDF view rotation options 2022-10-03 13:14:37 +03:00
Dan Stillman
473dd2ed64 Update pdf-reader submodule 2022-09-30 17:30:57 -04:00
Dan Stillman
e3b1cbd4eb Update version 2022-09-30 17:30:29 -04:00
Dan Stillman
3b93147052 Update locales from Transifex 2022-09-30 16:52:13 -04:00
Dan Stillman
b9a1e93518 Merge new English strings 2022-09-30 16:09:56 -04:00
Dan Stillman
99084084d4 Remove default OpenURL gateway
https://www.worldcat.org/registry/gateway is no longer supported -- see
https://forums.zotero.org/discussion/comment/418373/#Comment_418373 for
details

If no resolver is selected, show an error on "Library Lookup" with a
button to open the prefs.
2022-09-30 16:08:12 -04:00
Dan Stillman
64047f181a Remove stray "aaa" in "Split Horizontally" in PDF window
From 12b1897478
2022-09-30 15:33:00 -04:00
Dan Stillman
12b1897478 Swap "Split Vertically" and "Split Horizontally" in PDF reader 2022-09-30 04:22:48 -04:00
Dan Stillman
8fc9ebe4ab Update locales from Transifex and merge new English strings 2022-09-30 02:59:52 -04:00
Dan Stillman
5162845179 Update version 2022-09-30 02:52:48 -04:00
Martynas Bagdonas
2569ad6506 Update note-editor submodule 2022-09-30 09:45:41 +03:00
Martynas Bagdonas
4fdf43ed92 Prevent error when creating a note from annotations that have ink 2022-09-30 09:44:27 +03:00
Martynas Bagdonas
9691fcafc2 Import external annotations before trying to add a note from annotations 2022-09-30 09:40:35 +03:00
Martynas Bagdonas
6aad0cbb9c
Add split view for PDF reader (#2832) 2022-09-29 13:25:58 +03:00
Dan Stillman
a7e6fc330c Update locales from Transifex 2022-09-28 05:09:52 -04:00
Dan Stillman
677499865a Update translators and CSL locales 2022-09-28 04:50:28 -04:00
Dan Stillman
0e60389ee4 Update note-editor submodule 2022-09-28 04:28:56 -04:00
Dan Stillman
2271913e49 Add Number as items list column option
https://forums.zotero.org/discussion/99844/add-columns-to-desktop-app-report-number
2022-09-22 01:48:15 -04:00
Dan Stillman
ff5e09201d Update package-lock.json 2022-09-22 01:16:11 -04:00
Dan Stillman
0b89b15f66 Update locales from Transifex and merge new English strings 2022-09-22 01:14:26 -04:00
Dan Stillman
f6e11981e3 Update pdf-reader and pdf-worker submodules 2022-09-22 01:14:26 -04:00
Dan Stillman
230fc7f2df Merge pull request #2826 from tnajdek/999-keyboard-nav-creators
Make creator buttons accessible via keyboard
2022-09-21 23:18:54 -04:00
Dan Stillman
f179f0a54f Additional tweaks for creator button keyboard access (#2826)
- Open creator type menu on ArrowUp as well
- Fix outline on Windows/Linux
- Don't show focus ring on hover, since we already show a background
2022-09-21 23:16:53 -04:00
Tom Najdek
ea09aac616 Make creator buttons accessible via keyboard #999 2022-09-21 23:16:53 -04:00
Dan Stillman
4a49ffec0a Update ESLint config 2022-09-21 22:25:34 -04:00
Martynas Bagdonas
0d09618865 Fix focusContextPane error when pressing tab in standalone PDF window
Fixes #2823
2022-09-15 11:59:03 +03:00
Dan Stillman
b96fcd41b1 Update translate submodule 2022-09-15 01:06:47 -04:00
Martynas Bagdonas
c13d17b5e6 Fix PDF reader sometimes showing UTC time and sometimes local 2022-09-13 16:38:17 +03:00
Martynas Bagdonas
eb255c5868 Convert Mendeley colors to equivalent Zotero colors on PDF open
Fixes #2820
2022-09-13 14:35:16 +03:00
Tom Najdek
88217492f9
Map annotation colors colors on import #2819 (#2822) 2022-09-12 19:57:24 -04:00
Martynas Bagdonas
64897455f4 Adjust PDF reader and context pane UI based on font size pref
Fixes #2809
2022-09-09 18:03:20 +03:00
Abe Jellinek
5015b0e4cc Hide "Find Available PDF" for feed items (#2807) 2022-09-07 10:07:43 -04:00
Martynas Bagdonas
a2d378448a Add context menu options to copy/save image from pdf-reader/note-editor
Fixes #2664
2022-09-06 16:35:14 +03:00
Dan Stillman
32b64b3cf1 Merge new English strings 2022-09-05 15:47:06 +01:00
Dan Stillman
66fccec6b4 Add "Create Note from Annotations" option to items list context menu
To allow creation of a standalone note with annotations from all the
selected top-level items and/or attachments. Annotations will be sorted
by the order of the items in the items list.

"Add Note from Annotations" remains when a single regular item or one or
more attachments under a single regular item are selected.
2022-09-05 10:37:07 -04:00
Dan Stillman
894309d061 Add collectionID option to EditorInstance.createNoteFromAnnotations()
For adding a standalone note to the current collection
2022-09-05 10:37:07 -04:00
Dan Stillman
b24c7577ec Rename Zotero.Items::keepParents() to keepTopLevel()
So that it works for more than two levels of items

Also fix a bug where the parent item could be returned more than once if
multiple child items were selected.
2022-09-05 10:37:07 -04:00
Dan Stillman
ca4ced1e9f Add Zotero.Item::topLevelItem and Zotero.Items.getTopLevel(items) 2022-09-05 10:37:07 -04:00
Martynas Bagdonas
6646be66d0 EditorInstance.createNoteFromAnnotations(): Handle multiple attachments 2022-09-05 10:37:07 -04:00
Dan Stillman
ef0811bd4c Merge new English strings 2022-09-02 12:07:13 +02:00
Martynas Bagdonas
f893206dfd Allow inserting images over context menu
Fixes #2759
2022-09-01 11:49:30 +03:00
Martynas Bagdonas
ae5de2c2f5 Update pdf-reader submodule 2022-08-31 15:45:56 +03:00
Martynas Bagdonas
a3b45a74e9 Add missing noteEditor.mathBlock string 2022-08-31 14:08:42 +03:00
Dan Stillman
e474ca524b Update pdf-reader submodule 2022-08-30 11:38:44 +02:00
Dan Stillman
bc0f6203e1 Update note-editor submodule 2022-08-30 11:18:07 +02:00
Dan Stillman
91ae576074 Fix HTTP.request() 5xx handling with errorDelayMax=0
This was causing delay-less infinite loops for OPTIONS requests to Box,
which are returning 502 errors.
2022-08-29 23:44:55 +02:00
Abe Jellinek
c255104ada duplicateAndConvertSelectedItem: Don't copy abstracts (#2799) 2022-08-29 16:27:26 -04:00
Dan Stillman
75f5d6aca1 Merge new English strings 2022-08-25 17:34:41 -04:00
Martynas Bagdonas
d156bef42a Update note-editor submodule and strings for math and table insertion 2022-08-25 14:19:38 +03:00
Dan Stillman
79246196ac Use items-list sort order for report from collection or saved search
https://forums.zotero.org/discussion/99284/exporting-a-report-sorted-by-first-date

Regression from HTML tree
2022-08-25 00:47:57 -04:00
Adomas Venčkauskas
262e1964ae Fix SingleFile snapshot saving stalling in Firefox Connector
Update SingleFile submodule
2022-08-24 11:48:02 +03:00
Dan Stillman
f4dfea6849 Don't repeatedly upload relations
Fixes #2772
2022-08-22 01:12:31 -04:00
Dan Stillman
999f78b0f5 Update translators 2022-08-21 23:30:39 -04:00
Dan Stillman
2e6321a4bf Update version 2022-08-21 23:29:48 -04:00
Tom Najdek
35a1bb20cb Make citavi annotations importer more resilient 2022-08-19 11:20:24 -04:00
Tom Najdek
8c80ea431e Fix importing Citavi annotations in some cases 2022-08-19 11:20:24 -04:00
Dan Stillman
3abd720957 Update pdf-reader submodule 2022-08-19 02:50:22 -04:00
Dan Stillman
8379540719 Include proper test for b373291c02 for #2771 2022-08-19 02:47:24 -04:00
Dan Stillman
b373291c02 Fixed filed items with annotations appearing in Unfiled Items
Fixes #2771

Regression from 20c6fe67
2022-08-19 00:15:55 -04:00
Abe Jellinek
3391f3bc99
Empty Trash: Visually remove deleted items, refresh icon (#2606)
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.
2022-08-18 21:14:34 -04:00
Martynas Bagdonas
6ca854a018 Fix navigation to annotation when opening an unloaded PDF reader tab
Regression from 142e3b09
2022-08-18 10:21:37 +03:00
Martynas Bagdonas
fa32eb6790 Fix regression for broken "Snow in Page"
Fixes #2764
Regression from 7ec54fd
2022-08-18 10:21:37 +03:00
Abe Jellinek
96c4427d6c Scaffold: Non-async detectWeb and async scrape by default
We don't usually want an async detectWeb, since HTTP requests should only be
used there in very exceptional cases. We do usually want an async scrape (and we
were already - mistakenly - awaiting it).
2022-08-17 16:34:19 -04:00
Dan Stillman
5a8b5a1829 Update localized string for pdf-reader 2022-08-17 03:46:27 -04:00
Dan Stillman
f368a53311 Fix and disable item-tree test for emptying trash
20c6fe6737 caused this to start failing, but only because the test was
testing something too specific. The change in that commit caused more
rows to be left behind when emptying the trash (for reasons I should
probably look into), but the trash wasn't being emptied properly before,
which #2606 should fix. This test should be restored as part of that PR.
2022-08-17 01:53:21 -04:00
Dan Stillman
49020b02a2 Update pdf-reader submodule 2022-08-17 00:03:34 -04:00
Dan Stillman
20c6fe6737 Match parent attachments for annotation tags
Expose annotation tags in tag selector and match parent attachments when
filtering/searching

This also fixes searching for annotation text or comments when using
Everything quick search.

This is temporary until we display annotations in the items list
directly.
2022-08-16 22:56:39 -04:00
Dan Stillman
80351a496b Update version 2022-08-16 22:56:39 -04:00
Dan Stillman
2d2e787111 Update translators 2022-08-15 08:32:57 -04:00
Dan Stillman
eaba3ae099 Update locales from Transifex 2022-08-15 08:32:17 -04:00
Dan Stillman
4746300d33 Update pdf-reader submodule 2022-08-15 08:24:17 -04:00
Dan Stillman
85b0c9dbad Fix importing of standalone attachments without URLs
And allow `importSnapshotFromFile()` without `parentItemID` or
`libraryID`, which is more consistent with other attachment functions

Follow-up to 318e4852e9

https://forums.zotero.org/discussion/comment/414802/#Comment_414802
2022-08-14 05:06:27 -04:00
Dan Stillman
318e4852e9 Fix importing of standalone attachments
Maybe has been broken for years?

https://forums.zotero.org/discussion/99020/warning-data-loss-when-exporting-collections
2022-08-13 16:23:00 -04:00
Dan Stillman
3dc3359cff Don't relate book sections to each other when creating from book
Fixes #2757
2022-08-13 05:45:23 -04:00
Dan Stillman
b505c630d3 Fix copying of standalone attachments between libraries
Regression in f684984b07 (maybe a race condition)
2022-08-13 02:00:41 -04:00
Dan Stillman
21b7baee35 Update translators 2022-08-12 00:38:19 -04:00
Dan Stillman
df59cee94a Update global schema for locales 2022-08-12 00:34:21 -04:00
Dan Stillman
d1275f9964 Update locales from Transifex and merge new English strings 2022-08-12 00:33:08 -04:00
Abe Jellinek
d4a7de2e8d
Add Book -> Book Section and Book Section -> Book to context menu (#2755) 2022-08-11 16:40:27 -04:00
Adomas Venčkauskas
864fd03c87 Prevent singlefile config from overwriting the Zotero.SingleFile namespace 2022-08-11 13:44:48 +03:00
Adomas Venčkauskas
d3cb7c86d9 Update SingleFile config for current version
They got updated upstream and the backwards-compat code
does not run for the way we execute singlefile

See https://github.com/gildas-lormeau/SingleFile/issues/1021
Addresses https://github.com/zotero/zotero-connectors/issues/398
2022-08-11 13:43:39 +03:00
Martynas Bagdonas
e4e3c72036 Fix PDF reader selector popup that was never opening 2022-08-11 12:18:32 +03:00
Martynas Bagdonas
0857cefb0c Fix regression preventing opening PDF reader window when the tab is open 2022-08-11 12:18:32 +03:00
Dan Stillman
9b7b4fe134 Improve test for event-title substitution
And add comments

Follow-up to 20d0f103f
2022-08-10 01:34:57 -04:00
Dan Stillman
a9c3fc7790 Reset Zotero.locale to 'en-US' after cite test 2022-08-10 01:01:48 -04:00
Dan Stillman
20d0f103fd Automatically substitute event-title for event in styles
Stopgap until styles are updated in citation-style-language/styles#6151
2022-08-10 01:01:48 -04:00
Abe Jellinek
20eca3781d Add test for 87455d 2022-08-08 11:48:33 -04:00
Abe Jellinek
87455d28c3 Item box: Fix fieldMode resetting after editor hidden twice
hideEditor() called switchCreatorMode() too early, setting the fieldMode
attribute on the soon-to-be-discarded textbox instead of the label
replacing it. Then, showing the editor a second time would carry over an
empty fieldMode attribute from the label to the new textbox. Hiding that
editor would update the creator in the item to fieldMode = 0 and trigger
a save.

Moving the switchCreatorMode() call does the trick, and the flex
settings changes still work fine when made there.
2022-08-08 10:56:56 -04:00
Abe Jellinek
2048319541
Advanced Search: Make sure items are loaded (#2743) 2022-08-06 15:00:33 -04:00
Dan Stillman
d03b0a716d Don't preselect Act for multiple sources in classic citation dialog
"Multiple Sources…" wasn't properly handled by 629892b0b8

https://forums.zotero.org/discussion/98877/act-instead-of-page
https://twitter.com/drezvera99/status/1554521940684984320
2022-08-06 02:29:26 -04:00
Abe Jellinek
bb2b37a73a Item box: Prevent tabbing to hidden fields
This one's probably been around for a while! Reproduce by creating an
item with a fieldMode = 1 creator, tabbing past the creator, and then
shift-tabbing back to it. Your cursor will end up in the invisible first
name field and further shift-tabs can't move it past.

Cherry-picked from fx102: 080ada78ee
2022-08-05 16:31:09 -04:00
Martynas Bagdonas
7ec54fdaa9 Select existing unloaded tab instead of opening a new one
Fixes #2740
2022-08-05 18:22:30 +03:00
Martynas Bagdonas
5a659b070d Allow to scroll tabs with mouse wheel
Fixes #2724
2022-07-29 13:06:33 +03:00
Abe Jellinek
ad96323881
Duplicates Merge: Preserve embedded annotations (#2728) 2022-07-29 05:06:44 -04:00
Adomas Venčkauskas
fc0f6157d0 Do not load-timeout on browser PDF download during content load
Closes #2726
2022-07-26 13:49:23 +03:00
Martynas Bagdonas
6733f2edca Freeze note schema version to 8
We can do it because it was only used to create a note from annotations.

No need to update schema version in Zotero client, unless using new
features when creating a note from annotations.
2022-07-25 18:04:00 +03:00
Martynas Bagdonas
e1997620a4 Fix regression that prevents showing the last selected tab on startup
Fixes #2723
2022-07-25 11:51:05 +03:00
Dan Stillman
a396771cbc Update version 2022-07-23 03:56:26 -04:00
Dan Stillman
0e0997078b Update global schema 2022-07-23 03:26:40 -04:00
Martynas Bagdonas
0785b3a086 Fix regression that prevents showing new notes in contextPane notes list 2022-07-21 13:27:32 +03:00
Adomas Venčkauskas
ba5cb7d5c6 Fixes some assets not being saved with SingleFile snapshots
Part of zotero/zotero-connectors#394

When saving an item (e.g. on arxiv) with a site translator, the
translator may add a custom snapshot attachment URL which does not match
the translation URL (the URL in the browser where translation was
initiated) in which case Zotero handles snapshot saving. Zotero loads
the page in a background browser. By default images are not loaded in
this browser. SingleFile due to CORS restrictions has to refetch all
resources that are not from the same domain or subdomain, but uses
pre-loaded resources (images) from the same domain. Before this change
any images that were from the same domain as the saved website were not
saved in the snapshot. This commit fixes that.
2022-07-20 14:28:56 +03:00
Adomas Venčkauskas
d822da4c06 Changes for updated SingleFile submodule 2022-07-20 14:28:56 +03:00
Adomas Venčkauskas
a297692487 Update SingleFile submodule 2022-07-20 14:28:56 +03:00
Dan Stillman
6921268dc0 Localize "Export PDF…" menu option
Fixes #2710
2022-07-20 01:04:00 -04:00
Dan Stillman
ba1906c054 Update version 2022-07-18 23:10:31 -04:00
Dan Stillman
b5be7cbd57 Update note-editor submodule 2022-07-18 19:47:23 -04:00
Dan Stillman
5c61815f36 Update translate submodule 2022-07-18 19:43:18 -04:00
Dan Stillman
b5ca5aed3a Update submodules 2022-07-18 19:14:24 -04:00
Dan Stillman
4b6598acc4 Update locales from Transifex 2022-07-18 08:57:41 -04:00
Dan Stillman
14a6ce000e Disable sort tests from 26a5e2900a for now
Until I can figure out the race-condition breakage they trigger later in
the file
2022-07-18 08:56:57 -04:00
Dan Stillman
9d0cd005f8 Increase Mendeley API timeout to 60 seconds
To see if we can fix the 30-second timeout here:

https://forums.zotero.org/discussion/comment/412750/#Comment_412750
2022-07-18 08:29:45 -04:00
Dan Stillman
26db75971e Update pdf-reader submodule 2022-07-18 03:51:09 -04:00
Dan Stillman
cc45e9019b Apply Big Sur styling when macOS version comes through correctly
Follow-up to zotero/zotero-standalone-build@f7a506a32 -- now that some
people are somehow getting an accurate macOS version number, we need to
apply the Big Sur CSS to all higher versions.

https://forums.zotero.org/discussion/98406/zotero-will-not-open-mac-os-monterey
2022-07-17 05:09:05 -04:00
Dan Stillman
98d6f40e4a Add warning in toolbar for 'dev' channel builds 2022-07-16 04:19:42 -04:00
Martynas Bagdonas
2cad5b00d1 Use Quick Copy (Markdown) for direct plain-text copying from note-editor
Fixes #2505
2022-07-15 15:06:19 +03:00
Abe Jellinek
95239db0fd Prevent deleting My Publications
Previously showed a blank confirmation dialog and logged an error.
2022-07-13 16:23:50 -04:00
Martynas Bagdonas
4c063e7b6e Prevent delayed context pane expansion and pdf-reader rerender/resize when switching from library tab 2022-07-12 18:00:21 +03:00
Martynas Bagdonas
07f1517712 Don't include quotes when {{highlight}} is placed in a blockquote
Fixes #2697
2022-07-12 12:12:21 +03:00
Martynas Bagdonas
f0e0971c69 Include quotation marks for highlights by default, but allow to disable
Fixes #2697
2022-07-11 15:32:02 +03:00
Dan Stillman
acba90f272 Fix test failure after 26a5e2900a 2022-07-11 05:19:18 -04:00
Dan Stillman
26a5e2900a Better fix for errors from invalid sort fields
Just catch the error from `ItemTree::sort()` and clear the
secondary-sort and fallback-sort prefs so that sorting works on the next
attempt.

Replacement for a8ed30ce80

https://groups.google.com/g/zotero-dev/c/kc0-C6-SA74/m/bhHniGceAQAJ
2022-07-11 01:30:38 -04:00
Dan Stillman
34d66381d1 Revert "Validate fields in ItemTree::getSortFields()"
This reverts commit a8ed30ce80 and related
commits.

We'll address breakage from invalid sort fields another way, without
inconveniencing plugin authors.

https://groups.google.com/g/zotero-dev/c/kc0-C6-SA74/m/bhHniGceAQAJ
2022-07-11 00:43:15 -04:00
Dan Stillman
f7dbcdde4b Sort empty rows last for Date/Year columns when descending
Date columns start in descending order, so put empty rows at end

Also remove a seemingly unnecessary exception for Title when not the
primary sort and just always sort it with empty rows first
2022-07-10 23:47:02 -04:00
Dan Stillman
c4563af381 Fix sorting of attachments and notes columns
Regression from a8ed30ce80
2022-07-10 23:26:36 -04:00
Dan Stillman
56e7f0a10f Fix sorting by Item Type and feed item itemID
Regression from a8ed30ce80
2022-07-10 17:31:01 -04:00
Dan Stillman
3a160b2630 Increase downloadPDFViaBrowser.onLoadTimeout to 3000
Apparently necessary for some people to get PDFs on ScienceDirect. We
should come up with a better way of detecting the page load if we can,
so that we can avoid this long of a timeout for people who just will
never have access to ScienceDirect PDFs.

https://forums.zotero.org/discussion/comment/411056/#Comment_411056

Addresses #2698
2022-07-10 06:40:32 -04:00
Dan Stillman
124c18d54d CookieSandbox: Fix error setting request cookies
Regression from 6af3b0276a, showing up as "trackedBy is undefined" error

https://forums.zotero.org/discussion/98322/no-snapshot-or-pdf-autosaved-using-zotero-connector-on-safari
2022-07-10 06:28:56 -04:00
Dan Stillman
57c582e171 Merge new English strings 2022-07-09 18:08:38 -04:00
Dan Stillman
cf8819cc06 Add localized string for timestamp CSL locator
Not included in the CSL locales:

https://forums.zotero.org/discussion/comment/412060/#Comment_412060
2022-07-09 18:07:07 -04:00
Dan Stillman
fe74b02d28 Merge new English strings 2022-07-09 01:56:56 -04:00
Dan Stillman
1dcc2728dd Tweak confirmation message for recursive collection-item removal
I guess the "its" is technically a little clearer, but it's a bit
awkward, and I think it's reasonably implied.
2022-07-09 01:54:07 -04:00
Dan Stillman
c5dbf0169f
Merge pull request #2693 from tnajdek/mendeley-import-improve-feedback
Multiple improvements to Mendeley importer
2022-07-09 01:43:50 -04:00
Abe Jellinek
3f282fc25f
recursiveCollections: Remove from all subcollections on delete (#2689) 2022-07-09 01:37:14 -04:00
Dan Stillman
7163a196f8 Update pdf-reader and pdf-worker submodules 2022-07-09 01:32:45 -04:00
Dan Stillman
8c0499468e Update locales from Transifex 2022-07-09 01:28:18 -04:00
Dan Stillman
629892b0b8 Add CSL 1.0.2 locators and switch to CSL locales for localization
And move Page to middle of list, while still selecting by default

https://forums.zotero.org/discussion/comment/396602/#Comment_396602
2022-07-09 01:00:07 -04:00
Dan Stillman
2ff67c987e Fix test failure after a8ed30ce8
We added a `toggleSort()` call on the title, so then when called again
it reverses the direction. To avoid, sort by creator and then title in
the second test.
2022-07-08 06:44:10 -04:00
Dan Stillman
a8ed30ce80 Validate fields in ItemTree::getSortFields()
To avoid startup hang if a plugin does something bad:

https://forums.zotero.org/discussion/comment/411843/#Comment_411843

Fixes #2692
2022-07-08 06:01:23 -04:00
Tom Najdek
43f15e7517
Improve log output for multiple Mendeley imports
Helps with debugging cases where new import is started while previous
one is still being cancelled
2022-07-07 13:40:40 +02:00
Tom Najdek
df11227e0a
Lint code related to Mendeley importer 2022-07-07 13:40:40 +02:00
Tom Najdek
d418d79e70
Fix a bug in regex extracting fields to "extra"
Because regex is built using a template string, \s* is actually escaped
into s*, i.e. literal "s" appearing 0 or more times. In most cases this
would mean that output can have spacing slightly off. In extreme case,
when identifier starts with letter "s", this could this could lead to
identifier being stored incorrectly.

Also adjusted tests to be more strict and mock data to cover this case.
2022-07-07 13:40:40 +02:00
Tom Najdek
42aebccbf4
Mendeley import: detect win close, better feedback
* Detect wizard cancel/close and interrupt import. This will still
  have to wait for current fetch (file or metadata) to complete but will
  then advance to the cleanup stage
* Advance progress bar during metadata fetch
* Add some extra logging
2022-07-07 13:40:33 +02:00
Tom Najdek
7b6e19435b
Don't try to remove temporary dir after import
Previously we had a structure that also created folders in temp dir,
that has since changed but code in cleanup phase to remove containing
dir has been accidentally left unremoved.
2022-07-07 10:09:53 +02:00
Martynas Bagdonas
f650f95081 Fix disabled "Add to Note" when a child note is open
Fixes #2687
2022-07-06 11:33:22 +03:00
Dan Stillman
0357026865 Update global schema and utilities for CSL 1.0.2 2022-07-05 01:34:10 -04:00
Martynas Bagdonas
92d89becc7 Adjust page rotation labels in PDF reader
Fixes #2669
2022-07-01 16:33:08 +03:00
Martynas Bagdonas
49f9642c0d Show parent item title for child notes in context pane
Fixes #2681
2022-07-01 15:32:14 +03:00
Dan Stillman
94372111f5 Update pdf-reader submodule 2022-07-01 04:32:42 -04:00
Martynas Bagdonas
f0b896d282
Implement smart notes switching in contextPane (#2671)
Fixes #2650
2022-07-01 03:28:20 -04:00
Dan Stillman
ec68216310 Update version 2022-06-24 01:05:08 -04:00
Dan Stillman
b215d93ac9 Update utilities submodule and date-parsing tests
Includes strToDate() fixes
2022-06-22 04:41:33 -04:00
Dan Stillman
bf1480f8f0 Update locales from Transifex 2022-06-21 23:05:20 -04:00
Dan Stillman
6f089445a5 Update translators 2022-06-21 22:17:44 -04:00
Martynas Bagdonas
14e62ee34f Update pdf-reader and note-editor, use specific prebuilds, tweak code 2022-06-17 16:47:53 +03:00
Adomas Venčkauskas
43c86c0e58 Limit the force-click during drag fix to macOS (4fb0661b) 2022-06-15 09:08:30 +03:00
Adomas Venčkauskas
6f3f5d2ea8 Fix alt-arrow not working on macOS when editing collection names.
Fixes #2651
2022-06-14 14:27:34 +03:00
Adomas Venčkauskas
4fb0661b50 Fix force-click during drag sometimes causing collections to switch
Closes #2652
2022-06-14 14:18:36 +03:00
Abe Jellinek
57ab92b5b9 Fix error if New Collection prompt is canceled 2022-06-09 18:32:14 -05:00
Dan Stillman
7b20972c27 Use String.fromCharCode() to load Chinese characters in strToDate() test
Follow-up to dc045250a6
2022-06-09 00:03:34 -04:00
Dan Stillman
dc045250a6 Fix parsing of Chinese and other non-ASCII month names
https://forums.zotero.org/discussion/97683/bug-locale-issues-concerning-zotero-and-zotero-connector
2022-06-08 23:45:18 -04:00
Dan Stillman
256bd157ed Properly strip ZWJ in isOnlyEmoji() 2022-06-07 20:36:48 -04:00
Dan Stillman
2b45b7fea3 Fix Emoji detection
And move to Zotero.Utilities.Internal.isOnlyEmoji()

Fixes #2643
2022-06-07 20:12:29 -04:00
Dan Stillman
1d5fd1308a Update styles submodule 2022-06-07 03:36:10 -04:00
Abe Jellinek
d8069fac50
Fix sorting by attachment (#2586) 2022-06-07 01:32:27 -04:00
Dan Stillman
18516a33d9 Update locales from Transifex and merge new English strings 2022-06-07 01:25:57 -04:00
Dan Stillman
b658f09dd8 citeproc-js: Add space after left margin in plain-text mode
This adds a space after the the number in some numeric styles ("[1]" in
IEEE style, "1." in Nature) when pasting bibliography entries into a
plain-text editor.

Some styles (ACS, AMA, Vancouver) will need to be updated to remove
their hard-coded spaces to avoid double spaces after this change.

Patch from @zepinglee

Fixes #2633
2022-06-07 00:57:01 -04:00
Martynas Bagdonas
5799bdd554
Add support for PDF page deletion and rotation (#2595)
* Add support for PDF page deletion and rotation
Fixes #2561
2022-06-07 00:53:37 -04:00
Adomas Venčkauskas
62c7b4db2b Log full hidden browser count after its creation 2022-06-06 12:50:21 +03:00
Adomas Venčkauskas
caa0b05e5f Fix a MIMETypeHandler.removeHandler() bug 2022-06-06 12:49:58 +03:00
Adomas Venčkauskas
5095543bef Improve downloadPDFViaBrowser handlers and logging 2022-06-06 12:49:28 +03:00
Adomas Venčkauskas
6af3b0276a Fix cookieSandbox not overriding user agent if no cookies for domain.
Potential fix for #2635
2022-06-06 12:48:29 +03:00
Martynas Bagdonas
67700f8a2c Fix library tab tooltip always staying on top 2022-05-31 15:37:30 +03:00
Dan Stillman
d12f3eda61 Fix detection of some emoji set as colored tags
Emoji such as ⭐️ that began as text-based Unicode characters and depend
on Variation Selector-16 to display the emoji presentation weren't being
properly detected.
2022-05-26 06:53:36 -04:00
Dan Stillman
deabf3f042 Fix support tests after 28ed3e34b 2022-05-24 04:32:05 -04:00
Dan Stillman
28ed3e34b5 Update global schema
Includes the Preprint archiveID/number mapping for #2481
2022-05-24 03:43:36 -04:00
Dan Stillman
354183c25d Update locales from Transifex 2022-05-24 03:43:36 -04:00
Dan Stillman
b0f69ef7e3 Update note-editor submodule 2022-05-23 16:19:20 -04:00
Dan Stillman
ebc23d73b0 Add prefs for timeouts for downloading PDF via browser
1 second isn't enough for some users, so try 1.5, and allow for easier
debugging via a pref.

https://forums.zotero.org/discussion/96431/i-have-access-to-pdf-but-zotero-cannot-automatically-add-it
https://forums.zotero.org/discussion/97277/unable-to-download-papers-using-zotero-chrome-extension
2022-05-20 05:01:57 -04:00
Dan Stillman
419f74379b Feed import: Don't fail on OPML entry with no title or text
https://forums.zotero.org/discussion/96841/impossible-dimporter-ompl-rss
2022-05-20 00:33:25 -04:00
Dan Stillman
603b843f71 Update version 2022-05-17 23:54:20 -04:00
Dan Stillman
1e3959020d Update note-editor submodule 2022-05-17 04:57:06 -04:00
Martynas Bagdonas
3fd0abf6b0 Avoid error interrupting annotation extraction from multiple items 2022-05-16 13:42:44 +03:00
Dan Stillman
111a254696 Update locales from Transifex 2022-05-16 00:56:22 -04:00
Dan Stillman
69616500d8 Update translators, styles, and CSL locales 2022-05-16 00:56:13 -04:00
Dan Stillman
693f9a7e1b Try to open external PDF reader to page number for "Show on Page"
PDF-handling code from `ZoteroPane.viewAttachment()` predates
`Zotero.OpenPDF` and didn't handle page numbers for external PDF
readers, so when called from "Show on Page" in a note (via
`ZoteroPane.viewPDF()`) it just launched the file itself in the
specified handler or via the OS.

For now, change to use `Zotero.OpenPDF.openToPage()` when there's a page
number. Later we should use `Zotero.OpenPDF` for everything, but that
code is more complicated than just using `launchFile()`, so we'll need
to do some testing (particularly on Linux) to see what's best.

(Note that "Show on Page" with an external reader doesn't make a ton of
sense, since you can't actually see the annotation, but opening the
right page is better than nothing.)

Fixes #2612
2022-05-15 04:28:30 -04:00
Abe Jellinek
0c0afb129b Scaffold templates: Pull URL from doc
Link URLs could lead to redirects.
2022-05-12 14:49:30 -07:00
Abe Jellinek
a11c5987ad Fix Scaffold load error when fontSize pref is set 2022-05-10 09:10:13 -07:00
420 changed files with 19110 additions and 7711 deletions

View file

@ -5,14 +5,14 @@ jobs:
name: Build, Upload, Test name: Build, Upload, Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Install Node - name: Install Node
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: 14 node-version: 18
#cache: npm #cache: npm
# On GitHub # On GitHub
@ -27,7 +27,7 @@ jobs:
- name: Cache Firefox - name: Cache Firefox
id: firefox-cache id: firefox-cache
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: firefox-x86_64 path: firefox-x86_64
key: firefox-60.9.0esr key: firefox-60.9.0esr
@ -42,7 +42,7 @@ jobs:
- name: Cache Node modules - name: Cache Node modules
id: node-cache id: node-cache
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: node_modules path: node_modules
key: node-modules-${{ hashFiles('package-lock.json') }} key: node-modules-${{ hashFiles('package-lock.json') }}
@ -53,13 +53,16 @@ jobs:
- name: Build Zotero - name: Build Zotero
run: npm run build run: npm run build
# Currently necessary for pdf-worker Webpack: https://stackoverflow.com/a/69746937
env:
NODE_OPTIONS: --openssl-legacy-provider
- name: Upload deployment ZIP - name: Upload deployment ZIP
if: | if: |
env.ACT != 'true' env.ACT != 'true'
&& github.repository == 'zotero/zotero' && github.repository == 'zotero/zotero'
&& github.event_name == 'push' && github.event_name == 'push'
&& (github.ref == 'refs/heads/master' || endsWith(github.ref, '-hotfix') || github.ref == 'refs/heads/gh-actions-ci-test') && (github.ref == 'refs/heads/6.0' || endsWith(github.ref, '-hotfix') || github.ref == 'refs/heads/gh-actions-ci-test')
env: env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@ -76,7 +79,7 @@ jobs:
- name: Cache utilities Node modules - name: Cache utilities Node modules
id: utilities-node-cache id: utilities-node-cache
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: chrome/content/zotero/xpcom/utilities/node_modules path: chrome/content/zotero/xpcom/utilities/node_modules
key: utilities-node-modules-${{ hashFiles('chrome/content/zotero/xpcom/utilities/package-lock.json') }} key: utilities-node-modules-${{ hashFiles('chrome/content/zotero/xpcom/utilities/package-lock.json') }}

View file

@ -2,7 +2,7 @@
## Bug Reports and Feature Requests ## Bug Reports and Feature Requests
Zotero does not use GitHub Issues for bug reports or feature requests. Please post all such requests to the [Zotero Forums](https://forums.zotero.org), where Zotero developers and many others can help. See [How Zotero Support Works](https://www.zotero.org/support/zotero_support) for more information. Zotero does not use GitHub Issues for bug reports, feature requests, or support questions. Please post all such requests to the [Zotero Forums](https://forums.zotero.org), where Zotero developers and many others can help. See [How Zotero Support Works](https://www.zotero.org/support/zotero_support) for more information.
For confirmed bugs or agreed-upon changes, Zotero developers will create new issues in the relevant repositories. For confirmed bugs or agreed-upon changes, Zotero developers will create new issues in the relevant repositories.

View file

@ -7,8 +7,9 @@ content zotero-platform chrome/content/zotero-platform/unix/ os=FreeBSD
content zotero-platform chrome/content/zotero-platform/unix/ os=OpenBSD content zotero-platform chrome/content/zotero-platform/unix/ os=OpenBSD
content zotero-platform-version chrome/content/zotero-platform/default-version/ content zotero-platform-version chrome/content/zotero-platform/default-version/
# Big Sur shows up as 10.16 when linked against 10.11 # Big Sur and later show up as 10.16 when linked against 10.11
content zotero-platform-version chrome/content/zotero-platform/mac-big-sur/ os=Darwin osversion=10.16 # (Usually: https://forums.zotero.org/discussion/98406/zotero-will-not-open-mac-os-monterey)
content zotero-platform-version chrome/content/zotero-platform/mac-big-sur/ os=Darwin osversion>=10.16
#content zotero-platform-version chrome/content/zotero-platform/mac-big-sur/ os=Darwin #content zotero-platform-version chrome/content/zotero-platform/mac-big-sur/ os=Darwin
resource zotero resource/ resource zotero resource/

View file

@ -57,6 +57,7 @@ var Scaffold = new function () {
var _translatorsLoadedPromise; var _translatorsLoadedPromise;
var _translatorProvider = null; var _translatorProvider = null;
var _lastModifiedTime = 0; var _lastModifiedTime = 0;
var _needRebuildTranslatorSuggestions = true;
var _editors = {}; var _editors = {};
@ -98,16 +99,7 @@ var Scaffold = new function () {
); );
} }
}); });
// Set font size from general pref
Zotero.setFontSize(document.getElementById('scaffold-pane'));
// Set font size of code editor
var size = Zotero.Prefs.get("scaffold.fontSize");
if (size) {
this.setFontSize(size);
}
this.generateTranslatorID(); this.generateTranslatorID();
// Add List fields help menu entries for all other item types // Add List fields help menu entries for all other item types
@ -146,6 +138,15 @@ var Scaffold = new function () {
this.initCodeEditor(); this.initCodeEditor();
this.initTestsEditor(); this.initTestsEditor();
// Set font size from general pref
Zotero.setFontSize(document.getElementById('scaffold-pane'));
// Set font size of code editor
var size = Zotero.Prefs.get("scaffold.fontSize");
if (size) {
this.setFontSize(size);
}
// Listen for Scaffold coming to the foreground and reload translators // Listen for Scaffold coming to the foreground and reload translators
window.addEventListener('activate', () => this.reloadTranslators()); window.addEventListener('activate', () => this.reloadTranslators());
@ -156,6 +157,7 @@ var Scaffold = new function () {
onLoadComplete: () => { onLoadComplete: () => {
document.getElementById('cmd_load').removeAttribute('disabled'); document.getElementById('cmd_load').removeAttribute('disabled');
_needRebuildTranslatorSuggestions = true;
} }
}); });
@ -267,6 +269,7 @@ var Scaffold = new function () {
monaco.languages.registerCodeLensProvider('javascript', this.createRunCodeLensProvider(monaco, editor)); monaco.languages.registerCodeLensProvider('javascript', this.createRunCodeLensProvider(monaco, editor));
monaco.languages.registerHoverProvider('javascript', this.createHoverProvider(monaco, editor)); monaco.languages.registerHoverProvider('javascript', this.createHoverProvider(monaco, editor));
monaco.languages.registerCompletionItemProvider('javascript', this.createCompletionProvider(monaco, editor));
let tsLib = await Zotero.File.getContentsAsync( let tsLib = await Zotero.File.getContentsAsync(
OS.Path.join(Scaffold_Translators.getDirectory(), 'index.d.ts')); OS.Path.join(Scaffold_Translators.getDirectory(), 'index.d.ts'));
@ -500,6 +503,43 @@ var Scaffold = new function () {
resolveCodeLens: (_model, codeLens, _token) => codeLens resolveCodeLens: (_model, codeLens, _token) => codeLens
}; };
}; };
this.createCompletionProvider = function (monaco, editor) {
let suggestions = null;
return {
provideCompletionItems(model, position) {
let prefixText = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
});
if (/setTranslator\([^)]*$/.test(prefixText)) {
let word = model.getWordUntilPosition(position);
let range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
};
if (!suggestions || _needRebuildTranslatorSuggestions) {
// Cache the suggestions minus the range field
suggestions = [...Scaffold_Translators._translators.entries()].map(([id, meta]) => {
return {
label: `${meta.translator.label}: '${id}'`,
kind: monaco.languages.CompletionItemKind.Constant,
insertText: `'${id}'`
};
});
_needRebuildTranslatorSuggestions = false;
}
// Add the range to each suggestion before returning
return { suggestions: suggestions.map(s => ({ ...s, range })) };
}
return { suggestions: [] };
}
};
};
this.updateModelMarkers = function (translatorPath) { this.updateModelMarkers = function (translatorPath) {
runESLint(translatorPath) runESLint(translatorPath)
@ -802,7 +842,8 @@ var Scaffold = new function () {
var fieldList = Zotero.ItemFields.getItemTypeFields(typeID); var fieldList = Zotero.ItemFields.getItemTypeFields(typeID);
for (let field of fieldList) { for (let field of fieldList) {
var key = Zotero.ItemFields.getName(field); var key = Zotero.ItemFields.getName(field);
outputObject[key] = ""; let fieldLocalizedName = Zotero.ItemFields.getLocalizedString(field);
outputObject[key] = fieldLocalizedName;
} }
var creatorList = Zotero.CreatorTypes.getTypesForItemType(typeID); var creatorList = Zotero.CreatorTypes.getTypesForItemType(typeID);
var creators = []; var creators = [];
@ -1097,13 +1138,19 @@ var Scaffold = new function () {
string = fix2028(Zotero.Utilities.varDump(string)); string = fix2028(Zotero.Utilities.varDump(string));
} }
if (output.value) output.value += "\n"; // Put off actually building the log message and appending it to the console until the next animation frame
output.value += Zotero.Utilities.lpad(date.getHours(), '0', 2) // so as not to slow down translation with repeated layout recalculations triggered by appending text
// and accessing scrollHeight
// requestAnimationFrame() callbacks are guaranteed to be called in the order they were set
requestAnimationFrame(() => {
if (output.value) output.value += "\n";
output.value += Zotero.Utilities.lpad(date.getHours(), '0', 2)
+ ":" + Zotero.Utilities.lpad(date.getMinutes(), '0', 2) + ":" + Zotero.Utilities.lpad(date.getMinutes(), '0', 2)
+ ":" + Zotero.Utilities.lpad(date.getSeconds(), '0', 2) + ":" + Zotero.Utilities.lpad(date.getSeconds(), '0', 2)
+ " " + string.replace(/\n/g, "\n "); + " " + string.replace(/\n/g, "\n ");
// move to end // move to end
output.inputField.scrollTop = output.inputField.scrollHeight; output.inputField.scrollTop = output.inputField.scrollHeight;
});
} }
/* /*
@ -1258,6 +1305,12 @@ var Scaffold = new function () {
function _writeTestsToPane(tests) { function _writeTestsToPane(tests) {
_writeToEditor(_editors.tests, _stringifyTests(tests)); _writeToEditor(_editors.tests, _stringifyTests(tests));
} }
function _confirmCreateExpectedFailTest() {
return Services.prompt.confirm(null,
'Detection Failed',
'Add test ensuring that detection always fails on this page?');
}
/** /**
* Mimics most of the behavior of Zotero.Item#fromJSON. Most importantly, * Mimics most of the behavior of Zotero.Item#fromJSON. Most importantly,
@ -1475,7 +1528,7 @@ var Scaffold = new function () {
if (level < 2 && value.items) { if (level < 2 && value.items) {
// Test object. Arrange properties in set order // Test object. Arrange properties in set order
let order = ['type', 'url', 'input', 'defer', 'items']; let order = ['type', 'url', 'input', 'defer', 'detectedItemType', 'items'];
for (let i = 0; i < order.length; i++) { for (let i = 0; i < order.length; i++) {
let val = processRow(order[i], value[order[i]]); let val = processRow(order[i], value[order[i]]);
if (val === undefined) continue; if (val === undefined) continue;
@ -1582,14 +1635,16 @@ var Scaffold = new function () {
_translatorProvider _translatorProvider
); );
return new Promise( return new Promise(
(resolve, reject) => tester.newTest(input, function (obj, newTest) { // "done" handler for do (resolve, reject) => tester.newTest(input,
if (newTest) { (obj, newTest) => { // "done" handler for do
resolve(_sanitizeItemsInTest(newTest)); if (newTest) {
} resolve(_sanitizeItemsInTest(newTest));
else { }
reject(new Error('Creation failed')); else {
} reject(new Error('Creation failed'));
}) }
},
_confirmCreateExpectedFailTest)
); );
} }
else if (type == "import" || type == "search") { else if (type == "import" || type == "search") {
@ -1729,6 +1784,9 @@ var Scaffold = new function () {
var url = item.getElementsByTagName("listcell")[0].getAttribute("label"); var url = item.getElementsByTagName("listcell")[0].getAttribute("label");
var test = JSON.parse(item.getUserData("test-string")); var test = JSON.parse(item.getUserData("test-string"));
var urlOrData = (test.input !== undefined) ? test.input : url; var urlOrData = (test.input !== undefined) ? test.input : url;
if (typeof urlOrData !== 'string') {
urlOrData = JSON.stringify(urlOrData, null, '\t');
}
Zotero.Utilities.Internal.copyTextToClipboard(urlOrData); Zotero.Utilities.Internal.copyTextToClipboard(urlOrData);
}; };
@ -1903,13 +1961,11 @@ var Scaffold = new function () {
var test = this.testsToUpdate.shift(); var test = this.testsToUpdate.shift();
_logOutput("Updating test " + (this.numTestsTotal - this.testsToUpdate.length)); _logOutput("Updating test " + (this.numTestsTotal - this.testsToUpdate.length));
var me = this;
if (test.type == 'web') { if (test.type == 'web') {
_logOutput("Loading web page from " + test.url); _logOutput("Loading web page from " + test.url);
var hiddenBrowser = Zotero.HTTP.loadDocuments( var hiddenBrowser = Zotero.HTTP.loadDocuments(
test.url, test.url,
function (doc) { (doc) => {
_logOutput("Page loaded"); _logOutput("Page loaded");
if (test.defer) { if (test.defer) {
_logOutput("Waiting " + (Zotero_TranslatorTester.DEFER_DELAY / 1000) _logOutput("Waiting " + (Zotero_TranslatorTester.DEFER_DELAY / 1000)
@ -1917,32 +1973,34 @@ var Scaffold = new function () {
); );
} }
Zotero.setTimeout( Zotero.setTimeout(
function () { () => {
doc = hiddenBrowser.contentDocument; doc = hiddenBrowser.contentDocument;
if (doc.location.href != test.url) { if (doc.location.href != test.url) {
_logOutput("Page URL differs from test. Will be updated. " + doc.location.href); _logOutput("Page URL differs from test. Will be updated. " + doc.location.href);
} }
me.tester.newTest(doc, function (obj, newTest) { this.tester.newTest(doc,
Zotero.Browser.deleteHiddenBrowser(hiddenBrowser); (obj, newTest) => {
if (test.defer) { Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
newTest.defer = true; if (test.defer) {
} newTest.defer = true;
newTest = _sanitizeItemsInTest(newTest); }
me.newTests.push(newTest); newTest = _sanitizeItemsInTest(newTest);
me.testDoneCallback(newTest); this.newTests.push(newTest);
me._updateTests(); this.testDoneCallback(newTest);
}); this._updateTests();
},
_confirmCreateExpectedFailTest);
}, },
test.defer ? Zotero_TranslatorTester.DEFER_DELAY : 0, test.defer ? Zotero_TranslatorTester.DEFER_DELAY : 0,
true true
); );
}, },
null, null,
function (e) { (e) => {
Zotero.logError(e); Zotero.logError(e);
me.newTests.push(false); this.newTests.push(false);
me.testDoneCallback(false); this.testDoneCallback(false);
me._updateTests(); this._updateTests();
}, },
true true
); );
@ -1960,15 +2018,15 @@ var Scaffold = new function () {
// Re-runs the test. // Re-runs the test.
// TranslatorTester doesn't handle these correctly, so we do it manually // TranslatorTester doesn't handle these correctly, so we do it manually
_run(methods[test.type], test.input, null, function (obj, item) { _run(methods[test.type], test.input, null, (obj, item) => {
if (item) { if (item) {
test.items.push(Zotero_TranslatorTester._sanitizeItem(item)); test.items.push(Zotero_TranslatorTester._sanitizeItem(item));
} }
}, null, function () { }, null, () => {
if (!test.items.length) test = false; if (!test.items.length) test = false;
me.newTests.push(test); this.newTests.push(test);
me.testDoneCallback(test); this.testDoneCallback(test);
me._updateTests(); this._updateTests();
}); });
} }
}; };

View file

@ -22,7 +22,7 @@
*/ */
async function detectWeb(doc, url) { function detectWeb(doc, url) {
// TODO: adjust the logic here // TODO: adjust the logic here
if ($$CURSOR$$url.includes('/article/')) { if ($$CURSOR$$url.includes('/article/')) {
return 'newspaperArticle'; return 'newspaperArticle';
@ -52,16 +52,19 @@ function getSearchResults(doc, checkOnly) {
} }
async function doWeb(doc, url) { async function doWeb(doc, url) {
if (await detectWeb(doc, url) == 'multiple') { if (detectWeb(doc, url) == 'multiple') {
let items = await Zotero.selectItems(getSearchResults(doc, false)); let items = await Zotero.selectItems(getSearchResults(doc, false));
if (items) { if (!items) return;
await Promise.all( for (let url of Object.keys(items)) {
Object.keys(items) await scrape(await requestDocument(url));
.map(url => requestDocument(url).then(doc => scrape(doc, url)))
);
} }
} }
else { else {
await scrape(doc, url); await scrape(doc, url);
} }
} }
async function scrape(doc, url = doc.location.href) {
// TODO: implement or add a scrape function template
}

View file

@ -1,4 +1,4 @@
async function scrape(doc, url) { async function scrape(doc, url = doc.location.href) {
// TODO adjust the url building // TODO adjust the url building
let m = url.match(/FId=([\w\d]+)&/); let m = url.match(/FId=([\w\d]+)&/);
if (m) { if (m) {

View file

@ -1,4 +1,4 @@
async function scrape(doc, url) { async function scrape(doc, url = doc.location.href) {
let translator = Zotero.loadTranslator('web'); let translator = Zotero.loadTranslator('web');
// Embedded Metadata // Embedded Metadata
translator.setTranslator('951c027d-74ac-47d4-a107-9c3069ab7b48'); translator.setTranslator('951c027d-74ac-47d4-a107-9c3069ab7b48');

View file

@ -1,4 +1,4 @@
async function scrape(doc, url) { async function scrape(doc, url = doc.location.href) {
// TODO adjust the selector for the lines here // TODO adjust the selector for the lines here
let lines = doc.querySelectorAll('table#marcData tr'); let lines = doc.querySelectorAll('table#marcData tr');

View file

@ -1,4 +1,4 @@
async function scrape(doc, url) { async function scrape(doc, url = doc.location.href) {
let DOI = url.match(/\/(10\.[^#?]+)/)[1]; let DOI = url.match(/\/(10\.[^#?]+)/)[1];
// TODO adjust the URL here // TODO adjust the URL here
let risURL = `http://citation-needed.services.springer.com/v2/references/${DOI}?format=refman&flavour=citation`; let risURL = `http://citation-needed.services.springer.com/v2/references/${DOI}?format=refman&flavour=citation`;

View file

@ -25,6 +25,12 @@ row > label:first-child, .creator-type-label, #more-creators-label
max-height: 7px; max-height: 7px;
} }
.zotero-focusable:focus {
/* At least in Fx60, the color doesn't quite match Monterey's native textbox outline,
but it will have to do for now */
outline: 2px solid -moz-mac-focusring !important;
}
textbox textbox
{ {
margin: -1px 5px -1px 0; margin: -1px 5px -1px 0;

View file

@ -162,7 +162,8 @@ input {
/* This seems to be necessary to center the tabs. Not sure why. */ /* This seems to be necessary to center the tabs. Not sure why. */
.zotero-editpane-tabs > tab:last-of-type > hbox .tab-text { .zotero-editpane-tabs > tab:last-of-type > hbox .tab-text {
margin: 2px 9px 2px 9px !important; margin-left: 9px !important;
margin-right: 9px !important;
} }
.zotero-editpane-tabs > tab[selected=true] > hbox .tab-text { .zotero-editpane-tabs > tab[selected=true] > hbox .tab-text {

View file

@ -63,3 +63,7 @@ tab {
background-color: #ececec; background-color: #ececec;
border-top: 1px solid hsla(0, 0%, 0%, 0.2); border-top: 1px solid hsla(0, 0%, 0%, 0.2);
} }
.zotero-tb-button:focus {
border: 1px dotted #999;
}

View file

@ -40,6 +40,14 @@
<overlay id="menuOverlay" <overlay id="menuOverlay"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<keyset id="mainKeyset">
<key id="key_quitApplication"
key="&quitApplicationCmd.accesskey;"
command="cmd_quitApplication"
modifiers="accel"/>
</keyset>
<menupopup id="menu_FilePopup"> <menupopup id="menu_FilePopup">
<menuseparator/> <menuseparator/>
<menuitem id="menu_FileQuitItem" <menuitem id="menu_FileQuitItem"

View file

@ -44,7 +44,7 @@
<menuseparator/> <menuseparator/>
<menuitem id="menu_FileQuitItem" <menuitem id="menu_FileQuitItem"
label="&quitApplicationCmdWin.label;" label="&quitApplicationCmdWin.label;"
key="key_quitApplication" accesskey="&quitApplicationCmdWin.accesskey;"
command="cmd_quitApplication"/> command="cmd_quitApplication"/>
</menupopup> </menupopup>
<menupopup id="menu_EditPopup"> <menupopup id="menu_EditPopup">

View file

@ -96,6 +96,8 @@ var ZoteroAdvancedSearch = new function() {
ref: _searchBox.search, ref: _searchBox.search,
isSearchMode: () => true, isSearchMode: () => true,
getItems: async function () { getItems: async function () {
await Zotero.Libraries.get(_libraryID).waitForDataLoad('item');
var search = _searchBox.search.clone(); var search = _searchBox.search.clone();
search.libraryID = _libraryID; search.libraryID = _libraryID;
var ids = await search.search(); var ids = await search.search();

View file

@ -245,7 +245,9 @@
urlField.setAttribute('hidden', false); urlField.setAttribute('hidden', false);
if (this.clickableLink) { if (this.clickableLink) {
urlField.onclick = function (event) { urlField.onclick = function (event) {
ZoteroPane_Local.loadURI(this.value, event) if (event.button != 2) {
ZoteroPane_Local.loadURI(this.value, event)
}
}; };
urlField.className = 'zotero-text-link'; urlField.className = 'zotero-text-link';
} }

View file

@ -111,6 +111,7 @@
} }
this._item = val; this._item = val;
this._lastTabIndex = null;
this.refresh(); this.refresh();
]]></setter> ]]></setter>
</property> </property>
@ -391,7 +392,7 @@
&& Zotero.ItemFields.isDate(fieldName) && Zotero.ItemFields.isDate(fieldName)
// TEMP - NSF // TEMP - NSF
&& fieldName != 'dateSent') { && fieldName != 'dateSent') {
this.addDateRow(fieldNames[i], this.item.getField(fieldName, true), tabindex); this.addDateRow(fieldName, this.item.getField(fieldName, true), tabindex);
continue; continue;
} }
} }
@ -422,7 +423,7 @@
&& Zotero.Utilities.isHTTPURL(val, true)) { && Zotero.Utilities.isHTTPURL(val, true)) {
label.classList.add("pointer"); label.classList.add("pointer");
// TODO: make getFieldValue non-private and use below instead // TODO: make getFieldValue non-private and use below instead
label.setAttribute("onclick", "Zotero.launchURL(this.nextSibling.firstChild ? this.nextSibling.firstChild.nodeValue : this.nextSibling.value)"); label.addEventListener('click', () => Zotero.launchURL(label.nextSibling.firstChild ? label.nextSibling.firstChild.nodeValue : label.nextSibling.value));
label.setAttribute("tooltiptext", Zotero.getString('pane.item.viewOnline.tooltip')); label.setAttribute("tooltiptext", Zotero.getString('pane.item.viewOnline.tooltip'));
} }
else if (fieldName == 'DOI' && val && typeof val == 'string') { else if (fieldName == 'DOI' && val && typeof val == 'string') {
@ -437,17 +438,14 @@
.replace(/%/g, '%25') .replace(/%/g, '%25')
.replace(/"/g, '%22'); .replace(/"/g, '%22');
label.classList.add("pointer"); label.classList.add("pointer");
label.setAttribute("onclick", "ZoteroPane_Local.loadURI('" + doi + "', event)"); label.addEventListener('click', event => ZoteroPane_Local.loadURI(doi, event));
label.setAttribute("tooltiptext", Zotero.getString('pane.item.viewOnline.tooltip')); label.setAttribute("tooltiptext", Zotero.getString('pane.item.viewOnline.tooltip'));
valueElement.oncontextmenu = () => {
this._id('zotero-doi-menu').openPopup(valueElement);
};
var openURLMenuItem = this._id('zotero-doi-menu-view-online'); var openURLMenuItem = this._id('zotero-doi-menu-view-online');
openURLMenuItem.setAttribute("oncommand", "ZoteroPane_Local.loadURI('" + doi + "', event)"); openURLMenuItem.addEventListener('command', event => ZoteroPane_Local.loadURI(doi, event));
var copyMenuItem = this._id('zotero-doi-menu-copy'); var copyMenuItem = this._id('zotero-doi-menu-copy');
copyMenuItem.setAttribute("oncommand", "Zotero.Utilities.Internal.copyTextToClipboard('" + doi + "')"); copyMenuItem.addEventListener('command', () => Zotero.Utilities.Internal.copyTextToClipboard(doi));
} }
} }
else if (fieldName == 'abstractNote') { else if (fieldName == 'abstractNote') {
@ -616,7 +614,7 @@
this.disableCreatorAddButtons(); this.disableCreatorAddButtons();
} }
// Move to next or previous field if (shift-)tab was pressed // Refocus on last stored tab index. This is required e.g. when remote change triggers refresh.
if (this._lastTabIndex && this._lastTabIndex != -1) { if (this._lastTabIndex && this._lastTabIndex != -1) {
this._focusNextField(this._lastTabIndex); this._focusNextField(this._lastTabIndex);
} }
@ -719,7 +717,7 @@
<![CDATA[ <![CDATA[
// getCreatorFields(), switchCreatorMode() and handleCreatorAutoCompleteSelect() // getCreatorFields(), switchCreatorMode() and handleCreatorAutoCompleteSelect()
// may need need to be adjusted if this DOM structure changes // may need need to be adjusted if this DOM structure changes
var tabindex = this._tabIndexMinCreators + (this._creatorCount * 6);
var fieldMode = Zotero.Prefs.get('lastCreatorFieldMode'); var fieldMode = Zotero.Prefs.get('lastCreatorFieldMode');
var firstName = ''; var firstName = '';
var lastName = ''; var lastName = '';
@ -757,9 +755,15 @@
} }
typeBox.setAttribute("fieldname", 'creator-' + this._creatorCount + '-typeID'); typeBox.setAttribute("fieldname", 'creator-' + this._creatorCount + '-typeID');
if (this.editable) { if (this.editable) {
typeBox.className = 'creator-type-label zotero-clicky'; typeBox.className = 'creator-type-label zotero-clicky zotero-focusable';
typeBox.setAttribute('ztabindex', tabindex - 1);
typeBox.setAttribute('role', 'listbox');
typeBox.addEventListener('focus', ev => {
this._lastTabIndex = parseInt(ev.target.getAttribute('ztabindex'));
});
var img = document.createElement('image'); var img = document.createElement('image');
typeBox.appendChild(img); typeBox.appendChild(img);
typeBox.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
} }
else { else {
typeBox.className = 'creator-type-label'; typeBox.className = 'creator-type-label';
@ -772,12 +776,18 @@
var hbox = document.createElement("hbox"); var hbox = document.createElement("hbox");
hbox.className = 'creator-type-value'; hbox.className = 'creator-type-value';
hbox.addEventListener('focusin', ev => {
if(ev.target.classList.contains('zotero-focusable')) {
this._lastTabIndex = parseInt(ev.target.getAttribute('ztabindex'));
}
});
// Name // Name
var firstlast = document.createElement("hbox"); var firstlast = document.createElement("hbox");
firstlast.className = 'creator-name-box'; firstlast.className = 'creator-name-box';
firstlast.setAttribute("flex","1"); firstlast.setAttribute("flex","1");
var tabindex = this._tabIndexMinCreators + (this._creatorCount * 2);
var fieldName = 'creator-' + this._creatorCount + '-lastName'; var fieldName = 'creator-' + this._creatorCount + '-lastName';
var lastNameLabel = firstlast.appendChild( var lastNameLabel = firstlast.appendChild(
this.createValueElement( this.createValueElement(
@ -820,29 +830,40 @@
var toggleButton = document.createElement('label'); var toggleButton = document.createElement('label');
toggleButton.setAttribute('fieldname', toggleButton.setAttribute('fieldname',
'creator-' + this._creatorCount + '-fieldMode'); 'creator-' + this._creatorCount + '-fieldMode');
toggleButton.className = 'zotero-field-toggle zotero-clicky'; toggleButton.setAttribute('ztabindex', tabindex + 2);
toggleButton.setAttribute('role', 'button');
toggleButton.className = 'zotero-field-toggle zotero-focusable zotero-clicky';
toggleButton.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
hbox.appendChild(toggleButton); hbox.appendChild(toggleButton);
// Minus (-) button // Minus (-) button
var removeButton = document.createElement('label'); var removeButton = document.createElement('label');
removeButton.setAttribute('ztabindex', tabindex + 3);
removeButton.setAttribute('role', 'button');
removeButton.setAttribute('aria-label', Zotero.getString('general.delete'));
removeButton.setAttribute("value","-"); removeButton.setAttribute("value","-");
removeButton.setAttribute("class","zotero-clicky zotero-clicky-minus"); removeButton.setAttribute('class','zotero-clicky zotero-focusable zotero-clicky-minus');
removeButton.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
// If default first row, don't let user remove it // If default first row, don't let user remove it
if (defaultRow) { if (defaultRow) {
this.disableButton(removeButton); this.disableButton(removeButton);
} }
else { else {
removeButton.setAttribute("onclick", let index = this._creatorCount;
"document.getBindingParent(this).removeCreator(" removeButton.onclick = (ev) => {
+ this._creatorCount document.getBindingParent(ev.currentTarget)
+ ", this.parentNode.parentNode)"); .removeCreator(index, ev.currentTarget.parentNode.parentNode);
}
} }
hbox.appendChild(removeButton); hbox.appendChild(removeButton);
// Plus (+) button // Plus (+) button
var addButton = document.createElement('label'); var addButton = document.createElement('label');
addButton.setAttribute('ztabindex', tabindex + 4);
addButton.setAttribute("value","+"); addButton.setAttribute("value","+");
addButton.setAttribute("class", "zotero-clicky zotero-clicky-plus"); addButton.setAttribute('role', 'button');
addButton.setAttribute('class', 'zotero-clicky zotero-focusable zotero-clicky-plus');
addButton.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
// If row isn't saved, don't let user add more // If row isn't saved, don't let user add more
if (unsaved) { if (unsaved) {
this.disableButton(addButton); this.disableButton(addButton);
@ -958,7 +979,11 @@
button.style.background = `url("chrome://zotero/skin/textfield-dual${Zotero.hiDPISuffix}.png") center/21px auto no-repeat`; button.style.background = `url("chrome://zotero/skin/textfield-dual${Zotero.hiDPISuffix}.png") center/21px auto no-repeat`;
button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.two')); button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.two'));
lastName.setAttribute('fieldMode', '1'); lastName.setAttribute('fieldMode', '1');
button.setAttribute('onclick', "document.getBindingParent(this).switchCreatorMode(Zotero.getAncestorByTagName(this, 'row'), 0, false, true)"); button.onclick = (ev) => {
document
.getBindingParent(ev.currentTarget)
.switchCreatorMode(Zotero.getAncestorByTagName(ev.currentTarget, 'row'), 0, false, true);
};
lastName.setAttribute('flex', '1'); lastName.setAttribute('flex', '1');
delete lastName.style.width; delete lastName.style.width;
delete lastName.style.maxWidth; delete lastName.style.maxWidth;
@ -998,7 +1023,11 @@
button.style.background = `url("chrome://zotero/skin/textfield-single${Zotero.hiDPISuffix}.png") center/21px auto no-repeat`; button.style.background = `url("chrome://zotero/skin/textfield-single${Zotero.hiDPISuffix}.png") center/21px auto no-repeat`;
button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.one')); button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.one'));
lastName.setAttribute('fieldMode', '0'); lastName.setAttribute('fieldMode', '0');
button.setAttribute('onclick', "document.getBindingParent(this).switchCreatorMode(Zotero.getAncestorByTagName(this, 'row'), 1, false, true)"); button.onclick = (ev) => {
document
.getBindingParent(ev.currentTarget)
.switchCreatorMode(Zotero.getAncestorByTagName(ev.currentTarget, 'row'), 1, false, true)
};
lastName.setAttribute('flex', '0'); lastName.setAttribute('flex', '0');
// appropriately truncate lastName // appropriately truncate lastName
@ -1391,6 +1420,13 @@
valueElement.setAttribute('flex', '1'); valueElement.setAttribute('flex', '1');
} }
// Add popup menu on DOI field with value
if (fieldName == 'DOI' && valueText) {
valueElement.oncontextmenu = () => {
this._id('zotero-doi-menu').openPopup(valueElement);
};
}
var firstSpace = valueText.indexOf(" "); var firstSpace = valueText.indexOf(" ");
// To support newlines in Abstract and Extra fields, use multiple // To support newlines in Abstract and Extra fields, use multiple
@ -1552,8 +1588,7 @@
} }
else { else {
// Add auto-complete for certain fields // Add auto-complete for certain fields
if (Zotero.ItemFields.isAutocompleteField(fieldName) if (field == 'creator' || Zotero.ItemFields.isAutocompleteField(fieldName)) {
|| fieldName == 'creator') {
t.setAttribute('type', 'autocomplete'); t.setAttribute('type', 'autocomplete');
t.setAttribute('autocompletesearch', 'zotero'); t.setAttribute('autocompletesearch', 'zotero');
@ -1720,6 +1755,19 @@
<![CDATA[ <![CDATA[
var target = event.target; var target = event.target;
var focused = document.commandDispatcher.focusedElement; var focused = document.commandDispatcher.focusedElement;
if ((event.key == 'ArrowUp' || event.key === 'ArrowDown')
&& target.classList.contains('creator-type-label')) {
event.preventDefault();
target.click();
return true;
}
if (event.key === ' ' && target.classList.contains('zotero-focusable')) {
event.preventDefault();
target.click();
return true;
}
switch (event.keyCode) switch (event.keyCode)
{ {
@ -1932,8 +1980,6 @@
newVal = val; newVal = val;
// Reset creator mode settings here so that flex attribute gets reset
this.switchCreatorMode(row, (otherFields.fieldMode ? 1 : 0), true);
if (Zotero.ItemTypes.getName(this.item.itemTypeID) === "bookSection") { if (Zotero.ItemTypes.getName(this.item.itemTypeID) === "bookSection") {
var creatorTypeLabels = document.getAnonymousNodes(this)[0].getElementsByClassName("creator-type-label"); var creatorTypeLabels = document.getAnonymousNodes(this)[0].getElementsByClassName("creator-type-label");
Zotero.debug(creatorTypeLabels[creatorTypeLabels.length-1] + ""); Zotero.debug(creatorTypeLabels[creatorTypeLabels.length-1] + "");
@ -2018,6 +2064,11 @@
// Disassociate textbox from label // Disassociate textbox from label
label.setAttribute('control', elem.getAttribute('id')); label.setAttribute('control', elem.getAttribute('id'));
if (field == 'creator') {
// Set correct flex settings and fieldMode attribute
this.switchCreatorMode(row, (otherFields.fieldMode ? 1 : 0), true);
}
if (this.saveOnEdit) { if (this.saveOnEdit) {
await this.item.saveTx(); await this.item.saveTx();
} }
@ -2293,6 +2344,32 @@
]]> ]]>
</body> </body>
</method> </method>
<method name="focusLastField">
<body><![CDATA[
const tabbableFields = this._dynamicFields.querySelectorAll('*[ztabindex]:not([disabled=true])');
const last = tabbableFields[tabbableFields.length - 1];
if (last.classList.contains('zotero-focusable')) {
last.focus();
}
// Fields need to be clicked
else {
last.click();
}
]]></body>
</method>
<method name="focusField">
<parameter name="fieldName"/>
<body>
<![CDATA[
let field = document.getAnonymousNodes(this)[0].querySelector(`[fieldname="${fieldName}"][ztabindex]`);
if (!field) return false;
return this._focusNextField(field.getAttribute('ztabindex'));
]]>
</body>
</method>
<!-- <!--
@ -2312,7 +2389,7 @@
tabindex = parseInt(tabindex); tabindex = parseInt(tabindex);
// Get all fields with ztabindex attributes // Get all fields with ztabindex attributes
var tabbableFields = box.querySelectorAll('*[ztabindex]'); var tabbableFields = box.querySelectorAll('*[ztabindex]:not([disabled=true])');
if (!tabbableFields.length) { if (!tabbableFields.length) {
Zotero.debug("No tabbable fields found"); Zotero.debug("No tabbable fields found");
@ -2323,16 +2400,20 @@
if (back) { if (back) {
Zotero.debug('Looking for previous tabindex before ' + tabindex, 4); Zotero.debug('Looking for previous tabindex before ' + tabindex, 4);
for (let i = tabbableFields.length - 1; i >= 0; i--) { for (let i = tabbableFields.length - 1; i >= 0; i--) {
if (parseInt(tabbableFields[i].getAttribute('ztabindex')) < tabindex) { let field = tabbableFields[i];
let tabIndexHere = parseInt(field.getAttribute('ztabindex'));
if (tabIndexHere !== -1 && tabIndexHere < tabindex) {
next = tabbableFields[i]; next = tabbableFields[i];
break; break;
} }
} }
} }
else { else {
Zotero.debug('Looking for tabindex ' + tabindex, 4); Zotero.debug('Looking for tabindex ' + tabindex, 4);
for (var pos = 0; pos < tabbableFields.length; pos++) { for (var pos = 0; pos < tabbableFields.length; pos++) {
if (parseInt(tabbableFields[pos].getAttribute('ztabindex')) >= tabindex) { let field = tabbableFields[pos];
let tabIndexHere = parseInt(field.getAttribute('ztabindex'));
if (tabIndexHere !== -1 && tabIndexHere >= tabindex) {
next = tabbableFields[pos]; next = tabbableFields[pos];
break; break;
} }
@ -2345,7 +2426,7 @@
} }
// Drop-down needs to be focused // Drop-down needs to be focused
if (next.id == 'item-type-menu') { if (next.id == 'item-type-menu' || next.classList.contains('zotero-focusable')) {
next.focus(); next.focus();
} }
// Fields need to be clicked // Fields need to be clicked

View file

@ -353,15 +353,16 @@
<body> <body>
<![CDATA[ <![CDATA[
(async () => { (async () => {
let n = 0;
while (!this._editorInstance && n++ < 100) {
await Zotero.Promise.delay(10);
}
await this._editorInstance._initPromise;
this._iframe.focus();
try { try {
let n = 0;
while (!this._editorInstance && n++ < 100) {
await Zotero.Promise.delay(10);
}
await this._editorInstance._initPromise;
this._iframe.focus();
this._editorInstance._iframeWindow.document.querySelector('.toolbar-button-return').focus(); this._editorInstance._iframeWindow.document.querySelector('.toolbar-button-return').focus();
} catch(e) { }
catch (e) {
} }
})(); })();
]]> ]]>

View file

@ -330,6 +330,15 @@
]]> ]]>
</body> </body>
</method> </method>
<method name="receiveKeyboardFocus">
<parameter name="direction" />
<body><![CDATA[
this.id("addButton").focus();
// TODO: the relatedbox is not currently keyboard accessible
// so we are ignoring the direction
]]></body>
</method>
</implementation> </implementation>
<content> <content>
<xul:vbox xbl:inherits="flex" class="zotero-box"> <xul:vbox xbl:inherits="flex" class="zotero-box">

View file

@ -407,6 +407,7 @@
<parameter name="condition"/> <parameter name="condition"/>
<body><![CDATA[ <body><![CDATA[
switch (condition) { switch (condition) {
case 'anyField':
case 'collection': case 'collection':
case 'creator': case 'creator':
case 'title': case 'title':

View file

@ -25,7 +25,6 @@
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
const { IntlProvider } = require('react-intl');
const LibraryTree = require('./libraryTree'); const LibraryTree = require('./libraryTree');
const VirtualizedTable = require('components/virtualized-table'); const VirtualizedTable = require('components/virtualized-table');
const { TreeSelectionStub } = VirtualizedTable; const { TreeSelectionStub } = VirtualizedTable;
@ -42,9 +41,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
var ref; var ref;
opts.domEl = domEl; opts.domEl = domEl;
let elem = ( let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <CollectionTree ref={c => ref = c } {...opts} />
<CollectionTree ref={c => ref = c } {...opts} />
</IntlProvider>
); );
await new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); await new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
@ -640,15 +637,15 @@ var CollectionTree = class CollectionTree extends LibraryTree {
this.tree.invalidate(); this.tree.invalidate();
return; return;
} }
if (action == 'refresh') { if (action == 'refresh' && type != 'trash') {
// If trash is refreshed, we probably need to update the icon from full to empty // Trash handled below
if (type == 'trash') {
this.tree.invalidate();
}
return; return;
} }
if (type == 'feed' && (action == 'unreadCountUpdated' || action == 'statusChanged')) { if (type == 'feed' && (action == 'unreadCountUpdated' || action == 'statusChanged')) {
this.tree.invalidate(); let feedRow = this.getRowIndexByID("L" + ids[0]);
if (feedRow !== false) {
this.tree.invalidateRow(feedRow);
}
return; return;
} }
@ -753,7 +750,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
// Invalidate parent in case it's become non-empty // Invalidate parent in case it's become non-empty
let parentRow = this.getRowIndexByID("C" + collection.parentID); let parentRow = this.getRowIndexByID("C" + collection.parentID);
if (parentRow !== false) { if (parentRow !== false) {
this._treebox.invalidateRow(parentRow); this.tree.invalidateRow(parentRow);
} }
} }
break; break;
@ -777,6 +774,9 @@ var CollectionTree = class CollectionTree extends LibraryTree {
} }
} }
break; break;
case 'feed':
break;
default: default:
await this.reload(); await this.reload();
@ -826,6 +826,11 @@ var CollectionTree = class CollectionTree extends LibraryTree {
} }
} }
} }
else if (action == 'refresh' && type == 'trash') {
// We need to update the trash's status (full or empty), and if empty,
// the row might be removed
await this.reload();
}
this.forceUpdate(); this.forceUpdate();
var promise = this.waitForSelect(); var promise = this.waitForSelect();
@ -839,6 +844,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
* @param ids {String[]} list of row ids to be highlighted * @param ids {String[]} list of row ids to be highlighted
*/ */
async setHighlightedRows(ids) { async setHighlightedRows(ids) {
if (this._editing) return;
try { try {
this._highlightedRows = new Set(); this._highlightedRows = new Set();
@ -1614,7 +1620,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
return false; return false;
} }
let newAttachment = Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID); let newAttachment = await Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID);
if (options.annotations) { if (options.annotations) {
await Zotero.Items.copyChildItems(item, newAttachment); await Zotero.Items.copyChildItems(item, newAttachment);
} }
@ -2306,7 +2312,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
} }
if (showTrash) { if (showTrash) {
let deletedItems = await Zotero.Items.getDeleted(libraryID); let deletedItems = await Zotero.Items.getDeleted(libraryID, true);
if (deletedItems.length || Zotero.Prefs.get("showTrashWhenEmpty")) { if (deletedItems.length || Zotero.Prefs.get("showTrashWhenEmpty")) {
var ref = { var ref = {
libraryID: libraryID libraryID: libraryID

View file

@ -29,7 +29,6 @@ import React, { memo } from 'react';
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cx from 'classnames'; import cx from 'classnames';
import { IntlProvider, FormattedMessage } from "react-intl";
// This is a quick reimplementation of the annotation for use in the conflict resolution window. // This is a quick reimplementation of the annotation for use in the conflict resolution window.
// We'll want to replace this with a single component shared between the PDF reader and the rest // We'll want to replace this with a single component shared between the PDF reader and the rest
@ -40,31 +39,26 @@ function AnnotationBox({ data }) {
}; };
return ( return (
<IntlProvider <div className="AnnotationBox">
locale={ Zotero.locale } <div className="title">{Zotero.getString('itemTypes.annotation')}</div>
messages={ Zotero.Intl.strings } <div className="container">
> <div className="header">
<div className="AnnotationBox"> <div>{Zotero.Cite.getLocatorString('page')} {data.pageLabel}</div>
<div className="title">{Zotero.getString('itemTypes.annotation')}</div>
<div className="container">
<div className="header">
<div>{Zotero.getString('citation.locator.page')} {data.pageLabel}</div>
</div>
{data.text !== undefined
? <div className="text" style={textStyle}>{data.text}</div>
: ''}
{data.type == 'image'
// TODO: Localize
// TODO: Render from PDF based on position, if file is the same? Or don't
// worry about it?
? <div className="image-placeholder">[image not shown]</div>
: ''}
{data.comment !== undefined
? <div className="comment">{data.comment}</div>
: ''}
</div> </div>
{data.text !== undefined
? <div className="text" style={textStyle}>{data.text}</div>
: ''}
{data.type == 'image'
// TODO: Localize
// TODO: Render from PDF based on position, if file is the same? Or don't
// worry about it?
? <div className="image-placeholder">[image not shown]</div>
: ''}
{data.comment !== undefined
? <div className="comment">{data.comment}</div>
: ''}
</div> </div>
</IntlProvider> </div>
); );
} }

View file

@ -27,7 +27,6 @@
const React = require('react') const React = require('react')
const { PureComponent, createElement: create } = React const { PureComponent, createElement: create } = React
const { injectIntl } = require('react-intl')
const { IconDownChevron } = require('./icons') const { IconDownChevron } = require('./icons')
const cx = require('classnames') const cx = require('classnames')
const { const {
@ -59,19 +58,19 @@ class Button extends PureComponent {
} }
get text() { get text() {
const { intl, text } = this.props const { text } = this.props;
return text ? return text ?
intl.formatMessage({ id: text }) : Zotero.getString(text) :
null null;
} }
get title() { get title() {
const { intl, title } = this.props const { title } = this.props;
return title ? return title ?
intl.formatMessage({ id: title }) : Zotero.getString(title) :
null null;
} }
get menuMarker() { get menuMarker() {
@ -159,5 +158,5 @@ class Button extends PureComponent {
module.exports = { module.exports = {
ButtonGroup, ButtonGroup,
Button: injectIntl(Button) Button
} }

View file

@ -29,7 +29,6 @@ import React, { memo } from 'react';
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cx from 'classnames'; import cx from 'classnames';
import { IntlProvider } from "react-intl";
function CreateParent({ loading, item, toggleAccept }) { function CreateParent({ loading, item, toggleAccept }) {
// When the input has/does not have characters toggle the accept button on the dialog // When the input has/does not have characters toggle the accept button on the dialog
@ -43,31 +42,26 @@ function CreateParent({ loading, item, toggleAccept }) {
}; };
return ( return (
<IntlProvider <div className="create-parent-container">
locale={ Zotero.locale } <span className="title">
messages={ Zotero.Intl.strings } { item.attachmentFilename }
> </span>
<div className="create-parent-container"> <div className="body">
<span className="title"> <input
{ item.attachmentFilename } id="parent-item-identifier"
</span> placeholder={ Zotero.getString('createParent.prompt') }
<div className="body"> size="50"
<input disabled={ loading }
id="parent-item-identifier" onChange={ handleInput }
placeholder={ Zotero.getString('createParent.prompt') } />
size="50" <div
disabled={ loading } mode="undetermined"
onChange={ handleInput } className={ cx('downloadProgress', { hidden: !loading }) }
/> >
<div <div className="progress-bar"></div>
mode="undetermined"
className={ cx('downloadProgress', { hidden: !loading }) }
>
<div className="progress-bar"></div>
</div>
</div> </div>
</div> </div>
</IntlProvider> </div>
); );
} }

View file

@ -124,6 +124,7 @@ i('TreeitemBookSection', 'chrome://zotero/skin/treeitem-bookSection.png');
i('TreeitemCase', 'chrome://zotero/skin/treeitem-case.png'); i('TreeitemCase', 'chrome://zotero/skin/treeitem-case.png');
i('TreeitemComputerProgram', 'chrome://zotero/skin/treeitem-computerProgram.png'); i('TreeitemComputerProgram', 'chrome://zotero/skin/treeitem-computerProgram.png');
i('TreeitemConferencePaper', 'chrome://zotero/skin/treeitem-conferencePaper.png'); i('TreeitemConferencePaper', 'chrome://zotero/skin/treeitem-conferencePaper.png');
i('TreeitemDataset', 'chrome://zotero/skin/treeitem-dataset.png');
i('TreeitemDictionaryEntry', 'chrome://zotero/skin/treeitem-dictionaryEntry.png'); i('TreeitemDictionaryEntry', 'chrome://zotero/skin/treeitem-dictionaryEntry.png');
i('TreeitemEmail', 'chrome://zotero/skin/treeitem-email.png'); i('TreeitemEmail', 'chrome://zotero/skin/treeitem-email.png');
i('TreeitemEncyclopediaArticle', 'chrome://zotero/skin/treeitem-encyclopediaArticle.png'); i('TreeitemEncyclopediaArticle', 'chrome://zotero/skin/treeitem-encyclopediaArticle.png');
@ -147,12 +148,13 @@ i('TreeitemPreprint', 'chrome://zotero/skin/treeitem-preprint.png');
i('TreeitemPresentation', 'chrome://zotero/skin/treeitem-presentation.png'); i('TreeitemPresentation', 'chrome://zotero/skin/treeitem-presentation.png');
i('TreeitemRadioBroadcast', 'chrome://zotero/skin/treeitem-radioBroadcast.png', false); i('TreeitemRadioBroadcast', 'chrome://zotero/skin/treeitem-radioBroadcast.png', false);
i('TreeitemReport', 'chrome://zotero/skin/treeitem-report.png'); i('TreeitemReport', 'chrome://zotero/skin/treeitem-report.png');
i('TreeitemStandard', 'chrome://zotero/skin/treeitem-standard.png');
i('TreeitemStatute', 'chrome://zotero/skin/treeitem-statute.png'); i('TreeitemStatute', 'chrome://zotero/skin/treeitem-statute.png');
i('TreeitemThesis', 'chrome://zotero/skin/treeitem-thesis.png'); i('TreeitemThesis', 'chrome://zotero/skin/treeitem-thesis.png');
i('TreeitemTvBroadcast', 'chrome://zotero/skin/treeitem-tvBroadcast.png', false); i('TreeitemTvBroadcast', 'chrome://zotero/skin/treeitem-tvBroadcast.png', false);
i('TreeitemVideoRecording', 'chrome://zotero/skin/treeitem-videoRecording.png', false); i('TreeitemVideoRecording', 'chrome://zotero/skin/treeitem-videoRecording.png', false);
i('TreeitemWebpageGray', 'chrome://zotero/skin/treeitem-webpage-gray.png'); i('TreeitemWebpageGray', 'chrome://zotero/skin/treeitem-webpage-gray.png');
i('TreeitemWebpage', 'chrome://zotero/skin/treeitem-webpage.png', false); i('TreeitemWebpage', 'chrome://zotero/skin/treeitem-webpage.png');
// Treesource // Treesource
i('TreesourceBucket', 'chrome://zotero/skin/treesource-bucket.png', false); i('TreesourceBucket', 'chrome://zotero/skin/treesource-bucket.png', false);

View file

@ -415,7 +415,7 @@ const TagsBox = React.forwardRef((props, ref) => {
<div className="tags-box" ref={rootRef} onClick={blurOpenField}> <div className="tags-box" ref={rootRef} onClick={blurOpenField}>
<div className="tags-box-header"> <div className="tags-box-header">
<div className="tags-box-count">{renderCount()}</div> <div className="tags-box-count">{renderCount()}</div>
{ props.editable && <div><button onClick={handleAddTag}>Add</button></div> } { props.editable && <div><button onClick={handleAddTag} id="tags-box-add-button">Add</button></div> }
</div> </div>
<div className="tags-box-list-container"> <div className="tags-box-list-container">
<ul className="tags-box-list"> <ul className="tags-box-list">

View file

@ -25,7 +25,6 @@
import React, { memo, useCallback, useEffect, useRef } from 'react'; import React, { memo, useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { getDOMElement } from 'components/icons'; import { getDOMElement } from 'components/icons';
import { IntlProvider } from 'react-intl';
import VirtualizedTable, { renderCell } from 'components/virtualized-table'; import VirtualizedTable, { renderCell } from 'components/virtualized-table';
import { noop } from './utils'; import { noop } from './utils';
@ -101,17 +100,15 @@ const ProgressQueueTable = ({ onActivate = noop, progressQueue }) => {
}, []); // eslint-disable-line react-hooks/exhaustive-deps }, []); // eslint-disable-line react-hooks/exhaustive-deps
return ( return (
<IntlProvider locale={ Zotero.locale } messages={ Zotero.Intl.strings }> <VirtualizedTable
<VirtualizedTable getRowCount={ getRowCount }
getRowCount={ getRowCount } ref={ treeRef }
ref={ treeRef } id="progress-queue-table"
id="progress-queue-table" renderItem={ rowToTreeItem }
renderItem={ rowToTreeItem } showHeader={ true }
showHeader={ true } columns={ tableColumns }
columns={ tableColumns } onActivate={ onActivate }
onActivate={ onActivate } />
/>
</IntlProvider>
); );
}; };

View file

@ -235,7 +235,7 @@ const TabBar = forwardRef(function (props, ref) {
function handleWheel(event) { function handleWheel(event) {
// Normalize wheel speed // Normalize wheel speed
let x = event.deltaX; let x = event.deltaX || event.deltaY;
if (x && event.deltaMode) { if (x && event.deltaMode) {
if (event.deltaMode === 1) { if (event.deltaMode === 1) {
x *= 20; x *= 20;
@ -302,7 +302,10 @@ const TabBar = forwardRef(function (props, ref) {
onWheel={handleWheel} onWheel={handleWheel}
> >
<div className="pinned-tabs"> <div className="pinned-tabs">
<div className="tabs"> <div
className="tabs"
onMouseOut={handleTabBarMouseOut}
>
{tabs.length ? renderTab(tabs[0], 0) : null} {tabs.length ? renderTab(tabs[0], 0) : null}
</div> </div>
</div> </div>

View file

@ -25,7 +25,6 @@
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const { FormattedMessage } = require('react-intl');
var { Collection } = require('react-virtualized'); var { Collection } = require('react-virtualized');
// See also .tag-selector-item in _tag-selector.scss // See also .tag-selector-item in _tag-selector.scss
@ -191,14 +190,14 @@ class TagList extends React.PureComponent {
if (!this.props.loaded) { if (!this.props.loaded) {
tagList = ( tagList = (
<div className="tag-selector-message"> <div className="tag-selector-message">
<FormattedMessage id="zotero.tagSelector.loadingTags" /> {Zotero.getString('zotero.tagSelector.loadingTags')}
</div> </div>
); );
} }
else if (tagCount == 0) { else if (tagCount == 0) {
tagList = ( tagList = (
<div className="tag-selector-message"> <div className="tag-selector-message">
<FormattedMessage id="zotero.tagSelector.noTagsToDisplay" /> {Zotero.getString('zotero.tagSelector.noTagsToDisplay')}
</div> </div>
); );
} }

View file

@ -30,7 +30,6 @@ const PropTypes = require('prop-types');
const cx = require('classnames'); const cx = require('classnames');
const WindowedList = require('./windowed-list'); const WindowedList = require('./windowed-list');
const Draggable = require('./draggable'); const Draggable = require('./draggable');
const { injectIntl } = require('react-intl');
const { IconDownChevron, getDOMElement } = require('components/icons'); const { IconDownChevron, getDOMElement } = require('components/icons');
const TYPING_TIMEOUT = 1000; const TYPING_TIMEOUT = 1000;
@ -639,7 +638,10 @@ class VirtualizedTable extends React.Component {
} }
} }
else { else {
for (let i = (this.selection.focused) % rowCount; i != this.selection.focused - 1; i = (i + 1) % rowCount) { // Stop at the row before the selection's focus
// but don't set a negative stop point, or we'll loop infinitely
let stopRow = Math.max(this.selection.focused - 1, 0);
for (let i = (this.selection.focused) % rowCount; i != stopRow; i = (i + 1) % rowCount) {
let rowString = this.props.getRowString(i); let rowString = this.props.getRowString(i);
if (rowString.toLowerCase().indexOf(this._typingString) == 0) { if (rowString.toLowerCase().indexOf(this._typingString) == 0) {
if (i != this.selection.focused) { if (i != this.selection.focused) {
@ -660,7 +662,14 @@ class VirtualizedTable extends React.Component {
this._isMouseDrag = true; this._isMouseDrag = true;
} }
_onDragEnd = () => { _onDragEnd = async () => {
// macOS force-click sometimes causes a second mouseup event to be fired some time later
// causing a collection change on dragend, so we add a delay here. It shouldn't cause any issues
// because isMouseDrag is only used in mouseup handler to exactly prevent from accidentally switching
// selection after dragend.
if (Zotero.isMac) {
await Zotero.Promise.delay(500);
}
this._isMouseDrag = false; this._isMouseDrag = false;
} }
@ -740,6 +749,7 @@ class VirtualizedTable extends React.Component {
this.selection.pivot = index; this.selection.pivot = index;
this.invalidateRow(previousFocused); this.invalidateRow(previousFocused);
this.invalidateRow(index); this.invalidateRow(index);
this.selection._updateTree(shouldDebounce);
} }
// Normal selection // Normal selection
else if (!toggleSelection) { else if (!toggleSelection) {
@ -1625,7 +1635,7 @@ function makeRowRenderer(getRowData) {
function formatColumnName(column) { function formatColumnName(column) {
if (column.label in Zotero.Intl.strings) { if (column.label in Zotero.Intl.strings) {
return Zotero.Intl.strings[column.label]; return Zotero.getString(column.label);
} }
else if (/^[^\s]+\w\.\w[^\s]+$/.test(column.label)) { else if (/^[^\s]+\w\.\w[^\s]+$/.test(column.label)) {
try { try {
@ -1641,7 +1651,7 @@ function formatColumnName(column) {
return column.label; return column.label;
} }
module.exports = injectIntl(VirtualizedTable, { forwardRef: true }); module.exports = VirtualizedTable;
module.exports.TreeSelection = TreeSelection; module.exports.TreeSelection = TreeSelection;
module.exports.TreeSelectionStub = TreeSelectionStub; module.exports.TreeSelectionStub = TreeSelectionStub;
module.exports.renderCell = renderCell; module.exports.renderCell = renderCell;

View file

@ -31,7 +31,6 @@
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const { IntlProvider } = require('react-intl');
const TagSelector = require('components/tagSelector.js'); const TagSelector = require('components/tagSelector.js');
const defaults = { const defaults = {
tagColors: new Map(), tagColors: new Map(),
@ -762,9 +761,7 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
static init(domEl, opts) { static init(domEl, opts) {
var ref; var ref;
let elem = ( let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <TagSelectorContainer ref={c => ref = c } {...opts} />
<TagSelectorContainer ref={c => ref = c } {...opts} />
</IntlProvider>
); );
ReactDOM.render(elem, domEl); ReactDOM.render(elem, domEl);
ref.domEl = domEl; ref.domEl = domEl;

View file

@ -51,7 +51,10 @@ var ZoteroContextPane = new function () {
var _itemContexts = []; var _itemContexts = [];
var _notesContexts = []; var _notesContexts = [];
var _globalDeckIndex = [];
var _preventGlobalDeckChange = false;
// Using attribute instead of property to set 'selectedIndex' // Using attribute instead of property to set 'selectedIndex'
// is more reliable // is more reliable
@ -176,6 +179,20 @@ var ZoteroContextPane = new function () {
if (Zotero_Tabs.deck.children.length == 1) { if (Zotero_Tabs.deck.children.length == 1) {
_notesContexts.forEach(x => x.notesListRef.current.setExpanded(false)); _notesContexts.forEach(x => x.notesListRef.current.setExpanded(false));
} }
// Close tab specific notes if tab id no longer exists, but
// do that only when unloaded tab is reloaded
setTimeout(() => {
var contextNodes = Array.from(_notesPaneDeck.children);
for (let contextNode of contextNodes) {
var nodes = Array.from(contextNode.querySelector('.zotero-context-pane-tab-notes-deck').children);
for (let node of nodes) {
var tabID = node.getAttribute('data-tab-id');
if (!document.getElementById(tabID)) {
node.remove();
}
}
}
});
} }
else if (action == 'select') { else if (action == 'select') {
// It seems that changing `hidden` or `collapsed` values might // It seems that changing `hidden` or `collapsed` values might
@ -188,6 +205,14 @@ var ZoteroContextPane = new function () {
_tabCover.hidden = true; _tabCover.hidden = true;
} }
else if (Zotero_Tabs.selectedType == 'reader') { else if (Zotero_Tabs.selectedType == 'reader') {
if (_panesDeck.selectedIndex == 1
&& _notesPaneDeck.selectedPanel.selectedIndex != 2
&& !_preventGlobalDeckChange) {
let libraryID = _notesPaneDeck.selectedPanel.getAttribute('data-library-id');
_globalDeckIndex[libraryID] = _notesPaneDeck.selectedPanel.selectedIndex;
}
_preventGlobalDeckChange = false;
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
if (reader) { if (reader) {
_tabCover.hidden = false; _tabCover.hidden = false;
@ -207,11 +232,25 @@ var ZoteroContextPane = new function () {
var notesContext = _getNotesContext(attachment.libraryID); var notesContext = _getNotesContext(attachment.libraryID);
notesContext.updateFromCache(); notesContext.updateFromCache();
} }
let tabNotesDeck = _notesPaneDeck.selectedPanel.querySelector('.zotero-context-pane-tab-notes-deck');
let selectedIndex = Array.from(tabNotesDeck.children).findIndex(x => x.getAttribute('data-tab-id') == ids[0]);
if (selectedIndex != -1) {
tabNotesDeck.setAttribute('selectedIndex', selectedIndex);
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', 2);
}
else {
let libraryID = _notesPaneDeck.selectedPanel.getAttribute('data-library-id');
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', _globalDeckIndex[libraryID] || 0);
}
})(); })();
} }
_contextPaneSplitter.setAttribute('hidden', false); _contextPaneSplitter.setAttribute('hidden', false);
// It seems that on heavy load (i.e. syncing) the line below doesn't set the correct value
_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
// It seems that on heavy load (i.e. syncing) the line below doesn't set the correct value,
// therefore we repeat the same operation at the end of JS message queue
setTimeout(() => { setTimeout(() => {
_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed')); _contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
}); });
@ -243,9 +282,14 @@ var ZoteroContextPane = new function () {
if (splitter.getAttribute('state') != 'collapsed') { if (splitter.getAttribute('state') != 'collapsed') {
if (_panesDeck.selectedIndex == 1) { if (_panesDeck.selectedIndex == 1) {
var node = _notesPaneDeck.selectedPanel; var libraryContext = _notesPaneDeck.selectedPanel;
if (node.selectedIndex == 1) { // Global note
return node.querySelector('zoteronoteeditor'); if (libraryContext.selectedIndex == 1) {
return libraryContext.querySelector('zoteronoteeditor');
}
// Tab specific child note
else if (libraryContext.selectedIndex == 2) {
return libraryContext.querySelector('.zotero-context-pane-tab-notes-deck').selectedPanel.querySelector('zoteronoteeditor');
} }
} }
} }
@ -419,12 +463,21 @@ var ZoteroContextPane = new function () {
editor.className = 'zotero-context-pane-pinned-note'; editor.className = 'zotero-context-pane-pinned-note';
editor.setAttribute('flex', 1); editor.setAttribute('flex', 1);
noteContainer.append(title, editor); noteContainer.append(title, editor);
var tabNotesContainer = document.createElement('vbox');
var title = document.createElement('vbox');
title.className = 'zotero-context-pane-editor-parent-line';
let tabNotesDeck = document.createElement('deck');
tabNotesDeck.className = 'zotero-context-pane-tab-notes-deck';
tabNotesDeck.setAttribute('flex', 1);
tabNotesContainer.append(title, tabNotesDeck);
let contextNode = document.createElement('deck'); let contextNode = document.createElement('deck');
contextNode.append(list, noteContainer); contextNode.append(list, noteContainer, tabNotesContainer);
_notesPaneDeck.append(contextNode); _notesPaneDeck.append(contextNode);
contextNode.className = 'context-node'; contextNode.className = 'context-node';
contextNode.setAttribute('data-library-id', libraryID);
contextNode.setAttribute('selectedIndex', 0); contextNode.setAttribute('selectedIndex', 0);
editor.returnHandler = () => { editor.returnHandler = () => {
@ -440,8 +493,15 @@ var ZoteroContextPane = new function () {
if (!attachment) { if (!attachment) {
return; return;
} }
var annotations = attachment.getAnnotations().filter(x => x.annotationType != 'ink');
if (!annotations.length) {
return;
}
var note = await Zotero.EditorInstance.createNoteFromAnnotations( var note = await Zotero.EditorInstance.createNoteFromAnnotations(
attachment.getAnnotations().filter(x => x.annotationType != 'ink'), child && attachment.parentID annotations,
{
parentID: child && attachment.parentID
}
); );
_updateAddToNote(); _updateAddToNote();
@ -449,7 +509,7 @@ var ZoteroContextPane = new function () {
input.value = ''; input.value = '';
_updateNotesList(); _updateNotesList();
_setPinnedNote(note.id); _setPinnedNote(note);
} }
function _createNote(child) { function _createNote(child) {
@ -463,10 +523,7 @@ var ZoteroContextPane = new function () {
} }
item.parentID = attachment.parentID; item.parentID = attachment.parentID;
} }
editor.mode = 'edit'; _setPinnedNote(item);
editor.item = item;
editor.parentItem = null;
editor.focus();
_updateAddToNote(); _updateAddToNote();
input.value = ''; input.value = '';
@ -672,7 +729,10 @@ var ZoteroContextPane = new function () {
<NotesList <NotesList
ref={notesListRef} ref={notesListRef}
onClick={(id) => { onClick={(id) => {
_setPinnedNote(id); let item = Zotero.Items.get(id);
if (item) {
_setPinnedNote(item);
}
}} }}
onContextMenu={(id, event) => { onContextMenu={(id, event) => {
document.getElementById('context-pane-list-move-to-trash').setAttribute('disabled', readOnly); document.getElementById('context-pane-list-move-to-trash').setAttribute('disabled', readOnly);
@ -727,21 +787,63 @@ var ZoteroContextPane = new function () {
return !Zotero.Libraries.get(libraryID).editable; return !Zotero.Libraries.get(libraryID).editable;
} }
function _setPinnedNote(itemID) { function _setPinnedNote(item) {
var item = Zotero.Items.get(itemID);
if (!item) {
return;
}
var readOnly = _isLibraryReadOnly(item.libraryID); var readOnly = _isLibraryReadOnly(item.libraryID);
var context = _getNotesContext(item.libraryID); var context = _getNotesContext(item.libraryID);
if (context) { if (context) {
var { editor, node } = context; var { editor, node } = context;
node.setAttribute('selectedIndex', 1);
editor.mode = readOnly ? 'view' : 'edit'; let isChild = false;
editor.item = item; var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
editor.parentItem = null; if (reader) {
let attachment = Zotero.Items.get(reader.itemID);
node.querySelector('.zotero-context-pane-editor-parent-line').innerHTML = ''; if (attachment.parentItemID == item.parentItemID) {
isChild = true;
}
}
var tabNotesDeck = _notesPaneDeck.selectedPanel.querySelector('.zotero-context-pane-tab-notes-deck');
var parentTitleContainer;
if (isChild) {
var vbox = document.createElement('vbox');
vbox.setAttribute('data-tab-id', Zotero_Tabs.selectedID);
vbox.style.display = 'flex';
editor = document.createElement('zoteronoteeditor');
editor.style.display = 'flex';
editor.style.width = '100%';
vbox.append(editor);
tabNotesDeck.append(vbox);
editor.mode = readOnly ? 'view' : 'edit';
editor.item = item;
editor.parentItem = null;
editor.returnHandler = () => {
_panesDeck.setAttribute('selectedIndex', 1);
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', 0);
vbox.remove();
_updateAddToNote();
_preventGlobalDeckChange = true;
};
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', 2);
tabNotesDeck.setAttribute('selectedIndex', tabNotesDeck.children.length - 1);
parentTitleContainer = _notesPaneDeck.selectedPanel.children[2].querySelector('.zotero-context-pane-editor-parent-line');
}
else {
node.setAttribute('selectedIndex', 1);
editor.mode = readOnly ? 'view' : 'edit';
editor.item = item;
editor.parentItem = null;
parentTitleContainer = node.querySelector('.zotero-context-pane-editor-parent-line');
}
editor.focus();
parentTitleContainer.innerHTML = '';
var parentItem = item.parentItem; var parentItem = item.parentItem;
if (parentItem) { if (parentItem) {
var container = document.createElementNS(HTML_NS, 'div'); var container = document.createElementNS(HTML_NS, 'div');
@ -752,7 +854,7 @@ var ZoteroContextPane = new function () {
title.append(parentItem.getDisplayTitle()); title.append(parentItem.getDisplayTitle());
title.className = 'parent-title'; title.className = 'parent-title';
container.append(img, title); container.append(img, title);
node.querySelector('.zotero-context-pane-editor-parent-line').append(container); parentTitleContainer.append(container);
} }
_updateAddToNote(); _updateAddToNote();
} }
@ -835,13 +937,13 @@ var ZoteroContextPane = new function () {
// Info tab // Info tab
var tabInfo = document.createElement('tab'); var tabInfo = document.createElement('tab');
tabInfo.setAttribute('label', Zotero.Intl.strings['zotero.tabs.info.label']); tabInfo.setAttribute('label', Zotero.getString('zotero.tabs.info.label'));
// Tags tab // Tags tab
var tabTags = document.createElement('tab'); var tabTags = document.createElement('tab');
tabTags.setAttribute('label', Zotero.Intl.strings['zotero.tabs.tags.label']); tabTags.setAttribute('label', Zotero.getString('zotero.tabs.tags.label'));
// Related tab // Related tab
var tabRelated = document.createElement('tab'); var tabRelated = document.createElement('tab');
tabRelated.setAttribute('label', Zotero.Intl.strings['zotero.tabs.related.label']); tabRelated.setAttribute('label', Zotero.getString('zotero.tabs.related.label'));
tabs.append(tabInfo, tabTags, tabRelated); tabs.append(tabInfo, tabTags, tabRelated);

View file

@ -68,7 +68,7 @@ async function doAccept() {
); );
// If we successfully created a parent, return it // If we successfully created a parent, return it
if (newItems) { if (newItems.length) {
io.dataOut = { parent: newItems[0] }; io.dataOut = { parent: newItems[0] };
window.close(); window.close();
} }

View file

@ -93,10 +93,11 @@ Zotero_File_Exporter.prototype.save = async function () {
} }
} }
} }
// Otherwise exclude note export translators }
else {
translators = translators.filter(t => !t.configOptions || !t.configOptions.noteTranslator); // Exclude note translators if not exporting notes
} if (!exportingNotes) {
translators = translators.filter(t => !t.configOptions || !t.configOptions.noteTranslator);
} }
// present options dialog // present options dialog
@ -238,26 +239,29 @@ var Zotero_File_Interface = new function() {
/* /*
* exports items to clipboard * exports items to clipboard
*/ */
function exportItemsToClipboard(items, translatorID) { function exportItemsToClipboard(items, format) {
function _translate(items, translatorID, callback) { function _translate(items, format, callback) {
let translation = new Zotero.Translate.Export(); let translation = new Zotero.Translate.Export();
translation.setItems(items.slice()); translation.setItems(items.slice());
translation.setTranslator(translatorID); translation.setTranslator(format.id);
if (format.options) {
translation.setDisplayOptions(format.options);
}
translation.setHandler("done", callback); translation.setHandler("done", callback);
translation.translate(); translation.translate();
} }
// If translating with virtual "Markdown + Rich Text" translator, use Note Markdown and // If translating with virtual "Markdown + Rich Text" translator, use Note Markdown and
// Note HTML instead // Note HTML instead
if (translatorID == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) { if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
translatorID = Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN; let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
_translate(items, translatorID, (obj, worked) => { let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
_translate(items, markdownFormat, (obj, worked) => {
if (!worked) { if (!worked) {
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning'); Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
return; return;
} }
translatorID = Zotero.Translators.TRANSLATOR_ID_NOTE_HTML; _translate(items, htmlFormat, (obj2, worked) => {
_translate(items, translatorID, (obj2, worked) => {
if (!worked) { if (!worked) {
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning'); Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
return; return;
@ -293,14 +297,14 @@ var Zotero_File_Interface = new function() {
}); });
} }
else { else {
_translate(items, translatorID, (obj, worked) => { _translate(items, format, (obj, worked) => {
if (!worked) { if (!worked) {
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning'); Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
return; return;
} }
let text = obj.string; let text = obj.string;
// For Note HTML translator use body content only // For Note HTML translator use body content only
if (translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) { if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1'] let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
.createInstance(Components.interfaces.nsIDOMParser); .createInstance(Components.interfaces.nsIDOMParser);
let doc = parser.parseFromString(text, 'text/html'); let doc = parser.parseFromString(text, 'text/html');
@ -441,10 +445,13 @@ var Zotero_File_Interface = new function() {
var translation; var translation;
if (options.mendeleyCode) { if (options.mendeleyAuth || options.mendeleyCode) {
translation = yield _getMendeleyTranslation(); translation = yield _getMendeleyTranslation();
translation.createNewCollection = createNewCollection; translation.createNewCollection = createNewCollection;
translation.mendeleyAuth = options.mendeleyAuth;
translation.mendeleyCode = options.mendeleyCode; translation.mendeleyCode = options.mendeleyCode;
translation.newItemsOnly = options.newItemsOnly;
translation.relinkOnly = options.relinkOnly;
} }
else { else {
// Check if the file is an SQLite database // Check if the file is an SQLite database
@ -980,7 +987,7 @@ var Zotero_File_Interface = new function() {
if (matchResult) { if (matchResult) {
const mendeleyCode = matchResult[1]; const mendeleyCode = matchResult[1];
Zotero.getMainWindow().setTimeout(() => this.showImportWizard({ mendeleyCode }), 0); Zotero.getMainWindow().setTimeout(() => this.showImportWizard({ mendeleyCode }), 0);
// Clear all cookies to remove access // Clear all cookies to remove access
// //
// This includes unrelated cookies in the central cookie store, but that's fine for // This includes unrelated cookies in the central cookie store, but that's fine for
@ -995,7 +1002,7 @@ var Zotero_File_Interface = new function() {
catch (e) { catch (e) {
Zotero.logError(e); Zotero.logError(e);
} }
win.close(); win.close();
return; return;
} }

View file

@ -9,6 +9,9 @@ const parseCitavi5Quads = (quadsRaw) => {
const ImportCitaviAnnotatons = async (translation) => { const ImportCitaviAnnotatons = async (translation) => {
const IDMap = translation._itemSaver._IDMap; const IDMap = translation._itemSaver._IDMap;
const ZU = translation._sandboxZotero.Utilities; const ZU = translation._sandboxZotero.Utilities;
// stream might be closed by now, re-init to make sure getXML() works
translation._io.init('xml/dom');
const doc = translation._sandboxZotero.getXML(); const doc = translation._sandboxZotero.getXML();
const isCitavi5 = ZU.xpathText(doc, '//CitaviExchangeData/@Version').startsWith('5'); const isCitavi5 = ZU.xpathText(doc, '//CitaviExchangeData/@Version').startsWith('5');
var annotationNodes = ZU.xpath(doc, '//Annotations/Annotation'); var annotationNodes = ZU.xpath(doc, '//Annotations/Annotation');
@ -29,17 +32,29 @@ const ImportCitaviAnnotatons = async (translation) => {
const stageProgress = 50; const stageProgress = 50;
let progress = baseProgress; let progress = baseProgress;
translation.getProgress = () => progress; translation.getProgress = () => progress;
for (var i = 0, n = annotationNodes.length; i < n; i++) { for (var i = 0, n = annotationNodes.length; i < n; i++) {
const id = ZU.xpathText(annotationNodes[i], '@id'); const id = ZU.xpathText(annotationNodes[i], '@id');
const quadsRaw = ZU.xpathText(annotationNodes[i], './Quads'); const quadsRaw = ZU.xpathText(annotationNodes[i], './Quads');
const locationID = ZU.xpathText(annotationNodes[i], './LocationID'); const locationID = ZU.xpathText(annotationNodes[i], './LocationID');
const location = ZU.xpath(doc, `//Locations/Location[@${isCitavi5 ? 'ID' : 'id'}='${locationID}']`)[0]; const location = ZU.xpath(doc, `//Locations/Location[@id='${locationID}']|//Locations/Location[@ID='${locationID}']`)[0];
if (!location) {
Zotero.debug(`Missing <Location> entry for annotation ${id}, skipping...`);
continue;
}
const referenceID = ZU.xpathText(location, './ReferenceID'); const referenceID = ZU.xpathText(location, './ReferenceID');
const entityLink = ZU.xpath(doc, `//EntityLinks/EntityLink[TargetID='${id}']`)[0]; const entityLink = ZU.xpath(doc, `//EntityLinks/EntityLink[TargetID='${id}']`)[0];
if (!entityLink) {
Zotero.debug(`Missing <EntityLink> entry for annotation ${id}, skipping...`);
continue;
}
const entitySourceID = ZU.xpathText(entityLink, './SourceID'); const entitySourceID = ZU.xpathText(entityLink, './SourceID');
const knowledgeItem = ZU.xpath(doc, `//KnowledgeItems/KnowledgeItem[@${isCitavi5 ? 'ID' : 'id'}='${entitySourceID}']`)[0]; const knowledgeItem = ZU.xpath(doc, `//KnowledgeItems/KnowledgeItem[@id='${entitySourceID}']|//KnowledgeItems/KnowledgeItem[@ID='${entitySourceID}']`)[0];
const keywordsIDsText = ZU.xpathText(doc, `//KnowledgeItemKeywords/OnetoN[starts-with(text(), "${entitySourceID}")]`); const keywordsIDsText = ZU.xpathText(doc, `//KnowledgeItemKeywords/OnetoN[starts-with(text(), "${entitySourceID}")]`);
const keywords = keywordsIDsText const keywords = keywordsIDsText
? keywordsIDsText ? keywordsIDsText
@ -134,16 +149,22 @@ const ImportCitaviAnnotatons = async (translation) => {
annotations.push(annotation); annotations.push(annotation);
}); });
annotations = await Zotero.PDFWorker.processCitaviAnnotations(
itemAttachment.getFilePath(), annotations try {
); // eslint-disable-next-line no-await-in-loop
annotations = await Zotero.PDFWorker.processCitaviAnnotations(
annotations.forEach((annotation) => { itemAttachment.getFilePath(), annotations
promises.push(Zotero.Annotations.saveFromJSON( );
itemAttachment, annotation, { skipSelect: true } annotations.forEach((annotation) => {
)); promises.push(Zotero.Annotations.saveFromJSON(
}); itemAttachment, annotation, { skipSelect: true }
));
});
}
catch (e) {
Zotero.debug(`Could not process annotations for attachment item ${itemAttachment.key} (file path: ${itemAttachment.getFilePath()})`);
}
progress = baseProgress + Math.ceil((i / annotationNodes.length) * stageProgress); progress = baseProgress + Math.ceil((i / annotationNodes.length) * stageProgress);
translation._runHandler('itemDone', []); translation._runHandler('itemDone', []);

View file

@ -1,13 +1,19 @@
/* global mendeleyAPIUtils:false */
import FilePicker from 'zotero/modules/filePicker'; import FilePicker from 'zotero/modules/filePicker';
Components.utils.import("resource://gre/modules/Services.jsm");
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyAPIUtils.js");
const { directAuth } = mendeleyAPIUtils;
var Zotero_Import_Wizard = { var Zotero_Import_Wizard = {
_wizard: null, _wizard: null,
_dbs: null, _dbs: null,
_file: null, _file: null,
_translation: null, _translation: null,
_mendeleyOnlineRedirectURLWithCode: null,
_mendeleyCode: null, _mendeleyCode: null,
_mendeleyAuth: null,
_mendeleyHasPreviouslyImported: false,
_mendeleyImporterVersion: 0,
_isZotfileInstalled: false,
init: async function () { init: async function () {
this._wizard = document.getElementById('import-wizard'); this._wizard = document.getElementById('import-wizard');
@ -16,6 +22,18 @@ var Zotero_Import_Wizard = {
// Local import disabled // Local import disabled
//document.getElementById('radio-import-source-mendeley').hidden = false; //document.getElementById('radio-import-source-mendeley').hidden = false;
} }
const extensions = await Zotero.getInstalledExtensions();
this._isZotfileInstalled = !!extensions.find(extName => extName.match(/^ZotFile((?!disabled).)*$/));
const predicateID = Zotero.RelationPredicates.getID('mendeleyDB:documentUUID');
if (predicateID) {
const relSQL = 'SELECT ROWID FROM itemRelations WHERE predicateID = ? LIMIT 1';
this._mendeleyHasPreviouslyImported = !!(await Zotero.DB.valueQueryAsync(relSQL, predicateID));
}
this._mendeleyImporterVersion = parseInt((await Zotero.DB.valueQueryAsync("SELECT value FROM settings WHERE setting='mendeleyImport' AND key='version'")) || 0);
// If no existing collections or non-trash items in the library, don't create a new // If no existing collections or non-trash items in the library, don't create a new
// collection by default // collection by default
@ -34,8 +52,12 @@ var Zotero_Import_Wizard = {
document.getElementById('create-collection-checkbox').removeAttribute('checked'); document.getElementById('create-collection-checkbox').removeAttribute('checked');
} }
} }
if (args && args.pageID) {
this._wizard.goTo(args.pageID);
}
if (args && args.mendeleyCode) { if (args && args.mendeleyCode && Zotero.Prefs.get("import.mendeleyUseOAuth")) {
this._mendeleyCode = args.mendeleyCode; this._mendeleyCode = args.mendeleyCode;
this._wizard.goTo('page-options'); this._wizard.goTo('page-options');
} }
@ -54,10 +76,30 @@ var Zotero_Import_Wizard = {
'import.fileHandling.description', 'import.fileHandling.description',
Zotero.appName Zotero.appName
); );
// Set up Mendeley username/password fields
document.querySelector('label[for="mendeley-username"]').textContent
= Zotero.Utilities.Internal.stringWithColon(Zotero.getString('general.username'));
document.querySelector('label[for="mendeley-password"]').textContent
= Zotero.Utilities.Internal.stringWithColon(Zotero.getString('general.password'));
document.getElementById('mendeley-username').addEventListener('keyup', this.onMendeleyAuthKeyUp.bind(this));
document.getElementById('mendeley-password').addEventListener('keyup', this.onMendeleyAuthKeyUp.bind(this));
document.getElementById('relink-only-checkbox').addEventListener('command', this.onRelinkOnlyChange.bind(this));
// Set from integration.js prompt
if (args && args.relinkOnly) {
document.getElementById('relink-only-checkbox').checked = true;
this.onRelinkOnlyChange();
}
Zotero.Translators.init(); // async Zotero.Translators.init(); // async
}, },
onCancel: function () {
if (this._translation && this._translation.interrupt) {
this._translation.interrupt();
}
},
onModeChosen: async function () { onModeChosen: async function () {
var wizard = this._wizard; var wizard = this._wizard;
@ -70,6 +112,14 @@ var Zotero_Import_Wizard = {
break; break;
case 'radio-import-source-mendeley-online': case 'radio-import-source-mendeley-online':
if (this._isZotfileInstalled) {
this._onDone(
Zotero.getString('general.error'),
Zotero.getString('import.online.blockedByPlugin', 'ZotFile'),
false
);
return;
}
wizard.goTo('mendeley-online-explanation'); wizard.goTo('mendeley-online-explanation');
wizard.canRewind = true; wizard.canRewind = true;
break; break;
@ -103,19 +153,58 @@ var Zotero_Import_Wizard = {
}, },
onMendeleyOnlineShow: async function () { onMendeleyOnlineShow: async function () {
document.getElementById('mendeley-online-description').textContent = Zotero.getString( document.getElementById('mendeley-online-description').textContent = Zotero.Prefs.get("import.mendeleyUseOAuth")
'import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley'] ? Zotero.getString('import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley'])
); : Zotero.getString('import.online.formIntro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley']);
document.getElementById('mendeley-online-description2').textContent = Zotero.getString( document.getElementById('mendeley-online-description2').textContent = Zotero.getString(
'import.online.intro2', [Zotero.appName, 'Mendeley'] 'import.online.intro2', [Zotero.appName, 'Mendeley']
); );
document.getElementById('mendeley-login').style.display = Zotero.Prefs.get("import.mendeleyUseOAuth") ? 'none' : '';
document.getElementById('mendeley-online-login-feedback').style.display = 'none';
this._wizard.canAdvance = Zotero.Prefs.get("import.mendeleyUseOAuth");
}, },
onMendeleyOnlineAdvance: function () { onMendeleyOnlineAdvance: async function () {
if (!this._mendeleyOnlineRedirectURLWithCode) { if (Zotero.Prefs.get("import.mendeleyUseOAuth")) {
Zotero_File_Interface.authenticateMendeleyOnline(); Zotero_File_Interface.authenticateMendeleyOnline();
window.close(); window.close();
} }
else {
const userNameEl = document.getElementById('mendeley-username');
const passwordEl = document.getElementById('mendeley-password');
userNameEl.disabled = true;
passwordEl.disabled = true;
try {
this._mendeleyAuth = await directAuth(userNameEl.value, passwordEl.value);
this._wizard.goTo('page-options');
}
catch (e) {
const feedbackEl = document.getElementById('mendeley-online-login-feedback');
feedbackEl.textContent = Zotero.getString('import.online.wrongCredentials', ['Mendeley']);
feedbackEl.style.display = '';
this._wizard.canAdvance = false; // change to either of the inputs will reset thi
}
finally {
userNameEl.disabled = false;
passwordEl.disabled = false;
}
}
},
onMendeleyAuthKeyUp: function () {
document.getElementById('mendeley-online-login-feedback').style.display = 'none';
this._wizard.canAdvance = document.getElementById('mendeley-username').value.length > 0
&& document.getElementById('mendeley-password').value.length > 0;
},
onRelinkOnlyChange: function () {
if (document.getElementById('relink-only-checkbox').checked) {
document.getElementById('new-items-only-checkbox').checked = true;
document.getElementById('create-collection-checkbox').checked = false;
}
document.getElementById('new-items-only-checkbox').disabled = document.getElementById('relink-only-checkbox').checked;
document.getElementById('create-collection-checkbox').disabled = document.getElementById('relink-only-checkbox').checked;
}, },
goToStart: function () { goToStart: function () {
@ -196,7 +285,17 @@ var Zotero_Import_Wizard = {
onOptionsShown: function () { onOptionsShown: function () {
document.getElementById('file-handling-options').hidden = !!this._mendeleyCode; document.getElementById('file-handling-options').hidden = !!(this._mendeleyAuth || this._mendeleyCode);
const hideExtraMendeleyOptions = !this._mendeleyHasPreviouslyImported || !(this._mendeleyAuth || this._mendeleyCode);
document.getElementById('mendeley-options').hidden = hideExtraMendeleyOptions;
document.getElementById('relink-only-wrapper').hidden = hideExtraMendeleyOptions || this._mendeleyImporterVersion > 0;
document.getElementById('relink-only-more-info').addEventListener('click', function () {
Zotero.launchURL('https://www.zotero.org/support/kb/mendeley_import#using_mendeley_citations');
window.close();
});
if (hideExtraMendeleyOptions) {
document.getElementById('new-items-only-checkbox').removeAttribute('checked');
}
}, },
@ -223,7 +322,7 @@ var Zotero_Import_Wizard = {
onImportStart: async function () { onImportStart: async function () {
if (!this._file && !this._mendeleyCode) { if (!this._file && !(this._mendeleyAuth || this._mendeleyCode)) {
let index = document.getElementById('file-list').selectedIndex; let index = document.getElementById('file-list').selectedIndex;
this._file = this._dbs[index].path; this._file = this._dbs[index].path;
} }
@ -235,10 +334,12 @@ var Zotero_Import_Wizard = {
let result = await Zotero_File_Interface.importFile({ let result = await Zotero_File_Interface.importFile({
file: this._file, file: this._file,
onBeforeImport: this.onBeforeImport.bind(this), onBeforeImport: this.onBeforeImport.bind(this),
addToLibraryRoot: !document.getElementById('create-collection-checkbox') addToLibraryRoot: !document.getElementById('create-collection-checkbox').checked,
.hasAttribute('checked'),
linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1, linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1,
mendeleyCode: this._mendeleyCode mendeleyAuth: this._mendeleyAuth,
mendeleyCode: this._mendeleyCode,
newItemsOnly: document.getElementById('new-items-only-checkbox').checked,
relinkOnly: document.getElementById('relink-only-checkbox').checked
}); });
// Cancelled by user or due to error // Cancelled by user or due to error
@ -248,9 +349,12 @@ var Zotero_Import_Wizard = {
} }
let numItems = this._translation.newItems.length; let numItems = this._translation.newItems.length;
let numRelinked = this._translation.numRelinked;
this._onDone( this._onDone(
Zotero.getString('fileInterface.importComplete'), Zotero.getString('fileInterface.importComplete'),
Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems) document.getElementById('relink-only-checkbox').checked
? Zotero.getString(`fileInterface.itemsWereRelinked`, numRelinked, numRelinked)
: Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems)
); );
} }
catch (e) { catch (e) {

View file

@ -10,6 +10,7 @@
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml"
title="&zotero.import;" title="&zotero.import;"
onwizardcancel="Zotero_Import_Wizard.onCancel()"
onload="Zotero_Import_Wizard.init()"> onload="Zotero_Import_Wizard.init()">
<script src="../include.js"/> <script src="../include.js"/>
@ -36,6 +37,17 @@
> >
<description id="mendeley-online-description" /> <description id="mendeley-online-description" />
<description id="mendeley-online-description2" /> <description id="mendeley-online-description2" />
<html:fieldset id="mendeley-login">
<html:div class="field">
<html:label for="mendeley-username"/>
<html:input type="text" id="mendeley-username" />
</html:div>
<html:div class="field">
<html:label for="mendeley-password"/>
<html:input type="password" id="mendeley-password" />
</html:div>
</html:fieldset>
<description id="mendeley-online-login-feedback" />
</wizardpage> </wizardpage>
<wizardpage pageid="page-file-list" <wizardpage pageid="page-file-list"
@ -66,6 +78,14 @@
onpageshow="Zotero_Import_Wizard.onOptionsShown()" onpageshow="Zotero_Import_Wizard.onOptionsShown()"
onpagerewound="return Zotero_Import_Wizard.goToStart()" onpagerewound="return Zotero_Import_Wizard.goToStart()"
onpageadvanced="Zotero_Import_Wizard.onImportStart()"> onpageadvanced="Zotero_Import_Wizard.onImportStart()">
<vbox id="relink-only-wrapper">
<hbox align="center">
<checkbox id="relink-only-checkbox" label="&zotero.import.online.relinkOnly;" />
<label id="relink-only-more-info" class="zotero-text-link" value="&zotero.general.moreInformation;"/>
</hbox>
</vbox>
<checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" /> <checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" />
<vbox id="file-handling-options"> <vbox id="file-handling-options">
@ -76,6 +96,9 @@
</radiogroup> </radiogroup>
<description id="file-handling-description"/> <description id="file-handling-description"/>
</vbox> </vbox>
<vbox id="mendeley-options">
<checkbox id="new-items-only-checkbox" label="&zotero.import.online.newItemsOnly;" checked="true" />
</vbox>
</wizardpage> </wizardpage>
<wizardpage pageid="page-progress" <wizardpage pageid="page-progress"

View file

@ -1,22 +1,67 @@
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
var mendeleyAPIUtils = (function () { var mendeleyAPIUtils = (function () {
const OAUTH_URL = 'https://www.zotero.org/utils/mendeley/oauth'; const ZOTERO_OAUTH_URL = 'https://www.zotero.org/utils/mendeley/oauth';
const OAUTH_URL = 'https://api.mendeley.com/oauth/token';
const MENDELEY_API_URL = 'https://api.mendeley.com'; const MENDELEY_API_URL = 'https://api.mendeley.com';
const CLIENT_ID = '6';
const CLIENT_NOT_VERY_SECRET = 'JtSAMzFdwC6RAED3RMZU';
const USER_AGENT = 'Mendeley Desktop/1.18';
const getTokens = async (codeOrRefreshToken, isRefresh = false) => { const getTokens = async (url, bodyProps, headers = {}, options = {}) => {
const options = { const body = Object.entries(bodyProps)
body: isRefresh .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
? `grant_type=refresh_token&refresh_token=${codeOrRefreshToken}` .join('&');
: `grant_type=authorization_code&code=${codeOrRefreshToken}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
};
const response = await Zotero.HTTP.request('POST', OAUTH_URL, options);
headers = { ...headers, 'Content-Type': 'application/x-www-form-urlencoded' };
if (!Zotero.Prefs.get('import.mendeleyUseOAuth')) {
headers['User-Agent'] = USER_AGENT;
}
options = { ...options, body, headers, timeout: 30000 };
const response = await Zotero.HTTP.request('POST', url, options);
return JSON.parse(response.responseText); return JSON.parse(response.responseText);
}; };
const directAuth = async (username, password, headers = {}, options = {}) => {
const bodyProps = {
client_id: CLIENT_ID, // eslint-disable-line camelcase
client_secret: CLIENT_NOT_VERY_SECRET, // eslint-disable-line camelcase
grant_type: 'password', // eslint-disable-line camelcase
password,
scope: 'all',
username
};
return getTokens(OAUTH_URL, bodyProps, headers, options);
};
const codeAuth = async (code, headers = {}, options = {}) => {
const bodyProps = {
grant_type: 'authorization_code', // eslint-disable-line camelcase
code,
};
return getTokens(ZOTERO_OAUTH_URL, bodyProps, headers, options);
};
const refreshAuth = async (refreshToken, headers = {}, options = {}) => {
const bodyProps = {
grant_type: 'refresh_token', // eslint-disable-line camelcase
refresh_token: refreshToken, // eslint-disable-line camelcase
};
if (!Zotero.Prefs.get('import.mendeleyUseOAuth')) {
bodyProps.client_id = CLIENT_ID; // eslint-disable-line camelcase
bodyProps.client_secret = CLIENT_NOT_VERY_SECRET; // eslint-disable-line camelcase
}
return getTokens(
Zotero.Prefs.get('import.mendeleyUseOAuth') ? ZOTERO_OAUTH_URL : OAUTH_URL,
bodyProps, headers, options
);
};
const getNextLinkFromResponse = (response) => { const getNextLinkFromResponse = (response) => {
let next = null; let next = null;
let links = response.getResponseHeader('link'); let links = response.getResponseHeader('link');
@ -33,7 +78,7 @@ const getNextLinkFromResponse = (response) => {
const apiFetchUrl = async (tokens, url, headers = {}, options = {}) => { const apiFetchUrl = async (tokens, url, headers = {}, options = {}) => {
headers = { ...headers, Authorization: `Bearer ${tokens.access_token}` }; headers = { ...headers, Authorization: `Bearer ${tokens.access_token}` };
options = { ...options, headers }; options = { ...options, headers, timeout: 60000 };
const method = 'GET'; const method = 'GET';
// Run the request. If we see 401 or 403, try to refresh tokens and run the request again // Run the request. If we see 401 or 403, try to refresh tokens and run the request again
@ -42,7 +87,7 @@ const apiFetchUrl = async (tokens, url, headers = {}, options = {}) => {
} }
catch (e) { catch (e) {
if (e.status === 401 || e.status === 403) { if (e.status === 401 || e.status === 403) {
const newTokens = await getTokens(tokens.refresh_token, true); const newTokens = await refreshAuth(tokens.refresh_token);
// update tokens in the tokens object and in the header for next request // update tokens in the tokens object and in the header for next request
tokens.access_token = newTokens.access_token; // eslint-disable-line camelcase tokens.access_token = newTokens.access_token; // eslint-disable-line camelcase
tokens.refresh_token = newTokens.refresh_token; // eslint-disable-line camelcase tokens.refresh_token = newTokens.refresh_token; // eslint-disable-line camelcase
@ -64,21 +109,22 @@ const get = async (tokens, endPoint, params = {}, headers = {}, options = {}) =>
return JSON.parse(response.responseText); return JSON.parse(response.responseText);
}; };
const getAll = async (tokens, endPoint, params = {}, headers = {}, options = {}) => { const getAll = async (tokens, endPoint, params = {}, headers = {}, options = {}, interruptChecker = () => {}) => {
const PER_PAGE = endPoint === 'annotations' ? 200 : 500; const PER_PAGE = endPoint === 'annotations' ? 200 : 500;
const response = await apiFetch(tokens, endPoint, { ...params, limit: PER_PAGE }, headers, options); const response = await apiFetch(tokens, endPoint, { ...params, limit: PER_PAGE }, headers, options);
var next = getNextLinkFromResponse(response); var next = getNextLinkFromResponse(response);
var data = JSON.parse(response.responseText); var data = JSON.parse(response.responseText);
interruptChecker();
while (next) { while (next) {
const response = await apiFetchUrl(tokens, next, headers, options); //eslint-disable-line no-await-in-loop const response = await apiFetchUrl(tokens, next, headers, options); //eslint-disable-line no-await-in-loop
data = [...data, ...JSON.parse(response.responseText)]; data = [...data, ...JSON.parse(response.responseText)];
next = getNextLinkFromResponse(response); next = getNextLinkFromResponse(response);
interruptChecker();
} }
return data; return data;
}; };
return { codeAuth, directAuth, getNextLinkFromResponse, apiFetch, apiFetchUrl, get, getAll };
return { getNextLinkFromResponse, getTokens, apiFetch, apiFetchUrl, get, getAll };
})(); })();

View file

@ -1,29 +1,67 @@
/* global map:false, mendeleyOnlineMappings:false, mendeleyAPIUtils:false */ /* eslint-disable no-await-in-loop, camelcase */
var EXPORTED_SYMBOLS = ["Zotero_Import_Mendeley"]; /* global mendeleyDBMaps:false, mendeleyOnlineMappings:false, mendeleyAPIUtils:false */
var EXPORTED_SYMBOLS = ["Zotero_Import_Mendeley"]; //eslint-disable-line no-unused-vars
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm"); Components.utils.import("resource://gre/modules/osfile.jsm");
Services.scriptloader.loadSubScript("chrome://zotero/content/include.js"); Services.scriptloader.loadSubScript("chrome://zotero/content/include.js");
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyOnlineMappings.js"); Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyOnlineMappings.js");
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyAPIUtils.js"); Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyAPIUtils.js");
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleySchemaMap.js");
const importerVersion = 1;
const { apiTypeToDBType, apiFieldToDBField } = mendeleyOnlineMappings; const { apiTypeToDBType, apiFieldToDBField } = mendeleyOnlineMappings;
const { apiFetch, get, getAll, getTokens } = mendeleyAPIUtils; const { apiFetch, codeAuth, directAuth, get, getAll } = mendeleyAPIUtils;
const colorMap = new Map();
colorMap.set('rgb(255, 245, 173)', '#ffd400');
colorMap.set('#fff5ad', '#ffd400');
colorMap.set('rgb(255, 181, 182)', '#ff6666');
colorMap.set('#ffb5b6', '#ff6666');
colorMap.set('rgb(186, 226, 255)', '#2ea8e5');
colorMap.set('#bae2ff', '#2ea8e5');
colorMap.set('rgb(211, 194, 255)', '#a28ae5');
colorMap.set('#d3c2ff', '#a28ae5');
colorMap.set('rgb(220, 255, 176)', '#5fb236');
colorMap.set('#dcffb0', '#5fb236');
var Zotero_Import_Mendeley = function () { var Zotero_Import_Mendeley = function () {
this.createNewCollection = null; this.createNewCollection = null;
this.linkFiles = null; this.linkFiles = null;
this.newItems = []; this.newItems = [];
this.mendeleyCode = null; this.newCollections = [];
this.mendeleyAuth = null;
this.newItemsOnly = false;
this.relinkOnly = false;
this.numRelinked = 0;
this._tokens = null; this._tokens = null;
this._db; this._db = null;
this._file; this._file = null;
this._saveOptions = null; this._saveOptions = null;
this._itemDone; this._itemDone = () => {};
this._progress = 0; this._progress = 0;
this._progressMax; this._progressMax = 0;
this._tmpFilesToDelete = []; this._tmpFilesToDelete = [];
this._caller = null;
this._interrupted = false;
this._totalSize = 0;
this._started = Date.now();
this._interruptChecker = (tickProgress = false) => {
if (this._interrupted) {
throw new Error(`Mendeley import interrupted!
Started: ${this._started} (${Math.round((Date.now() - this._started) / 1000)}s ago)
Progress: ${this._progress} / ${this._progressMax}
New items created: ${this.newItems.length}
Total size of files to download: ${Math.round(this._totalSize / 1024)}KB
`);
}
if (tickProgress) {
this._progress++;
this._itemDone();
}
};
}; };
Zotero_Import_Mendeley.prototype.setLocation = function (file) { Zotero_Import_Mendeley.prototype.setLocation = function (file) {
@ -32,9 +70,9 @@ Zotero_Import_Mendeley.prototype.setLocation = function (file) {
Zotero_Import_Mendeley.prototype.setHandler = function (name, handler) { Zotero_Import_Mendeley.prototype.setHandler = function (name, handler) {
switch (name) { switch (name) {
case 'itemDone': case 'itemDone':
this._itemDone = handler; this._itemDone = handler;
break; break;
} }
}; };
@ -56,35 +94,19 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
skipSelect: true, skipSelect: true,
...(options.saveOptions || {}) ...(options.saveOptions || {})
}; };
if (true) { this.newItemsOnly = this.newItemsOnly || this.relinkOnly;
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleySchemaMap.js");
}
// TEMP: Load uncached from ~/zotero-client for development
else {
Components.utils.import("resource://gre/modules/FileUtils.jsm");
let file = FileUtils.getDir("Home", []);
file = OS.Path.join(file.path, 'zotero-client', 'chrome', 'content', 'zotero', 'import', 'mendeley', 'mendeleySchemaMap.js');
let fileURI = OS.Path.toFileURI(file);
let xmlhttp = await Zotero.HTTP.request(
'GET',
fileURI,
{
noCache: true,
responseType: 'text'
}
);
eval(xmlhttp.response);
}
const libraryID = options.libraryID || Zotero.Libraries.userLibraryID; const libraryID = options.libraryID || Zotero.Libraries.userLibraryID;
const { key: rootCollectionKey } = options.collections const { key: rootCollectionKey } = options.collections
? Zotero.Collections.getLibraryAndKeyFromID(options.collections[0]) ? Zotero.Collections.getLibraryAndKeyFromID(options.collections[0])
: {}; : {};
Zotero.debug(`Begining Mendeley import at ${this._started}. libraryID: ${libraryID}, linkFiles: ${this.linkFiles}, rootCollectionKey: ${rootCollectionKey}, newItemsOnly: ${this.newItemsOnly}, relinkOnly: ${this.relinkOnly}`);
// TODO: Get appropriate version based on schema version // TODO: Get appropriate version based on schema version
const mapVersion = 83; const mapVersion = 83;
map = map[mapVersion]; const map = mendeleyDBMaps[mapVersion];
const mendeleyGroupID = 0; const mendeleyGroupID = 0;
@ -94,28 +116,42 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
if (this._file) { if (this._file) {
this._db = new Zotero.DBConnection(this._file); this._db = new Zotero.DBConnection(this._file);
} }
try { try {
if (this._file && !await this._isValidDatabase()) { if (this._file && !await this._isValidDatabase()) {
throw new Error("Not a valid Mendeley database"); throw new Error("Not a valid Mendeley database");
} }
if (this.mendeleyCode) { if (this.mendeleyAuth) {
this._tokens = await getTokens(this.mendeleyCode); this._tokens = this.mendeleyAuth;
}
else if (this.mendeleyCode) {
this._tokens = await codeAuth(this.mendeleyCode);
} }
if (!this._file && !this._tokens) { if (!this._file && !this._tokens) {
throw new Error("Missing import token"); throw new Error("Missing import token");
} }
const folders = this._tokens // we don't know how long the import will be but want to show progress to give
? await this._getFoldersAPI(mendeleyGroupID) // feedback that import has started so we arbitrary set progress at 2%
: await this._getFoldersDB(mendeleyGroupID); this._progress = 1;
this._progressMax = 50;
this._itemDone();
const collectionJSON = this._foldersToAPIJSON(folders, rootCollectionKey); let folderKeys = new Map();
const folderKeys = this._getFolderKeys(collectionJSON); if(!this.relinkOnly) {
await this._saveCollections(libraryID, collectionJSON, folderKeys); const folders = this._tokens
? await this._getFoldersAPI(mendeleyGroupID)
: await this._getFoldersDB(mendeleyGroupID);
const collectionJSON = this._foldersToAPIJSON(folders, rootCollectionKey);
folderKeys = this._getFolderKeys(collectionJSON);
await this._saveCollections(libraryID, collectionJSON, folderKeys);
}
this._interruptChecker(true);
// //
// Items // Items
// //
@ -123,40 +159,59 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
? await this._getDocumentsAPI(mendeleyGroupID) ? await this._getDocumentsAPI(mendeleyGroupID)
: await this._getDocumentsDB(mendeleyGroupID); : await this._getDocumentsDB(mendeleyGroupID);
this._progressMax = documents.length; // Update progress to reflect items to import and remaining meta data stages
// We arbitrary set progress at approx 4%. We then add 8, one "tick" for each remaining meta data download.
this._progress = Math.max(Math.floor(0.04 * documents.length), 2);
this._progressMax = documents.length + this._progress + 8;
// Get various attributes mapped to document ids // Get various attributes mapped to document ids
let urls = this._tokens let urls = this._tokens
? await this._getDocumentURLsAPI(documents) ? await this._getDocumentURLsAPI(documents)
: await this._getDocumentURLsDB(mendeleyGroupID); : await this._getDocumentURLsDB(mendeleyGroupID);
this._interruptChecker(true);
let creators = this._tokens let creators = this._tokens
? await this._getDocumentCreatorsAPI(documents) ? await this._getDocumentCreatorsAPI(documents)
: await this._getDocumentCreatorsDB(mendeleyGroupID, map.creatorTypes); : await this._getDocumentCreatorsDB(mendeleyGroupID, map.creatorTypes);
this._interruptChecker(true);
let tags = this._tokens let tags = this._tokens
? await this._getDocumentTagsAPI(documents) ? await this._getDocumentTagsAPI(documents)
: await this._getDocumentTagsDB(mendeleyGroupID); : await this._getDocumentTagsDB(mendeleyGroupID);
let collections = this._tokens this._interruptChecker(true);
let collections = this.relinkOnly ? new Map() : this._tokens
? await this._getDocumentCollectionsAPI(documents, rootCollectionKey, folderKeys) ? await this._getDocumentCollectionsAPI(documents, rootCollectionKey, folderKeys)
: await this._getDocumentCollectionsDB(mendeleyGroupID, documents, rootCollectionKey, folderKeys); : await this._getDocumentCollectionsDB(mendeleyGroupID, documents, rootCollectionKey, folderKeys);
let files = this._tokens this._interruptChecker(true);
let files = this.relinkOnly ? new Map() : this._tokens
? await this._getDocumentFilesAPI(documents) ? await this._getDocumentFilesAPI(documents)
: await this._getDocumentFilesDB(mendeleyGroupID); : await this._getDocumentFilesDB(mendeleyGroupID);
this._interruptChecker(true);
let annotations = this._tokens let annotations = this._tokens
? await this._getDocumentAnnotationsAPI(mendeleyGroupID) ? await this._getDocumentAnnotationsAPI(mendeleyGroupID)
: await this._getDocumentAnnotationsDB(mendeleyGroupID); : await this._getDocumentAnnotationsDB(mendeleyGroupID);
this._interruptChecker(true);
let profile = this._tokens let profile = this._tokens
? await this._getProfileAPI() ? await this._getProfileAPI()
: await this._getProfileDB(); : await this._getProfileDB();
this._interruptChecker(true);
let groups = this._tokens let groups = this._tokens
? await this._getGroupsAPI() ? await this._getGroupsAPI()
: await this._getGroupsDB(); : await this._getGroupsDB();
this._interruptChecker(true);
const fileHashLookup = new Map(); const fileHashLookup = new Map();
for (let [documentID, fileEntries] of files) { for (let [documentID, fileEntries] of files) {
@ -165,7 +220,6 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
} }
} }
for (let group of groups) { for (let group of groups) {
let groupAnnotations = this._tokens let groupAnnotations = this._tokens
? await this._getDocumentAnnotationsAPI(group.id, profile.id) ? await this._getDocumentAnnotationsAPI(group.id, profile.id)
@ -262,12 +316,20 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
annotations.get(document.id) annotations.get(document.id)
); );
} }
this.newItems.push(Zotero.Items.get(documentIDMap.get(document.id))); this._interruptChecker(true);
this._progress++;
if (this._itemDone) {
this._itemDone();
}
} }
if (this.newItemsOnly && rootCollectionKey && this.newItems.length === 0) {
Zotero.debug(`Mendeley Import detected no new items, removing import collection containing ${this.newCollections.length} collections created during the import`);
const rootCollection = await Zotero.Collections.getAsync(options.collections[0]);
await rootCollection.eraseTx(this._saveOptions);
}
await Zotero.DB.queryAsync("REPLACE INTO settings VALUES ('mendeleyImport', 'version', ?)", importerVersion);
Zotero.debug(`Completed Mendeley import in ${Math.round((Date.now() - this._started) / 1000)}s. (Started: ${this._started})`);
}
catch (e) {
Zotero.logError(e);
} }
finally { finally {
try { try {
@ -275,6 +337,7 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
await this._db.closeDatabase(); await this._db.closeDatabase();
} }
if (this._tokens) { if (this._tokens) {
Zotero.debug(`Clearing ${this._tmpFilesToDelete.length} temporary files after Mendeley import`);
await Promise.all( await Promise.all(
this._tmpFilesToDelete.map(f => this._removeTemporaryFile(f)) this._tmpFilesToDelete.map(f => this._removeTemporaryFile(f))
); );
@ -288,14 +351,20 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
} }
}; };
Zotero_Import_Mendeley.prototype.interrupt = function () {
this._interrupted = true;
if (this._caller) {
this._caller.stop();
}
};
Zotero_Import_Mendeley.prototype._removeTemporaryFile = async function (file) { Zotero_Import_Mendeley.prototype._removeTemporaryFile = async function (file) {
const containingDir = OS.Path.dirname(file);
try { try {
await Zotero.File.removeIfExists(file); await Zotero.File.removeIfExists(file);
await OS.File.removeEmptyDir(containingDir);
} }
catch (e) { catch (e) {
Zotero.logError(e); Zotero.logError("Error while removing temporary file " + file + ": " + e);
} }
}; };
@ -345,7 +414,7 @@ Zotero_Import_Mendeley.prototype._getFoldersAPI = async function (groupID) {
if (groupID && groupID !== 0) { if (groupID && groupID !== 0) {
params.group_id = groupID; //eslint-disable-line camelcase params.group_id = groupID; //eslint-disable-line camelcase
} }
return (await getAll(this._tokens, 'folders', params, headers)).map(f => ({ return (await getAll(this._tokens, 'folders', params, headers, {}, this._interruptChecker)).map(f => ({
id: f.id, id: f.id,
uuid: f.id, uuid: f.id,
name: f.name, name: f.name,
@ -365,11 +434,11 @@ Zotero_Import_Mendeley.prototype._foldersToAPIJSON = function (folderRows, paren
}; };
Zotero_Import_Mendeley.prototype._getFolderDescendents = function (folderID, folderKey, folderRows, maxDepth) { Zotero_Import_Mendeley.prototype._getFolderDescendents = function (folderID, folderKey, folderRows, maxDepth) {
if (maxDepth == 0) return [] if (maxDepth == 0) return [];
var descendents = []; var descendents = [];
var children = folderRows var children = folderRows
.filter(f => f.parentId == folderID) .filter(f => f.parentId == folderID)
.map(f => { .map((f) => {
let c = { let c = {
folderID: f.id, folderID: f.id,
remoteUUID: f.remoteUuid, remoteUUID: f.remoteUuid,
@ -431,6 +500,7 @@ Zotero_Import_Mendeley.prototype._saveCollections = async function (libraryID, j
collection.key = collectionJSON.key; collection.key = collectionJSON.key;
await collection.loadPrimaryData(); await collection.loadPrimaryData();
} }
this.newCollections.push(collection);
} }
// Remove external ids before saving // Remove external ids before saving
@ -492,11 +562,19 @@ Zotero_Import_Mendeley.prototype._getDocumentsAPI = async function (groupID) {
} }
return (await getAll(this._tokens, 'documents', params, headers)).map(d => ({ return (await getAll(this._tokens, 'documents', params, headers, {}, this._interruptChecker)).map(d => {
...d, const processedDocument = { ...d, remoteUuid: d.id };
uuid: d.id,
remoteUuid: d.id try {
})); const clientData = JSON.parse(d.client_data);
processedDocument.uuid = clientData.desktop_id ? clientData.desktop_id : d.id;
}
catch (_) {
processedDocument.uuid = d.id;
}
return processedDocument;
});
}; };
/** /**
@ -558,7 +636,12 @@ Zotero_Import_Mendeley.prototype._getDocumentCreatorsAPI = async function (docum
const authors = (doc.authors || []).map(c => this._makeCreator('author', c.first_name, c.last_name)); const authors = (doc.authors || []).map(c => this._makeCreator('author', c.first_name, c.last_name));
const editors = (doc.editors || []).map(c => this._makeCreator('editor', c.first_name, c.last_name)); const editors = (doc.editors || []).map(c => this._makeCreator('editor', c.first_name, c.last_name));
const translators = (doc.translators || []).map(c => this._makeCreator('translator', c.first_name, c.last_name)); const translators = (doc.translators || []).map(c => this._makeCreator('translator', c.first_name, c.last_name));
map.set(doc.id, [...authors, ...editors, ...translators]); const creators = [...authors, ...editors, ...translators];
const validCreators = creators.filter(c => c.name || c.firstName || c.lastName);
if (creators.length !== validCreators.length) {
Zotero.debug(`Discarding ${creators.length - validCreators.length} invalid creators for document ${doc.id}`);
}
map.set(doc.id, validCreators);
} }
return map; return map;
}; };
@ -597,7 +680,10 @@ Zotero_Import_Mendeley.prototype._getDocumentTagsDB = async function (groupID) {
Zotero_Import_Mendeley.prototype._getDocumentTagsAPI = async function (documents) { Zotero_Import_Mendeley.prototype._getDocumentTagsAPI = async function (documents) {
var map = new Map(); var map = new Map();
for (let doc of documents) { for (let doc of documents) {
const tags = [...(doc.tags || []).map(tag => ({ tag, type: 0 })), ...(doc.keywords || []).map(tag => ({ tag, type: 1 }))]; const tags = [
...(doc.tags || []).map(tag => ({ tag, type: 0 })),
...(doc.keywords || []).map(tag => ({ tag, type: 1 }))
].filter(t => t.tag && t.tag.trim());
map.set(doc.id, tags); map.set(doc.id, tags);
} }
return map; return map;
@ -693,12 +779,13 @@ Zotero_Import_Mendeley.prototype._fetchFile = async function (fileID, filePath)
Zotero_Import_Mendeley.prototype._getDocumentFilesAPI = async function (documents) { Zotero_Import_Mendeley.prototype._getDocumentFilesAPI = async function (documents) {
const map = new Map(); const map = new Map();
let totalSize = 0; this._totalSize = 0;
Components.utils.import("resource://zotero/concurrentCaller.js"); Components.utils.import("resource://zotero/concurrentCaller.js");
var caller = new ConcurrentCaller({ this._caller = new ConcurrentCaller({
numConcurrent: 6, numConcurrent: 6,
onError: e => Zotero.logError(e), onError: e => Zotero.logError(e),
logger: Zotero.debug,
Promise: Zotero.Promise Promise: Zotero.Promise
}); });
@ -712,7 +799,7 @@ Zotero_Import_Mendeley.prototype._getDocumentFilesAPI = async function (document
let tmpFile = OS.Path.join(Zotero.getTempDirectory().path, `m-api-${file.id}.${ext}`); let tmpFile = OS.Path.join(Zotero.getTempDirectory().path, `m-api-${file.id}.${ext}`);
this._tmpFilesToDelete.push(tmpFile); this._tmpFilesToDelete.push(tmpFile);
caller.add(this._fetchFile.bind(this, file.id, tmpFile)); this._caller.add(this._fetchFile.bind(this, file.id, tmpFile));
files.push({ files.push({
fileURL: OS.Path.toFileURI(tmpFile), fileURL: OS.Path.toFileURI(tmpFile),
title: file.file_name || '', title: file.file_name || '',
@ -720,13 +807,14 @@ Zotero_Import_Mendeley.prototype._getDocumentFilesAPI = async function (document
hash: file.filehash, hash: file.filehash,
fileBaseName fileBaseName
}); });
totalSize += file.size; this._totalSize += file.size;
this._progressMax += 1; this._progressMax += 1;
} }
map.set(doc.id, files); map.set(doc.id, files);
} }
// TODO: check if enough space available totalSize // TODO: check if enough space available totalSize
await caller.runAll(); await this._caller.runAll();
this._caller = null;
return map; return map;
}; };
@ -823,7 +911,7 @@ Zotero_Import_Mendeley.prototype._getDocumentAnnotationsAPI = async function (gr
} }
const map = new Map(); const map = new Map();
(await getAll(this._tokens, 'annotations', params, { Accept: 'application/vnd.mendeley-annotation.1+json' })) (await getAll(this._tokens, 'annotations', params, { Accept: 'application/vnd.mendeley-annotation.1+json' }, {}, this._interruptChecker))
.forEach((a) => { .forEach((a) => {
if (profileID !== null && a.profile_id !== profileID) { if (profileID !== null && a.profile_id !== profileID) {
// optionally filter annotations by profile id // optionally filter annotations by profile id
@ -885,7 +973,7 @@ Zotero_Import_Mendeley.prototype._getGroupsAPI = async function () {
const params = { type: 'all' }; const params = { type: 'all' };
const headers = { Accept: 'application/vnd.mendeley-group-list+json' }; const headers = { Accept: 'application/vnd.mendeley-group-list+json' };
return getAll(this._tokens, 'groups/v2', params, headers); return getAll(this._tokens, 'groups/v2', params, headers, {}, this._interruptChecker);
}; };
Zotero_Import_Mendeley.prototype._getGroupsDB = async function () { Zotero_Import_Mendeley.prototype._getGroupsDB = async function () {
@ -985,14 +1073,14 @@ Zotero_Import_Mendeley.prototype._documentToAPIJSON = async function (map, docum
for (let field in parent) { for (let field in parent) {
switch (field) { switch (field) {
case 'itemType': case 'itemType':
case 'key': case 'key':
case 'parentItem': case 'parentItem':
case 'note': case 'note':
case 'creators': case 'creators':
case 'dateAdded': case 'dateAdded':
case 'dateModified': case 'dateModified':
continue; continue;
} }
// Move unknown/invalid fields to Extra // Move unknown/invalid fields to Extra
@ -1035,7 +1123,7 @@ Zotero_Import_Mendeley.prototype._documentToAPIJSON = async function (map, docum
// seriesEditor isn't valid on some item types (e.g., book) // seriesEditor isn't valid on some item types (e.g., book)
if (creator.creatorType == 'seriesEditor' if (creator.creatorType == 'seriesEditor'
&& !Zotero.CreatorTypes.isValidForItemType( && !Zotero.CreatorTypes.isValidForItemType(
Zotero.CreatorTypes.getID('seriesEditor'), itemTypeID)) { Zotero.CreatorTypes.getID('seriesEditor'), itemTypeID)) {
creator.creatorType = 'editor'; creator.creatorType = 'editor';
} }
} }
@ -1160,7 +1248,6 @@ Zotero_Import_Mendeley.prototype._processField = function (parent, children, zFi
} }
else { else {
Zotero.warn(`Unknown function subfield: ${subfield}`); Zotero.warn(`Unknown function subfield: ${subfield}`);
return;
} }
} }
else { else {
@ -1191,20 +1278,21 @@ Zotero_Import_Mendeley.prototype._makeCreator = function (creatorType, firstName
Zotero_Import_Mendeley.prototype._addExtraField = function (extra, field, val) { Zotero_Import_Mendeley.prototype._addExtraField = function (extra, field, val) {
// Strip the field if it appears at the beginning of the value (to avoid "DOI: DOI: 10...") // Strip the field if it appears at the beginning of the value (to avoid "DOI: DOI: 10...")
if (typeof val == 'string') { if (typeof val == 'string') {
val = val.replace(new RegExp(`^${field}:\s*`, 'i'), ""); val = val.replace(new RegExp(`^${field}:\\s*`, 'i'), "");
} }
extra = extra ? extra + '\n' : ''; extra = extra ? extra + '\n' : '';
if (field != 'arXiv') { if (field != 'arXiv') {
field = field[0].toUpperCase() + field.substr(1); field = field[0].toUpperCase() + field.substr(1);
field = field.replace(/([a-z])([A-Z][a-z])/, "$1 $2"); field = field.replace(/([a-z])([A-Z][a-z])/, "$1 $2");
} }
return extra + `${field}: ${val}`; return extra + `${field}: ${val}`;
}; };
Zotero_Import_Mendeley.prototype._convertNote = function (note) { Zotero_Import_Mendeley.prototype._convertNote = function (note) {
return note return note
// Add newlines after <br> // Add newlines after <br>
.replace(/<br\s*\/>/g, '<br\/>\n') .replace(/<br\s*\/>/g, '<br/>\n')
// //
// Legacy pre-HTML stuff // Legacy pre-HTML stuff
// //
@ -1228,16 +1316,19 @@ Zotero_Import_Mendeley.prototype._saveItems = async function (libraryID, json) {
var lastExistingParentItem; var lastExistingParentItem;
for (let i = 0; i < json.length; i++) { for (let i = 0; i < json.length; i++) {
let itemJSON = json[i]; let itemJSON = json[i];
let isMappedToExisting = false;
// Check if the item has been previously imported // Check if the item has been previously imported
let item = await this._findExistingItem(libraryID, itemJSON, lastExistingParentItem); let item = await this._findExistingItem(libraryID, itemJSON, lastExistingParentItem);
if (item) { if (item) {
isMappedToExisting = true;
if (item.isRegularItem()) { if (item.isRegularItem()) {
lastExistingParentItem = item; lastExistingParentItem = item;
// Update any child items to point to the existing item's key instead of the // Update any child items to point to the existing item's key instead of the
// new generated one // new generated one
this._updateParentKeys('item', json, i + 1, itemJSON.key, item.key); this._updateParentKeys('item', json, i + 1, itemJSON.key, item.key);
this.numRelinked++;
// Leave item in any collections it's in // Leave item in any collections it's in
itemJSON.collections = item.getCollections() itemJSON.collections = item.getCollections()
@ -1255,12 +1346,33 @@ Zotero_Import_Mendeley.prototype._saveItems = async function (libraryID, json) {
await item.loadPrimaryData(); await item.loadPrimaryData();
} }
} }
if(this.relinkOnly && !isMappedToExisting) {
continue;
}
// Remove external id before save // Remove external id before save
let toSave = Object.assign({}, itemJSON); let toSave = Object.assign({}, itemJSON);
delete toSave.documentID; delete toSave.documentID;
item.fromJSON(toSave); if ((this.newItemsOnly && !isMappedToExisting) || !this.newItemsOnly) {
if (isMappedToExisting) {
// dateAdded shouldn't change on an updated item. See #2881
delete toSave.dateAdded;
}
item.fromJSON(toSave);
this.newItems.push(item);
}
else if (isMappedToExisting && toSave.relations) {
const predicate = 'mendeleyDB:documentUUID';
const existingRels = item.getRelationsByPredicate(predicate);
const newRel = toSave.relations[predicate];
if (existingRels.length && newRel && existingRels[0] !== newRel) {
Zotero.debug(`Migrating relation ${predicate} for existing item ${item.key} from ${existingRels[0]} to ${newRel}`);
item.removeRelation(predicate, existingRels[0]);
item.addRelation(predicate, newRel);
}
}
await item.saveTx({ await item.saveTx({
skipDateModifiedUpdate: true, skipDateModifiedUpdate: true,
...this._saveOptions ...this._saveOptions
@ -1360,7 +1472,7 @@ Zotero_Import_Mendeley.prototype._findExistingItem = async function (libraryID,
Zotero.debug(`Found existing item ${existingItem.libraryKey} for ` Zotero.debug(`Found existing item ${existingItem.libraryKey} for `
+ `${predicate} ${itemJSON.relations[predicate]}`); + `${predicate} ${itemJSON.relations[predicate]}`);
return existingItem; return existingItem;
} };
Zotero_Import_Mendeley.prototype._getItemByRelation = async function (libraryID, predicate, object) { Zotero_Import_Mendeley.prototype._getItemByRelation = async function (libraryID, predicate, object) {
@ -1443,7 +1555,7 @@ Zotero_Import_Mendeley.prototype._saveFilesAndAnnotations = async function (file
Zotero.logError(e); Zotero.logError(e);
} }
} }
} };
Zotero_Import_Mendeley.prototype._findExistingFile = function (parentItemID, file) { Zotero_Import_Mendeley.prototype._findExistingFile = function (parentItemID, file) {
@ -1460,7 +1572,7 @@ Zotero_Import_Mendeley.prototype._findExistingFile = function (parentItemID, fil
} }
} }
return false; return false;
} };
Zotero_Import_Mendeley.prototype._isDownloadedFile = function (path) { Zotero_Import_Mendeley.prototype._isDownloadedFile = function (path) {
var parentDir = OS.Path.dirname(path); var parentDir = OS.Path.dirname(path);
@ -1468,7 +1580,7 @@ Zotero_Import_Mendeley.prototype._isDownloadedFile = function (path) {
|| parentDir.endsWith(OS.Path.join('Local', 'Mendeley Ltd', 'Mendeley Desktop', 'Downloaded')) || parentDir.endsWith(OS.Path.join('Local', 'Mendeley Ltd', 'Mendeley Desktop', 'Downloaded'))
|| parentDir.endsWith(OS.Path.join('Local', 'Mendeley Ltd.', 'Mendeley Desktop', 'Downloaded')) || parentDir.endsWith(OS.Path.join('Local', 'Mendeley Ltd.', 'Mendeley Desktop', 'Downloaded'))
|| parentDir.endsWith(OS.Path.join('data', 'Mendeley Ltd.', 'Mendeley Desktop', 'Downloaded')); || parentDir.endsWith(OS.Path.join('data', 'Mendeley Ltd.', 'Mendeley Desktop', 'Downloaded'));
} };
Zotero_Import_Mendeley.prototype._isTempDownloadedFile = function (path) { Zotero_Import_Mendeley.prototype._isTempDownloadedFile = function (path) {
return path.startsWith(OS.Path.join(Zotero.getTempDirectory().path, 'm-api')); return path.startsWith(OS.Path.join(Zotero.getTempDirectory().path, 'm-api'));
@ -1498,7 +1610,7 @@ Zotero_Import_Mendeley.prototype._getRealFilePath = async function (path) {
return altPath; return altPath;
} }
return false; return false;
} };
Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations, parentItemID, attachmentItemID, fileHash) { Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations, parentItemID, attachmentItemID, fileHash) {
if (!annotations.length) return; if (!annotations.length) return;
@ -1507,7 +1619,6 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
var libraryID = parentItem.libraryID; var libraryID = parentItem.libraryID;
if (attachmentItemID) { if (attachmentItemID) {
var attachmentItem = Zotero.Items.get(attachmentItemID); var attachmentItem = Zotero.Items.get(attachmentItemID);
var attachmentURIPath = Zotero.API.getLibraryPrefix(libraryID) + '/items/' + attachmentItem.key;
if (attachmentItem) { if (attachmentItem) {
let file = await attachmentItem.getFilePathAsync(); let file = await attachmentItem.getFilePathAsync();
@ -1530,6 +1641,10 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
} }
// PDFWorker needs 'id' // PDFWorker needs 'id'
annotations.forEach(annotation => annotation.id = annotation.uuid); annotations.forEach(annotation => annotation.id = annotation.uuid);
annotations.forEach(annotation => (colorMap.has(annotation.color)
? annotation.color = colorMap.get(annotation.color)
: annotation.color
));
// Returns 'id', 'position', 'type', 'pageLabel', 'sortIndex', 'text' (for highlight) // Returns 'id', 'position', 'type', 'pageLabel', 'sortIndex', 'text' (for highlight)
Zotero.debug("Processing annotations in " + file); Zotero.debug("Processing annotations in " + file);
annotations = await Zotero.PDFWorker.processMendeleyAnnotations(file, annotations); annotations = await Zotero.PDFWorker.processMendeleyAnnotations(file, annotations);
@ -1616,7 +1731,7 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
}); });
} }
note.setNote('<h1>' + Zotero.getString('extractedAnnotations') + '</h1>\n' + noteStrings.join('\n')); note.setNote('<h1>' + Zotero.getString('extractedAnnotations') + '</h1>\n' + noteStrings.join('\n'));
return note.saveTx(this._saveOptions); await note.saveTx(this._saveOptions);
}; };
@ -1633,17 +1748,7 @@ Zotero_Import_Mendeley.prototype._updateParentKeys = function (objectType, json,
break; break;
} }
} }
} };
Zotero_Import_Mendeley.prototype._updateItemCollectionKeys = function (json, oldKey, newKey) {
for (; i < json.length; i++) {
let x = json[i];
if (x[prop] == oldKey) {
x[prop] = newKey;
}
}
}
// //
// Clean up extra files created <5.0.51 // Clean up extra files created <5.0.51

View file

@ -28,6 +28,7 @@ var mendeleyOnlineMappings = {
arxiv: 'arxivId', arxiv: 'arxivId',
accessed: 'dateAccessed', accessed: 'dateAccessed',
authors: false, // all author types handled separately authors: false, // all author types handled separately
client_data: false, // desktop_id extraction handled separately
citation_key: 'citationKey', citation_key: 'citationKey',
created: 'added', created: 'added',
edition: 'edition', edition: 'edition',

View file

@ -1,4 +1,5 @@
var map = { // eslint-disable-next-line no-unused-vars
var mendeleyDBMaps = {
83: { 83: {
itemTypes: { itemTypes: {
Bill: "bill", Bill: "bill",

View file

@ -113,9 +113,10 @@ var Zotero_Citation_Dialog = new function () {
var menu = document.getElementById("label"); var menu = document.getElementById("label");
var label_list = document.getElementById("locator-type-popup"); var label_list = document.getElementById("locator-type-popup");
var i = 0; var i = 0;
var pageLocatorIndex;
for(var value in locators) { for(var value in locators) {
var locator = locators[value]; var locator = locators[value];
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,'')); let locatorLabel = Zotero.Cite.getLocatorString(locator);
// add to list of labels // add to list of labels
var child = document.createElement("menuitem"); var child = document.createElement("menuitem");
child.setAttribute("value", value); child.setAttribute("value", value);
@ -124,9 +125,13 @@ var Zotero_Citation_Dialog = new function () {
// add to array // add to array
_locatorIndexArray[locator] = i; _locatorIndexArray[locator] = i;
_locatorNameArray[i] = locator; _locatorNameArray[i] = locator;
if (locator == 'page') {
pageLocatorIndex = i;
_preserveData.label[1] = i;
}
i++; i++;
} }
menu.selectedIndex = 0; menu.selectedIndex = pageLocatorIndex;
// load (from selectItemsDialog.js) // load (from selectItemsDialog.js)
yield doLoad(); yield doLoad();

View file

@ -28,7 +28,7 @@ var Zotero_QuickFormat = new function () {
const pixelRe = /^([0-9]+)px$/ const pixelRe = /^([0-9]+)px$/
const specifiedLocatorRe = /^(?:,? *(p{1,2})(?:\. *| *)|:)([0-9\-]+) *$/; const specifiedLocatorRe = /^(?:,? *(p{1,2})(?:\. *| *)|:)([0-9\-]+) *$/;
const yearRe = /,? *([0-9]+) *(B[. ]*C[. ]*(?:E[. ]*)?|A[. ]*D[. ]*|C[. ]*E[. ]*)?$/i; const yearRe = /,? *([0-9]+) *(B[. ]*C[. ]*(?:E[. ]*)?|A[. ]*D[. ]*|C[. ]*E[. ]*)?$/i;
const locatorRe = /(?:,? *(p{0,2})\.?|(\:)) *([0-9\-–]+)$/i; const locatorRe = / (?:,? *(p{0,2})\.?|(\:)) *([0-9\-–]+)$/i;
const creatorSplitRe = /(?:,| *(?:and|\&)) +/; const creatorSplitRe = /(?:,| *(?:and|\&)) +/;
const charRe = /[\w\u007F-\uFFFF]/; const charRe = /[\w\u007F-\uFFFF]/;
const numRe = /^[0-9\-–]+$/; const numRe = /^[0-9\-–]+$/;
@ -37,7 +37,8 @@ var Zotero_QuickFormat = new function () {
keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0, keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0,
separatorHeight = 0, currentLocator, currentLocatorLabel, currentSearchTime, dragging, separatorHeight = 0, currentLocator, currentLocatorLabel, currentSearchTime, dragging,
panel, panelPrefix, panelSuffix, panelSuppressAuthor, panelLocatorLabel, panelLocator, panel, panelPrefix, panelSuffix, panelSuppressAuthor, panelLocatorLabel, panelLocator,
panelLibraryLink, panelInfo, panelRefersToBubble, panelFrameHeight = 0, accepted = false; panelLibraryLink, panelInfo, panelRefersToBubble, panelFrameHeight = 0, accepted = false,
isPaste = false;
var locatorLocked = true; var locatorLocked = true;
var locatorNode = null; var locatorNode = null;
var _searchPromise; var _searchPromise;
@ -116,10 +117,9 @@ var Zotero_QuickFormat = new function () {
// add labels to popup // add labels to popup
var locators = Zotero.Cite.labels; var locators = Zotero.Cite.labels;
var menu = document.getElementById("locator-label");
var labelList = document.getElementById("locator-label-popup"); var labelList = document.getElementById("locator-label-popup");
for(var locator of locators) { for(var locator of locators) {
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,'')); let locatorLabel = Zotero.Cite.getLocatorString(locator);
// add to list of labels // add to list of labels
var child = document.createElement("menuitem"); var child = document.createElement("menuitem");
@ -127,7 +127,6 @@ var Zotero_QuickFormat = new function () {
child.setAttribute("label", locatorLabel); child.setAttribute("label", locatorLabel);
labelList.appendChild(child); labelList.appendChild(child);
} }
menu.selectedIndex = 0;
} }
@ -217,10 +216,10 @@ var Zotero_QuickFormat = new function () {
// Range could be referenced to the body element // Range could be referenced to the body element
if(node === qfe) { if(node === qfe) {
var offset = range.startOffset; for (let i = qfe.childNodes.length - 1; i >= 0; i--) {
if(offset !== range.endOffset) return false; node = qfe.childNodes[i];
node = qfe.childNodes[Math.min(qfe.childNodes.length-1, offset)]; if(node.nodeType === Node.TEXT_NODE) return node;
if(node.nodeType === Node.TEXT_NODE) return node; }
} }
return false; return false;
} }
@ -240,7 +239,7 @@ var Zotero_QuickFormat = new function () {
* @return {String} str without locator * @return {String} str without locator
*/ */
function _updateLocator(str) { function _updateLocator(str) {
m = locatorRe.exec(str); m = !isPaste && locatorRe.exec(str);
if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) { if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) {
currentLocator = m[3]; currentLocator = m[3];
str = str.substr(0, m.index)+str.substring(m.index+m[0].length); str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
@ -500,6 +499,7 @@ var Zotero_QuickFormat = new function () {
await Zotero.Items.loadDataTypes(items); await Zotero.Items.loadDataTypes(items);
searchString = searchString.toLowerCase(); searchString = searchString.toLowerCase();
let searchParts = Zotero.SearchConditions.parseSearchString(searchString);
var collation = Zotero.getLocaleCollation(); var collation = Zotero.getLocaleCollation();
function _itemSort(a, b) { function _itemSort(a, b) {
@ -508,13 +508,15 @@ var Zotero_QuickFormat = new function () {
// Favor left-bound name matches (e.g., "Baum" < "Appelbaum"), // Favor left-bound name matches (e.g., "Baum" < "Appelbaum"),
// using last name of first author // using last name of first author
if (firstCreatorA && firstCreatorB) { if (firstCreatorA && firstCreatorB) {
let caStartsWith = firstCreatorA.toLowerCase().indexOf(searchString) == 0; for (let part of searchParts) {
let cbStartsWith = firstCreatorB.toLowerCase().indexOf(searchString) == 0; let caStartsWith = firstCreatorA.toLowerCase().startsWith(part.text);
if (caStartsWith && !cbStartsWith) { let cbStartsWith = firstCreatorB.toLowerCase().startsWith(part.text);
return -1; if (caStartsWith && !cbStartsWith) {
} return -1;
else if (!caStartsWith && cbStartsWith) { }
return 1; else if (!caStartsWith && cbStartsWith) {
return 1;
}
} }
} }
@ -729,9 +731,7 @@ var Zotero_QuickFormat = new function () {
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field) // Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
title = item.getDisplayTitle(); title = item.getDisplayTitle();
if (item.isNote()) { title = title.substr(0, 32) + (title.length > 32 ? "…" : "");
title = title.substr(0, 24) + '…';
}
if (!str) { if (!str) {
str = Zotero.getString("punctuation.openingQMark") + title + Zotero.getString("punctuation.closingQMark"); str = Zotero.getString("punctuation.openingQMark") + title + Zotero.getString("punctuation.closingQMark");
} }
@ -758,15 +758,17 @@ var Zotero_QuickFormat = new function () {
// Prefix // Prefix
if(citationItem.prefix && Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP) { if(citationItem.prefix && Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP) {
str = citationItem.prefix let prefix = citationItem.prefix.substr(0, 10) + (citationItem.prefix.length > 10 ? "…" : "")
str = prefix
+(Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? " " : "") +(Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? " " : "")
+str; +str;
} }
// Suffix // Suffix
if(citationItem.suffix && Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP) { if(citationItem.suffix && Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP) {
let suffix = citationItem.suffix.substr(0, 10) + (citationItem.suffix.length > 10 ? "…" : "")
str += (Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? " " : "") str += (Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? " " : "")
+citationItem.suffix; + suffix;
} }
return str; return str;
@ -778,9 +780,6 @@ var Zotero_QuickFormat = new function () {
function _insertBubble(citationItem, nextNode) { function _insertBubble(citationItem, nextNode) {
var str = _buildBubbleString(citationItem); var str = _buildBubbleString(citationItem);
// It's entirely unintuitive why, but after trying a bunch of things, it looks like using
// a XUL label for these things works best. A regular span causes issues with moving the
// cursor.
var bubble = qfiDocument.createElement("span"); var bubble = qfiDocument.createElement("span");
bubble.setAttribute("class", "citation-dialog bubble"); bubble.setAttribute("class", "citation-dialog bubble");
bubble.setAttribute("draggable", "true"); bubble.setAttribute("draggable", "true");
@ -871,6 +870,7 @@ var Zotero_QuickFormat = new function () {
// handling to maintain the correct locator node in // handling to maintain the correct locator node in
// _showCitation() // _showCitation()
var bubble = locatorNode = _insertBubble(citationItem, node); var bubble = locatorNode = _insertBubble(citationItem, node);
isPaste = false;
_clearEntryList(); _clearEntryList();
yield _previewAndSort(); yield _previewAndSort();
_refocusQfe(); _refocusQfe();
@ -1085,15 +1085,16 @@ var Zotero_QuickFormat = new function () {
let citationItem = JSON.parse(target.dataset.citationItem); let citationItem = JSON.parse(target.dataset.citationItem);
panelPrefix.value = citationItem["prefix"] ? citationItem["prefix"] : ""; panelPrefix.value = citationItem["prefix"] ? citationItem["prefix"] : "";
panelSuffix.value = citationItem["suffix"] ? citationItem["suffix"] : ""; panelSuffix.value = citationItem["suffix"] ? citationItem["suffix"] : "";
var pageOption = panelLocatorLabel.getElementsByAttribute("value", "page")[0];
if(citationItem["label"]) { if(citationItem["label"]) {
var option = panelLocatorLabel.getElementsByAttribute("value", citationItem["label"]); var option = panelLocatorLabel.getElementsByAttribute("value", citationItem["label"]);
if(option.length) { if(option.length) {
panelLocatorLabel.selectedItem = option[0]; panelLocatorLabel.selectedItem = option[0];
} else { } else {
panelLocatorLabel.selectedIndex = 0; panelLocatorLabel.selectedItem = pageOption;
} }
} else { } else {
panelLocatorLabel.selectedIndex = 0; panelLocatorLabel.selectedItem = pageOption;
} }
panelLocator.value = citationItem["locator"] ? citationItem["locator"] : ""; panelLocator.value = citationItem["locator"] ? citationItem["locator"] : "";
panelSuppressAuthor.checked = !!citationItem["suppress-author"]; panelSuppressAuthor.checked = !!citationItem["suppress-author"];
@ -1356,6 +1357,7 @@ var Zotero_QuickFormat = new function () {
event.preventDefault(); event.preventDefault();
} }
} else { } else {
isPaste = false;
_resetSearchTimer(); _resetSearchTimer();
} }
}); });

View file

@ -27,7 +27,6 @@ const { noop, getDragTargetOrient } = require("components/utils");
const PropTypes = require("prop-types"); const PropTypes = require("prop-types");
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
const { IntlProvider } = require('react-intl');
const LibraryTree = require('./libraryTree'); const LibraryTree = require('./libraryTree');
const VirtualizedTable = require('components/virtualized-table'); const VirtualizedTable = require('components/virtualized-table');
const { renderCell, formatColumnName } = VirtualizedTable; const { renderCell, formatColumnName } = VirtualizedTable;
@ -38,9 +37,8 @@ const { Cc, Ci, Cu } = require('chrome');
Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/osfile.jsm");
const CHILD_INDENT = 12; const CHILD_INDENT = 12;
const COLORED_TAGS_RE = new RegExp("^[0-" + Zotero.Tags.MAX_COLORED_TAGS + "]{1}$"); const COLORED_TAGS_RE = new RegExp("^(?:Numpad|Digit)([0-" + Zotero.Tags.MAX_COLORED_TAGS + "]{1})$");
const COLUMN_PREFS_FILEPATH = OS.Path.join(Zotero.Profile.dir, "treePrefs.json"); const COLUMN_PREFS_FILEPATH = OS.Path.join(Zotero.Profile.dir, "treePrefs.json");
const EMOJI_RE = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu;
const HTML_NS = "http://www.w3.org/1999/xhtml"; const HTML_NS = "http://www.w3.org/1999/xhtml";
const ATTACHMENT_STATE_LOAD_DELAY = 150; //ms const ATTACHMENT_STATE_LOAD_DELAY = 150; //ms
@ -50,9 +48,7 @@ var ItemTree = class ItemTree extends LibraryTree {
var ref; var ref;
opts.domEl = domEl; opts.domEl = domEl;
let elem = ( let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <ItemTree ref={c => ref = c } {...opts} />
<ItemTree ref={c => ref = c } {...opts} />
</IntlProvider>
); );
await new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); await new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
@ -448,11 +444,20 @@ var ItemTree = class ItemTree extends LibraryTree {
let row = this.getRowIndexByID(id); let row = this.getRowIndexByID(id);
if (row === false) continue; if (row === false) continue;
let item = Zotero.Items.get(id); let item = Zotero.Items.get(id);
if (!item.deleted && !item.numChildren()) { // Remove parent row if it isn't deleted and doesn't have any deleted children
// (shown by the numChildren including deleted being the same as numChildren not including deleted)
if (!item.deleted && (!item.isRegularItem() || item.numChildren(true) == item.numChildren(false))) {
rows.push(row); rows.push(row);
// And all its children in the tree
for (let child = row + 1; child < this.rowCount && this.getLevel(child) > this.getLevel(row); child++) {
rows.push(child);
}
} }
} }
this._removeRows(rows); if (rows.length) {
this._removeRows(rows);
this.tree.invalidate();
}
} }
return; return;
@ -475,14 +480,17 @@ var ItemTree = class ItemTree extends LibraryTree {
return; return;
} }
var visibleSubcollections = Zotero.Prefs.get('recursiveCollections')
? collectionTreeRow.ref.getDescendents(false, 'collection')
: [];
var splitIDs = []; var splitIDs = [];
for (let id of ids) { for (let id of ids) {
var split = id.split('-'); var split = id.split('-');
// Skip if not an item in this collection // Include if an item in this collection or a visible subcollection
if (split[0] != collectionTreeRow.ref.id) { if (split[0] == collectionTreeRow.ref.id
continue; || visibleSubcollections.some(c => split[0] == c.id)) {
splitIDs.push(split[1]);
} }
splitIDs.push(split[1]);
} }
ids = splitIDs; ids = splitIDs;
} }
@ -859,9 +867,9 @@ var ItemTree = class ItemTree extends LibraryTree {
} }
return false; return false;
} }
if (!event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey && COLORED_TAGS_RE.test(event.key)) { if (!event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey && COLORED_TAGS_RE.test(event.code)) {
let libraryID = this.collectionTreeRow.ref.libraryID; let libraryID = this.collectionTreeRow.ref.libraryID;
let position = parseInt(event.key) - 1; let position = COLORED_TAGS_RE.exec(event.code)[1] - 1;
// When 0 is pressed, remove all colored tags // When 0 is pressed, remove all colored tags
if (position == -1) { if (position == -1) {
let items = this.getSelectedItems(); let items = this.getSelectedItems();
@ -978,7 +986,7 @@ var ItemTree = class ItemTree extends LibraryTree {
onItemContextMenu: (...args) => this.props.onContextMenu(...args), onItemContextMenu: (...args) => this.props.onContextMenu(...args),
role: 'treegrid', role: 'tree',
label: Zotero.getString('pane.items.title'), label: Zotero.getString('pane.items.title'),
} }
); );
@ -1241,20 +1249,14 @@ var ItemTree = class ItemTree extends LibraryTree {
? `for ${itemIDs.length} ` + Zotero.Utilities.pluralize(itemIDs.length, ['item', 'items']) ? `for ${itemIDs.length} ` + Zotero.Utilities.pluralize(itemIDs.length, ['item', 'items'])
: "")); : ""));
// Set whether rows with empty values should be displayed last, // Set whether rows with empty values should sort at the beginning
// which may be different for primary and secondary sorting. var emptyFirst = {
var emptyFirst = {}; title: true,
switch (primaryField) {
case 'title': // Date columns start descending, so put empty rows at end
emptyFirst.title = true; date: true,
break; year: true,
};
// When sorting by title we want empty titles at the top, but if not
// sorting by title, empty titles should sort to the bottom so that new
// empty items don't get sorted to the middle of the items list.
default:
emptyFirst.title = false;
}
// Cache primary values while sorting, since base-field-mapped getField() // Cache primary values while sorting, since base-field-mapped getField()
// calls are relatively expensive // calls are relatively expensive
@ -1262,7 +1264,7 @@ var ItemTree = class ItemTree extends LibraryTree {
sortFields.forEach(x => cache[x] = {}); sortFields.forEach(x => cache[x] = {});
// Get the display field for a row (which might be a placeholder title) // Get the display field for a row (which might be a placeholder title)
function getField(field, row) { let getField = (field, row) => {
var item = row.ref; var item = row.ref;
switch (field) { switch (field) {
@ -1270,23 +1272,12 @@ var ItemTree = class ItemTree extends LibraryTree {
return Zotero.Items.getSortTitle(item.getDisplayTitle()); return Zotero.Items.getSortTitle(item.getDisplayTitle());
case 'hasAttachment': case 'hasAttachment':
if (item.isFileAttachment()) { if (this._canGetBestAttachmentState(item)) {
var state = item.fileExistsCached() ? 1 : -1; return item.getBestAttachmentStateCached();
}
else if (item.isRegularItem()) {
var state = item.getBestAttachmentStateCached();
} }
else { else {
return 0; return 0;
} }
// Make sort order present, missing, empty when ascending
if (state === 1) {
state = 2;
}
else if (state === -1) {
state = 1;
}
return state;
case 'numNotes': case 'numNotes':
return row.numNotes(false, true) || 0; return row.numNotes(false, true) || 0;
@ -1350,7 +1341,11 @@ var ItemTree = class ItemTree extends LibraryTree {
} }
if (sortField == 'hasAttachment') { if (sortField == 'hasAttachment') {
return fieldB - fieldA; // PDFs at the top
const order = ['pdf', 'snapshot', 'other', 'none'];
fieldA = order.indexOf(fieldA.type || 'none') + (fieldA.exists ? 0 : 4);
fieldB = order.indexOf(fieldB.type || 'none') + (fieldB.exists ? 0 : 4);
return fieldA - fieldB;
} }
if (sortField == 'callNumber') { if (sortField == 'callNumber') {
@ -1439,18 +1434,27 @@ var ItemTree = class ItemTree extends LibraryTree {
var openItemIDs = this._saveOpenState(true); var openItemIDs = this._saveOpenState(true);
// Sort specific items // Sort specific items
if (itemIDs) { try {
let idsToSort = new Set(itemIDs); if (itemIDs) {
this._rows.sort((a, b) => { let idsToSort = new Set(itemIDs);
// Don't re-sort existing items. This assumes a stable sort(), which is the case in Firefox this._rows.sort((a, b) => {
// but not Chrome/v8. // Don't re-sort existing items. This assumes a stable sort(), which is the case in Firefox
if (!idsToSort.has(a.ref.id) && !idsToSort.has(b.ref.id)) return 0; // but not Chrome/v8.
return rowSort(a, b) * order; if (!idsToSort.has(a.ref.id) && !idsToSort.has(b.ref.id)) return 0;
}); return rowSort(a, b) * order;
});
}
// Full sort
else {
this._rows.sort((a, b) => rowSort(a, b) * order);
}
} }
// Full sort catch (e) {
else { Zotero.logError("Error sorting fields: " + e.message);
this._rows.sort((a, b) => rowSort(a, b) * order); Zotero.debug(e, 1);
// Clear anything that might be contributing to the error
Zotero.Prefs.clear('secondarySort.' + this.getSortField());
Zotero.Prefs.clear('fallbackSort');
} }
this._refreshRowMap(); this._refreshRowMap();
@ -1748,8 +1752,21 @@ var ItemTree = class ItemTree extends LibraryTree {
await Zotero.Items.trashTx(ids); await Zotero.Items.trashTx(ids);
} }
else if (collectionTreeRow.isCollection()) { else if (collectionTreeRow.isCollection()) {
let collectionIDs = [collectionTreeRow.ref.id];
if (Zotero.Prefs.get('recursiveCollections')) {
collectionIDs.push(...collectionTreeRow.ref.getDescendents(false, 'collection').map(c => c.id));
}
await Zotero.DB.executeTransaction(async () => { await Zotero.DB.executeTransaction(async () => {
await collectionTreeRow.ref.removeItems(ids); for (let itemID of ids) {
let item = Zotero.Items.get(itemID);
for (let collectionID of collectionIDs) {
item.removeFromCollection(collectionID);
}
await item.save({
skipDateModifiedUpdate: true
});
}
}); });
} }
else if (collectionTreeRow.isPublications()) { else if (collectionTreeRow.isPublications()) {
@ -1788,6 +1805,11 @@ var ItemTree = class ItemTree extends LibraryTree {
return this._rows.map(row => asIDs ? row.ref.id : row.ref); return this._rows.map(row => asIDs ? row.ref.id : row.ref);
} }
/**
* Get the current sort order of the items list
*
* @return {Number} - -1 for descending, 1 for ascending
*/
getSortDirection(sortFields) { getSortDirection(sortFields) {
sortFields = sortFields || this.getSortFields(); sortFields = sortFields || this.getSortFields();
if (this.collectionTreeRow.isFeed()) { if (this.collectionTreeRow.isFeed()) {
@ -1987,8 +2009,8 @@ var ItemTree = class ItemTree extends LibraryTree {
// If exporting with virtual "Markdown + Rich Text" translator, call Note Markdown // If exporting with virtual "Markdown + Rich Text" translator, call Note Markdown
// and Note HTML translators instead // and Note HTML translators instead
if (format.id === Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) { if (format.id === Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN }; let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML }; let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
Zotero.QuickCopy.getContentFromItems(items, markdownFormat, (obj, worked) => { Zotero.QuickCopy.getContentFromItems(items, markdownFormat, (obj, worked) => {
if (!worked) { if (!worked) {
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning'); Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
@ -2752,93 +2774,60 @@ var ItemTree = class ItemTree extends LibraryTree {
// TEMP: For now, we use the blue bullet for all non-PDF attachments, but there's // TEMP: For now, we use the blue bullet for all non-PDF attachments, but there's
// commented-out code for showing different icons for snapshots, files, and URL/DOI links // commented-out code for showing different icons for snapshots, files, and URL/DOI links
if (this.isContainer(index)) { if (this._canGetBestAttachmentState(item)) {
if (item.isRegularItem()) { const { type, exists } = item.getBestAttachmentStateCached();
const { type, exists } = item.getBestAttachmentStateCached();
let icon = "";
let ariaLabel;
// If the item has a child attachment
if (type !== null && type != 'none') {
if (type == 'pdf') {
icon = getDOMElement('IconTreeitemAttachmentPDF');
ariaLabel = Zotero.getString('pane.item.attachments.hasPDF');
if (!exists) {
icon.classList.add('icon-missing-file');
}
}
else if (type == 'snapshot') {
//icon = getDOMElement('IconTreeitemAttachmentSnapshot');
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
ariaLabel = Zotero.getString('pane.item.attachments.hasSnapshot');
}
else {
//icon = getDOMElement('IconTreeitem');
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
ariaLabel = Zotero.getString('pane.item.attachments.has');
}
icon.classList.add('cell-icon');
//if (!exists) {
// icon.classList.add('icon-missing-file');
//}
}
//else if (type == 'none') {
// if (item.getField('url') || item.getField('DOI')) {
// icon = getDOMElement('IconLink');
// ariaLabel = Zotero.getString('pane.item.attachments.hasLink');
// icon.classList.add('cell-icon');
// }
//}
if (ariaLabel) {
icon.setAttribute('aria-label', ariaLabel + '.');
span.setAttribute('title', ariaLabel);
}
span.append(icon);
// Don't run this immediately since it might cause a db check and disk access
// but delay for some time and see if the item is still visible in the tree
// (i.e. if we haven't scrolled right past it)
setTimeout(() => {
if (!this.tree.rowIsVisible(index)) return;
item.getBestAttachmentState()
// Refresh cell when promise is fulfilled
.then(({ type: newType, exists: newExists }) => {
if (newType !== type || newExists !== exists) {
this.tree.invalidateRow(index);
}
});
}, ATTACHMENT_STATE_LOAD_DELAY);
}
}
if (item.isFileAttachment()) {
const exists = item.fileExistsCached();
let icon = ""; let icon = "";
if (exists !== null) { let ariaLabel;
if (item.isPDFAttachment()) { // If the item has a child attachment
if (type !== null && type != 'none') {
if (type == 'pdf') {
icon = getDOMElement('IconTreeitemAttachmentPDF'); icon = getDOMElement('IconTreeitemAttachmentPDF');
ariaLabel = Zotero.getString('pane.item.attachments.hasPDF');
if (!exists) { if (!exists) {
icon.classList.add('icon-missing-file'); icon.classList.add('icon-missing-file');
} }
} }
else if (item.isSnapshotAttachment()) { else if (type == 'snapshot') {
//icon = getDOMElement('IconTreeitemAttachmentSnapshot'); //icon = getDOMElement('IconTreeitemAttachmentSnapshot');
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty'); icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
ariaLabel = Zotero.getString('pane.item.attachments.hasSnapshot');
} }
else { else {
//icon = getDOMElement('IconTreeitem'); //icon = getDOMElement('IconTreeitem');
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty'); icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
ariaLabel = Zotero.getString('pane.item.attachments.has');
} }
icon.classList.add('cell-icon'); icon.classList.add('cell-icon');
//if (!exists) { //if (!exists) {
// icon.classList.add('icon-missing-file'); // icon.classList.add('icon-missing-file');
//} //}
} }
//else if (type == 'none') {
// if (item.getField('url') || item.getField('DOI')) {
// icon = getDOMElement('IconLink');
// ariaLabel = Zotero.getString('pane.item.attachments.hasLink');
// icon.classList.add('cell-icon');
// }
//}
if (ariaLabel) {
icon.setAttribute('aria-label', ariaLabel + '.');
span.setAttribute('title', ariaLabel);
}
span.append(icon); span.append(icon);
item.fileExists() // Don't run this immediately since it might cause a db check and disk access
// TODO: With no cell refreshing this is possibly somewhat inefficient // but delay for some time and see if the item is still visible in the tree
// Refresh cell when promise is fulfilled // (i.e. if we haven't scrolled right past it)
.then(realExists => realExists != exists && this.tree.invalidateRow(index)); setTimeout(() => {
if (!this.tree.rowIsVisible(index)) return;
item.getBestAttachmentState()
// Refresh cell when promise is fulfilled
.then(({ type: newType, exists: newExists }) => {
if (newType !== type || newExists !== exists) {
this.tree.invalidateRow(index);
}
});
}, ATTACHMENT_STATE_LOAD_DELAY);
} }
return span; return span;
@ -2911,7 +2900,7 @@ var ItemTree = class ItemTree extends LibraryTree {
} }
// Accessibility // Accessibility
div.setAttribute('role', 'row'); div.setAttribute('role', 'treeitem');
div.setAttribute('aria-level', this.getLevel(index) + 1); div.setAttribute('aria-level', this.getLevel(index) + 1);
if (!this.isContainerEmpty(index)) { if (!this.isContainerEmpty(index)) {
div.setAttribute('aria-expanded', this.isContainerOpen(index)); div.setAttribute('aria-expanded', this.isContainerOpen(index));
@ -3550,7 +3539,7 @@ var ItemTree = class ItemTree extends LibraryTree {
let t = new Date(); let t = new Date();
for (let i = 0; i < this._rows.length; i++) { for (let i = 0; i < this._rows.length; i++) {
let item = this.getRow(i).ref; let item = this.getRow(i).ref;
if (item.isRegularItem()) { if (this._canGetBestAttachmentState(item)) {
await item.getBestAttachmentState(); await item.getBestAttachmentState();
} }
} }
@ -3738,7 +3727,7 @@ var ItemTree = class ItemTree extends LibraryTree {
// Restore Default Column Order // Restore Default Column Order
// //
let menuitem = doc.createElementNS(ns, 'menuitem'); let menuitem = doc.createElementNS(ns, 'menuitem');
menuitem.setAttribute('label', Zotero.Intl.strings['zotero.items.restoreColumnOrder.label']); menuitem.setAttribute('label', Zotero.getString('zotero.items.restoreColumnOrder.label'));
menuitem.setAttribute('anonid', prefix + 'restore-order'); menuitem.setAttribute('anonid', prefix + 'restore-order');
menuitem.addEventListener('command', () => this.tree._columns.restoreDefaultOrder()); menuitem.addEventListener('command', () => this.tree._columns.restoreDefaultOrder());
menupopup.appendChild(menuitem); menupopup.appendChild(menuitem);
@ -3824,10 +3813,10 @@ var ItemTree = class ItemTree extends LibraryTree {
} }
return icon; return icon;
} }
_isOnlyEmoji(str) { _canGetBestAttachmentState(item) {
// Remove emoji and zero-width joiner and see if anything's left return (item.isRegularItem() && item.numAttachments())
return !str.replace(EMOJI_RE, '').replace(/\u200D/g,''); || (item.isFileAttachment() && item.isTopLevelItem());
} }
_getTagSwatch(tag, color) { _getTagSwatch(tag, color) {
@ -3837,7 +3826,7 @@ var ItemTree = class ItemTree extends LibraryTree {
// //
// TODO: Check for a maximum number of graphemes, which is hard to do // TODO: Check for a maximum number of graphemes, which is hard to do
// https://stackoverflow.com/a/54369605 // https://stackoverflow.com/a/54369605
if (this._isOnlyEmoji(tag)) { if (Zotero.Utilities.Internal.isOnlyEmoji(tag)) {
span.textContent = tag; span.textContent = tag;
} }
// Otherwise display color // Otherwise display color

View file

@ -207,6 +207,14 @@ const COLUMNS = [
flex: 1, flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"]) zoteroPersist: new Set(["width", "hidden", "sortDirection"])
}, },
{
dataKey: "number",
disabledIn: "feed",
submenu: true,
label: "itemFields.number",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{ {
dataKey: "pages", dataKey: "pages",
disabledIn: "feed", disabledIn: "feed",

@ -1 +1 @@
Subproject commit 4877f181b1a962ea3e26d23f12d1acba3ccce347 Subproject commit d704bf80afe8a692373e11f670b08553b9f74a31

View file

@ -24,7 +24,6 @@
*/ */
import VirtualizedTable from 'components/virtualized-table'; import VirtualizedTable from 'components/virtualized-table';
const { IntlProvider } = require('react-intl');
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
@ -41,20 +40,18 @@ function init() {
engines = Zotero.LocateManager.getEngines(); engines = Zotero.LocateManager.getEngines();
const domEl = document.querySelector('#locateManager-tree'); const domEl = document.querySelector('#locateManager-tree');
let elem = ( let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <VirtualizedTable
<VirtualizedTable getRowCount={() => engines.length}
getRowCount={() => engines.length} id="locateManager-table"
id="locateManager-table" ref={ref => tree = ref}
ref={ref => tree = ref} renderItem={VirtualizedTable.makeRowRenderer(getRowData)}
renderItem={VirtualizedTable.makeRowRenderer(getRowData)} showHeader={true}
showHeader={true} multiSelect={true}
multiSelect={true} columns={columns}
columns={columns} disableFontSizeScaling={true}
disableFontSizeScaling={true} getRowString={index => getRowData(index).name}
getRowString={index => getRowData(index).name} onActivate={handleActivate}
onActivate={handleActivate} />
/>
</IntlProvider>
); );
return new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); return new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
} }

View file

@ -520,7 +520,13 @@ var Zotero_LocateMenu = new function() {
if(attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) { if(attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) {
var path = yield attachment.getFilePathAsync(); var path = yield attachment.getFilePathAsync();
if (path) { if (path) {
var ext = Zotero.File.getExtension(Zotero.File.pathToFile(path)); try {
var ext = Zotero.File.getExtension(Zotero.File.pathToFile(path));
}
catch (e) {
Zotero.logError(e);
return false;
}
if(!attachment.attachmentContentType || if(!attachment.attachmentContentType ||
Zotero.MIME.hasNativeHandler(attachment.attachmentContentType, ext) || Zotero.MIME.hasNativeHandler(attachment.attachmentContentType, ext) ||
!Zotero.MIME.hasInternalHandler(attachment.attachmentContentType, ext)) { !Zotero.MIME.hasInternalHandler(attachment.attachmentContentType, ext)) {
@ -589,9 +595,23 @@ var Zotero_LocateMenu = new function() {
this.icon = "chrome://zotero/skin/locate-library-lookup.png"; this.icon = "chrome://zotero/skin/locate-library-lookup.png";
this.canHandleItem = function (item) { return Zotero.Promise.resolve(item.isRegularItem()); }; this.canHandleItem = function (item) { return Zotero.Promise.resolve(item.isRegularItem()); };
this.handleItems = Zotero.Promise.method(function (items, event) { this.handleItems = Zotero.Promise.method(function (items, event) {
// If no resolver configured, just switch to the default // If no resolver configured, show error
if (!Zotero.Prefs.get('openURL.resolver')) { if (!Zotero.Prefs.get('openURL.resolver')) {
Zotero.Prefs.clear('openURL.resolver') let ps = Services.prompt;
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
let index = ps.confirmEx(
null,
Zotero.getString('locate.libraryLookup.noResolver.title'),
Zotero.getString('locate.libraryLookup.noResolver.text', Zotero.appName),
buttonFlags,
Zotero.getString('general.openPreferences'),
null, null, null, {}
);
if (index == 0) {
Zotero.Utilities.Internal.openPreferences('zotero-prefpane-advanced');
}
return;
} }
var urls = []; var urls = [];
for (let item of items) { for (let item of items) {

View file

@ -28,6 +28,7 @@
* @namespace * @namespace
*/ */
var Zotero_Lookup = new function () { var Zotero_Lookup = new function () {
this._button = null;
/** /**
* Performs a lookup by DOI, PMID, or ISBN on the given textBox value * Performs a lookup by DOI, PMID, or ISBN on the given textBox value
* and adds any items it can. * and adds any items it can.
@ -39,7 +40,7 @@ var Zotero_Lookup = new function () {
* @param textBox {HTMLElement} - Textbox containing identifiers * @param textBox {HTMLElement} - Textbox containing identifiers
* @param childItem {Zotero.Item|false} - Child item (optional) * @param childItem {Zotero.Item|false} - Child item (optional)
* @param toggleProgress {function} - Callback to toggle progress on/off * @param toggleProgress {function} - Callback to toggle progress on/off
* @returns {Promise<boolean>} * @returns {Promise<Zotero.Item[]>}
*/ */
this.addItemsFromIdentifier = async function (textBox, childItem, toggleProgress) { this.addItemsFromIdentifier = async function (textBox, childItem, toggleProgress) {
var identifiers = Zotero.Utilities.extractIdentifiers(textBox.value); var identifiers = Zotero.Utilities.extractIdentifiers(textBox.value);
@ -78,11 +79,28 @@ var Zotero_Lookup = new function () {
} }
} }
let newItems = false;
toggleProgress(true); toggleProgress(true);
await Zotero.Promise.all(identifiers.map(async (identifier) => { // Group PubMed IDs into batches of 200
var translate = new Zotero.Translate.Search(); //
// Up to 10,000 ids can apparently be passed in a single request, but 200 is the recommended
// limit for GET, which we currently use, and passing batches of 200 seems...fine.
//
// https://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.id
if (identifiers.length && identifiers[0].PMID) {
let chunkSize = 200;
let newIdentifiers = [];
for (let i = 0; i < identifiers.length; i += chunkSize) {
newIdentifiers.push({
PMID: identifiers.slice(i, i + chunkSize).map(x => x.PMID)
});
}
identifiers = newIdentifiers;
}
let newItems = [];
for (let identifier of identifiers) {
let translate = new Zotero.Translate.Search();
translate.setIdentifier(identifier); translate.setIdentifier(identifier);
// be lenient about translators // be lenient about translators
@ -90,20 +108,20 @@ var Zotero_Lookup = new function () {
translate.setTranslator(translators); translate.setTranslator(translators);
try { try {
newItems = await translate.translate({ newItems.push(...await translate.translate({
libraryID, libraryID,
collections, collections,
saveAttachments: !childItem saveAttachments: !childItem
}); }));
} }
// Continue with other ids on failure // Continue with other ids on failure
catch (e) { catch (e) {
Zotero.logError(e); Zotero.logError(e);
} }
})); }
toggleProgress(false); toggleProgress(false);
if (!newItems) { if (!newItems.length) {
Zotero.alert( Zotero.alert(
window, window,
Zotero.getString("lookup.failure.title"), Zotero.getString("lookup.failure.title"),
@ -125,7 +143,7 @@ var Zotero_Lookup = new function () {
on => Zotero_Lookup.toggleProgress(on) on => Zotero_Lookup.toggleProgress(on)
); );
if (newItems) { if (newItems.length) {
document.getElementById("zotero-lookup-panel").hidePopup(); document.getElementById("zotero-lookup-panel").hidePopup();
} }
return false; return false;
@ -134,6 +152,10 @@ var Zotero_Lookup = new function () {
this.showPanel = function (button) { this.showPanel = function (button) {
var panel = document.getElementById('zotero-lookup-panel'); var panel = document.getElementById('zotero-lookup-panel');
this._button = button;
if (!button) {
button = document.getElementById("zotero-tb-lookup");
}
panel.openPopup(button, "after_start", 16, -2, false, false); panel.openPopup(button, "after_start", 16, -2, false, false);
} }
@ -201,6 +223,25 @@ var Zotero_Lookup = new function () {
} }
return true; return true;
} }
this.onFocusOut = function(event) {
/*
if the lookup popup was triggered by the lookup button,
we want to return there on focus out. So we check
(1) that we came from a button and (2) that
event.relatedTarget === null, i.e. that the user hasn't used
the mouse or keyboard to select something, and focus is leaving
the popup because the popup was hidden/dismissed.
*/
if (this._button && event.relatedTarget === null) {
event.preventDefault();
event.stopPropagation();
this._button.focus();
this._button = null;
} else {
this._button = null;
}
}
this.onInput = function (event, textbox) { this.onInput = function (event, textbox) {

View file

@ -27,8 +27,6 @@ Components.utils.import("resource://gre/modules/Services.jsm");
import FilePicker from 'zotero/modules/filePicker'; import FilePicker from 'zotero/modules/filePicker';
Zotero_Preferences.Advanced = { Zotero_Preferences.Advanced = {
DEFAULT_OPENURL_RESOLVER: 'https://www.worldcat.org/registry/gateway',
_openURLResolvers: null, _openURLResolvers: null,
init: function () { init: function () {
@ -462,12 +460,6 @@ Zotero_Preferences.Advanced = {
var menupopup = openURLMenu.firstChild; var menupopup = openURLMenu.firstChild;
menupopup.innerHTML = ''; menupopup.innerHTML = '';
var defaultMenuItem = document.createElement('menuitem');
defaultMenuItem.setAttribute('label', Zotero.getString('general.default'));
defaultMenuItem.setAttribute('value', this.DEFAULT_OPENURL_RESOLVER);
defaultMenuItem.setAttribute('type', 'checkbox');
menupopup.appendChild(defaultMenuItem);
var customMenuItem = document.createElement('menuitem'); var customMenuItem = document.createElement('menuitem');
customMenuItem.setAttribute('label', Zotero.getString('general.custom')); customMenuItem.setAttribute('label', Zotero.getString('general.custom'));
customMenuItem.setAttribute('value', 'custom'); customMenuItem.setAttribute('value', 'custom');
@ -521,13 +513,8 @@ Zotero_Preferences.Advanced = {
} }
} }
// Default // From directory
if (currentResolver == this.DEFAULT_OPENURL_RESOLVER) { if (selectedName) {
openURLMenu.setAttribute('label', Zotero.getString('general.default'));
defaultMenuItem.setAttribute('checked', true);
Zotero.Prefs.clear('openURL.name');
}
else if (selectedName) {
openURLMenu.setAttribute('label', selectedName); openURLMenu.setAttribute('label', selectedName);
// If we found a match, update stored name // If we found a match, update stored name
Zotero.Prefs.set('openURL.name', selectedName); Zotero.Prefs.set('openURL.name', selectedName);
@ -555,15 +542,8 @@ Zotero_Preferences.Advanced = {
var openURLServerField = document.getElementById('openURLServerField'); var openURLServerField = document.getElementById('openURLServerField');
var openURLVersionMenu = document.getElementById('openURLVersionMenu'); var openURLVersionMenu = document.getElementById('openURLVersionMenu');
// Default
if (event.target.value == this.DEFAULT_OPENURL_RESOLVER) {
Zotero.Prefs.clear('openURL.name');
Zotero.Prefs.clear('openURL.resolver');
Zotero.Prefs.clear('openURL.version');
openURLServerField.value = this.DEFAULT_OPENURL_RESOLVER;
}
// If "Custom" selected, clear URL field // If "Custom" selected, clear URL field
else if (event.target.value == "custom") { if (event.target.value == "custom") {
Zotero.Prefs.clear('openURL.name'); Zotero.Prefs.clear('openURL.name');
Zotero.Prefs.set('openURL.resolver', ''); Zotero.Prefs.set('openURL.resolver', '');
Zotero.Prefs.clear('openURL.version'); Zotero.Prefs.clear('openURL.version');

View file

@ -30,7 +30,6 @@ import FilePicker from 'zotero/modules/filePicker';
var React = require('react'); var React = require('react');
var ReactDOM = require('react-dom'); var ReactDOM = require('react-dom');
var VirtualizedTable = require('components/virtualized-table'); var VirtualizedTable = require('components/virtualized-table');
var { IntlProvider } = require('react-intl');
var { makeRowRenderer } = VirtualizedTable; var { makeRowRenderer } = VirtualizedTable;
Zotero_Preferences.Cite = { Zotero_Preferences.Cite = {
@ -118,22 +117,20 @@ Zotero_Preferences.Cite = {
} }
}; };
let elem = ( let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <VirtualizedTable
<VirtualizedTable getRowCount={() => this.styles.length}
getRowCount={() => this.styles.length} id="styleManager-table"
id="styleManager-table" ref={ref => this._tree = ref}
ref={ref => this._tree = ref} renderItem={makeRowRenderer(index => this.styles[index])}
renderItem={makeRowRenderer(index => this.styles[index])} showHeader={true}
showHeader={true} multiSelect={true}
multiSelect={true} columns={columns}
columns={columns} staticColumns={true}
staticColumns={true} disableFontSizeScaling={true}
disableFontSizeScaling={true} onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined}
onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined} onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown} getRowString={index => this.styles[index].title}
getRowString={index => this.styles[index].title} />
/>
</IntlProvider>
); );
await new Promise(resolve => ReactDOM.render(elem, document.getElementById("styleManager"), resolve)); await new Promise(resolve => ReactDOM.render(elem, document.getElementById("styleManager"), resolve));
} }

View file

@ -28,7 +28,6 @@
var React = require('react'); var React = require('react');
var ReactDOM = require('react-dom'); var ReactDOM = require('react-dom');
var VirtualizedTable = require('components/virtualized-table'); var VirtualizedTable = require('components/virtualized-table');
var { IntlProvider } = require('react-intl');
var { makeRowRenderer } = VirtualizedTable; var { makeRowRenderer } = VirtualizedTable;
Zotero_Preferences.Export = { Zotero_Preferences.Export = {
@ -89,12 +88,16 @@ Zotero_Preferences.Export = {
* Builds the note Quick Copy drop-down from the current global pref * Builds the note Quick Copy drop-down from the current global pref
*/ */
populateNoteQuickCopyList: async function () { populateNoteQuickCopyList: async function () {
document.getElementById('noteQuickCopy-format-options').removeAttribute('hidden');
// Initialize default format drop-down // Initialize default format drop-down
var format = Zotero.Prefs.get("export.noteQuickCopy.setting"); var format = Zotero.Prefs.get("export.noteQuickCopy.setting");
format = Zotero.QuickCopy.unserializeSetting(format); format = Zotero.QuickCopy.unserializeSetting(format);
var menulist = document.getElementById("zotero-noteQuickCopy-menu"); var menulist = document.getElementById("zotero-noteQuickCopy-menu");
menulist.setAttribute('preference', "pref-noteQuickCopy-setting"); menulist.setAttribute('preference', "pref-noteQuickCopy-setting");
menulist.removeEventListener('command', this.updateNoteQuickCopyUI);
menulist.addEventListener('command', this.updateNoteQuickCopyUI);
if (!format) { if (!format) {
format = menulist.value; format = menulist.value;
} }
@ -149,11 +152,42 @@ Zotero_Preferences.Export = {
return; return;
} }
var val = JSON.stringify({ mode: 'export', id: translator.translatorID }); var value = { mode: 'export', id: translator.translatorID };
if (translator.translatorID == format.id) {
value = format;
}
else if (translator.translatorID == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
value = {
mode: 'export',
id: translator.translatorID,
markdownOptions: {
includeAppLinks: true
},
htmlOptions: {
includeAppLinks: false
}
};
if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML && format.options) {
value.htmlOptions = format.options;
}
}
else if (translator.translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
value = {
mode: 'export',
id: translator.translatorID,
options: {
includeAppLinks: false
}
};
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT && format.htmlOptions) {
value.options = format.htmlOptions;
}
}
value = JSON.stringify(value);
var itemNode = document.createElement('menuitem'); var itemNode = document.createElement('menuitem');
itemNode.setAttribute('value', val); itemNode.setAttribute('value', value);
itemNode.setAttribute('label', translator.label); itemNode.setAttribute('label', translator.label);
// itemNode.setAttribute('oncommand', 'Zotero_Preferences.Export.updateQuickCopyUI()');
popup.appendChild(itemNode); popup.appendChild(itemNode);
if (format.mode == 'export' && format.id == translator.translatorID) { if (format.mode == 'export' && format.id == translator.translatorID) {
@ -162,6 +196,76 @@ Zotero_Preferences.Export = {
}); });
menulist.click(); menulist.click();
this.updateNoteQuickCopyUI();
},
updateNoteQuickCopyUI: () => {
var format = document.getElementById('zotero-noteQuickCopy-menu').value;
format = JSON.parse(format);
var markdownOptions = document.getElementById('noteQuickCopy-markdown-options');
var htmlOptions = document.getElementById('noteQuickCopy-html-options');
var markdownOptionsLabel = document.querySelector('#noteQuickCopy-markdown-options label');
var htmlOptionsLabel = document.querySelector('#noteQuickCopy-html-options label');
var markdownIncludeAppLinks = document.getElementById("noteQuickCopy-markdown-includeAppLinks");
var htmlIncludeAppLinks = document.getElementById("noteQuickCopy-html-includeAppLinks");
markdownOptionsLabel.value = Zotero.Utilities.Internal.stringWithColon("Markdown");
htmlOptionsLabel.value = Zotero.Utilities.Internal.stringWithColon(
Zotero.getString('zotero.preferences.export.quickCopy.note.htmlOptions.label')
);
markdownIncludeAppLinks.label = Zotero.getString('exportOptions.includeAppLinks', Zotero.appName);
htmlIncludeAppLinks.label = Zotero.getString('exportOptions.includeAppLinks', Zotero.appName);
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
markdownOptions.hidden = false;
htmlOptions.hidden = false;
markdownIncludeAppLinks.checked = format.markdownOptions && format.markdownOptions.includeAppLinks;
htmlIncludeAppLinks.checked = format.htmlOptions && format.htmlOptions.includeAppLinks;
}
else if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
markdownOptions.hidden = true;
htmlOptions.hidden = false;
htmlIncludeAppLinks.checked = format.options && format.options.includeAppLinks;
}
else {
markdownOptions.hidden = true;
htmlOptions.hidden = true;
}
},
onUpdateNoteExportOptions() {
var menulist = document.getElementById("zotero-noteQuickCopy-menu");
var markdownIncludeAppLinks = document.getElementById("noteQuickCopy-markdown-includeAppLinks");
var htmlIncludeAppLinks = document.getElementById("noteQuickCopy-html-includeAppLinks");
for (let i = 0; i < menulist.itemCount; i++) {
let item = menulist.getItemAtIndex(i);
let format = JSON.parse(item.getAttribute('value'));
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
if (!format.markdownOptions) {
format.markdownOptions = {};
}
if (!format.htmlOptions) {
format.htmlOptions = {};
}
format.markdownOptions.includeAppLinks = markdownIncludeAppLinks.checked;
format.htmlOptions.includeAppLinks = htmlIncludeAppLinks.checked;
}
else if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
if (!format.options) {
format.options = {};
}
format.options.includeAppLinks = htmlIncludeAppLinks.checked;
}
else {
continue;
}
item.value = JSON.stringify(format);
}
// After updating menulist item value we have to wait a bit before doing click(), to avoid anomalies
// like an empty row in menulist. 0 in setTimeout is not enough
setTimeout(() => menulist.click(), 50);
}, },
@ -387,22 +491,20 @@ Zotero_Preferences.Export = {
}; };
let elem = ( let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <VirtualizedTable
<VirtualizedTable getRowCount={() => this._rows.length}
getRowCount={() => this._rows.length} id="quickCopy-siteSettings-table"
id="quickCopy-siteSettings-table" ref={ref => this._tree = ref}
ref={ref => this._tree = ref} renderItem={makeRowRenderer(index => this._rows[index])}
renderItem={makeRowRenderer(index => this._rows[index])} showHeader={true}
showHeader={true} columns={columns}
columns={columns} staticColumns={true}
staticColumns={true} disableFontSizeScaling={true}
disableFontSizeScaling={true} onSelectionChange={handleSelectionChange}
onSelectionChange={handleSelectionChange} onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown} getRowString={index => this._rows[index].domain}
getRowString={index => this._rows[index].domain} onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()}
onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()} />
/>
</IntlProvider>
); );
await new Promise(resolve => ReactDOM.render(elem, document.getElementById("quickCopy-siteSettings"), resolve)); await new Promise(resolve => ReactDOM.render(elem, document.getElementById("quickCopy-siteSettings"), resolve));
} else { } else {

View file

@ -71,9 +71,38 @@
<label value="&zotero.preferences.quickCopy.noteFormat;" control="zotero-noteQuickCopy-menu"/> <label value="&zotero.preferences.quickCopy.noteFormat;" control="zotero-noteQuickCopy-menu"/>
<menulist id="zotero-noteQuickCopy-menu" label="&zotero.general.loading;"/> <menulist id="zotero-noteQuickCopy-menu" label="&zotero.general.loading;"/>
<table id="noteQuickCopy-format-options"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
hidden="hidden">
<tbody id="noteQuickCopy-markdown-options">
<tr>
<th>
<xul:label/>
</th>
<td>
<xul:checkbox id="noteQuickCopy-markdown-includeAppLinks"
oncommand="Zotero_Preferences.Export.onUpdateNoteExportOptions()"/>
</td>
</tr>
</tbody>
<tbody id="noteQuickCopy-html-options">
<tr>
<th>
<xul:label/>
</th>
<td>
<xul:checkbox id="noteQuickCopy-html-includeAppLinks"
oncommand="Zotero_Preferences.Export.onUpdateNoteExportOptions()"/>
</td>
</tr>
</tbody>
</table>
<separator/> <separator/>
<label value="&zotero.preferences.quickCopy.siteEditor.setings;" control="quickCopy-siteSettings"/> <label value="&zotero.preferences.quickCopy.siteEditor.setings;" control="quickCopy-siteSettings"/>
<hbox class="virtualized-table-container" flex="1" height="120px"> <hbox class="virtualized-table-container" flex="1" height="120px">
<html:div id="quickCopy-siteSettings"/> <html:div id="quickCopy-siteSettings"/>

View file

@ -40,6 +40,8 @@
<preference id="pref-automaticTags" name="extensions.zotero.automaticTags" type="bool"/> <preference id="pref-automaticTags" name="extensions.zotero.automaticTags" type="bool"/>
<preference id="pref-trashAutoEmptyDays" name="extensions.zotero.trashAutoEmptyDays" type="int"/> <preference id="pref-trashAutoEmptyDays" name="extensions.zotero.trashAutoEmptyDays" type="int"/>
<preference id="pref-reader-tabs-title" name="extensions.zotero.tabs.title.reader" type="string"/>
<preference id="pref-groups-copyChildNotes" name="extensions.zotero.groups.copyChildNotes" type="bool"/> <preference id="pref-groups-copyChildNotes" name="extensions.zotero.groups.copyChildNotes" type="bool"/>
<preference id="pref-groups-copyChildFileAttachments" name="extensions.zotero.groups.copyChildFileAttachments" type="bool"/> <preference id="pref-groups-copyChildFileAttachments" name="extensions.zotero.groups.copyChildFileAttachments" type="bool"/>
<preference id="pref-groups-copyAnnotations" name="extensions.zotero.groups.copyAnnotations" type="bool"/> <preference id="pref-groups-copyAnnotations" name="extensions.zotero.groups.copyAnnotations" type="bool"/>
@ -90,6 +92,22 @@
</hbox> </hbox>
</groupbox> </groupbox>
<groupbox id="zotero-prefpane-reader-groupbox">
<caption label="&zotero.preferences.pdfReader;"/>
<hbox align="center">
<label value="&zotero.preferences.reader.tabsTitle.label;" control="reader-tabs-title"/>
<menulist id="reader-tabs-title"
preference="pref-reader-tabs-title">
<menupopup>
<menuitem label="&zotero.preferences.reader.tabsTitle.titleCreatorYear;" value="titleCreatorYear"/>
<menuitem label="&zotero.preferences.reader.tabsTitle.creatorYearTitle;" value="creatorYearTitle"/>
<menuitem label="&zotero.preferences.reader.tabsTitle.filename;" value="filename"/>
</menupopup>
</menulist>
</hbox>
</groupbox>
<groupbox> <groupbox>
<caption label="&zotero.preferences.groups;"/> <caption label="&zotero.preferences.groups;"/>

View file

@ -32,7 +32,6 @@ var React = require('react');
var ReactDOM = require('react-dom'); var ReactDOM = require('react-dom');
var VirtualizedTable = require('components/virtualized-table'); var VirtualizedTable = require('components/virtualized-table');
var { getDOMElement } = require('components/icons'); var { getDOMElement } = require('components/icons');
var { IntlProvider } = require('react-intl');
var { renderCell } = VirtualizedTable; var { renderCell } = VirtualizedTable;
Zotero_Preferences.Sync = { Zotero_Preferences.Sync = {
@ -319,20 +318,18 @@ Zotero_Preferences.Sync = {
} }
}; };
let elem = ( let elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <VirtualizedTable
<VirtualizedTable getRowCount={() => this._rows.length}
getRowCount={() => this._rows.length} id="librariesToSync-table"
id="librariesToSync-table" ref={ref => this._tree = ref}
ref={ref => this._tree = ref} renderItem={renderItem}
renderItem={renderItem} showHeader={true}
showHeader={true} columns={columns}
columns={columns} staticColumns={true}
staticColumns={true} getRowString={index => this._rows[index].name}
getRowString={index => this._rows[index].name} disableFontSizeScaling={true}
disableFontSizeScaling={true} onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown} />
/>
</IntlProvider>
); );
ReactDOM.render(elem, document.getElementById("libraries-to-sync-tree")); ReactDOM.render(elem, document.getElementById("libraries-to-sync-tree"));

View file

@ -288,15 +288,15 @@
<ul id="sync-reset-list"> <ul id="sync-reset-list">
<!--<li> <!--<li>
<p>&zotero.preferences.sync.reset.restoreFromServer;</p> <p>&zotero.preferences.sync.reset.restoreFromOnlineLibrary;</p>
<p>&zotero.preferences.sync.reset.restoreFromServer.desc;</p> <p>&zotero.preferences.sync.reset.restoreFromOnlineLibrary.desc;</p>
</li>--> </li>-->
<li id="restore-to-server"> <li id="restore-to-server">
<label> <label>
<input name="sync-reset-radiogroup" value="restore-to-server" type="radio"/> <input name="sync-reset-radiogroup" value="restore-to-server" type="radio"/>
<span class="sync-reset-option-name">&zotero.preferences.sync.reset.restoreToServer;</span> <span class="sync-reset-option-name">&zotero.preferences.sync.reset.restoreToOnlineLibrary;</span>
<span class="sync-reset-option-desc">&zotero.preferences.sync.reset.restoreToServer.desc;</span> <span class="sync-reset-option-desc">&zotero.preferences.sync.reset.restoreToOnlineLibrary.desc;</span>
</label> </label>
</li> </li>

View file

@ -69,6 +69,25 @@
<menuitem id="menu_copy"/> <menuitem id="menu_copy"/>
<menuitem id="menu_paste"/> <menuitem id="menu_paste"/>
<menuitem id="menu_delete"/> <menuitem id="menu_delete"/>
<menuseparator class="menu-type-reader"/>
<menuitem
id="edit-menuitem-rotate-left"
class="menu-type-reader"
label="&zotero.pdfReader.rotatePageLeft;"
oncommand="menuCmd('rotateLeft')"
/>
<menuitem
id="edit-menuitem-rotate-right"
class="menu-type-reader"
label="&zotero.pdfReader.rotatePageRight;"
oncommand="menuCmd('rotateRight')"
/>
<!--<menuitem
id="edit-menuitem-rotate-180"
class="menu-type-reader"
label="&zotero.pdfReader.rotatePage180;"
oncommand="menuCmd('rotate180')"
/>-->
</menupopup> </menupopup>
</menu> </menu>
<menu label="View" onpopupshowing="onViewMenuOpen()"> <menu label="View" onpopupshowing="onViewMenuOpen()">
@ -81,15 +100,6 @@
--> -->
<!-- <menuitem label="Switch to Presentation Mode" oncommand="menuCmd('presentationmode')"/>--> <!-- <menuitem label="Switch to Presentation Mode" oncommand="menuCmd('presentationmode')"/>-->
<!-- <menuseparator/>--> <!-- <menuseparator/>-->
<menuitem
label="&zotero.pdfReader.rotateClockwise;"
oncommand="menuCmd('rotatecw')"
/>
<menuitem
label="&zotero.pdfReader.rotateCounterclockwise;"
oncommand="menuCmd('rotateccw')"
/>
<menuseparator/>
<menuitem <menuitem
id="view-menuitem-hand-tool" id="view-menuitem-hand-tool"
label="&zotero.pdfReader.handTool;" label="&zotero.pdfReader.handTool;"
@ -163,6 +173,19 @@
label="&zotero.pdfReader.zoomPageHeight;" label="&zotero.pdfReader.zoomPageHeight;"
oncommand="menuCmd('zoomPageHeight')" oncommand="menuCmd('zoomPageHeight')"
/> />
<menuseparator class="menu-type-reader"/>
<menuitem
id="view-menuitem-split-horizontally"
type="checkbox"
label="&zotero.pdfReader.splitHorizontally;"
oncommand="menuCmd('splitHorizontally')"
/>
<menuitem
id="view-menuitem-split-vertically"
type="checkbox"
label="&zotero.pdfReader.splitVertically;"
oncommand="menuCmd('splitVertically')"
/>
</menupopup> </menupopup>
</menu> </menu>
<menu <menu

View file

@ -30,9 +30,8 @@ var Zotero_Report_Interface = new function() {
*/ */
this.loadCollectionReport = function () { this.loadCollectionReport = function () {
var sortColumn = ZoteroPane_Local.getSortField(); var sortColumn = ZoteroPane_Local.getSortField();
var sortDirection = ZoteroPane_Local.getSortDirection();
var queryString = '?sort=' + sortColumn var queryString = '?sort=' + sortColumn
+ '&direction=' + (sortDirection == 'ascending' ? 'asc' : 'desc'); + '&direction=' + (ZoteroPane.getSortDirection() == 1 ? 'asc' : 'desc');
var url = 'zotero://report/'; var url = 'zotero://report/';

View file

@ -31,7 +31,6 @@ import FilePicker from 'zotero/modules/filePicker';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import VirtualizedTable from 'components/virtualized-table'; import VirtualizedTable from 'components/virtualized-table';
import { IntlProvider } from 'react-intl';
import { getDOMElement } from 'components/icons'; import { getDOMElement } from 'components/icons';
/** /**
@ -51,9 +50,9 @@ var Zotero_RTFScan = new function() {
var ids = 0; var ids = 0;
var tree; var tree;
this._rows = [ this._rows = [
{ id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false }, { id: 'unmapped', rtf: Zotero.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false },
{ id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false }, { id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false },
{ id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false }, { id: 'mapped', rtf: Zotero.getString('zotero.rtfScan.mappedCitations.label'), collapsed: false },
]; ];
this._rowMap = {}; this._rowMap = {};
this._rows.forEach((row, index) => this._rowMap[row.id] = index); this._rows.forEach((row, index) => this._rowMap[row.id] = index);
@ -388,9 +387,9 @@ var Zotero_RTFScan = new function() {
document.documentElement.currentPage = document.getElementById('intro-page'); document.documentElement.currentPage = document.getElementById('intro-page');
this._rows = [ this._rows = [
{ id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false }, { id: 'unmapped', rtf: Zotero.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false },
{ id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false }, { id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false },
{ id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false }, { id: 'mapped', rtf: Zotero.getString('zotero.rtfScan.mappedCitations.label'), collapsed: false },
]; ];
this._rowMap = {}; this._rowMap = {};
this._rows.forEach((row, index) => this._rowMap[row.id] = index); this._rows.forEach((row, index) => this._rowMap[row.id] = index);
@ -768,17 +767,15 @@ var Zotero_RTFScan = new function() {
this._initCitationTree = function () { this._initCitationTree = function () {
const domEl = document.querySelector('#tree'); const domEl = document.querySelector('#tree');
const elem = ( const elem = (
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}> <VirtualizedTable
<VirtualizedTable getRowCount={() => this._rows.length}
getRowCount={() => this._rows.length} id="rtfScan-table"
id="rtfScan-table" ref={ref => tree = ref}
ref={ref => tree = ref} renderItem={this._renderItem}
renderItem={this._renderItem} showHeader={true}
showHeader={true} columns={columns}
columns={columns} disableFontSizeScaling={true}
disableFontSizeScaling={true} />
/>
</IntlProvider>
); );
return new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); return new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
}; };

View file

@ -91,8 +91,12 @@ function doUnload()
} }
var onCollectionSelected = async function () { var onCollectionSelected = async function () {
if (!collectionsView.selection.count) return;
var collectionTreeRow = collectionsView.getRow(collectionsView.selection.focused); var collectionTreeRow = collectionsView.getRow(collectionsView.selection.focused);
if (!collectionsView.selection.count) return;
// Collection not changed
if (itemsView && itemsView.collectionTreeRow && itemsView.collectionTreeRow.id == collectionTreeRow.id) {
return;
}
collectionTreeRow.setSearch(''); collectionTreeRow.setSearch('');
Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id); Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);

View file

@ -160,8 +160,7 @@ const ZoteroStandalone = new function() {
let item = Zotero.Items.get(reader.itemID); let item = Zotero.Items.get(reader.itemID);
let library = Zotero.Libraries.get(item.libraryID); let library = Zotero.Libraries.get(item.libraryID);
if (item if (item
// Don't allow annotation transfer in group libraries && library.filesEditable
&& library.libraryType == 'user'
&& library.editable && library.editable
&& !(item.deleted || item.parentItem && item.parentItem.deleted)) { && !(item.deleted || item.parentItem && item.parentItem.deleted)) {
let annotations = item.getAnnotations(); let annotations = item.getAnnotations();
@ -192,12 +191,9 @@ const ZoteroStandalone = new function() {
if (numFiles) { if (numFiles) {
menuitem.hidden = false; menuitem.hidden = false;
sep.hidden = false; sep.hidden = false;
if (numFiles == 1) { menuitem.label = Zotero.getString(
menuitem.label = 'Export PDF…'; 'pane.items.menu.exportPDF' + (numFiles == 1 ? '' : '.multiple')
} );
else {
menuitem.label = 'Export PDFs…';
}
} }
else { else {
menuitem.hidden = true; menuitem.hidden = true;
@ -408,6 +404,8 @@ const ZoteroStandalone = new function() {
this.updateMenuItemCheckmark('view-menuitem-zoom-auto', reader.isZoomAutoActive()); this.updateMenuItemCheckmark('view-menuitem-zoom-auto', reader.isZoomAutoActive());
this.updateMenuItemCheckmark('view-menuitem-zoom-page-width', reader.isZoomPageWidthActive()); this.updateMenuItemCheckmark('view-menuitem-zoom-page-width', reader.isZoomPageWidthActive());
this.updateMenuItemCheckmark('view-menuitem-zoom-page-height', reader.isZoomPageHeightActive()); this.updateMenuItemCheckmark('view-menuitem-zoom-page-height', reader.isZoomPageHeightActive());
this.updateMenuItemCheckmark('view-menuitem-split-vertically', reader.isSplitVerticallyActive());
this.updateMenuItemCheckmark('view-menuitem-split-horizontally', reader.isSplitHorizontallyActive());
} }
// Layout mode // Layout mode

View file

@ -232,6 +232,25 @@
label="&bidiSwitchTextDirectionItem.label;" label="&bidiSwitchTextDirectionItem.label;"
accesskey="&bidiSwitchTextDirectionItem.accesskey;" accesskey="&bidiSwitchTextDirectionItem.accesskey;"
hidden="true"/> hidden="true"/>
<menuseparator class="menu-type-reader"/>
<menuitem
id="edit-menuitem-rotate-left"
class="menu-type-reader"
label="&zotero.pdfReader.rotatePageLeft;"
oncommand="ZoteroStandalone.onReaderCmd('rotateLeft')"
/>
<menuitem
id="edit-menuitem-rotate-right"
class="menu-type-reader"
label="&zotero.pdfReader.rotatePageRight;"
oncommand="ZoteroStandalone.onReaderCmd('rotateRight')"
/>
<!--<menuitem
id="edit-menuitem-rotate-180"
class="menu-type-reader"
label="&zotero.pdfReader.rotatePage180;"
oncommand="ZoteroStandalone.onReaderCmd('rotate180')"
/>-->
</menupopup> </menupopup>
</menu> </menu>
@ -240,17 +259,6 @@
accesskey="&viewMenu.accesskey;" accesskey="&viewMenu.accesskey;"
onpopupshowing="ZoteroStandalone.onViewMenuOpen()"> onpopupshowing="ZoteroStandalone.onViewMenuOpen()">
<menupopup id="menu_viewPopup"> <menupopup id="menu_viewPopup">
<menuitem
class="menu-type-reader"
label="&zotero.pdfReader.rotateClockwise;"
oncommand="ZoteroStandalone.onReaderCmd('rotatecw')"
/>
<menuitem
class="menu-type-reader"
label="&zotero.pdfReader.rotateCounterclockwise;"
oncommand="ZoteroStandalone.onReaderCmd('rotateccw')"
/>
<menuseparator class="menu-type-reader"/>
<menuitem <menuitem
id="view-menuitem-hand-tool" id="view-menuitem-hand-tool"
class="menu-type-reader" class="menu-type-reader"
@ -337,6 +345,21 @@
oncommand="ZoteroStandalone.onReaderCmd('zoomPageHeight')" oncommand="ZoteroStandalone.onReaderCmd('zoomPageHeight')"
/> />
<menuseparator class="menu-type-reader"/> <menuseparator class="menu-type-reader"/>
<menuitem
id="view-menuitem-split-horizontally"
class="menu-type-reader"
type="checkbox"
label="&zotero.pdfReader.splitHorizontally;"
oncommand="ZoteroStandalone.onReaderCmd('splitHorizontally')"
/>
<menuitem
id="view-menuitem-split-vertically"
class="menu-type-reader"
type="checkbox"
label="&zotero.pdfReader.splitVertically;"
oncommand="ZoteroStandalone.onReaderCmd('splitVertically')"
/>
<menuseparator class="menu-type-reader"/>
<menu id="layout-menu" label="&layout.label;"> <menu id="layout-menu" label="&layout.label;">
<menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)"> <menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)">
@ -390,6 +413,8 @@
<menuitem label="12" type="checkbox"/> <menuitem label="12" type="checkbox"/>
<menuitem label="13" type="checkbox"/> <menuitem label="13" type="checkbox"/>
<menuitem label="14" type="checkbox"/> <menuitem label="14" type="checkbox"/>
<menuitem label="15" type="checkbox"/>
<menuitem label="16" type="checkbox"/>
<menuitem label="18" type="checkbox"/> <menuitem label="18" type="checkbox"/>
<menuitem label="24" type="checkbox"/> <menuitem label="24" type="checkbox"/>
<menuitem label="36" type="checkbox"/> <menuitem label="36" type="checkbox"/>

View file

@ -30,7 +30,10 @@ var React = require('react');
var ReactDOM = require('react-dom'); var ReactDOM = require('react-dom');
import TabBar from 'components/tabBar'; import TabBar from 'components/tabBar';
const MAX_LOADED_TABS = 5; // Reduce loaded tabs limit if the system has 8 GB or less memory, or it's running
// on Windows which means it doesn't support more than 3 GB
// TODO: Revise this after upgrading to Zotero 7
const MAX_LOADED_TABS = Services.sysinfo.getProperty("memsize") / 1024 / 1024 / 1024 <= 8 || Zotero.isWin ? 3 : 5;
const UNLOAD_UNUSED_AFTER = 86400; // 24h const UNLOAD_UNUSED_AFTER = 86400; // 24h
var Zotero_Tabs = new function () { var Zotero_Tabs = new function () {
@ -84,6 +87,17 @@ var Zotero_Tabs = new function () {
window.Zotero_Tooltip.stop(); window.Zotero_Tooltip.stop();
}; };
this.getTabIDByItemID = function (itemID) {
let tab = this._tabs.find(tab => tab.data && tab.data.itemID === itemID);
return tab && tab.id;
};
this.setSecondViewState = function (tabID, state) {
let { tab } = this._getTab(tabID);
tab.data.secondViewState = state;
Zotero.Session.debounceSave();
};
this.init = function () { this.init = function () {
ReactDOM.render( ReactDOM.render(
<TabBar <TabBar
@ -128,12 +142,24 @@ var Zotero_Tabs = new function () {
} }
else if (tab.type === 'reader') { else if (tab.type === 'reader') {
if (Zotero.Items.exists(tab.data.itemID)) { if (Zotero.Items.exists(tab.data.itemID)) {
this.add({ if (tab.selected) {
type: 'reader-unloaded', Zotero.Reader.open(tab.data.itemID,
title: tab.title, null,
index: i, {
data: tab.data title: tab.title,
}); openInBackground: !tab.selected,
secondViewState: tab.data.secondViewState
}
);
}
else {
this.add({
type: 'reader-unloaded',
title: tab.title,
index: i,
data: tab.data
});
}
} }
} }
} }
@ -152,7 +178,7 @@ var Zotero_Tabs = new function () {
* @param {Function} onClose * @param {Function} onClose
* @return {{ id: string, container: XULElement}} id - tab id, container - a new tab container created in the deck * @return {{ id: string, container: XULElement}} id - tab id, container - a new tab container created in the deck
*/ */
this.add = function ({ type, data, title, index, select, onClose }) { this.add = function ({ id, type, data, title, index, select, onClose }) {
if (typeof type != 'string') { if (typeof type != 'string') {
} }
if (typeof title != 'string') { if (typeof title != 'string') {
@ -164,7 +190,7 @@ var Zotero_Tabs = new function () {
if (onClose !== undefined && typeof onClose != 'function') { if (onClose !== undefined && typeof onClose != 'function') {
throw new Error(`'onClose' should be a function (was ${typeof onClose})`); throw new Error(`'onClose' should be a function (was ${typeof onClose})`);
} }
var id = 'tab-' + Zotero.Utilities.randomString(); id = id || 'tab-' + Zotero.Utilities.randomString();
var container = document.createElement('vbox'); var container = document.createElement('vbox');
container.id = id; container.id = id;
this.deck.appendChild(container); this.deck.appendChild(container);
@ -251,7 +277,7 @@ var Zotero_Tabs = new function () {
closedIDs.push(id); closedIDs.push(id);
} }
this._history.push(historyEntry); this._history.push(historyEntry);
Zotero.Notifier.trigger('close', 'tab', [closedIDs]); Zotero.Notifier.trigger('close', 'tab', [closedIDs], true);
this._update(); this._update();
}; };
@ -321,7 +347,7 @@ var Zotero_Tabs = new function () {
* @param {String} id * @param {String} id
* @param {Boolean} reopening * @param {Boolean} reopening
*/ */
this.select = function (id, reopening) { this.select = function (id, reopening, options) {
var { tab, tabIndex } = this._getTab(id); var { tab, tabIndex } = this._getTab(id);
if (!tab || tab.id === this._selectedID) { if (!tab || tab.id === this._selectedID) {
return; return;
@ -332,10 +358,12 @@ var Zotero_Tabs = new function () {
} }
if (tab.type === 'reader-unloaded') { if (tab.type === 'reader-unloaded') {
this.close(tab.id); this.close(tab.id);
Zotero.Reader.open(tab.data.itemID, null, { Zotero.Reader.open(tab.data.itemID, options && options.location, {
tabID: tab.id,
title: tab.title, title: tab.title,
tabIndex, tabIndex,
allowDuplicate: true allowDuplicate: true,
secondViewState: tab.data.secondViewState
}); });
return; return;
} }
@ -385,6 +413,7 @@ var Zotero_Tabs = new function () {
} }
this.close(tab.id); this.close(tab.id);
this.add({ this.add({
id: tab.id,
type: 'reader-unloaded', type: 'reader-unloaded',
title: tab.title, title: tab.title,
index: tabIndex, index: tabIndex,
@ -440,7 +469,7 @@ var Zotero_Tabs = new function () {
}; };
this._openMenu = function (x, y, id) { this._openMenu = function (x, y, id) {
var { tabIndex } = this._getTab(id); var { tab, tabIndex } = this._getTab(id);
window.Zotero_Tooltip.stop(); window.Zotero_Tooltip.stop();
let menuitem; let menuitem;
let popup = document.createElement('menupopup'); let popup = document.createElement('menupopup');
@ -497,7 +526,8 @@ var Zotero_Tabs = new function () {
var reader = Zotero.Reader.getByTabID(id); var reader = Zotero.Reader.getByTabID(id);
if (reader) { if (reader) {
this.close(id); this.close(id);
Zotero.Reader.open(reader.itemID, null, { openInWindow: true }); let { secondViewState } = tab.data;
Zotero.Reader.open(reader.itemID, null, { openInWindow: true, secondViewState });
} }
}); });
menupopup.appendChild(menuitem); menupopup.appendChild(menuitem);
@ -505,11 +535,10 @@ var Zotero_Tabs = new function () {
menuitem = document.createElement('menuitem'); menuitem = document.createElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('tabs.duplicate')); menuitem.setAttribute('label', Zotero.getString('tabs.duplicate'));
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
var { tab, tabIndex } = this._getTab(id);
if (tab.data.itemID) { if (tab.data.itemID) {
tabIndex++; tabIndex++;
Zotero.Reader.open(tab.data.itemID, null, { tabIndex, allowDuplicate: true }); let { secondViewState } = tab.data;
Zotero.Reader.open(tab.data.itemID, null, { tabIndex, allowDuplicate: true, secondViewState });
} }
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);

View file

@ -1,5 +1,5 @@
import FilePicker from 'zotero/modules/filePicker'; import FilePicker from 'zotero/modules/filePicker';
(async function () { (async function () {
// Create schema // Create schema
var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}}; var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
@ -77,11 +77,12 @@ import FilePicker from 'zotero/modules/filePicker';
resultElem.innerHTML = '<p>Failed.</p>'; resultElem.innerHTML = '<p>Failed.</p>';
} else { } else {
let schemaFile = Zotero.File.pathToFile(fp.file); let schemaFile = Zotero.File.pathToFile(fp.file);
schemaFile.append("connectorTypeSchemaData.js"); schemaFile.append("zoteroTypeSchemaData.js");
await Zotero.File.putContentsAsync( await Zotero.File.putContentsAsync(
schemaFile, schemaFile,
`Zotero.Connector_Types.schema = ${JSON.stringify(schema)}` `var ZOTERO_TYPE_SCHEMA = ${JSON.stringify(schema)};\n\n`
+ "if (typeof module !== 'undefined') {\n\tmodule.exports = ZOTERO_TYPE_SCHEMA;\n}\n"
); );
resultElem.innerHTML = `<p>Wrote ${schemaFile} successfully.</p>`; resultElem.innerHTML = `<p>Wrote ${schemaFile.path} successfully.</p>`;
} }
})(); })();

View file

@ -59,10 +59,8 @@ var Zotero_CSL_Editor = new function() {
var pageList = document.getElementById('zotero-csl-page-type'); var pageList = document.getElementById('zotero-csl-page-type');
var locators = Zotero.Cite.labels; var locators = Zotero.Cite.labels;
for (let type of locators) { for (let locator of locators) {
var locator = type; pageList.appendItem(Zotero.Cite.getLocatorString(locator), locator);
locator = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
pageList.appendItem(locator, type);
} }
pageList.selectedIndex = 0; pageList.selectedIndex = 0;

View file

@ -32,6 +32,10 @@ Zotero.Annotations = new function () {
Zotero.defineProperty(this, 'ANNOTATION_TYPE_NOTE', { value: 2 }); Zotero.defineProperty(this, 'ANNOTATION_TYPE_NOTE', { value: 2 });
Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 }); Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 });
Zotero.defineProperty(this, 'ANNOTATION_TYPE_INK', { value: 4 }); Zotero.defineProperty(this, 'ANNOTATION_TYPE_INK', { value: 4 });
Zotero.defineProperty(this, 'ANNOTATION_TYPE_UNDERLINE', { value: 5 });
Zotero.defineProperty(this, 'ANNOTATION_TYPE_TEXT', { value: 6 });
Zotero.defineProperty(this, 'DEFAULT_COLOR', { value: '#ffd400' });
Zotero.defineProperty(this, 'PROPS', { Zotero.defineProperty(this, 'PROPS', {
value: ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel', 'sortIndex', 'position'], value: ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel', 'sortIndex', 'position'],
@ -135,7 +139,7 @@ Zotero.Annotations = new function () {
} }
} }
o.readOnly = o.isExternal || !isAuthor; o.readOnly = o.isExternal || !isAuthor;
if (o.type == 'highlight') { if (['highlight', 'underline'].includes(o.type)) {
o.text = item.annotationText; o.text = item.annotationText;
} }
else if (['image', 'ink'].includes(o.type)) { else if (['image', 'ink'].includes(o.type)) {
@ -177,7 +181,7 @@ Zotero.Annotations = new function () {
o.tags = tags; o.tags = tags;
} }
o.dateModified = item.dateModified; o.dateModified = Zotero.Date.sqlToISO8601(item.dateModified);
return o; return o;
}; };
@ -211,7 +215,7 @@ Zotero.Annotations = new function () {
item._requireData('annotationDeferred'); item._requireData('annotationDeferred');
item.annotationType = json.type; item.annotationType = json.type;
item.annotationAuthorName = json.authorName || ''; item.annotationAuthorName = json.authorName || '';
if (json.type == 'highlight') { if (['highlight', 'underline'].includes(json.type)) {
item.annotationText = json.text; item.annotationText = json.text;
} }
item.annotationIsExternal = !!json.isExternal; item.annotationIsExternal = !!json.isExternal;
@ -231,6 +235,10 @@ Zotero.Annotations = new function () {
} }
} }
// Don't try to select annotation, which would clear an active quick search (at least
// until annotations are visible in the items list)
saveOptions.skipSelect = true;
await item.saveTx(saveOptions); await item.saveTx(saveOptions);
return item; return item;

View file

@ -23,7 +23,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.Attachments = new function(){ Zotero.Attachments = new function () {
// Keep in sync with Zotero.Schema.integrityCheck() and this.linkModeToName() // Keep in sync with Zotero.Schema.integrityCheck() and this.linkModeToName()
this.LINK_MODE_IMPORTED_FILE = 0; this.LINK_MODE_IMPORTED_FILE = 0;
this.LINK_MODE_IMPORTED_URL = 1; this.LINK_MODE_IMPORTED_URL = 1;
@ -270,7 +270,7 @@ Zotero.Attachments = new function(){
/** /**
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID', 'singleFile' * @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'libraryID', 'parentItemID', 'singleFile'
* @param {Object} [options.saveOptions] - Options to pass to Zotero.Item::save() * @param {Object} [options.saveOptions] - Options to pass to Zotero.Item::save()
* @return {Promise<Zotero.Item>} * @return {Promise<Zotero.Item>}
*/ */
@ -287,10 +287,19 @@ Zotero.Attachments = new function(){
var title = options.title; var title = options.title;
var contentType = options.contentType; var contentType = options.contentType;
var charset = options.charset; var charset = options.charset;
var libraryID = options.libraryID;
var parentItemID = options.parentItemID; var parentItemID = options.parentItemID;
var saveOptions = options.saveOptions; var saveOptions = options.saveOptions;
if (!parentItemID) { if (parentItemID) {
libraryID = Zotero.Items.getLibraryAndKeyFromID(parentItemID).libraryID;
}
else if (contentType == 'text/html') {
throw new Error("parentItemID not provided");
}
// Webpage snapshots must have parent items
if (!parentItemID && contentType == 'text/html') {
throw new Error("parentItemID not provided"); throw new Error("parentItemID not provided");
} }
@ -299,8 +308,9 @@ Zotero.Attachments = new function(){
yield Zotero.DB.executeTransaction(function* () { yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment // Create a new attachment
attachmentItem = new Zotero.Item('attachment'); attachmentItem = new Zotero.Item('attachment');
let {libraryID, key: parentKey} = Zotero.Items.getLibraryAndKeyFromID(parentItemID); if (libraryID) {
attachmentItem.libraryID = libraryID; attachmentItem.libraryID = libraryID;
}
attachmentItem.setField('title', title); attachmentItem.setField('title', title);
attachmentItem.setField('url', url); attachmentItem.setField('url', url);
attachmentItem.parentID = parentItemID; attachmentItem.parentID = parentItemID;
@ -563,7 +573,8 @@ Zotero.Attachments = new function(){
reject(e); reject(e);
}, },
true, true,
cookieSandbox cookieSandbox,
{ allowImages: true }
); );
}); });
}; };
@ -1143,67 +1154,82 @@ Zotero.Attachments = new function(){
*/ */
this.downloadPDFViaBrowser = async function (url, path, options = {}) { this.downloadPDFViaBrowser = async function (url, path, options = {}) {
Zotero.debug(`downloadPDFViaBrowser: Downloading file via browser from ${url}`); Zotero.debug(`downloadPDFViaBrowser: Downloading file via browser from ${url}`);
const onLoadTimeout = 1e3; const onLoadTimeout = Zotero.Prefs.get('downloadPDFViaBrowser.onLoadTimeout');
// Technically this is not a download, but the full operation timeout // Technically this is not a download, but the full operation timeout
const downloadTimeout = 60e3; const downloadTimeout = Zotero.Prefs.get('downloadPDFViaBrowser.downloadTimeout');
let channelBrowser, hiddenBrowser; let channelBrowser, hiddenBrowser;
let hiddenBrowserPDFFoundDeferred = Zotero.Promise.defer(); let hiddenBrowserPDFFoundDeferred = Zotero.Promise.defer();
var pdfMIMETypeHandler = async (blob, name, _, channel) => { let isOurPDF = false;
Zotero.debug(`downloadPDFViaBrowser: Sniffing a PDF loaded at ${name}`); var pdfMIMETypeHandler = {
onStartRequest: function (name, _, channel) {
let isOurPDF = false; Zotero.debug(`downloadPDFViaBrowser: Sniffing a PDF loaded at ${name}`);
// try the browser // try the browser
try {
channelBrowser = channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
}
catch (e) {}
if (channelBrowser) {
isOurPDF = hiddenBrowser === channelBrowser;
}
else {
// try the document for the load group
try { try {
channelBrowser = channel.loadGroup.notificationCallbacks.getInterface(Ci.nsIWebNavigation) channelBrowser = channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell).chromeEventHandler; .QueryInterface(Ci.nsIDocShell).chromeEventHandler;
} }
catch(e) {} catch (e) {}
if (channelBrowser) { if (channelBrowser) {
isOurPDF = hiddenBrowser === channelBrowser; isOurPDF = hiddenBrowser === channelBrowser;
} }
} else {
// try the document for the load group
if (isOurPDF) { try {
Zotero.debug(`downloadPDFViaBrowser: Found our PDF at ${name}`); channelBrowser = channel.loadGroup.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
await Zotero.File.putContentsAsync(path, blob); .QueryInterface(Ci.nsIDocShell).chromeEventHandler;
hiddenBrowserPDFFoundDeferred.resolve(); }
return true; catch(e) {}
} if (channelBrowser) {
else { isOurPDF = hiddenBrowser === channelBrowser;
Zotero.debug(`downloadPDFViaBrowser: Not our PDF at ${name}`); }
return false; }
},
onContent: async (blob, name, _, channel) => {
if (isOurPDF) {
Zotero.debug(`downloadPDFViaBrowser: Found our PDF at ${name}`);
await Zotero.File.putContentsAsync(path, blob);
hiddenBrowserPDFFoundDeferred.resolve();
return true;
}
else {
Zotero.debug(`downloadPDFViaBrowser: Not our PDF at ${name}`);
return false;
}
} }
}; };
try { try {
Zotero.MIMETypeHandler.addHandler("application/pdf", pdfMIMETypeHandler, true); Zotero.MIMETypeHandler.addHandlers("application/pdf", pdfMIMETypeHandler, true);
let noop = () => 0; function noop() {};
hiddenBrowser = Zotero.HTTP.loadDocuments([url], noop, noop, noop, true, options.cookieSandbox); hiddenBrowser = Zotero.HTTP.loadDocuments([url], noop, noop, noop, true, options.cookieSandbox);
let onLoadTimeoutDeferred = Zotero.Promise.defer(); let onLoadTimeoutDeferred = Zotero.Promise.defer();
hiddenBrowser.addEventListener("load", async function onLoad() { let currentUrl = "";
Zotero.debug(`downloadPDFViaBrowser: Page with potential JS redirect loaded, giving it ${onLoadTimeout}ms to process`); hiddenBrowser.addProgressListener({
hiddenBrowser.addEventListener("unload", () => { QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIWebProgressListener,
Zotero.debug(`downloadPDFViaBrowser: A JS redirect occurred, short timeout cancelled`); Components.interfaces.nsISupportsWeakReference]),
hiddenBrowser.removeEventListener('load', onLoad); onProgressChange: noop,
}); onStateChange: noop,
await Zotero.Promise.delay(onLoadTimeout); onStatusChange: noop,
onLoadTimeoutDeferred.reject(`Loading PDF via browser timed out on the JS challenge page after ${onLoadTimeout}ms`); onSecurityChange: noop,
}, true); async onLocationChange() {
let url = hiddenBrowser.contentDocument.location.href;
if (currentUrl) {
Zotero.debug(`downloadPDFViaBrowser: A JS redirect occurred to ${hiddenBrowser.contentDocument.location.href}`);
}
currentUrl = url;
Zotero.debug(`downloadPDFViaBrowser: Page with potential JS redirect loaded, giving it ${onLoadTimeout}ms to process`);
await Zotero.Promise.delay(onLoadTimeout);
// If URL changed that means we got redirected and the onLoadTimeout needs to restart
if (currentUrl === url && !isOurPDF) {
onLoadTimeoutDeferred.reject(new Error(`downloadPDFViaBrowser: Loading PDF via browser timed out on the JS challenge page after ${onLoadTimeout}ms`));
}
}
});
await Zotero.Promise.race([ await Zotero.Promise.race([
onLoadTimeoutDeferred.promise, onLoadTimeoutDeferred.promise,
Zotero.Promise.delay(downloadTimeout).then(() => { Zotero.Promise.delay(downloadTimeout).then(() => {
if (!hiddenBrowserPDFFoundDeferred.promise.isResolved()) { if (!isOurPDF) {
throw new Error(`Loading PDF via browser timed out after ${downloadTimeout}ms`); throw new Error(`downloadPDFViaBrowser: Loading PDF via browser timed out after ${downloadTimeout}ms`);
} }
}), }),
hiddenBrowserPDFFoundDeferred.promise hiddenBrowserPDFFoundDeferred.promise
@ -1213,13 +1239,13 @@ Zotero.Attachments = new function(){
try { try {
await OS.File.remove(path, { ignoreAbsent: true }); await OS.File.remove(path, { ignoreAbsent: true });
} }
catch (e) { catch (err) {
Zotero.logError(e, 1); Zotero.logError(err);
} }
throw e; throw e;
} }
finally { finally {
Zotero.MIMETypeHandler.removeHandler('application/pdf', pdfMIMETypeHandler); Zotero.MIMETypeHandler.removeHandlers('application/pdf', pdfMIMETypeHandler);
if (hiddenBrowser) { if (hiddenBrowser) {
Zotero.Browser.deleteHiddenBrowser(hiddenBrowser); Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
} }
@ -1259,13 +1285,14 @@ Zotero.Attachments = new function(){
this.canFindPDFForItem = function (item) { this.canFindPDFForItem = function (item) {
return item.isRegularItem() return item.isRegularItem()
&& !item.isFeedItem
&& (!!item.getField('DOI') || !!item.getField('url') || !!item.getExtraField('DOI')) && (!!item.getField('DOI') || !!item.getField('url') || !!item.getExtraField('DOI'))
&& item.numPDFAttachments() == 0; && item.numPDFAttachments() == 0;
}; };
/** /**
* Look for an available PDF for an item and add it as an attachment * Get the PDF resolvers that can be used for a given item based on the available fields
* *
* @param {Zotero.Item} item * @param {Zotero.Item} item
* @param {String[]} [methods=['doi', 'url', 'oa', 'custom']] * @param {String[]} [methods=['doi', 'url', 'oa', 'custom']]
@ -1405,6 +1432,12 @@ Zotero.Attachments = new function(){
? elem.getAttribute(attribute) ? elem.getAttribute(attribute)
: elem.textContent; : elem.textContent;
if (!val) return []; if (!val) return [];
// Handle relative paths
val = Services.io.newURI(
val, null, Services.io.newURI(url)
).spec;
return [{ return [{
accessMethod: name, accessMethod: name,
url: val, url: val,
@ -2011,7 +2044,7 @@ Zotero.Attachments = new function(){
addTriedURL(url); addTriedURL(url);
// Backoff loop // Backoff loop
let tries = 3; let tries = 3;
while (tries-- >= 0) { while (tries-- > 0) {
try { try {
await beforeRequest(url); await beforeRequest(url);
await this.downloadFile(url, path, options); await this.downloadFile(url, path, options);
@ -2043,14 +2076,22 @@ Zotero.Attachments = new function(){
let contentType; let contentType;
let skip = false; let skip = false;
let domains = new Set(); let domains = new Set();
let redirectLimit = 10;
let redirectURLTries = new Map();
while (true) { while (true) {
if (redirectLimit == 0) {
Zotero.debug("Too many redirects -- stopping");
skip = true;
break;
}
let domain = urlToDomain(nextURL); let domain = urlToDomain(nextURL);
let noDelay = domains.has(domain); let noDelay = domains.has(domain);
domains.add(domain); domains.add(domain);
// Backoff loop // Backoff loop
let tries = 3; let tries = 3;
while (tries-- >= 0) { while (tries-- > 0) {
try { try {
await beforeRequest(nextURL, noDelay); await beforeRequest(nextURL, noDelay);
req = await Zotero.HTTP.request( req = await Zotero.HTTP.request(
@ -2058,7 +2099,9 @@ Zotero.Attachments = new function(){
nextURL, nextURL,
{ {
responseType: 'blob', responseType: 'blob',
followRedirects: false followRedirects: false,
// Use our own error handling
errorDelayMax: 0
} }
); );
} }
@ -2069,22 +2112,39 @@ Zotero.Attachments = new function(){
noDelay = false; noDelay = false;
continue; continue;
} }
throw e;
} }
break; break;
} }
afterRequest(nextURL); afterRequest(nextURL);
if (!req) {
break;
}
if ([301, 302, 303, 307].includes(req.status)) { if ([301, 302, 303, 307].includes(req.status)) {
let location = req.getResponseHeader('Location'); let location = req.getResponseHeader('Location');
if (!location) { if (!location) {
throw new Error("Location header not provided"); throw new Error("Location header not provided");
} }
let currentURL = nextURL;
nextURL = Services.io.newURI(nextURL, null, null).resolve(location); nextURL = Services.io.newURI(nextURL, null, null).resolve(location);
if (isTriedURL(nextURL)) { if (isTriedURL(nextURL)) {
Zotero.debug("Redirect URL has already been tried -- skipping"); Zotero.debug("Redirect URL has already been tried -- skipping");
skip = true; skip = true;
break; break;
} }
// Keep track of tries for each redirect URL, and stop if too many
let maxTriesPerRedirectURL = 2;
let tries = (redirectURLTries.get(currentURL) || 0) + 1;
if (tries > maxTriesPerRedirectURL) {
Zotero.debug(`Too many redirects to ${currentURL} -- stopping`);
skip = true;
break;
}
redirectURLTries.set(currentURL, tries);
// And keep track of total redirects for this chain
redirectLimit--;
continue; continue;
} }
@ -2131,7 +2191,7 @@ Zotero.Attachments = new function(){
} }
// If DOI resolves directly to a PDF, save it to disk // If DOI resolves directly to a PDF, save it to disk
if (contentType.startsWith('application/pdf')) { if (contentType && contentType.startsWith('application/pdf')) {
Zotero.debug("URL resolves directly to PDF"); Zotero.debug("URL resolves directly to PDF");
await Zotero.File.putContentsAsync(path, blob); await Zotero.File.putContentsAsync(path, blob);
await _enforcePDF(path); await _enforcePDF(path);
@ -2147,7 +2207,7 @@ Zotero.Attachments = new function(){
continue; continue;
} }
if (!url) { if (!url) {
Zotero.debug(`No PDF found on ${responseURL}`); Zotero.debug(`No PDF found on ${responseURL || pageURL}`);
continue; continue;
} }
if (isTriedURL(url)) { if (isTriedURL(url)) {

View file

@ -8,9 +8,110 @@ Zotero.Cite = {
/** /**
* Locator labels * Locator labels
*/ */
"labels":["page", "book", "chapter", "column", "figure", "folio", "labels": [
"issue", "line", "note", "opus", "paragraph", "part", "section", "sub verbo", "act",
"volume", "verse"], "appendix",
"article-locator",
"book",
"canon",
"chapter",
"column",
"elocation",
"equation",
"figure",
"folio",
"issue",
"line",
"note",
"opus",
"page",
"paragraph",
"part",
"rule",
"scene",
"section",
"sub-verbo",
"table",
//"timestamp",
"title-locator",
"verse",
"volume"
],
_locatorStrings: new Map(),
/**
* Get localized string for locator
*
* @param {String} locator - Locator name (e.g., 'book')
* @return {String} - Localized string (e.g., 'Livre')
*/
getLocatorString: function (locator) {
// 'timestamp' not included in CSL locales
if (locator == 'timestamp') {
return Zotero.getString('citation.locator.timestamp');
}
// Get the best CSL locale for the current Zotero locale
var cslLocale = Zotero.Utilities.Internal.resolveLocale(
Zotero.locale,
Object.keys(Zotero.Styles.locales)
);
// If locator strings are already cached for the current locale, use that
if (this._locatorStrings.has(cslLocale)) {
return this._locatorStrings.get(cslLocale).get(locator);
}
var map = new Map();
this._locatorStrings.set(cslLocale, map);
var localeXML = Zotero.Cite.Locale.get(cslLocale);
var parser;
if (Zotero.platformMajorVersion > 60) {
parser = new DOMParser();
}
else {
parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser);
}
var doc = parser.parseFromString(localeXML, 'text/xml');
var englishDoc;
// Cache all locators for the current locale
for (let locator of this.labels) {
let elem = doc.querySelector(`term[name="${locator}"]:not([form="short"]) > single`);
if (!elem) {
// If locator not found, get from the U.S. English locale
if (cslLocale != 'en-US') {
Zotero.logError(`Locator '${locator}' not found in ${cslLocale} locale -- trying en-US`);
if (!englishDoc) {
englishDoc = parser.parseFromString(Zotero.Cite.Locale.get('en-US'), 'text/xml');
}
elem = englishDoc.querySelector(`term[name="${locator}"]:not([form="short"]) > single`);
if (!elem) {
Zotero.logError(`Locator '${locator}' not found in en-US locale -- using name`);
}
}
else {
Zotero.logError(`Locator '${locator}' not found in en-US locale -- using name`);
}
// If still not found, use the locator name directly
if (!elem) {
map.set(locator, Zotero.Utilities.capitalize(locator));
continue;
}
}
// If <single> is empty, use the locator name directly
let str = elem.textContent;
if (!str) {
Zotero.logError(`Locator '${locator}' is empty in ${cslLocale} locale -- using name`);
map.set(locator, Zotero.Utilities.capitalize(locator));
continue;
}
map.set(locator, Zotero.Utilities.capitalize(str));
}
return map.get(locator);
},
/** /**
* Remove specified item IDs in-place from a citeproc-js bibliography object returned * Remove specified item IDs in-place from a citeproc-js bibliography object returned

View file

@ -120,7 +120,6 @@ var CSL = {
"cmt.": "comment", "cmt.": "comment",
"dec.": "decision", "dec.": "decision",
"dept.": "department", "dept.": "department",
"div.": "division",
"ex.": "example", "ex.": "example",
"fig.": "figure", "fig.": "figure",
"fld.": "field", "fld.": "field",
@ -170,7 +169,6 @@ var CSL = {
"comment": "cmt.", "comment": "cmt.",
"decision": "dec.", "decision": "dec.",
"department": "dept.", "department": "dept.",
"division": "div.",
"example": "ex.", "example": "ex.",
"figure": "fig.", "figure": "fig.",
"field": "fld.", "field": "fld.",
@ -219,7 +217,6 @@ var CSL = {
"cmt": "comment", "cmt": "comment",
"dec": "decision", "dec": "decision",
"dept": "department", "dept": "department",
"div": "division",
"ex": "example", "ex": "example",
"fig": "figure", "fig": "figure",
"fld": "field", "fld": "field",
@ -725,33 +722,61 @@ var CSL = {
NAME_VARIABLES: [ NAME_VARIABLES: [
"author", "author",
"chair",
"collection-editor", "collection-editor",
"compiler",
"composer", "composer",
"container-author", "container-author",
"contributor",
"curator",
"director", "director",
"editor", "editor",
"editor-translator",
"editorial-director", "editorial-director",
"executive-producer",
"guest",
"host",
"illustrator", "illustrator",
"interviewer", "interviewer",
"narrator",
"organizer",
"original-author", "original-author",
"performer",
"producer",
"recipient", "recipient",
"reviewed-author", "reviewed-author",
"script-writer",
"series-creator",
"translator", "translator",
"commenter" "commenter"
], ],
CREATORS: [ CREATORS: [
"author", "author",
"chair",
"collection-editor", "collection-editor",
"compiler",
"composer", "composer",
"container-author", "container-author",
"contributor",
"curator",
"director", "director",
"editor", "editor",
"editor-translator",
"editorial-director", "editorial-director",
"executive-producer",
"guest",
"host",
"illustrator", "illustrator",
"interviewer", "interviewer",
"narrator",
"organizer",
"original-author", "original-author",
"performer",
"producer",
"recipient", "recipient",
"reviewed-author", "reviewed-author",
"script-writer",
"series-creator",
"translator", "translator",
"commenter" "commenter"
], ],
@ -768,10 +793,13 @@ var CSL = {
"number", "number",
"number-of-pages", "number-of-pages",
"number-of-volumes", "number-of-volumes",
"part-number",
"printing-number",
"section",
"supplement-number",
"version", "version",
"volume", "volume",
// "section", ??? add this? "supplement", // maybe deprecated this? supplement-number should serve this purpose in standard CSL.
"supplement",
"citation-number" "citation-number"
], ],
//var x = new Array(); //var x = new Array();
@ -1476,6 +1504,9 @@ var CSL = {
}, },
INIT_JURISDICTION_MACROS: function (state, Item, item, macroName) { INIT_JURISDICTION_MACROS: function (state, Item, item, macroName) {
if (Item["best-jurisdiction"]) {
return true;
}
if (!state.sys.retrieveStyleModule || !CSL.MODULE_MACROS[macroName] || !Item.jurisdiction) { if (!state.sys.retrieveStyleModule || !CSL.MODULE_MACROS[macroName] || !Item.jurisdiction) {
return false; return false;
} }
@ -1495,6 +1526,7 @@ var CSL = {
} }
} }
} }
var jurisdictionList = state.getJurisdictionList(Item.jurisdiction);
if (state.opt.parallel.enable) { if (state.opt.parallel.enable) {
if (!state.parallel) { if (!state.parallel) {
state.parallel = new CSL.Parallel(state); state.parallel = new CSL.Parallel(state);
@ -1505,7 +1537,7 @@ var CSL = {
var jurisdiction = jurisdictionList[i]; var jurisdiction = jurisdictionList[i];
if (item) { if (item) {
if (state.juris[jurisdiction] && !item["best-jurisdiction"] && state.juris[jurisdiction].types.locator) { if (state.juris[jurisdiction] && !item["best-jurisdiction"] && state.juris[jurisdiction].types.locator) {
item["best-jurisdiction"] = jurisdiction; Item["best-jurisdiction"] = jurisdiction;
} }
} }
if(state.juris[jurisdiction] && state.juris[jurisdiction].types[Item.type]) { if(state.juris[jurisdiction] && state.juris[jurisdiction].types[Item.type]) {
@ -10032,7 +10064,7 @@ CSL.Node.date = {
CSL.Node["date-part"] = { CSL.Node["date-part"] = {
build: function (state, target) { build: function (state, target) {
var func, pos, len, first_date, value, value_end, real, have_collapsed, invoked, precondition, known_year, bc, ad, bc_end, ad_end, ready, curr, dcurr, number, num, formatter, item; var func, pos, len, first_date, value, value_end, real, have_collapsed, invoked, precondition, known_year, bc, ad, bc_end, ad_end, ready, curr, dcurr, number, num, formatter, item, blob;
if (!this.strings.form) { if (!this.strings.form) {
this.strings.form = "long"; this.strings.form = "long";
} }
@ -10175,7 +10207,7 @@ CSL.Node["date-part"] = {
// and we reach this block, then we can combine the dates // and we reach this block, then we can combine the dates
// to a string, run minimial-two, and output the trailing // to a string, run minimial-two, and output the trailing
// year right here. No impact on other functionality. // year right here. No impact on other functionality.
if (state.opt["year-range-format"] if (state.opt["year-range-format"]
&& state.opt["year-range-format"] !== "expanded" && state.opt["year-range-format"] !== "expanded"
&& !state.tmp.date_object.day && !state.tmp.date_object.day
@ -10183,7 +10215,7 @@ CSL.Node["date-part"] = {
&& !state.tmp.date_object.season && !state.tmp.date_object.season
&& this.strings.name === "year" && this.strings.name === "year"
&& value && value_end) { && value && value_end) {
// second argument adjusts collapse as required for years // second argument adjusts collapse as required for years
// See OSCOLA section 1.3.2 // See OSCOLA section 1.3.2
value_end = state.fun.year_mangler(value + "-" + value_end, true); value_end = state.fun.year_mangler(value + "-" + value_end, true);
@ -10193,13 +10225,21 @@ CSL.Node["date-part"] = {
last_string_output = value_end; last_string_output = value_end;
state.dateput.append(value_end, this); state.dateput.append(value_end, this);
if (first_date) { if (first_date) {
state.dateput.current.value().blobs[0].strings.prefix = ""; blob = state.dateput.current.value().blobs[0];
if (blob) {
blob.strings.prefix = "";
}
} }
} }
last_string_output = value; last_string_output = value;
state.output.append(value, this); state.output.append(value, this);
curr = state.output.current.value(); curr = state.output.current.value();
curr.blobs[(curr.blobs.length - 1)].strings.suffix = ""; blob = curr.blobs[(curr.blobs.length - 1)];
if (blob) {
blob.strings.suffix = "";
}
if (this.strings["range-delimiter"]) { if (this.strings["range-delimiter"]) {
state.output.append(this.strings["range-delimiter"]); state.output.append(this.strings["range-delimiter"]);
} else { } else {
@ -10234,7 +10274,10 @@ CSL.Node["date-part"] = {
last_string_output = value_end; last_string_output = value_end;
state.dateput.append(value_end, this); state.dateput.append(value_end, this);
if (first_date) { if (first_date) {
state.dateput.current.value().blobs[0].strings.prefix = ""; blob = state.dateput.current.value().blobs[0];
if (blob) {
blob.strings.prefix = "";
}
} }
if (bc) { if (bc) {
last_string_output = bc; last_string_output = bc;
@ -10326,8 +10369,6 @@ CSL.Node["date-part"] = {
} }
}; };
/*global CSL: true */ /*global CSL: true */
CSL.Node["else-if"] = { CSL.Node["else-if"] = {
@ -11752,12 +11793,12 @@ CSL.Node["alternative-text"] = {
CSL.NameOutput = function(state, Item, item) { CSL.NameOutput = function(state, Item, item) {
this.debug = false; this.debug = false;
this.state = state;
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(1)"); this.state.sys.print("(1)");
} }
//SNIP-END //SNIP-END
this.state = state;
this.Item = Item; this.Item = Item;
this.item = item; this.item = item;
this.nameset_base = 0; this.nameset_base = 0;
@ -11911,21 +11952,21 @@ CSL.NameOutput.prototype.outputNames = function () {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(2)"); this.state.sys.print("(2)");
} }
//SNIP-END //SNIP-END
// util_names_etalconfig.js // util_names_etalconfig.js
this.getEtAlConfig(); this.getEtAlConfig();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(3)"); this.state.sys.print("(3)");
} }
//SNIP-END //SNIP-END
// util_names_divide.js // util_names_divide.js
this.divideAndTransliterateNames(); this.divideAndTransliterateNames();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(4)"); this.state.sys.print("(4)");
} }
//SNIP-END //SNIP-END
// util_names_truncate.js // util_names_truncate.js
@ -11933,13 +11974,13 @@ CSL.NameOutput.prototype.outputNames = function () {
this.truncatePersonalNameLists(); this.truncatePersonalNameLists();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(5)"); this.state.sys.print("(5)");
} }
//SNIP-END //SNIP-END
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(6)"); this.state.sys.print("(6)");
} }
//SNIP-END //SNIP-END
// util_names_disambig.js // util_names_disambig.js
@ -11949,7 +11990,7 @@ CSL.NameOutput.prototype.outputNames = function () {
this.constrainNames(); this.constrainNames();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(7)"); this.state.sys.print("(7)");
} }
//SNIP-END //SNIP-END
// form="count" // form="count"
@ -11963,25 +12004,25 @@ CSL.NameOutput.prototype.outputNames = function () {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(8)"); this.state.sys.print("(8)");
} }
//SNIP-END //SNIP-END
this.setEtAlParameters(); this.setEtAlParameters();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(9)"); this.state.sys.print("(9)");
} }
//SNIP-END //SNIP-END
this.setCommonTerm(this.requireMatch); this.setCommonTerm(this.requireMatch);
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(10)"); this.state.sys.print("(10)");
} }
//SNIP-END //SNIP-END
this.renderAllNames(); this.renderAllNames();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(11)"); this.state.sys.print("(11)");
} }
//SNIP-END //SNIP-END
var blob_list = []; var blob_list = [];
@ -11995,7 +12036,7 @@ CSL.NameOutput.prototype.outputNames = function () {
} else { } else {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(11a)"); this.state.sys.print("(11a)");
} }
//SNIP-END //SNIP-END
for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) { for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) {
@ -12003,7 +12044,7 @@ CSL.NameOutput.prototype.outputNames = function () {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(11b)"); this.state.sys.print("(11b)");
} }
//SNIP-END //SNIP-END
if (this.institutions[v].length) { if (this.institutions[v].length) {
@ -12015,13 +12056,13 @@ CSL.NameOutput.prototype.outputNames = function () {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(11c)"); this.state.sys.print("(11c)");
} }
//SNIP-END //SNIP-END
var varblob = this.joinFreetersAndInstitutionSets([this.freeters[v], institutions]); var varblob = this.joinFreetersAndInstitutionSets([this.freeters[v], institutions]);
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(11d)"); this.state.sys.print("(11d)");
} }
//SNIP-END //SNIP-END
} }
@ -12034,7 +12075,7 @@ CSL.NameOutput.prototype.outputNames = function () {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(11e)"); this.state.sys.print("(11e)");
} }
//SNIP-END //SNIP-END
if (this.common_term) { if (this.common_term) {
@ -12043,14 +12084,14 @@ CSL.NameOutput.prototype.outputNames = function () {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(12)"); this.state.sys.print("(12)");
} }
//SNIP-END //SNIP-END
this.state.output.openLevel("empty"); this.state.output.openLevel("empty");
this.state.output.current.value().strings.delimiter = this.state.inheritOpt(this.names, "delimiter", "names-delimiter"); this.state.output.current.value().strings.delimiter = this.state.inheritOpt(this.names, "delimiter", "names-delimiter");
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(13)"); this.state.sys.print("(13)");
} }
//SNIP-END //SNIP-END
for (i = 0, ilen = blob_list.length; i < ilen; i += 1) { for (i = 0, ilen = blob_list.length; i < ilen; i += 1) {
@ -12062,20 +12103,20 @@ CSL.NameOutput.prototype.outputNames = function () {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(14)"); this.state.sys.print("(14)");
} }
//SNIP-END //SNIP-END
this.state.output.closeLevel("empty"); this.state.output.closeLevel("empty");
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(15)"); this.state.sys.print("(15)");
} }
//SNIP-END //SNIP-END
var blob = this.state.output.pop(); var blob = this.state.output.pop();
this.state.tmp.name_node.top = blob; this.state.tmp.name_node.top = blob;
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(16)"); this.state.sys.print("(16)");
} }
//SNIP-END //SNIP-END
@ -12092,14 +12133,14 @@ CSL.NameOutput.prototype.outputNames = function () {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(17)"); this.state.sys.print("(17)");
} }
//SNIP-END //SNIP-END
// Also used in CSL.Util.substituteEnd (which could do with // Also used in CSL.Util.substituteEnd (which could do with
// some cleanup at this writing). // some cleanup at this writing).
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(18)"); this.state.sys.print("(18)");
} }
//SNIP-END //SNIP-END
if (variables[0] !== "authority") { if (variables[0] !== "authority") {
@ -12164,7 +12205,7 @@ CSL.NameOutput.prototype.outputNames = function () {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("(19)"); this.state.sys.print("(19)");
} }
//SNIP-END //SNIP-END
}; };
@ -12256,7 +12297,7 @@ CSL.NameOutput.prototype._collapseAuthor = function () {
this.state.registry.authorstrings[this.Item.id] = mystr; this.state.registry.authorstrings[this.Item.id] = mystr;
} else if (!this.state.tmp.just_looking } else if (!this.state.tmp.just_looking
&& !this.state.tmp.suppress_decorations && ((this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length) || this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter)) { && !this.state.tmp.suppress_decorations && ((this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length) || this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter)) {
// XX1 print("RENDER: "+this.Item.id); // XX1 this.state.sys.print("RENDER: "+this.Item.id);
mystr = ""; mystr = "";
myqueue = this.state.tmp.name_node.top.blobs.slice(-1)[0].blobs; myqueue = this.state.tmp.name_node.top.blobs.slice(-1)[0].blobs;
oldchars = this.state.tmp.offset_characters; oldchars = this.state.tmp.offset_characters;
@ -12265,7 +12306,7 @@ CSL.NameOutput.prototype._collapseAuthor = function () {
} }
if (mystr === this.state.tmp.last_primary_names_string) { if (mystr === this.state.tmp.last_primary_names_string) {
if (this.item["suppress-author"] || (this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length)) { if (this.item["suppress-author"] || (this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length)) {
// XX1 print(" CUT!"); // XX1 this.state.sys.print(" CUT!");
this.state.tmp.name_node.top.blobs.pop(); this.state.tmp.name_node.top.blobs.pop();
this.state.tmp.name_node.children = []; this.state.tmp.name_node.children = [];
// If popped, avoid side-effects on character counting: we're only interested // If popped, avoid side-effects on character counting: we're only interested
@ -12277,7 +12318,7 @@ CSL.NameOutput.prototype._collapseAuthor = function () {
this.state.tmp.use_cite_group_delimiter = true; this.state.tmp.use_cite_group_delimiter = true;
} }
} else { } else {
// XX1 print("remembering: "+mystr); // XX1 this.state.sys.print("remembering: "+mystr);
this.state.tmp.last_primary_names_string = mystr; this.state.tmp.last_primary_names_string = mystr;
// XXXXX A little more precision would be nice. // XXXXX A little more precision would be nice.
// This will clobber variable="author editor" as well as variable="author". // This will clobber variable="author editor" as well as variable="author".
@ -20879,7 +20920,7 @@ CSL.Util.outputNumericField = function(state, varname, itemID) {
CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, masterStyling.strings.prefix, null, masterStyling, `${num.particle}${num.value}`); CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, masterStyling.strings.prefix, null, masterStyling, `${num.particle}${num.value}`);
if (num.collapsible) { if (num.collapsible) {
var blob; var blob;
if (num.value.match(/^[1-9][0-9]*$/)) { if (num.value.match(/^[1-9][0-9]*$/) && Number.isSafeInteger(parseInt(num.value, 10))) {
blob = new CSL.NumericBlob(state, num.particle, parseInt(num.value, 10), numStyling, itemID); blob = new CSL.NumericBlob(state, num.particle, parseInt(num.value, 10), numStyling, itemID);
} else { } else {
blob = new CSL.NumericBlob(state, num.particle, num.value, numStyling, itemID); blob = new CSL.NumericBlob(state, num.particle, num.value, numStyling, itemID);
@ -20913,7 +20954,7 @@ CSL.Util.outputNumericField = function(state, varname, itemID) {
CSL.Util.PageRangeMangler = {}; CSL.Util.PageRangeMangler = {};
CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) { CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
var rangerex, pos, len, stringify, listify, expand, minimize, minimize_internal, chicago, lst, m, b, e, ret, begin, end, ret_func; var rangerex, pos, len, stringify, listify, expand, minimize, minimize_internal, chicago15, chicago16, lst, m, b, e, ret, begin, end, ret_func;
var range_delimiter = state.getTerm(rangeType + "-range-delimiter"); var range_delimiter = state.getTerm(rangeType + "-range-delimiter");
@ -21021,7 +21062,7 @@ CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
return ret.join(""); return ret.join("");
}; };
chicago = function (lst) { chicago15 = function (lst) {
len = lst.length; len = lst.length;
for (pos = 1; pos < len; pos += 2) { for (pos = 1; pos < len; pos += 2) {
if ("object" === typeof lst[pos]) { if ("object" === typeof lst[pos]) {
@ -21041,6 +21082,31 @@ CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
return stringify(lst); return stringify(lst);
}; };
chicago16 = function (lst) {
len = lst.length;
for (pos = 1; pos < len; pos += 2) {
if ("object" === typeof lst[pos]) {
m = lst[pos];
begin = parseInt(m[1], 10);
end = parseInt(m[3], 10);
e = "" + end;
if (begin > 100 && begin % 100) {
for (var i = 2; i < e.length; i++) {
var divisor = Math.pow(10, i);
if (Math.floor(begin / divisor) === Math.floor(end / divisor)) {
m[3] = "" + (end % divisor);
break;
}
}
}
}
if (m[2].slice(1) === m[0]) {
m[2] = range_delimiter;
}
}
return stringify(lst);
};
// //
// The top-level option handlers. // The top-level option handlers.
// //
@ -21070,7 +21136,15 @@ CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
}; };
} else if (state.opt[rangeType + "-range-format"] === "chicago") { } else if (state.opt[rangeType + "-range-format"] === "chicago") {
ret_func = function (str) { ret_func = function (str) {
return sniff(str, chicago); return sniff(str, chicago15);
};
} else if (state.opt[rangeType + "-range-format"] === "chicago-15") {
ret_func = function (str) {
return sniff(str, chicago15);
};
} else if (state.opt[rangeType + "-range-format"] === "chicago-16") {
ret_func = function (str) {
return sniff(str, chicago16);
}; };
} }
@ -21668,7 +21742,7 @@ CSL.Output.Formatters = (function () {
var rexNameStr = "(?:[-\\s]*<\\/*(?:span\s+class=\"no(?:case|decor)\"|i|sc|b|sub|sup)>[-\\s]*|[-\\s]+)"; var rexNameStr = "(?:[-\\s]*<\\/*(?:span\s+class=\"no(?:case|decor)\"|i|sc|b|sub|sup)>[-\\s]*|[-\\s]+)";
var nameDoppel = new CSL.Doppeler(rexNameStr); var nameDoppel = new CSL.Doppeler(rexNameStr);
var wordDoppel = new CSL.Doppeler("(?:[\u0020\u00A0\u2000-\u200B\u205F\u3000]+)"); var wordDoppel = new CSL.Doppeler("(?:[\u00A0\u0020\u00A0\u2000-\u200B\u205F\u3000]+)");
/** /**
* INTERNAL * INTERNAL
@ -21941,7 +22015,6 @@ CSL.Output.Formatters = (function () {
quoteState: [], quoteState: [],
capitaliseWords: function(str, i, followingTag) { capitaliseWords: function(str, i, followingTag) {
if (str.trim()) { if (str.trim()) {
var words = str.split(/[ \u00A0]+/);
var wordle = wordDoppel.split(str); var wordle = wordDoppel.split(str);
var words = wordle.strings; var words = wordle.strings;
for (var j=0,jlen=words.length;j<jlen;j++) { for (var j=0,jlen=words.length;j<jlen;j++) {
@ -21949,17 +22022,23 @@ CSL.Output.Formatters = (function () {
if (!word) { if (!word) {
continue; continue;
} }
if (word.length > 1 && !CSL.toLocaleLowerCase.call(state, word).match(config.skipWordsRex)) { let lcase = CSL.toLocaleLowerCase.call(state, word);
let capitalize = false;
if (word.length > 1 && !lcase.match(config.skipWordsRex)) {
// Capitalize every word that is not a stop-word // Capitalize every word that is not a stop-word
words[j] = _capitalise.call(state, words[j]); capitalize = true;
} else if (j === (words.length - 1) && followingTag === "-") { } else if (j === (words.length - 1) && followingTag === "-") {
words[j] = _capitalise.call(state, words[j]); capitalize = true;
} else if (config.isFirst) { } else if (config.isFirst) {
// Capitalize first word, even if a stop-word // Capitalize first word, even if a stop-word
words[j] = _capitalise.call(state, words[j]); capitalize = true;
} else if (config.afterPunct) { } else if (config.afterPunct) {
// Capitalize after punctuation // Capitalize after punctuation
words[j] = _capitalise.call(state, words[j]); capitalize = true;
}
// Don't capitalize if word already contains capitalization
if (capitalize && word === lcase) {
words[j] = _capitalise.call(state, word);
} }
config.afterPunct = false; config.afterPunct = false;
config.isFirst = false; config.isFirst = false;
@ -21989,18 +22068,22 @@ CSL.Output.Formatters = (function () {
var config = { var config = {
quoteState: [], quoteState: [],
capitaliseWords: function(str) { capitaliseWords: function(str) {
var words = str.split(" "); var wordle = wordDoppel.split(str);
var words = wordle.strings;
for (var i=0,ilen=words.length;i<ilen;i++) { for (var i=0,ilen=words.length;i<ilen;i++) {
var word = words[i]; var word = words[i];
if (word) { if (word) {
if (config.isFirst) { if (config.isFirst) {
words[i] = _capitalise.call(state, word); // Don't capitalize if word already contains capitalization
if (word === CSL.toLocaleLowerCase.call(state, word)) {
words[i] = _capitalise.call(state, word);
}
config.isFirst = false; config.isFirst = false;
break; break;
} }
} }
} }
return words.join(" "); return wordDoppel.join(wordle);
}, },
skipWordsRex: null, skipWordsRex: null,
tagState: [], tagState: [],
@ -22020,14 +22103,18 @@ CSL.Output.Formatters = (function () {
var config = { var config = {
quoteState: [], quoteState: [],
capitaliseWords: function(str) { capitaliseWords: function(str) {
var words = str.split(" "); var wordle = wordDoppel.split(str);
var words = wordle.strings;
for (var i=0,ilen=words.length;i<ilen;i++) { for (var i=0,ilen=words.length;i<ilen;i++) {
var word = words[i]; var word = words[i];
if (word) { if (word) {
words[i] = _capitalise.call(state, word); // Don't capitalize if word already contains capitalization
if (word === CSL.toLocaleLowerCase.call(state, word)) {
words[i] = _capitalise.call(state, word);
}
} }
} }
return words.join(" "); return wordDoppel.join(wordle);
}, },
skipWordsRex: null, skipWordsRex: null,
tagState: [], tagState: [],
@ -22261,7 +22348,7 @@ CSL.Output.Formats.prototype.text = {
return "\n"+str; return "\n"+str;
}, },
"@display/left-margin": function (state, str) { "@display/left-margin": function (state, str) {
return str; return str + " ";
}, },
"@display/right-inline": function (state, str) { "@display/right-inline": function (state, str) {
return str; return str;
@ -22630,7 +22717,7 @@ CSL.Output.Formats.prototype.latex = {
return text; return text;
}, },
"bibstart": "\\begin{thebibliography}{4}", "bibstart": "\\begin{thebibliography}{4}",
"bibend": "\end{thebibliography}", "bibend": "\\end{thebibliography}",
"@font-style/italic": "{\\em %%STRING%%}", "@font-style/italic": "{\\em %%STRING%%}",
"@font-style/oblique": false, "@font-style/oblique": false,
"@font-style/normal": false, "@font-style/normal": false,
@ -23535,6 +23622,11 @@ CSL.Registry.NameReg = function (state) {
set_keys = function (state, itemid, nameobj) { set_keys = function (state, itemid, nameobj) {
pkey = strip_periods(nameobj.family); pkey = strip_periods(nameobj.family);
if (state.opt["demote-non-dropping-particle"] === "never" && nameobj["non-dropping-particle"] && nameobj["family"]) {
pkey = `${pkey} ${nameobj["non-dropping-particle"]}`;
}
skey = strip_periods(nameobj.given); skey = strip_periods(nameobj.given);
// Drop lowercase suffixes (such as et al.) from given name field // Drop lowercase suffixes (such as et al.) from given name field
// for disambiguation purposes. // for disambiguation purposes.
@ -23746,12 +23838,10 @@ CSL.Registry.NameReg = function (state) {
&& pos !== 0) { && pos !== 0) {
return; return;
} }
// A hack. Safe if the name object is used only here, for disambiguation purposes. // A hack. Safe if the name object is used only here, for disambiguation purposes.
// TEMP: Disabled due to https://github.com/Juris-M/citeproc-js/issues/179 /*
/*if (state.opt["demote-non-dropping-particle"] === "never" && nameobj["non-dropping-particle"] && nameobj["family"]) { */
nameobj["family"] = nameobj["non-dropping-particle"] + " " + nameobj["family"];
}*/
//CSL.debug("INS"); //CSL.debug("INS");
set_keys(this.state, "" + item_id, nameobj); set_keys(this.state, "" + item_id, nameobj);
@ -23853,7 +23943,7 @@ CSL.Disambiguation.prototype.run = function(akey) {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("[A] === RUN ==="); this.state.sys.print("[A] === RUN ===");
} }
//SNIP-END //SNIP-END
this.akey = akey; this.akey = akey;
@ -23867,7 +23957,7 @@ CSL.Disambiguation.prototype.runDisambig = function () {
var ismax; var ismax;
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("[C] === runDisambig() ==="); this.state.sys.print("[C] === runDisambig() ===");
} }
//SNIP-END //SNIP-END
this.initGivens = true; this.initGivens = true;
@ -23896,7 +23986,7 @@ CSL.Disambiguation.prototype.scanItems = function (list) {
var pos, len, otherItem; var pos, len, otherItem;
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("[2] === scanItems() ==="); this.state.sys.print("[2] === scanItems() ===");
} }
//SNIP-END //SNIP-END
@ -23915,15 +24005,15 @@ CSL.Disambiguation.prototype.scanItems = function (list) {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
if (pos > 1) { if (pos > 1) {
print(" -----------"); this.state.sys.print(" -----------");
} }
} }
//SNIP-END //SNIP-END
if (this.ItemCite === otherItemCite) { if (this.ItemCite === otherItemCite) {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" [CLASH]--> "+this.Item.id+": "+this.ItemCite); this.state.sys.print(" [CLASH]--> "+this.Item.id+": "+this.ItemCite);
print(" "+otherItem.id+": "+otherItemCite); this.state.sys.print(" "+otherItem.id+": "+otherItemCite);
} }
//SNIP-END //SNIP-END
clashes += 1; clashes += 1;
@ -23931,8 +24021,8 @@ CSL.Disambiguation.prototype.scanItems = function (list) {
} else { } else {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" [clear]--> "+this.Item.id+": "+this.ItemCite); this.state.sys.print(" [clear]--> "+this.Item.id+": "+this.ItemCite);
print(" "+otherItem.id+": "+otherItemCite); this.state.sys.print(" "+otherItem.id+": "+otherItemCite);
} }
//SNIP-END //SNIP-END
this.nonpartners.push(otherItem); this.nonpartners.push(otherItem);
@ -23958,9 +24048,9 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("[3] == disNames() =="); this.state.sys.print("[3] == disNames() ==");
//print(" partners: "+[this.partners[i].id for (i in this.partners)].join(", ")); //this.state.sys.print(" partners: "+[this.partners[i].id for (i in this.partners)].join(", "));
//print(" nonpartners: "+[this.nonpartners[i].id for (i in this.nonpartners)].join(", ")); //this.state.sys.print(" nonpartners: "+[this.nonpartners[i].id for (i in this.nonpartners)].join(", "));
} }
//SNIP-END //SNIP-END
@ -23988,8 +24078,8 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
this.captureStepToBase(); this.captureStepToBase();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" ** RESOLUTION [a]: lone partner, one nonpartner"); this.state.sys.print(" ** RESOLUTION [a]: lone partner, one nonpartner");
print(" registering "+this.partners[0].id+" and "+this.nonpartners[0].id); this.state.sys.print(" registering "+this.partners[0].id+" and "+this.nonpartners[0].id);
} }
//SNIP-END //SNIP-END
this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase); this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase);
@ -23999,8 +24089,8 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
this.captureStepToBase(); this.captureStepToBase();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" ** RESOLUTION [b]: lone partner, unknown number of remaining nonpartners"); this.state.sys.print(" ** RESOLUTION [b]: lone partner, unknown number of remaining nonpartners");
print(" registering "+this.partners[0].id); this.state.sys.print(" registering "+this.partners[0].id);
} }
//SNIP-END //SNIP-END
this.state.registry.registerAmbigToken(this.akey, "" + this.partners[0].id, this.betterbase); this.state.registry.registerAmbigToken(this.akey, "" + this.partners[0].id, this.betterbase);
@ -24012,8 +24102,8 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
this.captureStepToBase(); this.captureStepToBase();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" ** RESOLUTION [c]: lone nonpartner, unknown number of partners remaining"); this.state.sys.print(" ** RESOLUTION [c]: lone nonpartner, unknown number of partners remaining");
print(" registering "+this.nonpartners[0].id); this.state.sys.print(" registering "+this.nonpartners[0].id);
} }
//SNIP-END //SNIP-END
this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase); this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase);
@ -24023,7 +24113,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
this.captureStepToBase(); this.captureStepToBase();
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" ** RESOLUTION [d]: better result, but no entries safe to register"); this.state.sys.print(" ** RESOLUTION [d]: better result, but no entries safe to register");
} }
//SNIP-END //SNIP-END
this.lists[this.listpos] = [this.betterbase, this.partners]; this.lists[this.listpos] = [this.betterbase, this.partners];
@ -24031,7 +24121,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
} else { } else {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" ** RESOLUTION [e]: no improvement, and clashes remain"); this.state.sys.print(" ** RESOLUTION [e]: no improvement, and clashes remain");
} }
//SNIP-END //SNIP-END
if (ismax) { if (ismax) {
@ -24040,7 +24130,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
if (this.modeindex === this.modes.length - 1) { if (this.modeindex === this.modes.length - 1) {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" (registering clashing entries because we've run out of options)"); this.state.sys.print(" (registering clashing entries because we've run out of options)");
} }
//SNIP-END //SNIP-END
for (var i = 0, ilen = this.partners.length; i < ilen; i += 1) { for (var i = 0, ilen = this.partners.length; i < ilen; i += 1) {
@ -24055,7 +24145,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
CSL.Disambiguation.prototype.disExtraText = function () { CSL.Disambiguation.prototype.disExtraText = function () {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("[3] === disExtraText =="); this.state.sys.print("[3] === disExtraText ==");
} }
//SNIP-END //SNIP-END
@ -24117,7 +24207,7 @@ CSL.Disambiguation.prototype.disYears = function () {
var pos, len, tokens, token; var pos, len, tokens, token;
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("[3] === disYears =="); this.state.sys.print("[3] === disYears ==");
} }
//SNIP-END //SNIP-END
tokens = []; tokens = [];
@ -24153,7 +24243,7 @@ CSL.Disambiguation.prototype.disYears = function () {
CSL.Disambiguation.prototype.incrementDisambig = function () { CSL.Disambiguation.prototype.incrementDisambig = function () {
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("\n[1] === incrementDisambig() ==="); this.state.sys.print("\n[1] === incrementDisambig() ===");
} }
//SNIP-END //SNIP-END
if (this.initGivens) { if (this.initGivens) {
@ -24217,20 +24307,20 @@ CSL.Disambiguation.prototype.incrementDisambig = function () {
} }
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" ------------------"); this.state.sys.print(" ------------------");
print(" incremented values"); this.state.sys.print(" incremented values");
print(" ------------------"); this.state.sys.print(" ------------------");
print(" | gnameset: "+this.gnameset); this.state.sys.print(" | gnameset: "+this.gnameset);
print(" | gname: "+this.gname); this.state.sys.print(" | gname: "+this.gname);
print(" | names value: "+this.base.names[this.gnameset]); this.state.sys.print(" | names value: "+this.base.names[this.gnameset]);
if (this.base.givens.length) { if (this.base.givens.length) {
print(" | givens value: "+this.base.givens[this.gnameset][this.gname]); this.state.sys.print(" | givens value: "+this.base.givens[this.gnameset][this.gname]);
} else { } else {
print(" | givens value: nil"); this.state.sys.print(" | givens value: nil");
} }
print(" | namesetsMax: "+this.namesetsMax); this.state.sys.print(" | namesetsMax: "+this.namesetsMax);
print(" | namesMax: "+this.namesMax); this.state.sys.print(" | namesMax: "+this.namesMax);
print(" | givensMax: "+this.givensMax); this.state.sys.print(" | givensMax: "+this.givensMax);
} }
//SNIP-END //SNIP-END
if (("number" !== typeof this.namesetsMax || this.namesetsMax === -1 || this.gnameset === this.namesetsMax) if (("number" !== typeof this.namesetsMax || this.namesetsMax === -1 || this.gnameset === this.namesetsMax)
@ -24241,7 +24331,7 @@ CSL.Disambiguation.prototype.incrementDisambig = function () {
maxed = true; maxed = true;
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print(" MAXED"); this.state.sys.print(" MAXED");
} }
//SNIP-END //SNIP-END
} }
@ -24256,7 +24346,7 @@ CSL.Disambiguation.prototype.initVars = function (akey) {
var i, ilen, myIds, myItemBundles, myItems; var i, ilen, myIds, myItemBundles, myItems;
//SNIP-START //SNIP-START
if (this.debug) { if (this.debug) {
print("[B] === initVars() ==="); this.state.sys.print("[B] === initVars() ===");
} }
//SNIP-END //SNIP-END
this.lists = []; this.lists = [];

View file

@ -350,7 +350,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
} }
else { else {
var s = new Zotero.Search(); var s = new Zotero.Search();
s.addCondition('libraryID', 'is', this.ref.libraryID); s.libraryID = this.ref.libraryID;
// Library root // Library root
if (this.isLibrary(true)) { if (this.isLibrary(true)) {
s.addCondition('noChildren', 'true'); s.addCondition('noChildren', 'true');
@ -379,7 +379,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
// Create the outer (filter) search // Create the outer (filter) search
var s2 = new Zotero.Search(); var s2 = new Zotero.Search();
s2.addCondition('libraryID', 'is', this.ref.libraryID); s2.libraryID = this.ref.libraryID;
if (this.isTrash()) { if (this.isTrash()) {
s2.addCondition('deleted', 'true'); s2.addCondition('deleted', 'true');

View file

@ -61,6 +61,7 @@ Zotero.HTTPIntegrationClient.Application = function() {
this.supportedNotes = ['footnotes']; this.supportedNotes = ['footnotes'];
this.supportsImportExport = false; this.supportsImportExport = false;
this.supportsTextInsertion = false; this.supportsTextInsertion = false;
this.supportsCitationMerging = false;
this.processorName = "HTTP Integration"; this.processorName = "HTTP Integration";
}; };
Zotero.HTTPIntegrationClient.Application.prototype = { Zotero.HTTPIntegrationClient.Application.prototype = {
@ -70,6 +71,7 @@ Zotero.HTTPIntegrationClient.Application.prototype = {
this.supportedNotes = result.supportedNotes || this.supportedNotes; this.supportedNotes = result.supportedNotes || this.supportedNotes;
this.supportsImportExport = result.supportsImportExport || this.supportsImportExport; this.supportsImportExport = result.supportsImportExport || this.supportsImportExport;
this.supportsTextInsertion = result.supportsTextInsertion || this.supportsTextInsertion; this.supportsTextInsertion = result.supportsTextInsertion || this.supportsTextInsertion;
this.supportsCitationMerging = result.supportsCitationMerging || this.supportsCitationMerging;
this.processorName = result.processorName || this.processorName; this.processorName = result.processorName || this.processorName;
return new Zotero.HTTPIntegrationClient.Document(result.documentID, this.processorName); return new Zotero.HTTPIntegrationClient.Document(result.documentID, this.processorName);
} }
@ -168,6 +170,10 @@ Zotero.HTTPIntegrationClient.Field = function(documentID, json) {
this._code = json.code; this._code = json.code;
this._text = json.text; this._text = json.text;
this._noteIndex = json.noteIndex; this._noteIndex = json.noteIndex;
this._adjacent = json.adjacent;
if (this._adjacent !== 'undefined') {
this.isAdjacentToNextField = this._isAdjacentToNextField;
}
}; };
Zotero.HTTPIntegrationClient.Field.prototype = {}; Zotero.HTTPIntegrationClient.Field.prototype = {};
@ -202,3 +208,6 @@ Zotero.HTTPIntegrationClient.Field.prototype.getNoteIndex = async function() {
Zotero.HTTPIntegrationClient.Field.prototype.equals = async function(arg) { Zotero.HTTPIntegrationClient.Field.prototype.equals = async function(arg) {
return this._id === arg._id; return this._id === arg._id;
}; };
Zotero.HTTPIntegrationClient.Field.prototype._isAdjacentToNextField = async function() {
return this._adjacent;
}

View file

@ -1135,7 +1135,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
} }
try { try {
let item = await this.saveSnapshot(targetID, requestData); var item = await this.saveSnapshot(targetID, requestData);
await session.addItem(item); await session.addItem(item);
} }
catch (e) { catch (e) {
@ -1143,9 +1143,15 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
return 500; return 500;
} }
let attachments = [];
let hasAttachments = !item.isAttachment() && item.getAttachments().length;
if (hasAttachments) {
attachments = [{mimeType: "text/html", title: data.title, url: data.url}];
}
return [201, return [201,
"application/json", "application/json",
JSON.stringify({ saveSingleFile: !data.skipSnapshot && !data.pdf })]; JSON.stringify({ saveSingleFile: !data.skipSnapshot && !data.pdf && data.singleFile, attachments })];
}, },
/* /*
@ -1175,6 +1181,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
let item = await Zotero.Attachments.importFromURL({ let item = await Zotero.Attachments.importFromURL({
libraryID, libraryID,
url: data.url, url: data.url,
referrer: data.referrer,
collections: collection ? [collection.id] : undefined, collections: collection ? [collection.id] : undefined,
contentType: "application/pdf", contentType: "application/pdf",
cookieSandbox cookieSandbox
@ -1234,6 +1241,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
await Zotero.Attachments.importFromURL({ await Zotero.Attachments.importFromURL({
libraryID, libraryID,
url: data.url, url: data.url,
referrer: data.referrer,
title, title,
parentItemID: itemID, parentItemID: itemID,
contentType: "text/html", contentType: "text/html",
@ -1689,9 +1697,8 @@ Zotero.Server.Connector.Ping.prototype = {
*/ */
init: function (req) { init: function (req) {
if (req.method == 'GET') { if (req.method == 'GET') {
return [200, "text/html", '<!DOCTYPE html><html><head>' + return [200, "text/html", '<!DOCTYPE html><html>'
'<title>Zotero Connector Server is Available</title></head>' + + '<body>Zotero is running</body></html>'];
'<body>Zotero Connector Server is Available</body></html>'];
} else { } else {
// Store the active URL so it can be used for site-specific Quick Copy // Store the active URL so it can be used for site-specific Quick Copy
if (req.data.activeURL) { if (req.data.activeURL) {
@ -1846,3 +1853,103 @@ Zotero.Server.Connector.IEHack.prototype = {
'</head><body></body></html>'); '</head><body></body></html>');
} }
} }
/**
* Make an HTTP request from the client. Accepts {@link Zotero.HTTP.request} options and returns a minimal response
* object with the same form as the one returned from {@link Zotero.Utilities.Translate#request}.
*
* Accepts:
* method - The request method ('GET', 'POST', etc.)
* url - The URL to make the request to. Must be an absolute HTTP(S) URL.
* options - See Zotero.HTTP.request() documentation. Differences:
* - responseType is always set to 'text'
* - successCodes is always set to false (non-2xx status codes will not trigger an error)
* Returns:
* Response code is always 200. Body contains:
* status - The response status code, as a number
* headers - An object mapping header names to values
* body - The response body, as a string
*/
Zotero.Server.Connector.Request = function () {};
/**
* The list of allowed hosts. Intentionally hardcoded.
*/
Zotero.Server.Connector.Request.allowedHosts = ['www.worldcat.org'];
/**
* For testing: allow disabling validation so we can make requests to the server.
*/
Zotero.Server.Connector.Request.enableValidation = false;
Zotero.Server.Endpoints["/connector/request"] = Zotero.Server.Connector.Request;
Zotero.Server.Connector.Request.prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
init: async function (req) {
let { method, url, options } = req.data;
if (typeof method !== 'string' || typeof url !== 'string') {
return [400, 'text/plain', 'method and url are required and must be strings'];
}
let uri;
try {
uri = Services.io.newURI(url);
}
catch (e) {
return [400, 'text/plain', 'Invalid URL'];
}
if (uri.scheme != 'http' && uri.scheme != 'https') {
return [400, 'text/plain', 'Unsupported scheme'];
}
if (Zotero.Server.Connector.Request.enableValidation) {
if (!Zotero.Server.Connector.Request.allowedHosts.includes(uri.host)) {
return [
400,
'text/plain',
'Unsupported URL'
];
}
if (!req.headers['User-Agent'] || !req.headers['User-Agent'].startsWith('Mozilla/')) {
return [400, 'text/plain', 'Unsupported User-Agent'];
}
}
options = options || {};
options.responseType = 'text';
options.successCodes = false;
let xhr;
try {
xhr = await Zotero.HTTP.request(req.data.method, req.data.url, options);
}
catch (e) {
if (e instanceof Zotero.HTTP.BrowserOfflineException) {
return [503, 'text/plain', 'Client is offline'];
}
else {
throw e;
}
}
let status = xhr.status;
let headers = {};
xhr.getAllResponseHeaders()
.trim()
.split(/[\r\n]+/)
.map(line => line.split(': '))
.forEach(parts => headers[parts.shift()] = parts.join(': '));
let body = xhr.response;
return [200, 'application/json', JSON.stringify({
status,
headers,
body
})];
}
};

View file

@ -317,10 +317,17 @@ Zotero.CookieSandbox.Observer = new function() {
this.trackedInterfaceRequestors.splice(i, 1); this.trackedInterfaceRequestors.splice(i, 1);
this.trackedInterfaceRequestorSandboxes.splice(i, 1); this.trackedInterfaceRequestorSandboxes.splice(i, 1);
i--; i--;
} else if(ir == notificationCallbacks) { } else {
// We are tracking this interface requestor let tracked = ir === notificationCallbacks;
trackedBy = this.trackedInterfaceRequestorSandboxes[i]; try {
break; tracked = ir === notificationCallbacks.getInterface(Ci.nsIWebBrowserPersist);
} catch (e) { }
if (tracked) {
// We are tracking this interface requestor
trackedBy = this.trackedInterfaceRequestorSandboxes[i];
break;
}
} }
} }
@ -350,12 +357,6 @@ Zotero.CookieSandbox.Observer = new function() {
notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest); notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest);
tested = true; tested = true;
} catch(e) {} } catch(e) {}
if(!tested) {
try {
notificationCallbacks.QueryInterface(Components.interfaces.nsIWebBrowserPersist);
tested = true;
} catch(e) {}
}
} }
} }
} }
@ -383,6 +384,10 @@ Zotero.CookieSandbox.Observer = new function() {
if(trackedBy) { if(trackedBy) {
var cookiesForURI = trackedBy.getCookiesForURI(channel.URI); var cookiesForURI = trackedBy.getCookiesForURI(channel.URI);
} }
if (trackedBy && trackedBy.userAgent) {
channel.setRequestHeader("User-Agent", trackedBy.userAgent, false);
}
if(!trackedBy || !cookiesForURI) { if(!trackedBy || !cookiesForURI) {
channel.setRequestHeader("Cookie", "", false); channel.setRequestHeader("Cookie", "", false);
@ -390,11 +395,7 @@ Zotero.CookieSandbox.Observer = new function() {
Zotero.debug("CookieSandbox: Cleared cookies to be sent to "+channelURI, 5); Zotero.debug("CookieSandbox: Cleared cookies to be sent to "+channelURI, 5);
return; return;
} }
if(trackedBy.userAgent) {
channel.setRequestHeader("User-Agent", trackedBy.userAgent, false);
}
// add cookies to be sent to this domain // add cookies to be sent to this domain
channel.setRequestHeader("Cookie", Zotero.CookieSandbox.generateCookieString(cookiesForURI), false); channel.setRequestHeader("Cookie", Zotero.CookieSandbox.generateCookieString(cookiesForURI), false);
Zotero.debug("CookieSandbox: Added cookies for request to "+channelURI, 5); Zotero.debug("CookieSandbox: Added cookies for request to "+channelURI, 5);

View file

@ -470,6 +470,7 @@ Zotero.ItemTypes = new function() {
case 'case': case 'case':
case 'computerProgram': case 'computerProgram':
case 'conferencePaper': case 'conferencePaper':
case 'dataset':
case 'dictionaryEntry': case 'dictionaryEntry':
case 'email': case 'email':
case 'encyclopediaArticle': case 'encyclopediaArticle':
@ -488,6 +489,7 @@ Zotero.ItemTypes = new function() {
case 'preprint': case 'preprint':
case 'presentation': case 'presentation':
case 'report': case 'report':
case 'standard':
case 'statute': case 'statute':
case 'thesis': case 'thesis':
case 'webpage': case 'webpage':

View file

@ -117,10 +117,10 @@ for (let name of ['deleted']) {
val = !!val; val = !!val;
var oldVal = this._getLatestField(name); var oldVal = this._getLatestField(name);
if (oldVal == val) { if (oldVal == val) {
Zotero.debug(Zotero.Utilities.capitalize(name) Zotero.debug(`Field '${name}' hasn't changed`);
+ ` state hasn't changed for ${this._objectType} ${this.id}`);
return; return;
} }
Zotero.debug(`Field '${name}' has changed from '${oldVal}' to '${val}'`, 4);
this._markFieldChange(name, val); this._markFieldChange(name, val);
} }
}); });
@ -987,6 +987,11 @@ Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env)
env.isNew = !this.id; env.isNew = !this.id;
if (!this.hasChanged()) {
Zotero.debug(this._ObjectType + ' ' + this.id + ' has not changed', 4);
return false;
}
if (!env.options.skipEditCheck) { if (!env.options.skipEditCheck) {
if (!this.isEditable()) { if (!this.isEditable()) {
throw new Error("Cannot edit " + this._objectType + " in library " throw new Error("Cannot edit " + this._objectType + " in library "
@ -999,11 +1004,6 @@ Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env)
throw new Error("Cannot add " + this._objectType + " to a " + targetLib.libraryType + " library"); throw new Error("Cannot add " + this._objectType + " to a " + targetLib.libraryType + " library");
} }
if (!this.hasChanged()) {
Zotero.debug(this._ObjectType + ' ' + this.id + ' has not changed', 4);
return false;
}
// Undo registerObject() on failure // Undo registerObject() on failure
if (env.isNew) { if (env.isNew) {
var func = function () { var func = function () {

View file

@ -275,7 +275,7 @@ Zotero.DataObjectUtilities = {
var pred2 = Object.keys(data2); var pred2 = Object.keys(data2);
pred2.sort(); pred2.sort();
if (!Zotero.Utilities.arrayEquals(pred1, pred2)) return true; if (!Zotero.Utilities.arrayEquals(pred1, pred2)) return true;
for (let pred in pred1) { for (let pred of pred1) {
let vals1 = typeof data1[pred] == 'string' ? [data1[pred]] : data1[pred]; let vals1 = typeof data1[pred] == 'string' ? [data1[pred]] : data1[pred];
let vals2 = (!data2[pred] || data2[pred] === '') let vals2 = (!data2[pred] || data2[pred] === '')
? [] ? []

View file

@ -133,8 +133,9 @@ Zotero.Feeds = new function() {
for (let feedElem of feedElems) { for (let feedElem of feedElems) {
let url = feedElem.getAttribute('xmlUrl'); let url = feedElem.getAttribute('xmlUrl');
if (!url) url = feedElem.getAttribute('url'); if (!url) url = feedElem.getAttribute('url');
let name = feedElem.getAttribute('title'); let name = feedElem.getAttribute('title')
if (!name) name = feedElem.getAttribute('text'); || feedElem.getAttribute('text')
|| Zotero.getString('pane.collections.untitled');
if (Zotero.Feeds.existsByURL(url) || registeredUrls.has(url)) { if (Zotero.Feeds.existsByURL(url) || registeredUrls.has(url)) {
Zotero.debug("Feed Import from OPML: Feed " + name + " : " + url + " already exists. Skipping"); Zotero.debug("Feed Import from OPML: Feed " + name + " : " + url + " already exists. Skipping");
continue; continue;

View file

@ -153,7 +153,16 @@ Zotero.defineProperty(Zotero.Item.prototype, 'parentItemKey', {
Zotero.defineProperty(Zotero.Item.prototype, 'parentItem', { Zotero.defineProperty(Zotero.Item.prototype, 'parentItem', {
get: function() { return Zotero.Items.get(this.parentID) || undefined; }, get: function() { return Zotero.Items.get(this.parentID) || undefined; },
}); });
Zotero.defineProperty(Zotero.Item.prototype, 'topLevelItem', {
get: function () {
var item = this; // eslint-disable-line consistent-this
var parentItem;
while ((parentItem = item.parentItem)) {
item = parentItem;
}
return item;
}
});
Zotero.defineProperty(Zotero.Item.prototype, 'firstCreator', { Zotero.defineProperty(Zotero.Item.prototype, 'firstCreator', {
get: function() { return this._firstCreator; } get: function() { return this._firstCreator; }
@ -768,7 +777,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
if (value !== false && !Zotero.ItemFields.isValidForType(fieldID, itemTypeID)) { if (value !== false && !Zotero.ItemFields.isValidForType(fieldID, itemTypeID)) {
let msg = "'" + field + "' is not a valid field for type '" let msg = "'" + field + "' is not a valid field for type '"
+ Zotero.ItemFields.getName(itemTypeID) + "'"; + Zotero.ItemTypes.getName(itemTypeID) + "'";
if (loadIn) { if (loadIn) {
Zotero.debug(msg + " -- ignoring value '" + value + "'", 2); Zotero.debug(msg + " -- ignoring value '" + value + "'", 2);
@ -1897,7 +1906,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
throw new Error("Annotation parent must be a file attachment"); throw new Error("Annotation parent must be a file attachment");
} }
if (parentItem.attachmentContentType != 'application/pdf') { if (parentItem.attachmentContentType != 'application/pdf') {
throw new Error("Annotation parent must be a PDF"); //throw new Error("Annotation parent must be a PDF");
} }
let type = this._getLatestField('annotationType'); let type = this._getLatestField('annotationType');
let typeID = Zotero.Annotations[`ANNOTATION_TYPE_${type.toUpperCase()}`]; let typeID = Zotero.Annotations[`ANNOTATION_TYPE_${type.toUpperCase()}`];
@ -1926,7 +1935,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
authorName || null, authorName || null,
text || null, text || null,
comment || null, comment || null,
color || null, color || Zotero.Annotations.DEFAULT_COLOR,
pageLabel || null, pageLabel || null,
sortIndex, sortIndex,
position, position,
@ -2231,6 +2240,10 @@ Zotero.Item.prototype.setNote = function(text) {
throw ("updateNote() can only be called on notes and attachments"); throw ("updateNote() can only be called on notes and attachments");
} }
if (this.isEmbeddedImageAttachment()) {
throw new Error("setNote() cannot be called on embedded-image attachments");
}
if (typeof text != 'string') { if (typeof text != 'string') {
throw ("text must be a string in Zotero.Item.setNote() (was " + typeof text + ")"); throw ("text must be a string in Zotero.Item.setNote() (was " + typeof text + ")");
} }
@ -3396,7 +3409,7 @@ Zotero.Item.prototype.getAttachmentLastPageIndex = function () {
var id = this._getLastPageIndexSettingKey(); var id = this._getLastPageIndexSettingKey();
var val = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, id); var val = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, id);
if (val !== null && (typeof val != 'number' || val != parseInt(val))) { if (this.isPDFAttachment() && val !== null && (typeof val != 'number' || val != parseInt(val))) {
Zotero.logError(`Setting contains an invalid attachment page index ('${val}') -- discarding`); Zotero.logError(`Setting contains an invalid attachment page index ('${val}') -- discarding`);
return null; return null;
} }
@ -3408,7 +3421,7 @@ Zotero.Item.prototype.setAttachmentLastPageIndex = async function (val) {
throw new Error("setAttachmentLastPageIndex() can only be called on file attachments"); throw new Error("setAttachmentLastPageIndex() can only be called on file attachments");
} }
if (typeof val != 'number' || val != parseInt(val)) { if (this.isPDFAttachment() && (typeof val != 'number' || val != parseInt(val))) {
Zotero.debug(val, 2); Zotero.debug(val, 2);
throw new Error(`setAttachmentLastPageIndex() must be passed an integer`); throw new Error(`setAttachmentLastPageIndex() must be passed an integer`);
} }
@ -3713,7 +3726,7 @@ Zotero.Item.prototype.getBestAttachments = Zotero.Promise.coroutine(function* ()
/** /**
* Return state of best attachment * Return state of best attachment (or this item if it's a standalone attachment)
* *
* @return {Promise<Object>} - Promise for object with string 'type' ('none'|'pdf'|'snapshot'|'other') * @return {Promise<Object>} - Promise for object with string 'type' ('none'|'pdf'|'snapshot'|'other')
* and boolean 'exists' * and boolean 'exists'
@ -3722,7 +3735,9 @@ Zotero.Item.prototype.getBestAttachmentState = async function () {
if (this._bestAttachmentState !== null) { if (this._bestAttachmentState !== null) {
return this._bestAttachmentState; return this._bestAttachmentState;
} }
var item = await this.getBestAttachment(); var item = this.isAttachment() && this.isTopLevelItem()
? this
: await this.getBestAttachment();
if (!item) { if (!item) {
return this._bestAttachmentState = { return this._bestAttachmentState = {
type: 'none' type: 'none'
@ -3800,7 +3815,7 @@ for (let name of ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel',
if (currentType && currentType != value) { if (currentType && currentType != value) {
throw new Error("Cannot change annotation type"); throw new Error("Cannot change annotation type");
} }
if (!['highlight', 'note', 'image', 'ink'].includes(value)) { if (!['highlight', 'underline', 'note', 'text', 'image', 'ink'].includes(value)) {
let e = new Error(`Unknown annotation type '${value}'`); let e = new Error(`Unknown annotation type '${value}'`);
e.name = "ZoteroInvalidDataError"; e.name = "ZoteroInvalidDataError";
throw e; throw e;
@ -3808,8 +3823,8 @@ for (let name of ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel',
break; break;
} }
case 'text': case 'text':
if (this._getLatestField('annotationType') != 'highlight') { if (!['highlight', 'underline'].includes(this._getLatestField('annotationType'))) {
throw new Error("'annotationText' can only be set for highlight annotations"); throw new Error("'annotationText' can only be set for highlight and underline annotations");
} }
break; break;
@ -3823,7 +3838,7 @@ for (let name of ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel',
break; break;
case 'sortIndex': case 'sortIndex':
if (!/^\d{5}\|\d{6}\|\d{5}$/.test(value)) { if (!/^(\d{5}\|\d{6}\|\d{5}|\d{5}\|\d{8}|\d{8})$/.test(value)) {
throw new Error(`Invalid sortIndex '${value}'`); throw new Error(`Invalid sortIndex '${value}'`);
} }
break; break;
@ -3949,10 +3964,35 @@ Zotero.Item.prototype.getAnnotations = function (includeTrashed) {
} }
var ids = rows.map(row => row.itemID); var ids = rows.map(row => row.itemID);
this._annotations[cacheKey] = ids; this._annotations[cacheKey] = ids;
return Zotero.Items.get(ids); return Zotero.Items.get(ids)
// Filter out underline and text annotations in Zotero 6
.filter(x => !['underline', 'text'].includes(x.annotationType));
}; };
/**
* Determine if the item is a PDF attachment that exists on disk and contains
* embedded markup annotations.
*
* @return {Promise<Boolean>}
*/
Zotero.Item.prototype.hasEmbeddedAnnotations = async function () {
if (!this.isPDFAttachment()) {
return false;
}
let path = await this.getFilePathAsync();
if (!path) {
return false;
}
let contents = await Zotero.File.getContentsAsync(path);
// Check for "markup" annotations per the PDF spec
// https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf, p. 390
let re = /\s\/Subtype\s+\/(Text|FreeText|Line|Square|Circle|Polygon|PolyLine|Highlight|Underline|Squiggly|StrikeOut|Stamp|Caret|Ink|FileAttachment|Sound|Redact)\s/;
return re.test(contents);
};
// //
// Methods dealing with item tags // Methods dealing with item tags
@ -5009,7 +5049,6 @@ Zotero.Item.prototype.fromJSON = function (json, options = {}) {
// Annotation fields // Annotation fields
// //
case 'annotationType': case 'annotationType':
case 'annotationType':
case 'annotationAuthorName': case 'annotationAuthorName':
case 'annotationText': case 'annotationText':
case 'annotationComment': case 'annotationComment':
@ -5189,7 +5228,7 @@ Zotero.Item.prototype.fromJSON = function (json, options = {}) {
let parentKey = json.parentItem; let parentKey = json.parentItem;
this.parentKey = parentKey ? parentKey : false; this.parentKey = parentKey ? parentKey : false;
if (!this.isAnnotation()) { if (!this.isAnnotation() && !this.isEmbeddedImageAttachment()) {
let note = json.note; let note = json.note;
this.setNote(note !== undefined ? note : ""); this.setNote(note !== undefined ? note : "");
} }
@ -5288,7 +5327,7 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
let type = this.annotationType; let type = this.annotationType;
obj.annotationType = type; obj.annotationType = type;
obj.annotationAuthorName = this.annotationAuthorName || ''; obj.annotationAuthorName = this.annotationAuthorName || '';
if (type == 'highlight') { if (['highlight', 'underline'].includes(type)) {
obj.annotationText = this.annotationText || ''; obj.annotationText = this.annotationText || '';
} }
obj.annotationComment = this.annotationComment || ''; obj.annotationComment = this.annotationComment || '';
@ -5408,7 +5447,16 @@ Zotero.Item.prototype.migrateExtraFields = function () {
try { try {
var { itemType, fields, creators, extra } = Zotero.Utilities.Internal.extractExtraFields( var { itemType, fields, creators, extra } = Zotero.Utilities.Internal.extractExtraFields(
originalExtra, this originalExtra,
this,
[
// Skip 'publisher-place' and 'event-place' for now, since the mappings will be changed
// https://github.com/citation-style-language/zotero-bits/issues/6
'place',
// Skip 'issued' for now, since we don't support date ranges in Date
// https://github.com/zotero/zotero/issues/3030
'date'
]
); );
if (itemType) { if (itemType) {
let originalType = this.itemTypeID; let originalType = this.itemTypeID;

View file

@ -76,11 +76,9 @@ Zotero.ItemFields = new function() {
var baseFields = yield Zotero.DB.columnQueryAsync(sql); var baseFields = yield Zotero.DB.columnQueryAsync(sql);
for (let field of fields) { for (let field of fields) {
let isBaseField = baseFields.includes(field.fieldID);
let label = field.label || Zotero.Schema.globalSchemaLocale.fields[field.fieldName]; let label = field.label || Zotero.Schema.globalSchemaLocale.fields[field.fieldName];
// If string not available, use the field name, except for some base fields that aren't // If string not available, use the field name
// used in the UI and therefore aren't localized if (!label) {
if (!label && !['number', 'type', 'medium'].includes(field.fieldName)) {
Zotero.logError(`Localized string not available for field '${field.fieldName}'`); Zotero.logError(`Localized string not available for field '${field.fieldName}'`);
label = Zotero.Utilities.Internal.camelToTitleCase(field.fieldName); label = Zotero.Utilities.Internal.camelToTitleCase(field.fieldName);
} }
@ -347,7 +345,11 @@ Zotero.ItemFields = new function() {
this.isAutocompleteField = function (field) { this.isAutocompleteField = function (field) {
field = this.getName(field); var fieldName = this.getName(field);
if (!fieldName) {
Zotero.logError(`Can't check autocomplete for invalid field '${field}'`);
return false;
}
var autoCompleteFields = [ var autoCompleteFields = [
'journalAbbreviation', 'journalAbbreviation',
@ -377,7 +379,7 @@ Zotero.ItemFields = new function() {
autoCompleteFields = autoCompleteFields.concat(add); autoCompleteFields = autoCompleteFields.concat(add);
} }
return autoCompleteFields.indexOf(field) != -1; return autoCompleteFields.includes(fieldName);
} }

View file

@ -500,55 +500,76 @@ Zotero.Items = function() {
+ "FROM items JOIN itemAnnotations IA USING (itemID) " + "FROM items JOIN itemAnnotations IA USING (itemID) "
+ "WHERE libraryID=?" + idSQL; + "WHERE libraryID=?" + idSQL;
var params = [libraryID]; var params = [libraryID];
await Zotero.DB.queryAsync(
sql, // TEMP: Fix faulty upgrade from early 6.0 beta
params, // https://github.com/zotero/zotero/issues/3013
{ try {
noCache: true, await Zotero.DB.queryAsync(
onRow: function (row) { sql,
let itemID = row.getResultByIndex(0); params,
{
let item = this._objectCache[itemID]; noCache: true,
if (!item) { onRow: function (row) {
throw new Error("Item " + itemID + " not found"); let itemID = row.getResultByIndex(0);
}
item._parentItemID = row.getResultByIndex(1);
var typeID = row.getResultByIndex(2);
var type;
switch (typeID) {
case Zotero.Annotations.ANNOTATION_TYPE_HIGHLIGHT:
type = 'highlight';
break;
case Zotero.Annotations.ANNOTATION_TYPE_NOTE: let item = this._objectCache[itemID];
type = 'note'; if (!item) {
break; throw new Error("Item " + itemID + " not found");
}
case Zotero.Annotations.ANNOTATION_TYPE_IMAGE: item._parentItemID = row.getResultByIndex(1);
type = 'image'; var typeID = row.getResultByIndex(2);
break; var type;
switch (typeID) {
case Zotero.Annotations.ANNOTATION_TYPE_HIGHLIGHT:
type = 'highlight';
break;
case Zotero.Annotations.ANNOTATION_TYPE_UNDERLINE:
type = 'underline';
break;
case Zotero.Annotations.ANNOTATION_TYPE_NOTE:
type = 'note';
break;
case Zotero.Annotations.ANNOTATION_TYPE_TEXT:
type = 'text';
break;
case Zotero.Annotations.ANNOTATION_TYPE_IMAGE:
type = 'image';
break;
case Zotero.Annotations.ANNOTATION_TYPE_INK:
type = 'ink';
break;
default:
throw new Error(`Unknown annotation type id ${typeID}`);
}
item._annotationType = type;
item._annotationAuthorName = row.getResultByIndex(3);
item._annotationText = row.getResultByIndex(4);
item._annotationComment = row.getResultByIndex(5);
item._annotationColor = row.getResultByIndex(6);
item._annotationSortIndex = row.getResultByIndex(7);
item._annotationIsExternal = !!row.getResultByIndex(8);
case Zotero.Annotations.ANNOTATION_TYPE_INK: item._loaded.annotation = true;
type = 'ink'; item._clearChanged('annotation');
break; }.bind(this)
}
default: );
throw new Error(`Unknown annotation type id ${typeID}`); }
} catch (e) {
item._annotationType = type; if (e.message.includes('no such column: IA.authorName')
item._annotationAuthorName = row.getResultByIndex(3); && await Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM version WHERE schema='userdata' AND version IN (120, 121)")) {
item._annotationText = row.getResultByIndex(4); await Zotero.DB.queryAsync("UPDATE version SET version=119 WHERE schema='userdata'");
item._annotationComment = row.getResultByIndex(5); Zotero.crash();
item._annotationColor = row.getResultByIndex(6);
item._annotationSortIndex = row.getResultByIndex(7);
item._annotationIsExternal = !!row.getResultByIndex(8);
item._loaded.annotation = true;
item._clearChanged('annotation');
}.bind(this)
} }
); throw e;
}
}; };
@ -1035,6 +1056,29 @@ Zotero.Items = function() {
for (let otherItem of otherItems) { for (let otherItem of otherItems) {
let mergedMasterAttachments = new Set(); let mergedMasterAttachments = new Set();
let doMerge = async (fromAttachment, toAttachment) => {
mergedMasterAttachments.add(toAttachment.id);
await this.moveChildItems(fromAttachment, toAttachment, true);
await this._moveEmbeddedNote(fromAttachment, toAttachment);
await this._moveRelations(fromAttachment, toAttachment);
fromAttachment.deleted = true;
await fromAttachment.save();
// Later on, when processing notes, we'll use this to remap
// URLs pointing to the old attachment.
remapAttachmentKeys.set(fromAttachment.key, toAttachment.key);
// Items can only have one replaced item predicate
if (!toAttachment.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate)) {
toAttachment.addRelation(Zotero.Relations.replacedItemPredicate,
Zotero.URI.getItemURI(fromAttachment));
}
await toAttachment.save();
};
for (let otherAttachment of await this.getAsync(otherItem.getAttachments(true))) { for (let otherAttachment of await this.getAsync(otherItem.getAttachments(true))) {
if (!otherAttachment.isPDFAttachment()) { if (!otherAttachment.isPDFAttachment()) {
continue; continue;
@ -1065,13 +1109,12 @@ Zotero.Items = function() {
await otherAttachment.save(); await otherAttachment.save();
continue; continue;
} }
mergedMasterAttachments.add(masterAttachmentID);
let masterAttachment = await this.getAsync(masterAttachmentID); let masterAttachment = await this.getAsync(masterAttachmentID);
if (masterAttachment.attachmentContentType !== otherAttachment.attachmentContentType) { if (masterAttachment.attachmentContentType !== otherAttachment.attachmentContentType) {
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, ` Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
+ 'but content types differ - moving'); + 'but content types differ - keeping both');
otherAttachment.parentItemID = item.id; otherAttachment.parentItemID = item.id;
await otherAttachment.save(); await otherAttachment.save();
continue; continue;
@ -1080,31 +1123,35 @@ Zotero.Items = function() {
if (!((masterAttachment.isImportedAttachment() && otherAttachment.isImportedAttachment()) if (!((masterAttachment.isImportedAttachment() && otherAttachment.isImportedAttachment())
|| (masterAttachment.isLinkedFileAttachment() && otherAttachment.isLinkedFileAttachment()))) { || (masterAttachment.isLinkedFileAttachment() && otherAttachment.isLinkedFileAttachment()))) {
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, ` Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
+ 'but link modes differ - moving'); + 'but link modes differ - keeping both');
otherAttachment.parentItemID = item.id; otherAttachment.parentItemID = item.id;
await otherAttachment.save(); await otherAttachment.save();
continue; continue;
} }
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key} - merging`); // Check whether master and other have embedded annotations
await this.moveChildItems(otherAttachment, masterAttachment, true); // Master yes, other yes -> keep both
await this._moveEmbeddedNote(otherAttachment, masterAttachment); // Master yes, other no -> keep master
await this._moveRelations(otherAttachment, masterAttachment); // Master no, other yes -> keep other
if (await otherAttachment.hasEmbeddedAnnotations()) {
otherAttachment.deleted = true; if (await masterAttachment.hasEmbeddedAnnotations()) {
await otherAttachment.save(); Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
+ 'but both have embedded annotations - keeping both');
// Later on, when processing notes, we'll use this to remap otherAttachment.parentItemID = item.id;
// URLs pointing to the old attachment. await otherAttachment.save();
remapAttachmentKeys.set(otherAttachment.key, masterAttachment.key); }
else {
// Items can only have one replaced item predicate Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
if (!masterAttachment.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate)) { + 'but other has embedded annotations - merging into other');
masterAttachment.addRelation(Zotero.Relations.replacedItemPredicate, await doMerge(masterAttachment, otherAttachment);
Zotero.URI.getItemURI(otherAttachment)); otherAttachment.parentItemID = item.id;
await otherAttachment.save();
}
continue;
} }
await masterAttachment.save(); Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key} - merging into master`);
await doMerge(otherAttachment, masterAttachment);
} }
} }
@ -1477,6 +1524,7 @@ Zotero.Items = function() {
); );
} }
Zotero.debug("Emptied " + deleted.length + " item(s) from trash in " + (new Date() - t) + " ms"); Zotero.debug("Emptied " + deleted.length + " item(s) from trash in " + (new Date() - t) + " ms");
Zotero.Notifier.trigger('refresh', 'trash', libraryID);
} }
return deleted.length; return deleted.length;
@ -1709,22 +1757,40 @@ Zotero.Items = function() {
/** /**
* Returns an array of items with children of selected parents removed * Get the top-level items of all passed items
* *
* @param {Zotero.Item[]} items
* @return {Zotero.Item[]} * @return {Zotero.Item[]}
*/ */
this.keepParents = function (items) { this.getTopLevel = function (items) {
var parentItems = new Set( return [...new Set(items.map(item => item.topLevelItem))];
items };
.filter(item => item.isTopLevelItem())
.map(item => item.id)
/**
* Return an array of items with descendants of selected top-level items removed
*
* Non-top-level items that aren't descendents of selected items are kept.
*
* @param {Zotero.Item[]}
* @return {Zotero.Item[]}
*/
this.keepTopLevel = function (items) {
var topLevelItems = new Set(
items.filter(item => item.isTopLevelItem())
); );
return items.filter(item => { return items.filter((item) => {
var parentItemID = item.parentItemID; var topLevelItem = !item.isTopLevelItem() && item.topLevelItem;
// Not a child item or not a child of one of the passed items // Not a child item or not a child of one of the passed items
return !parentItemID || !parentItems.has(parentItemID); return !topLevelItem || !topLevelItems.has(topLevelItem);
}); });
} };
this.keepParents = function (items) {
Zotero.debug("Zotero.Items.keepParents() is deprecated -- use Zotero.Items.keepTopLevel() instead");
return this.keepTopLevel(items);
};
/* /*
@ -2011,6 +2077,28 @@ Zotero.Items = function() {
} }
return title.trim(); return title.trim();
}; };
/**
* Find attachment items whose paths begin with the passed `pathPrefix` and don't exist on disk
*
* @param {Number} libraryID
* @param {String} pathPrefix
* @return {Zotero.Item[]}
*/
this.findMissingLinkedFiles = async function (libraryID, pathPrefix) {
let sql = "SELECT itemID FROM items JOIN itemAttachments USING (itemID) "
+ "WHERE itemID NOT IN (SELECT itemID FROM deletedItems) "
+ `AND linkMode=${Zotero.Attachments.LINK_MODE_LINKED_FILE} `
+ "AND path LIKE ? ESCAPE '\\' "
+ "AND libraryID=?";
let ids = await Zotero.DB.columnQueryAsync(sql, [Zotero.DB.escapeSQLExpression(pathPrefix) + '%', libraryID]);
let items = await this.getAsync(ids);
let missingItems = await Promise.all(
items.map(async item => (await item.fileExists() ? false : item))
);
return missingItems.filter(Boolean);
};
Zotero.DataObjects.call(this); Zotero.DataObjects.call(this);

View file

@ -75,7 +75,12 @@ Zotero.defineProperty(Zotero.Search.prototype, 'key', {
}); });
Zotero.defineProperty(Zotero.Search.prototype, 'name', { Zotero.defineProperty(Zotero.Search.prototype, 'name', {
get: function() { return this._get('name'); }, get: function() { return this._get('name'); },
set: function(val) { return this._set('name', val); } set: function (val) {
if (!val) {
throw new Error("Saved search name cannot be empty");
}
return this._set('name', val);
}
}); });
Zotero.defineProperty(Zotero.Search.prototype, 'version', { Zotero.defineProperty(Zotero.Search.prototype, 'version', {
get: function() { return this._get('version'); }, get: function() { return this._get('version'); },
@ -948,7 +953,11 @@ Zotero.Search.idsToTempTable = Zotero.Promise.coroutine(function* (ids) {
Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () { Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
this._requireData('conditions'); this._requireData('conditions');
var sql = 'SELECT itemID FROM items'; // TEMP: Match parent attachment for annotation matches
// var sql = 'SELECT itemID FROM items';
var sql = "SELECT COALESCE(IA.parentItemID, itemID) AS itemID FROM items "
+ "LEFT JOIN itemAnnotations IA USING (itemID)";
var sqlParams = []; var sqlParams = [];
// Separate ANY conditions for 'required' condition support // Separate ANY conditions for 'required' condition support
var anySQL = ''; var anySQL = '';
@ -957,12 +966,14 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
var conditions = []; var conditions = [];
let lastCondition; let lastCondition;
for (let condition of Object.values(this._conditions)) { let conditionsToProcess = Object.values(this._conditions);
for (let condition of conditionsToProcess) {
let name = condition.condition; let name = condition.condition;
let conditionData = Zotero.SearchConditions.get(name); let conditionData = Zotero.SearchConditions.get(name);
// Has a table (or 'savedSearch', which doesn't have a table but isn't special) // Has a table (or 'savedSearch', which doesn't have a table but isn't special)
if (conditionData.table || name == 'savedSearch' || name == 'tempTable') { // TEMP: Or 'tag', which needs to match annotation parents
if (conditionData.table || name == 'savedSearch' || name == 'tempTable' || name == 'tag') {
// For conditions with an inline filter using 'is'/'isNot', combine with last condition // For conditions with an inline filter using 'is'/'isNot', combine with last condition
// if the same // if the same
if (lastCondition if (lastCondition
@ -1050,6 +1061,39 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
case 'blockEnd': case 'blockEnd':
conditions.push({name:'blockEnd'}); conditions.push({name:'blockEnd'});
continue; continue;
case 'anyField':
// We expand this condition to the same underlying set of conditions as 'quicksearch-fields'
// (although we don't detect keys or split into quoted and unquoted segments). 'quicksearch-fields'
// is expanded in addCondition(), but we can't do that with this condition because we don't want
// to save the conditions it expands to in the search object
conditionsToProcess.push({ condition: 'blockStart' });
conditionsToProcess.push({
condition: 'field',
operator: condition.operator,
value: condition.value,
required: false
});
conditionsToProcess.push({
condition: 'tag',
operator: condition.operator,
value: condition.value,
required: false
});
conditionsToProcess.push({
condition: 'note',
operator: condition.operator,
value: condition.value,
required: false
});
conditionsToProcess.push({
condition: 'creator',
operator: condition.operator,
value: condition.value,
required: false
});
conditionsToProcess.push({ condition: 'blockEnd' });
continue;
} }
throw new Error('Unhandled special condition ' + name); throw new Error('Unhandled special condition ' + name);
@ -1058,16 +1102,24 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
// Exclude deleted items (and their child items) by default // Exclude deleted items (and their child items) by default
let not = deleted ? "" : "NOT "; let not = deleted ? "" : "NOT ";
let op = deleted ? "OR" : "AND"; sql += ` WHERE (itemID ${not} IN (`
sql += " WHERE (" // Deleted items
+ `itemID ${not} IN (SELECT itemID FROM deletedItems) ` + "SELECT itemID FROM deletedItems "
+ `${op} itemID ${not}IN (SELECT itemID FROM itemNotes ` // Child notes of deleted items
+ "UNION SELECT itemID FROM itemNotes "
+ "WHERE parentItemID IS NOT NULL AND " + "WHERE parentItemID IS NOT NULL AND "
+ "parentItemID IN (SELECT itemID FROM deletedItems)) " + "parentItemID IN (SELECT itemID FROM deletedItems) "
+ `${op} itemID ${not}IN (SELECT itemID FROM itemAttachments ` // Child attachments of deleted items
+ "UNION SELECT itemID FROM itemAttachments "
+ "WHERE parentItemID IS NOT NULL AND " + "WHERE parentItemID IS NOT NULL AND "
+ "parentItemID IN (SELECT itemID FROM deletedItems))" + "parentItemID IN (SELECT itemID FROM deletedItems)"
+ ")"; // Annotations of deleted attachments
+ "UNION SELECT itemID FROM itemAnnotations "
+ "WHERE parentItemID IN (SELECT itemID FROM deletedItems)"
// Annotations of attachments of deleted items
+ "UNION SELECT itemID FROM itemAnnotations "
+ "WHERE parentItemID IN (SELECT itemID FROM itemAttachments WHERE parentItemID IN (SELECT itemID FROM deletedItems))"
+ "))";
if (noChildren){ if (noChildren){
sql += " AND (itemID NOT IN (SELECT itemID FROM itemNotes " sql += " AND (itemID NOT IN (SELECT itemID FROM itemNotes "
@ -1077,14 +1129,15 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
} }
if (unfiled) { if (unfiled) {
sql += " AND (itemID NOT IN (SELECT itemID FROM collectionItems) " sql += " AND (itemID NOT IN ("
+ "SELECT itemID FROM collectionItems "
// Exclude children // Exclude children
+ "AND itemID NOT IN " + "UNION SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL "
+ "(SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL " + "UNION SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL "
+ "UNION SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL)" + "UNION SELECT itemID FROM itemAnnotations "
+ ") "
// Exclude My Publications // Exclude My Publications
+ "AND itemID NOT IN (SELECT itemID FROM publicationsItems)"; + "UNION SELECT itemID FROM publicationsItems "
+ "))";
} }
if (retracted) { if (retracted) {
@ -1119,22 +1172,42 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
// //
// Special table handling // Special table handling
// //
if (condition['table']){ if (condition.table) {
switch (condition['table']){ let negationOperators = ['isNot', 'doesNotContain'];
default: let isNegationOperator = negationOperators.includes(condition.operator);
condSelectSQL += 'itemID '
switch (condition['operator']){ condSelectSQL += 'itemID '
case 'isNot': if (isNegationOperator) {
case 'doesNotContain': condSelectSQL += 'NOT ';
condSelectSQL += 'NOT ';
break;
}
condSelectSQL += 'IN (';
selectOpenParens = 1;
condSQL += 'SELECT itemID FROM ' +
condition['table'] + ' WHERE (';
openParens = 1;
} }
condSelectSQL += 'IN (';
selectOpenParens = 1;
// TEMP: Don't match annotations for negation operators, since it would result in
// all parent attachments being returned
if (isNegationOperator) {
condSelectSQL += "SELECT itemID FROM items WHERE itemTypeID="
+ Zotero.ItemTypes.getID('annotation') + " UNION ";
}
switch (condition.name) {
// TEMP: Match parent attachments of matching annotations
case 'tag':
condSQL += "SELECT COALESCE(IAnT.parentItemID, itemID) FROM itemTags "
+ "LEFT JOIN itemAnnotations IAnT USING (itemID) WHERE (";
break;
// TEMP: Match parent attachments of matching annotations
case 'annotationText':
case 'annotationComment':
condSQL += `SELECT parentItemID FROM ${condition.table} WHERE (`
break;
default:
condSQL += `SELECT itemID FROM ${condition.table} WHERE (`;
}
openParens = 1;
} }
// //
@ -1709,7 +1782,11 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
// Add on quicksearch conditions // Add on quicksearch conditions
if (quicksearchSQLSet) { if (quicksearchSQLSet) {
sql = "SELECT itemID FROM items WHERE itemID IN (" + sql + ") " // TEMP: Match parent attachments for annotations
//sql = "SELECT itemID FROM items WHERE itemID IN (" + sql + ") "
sql = "SELECT COALESCE(IAn.parentItemID, itemID) AS itemID FROM items "
+ "LEFT JOIN itemAnnotations IAn USING (itemID) "
+ "WHERE itemID IN (" + sql + ") "
+ "AND ((" + quicksearchSQLSet.join(') AND (') + "))"; + "AND ((" + quicksearchSQLSet.join(') AND (') + "))";
for (var k=0; k<quicksearchParamsSet.length; k++) { for (var k=0; k<quicksearchParamsSet.length; k++) {

View file

@ -23,7 +23,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.SearchConditions = new function(){ Zotero.SearchConditions = new function () {
this.get = get; this.get = get;
this.getStandardConditions = getStandardConditions; this.getStandardConditions = getStandardConditions;
this.hasOperator = hasOperator; this.hasOperator = hasOperator;
@ -451,6 +451,18 @@ Zotero.SearchConditions = new function(){
+ "'section','seriesNumber','issue')"), + "'section','seriesNumber','issue')"),
template: true // mark for special handling template: true // mark for special handling
}, },
{
name: 'anyField',
operators: {
is: true,
isNot: true,
contains: true,
doesNotContain: true,
beginsWith: true
},
special: false
},
{ {
name: 'datefield', name: 'datefield',
@ -652,6 +664,10 @@ Zotero.SearchConditions = new function(){
var collation = Zotero.getLocaleCollation(); var collation = Zotero.getLocaleCollation();
_standardConditions.sort(function(a, b) { _standardConditions.sort(function(a, b) {
// Sort Any Field to the top
if (a.name == 'anyField') {
return -1;
}
return collation.compareString(1, a.localized, b.localized); return collation.compareString(1, a.localized, b.localized);
}); });
}); });

View file

@ -163,9 +163,6 @@ Zotero.Tags = new function() {
if (libraryID) { if (libraryID) {
sql += "JOIN items USING (itemID) WHERE libraryID = ? "; sql += "JOIN items USING (itemID) WHERE libraryID = ? ";
params.push(libraryID); params.push(libraryID);
// TEMP: Don't show annotation tags in tag selector
sql += "AND itemTypeID != ? ";
params.push(Zotero.ItemTypes.getID('annotation'));
} }
else { else {
sql += "WHERE 1 "; sql += "WHERE 1 ";
@ -174,7 +171,11 @@ Zotero.Tags = new function() {
if (libraryID) { if (libraryID) {
throw new Error("tmpTable and libraryID are mutually exclusive"); throw new Error("tmpTable and libraryID are mutually exclusive");
} }
sql += "AND itemID IN (SELECT itemID FROM " + tmpTable + ") "; sql += "AND itemID IN (SELECT itemID FROM " + tmpTable;
// TEMP: Match parent attachments for annotation tags
sql += " UNION SELECT itemID FROM itemAnnotations WHERE parentItemID IN "
+ "(SELECT itemID FROM " + tmpTable + ")";
sql += ") ";
} }
if (types && types.length) { if (types && types.length) {
sql += "AND type IN (" + new Array(types.length).fill('?').join(', ') + ") "; sql += "AND type IN (" + new Array(types.length).fill('?').join(', ') + ") ";

View file

@ -909,39 +909,6 @@ Zotero.DataDirectory = {
return false; return false;
} }
// Check for an existing pipe from other running versions of Zotero pointing at the same data
// directory, and skip migration if found
try {
let foundPipe = yield Zotero.IPC.pipeExists();
if (foundPipe) {
Zotero.debug("Found existing pipe -- skipping migration");
if (!automatic) {
let ps = Services.prompt;
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
let index = ps.confirmEx(null,
Zotero.getString('dataDir.migration.failure.title'),
Zotero.getString('dataDir.migration.failure.full.firefoxOpen'),
buttonFlags,
Zotero.getString('general.tryAgain'),
Zotero.getString('general.tryLater'),
null, null, {}
);
if (index == 0) {
return this.checkForMigration(newDir, newDir);
}
}
return false;
}
}
catch (e) {
Zotero.logError("Error checking for pipe -- skipping migration:\n\n" + e);
return false;
}
// If there are other profiles pointing to the old directory, make sure we can edit the prefs.js // If there are other profiles pointing to the old directory, make sure we can edit the prefs.js
// file before doing anything, or else we risk orphaning a 4.0 installation // file before doing anything, or else we risk orphaning a 4.0 installation
try { try {
@ -1043,10 +1010,6 @@ Zotero.DataDirectory = {
// Set data directory again // Set data directory again
Zotero.debug("Using new data directory " + newDir); Zotero.debug("Using new data directory " + newDir);
this._cache(newDir); this._cache(newDir);
// Tell Zotero for Firefox in connector mode to reload and find the new data directory
if (Zotero.isStandalone) {
Zotero.IPC.broadcast('reinit');
}
// At least the database was copied, but other things failed // At least the database was copied, but other things failed
if (errors.length) { if (errors.length) {

View file

@ -1303,7 +1303,6 @@ Zotero.DBConnection.prototype._checkException = async function (e) {
* @return {Boolean} - True if recovered, false if not * @return {Boolean} - True if recovered, false if not
*/ */
Zotero.DBConnection.prototype._handleCorruptionMarker = async function () { Zotero.DBConnection.prototype._handleCorruptionMarker = async function () {
var storage = Services.storage;
var file = this._dbPath; var file = this._dbPath;
var fileName = OS.Path.basename(file); var fileName = OS.Path.basename(file);
var backupFile = this._dbPath + '.bak'; var backupFile = this._dbPath + '.bak';
@ -1315,26 +1314,38 @@ Zotero.DBConnection.prototype._handleCorruptionMarker = async function () {
if (!await OS.File.exists(backupFile)) { if (!await OS.File.exists(backupFile)) {
this._debug("No backup file for DB '" + this._dbName + "' exists", 1); this._debug("No backup file for DB '" + this._dbName + "' exists", 1);
// Save damaged filed let damagedFile;
this._debug('Saving damaged DB file with .damaged extension', 1);
let damagedFile = this._dbPath + '.damaged'; // If database file exists, move it to .damaged
damagedFile = await Zotero.File.moveToUnique(file, damagedFile); if (await OS.File.exists(file)) {
this._debug('Saving damaged DB file with .damaged extension', 1);
damagedFile = this._dbPath + '.damaged';
damagedFile = await Zotero.File.moveToUnique(file, damagedFile);
}
// If it doesn't exist, assume we already showed a warning and moved it
else {
this._debug(`Database file '${fileName}' doesn't exist!`);
}
// Create new main database // Create new main database
this._connection = storage.openDatabase(file); this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
path: file
}));
if (await OS.File.exists(corruptMarker)) { if (await OS.File.exists(corruptMarker)) {
await OS.File.remove(corruptMarker); await OS.File.remove(corruptMarker);
} }
Zotero.alert( if (damagedFile) {
null, Zotero.alert(
Zotero.getString('startupError', Zotero.appName), null,
Zotero.getString( Zotero.getString('startupError', Zotero.appName),
'db.dbCorruptedNoBackup', Zotero.getString(
[Zotero.appName, fileName, OS.Path.basename(damagedFile)] 'db.dbCorruptedNoBackup',
) [Zotero.appName, fileName, OS.Path.basename(damagedFile)]
); )
);
}
return; return;
} }

View file

@ -384,12 +384,12 @@ Zotero.Duplicates.prototype._findDuplicates = Zotero.Promise.coroutine(function*
for (let i = 0; i < aCreatorRows.length; i++) { for (let i = 0; i < aCreatorRows.length; i++) {
let aCreatorRow = aCreatorRows[i]; let aCreatorRow = aCreatorRows[i];
let aLastName = aCreatorRow.lastName; let aLastName = aCreatorRow.lastName;
let aFirstInitial = aCreatorRow.firstInitial; let aFirstInitial = aCreatorRow.firstInitial || "";
for (let j = 0; j < bCreatorRows.length; j++) { for (let j = 0; j < bCreatorRows.length; j++) {
let bCreatorRow = bCreatorRows[j]; let bCreatorRow = bCreatorRows[j];
let bLastName = bCreatorRow.lastName; let bLastName = bCreatorRow.lastName;
let bFirstInitial = bCreatorRow.firstInitial; let bFirstInitial = bCreatorRow.firstInitial || "";
if (aLastName === bLastName && aFirstInitial === bFirstInitial) { if (aLastName === bLastName && aFirstInitial === bFirstInitial) {
return 1; return 1;

View file

@ -25,6 +25,8 @@
Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm"); Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
import FilePicker from 'zotero/modules/filePicker';
// Note: TinyMCE is automatically doing some meaningless corrections to // Note: TinyMCE is automatically doing some meaningless corrections to
// note-editor produced HTML. Which might result to more // note-editor produced HTML. Which might result to more
// conflicts, especially in group libraries // conflicts, especially in group libraries
@ -43,9 +45,6 @@ const DOWNLOADED_IMAGE_TYPE = [
'image/png' 'image/png'
]; ];
// Schema version here has to be the same as in note-editor!
const SCHEMA_VERSION = 8;
class EditorInstance { class EditorInstance {
constructor() { constructor() {
this.instanceID = Zotero.Utilities.randomString(); this.instanceID = Zotero.Utilities.randomString();
@ -89,6 +88,76 @@ class EditorInstance {
return doc.execCommand(command, ui, value); return doc.execCommand(command, ui, value);
}; };
// Translate note HTML into Markdown, for setting it as text/plain in clipboard (on text copy/drag)
this._iframeWindow.wrappedJSObject.zoteroTranslateToMarkdown = (html) => {
let item = new Zotero.Item('note');
item.libraryID = this._item.libraryID;
item.setNote(html);
let text = '';
var translation = new Zotero.Translate.Export;
translation.noWait = true;
translation.setItems([item]);
translation.setTranslator(Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN);
translation.setHandler("done", (obj, worked) => {
if (worked) {
text = obj.string.replace(/\r\n/g, '\n');
}
});
translation.translate();
return text;
};
this._iframeWindow.wrappedJSObject.zoteroCopyImage = async (dataURL) => {
let parts = dataURL.split(',');
if (!parts[0].includes('base64')) {
return;
}
let mime = parts[0].match(/:(.*?);/)[1];
let bstr = atob(parts[1]);
let n = bstr.length;
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let imgTools = Components.classes["@mozilla.org/image/tools;1"]
.getService(Components.interfaces.imgITools);
let transferable = Components.classes['@mozilla.org/widget/transferable;1']
.createInstance(Components.interfaces.nsITransferable);
let clipboardService = Components.classes['@mozilla.org/widget/clipboard;1']
.getService(Components.interfaces.nsIClipboard);
let imgPtr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
.createInstance(Components.interfaces.nsISupportsInterfacePointer);
imgPtr.data = imgTools.decodeImageFromArrayBuffer(u8arr.buffer, mime);
transferable.init(null);
transferable.addDataFlavor(mime);
transferable.setTransferData(mime, imgPtr, 0);
clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
};
this._iframeWindow.wrappedJSObject.zoteroSaveImageAs = async (dataURL) => {
let parts = dataURL.split(',');
if (!parts[0].includes('base64')) {
return;
}
let mime = parts[0].match(/:(.*?);/)[1];
let bstr = atob(parts[1]);
let n = bstr.length;
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let ext = Zotero.MIME.getPrimaryExtension(mime, '');
let fp = new FilePicker();
fp.init(this._iframeWindow, Zotero.getString('noteEditor.saveImageAs'), fp.modeSave);
fp.appendFilters(fp.filterImages);
fp.defaultString = Zotero.getString('fileTypes.image').toLowerCase() + '.' + ext;
let rv = await fp.show();
if (rv === fp.returnOK || rv === fp.returnReplace) {
let outputPath = fp.file;
await OS.File.writeAtomic(outputPath, u8arr);
}
};
this._iframeWindow.addEventListener('message', this._messageHandler); this._iframeWindow.addEventListener('message', this._messageHandler);
this._iframeWindow.addEventListener('error', (event) => { this._iframeWindow.addEventListener('error', (event) => {
Zotero.logError(event.error); Zotero.logError(event.error);
@ -697,6 +766,9 @@ class EditorInstance {
} }
menuitem.setAttribute('checked', item.checked); menuitem.setAttribute('checked', item.checked);
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
if (item.name === 'insertImage') {
return this._iframeWindow.eval('openImageFilePicker()');
}
this._postMessage({ this._postMessage({
action: 'contextMenuAction', action: 'contextMenuAction',
ctxAction: item.name, ctxAction: item.name,
@ -1193,10 +1265,12 @@ class EditorInstance {
* Create note from annotations * Create note from annotations
* *
* @param {Zotero.Item[]} annotations * @param {Zotero.Item[]} annotations
* @param {Integer} parentID Creates standalone note if not provided * @param {Object} options
* @param {Integer} options.parentID - Creates standalone note if not provided
* @param {Integer} options.collectionID - Only valid if parentID not provided
* @returns {Promise<Zotero.Item>} * @returns {Promise<Zotero.Item>}
*/ */
static async createNoteFromAnnotations(annotations, parentID) { static async createNoteFromAnnotations(annotations, { parentID, collectionID } = {}) {
if (!annotations.length) { if (!annotations.length) {
throw new Error("No annotations provided"); throw new Error("No annotations provided");
} }
@ -1217,7 +1291,12 @@ class EditorInstance {
let note = new Zotero.Item('note'); let note = new Zotero.Item('note');
note.libraryID = annotations[0].libraryID; note.libraryID = annotations[0].libraryID;
note.parentID = parentID; if (parentID) {
note.parentID = parentID;
}
else if (collectionID) {
note.addToCollection(collectionID);
}
await note.saveTx(); await note.saveTx();
let editorInstance = new EditorInstance(); let editorInstance = new EditorInstance();
editorInstance._item = note; editorInstance._item = note;
@ -1239,10 +1318,61 @@ class EditorInstance {
html += '\n'; html += '\n';
await editorInstance.importImages(jsonAnnotations); await editorInstance.importImages(jsonAnnotations);
let { html: serializedHTML, citationItems } = Zotero.EditorInstanceUtilities.serializeAnnotations(jsonAnnotations, true);
html += serializedHTML; let multipleParentParent = false;
let lastParentParentID;
let lastParentID;
// Group annotations per attachment
let groups = [];
for (let i = 0; i < annotations.length; i++) {
let annotation = annotations[i];
let jsonAnnotation = jsonAnnotations[i];
let parentParentID = annotation.parentItem.parentID;
let parentID = annotation.parentID;
if (groups.length) {
if (parentParentID !== lastParentParentID) {
// Multiple top level regular items detected, allow including their titles
multipleParentParent = true;
}
}
if (!groups.length || parentID !== lastParentID) {
groups.push({
parentTitle: annotation.parentItem.getDisplayTitle(),
parentParentID,
parentParentTitle: annotation.parentItem.parentItem && annotation.parentItem.parentItem.getDisplayTitle(),
jsonAnnotations: [jsonAnnotation]
});
}
else {
let group = groups[groups.length - 1];
group.jsonAnnotations.push(jsonAnnotation);
}
lastParentParentID = parentParentID;
lastParentID = parentID;
}
let citationItems = [];
lastParentParentID = null;
for (let group of groups) {
if (multipleParentParent && group.parentParentTitle && lastParentParentID !== group.parentParentID) {
html += `<h2>${group.parentParentTitle}</h2>\n`;
}
lastParentParentID = group.parentParentID;
// If attachment doesn't have a parent or there are more attachments with the same parent, show attachment title
if (!group.parentParentID || groups.filter(x => x.parentParentID === group.parentParentID).length > 1) {
html += `<h3>${group.parentTitle}</h3>\n`;
}
let { html: _html, citationItems: _citationItems } = Zotero.EditorInstanceUtilities.serializeAnnotations(group.jsonAnnotations, true);
html += _html + '\n';
for (let _citationItem of _citationItems) {
if (!citationItems.find(item => item.uris.some(uri => _citationItem.uris.includes(uri)))) {
citationItems.push(_citationItem);
}
}
}
citationItems = encodeURIComponent(JSON.stringify(citationItems)); citationItems = encodeURIComponent(JSON.stringify(citationItems));
html = `<div data-citation-items="${citationItems}" data-schema-version="${SCHEMA_VERSION}">${html}</div>`; // Note: Update schema version only if using new features
let schemaVersion = 8;
html = `<div data-citation-items="${citationItems}" data-schema-version="${schemaVersion}">${html}</div>`;
note.setNote(html); note.setNote(html);
await note.saveTx(); await note.saveTx();
return note; return note;
@ -1268,7 +1398,8 @@ class EditorInstanceUtilities {
if (!annotation.text if (!annotation.text
&& !annotation.comment && !annotation.comment
&& !annotation.imageAttachmentKey) { && !annotation.imageAttachmentKey
|| annotation.type === 'ink') {
continue; continue;
} }
@ -1359,16 +1490,24 @@ class EditorInstanceUtilities {
template = '<p>{{image}}<br/>{{citation}} {{comment}}</p>'; template = '<p>{{image}}<br/>{{citation}} {{comment}}</p>';
} }
Zotero.debug('Using note template:');
Zotero.debug(template);
template = template.replace(
/(<blockquote>[^<>]*?)({{highlight}})([\s\S]*?<\/blockquote>)/g,
(match, p1, p2, p3) => p1 + "{{highlight quotes='false'}}" + p3
);
let vars = { let vars = {
color: annotation.color || '', color: annotation.color || '',
highlight: (attrs) => attrs.quotes === 'true' ? quotedHighlightHTML : highlightHTML, // Include quotation marks by default, but allow to disable with `quotes='false'`
highlight: (attrs) => attrs.quotes === 'false' ? highlightHTML : quotedHighlightHTML,
comment: commentHTML, comment: commentHTML,
citation: citationHTML, citation: citationHTML,
image: imageHTML, image: imageHTML,
tags: (attrs) => (annotation.tags && annotation.tags.map(tag => tag.name) || []).join(attrs.join || ' ') tags: (attrs) => (annotation.tags && annotation.tags.map(tag => tag.name) || []).join(attrs.join || ' ')
}; };
Zotero.debug('Using note template:');
Zotero.debug(template);
let templateHTML = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars); let templateHTML = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
// Remove some spaces at the end of paragraph // Remove some spaces at the end of paragraph
templateHTML = templateHTML.replace(/([\s]*)(<\/p)/g, '$2'); templateHTML = templateHTML.replace(/([\s]*)(<\/p)/g, '$2');
@ -1529,5 +1668,4 @@ class EditorInstanceUtilities {
} }
Zotero.EditorInstance = EditorInstance; Zotero.EditorInstance = EditorInstance;
Zotero.EditorInstance.SCHEMA_VERSION = SCHEMA_VERSION;
Zotero.EditorInstanceUtilities = new EditorInstanceUtilities(); Zotero.EditorInstanceUtilities = new EditorInstanceUtilities();

View file

@ -27,7 +27,7 @@
* Functions for reading files * Functions for reading files
* @namespace * @namespace
*/ */
Zotero.File = new function(){ Zotero.File = new function () {
Components.utils.import("resource://gre/modules/NetUtil.jsm"); Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm"); Components.utils.import("resource://gre/modules/FileUtils.jsm");
@ -1086,7 +1086,31 @@ Zotero.File = new function(){
} }
throw e; throw e;
} }
} };
/**
* Normalize to a Unix-style path, replacing backslashes (interpreted as
* separators only on Windows) with forward slashes (interpreted as
* separators everywhere)
*
* @param {String} path
* @return {String}
*/
this.normalizeToUnix = function (path) {
// If we're on Windows, we need to normalize first and then replace
// the slashes, because OS.Path.normalize won't handle forward slashes
// correctly. Otherwise, we replace slashes first and *then* normalize.
// This should ensure consistent behavior across platforms.
if (Zotero.isWin) {
let normalized = OS.Path.normalize(path);
return normalized.replace(/\\/g, '/');
}
else {
let replaced = path.replace(/\\/g, '/');
return OS.Path.normalize(replaced);
}
};
/** /**
@ -1096,8 +1120,8 @@ Zotero.File = new function(){
if (typeof dir != 'string') throw new Error("dir must be a string"); if (typeof dir != 'string') throw new Error("dir must be a string");
if (typeof file != 'string') throw new Error("file must be a string"); if (typeof file != 'string') throw new Error("file must be a string");
dir = OS.Path.normalize(dir).replace(/\\/g, "/"); dir = this.normalizeToUnix(dir);
file = OS.Path.normalize(file).replace(/\\/g, "/"); file = this.normalizeToUnix(file);
// Normalize D:\ vs. D:\foo // Normalize D:\ vs. D:\foo
if (dir != file && !dir.endsWith('/')) { if (dir != file && !dir.endsWith('/')) {
dir += '/'; dir += '/';

View file

@ -23,7 +23,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.Fulltext = Zotero.FullText = new function(){ Zotero.Fulltext = Zotero.FullText = new function () {
this.isCachedMIMEType = isCachedMIMEType; this.isCachedMIMEType = isCachedMIMEType;
this.__defineGetter__("pdfConverterCacheFile", function () { return '.zotero-ft-cache'; }); this.__defineGetter__("pdfConverterCacheFile", function () { return '.zotero-ft-cache'; });
@ -381,8 +381,8 @@ Zotero.Fulltext = Zotero.FullText = new function(){
} }
if (!charset) { if (!charset) {
Zotero.logError(`Item ${itemID} didn't have a charset`); Zotero.debug(`Item ${itemID} doesn't have a charset set -- using 'utf-8'`);
return false; charset = 'utf-8';
} }
var maxLength = Zotero.Prefs.get('fulltext.textMaxLength'); var maxLength = Zotero.Prefs.get('fulltext.textMaxLength');

View file

@ -166,46 +166,48 @@ Zotero.HTTP = new function() {
if (e.xmlhttp.status == 503 && await _checkRetry(e.xmlhttp)) { if (e.xmlhttp.status == 503 && await _checkRetry(e.xmlhttp)) {
continue; continue;
} }
// Don't retry if errorDelayMax is 0
if (options.errorDelayMax === 0) {
throw e;
}
// Automatically retry other 5xx errors by default // Automatically retry other 5xx errors by default
if (options.errorDelayMax !== 0) { if (!errorDelayGenerator) {
if (!errorDelayGenerator) { // Keep trying for up to an hour
// Keep trying for up to an hour errorDelayGenerator = Zotero.Utilities.Internal.delayGenerator(
errorDelayGenerator = Zotero.Utilities.Internal.delayGenerator( options.errorDelayIntervals || _errorDelayIntervals,
options.errorDelayIntervals || _errorDelayIntervals, options.errorDelayMax !== undefined
options.errorDelayMax !== undefined ? options.errorDelayMax
? options.errorDelayMax : _errorDelayMax
: _errorDelayMax );
); }
let delayPromise = errorDelayGenerator.next().value;
let keepGoing;
// Provide caller with a callback to cancel while waiting to retry
if (options.cancellerReceiver) {
let resolve;
let reject;
let cancelPromise = new Zotero.Promise((res, rej) => {
resolve = res;
reject = function () {
rej(new Zotero.HTTP.CancelledException);
};
});
options.cancellerReceiver(reject);
try {
keepGoing = await Promise.race([delayPromise, cancelPromise]);
} }
let delayPromise = errorDelayGenerator.next().value; catch (e) {
let keepGoing; Zotero.debug("Request cancelled");
// Provide caller with a callback to cancel while waiting to retry
if (options.cancellerReceiver) {
let resolve;
let reject;
let cancelPromise = new Zotero.Promise((res, rej) => {
resolve = res;
reject = function () {
rej(new Zotero.HTTP.CancelledException);
};
});
options.cancellerReceiver(reject);
try {
keepGoing = await Promise.race([delayPromise, cancelPromise]);
}
catch (e) {
Zotero.debug("Request cancelled");
throw e;
}
resolve();
}
else {
keepGoing = await delayPromise;
}
if (!keepGoing) {
Zotero.logError("Failed too many times");
throw e; throw e;
} }
resolve();
}
else {
keepGoing = await delayPromise;
}
if (!keepGoing) {
Zotero.logError("Failed too many times");
throw e;
} }
continue; continue;
} }
@ -1217,9 +1219,10 @@ Zotero.HTTP = new function() {
* @param {Boolean} dontDelete Don't delete the hidden browser upon completion; calling function * @param {Boolean} dontDelete Don't delete the hidden browser upon completion; calling function
* must call deleteHiddenBrowser itself. * must call deleteHiddenBrowser itself.
* @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object * @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
* @param {Object} [docShellPrefs] See Zotero.Browser.createHiddenBrowser
* @return {browser} Hidden browser used for loading * @return {browser} Hidden browser used for loading
*/ */
this.loadDocuments = function (urls, processor, onDone, onError, dontDelete, cookieSandbox) { this.loadDocuments = function (urls, processor, onDone, onError, dontDelete, cookieSandbox, docShellPrefs={}) {
// (Approximately) how many seconds to wait if the document is left in the loading state and // (Approximately) how many seconds to wait if the document is left in the loading state and
// pageshow is called before we call pageshow with an incomplete document // pageshow is called before we call pageshow with an incomplete document
const LOADING_STATE_TIMEOUT = 120; const LOADING_STATE_TIMEOUT = 120;
@ -1342,6 +1345,9 @@ Zotero.HTTP = new function() {
currentURL = 0; currentURL = 0;
for(var i=0; i<urls.length; i++) { for(var i=0; i<urls.length; i++) {
let hiddenBrowser = Zotero.Browser.createHiddenBrowser(); let hiddenBrowser = Zotero.Browser.createHiddenBrowser();
for (let pref in docShellPrefs) {
hiddenBrowser.docShell[pref] = docShellPrefs[pref];
}
if (cookieSandbox) { if (cookieSandbox) {
cookieSandbox.attachToBrowser(hiddenBrowser); cookieSandbox.attachToBrowser(hiddenBrowser);
} }

View file

@ -63,7 +63,13 @@ const EXPORTED_DOCUMENT_MARKER = "ZOTERO_TRANSFER_DOCUMENT";
const NOTE_CITATION_PLACEHOLDER_LINK = 'https://www.zotero.org/?'; const NOTE_CITATION_PLACEHOLDER_LINK = 'https://www.zotero.org/?';
const TEMPLATE_VERSION = 1; const TEMPLATE_VERSIONS = {
MacWord16: 2,
WinWord: 1,
OpenOffice: 1
};
const MENDELEY_URI_RE = /^http:\/\/www\.mendeley\.com\/documents\/\?uuid=(.*)/;
Zotero.Integration = new function() { Zotero.Integration = new function() {
@ -104,7 +110,6 @@ Zotero.Integration = new function() {
this.deletePipe = function(pipe) { this.deletePipe = function(pipe) {
try { try {
if(pipe.exists()) { if(pipe.exists()) {
Zotero.IPC.safePipeWrite(pipe, "Zotero shutdown\n");
pipe.remove(false); pipe.remove(false);
} }
return true; return true;
@ -140,8 +145,8 @@ Zotero.Integration = new function() {
* @returns {Boolean} true if integration operation should be cancelled * @returns {Boolean} true if integration operation should be cancelled
*/ */
this.warnOutdatedTemplate = function (agent, templateVersion) { this.warnOutdatedTemplate = function (agent, templateVersion) {
const validAgents = new Set(['OpenOffice', 'MacWord2016', 'MacWord16', 'WinWord']); const expectedTemplateVersion = TEMPLATE_VERSIONS[agent];
if (!validAgents.has(agent) || templateVersion >= TEMPLATE_VERSION) return false; if (typeof expectedTemplateVersion == 'undefined' || templateVersion >= expectedTemplateVersion) return false;
const daysToIgnore = 30; const daysToIgnore = 30;
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const updateTemplateDelayedOn = Zotero.Prefs.get('integration.updateTemplateDelayedOn'); const updateTemplateDelayedOn = Zotero.Prefs.get('integration.updateTemplateDelayedOn');
@ -505,6 +510,7 @@ Zotero.Integration = new function() {
session._removeCodeFields = {}; session._removeCodeFields = {};
session._deleteFields = {}; session._deleteFields = {};
session._bibliographyFields = []; session._bibliographyFields = [];
session._shouldMerge = false;
if (dataString == EXPORTED_DOCUMENT_MARKER) { if (dataString == EXPORTED_DOCUMENT_MARKER) {
Zotero.Integration.currentSession = session; Zotero.Integration.currentSession = session;
@ -798,7 +804,8 @@ Zotero.Integration.Interface.prototype.addEditBibliography = Zotero.Promise.coro
* @return {Promise} * @return {Promise}
*/ */
Zotero.Integration.Interface.prototype.refresh = async function() { Zotero.Integration.Interface.prototype.refresh = async function() {
await this._session.init(true, false) await this._session.init(true, false);
this._session._shouldMerge = true;
this._session.reload = this._session.reload || this._session.data.prefs.delayCitationUpdates; this._session.reload = this._session.reload || this._session.data.prefs.delayCitationUpdates;
await this._session.updateFromDocument(FORCE_CITATIONS_REGENERATE); await this._session.updateFromDocument(FORCE_CITATIONS_REGENERATE);
@ -1037,6 +1044,10 @@ Zotero.Integration.Session.prototype.updateFromDocument = Zotero.Promise.corouti
this.progressBar.start(); this.progressBar.start();
if (forceCitations) { if (forceCitations) {
this.regenAll = true; this.regenAll = true;
// See Session.restoreProcessorState() for a comment
if (!Zotero.Prefs.get('cite.useCiteprocRs')) {
this.reload = true;
}
} }
yield this._processFields(); yield this._processFields();
try { try {
@ -1067,15 +1078,24 @@ Zotero.Integration.Session.prototype._processFields = async function () {
if (!this._fields) { if (!this._fields) {
throw new Error("_processFields called without fetching fields first"); throw new Error("_processFields called without fetching fields first");
} }
let adjacentCitations = [];
for (var i = 0; i < this._fields.length; i++) { for (var i = 0; i < this._fields.length; i++) {
let field = await Zotero.Integration.Field.loadExisting(this._fields[i]); let field = await Zotero.Integration.Field.loadExisting(this._fields[i]);
if (field.type === INTEGRATION_TYPE_ITEM) { if (field.type === INTEGRATION_TYPE_ITEM) {
var noteIndex = await field.getNoteIndex(), var noteIndex = await field.getNoteIndex(),
data = await field.unserialize(), data = await field.unserialize(),
citation = new Zotero.Integration.Citation(field, data, noteIndex); citation = new Zotero.Integration.Citation(field, data, noteIndex);
await this.addCitation(i, noteIndex, citation); if (this._shouldMerge && typeof field.isAdjacentToNextField === 'function' && await field.isAdjacentToNextField()) {
adjacentCitations.push(citation);
this._deleteFields[i] = true;
continue;
}
await this.addCitation(i, noteIndex, citation, adjacentCitations);
if (adjacentCitations.length) {
adjacentCitations = [];
}
} else if (field.type === INTEGRATION_TYPE_BIBLIOGRAPHY) { } else if (field.type === INTEGRATION_TYPE_BIBLIOGRAPHY) {
if (this.ignoreEmptyBibliography && (await field.getText()).trim() === "") { if (this.ignoreEmptyBibliography && (await field.getText()).trim() === "") {
this._removeCodeFields[i] = true; this._removeCodeFields[i] = true;
@ -1957,9 +1977,17 @@ Zotero.Integration.Session.prototype.importDocument = async function() {
/** /**
* Adds a citation to the arrays representing the document * Adds a citation to the arrays representing the document
*/ */
Zotero.Integration.Session.prototype.addCitation = async function (index, noteIndex, citation) { Zotero.Integration.Session.prototype.addCitation = async function (index, noteIndex, citation, adjacentCitations=[]) {
index = parseInt(index, 10); index = parseInt(index, 10);
if (adjacentCitations.length) {
Zotero.debug(`Merging adjacent citations ${adjacentCitations.map(c => c.citationID)} to citation ${citation.citationID}`);
for (let adjacentCitation of adjacentCitations) {
citation.mergeCitation(adjacentCitation);
}
this.updateIndices[index] = true;
}
var action = await citation.loadItemData(); var action = await citation.loadItemData();
if (action == Zotero.Integration.REMOVE_CODE) { if (action == Zotero.Integration.REMOVE_CODE) {
@ -2157,6 +2185,12 @@ Zotero.Integration.Session.prototype.restoreProcessorState = function() {
citations.push(this.citationsByIndex[i]); citations.push(this.citationsByIndex[i]);
} }
} }
if (!Zotero.Prefs.get('cite.useCiteprocRs')) {
// Due to a bug in citeproc-js there are disambiguation issues after changing items in Zotero library
// and rebuilding the processor state, so we reinitialize the processor altogether
let style = Zotero.Styles.get(this.data.style.styleID);
this.style = style.getCiteProc(this.data.style.locale, this.outputFormat, this.data.prefs.automaticJournalAbbreviations);
}
this.style.rebuildProcessorState(citations, this.outputFormat, uncited); this.style.rebuildProcessorState(citations, this.outputFormat, uncited);
} }
@ -2564,7 +2598,7 @@ Zotero.Integration.URIMap.prototype.getURIsForItemID = function(id) {
/** /**
* Gets Zotero item for a given set of URIs * Gets Zotero item for a given set of URIs
*/ */
Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = Zotero.Promise.coroutine(function* (uris) { Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = async function (uris) {
var zoteroItem = false; var zoteroItem = false;
var needUpdate = false; var needUpdate = false;
var embeddedItem = false;; var embeddedItem = false;;
@ -2579,43 +2613,94 @@ Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = Zotero.Promise.corout
// Next try getting URI directly // Next try getting URI directly
try { try {
zoteroItem = yield Zotero.URI.getURIItem(uri); var replacer = await Zotero.URI.getURIItem(uri);
if(zoteroItem) { if (replacer && !replacer.deleted) {
// Ignore items in the trash zoteroItem = replacer;
if(zoteroItem.deleted) { break;
zoteroItem = false;
} else {
break;
}
} }
} catch(e) {} } catch(e) {}
// Try merged item mapping // Try merged item mapping
var replacer = yield Zotero.Relations.getByPredicateAndObject( var replacer = await Zotero.Relations.getByPredicateAndObject(
'item', Zotero.Relations.replacedItemPredicate, uri 'item', Zotero.Relations.replacedItemPredicate, uri
); );
if (replacer.length && !replacer[0].deleted) { if (replacer.length && !replacer[0].deleted) {
zoteroItem = replacer[0]; zoteroItem = replacer[0];
break;
} }
if(zoteroItem) break; // Check if it's a mendeley URI and if we have imported the item
let m = MENDELEY_URI_RE.exec(uri);
if (m) {
replacer = await Zotero.Relations.getByPredicateAndObject(
'item', 'mendeleyDB:documentUUID', m[1]
);
if (replacer.length) {
if (!replacer[0].deleted) {
zoteroItem = replacer[0];
break;
}
}
// If not blocked by user having pressed skip in this session,
// or user having checked the checkbox to not be prompted about this,
// or user having imported their library with the new version of importer
else if (!(this.session.dontPromptForMendeley
|| Zotero.Prefs.get('integration.dontPromptMendeleyImport')
|| await Zotero.DB.valueQueryAsync("SELECT value FROM settings WHERE setting='mendeleyImport' AND key='version'")
)) {
// Prompt user to (re)import their mendeley database which might make us recognize
// these items
let checkbox = {};
let result = Zotero.Prompt.confirm({
title: Zotero.getString('integration.mendeleyImport.title'),
text: Zotero.getString('integration.mendeleyImport.description', [Zotero.appName]),
button0: Zotero.getString('integration.mendeleyImport.openImporter'),
button1: Zotero.getString('general.skip'),
button2: Zotero.getString('general.moreInformation'),
checkLabel: Zotero.getString('general.dontAskAgain'),
checkbox
});
if (result === 0) {
setTimeout(
() => Zotero.getMainWindow().Zotero_File_Interface.showImportWizard(
{
pageID: 'mendeley-online-explanation',
relinkOnly: true
}
)
);
throw new Zotero.Exception.UserCancelled("Importing mendeley citations");
}
else if (result == 1) {
this.session.dontPromptForMendeley = true;
}
else {
Zotero.launchURL("https://www.zotero.org/support/kb/mendeley_import#using_mendeley_citations");
}
if (checkbox.value) {
Zotero.Prefs.set('integration.dontPromptMendeleyImport', true);
}
}
};
} }
if(zoteroItem) { if (zoteroItem) {
// make sure URI is up to date (in case user just began syncing) // make sure URI is up to date (in case user just began syncing)
var newURI = Zotero.URI.getItemURI(zoteroItem); var newURI = Zotero.URI.getItemURI(zoteroItem);
if(newURI != uris[i]) { if (!uris.includes(newURI)) {
uris[i] = newURI; uris.push(newURI);
needUpdate = true; needUpdate = true;
} }
// cache uris // cache uris
this.itemIDURIs[zoteroItem.id] = uris; this.add(zoteroItem.id, uris)
} else if(embeddedItem) { }
else if (embeddedItem) {
return [embeddedItem, false]; return [embeddedItem, false];
} }
return [zoteroItem, needUpdate]; return [zoteroItem, needUpdate];
}); };
Zotero.Integration.Field = class { Zotero.Integration.Field = class {
constructor(field, rawCode) { constructor(field, rawCode) {
@ -2623,7 +2708,7 @@ Zotero.Integration.Field = class {
throw new Error("Trying to instantiate Integration.Field with Integration.Field, not doc field"); throw new Error("Trying to instantiate Integration.Field with Integration.Field, not doc field");
} }
for (let func of Zotero.Integration.Field.INTERFACE) { for (let func of Zotero.Integration.Field.INTERFACE) {
if (!(func in this)) { if (!(func in this) && (func in field)) {
this[func] = field[func].bind(field); this[func] = field[func].bind(field);
} }
} }
@ -2682,7 +2767,7 @@ Zotero.Integration.Field = class {
} }
}; };
Zotero.Integration.Field.INTERFACE = ['delete', 'removeCode', 'select', 'setText', Zotero.Integration.Field.INTERFACE = ['delete', 'removeCode', 'select', 'setText',
'getText', 'setCode', 'getCode', 'equals', 'getNoteIndex']; 'getText', 'setCode', 'getCode', 'equals', 'getNoteIndex', 'isAdjacentToNextField'];
/** /**
* Load existing field in document and return correct instance of field type * Load existing field in document and return correct instance of field type
@ -2794,8 +2879,13 @@ Zotero.Integration.CitationField = class extends Zotero.Integration.Field {
} }
// for update from Zotero 2.1 or earlier // for update from Zotero 2.1 or earlier
if(citationItem.uri) { if (citationItem.uri) {
citationItem.uris = citationItem.uri; if (Array.isArray(citationItem.uris)) {
citationItem.uris.push(citationItem.uri);
}
else {
citationItem.uris = [citationItem.uri];
}
delete citationItem.uri; delete citationItem.uri;
} }
} }
@ -2862,7 +2952,7 @@ Zotero.Integration.CitationField = class extends Zotero.Integration.Field {
Zotero.Integration.currentDoc.activate(); Zotero.Integration.currentDoc.activate();
var result = await Zotero.Integration.currentSession.displayAlert(msg, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO_CANCEL); var result = await Zotero.Integration.currentSession.displayAlert(msg, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO_CANCEL);
if (result == 0) { // Cancel if (result == 0) { // Cancel
return new Zotero.Exception.UserCancelled("corrupt citation resolution"); throw new Zotero.Exception.UserCancelled("corrupt citation resolution");
} else if (result == 1) { // No } else if (result == 1) { // No
return false; return false;
} else { // Yes } else { // Yes
@ -2921,6 +3011,23 @@ Zotero.Integration.Citation = class {
this._field = citationField; this._field = citationField;
} }
/**
* Merge citation items and remove duplicates, unless the items have different
* @param citation {Citation}
*/
mergeCitation(citation) {
let items = this.citationItems.concat(citation.citationItems);
let addedItems = new Set();
this.citationItems = []
for (let item of items) {
if (addedItems.has(item.id)) {
continue;
}
addedItems.add(item.id);
this.citationItems.push(item);
}
}
/** /**
* Load citation item data * Load citation item data
* @param {Boolean} [promptToReselect=true] - will throw a MissingItemException if false * @param {Boolean} [promptToReselect=true] - will throw a MissingItemException if false

View file

@ -55,7 +55,8 @@ Zotero.Intl = new function () {
Components.utils.import("resource://gre/modules/PluralForm.jsm"); Components.utils.import("resource://gre/modules/PluralForm.jsm");
bundle = Services.strings.createBundle('chrome://zotero/locale/zotero.properties'); // Exposed for tests
this._bundle = bundle = Services.strings.createBundle('chrome://zotero/locale/zotero.properties');
intlProps = Services.strings.createBundle('chrome://zotero/locale/mozilla/intl.properties'); intlProps = Services.strings.createBundle('chrome://zotero/locale/mozilla/intl.properties');
[pluralFormGet, pluralFormNumForms] = PluralForm.makeGetter(parseInt(getIntlProp('pluralRule', 1))); [pluralFormGet, pluralFormNumForms] = PluralForm.makeGetter(parseInt(getIntlProp('pluralRule', 1)));
@ -105,6 +106,9 @@ Zotero.Intl = new function () {
} }
l10n = bundle.formatStringFromName(name, params, params.length); l10n = bundle.formatStringFromName(name, params, params.length);
} }
else if (this.strings[name]) {
return this.strings[name];
}
else { else {
l10n = bundle.GetStringFromName(name); l10n = bundle.GetStringFromName(name);
} }

View file

@ -27,79 +27,6 @@ Zotero.IPC = new function() {
var _libc, _libcPath, _instancePipe, _user32, open, write, close; var _libc, _libcPath, _instancePipe, _user32, open, write, close;
/** /**
* Initialize pipe for communication with connector
*/
this.init = function() {
if(!Zotero.isWin) { // no pipe support on Fx 3.6
_instancePipe = _getPipeDirectory();
if(!_instancePipe.exists()) {
_instancePipe.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
}
_instancePipe.append(Zotero.instanceID);
Zotero.IPC.Pipe.initPipeListener(_instancePipe, this.parsePipeInput);
}
}
/**
* Parses input received via instance pipe
*/
this.parsePipeInput = function(msgs) {
for (let msg of msgs.split("\n")) {
if(!msg) continue;
Zotero.debug('IPC: Received "'+msg+'"');
/*
* The below messages coordinate switching Zotero for Firefox from extension mode to
* connector mode without restarting after Zotero Standalone has been launched. The
* dance typically proceeds as follows:
*
* 1. SA sends a releaseLock message to Z4Fx that tells it to release its lock.
* 2. Z4Fx releases its lock and sends a lockReleased message to SA.
* 3. Z4Fx restarts in connector mode. Once it's ready for an IPC command, it sends
* a checkInitComplete message to SA.
* 4. Once SA finishes initializing, or immediately after a checkInitComplete message
* has been received if it is already initialized, SA sends an initComplete message
* to Z4Fx.
*/
if(msg.substr(0, 11) === "releaseLock") {
// Standalone sends this to the Firefox extension to tell the Firefox extension to
// release its lock on the Zotero database
if(!Zotero.isConnector && (msg.length === 11 ||
msg.substr(12) === Zotero.DataDirectory.getDatabase())) {
switchConnectorMode(true);
}
} else if(msg === "lockReleased") {
// The Firefox extension sends this to Standalone to let Standalone know that it has
// released its lock
Zotero.onDBLockReleased();
} else if(msg === "checkInitComplete") {
// The Firefox extension sends this to Standalone to tell Standalone to send an
// initComplete message when it is fully initialized
if(Zotero.initialized) {
Zotero.IPC.broadcast("initComplete");
} else {
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
var _loadObserver = function() {
Zotero.IPC.broadcast("initComplete");
observerService.removeObserver(_loadObserver, "zotero-loaded");
};
observerService.addObserver(_loadObserver, "zotero-loaded", false);
}
} else if(msg === "initComplete") {
// Standalone sends this to the Firefox extension to let the Firefox extension
// know that Standalone has fully initialized and it should pull the list of
// translators
Zotero.initComplete();
}
else if (msg == "reinit") {
if (Zotero.isConnector) {
reinit(false, true);
}
}
}
}
/** /**
* Writes safely to a file, avoiding blocking. * Writes safely to a file, avoiding blocking.
@ -135,165 +62,6 @@ Zotero.IPC = new function() {
return true; return true;
} }
/**
* Broadcast a message to all other Zotero instances
*/
this.broadcast = function(msg) {
if(Zotero.isWin) { // communicate via WM_COPYDATA method
Components.utils.import("resource://gre/modules/ctypes.jsm");
// communicate via message window
var user32 = ctypes.open("user32.dll");
/* http://msdn.microsoft.com/en-us/library/ms633499%28v=vs.85%29.aspx
* HWND WINAPI FindWindow(
* __in_opt LPCTSTR lpClassName,
* __in_opt LPCTSTR lpWindowName
* );
*/
var FindWindow = user32.declare("FindWindowW", ctypes.winapi_abi, ctypes.int32_t,
ctypes.jschar.ptr, ctypes.jschar.ptr);
/* http://msdn.microsoft.com/en-us/library/ms633539%28v=vs.85%29.aspx
* BOOL WINAPI SetForegroundWindow(
* __in HWND hWnd
* );
*/
var SetForegroundWindow = user32.declare("SetForegroundWindow", ctypes.winapi_abi,
ctypes.bool, ctypes.int32_t);
/*
* LRESULT WINAPI SendMessage(
* __in HWND hWnd,
* __in UINT Msg,
* __in WPARAM wParam,
* __in LPARAM lParam
* );
*/
var SendMessage = user32.declare("SendMessageW", ctypes.winapi_abi, ctypes.uintptr_t,
ctypes.int32_t, ctypes.unsigned_int, ctypes.voidptr_t, ctypes.voidptr_t);
/* http://msdn.microsoft.com/en-us/library/ms649010%28v=vs.85%29.aspx
* typedef struct tagCOPYDATASTRUCT {
* ULONG_PTR dwData;
* DWORD cbData;
* PVOID lpData;
* } COPYDATASTRUCT, *PCOPYDATASTRUCT;
*/
var COPYDATASTRUCT = ctypes.StructType("COPYDATASTRUCT", [
{"dwData":ctypes.voidptr_t},
{"cbData":ctypes.uint32_t},
{"lpData":ctypes.voidptr_t}
]);
// Aurora/Nightly are always named "Firefox" in
// application.ini
const appNames = ["Firefox", "Zotero"];
// Different from Zotero.appName; this corresponds to the
// name in application.ini
const myAppName = Services.appinfo.name;
for (let appName of appNames) {
// don't send messages to ourself
if(appName === myAppName) continue;
var thWnd = FindWindow(appName+"MessageWindow", null);
if(thWnd) {
Zotero.debug('IPC: Broadcasting "'+msg+'" to window "'+appName+'MessageWindow"');
// allocate message
var data = ctypes.char.array()('firefox.exe -silent -ZoteroIPC "'+msg.replace('"', '""', "g")+'"\x00C:\\');
var dataSize = data.length*data.constructor.size;
// create new COPYDATASTRUCT
var cds = new COPYDATASTRUCT();
cds.dwData = null;
cds.cbData = dataSize;
cds.lpData = data.address();
// send COPYDATASTRUCT
var success = SendMessage(thWnd, 0x004A /** WM_COPYDATA **/, null, cds.address());
user32.close();
return !!success;
}
}
user32.close();
return false;
} else { // communicate via pipes
// look for other Zotero instances
var pipes = [];
var pipeDir = _getPipeDirectory();
if(pipeDir.exists()) {
var dirEntries = pipeDir.directoryEntries;
while (dirEntries.hasMoreElements()) {
var pipe = dirEntries.getNext().QueryInterface(Ci.nsIFile);
if(pipe.leafName[0] !== "." && (!_instancePipe || !pipe.equals(_instancePipe))) {
pipes.push(pipe);
}
}
}
if(!pipes.length) return false;
var success = false;
for (let pipe of pipes) {
Zotero.debug('IPC: Trying to broadcast "'+msg+'" to instance '+pipe.leafName);
var defunct = false;
if(pipe.isFile()) {
// not actually a pipe
if(pipe.isDirectory()) {
// not a file, so definitely defunct
defunct = true;
} else {
// check to see whether the size exceeds a certain threshold that we find
// reasonable for the queue, and if not, delete the pipe, because it's
// probably just a file that wasn't deleted on shutdown and is now
// accumulating vast amounts of data
defunct = pipe.fileSize > 1024;
}
}
if(!defunct) {
// Try to write to the pipe for 100 ms
var time = Date.now(), timeout = time+100, wroteToPipe;
do {
wroteToPipe = Zotero.IPC.safePipeWrite(pipe, msg+"\n");
} while(Date.now() < timeout && !wroteToPipe);
if (wroteToPipe) Zotero.debug('IPC: Pipe took '+(Date.now()-time)+' ms to become available');
success = success || wroteToPipe;
defunct = !wroteToPipe;
}
if(defunct) {
Zotero.debug('IPC: Removing defunct pipe '+pipe.leafName);
try {
pipe.remove(true);
} catch(e) {};
}
}
return success;
}
}
/**
* Get directory containing Zotero pipes
*/
function _getPipeDirectory() {
var dir = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
dir.append("pipes");
return dir;
}
this.pipeExists = Zotero.Promise.coroutine(function* () {
var dir = _getPipeDirectory().path;
return (yield OS.File.exists(dir)) && !(yield Zotero.File.directoryIsEmpty(dir));
});
/** /**
* Gets the path to libc as a string * Gets the path to libc as a string
*/ */
@ -359,19 +127,8 @@ Zotero.IPC.Pipe = new function() {
this.initPipeListener = function(file, callback) { this.initPipeListener = function(file, callback) {
Zotero.debug("IPC: Initializing pipe at "+file.path); Zotero.debug("IPC: Initializing pipe at "+file.path);
// determine type of pipe
if(!_pipeClass) {
var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
.getService(Components.interfaces.nsIVersionComparator);
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
getService(Components.interfaces.nsIXULAppInfo);
if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) { // Gecko 5
_pipeClass = Zotero.IPC.Pipe.DeferredOpen;
}
}
// make new pipe // make new pipe
new _pipeClass(file, callback); new Zotero.IPC.Pipe.DeferredOpen(file, callback);
} }
/** /**
@ -395,7 +152,7 @@ Zotero.IPC.Pipe = new function() {
* Adds a shutdown listener for a pipe that writes "Zotero shutdown\n" to the pipe and then * Adds a shutdown listener for a pipe that writes "Zotero shutdown\n" to the pipe and then
* deletes it * deletes it
*/ */
this.writeShutdownMessage = function(pipe, file) { this.remove = function(pipe, file) {
// Make sure pipe actually exists // Make sure pipe actually exists
if(!file.exists()) { if(!file.exists()) {
Zotero.debug("IPC: Not closing pipe "+file.path+": already deleted"); Zotero.debug("IPC: Not closing pipe "+file.path+": already deleted");
@ -425,7 +182,7 @@ Zotero.IPC.Pipe.DeferredOpen = function(file, callback) {
this._initPump(); this._initPump();
// add shutdown listener // add shutdown listener
Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, this, file)); Zotero.addShutdownListener(Zotero.IPC.Pipe.remove.bind(null, this, file));
} }
Zotero.IPC.Pipe.DeferredOpen.prototype = { Zotero.IPC.Pipe.DeferredOpen.prototype = {

View file

@ -23,7 +23,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.MIME = new function(){ Zotero.MIME = new function () {
this.isTextType = isTextType; this.isTextType = isTextType;
this.getPrimaryExtension = getPrimaryExtension; this.getPrimaryExtension = getPrimaryExtension;
this.sniffForBinary = sniffForBinary; this.sniffForBinary = sniffForBinary;

View file

@ -55,20 +55,22 @@ Zotero.MIMETypeHandler = new function () {
_observers = []; _observers = [];
// Install styles from the Cite preferences // Install styles from the Cite preferences
this.addHandler("application/vnd.citationstyles.style+xml", async function (blob, origin) { this.addHandlers("application/vnd.citationstyles.style+xml", {
let win = Services.wm.getMostRecentWindow("zotero:basicViewer"); onContent: async function (blob, origin) {
var data = await Zotero.Utilities.Internal.blobToText(blob); let win = Services.wm.getMostRecentWindow("zotero:basicViewer");
try { var data = await Zotero.Utilities.Internal.blobToText(blob);
await Zotero.Styles.install(data, origin, true); try {
} await Zotero.Styles.install(data, origin, true);
catch (e) { }
Zotero.logError(e); catch (e) {
(new Zotero.Exception.Alert("styles.install.unexpectedError", Zotero.logError(e);
origin, "styles.install.title", e)).present(); (new Zotero.Exception.Alert("styles.install.unexpectedError",
} origin, "styles.install.title", e)).present();
// Close styles page in basic viewer after installing a style }
if (win) { // Close styles page in basic viewer after installing a style
win.close(); if (win) {
win.close();
}
} }
}, true); }, true);
}; };
@ -76,17 +78,25 @@ Zotero.MIMETypeHandler = new function () {
/** /**
* Adds a handler to handle a specific MIME type * Adds a handler to handle a specific MIME type
* @param {String} type MIME type to handle * @param {String} type MIME type to handle
* @param {Function} fn Function to call to handle type - fn(string, uri) * @param {Object} handlers
* - handlers.onContent - function to call when content is received
* - handlers.onStartRequest - function to call when response for content type is first received
* @param {Boolean} ignoreContentDisposition If true, ignores the Content-Disposition header, * @param {Boolean} ignoreContentDisposition If true, ignores the Content-Disposition header,
* which is often used to force a file to download rather than let it be handled by the web * which is often used to force a file to download rather than let it be handled by the web
* browser * browser
*/ */
this.addHandler = function (type, fn, ignoreContentDisposition) { this.addHandlers = function (type, handlers, ignoreContentDisposition) {
if (typeof handlers == 'function') {
handlers = {
onContent: handlers
};
Zotero.debug('MIMETypeHandler.addHandler: second parameter function is deprecated. Pass an object', 1)
}
if (_typeHandlers[type]) { if (_typeHandlers[type]) {
_typeHandlers[type].push(fn); _typeHandlers[type].push(handlers);
} }
else { else {
_typeHandlers[type] = [fn]; _typeHandlers[type] = [handlers];
} }
if (ignoreContentDisposition) { if (ignoreContentDisposition) {
_ignoreContentDispositionTypes.add(type); _ignoreContentDispositionTypes.add(type);
@ -96,19 +106,19 @@ Zotero.MIMETypeHandler = new function () {
/** /**
* Removes a handler for a specific MIME type * Removes a handler for a specific MIME type
* @param {String} type MIME type to handle * @param {String} type MIME type to handle
* @param {Function} handler Function handler to remove * @param {Object} handlers Function handlers to remove
*/ */
this.removeHandler = function (type, handler) { this.removeHandlers = function (type, handlers) {
// If no handler specified or this is the last handler for the type // If no handler specified or this is the last handler for the type
// stop monitoring the content type completely. // stop monitoring the content type completely.
if (!handler || _typeHandlers[type] && _typeHandlers[type].length <= 1) { if (!handlers || _typeHandlers[type] && _typeHandlers[type].length <= 1) {
delete _typeHandlers[type]; delete _typeHandlers[type];
_ignoreContentDispositionTypes.delete(type); _ignoreContentDispositionTypes.delete(type);
} }
else if (_typeHandlers[type]) { else if (_typeHandlers[type]) {
var i = _typeHandlers[type].indexOf(handler); var i = _typeHandlers[type].indexOf(handlers);
if (i != -1) { if (i != -1) {
_typeHandlers.splice(i, 1); _typeHandlers[type].splice(i, 1);
} }
} }
}; };
@ -132,8 +142,10 @@ Zotero.MIMETypeHandler = new function () {
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) { if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
channel.QueryInterface(Components.interfaces.nsIHttpChannel); channel.QueryInterface(Components.interfaces.nsIHttpChannel);
try { try {
// Get the main directive of the Content-Type header
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#syntax
var contentType = channel.getResponseHeader("Content-Type").split(';')[0].toLowerCase();
// remove content-disposition headers for EndNote, etc. // remove content-disposition headers for EndNote, etc.
var contentType = channel.getResponseHeader("Content-Type").toLowerCase();
for (let handledType of _ignoreContentDispositionTypes) { for (let handledType of _ignoreContentDispositionTypes) {
if (contentType.startsWith(handledType)) { if (contentType.startsWith(handledType)) {
channel.setResponseHeader("Content-Disposition", "inline", false); channel.setResponseHeader("Content-Disposition", "inline", false);
@ -214,10 +226,27 @@ Zotero.MIMETypeHandler = new function () {
} }
/** /**
* Called when the request is started; we ignore this * Called when the request is started
*/ */
_StreamListener.prototype.onStartRequest = function(channel, context) {} _StreamListener.prototype.onStartRequest = async function(channel, context) {
try {
if (!_typeHandlers[this._contentType]) return;
for (let handlers of _typeHandlers[this._contentType]) {
if (!handlers.onStartRequest) continue;
let maybePromise = handlers.onStartRequest(
this._request.name ? this._request.name : null,
this._contentType,
channel
);
if (maybePromise && maybePromise.then) {
maybePromise = await maybePromise;
}
}
}
catch (e) {
Zotero.logError(e);
}
}
/** /**
* Called when there's data available; we collect this data and keep it until the request is * Called when there's data available; we collect this data and keep it until the request is
@ -259,14 +288,16 @@ Zotero.MIMETypeHandler = new function () {
var handled = false; var handled = false;
try { try {
for (let handler of _typeHandlers[this._contentType]) { if (!_typeHandlers[this._contentType]) return;
let maybePromise = handler( for (let handlers of _typeHandlers[this._contentType]) {
if (!handlers.onContent) continue;
let maybePromise = handlers.onContent(
blob, blob,
this._request.name ? this._request.name : null, this._request.name ? this._request.name : null,
this._contentType, this._contentType,
channel channel
); );
if (maybePromise.then) { if (maybePromise && maybePromise.then) {
maybePromise = await maybePromise; maybePromise = await maybePromise;
} }
handled = handled || maybePromise; handled = handled || maybePromise;

View file

@ -25,7 +25,7 @@
"use strict"; "use strict";
Zotero.Notifier = new function(){ Zotero.Notifier = new function () {
// Options that apply to an entire event, not a specific object // Options that apply to an entire event, not a specific object
this.EVENT_LEVEL_OPTIONS = ['autoSyncDelay', 'skipAutoSync']; this.EVENT_LEVEL_OPTIONS = ['autoSyncDelay', 'skipAutoSync'];

View file

@ -271,6 +271,10 @@ class PDFWorker {
})); }));
let path = await attachment.getFilePathAsync(); let path = await attachment.getFilePathAsync();
let fileSize = (await OS.File.stat(path)).size;
if (fileSize > Math.pow(2, 31) - 1) {
throw new Error(`The file "${path}" is too large`);
}
let buf = await OS.File.read(path, {}); let buf = await OS.File.read(path, {});
buf = new Uint8Array(buf).buffer; buf = new Uint8Array(buf).buffer;
@ -341,6 +345,10 @@ class PDFWorker {
async processCitaviAnnotations(pdfPath, citaviAnnotations, isPriority, password) { async processCitaviAnnotations(pdfPath, citaviAnnotations, isPriority, password) {
return this._enqueue(async () => { return this._enqueue(async () => {
let fileSize = (await OS.File.stat(pdfPath)).size;
if (fileSize > Math.pow(2, 31) - 1) {
throw new Error(`The file "${pdfPath}" is too large`);
}
let buf = await OS.File.read(pdfPath, {}); let buf = await OS.File.read(pdfPath, {});
buf = new Uint8Array(buf).buffer; buf = new Uint8Array(buf).buffer;
try { try {
@ -371,6 +379,10 @@ class PDFWorker {
*/ */
async processMendeleyAnnotations(pdfPath, mendeleyAnnotations, isPriority, password) { async processMendeleyAnnotations(pdfPath, mendeleyAnnotations, isPriority, password) {
return this._enqueue(async () => { return this._enqueue(async () => {
let fileSize = (await OS.File.stat(pdfPath)).size;
if (fileSize > Math.pow(2, 31) - 1) {
throw new Error(`The file "${pdfPath}" is too large`);
}
let buf = await OS.File.read(pdfPath, {}); let buf = await OS.File.read(pdfPath, {});
buf = new Uint8Array(buf).buffer; buf = new Uint8Array(buf).buffer;
try { try {
@ -410,6 +422,162 @@ class PDFWorker {
} }
await Promise.all(promises); await Promise.all(promises);
} }
/**
* Delete pages from PDF attachment
*
* @param {Integer} itemID Attachment item id
* @param {Array} pageIndexes
* @param {Boolean} [isPriority]
* @param {String} [password]
* @returns {Promise}
*/
async deletePages(itemID, pageIndexes, isPriority, password) {
return this._enqueue(async () => {
let attachment = await Zotero.Items.getAsync(itemID);
Zotero.debug(`Deleting [${pageIndexes.join(', ')}] pages for item ${attachment.libraryKey}`);
let t = new Date();
if (!attachment.isPDFAttachment()) {
throw new Error('Item must be a PDF attachment');
}
let annotations = attachment
.getAnnotations()
.map(annotation => ({
id: annotation.id,
position: JSON.parse(annotation.annotationPosition)
}));
let path = await attachment.getFilePathAsync();
let buf = await OS.File.read(path, {});
buf = new Uint8Array(buf).buffer;
try {
var { buf: modifiedBuf } = await this._query('deletePages', {
buf, pageIndexes, password
}, [buf]);
}
catch (e) {
let error = new Error(`Worker 'deletePages' failed: ${JSON.stringify({ error: e.message })}`);
try {
error.name = JSON.parse(e.message).name;
}
catch (e) {
Zotero.logError(e);
}
Zotero.logError(error);
throw error;
}
// Delete annotations from deleted pages
let ids = [];
for (let i = annotations.length - 1; i >= 0; i--) {
let { id, position } = annotations[i];
if (pageIndexes.includes(position.pageIndex)) {
ids.push(id);
annotations.splice(i, 1);
}
}
if (ids.length) {
await Zotero.Items.erase(ids);
}
// Shift page index for other annotations
ids = [];
await Zotero.DB.executeTransaction(async function () {
let rows = await Zotero.DB.queryAsync('SELECT itemID, position FROM itemAnnotations WHERE parentItemID=?', itemID);
for (let { itemID, position } of rows) {
try {
position = JSON.parse(position);
}
catch (e) {
Zotero.logError(e);
continue;
}
// Find the count of deleted pages before the current annotation page
let shift = pageIndexes.reduce((prev, cur) => cur < position.pageIndex ? prev + 1 : prev, 0);
if (shift > 0) {
position.pageIndex -= shift;
position = JSON.stringify(position);
await Zotero.DB.queryAsync('UPDATE itemAnnotations SET position=? WHERE itemID=?', [position, itemID]);
ids.push(itemID);
}
}
});
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('item');
let loadedObjects = objectsClass.getLoaded();
for (let object of loadedObjects) {
if (ids.includes(object.id)) {
await object.reload(null, true);
}
}
await Zotero.Notifier.trigger('modify', 'item', ids, {});
await OS.File.writeAtomic(path, new Uint8Array(modifiedBuf));
let mtime = Math.floor(await attachment.attachmentModificationTime / 1000);
attachment.attachmentLastProcessedModificationTime = mtime;
await attachment.saveTx({
skipAll: true
});
Zotero.debug(`Deleted pages for item ${attachment.libraryKey} in ${new Date() - t} ms`);
}, isPriority);
}
/**
* Rotate pages in PDF attachment
*
* @param {Integer} itemID Attachment item id
* @param {Array} pageIndexes
* @param {Integer} degrees 90, 180, 270
* @param {Boolean} [isPriority]
* @param {String} [password]
* @returns {Promise}
*/
async rotatePages(itemID, pageIndexes, degrees, isPriority, password) {
return this._enqueue(async () => {
let attachment = await Zotero.Items.getAsync(itemID);
Zotero.debug(`Rotating [${pageIndexes.join(', ')}] pages for item ${attachment.libraryKey}`);
let t = new Date();
if (!attachment.isPDFAttachment()) {
throw new Error('Item must be a PDF attachment');
}
let path = await attachment.getFilePathAsync();
let buf = await OS.File.read(path, {});
buf = new Uint8Array(buf).buffer;
try {
var { buf: modifiedBuf } = await this._query('rotatePages', {
buf, pageIndexes, degrees, password
}, [buf]);
}
catch (e) {
let error = new Error(`Worker 'rotatePages' failed: ${JSON.stringify({ error: e.message })}`);
try {
error.name = JSON.parse(e.message).name;
}
catch (e) {
Zotero.logError(e);
}
Zotero.logError(error);
throw error;
}
await OS.File.writeAtomic(path, new Uint8Array(modifiedBuf));
let mtime = Math.floor(await attachment.attachmentModificationTime / 1000);
attachment.attachmentLastProcessedModificationTime = mtime;
await attachment.saveTx({
skipAll: true
});
Zotero.debug(`Rotated pages for item ${attachment.libraryKey} in ${new Date() - t} ms`);
}, isPriority);
}
} }
Zotero.PDFWorker = new PDFWorker(); Zotero.PDFWorker = new PDFWorker();

View file

@ -22,7 +22,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.Prefs = new function(){ Zotero.Prefs = new function () {
// Privileged methods // Privileged methods
this.get = get; this.get = get;
this.set = set; this.set = set;
@ -49,7 +49,7 @@ Zotero.Prefs = new function(){
if (!fromVersion) { if (!fromVersion) {
fromVersion = 0; fromVersion = 0;
} }
var toVersion = 5; var toVersion = 6;
if (fromVersion < toVersion) { if (fromVersion < toVersion) {
for (var i = fromVersion + 1; i <= toVersion; i++) { for (var i = fromVersion + 1; i <= toVersion; i++) {
switch (i) { switch (i) {
@ -85,6 +85,24 @@ Zotero.Prefs = new function(){
case 5: case 5:
this.clear('extensions.spellcheck.inline.max-misspellings', true); this.clear('extensions.spellcheck.inline.max-misspellings', true);
break; break;
// If the note Quick Copy setting was set to Markdown + Rich Text but in a
// non-default way (e.g., because of whitespace differences), clear the pref
// to pick up the new app-link options
case 6:
var o = this.get('export.noteQuickCopy.setting');
try {
o = JSON.parse(o);
if (o.mode == 'export'
&& o.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
this.clear('export.noteQuickCopy.setting');
}
}
catch (e) {
Zotero.logError(e);
this.clear('export.noteQuickCopy.setting');
}
break;
} }
} }
this.set('prefVersion', toVersion); this.set('prefVersion', toVersion);
@ -230,6 +248,7 @@ Zotero.Prefs = new function(){
Zotero.setFontSize( Zotero.setFontSize(
Zotero.getActiveZoteroPane().document.getElementById('zotero-pane') Zotero.getActiveZoteroPane().document.getElementById('zotero-pane')
); );
Zotero.setFontSize(Zotero.getActiveZoteroPane().document.getElementById('zotero-context-pane'));
Zotero.getActiveZoteroPane().collectionsView && Zotero.getActiveZoteroPane().collectionsView.updateFontSize(); Zotero.getActiveZoteroPane().collectionsView && Zotero.getActiveZoteroPane().collectionsView.updateFontSize();
Zotero.getActiveZoteroPane().itemsView && Zotero.getActiveZoteroPane().itemsView.updateFontSize(); Zotero.getActiveZoteroPane().itemsView && Zotero.getActiveZoteroPane().itemsView.updateFontSize();
}], }],
@ -358,12 +377,13 @@ Zotero.Prefs = new function(){
delete _observersBySymbol[symbol]; delete _observersBySymbol[symbol];
var [name, handler] = obs; var [name, handler] = obs;
var i = obs.indexOf(handler); var handlers = _observers[name];
var i = handlers.indexOf(handler);
if (i == -1) { if (i == -1) {
Zotero.debug("Handler was not registered for preference " + name, 2); Zotero.debug("Handler was not registered for preference " + name, 2);
return; return;
} }
obs.splice(i, 1); handlers.splice(i, 1);
} }

View file

@ -0,0 +1,87 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2022 Corporation for Digital Scholarship
Vienna, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
Zotero.Prompt = {
BUTTON_TITLE_OK: Services.prompt.BUTTON_TITLE_OK,
BUTTON_TITLE_CANCEL: Services.prompt.BUTTON_TITLE_CANCEL,
BUTTON_TITLE_YES: Services.prompt.BUTTON_TITLE_YES,
BUTTON_TITLE_NO: Services.prompt.BUTTON_TITLE_NO,
BUTTON_TITLE_SAVE: Services.prompt.BUTTON_TITLE_SAVE,
BUTTON_TITLE_DONT_SAVE: Services.prompt.BUTTON_TITLE_DONT_SAVE,
BUTTON_TITLE_REVERT: Services.prompt.BUTTON_TITLE_REVERT,
/**
* A wrapper around XPCOM's Services.prompt.confirmEx()
* but with a friendlier interface.
*
* Button text can use special static variables from
* Zotero.Prompt
*
* @param options
* - {mozIDOMWindowProxy} window - The parent window or null.
* - {String} title - Text to appear in the title of the dialog.
* - {String} text - Text to appear in the body of the dialog.
* - {String|Number} button0 - Button 0 text
* - {String|Number} button1 - Button 1 text
* - {String|Number} button2 - Button 2 text
* - {String} checkLabel - Text to appear with the checkbox.
* - {Object} checkbox - Contains the initial checked state of the
* checkbox when this method is called and the final checked
* state after this method returns. Either {} or { value: true/false }.
* - {Number} defaultButton - The index of default button. 0 by default
* - {Boolean} delayButtons - Make the buttons initially disabled and enable them after some period
* so that the user doesn't click through the dialog without reading it.
* @returns {Number} The index of the button pressed.
*/
confirm(options = {}) {
let { window: win, title, text, button0, button1, button2, checkLabel, checkbox, defaultButton, delayButtons } = options;
if (!win) win = null;
if (!title) throw new Error('`title` is required');
if (!text) throw new Error('`text` is required');
if (!button0 && !button1 && !button2) {
throw new Error('At least one button is required');
}
if (checkLabel && (!checkbox || typeof checkbox != 'object')) {
throw new Error('`checkLabel` provided without `checkbox` option');
}
let flags = delayButtons ? Services.prompt.BUTTON_DELAY_ENABLE : 0;
if (typeof button0 == 'number') flags += Services.prompt.BUTTON_POS_0 * button0;
else if (typeof button0 == 'string') flags += Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING;
if (typeof button1 == 'number') flags += Services.prompt.BUTTON_POS_1 * button1;
else if (typeof button1 == 'string') flags += Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING;
if (typeof button2 == 'number') flags += Services.prompt.BUTTON_POS_2 * button2;
else if (typeof button2 == 'string') flags += Services.prompt.BUTTON_POS_2 * Services.prompt.BUTTON_TITLE_IS_STRING;
if (defaultButton) flags += defaultButton == 1 ? Services.prompt.BUTTON_POS_1_DEFAULT : Services.prompt.BUTTON_POS_2_DEFAULT;
return Services.prompt.confirmEx(
win, title, text, flags,
typeof button0 == 'number' ? null : button0,
typeof button1 == 'number' ? null : button1,
typeof button2 == 'number' ? null : button2,
checkLabel, checkbox
);
}
};

View file

@ -23,6 +23,8 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Components.utils.importGlobalProperties(["URL"]);
/** /**
* A singleton to handle URL rewriting proxies * A singleton to handle URL rewriting proxies
* @namespace * @namespace
@ -138,6 +140,8 @@ Zotero.Proxies = new function() {
* @type String * @type String
*/ */
this.proxyToProper = function(url, onlyReturnIfProxied) { this.proxyToProper = function(url, onlyReturnIfProxied) {
// make sure url has a trailing slash
url = new URL(url).href;
for (let proxy of Zotero.Proxies.proxies) { for (let proxy of Zotero.Proxies.proxies) {
if(proxy.regexp) { if(proxy.regexp) {
var m = proxy.regexp.exec(url); var m = proxy.regexp.exec(url);
@ -173,16 +177,18 @@ Zotero.Proxies = new function() {
* Check the url for potential proxies and deproxify, providing a scheme to build * Check the url for potential proxies and deproxify, providing a scheme to build
* a proxy object. * a proxy object.
* *
* @param URL * @param url
* @returns {Object} Unproxied url to proxy object * @returns {Object} Unproxied url to proxy object
*/ */
this.getPotentialProxies = function(URL) { this.getPotentialProxies = function(url) {
// make sure url has a trailing slash
url = new URL(url).href;
var urlToProxy = {}; var urlToProxy = {};
// If it's a known proxied URL just return it // If it's a known proxied URL just return it
if (Zotero.Proxies.transparent) { if (Zotero.Proxies.transparent) {
for (var proxy of Zotero.Proxies.proxies) { for (var proxy of Zotero.Proxies.proxies) {
if (proxy.regexp) { if (proxy.regexp) {
var m = proxy.regexp.exec(URL); var m = proxy.regexp.exec(url);
if (m) { if (m) {
let proper = proxy.toProper(m); let proper = proxy.toProper(m);
urlToProxy[proper] = proxy.toJSON(); urlToProxy[proper] = proxy.toJSON();
@ -191,12 +197,12 @@ Zotero.Proxies = new function() {
} }
} }
} }
urlToProxy[URL] = null; urlToProxy[url] = null;
// if there is a subdomain that is also a TLD, also test against URI with the domain // if there is a subdomain that is also a TLD, also test against URI with the domain
// dropped after the TLD // dropped after the TLD
// (i.e., www.nature.com.mutex.gmu.edu => www.nature.com) // (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
var m = /^(https?:\/\/)([^\/]+)/i.exec(URL); var m = /^(https?:\/\/)([^\/]+)/i.exec(url);
if (m) { if (m) {
// First, drop the 0- if it exists (this is an III invention) // First, drop the 0- if it exists (this is an III invention)
var host = m[2]; var host = m[2];
@ -211,16 +217,15 @@ Zotero.Proxies = new function() {
for (let i=0; i < hostnameParts.length; i++) { for (let i=0; i < hostnameParts.length; i++) {
let parts = hostnameParts[i]; let parts = hostnameParts[i];
// If hostnameParts has two entries, then the second one is with replaced hyphens // If hostnameParts has two entries, then the second one is with replaced hyphens
let dotsToHyphens = i == 1;
// skip the lowest level subdomain, domain and TLD // skip the lowest level subdomain, domain and TLD
for (let j=1; j<parts.length-2; j++) { for (let j=1; j<parts.length-2; j++) {
// if a part matches a TLD, everything up to it is probably the true URL // if a part matches a TLD, everything up to it is probably the true URL
if (TLDS[parts[j].toLowerCase()]) { if (TLDS[parts[j].toLowerCase()]) {
var properHost = parts.slice(0, j+1).join("."); var properHost = parts.slice(0, j+1).join(".");
// protocol + properHost + /path // protocol + properHost + /path
var properURL = m[1]+properHost+URL.substr(m[0].length); var properURL = m[1]+properHost+url.substr(m[0].length);
var proxyHost = parts.slice(j+1).join('.'); var proxyHost = parts.slice(j+1).join('.');
urlToProxy[properURL] = {scheme: m[1] + '%h.' + proxyHost + '/%p', dotsToHyphens}; urlToProxy[properURL] = {scheme: m[1] + '%h.' + proxyHost + '/%p'};
} }
} }
} }
@ -249,10 +254,6 @@ Zotero.Proxy.prototype._loadFromRow = function (row) {
this.multiHost = row.scheme && row.scheme.indexOf('%h') != -1 || !!row.multiHost; this.multiHost = row.scheme && row.scheme.indexOf('%h') != -1 || !!row.multiHost;
this.autoAssociate = !!row.autoAssociate; this.autoAssociate = !!row.autoAssociate;
this.scheme = row.scheme; this.scheme = row.scheme;
// Database query results will throw as this option is only present when the proxy comes along with the translator
if ('dotsToHyphens' in row) {
this.dotsToHyphens = !!row.dotsToHyphens;
}
if (this.scheme) { if (this.scheme) {
this.compileRegexp(); this.compileRegexp();
@ -263,7 +264,7 @@ Zotero.Proxy.prototype.toJSON = function() {
if (!this.scheme) { if (!this.scheme) {
throw Error('Cannot convert proxy to JSON - no scheme'); throw Error('Cannot convert proxy to JSON - no scheme');
} }
return {id: this.id, scheme: this.scheme, dotsToHyphens: this.dotsToHyphens}; return {id: this.id, scheme: this.scheme};
} }
/** /**
@ -456,6 +457,8 @@ Zotero.Proxy.prototype.erase = Zotero.Promise.coroutine(function* () {
*/ */
Zotero.Proxy.prototype.toProper = function(m) { Zotero.Proxy.prototype.toProper = function(m) {
if (!Array.isArray(m)) { if (!Array.isArray(m)) {
// make sure url has a trailing slash
m = new URL(m).href;
let match = this.regexp.exec(m); let match = this.regexp.exec(m);
if (!match) { if (!match) {
return m return m
@ -463,16 +466,16 @@ Zotero.Proxy.prototype.toProper = function(m) {
m = match; m = match;
} }
} }
let scheme = this.scheme.indexOf('https') == -1 ? 'http://' : 'https://'; let protocol = m[0].indexOf('https') == -1 ? 'http://' : 'https://';
if(this.multiHost) { if(this.multiHost) {
var properURL = scheme+m[this.parameters.indexOf("%h")+1]+"/"; var properURL = protocol+m[this.parameters.indexOf("%h")+1]+"/";
} else { } else {
var properURL = scheme+this.hosts[0]+"/"; var properURL = protocol+this.hosts[0]+"/";
} }
// Replace `-` with `.` in https to support EZProxy HttpsHyphens. // Replace `-` with `.` in https to support EZProxy HttpsHyphens.
// Potentially troublesome with domains that contain dashes // Potentially troublesome with domains that contain dashes
if (this.dotsToHyphens) { if (protocol.includes('https')) {
properURL = properURL.replace(/-/g, '.'); properURL = properURL.replace(/-/g, '.');
} }
@ -507,7 +510,7 @@ Zotero.Proxy.prototype.toProxy = function(uri) {
var param = this.parameters[i]; var param = this.parameters[i];
var value = ""; var value = "";
if(param == "%h") { if(param == "%h") {
value = this.dotsToHyphens ? uri.hostPort.replace(/-/g, '.') : uri.hostPort; value = uri.scheme === 'https' ? uri.hostPort.replace(/-/g, '.') : uri.hostPort;
} else if(param == "%p") { } else if(param == "%p") {
value = uri.pathQueryRef.substr(1); value = uri.pathQueryRef.substr(1);
} else if(param == "%d") { } else if(param == "%d") {

Some files were not shown because too many files have changed in this diff Show more