Compare commits

...

389 Commits

Author SHA1 Message Date
Dan Stillman
72481b072e Clean up extra files from Mendeley imports
Follow-up to cdee741a6
2018-06-16 03:07:46 -04:00
Dan Stillman
cdee741a6d Mendeley import: Fix duplicate PDF copying for PDFs in Downloaded
For each PDF with an associated URL in the Downloaded directory, we were
copying all files in the directory (!) to the attachment's storage
directory. (Zotero imports always have files in separate directories,
and this was a function used to save both single files and HTML
snapshots.)

We'll clean up the extra files in a separate step.
2018-06-16 01:51:37 -04:00
Dan Stillman
68d03bc6c3 Update version 2018-06-14 16:42:16 -04:00
Dan Stillman
0383f104dd Fix "Import into new collection" option when handling importable file 2018-06-14 16:41:33 -04:00
Dan Stillman
d38d55e2b4 Mendeley import: Don't use single transaction 2018-06-13 10:27:26 -04:00
Dan Stillman
94a0c3ce8c Update version 2018-06-12 15:33:17 -04:00
Dan Stillman
5ddbe433b9 Fix Backoff and Retry-After header parsing 2018-06-12 15:17:46 -04:00
Dan Stillman
79d7c15d4f Update translators 2018-06-12 07:54:20 -04:00
Dan Stillman
d4c201fbc0 Correct Mendeley data dir path on Windows 2018-06-12 07:04:32 -04:00
Dan Stillman
d4097d90f6 Update locales from Transifex and merge new English strings 2018-06-12 06:23:57 -04:00
Dan Stillman
e67db436c6 Mendeley import: Add link to support page on encrypted DB
Also localize the name of the collection created during import
2018-06-12 06:16:59 -04:00
Dan Stillman
cfbb3d3d47 Mendeley import: Fix "Collection undefined not found" error
If a folder appeared in the DocumentFolders table but not in
Folders/RemoteFolders, it caused an error.
2018-06-11 12:16:09 -04:00
Dan Stillman
bd5f2525dc Add star tag for favorite items 2018-06-11 09:44:01 -04:00
Dan Stillman
da5db4cb38 Update node-sass to fix build breakage 2018-06-11 09:01:32 -04:00
Dan Stillman
2831e8be3e Mendeley import: Fix "keys is undefined" error
An error would occur if a document existed in RemoteDocuments but not
Documents.
2018-06-10 02:50:52 -04:00
Dan Stillman
8547dedca9 Add debug output for unrecognized Mendeley DB issue 2018-06-09 02:26:12 -04:00
Dan Stillman
46fc28dc16 Remove unused conditional 2018-06-09 02:13:00 -04:00
Dan Stillman
06a819bf34 Fix Import from Clipboard breakage 2018-06-09 02:09:46 -04:00
Dan Stillman
7f64c6803a Update locales from Transifex 2018-06-08 06:21:46 -04:00
Dan Stillman
3c70dee5c1 Create import collection by default if there are non-trash items
Follow-up to 7494e4d88c, which only checked for collections
2018-06-08 05:57:20 -04:00
Dan Stillman
337144a5eb Mendeley import: Reuse existing collections
If "Place imported collections and items into new collection" is
unchecked, previously imported collections will be reused when they're
in the right place in the hierarchy rather than creating new ones.
2018-06-08 04:52:18 -04:00
Dan Stillman
7494e4d88c Don't create import collection by default if no collections in library
If the selected library doesn't have collections, "Place imported
collections and items into new collection" will be unchecked in the
import wizard.
2018-06-08 02:44:13 -04:00
Dan Stillman
2b49e94a61 Fix sync breakage from 78e87a351 2018-06-07 17:18:28 -04:00
Dan Stillman
d0087d59d5 Update version 2018-06-07 17:07:08 -04:00
Dan Stillman
854cb05883 Mendeley import: Support repeated imports without duplicating items
Existing items are overwritten with new data (or skipped in the case of
file attachments).

Addresses #1451
2018-06-07 06:37:50 -04:00
Dan Stillman
835bdf6941 Merge new English strings 2018-06-06 01:58:09 -04:00
Dan Stillman
d98e89cf26 Add import option for creating a new collection
Use the new wizard for all imports (even if no Mendeley DB), and add a
page with a "Place imported collections and items into new collection"
option. If deselected, collections are added to the library root.
2018-06-05 22:39:23 -04:00
Dan Stillman
4008848c64 Mendeley import: Create imported_url attachments when possible
If there's a single PDF file and a single PDF URL and the file exists,
make an imported_url attachment instead of separate file and linked_url
attachments.

Addresses #1451
2018-06-05 19:57:28 -04:00
Dan Stillman
024b428fe6 Mendeley import: Add "Other…" button for selecting another DB
E.g., for selecting a DB from a backup

Addresses #1451
2018-06-05 19:57:20 -04:00
Dan Stillman
17f2025c53 Fix import into selected library from wizard 2018-06-05 08:39:17 -04:00
Dan Stillman
6ac65373a3 Mendeley import: Look for Downloaded files relative to DB if not found
Addresses #1451
2018-06-05 08:30:14 -04:00
Dan Stillman
2939b3ae95 Merge branch 'mendeley-import' (#1451) 2018-06-05 07:52:12 -04:00
Dan Stillman
0f4e5ef508 Mendeley import
Accept Mendeley SQLite databases via File → Import… and perform a
direct import, including collections, timestamps, notes, attachments,
and extracted annotations.

When a Mendeley database is present, File → Import… shows a wizard that
lets you choose between a file and Mendeley for the source, and choosing
the latter shows a list of available databases in the Mendeley data
directory.

Known fields that aren't valid for a type are stored in Extra.

Files in the Mendeley 'Downloaded' folder are stored. Files elsewhere
are linked.
2018-06-05 07:52:01 -04:00
Dan Stillman
a894404ad3 Update locales from Transifex 2018-06-05 06:18:52 -04:00
Dan Stillman
e46f1d06e0 Update submodules 2018-06-05 05:46:41 -04:00
Dan Stillman
c5ba2e55f3 Add a debug line before submitting output
This will help clarify whether the person waited to submit or just
submitted while stuff was happening.
2018-06-04 19:58:10 -04:00
Dan Stillman
f7e411d561 Add support for databases in other directories
Previously you could use Zotero.DBConnection to open another database in
the data directory, but not one stored elsewhere in the filesystem. This
allows an absolute path to be passed instead. Various operations
(backups, corrupt DB recovery, pragma commands) are disabled for
external databases.
2018-06-02 04:10:39 -04:00
Dan Stillman
603388c79d Add missing quote to error 2018-06-02 02:27:24 -04:00
Adomas Venčkauskas
547f1c29e3 Integration: Fix non-existent session errors preventing displayError from working 2018-06-01 23:26:24 +03:00
Dan Stillman
2dd0a560b7
Update CONTRIBUTING.md 2018-05-29 17:48:55 -04:00
Dan Stillman
9220b2d9c2 Fix inconsequential bug in Zotero.MIME.sniffForMIMEType()
`undefined` was being passed as an argument to slice(), but 0 is the
only offset that's used anyway, and that's what happens if you pass
`undefined`.
2018-05-27 21:23:38 -04:00
Dan Stillman
7271fdf6b7 Add Zotero.Sync.Runner.delayIndefinite()
Delays syncing until the returned function is run
2018-05-27 21:23:38 -04:00
Dan Stillman
86b94ae713 Add optional 'db' parameter to Zotero.DB.tableExists()
To check attached databases
2018-05-27 21:23:38 -04:00
Dan Stillman
52737ec694 Set locking_mode=EXCLUSIVE only for main database in Zotero.DBConnection
Otherwise attached databases are also exclusive
2018-05-27 21:23:38 -04:00
Dan Stillman
6ff51103f5 Make Zotero.ItemFields.getFieldIDFromTypeAndBase() work on non-base fields
This previously returned false if a non-base-mapped field was passed,
even if the field was valid for the given item type. It now returns the
passed field as long as the field is valid for the type, which matches
the behavior for base fields.
2018-05-27 21:23:38 -04:00
Dan Stillman
44fd598699 Load creators if needed to update display title in Items._loadItemData() 2018-05-27 21:23:38 -04:00
Dan Stillman
a714f06670 Use Zotero.warn() for item.setCreator() warning 2018-05-27 21:23:38 -04:00
Dan Stillman
78e87a351c Handle relations property in collection fromJSON() 2018-05-27 21:23:38 -04:00
Dan Stillman
3a3f46530d Adjust note header sizing/styling 2018-05-27 21:23:38 -04:00
Dan Stillman
3241faf503 Fix tag saving for intercepted imports from connector
Save tags in intercepted files as automatic tags
2018-05-23 21:16:18 -04:00
Adomas Venčkauskas
2f620c5f91 Fix corrupt bibliography handling. Closes #1503 2018-05-22 13:28:13 +03:00
Dan Stillman
763d7dec75 Allow Link header for translation-server responses via CORS 2018-05-15 21:17:45 -04:00
Dan Stillman
4acd178819 Fix user-agent handling for faked UAs from connector 2018-05-15 20:18:41 -04:00
Adomas Venčkauskas
6960b7f86e Fixes the refresh error after style reinstall. Closes #1500 2018-05-15 14:14:07 +03:00
Dan Stillman
81ab8f7b20 Allow header object instead of just content type in server responses 2018-05-13 04:27:27 -04:00
Dan Stillman
05843bb093 Fix base fields in connectorTypeSchemaData.js 2018-05-10 02:26:44 -04:00
Dan Stillman
5751838a3d Update submodules 2018-05-09 04:20:41 -04:00
Dan Stillman
20dd81729b Update locales from Transifex 2018-05-09 04:18:23 -04:00
Dan Stillman
3516598f4c Update recommended connector version 2018-05-08 20:56:35 -04:00
Dan Stillman
4a7aad03c4 Fix text() brokenness, and remove change warning fo attr()/text()
1) text() wasn't handling the index property.

2) This removes the warning that attr()/text() no longer no require a
document as the first argument, because there's no reason to prevent
translators from being able to pass an element. It would require
rewriting various translators unnecessarily and make certain patterns
more verbose (because you'd need to match based on global scope in each
selector).

It won't be necessary to pass a Document once we remove 4.0 support and
the global attr()/text() are always available, so we can add a warning
for that then.

Fixes zotero/translators#1647
2018-05-08 20:33:13 -04:00
Dan Stillman
90677ae158 Add tick@2x.png 2018-05-08 19:17:35 -04:00
Adomas Venčkauskas
23224f6093 Fix 'idx is undefined' error in integration missing item handler 2018-05-07 15:23:07 +03:00
Martynas Bagdonas
9165a0247f Fix PMID and arXiv identifiers extraction (#1498) 2018-05-07 06:04:11 -04:00
Dan Stillman
e6dbd1ed92 Add PDF Expert support to zotero://open-pdf
Requested here: https://github.com/jlegewie/zotfile/issues/352#issuecomment-386896327
2018-05-06 14:07:23 -04:00
Dan Stillman
5a85d1ac9e Update citeproc-js to 1.1.206 2018-05-05 03:28:59 -04:00
Dan Stillman
9ebe4037a4 Update locales from Transifex 2018-05-05 02:48:08 -04:00
Dan Stillman
7dbfd5ce29 Remove some extraneous lines in Zotero.OpenPDF 2018-05-05 01:54:49 -04:00
Dan Stillman
040e0a726a Simplify page path component parsing for zotero://open-pdf 2018-05-05 01:30:42 -04:00
Dan Stillman
60e0d79e01 Don't allow page path component for new-style zotero://open-pdf URLs
That's only necessary for ZotFile URLs.
2018-05-05 01:19:38 -04:00
Dan Stillman
609657a8e4 Add zotero://open-pdf handler to open PDF at a given page
This is loosely based on the same functionality in ZotFile, but it tries
to do the right thing based on existing Zotero settings: either the new
PDF handler setting in the prefs or the system-default app. The latter
can only reliably be determined on Windows (and this uses ZotFile's
function to read that from the registry), but this tries to figure it
out on macOS and Linux too using the Mozilla handler service. (The
handler service only gets you an app name, not a path, so on Linux we
can try reading mimetypes.list and the like in case someone is using a
system-default okular or evince not in /usr/bin, but that's not yet
implemented.)

This uses the new 5.0 URL format, and a 'page' query parameter instead
of a path component:

zotero://open-pdf/library/items/[itemKey]?page=[page]
zotero://open-pdf/groups/[groupID]/items/[itemKey]?page=[page]

It also accepts ZotFile-style URLs, though, so if you uninstall ZotFile
you should still be able to open those links. ZotFile will need to
accept the new format for new links to work when ZotFile is installed,
since it will override this handler.

This functionality will be necessary for annotation extraction (#1018)
and for imported annotations from Mendeley (#1451).
2018-05-05 00:55:22 -04:00
Martynas Bagdonas
c0a4fa43f0 Add a test for PDF recognition by DOI (#1496) 2018-05-04 03:14:26 -04:00
Dan Stillman
8f39e9cb36 Rename PDF recognizer tests to reflect arXiv ID lookup
Addresses #1494 and #1495
2018-05-04 01:16:04 -04:00
Dan Stillman
1ef2469b40 Update version 2018-05-02 02:55:53 -04:00
Adomas Venčkauskas
5b595122b7 Fix inproper cleanup of legacy API word processor plugins 2018-05-02 09:39:08 +03:00
Dan Stillman
b8e0c3f7e4 Update version 2018-05-01 20:35:17 -04:00
Dan Stillman
81b876a5b2 Don't prompt to upgrade source version of connector 2018-04-29 20:29:01 -04:00
Dan Stillman
17b4c2e98e Show upgrade prompt for connector pings from below a certain version
Shows a prompt once per restart or once per day, whichever is longer,
with an option to not show again for a month. Can be disabled completely
by setting extensions.zotero.showConnectorVersionWarning to false.

Currently prompts for connector versions below 5.0.35.

This is a temporary solution for #1489 until the connector checks and
warns on its own when it's outdated and most people are on a version
that does that (particularly Safari users, who don't auto-upgrade).
2018-04-29 04:53:00 -04:00
Dan Stillman
6b4bce8754 Update submodules 2018-04-29 03:54:24 -04:00
Dan Stillman
1649cae956 Update locales from Transifex 2018-04-29 03:54:10 -04:00
Dan Stillman
41590741ba Add level: 0 for libraries in target list for connector 2018-04-27 18:59:11 -04:00
Dan Stillman
a0d85c0673 Don't switch libraries when uneditable in /getSelectedCollection
Follow-up to d0e42a1186
2018-04-27 18:58:12 -04:00
Dan Stillman
d0e42a1186 Automatically switch to My Library on connector save in noneditable view 2018-04-27 18:27:06 -04:00
Dan Stillman
c55b355548 Add "Firefox/[version]" to user agent
Some sites didn't render properly, both in snapshots and in Scaffold,
due to browser sniffing.
2018-04-27 01:38:41 -04:00
Dan Stillman
a4cd45ceb4 Exclude note prefix and suffix when searching
This avoids having "1" match all notes (though really we should be
searching on a plain-text string stored in a separate column).
2018-04-26 02:15:13 -04:00
Dan Stillman
6fb99d2d90 Filter logged server responses
- Limit size to 1500 characters
- Remove collection names in /getSelectedCollection response
2018-04-25 16:02:11 -04:00
Adomas Venčkauskas
6cf11f083b Add a sendToBack connector endpoint for macOS integration window management 2018-04-25 15:42:41 +03:00
Adomas Venčkauskas
ac24ffe636 Always center the integration progress window 2018-04-25 15:42:41 +03:00
Adomas Venčkauskas
aad29a5469 Use XPCOM DOMParser in http integration client to avoid addon conflicts 2018-04-25 15:42:41 +03:00
Adomas Venčkauskas
f69c7d2e2d Fix a http integration client conflict with zotero addons 2018-04-25 14:07:55 +03:00
Adomas Venčkauskas
8b36f33513 Ensure the progress bar is hidden if session initialisation fails 2018-04-24 12:54:08 +03:00
Dan Stillman
e31d706ee4 Date.strToDate: Only check for date.* localized strings in client
Zotero.getString() now exists in the connector, but the expected strings
aren't defined (or needed), so an error is logged.
2018-04-24 05:08:27 -04:00
Dan Stillman
d573a5b639 Merge pull request #1476 from adomasven:feature/connector-doc-integration
Doc Integration Endpoint for the Connector
2018-04-24 01:37:48 -04:00
Adomas Venčkauskas
390b8b2c86 Fix quickformat early dismisal error 2018-04-24 01:37:10 -04:00
Adomas Venčkauskas
387109c1da Don't needlessly regenerate citation IDs 2018-04-24 01:37:10 -04:00
Adomas Venčkauskas
593153eebe Adds a progress bar for non quick-format integration actions
The progress percentage is based on the most recent transaction
(or undeterminate if this is the first session transaction)

Fix undefined function call error
2018-04-24 01:37:10 -04:00
Dan Stillman
1c5e0475f5 Update locales from Transifex 2018-04-23 03:34:07 -04:00
Dan Stillman
33f8fcfafb Fix syncing of saved search changes
Condition changes were only uploaded after every other change + sync
2018-04-22 17:27:33 -04:00
Adomas Venčkauskas
5e5b567782 Add a connector document integration endpoint
Specifically for google docs via the connector, but could potentially be
used for any integration via HTTP or connector.
2018-04-20 13:08:34 +03:00
Dan Stillman
7c093b4fb0 Update locales from Transifex 2018-04-19 04:46:29 -04:00
Martynas Bagdonas
00efd01aaf Ask for description when reporting metadata (#1488) 2018-04-19 04:35:03 -04:00
Dan Stillman
6450d39933 Throw async error from _loadTranslator() 2018-04-19 02:59:41 -04:00
Dan Stillman
3bc08ba10c Change overwritten translator message from error to warning 2018-04-19 02:59:41 -04:00
Martynas Bagdonas
0b4e1f9360 Use the extracted arXiv identifier when recognizing PDF (#1487) 2018-04-19 02:35:20 -04:00
Dan Stillman
b22e496b05 Allow setting CORS allowed origins for translation-server 2018-04-18 16:15:26 -04:00
Dan Stillman
16c2f6602a Update locales from Transifex 2018-04-18 13:42:51 -04:00
Dan Stillman
653343149a Add "arXiv IDs" to Add Item by Identifier text 2018-04-18 13:25:09 -04:00
Martynas Bagdonas
fa0576a4dd Add arXiv identifier support (#1486) 2018-04-18 13:03:10 -04:00
Dan Stillman
8beda5eed4 Update version 2018-04-16 17:02:20 -04:00
Dan Stillman
bf26c230be Update submodules 2018-04-16 14:58:37 -04:00
Dan Stillman
8474c7b52a Update locales from Transifex 2018-04-16 14:57:00 -04:00
Dan Stillman
3835bc9110 Assign merge-tracking relations properly when merging >2 items
https://forums.zotero.org/discussion/71459/incorrect-document-refresh-after-three-item-merge
2018-04-15 17:05:55 -04:00
Dan Stillman
8853f8ca47 Allow higher local object version during full sync
Local object versions can be higher than remote versions, because we
upload in batches and only record the version from the last batch.

This could cause trouble if an object failed to upload during a Restore
to Online Library, causing it to be retried later with version 0 (unlike
during a restore when the version is omitted), causing the library to be
reset, causing any local objects with higher local versions to be
redownloaded.
2018-04-14 16:50:41 -04:00
Dan Stillman
794d3880e7 Set 'control' attribute for item box fields
Set 'control' to the id of either the value label or the textbox,
depending on whether the field is being edited. This causes NVDA to read
the label associated with the textbox, but seemingly only the first time
it's selected.

Unfortunately NVDA also does some other unpleasant things, like reading
a description from the duplicate-merging pane, which isn't the active
element in the deck, and reading the entire text, including all field
labels, of the item box) I'm not sure how much we can improve this until
we're on Electron.

VoiceOver doesn't read the textbox's label either, even though it does
so in the bookmarks window in Firefox. Maybe things have improved since
Firefox 52, so we can text again after upgrading to Firefox 60.

Addresses #1411
2018-04-14 13:25:31 -04:00
Dan Stillman
0cc3e64b8a Restore proper incompatible DB message
Fixes #1446
2018-04-14 11:38:54 -04:00
Dan Stillman
0e3071576b Show "Export Collection…" if there are items in subcollections
And generate collection context menu asynchronously

Closes #1482
2018-04-14 11:33:16 -04:00
Dan Stillman
460a423df3 Don't let an progress window block shutdown
Closes #1485
2018-04-14 10:28:00 -04:00
Dan Stillman
71f7ec5207 Avoid invalid-field warning for parentItem in fromJSON() after 6f1833f936 2018-04-14 10:27:40 -04:00
Dan Stillman
ce834fc5f3 Don't focus previous row when choosing autocompleted creator
https://forums.zotero.org/discussion/71310/
2018-04-14 10:27:40 -04:00
Frank Bennett
94271325d7 Asyncify Zotero.Relations.getByObject(), called only from item merge. (#1480)
Use getAsync() to acquire subject in Zotero.Relations.getByObject()
2018-04-14 10:26:16 -04:00
Dan Stillman
ceb9749937 Avoid invalid-field warning in Item::fromJSON() after 6f1833f936 2018-04-14 00:06:27 -04:00
Dan Stillman
6f1833f936 Remove items from trash and My Publications when removed via sync
Zotero.Item::fromJSON() wasn't properly accounting for missing 'deleted'
or 'inPublications' properties.
2018-04-13 23:36:59 -04:00
Dan Stillman
4248e59eeb Add log line when trying to drag already-linked collection 2018-04-13 16:12:24 -04:00
Dan Stillman
7da17ab63e Update strings from connector 2018-04-13 16:12:19 -04:00
Dan Stillman
42bbcabdfe Update locales from Transifex 2018-04-13 14:06:02 -04:00
Dan Stillman
ff2e8c9047 Add localizable strings from connector 2018-04-13 13:15:10 -04:00
Adomas Venčkauskas
029c9fc251 Don't refresh citations when citation delaying is enabled via doc prefs 2018-04-12 14:00:28 +03:00
Adomas Venčkauskas
ea8003c541 Fix an invalid variable reference 2018-04-12 14:00:28 +03:00
Dan Stillman
9a304b6699 Better handling of remotely changed items in locally missing collections 2018-04-07 17:04:35 -04:00
Dan Stillman
ac4abf0ebb Avoid race conditions in conflict resolution tests 2018-04-07 17:04:00 -04:00
Dan Stillman
b4244f8a25 Update to Sinon 4.5.0 2018-04-07 17:01:46 -04:00
Dan Stillman
018ec79a74 Update version 2018-04-07 17:01:46 -04:00
Dan Stillman
17f7bcbbad Target selector: Increase recents to 7 2018-04-06 07:36:13 -04:00
Dan Stillman
eeadeaa1b1 Target selector: Fix recents handling 2018-04-06 07:32:24 -04:00
Dan Stillman
222bb5bad4 Target selector: Don't lose recent targets when saving to same target
A target could fill up the list from different sessions, pushing other
recents out.
2018-04-06 04:27:58 -04:00
Adomas Venčkauskas
7d424f6d12 Return notes from /saveItems connector endpoint 2018-04-05 15:27:02 +03:00
Dan Stillman
bddb5ed243 Target selector: Fix moving translated items between certain libraries
Moving a translated item from a non-filesEditable library to a
filesEditable library would fail (because the endpoint handler was named
"SaveItem" instead of "SaveItems").

Fixes zotero/zotero-connectors#235
2018-04-05 05:41:45 -04:00
Adomas Venčkauskas
ac180c2324 Fix initial integration action requiring a slow citeproc (re)load
(Regression 9c7271c6)
2018-04-04 13:52:33 +03:00
Adomas Venčkauskas
21fadee7bb Refactor word plugin installer 2018-04-04 13:52:33 +03:00
Dan Stillman
d4fce3c855 Update citeproc-js to 1.1.201 2018-04-04 03:34:24 -04:00
Dan Stillman
3b17681db3 Update citeproc-js to 1.1.200 2018-04-03 18:41:40 -04:00
Dan Stillman
89d7dad126 Update version 2018-04-03 00:17:50 -04:00
Dan Stillman
13f788f3f1 Update locales from Transifex 2018-04-03 00:10:06 -04:00
Dan Stillman
be698e499f Update submodules 2018-04-03 00:03:22 -04:00
Dan Stillman
4f9847da04 Save parent item to correct library when recognizing PDF without DOI 2018-04-02 15:34:22 -04:00
Dan Stillman
a8d199967e Fix crash if item field invalid for new type is open on type change
https://forums.zotero.org/discussion/71200/bug-when-changing-item-type-report-id-607330517
2018-04-01 15:34:44 -04:00
Dan Stillman
8d0dc359b4 Move prefs.js parsing to Zotero.Profile.readPrefsFromFile(prefsFile) 2018-04-01 13:44:10 -04:00
Dan Stillman
7f81e62bc8 Automatically create new data directories for additional profiles
E.g., if you have a main profile using ~/Zotero and create a second
"Work" profile, a "~/Zotero Work" data directory will be created
automatically and set as a custom data directory
2018-04-01 13:36:00 -04:00
Dan Stillman
0b384abe66 Fix DST display bug in Accessed and a few other date fields
If you entered a date that was in DST (including the current date at
00:00:00) but you were in standard time, the previous day was displayed.

Since access dates for saved items include explicit timestamps and most
people don't work between the hours of midnight and 3 a.m., this didn't
come up very often, and it was easy to miss when it did, since you'd be
unlikely to notice it for previously entered dates. (Someone noticed it
today because DST ended in Australia.)

This only affected the item pane, so citations were unaffected.
2018-04-01 08:17:06 -04:00
Tobias Diez
53df1b8dae Track master branch of submodules (#1477) 2018-03-31 16:08:31 -04:00
Dan Stillman
350b47364e Allow library switching in target selector
If switching from a filesEditable library to a non-filesEditable
library, files are removed. If going the other direction (including if
the original save was to a non-filesEditable library), the save is
performed again from the beginning in order to include attachments. If
switching between two filesEditable libraries, the storage directory is
just moved.

Addresses zotero/zotero-connectors#220
2018-03-31 08:26:40 -04:00
Dan Stillman
9e955bde99 Add Zotero.Item.prototype.moveToLibrary()
Move an item and its attachments to another library. Attachments are
removed as necessary if linked files or all files aren't supported in
the target library.
2018-03-31 08:26:36 -04:00
Dan Stillman
bc141ce36b Add .allowsLinkedFiles property to Zotero.Library objects 2018-03-31 08:26:36 -04:00
Dan Stillman
20df18636d Keep track of recent save targets for target selector
Addresses zotero/zotero-connectors#220
2018-03-31 08:25:39 -04:00
Dan Stillman
cd1c65a784 Update version 2018-03-30 03:25:20 -04:00
Dan Stillman
37e850c67b Reopen targeted collection on window open if closed at save time (macOS) 2018-03-30 01:55:04 -04:00
Dan Stillman
5a08d22dbf Fix attachment save failure on macOS with window closed
Use hiddenDOMWindow if no other window
2018-03-30 01:47:54 -04:00
Dan Stillman
fe5af63277 Update citeproc-js to 1.1.199 2018-03-29 18:27:52 -04:00
Dan Stillman
52c1249523 Fix weird test failure after c5cd38b4a5
The change in c5cd38b4a5 seems like it shouldn't have had any effect,
but creating an empty Set before yielding somehow avoided a race
condition with the subsequent updateSession call.
2018-03-29 08:29:37 -04:00
Dan Stillman
c5cd38b4a5 Additional comments and cleanup for target selector handler 2018-03-29 07:44:44 -04:00
Dan Stillman
9b9773db16 Skip items in save session that no longer exist
Ideally I guess the entry would be removed from the save popup in the
connector and a second button click would do a fresh save rather than
reopening the popup, but for now just avoid an error.

Addresses zotero/zotero-connectors#220
2018-03-29 07:44:41 -04:00
Dan Stillman
a4aabd9f3e Don't select items in trash on target selector change
Items in the trash will still be updated properly (since they can still
exist in collections and have tags), but the collection selected in the
target selector should remain selected in the client.

Addresses zotero/zotero-connectors#220
2018-03-29 07:44:37 -04:00
Dan Stillman
00d85fb6da Update parent item from target selector if item was made a child item
This is necessary for automatic PDF recognition.

Addresses zotero/zotero-connectors#220
2018-03-29 07:44:25 -04:00
Dan Stillman
0f6b712963 Return proper text for 409 errors from HTTP server 2018-03-29 07:11:35 -04:00
Dan Stillman
46eb84e859 Add Zotero.Item.prototype.parentItem getter 2018-03-29 07:11:15 -04:00
Dan Stillman
38330e4c13 Support target selector sessions for /connector/import 2018-03-28 08:46:13 -04:00
Adomas Venčkauskas
bff7cee374 Add a test case for copied citations (for 9c7271c6) 2018-03-28 14:55:46 +03:00
Adomas Venčkauskas
9c7271c606 Fix citations copied from other documents causing citeproc errors
Might slow down the initial interaction with a document in
automatic updates mode.
2018-03-28 14:42:38 +03:00
Dan Stillman
9ae582e345 Fix non-HiDPI tag swatches when item type image is low-res 2018-03-28 01:24:05 -04:00
Dan Stillman
292a033c1a Update version 2018-03-27 12:11:12 -04:00
Dan Stillman
3c5bc44ddf Fix potential upgrade error from c96363746b
If a related item was defined using both an old userID and the current
one, the schema upgrade step could fail.
2018-03-27 10:42:01 -04:00
Dan Stillman
3db915516a Update locales from Transifex 2018-03-27 10:34:51 -04:00
Dan Stillman
24b9612a56 Force empty storage filenames to '_'
Hopefully fixes https://forums.zotero.org/discussion/71039/sync-error-empty-path-for-item
2018-03-27 10:27:23 -04:00
Dan Stillman
a9e047cc9e Update version 2018-03-27 10:19:13 -04:00
Dan Stillman
6052f4c211 Update version 2018-03-27 05:56:32 -04:00
Adomas Venčkauskas
4e977f91cc Ensure citation fields are properly initialized (regression 3445519) 2018-03-27 12:45:04 +03:00
Dan Stillman
b600885d11 Update translators 2018-03-27 03:10:56 -04:00
Adomas Venčkauskas
3445519714 Fix citationByIndex[i].sortedItem errors during citation insertion
Caused by inproper handling of copy-pasted citations in documents
2018-03-26 15:31:26 +03:00
Sean Takats
e746da4fab
Update COPYING 2018-03-26 11:27:48 +02:00
Dan Stillman
32aebd388b Fix upgrade error in c96363746b if no related items 2018-03-26 04:48:32 -04:00
Dan Stillman
c96363746b Fix related-item relations pointing to a previous user account
If somebody switched accounts in a previous version, it was apparently
possible for related items to end up pointing at an item URI with the
old userID, which could cause a 403 on sync.

https://forums.zotero.org/discussion/70989/report-id-477331252

(5.0 deletes data when switching accounts to avoid exactly this sort of
bug.)
2018-03-26 02:12:20 -04:00
Dan Stillman
43692ee564 Use HiDPI icons in related-items box 2018-03-26 02:12:20 -04:00
Martynas Bagdonas
39ed2217cd Return pdfMaxPages to search preferences (#1475) 2018-03-26 02:12:05 -04:00
Dan Stillman
2effad4f6a Add function to delay syncs, and connector server endpoint to trigger it
This will allow the connector to delay syncs while the target selector
window is open, and it can probably be used for imports and other
things.
2018-03-25 05:15:36 -04:00
Adomas Venčkauskas
b9837c690d Fix item URLs not unproxifying when saving from the connector
Regression from zotero/zotero-connectors#b7a0872
2018-03-22 15:36:52 +02:00
Dan Stillman
67b34b1482 Create automatic (not manual) tags when retrieving PDF metadata by ISBN
https://forums.zotero.org/discussion/71003/metadata-problem
2018-03-21 12:58:20 -07:00
Dan Stillman
a673956787 Update version 2018-03-21 10:35:20 -07:00
Dan Stillman
447659cdab Update locales from Transifex 2018-03-21 09:13:01 -07:00
Dan Stillman
8782eaf507 Merge new English strings 2018-03-20 15:41:46 -07:00
Dan Stillman
c71f657b08 Update translators 2018-03-20 15:31:58 -07:00
Dan Stillman
dabd7c1055 Update locales from Transifex 2018-03-20 15:31:58 -07:00
Adomas Venčkauskas
3e39cb33d9 Shows original and modified citation in warning prompt. Closes #1468 2018-03-20 11:57:09 +02:00
Dan Stillman
d44cc05c3d Use 1.25dppx for HiDPI icon threshold
On some laptops, scaling settings might cause the DPI to drop between
96 (1x) and 144 (1.5x), and downscaled icons are probably better than
upscaled ones.
2018-03-19 13:46:50 -07:00
Adomas Venčkauskas
a12b997b8a Fix corrupt citation prompt going into recursive loop 2018-03-19 11:37:07 +02:00
Adomas Venčkauskas
8d3e893aec Fix alert not displaying after failed style install 2018-03-19 11:37:07 +02:00
Philipp Zumstein
0bb2bdcbe3 Replace p tag with two line breaks in ZU.cleanTags (#1465) 2018-03-18 23:35:49 -07:00
Adomas Venčkauskas
a659c6c6ff Fix edited citation prompt not displaying (regression 25d8898) 2018-03-17 21:47:36 +02:00
Adomas Venčkauskas
749faabb46 Fix [(j-1)].sorteditems.slice(...)[0] is undefined integration error
Caused by empty citations being present in the document (i.e. without
any cited items and placeholder text like {Citation}).
2018-03-17 21:39:21 +02:00
Martynas Bagdonas
7b8f2e1dfe Use language field returned from recognizer-server 2018-03-16 11:23:41 -07:00
Dan Stillman
cf12f1e9cc Update citeproc-js to 1.1.193 2018-03-15 10:13:41 -07:00
Dan Stillman
da08d7f078 Update locales from Transifex and merge new English strings 2018-03-15 00:23:42 -07:00
Dan Stillman
06e17fd76d "Choose File Handler" -> "Choose Application" for PDF viewer
(And potentially others in the future)
2018-03-15 00:13:04 -07:00
Dan Stillman
d145cff000 Update locales from Transifex and merge new English strings 2018-03-14 10:23:24 -07:00
Adomas Venčkauskas
e3648dfda0 Ensure delayed citation prompt strings are localizable 2018-03-14 19:08:38 +02:00
Adomas Venčkauskas
25d8898ab0 Ensure that document refresh doesn't perform needless text writes 2018-03-14 15:27:10 +02:00
Adomas Venčkauskas
92346c8fb9 Clarify location of refresh button in delayed citation copy 2018-03-14 13:21:22 +02:00
Adomas Venčkauskas
6f6e18b406 Increase citation delay prompt timeout to 15 seconds
By prompting after 3 seconds we catch a lot of people who click through
the prompt without bothering to read or figure out what it does and then
come to the forums report the citing "bugs". An increased timeout will
catch less people and those who are caught are possibly more likely to
figure out how delayed citing updates work since they would greatly
benefit from it.
2018-03-14 09:22:00 +02:00
Adomas Venčkauskas
6c5aa06d6b Fix integration delayed citations popup timer. Closes #1461 2018-03-12 14:52:46 +02:00
Dan Stillman
c04b85e746 Use all DOI translators for metadata retrieval
The old recognizer code only used CrossRef, and the new code copied
that, but I think the old code simply predated additional DOI
translators and was never updated.
2018-03-11 18:16:19 -04:00
Dan Stillman
c89df69f14 Update version 2018-03-11 04:37:11 -04:00
Dan Stillman
71bee9cfe7 Update locales from Transifex 2018-03-11 03:01:21 -04:00
Dan Stillman
92dcd41a0a Remove obsolete line from Babel config
The whole resource/tinymce directory is symlinked now.
2018-03-11 01:42:34 -05:00
Dan Stillman
90b975445e Fix note display in read-only libraries and classic citation dialog 2018-03-11 01:41:16 -05:00
Dan Stillman
877ff972a9 Select created items when importing RIS/BibTeX from connector 2018-03-11 01:20:40 -05:00
Dan Stillman
137a0ebbfd Don't delete automatic tags after web translation
Regression from c8cf9b9e6f, probably
2018-03-10 11:18:44 -05:00
Dan Stillman
53bea1fb9f Update version 2018-03-10 11:15:05 -05:00
Dan Stillman
3dcdd7f2ba Safer fix for external note focus issues
Follow-up to 96e96c0348 and 07efe0304e
2018-03-09 15:15:18 -05:00
Dan Stillman
07efe0304e Fix separate-window button for child notes (broken by 96e96c0348) 2018-03-09 15:01:33 -05:00
Dan Stillman
efe5d8669c Update locales from Transifex 2018-03-09 14:35:41 -05:00
Dan Stillman
5f6de1c787 Update submodules 2018-03-09 14:35:41 -05:00
Dan Stillman
96e96c0348 Focus correct note window when multiple note windows are open
Probably broken by dcfaa5521e
2018-03-09 14:26:18 -05:00
Dan Stillman
6745ea9868 Fix multiple-item context menu (broken by 7e3bad7390) 2018-03-09 06:58:46 -05:00
Dan Stillman
6c8cf71c9a Select parent item after metadata retrieval if only PDF was selected
TODO: select all parents
2018-03-09 06:57:43 -05:00
Dan Stillman
16ad422e89 Fix cut-off top line of search bar on Windows 2018-03-09 06:35:22 -05:00
Dan Stillman
da30f9e25c Restore attachment title and filename when undoing Retrieve Metadata 2018-03-09 01:07:57 -05:00
Dan Stillman
f5a2534169 Update locales from Transifex 2018-03-08 22:03:59 -05:00
Dan Stillman
81a75d79be Keep PDF handler menulist the right size 2018-03-08 22:03:59 -05:00
Adomas Venčkauskas
f871da17c8 Don't hide the automatic citation updates option in doc prefs 2018-03-08 22:23:59 +02:00
Dan Stillman
2f7c93a4e6 Merge new English strings 2018-03-08 04:07:01 -05:00
Dan Stillman
23ebdedf48 Allow a day for undo/report for metadata retrieval, not an hour 2018-03-08 04:06:30 -05:00
Dan Stillman
7e3bad7390 "Undo Retrieve Metadata" and "Report Inaccurate Metadata"
New context menu options for items that were recognized in the last day
and that haven't been modified
2018-03-08 03:53:39 -05:00
Adomas Venčkauskas
cc5c17a6e1 Fix embedded items not showing up in the citation dialog.
Regression in 2ad0dc00d, bad fix in 553d2b00. Closes #1448
2018-03-08 10:25:00 +02:00
Dan Stillman
c7d05ad2f9 Use 'paste' plugin from TinyMCE 4.6.7 to fix drag and drop cursor issue
https://forums.zotero.org/discussion/70720/after-copy-by-dragging-the-cursor-disappears
2018-03-08 00:17:16 -05:00
Dan Stillman
438d9669c1 Fix font size scaling of tag selector tags on macOS and Linux 2018-03-07 22:16:18 -05:00
Dan Stillman
ea569b8b1a Prevent note font size pref from being cut off 2018-03-07 04:52:52 -05:00
Dan Stillman
37eb26df36 Merge new English strings 2018-03-07 04:52:52 -05:00
Dan Stillman
647463cc61 Reorganize file prefs and add UI for choosing PDF handler
- Add a File Handling section to the General pane of the preferences,
  and move several prefs from Miscellaneous to there
- Add a UI to that section for choosing the PDF handler
- Update Zotero.launchFileWithApplication() to use /usr/bin/open on
  macOS if the handler is an .app (which it will be if set through the
  chooser, since it's limited to Mac applications, though an executable
  can still be set via the hidden pref if desired for some reason)
- Move prefs for style/translator updates and translator error reporting
  (which isn't particularly relevant anymore in the client) to Advanced

By default the PDF handler chooser says "System Default", which isn't
the nicest thing, and there's probably a way to get/guess the system
default, since Firefox seems to know it (at least on macOS), but I
couldn't quickly find a way to do it.

Addresses #1450
2018-03-07 04:52:51 -05:00
Adomas Venčkauskas
079cb258f4 Fix unloaded citation items being serialized with string IDs 2018-03-06 11:46:49 +02:00
Dan Stillman
b25d99c241 Only show Quick Copy menu option when appropriate 2018-03-06 04:38:45 -05:00
Dan Stillman
c4a64216ce Reduce idle CPU use to ~0%
Various animated things (search spinner, progress meters) were using CPU
even when hidden, either because they weren't properly hidden
(equivalent of `display: none` rather than `visibility: hidden`) or
because of bizarre Firefox bugs with progress meters on Linux.

Addresses #1455
2018-03-06 04:19:01 -05:00
Dan Stillman
da09a3bb96 Allow custom handler for PDF files
Configurable via extensions.zotero.fileHandler.pdf hidden pref for now,
though we'll probably make it a visible pref.

We also appear to have been doing blocking launches when launch()
failed, which may have been causing UI hangs when opening files on some
Linux installations. (I'm not sure if that's an issue with recent
Firefox builds. launch() works on Ubuntu 17.10.) All launches are now
async.

This is a rewritten version of PR #1450 by @ehhc.

Closes #1450
2018-03-05 19:36:50 -05:00
Dan Stillman
f6926ccda6 Additional debugging lines for connector sessions 2018-03-05 18:10:17 -05:00
Dan Stillman
51ddc46322 Update translators 2018-03-05 18:10:17 -05:00
Dan Stillman
de4aa4c240 Update locales from Transifex 2018-03-05 18:08:22 -05:00
Dan Stillman
23bbb5a1e5 Make atob() available in translator sandbox
Addresses zotero/translators#1576
2018-03-05 18:08:22 -05:00
Dan Stillman
87efe75e24 Fix blue toolbar background on Windows with DWM compositor
Applies to Windows 7 with Aero effects and later Windows versions
2018-03-05 17:18:12 -05:00
Adomas Venčkauskas
381351b433 Fix an error being logged when window to activate is gone on Linux 2018-03-05 14:28:07 +02:00
Adomas Venčkauskas
13766e1f6c Show delayed citations checkbox in doc prefs dialog after initialization
Closes #1458
2018-03-05 14:26:57 +02:00
Adomas Venčkauskas
5a1cf3ab02 Fix dead integration field object function calls 2018-03-05 13:46:59 +02:00
Dan Stillman
3f905be77e Reduce delayed-citations timeout to 3 seconds
5 seconds is too long
2018-03-05 02:16:55 -05:00
Dan Stillman
44632e2218 Make the Windows UI somewhat less unpleasant
- Get rid of the hideous blue effect in the menubar and toolbar
- Slim down splitters
- Get rid of awful grippies except when collapsed (and make them
  slightly less ugly in the collapsed state)
- Reduce spacing of tags in tag selector
- Tweak twisty spacing
- Get rid of stray line to the right of item pane tabs

We may still want to get rid of the menubar (#366), and we can do better
than the ugly gray backgrounds (#365), but this is a start.

I'm not sure if everything will look the same on all Windows versions,
so this will need a little testing.

Closes #367
2018-03-02 20:28:51 -05:00
Dan Stillman
724329d948 Prevent Undo from clearing existing note
When loading the first note of a session in the right-hand pane or when
loading a note in the separate window, if you made a change and then
pressed Undo twice (or maybe only once in some situations), it could
undo to empty (though you could usually Redo to restore it).
2018-03-01 22:41:33 -05:00
Dan Stillman
bf122add81 Remove special handling of Cmd-Shift-Z in notes
No longer relevant in standalone app
2018-03-01 22:41:33 -05:00
Dan Stillman
7a15c88972 Symlink TinyMCE directory during build 2018-03-01 22:41:33 -05:00
Dan Stillman
e8403b9679 Update TinyMCE to 4.7.9 2018-03-01 22:41:28 -05:00
Dan Stillman
1f320e1f5d Be more lenient about Extra field values than citeproc-js
Allow fields like "Original Date: 2018" and convert them to
"original-date: 2018" when sending to citeproc-js.

For reference:

http://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#cheater-syntax-for-odd-fields
2018-03-01 17:05:24 -05:00
Dan Stillman
e1e5178869 Shorten auto-renaming pref
So we now have autoRecognizeFiles and autoRenameFiles.
2018-03-01 04:09:17 -05:00
Dan Stillman
6e4d6a2820 Disable background full-text processing during tests 2018-03-01 03:45:58 -05:00
Dan Stillman
18d0e980d1 Increase Travis test timeout to 15 seconds
Though it probably won't help, since I imagine most timeouts are hangs
caused by race conditions
2018-03-01 03:38:46 -05:00
Dan Stillman
7e290b262c Fix bug in 97661539dc 2018-03-01 03:04:25 -05:00
Dan Stillman
0d4fd26be9 Merge new English string 2018-03-01 02:03:00 -05:00
Dan Stillman
00b2823612 Avoid repeating line in Zotero.Fulltext.indexItems() 2018-03-01 01:28:28 -05:00
Dan Stillman
b50b055e44 Wait for DB transaction to end before indexing string
This might help avoid timeouts during indexing.

Addresses #1284
2018-03-01 01:28:28 -05:00
Dan Stillman
1cfc90186f Serialize attachment indexing
Add newly added attachments to a queue, start processing it after five
seconds have passed since the last attachment was added, and process
another every half second after that unless another is added.

This queue won't survive a restart, so the queue should really be in the
DB, but this should avoid problems when adding multiple attachments at
once.

Addresses #1284
2018-03-01 01:28:28 -05:00
Dan Stillman
26dcaad76a Don't show Reindex button for files that aren't PDFs or text files 2018-03-01 01:28:28 -05:00
Dan Stillman
97661539dc Automatically retrieve metadata when saving PDFs
Applies to dragging to the collections pane or the items pane, adding
via New Item menu, or saving via the connector server

If the renaming pref is enabled, the PDF is renamed after recognition.

Can be disabled in the preferences

Closes #917
2018-03-01 01:28:28 -05:00
Dan Stillman
081793f72f Disable auto_focus for TinyMCE
TinyMCE triggers a focus on the window itself, which means that the
window steals focus when an attachment is created even if another Zotero
window is in front. Instead, do a manual focus in the external note
window, which is the one place I could find where we rely on it (though
there might be others).
2018-03-01 01:20:29 -05:00
Dan Stillman
3cc92fe1e9 Fix PDF recognition within collection (broken by f8b41c971c) 2018-03-01 01:20:29 -05:00
Dan Stillman
f99038fd37 Fix recognizer dialog with multiple tests
Do cleanup on 'unload' rather than 'close' (which is limited to a click
on the close button and doesn't get called for win.close()) and clear
the queue after each test.
2018-03-01 01:20:29 -05:00
Dan Stillman
757709cf43 Fix auto-rename after PDF recognition (wrong pref in f8b41c971) 2018-03-01 01:20:29 -05:00
Dan Stillman
da49f9900f Add 'window' getter to CollectionTreeView and ItemTreeView 2018-03-01 01:20:29 -05:00
Dan Stillman
aff2bef3f5 Fix scope of Zotero_RecognizePDF_Dialog
When Window is accessed from another scope (e.g., ItemTreeView), the
`let` somehow prevents Zotero_RecognizePDF_Dialog from showing up on the
Window object like all the other objects loaded via <script>.
2018-03-01 01:19:30 -05:00
Dan Stillman
16f228b2a3 Update renamed styles 2018-02-28 04:31:22 -05:00
Dan Stillman
153ed728cb Update citeproc-js to 1.1.190 2018-02-28 04:31:22 -05:00
Dan Stillman
7210653c0b Update version 2018-02-28 04:31:22 -05:00
Dan Stillman
e03ecfae98 Update locales from Transifex and merge new English strings 2018-02-28 02:52:52 -05:00
Dan Stillman
1d367f0165 Fix await->yield in f8b41c971 2018-02-27 19:24:28 -05:00
Dan Stillman
f8b41c971c Automatic attachment file renaming changes
Automatic renaming is now done for dragging of an external file onto an
item (as added in 7cb95f41) as well as dragging as a linked file,
dragging a non-native (not text or HTML) URL, "Attach Link to File…",
"Attach Stored Copy of File…", and "Retrieve Metadata for PDF". It only
applies if a single file is being added and if the parent item has no
non-HTML attachments. By default, the renaming only applies to PDFs, but
this can be changed with the renameAttachmentFiles.automatic.fileTypes
hidden pref.

A new General pref, "Automatically rename attachment files using parent
metadata", controls whether the renaming happens.

Files saved via web translators are renamed regardless of this pref,
because they would often be gibberish otherwise.

Closes #113
2018-02-27 18:45:03 -05:00
Dan Stillman
68879a0ffc Replace some nsIWindowMediator getService() calls with Services.wm 2018-02-27 18:44:51 -05:00
Dan Stillman
b0d9a9c82e Don't use hidden browser to download images 2018-02-27 16:05:35 -05:00
Dan Stillman
f5b1ee44f3 Add Zotero.File.rename() (extracted from Zotero.Item::renameAttachmentFile()) 2018-02-27 14:24:26 -05:00
Dan Stillman
b5cc0f918a Remove Fx54 test from Travis config 2018-02-25 21:54:23 -05:00
Dan Stillman
c0b63e5928 Better handling of 403 for attachment metadata upload
Check file-editing access for the group from the API before offering to
reset, update the filesEditable setting properly, and restart the sync
automatically after resetting.
2018-02-23 17:59:32 -05:00
Dan Stillman
5ed10c6ba2 Make Esc in PDF recognizer window close rather than minimize if done 2018-02-23 02:38:01 -05:00
Dan Stillman
7c64666167 Use separate subdomain for recognizer requests for now 2018-02-22 02:01:05 -05:00
Dan Stillman
c28c8c820b Improved PDF recognizer and 1x PDF icons 2018-02-22 01:04:11 -05:00
Dan Stillman
3fbb4633a6 Update locales from Transifex and merge new English strings 2018-02-22 01:04:07 -05:00
Dan Stillman
9ce9502b76 Tweak recognizer strings 2018-02-22 01:04:00 -05:00
Dan Stillman
a8312acfe0
Merge pull request #1420 from mrtcode/recognizer
New PDF recognizer
2018-02-21 13:59:50 -05:00
Dan Stillman
9cb2b8167d Throw actual error if file open fails in md5Async() 2018-02-21 10:22:59 -05:00
Dan Stillman
164fea0cec Updated journal article icon with fixed transparency 2018-02-20 12:19:43 -05:00
Martynas Bagdonas
991a50d090 New PDF recognizer 2018-02-20 09:32:06 +02:00
Dan Stillman
ce0dd5cc5e Update citeproc-js to 1.1.186 2018-02-19 07:23:38 -05:00
Dan Stillman
55b30dd08c Update locales from Transifex and merge new English strings 2018-02-18 16:11:07 -05:00
Dan Stillman
7fed34fa30 Hide Accessed row in attachment box when provided
If, say, a link without an access date is created via an import
2018-02-18 15:24:28 -05:00
Dan Stillman
30b59ebc95 Show full URL in tooltip when hovering over attachment URL 2018-02-18 15:24:08 -05:00
Dan Stillman
2200badef2 Nicer, localized messages for various field-too-long sync errors
Closes #351
Closes #1177
2018-02-18 15:21:13 -05:00
Dan Stillman
63f206c80e Hard-code strings and tweak styling for disabled word processor plugins
This will hopefully be both temporary and rare, so let's not bother
localizing htem.
2018-02-16 06:43:48 -05:00
Adomas Venčkauskas
628bc536a0 Add a button in preferences to enable word plugins if some are disabled 2018-02-16 11:39:48 +02:00
Adomas Venčkauskas
00e52ae62b Improves citation processing speed
Prevents from unnecesarily sending the same citation to citeproc
multiple times
2018-02-15 12:26:50 +02:00
Dan Stillman
bb925723fd Automatically set Referer for external attachment downloads
Rather than requiring translators to explicitly set a referrer, as
proposed in #772 and #1375, this simply sets it to the URL where the
save button was triggered. This fixes the Project Euclid example
in #772. It's possible it won't fix all cases, since the translator might
build the URL manually or via an intermediate page, but hopefully it
will fix the majority of cases.

I guess there's a possibility that this would break something that
currently works, but it's hard to imagine a site would block based on
the wrong referrer from the right site and not block on no referrer.

Unlike #1375, this doesn't bother with the referrer for native downloads
(e.g., snapshots or images). The former probably don't need it, and the
latter should probably be switched to use `saveURI()` anyway.

This might also fix zotero/translators#523 (SSRN) if the translator
allowed it.

Closes #1375
2018-02-15 02:13:49 -05:00
Sean Takats
081f6bc77d
New tag selector colors. 2018-02-14 13:59:37 +01:00
Sean Takats
d0aaa7165c
Adds 10 more HiDPI itemType icons. 2018-02-14 11:56:49 +01:00
Martynas Bagdonas
d810deaa9c Update PDF tools version and fix fulltext test 2018-02-14 04:00:02 -05:00
Sean Takats
a8c19b4d8b
Probably adds HiDPI/Retina icon for thesis. 2018-02-14 09:58:16 +01:00
Dan Stillman
942b8b91f3 Simplify error logging in translator architecture
Hopefully the previous stuff is no longer necessary
2018-02-13 19:26:10 -05:00
Dan Stillman
542584eed3 Make no-results error for search translation available as a property
This allows translation-server to return a specific error code when
there were no results.

Also clarify the debug logging for search translation.
2018-02-13 19:26:10 -05:00
Dan Stillman
4fdfdd341b Fix hang for invalid DOI in translation-server /search 2018-02-13 19:26:10 -05:00
Dan Stillman
a937c99f09 Return "Not Implemented" from server for 501, not "Method Not Implemented" 2018-02-13 19:26:10 -05:00
Dan Stillman
aedd361569 Fix startup error in new PDF code if application directory is read-only
In Firefox 52, `FileUtils.getFile()` tries to create the path to the
file even if the path array is empty. (In later versions (maybe in 54,
which we're using on macOS) it only does this if more than one path
component is provided.) We only need the special directory anyway, so we
can just use `getDir()`.
2018-02-13 19:26:10 -05:00
Adomas Venčkauskas
e72da417c0 Fix citeproc errors upon first interaction with the doc 2018-02-12 11:35:24 +02:00
Adomas Venčkauskas
8e2c47933a Fix noteIndex not being passed to citeproc. Closes #1439 2018-02-12 10:58:46 +02:00
Adomas Venčkauskas
966c293dea Add a pref to always show doc integration automatic updates option 2018-02-12 10:47:16 +02:00
Dan Stillman
f04a8c3736 Fix "channel is undefined" for invalid HTTP response during WebDAV sync
Follow-up to 4bbae6e17

We're now using Zotero.HTTP.request(), which does its own checking for
security errors, so there's no need to do WebDAV-specific checks (though
we could consider checking for Zotero.HTTP.SecurityError and showing
more specific messages, since a self-signed certificate is more likely
in the case of WebDAV).
2018-02-11 01:07:16 -05:00
Dan Stillman
4bbae6e17a Better handling of invalid HTTP responses
If a server returns an invalid HTTP response (e.g., Content-Encoding:
gzip with a plaintext body, a.k.a. NS_ERROR_INVALID_CONTENT_ENCODING)
but we can still parse a 4xx or 5xx HTTP response code, use that for the
XHR status, since it might be enough for what we need to do (e.g.,
verify a 404 from a WebDAV server). This fixes a current problem with
Box [1].

Also fix a "msg is not defined" error when the XHR status is 0 but the
SSL connection is fine and include the channel and response status on
the UnexpectedStatusException object (though both of these would now
only happen on an invalid 2xx response, when the XHR status would remain
as 0).

[1] https://forums.zotero.org/discussion/comment/301014/#Comment_301014
2018-02-10 22:18:45 -05:00
Dan Stillman
1aade0f268 Fix detection of WebDAV settings changes when clicking Verify Server
onchange() weirdly isn't called when clicking off the textbox to a button

Fixes #1291
2018-02-10 07:29:18 -05:00
Adomas Venčkauskas
9ed1792d9e Fix citation text editor (regression 09ceaa953) 2018-02-09 16:15:12 +02:00
Dan Stillman
e9a3f9b0e0 Show "More Information…" button on WebDAV SSL cert error
(Though the button takes you to to
https://www.zotero.org/support/kb/ssl_certificate_error rather than
https://www.zotero.org/support/kb/incomplete_cert_chain, since the error
comes from Zotero.HTTP.)
2018-02-09 02:39:34 -05:00
Dan Stillman
84d8bb4020 Fix "msg is not defined" on HTTP connection failure 2018-02-09 02:34:52 -05:00
Dan Stillman
ad53b3311e Update locales from Transifex and merge new English strings 2018-02-08 03:23:16 -05:00
Dan Stillman
bd7e1b222d Wait for any DB transactions to finish before starting sync
purgeDataObjects(), which runs at the beginning of the sync process,
uses transactions, and those can fail after the default wait timeout if
there's another active transaction. Instead, check explicitly for
another transaction and, if there is one, display a nice message and
wait for it to finish. This isn't foolproof, but it should reduce the
frequency of "operation timed out" sync errors. (Avoiding all long
transactions would be a better solution.)
2018-02-08 03:06:06 -05:00
Dan Stillman
3f6ecc0021 Fix "Can't queue event outside of a transaction"
If a transaction took over 30 seconds and another transaction timed out
waiting for it, the second transaction would reset the notifier queue,
but if the first transaction then tried to queue an event, it would fail
with this error and roll back. (It would be nice to figure out why
transactions are taking over 30 seconds, though.)
2018-02-08 02:07:44 -05:00
Dan Stillman
80cfd609ea Add sessionID option to translate() that gets passed to the ItemSaver
This will allow the connector to send a sessionID with a save request.
2018-02-07 04:04:37 -05:00
Dan Stillman
c8cf9b9e6f Support for connector-based save target selection
- Updates /saveItems and /saveSnapshot to take a sessionID
- Provides a list of editable collections in the current library
- Adds an /updateSession method that takes a sessionID and updates the
  collection and tags of any items saved via that operation (and changes
  the currently selected collection)

Cross-library changes are not yet supported
2018-02-07 04:04:37 -05:00
Dan Stillman
4731b8f905 Remove fail() override in Mocha
I'm not sure what this was for, but at least with an async test function
it seems to be causing spurious "the string 'x' was thrown, throw an
Error :)" messages that hide the real error.
2018-02-07 04:04:37 -05:00
Dan Stillman
45ddf9827c Reset PDF tools path for tests in resetDB()
And include path on error when running PDF tool
2018-02-07 04:04:37 -05:00
Dan Stillman
06fb74aafd Use rowid to clean invalid entries from translatorCache
Since in truly weird cases [1] fileName might not exist

https://forums.zotero.org/discussion/comment/300558/#Comment_300558
2018-02-06 23:46:17 -05:00
Adomas Venčkauskas
46854b6b23 Amend comment regarding citationsByIndex keys sort order 2018-02-06 16:08:06 +02:00
Adomas Venčkauskas
4add3ec44c Fix field type changes triggering citation modified prompts 2018-02-06 15:19:08 +02:00
Adomas Venčkauskas
09ceaa953b Fix some more citation update bugs 2018-02-06 15:09:20 +02:00
Adomas Venčkauskas
edc18a4fe4 Remove LibreOffice specific setText() calls (addressed within plugin) 2018-02-05 14:15:51 +02:00
Dan Stillman
38411fb56c Allow dragging parent items to collection if children are selected
This is a simplified version of the fix from #872. Unlike the proposal
in #36, this doesn't require all child items to be selected, since in a
search some children might be grayed out. If the child of an unselected
parent item is included, the drag isn't allowed.

Closes #36
2018-02-03 04:15:09 -05:00
Dan Stillman
ad216bcf97 Allow parentItemID as a createDataObject() parameter in tests
Not just `parentID`
2018-02-03 04:15:09 -05:00
Adomas Venčkauskas
948a4dda64 Fix citation preview citeproc error. Closes #1430 2018-01-31 14:13:32 +02:00
Adomas Venčkauskas
97a045fe0f Fix slow display of citation dialog on first interaction with doc 2018-01-31 12:59:54 +02:00
Dan Stillman
b7a24a58fb Use temp directory for recognizePDFCache.txt 2018-01-31 05:35:13 -05:00
Dan Stillman
31f0f0d210 Fix "Retrieve Metadata for PDF" with bundled PDF tools 2018-01-31 05:34:22 -05:00
Dan Stillman
2d43518ef2 Reduce minimum width of Zotero pane to 670px
We could potentially do more, but this is the narrowest the pane can be
with the left pane collapsed without starting to hide toolbar icons, so
let's try this. (This will still hide the sync icon if the left pane
isn't collapsed.)

Closes #675
Closes #1183
2018-01-30 19:12:05 -05:00
Dan Stillman
e4a399671a Don't show Title in column picker, because hiding it doesn't make sense 2018-01-30 19:11:47 -05:00
Dan Stillman
2219bd9861 Update locales from Transifex 2018-01-29 17:38:56 -05:00
Dan Stillman
eca9d82aa2 Update submodules 2018-01-29 17:22:35 -05:00
Dan Stillman
4a41f219d8 Update citeproc-js to 1.1.183 2018-01-29 17:21:16 -05:00
Dan Stillman
5b46735204 Fix test breakage from d67c654245 2018-01-26 04:24:34 -05:00
Dan Stillman
c5fa1303e3 Prompt to reset local group files on 403 for file attachment upload
And reset modified file attachments when resetting files
2018-01-26 03:37:57 -05:00
Dan Stillman
d67c654245 Add version option to toResponseJSON() 2018-01-26 03:37:57 -05:00
Dan Stillman
3390f2405b Warn about version 0 when saving to sync cache
This should be an error, because the API will never return 0, but most
tests don't currently set the version properly in the response JSON.
2018-01-26 03:37:57 -05:00
Adomas Venčkauskas
d857a813b9 Fix integration test errors. Closes #1426 2018-01-25 12:48:12 +02:00
Adomas Venčkauskas
fe4b75758d Fixes index is not defined error. Closes #1422 2018-01-25 12:39:50 +02:00
Adomas Venčkauskas
2523bca659 Fix slow citation dialog initialization
Report: https://forums.zotero.org/discussion/69929/beta-delay-citation-feature
2018-01-25 12:35:02 +02:00
Adomas Venčkauskas
8544618445 Fix footnote edit crashing in MacWord 2018-01-24 16:00:24 +02:00
Dan Stillman
0bb0912a7b Enable 2x PDF icon 2018-01-24 06:15:23 -05:00
Adomas Venčkauskas
d41c8d6489 Fix an integration error when bibl present without citations 2018-01-22 13:26:43 +02:00
Adomas Venčkauskas
553d2b00d8 Fix 'id must be a positive integer' integration error
Report: https://forums.zotero.org/discussion/comment/298804#Comment_298804
2018-01-22 13:25:11 +02:00
Dan Stillman
a1b6f072c1 Run deploys from Travis on *-hotfix branches 2018-01-19 23:46:59 -05:00
Dan Stillman
2194dff7a4 Fix startup hang if note is null in database 2018-01-19 12:57:57 -05:00
Dan Stillman
ca9a7c685e Merge pull request #1417 from mrtcode/pdftools-test
Fix PDF tools usage in tests
2018-01-18 19:24:29 -05:00
Dan Stillman
37eb597ee8 Cache PDF tools in the source directory between test runs
This avoids having to download a 13 MB file on every test run.
2018-01-18 19:24:09 -05:00
Dan Stillman
db2ddfd493 Fix Zotero.Fulltext.setItemContent() test 2018-01-18 18:55:24 -05:00
Martynas Bagdonas
723b4d32e5 Fix Zotero.Fulltext tests 2018-01-18 18:54:54 -05:00
Martynas Bagdonas
5815088586 Fix PDF tools usage in tests 2018-01-18 12:23:27 +02:00
Dan Stillman
5d39221afe
Merge pull request #1415 from mrtcode/pdftools
Bundled PDF tools
2018-01-18 00:20:46 -05:00
Martynas Bagdonas
77ffc6ad5d Deleted strings that are no longer in use 2018-01-17 13:33:18 +02:00
Martynas Bagdonas
0d5ea8520a Use the bundled PDF tools 2018-01-17 13:33:18 +02:00
Dan Stillman
e3dee4dee7 Update translators 2018-01-17 01:15:31 -05:00
Dan Stillman
9a9a4bf9e9 Update version 2018-01-17 01:14:42 -05:00
Dan Stillman
d662a828d1 Update locales from Transifex and merge new English strings 2018-01-16 13:25:23 -05:00
Dan Stillman
48757f08ff Add missing spinners from 87cf336e2 2018-01-16 12:53:31 -05:00
Dan Stillman
17d4515002 Tweak padding in doc prefs 2018-01-16 12:53:31 -05:00
Dan Stillman
e4cc28ac60 Tweak wording of delayed-citations mode
"Delay citation updates until manual refresh" would be fine in the doc
prefs, but "until manual refresh" is a bit too awkward for the prompt.
Instead, go with @rmzelle's suggestion from #1242 of referring to
"automatic citation updates", and invert the checkbox.
2018-01-16 12:53:12 -05:00
Dan Stillman
cc9efde843 Fix translator architecture hangs on bad JSON in translatorCache 2018-01-16 11:31:34 -05:00
Dan Stillman
2b8311d3d7 Start sync icon spinning before purging data 2018-01-16 09:12:14 -05:00
Dan Stillman
0cd50b5560
Merge pull request #1242 from adomasven/feature/delay-updating-citatations
Refactor integration and delay citation updates
2018-01-16 09:11:21 -05:00
Adomas Venčkauskas
2b27e40308 Refresh citation text upon citation insertion 2018-01-16 14:04:13 +02:00
Adomas Venčkauskas
b985ef8a53 Ensure delayed citation styling is removed upon proper update
Ensure delayed citation styling is removed upon proper update
2018-01-16 14:04:10 +02:00
Adomas Venčkauskas
4b78ebcd72 Pause the document update timer during warning dialogs 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
47744e4ccd Simplify field handling
Don't use field.writeToDoc(), because it prevents from optimizing
libreoffice text writes
2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
2ad0dc00da Add session.getItems in preparation for document collections 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
f8f403eca4 Display a refresh notice instead of bibliography when citing delayed 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
2827f70daa Option to delay updating citation in document.
The checkbox in doc prefs is hidden until an update takes 5s or longer
after which the user is prompted to enable delaying.
2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
6d05c3472b Add more integration tests 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
5805c7e562 Integration refactor megacommit
- Removed obsolete logic for citation.properties.deleted, which is no
  longer set anywhere
- Introduced a bibliography class
- BibliographyEditInterface no longer edits state
- Fields._processFields() now has linear flow because:
- Exception handling for missing items and corrupt fields reworked to be
  handled in relevant Field classes, so that the flow remains linear
- Document modifying functions (i.e. Fields.updateDocument()) now only
  called in Zotero.Integration.Interface functions instead of all over
  the place
- document.setDocPrefs() now called after every execCommand() since
  the cost is trivial, but that simplifies a bunch of logic
- Misc code cleanup

TODO at some point in the future:
- Move Integration.(init/delete)Pipe out
- Decouple references and clarify functions in Integration.Fields and
  Integration.Session
2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
f44d563a15 Add Zotero.Integration.Citation
- Moves a bunch of citation related processing from Integration.Session
- Replaces missing item handling with a function instead of exception
- Solves some really confusing flow issues in _processFields
2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
a1acbd4038 Make current session globally available
Decoupling! Sessions can be instantiated without
Zotero.Integration.Interface
2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
54b4ec6f5c Fix bugs for style-switching and footnote citations 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
4159c63a3b Refactor CitationEditInterface
This is the first really big change that detangles UI stuff from
directly changing state. io.citation is no longer tied to the citation
loaded in the session in any way and CitationEditInterface does not
write anything to session or document. All writes are handled in
Fields.addEditCitation()
2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
41db61ecb9 Move citation unserialization
Using new es6 class syntax because getters/setters don't
retain `this` context with Zotero.extendClass and we're building
with at least FX45 on every platform now where the syntax is supported
2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
52fd0d992d Add a wrapper class for citation and bibliography fields 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
41c93ab034 Rename Integration.Document to Integration.Interface 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
18e4e37b32 Remove integration plugin version checks 2018-01-03 14:27:16 +02:00
Adomas Venčkauskas
430f58d3c4 Moves utilities code away from Zotero.Integration 2018-01-03 14:27:16 +02:00
385 changed files with 17653 additions and 64994 deletions

View File

@ -5,7 +5,6 @@
"ignore": [
"resource/require.js",
"chrome/content/zotero/include.js",
"resource/tinymce/tinymce.js",
"chrome/content/zotero/xpcom/citeproc.js",
"resource/csl-validator.js",
"resource/react.js",

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
node_modules
build
.signatures.json
tmp

6
.gitmodules vendored
View File

@ -1,18 +1,24 @@
[submodule "translators"]
path = translators
url = git://github.com/zotero/translators.git
branch = master
[submodule "chrome/content/zotero/locale/csl"]
path = chrome/content/zotero/locale/csl
url = git://github.com/citation-style-language/locales.git
branch = master
[submodule "styles"]
path = styles
url = git://github.com/zotero/bundled-styles.git
branch = master
[submodule "test/resource/chai"]
path = test/resource/chai
url = https://github.com/chaijs/chai.git
branch = master
[submodule "test/resource/mocha"]
path = test/resource/mocha
url = https://github.com/mochajs/mocha.git
branch = master
[submodule "test/resource/chai-as-promised"]
path = test/resource/chai-as-promised
url = https://github.com/domenic/chai-as-promised.git
branch = master

View File

@ -10,7 +10,6 @@ env:
global:
secure: "NxvkbZ7/Op7BTGQRR3C4q8lLoO29f8WtyNN27NSH7AO3H0vBr1Vp5xO8gn+H2qHEug5HvM+YrZ/xAkNXaZVbOInmBmKVMxqVvdpKp9JM1Amf+gzsXWQphfySvs6iqzyP6cwU/jspdvX/WSakgU5v7PWXxtUIaKxdANt6Rw7W+Pc="
matrix:
- FX_VERSION="54.0"
- FX_VERSION="52.0.3"
matrix:
fast_finish: true
@ -26,19 +25,15 @@ install:
- if [ $FX_VERSION = "52.0.3" ]; then
wget -O tarball "https://archive.mozilla.org/pub/firefox/tinderbox-builds/mozilla-release-linux64-add-on-devel/1491732920/firefox-52.0.3.en-US.linux-x86_64-add-on-devel.tar.bz2";
fi
- if [ $FX_VERSION = "54.0" ]; then
wget -O tarball "https://archive.mozilla.org/pub/firefox/tinderbox-builds/mozilla-release-linux64-add-on-devel/1496944705/firefox-54.0.en-US.linux-x86_64-add-on-devel.tar.bz2";
fi
- tar xf tarball
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- npm i
- npm run build
- if [ $FX_VERSION = "54.0" ] &&
[ $TRAVIS_REPO_SLUG = "zotero/zotero" ] &&
[ $TRAVIS_BRANCH = "master" ] &&
[ $TRAVIS_PULL_REQUEST = "false" ]; then
- if [[ $TRAVIS_REPO_SLUG = "zotero/zotero" &&
($TRAVIS_BRANCH = "master" || $TRAVIS_BRANCH = *-hotfix) &&
$TRAVIS_PULL_REQUEST = "false" ]]; then
mkdir build-zip;
cd build;
zip -r ../build-zip/$TRAVIS_COMMIT.zip *;

View File

@ -2,9 +2,9 @@
## Bug Reports and Feature Requests
In order to keep product discussions open to as many people as possible, Zotero does not use GitHub Issues for bug reports or feature requests. Please use the [Zotero Forums](https://forums.zotero.org) to report problems and suggest changes.
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. Keeping product discussions in the Zotero Forums allows the entire Zotero community to participate, including domain experts that can address many questions better than Zotero developers.
For confirmed bugs or agreed-upon changes, new issues will be created in the relevant repositories on GitHub by Zotero developers.
For confirmed bugs or agreed-upon changes, Zotero developers will create new issues in the relevant repositories.
## Working with Zotero Code

View File

@ -1,8 +1,11 @@
Zotero is Copyright © 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
Zotero is Copyright © 2018 Corporation for Digital Scholarship,
Vienna, Virginia, USA http://digitalscholar.org
Copyright © 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
Roy Rosenzweig Center for History and New Media, George Mason University,
Fairfax, Virginia, USA http://zotero.org
The Roy Rosenzweig Center for History and New Media distributes the Zotero source code
The Corporation for Digital Scholarship distributes the Zotero source code
under the GNU Affero General Public License, version 3 (AGPLv3). The full text
of this license is given below.

View File

@ -14,6 +14,10 @@ body[multiline="true"] {
width: 800px;
}
#quick-format-dialog.progress-bar #quick-format-deck {
height: 37px;
}
#quick-format-search {
background: white;
-moz-appearance: searchfield;

View File

@ -33,6 +33,6 @@ textbox
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
@media (min-resolution: 1.5dppx) {
@media (min-resolution: 1.25dppx) {
.creator-type-label > image { list-style-image: url('chrome://zotero/skin/mac/arrow-down@2x.png'); }
}

View File

@ -491,7 +491,7 @@ treechildren::-moz-tree-image {
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
@media (min-resolution: 1.5dppx) {
@media (min-resolution: 1.25dppx) {
.zotero-tb-button,.zotero-tb-button:first-child,.zotero-tb-button:last-child { background: url("chrome://zotero/skin/mac/menubutton-end@2x.png") right center/auto 24px no-repeat; }
.zotero-tb-button > .toolbarbutton-icon { background: url("chrome://zotero/skin/mac/menubutton-start@2x.png") left center/auto 24px no-repeat; }
.zotero-tb-button:-moz-window-inactive > .toolbarbutton-icon { background: url("chrome://zotero/skin/mac/menubutton-start-inactive-window@2x.png") left center/auto 24px no-repeat; }

View File

@ -173,7 +173,7 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
}
@media (min-resolution: 1.5dppx) {
@media (min-resolution: 1.25dppx) {
#zotero-pane .toolbarbutton-icon {
width: 16px;
}
@ -228,8 +228,14 @@ tab {
#zotero-prefs .numberbox-input-box{
-moz-appearance: textfield;
}
/* Grippy icon missing anyway */
#zotero-pane splitter{
width: 6px;
#zotero-prefs #noteFontSize {
min-width: 3.8em;
}
#zotero-pane splitter {
border: 0;
width: 6px;
background-color: transparent;
background-image: none;
}

View File

@ -1,3 +1,18 @@
:root {
--theme-border-color: #cecece;
}
/* Hide horrible blue effect for menu bar and toolbar */
#navigator-toolbox {
-moz-appearance: none;
}
#zotero-pane #zotero-toolbar {
-moz-appearance: none !important;
margin-top: -3px;
border-bottom-color: var(--theme-border-color);
}
/*
As of Fx36, the built-in styles don't properly handle a menu-button within combined buttons.
@ -176,7 +191,7 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
/* End toolbar buttons */
@media (min-resolution: 1.5dppx) {
@media (min-resolution: 1.25dppx) {
#zotero-toolbar .toolbarbutton-icon {
width: 16px;
}
@ -221,13 +236,44 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
padding-left: 2px;
}
#zotero-toolbar:-moz-system-metric(windows-compositor) {
-moz-appearance: none !important;
background-image: -moz-linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
background-color: rgb(207, 219, 236) !important;
border-width: 0 0 1px 0;
border-style: solid;
border-color: #818790;
#zotero-collections-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed]),
#zotero-tags-splitter:not([state=collapsed]) {
border: 0;
background-color: transparent;
position: relative;
/* Positive z-index positions the splitter on top of its siblings and makes
it clickable on both sides. */
z-index: 1;
}
#zotero-collections-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed]):not([orient=vertical]),
#zotero-tags-splitter:not([state=collapsed]) {
border-inline-end: 1px solid var(--theme-border-color);
min-width: 0;
width: 3px;
margin-inline-start: -3px;
}
#zotero-tags-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed])[orient=vertical] {
border-block-end: 1px solid var(--theme-border-color);
min-height: 0;
height: 3px;
margin-block-start: -3px;
}
#zotero-collections-splitter > grippy,
#zotero-items-splitter > grippy,
#zotero-tags-splitter > grippy {
border: 0;
}
#zotero-collections-splitter:not([state=collapsed]) > grippy,
#zotero-items-splitter:not([state=collapsed]) > grippy,
#zotero-tags-splitter:not([state=collapsed]) > grippy {
display: none;
}
#zotero-collections-tree, #zotero-items-tree, #zotero-view-item {
@ -242,6 +288,10 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
-moz-border-left-colors: none;
}
treechildren::-moz-tree-twisty {
padding: 0 4px;
}
/* Undo tree row spacing change in Fx25 on Windows */
#zotero-collections-tree treechildren::-moz-tree-row,
#zotero-items-tree treechildren::-moz-tree-row,
@ -249,8 +299,8 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
height: 1.6em;
}
#zotero-collections-tree {
border-width: 0 1px 1px 0;
tree {
border-width: 0;
}
/* Restore row highlighting on drag over, though I'm not sure how we're losing it to begin with. */
@ -258,15 +308,31 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
background-color: Highlight;
}
#zotero-items-tree {
border-width: 0 1px;
#zotero-tag-selector groupbox {
-moz-appearance: none;
padding: 0;
border: 0;
}
#tags-box {
padding-top: 0.1em;
padding-left: 0.05em;
}
#tags-box button {
margin: .04em 0 0 .15em !important;
}
#zotero-editpane-tabs spacer {
border: 0;
}
#zotero-view-item {
padding: 0 !important;
-moz-appearance: none;
background-color: -moz-field;
border-width: 1px 0 0 1px;
border-width: 1px 0 0 0;
border-color: var(--theme-border-color);
}
#zotero-view-tabbox > tabs {
@ -275,10 +341,7 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
#zotero-item-pane-groupbox {
-moz-appearance: none !important;
border-radius: 0;
border-width: 0 0 0 1px;
border-color: #818790;
border-style: solid;
border-width: 0;
}
#zotero-editpane-item-box > scrollbox, #zotero-view-item > tabpanel > vbox,

View File

@ -1,4 +1,3 @@
button {
font-family: Segoe UI, sans-serif;
font-size: 1em;
}

View File

@ -146,8 +146,9 @@ var Zotero_File_Interface_Bibliography = new function() {
if(_io.useEndnotes && _io.useEndnotes == 1) document.getElementById("displayAs").selectedIndex = 1;
let dialog = document.getElementById("zotero-doc-prefs-dialog");
dialog.setAttribute('title', `${Zotero.clientName} - ${dialog.getAttribute('title')}`);
}
if(document.getElementById("formatUsing")) {
if (document.getElementById("formatUsing-groupbox")) {
if (["Field", "ReferenceMark"].includes(_io.primaryFieldType)) {
if(_io.fieldType == "Bookmark") document.getElementById("formatUsing").selectedIndex = 1;
var formatOption = (_io.primaryFieldType == "ReferenceMark" ? "referenceMarks" : "fields");
document.getElementById("fields").label =
@ -158,6 +159,10 @@ var Zotero_File_Interface_Bibliography = new function() {
Zotero.getString("integration."+formatOption+".fileFormatNotice");
document.getElementById("bookmarks-file-format-notice").textContent =
Zotero.getString("integration.fields.fileFormatNotice");
} else {
document.getElementById("formatUsing-groupbox").style.display = "none";
_io.fieldType = _io.primaryFieldType;
}
}
if(document.getElementById("automaticJournalAbbreviations-checkbox")) {
if(_io.automaticJournalAbbreviations === undefined) {
@ -166,12 +171,23 @@ var Zotero_File_Interface_Bibliography = new function() {
if(_io.automaticJournalAbbreviations) {
document.getElementById("automaticJournalAbbreviations-checkbox").checked = true;
}
document.getElementById("automaticCitationUpdates-checkbox").checked = !_io.delayCitationUpdates;
}
}
// set style to false, in case this is cancelled
_io.style = false;
});
this.openHelpLink = function() {
var url = "https://www.zotero.org/support/word_processor_plugin_usage";
var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
Zotero.launchURL(url);
};
/*
* Called when locale is changed
*/
@ -192,22 +208,18 @@ var Zotero_File_Interface_Bibliography = new function() {
//
// For integrationDocPrefs.xul
//
if (isDocPrefs) {
// update status of displayAs box based on style class
if(document.getElementById("displayAs-groupbox")) {
var isNote = selectedStyleObj.class == "note";
document.getElementById("displayAs-groupbox").hidden = !isNote;
var multipleNotesSupported = _io.supportedNotes.length > 1;
document.getElementById("displayAs-groupbox").hidden = !isNote || !multipleNotesSupported;
// update status of formatUsing box based on style class
if(document.getElementById("formatUsing")) {
if(isNote) document.getElementById("formatUsing").selectedIndex = 0;
document.getElementById("bookmarks").disabled = isNote;
document.getElementById("bookmarks-caption").disabled = isNote;
}
}
// update status of displayAs box based on style class
if(document.getElementById("automaticJournalAbbreviations-vbox")) {
document.getElementById("automaticJournalAbbreviations-vbox").hidden =
!selectedStyleObj.usesAbbreviation;
}
@ -266,6 +278,7 @@ var Zotero_File_Interface_Bibliography = new function() {
}
_io.useEndnotes = document.getElementById("displayAs").selectedIndex;
_io.fieldType = (document.getElementById("formatUsing").selectedIndex == 0 ? _io.primaryFieldType : _io.secondaryFieldType);
_io.delayCitationUpdates = !document.getElementById("automaticCitationUpdates-checkbox").checked;
}
// remember style and locale if user selected these explicitly
@ -283,8 +296,7 @@ var Zotero_File_Interface_Bibliography = new function() {
document.documentElement.getButton('cancel').click();
var win = Zotero.Utilities.Internal.openPreferences('zotero-prefpane-cite', { tab: 'styles-tab' });
if (isDocPrefs) {
// TODO: Move activate() code elsewhere
Zotero.Integration.activate(win);
Zotero.Utilities.Internal.activate(win);
}
};
}

View File

@ -239,6 +239,7 @@
if (this.displayURL) {
var urlSpec = this.item.getField('url');
urlField.setAttribute('value', urlSpec);
urlField.setAttribute('tooltiptext', urlSpec);
urlField.setAttribute('hidden', false);
if (this.clickableLink) {
urlField.onclick = function (event) {
@ -259,15 +260,22 @@
if (this.displayAccessed) {
this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
+ Zotero.getString('punctuation.colon');
this._id("accessed").value = Zotero.Date.sqlToDate(
this.item.getField('accessDate'), true
).toLocaleString();
let val = this.item.getField('accessDate');
if (val) {
val = Zotero.Date.sqlToDate(val, true);
}
if (val) {
this._id("accessed").value = val.toLocaleString();
accessed.hidden = false;
}
else {
accessed.hidden = true;
}
}
else {
accessed.hidden = true;
}
}
// Metadata for files
else {
urlField.hidden = true;

View File

@ -1087,12 +1087,17 @@
<method name="changeTypeTo">
<parameter name="itemTypeID"/>
<parameter name="menu"/>
<body>
<![CDATA[
<body><![CDATA[
return (async function () {
if (itemTypeID == this.item.itemTypeID) {
return true;
}
if (this.saveOnEdit) {
await this.blurOpenField();
await this.item.saveTx();
}
var fieldsToDelete = this.item.getFieldsNotInType(itemTypeID, true);
// Special cases handled below
@ -1149,15 +1154,15 @@
if (this.saveOnEdit) {
// See note in transformText()
this.blurOpenField().then(() => this.item.saveTx());
await this.blurOpenField();
await this.item.saveTx();
}
else {
this.refresh();
}
if (this.eventHandlers['itemtypechange'] && this.eventHandlers['itemtypechange'].length) {
var self = this;
this.eventHandlers['itemtypechange'].forEach(function (f) f.bind(self)());
this.eventHandlers['itemtypechange'].forEach(f => f.bind(this)());
}
return true;
@ -1169,8 +1174,8 @@
}
return false;
]]>
</body>
}.bind(this))();
]]></body>
</method>
@ -1269,6 +1274,7 @@
var valueElement = document.createElement("label");
}
valueElement.setAttribute('id', `itembox-field-value-${fieldName}`);
valueElement.setAttribute('fieldname', fieldName);
valueElement.setAttribute('flex', 1);
@ -1308,7 +1314,10 @@
if (date) {
// If no time, interpret as local, not UTC
if (Zotero.Date.isSQLDate(valueText)) {
date = Zotero.Date.sqlToDate(valueText);
// Add time to avoid showing previous day if date is in
// DST (including the current date at 00:00:00) and we're
// in standard time
date = Zotero.Date.sqlToDate(valueText + ' 12:00:00');
valueText = date.toLocaleDateString();
}
else {
@ -1421,6 +1430,7 @@
return (async function () {
Zotero.debug(`Showing editor for ${elem.getAttribute('fieldname')}`);
var label = Zotero.getAncestorByTagName(elem, 'row').querySelector('label');
var lastTabIndex = this._lastTabIndex = parseInt(elem.getAttribute('ztabindex'));
// If a field is open, hide it before selecting the new field, which might
@ -1485,6 +1495,7 @@
}
var t = document.createElement("textbox");
t.setAttribute('id', `itembox-field-textbox-${fieldName}`);
t.setAttribute('value', value);
t.setAttribute('fieldname', fieldName);
t.setAttribute('ztabindex', tabindex);
@ -1541,6 +1552,9 @@
var box = elem.parentNode;
box.replaceChild(t, elem);
// Associate textbox with label
label.setAttribute('control', t.getAttribute('id'));
// Prevent error when clicking between a changed field
// and another -- there's probably a better way
if (!t.select) {
@ -1614,8 +1628,8 @@
textbox.getAttribute('fieldname').split('-');
if (stayFocused) {
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
this._tabDirection = 1;
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex'));
this._tabDirection = false;
}
var creator = Zotero.Creators.get(creatorID);
@ -1767,6 +1781,7 @@
return (async function () {
Zotero.debug(`Hiding editor for ${textbox.getAttribute('fieldname')}`);
var label = Zotero.getAncestorByTagName(textbox, 'row').querySelector('label');
this._lastTabIndex = -1;
// Prevent autocomplete breakage in Firefox 3
@ -1957,6 +1972,9 @@
var box = textbox.parentNode;
box.replaceChild(elem, textbox);
// Disassociate textbox from label
label.setAttribute('control', elem.getAttribute('id'));
if (this.saveOnEdit) {
await this.item.saveTx();
}

View File

@ -152,28 +152,7 @@
let id = relatedItem.id;
let icon = document.createElement("image");
icon.className = "zotero-box-icon";
let type = Zotero.ItemTypes.getName(relatedItem.itemTypeID);
if (type=='attachment')
{
switch (relatedItem.attaachmentLinkMode) {
case Zotero.Attachments.LINK_MODE_LINKED_URL:
type += '-web-link';
break;
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
type += '-snapshot';
break;
case Zotero.Attachments.LINK_MODE_LINKED_FILE:
type += '-link';
break;
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
type += '-file';
break;
}
}
icon.setAttribute('src','chrome://zotero/skin/treeitem-' + type + '.png');
icon.setAttribute('src', relatedItem.getImageSrc());
var label = document.createElement("label");
label.className = "zotero-box-label";

View File

@ -410,15 +410,6 @@
}
switch (event.type) {
case 'keydown':
// Intercept and manually trigger redo for Cmd-Shift-Z,
// which keeps it from toggling the Zotero pane instead
if (Zotero.isMac && event.metaKey && event.shiftKey && !event.ctrlKey
&& !event.altKey && event.keyCode == 90) {
event.stopPropagation();
event.preventDefault();
this.redo();
return;
}
// Handle forward-delete, which doesn't register as a keypress
// when a selection is cleared
if (event.which == event.DOM_VK_DELETE) {
@ -609,19 +600,12 @@
</body>
</method>
<method name="redo">
<body>
<![CDATA[
this._editor.undoManager.redo();
]]>
</body>
</method>
<method name="clearUndo">
<body>
<![CDATA[
if (this._editor) {
this._editor.undoManager.clear();
this._editor.undoManager.add();
}
]]>
</body>
@ -707,10 +691,16 @@
}
if (self._value) {
self.value = self._value;
// Prevent undoing to empty note after initialization
self._editor.undoManager.clear();
self._editor.undoManager.add();
}
if (self._focus) {
setTimeout(function () {
self._iframe.focus();
self._editor.focus();
});
self._focus = false;
}

View File

@ -125,8 +125,7 @@ var Zotero_DownloadOverlay = new function() {
try {
if (item && item.getFile()) {
timer.cancel();
var recognizer = new win.Zotero_RecognizePDF.ItemRecognizer();
recognizer.recognizeItems([item]);
Zotero.RecognizePDF.recognizeItems([item]);
}
} catch(e) { dump(e.toSource()) };
}, 1000, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
@ -145,15 +144,7 @@ var Zotero_DownloadOverlay = new function() {
// to happen automatically
if(zoteroSelected) document.getElementById('rememberChoice').selected = false;
document.getElementById('rememberChoice').disabled = zoteroSelected;
// disable recognizePDF checkbox as necessary
if(!Zotero.Fulltext.pdfConverterIsRegistered()) {
document.getElementById('zotero-noPDFTools-description').hidden = !zoteroSelected;
document.getElementById('zotero-recognizePDF').disabled = true;
window.sizeToContent();
} else {
document.getElementById('zotero-recognizePDF').disabled = !zoteroSelected;
}
Zotero_DownloadOverlay.updateLibraryNote();
};
@ -212,9 +203,6 @@ var Zotero_DownloadOverlay = new function() {
recognizePDF.label = Zotero.getString("pane.items.menu.recognizePDF");
recognizePDF.hidden = false;
recognizePDF.disabled = true;
if(!Zotero.Fulltext.pdfConverterIsRegistered()) {
recognizePDF.checked = false;
}
}
};
}

View File

@ -43,7 +43,6 @@
<vbox style="margin-left: 15px">
<description id="zotero-saveToLibrary-description" style="font: small-caption; font-weight: normal" hidden="true">&zotero.downloadManager.saveToLibrary.description;</description>
<checkbox id="zotero-recognizePDF" hidden="true" persist="checked" disabled="true"/>
<description style="margin-left: 20px; font: small-caption; font-weight: normal" id="zotero-noPDFTools-description" hidden="true">&zotero.downloadManager.noPDFTools.description;</description>
</vbox>
</vbox>
</radiogroup>

View File

@ -23,6 +23,8 @@
***** END LICENSE BLOCK *****
*/
Components.utils.import("resource://gre/modules/osfile.jsm")
/****Zotero_File_Exporter****
**
* A class to handle exporting of items, collections, or the entire library
@ -206,13 +208,119 @@ var Zotero_File_Interface = new function() {
}
}
this.getMendeleyDirectory = function () {
Components.classes["@mozilla.org/net/osfileconstantsservice;1"]
.getService(Components.interfaces.nsIOSFileConstantsService)
.init();
var path = OS.Constants.Path.homeDir;
if (Zotero.isMac) {
path = OS.Path.join(path, 'Library', 'Application Support', 'Mendeley Desktop');
}
else if (Zotero.isWin) {
path = OS.Path.join(path, 'AppData', 'Local', 'Mendeley Ltd', 'Mendeley Desktop');
}
else if (Zotero.isLinux) {
path = OS.Path.join(path, '.local', 'share', 'data', 'Mendeley Ltd.', 'Mendeley Desktop');
}
else {
throw new Error("Invalid platform");
}
return path;
};
this.findMendeleyDatabases = async function () {
var dbs = [];
try {
var dir = this.getMendeleyDirectory();
if (!await OS.File.exists(dir)) {
Zotero.debug(`${dir} does not exist`);
return dbs;
}
await Zotero.File.iterateDirectory(dir, function* (iterator) {
while (true) {
let entry = yield iterator.next();
if (entry.isDir) continue;
// online.sqlite, counterintuitively, is the default database before you sign in
if (entry.name == 'online.sqlite' || entry.name.endsWith('@www.mendeley.com.sqlite')) {
dbs.push({
name: entry.name,
path: entry.path,
lastModified: null,
size: null
});
}
}
});
for (let i = 0; i < dbs.length; i++) {
let dbPath = OS.Path.join(dir, dbs[i].name);
let info = await OS.File.stat(dbPath);
dbs[i].size = info.size;
dbs[i].lastModified = info.lastModificationDate;
}
dbs.sort((a, b) => {
return b.lastModified - a.lastModified;
});
}
catch (e) {
Zotero.logError(e);
}
return dbs;
};
this.showImportWizard = function () {
var libraryID = Zotero.Libraries.userLibraryID;
try {
let zp = Zotero.getActiveZoteroPane();
libraryID = zp.getSelectedLibraryID();
}
catch (e) {
Zotero.logError(e);
}
var args = {
libraryID
};
args.wrappedJSObject = args;
Services.ww.openWindow(null, "chrome://zotero/content/import/importWizard.xul",
"importFile", "chrome,dialog=yes,centerscreen,width=600,height=400", args);
};
/**
* Creates Zotero.Translate instance and shows file picker for file import
*
* @param {Object} options
* @param {nsIFile|string|null} [options.file=null] - File to import, or none to show a filepicker
* @param {Boolean} [options.addToLibraryRoot=false]
* @param {Boolean} [options.createNewCollection=true] - Put items in a new collection
* @param {Function} [options.onBeforeImport] - Callback to receive translation object, useful
* for displaying progress in a different way. This also causes an error to be throw
* instead of shown in the main window.
*/
this.importFile = Zotero.Promise.coroutine(function* (file, createNewCollection) {
if(createNewCollection === undefined) {
this.importFile = Zotero.Promise.coroutine(function* (options = {}) {
if (!options) {
options = {};
}
if (typeof options == 'string' || options instanceof Components.interfaces.nsIFile) {
Zotero.debug("WARNING: importFile() now takes a single options object -- update your code");
options = {
file: options,
createNewCollection: arguments[1]
};
}
var file = options.file ? Zotero.File.pathToFile(options.file) : null;
var createNewCollection = options.createNewCollection;
var addToLibraryRoot = options.addToLibraryRoot;
var onBeforeImport = options.onBeforeImport;
if (createNewCollection === undefined && !addToLibraryRoot) {
createNewCollection = true;
} else if(!createNewCollection) {
}
else if (!createNewCollection) {
try {
if (!ZoteroPane.collectionsView.editable) {
ZoteroPane.collectionsView.selectLibrary(null);
@ -220,32 +328,40 @@ var Zotero_File_Interface = new function() {
} catch(e) {}
}
var translation = new Zotero.Translate.Import();
if (!file) {
let translators = yield translation.getTranslators();
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window, Zotero.getString("fileInterface.import"), nsIFilePicker.modeOpen);
var defaultNewCollectionPrefix = Zotero.getString("fileInterface.imported");
fp.appendFilters(nsIFilePicker.filterAll);
var collation = Zotero.getLocaleCollation();
translators.sort((a, b) => collation.compareString(1, a.label, b.label))
for (let translator of translators) {
fp.appendFilter(translator.label, "*." + translator.target);
var translation;
// Check if the file is an SQLite database
var sample = yield Zotero.File.getSample(file.path);
if (file.path == Zotero.DataDirectory.getDatabase()) {
// Blacklist the current Zotero database, which would cause a hang
}
else if (Zotero.MIME.sniffForMIMEType(sample) == 'application/x-sqlite3') {
// Mendeley import doesn't use the real translation architecture, but we create a
// translation object with the same interface
translation = yield _getMendeleyTranslation();
translation.createNewCollection = createNewCollection;
defaultNewCollectionPrefix = Zotero.getString(
'fileInterface.appImportCollection', 'Mendeley'
);
}
else if (file.path.endsWith('@www.mendeley.com.sqlite')
|| file.path.endsWith('online.sqlite')) {
// Keep in sync with importWizard.js
throw new Error('Encrypted Mendeley database');
}
var rv = fp.show();
if (rv !== nsIFilePicker.returnOK && rv !== nsIFilePicker.returnReplace) {
return false;
if (!translation) {
translation = new Zotero.Translate.Import();
}
file = fp.file;
}
translation.setLocation(file);
yield _finishImport(translation, createNewCollection);
return _finishImport({
translation,
createNewCollection,
addToLibraryRoot,
defaultNewCollectionPrefix,
onBeforeImport
});
});
@ -273,7 +389,10 @@ var Zotero_File_Interface = new function() {
}
} catch(e) {}
yield _finishImport(translation, false);
yield _finishImport({
translation,
createNewCollection: false
});
// Select imported items
try {
@ -287,17 +406,36 @@ var Zotero_File_Interface = new function() {
});
var _finishImport = Zotero.Promise.coroutine(function* (translation, createNewCollection) {
var _finishImport = Zotero.Promise.coroutine(function* (options) {
var t = performance.now();
var translation = options.translation;
var addToLibraryRoot = options.addToLibraryRoot;
var createNewCollection = options.createNewCollection;
var defaultNewCollectionPrefix = options.defaultNewCollectionPrefix;
var onBeforeImport = options.onBeforeImport;
if (addToLibraryRoot && createNewCollection) {
throw new Error("Can't add to library root and create new collection");
}
var showProgressWindow = !onBeforeImport;
let translators = yield translation.getTranslators();
// Unrecognized file
if (!translators.length) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
if (onBeforeImport) {
yield onBeforeImport(false);
}
let ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
var index = ps.confirmEx(
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
let index = ps.confirmEx(
null,
"",
Zotero.getString('general.error'),
Zotero.getString("fileInterface.unsupportedFormat"),
buttonFlags,
null,
@ -305,16 +443,26 @@ var Zotero_File_Interface = new function() {
null, null, {}
);
if (index == 1) {
ZoteroPane_Local.loadURI("http://zotero.org/support/kb/importing");
Zotero.launchURL("https://www.zotero.org/support/kb/importing");
}
return;
return false;
}
let importCollection = null, libraryID = Zotero.Libraries.userLibraryID;
var libraryID = Zotero.Libraries.userLibraryID;
var importCollection = null;
try {
libraryID = ZoteroPane.getSelectedLibraryID();
importCollection = ZoteroPane.getSelectedCollection();
} catch(e) {}
let zp = Zotero.getActiveZoteroPane();
libraryID = zp.getSelectedLibraryID();
if (addToLibraryRoot) {
yield zp.collectionsView.selectLibrary(libraryID);
}
else if (!createNewCollection) {
importCollection = zp.getSelectedCollection();
}
}
catch (e) {
Zotero.logError(e);
}
if(createNewCollection) {
// Create a new collection to take imported items
@ -330,8 +478,9 @@ var Zotero_File_Interface = new function() {
break;
}
}
} else {
collectionName = Zotero.getString("fileInterface.imported")+" "+(new Date()).toLocaleString();
}
else {
collectionName = defaultNewCollectionPrefix + " " + (new Date()).toLocaleString();
}
importCollection = new Zotero.Collection;
importCollection.libraryID = libraryID;
@ -342,12 +491,15 @@ var Zotero_File_Interface = new function() {
translation.setTranslator(translators[0]);
// Show progress popup
var progressWin = new Zotero.ProgressWindow({
var progressWin;
var progress;
if (showProgressWindow) {
progressWin = new Zotero.ProgressWindow({
closeOnClick: false
});
progressWin.changeHeadline(Zotero.getString('fileInterface.importing'));
var icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
let progress = new progressWin.ItemProgress(
let icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
progress = new progressWin.ItemProgress(
icon, translation.path ? OS.Path.basename(translation.path) : translators[0].label
);
progressWin.show();
@ -357,6 +509,10 @@ var Zotero_File_Interface = new function() {
});
yield Zotero.Promise.delay(0);
}
else {
yield onBeforeImport(translation);
}
let failed = false;
try {
@ -365,6 +521,10 @@ var Zotero_File_Interface = new function() {
collections: importCollection ? [importCollection.id] : null
});
} catch(e) {
if (!showProgressWindow) {
throw e;
}
progressWin.close();
Zotero.logError(e);
Zotero.alert(
@ -372,26 +532,62 @@ var Zotero_File_Interface = new function() {
Zotero.getString('general.error'),
Zotero.getString("fileInterface.importError")
);
return;
return false;
}
// Show popup on completion
var numItems = translation.newItems.length;
// Show popup on completion
if (showProgressWindow) {
progressWin.changeHeadline(Zotero.getString('fileInterface.importComplete'));
let icon;
if (numItems == 1) {
var icon = translation.newItems[0].getImageSrc();
icon = translation.newItems[0].getImageSrc();
}
else {
var icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
}
var text = Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems);
let text = Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems);
progress.setIcon(icon);
progress.setText(text);
// For synchronous translators, which don't update progress
progress.setProgress(100);
progressWin.startCloseTimer(5000);
}
Zotero.debug(`Imported ${numItems} item(s) in ${performance.now() - t} ms`);
return true;
});
var _getMendeleyTranslation = async function () {
if (true) {
Components.utils.import("chrome://zotero/content/import/mendeley/mendeleyImport.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', 'mendeleyImport.js'
);
let fileURI = OS.Path.toFileURI(file);
let xmlhttp = await Zotero.HTTP.request(
'GET',
fileURI,
{
dontCache: true,
responseType: 'text'
}
);
eval(xmlhttp.response);
}
return new Zotero_Import_Mendeley();
}
/**
* Creates a bibliography from a collection or saved search
*/

View File

@ -0,0 +1,332 @@
var Zotero_Import_Wizard = {
_wizard: null,
_dbs: null,
_file: null,
_translation: null,
init: async function () {
this._wizard = document.getElementById('import-wizard');
var dbs = await Zotero_File_Interface.findMendeleyDatabases();
if (dbs.length) {
document.getElementById('radio-import-source-mendeley').hidden = false;
}
// If no existing collections or non-trash items in the library, don't create a new
// collection by default
var args = window.arguments[0].wrappedJSObject;
if (args && args.libraryID) {
let sql = "SELECT ROWID FROM collections WHERE libraryID=?1 "
+ "UNION "
+ "SELECT ROWID FROM items WHERE libraryID=?1 "
// Not in trash
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
// And not a child item (which doesn't necessarily show up in the trash)
+ "AND itemID NOT IN (SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
+ "AND itemID NOT IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL) "
+ "LIMIT 1";
if (!await Zotero.DB.valueQueryAsync(sql, args.libraryID)) {
document.getElementById('create-collection-checkbox').removeAttribute('checked');
}
}
Zotero.Translators.init(); // async
},
onModeChosen: async function () {
var wizard = this._wizard;
var mode = document.getElementById('import-source').selectedItem.id;
try {
switch (mode) {
case 'radio-import-source-file':
await this.chooseFile();
break;
case 'radio-import-source-mendeley':
this._dbs = await Zotero_File_Interface.findMendeleyDatabases();
// This shouldn't happen, because we only show the wizard if there are databases
if (!this._dbs.length) {
throw new Error("No databases found");
}
this._populateFileList(this._dbs);
document.getElementById('file-options-header').textContent
= Zotero.getString('fileInterface.chooseAppDatabaseToImport', 'Mendeley')
wizard.goTo('page-file-list');
wizard.canRewind = true;
this._enableCancel();
break;
default:
throw new Error(`Unknown mode ${mode}`);
}
}
catch (e) {
this._onDone(
Zotero.getString('general.error'),
Zotero.getString('fileInterface.importError'),
true
);
throw e;
}
},
goToStart: function () {
this._wizard.goTo('page-start');
this._wizard.canAdvance = true;
return false;
},
chooseFile: async function (translation) {
var translation = new Zotero.Translate.Import();
var translators = await translation.getTranslators();
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window, Zotero.getString("fileInterface.import"), nsIFilePicker.modeOpen);
fp.appendFilters(nsIFilePicker.filterAll);
var collation = Zotero.getLocaleCollation();
// Add Mendeley DB, which isn't a translator
var mendeleyFilter = {
label: "Mendeley Database", // TODO: Localize
target: "*.sqlite"
};
var filters = [...translators];
filters.push(mendeleyFilter);
filters.sort((a, b) => collation.compareString(1, a.label, b.label));
for (let filter of filters) {
fp.appendFilter(filter.label, "*." + filter.target);
}
var rv = fp.show();
if (rv !== nsIFilePicker.returnOK && rv !== nsIFilePicker.returnReplace) {
return false;
}
Zotero.debug(`File is ${fp.file.path}`);
this._file = fp.file.path;
this._wizard.canAdvance = true;
this._wizard.goTo('page-options');
},
/**
* When a file is clicked on in the file list
*/
onFileSelected: async function () {
var index = document.getElementById('file-list').selectedIndex;
if (index != -1) {
this._file = this._dbs[index].path;
this._wizard.canAdvance = true;
}
else {
this._file = null;
this._wizard.canAdvance = false;
}
},
/**
* When the user clicks "Other…" to choose a file not in the list
*/
chooseMendeleyDB: async function () {
document.getElementById('file-list').selectedIndex = -1;
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window, Zotero.getString('fileInterface.import'), nsIFilePicker.modeOpen);
fp.appendFilter("Mendeley Database", "*.sqlite"); // TODO: Localize
var rv = fp.show();
if (rv != nsIFilePicker.returnOK) {
return false;
}
this._file = fp.file.path;
this._wizard.canAdvance = true;
this._wizard.advance();
},
onOptionsShown: function () {
},
onImportStart: async function () {
if (!this._file) {
let index = document.getElementById('file-list').selectedIndex;
this._file = this._dbs[index].path;
}
this._disableCancel();
this._wizard.canRewind = false;
this._wizard.canAdvance = false;
await this.doImport({
createNewCollection: document.getElementById('create-collection-checkbox').hasAttribute('checked')
});
},
onBeforeImport: async function (translation) {
// Unrecognized translator
if (!translation) {
// Allow error dialog to be displayed, and then close window
setTimeout(function () {
window.close();
});
return;
}
this._translation = translation;
// Switch to progress pane
this._wizard.goTo('page-progress');
var pm = document.getElementById('import-progressmeter');
translation.setHandler('itemDone', function () {
pm.value = translation.getProgress();
});
},
doImport: async function (options) {
try {
let result = await Zotero_File_Interface.importFile({
file: this._file,
onBeforeImport: this.onBeforeImport.bind(this),
addToLibraryRoot: !options.createNewCollection
});
// Cancelled by user or due to error
if (!result) {
window.close();
return;
}
let numItems = this._translation.newItems.length;
this._onDone(
Zotero.getString('fileInterface.importComplete'),
Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems)
);
}
catch (e) {
if (e.message == 'Encrypted Mendeley database') {
let url = 'https://www.zotero.org/support/kb/mendeley_import';
this._onDone(
Zotero.getString('general.error'),
// TODO: Localize
`The selected Mendeley database cannot be read, likely because it is encrypted. `
+ `See <a href="${url}" class="text-link">How do I import a Mendeley library `
+ `into Zotero?</a> for more information.`
);
}
else {
this._onDone(
Zotero.getString('general.error'),
Zotero.getString('fileInterface.importError'),
true
);
}
throw e;
}
},
reportError: function () {
Zotero.getActiveZoteroPane().reportErrors();
window.close();
},
_populateFileList: async function (files) {
var listbox = document.getElementById('file-list');
// Remove existing entries
var items = listbox.getElementsByTagName('listitem');
for (let item of items) {
listbox.removeChild(item);
}
for (let file of files) {
let li = document.createElement('listitem');
let name = document.createElement('listcell');
// Simply filenames
let nameStr = file.name
.replace(/\.sqlite$/, '')
.replace(/@www\.mendeley\.com$/, '');
if (nameStr == 'online') {
nameStr = Zotero.getString('dataDir.default', 'online.sqlite');
}
name.setAttribute('label', nameStr + ' ');
li.appendChild(name);
let lastModified = document.createElement('listcell');
lastModified.setAttribute('label', file.lastModified.toLocaleString() + ' ');
li.appendChild(lastModified);
let size = document.createElement('listcell');
size.setAttribute(
'label',
Zotero.getString('general.nMegabytes', (file.size / 1024 / 1024).toFixed(1)) + ' '
);
li.appendChild(size);
listbox.appendChild(li);
}
if (files.length == 1) {
listbox.selectedIndex = 0;
}
},
_enableCancel: function () {
this._wizard.getButton('cancel').disabled = false;
},
_disableCancel: function () {
this._wizard.getButton('cancel').disabled = true;
},
_onDone: function (label, description, showReportErrorButton) {
var wizard = this._wizard;
wizard.getPageById('page-done').setAttribute('label', label);
var xulElem = document.getElementById('result-description');
var htmlElem = document.getElementById('result-description-html');
if (description.includes('href')) {
htmlElem.innerHTML = description;
Zotero.Utilities.Internal.updateHTMLInXUL(htmlElem);
xulElem.hidden = true;
htmlElem.setAttribute('display', 'block');
}
else {
xulElem.textContent = description;
xulElem.hidden = false;
htmlElem.setAttribute('display', 'none');
}
document.getElementById('result-description')
if (showReportErrorButton) {
let button = document.getElementById('result-report-error');
button.setAttribute('label', Zotero.getString('errorReport.reportError'));
button.hidden = false;
}
// When done, move to last page and allow closing
wizard.canAdvance = true;
wizard.goTo('page-done');
wizard.canRewind = false;
}
};

View File

@ -0,0 +1,75 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/importWizard.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<wizard id="import-wizard"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="&zotero.import;"
onload="Zotero_Import_Wizard.init()">
<script src="../include.js"/>
<script src="../fileInterface.js"/>
<script src="importWizard.js"/>
<wizardpage pageid="page-start"
label="&zotero.import.whereToImportFrom;"
next="page-options"
onpageadvanced="Zotero_Import_Wizard.onModeChosen(); return false;">
<radiogroup id="import-source">
<radio id="radio-import-source-file" label="&zotero.import.source.file;"/>
<radio id="radio-import-source-mendeley" label="Mendeley" hidden="true"/>
</radiogroup>
</wizardpage>
<wizardpage pageid="page-file-list"
next="page-options"
onpagerewound="return Zotero_Import_Wizard.goToStart()">
<description id="file-options-header"/>
<listbox id="file-list" onselect="Zotero_Import_Wizard.onFileSelected()">
<listhead>
<listheader label="&zotero.import.database;"/>
<listheader label="&zotero.import.lastModified;"/>
<listheader label="&zotero.import.size;"/>
</listhead>
<listcols>
<listcol flex="1"/>
<listcol/>
<listcol/>
</listcols>
</listbox>
<hbox>
<button label="&zotero.general.other;" oncommand="Zotero_Import_Wizard.chooseMendeleyDB()"/>
</hbox>
</wizardpage>
<wizardpage pageid="page-options"
label="&zotero.general.options;"
next="page-progress"
onpageshow="Zotero_Import_Wizard.onOptionsShown()"
onpagerewound="return Zotero_Import_Wizard.goToStart()"
onpageadvanced="Zotero_Import_Wizard.onImportStart()">
<checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" />
</wizardpage>
<wizardpage pageid="page-progress"
label="&zotero.import.importing;"
onpageshow="document.getElementById('import-wizard').canRewind = false;"
next="page-done">
<progressmeter id="import-progressmeter" mode="determined"/>
</wizardpage>
<wizardpage pageid="page-done">
<description id="result-description"/>
<html:div id="result-description-html"/>
<hbox>
<button id="result-report-error"
oncommand="Zotero_Import_Wizard.reportError()"
hidden="true"/>
</hbox>
</wizardpage>
</wizard>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,102 @@
var map = {
83: {
itemTypes: {
Bill: "bill",
Book: "book",
BookSection: "bookSection",
Case: "case",
ComputerProgram: "computerProgram",
ConferenceProceedings: "conferencePaper",
EncyclopediaArticle: "encyclopediaArticle",
Film: "film",
Generic: "document",
JournalArticle: "journalArticle",
MagazineArticle: "magazineArticle",
NewspaperArticle: "newspaperArticle",
Patent: "patent",
Report: "report",
Statute: "statute",
TelevisionBroadcast: "tvBroadcast",
Thesis: "thesis",
WebPage: "webpage",
WorkingPaper: "report"
},
fields: {
id: "",
uuid: "",
reviewedArticle: "",
revisionNumber: "",
publisher: "publisher",
reprintEdition: "",
series: "seriesTitle",
seriesNumber: "seriesNumber",
sections: "section",
seriesEditor: "creator[seriesEditor]", // falls back to editor if necessary
owner: "",
pages: "func[pages]",
month: "", // handled explicitly
originalPublication: "",
publication: "publicationTitle",
publicLawNumber: "publicLawNumber",
pmid: "extra[PMID]",
sourceType: "",
session: "session",
shortTitle: "shortTitle",
volume: "volume",
year: "", // handled explicitly
userType: "type",
country: "place[country]",
dateAccessed: "accessDate",
committee: "committee",
counsel: "creator[counsel]",
doi: "DOI",
edition: "edition",
day: "", // handled explicitly
department: "",
citationKey: "citationKey", // put in Extra
city: "place[city]",
chapter: "",
codeSection: "section",
codeVolume: "codeVolume",
code: "code",
codeNumber: "codeNumber",
issue: "issue",
language: "language",
isbn: "ISBN",
issn: "ISSN",
length: "",
medium: "medium",
lastUpdate: "",
legalStatus: "legalStatus",
hideFromMendeleyWebIndex: "",
institution: "publisher",
genre: "genre",
internationalTitle: "",
internationalUserType: "",
internationalAuthor: "",
internationalNumber: "",
deletionPending: "",
favourite: "", // tag?
confirmed: "", // tag?
deduplicated: "",
read: "", // tag?
type: "", // item type handled separately
title: "title",
privacy: "",
applicationNumber: "applicationNumber",
arxivId: "extra[arXiv]",
advisor: "",
articleColumn: "",
modified: "func[fromUnixtime:dateModified]",
abstract: "abstractNote",
added: "func[fromUnixtime:dateAdded]",
note: "func[note]",
importer: ""
},
creatorTypes: {
DocumentAuthor: "author",
DocumentEditor: "editor",
DocumentTranslator: "translator"
}
}
};

View File

@ -59,10 +59,8 @@ var Zotero_Citation_Dialog = new function () {
this.listItemSelected = listItemSelected;
this.up = up;
this.down = down;
this.add = add;
this.remove = remove;
this.setSortToggle = setSortToggle;
this.citationSortUnsort = citationSortUnsort;
this.confirmRegenerate = confirmRegenerate;
this.accept = accept;
this.cancel = cancel;
@ -373,13 +371,13 @@ var Zotero_Citation_Dialog = new function () {
/*
* Adds an item to the multipleSources list
*/
function add(first_item) {
this.add = Zotero.Promise.coroutine(function* (first_item) {
var pos, len;
var item = itemsView.getSelectedItems()[0]; // treeview from xpcom/itemTreeView.js
if (!item) {
sortCitation();
yield sortCitation();
_updateAccept();
_updatePreview();
return;
@ -412,11 +410,11 @@ var Zotero_Citation_Dialog = new function () {
_citationList.ensureElementIsVisible(selectionNode);
// allow user to press OK
selectionNode = sortCitation(selectionNode);
selectionNode = yield sortCitation(selectionNode);
_citationList.selectItem(selectionNode);
_updateAccept();
_updatePreview();
}
});
/*
* Deletes a citation from the multipleSources list
@ -446,11 +444,11 @@ var Zotero_Citation_Dialog = new function () {
/*
* Sorts preview citations, if preview is open.
*/
function citationSortUnsort() {
this.citationSortUnsort = Zotero.Promise.coroutine(function* () {
setSortToggle();
sortCitation();
yield sortCitation();
_updatePreview();
}
});
/*
* Sets the current sort toggle state persistently on the citation.
@ -468,7 +466,7 @@ var Zotero_Citation_Dialog = new function () {
/*
* Sorts the list of citations
*/
function sortCitation(scrollToItem) {
var sortCitation = Zotero.Promise.coroutine(function* (scrollToItem) {
if(!_sortCheckbox) return scrollToItem;
if(!_sortCheckbox.checked) {
io.citation.properties.unsorted = true;
@ -485,7 +483,7 @@ var Zotero_Citation_Dialog = new function () {
// run preview function to re-sort, if it hasn't already been
// run
io.sort();
yield io.sort();
// add items back to list
scrollToItem = null;
@ -502,7 +500,7 @@ var Zotero_Citation_Dialog = new function () {
if(scrollToItem) _citationList.ensureElementIsVisible(scrollToItem);
return scrollToItem;
}
});
/*
* Ask whether to modifiy the preview

View File

@ -74,8 +74,8 @@ var Zotero_Bibliography_Dialog = new function () {
if(selectedItemIDs.length) {
for (let itemID of selectedItemIDs) {
var itemIndexToSelect = false;
for(var i in bibEditInterface.bibliography[0].entry_ids) {
if(bibEditInterface.bibliography[0].entry_ids[i].indexOf(itemID) !== -1) {
for(var i in bibEditInterface.bib[0].entry_ids) {
if(bibEditInterface.bib[0].entry_ids[i].indexOf(itemID) !== -1) {
itemIndexToSelect = i;
continue;
}
@ -254,7 +254,7 @@ var Zotero_Bibliography_Dialog = new function () {
*/
function _getSelectedListItemIDs() {
return Array.from(_itemList.selectedItems)
.map(item => bibEditInterface.bibliography[0].entry_ids[item.value][0]);
.map(item => bibEditInterface.bib[0].entry_ids[item.value][0]);
}
/**
@ -287,8 +287,8 @@ var Zotero_Bibliography_Dialog = new function () {
editor.readonly = index === undefined;
if(index !== undefined) {
var itemID = bibEditInterface.bibliography[0].entry_ids[index];
editor.value = bibEditInterface.bibliography[1][index];
var itemID = bibEditInterface.bib[0].entry_ids[index];
editor.value = bibEditInterface.bib[1][index];
_lastSelectedIndex = index;
_lastSelectedItemID = itemID;
_lastSelectedValue = editor.value;
@ -304,7 +304,7 @@ var Zotero_Bibliography_Dialog = new function () {
* loads items from itemSet
*/
function _loadItems() {
var itemIDs = bibEditInterface.bibliography[0].entry_ids;
var itemIDs = bibEditInterface.bib[0].entry_ids;
var items = itemIDs.map(itemID => Zotero.Cite.getItem(itemID[0]));
// delete all existing items from list

View File

@ -31,10 +31,11 @@
<dialog
id="zotero-doc-prefs-dialog"
orient="vertical"
buttons="accept,cancel"
buttons="accept,cancel,help"
title="&zotero.integration.docPrefs.title;"
onload="Zotero_File_Interface_Bibliography.init();"
ondialogaccept="Zotero_File_Interface_Bibliography.acceptSelection();"
ondialoghelp="Zotero_File_Interface_Bibliography.openHelpLink();"
onclose="document.documentElement.cancelDialog(); event.preventDefault(); event.stopPropagation();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
persist="screenX screenY"
@ -52,7 +53,7 @@
</hbox>
</groupbox>
<groupbox>
<groupbox id="locale-box">
<hbox align="center">
<caption label="&zotero.bibliography.locale.label;"/>
<menulist id="locale-menu" oncommand="Zotero_File_Interface_Bibliography.localeChanged(this.selectedItem.value)"/>
@ -67,7 +68,7 @@
</radiogroup>
</groupbox>
<groupbox>
<groupbox id="formatUsing-groupbox">
<caption label="&zotero.integration.prefs.formatUsing.label;"/>
<radiogroup id="formatUsing" orient="vertical">
@ -84,5 +85,10 @@
<checkbox id="automaticJournalAbbreviations-checkbox" label="&zotero.integration.prefs.automaticJournalAbbeviations.label;"/>
<description class="radioDescription">&zotero.integration.prefs.automaticJournalAbbeviations.caption;</description>
</vbox>
<vbox id="automaticCitationUpdates-vbox">
<checkbox id="automaticCitationUpdates-checkbox" label="&zotero.integration.prefs.automaticCitationUpdates.label;" tooltiptext="&zotero.integration.prefs.automaticCitationUpdates.tooltip;"/>
<description class="radioDescription">&zotero.integration.prefs.automaticCitationUpdates.description;</description>
</vbox>
</vbox>
</dialog>

View File

@ -0,0 +1,121 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, 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 *****
*/
Components.utils.import("resource://gre/modules/Services.jsm");
var Zotero_ProgressBar = new function () {
var initialized, io;
/**
* Pre-initialization, when the dialog has loaded but has not yet appeared
*/
this.onDOMContentLoaded = function(event) {
if(event.target === document) {
initialized = true;
io = window.arguments[0].wrappedJSObject;
if (io.onLoad) {
io.onLoad(_onProgress);
}
// Only hide chrome on Windows or Mac
if(Zotero.isMac) {
document.documentElement.setAttribute("drawintitlebar", true);
} else if(Zotero.isWin) {
document.documentElement.setAttribute("hidechrome", true);
}
new WindowDraggingElement(document.getElementById("quick-format-dialog"), window);
}
};
/**
* Center the window
*/
this.onLoad = function(event) {
if(event.target !== document) return;
// make sure we are visible
window.focus();
window.setTimeout(function() {
var targetX = Math.floor(-window.outerWidth/2 + (window.screen.width / 2));
var targetY = Math.floor(-window.outerHeight/2 + (window.screen.height / 2));
Zotero.debug("Moving window to "+targetX+", "+targetY);
window.moveTo(targetX, targetY);
}, 0);
};
/**
* Called when progress changes
*/
function _onProgress(percent) {
var meter = document.getElementById("quick-format-progress-meter");
if(percent === null) {
meter.mode = "undetermined";
} else {
meter.mode = "determined";
meter.value = Math.round(percent);
}
}
/**
* Resizes windows
* @constructor
*/
var Resizer = function(panel, targetWidth, targetHeight, pixelsPerStep, stepsPerSecond) {
this.panel = panel;
this.curWidth = panel.clientWidth;
this.curHeight = panel.clientHeight;
this.difX = (targetWidth ? targetWidth - this.curWidth : 0);
this.difY = (targetHeight ? targetHeight - this.curHeight : 0);
this.step = 0;
this.steps = Math.ceil(Math.max(Math.abs(this.difX), Math.abs(this.difY))/pixelsPerStep);
this.timeout = (1000/stepsPerSecond);
var me = this;
this._animateCallback = function() { me.animate() };
};
/**
* Performs a step of the animation
*/
Resizer.prototype.animate = function() {
if(this.stopped) return;
this.step++;
this.panel.sizeTo(this.curWidth+Math.round(this.step*this.difX/this.steps),
this.curHeight+Math.round(this.step*this.difY/this.steps));
if(this.step !== this.steps) {
window.setTimeout(this._animateCallback, this.timeout);
}
};
/**
* Halts resizing
*/
Resizer.prototype.stop = function() {
this.stopped = true;
};
}
window.addEventListener("DOMContentLoaded", Zotero_ProgressBar.onDOMContentLoaded, false);
window.addEventListener("load", Zotero_ProgressBar.onLoad, false);

View File

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, 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 *****
-->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/integration.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/integration.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<window
id="quick-format-dialog"
class="progress-bar"
orient="vertical"
title="&zotero.progress.title;"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
persist="screenX screenY">
<script src="../include.js"/>
<script src="windowDraggingUtils.js" type="text/javascript"/>
<script src="progressBar.js" type="text/javascript"/>
<box orient="horizontal" id="quick-format-entry">
<deck id="quick-format-deck" selectedIndex="0" flex="1">
<progressmeter id="quick-format-progress-meter" mode="undetermined" value="0" flex="1"/>
</deck>
</box>
</window>

View File

@ -179,6 +179,7 @@ var Zotero_QuickFormat = new function () {
*/
function _getCurrentEditorTextNode() {
var selection = qfiWindow.getSelection();
if (!selection) return false;
var range = selection.getRangeAt(0);
var node = range.startContainer;
@ -711,7 +712,7 @@ var Zotero_QuickFormat = new function () {
/**
* Converts the selected item to a bubble
*/
function _bubbleizeSelected() {
var _bubbleizeSelected = Zotero.Promise.coroutine(function* () {
if(!referenceBox.hasChildNodes() || !referenceBox.selectedItem) return false;
var citationItem = {"id":referenceBox.selectedItem.getAttribute("zotero-item")};
@ -734,11 +735,11 @@ var Zotero_QuickFormat = new function () {
node.nodeValue = "";
var bubble = _insertBubble(citationItem, node);
_clearEntryList();
_previewAndSort();
yield _previewAndSort();
_refocusQfe();
return true;
}
});
/**
* Ignores clicks (for use on separators in the rich list box)
@ -902,13 +903,13 @@ var Zotero_QuickFormat = new function () {
/**
* Generates the preview and sorts citations
*/
function _previewAndSort() {
var _previewAndSort = Zotero.Promise.coroutine(function* () {
var shouldKeepSorted = keepSorted.hasAttribute("checked"),
editorShowing = showEditor.hasAttribute("checked");
if(!shouldKeepSorted && !editorShowing) return;
_updateCitationObject();
io.sort();
yield io.sort();
if(shouldKeepSorted) {
// means we need to resort citations
_clearCitation();
@ -920,7 +921,7 @@ var Zotero_QuickFormat = new function () {
_moveCursorToEnd();
}
}
});
/**
* Shows the citation properties panel for a given bubble
@ -1071,7 +1072,7 @@ var Zotero_QuickFormat = new function () {
/**
* Handle return or escape
*/
function _onQuickSearchKeyPress(event) {
var _onQuickSearchKeyPress = Zotero.Promise.coroutine(function* (event) {
// Prevent hang if another key is pressed after Enter
// https://forums.zotero.org/discussion/59157/
if (accepted) {
@ -1083,7 +1084,7 @@ var Zotero_QuickFormat = new function () {
var keyCode = event.keyCode;
if (keyCode === event.DOM_VK_RETURN) {
event.preventDefault();
if(!_bubbleizeSelected() && !_getEditorContent()) {
if(!(yield _bubbleizeSelected()) && !_getEditorContent()) {
_accept();
}
} else if(keyCode === event.DOM_VK_TAB || event.charCode === 59 /* ; */) {
@ -1162,7 +1163,7 @@ var Zotero_QuickFormat = new function () {
} else {
_resetSearchTimer();
}
}
});
/**
* Adds a dummy element to make dragging work
@ -1190,7 +1191,7 @@ var Zotero_QuickFormat = new function () {
/**
* Replaces the dummy element with a node to make dropping work
*/
function _onBubbleDrop(event) {
var _onBubbleDrop = Zotero.Promise.coroutine(function* (event) {
event.preventDefault();
event.stopPropagation();
@ -1208,9 +1209,9 @@ var Zotero_QuickFormat = new function () {
keepSorted.removeAttribute("checked");
}
_previewAndSort();
yield _previewAndSort();
_moveCursorToEnd();
}
});
/**
* Handle a click on a bubble
@ -1333,7 +1334,7 @@ var Zotero_QuickFormat = new function () {
pane.selectItem(id);
// Pull window to foreground
Zotero.Integration.activate(pane.document.defaultView);
Zotero.Utilities.Internal.activate(pane.document.defaultView);
}
/**

View File

@ -25,6 +25,7 @@
var ZoteroItemPane = new function() {
var _lastItem, _itemBox, _notesLabel, _notesButton, _notesList, _tagsBox, _relatedBox;
var _selectedNoteID;
var _translationTarget;
var _noteIDs;
@ -223,6 +224,8 @@ var ZoteroItemPane = new function() {
this.onNoteSelected = function (item, editable) {
_selectedNoteID = item.id;
// If an external note window is open for this item, don't show the editor
if (ZoteroPane.findNoteWindow(item.id)) {
this.showNoteWindowMessage();
@ -258,20 +261,20 @@ var ZoteroItemPane = new function() {
* Select the parent item and open the note editor
*/
this.openNoteWindow = async function () {
var noteEditor = document.getElementById('zotero-note-editor');
var item = noteEditor.item;
var selectedNote = Zotero.Items.get(_selectedNoteID);
// We don't want to show the note in two places, since it causes unnecessary UI updates
// and can result in weird bugs where note content gets lost.
//
// If this is a child note, select the parent
if (item.parentID) {
await ZoteroPane.selectItem(item.parentID);
if (selectedNote.parentID) {
await ZoteroPane.selectItem(selectedNote.parentID);
}
// Otherwise, hide note and replace with a message that we're editing externally
else {
this.showNoteWindowMessage();
}
ZoteroPane.openNoteWindow(item.id);
ZoteroPane.openNoteWindow(selectedNote.id);
};

@ -1 +1 @@
Subproject commit 684bad2035a563b0661c1eb17b0ee6ba6325e571
Subproject commit b8c370c8a978790d2aeefa302f05f3bfb1478e75

View File

@ -206,14 +206,20 @@ var Zotero_Lookup = new function () {
}
this.toggleProgress = function(on) {
// In Firefox 52.6.0, progressmeters burn CPU at idle on Linux when undetermined, even
// if they're hidden. (Being hidden is enough on macOS.)
var mode = on ? 'undetermined' : 'determined';
//single line
var txtBox = document.getElementById("zotero-lookup-textbox");
txtBox.style.opacity = on ? 0.5 : 1;
txtBox.disabled = !!on;
document.getElementById("zotero-lookup-progress").setAttribute("collapsed", !on);
var p1 = document.getElementById("zotero-lookup-progress");
p1.mode = mode;
//multiline
document.getElementById("zotero-lookup-multiline-textbox").disabled = !!on;
document.getElementById("zotero-lookup-multiline-progress").setAttribute("collapsed", !on);
var p2 = document.getElementById("zotero-lookup-multiline-progress");
p2.mode = mode;
}
}

View File

@ -43,18 +43,7 @@ async function onLoad() {
if (itemID) {
var ref = await Zotero.Items.getAsync(itemID);
// If loading new or different note, disable undo while we repopulate the text field
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
// undo content from another note into the current one.
let clearUndo = noteEditor.item ? noteEditor.item.id != itemID : false;
noteEditor.item = ref;
if (clearUndo) {
noteEditor.clearUndo();
}
document.title = ref.getNoteTitle();
}
else {
@ -70,6 +59,7 @@ async function onLoad() {
noteEditor.refresh();
}
noteEditor.focus();
notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item', 'noteWindow');
}
@ -95,12 +85,9 @@ function onUnload() {
var NotifyCallback = {
notify: function(action, type, ids){
if (noteEditor.item && ids.indexOf(noteEditor.item.id) != -1) {
// If the document title hasn't yet been set, reset undo so
// undoing to empty isn't possible
if (noteEditor.item && ids.includes(noteEditor.item.id)) {
var noteTitle = noteEditor.item.getNoteTitle();
if (!document.title && noteTitle != '') {
noteEditor.clearUndo();
document.title = noteTitle;
}

View File

@ -42,6 +42,28 @@ Zotero_Preferences.Advanced = {
},
updateTranslators: Zotero.Promise.coroutine(function* () {
var updated = yield Zotero.Schema.updateFromRepository(Zotero.Schema.REPO_UPDATE_MANUAL);
var button = document.getElementById('updateButton');
if (button) {
if (updated===-1) {
var label = Zotero.getString('zotero.preferences.update.upToDate');
}
else if (updated) {
var label = Zotero.getString('zotero.preferences.update.updated');
}
else {
var label = Zotero.getString('zotero.preferences.update.error');
}
button.setAttribute('label', label);
if (updated && Zotero_Preferences.Cite) {
yield Zotero_Preferences.Cite.refreshStylesList();
}
}
}),
migrateDataDirectory: Zotero.Promise.coroutine(function* () {
var currentDir = Zotero.DataDirectory.dir;
var defaultDir = Zotero.DataDirectory.defaultDir;

View File

@ -37,12 +37,16 @@
onpaneload="Zotero_Preferences.Advanced.init()"
helpTopic="advanced">
<preferences>
<preference id="pref-automaticScraperUpdates" name="extensions.zotero.automaticScraperUpdates" type="bool"/>
<preference id="pref-reportTranslationFailure" name="extensions.zotero.reportTranslationFailure" type="bool"/>
<preference id="pref-baseAttachmentPath" name="extensions.zotero.baseAttachmentPath" type="string"/>
<preference id="pref-useDataDir" name="extensions.zotero.useDataDir" type="bool"/>
<preference id="pref-dataDir" name="extensions.zotero.dataDir" type="string"/>
<preference id="pref-debug-output-enableAfterRestart" name="extensions.zotero.debug.store" type="bool"/>
<preference id="pref-openURL-resolver" name="extensions.zotero.openURL.resolver" type="string"/>
<preference id="pref-openURL-version" name="extensions.zotero.openURL.version" type="string"/>
<preference id="pref-keys-openZotero" name="extensions.zotero.keys.openZotero" type="string"/>
<preference id="pref-keys-saveToZotero" name="extensions.zotero.keys.saveToZotero" type="string"/>
<preference id="pref-keys-library" name="extensions.zotero.keys.library" type="string"/>
@ -74,6 +78,14 @@
<groupbox id="zotero-prefpane-advanced-miscellaneous">
<caption label="&zotero.preferences.miscellaneous;"/>
<hbox align="center">
<checkbox label="&zotero.preferences.autoUpdate;" preference="pref-automaticScraperUpdates"/>
<button id="updateButton" style="margin-top:0" label="&zotero.preferences.updateNow;"
oncommand="Zotero_Preferences.Advanced.updateTranslators()"/>
</hbox>
<checkbox label="&zotero.preferences.reportTranslationFailure;" preference="pref-reportTranslationFailure"/>
<hbox align="center">
<label value="&zotero.bibliography.locale.label;"/>
<menulist id="locale-menu"

View File

@ -26,22 +26,48 @@
"use strict";
Zotero_Preferences.Cite = {
wordPluginIDs: new Set([
'zoteroOpenOfficeIntegration@zotero.org',
'zoteroMacWordIntegration@zotero.org',
'zoteroWinWordIntegration@zotero.org'
]),
init: Zotero.Promise.coroutine(function* () {
Components.utils.import("resource://gre/modules/AddonManager.jsm");
this.updateWordProcessorInstructions();
yield this.refreshStylesList();
}),
/**
* Determines if there are word processors, and if not, enables no word processor message
* Determines if any word processors are disabled and if so, shows a message in the pref pane
*/
updateWordProcessorInstructions: function () {
if(document.getElementById("wordProcessors").childNodes.length == 2) {
document.getElementById("wordProcessors-noWordProcessorPluginsInstalled").hidden = undefined;
updateWordProcessorInstructions: async function () {
var someDisabled = false;
await new Promise(function(resolve) {
AddonManager.getAllAddons(function(addons) {
for (let addon of addons) {
if (Zotero_Preferences.Cite.wordPluginIDs.has(addon.id) && addon.userDisabled) {
someDisabled = true;
}
if(Zotero.isStandalone) {
document.getElementById("wordProcessors-getWordProcessorPlugins").hidden = true;
}
resolve();
});
});
if (someDisabled) {
document.getElementById("wordProcessors-somePluginsDisabled").hidden = undefined;
}
},
enableWordPlugins: function () {
AddonManager.getAllAddons(function(addons) {
for (let addon of addons) {
if (Zotero_Preferences.Cite.wordPluginIDs.has(addon.id) && addon.userDisabled) {
addon.userDisabled = false;
}
}
return Zotero.Utilities.Internal.quit(true);
});
},

View File

@ -96,11 +96,15 @@
</groupbox>
</tabpanel>
<tabpanel orient="vertical" id="wordProcessors">
<label id="wordProcessors-noWordProcessorPluginsInstalled" width="45em" hidden="true">
&zotero.preferences.cite.wordProcessors.noWordProcessorPluginsInstalled;
</label>
<vbox id="wordProcessors-somePluginsDisabled" hidden="true">
<label style="font-weight: bold; margin-top: 1em; text-align: center">Some word processor plugins are disabled.</label>
<hbox pack="center" style="margin-bottom: 2em">
<button id="wordProcessors-enablePlugins"
label="Enable Plugins and Restart Zotero"
oncommand="Zotero_Preferences.Cite.enableWordPlugins()"/>
</hbox>
</vbox>
<checkbox label="&zotero.preferences.cite.wordProcessors.useClassicAddCitationDialog;" preference="pref-cite-useClassicAddCitationDialog"/>
<label id="wordProcessors-getWordProcessorPlugins" class="zotero-text-link" href="&zotero.preferences.cite.wordProcessors.getPlugins.url;" value="&zotero.preferences.cite.wordProcessors.getPlugins;"/>
</tabpanel>
</tabpanels>
</tabbox>

View File

@ -25,6 +25,9 @@
"use strict";
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero_Preferences.General = {
init: function () {
// JS-based strings
@ -36,27 +39,89 @@ Zotero_Preferences.General = {
}
document.getElementById('noteFontSize').value = Zotero.Prefs.get('note.fontSize');
this._updateFileHandlerUI();
},
//
// File handlers
//
chooseFileHandler: function (type) {
var pref = this._getFileHandlerPref(type);
var currentPath = Zotero.Prefs.get(pref);
updateTranslators: Zotero.Promise.coroutine(function* () {
var updated = yield Zotero.Schema.updateFromRepository(Zotero.Schema.REPO_UPDATE_MANUAL);
var button = document.getElementById('updateButton');
if (button) {
if (updated===-1) {
var label = Zotero.getString('zotero.preferences.update.upToDate');
var nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
if (currentPath) {
fp.displayDirectory = Zotero.File.pathToFile(OS.Path.dirname(currentPath));
}
else if (updated) {
var label = Zotero.getString('zotero.preferences.update.updated');
fp.init(
window,
Zotero.getString('zotero.preferences.chooseApplication'),
nsIFilePicker.modeOpen
);
fp.appendFilters(nsIFilePicker.filterApps);
if (fp.show() != nsIFilePicker.returnOK) {
this._updateFileHandlerUI();
return false;
}
var newPath = OS.Path.normalize(fp.file.path);
this.setFileHandler(type, newPath);
},
setFileHandler: function (type, handler) {
var pref = this._getFileHandlerPref(type);
if (handler) {
Zotero.Prefs.set(pref, handler);
}
else {
var label = Zotero.getString('zotero.preferences.update.error');
Zotero.Prefs.clear(pref);
}
button.setAttribute('label', label);
this._updateFileHandlerUI();
},
if (updated && Zotero_Preferences.Cite) {
yield Zotero_Preferences.Cite.refreshStylesList();
_updateFileHandlerUI: function () {
var handler = Zotero.Prefs.get('fileHandler.pdf');
var menulist = document.getElementById('fileHandler-pdf');
var customMenuItem = document.getElementById('fileHandler-custom');
if (handler) {
let icon;
try {
let fph = Services.io.getProtocolHandler("file")
.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
let urlspec = fph.getURLSpecFromFile(Zotero.File.pathToFile(handler));
icon = "moz-icon://" + urlspec + "?size=16";
}
catch (e) {
Zotero.logError(e);
}
let handlerFilename = OS.Path.basename(handler);
if (Zotero.isMac) {
handlerFilename = handlerFilename.replace(/\.app$/, '');
}
customMenuItem.setAttribute('label', handlerFilename);
if (icon) {
customMenuItem.className = 'menuitem-iconic';
customMenuItem.setAttribute('image', icon);
}
else {
customMenuItem.className = '';
}
customMenuItem.hidden = false;
menulist.selectedIndex = 0;
}
else {
customMenuItem.hidden = true;
menulist.selectedIndex = 1;
}
},
_getFileHandlerPref: function (type) {
if (type != 'pdf') {
throw new Error(`Unknown file type ${type}`);
}
return 'fileHandler.pdf';
}
}
})
}

View File

@ -33,10 +33,12 @@
<preferences id="zotero-prefpane-general-preferences">
<preference id="pref-fontSize" name="extensions.zotero.fontSize" type="string"/>
<preference id="pref-layout" name="extensions.zotero.layout" type="string"/>
<preference id="pref-automaticScraperUpdates" name="extensions.zotero.automaticScraperUpdates" type="bool"/>
<preference id="pref-reportTranslationFailure" name="extensions.zotero.reportTranslationFailure" type="bool"/>
<preference id="pref-automaticSnapshots" name="extensions.zotero.automaticSnapshots" type="bool"/>
<preference id="pref-downloadAssociatedFiles" name="extensions.zotero.downloadAssociatedFiles" type="bool"/>
<preference id="pref-autoRecognizeFiles" name="extensions.zotero.autoRecognizeFiles" type="bool"/>
<preference id="pref-autoRenameFiles" name="extensions.zotero.autoRenameFiles" type="bool"/>
<preference id="pref-automaticTags" name="extensions.zotero.automaticTags" type="bool"/>
<preference id="pref-trashAutoEmptyDays" name="extensions.zotero.trashAutoEmptyDays" type="int"/>
@ -108,20 +110,33 @@
</grid>
</groupbox>
<groupbox id="zotero-prefpane-miscellaneous-groupbox">
<caption label="&zotero.preferences.miscellaneous;"/>
<groupbox id="zotero-prefpane-file-handling-groupbox">
<caption label="&zotero.preferences.fileHandling;"/>
<hbox align="center">
<checkbox label="&zotero.preferences.autoUpdate;" preference="pref-automaticScraperUpdates"/>
<button id="updateButton" style="margin-top:0" label="&zotero.preferences.updateNow;"
oncommand="Zotero_Preferences.General.updateTranslators()"/>
</hbox>
<checkbox label="&zotero.preferences.reportTranslationFailure;" preference="pref-reportTranslationFailure"/>
<checkbox id="automaticSnapshots-checkbox"
label="&zotero.preferences.automaticSnapshots;"
preference="pref-automaticSnapshots"/>
<checkbox label="&zotero.preferences.downloadAssociatedFiles;" preference="pref-downloadAssociatedFiles"/>
<checkbox label="&zotero.preferences.autoRecognizeFiles;" preference="pref-autoRecognizeFiles"/>
<checkbox label="&zotero.preferences.autoRenameFiles;" preference="pref-autoRenameFiles"/>
<hbox align="center">
<label value="&zotero.preferences.fileHandler.openPDFsUsing;" control="file-handler-pdf"/>
<menulist id="fileHandler-pdf" class="fileHandler-menu">
<menupopup>
<menuitem id="fileHandler-custom"/>
<menuitem label="&zotero.preferences.fileHandler.systemDefault;"
oncommand="Zotero_Preferences.General.setFileHandler('pdf', false)"/>
<menuitem label="&zotero.preferences.custom;"
oncommand="Zotero_Preferences.General.chooseFileHandler('pdf')"/>
</menupopup>
</menulist>
</hbox>
</groupbox>
<groupbox id="zotero-prefpane-miscellaneous-groupbox">
<caption label="&zotero.preferences.miscellaneous;"/>
<checkbox label="&zotero.preferences.automaticTags;" preference="pref-automaticTags"/>
<hbox align="center">
<label value="&zotero.preferences.trashAutoEmptyDaysPre;"/>

View File

@ -33,264 +33,10 @@ Zotero_Preferences.Search = {
document.getElementById('fulltext-clearIndex').setAttribute('label',
Zotero.getString('zotero.preferences.search.clearIndex')
+ Zotero.getString('punctuation.ellipsis'));
this.updatePDFToolsStatus();
this.updateIndexStats();
// Quick hack to support install prompt from PDF recognize option
var io = window.arguments[0];
if (io.action && io.action == 'pdftools-install') {
this.checkPDFToolsDownloadVersion();
}
},
/*
* Update window according to installation status for PDF tools
* (e.g. status line, install/update button, etc.)
*/
updatePDFToolsStatus: function () {
var converterIsRegistered = Zotero.Fulltext.pdfConverterIsRegistered();
var infoIsRegistered = Zotero.Fulltext.pdfInfoIsRegistered();
var converterStatusLabel = document.getElementById('pdfconverter-status');
var infoStatusLabel = document.getElementById('pdfinfo-status');
var requiredLabel = document.getElementById('pdftools-required');
var updateButton = document.getElementById('pdftools-update-button');
var documentationLink = document.getElementById('pdftools-documentation-link');
var settingsBox = document.getElementById('pdftools-settings');
// If we haven't already generated the required and documentation messages
if (!converterIsRegistered && !requiredLabel.hasChildNodes()) {
// Xpdf link
var str = Zotero.getString('zotero.preferences.search.pdf.toolsRequired',
[Zotero.Fulltext.pdfConverterName, Zotero.Fulltext.pdfInfoName,
'<a href="' + Zotero.Fulltext.pdfToolsURL + '">'
+ Zotero.Fulltext.pdfToolsName + '</a>']);
var parts = Zotero.Utilities.parseMarkup(str);
for (var i=0; i<parts.length; i++) {
var part = parts[i];
if (part.type == 'text') {
var elem = document.createTextNode(part.text);
}
else if (part.type == 'link') {
var elem = document.createElement('label');
elem.setAttribute('value', part.text);
elem.setAttribute('class', 'zotero-text-link');
for (var key in part.attributes) {
elem.setAttribute(key, part.attributes[key]);
if (key == 'href') {
elem.setAttribute('tooltiptext', part.attributes[key]);
}
}
}
requiredLabel.appendChild(elem);
}
requiredLabel.appendChild(document.createTextNode(' '
+ Zotero.getString('zotero.preferences.search.pdf.automaticInstall')));
// Documentation link
var link = '<a href="http://www.zotero.org/documentation/pdf_fulltext_indexing">'
+ Zotero.getString('zotero.preferences.search.pdf.documentationLink')
+ '</a>';
var str = Zotero.getString('zotero.preferences.search.pdf.advancedUsers', link);
var parts = Zotero.Utilities.parseMarkup(str);
for (var i=0; i<parts.length; i++) {
var part = parts[i];
if (part.type == 'text') {
var elem = document.createTextNode(part.text);
}
else if (part.type == 'link') {
var elem = document.createElement('label');
elem.setAttribute('value', part.text);
elem.setAttribute('class', 'zotero-text-link');
for (var key in part.attributes) {
elem.setAttribute(key, part.attributes[key]);
if (key == 'href') {
elem.setAttribute('tooltiptext', part.attributes[key]);
}
}
}
documentationLink.appendChild(elem);
}
}
// converter status line
var prefix = 'zotero.preferences.search.pdf.tool';
if (converterIsRegistered) {
var version = Zotero.Fulltext.pdfConverterVersion;
str = Zotero.getString(prefix + 'Registered',
Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfConverterName, version]));
}
else {
str = Zotero.getString(prefix + 'NotRegistered',
[Zotero.Fulltext.pdfConverterFileName]);
}
converterStatusLabel.setAttribute('value', str);
// pdfinfo status line
if (infoIsRegistered) {
var version = Zotero.Fulltext.pdfInfoVersion;
str = Zotero.getString(prefix + 'Registered',
Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfInfoName, version]));
}
else {
str = Zotero.getString(prefix + 'NotRegistered',
[Zotero.Fulltext.pdfInfoFileName]);
}
infoStatusLabel.setAttribute('value', str);
str = converterIsRegistered ?
Zotero.getString('general.checkForUpdate') :
Zotero.getString('zotero.preferences.search.pdf.checkForInstaller');
updateButton.setAttribute('label', str);
requiredLabel.setAttribute('hidden', converterIsRegistered);
documentationLink.setAttribute('hidden', converterIsRegistered);
settingsBox.setAttribute('hidden', !converterIsRegistered);
},
/*
* Check available versions of PDF tools from server and prompt for installation
* if a newer version is available
*/
checkPDFToolsDownloadVersion: Zotero.Promise.coroutine(function* () {
try {
var latestVersion = yield Zotero.Fulltext.getLatestPDFToolsVersion();
var converterIsRegistered = Zotero.Fulltext.pdfConverterIsRegistered();
var infoIsRegistered = Zotero.Fulltext.pdfInfoIsRegistered();
var bothRegistered = converterIsRegistered && infoIsRegistered;
// On Windows, install if not installed or anything other than 3.02a
if (Zotero.isWin) {
var converterVersionAvailable = !converterIsRegistered
|| Zotero.Fulltext.pdfConverterVersion != '3.02a';
var infoVersionAvailable = !infoIsRegistered
|| Zotero.Fulltext.pdfInfoVersion != '3.02a';
var bothAvailable = converterVersionAvailable && infoVersionAvailable;
}
// Install if not installed, version unknown, outdated, or
// Xpdf 3.02/3.04 (to upgrade to Poppler),
else {
var converterVersionAvailable = (!converterIsRegistered ||
Zotero.Fulltext.pdfConverterVersion == 'UNKNOWN'
|| latestVersion > Zotero.Fulltext.pdfConverterVersion
|| (!latestVersion.startsWith('3.02')
&& Zotero.Fulltext.pdfConverterVersion.startsWith('3.02'))
|| (!latestVersion.startsWith('3.02') && latestVersion != '3.04'
&& Zotero.Fulltext.pdfConverterVersion == '3.04'));
var infoVersionAvailable = (!infoIsRegistered ||
Zotero.Fulltext.pdfInfoVersion == 'UNKNOWN'
|| latestVersion > Zotero.Fulltext.pdfInfoVersion
|| (!latestVersion.startsWith('3.02')
&& Zotero.Fulltext.pdfInfoVersion.startsWith('3.02'))
|| (!latestVersion.startsWith('3.02') && latestVersion != '3.04'
&& Zotero.Fulltext.pdfInfoVersion == '3.04'));
var bothAvailable = converterVersionAvailable && infoVersionAvailable;
}
// Up to date -- disable update button
if (!converterVersionAvailable && !infoVersionAvailable) {
var button = document.getElementById('pdftools-update-button');
button.setAttribute('label', Zotero.getString('zotero.preferences.update.upToDate'));
button.setAttribute('disabled', true);
return;
}
// New version available -- display update prompt
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
var msg = Zotero.getString('zotero.preferences.search.pdf.available'
+ ((converterIsRegistered || infoIsRegistered) ? 'Updates' : 'Downloads'),
[Zotero.platform, 'zotero.org']) + '\n\n';
if (converterVersionAvailable) {
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfConverterName, latestVersion]);
msg += '- ' + tvp + '\n';
}
if (infoVersionAvailable) {
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfInfoName, latestVersion]);
msg += '- ' + tvp + '\n';
}
msg += '\n';
msg += Zotero.getString('zotero.preferences.search.pdf.zoteroCanInstallVersion'
+ (bothAvailable ? 's' : ''));
var index = ps.confirmEx(null,
converterIsRegistered ?
Zotero.getString('general.updateAvailable') : '',
msg,
buttonFlags,
converterIsRegistered ?
Zotero.getString('general.upgrade') :
Zotero.getString('general.install'),
null, null, null, {});
if (index != 0) {
return;
}
document.getElementById('pdftools-update-button').disabled = true;
var str = Zotero.getString('zotero.preferences.search.pdf.downloading');
document.getElementById('pdftools-update-button').setAttribute('label', str);
if (converterVersionAvailable) {
yield Zotero.Fulltext.downloadPDFTool('converter', latestVersion)
.catch(function (e) {
Zotero.logError(e);
throw new Error("Error downloading pdftotext");
});
}
if (infoVersionAvailable) {
yield Zotero.Fulltext.downloadPDFTool('info', latestVersion)
.catch(function (e) {
Zotero.logError(e);
throw new Error("Error downloading pdfinfo");
});
}
this.updatePDFToolsStatus();
}
catch (e) {
this.onPDFToolsDownloadError(e);
}
}),
onPDFToolsDownloadError: function (e) {
if (e == 404) {
var str = Zotero.getString('zotero.preferences.search.pdf.toolDownloadsNotAvailable',
Zotero.Fulltext.pdfToolsName) + ' '
+ Zotero.getString('zotero.preferences.search.pdf.viewManualInstructions');
}
else {
Components.utils.reportError(e);
var str = Zotero.getString('zotero.preferences.search.pdf.toolsDownloadError', Zotero.Fulltext.pdfToolsName)
+ ' ' + Zotero.getString('zotero.preferences.search.pdf.tryAgainOrViewManualInstructions');
}
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.createInstance(Components.interfaces.nsIPromptService);
ps.alert(
null,
Zotero.getString('pane.item.attachments.PDF.installTools.title'),
str
);
},
updateIndexStats: Zotero.Promise.coroutine(function* () {
var stats = yield Zotero.Fulltext.getIndexStats();
document.getElementById('fulltext-stats-indexed').

View File

@ -49,34 +49,10 @@
<textbox size="10" preference="pref-fulltext-textMaxLength"/>
<label value="(&zotero.preferences.default; 500000)"/>
</hbox>
</groupbox>
<groupbox id="pdftools-box">
<caption label="&zotero.preferences.search.pdfIndexing;"/>
<label id="pdfconverter-status"/>
<separator class="thin"/>
<label id="pdfinfo-status"/>
<separator class="thin"/>
<!-- This doesn't wrap without an explicit width -->
<label id="pdftools-required" width="45em" hidden="true"/>
<separator class="thin"/>
<hbox>
<button id="pdftools-update-button" flex="1" oncommand="Zotero_Preferences.Search.checkPDFToolsDownloadVersion()"/>
</hbox>
<separator class="thin"/>
<!-- This doesn't wrap without an explicit width -->
<label id="pdftools-documentation-link" width="45em" hidden="true"/>
<separator class="thin"/>
<hbox id="pdftools-settings" align="center" hidden="true">
<hbox align="center">
<label value="&zotero.preferences.fulltext.pdfMaxPages;"/>
<textbox size="5" preference="pref-fulltext-pdfmaxpages"/>
<label value="(&zotero.preferences.default; 100)"/>

View File

@ -398,7 +398,6 @@ Zotero_Preferences.Sync = {
onStorageSettingsKeyPress: Zotero.Promise.coroutine(function* (event) {
if (event.keyCode == 13) {
yield this.onStorageSettingsChange();
yield this.verifyStorageServer();
}
}),
@ -480,6 +479,11 @@ Zotero_Preferences.Sync = {
verifyStorageServer: Zotero.Promise.coroutine(function* () {
// onchange weirdly isn't triggered when clicking straight from a field to the button,
// so we have to trigger this here (and we don't trigger it for Enter in
// onStorageSettingsKeyPress()).
yield this.onStorageSettingsChange();
Zotero.debug("Verifying storage");
var verifyButton = document.getElementById("storage-verify");

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<window id="zotero-progress-meter-window"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
windowtype="alert:alert">
<vbox id="zotero-progress-text-box" flex="1">
<label/>
<progressmeter/>
</vbox>
</window>

View File

@ -1,943 +0,0 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, 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 *****
*/
/**
* @fileOverview Tools for automatically retrieving a citation for the given PDF
*/
/**
* Front end for recognizing PDFs
* @namespace
*/
var Zotero_RecognizePDF = new function() {
var _progressWindow, _progressIndicator;
/**
* Checks whether a given PDF could theoretically be recognized
* @returns {Boolean} True if the PDF can be recognized, false if it cannot be
*/
this.canRecognize = function(/**Zotero.Item*/ item) {
return item.attachmentContentType
&& item.attachmentContentType == "application/pdf"
&& item.isTopLevelItem();
}
/**
* Retrieves metadata for the PDF(s) selected in the Zotero Pane, placing the PDFs as a children
* of the new items
*/
this.recognizeSelected = function() {
var installed = ZoteroPane_Local.checkPDFConverter();
if (!installed) {
return;
}
var items = ZoteroPane_Local.getSelectedItems();
if (!items) return;
var itemRecognizer = new Zotero_RecognizePDF.ItemRecognizer();
itemRecognizer.recognizeItems(items);
}
/**
* Retrieves metadata for a PDF and saves it as an item
*
* @param {nsIFile} file The PDF file to retrieve metadata for
* @param {Integer} libraryID The library in which to save the PDF
* @param {Function} stopCheckCallback Function that returns true if the
* process is to be interrupted
* @return {Promise} A promise resolved when PDF metadata has been retrieved
*/
this.recognize = Zotero.Promise.coroutine(function* (file, libraryID, stopCheckCallback) {
const MAX_PAGES = 15;
var me = this;
var lines = yield _extractText(file, MAX_PAGES);
// Look for DOI - Use only first 80 lines to avoid catching article references
var allText = lines.join("\n"),
firstChunk = lines.slice(0,80).join('\n'),
doi = Zotero.Utilities.cleanDOI(firstChunk),
promise;
Zotero.debug(allText);
if(!doi) {
// Look for a JSTOR stable URL, which can be converted to a DOI by prepending 10.2307
doi = firstChunk.match(/www.\jstor\.org\/stable\/(\S+)/i);
if(doi) {
doi = Zotero.Utilities.cleanDOI(
doi[1].indexOf('10.') == 0 ? doi[1] : '10.2307/' + doi[1]
);
}
}
var newItem;
if (doi) {
// Look up DOI
Zotero.debug("RecognizePDF: Found DOI: "+doi);
var translateDOI = new Zotero.Translate.Search();
translateDOI.setTranslator("11645bd1-0420-45c1-badb-53fb41eeb753");
translateDOI.setSearch({"itemType":"journalArticle", "DOI":doi});
try {
newItem = yield _promiseTranslate(translateDOI, libraryID);
return newItem;
}
catch (e) {
Zotero.debug("RecognizePDF: " + e);
}
}
else {
Zotero.debug("RecognizePDF: No DOI found in text");
}
// Look for ISBNs if no DOI
var isbns = _findISBNs(allText);
if (isbns.length) {
Zotero.debug("RecognizePDF: Found ISBNs: " + isbns);
var translate = new Zotero.Translate.Search();
translate.setSearch({"itemType":"book", "ISBN":isbns[0]});
try {
newItem = yield _promiseTranslate(translate, libraryID);
return newItem;
}
catch (e) {
// If no DOI or ISBN, query Google Scholar
Zotero.debug("RecognizePDF: " + e);
}
}
else {
Zotero.debug("RecognizePDF: No ISBN found in text");
}
return this.GSFullTextSearch.findItem(lines, libraryID, stopCheckCallback);
});
/**
* Get text from a PDF
* @param {nsIFile} file PDF
* @param {Number} pages Number of pages to extract
* @return {Promise}
*/
function _extractText(file, pages) {
var cacheFile = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
cacheFile.append("recognizePDFcache.txt");
if(cacheFile.exists()) {
cacheFile.remove(false);
}
var {exec, args} = Zotero.Fulltext.getPDFConverterExecAndArgs();
args.push('-enc', 'UTF-8', '-nopgbrk', '-layout', '-l', pages, file.path, cacheFile.path);
Zotero.debug("RecognizePDF: Running " + exec.path + " " + args.map(arg => "'" + arg + "'").join(" "));
return Zotero.Utilities.Internal.exec(exec, args).then(function() {
if(!cacheFile.exists()) {
throw new Zotero.Exception.Alert("recognizePDF.couldNotRead");
}
try {
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
inputStream.init(cacheFile, 0x01, 0o664, 0);
try {
var intlStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
intlStream.init(inputStream, "UTF-8", 65535,
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
intlStream.QueryInterface(Components.interfaces.nsIUnicharLineInputStream);
// get the lines in this sample
var lines = [], str = {};
while(intlStream.readLine(str)) {
var line = str.value.trim();
if(line) lines.push(line);
}
} finally {
inputStream.close();
}
} finally {
cacheFile.remove(false);
}
return lines;
}, function() {
throw new Zotero.Exception.Alert("recognizePDF.couldNotRead");
});
}
/**
* Attach appropriate handlers to a Zotero.Translate instance and begin translation
* @return {Promise}
*/
var _promiseTranslate = Zotero.Promise.coroutine(function* (translate, libraryID) {
translate.setHandler("select", function(translate, items, callback) {
for(var i in items) {
var obj = {};
obj[i] = items[i];
callback(obj);
return;
}
});
/*translate.setHandler("done", function(translate, success) {
if(success && translate.newItems.length) {
deferred.resolve(translate.newItems[0]);
} else {
deferred.reject(translate.translator && translate.translator.length
? "Translation with " + translate.translator.map(t => t.label) + " failed"
: "Could not find a translator for given search item"
);
}
});*/
var newItems = yield translate.translate({
libraryID,
saveAttachments: false
});
if (newItems.length) {
return newItems[0];
}
throw new Error("No items found");
});
/**
* Search ISBNs in text
* @private
* @return {String[]} Array of ISBNs
*/
function _findISBNs(x) {
if(typeof(x) != "string") {
throw "findISBNs: argument must be a string";
}
var isbns = [];
// Match lines saying "isbn: " or "ISBN-10:" or similar, consider m-dashes and n-dashes as well
var pattern = /(SBN|sbn)[ \u2014\u2013\u2012-]?(10|13)?[: ]*([0-9X][0-9X \u2014\u2013\u2012-]+)/g;
var match;
while (match = pattern.exec(x)) {
var isbn = match[3];
isbn = isbn.replace(/[ \u2014\u2013\u2012-]/g, '');
if(isbn.length==20 || isbn.length==26) {
// Handle the case of two isbns (e.g. paper+hardback) next to each other
isbns.push(isbn.slice(0,isbn.length/2), isbn.slice(isbn.length/2));
} else if(isbn.length==23) {
// Handle the case of two isbns (10+13) next to each other
isbns.push(isbn.slice(0,10), isbn.slice(10));
} else if(isbn.length==10 || isbn.length==13) {
isbns.push(isbn);
}
}
// Validate ISBNs
var validIsbns = [], cleanISBN;
for (var i =0; i < isbns.length; i++) {
cleanISBN = Zotero.Utilities.cleanISBN(isbns[i]);
if(cleanISBN) validIsbns.push(cleanISBN);
}
return validIsbns;
}
/**
* @class Handles UI, etc. for recognizing multiple items
*/
this.ItemRecognizer = function () {
this._items = [];
}
this.ItemRecognizer.prototype = {
"_stopped": false,
"_itemsTotal": 0,
"_progressWindow": null,
"_progressIndicator": null,
/**
* Retreives metadata for the PDF items passed, displaying a progress dialog during conversion
* and placing the PDFs as a children of the new items
* @param {Zotero.Item[]} items
*/
"recognizeItems": function(items) {
var me = this;
this._items = items.slice();
this._itemTotal = items.length;
_progressWindow = this._progressWindow = window.openDialog("chrome://zotero/content/pdfProgress.xul", "", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen");
this._progressWindow.addEventListener("pageshow", function() { me._onWindowLoaded() }, false);
},
/**
* Halts recognition of PDFs
*/
"stop": function() {
this._stopped = true;
},
/**
* Halts recognition and closes window
*/
"close": function() {
this.stop();
this._progressWindow.close();
},
/**
* Called when the progress window has been opened; adds items to the tree and begins recognizing
* @param
*/
"_onWindowLoaded": function() {
// populate progress window
var treechildren = this._progressWindow.document.getElementById("treechildren");
this._rowIDs = [];
for(var i in this._items) {
var treeitem = this._progressWindow.document.createElement('treeitem');
var treerow = this._progressWindow.document.createElement('treerow');
this._rowIDs.push(this._items[i].id);
var treecell = this._progressWindow.document.createElement('treecell');
treecell.setAttribute("id", "item-"+this._items[i].id+"-icon");
treerow.appendChild(treecell);
treecell = this._progressWindow.document.createElement('treecell');
treecell.setAttribute("label", this._items[i].getField("title"));
treerow.appendChild(treecell);
treecell = this._progressWindow.document.createElement('treecell');
treecell.setAttribute("id", "item-"+this._items[i].id+"-title");
treerow.appendChild(treecell);
treeitem.appendChild(treerow);
treechildren.appendChild(treeitem);
}
var me = this;
this._progressWindow.document.getElementById("tree").addEventListener(
"dblclick", function(event) { me._onDblClick(event, this); });
this._cancelHandler = function() { me.stop() };
this._keypressCancelHandler = function(e) {
if(e.keyCode === KeyEvent.DOM_VK_ESCAPE) me.stop();
};
_progressIndicator = this._progressIndicator = this._progressWindow.document.getElementById("progress-indicator");
this._progressWindow.document.getElementById("cancel-button")
.addEventListener("command", this._cancelHandler, false);
// Also cancel if the user presses Esc
this._progressWindow.addEventListener("keypress", this._keypressCancelHandler);
this._progressWindow.addEventListener("close", this._cancelHandler, false);
Zotero_RecognizePDF.GSFullTextSearch.resetQueryLimit();
return this._recognizeItem();
},
/**
* Shifts an item off of this._items and recognizes it, then calls itself again if there are more
* @private
*/
"_recognizeItem": Zotero.Promise.coroutine(function* () {
const SUCCESS_IMAGE = "chrome://zotero/skin/tick.png";
const FAILURE_IMAGE = "chrome://zotero/skin/cross.png";
const LOADING_IMAGE = "chrome://global/skin/icons/loading_16.png";
if(!this._items.length) {
this._done();
return;
}
// Order here matters. Otherwise we may show an incorrect label
if(this._stopped) {
this._done(true);
return;
}
this._progressIndicator.value = (this._itemTotal-this._items.length)/this._itemTotal*100;
var item = this._items.shift(),
itemIcon = this._progressWindow.document.getElementById("item-"+item.id+"-icon"),
itemTitle = this._progressWindow.document.getElementById("item-"+item.id+"-title"),
rowNumber = this._rowIDs.indexOf(item.id);
itemIcon.setAttribute("src", LOADING_IMAGE);
itemTitle.setAttribute("label", "");
var file = item.getFile(), me = this;
try {
if (file) {
let newItem = yield Zotero_RecognizePDF.recognize(
file,
item.libraryID,
() => this._stopped
);
// If already stopped, delete
if (this._stopped) {
yield Zotero.Items.erase(newItem.id);
throw new Zotero.Exception.Alert('recognizePDF.stopped');
}
// put new item in same collections as the old one
let itemCollections = item.getCollections();
yield Zotero.DB.executeTransaction(function* () {
for (let i = 0; i < itemCollections.length; i++) {
let collection = Zotero.Collections.get(itemCollections[i]);
yield collection.addItem(newItem.id);
}
// put old item as a child of the new item
item.parentID = newItem.id;
yield item.save();
});
itemTitle.setAttribute("label", newItem.getField("title"));
itemIcon.setAttribute("src", SUCCESS_IMAGE);
this._rowIDs[rowNumber] = newItem.id;
return this._recognizeItem();
}
else {
throw new Zotero.Exception.Alert("recognizePDF.fileNotFound");
}
}
catch (e) {
Zotero.logError(e);
itemTitle.setAttribute(
"label",
e instanceof Zotero.Exception.Alert
? e.message
: Zotero.getString("recognizePDF.error")
);
itemIcon.setAttribute("src", FAILURE_IMAGE);
// Don't show "completed" label if stopped on last item
if (this._stopped && !this._items.length) {
this._done(true);
} else {
return this._recognizeItem();
}
}
finally {
// scroll to this item
this._progressWindow.document.getElementById("tree").treeBoxObject.scrollToRow(
Math.max(0, this._itemTotal - this._items.length - 4)
);
}
}),
/**
* Cleans up after items are recognized, disabling the cancel button and
* making the progress window close on blur.
* @param {Boolean} cancelled Whether the process was cancelled
*/
"_done": function(cancelled) {
this._progressIndicator.value = 100;
// Switch out cancel for close
var cancelButton = this._progressWindow.document.getElementById("cancel-button"),
me = this;
cancelButton.label = Zotero.getString("recognizePDF.close.label");
cancelButton.removeEventListener("command", this._cancelHandler, false);
cancelButton.addEventListener("command", function() { me.close() }, false);
this._progressWindow.removeEventListener("keypress", this._keypressCancelHandler);
this._progressWindow.addEventListener("keypress", function() { me.close() });
if(Zotero.isMac) {
// On MacOS X, the windows are not always on top, so we hide them on
// blur to avoid clutter
this._setCloseTimer();
}
this._progressWindow.document.getElementById("label").value =
cancelled ? Zotero.getString("recognizePDF.cancelled.label")
: Zotero.getString("recognizePDF.complete.label");
},
/**
* Set a timer after which the window will close automatically. If the
* window is refocused, clear the timer and do not attempt to auto-close
* any more
* @private
*/
"_setCloseTimer": function() {
var me = this, win = this._progressWindow;
var focusListener = function() {
if(!win.zoteroCloseTimeoutID) return;
win.clearTimeout(win.zoteroCloseTimeoutID);
delete win.zoteroCloseTimeoutID;
win.removeEventListener('blur', blurListener, false);
win.removeEventListener('focus', focusListener, false);
};
var blurListener = function() {
// Close window after losing focus for 5 seconds
win.zoteroCloseTimeoutID = win.setTimeout(function() { win.close() }, 5000);
// Prevent auto-close if we gain focus again
win.addEventListener("focus", focusListener, false);
};
win.addEventListener("blur", blurListener, false);
},
/**
* Focus items in Zotero library when double-clicking them in the Retrieve
* metadata window.
* @param {Event} event
* @param {tree} tree XUL tree object
* @private
*/
"_onDblClick": function(event, tree) {
if (event && tree && event.type == "dblclick") {
var itemID = this._rowIDs[tree.treeBoxObject.getRowAt(event.clientX, event.clientY)];
if(!itemID) return;
// Get the right window. In tab mode, it's the container window
var lastWin = (window.ZoteroTab ? window.ZoteroTab.containerWindow : window);
if (lastWin.ZoteroOverlay) {
lastWin.ZoteroOverlay.toggleDisplay(true);
}
lastWin.ZoteroPane.selectItem(itemID, false, true);
lastWin.focus();
}
}
};
/**
* Singleton for querying Google Scholar. Ensures that all queries are
* sequential and respect the delay inbetween queries.
* @namespace
*/
this.GSFullTextSearch = new function() {
const GOOGLE_SCHOLAR_QUERY_DELAY = 2000; // In ms
var queryLimitReached = false,
inProgress = false,
queue = [],
stopCheckCallback; // As long as we process one query at a time, this is ok
// Load nsICookieManager2
Components.utils.import("resource://gre/modules/Services.jsm");
var cookieService = Services.cookies;
/**
* Reset "Query Limit Reached" flag, so that we attempt to query Google again
*/
this.resetQueryLimit = function() {
queryLimitReached = false;
};
/**
* Queue up item for Google Scholar query
* @param {String[]} lines Lines of text to use for full-text query
* @param {Integer | null} libraryID Library to save the item to
* @param {Function} stopCheckCallback Function that returns true if the
* process is to be interrupted
* @return {Promise} A promise resolved when PDF metadata has been retrieved
*/
this.findItem = function(lines, libraryID, stopCheckCallback) {
if(!inProgress && queryLimitReached) {
// There's no queue, so we can reject immediately
return Zotero.Promise.reject(new Zotero.Exception.Alert("recognizePDF.limit"));
}
var deferred = Zotero.Promise.defer();
queue.push({
deferred: deferred,
lines: lines,
libraryID: libraryID,
stopCheckCallback: stopCheckCallback
});
_processQueue();
return deferred.promise;
};
/**
* Process Google Scholar queue
* @private
* @param {Boolean} proceed Whether we should pop the next item off the queue
* This should not be true unless being called after processing
* another item
*/
function _processQueue(proceed) {
if(inProgress && !proceed) return; //only one at a time
if(!queue.length) {
inProgress = false;
return;
}
inProgress = true;
if(queryLimitReached) {
// Irreversibly blocked. Reject remaining items in queue
var item;
while(item = queue.shift()) {
item.deferred.reject(new Zotero.Exception.Alert("recognizePDF.limit"));
}
_processQueue(true); // Wrap it up
} else {
var item = queue.shift();
stopCheckCallback = item.stopCheckCallback;
if(stopCheckCallback && stopCheckCallback()) {
item.deferred.reject(new Zotero.Exception.Alert('recognizePDF.stopped'));
_processQueue(true);
return;
}
item.deferred.resolve(
Zotero.Promise.try(function () {
var lines = getGoodLines(item.lines);
return queryGoogle(lines, item.libraryID, 3); // Try querying 3 times
})
.finally(function() { _processQueue(true); })
);
}
}
/**
* Select lines that are good candidates for Google Scholar query
* @private
* @param {String[]} lines
* @return {String[]}
*/
function getGoodLines(lines) {
// Use only first column from multi-column lines
const lineRe = /^[\s_]*([^\s]+(?: [^\s_]+)+)/;
var cleanedLines = [], cleanedLineLengths = [];
for(var i=0; i<lines.length && cleanedLines.length<100; i++) {
var m = lineRe.exec(
lines[i]
// Replace non-breaking spaces
.replace(/\xA0/g, ' ')
);
if(m && m[1].split(' ').length > 3) {
cleanedLines.push(m[1]);
cleanedLineLengths.push(m[1].length);
}
}
// Get (not quite) median length
var lineLengthsLength = cleanedLineLengths.length;
if(lineLengthsLength < 20
|| cleanedLines[0] === "This is a digital copy of a book that was preserved for generations on library shelves before it was carefully scanned by Google as part of a project") {
throw new Zotero.Exception.Alert("recognizePDF.noOCR");
}
var sortedLengths = cleanedLineLengths.sort(),
medianLength = sortedLengths[Math.floor(lineLengthsLength/2)];
// Pick lines within 6 chars of the median (this is completely arbitrary)
var goodLines = [],
uBound = medianLength + 6,
lBound = medianLength - 6;
for (var i=0; i<lineLengthsLength; i++) {
if(cleanedLineLengths[i] > lBound && cleanedLineLengths[i] < uBound) {
// Strip quotation marks so they don't mess up search query quoting
var line = cleanedLines[i].replace('"', '');
goodLines.push(line);
}
}
return goodLines;
}
/**
* Query Google Scholar
* @private
* @param {String[]} goodLines
* @param {Integer | null} libraryID
* @param {Integer} tries Number of queries to attempt before giving up
* @return {Promise} A promise resolved when PDF metadata has been retrieved
*/
var queryGoogle = Zotero.Promise.coroutine(function* (goodLines, libraryID, tries) {
if(tries <= 0) throw new Zotero.Exception.Alert("recognizePDF.noMatches");
// Take the relevant parts of some lines (exclude hyphenated word)
var queryString = "", queryStringWords = 0, nextLine = 0;
while(queryStringWords < 25) {
if(!goodLines.length) throw new Zotero.Exception.Alert("recognizePDF.noMatches");
var words = goodLines.splice(nextLine, 1)[0].split(/\s+/);
// Try to avoid picking adjacent strings so the odds of them appearing in another
// document quoting our document is low. Every 7th line is a magic value
nextLine = (nextLine + 7) % goodLines.length;
// Get rid of first and last words
words.shift();
words.pop();
// Make sure there are no long words (probably OCR mistakes)
var skipLine = false;
for(var i=0; i<words.length; i++) {
if(words[i].length > 20) {
skipLine = true;
break;
}
}
// Add words to query
if(!skipLine && words.length) {
queryStringWords += words.length;
queryString += '"'+words.join(" ")+'" ';
}
}
Zotero.debug("RecognizePDF: Query string " + queryString);
var url = "https://scholar.google.com/scholar?q="+encodeURIComponent(queryString)+"&hl=en&lr=&btnG=Search",
delay = GOOGLE_SCHOLAR_QUERY_DELAY - (Date.now() - Zotero.HTTP.lastGoogleScholarQueryTime);
// Delay
if (delay > 0) {
yield Zotero.Promise.delay(delay);
}
Zotero.HTTP.lastGoogleScholarQueryTime = Date.now();
try {
let xmlhttp = yield Zotero.HTTP.request("GET", url, { "responseType": "document" })
.then(
function (xmlhttp) {
return _checkCaptchaOK(xmlhttp, 3);
},
function (e) {
return _checkCaptchaError(e, 3);
}
);
let doc = xmlhttp.response,
deferred = Zotero.Promise.defer(),
translate = new Zotero.Translate.Web();
translate.setTranslator("57a00950-f0d1-4b41-b6ba-44ff0fc30289");
translate.setDocument(Zotero.HTTP.wrapDocument(doc, url));
translate.setHandler("translators", function(translate, detected) {
if(detected.length) {
deferred.resolve(_promiseTranslate(translate, libraryID));
} else {
deferred.resolve(Zotero.Promise.try(function() {
return queryGoogle(goodLines, libraryID, tries-1);
}));
}
});
translate.getTranslators();
return deferred.promise;
}
catch (e) {
if(e.name == "recognizePDF.limit") {
queryLimitReached = true;
}
throw e;
}
});
/**
* Check for CAPTCHA on a page with HTTP 200 status
* @private
* @param {XMLHttpRequest} xmlhttp
* @param {Integer} tries Number of queries to attempt before giving up
* @return {Promise} A promise resolved when PDF metadata has been retrieved
*/
function _checkCaptchaOK(xmlhttp, tries) {
if(stopCheckCallback && stopCheckCallback()) {
throw new Zotero.Exception.Alert('recognizePDF.stopped');
}
Zotero.debug("RecognizePDF: (" + xmlhttp.status + ") Got page with title " + xmlhttp.response.title);
if(Zotero.Utilities.xpath(xmlhttp.response, "//form[@action='Captcha']").length) {
Zotero.debug("RecognizePDF: Found CAPTCHA on page.");
return _solveCaptcha(xmlhttp, tries);
}
return xmlhttp;
}
/**
* Check for CAPTCHA on an error page. Handle 403 and 503 pages
* @private
* @param {Zotero.HTTP.UnexpectedStatusException} e HTTP response error object
* @param {Integer} tries Number of queries to attempt before giving up
* @param {Boolean} dontClearCookies Whether to attempt to clear cookies in
* in order to get CAPTCHA to show up
* @return {Promise} A promise resolved when PDF metadata has been retrieved
*/
var _checkCaptchaError = Zotero.Promise.coroutine(function* (e, tries, dontClearCookies) {
if(stopCheckCallback && stopCheckCallback()) {
throw new Zotero.Exception.Alert('recognizePDF.stopped');
}
Zotero.debug("RecognizePDF: Checking for CAPTCHA on Google Scholar error page (" + e.status + ")");
// Check for captcha on error page
if(e instanceof Zotero.HTTP.UnexpectedStatusException
&& (e.status == 403 || e.status == 503) && e.xmlhttp.response) {
if(_extractCaptchaFormData(e.xmlhttp.response)) {
Zotero.debug("RecognizePDF: CAPTCHA found");
return _solveCaptcha(e.xmlhttp, tries);
} else if(!dontClearCookies && e.xmlhttp.channel) { // Make sure we can obtain original URL
// AFAICT, for 403 errors, GS just says "sorry, try later",
// but if you clear cookies, you get a CAPTCHA
Zotero.debug("RecognizePDF: No CAPTCHA detected on page. Clearing cookies.");
if(!_clearGSCookies(e.xmlhttp.channel.originalURI.host)) {
//user said no or no cookies removed
throw new Zotero.Exception.Alert('recognizePDF.limit');
}
// Redo GET request
Zotero.debug("RecognizePDF: Reloading page after clearing cookies.");
return Zotero.HTTP.request(
"GET", e.xmlhttp.channel.originalURI.spec, { "responseType": "document" }
)
.then(
function (xmlhttp) {
return _checkCaptchaOK(xmlhttp, tries);
},
function (e) {
return _checkCaptchaError(e, tries, true); // Don't try this again
}
);
}
Zotero.debug("RecognizePDF: Google Scholar returned an unexpected page"
+ " with status " + e.status);
throw new Zotero.Exception.Alert('recognizePDF.limit');
}
throw e;
});
/**
* Prompt user to enter CPATCHA
* @private
* @param {XMLHttpRequest} xmlhttp
* @param {Integer} [tries] Number of queries to attempt before giving up
* @return {Promise} A promise resolved when PDF metadata has been retrieved
*/
function _solveCaptcha(xmlhttp, tries) {
var doc = xmlhttp.response;
if(tries === undefined) tries = 3;
if(!tries) {
Zotero.debug("RecognizePDF: Failed to solve CAPTCHA after multiple attempts.");
throw new Zotero.Exception.Alert('recognizePDF.limit');
}
tries--;
var formData = doc && _extractCaptchaFormData(doc);
if(!formData) {
Zotero.debug("RecognizePDF: Could not find CAPTCHA on page.");
throw new Zotero.Exception.Alert('recognizePDF.limit');
}
var io = { dataIn: {
title: Zotero.getString("recognizePDF.captcha.title"),
description: Zotero.getString("recognizePDF.captcha.description"),
imgUrl: formData.img
}};
_progressWindow.openDialog("chrome://zotero/content/captcha.xul", "",
"chrome,modal,resizable=no,centerscreen", io);
if(!io.dataOut) {
Zotero.debug("RecognizePDF: No CAPTCHA entered");
throw new Zotero.Exception.Alert('recognizePDF.limit');
}
Zotero.debug('RecognizePDF: User entered "' + io.dataOut.captcha + '" for CAPTCHA');
formData.input.captcha = io.dataOut.captcha;
var url = '', prop;
for(prop in formData.input) {
url += '&' + encodeURIComponent(prop) + '='
+ encodeURIComponent(formData.input[prop]);
}
url = formData.action + '?' + url.substr(1);
return Zotero.HTTP.promise("GET", url, {"responseType":"document"})
.then(function(xmlhttp) {
return _checkCaptchaOK(xmlhttp, tries);
},
function(e) {
return _checkCaptchaError(e, tries);
});
}
/**
* Extract CAPTCHA form-related data from the CAPTCHA page
* @private
* @param {Document} doc DOM document object for the CAPTCHA page
* @return {Object} Object containing data describing CAPTCHA form
*/
function _extractCaptchaFormData(doc) {
var formData = {};
var img = doc.getElementsByTagName('img')[0];
if(!img) return;
formData.img = img.src;
var form = doc.forms[0];
if(!form) return;
formData.action = form.action;
formData.input = {};
var inputs = form.getElementsByTagName('input');
for(var i=0, n=inputs.length; i<n; i++) {
if(!inputs[i].name) continue;
formData.input[inputs[i].name] = inputs[i].value;
}
formData.continue = "https://scholar.google.com";
return formData;
}
/**
* Clear Google cookies to get the CAPTCHA page to appear
* @private
* @param {String} host Host of the Google Scholar page (in case it's proxied)
* @return {Boolean} Whether any cookies were cleared
*/
function _clearGSCookies(host) {
/* There don't seem to be any negative effects of deleting GDSESS
if(!Zotero.isStandalone) {
//ask user first
var response = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.confirm(null, "Clear Google Scholar cookies?",
"Google Scholar is attempting to block further queries. We can "
+ "clear certain cookies and try again. This may affect some "
+ "temporary Google preferences or it may log you out. May we clear"
+ " your Google Scholar cookies?");
if(!response) return;
}*/
var removed = false, cookies = cookieService.getCookiesFromHost(host);
while(cookies.hasMoreElements()) {
var cookie = cookies.getNext().QueryInterface(Components.interfaces.nsICookie2);
if(["GDSESS", "PREF"].indexOf(cookie.name) !== -1) { // GDSESS doesn't seem to always be enough
Zotero.debug("RecognizePDF: Removing cookie " + cookie.name + " for host "
+ cookie.host + " and path " + cookie.path);
cookieService.remove(cookie.host, cookie.name, cookie.path, false);
removed = true;
}
}
if(!removed) {
Zotero.debug("RecognizePDF: No cookies removed");
}
return removed;
}
};
}

View File

@ -0,0 +1,219 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, 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 *****
*/
/**
* @fileOverview Tools for automatically retrieving a citation for the given PDF
*/
/**
* Front end for recognizing PDFs
* @namespace
*/
var Zotero_RecognizePDF_Dialog = new function () {
const SUCCESS_IMAGE = 'chrome://zotero/skin/tick.png';
const FAILURE_IMAGE = 'chrome://zotero/skin/cross.png';
const LOADING_IMAGE = 'chrome://zotero/skin/arrow_refresh.png';
let _progressWindow = null;
let _progressIndicator = null;
let _rowIDs = [];
this.open = function() {
if (_progressWindow) {
_progressWindow.focus();
return;
}
_progressWindow = window.openDialog('chrome://zotero/content/recognizePDFDialog.xul', '', 'chrome,close=yes,resizable=yes,dependent,dialog,centerscreen');
_progressWindow.addEventListener('pageshow', _onWindowLoaded.bind(this), false);
};
function close() {
_progressWindow.close();
}
function _getImageByStatus(status) {
if (status === Zotero.RecognizePDF.ROW_PROCESSING) {
return LOADING_IMAGE;
}
else if (status === Zotero.RecognizePDF.ROW_FAILED) {
return FAILURE_IMAGE;
}
else if (status === Zotero.RecognizePDF.ROW_SUCCEEDED) {
return SUCCESS_IMAGE;
}
return '';
}
function _rowToTreeItem(row) {
let treeitem = _progressWindow.document.createElement('treeitem');
treeitem.setAttribute('id', 'item-' + row.id);
let treerow = _progressWindow.document.createElement('treerow');
let treecell = _progressWindow.document.createElement('treecell');
treecell.setAttribute('id', 'item-' + row.id + '-icon');
treecell.setAttribute('src', _getImageByStatus(row.status));
treerow.appendChild(treecell);
treecell = _progressWindow.document.createElement('treecell');
treecell.setAttribute('label', row.fileName);
treerow.appendChild(treecell);
treecell = _progressWindow.document.createElement('treecell');
treecell.setAttribute('id', 'item-' + row.id + '-title');
treecell.setAttribute('label', row.message);
treerow.appendChild(treecell);
treeitem.appendChild(treerow);
return treeitem;
}
function _onWindowLoaded() {
let rows = Zotero.RecognizePDF.getRows();
_rowIDs = [];
let treechildren = _progressWindow.document.getElementById('treechildren');
for (let row of rows) {
_rowIDs.push(row.id);
let treeitem = _rowToTreeItem(row);
treechildren.appendChild(treeitem);
}
_progressWindow.document.getElementById('tree').addEventListener('dblclick',
function (event) {
_onDblClick(event, this);
}
);
_progressIndicator = _progressWindow.document.getElementById('progress-indicator');
_progressWindow.document.getElementById('cancel-button')
.addEventListener('command', function () {
close();
Zotero.RecognizePDF.cancel();
}, false);
_progressWindow.document.getElementById('minimize-button')
.addEventListener('command', function () {
close();
}, false);
_progressWindow.document.getElementById('close-button')
.addEventListener('command', function () {
close();
Zotero.RecognizePDF.cancel();
}, false);
_progressWindow.addEventListener('keypress', function (e) {
if (e.keyCode === KeyEvent.DOM_VK_ESCAPE) {
// If done processing, Esc is equivalent to Close rather than Minimize
if (Zotero.RecognizePDF.getTotal() == Zotero.RecognizePDF.getProcessedTotal()) {
Zotero.RecognizePDF.cancel();
}
close();
}
});
_progressWindow.addEventListener('unload', function () {
Zotero.RecognizePDF.removeListener('rowadded');
Zotero.RecognizePDF.removeListener('rowupdated');
Zotero.RecognizePDF.removeListener('rowdeleted');
_progressWindow = null;
_progressIndicator = null;
_rowIDs = [];
});
_updateProgress();
Zotero.RecognizePDF.addListener('rowadded', function (row) {
_rowIDs.push(row.id);
let treeitem = _rowToTreeItem(row);
treechildren.appendChild(treeitem);
_updateProgress();
});
Zotero.RecognizePDF.addListener('rowupdated', function (row) {
let itemIcon = _progressWindow.document.getElementById('item-' + row.id + '-icon');
let itemTitle = _progressWindow.document.getElementById('item-' + row.id + '-title');
itemIcon.setAttribute('src', _getImageByStatus(row.status));
itemTitle.setAttribute('label', row.message);
_updateProgress();
});
Zotero.RecognizePDF.addListener('rowdeleted', function (row) {
_rowIDs.splice(_rowIDs.indexOf(row.id), 1);
let treeitem = _progressWindow.document.getElementById('item-' + row.id);
treeitem.parentNode.removeChild(treeitem);
_updateProgress();
});
}
function _updateProgress() {
if (!_progressWindow) return;
let total = Zotero.RecognizePDF.getTotal();
let processed = Zotero.RecognizePDF.getProcessedTotal();
_progressIndicator.value = processed * 100 / total;
if (processed === total) {
_progressWindow.document.getElementById("cancel-button").hidden = true;
_progressWindow.document.getElementById("minimize-button").hidden = true;
_progressWindow.document.getElementById("close-button").hidden = false;
_progressWindow.document.getElementById("label").value = Zotero.getString('recognizePDF.complete.label');
}
else {
_progressWindow.document.getElementById("cancel-button").hidden = false;
_progressWindow.document.getElementById("minimize-button").hidden = false;
_progressWindow.document.getElementById("close-button").hidden = true;
_progressWindow.document.getElementById("label").value = Zotero.getString('recognizePDF.recognizing.label');
}
}
/**
* Focus items in Zotero library when double-clicking them in the Retrieve
* metadata window.
* @param {Event} event
* @param {tree} tree XUL tree object
* @private
*/
async function _onDblClick(event, tree) {
if (event && tree && event.type === 'dblclick') {
let itemID = _rowIDs[tree.treeBoxObject.getRowAt(event.clientX, event.clientY)];
if (!itemID) return;
let item = await Zotero.Items.getAsync(itemID);
if (!item) return;
if (item.parentItemID) itemID = item.parentItemID;
if (window.ZoteroOverlay) {
window.ZoteroOverlay.toggleDisplay(true);
}
window.ZoteroPane.selectItem(itemID, false, true);
window.focus();
}
}
};

View File

@ -6,10 +6,12 @@
title="&zotero.progress.title;" width="550" height="230"
id="zotero-progress">
<vbox style="padding:10px" flex="1">
<label id="label" control="progress-indicator" value="&zotero.recognizePDF.recognizing.label;"/>
<label id="label" control="progress-indicator" value=""/>
<hbox align="center">
<progressmeter id="progress-indicator" mode="determined" flex="1"/>
<button id="cancel-button" label="&zotero.recognizePDF.cancel.label;"/>
<button id="cancel-button" label="&zotero.general.cancel;"/>
<button id="minimize-button" label="&zotero.general.minimize;"/>
<button id="close-button" label="&zotero.general.close;"/>
</hbox>
<tree flex="1" id="tree" hidecolumnpicker="true">
<treecols>

View File

@ -139,6 +139,15 @@ const ZoteroStandalone = new function() {
this.updateQuickCopyOptions = function () {
var selected = false;
try {
selected = Zotero.getActiveZoteroPane()
.getSelectedItems()
.filter(item => item.isRegularItem())
.length;
}
catch (e) {}
var format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
format = Zotero.QuickCopy.unserializeSetting(format);
@ -146,16 +155,18 @@ const ZoteroStandalone = new function() {
var copyBibliography = document.getElementById('menu_copyBibliography');
var copyExport = document.getElementById('menu_copyExport');
copyCitation.hidden = format.mode != 'bibliography';
copyBibliography.hidden = format.mode != 'bibliography';
copyExport.hidden = format.mode != 'export';
copyCitation.hidden = !selected || format.mode != 'bibliography';
copyBibliography.hidden = !selected || format.mode != 'bibliography';
copyExport.hidden = !selected || format.mode != 'export';
if (format.mode == 'export') {
try {
let obj = Zotero.Translators.get(format.id);
copyExport.label = Zotero.getString('quickCopy.copyAs', obj.label);
}
catch (e) {
if (!(e instanceof Zotero.Exception.UnloadedDataException && e.dataType == 'translators')) {
Zotero.logError(e);
}
copyExport.hidden = true;
}
}
@ -270,6 +281,8 @@ ZoteroStandalone.DebugOutput = {
submit: function () {
// 'Zotero' isn't defined yet when this function is created, so do it inline
return Zotero.Promise.coroutine(function* () {
Zotero.debug("Submitting debug output");
Components.utils.import("resource://zotero/config.js");
var url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1";

View File

@ -52,9 +52,9 @@ var Zotero_Tag_Color_Chooser = new function() {
colorPicker.setAttribute('tileWidth', 24);
colorPicker.setAttribute('tileHeight', 24);
colorPicker.colors = [
'#990000', '#CC9933', '#FF9900',
'#FFCC00', '#007439', '#1049A9',
'#9999FF', '#CC66CC', '#993399'
'#FF6666', '#FF8C19', '#999999',
'#5FB236', '#009980', '#2EA8E5',
'#576DD9', '#A28AE5', '#A6507B'
];
var maxTags = document.getElementById('max-tags');

View File

@ -30,7 +30,7 @@
for (let type of types) {
var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
var baseFields = {};
for (let fieldID in fieldIDs) {
for (let fieldID of fieldIDs) {
if (baseMappedFields.includes(fieldID)) {
baseFields[fieldID] = Zotero.ItemFields.getBaseIDFromTypeAndField(type.id, fieldID);
}

View File

@ -154,7 +154,7 @@ Zotero.API = {
return 'groups/' + Zotero.Groups.getGroupIDFromLibraryID(libraryID);
default:
throw new Error(`Invalid type '${type}`);
throw new Error(`Invalid type '${type}'`);
}
}
};

View File

@ -176,14 +176,18 @@ Zotero.Attachments = new function(){
/**
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID'
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID', 'singleFile'
* @return {Promise<Zotero.Item>}
*/
this.importSnapshotFromFile = Zotero.Promise.coroutine(function* (options) {
Zotero.debug('Importing snapshot from file');
var file = Zotero.File.pathToFile(options.file);
var fileName = file.leafName;
// TODO: Fix main filename when copying directory, though in that case it's probably
// from our own export and already clean
var fileName = options.singleFile
? Zotero.File.getValidFileName(file.leafName)
: file.leafName;
var url = options.url;
var title = options.title;
var contentType = options.contentType;
@ -194,7 +198,7 @@ Zotero.Attachments = new function(){
throw new Error("parentItemID not provided");
}
var attachmentItem, itemID, destDir, newFile;
var attachmentItem, itemID, destDir, newPath;
try {
yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment
@ -217,13 +221,23 @@ Zotero.Attachments = new function(){
var storageDir = Zotero.getStorageDirectory();
destDir = this.getStorageDirectory(attachmentItem);
yield OS.File.removeDir(destDir.path);
newPath = OS.Path.join(destDir.path, fileName);
// Copy single file to new directory
if (options.singleFile) {
yield this.createDirectoryForItem(attachmentItem);
yield OS.File.copy(file.path, newPath);
}
// Copy entire parent directory (for HTML snapshots)
else {
file.parent.copyTo(storageDir, destDir.leafName);
// Point to copied file
newFile = destDir.clone();
newFile.append(file.leafName);
}
}.bind(this));
yield _postProcessFile(attachmentItem, newFile, contentType, charset);
yield _postProcessFile(
attachmentItem,
Zotero.File.pathToFile(newPath),
contentType,
charset
);
}
catch (e) {
Zotero.logError(e);
@ -245,8 +259,18 @@ Zotero.Attachments = new function(){
/**
* @param {Object} options - 'libraryID', 'url', 'parentItemID', 'collections', 'title',
* 'fileBaseName', 'contentType', 'cookieSandbox', 'saveOptions'
* @param {Object} options
* @param {Integer} options.libraryID
* @param {String} options.url
* @param {Integer} [options.parentItemID]
* @param {Integer[]} [options.collections]
* @param {String} [options.title]
* @param {String} [options.fileBaseName]
* @param {Boolean} [options.renameIfAllowedType=false]
* @param {String} [options.contentType]
* @param {String} [options.referrer]
* @param {CookieSandbox} [options.cookieSandbox]
* @param {Object} [options.saveOptions]
* @return {Promise<Zotero.Item>} - A promise for the created attachment item
*/
this.importFromURL = Zotero.Promise.coroutine(function* (options) {
@ -256,7 +280,9 @@ Zotero.Attachments = new function(){
var collections = options.collections;
var title = options.title;
var fileBaseName = options.fileBaseName;
var renameIfAllowedType = options.renameIfAllowedType;
var contentType = options.contentType;
var referrer = options.referrer;
var cookieSandbox = options.cookieSandbox;
var saveOptions = options.saveOptions;
@ -286,7 +312,7 @@ Zotero.Attachments = new function(){
if (channel.responseStatus < 200 || channel.responseStatus >= 400) {
reject(new Error("Invalid response " + channel.responseStatus + " "
+ channel.responseStatusText + " for '" + url + "'"));
return;
return false;
}
}
try {
@ -318,6 +344,11 @@ Zotero.Attachments = new function(){
// Save using remote web browser persist
var externalHandlerImport = Zotero.Promise.coroutine(function* (contentType) {
// Rename attachment
if (renameIfAllowedType && !fileBaseName && this.getRenamedFileTypes().includes(contentType)) {
let parentItem = Zotero.Items.get(parentItemID);
fileBaseName = this.getFileBaseNameFromItem(parentItem);
}
if (fileBaseName) {
let ext = _getExtensionFromURL(url, contentType);
var fileName = fileBaseName + (ext != '' ? '.' + ext : '');
@ -347,7 +378,12 @@ Zotero.Attachments = new function(){
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIURL);
nsIURL.spec = url;
Zotero.Utilities.Internal.saveURI(wbp, nsIURL, tmpFile);
var headers = {};
if (referrer) {
headers.Referer = referrer;
}
Zotero.Utilities.Internal.saveURI(wbp, nsIURL, tmpFile, headers);
yield deferred.promise;
let sample = yield Zotero.File.getContentsAsync(tmpFile, null, 1000);
@ -387,6 +423,8 @@ Zotero.Attachments = new function(){
attachmentItem.attachmentPath = 'storage:' + fileName;
var itemID = yield attachmentItem.save(saveOptions);
Zotero.Fulltext.queueItem(attachmentItem);
// DEBUG: Does this fail if 'storage' is symlinked to another drive?
destDir = this.getStorageDirectory(attachmentItem).path;
yield OS.File.move(tmpDir, destDir);
@ -406,16 +444,6 @@ Zotero.Attachments = new function(){
throw e;
}
// We don't have any way of knowing that the file is flushed to disk,
// so we just wait a second before indexing and hope for the best.
// We'll index it later if it fails. (This may not be necessary.)
//
// If this is removed, the afterEach() delay in the server_connector /connector/saveSnapshot
// tests can also be removed.
setTimeout(function () {
Zotero.Fulltext.indexItems([attachmentItem.id]);
}, 1000);
return attachmentItem;
}.bind(this));
@ -653,6 +681,8 @@ Zotero.Attachments = new function(){
attachmentItem.attachmentPath = 'storage:' + fileName;
var itemID = yield attachmentItem.save();
Zotero.Fulltext.queueItem(attachmentItem);
// DEBUG: Does this fail if 'storage' is symlinked to another drive?
destDir = this.getStorageDirectory(attachmentItem).path;
yield OS.File.move(tmpDir, destDir);
@ -677,21 +707,6 @@ Zotero.Attachments = new function(){
throw e;
}
// We don't have any way of knowing that the file is flushed to disk,
// so we just wait a second before indexing and hope for the best.
// We'll index it later if it fails. (This may not be necessary.)
if (contentType == 'application/pdf') {
setTimeout(function () {
Zotero.Fulltext.indexPDF(attachmentItem.getFilePath(), attachmentItem.id);
}, 1000);
}
else if (Zotero.MIME.isTextType(contentType)) {
// wbp.saveDocument consumes the document context (in Zotero.Utilities.Internal.saveDocument)
// Seems like a mozilla bug, but nothing on bugtracker.
// Either way, we don't rely on Zotero.Fulltext.indexDocument here anymore
yield Zotero.Fulltext.indexItems(attachmentItem.id, true, true);
}
return attachmentItem;
});
@ -797,6 +812,30 @@ Zotero.Attachments = new function(){
}
this.getRenamedFileTypes = function () {
try {
var types = Zotero.Prefs.get('autoRenameFiles.fileTypes');
return types ? types.split(',') : [];
}
catch (e) {
return [];
}
};
this.getRenamedFileBaseNameIfAllowedType = async function (parentItem, file) {
var types = this.getRenamedFileTypes();
var contentType = file.endsWith('.pdf')
// Don't bother reading file if there's a .pdf extension
? 'application/pdf'
: await Zotero.MIME.getMIMETypeFromFile(file);
if (!types.includes(contentType)) {
return false;
}
return this.getFileBaseNameFromItem(parentItem);
}
/**
* Create directory for attachment files within storage directory
*
@ -1090,13 +1129,75 @@ Zotero.Attachments = new function(){
/**
* Copy attachment item, including files, to another library
* Move attachment item, including file, to another library
*/
this.moveAttachmentToLibrary = async function (attachment, libraryID, parentItemID) {
if (attachment.libraryID == libraryID) {
throw new Error("Attachment is already in library " + libraryID);
}
Zotero.DB.requireTransaction();
var newAttachment = attachment.clone(libraryID);
if (attachment.isImportedAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath;
}
if (parentItemID) {
newAttachment.parentID = parentItemID;
}
await newAttachment.save();
// Move files over if they exist
var oldDir;
var newDir;
if (newAttachment.isImportedAttachment()) {
oldDir = this.getStorageDirectory(attachment).path;
if (await OS.File.exists(oldDir)) {
newDir = this.getStorageDirectory(newAttachment).path;
// Target directory shouldn't exist, but remove it if it does
//
// Testing for directories in OS.File, used by removeDir(), is broken on Travis,
// so use nsIFile
if (Zotero.automatedTest) {
let nsIFile = Zotero.File.pathToFile(newDir);
if (nsIFile.exists()) {
nsIFile.remove(true);
}
}
else {
await OS.File.removeDir(newDir, { ignoreAbsent: true });
}
await OS.File.move(oldDir, newDir);
}
}
try {
await attachment.erase();
}
catch (e) {
// Move files back if old item can't be deleted
if (newAttachment.isImportedAttachment()) {
try {
await OS.File.move(newDir, oldDir);
}
catch (e) {
Zotero.logError(e);
}
}
throw e;
}
return newAttachment.id;
};
/**
* Copy attachment item, including file, to another library
*/
this.copyAttachmentToLibrary = Zotero.Promise.coroutine(function* (attachment, libraryID, parentItemID) {
var linkMode = attachment.attachmentLinkMode;
if (attachment.libraryID == libraryID) {
throw ("Attachment is already in library " + libraryID);
throw new Error("Attachment is already in library " + libraryID);
}
Zotero.DB.requireTransaction();

View File

@ -16,13 +16,13 @@ Zotero.Cite = {
* Remove specified item IDs in-place from a citeproc-js bibliography object returned
* by makeBibliography()
* @param {bib} citeproc-js bibliography object
* @param {Array} itemsToRemove Array of items to remove
* @param {Set} itemsToRemove Set of items to remove
*/
"removeFromBibliography":function(bib, itemsToRemove) {
var removeItems = [];
for(let i in bib[0].entry_ids) {
for(let j in bib[0].entry_ids[i]) {
if(itemsToRemove[bib[0].entry_ids[i][j]]) {
if(itemsToRemove.has(`${bib[0].entry_ids[i][j]}`)) {
removeItems.push(i);
break;
}
@ -315,6 +315,104 @@ Zotero.Cite = {
} else {
return Zotero.Items.get(id);
}
},
extraToCSL: function (extra) {
return extra.replace(/^([A-Za-z \-]+)(:\s*.+)/gm, function (_, field, value) {
var originalField = field;
var field = field.toLowerCase().replace(/ /g, '-');
// Fields from https://aurimasv.github.io/z2csl/typeMap.xml
switch (field) {
// Standard fields
case 'abstract':
case 'accessed':
case 'annote':
case 'archive':
case 'archive-place':
case 'author':
case 'authority':
case 'call-number':
case 'chapter-number':
case 'citation-label':
case 'citation-number':
case 'collection-editor':
case 'collection-number':
case 'collection-title':
case 'composer':
case 'container':
case 'container-author':
case 'container-title':
case 'container-title-short':
case 'dimensions':
case 'director':
case 'edition':
case 'editor':
case 'editorial-director':
case 'event':
case 'event-date':
case 'event-place':
case 'first-reference-note-number':
case 'genre':
case 'illustrator':
case 'interviewer':
case 'issue':
case 'issued':
case 'jurisdiction':
case 'keyword':
case 'language':
case 'locator':
case 'medium':
case 'note':
case 'number':
case 'number-of-pages':
case 'number-of-volumes':
case 'original-author':
case 'original-date':
case 'original-publisher':
case 'original-publisher-place':
case 'original-title':
case 'page':
case 'page-first':
case 'publisher':
case 'publisher-place':
case 'recipient':
case 'references':
case 'reviewed-author':
case 'reviewed-title':
case 'scale':
case 'section':
case 'source':
case 'status':
case 'submitted':
case 'title':
case 'title-short':
case 'translator':
case 'version':
case 'volume':
case 'year-suffix':
break;
// Uppercase fields
case 'doi':
case 'isbn':
case 'issn':
case 'pmcid':
case 'pmid':
case 'url':
field = field.toUpperCase();
break;
// Weirdo
case 'archive-location':
field = 'archive_location';
break;
// Don't change other lines
default:
field = originalField;
}
return field + value;
});
}
};
@ -498,7 +596,7 @@ Zotero.Cite.System.prototype = {
/**
* citeproc-js system function for getting items
* See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#retrieveitem
* @param {String|Integer} Item ID, or string item for embedded citations
* @param {String|Integer} item - Item ID, or string item for embedded citations
* @return {Object} citeproc-js item
*/
"retrieveItem":function retrieveItem(item) {
@ -509,7 +607,7 @@ Zotero.Cite.System.prototype = {
} else if(typeof item === "string" && (slashIndex = item.indexOf("/")) !== -1) {
// is an embedded item
var sessionID = item.substr(0, slashIndex);
var session = Zotero.Integration.sessions[sessionID]
var session = Zotero.Integration.sessions[sessionID];
if(session) {
var embeddedCitation = session.embeddedItems[item.substr(slashIndex+1)];
if (embeddedCitation) {
@ -526,7 +624,7 @@ Zotero.Cite.System.prototype = {
}
if(!zoteroItem) {
throw "Zotero.Cite.System.retrieveItem called on non-item "+item;
throw new Error("Zotero.Cite.System.retrieveItem called on non-item "+item);
}
var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem);

File diff suppressed because it is too large Load Diff

View File

@ -76,6 +76,14 @@ Object.defineProperty(Zotero.CollectionTreeView.prototype, "selectedTreeRow", {
});
Object.defineProperty(Zotero.CollectionTreeView.prototype, 'window', {
get: function () {
return this._ownerDocument.defaultView;
},
enumerable: true
});
/*
* Called by the tree itself
*/
@ -87,6 +95,13 @@ Zotero.CollectionTreeView.prototype.setTree = Zotero.Promise.coroutine(function*
}
this._treebox = treebox;
if (!this._ownerDocument) {
try {
this._ownerDocument = treebox.treeBody.ownerDocument;
}
catch (e) {}
}
// Add a keypress listener for expand/collapse
var tree = this._treebox.treeBody.parentNode;
tree.addEventListener('keypress', function(event) {
@ -1606,6 +1621,7 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
if (dataType == 'zotero/item') {
var ids = data;
var items = Zotero.Items.get(ids);
items = Zotero.Items.keepParents(items);
var skip = true;
for (let item of items) {
// Can only drag top-level items
@ -1813,6 +1829,7 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
if (treeRow.ref.libraryID != draggedCollection.libraryID) {
// Disallow if linked collection already exists
if (yield draggedCollection.getLinkedCollection(treeRow.ref.libraryID, true)) {
Zotero.debug("Linked collection already exists in library");
return false;
}
@ -1824,6 +1841,7 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
// If this is allowed in the future for the root collection,
// need to allow drag only to root
if (yield descendent.getLinkedCollection(treeRow.ref.libraryID, true)) {
Zotero.debug("Linked subcollection already exists in library");
return false;
}
}
@ -2115,6 +2133,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
if (targetTreeRow.isPublications()) {
items = Zotero.Items.keepParents(items);
let io = this._treebox.treeBody.ownerDocument.defaultView
.ZoteroPane.showPublicationsWizard(items);
if (!io) {
@ -2186,9 +2205,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
*/
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
let lastWin = wm.getMostRecentWindow("navigator:browser");
let lastWin = Services.wm.getMostRecentWindow("navigator:browser");
lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
yield Zotero.DB.executeTransaction(function* () {
@ -2227,24 +2244,23 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
var targetLibraryID = targetTreeRow.ref.libraryID;
if (targetTreeRow.isCollection()) {
var parentCollectionID = targetTreeRow.ref.id;
}
else {
var parentCollectionID = false;
}
var addedItems = [];
for (var i=0; i<data.length; i++) {
var file = data[i];
if (dataType == 'text/x-moz-url') {
var url = data[i];
let item;
if (url.indexOf('file:///') == 0) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
let win = Services.wm.getMostRecentWindow("navigator:browser");
// If dragging currently loaded page, only convert to
// file if not an HTML document
if (win.content.location.href != url ||
@ -2262,9 +2278,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
// Still string, so remote URL
if (typeof file == 'string') {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack', null, row); // TODO: don't do this
continue;
}
@ -2273,13 +2287,13 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
if (dropEffect == 'link') {
yield Zotero.Attachments.linkFromFile({
item = yield Zotero.Attachments.linkFromFile({
file: file,
collections: parentCollectionID ? [parentCollectionID] : undefined
});
}
else {
yield Zotero.Attachments.importFromFile({
item = yield Zotero.Attachments.importFromFile({
file: file,
libraryID: targetLibraryID,
collections: parentCollectionID ? [parentCollectionID] : undefined
@ -2294,7 +2308,12 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
}
}
addedItems.push(item);
}
// Automatically retrieve metadata for PDFs
Zotero.RecognizePDF.autoRecognizeItems(addedItems);
}
});

View File

@ -0,0 +1,192 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2017 Center for History and New Media
George Mason University, Fairfax, 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 *****
*/
/**
* This is a HTTP-based integration interface for Zotero. The actual
* heavy lifting occurs in the connector and/or wherever the connector delegates the heavy
* lifting to.
*/
Zotero.HTTPIntegrationClient = {
deferredResponse: null,
sendCommandPromise: Zotero.Promise.resolve(),
sendCommand: async function(command, args=[]) {
let payload = JSON.stringify({command, arguments: args});
function sendCommand() {
Zotero.HTTPIntegrationClient.deferredResponse = Zotero.Promise.defer();
Zotero.HTTPIntegrationClient.sendResponse.apply(Zotero.HTTPIntegrationClient,
[200, 'application/json', payload]);
return Zotero.HTTPIntegrationClient.deferredResponse.promise;
}
// Force issued commands to occur sequentially, since these are really just
// a sequence of HTTP requests and responses.
// We might want to consider something better later, but this has the advantage of
// being easy to interface with as a Client, as you don't need SSE or WS.
if (command != 'Document.complete') {
Zotero.HTTPIntegrationClient.sendCommandPromise =
Zotero.HTTPIntegrationClient.sendCommandPromise.then(sendCommand, sendCommand);
} else {
await Zotero.HTTPIntegrationClient.sendCommandPromise;
sendCommand();
}
return Zotero.HTTPIntegrationClient.sendCommandPromise;
}
};
Zotero.HTTPIntegrationClient.Application = function() {
this.primaryFieldType = "Http";
this.secondaryFieldType = "Http";
this.outputFormat = 'html';
this.supportedNotes = ['footnotes'];
};
Zotero.HTTPIntegrationClient.Application.prototype = {
getActiveDocument: async function() {
let result = await Zotero.HTTPIntegrationClient.sendCommand('Application.getActiveDocument');
this.outputFormat = result.outputFormat || this.outputFormat;
this.supportedNotes = result.supportedNotes || this.supportedNotes;
return new Zotero.HTTPIntegrationClient.Document(result.documentID);
}
};
/**
* See integrationTests.js
*/
Zotero.HTTPIntegrationClient.Document = function(documentID) {
this._documentID = documentID;
};
for (let method of ["activate", "canInsertField", "displayAlert", "getDocumentData",
"setDocumentData", "setBibliographyStyle"]) {
Zotero.HTTPIntegrationClient.Document.prototype[method] = async function() {
return Zotero.HTTPIntegrationClient.sendCommand("Document."+method,
[this._documentID].concat(Array.prototype.slice.call(arguments)));
};
}
// @NOTE Currently unused, prompts are done using the connector
Zotero.HTTPIntegrationClient.Document.prototype._displayAlert = async function(dialogText, icon, buttons) {
var ps = Services.prompt;
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
switch (buttons) {
case DIALOG_BUTTONS_OK:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK; break;
case DIALOG_BUTTONS_OK_CANCEL:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.STD_OK_CANCEL_BUTTONS; break;
case DIALOG_BUTTONS_YES_NO:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.STD_YES_NO_BUTTONS; break;
case DIALOG_BUTTONS_YES_NO_CANCEL:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_YES +
ps.BUTTON_POS_1 * ps.BUTTON_TITLE_NO +
ps.BUTTON_POS_2 * ps.BUTTON_TITLE_CANCEL; break;
}
var result = ps.confirmEx(
null,
"Zotero",
dialogText,
buttonFlags,
null, null, null,
null,
{}
);
switch (buttons) {
default:
break;
case DIALOG_BUTTONS_OK_CANCEL:
case DIALOG_BUTTONS_YES_NO:
result = (result+1)%2; break;
case DIALOG_BUTTONS_YES_NO_CANCEL:
result = result == 0 ? 2 : result == 2 ? 0 : 1; break;
}
await this.activate();
return result;
}
Zotero.HTTPIntegrationClient.Document.prototype.cleanup = async function() {};
Zotero.HTTPIntegrationClient.Document.prototype.cursorInField = async function(fieldType) {
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.cursorInField", [this._documentID, fieldType]);
if (!retVal) return null;
return new Zotero.HTTPIntegrationClient.Field(this._documentID, retVal);
};
Zotero.HTTPIntegrationClient.Document.prototype.insertField = async function(fieldType, noteType) {
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.insertField", [this._documentID, fieldType, parseInt(noteType) || 0]);
return new Zotero.HTTPIntegrationClient.Field(this._documentID, retVal);
};
Zotero.HTTPIntegrationClient.Document.prototype.getFields = async function(fieldType) {
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.getFields", [this._documentID, fieldType]);
return retVal.map(field => new Zotero.HTTPIntegrationClient.Field(this._documentID, field));
};
Zotero.HTTPIntegrationClient.Document.prototype.convert = async function(fields, fieldType, noteTypes) {
fields = fields.map((f) => f._id);
await Zotero.HTTPIntegrationClient.sendCommand("Field.convert", [this._documentID, fields, fieldType, noteTypes]);
};
Zotero.HTTPIntegrationClient.Document.prototype.complete = async function() {
Zotero.HTTPIntegrationClient.inProgress = false;
Zotero.HTTPIntegrationClient.sendCommand("Document.complete", [this._documentID]);
};
/**
* See integrationTests.js
*/
Zotero.HTTPIntegrationClient.Field = function(documentID, json) {
this._documentID = documentID;
this._id = json.id;
this._code = json.code;
this._text = json.text;
this._noteIndex = json.noteIndex;
};
Zotero.HTTPIntegrationClient.Field.prototype = {};
for (let method of ["delete", "select", "removeCode"]) {
Zotero.HTTPIntegrationClient.Field.prototype[method] = async function() {
return Zotero.HTTPIntegrationClient.sendCommand("Field."+method,
[this._documentID, this._id].concat(Array.prototype.slice.call(arguments)));
};
}
Zotero.HTTPIntegrationClient.Field.prototype.getText = async function() {
return this._text;
};
Zotero.HTTPIntegrationClient.Field.prototype.setText = async function(text, isRich) {
// The HTML will be stripped by Google Docs and and since we're
// caching this value, we need to strip it ourselves
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser);
var doc = parser.parseFromString(text, "text/html");
this._text = doc.documentElement.textContent;
return Zotero.HTTPIntegrationClient.sendCommand("Field.setText", [this._documentID, this._id, text, true]);
};
Zotero.HTTPIntegrationClient.Field.prototype.getCode = async function() {
return this._code;
};
Zotero.HTTPIntegrationClient.Field.prototype.setCode = async function(code) {
this._code = code;
return Zotero.HTTPIntegrationClient.sendCommand("Field.setCode", [this._documentID, this._id, code]);
};
Zotero.HTTPIntegrationClient.Field.prototype.getNoteIndex = async function() {
return this._noteIndex;
};
Zotero.HTTPIntegrationClient.Field.prototype.equals = async function(arg) {
return this._id === arg._id;
};

View File

@ -27,22 +27,69 @@ const CONNECTOR_API_VERSION = 2;
Zotero.Server.Connector = {
_waitingForSelection: {},
getSaveTarget: function () {
var zp = Zotero.getActiveZoteroPane(),
library = null,
collection = null,
editable = true;
try {
getSaveTarget: function (allowReadOnly) {
var zp = Zotero.getActiveZoteroPane();
var library = null;
var collection = null;
var editable = null;
if (zp && zp.collectionsView) {
if (zp.collectionsView.editable || allowReadOnly) {
library = Zotero.Libraries.get(zp.getSelectedLibraryID());
collection = zp.getSelectedCollection();
editable = zp.collectionsView.editable;
}
catch (e) {
// If not editable, switch to My Library if it exists and is editable
else {
let userLibrary = Zotero.Libraries.userLibrary;
if (userLibrary && userLibrary.editable) {
Zotero.debug("Save target isn't editable -- switching to My Library");
// Don't wait for this, because we don't want to slow down all conenctor
// requests by making this function async
zp.collectionsView.selectByID(userLibrary.treeViewID);
library = userLibrary;
collection = null;
editable = true;
}
}
}
else {
let id = Zotero.Prefs.get('lastViewedFolder');
if (id) {
let type = id[0];
Zotero.debug(type);
id = parseInt(('' + id).substr(1));
({ library, collection, editable } = this.resolveTarget(id));
if (!editable && !allowReadOnly) {
let userLibrary = Zotero.Libraries.userLibrary;
if (userLibrary && userLibrary.editable) {
Zotero.debug("Save target isn't editable -- switching to My Library");
let treeViewID = userLibrary.treeViewID;
Zotero.Prefs.set('lastViewedFolder', treeViewID);
({ library, collection, editable } = this.resolveTarget(treeViewID));
}
}
}
}
// Default to My Library if present if pane not yet opened
// (which should never be the case anymore)
if (!library) {
let userLibrary = Zotero.Libraries.userLibrary;
if (userLibrary && userLibrary.editable) {
library = userLibrary;
}
}
return { library, collection, editable };
},
resolveTarget: function (targetID) {
var library;
var collection;
var editable;
var type = targetID[0];
var id = parseInt(('' + targetID).substr(1));
switch (type) {
case 'L':
@ -55,23 +102,219 @@ Zotero.Server.Connector = {
library = collection.library;
editable = collection.editable;
break;
}
}
}
// Default to My Library if present if pane not yet opened
// (which should never be the case anymore)
if (!library) {
let userLibrary = Zotero.Libraries.userLibrary;
if (userLibrary) {
library = userLibrary;
}
default:
throw new Error(`Unsupported target type '${type}'`);
}
return { library, collection, editable };
}
};
Zotero.Server.Connector.Data = {};
Zotero.Server.Connector.SessionManager = {
_sessions: new Map(),
get: function (id) {
return this._sessions.get(id);
},
create: function (id, action, requestData) {
// Legacy connector
if (!id) {
Zotero.debug("No session id provided by client", 2);
id = Zotero.Utilities.randomString();
}
if (this._sessions.has(id)) {
throw new Error(`Session ID ${id} exists`);
}
Zotero.debug("Creating connector save session " + id);
var session = new Zotero.Server.Connector.SaveSession(id, action, requestData);
this._sessions.set(id, session);
this.gc();
return session;
},
gc: function () {
// Delete sessions older than 10 minutes, or older than 1 minute if more than 10 sessions
var ttl = this._sessions.size >= 10 ? 60 : 600;
var deleteBefore = new Date() - ttl * 1000;
for (let session of this._sessions) {
if (session.created < deleteBefore) {
this._session.delete(session.id);
}
}
}
};
Zotero.Server.Connector.SaveSession = function (id, action, requestData) {
this.id = id;
this.created = new Date();
this._action = action;
this._requestData = requestData;
this._items = new Set();
};
Zotero.Server.Connector.SaveSession.prototype.addItem = async function (item) {
return this.addItems([item]);
};
Zotero.Server.Connector.SaveSession.prototype.addItems = async function (items) {
for (let item of items) {
this._items.add(item);
}
// Update the items with the current target data, in case it changed since the save began
await this._updateItems(items);
};
/**
* Change the target data for this session and update any items that have already been saved
*/
Zotero.Server.Connector.SaveSession.prototype.update = async function (targetID, tags) {
var previousTargetID = this._currentTargetID;
this._currentTargetID = targetID;
this._currentTags = tags || "";
// Select new destination in collections pane
var zp = Zotero.getActiveZoteroPane();
if (zp && zp.collectionsView) {
await zp.collectionsView.selectByID(targetID);
}
// If window is closed, select target collection re-open
else {
Zotero.Prefs.set('lastViewedFolder', targetID);
}
// If moving from a non-filesEditable library to a filesEditable library, resave from
// original data, since there might be files that weren't saved or were removed
if (previousTargetID && previousTargetID != targetID) {
let { library: oldLibrary } = Zotero.Server.Connector.resolveTarget(previousTargetID);
let { library: newLibrary } = Zotero.Server.Connector.resolveTarget(targetID);
if (oldLibrary != newLibrary && !oldLibrary.filesEditable && newLibrary.filesEditable) {
Zotero.debug("Resaving items to filesEditable library");
if (this._action == 'saveItems' || this._action == 'saveSnapshot') {
// Delete old items
for (let item of this._items) {
await item.eraseTx();
}
let actionUC = Zotero.Utilities.capitalize(this._action);
let newItems = await Zotero.Server.Connector[actionUC].prototype[this._action](
targetID, this._requestData
);
// saveSnapshot only returns a single item
if (this._action == 'saveSnapshot') {
newItems = [newItems];
}
this._items = new Set(newItems);
}
}
}
await this._updateItems(this._items);
// If a single item was saved, select it (or its parent, if it now has one)
if (zp && zp.collectionsView && this._items.size == 1) {
let item = Array.from(this._items)[0];
item = item.isTopLevelItem() ? item : item.parentItem;
// Don't select if in trash
if (!item.deleted) {
await zp.selectItem(item.id);
}
}
};
/**
* Update the passed items with the current target and tags
*/
Zotero.Server.Connector.SaveSession.prototype._updateItems = Zotero.serial(async function (items) {
if (items.length == 0) {
return;
}
var { library, collection, editable } = Zotero.Server.Connector.resolveTarget(this._currentTargetID);
var libraryID = library.libraryID;
var tags = this._currentTags.trim();
tags = tags ? tags.split(/\s*,\s*/) : [];
Zotero.debug("Updating items for connector save session " + this.id);
for (let item of items) {
let newLibrary = Zotero.Libraries.get(library.libraryID);
if (item.libraryID != libraryID) {
let newItem = await item.moveToLibrary(libraryID);
// Replace item in session
this._items.delete(item);
this._items.add(newItem);
}
// If the item is now a child item (e.g., from Retrieve Metadata for PDF), update the
// parent item instead
if (!item.isTopLevelItem()) {
item = item.parentItem;
}
// Skip deleted items
if (!Zotero.Items.exists(item.id)) {
Zotero.debug(`Item ${item.id} in save session no longer exists`);
continue;
}
// Keep automatic tags
let originalTags = item.getTags().filter(tag => tag.type == 1);
item.setTags(originalTags.concat(tags));
item.setCollections(collection ? [collection.id] : []);
await item.saveTx();
}
this._updateRecents();
});
Zotero.Server.Connector.SaveSession.prototype._updateRecents = function () {
var targetID = this._currentTargetID;
try {
let numRecents = 7;
let recents = Zotero.Prefs.get('recentSaveTargets') || '[]';
recents = JSON.parse(recents);
// If there's already a target from this session in the list, update it
for (let recent of recents) {
if (recent.sessionID == this.id) {
recent.id = targetID;
break;
}
}
// If a session is found with the same target, move it to the end without changing
// the sessionID. This could be the current session that we updated above or a different
// one. (We need to leave the old sessionID for the same target or we'll end up removing
// the previous target from the history if it's changed in the current one.)
let pos = recents.findIndex(r => r.id == targetID);
if (pos != -1) {
recents = [
...recents.slice(0, pos),
...recents.slice(pos + 1),
recents[pos]
];
}
// Otherwise just add this one to the end
else {
recents = recents.concat([{
id: targetID,
sessionID: this.id
}]);
}
recents = recents.slice(-1 * numRecents);
Zotero.Prefs.set('recentSaveTargets', JSON.stringify(recents));
}
catch (e) {
Zotero.logError(e);
Zotero.Prefs.clear('recentSaveTargets');
}
};
Zotero.Server.Connector.AttachmentProgressManager = new function() {
var attachmentsInProgress = new WeakMap(),
attachmentProgress = {},
@ -172,7 +415,6 @@ Zotero.Server.Connector.GetTranslators.prototype = {
*/
Zotero.Server.Connector.Detect = function() {};
Zotero.Server.Endpoints["/connector/detect"] = Zotero.Server.Connector.Detect;
Zotero.Server.Connector.Data = {};
Zotero.Server.Connector.Detect.prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
@ -270,6 +512,8 @@ Zotero.Server.Connector.SavePage.prototype = {
*/
init: function(url, data, sendResponseCallback) {
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
// Shouldn't happen as long as My Library exists
if (!library.editable) {
Zotero.logError("Can't add item to read-only library " + library.name);
return sendResponseCallback(500, "application/json", JSON.stringify({ libraryEditable: false }));
@ -356,9 +600,9 @@ Zotero.Server.Connector.SavePage.prototype = {
* Returns:
* 201 response code with item in body.
*/
Zotero.Server.Connector.SaveItem = function() {};
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItem;
Zotero.Server.Connector.SaveItem.prototype = {
Zotero.Server.Connector.SaveItems = function() {};
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItems;
Zotero.Server.Connector.SaveItems.prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
permitBookmarklet: true,
@ -367,69 +611,103 @@ Zotero.Server.Connector.SaveItem.prototype = {
* Either loads HTML into a hidden browser and initiates translation, or saves items directly
* to the database
*/
init: Zotero.Promise.coroutine(function* (options) {
var data = options.data;
init: Zotero.Promise.coroutine(function* (requestData) {
var data = requestData.data;
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
var libraryID = library.libraryID;
var targetID = collection ? collection.treeViewID : library.treeViewID;
try {
var session = Zotero.Server.Connector.SessionManager.create(
data.sessionID,
'saveItems',
requestData
);
}
catch (e) {
return [409, "application/json", JSON.stringify({ error: "SESSION_EXISTS" })];
}
yield session.update(targetID);
// Shouldn't happen as long as My Library exists
if (!library.editable) {
Zotero.logError("Can't add item to read-only library " + library.name);
return [500, "application/json", JSON.stringify({ libraryEditable: false })];
}
return new Zotero.Promise((resolve) => {
try {
this.saveItems(
targetID,
requestData,
function (topLevelItems) {
resolve([201, "application/json", JSON.stringify({items: topLevelItems})]);
}
)
// Add items to session once all attachments have been saved
.then(function (items) {
session.addItems(items);
});
}
catch (e) {
Zotero.logError(e);
resolve(500);
}
});
}),
saveItems: async function (target, requestData, onTopLevelItemsDone) {
var { library, collection, editable } = Zotero.Server.Connector.resolveTarget(target);
var data = requestData.data;
var cookieSandbox = data.uri
? new Zotero.CookieSandbox(
null,
data.uri,
data.detailedCookies ? "" : data.cookie || "",
options.headers["User-Agent"]
requestData.headers["User-Agent"]
)
: null;
if (cookieSandbox && data.detailedCookies) {
cookieSandbox.addCookiesFromHeader(data.detailedCookies);
}
for(var i=0; i<data.items.length; i++) {
Zotero.Server.Connector.AttachmentProgressManager.add(data.items[i].attachments);
for (let item of data.items) {
Zotero.Server.Connector.AttachmentProgressManager.add(item.attachments);
}
let proxy = data.proxy && new Zotero.Proxy(data.proxy);
// save items
var proxy = data.proxy && new Zotero.Proxy(data.proxy);
// Save items
var itemSaver = new Zotero.Translate.ItemSaver({
libraryID,
libraryID: library.libraryID,
collections: collection ? [collection.id] : undefined,
attachmentMode: Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD,
forceTagType: 1,
referrer: data.uri,
cookieSandbox,
proxy
});
try {
var deferred = Zotero.Promise.defer();
itemSaver.saveItems(
return itemSaver.saveItems(
data.items,
Zotero.Server.Connector.AttachmentProgressManager.onProgress,
function () {
// Remove attachments not being saved from item.attachments
for(var i=0; i<data.items.length; i++) {
var item = data.items[i];
for(var j=0; j<item.attachments.length; j++) {
if(!Zotero.Server.Connector.AttachmentProgressManager.has(item.attachments[j])) {
item.attachments.splice(j--, 1);
// Remove attachments from item.attachments that aren't being saved. We have to
// clone the items so that we don't mutate the data stored in the session.
var savedItems = [...data.items.map(item => Object.assign({}, item))];
for (let item of savedItems) {
item.attachments = item.attachments
.filter(attachment => {
return Zotero.Server.Connector.AttachmentProgressManager.has(attachment);
});
}
if (onTopLevelItemsDone) {
onTopLevelItemsDone(savedItems);
}
}
deferred.resolve([201, "application/json", JSON.stringify({items: data.items})]);
}
);
return deferred.promise;
}
catch (e) {
Zotero.logError(e);
return 500;
}
})
}
/**
@ -452,69 +730,84 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
/**
* Save snapshot
*/
init: Zotero.Promise.coroutine(function* (options) {
var data = options.data;
Zotero.Server.Connector.Data[data["url"]] = "<html>"+data["html"]+"</html>";
init: async function (requestData) {
var data = requestData.data;
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
var libraryID = library.libraryID;
var targetID = collection ? collection.treeViewID : library.treeViewID;
try {
var session = Zotero.Server.Connector.SessionManager.create(
data.sessionID,
'saveSnapshot',
requestData
);
}
catch (e) {
return [409, "application/json", JSON.stringify({ error: "SESSION_EXISTS" })];
}
await session.update(collection ? collection.treeViewID : library.treeViewID);
// Shouldn't happen as long as My Library exists
if (!library.editable) {
Zotero.logError("Can't add item to read-only library " + library.name);
return [500, "application/json", JSON.stringify({ libraryEditable: false })];
}
// determine whether snapshot can be saved
var filesEditable;
if (libraryID) {
let group = Zotero.Groups.getByLibraryID(libraryID);
filesEditable = group.filesEditable;
try {
let item = await this.saveSnapshot(targetID, requestData);
await session.addItem(item);
}
else {
filesEditable = true;
catch (e) {
Zotero.logError(e);
return 500;
}
return 201;
},
saveSnapshot: async function (target, requestData) {
var { library, collection, editable } = Zotero.Server.Connector.resolveTarget(target);
var libraryID = library.libraryID;
var data = requestData.data;
var cookieSandbox = data.url
? new Zotero.CookieSandbox(
null,
data.url,
data.detailedCookies ? "" : data.cookie || "",
options.headers["User-Agent"]
requestData.headers["User-Agent"]
)
: null;
if (cookieSandbox && data.detailedCookies) {
cookieSandbox.addCookiesFromHeader(data.detailedCookies);
}
if (data.pdf && filesEditable) {
delete Zotero.Server.Connector.Data[data.url];
try {
yield Zotero.Attachments.importFromURL({
if (data.pdf && library.filesEditable) {
let item = await Zotero.Attachments.importFromURL({
libraryID,
url: data.url,
collections: collection ? [collection.id] : undefined,
contentType: "application/pdf",
cookieSandbox
});
return 201;
// Automatically recognize PDF
Zotero.RecognizePDF.autoRecognizeItems([item]);
return item;
}
catch (e) {
Zotero.logError(e);
return 500;
}
}
else {
let deferred = Zotero.Promise.defer();
return new Zotero.Promise((resolve, reject) => {
Zotero.Server.Connector.Data[data.url] = "<html>" + data.html + "</html>";
Zotero.HTTP.loadDocuments(
["zotero://connector/" + encodeURIComponent(data.url)],
Zotero.Promise.coroutine(function* (doc) {
async function (doc) {
delete Zotero.Server.Connector.Data[data.url];
try {
// create new webpage item
var item = new Zotero.Item("webpage");
// Create new webpage item
let item = new Zotero.Item("webpage");
item.libraryID = libraryID;
item.setField("title", doc.title);
item.setField("url", data.url);
@ -522,28 +815,29 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
if (collection) {
item.setCollections([collection.id]);
}
var itemID = yield item.saveTx();
var itemID = await item.saveTx();
// save snapshot
if (filesEditable && !data.skipSnapshot) {
yield Zotero.Attachments.importFromDocument({
// Save snapshot
if (library.filesEditable && !data.skipSnapshot) {
await Zotero.Attachments.importFromDocument({
document: doc,
parentItemID: itemID
});
}
deferred.resolve(201);
} catch(e) {
Zotero.debug(e, 1);
deferred.resolve(500);
throw e;
resolve(item);
}
}),
null, null, false, cookieSandbox
catch (e) {
reject(e);
}
},
null,
null,
false,
cookieSandbox
);
return deferred.promise;
});
}
})
}
/**
@ -580,6 +874,66 @@ Zotero.Server.Connector.SelectItems.prototype = {
}
}
/**
*
*
* Accepts:
* sessionID - A session ID previously passed to /saveItems
* target - A treeViewID (L1, C23, etc.) for the library or collection to save to
* tags - A string of tags separated by commas
*
* Returns:
* 200 response on successful change
* 400 on error with 'error' property in JSON
*/
Zotero.Server.Connector.UpdateSession = function() {};
Zotero.Server.Endpoints["/connector/updateSession"] = Zotero.Server.Connector.UpdateSession;
Zotero.Server.Connector.UpdateSession.prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
permitBookmarklet: true,
init: async function (requestData) {
var data = requestData.data
if (!data.sessionID) {
return [400, "application/json", JSON.stringify({ error: "SESSION_ID_NOT_PROVIDED" })];
}
var session = Zotero.Server.Connector.SessionManager.get(data.sessionID);
if (!session) {
Zotero.debug("Can't find session " + data.sessionID, 1);
return [400, "application/json", JSON.stringify({ error: "SESSION_NOT_FOUND" })];
}
// Parse treeViewID
var [type, id] = [data.target[0], parseInt(data.target.substr(1))];
var tags = data.tags;
if (type == 'C') {
let collection = await Zotero.Collections.getAsync(id);
if (!collection) {
return [400, "application/json", JSON.stringify({ error: "COLLECTION_NOT_FOUND" })];
}
}
await session.update(data.target, tags);
return [200, "application/json", JSON.stringify({})];
}
};
Zotero.Server.Connector.DelaySync = function () {};
Zotero.Server.Endpoints["/connector/delaySync"] = Zotero.Server.Connector.DelaySync;
Zotero.Server.Connector.DelaySync.prototype = {
supportedMethods: ["POST"],
init: async function (requestData) {
Zotero.Sync.Runner.delaySync(10000);
return [204];
}
};
/**
* Gets progress for an attachment that is currently being saved
*
@ -620,25 +974,44 @@ Zotero.Server.Connector.Import.prototype = {
supportedDataTypes: '*',
permitBookmarklet: false,
init: Zotero.Promise.coroutine(function* (options) {
init: async function (requestData) {
let translate = new Zotero.Translate.Import();
translate.setString(options.data);
let translators = yield translate.getTranslators();
translate.setString(requestData.data);
let translators = await translate.getTranslators();
if (!translators || !translators.length) {
return 400;
}
translate.setTranslator(translators[0]);
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
var libraryID = library.libraryID;
// Shouldn't happen as long as My Library exists
if (!library.editable) {
Zotero.logError("Can't import into read-only library " + library.name);
return [500, "application/json", JSON.stringify({ libraryEditable: false })];
}
let items = yield translate.translate({
libraryID: library.libraryID,
collections: collection ? [collection.id] : null
try {
var session = Zotero.Server.Connector.SessionManager.create(requestData.query.session);
}
catch (e) {
return [409, "application/json", JSON.stringify({ error: "SESSION_EXISTS" })];
}
await session.update(collection ? collection.treeViewID : library.treeViewID);
let items = await translate.translate({
libraryID,
collections: collection ? [collection.id] : null,
forceTagType: 1,
// Import translation skips selection by default, so force it to occur
saveOptions: {
skipSelect: false
}
});
session.addItems(items);
return [201, "application/json", JSON.stringify(items)];
})
}
}
/**
@ -655,9 +1028,11 @@ Zotero.Server.Connector.InstallStyle.prototype = {
supportedDataTypes: '*',
permitBookmarklet: false,
init: Zotero.Promise.coroutine(function* (options) {
init: Zotero.Promise.coroutine(function* (requestData) {
try {
var styleName = yield Zotero.Styles.install(options.data, options.query.origin || null, true);
var styleName = yield Zotero.Styles.install(
requestData.data, requestData.query.origin || null, true
);
} catch (e) {
return [400, "text/plain", e.message];
}
@ -717,7 +1092,7 @@ Zotero.Server.Connector.GetSelectedCollection.prototype = {
* @param {Function} sendResponseCallback function to send HTTP response
*/
init: function(postData, sendResponseCallback) {
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget(true);
var response = {
libraryID: library.libraryID,
libraryName: library.name,
@ -725,9 +1100,6 @@ Zotero.Server.Connector.GetSelectedCollection.prototype = {
editable
};
response.libraryName = library.name;
response.libraryEditable = library.editable;
if(collection && collection.id) {
response.id = collection.id;
response.name = collection.name;
@ -736,7 +1108,64 @@ Zotero.Server.Connector.GetSelectedCollection.prototype = {
response.name = response.libraryName;
}
sendResponseCallback(200, "application/json", JSON.stringify(response));
// Get list of editable libraries and collections
var collections = [];
var originalLibraryID = library.libraryID;
for (let library of Zotero.Libraries.getAll()) {
if (!library.editable) continue;
// Add recent: true for recent targets
collections.push(
{
id: library.treeViewID,
name: library.name,
level: 0
},
...Zotero.Collections.getByLibrary(library.libraryID, true).map(c => ({
id: c.treeViewID,
name: c.name,
level: c.level + 1 || 1 // Added by Zotero.Collections._getByContainer()
}))
);
}
response.targets = collections;
// Mark recent targets
try {
let recents = Zotero.Prefs.get('recentSaveTargets');
if (recents) {
recents = new Set(JSON.parse(recents).map(o => o.id));
for (let target of response.targets) {
if (recents.has(target.id)) {
target.recent = true;
}
}
}
}
catch (e) {
Zotero.logError(e);
Zotero.Prefs.clear('recentSaveTargets');
}
sendResponseCallback(
200,
"application/json",
JSON.stringify(response),
{
// Filter out collection names in debug output
logFilter: function (str) {
try {
let json = JSON.parse(str.match(/^{"libraryID"[^]+/m)[0]);
json.targets.forEach(t => t.name = "\u2026");
return JSON.stringify(json);
}
catch (e) {
return str;
}
}
}
);
}
}
@ -759,7 +1188,7 @@ Zotero.Server.Connector.GetClientHostnames.prototype = {
/**
* Returns a 200 response to say the server is alive
*/
init: Zotero.Promise.coroutine(function* (options) {
init: Zotero.Promise.coroutine(function* (requestData) {
try {
var hostnames = yield Zotero.Proxies.DNS.getHostnames();
} catch(e) {
@ -835,8 +1264,108 @@ Zotero.Server.Connector.Ping.prototype = {
response.prefs.reportActiveURL = true;
}
this.versionWarning(req);
return [200, 'application/json', JSON.stringify(response)];
}
},
/**
* Warn on outdated connector version
*
* We can remove this once the connector checks and warns on its own and most people are on
* a version that does that.
*/
versionWarning: function (req) {
try {
if (!Zotero.Prefs.get('showConnectorVersionWarning')) return;
if (!req.headers) return;
var minVersion = ZOTERO_CONFIG.CONNECTOR_MIN_VERSION;
var appName = ZOTERO_CONFIG.CLIENT_NAME;
var domain = ZOTERO_CONFIG.DOMAIN_NAME;
var origin = req.headers.Origin;
var browser;
var message;
var showDownloadButton = false;
if (origin && origin.startsWith('safari-extension')) {
browser = 'safari';
message = `An update is available for the ${appName} Connector for Safari.\n\n`
+ 'You can upgrade from the Extensions pane of the Safari preferences.';
}
else if (origin && origin.startsWith('chrome-extension')) {
browser = 'chrome';
message = `An update is available for the ${appName} Connector for Chrome.\n\n`
+ `You can upgrade to the latest version from ${domain}.`;
showDownloadButton = true;
}
else if (req.headers['User-Agent'] && req.headers['User-Agent'].includes('Firefox/')) {
browser = 'firefox';
message = `An update is available for the ${appName} Connector for Firefox.\n\n`
+ `You can upgrade to the latest version from ${domain}.`;
showDownloadButton = true;
}
else {
Zotero.debug("Unknown browser");
return;
}
if (Zotero.Server.Connector['skipVersionWarning-' + browser]) return;
var version = req.headers['X-Zotero-Version'];
if (!version || version == '4.999.0') return;
// If connector is up to date, bail
if (Services.vc.compare(version, minVersion) >= 0) return;
var showNextPref = `nextConnectorVersionWarning.${browser}`;
var showNext = Zotero.Prefs.get(showNextPref);
if (showNext && new Date() < new Date(showNext * 1000)) return;
// Don't show again for this browser until restart
Zotero.Server.Connector['skipVersionWarning-' + browser] = true;
var ps = Services.prompt;
var buttonFlags;
if (showDownloadButton) {
buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
}
else {
buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK;
}
setTimeout(function () {
var dontShow = {};
var index = ps.confirmEx(null,
Zotero.getString('general.updateAvailable'),
message,
buttonFlags,
showDownloadButton ? Zotero.getString('general.upgrade') : null,
showDownloadButton ? Zotero.getString('general.notNow') : null,
null,
"Don\u0027t show again for a month",
dontShow
);
var nextShowDays;
if (dontShow.value) {
nextShowDays = 30;
}
// Don't show again for at least a day, even after a restart
else {
nextShowDays = 1;
}
Zotero.Prefs.set(showNextPref, Math.round(Date.now() / 1000) + 86400 * nextShowDays);
if (showDownloadButton && index == 0) {
Zotero.launchURL(ZOTERO_CONFIG.CONNECTORS_URL);
}
}, 500);
}
catch (e) {
Zotero.debug(e, 2);
}
}
}
@ -884,7 +1413,7 @@ Zotero.Server.Connector.IncompatibleVersion.prototype = {
sendResponseCallback(404);
if(Zotero.Server.Connector.IncompatibleVersion._errorShown) return;
Zotero.Integration.activate();
Zotero.Utilities.Internal.activate();
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
ps.alert(null,

View File

@ -0,0 +1,83 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2017 Center for History and New Media
George Mason University, Fairfax, 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 *****
*/
/**
* Adds integration endpoints related to doc integration via HTTP/connector.
*
* document/execCommand initiates an integration command and responds with the
* next request for the http client (e.g. 'Application.getDocument').
* The client should respond to document/respond with the payload and expect
* another response with the next request, until it receives 'Document.complete'
* at which point the integration transaction is considered complete.
*/
Zotero.Server.Endpoints['/connector/document/execCommand'] = function() {};
Zotero.Server.Endpoints['/connector/document/execCommand'].prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
permitBookmarklet: true,
init: function(data, sendResponse) {
if (Zotero.HTTPIntegrationClient.inProgress) {
// This will focus the last integration window if present
Zotero.Integration.execCommand('http', data.command, data.docId);
sendResponse(503, 'text/plain', 'Integration transaction is already in progress')
return;
}
Zotero.HTTPIntegrationClient.inProgress = true;
Zotero.HTTPIntegrationClient.sendResponse = sendResponse;
Zotero.Integration.execCommand('http', data.command, data.docId);
},
};
Zotero.Server.Endpoints['/connector/document/respond'] = function() {};
Zotero.Server.Endpoints['/connector/document/respond'].prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
permitBookmarklet: true,
init: function(data, sendResponse) {
data = JSON.parse(data);
if (data && data.error) {
// Apps Script stack is a JSON object
if (typeof data.stack != "string") {
data.stack = JSON.stringify(data.stack);
}
Zotero.HTTPIntegrationClient.deferredResponse.reject(data);
} else {
Zotero.HTTPIntegrationClient.deferredResponse.resolve(data);
}
Zotero.HTTPIntegrationClient.sendResponse = sendResponse;
}
};
// For managing macOS integration and progress window focus
Zotero.Server.Endpoints['/connector/sendToBack'] = function() {};
Zotero.Server.Endpoints['/connector/sendToBack'].prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
permitBookmarklet: true,
init: function() {
Zotero.Utilities.Internal.sendToBack();
},
};

View File

@ -459,44 +459,44 @@ Zotero.ItemTypes = new function() {
// HiDPI images available
case 'attachment-link':
case 'attachment-pdf':
case 'attachment-web-link':
case 'artwork':
case 'audioRecording':
case 'bill':
case 'blogPost':
case 'book':
case 'bookSection':
case 'case':
case 'computerProgram':
case 'dictionaryEntry':
case 'email':
case 'encyclopediaArticle':
case 'film':
case 'forumPost':
case 'hearing':
case 'instantMessage':
case 'interview':
case 'journalArticle':
case 'letter':
case 'magazineArticle':
case 'manuscript':
case 'newspaperArticle':
case 'note':
case 'patent':
case 'presentation':
case 'report':
case 'statute':
case 'thesis':
case 'webpage':
return "chrome://zotero/skin/treeitem-" + itemType + suffix + ".png";
// No HiDPI images available
case 'attachment-snapshot':
case 'attachment-pdf':
case 'blogPost':
case 'case':
case 'conferencePaper':
case 'dictionaryEntry':
case 'email':
case 'encyclopediaArticle':
case 'hearing':
case 'manuscript':
case 'map':
case 'patent':
case 'podcast':
case 'presentation':
case 'radioBroadcast':
case 'statute':
case 'thesis':
case 'tvBroadcast':
case 'videoRecording':
return "chrome://zotero/skin/treeitem-" + itemType + ".png";

View File

@ -698,8 +698,7 @@ Zotero.Collection.prototype.fromJSON = function (json) {
this.name = json.name;
this.parentKey = json.parentCollection ? json.parentCollection : false;
// TODO
//this.setRelations(json.relations);
this.setRelations(json.relations || {});
}
@ -713,7 +712,7 @@ Zotero.Collection.prototype.toJSON = function (options = {}) {
obj.name = this.name;
obj.parentCollection = this.parentKey ? this.parentKey : false;
obj.relations = {}; // TEMP
obj.relations = this.getRelations();
return this._postToJSON(env);
}

View File

@ -0,0 +1,5 @@
"use strict";
Zotero.DataCache = {
};

View File

@ -285,7 +285,7 @@ Zotero.DataObject.prototype._setParentKey = function(key) {
/**
* Returns all relations of the object
*
* @return {Object} - Object with predicates as keys and arrays of URIs as values
* @return {Object} - Object with predicates as keys and arrays of values
*/
Zotero.DataObject.prototype.getRelations = function () {
this._requireData('relations');
@ -410,7 +410,7 @@ Zotero.DataObject.prototype.setRelations = function (newRelations) {
// Limit predicates to letters and colons for now
for (let p in newRelations) {
if (!/[a-z]+:[a-z]+/.test(p)) {
if (!/^[a-z]+:[a-z]+$/i.test(p)) {
throw new Error(`Invalid relation predicate '${p}'`);
}
}
@ -1249,15 +1249,19 @@ Zotero.DataObject.prototype._finalizeErase = Zotero.Promise.coroutine(function*
});
Zotero.DataObject.prototype.toResponseJSON = function (options) {
Zotero.DataObject.prototype.toResponseJSON = function (options = {}) {
// TODO: library block?
return {
var json = {
key: this.key,
version: this.version,
meta: {},
data: this.toJSON(options)
};
if (options.version) {
json.version = json.data.version = options.version;
}
return json;
}

View File

@ -234,14 +234,9 @@ Zotero.DataObjectUtilities = {
},
_conditionsChanged: function (data1, data2) {
if (!data2) return true;
var pred1 = Object.keys(data1);
pred1.sort();
var pred2 = Object.keys(data2);
pred2.sort();
if (!Zotero.Utilities.arrayEquals(pred1, pred2)) return false;
for (let i in pred1) {
if (!Zotero.Utilities.arrayEquals(pred1[i], pred2[i])) {
if (!data2 || data1.length != data2.length) return true;
for (let i = 0; i < data1.length; i++) {
if (!Zotero.Searches.conditionEquals(data1[i], data2[i])) {
return true;
}
}

View File

@ -111,6 +111,10 @@ Zotero.defineProperty(Zotero.Feed.prototype, 'isFeed', {
value: true
});
Zotero.defineProperty(Zotero.Feed.prototype, 'allowsLinkedFiles', {
value: false
});
Zotero.defineProperty(Zotero.Feed.prototype, 'libraryTypes', {
value: Object.freeze(Zotero.Feed._super.prototype.libraryTypes.concat(['feed']))
});

View File

@ -86,6 +86,10 @@ Zotero.defineProperty(Zotero.Group.prototype, 'id', {
set: function(v) { return this.groupID = v; }
});
Zotero.defineProperty(Zotero.Group.prototype, 'allowsLinkedFiles', {
value: false
});
// Create accessors
(function() {
let accessors = ['name', 'description', 'version'];

View File

@ -145,6 +145,9 @@ Zotero.defineProperty(Zotero.Item.prototype, 'parentItemKey', {
get: function() { return this.parentKey; },
set: function(val) { return this.parentKey = val; }
});
Zotero.defineProperty(Zotero.Item.prototype, 'parentItem', {
get: function() { return Zotero.Items.get(this.parentID) || undefined; },
});
Zotero.defineProperty(Zotero.Item.prototype, 'firstCreator', {
@ -1048,8 +1051,7 @@ Zotero.Item.prototype.setCreator = function (orderIndex, data) {
var msg = "Creator type '" + Zotero.CreatorTypes.getName(data.creatorTypeID) + "' "
+ "isn't valid for " + Zotero.ItemTypes.getName(itemTypeID)
+ " -- changing to primary creator";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
Zotero.warn(msg);
data.creatorTypeID = Zotero.CreatorTypes.getPrimaryIDForType(itemTypeID);
}
@ -1641,8 +1643,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
let noteText = this._noteText ? this._noteText : '';
// Add <div> wrapper if not present
if (!noteText.match(/^<div class="zotero-note znv[0-9]+">[\s\S]*<\/div>$/)) {
// Keep consistent with getNote()
noteText = '<div class="zotero-note znv1">' + noteText + '</div>';
noteText = Zotero.Notes.notePrefix + noteText + Zotero.Notes.noteSuffix;
}
let params = [
@ -2120,6 +2121,15 @@ Zotero.Item.prototype.numAttachments = function (includeTrashed) {
}
Zotero.Item.prototype.numNonHTMLFileAttachments = function () {
this._requireData('childItems');
return this.getAttachments()
.map(itemID => Zotero.Items.get(itemID))
.filter(item => item.isFileAttachment() && item.attachmentContentType != 'text/html')
.length;
};
Zotero.Item.prototype.getFile = function () {
Zotero.debug("Zotero.Item.prototype.getFile() is deprecated -- use getFilePath[Async]()", 2);
@ -2432,102 +2442,67 @@ Zotero.Item.prototype.fileExistsCached = function () {
* -2 - Error renaming
* false - Attachment file not found
*/
Zotero.Item.prototype.renameAttachmentFile = Zotero.Promise.coroutine(function* (newName, overwrite=false, unique=false) {
var origPath = yield this.getFilePathAsync();
Zotero.Item.prototype.renameAttachmentFile = async function (newName, overwrite = false, unique = false) {
var origPath = await this.getFilePathAsync();
if (!origPath) {
Zotero.debug("Attachment file not found in renameAttachmentFile()", 2);
return false;
}
try {
var origName = OS.Path.basename(origPath);
var origModDate = (yield OS.File.stat(origPath)).lastModificationDate;
let origName = OS.Path.basename(origPath);
if (this.isImportedAttachment()) {
var origModDate = (await OS.File.stat(origPath)).lastModificationDate;
}
newName = Zotero.File.getValidFileName(newName);
// Ignore if no change
// No change
if (origName === newName) {
Zotero.debug("Filename has not changed");
return true;
}
var parentDir = OS.Path.dirname(origPath);
var destPath = OS.Path.join(parentDir, newName);
var destName = OS.Path.basename(destPath);
// Get root + extension, if there is one
var pos = destName.lastIndexOf('.');
if (pos > 0) {
var root = destName.substr(0, pos);
var ext = destName.substr(pos + 1);
}
else {
var root = destName;
}
// Update mod time and clear hash so the file syncs
// TODO: use an integer counter instead of mod time for change detection
// Update mod time first, because it may fail for read-only files on Windows
yield OS.File.setDates(origPath, null, null);
var result;
var incr = 0;
while (true) {
// If filename already exists, add a numeric suffix to the end of the root, before
// the extension if there is one
if (incr) {
if (ext) {
destName = root + ' ' + (incr + 1) + '.' + ext;
}
else {
destName = root + ' ' + (incr + 1);
}
destPath = OS.Path.join(parentDir, destName);
if (this.isImportedAttachment()) {
await OS.File.setDates(origPath, null, null);
}
try {
result = yield OS.File.move(origPath, destPath, { noOverwrite: !overwrite })
}
catch (e) {
if (e instanceof OS.File.Error) {
if (e.becauseExists) {
// Increment number to create unique suffix
if (unique) {
incr++;
continue;
}
// If no overwriting or making unique and file exists, return -1
return -1;
}
}
throw e;
}
break;
}
if (result) {
return result;
newName = await Zotero.File.rename(
origPath,
newName,
{
overwrite,
unique
}
);
let destPath = OS.Path.join(OS.Path.dirname(origPath), newName);
yield this.relinkAttachmentFile(destPath);
await this.relinkAttachmentFile(destPath);
if (this.isImportedAttachment()) {
this.attachmentSyncedHash = null;
this.attachmentSyncState = "to_upload";
yield this.saveTx({ skipAll: true });
await this.saveTx({ skipAll: true });
}
return true;
}
catch (e) {
Zotero.logError(e);
// Restore original modification date in case we managed to change it
if (this.isImportedAttachment()) {
try {
OS.File.setDates(origPath, null, origModDate);
} catch (e) {
Zotero.debug(e, 2);
}
Zotero.debug(e);
Components.utils.reportError(e);
}
return -2;
}
});
};
/**
@ -2836,7 +2811,7 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentFilename', {
if (!path) {
return '';
}
var prefixedPath = path.match(/^(?:attachments|storage):(.+)$/);
var prefixedPath = path.match(/^(?:attachments|storage):(.*)$/);
if (prefixedPath) {
return prefixedPath[1];
}
@ -3140,7 +3115,6 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentHash', {
*
* - Currently works on HTML, PDF and plaintext attachments
* - Paragraph breaks will be lost in PDF content
* - For PDFs, will return empty string if Zotero.Fulltext.pdfConverterIsRegistered() is false
*
* @return {Promise<String>} - A promise for attachment text or empty string if unavailable
*/
@ -3194,10 +3168,6 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentText', {
}
if (reindex) {
if (!Zotero.Fulltext.pdfConverterIsRegistered()) {
Zotero.debug("PDF converter is unavailable -- returning empty .attachmentText", 3);
return '';
}
yield Zotero.Fulltext.indexItems(this.id, false);
}
@ -4017,6 +3987,89 @@ Zotero.Item.prototype.clone = function (libraryID, options = {}) {
}
/**
* @param {Zotero.Item} item
* @param {Integer} libraryID
* @return {Zotero.Item} - New item
*/
Zotero.Item.prototype.moveToLibrary = async function (libraryID, onSkippedAttachment) {
if (!this.isEditable) {
throw new Error("Can't move item in read-only library");
}
var library = Zotero.Libraries.get(libraryID);
Zotero.debug("Moving item to " + library.name);
if (!library.editable) {
throw new Error("Can't move item to read-only library");
}
var filesEditable = library.filesEditable;
var allowsLinkedFiles = library.allowsLinkedFiles;
var newItem = await Zotero.DB.executeTransaction(async function () {
// Create new clone item in target library
var newItem = this.clone(libraryID);
var newItemID = await newItem.save({
skipSelect: true
});
if (this.isNote()) {
// Delete old item
await this.erase();
return newItem;
}
// For regular items, add child items
// Child notes
var noteIDs = this.getNotes();
var notes = Zotero.Items.get(noteIDs);
for (let note of notes) {
let newNote = note.clone(libraryID);
newNote.parentID = newItemID;
await newNote.save({
skipSelect: true
});
}
// Child attachments
var attachmentIDs = this.getAttachments();
var attachments = Zotero.Items.get(attachmentIDs);
for (let attachment of attachments) {
let linkMode = attachment.attachmentLinkMode;
// Skip linked files if not allowed in destination
if (!allowsLinkedFiles && linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
Zotero.debug("Target library doesn't support linked files -- skipping attachment");
if (onSkippedAttachment) {
await onSkippedAttachment(attachment);
}
continue;
}
// Skip files if not allowed in destination
if (!filesEditable && linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
Zotero.debug("Target library doesn't allow file editing -- skipping attachment");
if (onSkippedAttachment) {
await onSkippedAttachment(attachment);
}
continue;
}
await Zotero.Attachments.moveAttachmentToLibrary(
attachment, libraryID, newItemID
);
}
return newItem;
}.bind(this));
// Delete old item. Do this outside of a transaction so we don't leave stranded files
// in the target library if deleting fails.
await this.eraseTx();
return newItem;
};
Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
Zotero.DB.requireTransaction();
@ -4141,6 +4194,9 @@ Zotero.Item.prototype.fromJSON = function (json) {
case 'mtime':
// Handled below
case 'collections':
case 'parentItem':
case 'deleted':
case 'inPublications':
break;
case 'accessDate':
@ -4169,15 +4225,6 @@ Zotero.Item.prototype.fromJSON = function (json) {
this[field] = val;
break;
case 'parentItem':
this.parentKey = val;
break;
case 'deleted':
case 'inPublications':
this[field] = !!val;
break;
case 'creators':
this.setCreators(json.creators);
break;
@ -4265,6 +4312,13 @@ Zotero.Item.prototype.fromJSON = function (json) {
let note = json.note;
this.setNote(note !== undefined ? note : "");
}
// Update boolean fields that might not be present in JSON
['deleted', 'inPublications'].forEach(field => {
if (json[field] || this[field]) {
this[field] = !!json[field];
}
});
}

View File

@ -260,6 +260,11 @@ Zotero.ItemFields = new function() {
throw new Error("Invalid field '" + baseField + '" for base field');
}
// If field isn't a base field, return it if it's valid for the type
if (!this.isBaseField(baseFieldID)) {
return this.isValidForType(baseFieldID, itemTypeID) ? baseFieldID : false;
}
return _baseTypeFields[itemTypeID][baseFieldID];
}

View File

@ -315,8 +315,22 @@ Zotero.Items = function() {
item._clearChanged('itemData');
// Display titles
try {
item.updateDisplayTitle()
}
catch (e) {
// A few item types need creators to be loaded. Instead of making
// updateDisplayTitle() async and loading conditionally, just catch the error
// and load on demand
if (e instanceof Zotero.Exception.UnloadedDataException) {
yield item.loadDataType('creators');
item.updateDisplayTitle()
}
else {
throw e;
}
}
}
});
@ -415,6 +429,10 @@ Zotero.Items = function() {
// Convert non-HTML notes on-the-fly
if (note !== "") {
if (typeof note == 'number') {
note = '' + note;
}
if (typeof note == 'string') {
if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
note = Zotero.Utilities.htmlSpecialChars(note);
note = Zotero.Notes.notePrefix + '<p>'
@ -431,6 +449,12 @@ Zotero.Items = function() {
let endLen = 6; // "</div>".length
note = note.substr(startLen, note.length - startLen - endLen);
}
// Clear null notes
else {
note = '';
notesToUpdate.push([item.id, '']);
}
}
item._noteText = note ? note : '';
item._loaded.note = true;
@ -725,6 +749,8 @@ Zotero.Items = function() {
this.merge = function (item, otherItems) {
Zotero.debug("Merging items");
return Zotero.DB.executeTransaction(function* () {
var otherItemIDs = [];
var itemURI = Zotero.URI.getItemURI(item);
@ -748,7 +774,10 @@ Zotero.Items = function() {
}
// Add relations to master
item.setRelations(otherItem.getRelations());
let oldRelations = otherItem.getRelations();
for (let pred in oldRelations) {
oldRelations[pred].forEach(obj => item.addRelation(pred, obj));
}
// Remove merge-tracking relations from other item, so that there aren't two
// subjects for a given deleted object
@ -1176,6 +1205,25 @@ Zotero.Items = function() {
};
/**
* Returns an array of items with children of selected parents removed
*
* @return {Zotero.Item[]}
*/
this.keepParents = function (items) {
var parentItems = new Set(
items
.filter(item => item.isTopLevelItem())
.map(item => item.id)
);
return items.filter(item => {
var parentItemID = item.parentItemID;
// Not a child item or not a child of one of the passed items
return !parentItemID || !parentItems.has(parentItemID);
});
}
/*
* Generate SQL to retrieve firstCreator field
*
@ -1416,6 +1464,7 @@ Zotero.Items = function() {
return title.replace(/^[\[\'\"](.*)[\'\"\]]?$/, '$1')
}
Zotero.DataObjects.call(this);
return this;

View File

@ -190,6 +190,10 @@ Zotero.defineProperty(Zotero.Library.prototype, 'hasTrash', {
value: true
});
Zotero.defineProperty(Zotero.Library.prototype, 'allowsLinkedFiles', {
value: true
});
// Create other accessors
(function() {
let accessors = ['editable', 'filesEditable', 'storageVersion', 'archived'];

View File

@ -32,7 +32,8 @@ Zotero.Relations = new function () {
this._namespaces = {
dc: 'http://purl.org/dc/elements/1.1/',
owl: 'http://www.w3.org/2002/07/owl#'
owl: 'http://www.w3.org/2002/07/owl#',
mendeleyDB: 'http://zotero.org/namespaces/mendeleyDB#'
};
var _types = ['collection', 'item'];
@ -146,7 +147,7 @@ Zotero.Relations = new function () {
* @return {Object[]} - An array of objects with a Zotero.DataObject as 'subject'
* and a predicate string as 'predicate'
*/
this.getByObject = function (objectType, object) {
this.getByObject = Zotero.Promise.coroutine(function* (objectType, object) {
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
var predicateIDs = [];
var o = _subjectPredicatesByObject[objectType]
@ -156,13 +157,16 @@ Zotero.Relations = new function () {
}
var toReturn = [];
for (let predicateID in o) {
o[predicateID].forEach(subjectID => toReturn.push({
subject: objectsClass.get(subjectID),
for (let subjectID of o[predicateID]) {
var subject = yield objectsClass.getAsync(subjectID);
toReturn.push({
subject: subject,
predicate: Zotero.RelationPredicates.getName(predicateID)
}));
});
};
}
return toReturn;
};
});
this.updateUser = Zotero.Promise.coroutine(function* (fromUserID, toUserID) {
@ -179,9 +183,9 @@ Zotero.Relations = new function () {
let objects = yield Zotero.DB.columnQueryAsync(
sql, 'http://zotero.org/users/' + fromUserID + '/%'
);
Zotero.DB.addCurrentCallback("commit", function () {
Zotero.DB.addCurrentCallback("commit", function* () {
for (let object of objects) {
let subPrefs = this.getByObject(type, object);
let subPrefs = yield this.getByObject(type, object);
let newObject = object.replace(
new RegExp("^http://zotero.org/users/" + fromUserID + "/(.*)"),
"http://zotero.org/users/" + toUserID + "/$1"

View File

@ -338,7 +338,9 @@ Zotero.SearchConditions = new function(){
doesNotContain: true
},
table: 'itemNotes',
field: 'note'
// Exclude note prefix and suffix
field: `SUBSTR(note, ${1 + Zotero.Notes.notePrefix.length}, `
+ `LENGTH(note) - ${Zotero.Notes.notePrefix.length + Zotero.Notes.noteSuffix.length})`
},
{
@ -348,7 +350,9 @@ Zotero.SearchConditions = new function(){
doesNotContain: true
},
table: 'items',
field: 'note'
// Exclude note prefix and suffix
field: `SUBSTR(note, ${1 + Zotero.Notes.notePrefix.length}, `
+ `LENGTH(note) - ${Zotero.Notes.notePrefix.length + Zotero.Notes.noteSuffix.length})`
},
{

View File

@ -96,6 +96,13 @@ Zotero.Searches = function() {
}
this.conditionEquals = function (data1, data2) {
return data1.condition === data2.condition
&& data1.operator === data2.operator
&& data1.value === data2.value;
},
this._loadConditions = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT savedSearchID, searchConditionID, condition, operator, value, required "
+ "FROM savedSearches LEFT JOIN savedSearchConditions USING (savedSearchID) "

View File

@ -758,12 +758,13 @@ Zotero.Tags = new function() {
* @return {Q Promise} A Q promise for a data: URL for a PNG
*/
this.generateItemsListImage = function (colors, extraImage) {
var multiplier = (extraImage && extraImage.indexOf('2x') != -1) ? 2 : 1;
var multiplier = Zotero.hiDPI ? 2 : 1;
var swatchWidth = 8 * multiplier;
var separator = 3 * multiplier;
var extraImageSeparator = 1 * multiplier;
var extraImageWidth = 16 * multiplier;
var extraImageHeight = 16 * multiplier;
var canvasHeight = 16 * multiplier;
var swatchHeight = 8 * multiplier;
var prependExtraImage = true;
@ -831,7 +832,7 @@ Zotero.Tags = new function() {
// When extra image has loaded, draw it
img.onload = function () {
ctx.drawImage(img, x, 0);
ctx.drawImage(img, x, 0, extraImageWidth, extraImageHeight);
var dataURI = canvas.toDataURL("image/png");
var dataURIPromise = Zotero.Promise.resolve(dataURI);
@ -852,7 +853,7 @@ Zotero.Tags = new function() {
// for the composite image once it's ready
return _itemsListExtraImagePromises[extraImage]
.then(function (img) {
ctx.drawImage(img, x, 0);
ctx.drawImage(img, x, 0, extraImageWidth, extraImageHeight);
var dataURI = canvas.toDataURL("image/png");
var dataURIPromise = Zotero.Promise.resolve(dataURI);

View File

@ -182,6 +182,23 @@ Zotero.DataDirectory = {
dataDir = this.defaultDir;
// If there's already a profile pointing to the default location, use a different
// data directory named after the profile, as long as one either doesn't exist yet or
// one does and it contains a database
try {
if ((yield Zotero.Profile.findOtherProfilesUsingDataDirectory(dataDir, false)).length) {
let profileName = OS.Path.basename(Zotero.Profile.dir).match(/[^.]+\.(.+)/)[1];
let newDataDir = this.defaultDir + ' ' + profileName;
if (!(yield OS.File.exists(newDataDir))
|| (yield OS.File.exists(OS.Path.join(newDataDir, dbFilename)))) {
dataDir = newDataDir;
}
}
}
catch (e) {
Zotero.logError(e);
}
// Check for ~/Zotero/zotero.sqlite
let dbFile = OS.Path.join(dataDir, dbFilename);
if (yield OS.File.exists(dbFile)) {
@ -238,35 +255,7 @@ Zotero.DataDirectory = {
// Read in prefs
let prefsFile = OS.Path.join(profileDir, "prefs.js");
if (yield OS.File.exists(prefsFile)) {
// build sandbox
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
Components.utils.evalInSandbox(
"var prefs = {};"+
"function user_pref(key, val) {"+
"prefs[key] = val;"+
"}"
, sandbox);
(yield Zotero.File.getContentsAsync(prefsFile))
.split(/\n/)
.filter((line) => {
// Strip comments
return !line.startsWith('#')
// Only process lines in our pref branch
&& line.includes(ZOTERO_CONFIG.PREF_BRANCH);
})
// Process each line individually
.forEach((line) => {
try {
Zotero.debug("Processing " + line);
Components.utils.evalInSandbox(line, sandbox);
}
catch (e) {
Zotero.logError("Error processing prefs line: " + line);
}
});
var prefs = sandbox.prefs;
let prefs = yield Zotero.Profile.readPrefsFromFile(prefsFile);
// Check for data dir pref
if (prefs['extensions.zotero.dataDir'] && prefs['extensions.zotero.useDataDir']) {

View File

@ -296,13 +296,13 @@ Zotero.Date = new function(){
// Parse 'yesterday'/'today'/'tomorrow'
var lc = (string + '').toLowerCase();
if (lc == 'yesterday' || (Zotero.getString && lc === Zotero.getString('date.yesterday'))) {
if (lc == 'yesterday' || (Zotero.isClient && lc === Zotero.getString('date.yesterday'))) {
string = Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60*60*24)).substr(0, 10); // no 'this' for translator sandbox
}
else if (lc == 'today' || (Zotero.getString && lc == Zotero.getString('date.today'))) {
else if (lc == 'today' || (Zotero.isClient && lc == Zotero.getString('date.today'))) {
string = Zotero.Date.dateToSQL(new Date()).substr(0, 10);
}
else if (lc == 'tomorrow' || (Zotero.getString && lc == Zotero.getString('date.tomorrow'))) {
else if (lc == 'tomorrow' || (Zotero.isClient && lc == Zotero.getString('date.tomorrow'))) {
string = Zotero.Date.dateToSQL(new Date(Date.now() + 1000*60*60*24)).substr(0, 10);
}
else {

View File

@ -31,8 +31,8 @@
// the same database is accessed simultaneously by multiple Zotero instances.
const DB_LOCK_EXCLUSIVE = true;
Zotero.DBConnection = function(dbName) {
if (!dbName) {
Zotero.DBConnection = function(dbNameOrPath) {
if (!dbNameOrPath) {
throw ('DB name not provided in Zotero.DBConnection()');
}
@ -70,8 +70,18 @@ Zotero.DBConnection = function(dbName) {
return Zotero.Date.toUnixTimestamp(d);
});
// Private members
this._dbName = dbName;
// Absolute path to DB
if (dbNameOrPath.startsWith('/') || (Zotero.isWin && dbNameOrPath.includes('\\'))) {
this._dbName = OS.Path.basename(dbNameOrPath).replace(/\.sqlite$/, '');
this._dbPath = dbNameOrPath;
this._externalDB = true;
}
// DB name in data directory
else {
this._dbName = dbNameOrPath;
this._dbPath = Zotero.DataDirectory.getDatabase(dbNameOrPath);
this._externalDB = false;
}
this._shutdown = false;
this._connection = null;
this._transactionID = null;
@ -91,6 +101,14 @@ Zotero.DBConnection = function(dbName) {
this._dbIsCorrupt = null
this._transactionPromise = null;
if (dbNameOrPath == 'zotero') {
this.IncompatibleVersionException = function (msg, dbClientVersion) {
this.message = msg;
this.dbClientVersion = dbClientVersion;
}
this.IncompatibleVersionException.prototype = Object.create(Error.prototype);
}
}
/////////////////////////////////////////////////////////////////
@ -105,7 +123,7 @@ Zotero.DBConnection = function(dbName) {
* @return void
*/
Zotero.DBConnection.prototype.test = function () {
return this._getConnectionAsync().return();
return this._getConnectionAsync().then(() => {});
}
Zotero.DBConnection.prototype.getAsyncStatement = Zotero.Promise.coroutine(function* (sql) {
@ -485,7 +503,7 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
// Run begin callbacks
for (var i=0; i<this._callbacks.begin.length; i++) {
if (this._callbacks.begin[i]) {
this._callbacks.begin[i]();
this._callbacks.begin[i](id);
}
}
var conn = this._getConnection(options) || (yield this._getConnectionAsync(options));
@ -516,13 +534,13 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
// Run temporary commit callbacks
var f;
while (f = this._callbacks.current.commit.shift()) {
yield Zotero.Promise.resolve(f());
yield Zotero.Promise.resolve(f(id));
}
// Run commit callbacks
for (var i=0; i<this._callbacks.commit.length; i++) {
if (this._callbacks.commit[i]) {
yield this._callbacks.commit[i]();
yield this._callbacks.commit[i](id);
}
}
@ -549,13 +567,13 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
// Run temporary commit callbacks
var f;
while (f = this._callbacks.current.rollback.shift()) {
yield Zotero.Promise.resolve(f());
yield Zotero.Promise.resolve(f(id));
}
// Run rollback callbacks
for (var i=0; i<this._callbacks.rollback.length; i++) {
if (this._callbacks.rollback[i]) {
yield Zotero.Promise.resolve(this._callbacks.rollback[i]());
yield Zotero.Promise.resolve(this._callbacks.rollback[i](id));
}
}
@ -818,9 +836,10 @@ Zotero.DBConnection.prototype.logQuery = function (sql, params = [], options) {
}
Zotero.DBConnection.prototype.tableExists = Zotero.Promise.coroutine(function* (table) {
Zotero.DBConnection.prototype.tableExists = Zotero.Promise.coroutine(function* (table, db) {
yield this._getConnectionAsync();
var sql = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name=?";
var prefix = db ? db + '.' : '';
var sql = `SELECT COUNT(*) FROM ${prefix}sqlite_master WHERE type='table' AND tbl_name=?`;
var count = yield this.valueQueryAsync(sql, [table]);
return !!count;
});
@ -879,7 +898,7 @@ Zotero.DBConnection.prototype.vacuum = function () {
// TEMP
Zotero.DBConnection.prototype.info = Zotero.Promise.coroutine(function* () {
var info = {};
var pragmas = ['auto_vacuum', 'cache_size', 'locking_mode', 'page_size'];
var pragmas = ['auto_vacuum', 'cache_size', 'main.locking_mode', 'page_size'];
for (let p of pragmas) {
info[p] = yield Zotero.DB.valueQueryAsync(`PRAGMA ${p}`);
}
@ -894,9 +913,13 @@ Zotero.DBConnection.prototype.integrityCheck = Zotero.Promise.coroutine(function
Zotero.DBConnection.prototype.checkException = function (e) {
if (this._externalDB) {
return true;
}
if (e.message.includes(this.DB_CORRUPTION_STRING)) {
// Write corrupt marker to data directory
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'is.corrupt'));
var file = Zotero.File.pathToFile(this._dbPath + '.is.corrupt');
Zotero.File.putContents(file, '');
this._dbIsCorrupt = true;
@ -947,6 +970,11 @@ Zotero.DBConnection.prototype.closeDatabase = Zotero.Promise.coroutine(function*
Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function* (suffix, force) {
if (this.skipBackup || this._externalDB || Zotero.skipLoading) {
this._debug("Skipping backup of database '" + this._dbName + "'", 1);
return false;
}
var storageService = Components.classes["@mozilla.org/storage/service;1"]
.getService(Components.interfaces.mozIStorageService);
@ -980,27 +1008,21 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
});
try {
var corruptMarker = Zotero.File.pathToFile(
Zotero.DataDirectory.getDatabase(this._dbName, 'is.corrupt')
);
let corruptMarker = Zotero.File.pathToFile(this._dbPath + '.is.corrupt');
if (this.skipBackup || Zotero.skipLoading) {
this._debug("Skipping backup of database '" + this._dbName + "'", 1);
return false;
}
else if (this._dbIsCorrupt || corruptMarker.exists()) {
if (this._dbIsCorrupt || corruptMarker.exists()) {
this._debug("Database '" + this._dbName + "' is marked as corrupt -- skipping backup", 1);
return false;
}
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
let file = this._dbPath;
// For standard backup, make sure last backup is old enough to replace
if (!suffix && !force) {
var backupFile = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'bak'));
if (yield OS.File.exists(backupFile.path)) {
var currentDBTime = (yield OS.File.stat(file.path)).lastModificationDate;
var lastBackupTime = (yield OS.File.stat(backupFile.path)).lastModificationDate;
let backupFile = this._dbPath + '.bak';
if (yield OS.File.exists(backupFile)) {
let currentDBTime = (yield OS.File.stat(file.path)).lastModificationDate;
let lastBackupTime = (yield OS.File.stat(backupFile)).lastModificationDate;
if (currentDBTime == lastBackupTime) {
Zotero.debug("Database '" + this._dbName + "' hasn't changed -- skipping backup");
return;
@ -1021,7 +1043,7 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
// Copy via a temporary file so we don't run into disk space issues
// after deleting the old backup file
var tmpFile = Zotero.DataDirectory.getDatabase(this._dbName, 'tmp');
var tmpFile = this._dbPath + '.tmp';
if (yield OS.File.exists(tmpFile)) {
try {
yield OS.File.remove(tmpFile);
@ -1038,18 +1060,21 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
// the lock is lost
try {
if (DB_LOCK_EXCLUSIVE) {
yield this.queryAsync("PRAGMA locking_mode=NORMAL", false, { inBackup: true });
yield this.queryAsync("PRAGMA main.locking_mode=NORMAL", false, { inBackup: true });
}
storageService.backupDatabaseFile(file, OS.Path.basename(tmpFile), file.parent);
storageService.backupDatabaseFile(
Zotero.File.pathToFile(file),
OS.Path.basename(tmpFile),
Zotero.File.pathToFile(file).parent
);
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
Zotero.logError(e);
return false;
}
finally {
if (DB_LOCK_EXCLUSIVE) {
yield this.queryAsync("PRAGMA locking_mode=EXCLUSIVE", false, { inBackup: true });
yield this.queryAsync("PRAGMA main.locking_mode=EXCLUSIVE", false, { inBackup: true });
}
}
@ -1080,7 +1105,7 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
// Special backup
if (!suffix && numBackups > 1) {
// Remove oldest backup file
var targetFile = Zotero.DataDirectory.getDatabase(this._dbName, (numBackups - 1) + '.bak');
let targetFile = this._dbPath + '.' + (numBackups - 1) + '.bak';
if (yield OS.File.exists(targetFile)) {
yield OS.File.remove(targetFile);
}
@ -1090,12 +1115,8 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
var targetNum = i;
var sourceNum = targetNum - 1;
var targetFile = Zotero.DataDirectory.getDatabase(
this._dbName, targetNum + '.bak'
);
var sourceFile = Zotero.DataDirectory.getDatabase(
this._dbName, sourceNum ? sourceNum + '.bak' : 'bak'
);
let targetFile = this._dbPath + '.' + targetNum + '.bak';
let sourceFile = this._dbPath + '.' + (sourceNum ? sourceNum + '.bak' : 'bak')
if (!(yield OS.File.exists(sourceFile))) {
continue;
@ -1107,9 +1128,7 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
}
}
var backupFile = Zotero.DataDirectory.getDatabase(
this._dbName, (suffix ? suffix + '.' : '') + 'bak'
);
let backupFile = this._dbPath + '.' + (suffix ? suffix + '.' : '') + 'bak';
// Remove old backup file
if (yield OS.File.exists(backupFile)) {
@ -1146,11 +1165,11 @@ Zotero.DBConnection.prototype._getConnection = function (options) {
/*
* Retrieve a link to the data store asynchronously
*/
Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(function* (options) {
Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
// If a backup is in progress, wait until it's done
if (this._backupPromise && this._backupPromise.isPending() && (!options || !options.inBackup)) {
Zotero.debug("Waiting for database backup to complete", 2);
yield this._backupPromise;
await this._backupPromise;
}
if (this._connection) {
@ -1161,48 +1180,50 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
}
this._debug("Asynchronously opening database '" + this._dbName + "'");
Zotero.debug(this._dbPath);
// Get the storage service
var store = Components.classes["@mozilla.org/storage/service;1"].
getService(Components.interfaces.mozIStorageService);
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
var backupFile = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'bak'));
var fileName = this._dbName + '.sqlite';
var file = this._dbPath;
var backupFile = this._dbPath + '.bak';
var fileName = OS.Path.basename(file);
var corruptMarker = this._dbPath + '.is.corrupt';
catchBlock: try {
var corruptMarker = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'is.corrupt'));
if (corruptMarker.exists()) {
if (await OS.File.exists(corruptMarker)) {
throw new Error(this.DB_CORRUPTION_STRING);
}
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
path: file.path
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
path: file
}));
}
catch (e) {
// Don't deal with corrupted external dbs
if (this._externalDB) {
throw e;
}
Zotero.logError(e);
if (e.message.includes(this.DB_CORRUPTION_STRING)) {
this._debug("Database file '" + file.leafName + "' corrupted", 1);
this._debug(`Database file '${fileName}' corrupted`, 1);
// No backup file! Eek!
if (!backupFile.exists()) {
if (!await OS.File.exists(backupFile)) {
this._debug("No backup file for DB '" + this._dbName + "' exists", 1);
// Save damaged filed
this._debug('Saving damaged DB file with .damaged extension', 1);
var damagedFile = Zotero.File.pathToFile(
Zotero.DataDirectory.getDatabase(this._dbName, 'damaged')
);
let damagedFile = this._dbPath + '.damaged';
Zotero.moveToUnique(file, damagedFile);
// Create new main database
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
this._connection = store.openDatabase(file);
if (corruptMarker.exists()) {
corruptMarker.remove(null);
if (await OS.File.exists(corruptMarker)) {
await OS.File.remove(corruptMarker);
}
Zotero.alert(
@ -1215,24 +1236,21 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
// Save damaged file
this._debug('Saving damaged DB file with .damaged extension', 1);
var damagedFile = Zotero.File.pathToFile(
Zotero.DataDirectory.getDatabase(this._dbName, 'damaged')
);
let damagedFile = this._dbPath + '.damaged';
Zotero.moveToUnique(file, damagedFile);
// Test the backup file
try {
Zotero.debug("Asynchronously opening DB connection");
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
path: backupFile.path
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
path: backupFile
}));
}
// Can't open backup either
catch (e) {
// Create new main database
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
path: file.path
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
path: file
}));
Zotero.alert(
@ -1241,8 +1259,8 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
Zotero.getString('db.dbRestoreFailed', fileName)
);
if (corruptMarker.exists()) {
corruptMarker.remove(null);
if (await OS.File.exists(corruptMarker)) {
await OS.File.remove(corruptMarker);
}
break catchBlock;
@ -1253,7 +1271,7 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
// Copy backup file to main DB file
this._debug("Restoring database '" + this._dbName + "' from backup file", 1);
try {
backupFile.copyTo(backupFile.parent, fileName);
await OS.File.copy(backupFile, file);
}
catch (e) {
// TODO: deal with low disk space
@ -1261,8 +1279,7 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
}
// Open restored database
var file = OS.Path.join(Zotero.DataDirectory.dir, fileName);
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
path: file
}));
this._debug('Database restored', 1);
@ -1271,13 +1288,13 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
Zotero.getString('general.warning'),
Zotero.getString('db.dbRestored', [
fileName,
Zotero.Date.getFileDateString(backupFile),
Zotero.Date.getFileTimeString(backupFile)
Zotero.Date.getFileDateString(Zotero.File.pathToFile(backupFile)),
Zotero.Date.getFileTimeString(Zotero.File.pathToFile(backupFile))
])
);
if (corruptMarker.exists()) {
corruptMarker.remove(null);
if (await OS.File.exists(corruptMarker)) {
await OS.File.remove(corruptMarker);
}
break catchBlock;
@ -1287,20 +1304,21 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
throw (e);
}
if (!this._externalDB) {
if (DB_LOCK_EXCLUSIVE) {
yield this.queryAsync("PRAGMA locking_mode=EXCLUSIVE");
await this.queryAsync("PRAGMA main.locking_mode=EXCLUSIVE");
}
else {
yield this.queryAsync("PRAGMA locking_mode=NORMAL");
await this.queryAsync("PRAGMA main.locking_mode=NORMAL");
}
// Set page cache size to 8MB
var pageSize = yield this.valueQueryAsync("PRAGMA page_size");
var cacheSize = 8192000 / pageSize;
yield this.queryAsync("PRAGMA cache_size=" + cacheSize);
let pageSize = await this.valueQueryAsync("PRAGMA page_size");
let cacheSize = 8192000 / pageSize;
await this.queryAsync("PRAGMA cache_size=" + cacheSize);
// Enable foreign key checks
yield this.queryAsync("PRAGMA foreign_keys=true");
await this.queryAsync("PRAGMA foreign_keys=true");
// Register idle observer for DB backup
Zotero.Schema.schemaUpdatePromise.then(() => {
@ -1309,22 +1327,13 @@ Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(fun
.getService(Components.interfaces.nsIIdleService);
idleService.addIdleObserver(this, 300);
});
}
return this._connection;
});
};
Zotero.DBConnection.prototype._debug = function (str, level) {
var prefix = this._dbName == 'zotero' ? '' : '[' + this._dbName + '] ';
Zotero.debug(prefix + str, level);
}
// Initialize main database connection
Zotero.DB = new Zotero.DBConnection('zotero');
Zotero.DB.IncompatibleVersionException = function (msg, dbClientVersion) {
this.message = msg;
this.dbClientVersion = dbClientVersion;
}
Zotero.DB.IncompatibleVersionException.prototype = Object.create(Error.prototype);

View File

@ -427,6 +427,82 @@ Zotero.File = new function(){
});
/**
* Rename file within its parent directory
*
* @param {String} file - File path
* @param {String} newName
* @param {Object} [options]
* @param {Boolean} [options.overwrite=false] - Overwrite file if one exists
* @param {Boolean} [options.unique=false] - Add suffix to create unique filename if necessary
* @return {String|false} - New filename, or false if destination file exists and `overwrite` not set
*/
this.rename = async function (file, newName, options = {}) {
var overwrite = options.overwrite || false;
var unique = options.unique || false;
var origPath = file;
var origName = OS.Path.basename(origPath);
newName = Zotero.File.getValidFileName(newName);
// Ignore if no change
if (origName === newName) {
Zotero.debug("Filename has not changed");
return origName;
}
var parentDir = OS.Path.dirname(origPath);
var destPath = OS.Path.join(parentDir, newName);
var destName = OS.Path.basename(destPath);
// Get root + extension, if there is one
var pos = destName.lastIndexOf('.');
if (pos > 0) {
var root = destName.substr(0, pos);
var ext = destName.substr(pos + 1);
}
else {
var root = destName;
}
var incr = 0;
while (true) {
// If filename already exists, add a numeric suffix to the end of the root, before
// the extension if there is one
if (incr) {
if (ext) {
destName = root + ' ' + (incr + 1) + '.' + ext;
}
else {
destName = root + ' ' + (incr + 1);
}
destPath = OS.Path.join(parentDir, destName);
}
try {
Zotero.debug(`Renaming ${origPath} to ${OS.Path.basename(destPath)}`);
Zotero.debug(destPath);
await OS.File.move(origPath, destPath, { noOverwrite: !overwrite })
}
catch (e) {
if (e instanceof OS.File.Error) {
if (e.becauseExists) {
// Increment number to create unique suffix
if (unique) {
incr++;
continue;
}
// No overwriting or making unique and file exists
return false;
}
}
throw e;
}
break;
}
return destName;
};
/**
* Delete a file if it exists, asynchronously
*

View File

@ -24,17 +24,8 @@
*/
Zotero.Fulltext = Zotero.FullText = new function(){
const CACHE_FILE = '.zotero-ft-cache';
this.pdfConverterIsRegistered = pdfConverterIsRegistered;
this.pdfInfoIsRegistered = pdfInfoIsRegistered;
this.isCachedMIMEType = isCachedMIMEType;
this.pdfToolsDownloadBaseURL = ZOTERO_CONFIG.PDF_TOOLS_URL;
this.__defineGetter__("pdfToolsName", function() { return 'Xpdf'; });
this.__defineGetter__("pdfToolsURL", function() { return 'http://www.foolabs.com/xpdf/'; });
this.__defineGetter__("pdfConverterName", function() { return 'pdftotext'; });
this.__defineGetter__("pdfInfoName", function() { return 'pdfinfo'; });
this.__defineGetter__("pdfConverterCacheFile", function () { return '.zotero-ft-cache'; });
this.__defineGetter__("pdfInfoCacheFile", function () { return '.zotero-ft-info'; });
@ -61,15 +52,9 @@ Zotero.Fulltext = Zotero.FullText = new function(){
const kWbClassHWKatakanaLetter = 6;
const kWbClassThaiLetter = 7;
var _pdfConverterVersion = null;
var _pdfConverterFileName = null;
var _pdfConverterScript = null; // nsIFile of hidden window script on Windows
var _pdfConverter = null; // nsIFile to executable
var _pdfInfoVersion = null;
var _pdfInfoFileName = null;
var _pdfInfoScript = null; // nsIFile of redirection script
var _pdfInfo = null; // nsIFile to executable
var _pdfData = null;
var _idleObserverIsRegistered = false;
var _idleObserverDelay = 30;
@ -85,21 +70,33 @@ Zotero.Fulltext = Zotero.FullText = new function(){
this.decoder = Components.classes["@mozilla.org/intl/utf8converterservice;1"].
getService(Components.interfaces.nsIUTF8ConverterService);
var platform = Zotero.platform.replace(/ /g, '-');
_pdfConverterFileName = this.pdfConverterName + '-' + platform;
_pdfInfoFileName = this.pdfInfoName + '-' + platform;
let pdfConverterFileName = "pdftotext";
let pdfInfoFileName = "pdfinfo";
if (Zotero.isWin) {
_pdfConverterFileName += '.exe';
_pdfInfoFileName += '.exe';
pdfConverterFileName += '.exe';
pdfInfoFileName += '.exe';
}
this.__defineGetter__("pdfConverterFileName", function() { return _pdfConverterFileName; });
this.__defineGetter__("pdfConverterVersion", function() { return _pdfConverterVersion; });
this.__defineGetter__("pdfInfoFileName", function() { return _pdfInfoFileName; });
this.__defineGetter__("pdfInfoVersion", function() { return _pdfInfoVersion; });
let dir = FileUtils.getDir('AChrom', []).parent;
yield this.registerPDFTool('converter');
yield this.registerPDFTool('info');
_pdfData = dir.clone();
_pdfData.append('poppler-data');
_pdfData = _pdfData.path;
_pdfConverter = dir.clone();
_pdfInfo = dir.clone();
if(Zotero.isMac) {
_pdfConverter = _pdfConverter.parent;
_pdfConverter.append('MacOS');
_pdfInfo = _pdfInfo.parent;
_pdfInfo.append('MacOS');
}
_pdfConverter.append(pdfConverterFileName);
_pdfInfo.append(pdfInfoFileName);
Zotero.uiReadyPromise.delay(30000).then(() => {
this.registerContentProcessor();
@ -134,6 +131,22 @@ Zotero.Fulltext = Zotero.FullText = new function(){
});
this.setPDFConverterPath = function(path) {
_pdfConverter = Zotero.File.pathToFile(path);
};
this.setPDFInfoPath = function(path) {
_pdfInfo = Zotero.File.pathToFile(path);
};
this.setPDFDataPath = function(path) {
_pdfData = path;
};
this.getLibraryVersion = function (libraryID) {
if (!libraryID) throw new Error("libraryID not provided");
return Zotero.DB.valueQueryAsync(
@ -197,279 +210,12 @@ Zotero.Fulltext = Zotero.FullText = new function(){
}
this.getLatestPDFToolsVersion = Zotero.Promise.coroutine(function* () {
if (Zotero.isWin) {
return "3.02a";
}
// Find latest version for this platform
var url = Zotero.Fulltext.pdfToolsDownloadBaseURL + 'latest.json';
var xmlhttp = yield Zotero.HTTP.request("GET", url, { responseType: "json" });
var json = xmlhttp.response;
var platform = Zotero.platform.replace(/\s/g, '-');
var version = json[platform] || json['default'];
Zotero.debug("Latest PDF tools version for " + platform + " is " + version);
return version;
});
/*
* Download and install latest PDF tool
*/
this.downloadPDFTool = Zotero.Promise.coroutine(function* (tool, version) {
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
if (tool == 'converter') {
var fileName = this.pdfConverterFileName;
}
else {
var fileName = this.pdfInfoFileName;
}
var spec = this.pdfToolsDownloadBaseURL + version + "/" + fileName;
var uri = ioService.newURI(spec, null, null);
var tmpFile = OS.Path.join(Zotero.getTempDirectory().path, fileName);
yield Zotero.File.download(uri, tmpFile);
var fileInfo = yield OS.File.stat(tmpFile);
// Delete if too small, since a 404 might not be detected above
if (fileInfo.size < 50000) {
let msg = tmpFile + " is too small -- deleting";
Zotero.logError(msg);
try {
yield OS.File.remove(tmpFile);
}
catch (e) {
Zotero.logError(e);
}
throw new Error(msg);
}
var scriptExt = _getScriptExtension();
// On Windows, write out script to hide pdftotext console window
// TEMP: disabled
if (false && tool == 'converter') {
if (Zotero.isWin) {
let content = yield Zotero.File.getContentsFromURLAsync(
'resource://zotero/hide.' + scriptExt
);
var tmpScriptFile = OS.Path.join(
Zotero.getTempDirectory().path,
'pdftotext.' + scriptExt
);
yield Zotero.File.putContentsAsync(tmpScriptFile, content);
}
}
// Write out output redirection script for pdfinfo
// TEMP: disabled on Windows
else if (!Zotero.isWin && tool == 'info') {
let content = yield Zotero.File.getContentsFromURLAsync(
'resource://zotero/redirect.' + scriptExt
);
var tmpScriptFile = OS.Path.join(
Zotero.getTempDirectory().path,
'pdfinfo.' + scriptExt
);
yield Zotero.File.putContentsAsync(tmpScriptFile, content);
}
// Set permissions to 755
if (Zotero.isMac || Zotero.isLinux) {
yield OS.File.setPermissions(tmpFile, {
unixMode: 0o755
});
if (tmpScriptFile) {
yield OS.File.setPermissions(tmpScriptFile, {
unixMode: 0o755
});
}
}
var destDir = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
// Move redirect script and executable into data dir
if (tmpScriptFile) {
yield OS.File.move(
tmpScriptFile,
OS.Path.join(destDir.path, OS.Path.basename(tmpScriptFile))
);
}
yield OS.File.move(tmpFile, OS.Path.join(destDir.path, fileName));
// Write the version number to a file
var versionFile = destDir.clone();
versionFile.append(fileName + '.version');
// TEMP
if (Zotero.isWin) {
version = '3.02a';
}
yield Zotero.File.putContentsAsync(versionFile, version + '');
yield Zotero.Fulltext.registerPDFTool(tool);
});
/*
* Looks for pdftotext-{platform}[.exe] in the Zotero data directory
*
* {platform} is navigator.platform, with spaces replaced by hyphens
* e.g. "Win32", "Linux-i686", "MacPPC", "MacIntel", etc.
*/
this.registerPDFTool = Zotero.Promise.coroutine(function* (tool) {
var errMsg = false;
var exec = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
switch (tool) {
case 'converter':
var toolName = this.pdfConverterName;
var fileName = _pdfConverterFileName;
break;
case 'info':
var toolName = this.pdfInfoName;
var fileName = _pdfInfoFileName;
break;
default:
throw ("Invalid PDF tool type '" + tool + "' in Zotero.Fulltext.registerPDFTool()");
}
exec.append(fileName);
if (!exec.exists()) {
exec = null;
errMsg = fileName + ' not found';
}
if (!exec) {
if (tool == 'converter') {
Zotero.debug(errMsg + ' -- PDF indexing disabled');
}
return false;
}
var versionFile = exec.parent;
versionFile.append(fileName + '.version');
if (versionFile.exists()) {
try {
var version = (yield Zotero.File.getSample(versionFile)).split(/[\r\n\s]/)[0];
}
catch (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
}
}
if (!version) {
var version = 'UNKNOWN';
}
// If scripts exist, use those instead
switch (tool) {
case 'converter':
// TEMP: disabled
if (false && Zotero.isWin) {
var script = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
script.append('pdftotext.' + _getScriptExtension())
if (script.exists()) {
Zotero.debug(script.leafName + " registered");
_pdfConverterScript = script;
}
}
break;
case 'info':
// Modified 3.02 version doesn't use redirection script
if (version.startsWith('3.02')) break;
var script = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
// TEMP: disabled on Win
if (!Zotero.isWin) {
script.append('pdfinfo.' + _getScriptExtension())
// The redirection script is necessary to run pdfinfo
if (!script.exists()) {
Zotero.debug(script.leafName + " not found -- PDF statistics disabled");
return false;
}
Zotero.debug(toolName + " redirection script registered");
_pdfInfoScript = script;
}
break;
}
switch (tool) {
case 'converter':
_pdfConverter = exec;
_pdfConverterVersion = version;
break;
case 'info':
_pdfInfo = exec;
_pdfInfoVersion = version;
break;
}
Zotero.debug(toolName + ' version ' + version + ' registered');
return true;
});
/**
* Unregister and delete PDF tools
*
* Used only for tests
*/
this.uninstallPDFTools = Zotero.Promise.coroutine(function* () {
Zotero.debug("Uninstalling PDF tools");
if (_pdfConverter) {
yield Zotero.File.removeIfExists(_pdfConverter.path);
yield Zotero.File.removeIfExists(_pdfConverter.path + ".version");
}
if (_pdfInfo) {
yield Zotero.File.removeIfExists(_pdfInfo.path);
yield Zotero.File.removeIfExists(_pdfInfo.path + ".version");
}
if (_pdfConverterScript) yield Zotero.File.removeIfExists(_pdfConverterScript.path);
if (_pdfInfoScript) yield Zotero.File.removeIfExists(_pdfInfoScript.path);
_pdfConverter = null;
_pdfInfo = null;
_pdfInfoScript = null;
});
function pdfConverterIsRegistered() {
return !!_pdfConverter;
}
function pdfInfoIsRegistered() {
return !!_pdfInfo;
}
this.getPDFConverterExecAndArgs = function () {
if (!this.pdfConverterIsRegistered()) {
throw new Error("PDF converter is not registered");
}
if (_pdfConverterScript) {
return {
exec: _pdfConverterScript,
args: [_pdfConverter.path]
}
}
return {
exec: _pdfConverter,
args: []
}
args: ['-datadir', _pdfData]
}
};
/*
@ -515,6 +261,10 @@ Zotero.Fulltext = Zotero.FullText = new function(){
var indexString = Zotero.Promise.coroutine(function* (text, charset, itemID, stats, version, synced) {
var words = this.semanticSplitter(text, charset);
while (Zotero.DB.inTransaction()) {
yield Zotero.DB.waitForTransaction('indexString()');
}
yield Zotero.DB.executeTransaction(function* () {
this.clearItemWords(itemID, true);
yield indexWords(itemID, words, stats, version, synced);
@ -674,11 +424,6 @@ Zotero.Fulltext = Zotero.FullText = new function(){
* @return {Promise}
*/
this.indexPDF = Zotero.Promise.coroutine(function* (filePath, itemID, allPages) {
if (!_pdfConverter) {
Zotero.debug("PDF tools are not installed -- skipping indexing");
return false;
}
var maxPages = Zotero.Prefs.get('fulltext.pdfMaxPages');
if (maxPages == 0) {
return false;
@ -697,38 +442,21 @@ Zotero.Fulltext = Zotero.FullText = new function(){
var infoFilePath = OS.Path.join(parentDirPath, this.pdfInfoCacheFile);
var cacheFilePath = OS.Path.join(parentDirPath, this.pdfConverterCacheFile);
// Modified 3.02 version that can output a text file directly
if (_pdfInfo && _pdfInfoVersion.startsWith('3.02')) {
let args = [filePath, infoFilePath];
var args = [filePath, infoFilePath];
try {
yield Zotero.Utilities.Internal.exec(_pdfInfo, args);
var totalPages = yield getTotalPagesFromFile(itemID);
}
catch (e) {
Zotero.debug("Error running pdfinfo");
Zotero.debug("Error running " + _pdfInfo.path, 1);
Zotero.logError(e);
}
}
// Use redirection script
else if (_pdfInfoScript) {
let args = [_pdfInfo.path, filePath, infoFilePath];
try {
yield Zotero.Utilities.Internal.exec(_pdfInfoScript, args);
var totalPages = yield getTotalPagesFromFile(itemID);
}
catch (e) {
Components.utils.reportError(e);
Zotero.debug("Error running pdfinfo", 1);
Zotero.debug(e, 1);
}
}
else {
Zotero.debug(this.pdfInfoName + " is not available");
}
var {exec, args} = this.getPDFConverterExecAndArgs();
args.push('-enc', 'UTF-8', '-nopgbrk');
args.push('-nopgbrk');
if (allPages) {
if (totalPages) {
@ -745,9 +473,8 @@ Zotero.Fulltext = Zotero.FullText = new function(){
yield Zotero.Utilities.Internal.exec(exec, args);
}
catch (e) {
Components.utils.reportError(e);
Zotero.debug("Error running pdftotext", 1);
Zotero.debug(e, 1);
Zotero.debug("Error running " + exec.path, 1);
Zotero.logError(e);
return false;
}
@ -800,22 +527,61 @@ Zotero.Fulltext = Zotero.FullText = new function(){
continue;
}
if (ignoreErrors) {
try {
yield indexFile(path, item.attachmentContentType, item.attachmentCharset, itemID, complete);
}
catch (e) {
if (ignoreErrors) {
Components.utils.reportError("Error indexing " + path);
Zotero.logError(e);
}
}
else {
yield indexFile(path, item.attachmentContentType, item.attachmentCharset, itemID, complete);
throw e;
}
}
}
});
// TEMP: Temporary mechanism to serialize indexing of new attachments
//
// This should instead save the itemID to a table that's read by the content processor
var _queue = [];
var _indexing = false;
var _nextIndexTime;
var _indexDelay = 5000;
var _indexInterval = 500;
this.queueItem = function (item) {
// Don't index files in the background during tests
if (Zotero.test) return;
_queue.push(item.id);
_nextIndexTime = Date.now() + _indexDelay;
setTimeout(() => {
_processNextItem()
}, _indexDelay);
};
async function _processNextItem() {
if (!_queue.length) return;
// Another _processNextItem() was scheduled
if (Date.now() < _nextIndexTime) return;
// If indexing is already running, _processNextItem() will be called when it's done
if (_indexing) return;
_indexing = true;
var itemID = _queue.shift();
try {
await Zotero.Fulltext.indexItems([itemID], false, true);
}
finally {
_indexing = false;
}
setTimeout(() => {
_processNextItem();
}, _indexInterval);
};
//
// Full-text content syncing
//
@ -1054,6 +820,8 @@ Zotero.Fulltext = Zotero.FullText = new function(){
* Start the idle observer for the background content processor
*/
this.registerContentProcessor = function () {
// Don't start idle observer during tests
if (Zotero.test) return;
if (!Zotero.Prefs.get('sync.fulltext.enabled')) return;
if (!_idleObserverIsRegistered) {
@ -1624,7 +1392,12 @@ Zotero.Fulltext = Zotero.FullText = new function(){
* Item must be a non-web-link attachment that isn't already fully indexed
*/
this.canReindex = Zotero.Promise.coroutine(function* (item) {
if (item.isAttachment() && item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
if (item.isAttachment()
&& item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
let contentType = item.attachmentContentType;
if (!contentType || contentType != 'application/pdf' && !Zotero.MIME.isTextType(contentType)) {
return false;
}
switch (yield this.getIndexedState(item)) {
case this.INDEX_STATE_UNAVAILABLE:
case this.INDEX_STATE_UNINDEXED:

View File

@ -12,6 +12,8 @@ Zotero.HTTP = new function() {
this.UnexpectedStatusException = function(xmlhttp, msg) {
this.xmlhttp = xmlhttp;
this.status = xmlhttp.status;
this.channelStatus = null;
this.responseStatus = null;
this.channel = xmlhttp.channel;
this.message = msg;
this.stack = new Error().stack;
@ -33,6 +35,24 @@ Zotero.HTTP = new function() {
catch (e) {
Zotero.debug(e, 1);
}
// If the connection failed, try to find out what really happened
if (!this.status) {
try {
if (xmlhttp.channel.status) {
this.channelStatus = xmlhttp.channel.status;
Zotero.debug("Channel status was " + this.channelStatus, 2);
}
}
catch (e) {}
try {
if (xmlhttp.channel.responseStatus) {
this.responseStatus = xmlhttp.channel.responseStatus;
Zotero.debug("Response status was " + this.responseStatus, 2);
}
}
catch (e) {}
}
};
this.UnexpectedStatusException.prototype = Object.create(Error.prototype);
this.UnexpectedStatusException.prototype.is4xx = function () {
@ -248,6 +268,18 @@ Zotero.HTTP = new function() {
xmlhttp.onloadend = function() {
var status = xmlhttp.status;
// If an invalid HTTP response (e.g., NS_ERROR_INVALID_CONTENT_ENCODING) includes a
// 4xx or 5xx HTTP response code, swap it in, since it might be enough info to do
// what we need (e.g., verify a 404 from a WebDAV server)
try {
if (!status && xmlhttp.channel.responseStatus >= 400) {
Zotero.warn(`Overriding status for invalid response for ${dispURL} `
+ `(${xmlhttp.channel.status})`);
status = xmlhttp.channel.responseStatus;
}
}
catch (e) {}
if (options.successCodes) {
var success = options.successCodes.indexOf(status) != -1;
}
@ -264,13 +296,13 @@ Zotero.HTTP = new function() {
if(success) {
Zotero.debug("HTTP " + method + " " + dispURL
+ " succeeded with " + xmlhttp.status);
+ " succeeded with " + status);
if (options.debug) {
Zotero.debug(xmlhttp.responseText);
}
deferred.resolve(xmlhttp);
} else {
let msg = "HTTP " + method + " " + dispURL + " failed with status code " + xmlhttp.status;
let msg = "HTTP " + method + " " + dispURL + " failed with status code " + status;
if (!xmlhttp.responseType && xmlhttp.responseText) {
msg += ":\n\n" + xmlhttp.responseText;
}
@ -1024,6 +1056,9 @@ Zotero.HTTP = new function() {
}
let secInfo = channel.securityInfo;
let msg;
let dialogButtonText;
let dialogButtonCallback;
if (secInfo instanceof Ci.nsITransportSecurityInfo) {
secInfo.QueryInterface(Ci.nsITransportSecurityInfo);
if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
@ -1052,6 +1087,7 @@ Zotero.HTTP = new function() {
== Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
msg = Zotero.getString('sync.error.sslConnectionError');
}
if (msg) {
throw new Zotero.HTTP.SecurityException(
msg,
{
@ -1061,6 +1097,7 @@ Zotero.HTTP = new function() {
);
}
}
}
/**
* Mimics the window.location/document.location interface, given an nsIURL

File diff suppressed because it is too large Load Diff

View File

@ -67,22 +67,18 @@ Zotero.ItemTreeView.prototype.regularOnly = false;
Zotero.ItemTreeView.prototype.expandAll = false;
Zotero.ItemTreeView.prototype.collapseAll = false;
Object.defineProperty(Zotero.ItemTreeView.prototype, 'window', {
get: function () {
return this._ownerDocument.defaultView;
},
enumerable: true
});
/**
* Called by the tree itself
*/
Zotero.ItemTreeView.prototype.setTree = async function (treebox) {
try {
Zotero.debug("Setting tree for " + this.collectionTreeRow.id + " items view " + this.id);
var start = Date.now();
// Try to set the window document if not yet set
if (treebox && !this._ownerDocument) {
try {
this._ownerDocument = treebox.treeBody.ownerDocument;
}
catch (e) {}
}
if (this._treebox) {
if (this._needsSort) {
this.sort();
@ -90,28 +86,39 @@ Zotero.ItemTreeView.prototype.setTree = async function (treebox) {
return;
}
var start = Date.now();
Zotero.debug("Setting tree for " + this.collectionTreeRow.id + " items view " + this.id);
if (!treebox) {
Zotero.debug("Treebox not passed in setTree()", 2);
return;
}
this._treebox = treebox;
if (!this._ownerDocument) {
try {
this._ownerDocument = treebox.treeBody.ownerDocument;
}
catch (e) {}
if (!this._ownerDocument) {
Zotero.debug("No owner document in setTree()", 2);
return;
}
}
this._treebox = treebox;
this.setSortColumn();
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
if (this.window.ZoteroPane) {
this.window.ZoteroPane.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
}
if (Zotero.locked) {
Zotero.debug("Zotero is locked -- not loading items tree", 2);
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
if (this.window.ZoteroPane) {
this.window.ZoteroPane.clearItemsPaneMessage();
}
return;
}
@ -465,7 +472,7 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
// Clear My Publications intro text on a refresh with items
if (this.collectionTreeRow.isPublications() && this.rowCount) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
this.window.ZoteroPane.clearItemsPaneMessage();
}
yield this.runListeners('refresh');
@ -713,7 +720,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
}
}
else if (collectionTreeRow.isFeed()) {
this._ownerDocument.defaultView.ZoteroPane.updateReadLabel();
this.window.ZoteroPane.updateReadLabel();
}
// If not a search, process modifications manually
else {
@ -1682,7 +1689,7 @@ Zotero.ItemTreeView.prototype.sort = function (itemIDs) {
* Show intro text in middle pane for some views when no items
*/
Zotero.ItemTreeView.prototype._updateIntroText = function() {
if (!this._ownerDocument.defaultView.ZoteroPane) {
if (!this.window.ZoteroPane) {
return;
}
@ -1781,7 +1788,7 @@ Zotero.ItemTreeView.prototype._updateIntroText = function() {
}
if (this._introText || this._introText === null) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
this.window.ZoteroPane.clearItemsPaneMessage();
this._introText = false;
}
};
@ -1841,9 +1848,9 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
// No parent -- it's not here
// Clear the quick search and tag selection and try again (once)
if (!noRecurse && this._ownerDocument.defaultView.ZoteroPane_Local) {
let cleared1 = yield this._ownerDocument.defaultView.ZoteroPane_Local.clearQuicksearch();
let cleared2 = this._ownerDocument.defaultView.ZoteroPane_Local.clearTagSelection();
if (!noRecurse && this.window.ZoteroPane) {
let cleared1 = yield this.window.ZoteroPane.clearQuicksearch();
let cleared2 = this.window.ZoteroPane.clearTagSelection();
if (cleared1 || cleared2) {
return this.selectItem(id, expand, true);
}
@ -3211,9 +3218,7 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
// Disallow drop into read-only libraries
if (!collectionTreeRow.editable) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotEditLibraryMessage();
return;
}
@ -3231,34 +3236,29 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
var parentCollectionID = collectionTreeRow.ref.id;
}
let addedItems = [];
var notifierQueue = new Zotero.Notifier.Queue;
try {
// If there's a single file being added to a parent, automatic renaming is enabled,
// and there are no other non-HTML attachments, we'll rename the file as long as it's
// an allowed type. The dragged data could be a URL, so we don't yet know the file type.
// This should be kept in sync with ZoteroPane.addAttachmentFromDialog().
let renameIfAllowedType = false;
let parentItem;
let numExistingFileAttachments;
if (parentItemID) {
if (parentItemID && data.length == 1 && Zotero.Prefs.get('autoRenameFiles')) {
parentItem = Zotero.Items.get(parentItemID);
numExistingFileAttachments = parentItem.getAttachments()
.map(itemID => Zotero.Items.get(itemID))
.filter(item => item.isFileAttachment())
.length;
if (!parentItem.numNonHTMLFileAttachments()) {
renameIfAllowedType = true;
}
}
for (var i=0; i<data.length; i++) {
var file = data[i];
let fileBaseName;
// If only one item is being dragged and it's the only attachment, run
// "Rename File from Parent Metadata" automatically
if (data.length == 1 && parentItem && !numExistingFileAttachments) {
fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
}
if (dataType == 'text/x-moz-url') {
var url = data[i];
if (url.indexOf('file:///') == 0) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
let win = Services.wm.getMostRecentWindow("navigator:browser");
// If dragging currently loaded page, only convert to
// file if not an HTML document
if (win.content.location.href != url ||
@ -3276,18 +3276,17 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
// Still string, so remote URL
if (typeof file == 'string') {
let item;
if (parentItemID) {
if (!collectionTreeRow.filesEditable) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotEditLibraryFilesMessage();
return;
}
yield Zotero.Attachments.importFromURL({
item = yield Zotero.Attachments.importFromURL({
libraryID: targetLibraryID,
url,
fileBaseName,
renameIfAllowedType,
parentItemID,
saveOptions: {
notifierQueue
@ -3295,10 +3294,11 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
});
}
else {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack'); // TODO: don't do this
let win = Services.wm.getMostRecentWindow("navigator:browser");
item = yield win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack'); // TODO: don't do this
}
if (item) {
addedItems.push(item);
}
continue;
}
@ -3306,8 +3306,38 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
// Otherwise file, so fall through
}
file = file.path;
// Rename file if it's an allowed type
let fileBaseName = false;
if (renameIfAllowedType) {
fileBaseName = yield Zotero.Attachments.getRenamedFileBaseNameIfAllowedType(
parentItem, file
);
}
let item;
if (dropEffect == 'link') {
yield Zotero.Attachments.linkFromFile({
// Rename linked file, with unique suffix if necessary
try {
if (fileBaseName) {
let ext = Zotero.File.getExtension(file);
let newName = yield Zotero.File.rename(
file,
fileBaseName + (ext ? '.' + ext : ''),
{
unique: true
}
);
// Update path in case the name was changed to be unique
file = OS.Path.join(OS.Path.dirname(file), newName);
}
}
catch (e) {
Zotero.logError(e);
}
item = yield Zotero.Attachments.linkFromFile({
file,
parentItemID,
collections: parentCollectionID ? [parentCollectionID] : undefined,
@ -3317,15 +3347,13 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
});
}
else {
if (file.leafName.endsWith(".lnk")) {
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
let win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotAddShortcutMessage(file.path);
if (file.endsWith(".lnk")) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotAddShortcutMessage(file);
continue;
}
yield Zotero.Attachments.importFromFile({
item = yield Zotero.Attachments.importFromFile({
file,
fileBaseName,
libraryID: targetLibraryID,
@ -3338,18 +3366,27 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
// If moving, delete original file
if (dragData.dropEffect == 'move') {
try {
file.remove(false);
yield OS.File.remove(file);
}
catch (e) {
Components.utils.reportError("Error deleting original file " + file.path + " after drag");
Zotero.logError("Error deleting original file " + file + " after drag");
}
}
}
if (item) {
addedItems.push(item);
}
}
}
finally {
yield Zotero.Notifier.commit(notifierQueue);
}
// Automatically retrieve metadata for PDFs
if (!parentItemID) {
Zotero.RecognizePDF.autoRecognizeItems(addedItems);
}
}
});

View File

@ -26,7 +26,6 @@
Zotero.MIME = new function(){
this.isTextType = isTextType;
this.getPrimaryExtension = getPrimaryExtension;
this.sniffForMIMEType = sniffForMIMEType;
this.sniffForBinary = sniffForBinary;
this.hasNativeHandler = hasNativeHandler;
this.hasInternalHandler = hasInternalHandler;
@ -48,8 +47,9 @@ Zotero.MIME = new function(){
["\uFFFDPNG", 'image/png', 0],
["JFIF", 'image/jpeg'],
["FLV", "video/x-flv", 0],
["\u0000\u0000\u0001\u0000", "image/vnd.microsoft.icon", 0]
["\u0000\u0000\u0001\u0000", "image/vnd.microsoft.icon", 0],
["\u0053\u0051\u004C\u0069\u0074\u0065\u0020\u0066"
+ "\u006F\u0072\u006D\u0061\u0074\u0020\u0033\u0000", "application/x-sqlite3", 0]
];
var _extensions = {
@ -103,10 +103,6 @@ Zotero.MIME = new function(){
var _nativeMIMETypes = {
'text/html': true,
'text/css': true,
'image/jpeg': true,
'image/gif': true,
'image/png': true,
'image/svg+xml': true,
'text/xml': true,
'application/xhtml+xml': true,
'application/xml': true,
@ -232,12 +228,12 @@ Zotero.MIME = new function(){
/*
* Searches string for magic numbers
*/
function sniffForMIMEType(str){
for (var i in _snifferEntries){
var match = false;
this.sniffForMIMEType = function (str) {
for (let i in _snifferEntries) {
let match = false;
// If an offset is defined, match only from there
if (typeof _snifferEntries[i][2] != 'undefined') {
if (str.substr(i[2]).indexOf(_snifferEntries[i][0]) == 0) {
if (_snifferEntries[i][2] != undefined) {
if (str.substr(_snifferEntries[i][2]).indexOf(_snifferEntries[i][0]) == 0) {
match = true;
}
}
@ -278,7 +274,7 @@ Zotero.MIME = new function(){
* ext is an optional file extension hint if data sniffing is unsuccessful
*/
this.getMIMETypeFromData = function (str, ext){
var mimeType = sniffForMIMEType(str);
var mimeType = this.sniffForMIMEType(str);
if (mimeType){
Zotero.debug('Detected MIME type ' + mimeType);
return mimeType;

View File

@ -32,7 +32,7 @@ Zotero.Notifier = new function(){
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash',
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key'
];
var _inTransaction;
var _transactionID = false;
var _queue = {};
@ -106,7 +106,7 @@ Zotero.Notifier = new function(){
* - New events and types should be added to the order arrays in commit()
**/
this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) {
if (_inTransaction && !force) {
if (_transactionID && !force) {
return this.queue(event, type, ids, extraData);
}
@ -173,7 +173,7 @@ Zotero.Notifier = new function(){
queue = queue._queue;
}
else {
if (!_inTransaction) {
if (!_transactionID) {
throw new Error("Can't queue event outside of a transaction");
}
queue = _queue;
@ -278,11 +278,11 @@ Zotero.Notifier = new function(){
*
* Note: Be sure the matching commit() gets called (e.g. in a finally{...} block) or
* notifications will break until Firefox is restarted or commit(true)/reset() is called manually
*
* @param {String} [transactionID]
*/
this.begin = function () {
if (!_inTransaction) {
_inTransaction = true;
}
this.begin = function (transactionID = true) {
_transactionID = transactionID;
}
@ -291,8 +291,9 @@ Zotero.Notifier = new function(){
*
* @param {Zotero.Notifier.Queue|Zotero.Notifier.Queue[]} [queues] - One or more queues to use
* instead of the internal queue
* @param {String} [transactionID]
*/
this.commit = Zotero.Promise.coroutine(function* (queues) {
this.commit = Zotero.Promise.coroutine(function* (queues, transactionID = true) {
if (queues) {
if (!Array.isArray(queues)) {
queues = [queues];
@ -308,7 +309,7 @@ Zotero.Notifier = new function(){
}
}
}
else if (!_inTransaction) {
else if (!_transactionID) {
throw new Error("Can't commit outside of transaction");
}
else {
@ -375,7 +376,7 @@ Zotero.Notifier = new function(){
}
if (!queues) {
this.reset();
this.reset(transactionID);
}
if (totals) {
@ -407,10 +408,13 @@ Zotero.Notifier = new function(){
/*
* Reset the event queue
*/
this.reset = function () {
this.reset = function (transactionID = true) {
if (transactionID != _transactionID) {
return;
}
//Zotero.debug("Resetting notifier event queue");
_queue = {};
_inTransaction = false;
_transactionID = false;
}
}

View File

@ -0,0 +1,287 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
https://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 *****
*/
Zotero.OpenPDF = {
openToPage: async function (path, page) {
var handler = Zotero.Prefs.get("fileHandler.pdf");
var opened = false;
if (Zotero.isMac) {
if (handler.includes('Preview')) {
this._openWithPreview(path, page);
}
else if (handler.includes('Skim')) {
this._openWithSkim(path, page);
}
else if (handler.includes('PDF Expert')) {
this._openWithPDFExpert(path, page);
}
else {
// Try to detect default app
handler = this._getPDFHandlerName();
Zotero.debug(`Handler is ${handler}`);
if (handler && handler == 'Skim') {
this._openWithSkim(path, page);
}
else if (handler && handler == 'PDF Expert') {
this._openWithPDFExpert(path, page);
}
// Fall back to Preview
else {
this._openWithPreview(path, page);
}
}
opened = true;
}
else if (Zotero.isWin) {
handler = handler || this._getPDFHandlerWindows();
// Include flags to open the PDF on a given page in various apps
//
// Adobe Acrobat: http://partners.adobe.com/public/developer/en/acrobat/PDFOpenParameters.pdf
// PDF-XChange: http://help.tracker-software.com/eu/default.aspx?pageid=PDFXView25:command_line_options
let args = ['/A', 'page=' + page, path];
Zotero.Utilities.Internal.exec(handler, args);
opened = true;
}
else if (Zotero.isLinux) {
if (handler.includes('evince') || handler.includes('okular')) {
this._openWithEvinceOrOkular(handler, path, page);
opened = true;
}
else {
let handler = await this._getPDFHandlerLinux();
if (handler.includes('evince') || handler.includes('okular')) {
this._openWithEvinceOrOkular(handler, path, page);
opened = true;
}
// Fall back to okular and then evince if unknown handler
else if (await OS.File.exists('/usr/bin/okular')) {
this._openWithEvinceOrOkular('/usr/bin/okular', path, page);
opened = true;
}
else if (await OS.File.exists('/usr/bin/evince')) {
this._openWithEvinceOrOkular('/usr/bin/evince', path, page);
opened = true;
}
else {
Zotero.debug("No handler found");
}
}
}
return opened;
},
_getPDFHandlerName: function () {
var handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
.getService(Ci.nsIHandlerService);
var handlers = handlerService.enumerate();
var handler;
while (handlers.hasMoreElements()) {
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
if (handlerInfo.type == 'application/pdf') {
handler = handlerInfo;
break;
}
}
if (!handler) {
// We can't get the name of the system default handler unless we add an entry
Zotero.debug("Default handler not found -- adding default entry");
let mimeService = Components.classes["@mozilla.org/mime;1"]
.getService(Components.interfaces.nsIMIMEService);
let mimeInfo = mimeService.getFromTypeAndExtension("application/pdf", "");
mimeInfo.preferredAction = 4;
mimeInfo.alwaysAskBeforeHandling = false;
handlerService.store(mimeInfo);
// And once we do that, we can get the name (but not the path, unfortunately)
let handlers = handlerService.enumerate();
while (handlers.hasMoreElements()) {
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
if (handlerInfo.type == 'application/pdf') {
handler = handlerInfo;
break;
}
}
}
if (handler) {
Zotero.debug(`Default handler is ${handler.defaultDescription}`);
return handler.defaultDescription;
}
return false;
},
//
// Mac
//
_openWithPreview: async function (filePath, page) {
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', 'Preview', filePath]);
// Go to page using AppleScript
let args = [
'-e', 'tell app "Preview" to activate',
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
'-e', `tell app "System Events" to keystroke "${page}"`,
'-e', 'tell app "System Events" to keystroke return'
];
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
},
_openWithSkim: async function (filePath, page) {
// Escape double-quotes in path
var quoteRE = /"/g;
filePath = filePath.replace(quoteRE, '\\"');
let filename = OS.Path.basename(filePath).replace(quoteRE, '\\"');
let args = [
'-e', 'tell app "Skim" to activate',
'-e', `tell app "Skim" to open "${filePath}"`
];
args.push('-e', `tell document "${filename}" of application "Skim" to go to page ${page}`);
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
},
_openWithPDFExpert: async function (filePath, page) {
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', 'PDF Expert', filePath]);
// Go to page using AppleScript (same as Preview)
let args = [
'-e', 'tell app "PDF Expert" to activate',
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
'-e', `tell app "System Events" to keystroke "${page}"`,
'-e', 'tell app "System Events" to keystroke return'
];
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
},
//
// Windows
//
/**
* Get path to default pdf reader application on windows
* @return {string} Path to default pdf reader application
*
* From getPDFReader() in ZotFile (GPL)
* https://github.com/jlegewie/zotfile/blob/master/chrome/content/zotfile/utils.js
*/
_getPDFHandlerWindows: function () {
var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
.createInstance(Components.interfaces.nsIWindowsRegKey);
// Get handler for PDFs
var tryKeys = [
{
root: wrk.ROOT_KEY_CURRENT_USER,
path: 'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.pdf\\UserChoice',
value: 'Progid'
},
{
root: wrk.ROOT_KEY_CLASSES_ROOT,
path: '.pdf',
value: ''
}
];
var progId;
for (let i = 0; !progId && i < tryKeys.length; i++) {
try {
wrk.open(
tryKeys[i].root,
tryKeys[i].path,
wrk.ACCESS_READ
);
progId = wrk.readStringValue(tryKeys[i].value);
}
catch (e) {}
}
if (!progId) {
wrk.close();
return;
}
// Get version specific handler, if it exists
try {
wrk.open(
wrk.ROOT_KEY_CLASSES_ROOT,
progId + '\\CurVer',
wrk.ACCESS_READ
);
progId = wrk.readStringValue('') || progId;
}
catch (e) {}
// Get command
var success = false;
tryKeys = [
progId + '\\shell\\Read\\command',
progId + '\\shell\\Open\\command'
];
for (let i = 0; !success && i < tryKeys.length; i++) {
try {
wrk.open(
wrk.ROOT_KEY_CLASSES_ROOT,
tryKeys[i],
wrk.ACCESS_READ
);
success = true;
}
catch (e) {}
}
if (!success) {
wrk.close();
return;
}
var command = wrk.readStringValue('').match(/^(?:".+?"|[^"]\S+)/);
wrk.close();
if (!command) return;
return command[0].replace(/"/g, '');
},
//
// Linux
//
_getPDFHandlerLinux: async function () {
var name = this._getPDFHandlerName();
switch (name.toLowerCase()) {
case 'okular':
return `/usr/bin/${name}`;
// It's "Document Viewer" on stock Ubuntu
case 'document viewer':
case 'evince':
return `/usr/bin/evince`;
}
// TODO: Try to get default from mimeapps.list, etc., in case system default is okular
// or evince somewhere other than /usr/bin
var homeDir = OS.Constants.Path.homeDir;
return false;
},
_openWithEvinceOrOkular: function (appPath, filePath, page) {
var args = ['-p', page, filePath];
Zotero.Utilities.Internal.exec(appPath, args);
}
}

View File

@ -129,10 +129,12 @@ Zotero.Profile = {
/**
* Find other profile directories (for this app or the other app) using the given data directory
*
* @param {String} dataDir
* @param {Boolean} [includeOtherApps=false] - Check Firefox profiles
* @return {String[]}
*/
findOtherProfilesUsingDataDirectory: Zotero.Promise.coroutine(function* (dataDir) {
let otherAppProfiles = yield this._findOtherAppProfiles();
findOtherProfilesUsingDataDirectory: Zotero.Promise.coroutine(function* (dataDir, includeOtherApps = true) {
let otherAppProfiles = includeOtherApps ? (yield this._findOtherAppProfiles()) : [];
let otherProfiles = (yield this._findOtherProfiles()).concat(otherAppProfiles);
// First get profiles pointing at this directory
@ -238,6 +240,38 @@ Zotero.Profile = {
},
readPrefsFromFile: async function (prefsFile) {
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
Components.utils.evalInSandbox(
"var prefs = {};"+
"function user_pref(key, val) {"+
"prefs[key] = val;"+
"}"
, sandbox);
(await Zotero.File.getContentsAsync(prefsFile))
.split(/\n/)
.filter((line) => {
// Strip comments
return !line.startsWith('#')
// Only process lines in our pref branch
&& line.includes(ZOTERO_CONFIG.PREF_BRANCH);
})
// Process each line individually
.forEach((line) => {
try {
Zotero.debug("Processing " + line);
Components.utils.evalInSandbox(line, sandbox);
}
catch (e) {
Zotero.logError("Error processing prefs line: " + line);
}
});
return sandbox.prefs;
},
//
// Private methods
//

View File

@ -154,6 +154,9 @@ Zotero.ProgressWindow = function(options = {}) {
_progressWindow.addEventListener("mouseover", _onMouseOver, false);
_progressWindow.addEventListener("mouseout", _onMouseOut, false);
_progressWindow.addEventListener("mouseup", _onMouseUp, false);
_window.addEventListener('close', () => {
this.close();
});
_windowLoading = true;
@ -281,7 +284,10 @@ Zotero.ProgressWindow = function(options = {}) {
try {
_progressWindow.close();
} catch(ex) {}
}
catch (e) {
Zotero.logError(e);
}
}
/**

View File

@ -321,7 +321,11 @@ Zotero.Proxy.prototype.compileRegexp = function() {
})
// now replace with regexp fragment in reverse order
var re = "^"+Zotero.Utilities.quotemeta(this.scheme)+"$";
if (this.scheme.includes('://')) {
re = "^"+Zotero.Utilities.quotemeta(this.scheme)+"$";
} else {
re = "^https?"+Zotero.Utilities.quotemeta('://'+this.scheme)+"$";
}
for(var i=this.parameters.length-1; i>=0; i--) {
var param = this.parameters[i];
re = re.replace(Zotero_Proxy_schemeParameterRegexps[param], "$1"+parametersToCheck[param]);

View File

@ -0,0 +1,681 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, 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 *****
*/
Zotero.RecognizePDF = new function () {
const OFFLINE_RECHECK_DELAY = 60 * 1000;
const MAX_PAGES = 5;
const UNRECOGNIZE_TIMEOUT = 86400 * 1000;
this.ROW_QUEUED = 1;
this.ROW_PROCESSING = 2;
this.ROW_FAILED = 3;
this.ROW_SUCCEEDED = 4;
let _newItems = new WeakMap();
let _listeners = {};
let _rows = [];
let _queue = [];
let _queueProcessing = false;
/**
* Add listener
* @param name Event name
* @param callback
*/
this.addListener = function (name, callback) {
_listeners[name] = callback;
};
/**
* Remove listener
* @param name Event name
*/
this.removeListener = function (name) {
delete _listeners[name];
};
/**
* Checks whether a given PDF could theoretically be recognized
* @param {Zotero.Item} item
* @return {Boolean} True if the PDF can be recognized, false if it cannot be
*/
this.canRecognize = function (item) {
return item.attachmentContentType
&& item.attachmentContentType === 'application/pdf'
&& item.isTopLevelItem();
};
/**
* Adds items to the queue and starts processing it
* @param items {Zotero.Item}
*/
this.recognizeItems = function (items) {
for (let item of items) {
_addItem(item);
}
_processQueue();
};
this.autoRecognizeItems = function (items) {
if (!Zotero.Prefs.get('autoRecognizeFiles')) return;
var pdfs = items.filter((item) => {
return item
&& item.isFileAttachment()
&& item.attachmentContentType == 'application/pdf';
});
if (!pdfs.length) {
return;
}
this.recognizeItems(pdfs);
var win = Services.wm.getMostRecentWindow("navigator:browser");
if (win) {
win.Zotero_RecognizePDF_Dialog.open();
}
};
/**
* Returns all rows
* @return {Array}
*/
this.getRows = function () {
return _rows;
};
/**
* Returns rows count
* @return {Number}
*/
this.getTotal = function () {
return _rows.length;
};
/**
* Returns processed rows count
* @return {Number}
*/
this.getProcessedTotal = function () {
return _rows.filter(row => row.status > Zotero.RecognizePDF.ROW_PROCESSING).length;
};
/**
* Stop processing items
*/
this.cancel = function () {
_queue = [];
_rows = [];
if (_listeners['empty']) {
_listeners['empty']();
}
};
this.canUnrecognize = function (item) {
var { dateModified } = _newItems.get(item) || {};
// Item must have been recognized recently, must not have been modified since it was
// created, and must have only one attachment and no other children
if (!dateModified
|| Zotero.Date.sqlToDate(dateModified, true) < new Date() - UNRECOGNIZE_TIMEOUT
|| item.dateModified != dateModified
|| item.numAttachments(true) != 1
|| item.numChildren(true) != 1) {
_newItems.delete(item);
return false;
}
// Child attachment must be not be in trash and must be a PDF
var attachments = Zotero.Items.get(item.getAttachments());
if (!attachments.length || attachments[0].attachmentContentType != 'application/pdf') {
_newItems.delete(item);
return false;
}
return true;
};
this.unrecognize = async function (item) {
var { originalTitle, originalFilename } = _newItems.get(item);
var attachment = Zotero.Items.get(item.getAttachments()[0]);
try {
let currentFilename = attachment.attachmentFilename;
if (currentFilename != originalFilename) {
let renamed = await attachment.renameAttachmentFile(originalFilename);
if (renamed) {
attachment.setField('title', originalTitle);
}
}
}
catch (e) {
Zotero.logError(e);
}
return Zotero.DB.executeTransaction(async function () {
let collections = item.getCollections();
attachment.parentItemID = null
attachment.setCollections(collections);
await attachment.save();
await item.erase();
}.bind(this));
};
this.report = async function (item, description) {
var attachment = Zotero.Items.get(item.getAttachments()[0]);
var filePath = attachment.getFilePath();
if (!filePath || !await OS.File.exists(filePath)) {
throw new Error("File not found when reporting metadata");
}
var version = Zotero.version;
var json = await extractJSON(filePath, MAX_PAGES);
var metadata = item.toJSON();
var data = { description, version, json, metadata };
var uri = ZOTERO_CONFIG.RECOGNIZE_URL + 'report';
return Zotero.HTTP.request(
"POST",
uri,
{
successCodes: [200, 204],
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}
);
};
/**
* Add item for processing
* @param item
* @return {null}
*/
function _addItem(item) {
for (let row of _rows) {
if (row.id === item.id) {
if (row.status > Zotero.RecognizePDF.ROW_PROCESSING) {
_deleteRow(row.id);
break;
}
return null;
}
}
let row = {
id: item.id,
status: Zotero.RecognizePDF.ROW_QUEUED,
fileName: item.getField('title'),
message: ''
};
_rows.unshift(row);
_queue.unshift(item.id);
if (_listeners['rowadded']) {
_listeners['rowadded'](row);
}
if (_listeners['nonempty'] && _rows.length === 1) {
_listeners['nonempty']();
}
}
/**
* Update row status and message
* @param itemID
* @param status
* @param message
*/
function _updateRow(itemID, status, message) {
for (let row of _rows) {
if (row.id === itemID) {
row.status = status;
row.message = message;
if (_listeners['rowupdated']) {
_listeners['rowupdated']({
id: row.id,
status,
message: message || ''
});
}
return;
}
}
}
/**
* Delete row
* @param itemID
*/
function _deleteRow(itemID) {
for (let i = 0; i < _rows.length; i++) {
let row = _rows[i];
if (row.id === itemID) {
_rows.splice(i, 1);
if (_listeners['rowdeleted']) {
_listeners['rowdeleted']({
id: row.id
});
}
return;
}
}
}
/**
* Triggers queue processing and returns when all items in the queue are processed
* @return {Promise}
*/
async function _processQueue() {
await Zotero.Schema.schemaUpdatePromise;
if (_queueProcessing) return;
_queueProcessing = true;
while (1) {
if (Zotero.HTTP.browserIsOffline()) {
await Zotero.Promise.delay(OFFLINE_RECHECK_DELAY);
continue;
}
let itemID = _queue.shift();
if (!itemID) break;
_updateRow(itemID, Zotero.RecognizePDF.ROW_PROCESSING, Zotero.getString('general.processing'));
try {
let newItem = await _processItem(itemID);
if (newItem) {
_updateRow(itemID, Zotero.RecognizePDF.ROW_SUCCEEDED, newItem.getField('title'));
}
else {
_updateRow(itemID, Zotero.RecognizePDF.ROW_FAILED, Zotero.getString('recognizePDF.noMatches'));
}
}
catch (e) {
Zotero.logError(e);
_updateRow(
itemID,
Zotero.RecognizePDF.ROW_FAILED,
e instanceof Zotero.Exception.Alert
? e.message
: Zotero.getString('recognizePDF.error')
);
}
}
_queueProcessing = false;
}
/**
* Processes the item and places it as a children of the new item
* @param itemID
* @return {Promise}
*/
async function _processItem(itemID) {
let attachment = await Zotero.Items.getAsync(itemID);
if (!attachment || attachment.parentItemID) {
throw new Zotero.Exception.Alert('recognizePDF.error');
}
var zp = Zotero.getActiveZoteroPane();
var selectParent = false;
if (zp) {
let selected = zp.getSelectedItems();
if (selected.length) {
// If only the PDF was selected, select the parent when we're done
selectParent = selected.length == 1 && selected[0] == attachment;
}
}
let parentItem = await _recognize(attachment);
if (!parentItem) {
return null;
}
// Put new item in same collections as the old one
let collections = attachment.getCollections();
await Zotero.DB.executeTransaction(async function () {
if (collections.length) {
for (let collectionID of collections) {
parentItem.addToCollection(collectionID);
}
await parentItem.save();
}
// Put old item as a child of the new item
attachment.parentID = parentItem.id;
await attachment.save();
});
var originalTitle = attachment.getField('title');
var path = attachment.getFilePath();
var originalFilename = OS.Path.basename(path);
// Rename attachment file to match new metadata
if (Zotero.Prefs.get('autoRenameFiles')) {
let ext = Zotero.File.getExtension(path);
let fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
let newName = fileBaseName + (ext ? '.' + ext : '');
let result = await attachment.renameAttachmentFile(newName, false, true);
if (result !== true) {
throw new Error("Error renaming " + path);
}
// Rename attachment title
attachment.setField('title', newName);
await attachment.saveTx();
}
try {
zp = Zotero.getActiveZoteroPane();
if (zp) {
if (selectParent) {
await zp.selectItem(parentItem.id);
}
}
}
catch (e) {
Zotero.logError(e);
}
_newItems.set(
parentItem,
{
originalTitle,
originalFilename,
dateModified: parentItem.dateModified
}
);
return parentItem;
}
/**
* Get json from a PDF
* @param {String} filePath PDF file path
* @param {Number} pages Number of pages to extract
* @return {Promise}
*/
async function extractJSON(filePath, pages) {
let cacheFile = Zotero.getTempDirectory();
cacheFile.append("recognizePDFcache.txt");
if (cacheFile.exists()) {
cacheFile.remove(false);
}
let {exec, args} = Zotero.Fulltext.getPDFConverterExecAndArgs();
args.push('-json', '-l', pages, filePath, cacheFile.path);
Zotero.debug("RecognizePDF: Running " + exec.path + " " + args.map(arg => "'" + arg + "'").join(" "));
try {
await Zotero.Utilities.Internal.exec(exec, args);
let content = await Zotero.File.getContentsAsync(cacheFile.path);
Zotero.debug("RecognizePDF: Extracted JSON:");
Zotero.debug(content);
cacheFile.remove(false);
return JSON.parse(content);
}
catch (e) {
Zotero.logError(e);
try {
cacheFile.remove(false);
} catch(e) {
Zotero.logError(e);
}
throw new Zotero.Exception.Alert("recognizePDF.couldNotRead");
}
}
/**
* Attach appropriate handlers to a Zotero.Translate instance and begin translation
* @return {Promise}
*/
async function _promiseTranslate(translate, libraryID) {
translate.setHandler('select', function (translate, items, callback) {
for (let i in items) {
let obj = {};
obj[i] = items[i];
callback(obj);
return;
}
});
let newItems = await translate.translate({
libraryID,
saveAttachments: false
});
if (newItems.length) {
return newItems[0];
}
throw new Error('No items found');
}
async function _query(json) {
// TODO: Use main API URL for recognizer server
//let uri = Zotero.Prefs.get("api.url") || ZOTERO_CONFIG.API_URL;
let uri = Zotero.Prefs.get("api.url") || ZOTERO_CONFIG.RECOGNIZE_URL;
if (!uri.endsWith('/')) {
uri += '/';
}
uri += 'recognize';
let client = Zotero.Sync.Runner.getAPIClient();
let req = await client.makeRequest(
'POST',
uri,
{
successCodes: [200],
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(json),
noAPIKey: true
}
);
return JSON.parse(req.responseText);
}
/**
* Retrieves metadata for a PDF and saves it as an item
* @param {Zotero.Item} item
* @return {Promise}
*/
async function _recognize(item) {
let filePath = await item.getFilePath();
if (!filePath || !await OS.File.exists(filePath)) throw new Zotero.Exception.Alert('recognizePDF.fileNotFound');
let json = await extractJSON(filePath, MAX_PAGES);
let containingTextPages = 0;
for(let page of json.pages) {
if(page[2].length) {
containingTextPages++;
}
}
if(!containingTextPages) {
throw new Zotero.Exception.Alert('recognizePDF.noOCR');
}
let libraryID = item.libraryID;
let res = await _query(json);
if (!res) return null;
if (res.arxiv) {
Zotero.debug('RecognizePDF: Getting metadata by arXiv');
let translate = new Zotero.Translate.Search();
translate.setIdentifier({arXiv: res.arxiv});
let translators = await translate.getTranslators();
translate.setTranslator(translators);
try {
let newItem = await _promiseTranslate(translate, libraryID);
if (!newItem.abstractNote && res.abstract) {
newItem.setField('abstractNote', res.abstract);
}
if (!newItem.language && res.language) {
newItem.setField('language', res.language);
}
newItem.saveTx();
return newItem;
}
catch (e) {
Zotero.debug('RecognizePDF: ' + e);
}
}
if (res.doi) {
Zotero.debug('RecognizePDF: Getting metadata by DOI');
let translate = new Zotero.Translate.Search();
translate.setIdentifier({
DOI: res.doi
});
let translators = await translate.getTranslators();
translate.setTranslator(translators);
try {
let newItem = await _promiseTranslate(translate, libraryID);
if (!newItem.abstractNote && res.abstract) {
newItem.setField('abstractNote', res.abstract);
}
if (!newItem.language && res.language) {
newItem.setField('language', res.language);
}
newItem.saveTx();
return newItem;
}
catch (e) {
Zotero.debug('RecognizePDF: ' + e);
}
}
if (res.isbn) {
Zotero.debug('RecognizePDF: Getting metadata by ISBN');
let translate = new Zotero.Translate.Search();
translate.setSearch({'itemType': 'book', 'ISBN': res.isbn});
try {
let translatedItems = await translate.translate({
libraryID: false,
saveAttachments: false
});
Zotero.debug('RecognizePDF: Translated items:');
Zotero.debug(translatedItems);
if (translatedItems.length) {
let newItem = new Zotero.Item;
newItem.libraryID = libraryID;
// Convert tags to automatic. For other items this is done automatically in
// translate.js for other items, but for ISBNs we just get the data
// (libraryID=false) and do the saving manually.
translatedItems[0].tags = translatedItems[0].tags.map(tag => {
if (typeof tag == 'string') {
return {
tag,
type: 1
};
}
tag.type = 1;
return tag;
});
newItem.fromJSON(translatedItems[0]);
if (!newItem.abstractNote && res.abstract) {
newItem.setField('abstractNote', res.abstract);
}
if (!newItem.language && res.language) {
newItem.setField('language', res.language);
}
newItem.saveTx();
return newItem;
}
}
catch (e) {
Zotero.debug('RecognizePDF: ' + e);
}
}
if (res.title) {
let type = 'journalArticle';
if (res.type === 'book-chapter') {
type = 'bookSection';
}
let newItem = new Zotero.Item(type);
newItem.libraryID = libraryID;
newItem.setField('title', res.title);
let creators = [];
for (let author of res.authors) {
creators.push({
firstName: author.firstName,
lastName: author.lastName,
creatorType: 'author'
})
}
newItem.setCreators(creators);
if (res.abstract) newItem.setField('abstractNote', res.abstract);
if (res.year) newItem.setField('date', res.year);
if (res.pages) newItem.setField('pages', res.pages);
if (res.volume) newItem.setField('volume', res.volume);
if (res.url) newItem.setField('url', res.url);
if (res.language) newItem.setField('language', res.language);
if (type === 'journalArticle') {
if (res.issue) newItem.setField('issue', res.issue);
if (res.ISSN) newItem.setField('issn', res.issn);
if (res.container) newItem.setField('publicationTitle', res.container);
}
else if (type === 'bookSection') {
if (res.container) newItem.setField('bookTitle', res.container);
if (res.publisher) newItem.setField('publisher', res.publisher);
}
newItem.setField('libraryCatalog', 'Zotero');
await newItem.saveTx();
return newItem;
}
return null;
}
};

View File

@ -744,18 +744,31 @@ Zotero.Schema = new function(){
index[id].extract = true;
}
let sql = "SELECT metadataJSON FROM translatorCache";
let dbCache = yield Zotero.DB.columnQueryAsync(sql);
let sql = "SELECT rowid, fileName, metadataJSON FROM translatorCache";
let rows = yield Zotero.DB.queryAsync(sql);
// If there's anything in the cache, see what we actually need to extract
if (dbCache) {
for (let i = 0; i < dbCache.length; i++) {
let metadata = JSON.parse(dbCache[i]);
for (let i = 0; i < rows.length; i++) {
let json = rows[i].metadataJSON;
let metadata;
try {
metadata = JSON.parse(json);
}
catch (e) {
Zotero.logError(e);
Zotero.debug(json, 1);
// If JSON is invalid, clear from cache
yield Zotero.DB.queryAsync(
"DELETE FROM translatorCache WHERE rowid=?",
rows[i].rowid
);
continue;
}
let id = metadata.translatorID;
if (index[id] && index[id].lastUpdated <= metadata.lastUpdated) {
index[id].extract = false;
}
}
}
for (let translatorID in index) {
// Use index file and DB cache for translator entries,
@ -788,9 +801,8 @@ Zotero.Schema = new function(){
catch (e) {
if (e instanceof OS.File.Error && e.becauseExists) {
// Could overwrite automatically, but we want to log this
let msg = "Overwriting translator with same filename '" + entry.fileName + "'";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.warn("Overwriting translator with same filename '"
+ entry.fileName + "'");
yield OS.File.move(tmpFile, destFile);
}
else {
@ -952,10 +964,8 @@ Zotero.Schema = new function(){
catch (e) {
if (e instanceof OS.File.Error && e.becauseExists) {
// Could overwrite automatically, but we want to log this
let msg = "Overwriting " + modeType + " with same filename "
+ "'" + fileName + "'";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.warn("Overwriting " + modeType + " with same filename "
+ "'" + fileName + "'", 1);
yield OS.File.copy(entry.path, destFile);
}
else {
@ -1536,8 +1546,13 @@ Zotero.Schema = new function(){
return false;
}
if (dbVersion > schemaVersion) {
throw new Error("Zotero '" + schema + "' DB version (" + dbVersion
+ ") is newer than SQL file (" + schemaVersion + ")");
let dbClientVersion = yield Zotero.DB.valueQueryAsync(
"SELECT value FROM settings WHERE setting='client' AND key='lastCompatibleVersion'"
);
throw new Zotero.DB.IncompatibleVersionException(
`Zotero '${schema}' DB version (${dbVersion}) is newer than SQL file (${schemaVersion})`,
dbClientVersion
);
}
let sql = yield _getSchemaSQL(schema);
yield Zotero.DB.executeSQLFile(sql);
@ -1620,116 +1635,6 @@ Zotero.Schema = new function(){
var translatorUpdates = xmlhttp.responseXML.getElementsByTagName('translator');
var styleUpdates = xmlhttp.responseXML.getElementsByTagName('style');
var updatePDFTools = function () {
// No updates for PPC
if (Zotero.platform == 'MacPPC') return;
let pdfToolsUpdates = xmlhttp.responseXML.getElementsByTagName('pdftools');
if (pdfToolsUpdates.length) {
let availableVersion = pdfToolsUpdates[0].getAttribute('version');
let installInfo = false;
let installConverter = false;
// Don't auto-install if not installed
if (!Zotero.Fulltext.pdfInfoIsRegistered() && !Zotero.Fulltext.pdfConverterIsRegistered()) {
return;
}
// TEMP
if (Zotero.isWin) {
if (Zotero.Fulltext.pdfInfoIsRegistered()) {
if (Zotero.Fulltext.pdfInfoVersion != '3.02a') {
installInfo = true;
}
}
// Install missing component if one is installed
else if (Zotero.Fulltext.pdfConverterIsRegistered()) {
installInfo = true;
}
if (Zotero.Fulltext.pdfConverterIsRegistered()) {
if (Zotero.Fulltext.pdfConverterVersion != '3.02a') {
installConverter = true;
}
}
// Install missing component if one is installed
else if (Zotero.Fulltext.pdfInfoIsRegistered()) {
installConverter = true;
}
availableVersion = '3.02';
}
else {
if (Zotero.Fulltext.pdfInfoIsRegistered()) {
let currentVersion = Zotero.Fulltext.pdfInfoVersion;
if (currentVersion < availableVersion || currentVersion.startsWith('3.02')
|| currentVersion == 'UNKNOWN') {
installInfo = true;
}
}
// Install missing component if one is installed
else if (Zotero.Fulltext.pdfConverterIsRegistered()) {
installInfo = true;
}
if (Zotero.Fulltext.pdfConverterIsRegistered()) {
let currentVersion = Zotero.Fulltext.pdfConverterVersion;
if (currentVersion < availableVersion || currentVersion.startsWith('3.02')
|| currentVersion == 'UNKNOWN') {
installConverter = true;
}
}
// Install missing component if one is installed
else if (Zotero.Fulltext.pdfInfoIsRegistered()) {
installConverter = true;
}
}
let prefKey = 'pdfToolsInstallError';
let lastTry = 0, delay = 43200000; // half a day, so doubles to a day initially
try {
[lastTry, delay] = Zotero.Prefs.get(prefKey).split(';');
}
catch (e) {}
// Allow an additional minute, since repo updates might not be exact
if (Date.now() < (parseInt(lastTry) + parseInt(delay) - 60000)) {
Zotero.debug("Now enough time since last PDF tools installation failure -- skipping", 2);
return;
}
var checkResult = function (success) {
if (success) {
try {
Zotero.Prefs.clear(prefKey);
}
catch (e) {}
}
else {
// Keep doubling delay, to a max of 1 week
Zotero.Prefs.set(prefKey, Date.now() + ";" + Math.min(delay * 2, 7*24*60*60*1000));
let msg = "Error downloading PDF tool";
Zotero.debug(msg, 1);
throw new Error(msg);
}
};
if (installConverter && installInfo) {
Zotero.Fulltext.downloadPDFTool('converter', availableVersion, function (success) {
checkResult(success);
Zotero.Fulltext.downloadPDFTool('info', availableVersion, checkResult);
});
}
else if (installConverter) {
Zotero.Fulltext.downloadPDFTool('converter', availableVersion, checkResult);
}
else if (installInfo) {
Zotero.Fulltext.downloadPDFTool('info', availableVersion, checkResult);
}
else {
Zotero.debug("PDF tools are up to date");
}
}
};
if (!translatorUpdates.length && !styleUpdates.length){
await Zotero.DB.executeTransaction(function* (conn) {
// Store the timestamp provided by the server
@ -1743,7 +1648,6 @@ Zotero.Schema = new function(){
if (!force) {
_setRepositoryTimer(ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL);
}
updatePDFTools();
return true;
}
@ -1777,8 +1681,6 @@ Zotero.Schema = new function(){
});
}
updatePDFTools();
return updated;
}
@ -2514,6 +2416,31 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync("DELETE FROM relationPredicates WHERE predicate='dc:isReplacedBy'");
}
else if (i == 100) {
let userID = yield Zotero.DB.valueQueryAsync("SELECT value FROM settings WHERE setting='account' AND key='userID'");
let predicateID = yield Zotero.DB.valueQueryAsync("SELECT predicateID FROM relationPredicates WHERE predicate='dc:relation'");
if (userID && predicateID) {
let rows = yield Zotero.DB.queryAsync("SELECT itemID, object FROM items JOIN itemRelations IR USING (itemID) WHERE libraryID=? AND predicateID=?", [1, predicateID]);
for (let row of rows) {
let matches = row.object.match(/^http:\/\/zotero.org\/users\/(\d+)\/items\/([A-Z0-9]+)$/);
if (matches) {
// Wrong libraryID
if (matches[1] != userID) {
yield Zotero.DB.queryAsync(`UPDATE OR REPLACE itemRelations SET object='http://zotero.org/users/${userID}/items/${matches[2]}' WHERE itemID=? AND predicateID=?`, [row.itemID, predicateID]);
}
}
}
}
}
else if (i == 101) {
Components.utils.import("chrome://zotero/content/import/mendeley/mendeleyImport.js");
let importer = new Zotero_Import_Mendeley();
if (yield importer.hasImportedFiles()) {
yield importer.queueFileCleanup();
}
}
// If breaking compatibility or doing anything dangerous, clear minorUpdateFrom
}

View File

@ -28,12 +28,15 @@ Zotero.Server = new function() {
this.responseCodes = {
200:"OK",
201:"Created",
204:"No Content",
300:"Multiple Choices",
400:"Bad Request",
404:"Not Found",
409:"Conflict",
412:"Precondition Failed",
500:"Internal Server Error",
501:"Method Not Implemented",
501:"Not Implemented",
503:"Service Unavailable",
504:"Gateway Timeout"
};
@ -240,17 +243,18 @@ Zotero.Server.DataListener.prototype._headerFinished = function() {
const hostRe = /[\r\n]Host: *(localhost|127\.0\.0\.1)(:[0-9]+)?[\r\n]/i;
const contentTypeRe = /[\r\n]Content-Type: *([^ \r\n]+)/i;
if(!Zotero.isServer) {
const originRe = /[\r\n]Origin: *([^ \r\n]+)/i;
var m = originRe.exec(this.header);
if (m) {
this.origin = m[1];
} else {
}
else {
const bookmarkletRe = /[\r\n]Zotero-Bookmarklet: *([^ \r\n]+)/i;
var m = bookmarkletRe.exec(this.header);
if (m) this.origin = "https://www.zotero.org";
}
if (!Zotero.isServer) {
// Make sure the Host header is set to localhost/127.0.0.1 to prevent DNS rebinding attacks
if (!hostRe.exec(this.header)) {
this._requestFinished(this._generateResponse(400, "text/plain", "Invalid Host header\n"));
@ -329,11 +333,29 @@ Zotero.Server.DataListener.prototype._bodyData = function() {
/**
* Generates the response to an HTTP request
*/
Zotero.Server.DataListener.prototype._generateResponse = function(status, contentType, body) {
Zotero.Server.DataListener.prototype._generateResponse = function (status, contentTypeOrHeaders, body) {
var response = "HTTP/1.0 "+status+" "+Zotero.Server.responseCodes[status]+"\r\n";
if(!Zotero.isServer) {
// Translation server
if (Zotero.isServer) {
// Add CORS headers if Origin header matches the allowed origins
if (this.origin) {
let allowedOrigins = Zotero.Prefs.get('httpServer.allowedOrigins')
.split(/, */).filter(x => x);
let allAllowed = allowedOrigins.includes('*');
if (allAllowed || allowedOrigins.includes(this.origin)) {
response += "Access-Control-Allow-Origin: " + (allAllowed ? '*' : this.origin) + "\r\n";
response += "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n";
response += "Access-Control-Allow-Headers: Content-Type\r\n";
response += "Access-Control-Expose-Headers: Link\r\n";
}
}
}
// Client
else {
response += "X-Zotero-Version: "+Zotero.version+"\r\n";
response += "X-Zotero-Connector-API-Version: "+CONNECTOR_API_VERSION+"\r\n";
if (this.origin === ZOTERO_CONFIG.BOOKMARKLET_ORIGIN ||
this.origin === ZOTERO_CONFIG.HTTP_BOOKMARKLET_ORIGIN) {
response += "Access-Control-Allow-Origin: " + this.origin + "\r\n";
@ -342,8 +364,15 @@ Zotero.Server.DataListener.prototype._generateResponse = function(status, conten
}
}
if(contentType) {
response += "Content-Type: "+contentType+"\r\n";
if (contentTypeOrHeaders) {
if (typeof contentTypeOrHeaders == 'string') {
contentTypeOrHeaders = {
'Content-Type': contentTypeOrHeaders
};
}
for (let header in contentTypeOrHeaders) {
response += `${header}: ${contentTypeOrHeaders[header]}\r\n`;
}
}
if(body) {
@ -418,10 +447,12 @@ Zotero.Server.DataListener.prototype._processEndpoint = Zotero.Promise.coroutine
}
// set up response callback
var me = this;
var sendResponseCallback = function(code, contentType, arg) {
me._requestFinished(me._generateResponse(code, contentType, arg));
}
var sendResponseCallback = function (code, contentTypeOrHeaders, arg, options) {
this._requestFinished(
this._generateResponse(code, contentTypeOrHeaders, arg),
options
);
}.bind(this);
// Pass to endpoint
//
@ -491,7 +522,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = Zotero.Promise.coroutine
/*
* returns HTTP data from a request
*/
Zotero.Server.DataListener.prototype._requestFinished = function(response) {
Zotero.Server.DataListener.prototype._requestFinished = function (response, options) {
if(this._responseSent) {
Zotero.debug("Request already finished; not sending another response");
return;
@ -509,8 +540,19 @@ Zotero.Server.DataListener.prototype._requestFinished = function(response) {
try {
intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
// write response
Zotero.debug(response, 5);
// Filter logged response
if (Zotero.Debug.enabled) {
let maxLogLength = 2000;
let str = response;
if (options && options.logFilter) {
str = options.logFilter(str);
}
if (str.length > maxLogLength) {
str = str.substr(0, maxLogLength) + `\u2026 (${response.length} chars)`;
}
Zotero.debug(str, 5);
}
intlStream.writeString(response);
} finally {
intlStream.close();

View File

@ -687,7 +687,7 @@ Zotero.Sync.Storage.Local = {
var filename = item.attachmentFilename;
if (!filename) {
throw new Error("Empty path for item " + item.key);
Zotero.debug("Empty filename for item " + item.key, 2);
}
// Don't save Windows aliases
if (filename.endsWith('.lnk')) {

View File

@ -224,7 +224,6 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
try {
var req = yield Zotero.HTTP.request("OPTIONS", this.rootURI);
this._checkResponse(req);
Zotero.debug("WebDAV credentials cached");
this._cachedCredentials = true;
@ -595,7 +594,6 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
}
// Test whether URL is WebDAV-enabled
try {
var req = yield Zotero.HTTP.request(
"OPTIONS",
uri,
@ -612,13 +610,6 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
debug: true
}
);
}
catch (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
this._checkResponse(e.xmlhttp, e.channel);
}
throw e;
}
Zotero.debug(req.getAllResponseHeaders());
@ -866,7 +857,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
break;
case "NONEXISTENT_FILE_NOT_MISSING":
var errorTitle = Zotero.getString('sync.storage.error.webdav.serverConfig.title');
errorTitle = Zotero.getString('sync.storage.error.webdav.serverConfig.title');
errorMsg = Zotero.getString('sync.storage.error.webdav.nonexistentFileNotMissing');
break;
@ -877,18 +868,25 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
}
}
// TEMP
if (!errorMsg) {
errorMsg = err;
}
var e;
if (errorMsg) {
e = {
message: errorMsg,
// Prevent Report Errors button for known errors
dialogButtonText: null
};
Zotero.logError(errorMsg);
}
else {
e = err;
Zotero.logError(err);
}
if (!skipSuccessMessage) {
if (!errorTitle) {
var errorTitle = Zotero.getString("general.error");
errorTitle = Zotero.getString("general.error");
}
promptService.alert(window, errorTitle, errorMsg);
Zotero.Utilities.Internal.errorPrompt(errorTitle, e);
}
return false;
}),
@ -1126,8 +1124,6 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
throw e;
}
this._checkResponse(req);
// mod_speling can return 300s for 404s with base name matches
if (req.status == 404 || req.status == 300) {
return false;
@ -1462,87 +1458,6 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
}),
/**
* Checks for an invalid SSL certificate and throws a nice error
*/
_checkResponse: function (req, channel) {
if (req.status != 0) return;
// Check if the error we encountered is really an SSL error
// Logic borrowed from https://developer.mozilla.org/en-US/docs/How_to_check_the_security_state_of_an_XMLHTTPRequest_over_SSL
// http://mxr.mozilla.org/mozilla-central/source/security/nss/lib/ssl/sslerr.h
// http://mxr.mozilla.org/mozilla-central/source/security/nss/lib/util/secerr.h
var secErrLimit = Ci.nsINSSErrorsService.NSS_SEC_ERROR_LIMIT - Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
var secErr = Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (channel.status & 0xffff);
var sslErrLimit = Ci.nsINSSErrorsService.NSS_SSL_ERROR_LIMIT - Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
var sslErr = Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (channel.status & 0xffff);
if( (secErr < 0 || secErr > secErrLimit) && (sslErr < 0 || sslErr > sslErrLimit) ) {
return;
}
var secInfo = channel.securityInfo;
if (secInfo instanceof Ci.nsITransportSecurityInfo) {
secInfo.QueryInterface(Ci.nsITransportSecurityInfo);
if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE) == Ci.nsIWebProgressListener.STATE_IS_INSECURE) {
var host = 'host';
try {
host = channel.URI.host;
}
catch (e) {
Zotero.debug(e);
}
var msg = Zotero.getString('sync.storage.error.webdav.sslCertificateError', host);
// In Standalone, provide cert_override.txt instructions and a
// button to open the Zotero profile directory
if (Zotero.isStandalone) {
msg += "\n\n" + Zotero.getString('sync.storage.error.webdav.seeCertOverrideDocumentation');
var buttonText = Zotero.getString('general.openDocumentation');
var func = function () {
var zp = Zotero.getActiveZoteroPane();
zp.loadURI("https://www.zotero.org/support/kb/cert_override", { shiftKey: true });
};
}
// In Firefox display a button to load the WebDAV URL
else {
msg += "\n\n" + Zotero.getString('sync.storage.error.webdav.loadURLForMoreInfo');
var buttonText = Zotero.getString('sync.storage.error.webdav.loadURL');
var func = function () {
var zp = Zotero.getActiveZoteroPane();
zp.loadURI(channel.URI.spec, { shiftKey: true });
};
}
var e = new Zotero.Error(
msg,
0,
{
dialogButtonText: buttonText,
dialogButtonCallback: func
}
);
throw e;
}
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
var msg = Zotero.getString('sync.storage.error.webdav.sslConnectionError', host) +
Zotero.getString('sync.storage.error.webdav.loadURLForMoreInfo');
var e = new Zotero.Error(
msg,
0,
{
dialogButtonText: Zotero.getString('sync.storage.error.webdav.loadURL'),
dialogButtonCallback: function () {
var zp = Zotero.getActiveZoteroPane();
zp.loadURI(channel.URI.spec, { shiftKey: true });
}
}
);
throw e;
}
}
},
_throwFriendlyError: function (method, url, status) {
throw new Error(
Zotero.getString('sync.storage.error.webdav.requestError', [status, method])

View File

@ -820,7 +820,7 @@ Zotero.Sync.APIClient.prototype = {
_checkBackoff: function (xmlhttp) {
var backoff = xmlhttp.getResponseHeader("Backoff");
if (backoff && Number.isInteger(backoff)) {
if (backoff && parseInt(backoff) == backoff) {
// TODO: Update status?
this.caller.pause(backoff * 1000);
}
@ -831,7 +831,7 @@ Zotero.Sync.APIClient.prototype = {
var retryAfter = xmlhttp.getResponseHeader("Retry-After");
var delay;
if (!retryAfter) return false;
if (!Number.isInteger(retryAfter)) {
if (parseInt(retryAfter) != retryAfter) {
Zotero.logError(`Invalid Retry-After delay ${retryAfter}`);
return false;
}

View File

@ -46,6 +46,7 @@ Zotero.Sync.Data.Engine = function (options) {
}
this.apiClient = options.apiClient;
this.userID = options.userID;
this.libraryID = options.libraryID;
this.library = Zotero.Libraries.get(options.libraryID);
this.libraryTypeID = this.library.libraryTypeID;
@ -1690,22 +1691,14 @@ Zotero.Sync.Data.Engine.prototype._fullSync = Zotero.Promise.coroutine(function*
for (let key in results.versions) {
let version = results.versions[key];
let obj = objectsClass.getByLibraryAndKey(this.libraryID, key);
// If object already at latest version, skip
// If object is already at or above latest version, skip. Local version can be
// higher because, as explained in _uploadObjects(), we upload items in batches
// and only get the last version to record in the database.
let localVersion = localVersions[key];
if (localVersion && localVersion === version) {
if (localVersion && localVersion >= version) {
continue;
}
// This should never happen
if (localVersion > version) {
Zotero.logError(`Local version of ${objectType} ${this.libraryID}/${key} `
+ `is later than remote! (${localVersion} > ${version})`);
// Delete cache version if it's there
yield Zotero.Sync.Data.Local.deleteCacheObjectVersions(
objectType, this.libraryID, key, localVersion, localVersion
);
}
if (obj) {
Zotero.debug(`${ObjectType} ${obj.libraryKey} is older than remote version`);
}
@ -1924,6 +1917,12 @@ Zotero.Sync.Data.Engine.prototype._handleUploadError = Zotero.Promise.coroutine(
return this.UPLOAD_RESULT_OBJECT_CONFLICT;
}
}
else if (e.name == "ZoteroUploadRestartError") {
return this.UPLOAD_RESULT_RESTART;
}
else if (e.name == "ZoteroUploadCancelError") {
return this.UPLOAD_RESULT_CANCEL;
}
throw e;
});
@ -1983,6 +1982,48 @@ Zotero.Sync.Data.Engine.prototype._checkObjectUploadError = Zotero.Promise.corou
}
}
}
else if (code == 403) {
// If we get a 403 for a local group attachment, check the group permissions to confirm
// that we no longer have file-editing access and prompt to reset local group files
if (objectType == 'item') {
let item = Zotero.Items.getByLibraryAndKey(this.libraryID, key);
if (this.library.libraryType == 'group' && item.isFileAttachment()) {
let reset = false;
let groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
let info = yield this.apiClient.getGroup(groupID);
if (info) {
Zotero.debug(info);
let { editable, filesEditable } = Zotero.Groups.getPermissionsFromJSON(
info.data, this.userID
);
// If we do still have file-editing access, something else went wrong,
// and we should just fail without resetting
if (!filesEditable) {
let index = Zotero.Sync.Storage.Utilities.showFileWriteAccessLostPrompt(
null, this.library
);
let e = new Error(message);
if (index === 0) {
let group = Zotero.Groups.get(groupID);
group.filesEditable = false;
yield group.saveTx();
yield Zotero.Sync.Data.Local.resetUnsyncedLibraryFiles(this.libraryID);
e.name = "ZoteroUploadRestartError";
}
else {
e.name = "ZoteroUploadCancelError";
}
throw e;
}
}
else {
Zotero.logError("Couldn't get metadata for group " + groupID);
}
}
}
}
// This shouldn't happen, because the upload request includes a library version and should
// prevent an outdated upload before the object version is checked. If it does, we need to
// do a full sync. This error is checked in handleUploadError().

View File

@ -239,6 +239,8 @@ Zotero.Sync.Data.Local = {
_libraryHasUnsyncedFiles: Zotero.Promise.coroutine(function* (libraryID) {
// TODO: Check for modified file attachment items, which also can't be uploaded
// (and which are corrected by resetUnsyncedLibraryFiles())
yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID);
return !!(yield Zotero.Sync.Storage.Local.getFilesToUpload(libraryID)).length;
}),
@ -253,42 +255,9 @@ Zotero.Sync.Data.Local = {
}
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(libraryID)) {
let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
// New/modified objects
let ids = yield Zotero.Sync.Data.Local.getUnsynced(objectType, libraryID);
let keys = ids.map(id => objectsClass.getLibraryAndKeyFromID(id).key);
let cacheVersions = yield this.getLatestCacheObjectVersions(objectType, libraryID, keys);
let toDelete = [];
for (let key of keys) {
let obj = objectsClass.getByLibraryAndKey(libraryID, key);
// If object is in cache, overwrite with pristine data
if (cacheVersions[key]) {
let json = yield this.getCacheObject(objectType, libraryID, key, cacheVersions[key]);
yield Zotero.DB.executeTransaction(function* () {
yield this._saveObjectFromJSON(obj, json, {});
}.bind(this));
}
// Otherwise, erase
else {
toDelete.push(objectsClass.getIDFromLibraryAndKey(libraryID, key));
}
}
if (toDelete.length) {
yield objectsClass.erase(
toDelete,
{
skipEditCheck: true,
skipDeleteLog: true
}
);
}
// Deleted objects
keys = yield Zotero.Sync.Data.Local.getDeleted(objectType, libraryID);
yield this.removeObjectsFromDeleteLog(objectType, libraryID, keys);
let ids = yield this.getUnsynced(objectType, libraryID);
yield this._resetObjects(libraryID, objectType, ids);
}
// Mark library for full sync
@ -305,13 +274,62 @@ Zotero.Sync.Data.Local = {
*
* _libraryHasUnsyncedFiles(), which checks for updated files, must be called first.
*/
resetUnsyncedLibraryFiles: Zotero.Promise.coroutine(function* (libraryID) {
var itemIDs = yield Zotero.Sync.Storage.Local.getFilesToUpload(libraryID);
resetUnsyncedLibraryFiles: async function (libraryID) {
// Reset unsynced file attachments
var itemIDs = await Zotero.Sync.Data.Local.getUnsynced('item', libraryID);
var toReset = [];
for (let itemID of itemIDs) {
let item = Zotero.Items.get(itemID);
yield item.deleteAttachmentFile();
if (item.isFileAttachment()) {
toReset.push(item.id);
}
}),
}
await this._resetObjects(libraryID, 'item', toReset);
// Delete unsynced files
var itemIDs = await Zotero.Sync.Storage.Local.getFilesToUpload(libraryID);
for (let itemID of itemIDs) {
let item = Zotero.Items.get(itemID);
await item.deleteAttachmentFile();
}
},
_resetObjects: async function (libraryID, objectType, ids) {
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
var keys = ids.map(id => objectsClass.getLibraryAndKeyFromID(id).key);
var cacheVersions = await this.getLatestCacheObjectVersions(objectType, libraryID, keys);
var toDelete = [];
for (let key of keys) {
let obj = objectsClass.getByLibraryAndKey(libraryID, key);
// If object is in cache, overwrite with pristine data
if (cacheVersions[key]) {
let json = await this.getCacheObject(objectType, libraryID, key, cacheVersions[key]);
await Zotero.DB.executeTransaction(async function () {
await this._saveObjectFromJSON(obj, json, {});
}.bind(this));
}
// Otherwise, erase
else {
toDelete.push(objectsClass.getIDFromLibraryAndKey(libraryID, key));
}
}
if (toDelete.length) {
await objectsClass.erase(
toDelete,
{
skipEditCheck: true,
skipDeleteLog: true
}
);
}
// Deleted objects
keys = await Zotero.Sync.Data.Local.getDeleted(objectType, libraryID);
await this.removeObjectsFromDeleteLog(objectType, libraryID, keys);
},
getSkippedLibraries: function () {
@ -776,6 +794,7 @@ Zotero.Sync.Data.Local = {
// Skip objects with unmet dependencies
if (objectType == 'item' || objectType == 'collection') {
// Missing parent collection or item
let parentProp = 'parent' + objectType[0].toUpperCase() + objectType.substr(1);
let parentKey = jsonData[parentProp];
if (parentKey) {
@ -797,17 +816,37 @@ Zotero.Sync.Data.Local = {
}
}
/*if (objectType == 'item') {
for (let j = 0; j < jsonData.collections.length; i++) {
let parentKey = jsonData.collections[j];
let parentCollection = Zotero.Collections.getByLibraryAndKey(
libraryID, parentKey, { noCache: true }
);
if (!parentCollection) {
// ???
// Missing collection -- this could happen if the collection was deleted
// locally and an item in it was modified remotely
if (objectType == 'item' && jsonData.collections) {
let error;
for (let key of jsonData.collections) {
let collection = Zotero.Collections.getByLibraryAndKey(libraryID, key);
if (!collection) {
error = new Error(`Collection ${libraryID}/${key} not found `
+ `-- skipping item`);
error.name = "ZoteroMissingObjectError";
Zotero.debug(error.message);
results.push({
key: objectKey,
processed: false,
error,
retry: false
});
// If the collection is in the delete log, the deletion will upload
// after downloads are done. Otherwise, we somehow missed
// downloading it and should add it to the queue to try again.
if (!(yield this.getDateDeleted('collection', libraryID, key))) {
yield this.addObjectsToSyncQueue('collection', libraryID, [key]);
}
break;
}
}
if (error) {
continue;
}
}
}*/
}
// Errors have to be thrown in order to roll back the transaction, so catch those here
@ -1056,6 +1095,12 @@ Zotero.Sync.Data.Local = {
Zotero.debug(json, 1);
throw new Error("Missing 'version' property in JSON");
}
if (json.version === 0) {
Zotero.debug(json, 1);
// TODO: Fix tests so this doesn't happen
Zotero.warn("'version' cannot be 0 in cache JSON");
//throw new Error("'version' cannot be 0 in cache JSON");
}
// If direct data object passed, wrap in fake response object
return json.data === undefined ? {
key: json.key,

View File

@ -69,6 +69,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
var _enabled = false;
var _autoSyncTimer;
var _delaySyncUntil;
var _delayPromises = [];
var _firstInSession = true;
var _syncInProgress = false;
var _stopping = false;
@ -127,9 +129,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
try {
yield Zotero.Notifier.trigger('start', 'sync', []);
// Purge deleted objects so they don't cause sync errors (e.g., long tags)
yield Zotero.purgeDataObjects(true);
let apiKey = yield _getAPIKey();
if (!apiKey) {
throw new Zotero.Error("API key not set", Zotero.Error.ERROR_API_KEY_NOT_SET);
@ -142,6 +141,45 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.updateIcons('animate');
// If a delay is set (e.g., from the connector target selector), wait to sync
while (_delaySyncUntil && new Date() < _delaySyncUntil) {
this.setSyncStatus(Zotero.getString('sync.status.waiting'));
let delay = _delaySyncUntil - new Date();
Zotero.debug(`Waiting ${delay} ms to sync`);
yield Zotero.Promise.delay(delay);
}
// If paused, wait until we're done
while (true) {
if (_delayPromises.some(p => p.isPending())) {
this.setSyncStatus(Zotero.getString('sync.status.waiting'));
Zotero.debug("Syncing is paused -- waiting to sync");
yield Zotero.Promise.all(_delayPromises);
// If more were added, continue
if (_delayPromises.some(p => p.isPending())) {
continue;
}
_delayPromises = [];
}
break;
}
// purgeDataObjects() starts a transaction, so if there's an active one then show a
// nice message and wait until there's not. Another transaction could still start
// before purgeDataObjects() and result in a wait timeout, but this should reduce the
// frequency of that.
while (Zotero.DB.inTransaction()) {
this.setSyncStatus(Zotero.getString('sync.status.waiting'));
Zotero.debug("Transaction in progress -- waiting to sync");
yield Zotero.DB.waitForTransaction('sync');
_stopCheck();
}
this.setSyncStatus(Zotero.getString('sync.status.preparing'));
// Purge deleted objects so they don't cause sync errors (e.g., long tags)
yield Zotero.purgeDataObjects(true);
let client = this.getAPIClient({ apiKey });
let keyInfo = yield this.checkAccess(client, options);
@ -162,6 +200,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
}
let engineOptions = {
userID: keyInfo.userID,
apiClient: client,
caller: this.caller,
setStatus: this.setSyncStatus.bind(this),
@ -870,11 +909,20 @@ Zotero.Sync.Runner_Module = function (options = {}) {
// Implements nsITimerCallback
var callback = {
notify: function (timer) {
notify: async function (timer) {
if (!_getAPIKey()) {
return;
}
// If a delay is set (e.g., from the connector target selector), wait to sync.
// We do this in sync() too for manual syncs, but no need to start spinning if
// it's just an auto-sync.
while (_delaySyncUntil && new Date() < _delaySyncUntil) {
let delay = _delaySyncUntil - new Date();
Zotero.debug(`Waiting ${delay} ms to start auto-sync`);
await Zotero.Promise.delay(delay);
}
if (Zotero.locked) {
Zotero.debug('Zotero is locked -- skipping auto-sync', 4);
return;
@ -921,6 +969,26 @@ Zotero.Sync.Runner_Module = function (options = {}) {
}
this.delaySync = function (ms) {
_delaySyncUntil = new Date(Date.now() + ms);
};
/**
* Delay syncs until the returned function is called
*
* @return {Function} - Resolve function
*/
this.delayIndefinite = function () {
var resolve;
var promise = new Zotero.Promise(function () {
resolve = arguments[0];
});
_delayPromises.push(promise);
return resolve;
};
/**
* Trigger updating of the main sync icon, the sync error icon, and
* library-specific sync error icons across all windows
@ -1046,8 +1114,21 @@ Zotero.Sync.Runner_Module = function (options = {}) {
else if (e.name && e.name == 'ZoteroObjectUploadError') {
let { code, data, objectType, object } = e;
if (code == 413) {
// Collection name too long
if (objectType == 'collection' && data && data.value) {
e.message = Zotero.getString('sync.error.collectionTooLong', [data.value]);
e.dialogButtonText = Zotero.getString('pane.collections.showCollectionInLibrary');
e.dialogButtonCallback = () => {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.collectionsView.selectCollection(object.id);
};
}
else if (objectType == 'item') {
// Tag too long
if (code == 413 && objectType == 'item') {
if (data && data.tag !== undefined) {
// Show long tag fixer and handle result
e.dialogButtonText = Zotero.getString('general.fix');
@ -1118,15 +1199,39 @@ Zotero.Sync.Runner_Module = function (options = {}) {
options.restartSync = true;
});
}
else {
// Note too long
else if (object.isNote() || object.isAttachment()) {
if (object.isNote() || object.isAttachment()) {
// Throw an error that adds a button for selecting the item to the sync error dialog
if (e.message.includes('<img src="data:image')) {
// TODO: Localize
e.message = "Notes with embedded images cannot currently be synced to "
+ `${ZOTERO_CONFIG.DOMAIN_NAME}.`
e.message = Zotero.getString('sync.error.noteEmbeddedImage');
}
else if (e.message.match(/^Note '.*' too long for item/)) {
e.message = Zotero.getString(
'sync.error.noteTooLong',
Zotero.Utilities.ellipsize(object.getNoteTitle(), 40)
);
}
}
// Field or creator too long
else if (data && data.field) {
e.message = (data.field == 'creator'
? Zotero.getString(
'sync.error.creatorTooLong',
[data.value]
)
: Zotero.getString(
'sync.error.fieldTooLong',
[data.field, data.value]
))
+ '\n\n'
+ Zotero.getString(
'sync.error.reportSiteIssuesToForums',
Zotero.clientName
);
}
// Include "Show Item in Library" button
e.dialogButtonText = Zotero.getString('pane.items.showItemInLibrary');
e.dialogButtonCallback = () => {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
@ -1135,6 +1240,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
win.ZoteroPane.selectItem(object.id);
};
}
}
// If not a background sync, show dialog immediately
if (!options.background && e.dialogButtonCallback) {

View File

@ -1292,12 +1292,15 @@ Zotero.Translate.Base.prototype = {
this._currentState = "translate";
this._sessionID = options.sessionID;
this._libraryID = options.libraryID;
if (options.collections && !Array.isArray(options.collections)) {
throw new Error("'collections' must be an array");
}
this._collections = options.collections;
this._saveAttachments = options.saveAttachments === undefined || options.saveAttachments;
this._forceTagType = options.forceTagType;
this._saveOptions = options.saveOptions;
this._savingAttachments = [];
this._savingItems = 0;
@ -1340,11 +1343,15 @@ Zotero.Translate.Base.prototype = {
Zotero.Promise.resolve(this.translator[0])
.then(function (translator) {
this.translator[0] = translator;
this._loadTranslator(translator).then(() => this._translateTranslatorLoaded());
this._loadTranslator(translator)
.then(() => this._translateTranslatorLoaded())
.catch(e => deferred.reject(e));
}.bind(this));
}
else {
this._loadTranslator(this.translator[0]).then(() => this._translateTranslatorLoaded());
this._loadTranslator(this.translator[0])
.then(() => this._translateTranslatorLoaded())
.catch(e => deferred.reject(e));
}
return deferred.promise;
@ -1879,14 +1886,17 @@ Zotero.Translate.Base.prototype = {
*/
_attr: function (selector, attr, index) {
if (typeof arguments[0] == 'string') {
var doc = this.document;
var docOrElem = this.document;
}
// Support legacy polyfill signature
// Document or element passed as first argument
else {
this._debug("WARNING: attr() no longer requires a document as the first argument");
[doc, selector, attr, index] = arguments;
// TODO: Warn if Document rather than Element is passed once we drop 4.0 translator
// support
[docOrElem, selector, attr, index] = arguments;
}
var elem = index ? doc.querySelectorAll(selector).item(index) : doc.querySelector(selector);
var elem = index
? docOrElem.querySelectorAll(selector).item(index)
: docOrElem.querySelector(selector);
return elem ? elem.getAttribute(attr) : null;
},
@ -1895,14 +1905,17 @@ Zotero.Translate.Base.prototype = {
*/
_text: function (selector, index) {
if (typeof arguments[0] == 'string') {
var doc = this.document;
var docOrElem = this.document;
}
// Support legacy polyfill signature
// Document or element passed as first argument
else {
this._debug("WARNING: text() no longer requires a document as the first argument");
[doc, selector, attr, index] = arguments;
// TODO: Warn if Document rather than Element is passed once we drop 4.0 translator
// support
[docOrElem, selector, index] = arguments;
}
var elem = index ? doc.querySelectorAll(selector).item(index) : doc.querySelector(selector);
var elem = index
? docOrElem.querySelectorAll(selector).item(index)
: docOrElem.querySelector(selector);
return elem ? elem.textContent : null;
},
@ -1928,29 +1941,21 @@ Zotero.Translate.Base.prototype = {
* Generates a string from an exception
* @param {String|Exception} error
*/
"_generateErrorString":function(error) {
var errorString = "";
if(typeof(error) == "string") {
errorString = "\nthrown exception => "+error;
} else {
var haveStack = false;
for(var i in error) {
if(typeof(error[i]) != "object") {
if(i === "stack") haveStack = true;
errorString += "\n"+i+' => '+error[i];
_generateErrorString: function (error) {
var errorString = error;
if (error.stack && error) {
errorString += "\n\n" + error.stack;
}
if (this.path) {
errorString += `\nurl => ${this.path}`;
}
errorString += "\nstring => "+error.toString();
if(!haveStack && error.stack) {
// In case the stack is not enumerable
errorString += "\nstack => "+error.stack.toString();
if (Zotero.Prefs.get("downloadAssociatedFiles")) {
errorString += "\ndownloadAssociatedFiles => true";
}
if (Zotero.Prefs.get("automaticSnapshots")) {
errorString += "\nautomaticSnapshots => true";
}
errorString += "\nurl => "+this.path
+ "\ndownloadAssociatedFiles => "+Zotero.Prefs.get("downloadAssociatedFiles")
+ "\nautomaticSnapshots => "+Zotero.Prefs.get("automaticSnapshots");
return errorString.substr(1);
return errorString;
},
/**
@ -2082,6 +2087,7 @@ Zotero.Translate.Web.prototype._prepareTranslation = Zotero.Promise.method(funct
collections: this._collections,
attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")],
forceTagType: 1,
sessionID: this._sessionID,
cookieSandbox: this._cookieSandbox,
proxy: this._proxy,
baseURI: this.location
@ -2373,11 +2379,15 @@ Zotero.Translate.Import.prototype._prepareTranslation = Zotero.Promise.method(fu
this._itemSaver = new Zotero.Translate.ItemSaver({
libraryID: this._libraryID,
collections: this._collections,
forceTagType: this._forceTagType,
attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
baseURI,
saveOptions: {
saveOptions: Object.assign(
{
skipSelect: true
}
},
this._saveOptions || {}
)
});
this.newItems = [];
this.newCollections = [];
@ -2585,6 +2595,7 @@ Zotero.Translate.Search.prototype = new Zotero.Translate.Base();
Zotero.Translate.Search.prototype.type = "search";
Zotero.Translate.Search.prototype._entryFunctionSuffix = "Search";
Zotero.Translate.Search.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Search);
Zotero.Translate.Search.prototype.ERROR_NO_RESULTS = "No items returned from any translator";
/**
* @borrows Zotero.Translate.Web#setCookieSandbox
@ -2625,6 +2636,12 @@ Zotero.Translate.Search.prototype.setIdentifier = function (identifier) {
contextObject: "rft_id=info:pmid/" + identifier.PMID
};
}
else if (identifier.arXiv) {
search = {
itemType: "journalArticle",
arXiv: identifier.arXiv
};
}
else {
throw new Error("Unrecognized identifier");
}
@ -2662,7 +2679,8 @@ Zotero.Translate.Search.prototype.complete = function(returnValue, error) {
&& !this._savingItems
//length is 0 only when translate was called without translators
&& this.translator.length) {
Zotero.debug("Translate: Could not find a result using "+this.translator[0].label, 3);
Zotero.debug("Translate: Could not find a result using " + this.translator[0].label
+ (this.translator.length > 1 ? " -- trying next translator" : ""), 3);
if(error) Zotero.debug(this._generateErrorString(error), 3);
if(this.translator.length > 1) {
this.translator.shift();
@ -2673,7 +2691,8 @@ Zotero.Translate.Search.prototype.complete = function(returnValue, error) {
});
return;
} else {
error = "No items returned from any translator";
Zotero.debug("No more translators to try");
error = this.ERROR_NO_RESULTS;
returnValue = false;
}
}

View File

@ -368,7 +368,16 @@ Zotero.Translate.DOMWrapper = new function() {
*/
Zotero.Translate.SandboxManager = function(sandboxLocation) {
// sandboxLocation = Components.classes["@mozilla.org/systemprincipal;1"].createInstance(Components.interfaces.nsIPrincipal);
var sandbox = this.sandbox = new Components.utils.Sandbox(sandboxLocation, {wantComponents:false, wantGlobalProperties:["XMLHttpRequest"]});
var sandbox = this.sandbox = new Components.utils.Sandbox(
sandboxLocation,
{
wantComponents: false,
wantGlobalProperties: [
'atob',
'XMLHttpRequest'
]
}
);
this.sandbox.Zotero = {};
// import functions missing from global scope into Fx sandbox

View File

@ -54,6 +54,7 @@ Zotero.Translate.ItemSaver = function(options) {
this.attachmentMode = Zotero.Libraries.get(this._libraryID).filesEditable ? options.attachmentMode :
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
this._forceTagType = options.forceTagType;
this._referrer = options.referrer;
this._cookieSandbox = options.cookieSandbox;
this._proxy = options.proxy;
@ -141,6 +142,7 @@ Zotero.Translate.ItemSaver.prototype = {
for (let i=0; i<specialFields.notes.length; i++) {
yield this._saveNote(specialFields.notes[i], myID);
}
item.notes = specialFields.notes;
}
// handle attachments
@ -634,6 +636,7 @@ Zotero.Translate.ItemSaver.prototype = {
title,
fileBaseName,
contentType: mimeType,
referrer: this._referrer,
cookieSandbox: this._cookieSandbox,
collections: !parentItemID ? this._collections : undefined
});

View File

@ -68,7 +68,7 @@ Zotero.Translators = new function() {
_cache = {"import":[], "export":[], "web":[], "webWithTargetAll":[], "search":[]};
_translators = {};
var sql = "SELECT fileName, metadataJSON, lastModifiedTime FROM translatorCache";
var sql = "SELECT rowid, fileName, metadataJSON, lastModifiedTime FROM translatorCache";
var dbCacheResults = yield Zotero.DB.queryAsync(sql);
var dbCache = {};
for (let i = 0; i < dbCacheResults.length; i++) {
@ -116,10 +116,23 @@ Zotero.Translators = new function() {
// Get JSON from cache if possible
if (memCacheJSON || dbCacheEntry) {
try {
var translator = Zotero.Translators.load(
memCacheJSON || dbCacheEntry.metadataJSON, path
);
}
catch (e) {
Zotero.logError(e);
Zotero.debug(memCacheJSON || dbCacheEntry.metadataJSON, 1);
// If JSON is invalid, clear from cache
yield Zotero.DB.queryAsync(
"DELETE FROM translatorCache WHERE fileName=?",
fileName
);
continue;
}
}
// Otherwise, load from file
else {
try {
@ -198,7 +211,8 @@ Zotero.Translators = new function() {
for (let fileName in dbCache) {
if (!filesInCache[fileName]) {
yield Zotero.DB.queryAsync(
"DELETE FROM translatorCache WHERE fileName = ?", fileName
"DELETE FROM translatorCache WHERE rowid=?",
dbCache[fileName].rowid
);
}
}

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