mirror of
https://github.com/zotero/zotero.git
synced 2026-10-12 03:48:16 +00:00
Compare commits
396 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f68dc7c90d | ||
|
|
55e2366c1f | ||
|
|
ecbee6ee26 | ||
|
|
fe8308c9dd | ||
|
|
90589606c2 | ||
|
|
77e0c71c74 | ||
|
|
ac2c001291 | ||
|
|
c641b28a9f | ||
|
|
31fc418a97 | ||
|
|
b213b8f15d | ||
|
|
7e6f7ac4b3 | ||
|
|
5327616376 | ||
|
|
9a7486e53c | ||
|
|
1d6cae08d2 | ||
|
|
ce16a28f4a | ||
|
|
400396be25 | ||
|
|
41c0d9346e | ||
|
|
543bfdabd3 | ||
|
|
dcfce382d9 | ||
|
|
5ad5598089 | ||
|
|
a66db9b52b | ||
|
|
49b08df0dd | ||
|
|
2e4fed4000 | ||
|
|
db99d4cfe1 | ||
|
|
85c35a2270 | ||
|
|
3cb5837bc6 | ||
|
|
7b614962c6 | ||
|
|
c8109fef18 | ||
|
|
1497524bab | ||
|
|
cd88d1f661 | ||
|
|
23ebf277fe | ||
|
|
42a4649420 | ||
|
|
778fba60cb | ||
|
|
bba09658e1 | ||
|
|
b298a48cda | ||
|
|
8a1dd3637c | ||
|
|
e024449732 | ||
|
|
14bb46f434 | ||
|
|
bc07bf803c | ||
|
|
367dfdf969 | ||
|
|
c57c3c470e | ||
|
|
a1d06066b2 | ||
|
|
1f45c44b86 | ||
|
|
8ac77bf9f0 | ||
|
|
59ba212cf8 | ||
|
|
935c67bc01 | ||
|
|
adf6f9f2fb | ||
|
|
bdadd363d3 | ||
|
|
c3cde7784d | ||
|
|
d7a8b2bb87 | ||
|
|
4ae4d98417 | ||
|
|
a9f1526bfc | ||
|
|
f868f4f713 | ||
|
|
f3c0c01fe1 | ||
|
|
0bd39ef42e | ||
|
|
a78920b16f | ||
|
|
d866a10a2b | ||
|
|
0f9024dc98 | ||
|
|
d8f7626dd4 | ||
|
|
c8da33d19c | ||
|
|
0f63b2e654 | ||
|
|
19be7b9b70 | ||
|
|
52a1981747 | ||
|
|
19e1b231de | ||
|
|
b956d9b429 | ||
|
|
962cc23d1a | ||
|
|
bbf72bfbcf | ||
|
|
b924720a2d | ||
|
|
34bb51f58a | ||
|
|
3aae80ca7d | ||
|
|
ce60ce90f6 | ||
|
|
6d0e5959e6 | ||
|
|
a5fb64f295 | ||
|
|
87fbf3c7fb | ||
|
|
c0bc2bafed | ||
|
|
886500812e | ||
|
|
4a06053d72 | ||
|
|
0ffe3122c2 | ||
|
|
1f721feab7 | ||
|
|
9f53b48984 | ||
|
|
9dad14c244 | ||
|
|
6374aea1c8 | ||
|
|
be6eee5412 | ||
|
|
5919f0e77a | ||
|
|
f006995676 | ||
|
|
665473b108 | ||
|
|
21777af980 | ||
|
|
4fbb89a588 | ||
|
|
ff38ff5b9d | ||
|
|
9d2c7d44f8 | ||
|
|
3678639273 | ||
|
|
52a079044d | ||
|
|
7e61be9799 | ||
|
|
0862e1e1bc | ||
|
|
f6dd47dd1f | ||
|
|
86c56951df | ||
|
|
51762e1c41 | ||
|
|
5697f12310 | ||
|
|
4184dc18d2 | ||
|
|
da392518ed | ||
|
|
1a83b472e8 | ||
|
|
7262465109 | ||
|
|
91c385a67a | ||
|
|
48ad7c0ab7 | ||
|
|
2082b68dd3 | ||
|
|
dcd6cdbcb3 | ||
|
|
d27fc31ef0 | ||
|
|
95e46ac8a2 | ||
|
|
41b0e64109 | ||
|
|
77eb325b26 | ||
|
|
a9dc25decc | ||
|
|
df3856f735 | ||
|
|
6a0ecd243d | ||
|
|
1422d84bd6 | ||
|
|
3d8bbb3e55 | ||
|
|
f33cf1cde3 | ||
|
|
deb5d1cd27 | ||
|
|
29006c556d | ||
|
|
2db90511f9 | ||
|
|
8135895313 | ||
|
|
dfdc66a41c | ||
|
|
1106685f2d | ||
|
|
deb17d450e | ||
|
|
89bc094690 | ||
|
|
1a1c08890f | ||
|
|
a48e0f2437 | ||
|
|
ac4308b3e7 | ||
|
|
c3c0b363c1 | ||
|
|
ae14d433f9 | ||
|
|
b91d85f288 | ||
|
|
26847c672f | ||
|
|
a505c94751 | ||
|
|
52176dc30a | ||
|
|
0e12126436 | ||
|
|
d131fe3dc9 | ||
|
|
c2941738e6 | ||
|
|
0aae101196 | ||
|
|
02098a9a34 | ||
|
|
378d8180e8 | ||
|
|
e3d13826c8 | ||
|
|
6fc5b037d9 | ||
|
|
6a4a329ec6 | ||
|
|
656acf3908 | ||
|
|
1920a5efd7 | ||
|
|
13ab3de1a1 | ||
|
|
47610d0faa | ||
|
|
42e042c6c6 | ||
|
|
b9a5fd9f78 | ||
|
|
cbb524d257 | ||
|
|
93ea818bf2 | ||
|
|
d2243cc339 | ||
|
|
f06267e4cd | ||
|
|
624101832e | ||
|
|
6bc1551f7f | ||
|
|
a8a0cabcc4 | ||
|
|
9d80c54d5d | ||
|
|
1c8554d527 | ||
|
|
5cea183ae3 | ||
|
|
92434dbb78 | ||
|
|
2c5a91682e | ||
|
|
3cc54eaf95 | ||
|
|
7f91a0c1a8 | ||
|
|
77fdc8e40c | ||
|
|
c5a769285b | ||
|
|
fe09afa0a6 | ||
|
|
ab2bec936e | ||
|
|
af66712c77 | ||
|
|
7db16843b3 | ||
|
|
a4eafd0307 | ||
|
|
4e1754b960 | ||
|
|
238c7ce410 | ||
|
|
8a15e5abe0 | ||
|
|
159c112257 | ||
|
|
70f9462913 | ||
|
|
4b5e2eae1b | ||
|
|
c4876fd5ae | ||
|
|
7fcc1d35a4 | ||
|
|
d0d3ed731d | ||
|
|
f2bfaeb7a9 | ||
|
|
2fa67bae64 | ||
|
|
a2db01c633 | ||
|
|
2b9e9c174f | ||
|
|
aa0ef7012c | ||
|
|
73306ee5fb | ||
|
|
d8d6054927 | ||
|
|
410ebc288b | ||
|
|
55095ba354 | ||
|
|
3ba6343668 | ||
|
|
d37aa37629 | ||
|
|
3582405f55 | ||
|
|
78eb857e73 | ||
|
|
618e71b5f1 | ||
|
|
7bada8ce0c | ||
|
|
af436d34a8 | ||
|
|
e45becce2a | ||
|
|
fb4f7b7932 | ||
|
|
fd8aa4433c | ||
|
|
29dd0cf5dc | ||
|
|
28329aa785 | ||
|
|
c5d418fc16 | ||
|
|
e9c6c76e79 | ||
|
|
f13966894f | ||
|
|
b7cc1d549b | ||
|
|
9a3f8dbc94 | ||
|
|
b594ad0b0a | ||
|
|
4dd1851140 | ||
|
|
006dd20d5b | ||
|
|
55afe7c08d | ||
|
|
9fadfac7ee | ||
|
|
38ae3a2600 | ||
|
|
473dd2ed64 | ||
|
|
e3b1cbd4eb | ||
|
|
3b93147052 | ||
|
|
b9a1e93518 | ||
|
|
99084084d4 | ||
|
|
64047f181a | ||
|
|
12b1897478 | ||
|
|
8fc9ebe4ab | ||
|
|
5162845179 | ||
|
|
2569ad6506 | ||
|
|
4fdf43ed92 | ||
|
|
9691fcafc2 | ||
|
|
6aad0cbb9c | ||
|
|
a7e6fc330c | ||
|
|
677499865a | ||
|
|
0e60389ee4 | ||
|
|
2271913e49 | ||
|
|
ff5e09201d | ||
|
|
0b89b15f66 | ||
|
|
f6e11981e3 | ||
|
|
230fc7f2df | ||
|
|
f179f0a54f | ||
|
|
ea09aac616 | ||
|
|
4a49ffec0a | ||
|
|
0d09618865 | ||
|
|
b96fcd41b1 | ||
|
|
c13d17b5e6 | ||
|
|
eb255c5868 | ||
|
|
88217492f9 | ||
|
|
64897455f4 | ||
|
|
5015b0e4cc | ||
|
|
a2d378448a | ||
|
|
32b64b3cf1 | ||
|
|
66fccec6b4 | ||
|
|
894309d061 | ||
|
|
b24c7577ec | ||
|
|
ca4ced1e9f | ||
|
|
6646be66d0 | ||
|
|
ef0811bd4c | ||
|
|
f893206dfd | ||
|
|
ae5de2c2f5 | ||
|
|
a3b45a74e9 | ||
|
|
e474ca524b | ||
|
|
bc0f6203e1 | ||
|
|
91ae576074 | ||
|
|
c255104ada | ||
|
|
75f5d6aca1 | ||
|
|
d156bef42a | ||
|
|
79246196ac | ||
|
|
262e1964ae | ||
|
|
f4dfea6849 | ||
|
|
999f78b0f5 | ||
|
|
2e6321a4bf | ||
|
|
35a1bb20cb | ||
|
|
8c80ea431e | ||
|
|
3abd720957 | ||
|
|
8379540719 | ||
|
|
b373291c02 | ||
|
|
3391f3bc99 | ||
|
|
6ca854a018 | ||
|
|
fa32eb6790 | ||
|
|
96c4427d6c | ||
|
|
5a8b5a1829 | ||
|
|
f368a53311 | ||
|
|
49020b02a2 | ||
|
|
20c6fe6737 | ||
|
|
80351a496b | ||
|
|
2d2e787111 | ||
|
|
eaba3ae099 | ||
|
|
4746300d33 | ||
|
|
85b0c9dbad | ||
|
|
318e4852e9 | ||
|
|
3dc3359cff | ||
|
|
b505c630d3 | ||
|
|
21b7baee35 | ||
|
|
df59cee94a | ||
|
|
d1275f9964 | ||
|
|
d4a7de2e8d | ||
|
|
864fd03c87 | ||
|
|
d3cb7c86d9 | ||
|
|
e4e3c72036 | ||
|
|
0857cefb0c | ||
|
|
9b7b4fe134 | ||
|
|
a9c3fc7790 | ||
|
|
20d0f103fd | ||
|
|
20eca3781d | ||
|
|
87455d28c3 | ||
|
|
2048319541 | ||
|
|
d03b0a716d | ||
|
|
bb2b37a73a | ||
|
|
7ec54fdaa9 | ||
|
|
5a659b070d | ||
|
|
ad96323881 | ||
|
|
fc0f6157d0 | ||
|
|
6733f2edca | ||
|
|
e1997620a4 | ||
|
|
a396771cbc | ||
|
|
0e0997078b | ||
|
|
0785b3a086 | ||
|
|
ba5cb7d5c6 | ||
|
|
d822da4c06 | ||
|
|
a297692487 | ||
|
|
6921268dc0 | ||
|
|
ba1906c054 | ||
|
|
b5be7cbd57 | ||
|
|
5c61815f36 | ||
|
|
b5ca5aed3a | ||
|
|
4b6598acc4 | ||
|
|
14a6ce000e | ||
|
|
9d0cd005f8 | ||
|
|
26db75971e | ||
|
|
cc45e9019b | ||
|
|
98d6f40e4a | ||
|
|
2cad5b00d1 | ||
|
|
95239db0fd | ||
|
|
4c063e7b6e | ||
|
|
07f1517712 | ||
|
|
f0e0971c69 | ||
|
|
acba90f272 | ||
|
|
26a5e2900a | ||
|
|
34d66381d1 | ||
|
|
f7dbcdde4b | ||
|
|
c4563af381 | ||
|
|
56e7f0a10f | ||
|
|
3a160b2630 | ||
|
|
124c18d54d | ||
|
|
57c582e171 | ||
|
|
cf8819cc06 | ||
|
|
fe74b02d28 | ||
|
|
1dcc2728dd | ||
|
|
c5dbf0169f | ||
|
|
3f282fc25f | ||
|
|
7163a196f8 | ||
|
|
8c0499468e | ||
|
|
629892b0b8 | ||
|
|
2ff67c987e | ||
|
|
a8ed30ce80 | ||
|
|
43f15e7517 | ||
|
|
df11227e0a | ||
|
|
d418d79e70 | ||
|
|
42aebccbf4 | ||
|
|
7b6e19435b | ||
|
|
f650f95081 | ||
|
|
0357026865 | ||
|
|
92d89becc7 | ||
|
|
49f9642c0d | ||
|
|
94372111f5 | ||
|
|
f0b896d282 | ||
|
|
ec68216310 | ||
|
|
b215d93ac9 | ||
|
|
bf1480f8f0 | ||
|
|
6f089445a5 | ||
|
|
14e62ee34f | ||
|
|
43c86c0e58 | ||
|
|
6f3f5d2ea8 | ||
|
|
4fb0661b50 | ||
|
|
57ab92b5b9 | ||
|
|
7b20972c27 | ||
|
|
dc045250a6 | ||
|
|
256bd157ed | ||
|
|
2b45b7fea3 | ||
|
|
1d5fd1308a | ||
|
|
d8069fac50 | ||
|
|
18516a33d9 | ||
|
|
b658f09dd8 | ||
|
|
5799bdd554 | ||
|
|
62c7b4db2b | ||
|
|
caa0b05e5f | ||
|
|
5095543bef | ||
|
|
6af3b0276a | ||
|
|
67700f8a2c | ||
|
|
d12f3eda61 | ||
|
|
deabf3f042 | ||
|
|
28ed3e34b5 | ||
|
|
354183c25d | ||
|
|
b0f69ef7e3 | ||
|
|
ebc23d73b0 | ||
|
|
419f74379b | ||
|
|
603b843f71 | ||
|
|
1e3959020d | ||
|
|
3fd0abf6b0 | ||
|
|
111a254696 | ||
|
|
69616500d8 | ||
|
|
693f9a7e1b | ||
|
|
0c0afb129b | ||
|
|
a11c5987ad |
411 changed files with 18681 additions and 7547 deletions
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
|
|
@ -5,14 +5,14 @@ jobs:
|
|||
name: Build, Upload, Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 18
|
||||
#cache: npm
|
||||
|
||||
# On GitHub
|
||||
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
- name: Cache Firefox
|
||||
id: firefox-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: firefox-x86_64
|
||||
key: firefox-60.9.0esr
|
||||
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
|
||||
- name: Cache Node modules
|
||||
id: node-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: node-modules-${{ hashFiles('package-lock.json') }}
|
||||
|
|
@ -53,6 +53,9 @@ jobs:
|
|||
|
||||
- name: Build Zotero
|
||||
run: npm run build
|
||||
# Currently necessary for pdf-worker Webpack: https://stackoverflow.com/a/69746937
|
||||
env:
|
||||
NODE_OPTIONS: --openssl-legacy-provider
|
||||
|
||||
- name: Upload deployment ZIP
|
||||
if: |
|
||||
|
|
@ -76,7 +79,7 @@ jobs:
|
|||
|
||||
- name: Cache utilities Node modules
|
||||
id: utilities-node-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: chrome/content/zotero/xpcom/utilities/node_modules
|
||||
key: utilities-node-modules-${{ hashFiles('chrome/content/zotero/xpcom/utilities/package-lock.json') }}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Bug Reports and Feature Requests
|
||||
|
||||
Zotero does not use GitHub Issues for bug reports or feature requests. Please post all such requests to the [Zotero Forums](https://forums.zotero.org), where Zotero developers and many others can help. See [How Zotero Support Works](https://www.zotero.org/support/zotero_support) for more information.
|
||||
Zotero does not use GitHub Issues for bug reports, feature requests, or support questions. Please post all such requests to the [Zotero Forums](https://forums.zotero.org), where Zotero developers and many others can help. See [How Zotero Support Works](https://www.zotero.org/support/zotero_support) for more information.
|
||||
|
||||
For confirmed bugs or agreed-upon changes, Zotero developers will create new issues in the relevant repositories.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ content zotero-platform chrome/content/zotero-platform/unix/ os=FreeBSD
|
|||
content zotero-platform chrome/content/zotero-platform/unix/ os=OpenBSD
|
||||
|
||||
content zotero-platform-version chrome/content/zotero-platform/default-version/
|
||||
# Big Sur shows up as 10.16 when linked against 10.11
|
||||
content zotero-platform-version chrome/content/zotero-platform/mac-big-sur/ os=Darwin osversion=10.16
|
||||
# Big Sur and later show up as 10.16 when linked against 10.11
|
||||
# (Usually: https://forums.zotero.org/discussion/98406/zotero-will-not-open-mac-os-monterey)
|
||||
content zotero-platform-version chrome/content/zotero-platform/mac-big-sur/ os=Darwin osversion>=10.16
|
||||
#content zotero-platform-version chrome/content/zotero-platform/mac-big-sur/ os=Darwin
|
||||
|
||||
resource zotero resource/
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ var Scaffold = new function () {
|
|||
var _translatorsLoadedPromise;
|
||||
var _translatorProvider = null;
|
||||
var _lastModifiedTime = 0;
|
||||
var _needRebuildTranslatorSuggestions = true;
|
||||
|
||||
var _editors = {};
|
||||
|
||||
|
|
@ -98,16 +99,7 @@ var Scaffold = new function () {
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Set font size from general pref
|
||||
Zotero.setFontSize(document.getElementById('scaffold-pane'));
|
||||
|
||||
// Set font size of code editor
|
||||
var size = Zotero.Prefs.get("scaffold.fontSize");
|
||||
if (size) {
|
||||
this.setFontSize(size);
|
||||
}
|
||||
|
||||
|
||||
this.generateTranslatorID();
|
||||
|
||||
// Add List fields help menu entries for all other item types
|
||||
|
|
@ -146,6 +138,15 @@ var Scaffold = new function () {
|
|||
this.initCodeEditor();
|
||||
this.initTestsEditor();
|
||||
|
||||
// Set font size from general pref
|
||||
Zotero.setFontSize(document.getElementById('scaffold-pane'));
|
||||
|
||||
// Set font size of code editor
|
||||
var size = Zotero.Prefs.get("scaffold.fontSize");
|
||||
if (size) {
|
||||
this.setFontSize(size);
|
||||
}
|
||||
|
||||
// Listen for Scaffold coming to the foreground and reload translators
|
||||
window.addEventListener('activate', () => this.reloadTranslators());
|
||||
|
||||
|
|
@ -156,6 +157,7 @@ var Scaffold = new function () {
|
|||
|
||||
onLoadComplete: () => {
|
||||
document.getElementById('cmd_load').removeAttribute('disabled');
|
||||
_needRebuildTranslatorSuggestions = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -267,6 +269,7 @@ var Scaffold = new function () {
|
|||
|
||||
monaco.languages.registerCodeLensProvider('javascript', this.createRunCodeLensProvider(monaco, editor));
|
||||
monaco.languages.registerHoverProvider('javascript', this.createHoverProvider(monaco, editor));
|
||||
monaco.languages.registerCompletionItemProvider('javascript', this.createCompletionProvider(monaco, editor));
|
||||
|
||||
let tsLib = await Zotero.File.getContentsAsync(
|
||||
OS.Path.join(Scaffold_Translators.getDirectory(), 'index.d.ts'));
|
||||
|
|
@ -500,6 +503,43 @@ var Scaffold = new function () {
|
|||
resolveCodeLens: (_model, codeLens, _token) => codeLens
|
||||
};
|
||||
};
|
||||
|
||||
this.createCompletionProvider = function (monaco, editor) {
|
||||
let suggestions = null;
|
||||
return {
|
||||
provideCompletionItems(model, position) {
|
||||
let prefixText = model.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: 1,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: position.column
|
||||
});
|
||||
if (/setTranslator\([^)]*$/.test(prefixText)) {
|
||||
let word = model.getWordUntilPosition(position);
|
||||
let range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endColumn: word.endColumn
|
||||
};
|
||||
if (!suggestions || _needRebuildTranslatorSuggestions) {
|
||||
// Cache the suggestions minus the range field
|
||||
suggestions = [...Scaffold_Translators._translators.entries()].map(([id, meta]) => {
|
||||
return {
|
||||
label: `${meta.translator.label}: '${id}'`,
|
||||
kind: monaco.languages.CompletionItemKind.Constant,
|
||||
insertText: `'${id}'`
|
||||
};
|
||||
});
|
||||
_needRebuildTranslatorSuggestions = false;
|
||||
}
|
||||
// Add the range to each suggestion before returning
|
||||
return { suggestions: suggestions.map(s => ({ ...s, range })) };
|
||||
}
|
||||
return { suggestions: [] };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
this.updateModelMarkers = function (translatorPath) {
|
||||
runESLint(translatorPath)
|
||||
|
|
@ -802,7 +842,8 @@ var Scaffold = new function () {
|
|||
var fieldList = Zotero.ItemFields.getItemTypeFields(typeID);
|
||||
for (let field of fieldList) {
|
||||
var key = Zotero.ItemFields.getName(field);
|
||||
outputObject[key] = "";
|
||||
let fieldLocalizedName = Zotero.ItemFields.getLocalizedString(field);
|
||||
outputObject[key] = fieldLocalizedName;
|
||||
}
|
||||
var creatorList = Zotero.CreatorTypes.getTypesForItemType(typeID);
|
||||
var creators = [];
|
||||
|
|
@ -1258,6 +1299,12 @@ var Scaffold = new function () {
|
|||
function _writeTestsToPane(tests) {
|
||||
_writeToEditor(_editors.tests, _stringifyTests(tests));
|
||||
}
|
||||
|
||||
function _confirmCreateExpectedFailTest() {
|
||||
return Services.prompt.confirm(null,
|
||||
'Detection Failed',
|
||||
'Add test ensuring that detection always fails on this page?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimics most of the behavior of Zotero.Item#fromJSON. Most importantly,
|
||||
|
|
@ -1475,7 +1522,7 @@ var Scaffold = new function () {
|
|||
|
||||
if (level < 2 && value.items) {
|
||||
// Test object. Arrange properties in set order
|
||||
let order = ['type', 'url', 'input', 'defer', 'items'];
|
||||
let order = ['type', 'url', 'input', 'defer', 'detectedItemType', 'items'];
|
||||
for (let i = 0; i < order.length; i++) {
|
||||
let val = processRow(order[i], value[order[i]]);
|
||||
if (val === undefined) continue;
|
||||
|
|
@ -1582,14 +1629,16 @@ var Scaffold = new function () {
|
|||
_translatorProvider
|
||||
);
|
||||
return new Promise(
|
||||
(resolve, reject) => tester.newTest(input, function (obj, newTest) { // "done" handler for do
|
||||
if (newTest) {
|
||||
resolve(_sanitizeItemsInTest(newTest));
|
||||
}
|
||||
else {
|
||||
reject(new Error('Creation failed'));
|
||||
}
|
||||
})
|
||||
(resolve, reject) => tester.newTest(input,
|
||||
(obj, newTest) => { // "done" handler for do
|
||||
if (newTest) {
|
||||
resolve(_sanitizeItemsInTest(newTest));
|
||||
}
|
||||
else {
|
||||
reject(new Error('Creation failed'));
|
||||
}
|
||||
},
|
||||
_confirmCreateExpectedFailTest)
|
||||
);
|
||||
}
|
||||
else if (type == "import" || type == "search") {
|
||||
|
|
@ -1729,6 +1778,9 @@ var Scaffold = new function () {
|
|||
var url = item.getElementsByTagName("listcell")[0].getAttribute("label");
|
||||
var test = JSON.parse(item.getUserData("test-string"));
|
||||
var urlOrData = (test.input !== undefined) ? test.input : url;
|
||||
if (typeof urlOrData !== 'string') {
|
||||
urlOrData = JSON.stringify(urlOrData, null, '\t');
|
||||
}
|
||||
Zotero.Utilities.Internal.copyTextToClipboard(urlOrData);
|
||||
};
|
||||
|
||||
|
|
@ -1903,13 +1955,11 @@ var Scaffold = new function () {
|
|||
var test = this.testsToUpdate.shift();
|
||||
_logOutput("Updating test " + (this.numTestsTotal - this.testsToUpdate.length));
|
||||
|
||||
var me = this;
|
||||
|
||||
if (test.type == 'web') {
|
||||
_logOutput("Loading web page from " + test.url);
|
||||
var hiddenBrowser = Zotero.HTTP.loadDocuments(
|
||||
test.url,
|
||||
function (doc) {
|
||||
(doc) => {
|
||||
_logOutput("Page loaded");
|
||||
if (test.defer) {
|
||||
_logOutput("Waiting " + (Zotero_TranslatorTester.DEFER_DELAY / 1000)
|
||||
|
|
@ -1917,32 +1967,34 @@ var Scaffold = new function () {
|
|||
);
|
||||
}
|
||||
Zotero.setTimeout(
|
||||
function () {
|
||||
() => {
|
||||
doc = hiddenBrowser.contentDocument;
|
||||
if (doc.location.href != test.url) {
|
||||
_logOutput("Page URL differs from test. Will be updated. " + doc.location.href);
|
||||
}
|
||||
me.tester.newTest(doc, function (obj, newTest) {
|
||||
Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
|
||||
if (test.defer) {
|
||||
newTest.defer = true;
|
||||
}
|
||||
newTest = _sanitizeItemsInTest(newTest);
|
||||
me.newTests.push(newTest);
|
||||
me.testDoneCallback(newTest);
|
||||
me._updateTests();
|
||||
});
|
||||
this.tester.newTest(doc,
|
||||
(obj, newTest) => {
|
||||
Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
|
||||
if (test.defer) {
|
||||
newTest.defer = true;
|
||||
}
|
||||
newTest = _sanitizeItemsInTest(newTest);
|
||||
this.newTests.push(newTest);
|
||||
this.testDoneCallback(newTest);
|
||||
this._updateTests();
|
||||
},
|
||||
_confirmCreateExpectedFailTest);
|
||||
},
|
||||
test.defer ? Zotero_TranslatorTester.DEFER_DELAY : 0,
|
||||
true
|
||||
);
|
||||
},
|
||||
null,
|
||||
function (e) {
|
||||
(e) => {
|
||||
Zotero.logError(e);
|
||||
me.newTests.push(false);
|
||||
me.testDoneCallback(false);
|
||||
me._updateTests();
|
||||
this.newTests.push(false);
|
||||
this.testDoneCallback(false);
|
||||
this._updateTests();
|
||||
},
|
||||
true
|
||||
);
|
||||
|
|
@ -1960,15 +2012,15 @@ var Scaffold = new function () {
|
|||
|
||||
// Re-runs the test.
|
||||
// TranslatorTester doesn't handle these correctly, so we do it manually
|
||||
_run(methods[test.type], test.input, null, function (obj, item) {
|
||||
_run(methods[test.type], test.input, null, (obj, item) => {
|
||||
if (item) {
|
||||
test.items.push(Zotero_TranslatorTester._sanitizeItem(item));
|
||||
}
|
||||
}, null, function () {
|
||||
}, null, () => {
|
||||
if (!test.items.length) test = false;
|
||||
me.newTests.push(test);
|
||||
me.testDoneCallback(test);
|
||||
me._updateTests();
|
||||
this.newTests.push(test);
|
||||
this.testDoneCallback(test);
|
||||
this._updateTests();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
*/
|
||||
|
||||
|
||||
async function detectWeb(doc, url) {
|
||||
function detectWeb(doc, url) {
|
||||
// TODO: adjust the logic here
|
||||
if ($$CURSOR$$url.includes('/article/')) {
|
||||
return 'newspaperArticle';
|
||||
|
|
@ -52,16 +52,19 @@ function getSearchResults(doc, checkOnly) {
|
|||
}
|
||||
|
||||
async function doWeb(doc, url) {
|
||||
if (await detectWeb(doc, url) == 'multiple') {
|
||||
if (detectWeb(doc, url) == 'multiple') {
|
||||
let items = await Zotero.selectItems(getSearchResults(doc, false));
|
||||
if (items) {
|
||||
await Promise.all(
|
||||
Object.keys(items)
|
||||
.map(url => requestDocument(url).then(doc => scrape(doc, url)))
|
||||
);
|
||||
if (!items) return;
|
||||
for (let url of Object.keys(items)) {
|
||||
await scrape(await requestDocument(url));
|
||||
}
|
||||
}
|
||||
else {
|
||||
await scrape(doc, url);
|
||||
}
|
||||
}
|
||||
|
||||
async function scrape(doc, url = doc.location.href) {
|
||||
// TODO: implement or add a scrape function template
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
async function scrape(doc, url) {
|
||||
async function scrape(doc, url = doc.location.href) {
|
||||
// TODO adjust the url building
|
||||
let m = url.match(/FId=([\w\d]+)&/);
|
||||
if (m) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
async function scrape(doc, url) {
|
||||
async function scrape(doc, url = doc.location.href) {
|
||||
let translator = Zotero.loadTranslator('web');
|
||||
// Embedded Metadata
|
||||
translator.setTranslator('951c027d-74ac-47d4-a107-9c3069ab7b48');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
async function scrape(doc, url) {
|
||||
async function scrape(doc, url = doc.location.href) {
|
||||
// TODO adjust the selector for the lines here
|
||||
let lines = doc.querySelectorAll('table#marcData tr');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
async function scrape(doc, url) {
|
||||
async function scrape(doc, url = doc.location.href) {
|
||||
let DOI = url.match(/\/(10\.[^#?]+)/)[1];
|
||||
// TODO adjust the URL here
|
||||
let risURL = `http://citation-needed.services.springer.com/v2/references/${DOI}?format=refman&flavour=citation`;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ row > label:first-child, .creator-type-label, #more-creators-label
|
|||
max-height: 7px;
|
||||
}
|
||||
|
||||
.zotero-focusable:focus {
|
||||
/* At least in Fx60, the color doesn't quite match Monterey's native textbox outline,
|
||||
but it will have to do for now */
|
||||
outline: 2px solid -moz-mac-focusring !important;
|
||||
}
|
||||
|
||||
textbox
|
||||
{
|
||||
margin: -1px 5px -1px 0;
|
||||
|
|
|
|||
|
|
@ -162,7 +162,8 @@ input {
|
|||
|
||||
/* This seems to be necessary to center the tabs. Not sure why. */
|
||||
.zotero-editpane-tabs > tab:last-of-type > hbox .tab-text {
|
||||
margin: 2px 9px 2px 9px !important;
|
||||
margin-left: 9px !important;
|
||||
margin-right: 9px !important;
|
||||
}
|
||||
|
||||
.zotero-editpane-tabs > tab[selected=true] > hbox .tab-text {
|
||||
|
|
|
|||
|
|
@ -63,3 +63,7 @@ tab {
|
|||
background-color: #ececec;
|
||||
border-top: 1px solid hsla(0, 0%, 0%, 0.2);
|
||||
}
|
||||
|
||||
.zotero-tb-button:focus {
|
||||
border: 1px dotted #999;
|
||||
}
|
||||
|
|
@ -40,6 +40,14 @@
|
|||
<overlay id="menuOverlay"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<key id="key_quitApplication"
|
||||
key="&quitApplicationCmd.accesskey;"
|
||||
command="cmd_quitApplication"
|
||||
modifiers="accel"/>
|
||||
</keyset>
|
||||
|
||||
<menupopup id="menu_FilePopup">
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_FileQuitItem"
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<menuseparator/>
|
||||
<menuitem id="menu_FileQuitItem"
|
||||
label="&quitApplicationCmdWin.label;"
|
||||
key="key_quitApplication"
|
||||
accesskey="&quitApplicationCmdWin.accesskey;"
|
||||
command="cmd_quitApplication"/>
|
||||
</menupopup>
|
||||
<menupopup id="menu_EditPopup">
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ var ZoteroAdvancedSearch = new function() {
|
|||
ref: _searchBox.search,
|
||||
isSearchMode: () => true,
|
||||
getItems: async function () {
|
||||
await Zotero.Libraries.get(_libraryID).waitForDataLoad('item');
|
||||
|
||||
var search = _searchBox.search.clone();
|
||||
search.libraryID = _libraryID;
|
||||
var ids = await search.search();
|
||||
|
|
|
|||
|
|
@ -245,7 +245,9 @@
|
|||
urlField.setAttribute('hidden', false);
|
||||
if (this.clickableLink) {
|
||||
urlField.onclick = function (event) {
|
||||
ZoteroPane_Local.loadURI(this.value, event)
|
||||
if (event.button != 2) {
|
||||
ZoteroPane_Local.loadURI(this.value, event)
|
||||
}
|
||||
};
|
||||
urlField.className = 'zotero-text-link';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
}
|
||||
|
||||
this._item = val;
|
||||
this._lastTabIndex = null;
|
||||
this.refresh();
|
||||
]]></setter>
|
||||
</property>
|
||||
|
|
@ -391,7 +392,7 @@
|
|||
&& Zotero.ItemFields.isDate(fieldName)
|
||||
// TEMP - NSF
|
||||
&& fieldName != 'dateSent') {
|
||||
this.addDateRow(fieldNames[i], this.item.getField(fieldName, true), tabindex);
|
||||
this.addDateRow(fieldName, this.item.getField(fieldName, true), tabindex);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -422,7 +423,7 @@
|
|||
&& Zotero.Utilities.isHTTPURL(val, true)) {
|
||||
label.classList.add("pointer");
|
||||
// TODO: make getFieldValue non-private and use below instead
|
||||
label.setAttribute("onclick", "Zotero.launchURL(this.nextSibling.firstChild ? this.nextSibling.firstChild.nodeValue : this.nextSibling.value)");
|
||||
label.addEventListener('click', () => Zotero.launchURL(label.nextSibling.firstChild ? label.nextSibling.firstChild.nodeValue : label.nextSibling.value));
|
||||
label.setAttribute("tooltiptext", Zotero.getString('pane.item.viewOnline.tooltip'));
|
||||
}
|
||||
else if (fieldName == 'DOI' && val && typeof val == 'string') {
|
||||
|
|
@ -437,17 +438,14 @@
|
|||
.replace(/%/g, '%25')
|
||||
.replace(/"/g, '%22');
|
||||
label.classList.add("pointer");
|
||||
label.setAttribute("onclick", "ZoteroPane_Local.loadURI('" + doi + "', event)");
|
||||
label.addEventListener('click', event => ZoteroPane_Local.loadURI(doi, event));
|
||||
label.setAttribute("tooltiptext", Zotero.getString('pane.item.viewOnline.tooltip'));
|
||||
valueElement.oncontextmenu = () => {
|
||||
this._id('zotero-doi-menu').openPopup(valueElement);
|
||||
};
|
||||
|
||||
var openURLMenuItem = this._id('zotero-doi-menu-view-online');
|
||||
openURLMenuItem.setAttribute("oncommand", "ZoteroPane_Local.loadURI('" + doi + "', event)");
|
||||
openURLMenuItem.addEventListener('command', event => ZoteroPane_Local.loadURI(doi, event));
|
||||
|
||||
var copyMenuItem = this._id('zotero-doi-menu-copy');
|
||||
copyMenuItem.setAttribute("oncommand", "Zotero.Utilities.Internal.copyTextToClipboard('" + doi + "')");
|
||||
copyMenuItem.addEventListener('command', () => Zotero.Utilities.Internal.copyTextToClipboard(doi));
|
||||
}
|
||||
}
|
||||
else if (fieldName == 'abstractNote') {
|
||||
|
|
@ -616,7 +614,7 @@
|
|||
this.disableCreatorAddButtons();
|
||||
}
|
||||
|
||||
// Move to next or previous field if (shift-)tab was pressed
|
||||
// Refocus on last stored tab index. This is required e.g. when remote change triggers refresh.
|
||||
if (this._lastTabIndex && this._lastTabIndex != -1) {
|
||||
this._focusNextField(this._lastTabIndex);
|
||||
}
|
||||
|
|
@ -719,7 +717,7 @@
|
|||
<![CDATA[
|
||||
// getCreatorFields(), switchCreatorMode() and handleCreatorAutoCompleteSelect()
|
||||
// may need need to be adjusted if this DOM structure changes
|
||||
|
||||
var tabindex = this._tabIndexMinCreators + (this._creatorCount * 6);
|
||||
var fieldMode = Zotero.Prefs.get('lastCreatorFieldMode');
|
||||
var firstName = '';
|
||||
var lastName = '';
|
||||
|
|
@ -757,9 +755,15 @@
|
|||
}
|
||||
typeBox.setAttribute("fieldname", 'creator-' + this._creatorCount + '-typeID');
|
||||
if (this.editable) {
|
||||
typeBox.className = 'creator-type-label zotero-clicky';
|
||||
typeBox.className = 'creator-type-label zotero-clicky zotero-focusable';
|
||||
typeBox.setAttribute('ztabindex', tabindex - 1);
|
||||
typeBox.setAttribute('role', 'listbox');
|
||||
typeBox.addEventListener('focus', ev => {
|
||||
this._lastTabIndex = parseInt(ev.target.getAttribute('ztabindex'));
|
||||
});
|
||||
var img = document.createElement('image');
|
||||
typeBox.appendChild(img);
|
||||
typeBox.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
|
||||
}
|
||||
else {
|
||||
typeBox.className = 'creator-type-label';
|
||||
|
|
@ -772,12 +776,18 @@
|
|||
|
||||
var hbox = document.createElement("hbox");
|
||||
hbox.className = 'creator-type-value';
|
||||
|
||||
hbox.addEventListener('focusin', ev => {
|
||||
if(ev.target.classList.contains('zotero-focusable')) {
|
||||
this._lastTabIndex = parseInt(ev.target.getAttribute('ztabindex'));
|
||||
}
|
||||
});
|
||||
|
||||
// Name
|
||||
var firstlast = document.createElement("hbox");
|
||||
firstlast.className = 'creator-name-box';
|
||||
firstlast.setAttribute("flex","1");
|
||||
var tabindex = this._tabIndexMinCreators + (this._creatorCount * 2);
|
||||
|
||||
var fieldName = 'creator-' + this._creatorCount + '-lastName';
|
||||
var lastNameLabel = firstlast.appendChild(
|
||||
this.createValueElement(
|
||||
|
|
@ -820,29 +830,40 @@
|
|||
var toggleButton = document.createElement('label');
|
||||
toggleButton.setAttribute('fieldname',
|
||||
'creator-' + this._creatorCount + '-fieldMode');
|
||||
toggleButton.className = 'zotero-field-toggle zotero-clicky';
|
||||
toggleButton.setAttribute('ztabindex', tabindex + 2);
|
||||
toggleButton.setAttribute('role', 'button');
|
||||
toggleButton.className = 'zotero-field-toggle zotero-focusable zotero-clicky';
|
||||
toggleButton.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
|
||||
hbox.appendChild(toggleButton);
|
||||
|
||||
// Minus (-) button
|
||||
var removeButton = document.createElement('label');
|
||||
removeButton.setAttribute('ztabindex', tabindex + 3);
|
||||
removeButton.setAttribute('role', 'button');
|
||||
removeButton.setAttribute('aria-label', Zotero.getString('general.delete'));
|
||||
removeButton.setAttribute("value","-");
|
||||
removeButton.setAttribute("class","zotero-clicky zotero-clicky-minus");
|
||||
removeButton.setAttribute('class','zotero-clicky zotero-focusable zotero-clicky-minus');
|
||||
removeButton.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
|
||||
// If default first row, don't let user remove it
|
||||
if (defaultRow) {
|
||||
this.disableButton(removeButton);
|
||||
}
|
||||
else {
|
||||
removeButton.setAttribute("onclick",
|
||||
"document.getBindingParent(this).removeCreator("
|
||||
+ this._creatorCount
|
||||
+ ", this.parentNode.parentNode)");
|
||||
let index = this._creatorCount;
|
||||
removeButton.onclick = (ev) => {
|
||||
document.getBindingParent(ev.currentTarget)
|
||||
.removeCreator(index, ev.currentTarget.parentNode.parentNode);
|
||||
}
|
||||
}
|
||||
hbox.appendChild(removeButton);
|
||||
|
||||
// Plus (+) button
|
||||
var addButton = document.createElement('label');
|
||||
addButton.setAttribute('ztabindex', tabindex + 4);
|
||||
addButton.setAttribute("value","+");
|
||||
addButton.setAttribute("class", "zotero-clicky zotero-clicky-plus");
|
||||
addButton.setAttribute('role', 'button');
|
||||
addButton.setAttribute('class', 'zotero-clicky zotero-focusable zotero-clicky-plus');
|
||||
addButton.setAttribute('onkeypress', 'return document.getBindingParent(this).handleKeyPress(event)');
|
||||
// If row isn't saved, don't let user add more
|
||||
if (unsaved) {
|
||||
this.disableButton(addButton);
|
||||
|
|
@ -958,7 +979,11 @@
|
|||
button.style.background = `url("chrome://zotero/skin/textfield-dual${Zotero.hiDPISuffix}.png") center/21px auto no-repeat`;
|
||||
button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.two'));
|
||||
lastName.setAttribute('fieldMode', '1');
|
||||
button.setAttribute('onclick', "document.getBindingParent(this).switchCreatorMode(Zotero.getAncestorByTagName(this, 'row'), 0, false, true)");
|
||||
button.onclick = (ev) => {
|
||||
document
|
||||
.getBindingParent(ev.currentTarget)
|
||||
.switchCreatorMode(Zotero.getAncestorByTagName(ev.currentTarget, 'row'), 0, false, true);
|
||||
};
|
||||
lastName.setAttribute('flex', '1');
|
||||
delete lastName.style.width;
|
||||
delete lastName.style.maxWidth;
|
||||
|
|
@ -998,7 +1023,11 @@
|
|||
button.style.background = `url("chrome://zotero/skin/textfield-single${Zotero.hiDPISuffix}.png") center/21px auto no-repeat`;
|
||||
button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.one'));
|
||||
lastName.setAttribute('fieldMode', '0');
|
||||
button.setAttribute('onclick', "document.getBindingParent(this).switchCreatorMode(Zotero.getAncestorByTagName(this, 'row'), 1, false, true)");
|
||||
button.onclick = (ev) => {
|
||||
document
|
||||
.getBindingParent(ev.currentTarget)
|
||||
.switchCreatorMode(Zotero.getAncestorByTagName(ev.currentTarget, 'row'), 1, false, true)
|
||||
};
|
||||
lastName.setAttribute('flex', '0');
|
||||
|
||||
// appropriately truncate lastName
|
||||
|
|
@ -1391,6 +1420,13 @@
|
|||
valueElement.setAttribute('flex', '1');
|
||||
}
|
||||
|
||||
// Add popup menu on DOI field with value
|
||||
if (fieldName == 'DOI' && valueText) {
|
||||
valueElement.oncontextmenu = () => {
|
||||
this._id('zotero-doi-menu').openPopup(valueElement);
|
||||
};
|
||||
}
|
||||
|
||||
var firstSpace = valueText.indexOf(" ");
|
||||
|
||||
// To support newlines in Abstract and Extra fields, use multiple
|
||||
|
|
@ -1552,8 +1588,7 @@
|
|||
}
|
||||
else {
|
||||
// Add auto-complete for certain fields
|
||||
if (Zotero.ItemFields.isAutocompleteField(fieldName)
|
||||
|| fieldName == 'creator') {
|
||||
if (field == 'creator' || Zotero.ItemFields.isAutocompleteField(fieldName)) {
|
||||
t.setAttribute('type', 'autocomplete');
|
||||
t.setAttribute('autocompletesearch', 'zotero');
|
||||
|
||||
|
|
@ -1720,6 +1755,19 @@
|
|||
<![CDATA[
|
||||
var target = event.target;
|
||||
var focused = document.commandDispatcher.focusedElement;
|
||||
|
||||
if ((event.key == 'ArrowUp' || event.key === 'ArrowDown')
|
||||
&& target.classList.contains('creator-type-label')) {
|
||||
event.preventDefault();
|
||||
target.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === ' ' && target.classList.contains('zotero-focusable')) {
|
||||
event.preventDefault();
|
||||
target.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (event.keyCode)
|
||||
{
|
||||
|
|
@ -1932,8 +1980,6 @@
|
|||
|
||||
newVal = val;
|
||||
|
||||
// Reset creator mode settings here so that flex attribute gets reset
|
||||
this.switchCreatorMode(row, (otherFields.fieldMode ? 1 : 0), true);
|
||||
if (Zotero.ItemTypes.getName(this.item.itemTypeID) === "bookSection") {
|
||||
var creatorTypeLabels = document.getAnonymousNodes(this)[0].getElementsByClassName("creator-type-label");
|
||||
Zotero.debug(creatorTypeLabels[creatorTypeLabels.length-1] + "");
|
||||
|
|
@ -2018,6 +2064,11 @@
|
|||
// Disassociate textbox from label
|
||||
label.setAttribute('control', elem.getAttribute('id'));
|
||||
|
||||
if (field == 'creator') {
|
||||
// Set correct flex settings and fieldMode attribute
|
||||
this.switchCreatorMode(row, (otherFields.fieldMode ? 1 : 0), true);
|
||||
}
|
||||
|
||||
if (this.saveOnEdit) {
|
||||
await this.item.saveTx();
|
||||
}
|
||||
|
|
@ -2293,6 +2344,32 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="focusLastField">
|
||||
<body><![CDATA[
|
||||
const tabbableFields = this._dynamicFields.querySelectorAll('*[ztabindex]:not([disabled=true])');
|
||||
const last = tabbableFields[tabbableFields.length - 1];
|
||||
|
||||
if (last.classList.contains('zotero-focusable')) {
|
||||
last.focus();
|
||||
}
|
||||
// Fields need to be clicked
|
||||
else {
|
||||
last.click();
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="focusField">
|
||||
<parameter name="fieldName"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let field = document.getAnonymousNodes(this)[0].querySelector(`[fieldname="${fieldName}"][ztabindex]`);
|
||||
if (!field) return false;
|
||||
return this._focusNextField(field.getAttribute('ztabindex'));
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<!--
|
||||
|
|
@ -2312,7 +2389,7 @@
|
|||
tabindex = parseInt(tabindex);
|
||||
|
||||
// Get all fields with ztabindex attributes
|
||||
var tabbableFields = box.querySelectorAll('*[ztabindex]');
|
||||
var tabbableFields = box.querySelectorAll('*[ztabindex]:not([disabled=true])');
|
||||
|
||||
if (!tabbableFields.length) {
|
||||
Zotero.debug("No tabbable fields found");
|
||||
|
|
@ -2323,16 +2400,20 @@
|
|||
if (back) {
|
||||
Zotero.debug('Looking for previous tabindex before ' + tabindex, 4);
|
||||
for (let i = tabbableFields.length - 1; i >= 0; i--) {
|
||||
if (parseInt(tabbableFields[i].getAttribute('ztabindex')) < tabindex) {
|
||||
let field = tabbableFields[i];
|
||||
let tabIndexHere = parseInt(field.getAttribute('ztabindex'));
|
||||
if (tabIndexHere !== -1 && tabIndexHere < tabindex) {
|
||||
next = tabbableFields[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Zotero.debug('Looking for tabindex ' + tabindex, 4);
|
||||
for (var pos = 0; pos < tabbableFields.length; pos++) {
|
||||
if (parseInt(tabbableFields[pos].getAttribute('ztabindex')) >= tabindex) {
|
||||
let field = tabbableFields[pos];
|
||||
let tabIndexHere = parseInt(field.getAttribute('ztabindex'));
|
||||
if (tabIndexHere !== -1 && tabIndexHere >= tabindex) {
|
||||
next = tabbableFields[pos];
|
||||
break;
|
||||
}
|
||||
|
|
@ -2345,7 +2426,7 @@
|
|||
}
|
||||
|
||||
// Drop-down needs to be focused
|
||||
if (next.id == 'item-type-menu') {
|
||||
if (next.id == 'item-type-menu' || next.classList.contains('zotero-focusable')) {
|
||||
next.focus();
|
||||
}
|
||||
// Fields need to be clicked
|
||||
|
|
|
|||
|
|
@ -353,15 +353,16 @@
|
|||
<body>
|
||||
<![CDATA[
|
||||
(async () => {
|
||||
let n = 0;
|
||||
while (!this._editorInstance && n++ < 100) {
|
||||
await Zotero.Promise.delay(10);
|
||||
}
|
||||
await this._editorInstance._initPromise;
|
||||
this._iframe.focus();
|
||||
try {
|
||||
let n = 0;
|
||||
while (!this._editorInstance && n++ < 100) {
|
||||
await Zotero.Promise.delay(10);
|
||||
}
|
||||
await this._editorInstance._initPromise;
|
||||
this._iframe.focus();
|
||||
this._editorInstance._iframeWindow.document.querySelector('.toolbar-button-return').focus();
|
||||
} catch(e) {
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
})();
|
||||
]]>
|
||||
|
|
|
|||
|
|
@ -330,6 +330,15 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="receiveKeyboardFocus">
|
||||
<parameter name="direction" />
|
||||
<body><![CDATA[
|
||||
this.id("addButton").focus();
|
||||
// TODO: the relatedbox is not currently keyboard accessible
|
||||
// so we are ignoring the direction
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
<content>
|
||||
<xul:vbox xbl:inherits="flex" class="zotero-box">
|
||||
|
|
|
|||
|
|
@ -407,6 +407,7 @@
|
|||
<parameter name="condition"/>
|
||||
<body><![CDATA[
|
||||
switch (condition) {
|
||||
case 'anyField':
|
||||
case 'collection':
|
||||
case 'creator':
|
||||
case 'title':
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const { IntlProvider } = require('react-intl');
|
||||
const LibraryTree = require('./libraryTree');
|
||||
const VirtualizedTable = require('components/virtualized-table');
|
||||
const { TreeSelectionStub } = VirtualizedTable;
|
||||
|
|
@ -42,9 +41,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
var ref;
|
||||
opts.domEl = domEl;
|
||||
let elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<CollectionTree ref={c => ref = c } {...opts} />
|
||||
</IntlProvider>
|
||||
<CollectionTree ref={c => ref = c } {...opts} />
|
||||
);
|
||||
await new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
|
||||
|
||||
|
|
@ -640,15 +637,15 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
this.tree.invalidate();
|
||||
return;
|
||||
}
|
||||
if (action == 'refresh') {
|
||||
// If trash is refreshed, we probably need to update the icon from full to empty
|
||||
if (type == 'trash') {
|
||||
this.tree.invalidate();
|
||||
}
|
||||
if (action == 'refresh' && type != 'trash') {
|
||||
// Trash handled below
|
||||
return;
|
||||
}
|
||||
if (type == 'feed' && (action == 'unreadCountUpdated' || action == 'statusChanged')) {
|
||||
this.tree.invalidate();
|
||||
let feedRow = this.getRowIndexByID("L" + ids[0]);
|
||||
if (feedRow !== false) {
|
||||
this.tree.invalidateRow(feedRow);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -753,7 +750,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
// Invalidate parent in case it's become non-empty
|
||||
let parentRow = this.getRowIndexByID("C" + collection.parentID);
|
||||
if (parentRow !== false) {
|
||||
this._treebox.invalidateRow(parentRow);
|
||||
this.tree.invalidateRow(parentRow);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -777,6 +774,9 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'feed':
|
||||
break;
|
||||
|
||||
default:
|
||||
await this.reload();
|
||||
|
|
@ -826,6 +826,11 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (action == 'refresh' && type == 'trash') {
|
||||
// We need to update the trash's status (full or empty), and if empty,
|
||||
// the row might be removed
|
||||
await this.reload();
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
var promise = this.waitForSelect();
|
||||
|
|
@ -839,6 +844,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
* @param ids {String[]} list of row ids to be highlighted
|
||||
*/
|
||||
async setHighlightedRows(ids) {
|
||||
if (this._editing) return;
|
||||
try {
|
||||
this._highlightedRows = new Set();
|
||||
|
||||
|
|
@ -1614,7 +1620,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
return false;
|
||||
}
|
||||
|
||||
let newAttachment = Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID);
|
||||
let newAttachment = await Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID);
|
||||
if (options.annotations) {
|
||||
await Zotero.Items.copyChildItems(item, newAttachment);
|
||||
}
|
||||
|
|
@ -2306,7 +2312,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
}
|
||||
|
||||
if (showTrash) {
|
||||
let deletedItems = await Zotero.Items.getDeleted(libraryID);
|
||||
let deletedItems = await Zotero.Items.getDeleted(libraryID, true);
|
||||
if (deletedItems.length || Zotero.Prefs.get("showTrashWhenEmpty")) {
|
||||
var ref = {
|
||||
libraryID: libraryID
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import React, { memo } from 'react';
|
|||
import ReactDOM from "react-dom";
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import { IntlProvider, FormattedMessage } from "react-intl";
|
||||
|
||||
// This is a quick reimplementation of the annotation for use in the conflict resolution window.
|
||||
// We'll want to replace this with a single component shared between the PDF reader and the rest
|
||||
|
|
@ -40,31 +39,26 @@ function AnnotationBox({ data }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={ Zotero.locale }
|
||||
messages={ Zotero.Intl.strings }
|
||||
>
|
||||
<div className="AnnotationBox">
|
||||
<div className="title">{Zotero.getString('itemTypes.annotation')}</div>
|
||||
<div className="container">
|
||||
<div className="header">
|
||||
<div>{Zotero.getString('citation.locator.page')} {data.pageLabel}</div>
|
||||
</div>
|
||||
{data.text !== undefined
|
||||
? <div className="text" style={textStyle}>{data.text}</div>
|
||||
: ''}
|
||||
{data.type == 'image'
|
||||
// TODO: Localize
|
||||
// TODO: Render from PDF based on position, if file is the same? Or don't
|
||||
// worry about it?
|
||||
? <div className="image-placeholder">[image not shown]</div>
|
||||
: ''}
|
||||
{data.comment !== undefined
|
||||
? <div className="comment">{data.comment}</div>
|
||||
: ''}
|
||||
<div className="AnnotationBox">
|
||||
<div className="title">{Zotero.getString('itemTypes.annotation')}</div>
|
||||
<div className="container">
|
||||
<div className="header">
|
||||
<div>{Zotero.Cite.getLocatorString('page')} {data.pageLabel}</div>
|
||||
</div>
|
||||
{data.text !== undefined
|
||||
? <div className="text" style={textStyle}>{data.text}</div>
|
||||
: ''}
|
||||
{data.type == 'image'
|
||||
// TODO: Localize
|
||||
// TODO: Render from PDF based on position, if file is the same? Or don't
|
||||
// worry about it?
|
||||
? <div className="image-placeholder">[image not shown]</div>
|
||||
: ''}
|
||||
{data.comment !== undefined
|
||||
? <div className="comment">{data.comment}</div>
|
||||
: ''}
|
||||
</div>
|
||||
</IntlProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
const React = require('react')
|
||||
const { PureComponent, createElement: create } = React
|
||||
const { injectIntl } = require('react-intl')
|
||||
const { IconDownChevron } = require('./icons')
|
||||
const cx = require('classnames')
|
||||
const {
|
||||
|
|
@ -59,19 +58,19 @@ class Button extends PureComponent {
|
|||
}
|
||||
|
||||
get text() {
|
||||
const { intl, text } = this.props
|
||||
const { text } = this.props;
|
||||
|
||||
return text ?
|
||||
intl.formatMessage({ id: text }) :
|
||||
null
|
||||
Zotero.getString(text) :
|
||||
null;
|
||||
}
|
||||
|
||||
get title() {
|
||||
const { intl, title } = this.props
|
||||
const { title } = this.props;
|
||||
|
||||
return title ?
|
||||
intl.formatMessage({ id: title }) :
|
||||
null
|
||||
Zotero.getString(title) :
|
||||
null;
|
||||
}
|
||||
|
||||
get menuMarker() {
|
||||
|
|
@ -159,5 +158,5 @@ class Button extends PureComponent {
|
|||
|
||||
module.exports = {
|
||||
ButtonGroup,
|
||||
Button: injectIntl(Button)
|
||||
Button
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import React, { memo } from 'react';
|
|||
import ReactDOM from "react-dom";
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import { IntlProvider } from "react-intl";
|
||||
|
||||
function CreateParent({ loading, item, toggleAccept }) {
|
||||
// When the input has/does not have characters toggle the accept button on the dialog
|
||||
|
|
@ -43,31 +42,26 @@ function CreateParent({ loading, item, toggleAccept }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={ Zotero.locale }
|
||||
messages={ Zotero.Intl.strings }
|
||||
>
|
||||
<div className="create-parent-container">
|
||||
<span className="title">
|
||||
{ item.attachmentFilename }
|
||||
</span>
|
||||
<div className="body">
|
||||
<input
|
||||
id="parent-item-identifier"
|
||||
placeholder={ Zotero.getString('createParent.prompt') }
|
||||
size="50"
|
||||
disabled={ loading }
|
||||
onChange={ handleInput }
|
||||
/>
|
||||
<div
|
||||
mode="undetermined"
|
||||
className={ cx('downloadProgress', { hidden: !loading }) }
|
||||
>
|
||||
<div className="progress-bar"></div>
|
||||
</div>
|
||||
<div className="create-parent-container">
|
||||
<span className="title">
|
||||
{ item.attachmentFilename }
|
||||
</span>
|
||||
<div className="body">
|
||||
<input
|
||||
id="parent-item-identifier"
|
||||
placeholder={ Zotero.getString('createParent.prompt') }
|
||||
size="50"
|
||||
disabled={ loading }
|
||||
onChange={ handleInput }
|
||||
/>
|
||||
<div
|
||||
mode="undetermined"
|
||||
className={ cx('downloadProgress', { hidden: !loading }) }
|
||||
>
|
||||
<div className="progress-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</IntlProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ i('TreeitemBookSection', 'chrome://zotero/skin/treeitem-bookSection.png');
|
|||
i('TreeitemCase', 'chrome://zotero/skin/treeitem-case.png');
|
||||
i('TreeitemComputerProgram', 'chrome://zotero/skin/treeitem-computerProgram.png');
|
||||
i('TreeitemConferencePaper', 'chrome://zotero/skin/treeitem-conferencePaper.png');
|
||||
i('TreeitemDataset', 'chrome://zotero/skin/treeitem-dataset.png');
|
||||
i('TreeitemDictionaryEntry', 'chrome://zotero/skin/treeitem-dictionaryEntry.png');
|
||||
i('TreeitemEmail', 'chrome://zotero/skin/treeitem-email.png');
|
||||
i('TreeitemEncyclopediaArticle', 'chrome://zotero/skin/treeitem-encyclopediaArticle.png');
|
||||
|
|
@ -147,12 +148,13 @@ i('TreeitemPreprint', 'chrome://zotero/skin/treeitem-preprint.png');
|
|||
i('TreeitemPresentation', 'chrome://zotero/skin/treeitem-presentation.png');
|
||||
i('TreeitemRadioBroadcast', 'chrome://zotero/skin/treeitem-radioBroadcast.png', false);
|
||||
i('TreeitemReport', 'chrome://zotero/skin/treeitem-report.png');
|
||||
i('TreeitemStandard', 'chrome://zotero/skin/treeitem-standard.png');
|
||||
i('TreeitemStatute', 'chrome://zotero/skin/treeitem-statute.png');
|
||||
i('TreeitemThesis', 'chrome://zotero/skin/treeitem-thesis.png');
|
||||
i('TreeitemTvBroadcast', 'chrome://zotero/skin/treeitem-tvBroadcast.png', false);
|
||||
i('TreeitemVideoRecording', 'chrome://zotero/skin/treeitem-videoRecording.png', false);
|
||||
i('TreeitemWebpageGray', 'chrome://zotero/skin/treeitem-webpage-gray.png');
|
||||
i('TreeitemWebpage', 'chrome://zotero/skin/treeitem-webpage.png', false);
|
||||
i('TreeitemWebpage', 'chrome://zotero/skin/treeitem-webpage.png');
|
||||
|
||||
// Treesource
|
||||
i('TreesourceBucket', 'chrome://zotero/skin/treesource-bucket.png', false);
|
||||
|
|
|
|||
|
|
@ -415,7 +415,7 @@ const TagsBox = React.forwardRef((props, ref) => {
|
|||
<div className="tags-box" ref={rootRef} onClick={blurOpenField}>
|
||||
<div className="tags-box-header">
|
||||
<div className="tags-box-count">{renderCount()}</div>
|
||||
{ props.editable && <div><button onClick={handleAddTag}>Add</button></div> }
|
||||
{ props.editable && <div><button onClick={handleAddTag} id="tags-box-add-button">Add</button></div> }
|
||||
</div>
|
||||
<div className="tags-box-list-container">
|
||||
<ul className="tags-box-list">
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
import React, { memo, useCallback, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getDOMElement } from 'components/icons';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import VirtualizedTable, { renderCell } from 'components/virtualized-table';
|
||||
import { noop } from './utils';
|
||||
|
|
@ -101,17 +100,15 @@ const ProgressQueueTable = ({ onActivate = noop, progressQueue }) => {
|
|||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<IntlProvider locale={ Zotero.locale } messages={ Zotero.Intl.strings }>
|
||||
<VirtualizedTable
|
||||
getRowCount={ getRowCount }
|
||||
ref={ treeRef }
|
||||
id="progress-queue-table"
|
||||
renderItem={ rowToTreeItem }
|
||||
showHeader={ true }
|
||||
columns={ tableColumns }
|
||||
onActivate={ onActivate }
|
||||
/>
|
||||
</IntlProvider>
|
||||
<VirtualizedTable
|
||||
getRowCount={ getRowCount }
|
||||
ref={ treeRef }
|
||||
id="progress-queue-table"
|
||||
renderItem={ rowToTreeItem }
|
||||
showHeader={ true }
|
||||
columns={ tableColumns }
|
||||
onActivate={ onActivate }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ const TabBar = forwardRef(function (props, ref) {
|
|||
|
||||
function handleWheel(event) {
|
||||
// Normalize wheel speed
|
||||
let x = event.deltaX;
|
||||
let x = event.deltaX || event.deltaY;
|
||||
if (x && event.deltaMode) {
|
||||
if (event.deltaMode === 1) {
|
||||
x *= 20;
|
||||
|
|
@ -302,7 +302,10 @@ const TabBar = forwardRef(function (props, ref) {
|
|||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="pinned-tabs">
|
||||
<div className="tabs">
|
||||
<div
|
||||
className="tabs"
|
||||
onMouseOut={handleTabBarMouseOut}
|
||||
>
|
||||
{tabs.length ? renderTab(tabs[0], 0) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const { FormattedMessage } = require('react-intl');
|
||||
var { Collection } = require('react-virtualized');
|
||||
|
||||
// See also .tag-selector-item in _tag-selector.scss
|
||||
|
|
@ -191,14 +190,14 @@ class TagList extends React.PureComponent {
|
|||
if (!this.props.loaded) {
|
||||
tagList = (
|
||||
<div className="tag-selector-message">
|
||||
<FormattedMessage id="zotero.tagSelector.loadingTags" />
|
||||
{Zotero.getString('zotero.tagSelector.loadingTags')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if (tagCount == 0) {
|
||||
tagList = (
|
||||
<div className="tag-selector-message">
|
||||
<FormattedMessage id="zotero.tagSelector.noTagsToDisplay" />
|
||||
{Zotero.getString('zotero.tagSelector.noTagsToDisplay')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ const PropTypes = require('prop-types');
|
|||
const cx = require('classnames');
|
||||
const WindowedList = require('./windowed-list');
|
||||
const Draggable = require('./draggable');
|
||||
const { injectIntl } = require('react-intl');
|
||||
const { IconDownChevron, getDOMElement } = require('components/icons');
|
||||
|
||||
const TYPING_TIMEOUT = 1000;
|
||||
|
|
@ -639,7 +638,10 @@ class VirtualizedTable extends React.Component {
|
|||
}
|
||||
}
|
||||
else {
|
||||
for (let i = (this.selection.focused) % rowCount; i != this.selection.focused - 1; i = (i + 1) % rowCount) {
|
||||
// Stop at the row before the selection's focus
|
||||
// but don't set a negative stop point, or we'll loop infinitely
|
||||
let stopRow = Math.max(this.selection.focused - 1, 0);
|
||||
for (let i = (this.selection.focused) % rowCount; i != stopRow; i = (i + 1) % rowCount) {
|
||||
let rowString = this.props.getRowString(i);
|
||||
if (rowString.toLowerCase().indexOf(this._typingString) == 0) {
|
||||
if (i != this.selection.focused) {
|
||||
|
|
@ -660,7 +662,14 @@ class VirtualizedTable extends React.Component {
|
|||
this._isMouseDrag = true;
|
||||
}
|
||||
|
||||
_onDragEnd = () => {
|
||||
_onDragEnd = async () => {
|
||||
// macOS force-click sometimes causes a second mouseup event to be fired some time later
|
||||
// causing a collection change on dragend, so we add a delay here. It shouldn't cause any issues
|
||||
// because isMouseDrag is only used in mouseup handler to exactly prevent from accidentally switching
|
||||
// selection after dragend.
|
||||
if (Zotero.isMac) {
|
||||
await Zotero.Promise.delay(500);
|
||||
}
|
||||
this._isMouseDrag = false;
|
||||
}
|
||||
|
||||
|
|
@ -740,6 +749,7 @@ class VirtualizedTable extends React.Component {
|
|||
this.selection.pivot = index;
|
||||
this.invalidateRow(previousFocused);
|
||||
this.invalidateRow(index);
|
||||
this.selection._updateTree(shouldDebounce);
|
||||
}
|
||||
// Normal selection
|
||||
else if (!toggleSelection) {
|
||||
|
|
@ -1625,7 +1635,7 @@ function makeRowRenderer(getRowData) {
|
|||
|
||||
function formatColumnName(column) {
|
||||
if (column.label in Zotero.Intl.strings) {
|
||||
return Zotero.Intl.strings[column.label];
|
||||
return Zotero.getString(column.label);
|
||||
}
|
||||
else if (/^[^\s]+\w\.\w[^\s]+$/.test(column.label)) {
|
||||
try {
|
||||
|
|
@ -1641,7 +1651,7 @@ function formatColumnName(column) {
|
|||
return column.label;
|
||||
}
|
||||
|
||||
module.exports = injectIntl(VirtualizedTable, { forwardRef: true });
|
||||
module.exports = VirtualizedTable;
|
||||
module.exports.TreeSelection = TreeSelection;
|
||||
module.exports.TreeSelectionStub = TreeSelectionStub;
|
||||
module.exports.renderCell = renderCell;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const PropTypes = require('prop-types');
|
||||
const { IntlProvider } = require('react-intl');
|
||||
const TagSelector = require('components/tagSelector.js');
|
||||
const defaults = {
|
||||
tagColors: new Map(),
|
||||
|
|
@ -762,9 +761,7 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
|||
static init(domEl, opts) {
|
||||
var ref;
|
||||
let elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<TagSelectorContainer ref={c => ref = c } {...opts} />
|
||||
</IntlProvider>
|
||||
<TagSelectorContainer ref={c => ref = c } {...opts} />
|
||||
);
|
||||
ReactDOM.render(elem, domEl);
|
||||
ref.domEl = domEl;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
var _itemContexts = [];
|
||||
var _notesContexts = [];
|
||||
|
||||
|
||||
var _globalDeckIndex = [];
|
||||
var _preventGlobalDeckChange = false;
|
||||
|
||||
// Using attribute instead of property to set 'selectedIndex'
|
||||
// is more reliable
|
||||
|
||||
|
|
@ -176,6 +179,20 @@ var ZoteroContextPane = new function () {
|
|||
if (Zotero_Tabs.deck.children.length == 1) {
|
||||
_notesContexts.forEach(x => x.notesListRef.current.setExpanded(false));
|
||||
}
|
||||
// Close tab specific notes if tab id no longer exists, but
|
||||
// do that only when unloaded tab is reloaded
|
||||
setTimeout(() => {
|
||||
var contextNodes = Array.from(_notesPaneDeck.children);
|
||||
for (let contextNode of contextNodes) {
|
||||
var nodes = Array.from(contextNode.querySelector('.zotero-context-pane-tab-notes-deck').children);
|
||||
for (let node of nodes) {
|
||||
var tabID = node.getAttribute('data-tab-id');
|
||||
if (!document.getElementById(tabID)) {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (action == 'select') {
|
||||
// It seems that changing `hidden` or `collapsed` values might
|
||||
|
|
@ -188,6 +205,14 @@ var ZoteroContextPane = new function () {
|
|||
_tabCover.hidden = true;
|
||||
}
|
||||
else if (Zotero_Tabs.selectedType == 'reader') {
|
||||
if (_panesDeck.selectedIndex == 1
|
||||
&& _notesPaneDeck.selectedPanel.selectedIndex != 2
|
||||
&& !_preventGlobalDeckChange) {
|
||||
let libraryID = _notesPaneDeck.selectedPanel.getAttribute('data-library-id');
|
||||
_globalDeckIndex[libraryID] = _notesPaneDeck.selectedPanel.selectedIndex;
|
||||
}
|
||||
_preventGlobalDeckChange = false;
|
||||
|
||||
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
_tabCover.hidden = false;
|
||||
|
|
@ -207,11 +232,25 @@ var ZoteroContextPane = new function () {
|
|||
var notesContext = _getNotesContext(attachment.libraryID);
|
||||
notesContext.updateFromCache();
|
||||
}
|
||||
|
||||
let tabNotesDeck = _notesPaneDeck.selectedPanel.querySelector('.zotero-context-pane-tab-notes-deck');
|
||||
let selectedIndex = Array.from(tabNotesDeck.children).findIndex(x => x.getAttribute('data-tab-id') == ids[0]);
|
||||
if (selectedIndex != -1) {
|
||||
tabNotesDeck.setAttribute('selectedIndex', selectedIndex);
|
||||
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', 2);
|
||||
}
|
||||
else {
|
||||
let libraryID = _notesPaneDeck.selectedPanel.getAttribute('data-library-id');
|
||||
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', _globalDeckIndex[libraryID] || 0);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
_contextPaneSplitter.setAttribute('hidden', false);
|
||||
// It seems that on heavy load (i.e. syncing) the line below doesn't set the correct value
|
||||
|
||||
_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
|
||||
// It seems that on heavy load (i.e. syncing) the line below doesn't set the correct value,
|
||||
// therefore we repeat the same operation at the end of JS message queue
|
||||
setTimeout(() => {
|
||||
_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
|
||||
});
|
||||
|
|
@ -243,9 +282,14 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
if (splitter.getAttribute('state') != 'collapsed') {
|
||||
if (_panesDeck.selectedIndex == 1) {
|
||||
var node = _notesPaneDeck.selectedPanel;
|
||||
if (node.selectedIndex == 1) {
|
||||
return node.querySelector('zoteronoteeditor');
|
||||
var libraryContext = _notesPaneDeck.selectedPanel;
|
||||
// Global note
|
||||
if (libraryContext.selectedIndex == 1) {
|
||||
return libraryContext.querySelector('zoteronoteeditor');
|
||||
}
|
||||
// Tab specific child note
|
||||
else if (libraryContext.selectedIndex == 2) {
|
||||
return libraryContext.querySelector('.zotero-context-pane-tab-notes-deck').selectedPanel.querySelector('zoteronoteeditor');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -419,12 +463,21 @@ var ZoteroContextPane = new function () {
|
|||
editor.className = 'zotero-context-pane-pinned-note';
|
||||
editor.setAttribute('flex', 1);
|
||||
noteContainer.append(title, editor);
|
||||
|
||||
var tabNotesContainer = document.createElement('vbox');
|
||||
var title = document.createElement('vbox');
|
||||
title.className = 'zotero-context-pane-editor-parent-line';
|
||||
let tabNotesDeck = document.createElement('deck');
|
||||
tabNotesDeck.className = 'zotero-context-pane-tab-notes-deck';
|
||||
tabNotesDeck.setAttribute('flex', 1);
|
||||
tabNotesContainer.append(title, tabNotesDeck);
|
||||
|
||||
let contextNode = document.createElement('deck');
|
||||
contextNode.append(list, noteContainer);
|
||||
contextNode.append(list, noteContainer, tabNotesContainer);
|
||||
_notesPaneDeck.append(contextNode);
|
||||
|
||||
contextNode.className = 'context-node';
|
||||
contextNode.setAttribute('data-library-id', libraryID);
|
||||
contextNode.setAttribute('selectedIndex', 0);
|
||||
|
||||
editor.returnHandler = () => {
|
||||
|
|
@ -440,8 +493,15 @@ var ZoteroContextPane = new function () {
|
|||
if (!attachment) {
|
||||
return;
|
||||
}
|
||||
var annotations = attachment.getAnnotations().filter(x => x.annotationType != 'ink');
|
||||
if (!annotations.length) {
|
||||
return;
|
||||
}
|
||||
var note = await Zotero.EditorInstance.createNoteFromAnnotations(
|
||||
attachment.getAnnotations().filter(x => x.annotationType != 'ink'), child && attachment.parentID
|
||||
annotations,
|
||||
{
|
||||
parentID: child && attachment.parentID
|
||||
}
|
||||
);
|
||||
|
||||
_updateAddToNote();
|
||||
|
|
@ -449,7 +509,7 @@ var ZoteroContextPane = new function () {
|
|||
input.value = '';
|
||||
_updateNotesList();
|
||||
|
||||
_setPinnedNote(note.id);
|
||||
_setPinnedNote(note);
|
||||
}
|
||||
|
||||
function _createNote(child) {
|
||||
|
|
@ -463,10 +523,7 @@ var ZoteroContextPane = new function () {
|
|||
}
|
||||
item.parentID = attachment.parentID;
|
||||
}
|
||||
editor.mode = 'edit';
|
||||
editor.item = item;
|
||||
editor.parentItem = null;
|
||||
editor.focus();
|
||||
_setPinnedNote(item);
|
||||
_updateAddToNote();
|
||||
|
||||
input.value = '';
|
||||
|
|
@ -672,7 +729,10 @@ var ZoteroContextPane = new function () {
|
|||
<NotesList
|
||||
ref={notesListRef}
|
||||
onClick={(id) => {
|
||||
_setPinnedNote(id);
|
||||
let item = Zotero.Items.get(id);
|
||||
if (item) {
|
||||
_setPinnedNote(item);
|
||||
}
|
||||
}}
|
||||
onContextMenu={(id, event) => {
|
||||
document.getElementById('context-pane-list-move-to-trash').setAttribute('disabled', readOnly);
|
||||
|
|
@ -727,21 +787,63 @@ var ZoteroContextPane = new function () {
|
|||
return !Zotero.Libraries.get(libraryID).editable;
|
||||
}
|
||||
|
||||
function _setPinnedNote(itemID) {
|
||||
var item = Zotero.Items.get(itemID);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
function _setPinnedNote(item) {
|
||||
var readOnly = _isLibraryReadOnly(item.libraryID);
|
||||
var context = _getNotesContext(item.libraryID);
|
||||
if (context) {
|
||||
var { editor, node } = context;
|
||||
node.setAttribute('selectedIndex', 1);
|
||||
editor.mode = readOnly ? 'view' : 'edit';
|
||||
editor.item = item;
|
||||
editor.parentItem = null;
|
||||
|
||||
node.querySelector('.zotero-context-pane-editor-parent-line').innerHTML = '';
|
||||
|
||||
let isChild = false;
|
||||
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
let attachment = Zotero.Items.get(reader.itemID);
|
||||
if (attachment.parentItemID == item.parentItemID) {
|
||||
isChild = true;
|
||||
}
|
||||
}
|
||||
|
||||
var tabNotesDeck = _notesPaneDeck.selectedPanel.querySelector('.zotero-context-pane-tab-notes-deck');
|
||||
var parentTitleContainer;
|
||||
if (isChild) {
|
||||
var vbox = document.createElement('vbox');
|
||||
vbox.setAttribute('data-tab-id', Zotero_Tabs.selectedID);
|
||||
vbox.style.display = 'flex';
|
||||
|
||||
editor = document.createElement('zoteronoteeditor');
|
||||
editor.style.display = 'flex';
|
||||
editor.style.width = '100%';
|
||||
vbox.append(editor);
|
||||
|
||||
tabNotesDeck.append(vbox);
|
||||
|
||||
editor.mode = readOnly ? 'view' : 'edit';
|
||||
editor.item = item;
|
||||
editor.parentItem = null;
|
||||
editor.returnHandler = () => {
|
||||
_panesDeck.setAttribute('selectedIndex', 1);
|
||||
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', 0);
|
||||
vbox.remove();
|
||||
_updateAddToNote();
|
||||
_preventGlobalDeckChange = true;
|
||||
};
|
||||
|
||||
_notesPaneDeck.selectedPanel.setAttribute('selectedIndex', 2);
|
||||
tabNotesDeck.setAttribute('selectedIndex', tabNotesDeck.children.length - 1);
|
||||
|
||||
parentTitleContainer = _notesPaneDeck.selectedPanel.children[2].querySelector('.zotero-context-pane-editor-parent-line');
|
||||
}
|
||||
else {
|
||||
node.setAttribute('selectedIndex', 1);
|
||||
editor.mode = readOnly ? 'view' : 'edit';
|
||||
editor.item = item;
|
||||
editor.parentItem = null;
|
||||
|
||||
parentTitleContainer = node.querySelector('.zotero-context-pane-editor-parent-line');
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
|
||||
parentTitleContainer.innerHTML = '';
|
||||
var parentItem = item.parentItem;
|
||||
if (parentItem) {
|
||||
var container = document.createElementNS(HTML_NS, 'div');
|
||||
|
|
@ -752,7 +854,7 @@ var ZoteroContextPane = new function () {
|
|||
title.append(parentItem.getDisplayTitle());
|
||||
title.className = 'parent-title';
|
||||
container.append(img, title);
|
||||
node.querySelector('.zotero-context-pane-editor-parent-line').append(container);
|
||||
parentTitleContainer.append(container);
|
||||
}
|
||||
_updateAddToNote();
|
||||
}
|
||||
|
|
@ -835,13 +937,13 @@ var ZoteroContextPane = new function () {
|
|||
|
||||
// Info tab
|
||||
var tabInfo = document.createElement('tab');
|
||||
tabInfo.setAttribute('label', Zotero.Intl.strings['zotero.tabs.info.label']);
|
||||
tabInfo.setAttribute('label', Zotero.getString('zotero.tabs.info.label'));
|
||||
// Tags tab
|
||||
var tabTags = document.createElement('tab');
|
||||
tabTags.setAttribute('label', Zotero.Intl.strings['zotero.tabs.tags.label']);
|
||||
tabTags.setAttribute('label', Zotero.getString('zotero.tabs.tags.label'));
|
||||
// Related tab
|
||||
var tabRelated = document.createElement('tab');
|
||||
tabRelated.setAttribute('label', Zotero.Intl.strings['zotero.tabs.related.label']);
|
||||
tabRelated.setAttribute('label', Zotero.getString('zotero.tabs.related.label'));
|
||||
|
||||
tabs.append(tabInfo, tabTags, tabRelated);
|
||||
|
||||
|
|
|
|||
|
|
@ -93,10 +93,11 @@ Zotero_File_Exporter.prototype.save = async function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Otherwise exclude note export translators
|
||||
else {
|
||||
translators = translators.filter(t => !t.configOptions || !t.configOptions.noteTranslator);
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude note translators if not exporting notes
|
||||
if (!exportingNotes) {
|
||||
translators = translators.filter(t => !t.configOptions || !t.configOptions.noteTranslator);
|
||||
}
|
||||
|
||||
// present options dialog
|
||||
|
|
@ -238,26 +239,29 @@ var Zotero_File_Interface = new function() {
|
|||
/*
|
||||
* exports items to clipboard
|
||||
*/
|
||||
function exportItemsToClipboard(items, translatorID) {
|
||||
function _translate(items, translatorID, callback) {
|
||||
function exportItemsToClipboard(items, format) {
|
||||
function _translate(items, format, callback) {
|
||||
let translation = new Zotero.Translate.Export();
|
||||
translation.setItems(items.slice());
|
||||
translation.setTranslator(translatorID);
|
||||
translation.setTranslator(format.id);
|
||||
if (format.options) {
|
||||
translation.setDisplayOptions(format.options);
|
||||
}
|
||||
translation.setHandler("done", callback);
|
||||
translation.translate();
|
||||
}
|
||||
|
||||
// If translating with virtual "Markdown + Rich Text" translator, use Note Markdown and
|
||||
// Note HTML instead
|
||||
if (translatorID == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
translatorID = Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN;
|
||||
_translate(items, translatorID, (obj, worked) => {
|
||||
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
|
||||
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
|
||||
_translate(items, markdownFormat, (obj, worked) => {
|
||||
if (!worked) {
|
||||
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
|
||||
return;
|
||||
}
|
||||
translatorID = Zotero.Translators.TRANSLATOR_ID_NOTE_HTML;
|
||||
_translate(items, translatorID, (obj2, worked) => {
|
||||
_translate(items, htmlFormat, (obj2, worked) => {
|
||||
if (!worked) {
|
||||
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
|
||||
return;
|
||||
|
|
@ -293,14 +297,14 @@ var Zotero_File_Interface = new function() {
|
|||
});
|
||||
}
|
||||
else {
|
||||
_translate(items, translatorID, (obj, worked) => {
|
||||
_translate(items, format, (obj, worked) => {
|
||||
if (!worked) {
|
||||
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
|
||||
return;
|
||||
}
|
||||
let text = obj.string;
|
||||
// For Note HTML translator use body content only
|
||||
if (translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
|
||||
if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
|
||||
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
let doc = parser.parseFromString(text, 'text/html');
|
||||
|
|
@ -441,10 +445,13 @@ var Zotero_File_Interface = new function() {
|
|||
|
||||
var translation;
|
||||
|
||||
if (options.mendeleyCode) {
|
||||
if (options.mendeleyAuth || options.mendeleyCode) {
|
||||
translation = yield _getMendeleyTranslation();
|
||||
translation.createNewCollection = createNewCollection;
|
||||
translation.mendeleyAuth = options.mendeleyAuth;
|
||||
translation.mendeleyCode = options.mendeleyCode;
|
||||
translation.newItemsOnly = options.newItemsOnly;
|
||||
translation.relinkOnly = options.relinkOnly;
|
||||
}
|
||||
else {
|
||||
// Check if the file is an SQLite database
|
||||
|
|
@ -980,7 +987,7 @@ var Zotero_File_Interface = new function() {
|
|||
if (matchResult) {
|
||||
const mendeleyCode = matchResult[1];
|
||||
Zotero.getMainWindow().setTimeout(() => this.showImportWizard({ mendeleyCode }), 0);
|
||||
|
||||
|
||||
// Clear all cookies to remove access
|
||||
//
|
||||
// This includes unrelated cookies in the central cookie store, but that's fine for
|
||||
|
|
@ -995,7 +1002,7 @@ var Zotero_File_Interface = new function() {
|
|||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
|
||||
win.close();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ const parseCitavi5Quads = (quadsRaw) => {
|
|||
const ImportCitaviAnnotatons = async (translation) => {
|
||||
const IDMap = translation._itemSaver._IDMap;
|
||||
const ZU = translation._sandboxZotero.Utilities;
|
||||
|
||||
// stream might be closed by now, re-init to make sure getXML() works
|
||||
translation._io.init('xml/dom');
|
||||
const doc = translation._sandboxZotero.getXML();
|
||||
const isCitavi5 = ZU.xpathText(doc, '//CitaviExchangeData/@Version').startsWith('5');
|
||||
var annotationNodes = ZU.xpath(doc, '//Annotations/Annotation');
|
||||
|
|
@ -29,17 +32,29 @@ const ImportCitaviAnnotatons = async (translation) => {
|
|||
const stageProgress = 50;
|
||||
let progress = baseProgress;
|
||||
translation.getProgress = () => progress;
|
||||
|
||||
|
||||
for (var i = 0, n = annotationNodes.length; i < n; i++) {
|
||||
const id = ZU.xpathText(annotationNodes[i], '@id');
|
||||
const quadsRaw = ZU.xpathText(annotationNodes[i], './Quads');
|
||||
const locationID = ZU.xpathText(annotationNodes[i], './LocationID');
|
||||
|
||||
const location = ZU.xpath(doc, `//Locations/Location[@${isCitavi5 ? 'ID' : 'id'}='${locationID}']`)[0];
|
||||
const location = ZU.xpath(doc, `//Locations/Location[@id='${locationID}']|//Locations/Location[@ID='${locationID}']`)[0];
|
||||
|
||||
if (!location) {
|
||||
Zotero.debug(`Missing <Location> entry for annotation ${id}, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const referenceID = ZU.xpathText(location, './ReferenceID');
|
||||
const entityLink = ZU.xpath(doc, `//EntityLinks/EntityLink[TargetID='${id}']`)[0];
|
||||
|
||||
if (!entityLink) {
|
||||
Zotero.debug(`Missing <EntityLink> entry for annotation ${id}, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const entitySourceID = ZU.xpathText(entityLink, './SourceID');
|
||||
const knowledgeItem = ZU.xpath(doc, `//KnowledgeItems/KnowledgeItem[@${isCitavi5 ? 'ID' : 'id'}='${entitySourceID}']`)[0];
|
||||
const knowledgeItem = ZU.xpath(doc, `//KnowledgeItems/KnowledgeItem[@id='${entitySourceID}']|//KnowledgeItems/KnowledgeItem[@ID='${entitySourceID}']`)[0];
|
||||
const keywordsIDsText = ZU.xpathText(doc, `//KnowledgeItemKeywords/OnetoN[starts-with(text(), "${entitySourceID}")]`);
|
||||
const keywords = keywordsIDsText
|
||||
? keywordsIDsText
|
||||
|
|
@ -134,16 +149,22 @@ const ImportCitaviAnnotatons = async (translation) => {
|
|||
|
||||
annotations.push(annotation);
|
||||
});
|
||||
|
||||
annotations = await Zotero.PDFWorker.processCitaviAnnotations(
|
||||
itemAttachment.getFilePath(), annotations
|
||||
);
|
||||
|
||||
annotations.forEach((annotation) => {
|
||||
promises.push(Zotero.Annotations.saveFromJSON(
|
||||
itemAttachment, annotation, { skipSelect: true }
|
||||
));
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
annotations = await Zotero.PDFWorker.processCitaviAnnotations(
|
||||
itemAttachment.getFilePath(), annotations
|
||||
);
|
||||
annotations.forEach((annotation) => {
|
||||
promises.push(Zotero.Annotations.saveFromJSON(
|
||||
itemAttachment, annotation, { skipSelect: true }
|
||||
));
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(`Could not process annotations for attachment item ${itemAttachment.key} (file path: ${itemAttachment.getFilePath()})`);
|
||||
}
|
||||
|
||||
progress = baseProgress + Math.ceil((i / annotationNodes.length) * stageProgress);
|
||||
translation._runHandler('itemDone', []);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
/* global mendeleyAPIUtils:false */
|
||||
import FilePicker from 'zotero/modules/filePicker';
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyAPIUtils.js");
|
||||
const { directAuth } = mendeleyAPIUtils;
|
||||
|
||||
var Zotero_Import_Wizard = {
|
||||
_wizard: null,
|
||||
_dbs: null,
|
||||
_file: null,
|
||||
_translation: null,
|
||||
_mendeleyOnlineRedirectURLWithCode: null,
|
||||
_mendeleyCode: null,
|
||||
|
||||
_mendeleyAuth: null,
|
||||
_mendeleyHasPreviouslyImported: false,
|
||||
_mendeleyImporterVersion: 0,
|
||||
_isZotfileInstalled: false,
|
||||
|
||||
init: async function () {
|
||||
this._wizard = document.getElementById('import-wizard');
|
||||
|
|
@ -16,6 +22,18 @@ var Zotero_Import_Wizard = {
|
|||
// Local import disabled
|
||||
//document.getElementById('radio-import-source-mendeley').hidden = false;
|
||||
}
|
||||
|
||||
const extensions = await Zotero.getInstalledExtensions();
|
||||
this._isZotfileInstalled = !!extensions.find(extName => extName.match(/^ZotFile((?!disabled).)*$/));
|
||||
|
||||
const predicateID = Zotero.RelationPredicates.getID('mendeleyDB:documentUUID');
|
||||
|
||||
if (predicateID) {
|
||||
const relSQL = 'SELECT ROWID FROM itemRelations WHERE predicateID = ? LIMIT 1';
|
||||
this._mendeleyHasPreviouslyImported = !!(await Zotero.DB.valueQueryAsync(relSQL, predicateID));
|
||||
}
|
||||
|
||||
this._mendeleyImporterVersion = parseInt((await Zotero.DB.valueQueryAsync("SELECT value FROM settings WHERE setting='mendeleyImport' AND key='version'")) || 0);
|
||||
|
||||
// If no existing collections or non-trash items in the library, don't create a new
|
||||
// collection by default
|
||||
|
|
@ -34,8 +52,12 @@ var Zotero_Import_Wizard = {
|
|||
document.getElementById('create-collection-checkbox').removeAttribute('checked');
|
||||
}
|
||||
}
|
||||
|
||||
if (args && args.pageID) {
|
||||
this._wizard.goTo(args.pageID);
|
||||
}
|
||||
|
||||
if (args && args.mendeleyCode) {
|
||||
if (args && args.mendeleyCode && Zotero.Prefs.get("import.mendeleyUseOAuth")) {
|
||||
this._mendeleyCode = args.mendeleyCode;
|
||||
this._wizard.goTo('page-options');
|
||||
}
|
||||
|
|
@ -54,10 +76,30 @@ var Zotero_Import_Wizard = {
|
|||
'import.fileHandling.description',
|
||||
Zotero.appName
|
||||
);
|
||||
|
||||
// Set up Mendeley username/password fields
|
||||
document.querySelector('label[for="mendeley-username"]').textContent
|
||||
= Zotero.Utilities.Internal.stringWithColon(Zotero.getString('general.username'));
|
||||
document.querySelector('label[for="mendeley-password"]').textContent
|
||||
= Zotero.Utilities.Internal.stringWithColon(Zotero.getString('general.password'));
|
||||
document.getElementById('mendeley-username').addEventListener('keyup', this.onMendeleyAuthKeyUp.bind(this));
|
||||
document.getElementById('mendeley-password').addEventListener('keyup', this.onMendeleyAuthKeyUp.bind(this));
|
||||
|
||||
document.getElementById('relink-only-checkbox').addEventListener('command', this.onRelinkOnlyChange.bind(this));
|
||||
// Set from integration.js prompt
|
||||
if (args && args.relinkOnly) {
|
||||
document.getElementById('relink-only-checkbox').checked = true;
|
||||
this.onRelinkOnlyChange();
|
||||
}
|
||||
|
||||
Zotero.Translators.init(); // async
|
||||
},
|
||||
|
||||
|
||||
onCancel: function () {
|
||||
if (this._translation && this._translation.interrupt) {
|
||||
this._translation.interrupt();
|
||||
}
|
||||
},
|
||||
|
||||
onModeChosen: async function () {
|
||||
var wizard = this._wizard;
|
||||
|
|
@ -70,6 +112,14 @@ var Zotero_Import_Wizard = {
|
|||
break;
|
||||
|
||||
case 'radio-import-source-mendeley-online':
|
||||
if (this._isZotfileInstalled) {
|
||||
this._onDone(
|
||||
Zotero.getString('general.error'),
|
||||
Zotero.getString('import.online.blockedByPlugin', 'ZotFile'),
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
wizard.goTo('mendeley-online-explanation');
|
||||
wizard.canRewind = true;
|
||||
break;
|
||||
|
|
@ -103,19 +153,58 @@ var Zotero_Import_Wizard = {
|
|||
},
|
||||
|
||||
onMendeleyOnlineShow: async function () {
|
||||
document.getElementById('mendeley-online-description').textContent = Zotero.getString(
|
||||
'import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley']
|
||||
);
|
||||
document.getElementById('mendeley-online-description').textContent = Zotero.Prefs.get("import.mendeleyUseOAuth")
|
||||
? Zotero.getString('import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley'])
|
||||
: Zotero.getString('import.online.formIntro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley']);
|
||||
document.getElementById('mendeley-online-description2').textContent = Zotero.getString(
|
||||
'import.online.intro2', [Zotero.appName, 'Mendeley']
|
||||
);
|
||||
document.getElementById('mendeley-login').style.display = Zotero.Prefs.get("import.mendeleyUseOAuth") ? 'none' : '';
|
||||
document.getElementById('mendeley-online-login-feedback').style.display = 'none';
|
||||
this._wizard.canAdvance = Zotero.Prefs.get("import.mendeleyUseOAuth");
|
||||
},
|
||||
|
||||
onMendeleyOnlineAdvance: function () {
|
||||
if (!this._mendeleyOnlineRedirectURLWithCode) {
|
||||
onMendeleyOnlineAdvance: async function () {
|
||||
if (Zotero.Prefs.get("import.mendeleyUseOAuth")) {
|
||||
Zotero_File_Interface.authenticateMendeleyOnline();
|
||||
window.close();
|
||||
}
|
||||
else {
|
||||
const userNameEl = document.getElementById('mendeley-username');
|
||||
const passwordEl = document.getElementById('mendeley-password');
|
||||
userNameEl.disabled = true;
|
||||
passwordEl.disabled = true;
|
||||
try {
|
||||
this._mendeleyAuth = await directAuth(userNameEl.value, passwordEl.value);
|
||||
this._wizard.goTo('page-options');
|
||||
}
|
||||
catch (e) {
|
||||
const feedbackEl = document.getElementById('mendeley-online-login-feedback');
|
||||
feedbackEl.textContent = Zotero.getString('import.online.wrongCredentials', ['Mendeley']);
|
||||
feedbackEl.style.display = '';
|
||||
this._wizard.canAdvance = false; // change to either of the inputs will reset thi
|
||||
}
|
||||
finally {
|
||||
userNameEl.disabled = false;
|
||||
passwordEl.disabled = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onMendeleyAuthKeyUp: function () {
|
||||
document.getElementById('mendeley-online-login-feedback').style.display = 'none';
|
||||
this._wizard.canAdvance = document.getElementById('mendeley-username').value.length > 0
|
||||
&& document.getElementById('mendeley-password').value.length > 0;
|
||||
},
|
||||
|
||||
onRelinkOnlyChange: function () {
|
||||
if (document.getElementById('relink-only-checkbox').checked) {
|
||||
document.getElementById('new-items-only-checkbox').checked = true;
|
||||
document.getElementById('create-collection-checkbox').checked = false;
|
||||
}
|
||||
|
||||
document.getElementById('new-items-only-checkbox').disabled = document.getElementById('relink-only-checkbox').checked;
|
||||
document.getElementById('create-collection-checkbox').disabled = document.getElementById('relink-only-checkbox').checked;
|
||||
},
|
||||
|
||||
goToStart: function () {
|
||||
|
|
@ -196,7 +285,17 @@ var Zotero_Import_Wizard = {
|
|||
|
||||
|
||||
onOptionsShown: function () {
|
||||
document.getElementById('file-handling-options').hidden = !!this._mendeleyCode;
|
||||
document.getElementById('file-handling-options').hidden = !!(this._mendeleyAuth || this._mendeleyCode);
|
||||
const hideExtraMendeleyOptions = !this._mendeleyHasPreviouslyImported || !(this._mendeleyAuth || this._mendeleyCode);
|
||||
document.getElementById('mendeley-options').hidden = hideExtraMendeleyOptions;
|
||||
document.getElementById('relink-only-wrapper').hidden = hideExtraMendeleyOptions || this._mendeleyImporterVersion > 0;
|
||||
document.getElementById('relink-only-more-info').addEventListener('click', function () {
|
||||
Zotero.launchURL('https://www.zotero.org/support/kb/mendeley_import#using_mendeley_citations');
|
||||
window.close();
|
||||
});
|
||||
if (hideExtraMendeleyOptions) {
|
||||
document.getElementById('new-items-only-checkbox').removeAttribute('checked');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
|
@ -223,7 +322,7 @@ var Zotero_Import_Wizard = {
|
|||
|
||||
|
||||
onImportStart: async function () {
|
||||
if (!this._file && !this._mendeleyCode) {
|
||||
if (!this._file && !(this._mendeleyAuth || this._mendeleyCode)) {
|
||||
let index = document.getElementById('file-list').selectedIndex;
|
||||
this._file = this._dbs[index].path;
|
||||
}
|
||||
|
|
@ -235,10 +334,12 @@ var Zotero_Import_Wizard = {
|
|||
let result = await Zotero_File_Interface.importFile({
|
||||
file: this._file,
|
||||
onBeforeImport: this.onBeforeImport.bind(this),
|
||||
addToLibraryRoot: !document.getElementById('create-collection-checkbox')
|
||||
.hasAttribute('checked'),
|
||||
addToLibraryRoot: !document.getElementById('create-collection-checkbox').checked,
|
||||
linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1,
|
||||
mendeleyCode: this._mendeleyCode
|
||||
mendeleyAuth: this._mendeleyAuth,
|
||||
mendeleyCode: this._mendeleyCode,
|
||||
newItemsOnly: document.getElementById('new-items-only-checkbox').checked,
|
||||
relinkOnly: document.getElementById('relink-only-checkbox').checked
|
||||
});
|
||||
|
||||
// Cancelled by user or due to error
|
||||
|
|
@ -248,9 +349,12 @@ var Zotero_Import_Wizard = {
|
|||
}
|
||||
|
||||
let numItems = this._translation.newItems.length;
|
||||
let numRelinked = this._translation.numRelinked;
|
||||
this._onDone(
|
||||
Zotero.getString('fileInterface.importComplete'),
|
||||
Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems)
|
||||
document.getElementById('relink-only-checkbox').checked
|
||||
? Zotero.getString(`fileInterface.itemsWereRelinked`, numRelinked, numRelinked)
|
||||
: Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems)
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
title="&zotero.import;"
|
||||
onwizardcancel="Zotero_Import_Wizard.onCancel()"
|
||||
onload="Zotero_Import_Wizard.init()">
|
||||
|
||||
<script src="../include.js"/>
|
||||
|
|
@ -36,6 +37,17 @@
|
|||
>
|
||||
<description id="mendeley-online-description" />
|
||||
<description id="mendeley-online-description2" />
|
||||
<html:fieldset id="mendeley-login">
|
||||
<html:div class="field">
|
||||
<html:label for="mendeley-username"/>
|
||||
<html:input type="text" id="mendeley-username" />
|
||||
</html:div>
|
||||
<html:div class="field">
|
||||
<html:label for="mendeley-password"/>
|
||||
<html:input type="password" id="mendeley-password" />
|
||||
</html:div>
|
||||
</html:fieldset>
|
||||
<description id="mendeley-online-login-feedback" />
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-file-list"
|
||||
|
|
@ -66,6 +78,14 @@
|
|||
onpageshow="Zotero_Import_Wizard.onOptionsShown()"
|
||||
onpagerewound="return Zotero_Import_Wizard.goToStart()"
|
||||
onpageadvanced="Zotero_Import_Wizard.onImportStart()">
|
||||
|
||||
<vbox id="relink-only-wrapper">
|
||||
<hbox align="center">
|
||||
<checkbox id="relink-only-checkbox" label="&zotero.import.online.relinkOnly;" />
|
||||
<label id="relink-only-more-info" class="zotero-text-link" value="&zotero.general.moreInformation;"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" />
|
||||
|
||||
<vbox id="file-handling-options">
|
||||
|
|
@ -76,6 +96,9 @@
|
|||
</radiogroup>
|
||||
<description id="file-handling-description"/>
|
||||
</vbox>
|
||||
<vbox id="mendeley-options">
|
||||
<checkbox id="new-items-only-checkbox" label="&zotero.import.online.newItemsOnly;" checked="true" />
|
||||
</vbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-progress"
|
||||
|
|
|
|||
|
|
@ -1,22 +1,67 @@
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
var mendeleyAPIUtils = (function () {
|
||||
const OAUTH_URL = 'https://www.zotero.org/utils/mendeley/oauth';
|
||||
const ZOTERO_OAUTH_URL = 'https://www.zotero.org/utils/mendeley/oauth';
|
||||
const OAUTH_URL = 'https://api.mendeley.com/oauth/token';
|
||||
const MENDELEY_API_URL = 'https://api.mendeley.com';
|
||||
const CLIENT_ID = '6';
|
||||
const CLIENT_NOT_VERY_SECRET = 'JtSAMzFdwC6RAED3RMZU';
|
||||
const USER_AGENT = 'Mendeley Desktop/1.18';
|
||||
|
||||
const getTokens = async (codeOrRefreshToken, isRefresh = false) => {
|
||||
const options = {
|
||||
body: isRefresh
|
||||
? `grant_type=refresh_token&refresh_token=${codeOrRefreshToken}`
|
||||
: `grant_type=authorization_code&code=${codeOrRefreshToken}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
};
|
||||
const response = await Zotero.HTTP.request('POST', OAUTH_URL, options);
|
||||
const getTokens = async (url, bodyProps, headers = {}, options = {}) => {
|
||||
const body = Object.entries(bodyProps)
|
||||
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
||||
.join('&');
|
||||
|
||||
headers = { ...headers, 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
|
||||
if (!Zotero.Prefs.get('import.mendeleyUseOAuth')) {
|
||||
headers['User-Agent'] = USER_AGENT;
|
||||
}
|
||||
|
||||
options = { ...options, body, headers, timeout: 30000 };
|
||||
const response = await Zotero.HTTP.request('POST', url, options);
|
||||
return JSON.parse(response.responseText);
|
||||
};
|
||||
|
||||
const directAuth = async (username, password, headers = {}, options = {}) => {
|
||||
const bodyProps = {
|
||||
client_id: CLIENT_ID, // eslint-disable-line camelcase
|
||||
client_secret: CLIENT_NOT_VERY_SECRET, // eslint-disable-line camelcase
|
||||
grant_type: 'password', // eslint-disable-line camelcase
|
||||
password,
|
||||
scope: 'all',
|
||||
username
|
||||
};
|
||||
|
||||
return getTokens(OAUTH_URL, bodyProps, headers, options);
|
||||
};
|
||||
|
||||
const codeAuth = async (code, headers = {}, options = {}) => {
|
||||
const bodyProps = {
|
||||
grant_type: 'authorization_code', // eslint-disable-line camelcase
|
||||
code,
|
||||
};
|
||||
|
||||
return getTokens(ZOTERO_OAUTH_URL, bodyProps, headers, options);
|
||||
};
|
||||
|
||||
const refreshAuth = async (refreshToken, headers = {}, options = {}) => {
|
||||
const bodyProps = {
|
||||
grant_type: 'refresh_token', // eslint-disable-line camelcase
|
||||
refresh_token: refreshToken, // eslint-disable-line camelcase
|
||||
};
|
||||
|
||||
if (!Zotero.Prefs.get('import.mendeleyUseOAuth')) {
|
||||
bodyProps.client_id = CLIENT_ID; // eslint-disable-line camelcase
|
||||
bodyProps.client_secret = CLIENT_NOT_VERY_SECRET; // eslint-disable-line camelcase
|
||||
}
|
||||
|
||||
return getTokens(
|
||||
Zotero.Prefs.get('import.mendeleyUseOAuth') ? ZOTERO_OAUTH_URL : OAUTH_URL,
|
||||
bodyProps, headers, options
|
||||
);
|
||||
};
|
||||
|
||||
const getNextLinkFromResponse = (response) => {
|
||||
let next = null;
|
||||
let links = response.getResponseHeader('link');
|
||||
|
|
@ -33,7 +78,7 @@ const getNextLinkFromResponse = (response) => {
|
|||
|
||||
const apiFetchUrl = async (tokens, url, headers = {}, options = {}) => {
|
||||
headers = { ...headers, Authorization: `Bearer ${tokens.access_token}` };
|
||||
options = { ...options, headers };
|
||||
options = { ...options, headers, timeout: 60000 };
|
||||
const method = 'GET';
|
||||
|
||||
// Run the request. If we see 401 or 403, try to refresh tokens and run the request again
|
||||
|
|
@ -42,7 +87,7 @@ const apiFetchUrl = async (tokens, url, headers = {}, options = {}) => {
|
|||
}
|
||||
catch (e) {
|
||||
if (e.status === 401 || e.status === 403) {
|
||||
const newTokens = await getTokens(tokens.refresh_token, true);
|
||||
const newTokens = await refreshAuth(tokens.refresh_token);
|
||||
// update tokens in the tokens object and in the header for next request
|
||||
tokens.access_token = newTokens.access_token; // eslint-disable-line camelcase
|
||||
tokens.refresh_token = newTokens.refresh_token; // eslint-disable-line camelcase
|
||||
|
|
@ -64,21 +109,22 @@ const get = async (tokens, endPoint, params = {}, headers = {}, options = {}) =>
|
|||
return JSON.parse(response.responseText);
|
||||
};
|
||||
|
||||
const getAll = async (tokens, endPoint, params = {}, headers = {}, options = {}) => {
|
||||
const getAll = async (tokens, endPoint, params = {}, headers = {}, options = {}, interruptChecker = () => {}) => {
|
||||
const PER_PAGE = endPoint === 'annotations' ? 200 : 500;
|
||||
const response = await apiFetch(tokens, endPoint, { ...params, limit: PER_PAGE }, headers, options);
|
||||
var next = getNextLinkFromResponse(response);
|
||||
var data = JSON.parse(response.responseText);
|
||||
interruptChecker();
|
||||
|
||||
while (next) {
|
||||
const response = await apiFetchUrl(tokens, next, headers, options); //eslint-disable-line no-await-in-loop
|
||||
data = [...data, ...JSON.parse(response.responseText)];
|
||||
next = getNextLinkFromResponse(response);
|
||||
interruptChecker();
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
return { getNextLinkFromResponse, getTokens, apiFetch, apiFetchUrl, get, getAll };
|
||||
return { codeAuth, directAuth, getNextLinkFromResponse, apiFetch, apiFetchUrl, get, getAll };
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,29 +1,67 @@
|
|||
/* global map:false, mendeleyOnlineMappings:false, mendeleyAPIUtils:false */
|
||||
var EXPORTED_SYMBOLS = ["Zotero_Import_Mendeley"];
|
||||
/* eslint-disable no-await-in-loop, camelcase */
|
||||
/* global mendeleyDBMaps:false, mendeleyOnlineMappings:false, mendeleyAPIUtils:false */
|
||||
var EXPORTED_SYMBOLS = ["Zotero_Import_Mendeley"]; //eslint-disable-line no-unused-vars
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/include.js");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyOnlineMappings.js");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyAPIUtils.js");
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleySchemaMap.js");
|
||||
|
||||
const importerVersion = 1;
|
||||
const { apiTypeToDBType, apiFieldToDBField } = mendeleyOnlineMappings;
|
||||
const { apiFetch, get, getAll, getTokens } = mendeleyAPIUtils;
|
||||
const { apiFetch, codeAuth, directAuth, get, getAll } = mendeleyAPIUtils;
|
||||
|
||||
const colorMap = new Map();
|
||||
colorMap.set('rgb(255, 245, 173)', '#ffd400');
|
||||
colorMap.set('#fff5ad', '#ffd400');
|
||||
colorMap.set('rgb(255, 181, 182)', '#ff6666');
|
||||
colorMap.set('#ffb5b6', '#ff6666');
|
||||
colorMap.set('rgb(186, 226, 255)', '#2ea8e5');
|
||||
colorMap.set('#bae2ff', '#2ea8e5');
|
||||
colorMap.set('rgb(211, 194, 255)', '#a28ae5');
|
||||
colorMap.set('#d3c2ff', '#a28ae5');
|
||||
colorMap.set('rgb(220, 255, 176)', '#5fb236');
|
||||
colorMap.set('#dcffb0', '#5fb236');
|
||||
|
||||
var Zotero_Import_Mendeley = function () {
|
||||
this.createNewCollection = null;
|
||||
this.linkFiles = null;
|
||||
this.newItems = [];
|
||||
this.mendeleyCode = null;
|
||||
this.newCollections = [];
|
||||
this.mendeleyAuth = null;
|
||||
this.newItemsOnly = false;
|
||||
this.relinkOnly = false;
|
||||
this.numRelinked = 0;
|
||||
|
||||
this._tokens = null;
|
||||
this._db;
|
||||
this._file;
|
||||
this._db = null;
|
||||
this._file = null;
|
||||
this._saveOptions = null;
|
||||
this._itemDone;
|
||||
this._itemDone = () => {};
|
||||
this._progress = 0;
|
||||
this._progressMax;
|
||||
this._progressMax = 0;
|
||||
this._tmpFilesToDelete = [];
|
||||
this._caller = null;
|
||||
this._interrupted = false;
|
||||
this._totalSize = 0;
|
||||
this._started = Date.now();
|
||||
this._interruptChecker = (tickProgress = false) => {
|
||||
if (this._interrupted) {
|
||||
throw new Error(`Mendeley import interrupted!
|
||||
Started: ${this._started} (${Math.round((Date.now() - this._started) / 1000)}s ago)
|
||||
Progress: ${this._progress} / ${this._progressMax}
|
||||
New items created: ${this.newItems.length}
|
||||
Total size of files to download: ${Math.round(this._totalSize / 1024)}KB
|
||||
`);
|
||||
}
|
||||
|
||||
if (tickProgress) {
|
||||
this._progress++;
|
||||
this._itemDone();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype.setLocation = function (file) {
|
||||
|
|
@ -32,9 +70,9 @@ Zotero_Import_Mendeley.prototype.setLocation = function (file) {
|
|||
|
||||
Zotero_Import_Mendeley.prototype.setHandler = function (name, handler) {
|
||||
switch (name) {
|
||||
case 'itemDone':
|
||||
this._itemDone = handler;
|
||||
break;
|
||||
case 'itemDone':
|
||||
this._itemDone = handler;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -56,35 +94,19 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
skipSelect: true,
|
||||
...(options.saveOptions || {})
|
||||
};
|
||||
|
||||
if (true) {
|
||||
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleySchemaMap.js");
|
||||
}
|
||||
// TEMP: Load uncached from ~/zotero-client for development
|
||||
else {
|
||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
||||
let file = FileUtils.getDir("Home", []);
|
||||
file = OS.Path.join(file.path, 'zotero-client', 'chrome', 'content', 'zotero', 'import', 'mendeley', 'mendeleySchemaMap.js');
|
||||
let fileURI = OS.Path.toFileURI(file);
|
||||
let xmlhttp = await Zotero.HTTP.request(
|
||||
'GET',
|
||||
fileURI,
|
||||
{
|
||||
noCache: true,
|
||||
responseType: 'text'
|
||||
}
|
||||
);
|
||||
eval(xmlhttp.response);
|
||||
}
|
||||
|
||||
this.newItemsOnly = this.newItemsOnly || this.relinkOnly;
|
||||
|
||||
const libraryID = options.libraryID || Zotero.Libraries.userLibraryID;
|
||||
const { key: rootCollectionKey } = options.collections
|
||||
? Zotero.Collections.getLibraryAndKeyFromID(options.collections[0])
|
||||
: {};
|
||||
|
||||
Zotero.debug(`Begining Mendeley import at ${this._started}. libraryID: ${libraryID}, linkFiles: ${this.linkFiles}, rootCollectionKey: ${rootCollectionKey}, newItemsOnly: ${this.newItemsOnly}, relinkOnly: ${this.relinkOnly}`);
|
||||
|
||||
// TODO: Get appropriate version based on schema version
|
||||
const mapVersion = 83;
|
||||
map = map[mapVersion];
|
||||
const map = mendeleyDBMaps[mapVersion];
|
||||
|
||||
const mendeleyGroupID = 0;
|
||||
|
||||
|
|
@ -94,28 +116,42 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
if (this._file) {
|
||||
this._db = new Zotero.DBConnection(this._file);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
if (this._file && !await this._isValidDatabase()) {
|
||||
throw new Error("Not a valid Mendeley database");
|
||||
}
|
||||
|
||||
if (this.mendeleyCode) {
|
||||
this._tokens = await getTokens(this.mendeleyCode);
|
||||
if (this.mendeleyAuth) {
|
||||
this._tokens = this.mendeleyAuth;
|
||||
}
|
||||
else if (this.mendeleyCode) {
|
||||
this._tokens = await codeAuth(this.mendeleyCode);
|
||||
}
|
||||
|
||||
if (!this._file && !this._tokens) {
|
||||
throw new Error("Missing import token");
|
||||
}
|
||||
|
||||
const folders = this._tokens
|
||||
? await this._getFoldersAPI(mendeleyGroupID)
|
||||
: await this._getFoldersDB(mendeleyGroupID);
|
||||
// we don't know how long the import will be but want to show progress to give
|
||||
// feedback that import has started so we arbitrary set progress at 2%
|
||||
this._progress = 1;
|
||||
this._progressMax = 50;
|
||||
this._itemDone();
|
||||
|
||||
const collectionJSON = this._foldersToAPIJSON(folders, rootCollectionKey);
|
||||
const folderKeys = this._getFolderKeys(collectionJSON);
|
||||
await this._saveCollections(libraryID, collectionJSON, folderKeys);
|
||||
let folderKeys = new Map();
|
||||
if(!this.relinkOnly) {
|
||||
const folders = this._tokens
|
||||
? await this._getFoldersAPI(mendeleyGroupID)
|
||||
: await this._getFoldersDB(mendeleyGroupID);
|
||||
|
||||
const collectionJSON = this._foldersToAPIJSON(folders, rootCollectionKey);
|
||||
folderKeys = this._getFolderKeys(collectionJSON);
|
||||
|
||||
await this._saveCollections(libraryID, collectionJSON, folderKeys);
|
||||
}
|
||||
|
||||
this._interruptChecker(true);
|
||||
//
|
||||
// Items
|
||||
//
|
||||
|
|
@ -123,40 +159,59 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
? await this._getDocumentsAPI(mendeleyGroupID)
|
||||
: await this._getDocumentsDB(mendeleyGroupID);
|
||||
|
||||
this._progressMax = documents.length;
|
||||
// Update progress to reflect items to import and remaining meta data stages
|
||||
// We arbitrary set progress at approx 4%. We then add 8, one "tick" for each remaining meta data download.
|
||||
this._progress = Math.max(Math.floor(0.04 * documents.length), 2);
|
||||
this._progressMax = documents.length + this._progress + 8;
|
||||
// Get various attributes mapped to document ids
|
||||
let urls = this._tokens
|
||||
? await this._getDocumentURLsAPI(documents)
|
||||
: await this._getDocumentURLsDB(mendeleyGroupID);
|
||||
|
||||
this._interruptChecker(true);
|
||||
|
||||
let creators = this._tokens
|
||||
? await this._getDocumentCreatorsAPI(documents)
|
||||
: await this._getDocumentCreatorsDB(mendeleyGroupID, map.creatorTypes);
|
||||
|
||||
this._interruptChecker(true);
|
||||
|
||||
let tags = this._tokens
|
||||
? await this._getDocumentTagsAPI(documents)
|
||||
: await this._getDocumentTagsDB(mendeleyGroupID);
|
||||
|
||||
let collections = this._tokens
|
||||
this._interruptChecker(true);
|
||||
|
||||
let collections = this.relinkOnly ? new Map() : this._tokens
|
||||
? await this._getDocumentCollectionsAPI(documents, rootCollectionKey, folderKeys)
|
||||
: await this._getDocumentCollectionsDB(mendeleyGroupID, documents, rootCollectionKey, folderKeys);
|
||||
|
||||
let files = this._tokens
|
||||
this._interruptChecker(true);
|
||||
|
||||
let files = this.relinkOnly ? new Map() : this._tokens
|
||||
? await this._getDocumentFilesAPI(documents)
|
||||
: await this._getDocumentFilesDB(mendeleyGroupID);
|
||||
|
||||
|
||||
this._interruptChecker(true);
|
||||
|
||||
let annotations = this._tokens
|
||||
? await this._getDocumentAnnotationsAPI(mendeleyGroupID)
|
||||
: await this._getDocumentAnnotationsDB(mendeleyGroupID);
|
||||
|
||||
this._interruptChecker(true);
|
||||
|
||||
let profile = this._tokens
|
||||
? await this._getProfileAPI()
|
||||
: await this._getProfileDB();
|
||||
|
||||
this._interruptChecker(true);
|
||||
|
||||
let groups = this._tokens
|
||||
? await this._getGroupsAPI()
|
||||
: await this._getGroupsDB();
|
||||
|
||||
this._interruptChecker(true);
|
||||
|
||||
const fileHashLookup = new Map();
|
||||
|
||||
for (let [documentID, fileEntries] of files) {
|
||||
|
|
@ -165,7 +220,6 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
for (let group of groups) {
|
||||
let groupAnnotations = this._tokens
|
||||
? await this._getDocumentAnnotationsAPI(group.id, profile.id)
|
||||
|
|
@ -262,12 +316,20 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
annotations.get(document.id)
|
||||
);
|
||||
}
|
||||
this.newItems.push(Zotero.Items.get(documentIDMap.get(document.id)));
|
||||
this._progress++;
|
||||
if (this._itemDone) {
|
||||
this._itemDone();
|
||||
}
|
||||
this._interruptChecker(true);
|
||||
}
|
||||
if (this.newItemsOnly && rootCollectionKey && this.newItems.length === 0) {
|
||||
Zotero.debug(`Mendeley Import detected no new items, removing import collection containing ${this.newCollections.length} collections created during the import`);
|
||||
const rootCollection = await Zotero.Collections.getAsync(options.collections[0]);
|
||||
await rootCollection.eraseTx(this._saveOptions);
|
||||
}
|
||||
|
||||
await Zotero.DB.queryAsync("REPLACE INTO settings VALUES ('mendeleyImport', 'version', ?)", importerVersion);
|
||||
|
||||
Zotero.debug(`Completed Mendeley import in ${Math.round((Date.now() - this._started) / 1000)}s. (Started: ${this._started})`);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
|
|
@ -275,6 +337,7 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
await this._db.closeDatabase();
|
||||
}
|
||||
if (this._tokens) {
|
||||
Zotero.debug(`Clearing ${this._tmpFilesToDelete.length} temporary files after Mendeley import`);
|
||||
await Promise.all(
|
||||
this._tmpFilesToDelete.map(f => this._removeTemporaryFile(f))
|
||||
);
|
||||
|
|
@ -288,14 +351,20 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
}
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype.interrupt = function () {
|
||||
this._interrupted = true;
|
||||
|
||||
if (this._caller) {
|
||||
this._caller.stop();
|
||||
}
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype._removeTemporaryFile = async function (file) {
|
||||
const containingDir = OS.Path.dirname(file);
|
||||
try {
|
||||
await Zotero.File.removeIfExists(file);
|
||||
await OS.File.removeEmptyDir(containingDir);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.logError("Error while removing temporary file " + file + ": " + e);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -345,7 +414,7 @@ Zotero_Import_Mendeley.prototype._getFoldersAPI = async function (groupID) {
|
|||
if (groupID && groupID !== 0) {
|
||||
params.group_id = groupID; //eslint-disable-line camelcase
|
||||
}
|
||||
return (await getAll(this._tokens, 'folders', params, headers)).map(f => ({
|
||||
return (await getAll(this._tokens, 'folders', params, headers, {}, this._interruptChecker)).map(f => ({
|
||||
id: f.id,
|
||||
uuid: f.id,
|
||||
name: f.name,
|
||||
|
|
@ -365,11 +434,11 @@ Zotero_Import_Mendeley.prototype._foldersToAPIJSON = function (folderRows, paren
|
|||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype._getFolderDescendents = function (folderID, folderKey, folderRows, maxDepth) {
|
||||
if (maxDepth == 0) return []
|
||||
if (maxDepth == 0) return [];
|
||||
var descendents = [];
|
||||
var children = folderRows
|
||||
.filter(f => f.parentId == folderID)
|
||||
.map(f => {
|
||||
.map((f) => {
|
||||
let c = {
|
||||
folderID: f.id,
|
||||
remoteUUID: f.remoteUuid,
|
||||
|
|
@ -431,6 +500,7 @@ Zotero_Import_Mendeley.prototype._saveCollections = async function (libraryID, j
|
|||
collection.key = collectionJSON.key;
|
||||
await collection.loadPrimaryData();
|
||||
}
|
||||
this.newCollections.push(collection);
|
||||
}
|
||||
|
||||
// Remove external ids before saving
|
||||
|
|
@ -492,11 +562,19 @@ Zotero_Import_Mendeley.prototype._getDocumentsAPI = async function (groupID) {
|
|||
}
|
||||
|
||||
|
||||
return (await getAll(this._tokens, 'documents', params, headers)).map(d => ({
|
||||
...d,
|
||||
uuid: d.id,
|
||||
remoteUuid: d.id
|
||||
}));
|
||||
return (await getAll(this._tokens, 'documents', params, headers, {}, this._interruptChecker)).map(d => {
|
||||
const processedDocument = { ...d, remoteUuid: d.id };
|
||||
|
||||
try {
|
||||
const clientData = JSON.parse(d.client_data);
|
||||
processedDocument.uuid = clientData.desktop_id ? clientData.desktop_id : d.id;
|
||||
}
|
||||
catch (_) {
|
||||
processedDocument.uuid = d.id;
|
||||
}
|
||||
|
||||
return processedDocument;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -558,7 +636,12 @@ Zotero_Import_Mendeley.prototype._getDocumentCreatorsAPI = async function (docum
|
|||
const authors = (doc.authors || []).map(c => this._makeCreator('author', c.first_name, c.last_name));
|
||||
const editors = (doc.editors || []).map(c => this._makeCreator('editor', c.first_name, c.last_name));
|
||||
const translators = (doc.translators || []).map(c => this._makeCreator('translator', c.first_name, c.last_name));
|
||||
map.set(doc.id, [...authors, ...editors, ...translators]);
|
||||
const creators = [...authors, ...editors, ...translators];
|
||||
const validCreators = creators.filter(c => c.name || c.firstName || c.lastName);
|
||||
if (creators.length !== validCreators.length) {
|
||||
Zotero.debug(`Discarding ${creators.length - validCreators.length} invalid creators for document ${doc.id}`);
|
||||
}
|
||||
map.set(doc.id, validCreators);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
|
@ -597,7 +680,10 @@ Zotero_Import_Mendeley.prototype._getDocumentTagsDB = async function (groupID) {
|
|||
Zotero_Import_Mendeley.prototype._getDocumentTagsAPI = async function (documents) {
|
||||
var map = new Map();
|
||||
for (let doc of documents) {
|
||||
const tags = [...(doc.tags || []).map(tag => ({ tag, type: 0 })), ...(doc.keywords || []).map(tag => ({ tag, type: 1 }))];
|
||||
const tags = [
|
||||
...(doc.tags || []).map(tag => ({ tag, type: 0 })),
|
||||
...(doc.keywords || []).map(tag => ({ tag, type: 1 }))
|
||||
].filter(t => t.tag && t.tag.trim());
|
||||
map.set(doc.id, tags);
|
||||
}
|
||||
return map;
|
||||
|
|
@ -693,12 +779,13 @@ Zotero_Import_Mendeley.prototype._fetchFile = async function (fileID, filePath)
|
|||
Zotero_Import_Mendeley.prototype._getDocumentFilesAPI = async function (documents) {
|
||||
const map = new Map();
|
||||
|
||||
let totalSize = 0;
|
||||
this._totalSize = 0;
|
||||
|
||||
Components.utils.import("resource://zotero/concurrentCaller.js");
|
||||
var caller = new ConcurrentCaller({
|
||||
this._caller = new ConcurrentCaller({
|
||||
numConcurrent: 6,
|
||||
onError: e => Zotero.logError(e),
|
||||
logger: Zotero.debug,
|
||||
Promise: Zotero.Promise
|
||||
});
|
||||
|
||||
|
|
@ -712,7 +799,7 @@ Zotero_Import_Mendeley.prototype._getDocumentFilesAPI = async function (document
|
|||
let tmpFile = OS.Path.join(Zotero.getTempDirectory().path, `m-api-${file.id}.${ext}`);
|
||||
|
||||
this._tmpFilesToDelete.push(tmpFile);
|
||||
caller.add(this._fetchFile.bind(this, file.id, tmpFile));
|
||||
this._caller.add(this._fetchFile.bind(this, file.id, tmpFile));
|
||||
files.push({
|
||||
fileURL: OS.Path.toFileURI(tmpFile),
|
||||
title: file.file_name || '',
|
||||
|
|
@ -720,13 +807,14 @@ Zotero_Import_Mendeley.prototype._getDocumentFilesAPI = async function (document
|
|||
hash: file.filehash,
|
||||
fileBaseName
|
||||
});
|
||||
totalSize += file.size;
|
||||
this._totalSize += file.size;
|
||||
this._progressMax += 1;
|
||||
}
|
||||
map.set(doc.id, files);
|
||||
}
|
||||
// TODO: check if enough space available totalSize
|
||||
await caller.runAll();
|
||||
await this._caller.runAll();
|
||||
this._caller = null;
|
||||
return map;
|
||||
};
|
||||
|
||||
|
|
@ -823,7 +911,7 @@ Zotero_Import_Mendeley.prototype._getDocumentAnnotationsAPI = async function (gr
|
|||
}
|
||||
|
||||
const map = new Map();
|
||||
(await getAll(this._tokens, 'annotations', params, { Accept: 'application/vnd.mendeley-annotation.1+json' }))
|
||||
(await getAll(this._tokens, 'annotations', params, { Accept: 'application/vnd.mendeley-annotation.1+json' }, {}, this._interruptChecker))
|
||||
.forEach((a) => {
|
||||
if (profileID !== null && a.profile_id !== profileID) {
|
||||
// optionally filter annotations by profile id
|
||||
|
|
@ -885,7 +973,7 @@ Zotero_Import_Mendeley.prototype._getGroupsAPI = async function () {
|
|||
const params = { type: 'all' };
|
||||
const headers = { Accept: 'application/vnd.mendeley-group-list+json' };
|
||||
|
||||
return getAll(this._tokens, 'groups/v2', params, headers);
|
||||
return getAll(this._tokens, 'groups/v2', params, headers, {}, this._interruptChecker);
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype._getGroupsDB = async function () {
|
||||
|
|
@ -985,14 +1073,14 @@ Zotero_Import_Mendeley.prototype._documentToAPIJSON = async function (map, docum
|
|||
|
||||
for (let field in parent) {
|
||||
switch (field) {
|
||||
case 'itemType':
|
||||
case 'key':
|
||||
case 'parentItem':
|
||||
case 'note':
|
||||
case 'creators':
|
||||
case 'dateAdded':
|
||||
case 'dateModified':
|
||||
continue;
|
||||
case 'itemType':
|
||||
case 'key':
|
||||
case 'parentItem':
|
||||
case 'note':
|
||||
case 'creators':
|
||||
case 'dateAdded':
|
||||
case 'dateModified':
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move unknown/invalid fields to Extra
|
||||
|
|
@ -1035,7 +1123,7 @@ Zotero_Import_Mendeley.prototype._documentToAPIJSON = async function (map, docum
|
|||
// seriesEditor isn't valid on some item types (e.g., book)
|
||||
if (creator.creatorType == 'seriesEditor'
|
||||
&& !Zotero.CreatorTypes.isValidForItemType(
|
||||
Zotero.CreatorTypes.getID('seriesEditor'), itemTypeID)) {
|
||||
Zotero.CreatorTypes.getID('seriesEditor'), itemTypeID)) {
|
||||
creator.creatorType = 'editor';
|
||||
}
|
||||
}
|
||||
|
|
@ -1160,7 +1248,6 @@ Zotero_Import_Mendeley.prototype._processField = function (parent, children, zFi
|
|||
}
|
||||
else {
|
||||
Zotero.warn(`Unknown function subfield: ${subfield}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -1191,20 +1278,21 @@ Zotero_Import_Mendeley.prototype._makeCreator = function (creatorType, firstName
|
|||
Zotero_Import_Mendeley.prototype._addExtraField = function (extra, field, val) {
|
||||
// Strip the field if it appears at the beginning of the value (to avoid "DOI: DOI: 10...")
|
||||
if (typeof val == 'string') {
|
||||
val = val.replace(new RegExp(`^${field}:\s*`, 'i'), "");
|
||||
val = val.replace(new RegExp(`^${field}:\\s*`, 'i'), "");
|
||||
}
|
||||
extra = extra ? extra + '\n' : '';
|
||||
if (field != 'arXiv') {
|
||||
field = field[0].toUpperCase() + field.substr(1);
|
||||
field = field.replace(/([a-z])([A-Z][a-z])/, "$1 $2");
|
||||
}
|
||||
|
||||
return extra + `${field}: ${val}`;
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype._convertNote = function (note) {
|
||||
return note
|
||||
// Add newlines after <br>
|
||||
.replace(/<br\s*\/>/g, '<br\/>\n')
|
||||
.replace(/<br\s*\/>/g, '<br/>\n')
|
||||
//
|
||||
// Legacy pre-HTML stuff
|
||||
//
|
||||
|
|
@ -1228,16 +1316,19 @@ Zotero_Import_Mendeley.prototype._saveItems = async function (libraryID, json) {
|
|||
var lastExistingParentItem;
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
let itemJSON = json[i];
|
||||
let isMappedToExisting = false;
|
||||
|
||||
// Check if the item has been previously imported
|
||||
let item = await this._findExistingItem(libraryID, itemJSON, lastExistingParentItem);
|
||||
if (item) {
|
||||
isMappedToExisting = true;
|
||||
if (item.isRegularItem()) {
|
||||
lastExistingParentItem = item;
|
||||
|
||||
// Update any child items to point to the existing item's key instead of the
|
||||
// new generated one
|
||||
this._updateParentKeys('item', json, i + 1, itemJSON.key, item.key);
|
||||
this.numRelinked++;
|
||||
|
||||
// Leave item in any collections it's in
|
||||
itemJSON.collections = item.getCollections()
|
||||
|
|
@ -1255,12 +1346,33 @@ Zotero_Import_Mendeley.prototype._saveItems = async function (libraryID, json) {
|
|||
await item.loadPrimaryData();
|
||||
}
|
||||
}
|
||||
|
||||
if(this.relinkOnly && !isMappedToExisting) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove external id before save
|
||||
let toSave = Object.assign({}, itemJSON);
|
||||
delete toSave.documentID;
|
||||
|
||||
item.fromJSON(toSave);
|
||||
|
||||
if ((this.newItemsOnly && !isMappedToExisting) || !this.newItemsOnly) {
|
||||
if (isMappedToExisting) {
|
||||
// dateAdded shouldn't change on an updated item. See #2881
|
||||
delete toSave.dateAdded;
|
||||
}
|
||||
item.fromJSON(toSave);
|
||||
this.newItems.push(item);
|
||||
}
|
||||
else if (isMappedToExisting && toSave.relations) {
|
||||
const predicate = 'mendeleyDB:documentUUID';
|
||||
const existingRels = item.getRelationsByPredicate(predicate);
|
||||
const newRel = toSave.relations[predicate];
|
||||
if (existingRels.length && newRel && existingRels[0] !== newRel) {
|
||||
Zotero.debug(`Migrating relation ${predicate} for existing item ${item.key} from ${existingRels[0]} to ${newRel}`);
|
||||
item.removeRelation(predicate, existingRels[0]);
|
||||
item.addRelation(predicate, newRel);
|
||||
}
|
||||
}
|
||||
await item.saveTx({
|
||||
skipDateModifiedUpdate: true,
|
||||
...this._saveOptions
|
||||
|
|
@ -1360,7 +1472,7 @@ Zotero_Import_Mendeley.prototype._findExistingItem = async function (libraryID,
|
|||
Zotero.debug(`Found existing item ${existingItem.libraryKey} for `
|
||||
+ `${predicate} ${itemJSON.relations[predicate]}`);
|
||||
return existingItem;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Zotero_Import_Mendeley.prototype._getItemByRelation = async function (libraryID, predicate, object) {
|
||||
|
|
@ -1443,7 +1555,7 @@ Zotero_Import_Mendeley.prototype._saveFilesAndAnnotations = async function (file
|
|||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Zotero_Import_Mendeley.prototype._findExistingFile = function (parentItemID, file) {
|
||||
|
|
@ -1460,7 +1572,7 @@ Zotero_Import_Mendeley.prototype._findExistingFile = function (parentItemID, fil
|
|||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype._isDownloadedFile = function (path) {
|
||||
var parentDir = OS.Path.dirname(path);
|
||||
|
|
@ -1468,7 +1580,7 @@ Zotero_Import_Mendeley.prototype._isDownloadedFile = function (path) {
|
|||
|| parentDir.endsWith(OS.Path.join('Local', 'Mendeley Ltd', 'Mendeley Desktop', 'Downloaded'))
|
||||
|| parentDir.endsWith(OS.Path.join('Local', 'Mendeley Ltd.', 'Mendeley Desktop', 'Downloaded'))
|
||||
|| parentDir.endsWith(OS.Path.join('data', 'Mendeley Ltd.', 'Mendeley Desktop', 'Downloaded'));
|
||||
}
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype._isTempDownloadedFile = function (path) {
|
||||
return path.startsWith(OS.Path.join(Zotero.getTempDirectory().path, 'm-api'));
|
||||
|
|
@ -1498,7 +1610,7 @@ Zotero_Import_Mendeley.prototype._getRealFilePath = async function (path) {
|
|||
return altPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations, parentItemID, attachmentItemID, fileHash) {
|
||||
if (!annotations.length) return;
|
||||
|
|
@ -1507,7 +1619,6 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
|
|||
var libraryID = parentItem.libraryID;
|
||||
if (attachmentItemID) {
|
||||
var attachmentItem = Zotero.Items.get(attachmentItemID);
|
||||
var attachmentURIPath = Zotero.API.getLibraryPrefix(libraryID) + '/items/' + attachmentItem.key;
|
||||
|
||||
if (attachmentItem) {
|
||||
let file = await attachmentItem.getFilePathAsync();
|
||||
|
|
@ -1530,6 +1641,10 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
|
|||
}
|
||||
// PDFWorker needs 'id'
|
||||
annotations.forEach(annotation => annotation.id = annotation.uuid);
|
||||
annotations.forEach(annotation => (colorMap.has(annotation.color)
|
||||
? annotation.color = colorMap.get(annotation.color)
|
||||
: annotation.color
|
||||
));
|
||||
// Returns 'id', 'position', 'type', 'pageLabel', 'sortIndex', 'text' (for highlight)
|
||||
Zotero.debug("Processing annotations in " + file);
|
||||
annotations = await Zotero.PDFWorker.processMendeleyAnnotations(file, annotations);
|
||||
|
|
@ -1616,7 +1731,7 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
|
|||
});
|
||||
}
|
||||
note.setNote('<h1>' + Zotero.getString('extractedAnnotations') + '</h1>\n' + noteStrings.join('\n'));
|
||||
return note.saveTx(this._saveOptions);
|
||||
await note.saveTx(this._saveOptions);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1633,17 +1748,7 @@ Zotero_Import_Mendeley.prototype._updateParentKeys = function (objectType, json,
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero_Import_Mendeley.prototype._updateItemCollectionKeys = function (json, oldKey, newKey) {
|
||||
for (; i < json.length; i++) {
|
||||
let x = json[i];
|
||||
if (x[prop] == oldKey) {
|
||||
x[prop] = newKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//
|
||||
// Clean up extra files created <5.0.51
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ var mendeleyOnlineMappings = {
|
|||
arxiv: 'arxivId',
|
||||
accessed: 'dateAccessed',
|
||||
authors: false, // all author types handled separately
|
||||
client_data: false, // desktop_id extraction handled separately
|
||||
citation_key: 'citationKey',
|
||||
created: 'added',
|
||||
edition: 'edition',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
var map = {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var mendeleyDBMaps = {
|
||||
83: {
|
||||
itemTypes: {
|
||||
Bill: "bill",
|
||||
|
|
|
|||
|
|
@ -113,9 +113,10 @@ var Zotero_Citation_Dialog = new function () {
|
|||
var menu = document.getElementById("label");
|
||||
var label_list = document.getElementById("locator-type-popup");
|
||||
var i = 0;
|
||||
var pageLocatorIndex;
|
||||
for(var value in locators) {
|
||||
var locator = locators[value];
|
||||
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
|
||||
let locatorLabel = Zotero.Cite.getLocatorString(locator);
|
||||
// add to list of labels
|
||||
var child = document.createElement("menuitem");
|
||||
child.setAttribute("value", value);
|
||||
|
|
@ -124,9 +125,13 @@ var Zotero_Citation_Dialog = new function () {
|
|||
// add to array
|
||||
_locatorIndexArray[locator] = i;
|
||||
_locatorNameArray[i] = locator;
|
||||
if (locator == 'page') {
|
||||
pageLocatorIndex = i;
|
||||
_preserveData.label[1] = i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
menu.selectedIndex = 0;
|
||||
menu.selectedIndex = pageLocatorIndex;
|
||||
|
||||
// load (from selectItemsDialog.js)
|
||||
yield doLoad();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ var Zotero_QuickFormat = new function () {
|
|||
const pixelRe = /^([0-9]+)px$/
|
||||
const specifiedLocatorRe = /^(?:,? *(p{1,2})(?:\. *| *)|:)([0-9\-]+) *$/;
|
||||
const yearRe = /,? *([0-9]+) *(B[. ]*C[. ]*(?:E[. ]*)?|A[. ]*D[. ]*|C[. ]*E[. ]*)?$/i;
|
||||
const locatorRe = /(?:,? *(p{0,2})\.?|(\:)) *([0-9\-–]+)$/i;
|
||||
const locatorRe = / (?:,? *(p{0,2})\.?|(\:)) *([0-9\-–]+)$/i;
|
||||
const creatorSplitRe = /(?:,| *(?:and|\&)) +/;
|
||||
const charRe = /[\w\u007F-\uFFFF]/;
|
||||
const numRe = /^[0-9\-–]+$/;
|
||||
|
|
@ -37,7 +37,8 @@ var Zotero_QuickFormat = new function () {
|
|||
keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0,
|
||||
separatorHeight = 0, currentLocator, currentLocatorLabel, currentSearchTime, dragging,
|
||||
panel, panelPrefix, panelSuffix, panelSuppressAuthor, panelLocatorLabel, panelLocator,
|
||||
panelLibraryLink, panelInfo, panelRefersToBubble, panelFrameHeight = 0, accepted = false;
|
||||
panelLibraryLink, panelInfo, panelRefersToBubble, panelFrameHeight = 0, accepted = false,
|
||||
isPaste = false;
|
||||
var locatorLocked = true;
|
||||
var locatorNode = null;
|
||||
var _searchPromise;
|
||||
|
|
@ -116,10 +117,9 @@ var Zotero_QuickFormat = new function () {
|
|||
|
||||
// add labels to popup
|
||||
var locators = Zotero.Cite.labels;
|
||||
var menu = document.getElementById("locator-label");
|
||||
var labelList = document.getElementById("locator-label-popup");
|
||||
for(var locator of locators) {
|
||||
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
|
||||
let locatorLabel = Zotero.Cite.getLocatorString(locator);
|
||||
|
||||
// add to list of labels
|
||||
var child = document.createElement("menuitem");
|
||||
|
|
@ -127,7 +127,6 @@ var Zotero_QuickFormat = new function () {
|
|||
child.setAttribute("label", locatorLabel);
|
||||
labelList.appendChild(child);
|
||||
}
|
||||
menu.selectedIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -217,10 +216,10 @@ var Zotero_QuickFormat = new function () {
|
|||
|
||||
// Range could be referenced to the body element
|
||||
if(node === qfe) {
|
||||
var offset = range.startOffset;
|
||||
if(offset !== range.endOffset) return false;
|
||||
node = qfe.childNodes[Math.min(qfe.childNodes.length-1, offset)];
|
||||
if(node.nodeType === Node.TEXT_NODE) return node;
|
||||
for (let i = qfe.childNodes.length - 1; i >= 0; i--) {
|
||||
node = qfe.childNodes[i];
|
||||
if(node.nodeType === Node.TEXT_NODE) return node;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -240,7 +239,7 @@ var Zotero_QuickFormat = new function () {
|
|||
* @return {String} str without locator
|
||||
*/
|
||||
function _updateLocator(str) {
|
||||
m = locatorRe.exec(str);
|
||||
m = !isPaste && locatorRe.exec(str);
|
||||
if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) {
|
||||
currentLocator = m[3];
|
||||
str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
|
||||
|
|
@ -500,6 +499,7 @@ var Zotero_QuickFormat = new function () {
|
|||
await Zotero.Items.loadDataTypes(items);
|
||||
|
||||
searchString = searchString.toLowerCase();
|
||||
let searchParts = Zotero.SearchConditions.parseSearchString(searchString);
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
|
||||
function _itemSort(a, b) {
|
||||
|
|
@ -508,13 +508,15 @@ var Zotero_QuickFormat = new function () {
|
|||
// Favor left-bound name matches (e.g., "Baum" < "Appelbaum"),
|
||||
// using last name of first author
|
||||
if (firstCreatorA && firstCreatorB) {
|
||||
let caStartsWith = firstCreatorA.toLowerCase().indexOf(searchString) == 0;
|
||||
let cbStartsWith = firstCreatorB.toLowerCase().indexOf(searchString) == 0;
|
||||
if (caStartsWith && !cbStartsWith) {
|
||||
return -1;
|
||||
}
|
||||
else if (!caStartsWith && cbStartsWith) {
|
||||
return 1;
|
||||
for (let part of searchParts) {
|
||||
let caStartsWith = firstCreatorA.toLowerCase().startsWith(part.text);
|
||||
let cbStartsWith = firstCreatorB.toLowerCase().startsWith(part.text);
|
||||
if (caStartsWith && !cbStartsWith) {
|
||||
return -1;
|
||||
}
|
||||
else if (!caStartsWith && cbStartsWith) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -729,9 +731,7 @@ var Zotero_QuickFormat = new function () {
|
|||
|
||||
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
|
||||
title = item.getDisplayTitle();
|
||||
if (item.isNote()) {
|
||||
title = title.substr(0, 24) + '…';
|
||||
}
|
||||
title = title.substr(0, 32) + (title.length > 32 ? "…" : "");
|
||||
if (!str) {
|
||||
str = Zotero.getString("punctuation.openingQMark") + title + Zotero.getString("punctuation.closingQMark");
|
||||
}
|
||||
|
|
@ -758,15 +758,17 @@ var Zotero_QuickFormat = new function () {
|
|||
|
||||
// Prefix
|
||||
if(citationItem.prefix && Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP) {
|
||||
str = citationItem.prefix
|
||||
let prefix = citationItem.prefix.substr(0, 10) + (citationItem.prefix.length > 10 ? "…" : "")
|
||||
str = prefix
|
||||
+(Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? " " : "")
|
||||
+str;
|
||||
}
|
||||
|
||||
// Suffix
|
||||
if(citationItem.suffix && Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP) {
|
||||
let suffix = citationItem.suffix.substr(0, 10) + (citationItem.suffix.length > 10 ? "…" : "")
|
||||
str += (Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? " " : "")
|
||||
+citationItem.suffix;
|
||||
+ suffix;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
|
@ -778,9 +780,6 @@ var Zotero_QuickFormat = new function () {
|
|||
function _insertBubble(citationItem, nextNode) {
|
||||
var str = _buildBubbleString(citationItem);
|
||||
|
||||
// It's entirely unintuitive why, but after trying a bunch of things, it looks like using
|
||||
// a XUL label for these things works best. A regular span causes issues with moving the
|
||||
// cursor.
|
||||
var bubble = qfiDocument.createElement("span");
|
||||
bubble.setAttribute("class", "citation-dialog bubble");
|
||||
bubble.setAttribute("draggable", "true");
|
||||
|
|
@ -871,6 +870,7 @@ var Zotero_QuickFormat = new function () {
|
|||
// handling to maintain the correct locator node in
|
||||
// _showCitation()
|
||||
var bubble = locatorNode = _insertBubble(citationItem, node);
|
||||
isPaste = false;
|
||||
_clearEntryList();
|
||||
yield _previewAndSort();
|
||||
_refocusQfe();
|
||||
|
|
@ -1085,15 +1085,16 @@ var Zotero_QuickFormat = new function () {
|
|||
let citationItem = JSON.parse(target.dataset.citationItem);
|
||||
panelPrefix.value = citationItem["prefix"] ? citationItem["prefix"] : "";
|
||||
panelSuffix.value = citationItem["suffix"] ? citationItem["suffix"] : "";
|
||||
var pageOption = panelLocatorLabel.getElementsByAttribute("value", "page")[0];
|
||||
if(citationItem["label"]) {
|
||||
var option = panelLocatorLabel.getElementsByAttribute("value", citationItem["label"]);
|
||||
if(option.length) {
|
||||
panelLocatorLabel.selectedItem = option[0];
|
||||
} else {
|
||||
panelLocatorLabel.selectedIndex = 0;
|
||||
panelLocatorLabel.selectedItem = pageOption;
|
||||
}
|
||||
} else {
|
||||
panelLocatorLabel.selectedIndex = 0;
|
||||
panelLocatorLabel.selectedItem = pageOption;
|
||||
}
|
||||
panelLocator.value = citationItem["locator"] ? citationItem["locator"] : "";
|
||||
panelSuppressAuthor.checked = !!citationItem["suppress-author"];
|
||||
|
|
@ -1356,6 +1357,7 @@ var Zotero_QuickFormat = new function () {
|
|||
event.preventDefault();
|
||||
}
|
||||
} else {
|
||||
isPaste = false;
|
||||
_resetSearchTimer();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ const { noop, getDragTargetOrient } = require("components/utils");
|
|||
const PropTypes = require("prop-types");
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const { IntlProvider } = require('react-intl');
|
||||
const LibraryTree = require('./libraryTree');
|
||||
const VirtualizedTable = require('components/virtualized-table');
|
||||
const { renderCell, formatColumnName } = VirtualizedTable;
|
||||
|
|
@ -38,9 +37,8 @@ const { Cc, Ci, Cu } = require('chrome');
|
|||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
const CHILD_INDENT = 12;
|
||||
const COLORED_TAGS_RE = new RegExp("^[0-" + Zotero.Tags.MAX_COLORED_TAGS + "]{1}$");
|
||||
const COLORED_TAGS_RE = new RegExp("^(?:Numpad|Digit)([0-" + Zotero.Tags.MAX_COLORED_TAGS + "]{1})$");
|
||||
const COLUMN_PREFS_FILEPATH = OS.Path.join(Zotero.Profile.dir, "treePrefs.json");
|
||||
const EMOJI_RE = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu;
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const ATTACHMENT_STATE_LOAD_DELAY = 150; //ms
|
||||
|
||||
|
|
@ -50,9 +48,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
var ref;
|
||||
opts.domEl = domEl;
|
||||
let elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<ItemTree ref={c => ref = c } {...opts} />
|
||||
</IntlProvider>
|
||||
<ItemTree ref={c => ref = c } {...opts} />
|
||||
);
|
||||
await new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
|
||||
|
||||
|
|
@ -448,11 +444,20 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
let row = this.getRowIndexByID(id);
|
||||
if (row === false) continue;
|
||||
let item = Zotero.Items.get(id);
|
||||
if (!item.deleted && !item.numChildren()) {
|
||||
// Remove parent row if it isn't deleted and doesn't have any deleted children
|
||||
// (shown by the numChildren including deleted being the same as numChildren not including deleted)
|
||||
if (!item.deleted && (!item.isRegularItem() || item.numChildren(true) == item.numChildren(false))) {
|
||||
rows.push(row);
|
||||
// And all its children in the tree
|
||||
for (let child = row + 1; child < this.rowCount && this.getLevel(child) > this.getLevel(row); child++) {
|
||||
rows.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._removeRows(rows);
|
||||
if (rows.length) {
|
||||
this._removeRows(rows);
|
||||
this.tree.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -475,14 +480,17 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
return;
|
||||
}
|
||||
|
||||
var visibleSubcollections = Zotero.Prefs.get('recursiveCollections')
|
||||
? collectionTreeRow.ref.getDescendents(false, 'collection')
|
||||
: [];
|
||||
var splitIDs = [];
|
||||
for (let id of ids) {
|
||||
var split = id.split('-');
|
||||
// Skip if not an item in this collection
|
||||
if (split[0] != collectionTreeRow.ref.id) {
|
||||
continue;
|
||||
// Include if an item in this collection or a visible subcollection
|
||||
if (split[0] == collectionTreeRow.ref.id
|
||||
|| visibleSubcollections.some(c => split[0] == c.id)) {
|
||||
splitIDs.push(split[1]);
|
||||
}
|
||||
splitIDs.push(split[1]);
|
||||
}
|
||||
ids = splitIDs;
|
||||
}
|
||||
|
|
@ -859,9 +867,9 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
if (!event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey && COLORED_TAGS_RE.test(event.key)) {
|
||||
if (!event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey && COLORED_TAGS_RE.test(event.code)) {
|
||||
let libraryID = this.collectionTreeRow.ref.libraryID;
|
||||
let position = parseInt(event.key) - 1;
|
||||
let position = COLORED_TAGS_RE.exec(event.code)[1] - 1;
|
||||
// When 0 is pressed, remove all colored tags
|
||||
if (position == -1) {
|
||||
let items = this.getSelectedItems();
|
||||
|
|
@ -978,7 +986,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
|
||||
onItemContextMenu: (...args) => this.props.onContextMenu(...args),
|
||||
|
||||
role: 'treegrid',
|
||||
role: 'tree',
|
||||
label: Zotero.getString('pane.items.title'),
|
||||
}
|
||||
);
|
||||
|
|
@ -1241,20 +1249,14 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
? `for ${itemIDs.length} ` + Zotero.Utilities.pluralize(itemIDs.length, ['item', 'items'])
|
||||
: ""));
|
||||
|
||||
// Set whether rows with empty values should be displayed last,
|
||||
// which may be different for primary and secondary sorting.
|
||||
var emptyFirst = {};
|
||||
switch (primaryField) {
|
||||
case 'title':
|
||||
emptyFirst.title = true;
|
||||
break;
|
||||
|
||||
// When sorting by title we want empty titles at the top, but if not
|
||||
// sorting by title, empty titles should sort to the bottom so that new
|
||||
// empty items don't get sorted to the middle of the items list.
|
||||
default:
|
||||
emptyFirst.title = false;
|
||||
}
|
||||
// Set whether rows with empty values should sort at the beginning
|
||||
var emptyFirst = {
|
||||
title: true,
|
||||
|
||||
// Date columns start descending, so put empty rows at end
|
||||
date: true,
|
||||
year: true,
|
||||
};
|
||||
|
||||
// Cache primary values while sorting, since base-field-mapped getField()
|
||||
// calls are relatively expensive
|
||||
|
|
@ -1262,7 +1264,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
sortFields.forEach(x => cache[x] = {});
|
||||
|
||||
// Get the display field for a row (which might be a placeholder title)
|
||||
function getField(field, row) {
|
||||
let getField = (field, row) => {
|
||||
var item = row.ref;
|
||||
|
||||
switch (field) {
|
||||
|
|
@ -1270,23 +1272,12 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
return Zotero.Items.getSortTitle(item.getDisplayTitle());
|
||||
|
||||
case 'hasAttachment':
|
||||
if (item.isFileAttachment()) {
|
||||
var state = item.fileExistsCached() ? 1 : -1;
|
||||
}
|
||||
else if (item.isRegularItem()) {
|
||||
var state = item.getBestAttachmentStateCached();
|
||||
if (this._canGetBestAttachmentState(item)) {
|
||||
return item.getBestAttachmentStateCached();
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
// Make sort order present, missing, empty when ascending
|
||||
if (state === 1) {
|
||||
state = 2;
|
||||
}
|
||||
else if (state === -1) {
|
||||
state = 1;
|
||||
}
|
||||
return state;
|
||||
|
||||
case 'numNotes':
|
||||
return row.numNotes(false, true) || 0;
|
||||
|
|
@ -1350,7 +1341,11 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
}
|
||||
|
||||
if (sortField == 'hasAttachment') {
|
||||
return fieldB - fieldA;
|
||||
// PDFs at the top
|
||||
const order = ['pdf', 'snapshot', 'other', 'none'];
|
||||
fieldA = order.indexOf(fieldA.type || 'none') + (fieldA.exists ? 0 : 4);
|
||||
fieldB = order.indexOf(fieldB.type || 'none') + (fieldB.exists ? 0 : 4);
|
||||
return fieldA - fieldB;
|
||||
}
|
||||
|
||||
if (sortField == 'callNumber') {
|
||||
|
|
@ -1439,18 +1434,27 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
var openItemIDs = this._saveOpenState(true);
|
||||
|
||||
// Sort specific items
|
||||
if (itemIDs) {
|
||||
let idsToSort = new Set(itemIDs);
|
||||
this._rows.sort((a, b) => {
|
||||
// Don't re-sort existing items. This assumes a stable sort(), which is the case in Firefox
|
||||
// but not Chrome/v8.
|
||||
if (!idsToSort.has(a.ref.id) && !idsToSort.has(b.ref.id)) return 0;
|
||||
return rowSort(a, b) * order;
|
||||
});
|
||||
try {
|
||||
if (itemIDs) {
|
||||
let idsToSort = new Set(itemIDs);
|
||||
this._rows.sort((a, b) => {
|
||||
// Don't re-sort existing items. This assumes a stable sort(), which is the case in Firefox
|
||||
// but not Chrome/v8.
|
||||
if (!idsToSort.has(a.ref.id) && !idsToSort.has(b.ref.id)) return 0;
|
||||
return rowSort(a, b) * order;
|
||||
});
|
||||
}
|
||||
// Full sort
|
||||
else {
|
||||
this._rows.sort((a, b) => rowSort(a, b) * order);
|
||||
}
|
||||
}
|
||||
// Full sort
|
||||
else {
|
||||
this._rows.sort((a, b) => rowSort(a, b) * order);
|
||||
catch (e) {
|
||||
Zotero.logError("Error sorting fields: " + e.message);
|
||||
Zotero.debug(e, 1);
|
||||
// Clear anything that might be contributing to the error
|
||||
Zotero.Prefs.clear('secondarySort.' + this.getSortField());
|
||||
Zotero.Prefs.clear('fallbackSort');
|
||||
}
|
||||
|
||||
this._refreshRowMap();
|
||||
|
|
@ -1748,8 +1752,21 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
await Zotero.Items.trashTx(ids);
|
||||
}
|
||||
else if (collectionTreeRow.isCollection()) {
|
||||
let collectionIDs = [collectionTreeRow.ref.id];
|
||||
if (Zotero.Prefs.get('recursiveCollections')) {
|
||||
collectionIDs.push(...collectionTreeRow.ref.getDescendents(false, 'collection').map(c => c.id));
|
||||
}
|
||||
|
||||
await Zotero.DB.executeTransaction(async () => {
|
||||
await collectionTreeRow.ref.removeItems(ids);
|
||||
for (let itemID of ids) {
|
||||
let item = Zotero.Items.get(itemID);
|
||||
for (let collectionID of collectionIDs) {
|
||||
item.removeFromCollection(collectionID);
|
||||
}
|
||||
await item.save({
|
||||
skipDateModifiedUpdate: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (collectionTreeRow.isPublications()) {
|
||||
|
|
@ -1788,6 +1805,11 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
return this._rows.map(row => asIDs ? row.ref.id : row.ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current sort order of the items list
|
||||
*
|
||||
* @return {Number} - -1 for descending, 1 for ascending
|
||||
*/
|
||||
getSortDirection(sortFields) {
|
||||
sortFields = sortFields || this.getSortFields();
|
||||
if (this.collectionTreeRow.isFeed()) {
|
||||
|
|
@ -1987,8 +2009,8 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
// If exporting with virtual "Markdown + Rich Text" translator, call Note Markdown
|
||||
// and Note HTML translators instead
|
||||
if (format.id === Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN };
|
||||
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML };
|
||||
let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
|
||||
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
|
||||
Zotero.QuickCopy.getContentFromItems(items, markdownFormat, (obj, worked) => {
|
||||
if (!worked) {
|
||||
Zotero.log(Zotero.getString('fileInterface.exportError'), 'warning');
|
||||
|
|
@ -2752,93 +2774,60 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
|
||||
// TEMP: For now, we use the blue bullet for all non-PDF attachments, but there's
|
||||
// commented-out code for showing different icons for snapshots, files, and URL/DOI links
|
||||
if (this.isContainer(index)) {
|
||||
if (item.isRegularItem()) {
|
||||
const { type, exists } = item.getBestAttachmentStateCached();
|
||||
let icon = "";
|
||||
let ariaLabel;
|
||||
// If the item has a child attachment
|
||||
if (type !== null && type != 'none') {
|
||||
if (type == 'pdf') {
|
||||
icon = getDOMElement('IconTreeitemAttachmentPDF');
|
||||
ariaLabel = Zotero.getString('pane.item.attachments.hasPDF');
|
||||
if (!exists) {
|
||||
icon.classList.add('icon-missing-file');
|
||||
}
|
||||
}
|
||||
else if (type == 'snapshot') {
|
||||
//icon = getDOMElement('IconTreeitemAttachmentSnapshot');
|
||||
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
|
||||
ariaLabel = Zotero.getString('pane.item.attachments.hasSnapshot');
|
||||
}
|
||||
else {
|
||||
//icon = getDOMElement('IconTreeitem');
|
||||
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
|
||||
ariaLabel = Zotero.getString('pane.item.attachments.has');
|
||||
}
|
||||
icon.classList.add('cell-icon');
|
||||
//if (!exists) {
|
||||
// icon.classList.add('icon-missing-file');
|
||||
//}
|
||||
}
|
||||
//else if (type == 'none') {
|
||||
// if (item.getField('url') || item.getField('DOI')) {
|
||||
// icon = getDOMElement('IconLink');
|
||||
// ariaLabel = Zotero.getString('pane.item.attachments.hasLink');
|
||||
// icon.classList.add('cell-icon');
|
||||
// }
|
||||
//}
|
||||
if (ariaLabel) {
|
||||
icon.setAttribute('aria-label', ariaLabel + '.');
|
||||
span.setAttribute('title', ariaLabel);
|
||||
}
|
||||
span.append(icon);
|
||||
|
||||
// Don't run this immediately since it might cause a db check and disk access
|
||||
// but delay for some time and see if the item is still visible in the tree
|
||||
// (i.e. if we haven't scrolled right past it)
|
||||
setTimeout(() => {
|
||||
if (!this.tree.rowIsVisible(index)) return;
|
||||
item.getBestAttachmentState()
|
||||
// Refresh cell when promise is fulfilled
|
||||
.then(({ type: newType, exists: newExists }) => {
|
||||
if (newType !== type || newExists !== exists) {
|
||||
this.tree.invalidateRow(index);
|
||||
}
|
||||
});
|
||||
}, ATTACHMENT_STATE_LOAD_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.isFileAttachment()) {
|
||||
const exists = item.fileExistsCached();
|
||||
if (this._canGetBestAttachmentState(item)) {
|
||||
const { type, exists } = item.getBestAttachmentStateCached();
|
||||
let icon = "";
|
||||
if (exists !== null) {
|
||||
if (item.isPDFAttachment()) {
|
||||
let ariaLabel;
|
||||
// If the item has a child attachment
|
||||
if (type !== null && type != 'none') {
|
||||
if (type == 'pdf') {
|
||||
icon = getDOMElement('IconTreeitemAttachmentPDF');
|
||||
ariaLabel = Zotero.getString('pane.item.attachments.hasPDF');
|
||||
if (!exists) {
|
||||
icon.classList.add('icon-missing-file');
|
||||
}
|
||||
}
|
||||
else if (item.isSnapshotAttachment()) {
|
||||
else if (type == 'snapshot') {
|
||||
//icon = getDOMElement('IconTreeitemAttachmentSnapshot');
|
||||
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
|
||||
ariaLabel = Zotero.getString('pane.item.attachments.hasSnapshot');
|
||||
}
|
||||
else {
|
||||
//icon = getDOMElement('IconTreeitem');
|
||||
icon = exists ? getDOMElement('IconBulletBlue') : getDOMElement('IconBulletBlueEmpty');
|
||||
ariaLabel = Zotero.getString('pane.item.attachments.has');
|
||||
}
|
||||
icon.classList.add('cell-icon');
|
||||
//if (!exists) {
|
||||
// icon.classList.add('icon-missing-file');
|
||||
//}
|
||||
}
|
||||
//else if (type == 'none') {
|
||||
// if (item.getField('url') || item.getField('DOI')) {
|
||||
// icon = getDOMElement('IconLink');
|
||||
// ariaLabel = Zotero.getString('pane.item.attachments.hasLink');
|
||||
// icon.classList.add('cell-icon');
|
||||
// }
|
||||
//}
|
||||
if (ariaLabel) {
|
||||
icon.setAttribute('aria-label', ariaLabel + '.');
|
||||
span.setAttribute('title', ariaLabel);
|
||||
}
|
||||
span.append(icon);
|
||||
|
||||
item.fileExists()
|
||||
// TODO: With no cell refreshing this is possibly somewhat inefficient
|
||||
// Refresh cell when promise is fulfilled
|
||||
.then(realExists => realExists != exists && this.tree.invalidateRow(index));
|
||||
// Don't run this immediately since it might cause a db check and disk access
|
||||
// but delay for some time and see if the item is still visible in the tree
|
||||
// (i.e. if we haven't scrolled right past it)
|
||||
setTimeout(() => {
|
||||
if (!this.tree.rowIsVisible(index)) return;
|
||||
item.getBestAttachmentState()
|
||||
// Refresh cell when promise is fulfilled
|
||||
.then(({ type: newType, exists: newExists }) => {
|
||||
if (newType !== type || newExists !== exists) {
|
||||
this.tree.invalidateRow(index);
|
||||
}
|
||||
});
|
||||
}, ATTACHMENT_STATE_LOAD_DELAY);
|
||||
}
|
||||
|
||||
return span;
|
||||
|
|
@ -2911,7 +2900,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
}
|
||||
|
||||
// Accessibility
|
||||
div.setAttribute('role', 'row');
|
||||
div.setAttribute('role', 'treeitem');
|
||||
div.setAttribute('aria-level', this.getLevel(index) + 1);
|
||||
if (!this.isContainerEmpty(index)) {
|
||||
div.setAttribute('aria-expanded', this.isContainerOpen(index));
|
||||
|
|
@ -3550,7 +3539,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
let t = new Date();
|
||||
for (let i = 0; i < this._rows.length; i++) {
|
||||
let item = this.getRow(i).ref;
|
||||
if (item.isRegularItem()) {
|
||||
if (this._canGetBestAttachmentState(item)) {
|
||||
await item.getBestAttachmentState();
|
||||
}
|
||||
}
|
||||
|
|
@ -3738,7 +3727,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
// Restore Default Column Order
|
||||
//
|
||||
let menuitem = doc.createElementNS(ns, 'menuitem');
|
||||
menuitem.setAttribute('label', Zotero.Intl.strings['zotero.items.restoreColumnOrder.label']);
|
||||
menuitem.setAttribute('label', Zotero.getString('zotero.items.restoreColumnOrder.label'));
|
||||
menuitem.setAttribute('anonid', prefix + 'restore-order');
|
||||
menuitem.addEventListener('command', () => this.tree._columns.restoreDefaultOrder());
|
||||
menupopup.appendChild(menuitem);
|
||||
|
|
@ -3824,10 +3813,10 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
_isOnlyEmoji(str) {
|
||||
// Remove emoji and zero-width joiner and see if anything's left
|
||||
return !str.replace(EMOJI_RE, '').replace(/\u200D/g,'');
|
||||
|
||||
_canGetBestAttachmentState(item) {
|
||||
return (item.isRegularItem() && item.numAttachments())
|
||||
|| (item.isFileAttachment() && item.isTopLevelItem());
|
||||
}
|
||||
|
||||
_getTagSwatch(tag, color) {
|
||||
|
|
@ -3837,7 +3826,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
//
|
||||
// TODO: Check for a maximum number of graphemes, which is hard to do
|
||||
// https://stackoverflow.com/a/54369605
|
||||
if (this._isOnlyEmoji(tag)) {
|
||||
if (Zotero.Utilities.Internal.isOnlyEmoji(tag)) {
|
||||
span.textContent = tag;
|
||||
}
|
||||
// Otherwise display color
|
||||
|
|
|
|||
|
|
@ -207,6 +207,14 @@ const COLUMNS = [
|
|||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
},
|
||||
{
|
||||
dataKey: "number",
|
||||
disabledIn: "feed",
|
||||
submenu: true,
|
||||
label: "itemFields.number",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
},
|
||||
{
|
||||
dataKey: "pages",
|
||||
disabledIn: "feed",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 4877f181b1a962ea3e26d23f12d1acba3ccce347
|
||||
Subproject commit d704bf80afe8a692373e11f670b08553b9f74a31
|
||||
|
|
@ -24,7 +24,6 @@
|
|||
*/
|
||||
|
||||
import VirtualizedTable from 'components/virtualized-table';
|
||||
const { IntlProvider } = require('react-intl');
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
|
|
@ -41,20 +40,18 @@ function init() {
|
|||
engines = Zotero.LocateManager.getEngines();
|
||||
const domEl = document.querySelector('#locateManager-tree');
|
||||
let elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => engines.length}
|
||||
id="locateManager-table"
|
||||
ref={ref => tree = ref}
|
||||
renderItem={VirtualizedTable.makeRowRenderer(getRowData)}
|
||||
showHeader={true}
|
||||
multiSelect={true}
|
||||
columns={columns}
|
||||
disableFontSizeScaling={true}
|
||||
getRowString={index => getRowData(index).name}
|
||||
onActivate={handleActivate}
|
||||
/>
|
||||
</IntlProvider>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => engines.length}
|
||||
id="locateManager-table"
|
||||
ref={ref => tree = ref}
|
||||
renderItem={VirtualizedTable.makeRowRenderer(getRowData)}
|
||||
showHeader={true}
|
||||
multiSelect={true}
|
||||
columns={columns}
|
||||
disableFontSizeScaling={true}
|
||||
getRowString={index => getRowData(index).name}
|
||||
onActivate={handleActivate}
|
||||
/>
|
||||
);
|
||||
return new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -520,7 +520,13 @@ var Zotero_LocateMenu = new function() {
|
|||
if(attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
var path = yield attachment.getFilePathAsync();
|
||||
if (path) {
|
||||
var ext = Zotero.File.getExtension(Zotero.File.pathToFile(path));
|
||||
try {
|
||||
var ext = Zotero.File.getExtension(Zotero.File.pathToFile(path));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
return false;
|
||||
}
|
||||
if(!attachment.attachmentContentType ||
|
||||
Zotero.MIME.hasNativeHandler(attachment.attachmentContentType, ext) ||
|
||||
!Zotero.MIME.hasInternalHandler(attachment.attachmentContentType, ext)) {
|
||||
|
|
@ -589,9 +595,23 @@ var Zotero_LocateMenu = new function() {
|
|||
this.icon = "chrome://zotero/skin/locate-library-lookup.png";
|
||||
this.canHandleItem = function (item) { return Zotero.Promise.resolve(item.isRegularItem()); };
|
||||
this.handleItems = Zotero.Promise.method(function (items, event) {
|
||||
// If no resolver configured, just switch to the default
|
||||
// If no resolver configured, show error
|
||||
if (!Zotero.Prefs.get('openURL.resolver')) {
|
||||
Zotero.Prefs.clear('openURL.resolver')
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
|
||||
let index = ps.confirmEx(
|
||||
null,
|
||||
Zotero.getString('locate.libraryLookup.noResolver.title'),
|
||||
Zotero.getString('locate.libraryLookup.noResolver.text', Zotero.appName),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.openPreferences'),
|
||||
null, null, null, {}
|
||||
);
|
||||
if (index == 0) {
|
||||
Zotero.Utilities.Internal.openPreferences('zotero-prefpane-advanced');
|
||||
}
|
||||
return;
|
||||
}
|
||||
var urls = [];
|
||||
for (let item of items) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
* @namespace
|
||||
*/
|
||||
var Zotero_Lookup = new function () {
|
||||
this._button = null;
|
||||
/**
|
||||
* Performs a lookup by DOI, PMID, or ISBN on the given textBox value
|
||||
* and adds any items it can.
|
||||
|
|
@ -134,6 +135,10 @@ var Zotero_Lookup = new function () {
|
|||
|
||||
this.showPanel = function (button) {
|
||||
var panel = document.getElementById('zotero-lookup-panel');
|
||||
this._button = button;
|
||||
if (!button) {
|
||||
button = document.getElementById("zotero-tb-lookup");
|
||||
}
|
||||
panel.openPopup(button, "after_start", 16, -2, false, false);
|
||||
}
|
||||
|
||||
|
|
@ -201,6 +206,25 @@ var Zotero_Lookup = new function () {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
this.onFocusOut = function(event) {
|
||||
/*
|
||||
if the lookup popup was triggered by the lookup button,
|
||||
we want to return there on focus out. So we check
|
||||
(1) that we came from a button and (2) that
|
||||
event.relatedTarget === null, i.e. that the user hasn't used
|
||||
the mouse or keyboard to select something, and focus is leaving
|
||||
the popup because the popup was hidden/dismissed.
|
||||
*/
|
||||
if (this._button && event.relatedTarget === null) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._button.focus();
|
||||
this._button = null;
|
||||
} else {
|
||||
this._button = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.onInput = function (event, textbox) {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
|||
import FilePicker from 'zotero/modules/filePicker';
|
||||
|
||||
Zotero_Preferences.Advanced = {
|
||||
DEFAULT_OPENURL_RESOLVER: 'https://www.worldcat.org/registry/gateway',
|
||||
|
||||
_openURLResolvers: null,
|
||||
|
||||
init: function () {
|
||||
|
|
@ -462,12 +460,6 @@ Zotero_Preferences.Advanced = {
|
|||
var menupopup = openURLMenu.firstChild;
|
||||
menupopup.innerHTML = '';
|
||||
|
||||
var defaultMenuItem = document.createElement('menuitem');
|
||||
defaultMenuItem.setAttribute('label', Zotero.getString('general.default'));
|
||||
defaultMenuItem.setAttribute('value', this.DEFAULT_OPENURL_RESOLVER);
|
||||
defaultMenuItem.setAttribute('type', 'checkbox');
|
||||
menupopup.appendChild(defaultMenuItem);
|
||||
|
||||
var customMenuItem = document.createElement('menuitem');
|
||||
customMenuItem.setAttribute('label', Zotero.getString('general.custom'));
|
||||
customMenuItem.setAttribute('value', 'custom');
|
||||
|
|
@ -521,13 +513,8 @@ Zotero_Preferences.Advanced = {
|
|||
}
|
||||
}
|
||||
|
||||
// Default
|
||||
if (currentResolver == this.DEFAULT_OPENURL_RESOLVER) {
|
||||
openURLMenu.setAttribute('label', Zotero.getString('general.default'));
|
||||
defaultMenuItem.setAttribute('checked', true);
|
||||
Zotero.Prefs.clear('openURL.name');
|
||||
}
|
||||
else if (selectedName) {
|
||||
// From directory
|
||||
if (selectedName) {
|
||||
openURLMenu.setAttribute('label', selectedName);
|
||||
// If we found a match, update stored name
|
||||
Zotero.Prefs.set('openURL.name', selectedName);
|
||||
|
|
@ -555,15 +542,8 @@ Zotero_Preferences.Advanced = {
|
|||
var openURLServerField = document.getElementById('openURLServerField');
|
||||
var openURLVersionMenu = document.getElementById('openURLVersionMenu');
|
||||
|
||||
// Default
|
||||
if (event.target.value == this.DEFAULT_OPENURL_RESOLVER) {
|
||||
Zotero.Prefs.clear('openURL.name');
|
||||
Zotero.Prefs.clear('openURL.resolver');
|
||||
Zotero.Prefs.clear('openURL.version');
|
||||
openURLServerField.value = this.DEFAULT_OPENURL_RESOLVER;
|
||||
}
|
||||
// If "Custom" selected, clear URL field
|
||||
else if (event.target.value == "custom") {
|
||||
if (event.target.value == "custom") {
|
||||
Zotero.Prefs.clear('openURL.name');
|
||||
Zotero.Prefs.set('openURL.resolver', '');
|
||||
Zotero.Prefs.clear('openURL.version');
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import FilePicker from 'zotero/modules/filePicker';
|
|||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var VirtualizedTable = require('components/virtualized-table');
|
||||
var { IntlProvider } = require('react-intl');
|
||||
var { makeRowRenderer } = VirtualizedTable;
|
||||
|
||||
Zotero_Preferences.Cite = {
|
||||
|
|
@ -118,22 +117,20 @@ Zotero_Preferences.Cite = {
|
|||
}
|
||||
};
|
||||
let elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this.styles.length}
|
||||
id="styleManager-table"
|
||||
ref={ref => this._tree = ref}
|
||||
renderItem={makeRowRenderer(index => this.styles[index])}
|
||||
showHeader={true}
|
||||
multiSelect={true}
|
||||
columns={columns}
|
||||
staticColumns={true}
|
||||
disableFontSizeScaling={true}
|
||||
onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined}
|
||||
onKeyDown={handleKeyDown}
|
||||
getRowString={index => this.styles[index].title}
|
||||
/>
|
||||
</IntlProvider>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this.styles.length}
|
||||
id="styleManager-table"
|
||||
ref={ref => this._tree = ref}
|
||||
renderItem={makeRowRenderer(index => this.styles[index])}
|
||||
showHeader={true}
|
||||
multiSelect={true}
|
||||
columns={columns}
|
||||
staticColumns={true}
|
||||
disableFontSizeScaling={true}
|
||||
onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined}
|
||||
onKeyDown={handleKeyDown}
|
||||
getRowString={index => this.styles[index].title}
|
||||
/>
|
||||
);
|
||||
await new Promise(resolve => ReactDOM.render(elem, document.getElementById("styleManager"), resolve));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var VirtualizedTable = require('components/virtualized-table');
|
||||
var { IntlProvider } = require('react-intl');
|
||||
var { makeRowRenderer } = VirtualizedTable;
|
||||
|
||||
Zotero_Preferences.Export = {
|
||||
|
|
@ -89,12 +88,16 @@ Zotero_Preferences.Export = {
|
|||
* Builds the note Quick Copy drop-down from the current global pref
|
||||
*/
|
||||
populateNoteQuickCopyList: async function () {
|
||||
document.getElementById('noteQuickCopy-format-options').removeAttribute('hidden');
|
||||
|
||||
// Initialize default format drop-down
|
||||
var format = Zotero.Prefs.get("export.noteQuickCopy.setting");
|
||||
format = Zotero.QuickCopy.unserializeSetting(format);
|
||||
var menulist = document.getElementById("zotero-noteQuickCopy-menu");
|
||||
menulist.setAttribute('preference', "pref-noteQuickCopy-setting");
|
||||
|
||||
menulist.removeEventListener('command', this.updateNoteQuickCopyUI);
|
||||
menulist.addEventListener('command', this.updateNoteQuickCopyUI);
|
||||
|
||||
if (!format) {
|
||||
format = menulist.value;
|
||||
}
|
||||
|
|
@ -149,11 +152,42 @@ Zotero_Preferences.Export = {
|
|||
return;
|
||||
}
|
||||
|
||||
var val = JSON.stringify({ mode: 'export', id: translator.translatorID });
|
||||
var value = { mode: 'export', id: translator.translatorID };
|
||||
if (translator.translatorID == format.id) {
|
||||
value = format;
|
||||
}
|
||||
else if (translator.translatorID == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
value = {
|
||||
mode: 'export',
|
||||
id: translator.translatorID,
|
||||
markdownOptions: {
|
||||
includeAppLinks: true
|
||||
},
|
||||
htmlOptions: {
|
||||
includeAppLinks: false
|
||||
}
|
||||
};
|
||||
if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML && format.options) {
|
||||
value.htmlOptions = format.options;
|
||||
}
|
||||
}
|
||||
else if (translator.translatorID == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
|
||||
value = {
|
||||
mode: 'export',
|
||||
id: translator.translatorID,
|
||||
options: {
|
||||
includeAppLinks: false
|
||||
}
|
||||
};
|
||||
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT && format.htmlOptions) {
|
||||
value.options = format.htmlOptions;
|
||||
}
|
||||
}
|
||||
|
||||
value = JSON.stringify(value);
|
||||
var itemNode = document.createElement('menuitem');
|
||||
itemNode.setAttribute('value', val);
|
||||
itemNode.setAttribute('value', value);
|
||||
itemNode.setAttribute('label', translator.label);
|
||||
// itemNode.setAttribute('oncommand', 'Zotero_Preferences.Export.updateQuickCopyUI()');
|
||||
popup.appendChild(itemNode);
|
||||
|
||||
if (format.mode == 'export' && format.id == translator.translatorID) {
|
||||
|
|
@ -162,6 +196,76 @@ Zotero_Preferences.Export = {
|
|||
});
|
||||
|
||||
menulist.click();
|
||||
this.updateNoteQuickCopyUI();
|
||||
},
|
||||
|
||||
updateNoteQuickCopyUI: () => {
|
||||
var format = document.getElementById('zotero-noteQuickCopy-menu').value;
|
||||
format = JSON.parse(format);
|
||||
|
||||
var markdownOptions = document.getElementById('noteQuickCopy-markdown-options');
|
||||
var htmlOptions = document.getElementById('noteQuickCopy-html-options');
|
||||
var markdownOptionsLabel = document.querySelector('#noteQuickCopy-markdown-options label');
|
||||
var htmlOptionsLabel = document.querySelector('#noteQuickCopy-html-options label');
|
||||
var markdownIncludeAppLinks = document.getElementById("noteQuickCopy-markdown-includeAppLinks");
|
||||
var htmlIncludeAppLinks = document.getElementById("noteQuickCopy-html-includeAppLinks");
|
||||
|
||||
markdownOptionsLabel.value = Zotero.Utilities.Internal.stringWithColon("Markdown");
|
||||
htmlOptionsLabel.value = Zotero.Utilities.Internal.stringWithColon(
|
||||
Zotero.getString('zotero.preferences.export.quickCopy.note.htmlOptions.label')
|
||||
);
|
||||
markdownIncludeAppLinks.label = Zotero.getString('exportOptions.includeAppLinks', Zotero.appName);
|
||||
htmlIncludeAppLinks.label = Zotero.getString('exportOptions.includeAppLinks', Zotero.appName);
|
||||
|
||||
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
markdownOptions.hidden = false;
|
||||
htmlOptions.hidden = false;
|
||||
markdownIncludeAppLinks.checked = format.markdownOptions && format.markdownOptions.includeAppLinks;
|
||||
htmlIncludeAppLinks.checked = format.htmlOptions && format.htmlOptions.includeAppLinks;
|
||||
}
|
||||
else if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
|
||||
markdownOptions.hidden = true;
|
||||
htmlOptions.hidden = false;
|
||||
htmlIncludeAppLinks.checked = format.options && format.options.includeAppLinks;
|
||||
}
|
||||
else {
|
||||
markdownOptions.hidden = true;
|
||||
htmlOptions.hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
onUpdateNoteExportOptions() {
|
||||
var menulist = document.getElementById("zotero-noteQuickCopy-menu");
|
||||
var markdownIncludeAppLinks = document.getElementById("noteQuickCopy-markdown-includeAppLinks");
|
||||
var htmlIncludeAppLinks = document.getElementById("noteQuickCopy-html-includeAppLinks");
|
||||
|
||||
for (let i = 0; i < menulist.itemCount; i++) {
|
||||
let item = menulist.getItemAtIndex(i);
|
||||
let format = JSON.parse(item.getAttribute('value'));
|
||||
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
if (!format.markdownOptions) {
|
||||
format.markdownOptions = {};
|
||||
}
|
||||
if (!format.htmlOptions) {
|
||||
format.htmlOptions = {};
|
||||
}
|
||||
format.markdownOptions.includeAppLinks = markdownIncludeAppLinks.checked;
|
||||
format.htmlOptions.includeAppLinks = htmlIncludeAppLinks.checked;
|
||||
}
|
||||
else if (format.id == Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
|
||||
if (!format.options) {
|
||||
format.options = {};
|
||||
}
|
||||
format.options.includeAppLinks = htmlIncludeAppLinks.checked;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
item.value = JSON.stringify(format);
|
||||
}
|
||||
// After updating menulist item value we have to wait a bit before doing click(), to avoid anomalies
|
||||
// like an empty row in menulist. 0 in setTimeout is not enough
|
||||
setTimeout(() => menulist.click(), 50);
|
||||
},
|
||||
|
||||
|
||||
|
|
@ -387,22 +491,20 @@ Zotero_Preferences.Export = {
|
|||
};
|
||||
|
||||
let elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this._rows.length}
|
||||
id="quickCopy-siteSettings-table"
|
||||
ref={ref => this._tree = ref}
|
||||
renderItem={makeRowRenderer(index => this._rows[index])}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
staticColumns={true}
|
||||
disableFontSizeScaling={true}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
getRowString={index => this._rows[index].domain}
|
||||
onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this._rows.length}
|
||||
id="quickCopy-siteSettings-table"
|
||||
ref={ref => this._tree = ref}
|
||||
renderItem={makeRowRenderer(index => this._rows[index])}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
staticColumns={true}
|
||||
disableFontSizeScaling={true}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
getRowString={index => this._rows[index].domain}
|
||||
onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()}
|
||||
/>
|
||||
);
|
||||
await new Promise(resolve => ReactDOM.render(elem, document.getElementById("quickCopy-siteSettings"), resolve));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -71,9 +71,38 @@
|
|||
|
||||
<label value="&zotero.preferences.quickCopy.noteFormat;" control="zotero-noteQuickCopy-menu"/>
|
||||
<menulist id="zotero-noteQuickCopy-menu" label="&zotero.general.loading;"/>
|
||||
|
||||
|
||||
<table id="noteQuickCopy-format-options"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
hidden="hidden">
|
||||
<tbody id="noteQuickCopy-markdown-options">
|
||||
<tr>
|
||||
<th>
|
||||
<xul:label/>
|
||||
</th>
|
||||
<td>
|
||||
<xul:checkbox id="noteQuickCopy-markdown-includeAppLinks"
|
||||
oncommand="Zotero_Preferences.Export.onUpdateNoteExportOptions()"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="noteQuickCopy-html-options">
|
||||
<tr>
|
||||
<th>
|
||||
<xul:label/>
|
||||
</th>
|
||||
<td>
|
||||
<xul:checkbox id="noteQuickCopy-html-includeAppLinks"
|
||||
oncommand="Zotero_Preferences.Export.onUpdateNoteExportOptions()"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<separator/>
|
||||
|
||||
|
||||
<label value="&zotero.preferences.quickCopy.siteEditor.setings;" control="quickCopy-siteSettings"/>
|
||||
<hbox class="virtualized-table-container" flex="1" height="120px">
|
||||
<html:div id="quickCopy-siteSettings"/>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@
|
|||
<preference id="pref-automaticTags" name="extensions.zotero.automaticTags" type="bool"/>
|
||||
<preference id="pref-trashAutoEmptyDays" name="extensions.zotero.trashAutoEmptyDays" type="int"/>
|
||||
|
||||
<preference id="pref-reader-tabs-title" name="extensions.zotero.tabs.title.reader" type="string"/>
|
||||
|
||||
<preference id="pref-groups-copyChildNotes" name="extensions.zotero.groups.copyChildNotes" type="bool"/>
|
||||
<preference id="pref-groups-copyChildFileAttachments" name="extensions.zotero.groups.copyChildFileAttachments" type="bool"/>
|
||||
<preference id="pref-groups-copyAnnotations" name="extensions.zotero.groups.copyAnnotations" type="bool"/>
|
||||
|
|
@ -90,6 +92,22 @@
|
|||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-reader-groupbox">
|
||||
<caption label="&zotero.preferences.pdfReader;"/>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.reader.tabsTitle.label;" control="reader-tabs-title"/>
|
||||
<menulist id="reader-tabs-title"
|
||||
preference="pref-reader-tabs-title">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.preferences.reader.tabsTitle.titleCreatorYear;" value="titleCreatorYear"/>
|
||||
<menuitem label="&zotero.preferences.reader.tabsTitle.creatorYearTitle;" value="creatorYearTitle"/>
|
||||
<menuitem label="&zotero.preferences.reader.tabsTitle.filename;" value="filename"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.groups;"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ var React = require('react');
|
|||
var ReactDOM = require('react-dom');
|
||||
var VirtualizedTable = require('components/virtualized-table');
|
||||
var { getDOMElement } = require('components/icons');
|
||||
var { IntlProvider } = require('react-intl');
|
||||
var { renderCell } = VirtualizedTable;
|
||||
|
||||
Zotero_Preferences.Sync = {
|
||||
|
|
@ -319,20 +318,18 @@ Zotero_Preferences.Sync = {
|
|||
}
|
||||
};
|
||||
let elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this._rows.length}
|
||||
id="librariesToSync-table"
|
||||
ref={ref => this._tree = ref}
|
||||
renderItem={renderItem}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
staticColumns={true}
|
||||
getRowString={index => this._rows[index].name}
|
||||
disableFontSizeScaling={true}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</IntlProvider>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this._rows.length}
|
||||
id="librariesToSync-table"
|
||||
ref={ref => this._tree = ref}
|
||||
renderItem={renderItem}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
staticColumns={true}
|
||||
getRowString={index => this._rows[index].name}
|
||||
disableFontSizeScaling={true}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
);
|
||||
|
||||
ReactDOM.render(elem, document.getElementById("libraries-to-sync-tree"));
|
||||
|
|
|
|||
|
|
@ -288,15 +288,15 @@
|
|||
|
||||
<ul id="sync-reset-list">
|
||||
<!--<li>
|
||||
<p>&zotero.preferences.sync.reset.restoreFromServer;</p>
|
||||
<p>&zotero.preferences.sync.reset.restoreFromServer.desc;</p>
|
||||
<p>&zotero.preferences.sync.reset.restoreFromOnlineLibrary;</p>
|
||||
<p>&zotero.preferences.sync.reset.restoreFromOnlineLibrary.desc;</p>
|
||||
</li>-->
|
||||
|
||||
<li id="restore-to-server">
|
||||
<label>
|
||||
<input name="sync-reset-radiogroup" value="restore-to-server" type="radio"/>
|
||||
<span class="sync-reset-option-name">&zotero.preferences.sync.reset.restoreToServer;</span>
|
||||
<span class="sync-reset-option-desc">&zotero.preferences.sync.reset.restoreToServer.desc;</span>
|
||||
<span class="sync-reset-option-name">&zotero.preferences.sync.reset.restoreToOnlineLibrary;</span>
|
||||
<span class="sync-reset-option-desc">&zotero.preferences.sync.reset.restoreToOnlineLibrary.desc;</span>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,25 @@
|
|||
<menuitem id="menu_copy"/>
|
||||
<menuitem id="menu_paste"/>
|
||||
<menuitem id="menu_delete"/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem
|
||||
id="edit-menuitem-rotate-left"
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotatePageLeft;"
|
||||
oncommand="menuCmd('rotateLeft')"
|
||||
/>
|
||||
<menuitem
|
||||
id="edit-menuitem-rotate-right"
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotatePageRight;"
|
||||
oncommand="menuCmd('rotateRight')"
|
||||
/>
|
||||
<!--<menuitem
|
||||
id="edit-menuitem-rotate-180"
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotatePage180;"
|
||||
oncommand="menuCmd('rotate180')"
|
||||
/>-->
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu label="View" onpopupshowing="onViewMenuOpen()">
|
||||
|
|
@ -81,15 +100,6 @@
|
|||
-->
|
||||
<!-- <menuitem label="Switch to Presentation Mode" oncommand="menuCmd('presentationmode')"/>-->
|
||||
<!-- <menuseparator/>-->
|
||||
<menuitem
|
||||
label="&zotero.pdfReader.rotateClockwise;"
|
||||
oncommand="menuCmd('rotatecw')"
|
||||
/>
|
||||
<menuitem
|
||||
label="&zotero.pdfReader.rotateCounterclockwise;"
|
||||
oncommand="menuCmd('rotateccw')"
|
||||
/>
|
||||
<menuseparator/>
|
||||
<menuitem
|
||||
id="view-menuitem-hand-tool"
|
||||
label="&zotero.pdfReader.handTool;"
|
||||
|
|
@ -163,6 +173,19 @@
|
|||
label="&zotero.pdfReader.zoomPageHeight;"
|
||||
oncommand="menuCmd('zoomPageHeight')"
|
||||
/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem
|
||||
id="view-menuitem-split-horizontally"
|
||||
type="checkbox"
|
||||
label="&zotero.pdfReader.splitHorizontally;"
|
||||
oncommand="menuCmd('splitHorizontally')"
|
||||
/>
|
||||
<menuitem
|
||||
id="view-menuitem-split-vertically"
|
||||
type="checkbox"
|
||||
label="&zotero.pdfReader.splitVertically;"
|
||||
oncommand="menuCmd('splitVertically')"
|
||||
/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu
|
||||
|
|
|
|||
|
|
@ -30,9 +30,8 @@ var Zotero_Report_Interface = new function() {
|
|||
*/
|
||||
this.loadCollectionReport = function () {
|
||||
var sortColumn = ZoteroPane_Local.getSortField();
|
||||
var sortDirection = ZoteroPane_Local.getSortDirection();
|
||||
var queryString = '?sort=' + sortColumn
|
||||
+ '&direction=' + (sortDirection == 'ascending' ? 'asc' : 'desc');
|
||||
+ '&direction=' + (ZoteroPane.getSortDirection() == 1 ? 'asc' : 'desc');
|
||||
|
||||
var url = 'zotero://report/';
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import FilePicker from 'zotero/modules/filePicker';
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import VirtualizedTable from 'components/virtualized-table';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { getDOMElement } from 'components/icons';
|
||||
|
||||
/**
|
||||
|
|
@ -51,9 +50,9 @@ var Zotero_RTFScan = new function() {
|
|||
var ids = 0;
|
||||
var tree;
|
||||
this._rows = [
|
||||
{ id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false },
|
||||
{ id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false },
|
||||
{ id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false },
|
||||
{ id: 'unmapped', rtf: Zotero.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false },
|
||||
{ id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false },
|
||||
{ id: 'mapped', rtf: Zotero.getString('zotero.rtfScan.mappedCitations.label'), collapsed: false },
|
||||
];
|
||||
this._rowMap = {};
|
||||
this._rows.forEach((row, index) => this._rowMap[row.id] = index);
|
||||
|
|
@ -388,9 +387,9 @@ var Zotero_RTFScan = new function() {
|
|||
document.documentElement.currentPage = document.getElementById('intro-page');
|
||||
|
||||
this._rows = [
|
||||
{ id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false },
|
||||
{ id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false },
|
||||
{ id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false },
|
||||
{ id: 'unmapped', rtf: Zotero.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false },
|
||||
{ id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false },
|
||||
{ id: 'mapped', rtf: Zotero.getString('zotero.rtfScan.mappedCitations.label'), collapsed: false },
|
||||
];
|
||||
this._rowMap = {};
|
||||
this._rows.forEach((row, index) => this._rowMap[row.id] = index);
|
||||
|
|
@ -768,17 +767,15 @@ var Zotero_RTFScan = new function() {
|
|||
this._initCitationTree = function () {
|
||||
const domEl = document.querySelector('#tree');
|
||||
const elem = (
|
||||
<IntlProvider locale={Zotero.locale} messages={Zotero.Intl.strings}>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this._rows.length}
|
||||
id="rtfScan-table"
|
||||
ref={ref => tree = ref}
|
||||
renderItem={this._renderItem}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
disableFontSizeScaling={true}
|
||||
/>
|
||||
</IntlProvider>
|
||||
<VirtualizedTable
|
||||
getRowCount={() => this._rows.length}
|
||||
id="rtfScan-table"
|
||||
ref={ref => tree = ref}
|
||||
renderItem={this._renderItem}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
disableFontSizeScaling={true}
|
||||
/>
|
||||
);
|
||||
return new Promise(resolve => ReactDOM.render(elem, domEl, resolve));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -91,8 +91,12 @@ function doUnload()
|
|||
}
|
||||
|
||||
var onCollectionSelected = async function () {
|
||||
if (!collectionsView.selection.count) return;
|
||||
var collectionTreeRow = collectionsView.getRow(collectionsView.selection.focused);
|
||||
if (!collectionsView.selection.count) return;
|
||||
// Collection not changed
|
||||
if (itemsView && itemsView.collectionTreeRow && itemsView.collectionTreeRow.id == collectionTreeRow.id) {
|
||||
return;
|
||||
}
|
||||
collectionTreeRow.setSearch('');
|
||||
Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);
|
||||
|
||||
|
|
|
|||
|
|
@ -160,8 +160,7 @@ const ZoteroStandalone = new function() {
|
|||
let item = Zotero.Items.get(reader.itemID);
|
||||
let library = Zotero.Libraries.get(item.libraryID);
|
||||
if (item
|
||||
// Don't allow annotation transfer in group libraries
|
||||
&& library.libraryType == 'user'
|
||||
&& library.filesEditable
|
||||
&& library.editable
|
||||
&& !(item.deleted || item.parentItem && item.parentItem.deleted)) {
|
||||
let annotations = item.getAnnotations();
|
||||
|
|
@ -192,12 +191,9 @@ const ZoteroStandalone = new function() {
|
|||
if (numFiles) {
|
||||
menuitem.hidden = false;
|
||||
sep.hidden = false;
|
||||
if (numFiles == 1) {
|
||||
menuitem.label = 'Export PDF…';
|
||||
}
|
||||
else {
|
||||
menuitem.label = 'Export PDFs…';
|
||||
}
|
||||
menuitem.label = Zotero.getString(
|
||||
'pane.items.menu.exportPDF' + (numFiles == 1 ? '' : '.multiple')
|
||||
);
|
||||
}
|
||||
else {
|
||||
menuitem.hidden = true;
|
||||
|
|
@ -408,6 +404,8 @@ const ZoteroStandalone = new function() {
|
|||
this.updateMenuItemCheckmark('view-menuitem-zoom-auto', reader.isZoomAutoActive());
|
||||
this.updateMenuItemCheckmark('view-menuitem-zoom-page-width', reader.isZoomPageWidthActive());
|
||||
this.updateMenuItemCheckmark('view-menuitem-zoom-page-height', reader.isZoomPageHeightActive());
|
||||
this.updateMenuItemCheckmark('view-menuitem-split-vertically', reader.isSplitVerticallyActive());
|
||||
this.updateMenuItemCheckmark('view-menuitem-split-horizontally', reader.isSplitHorizontallyActive());
|
||||
}
|
||||
|
||||
// Layout mode
|
||||
|
|
|
|||
|
|
@ -232,6 +232,25 @@
|
|||
label="&bidiSwitchTextDirectionItem.label;"
|
||||
accesskey="&bidiSwitchTextDirectionItem.accesskey;"
|
||||
hidden="true"/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem
|
||||
id="edit-menuitem-rotate-left"
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotatePageLeft;"
|
||||
oncommand="ZoteroStandalone.onReaderCmd('rotateLeft')"
|
||||
/>
|
||||
<menuitem
|
||||
id="edit-menuitem-rotate-right"
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotatePageRight;"
|
||||
oncommand="ZoteroStandalone.onReaderCmd('rotateRight')"
|
||||
/>
|
||||
<!--<menuitem
|
||||
id="edit-menuitem-rotate-180"
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotatePage180;"
|
||||
oncommand="ZoteroStandalone.onReaderCmd('rotate180')"
|
||||
/>-->
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
|
|
@ -240,17 +259,6 @@
|
|||
accesskey="&viewMenu.accesskey;"
|
||||
onpopupshowing="ZoteroStandalone.onViewMenuOpen()">
|
||||
<menupopup id="menu_viewPopup">
|
||||
<menuitem
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotateClockwise;"
|
||||
oncommand="ZoteroStandalone.onReaderCmd('rotatecw')"
|
||||
/>
|
||||
<menuitem
|
||||
class="menu-type-reader"
|
||||
label="&zotero.pdfReader.rotateCounterclockwise;"
|
||||
oncommand="ZoteroStandalone.onReaderCmd('rotateccw')"
|
||||
/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem
|
||||
id="view-menuitem-hand-tool"
|
||||
class="menu-type-reader"
|
||||
|
|
@ -337,6 +345,21 @@
|
|||
oncommand="ZoteroStandalone.onReaderCmd('zoomPageHeight')"
|
||||
/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem
|
||||
id="view-menuitem-split-horizontally"
|
||||
class="menu-type-reader"
|
||||
type="checkbox"
|
||||
label="&zotero.pdfReader.splitHorizontally;"
|
||||
oncommand="ZoteroStandalone.onReaderCmd('splitHorizontally')"
|
||||
/>
|
||||
<menuitem
|
||||
id="view-menuitem-split-vertically"
|
||||
class="menu-type-reader"
|
||||
type="checkbox"
|
||||
label="&zotero.pdfReader.splitVertically;"
|
||||
oncommand="ZoteroStandalone.onReaderCmd('splitVertically')"
|
||||
/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
|
||||
<menu id="layout-menu" label="&layout.label;">
|
||||
<menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)">
|
||||
|
|
@ -390,6 +413,8 @@
|
|||
<menuitem label="12" type="checkbox"/>
|
||||
<menuitem label="13" type="checkbox"/>
|
||||
<menuitem label="14" type="checkbox"/>
|
||||
<menuitem label="15" type="checkbox"/>
|
||||
<menuitem label="16" type="checkbox"/>
|
||||
<menuitem label="18" type="checkbox"/>
|
||||
<menuitem label="24" type="checkbox"/>
|
||||
<menuitem label="36" type="checkbox"/>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ var React = require('react');
|
|||
var ReactDOM = require('react-dom');
|
||||
import TabBar from 'components/tabBar';
|
||||
|
||||
const MAX_LOADED_TABS = 5;
|
||||
// Reduce loaded tabs limit if the system has 8 GB or less memory, or it's running
|
||||
// on Windows which means it doesn't support more than 3 GB
|
||||
// TODO: Revise this after upgrading to Zotero 7
|
||||
const MAX_LOADED_TABS = Services.sysinfo.getProperty("memsize") / 1024 / 1024 / 1024 <= 8 || Zotero.isWin ? 3 : 5;
|
||||
const UNLOAD_UNUSED_AFTER = 86400; // 24h
|
||||
|
||||
var Zotero_Tabs = new function () {
|
||||
|
|
@ -84,6 +87,17 @@ var Zotero_Tabs = new function () {
|
|||
window.Zotero_Tooltip.stop();
|
||||
};
|
||||
|
||||
this.getTabIDByItemID = function (itemID) {
|
||||
let tab = this._tabs.find(tab => tab.data && tab.data.itemID === itemID);
|
||||
return tab && tab.id;
|
||||
};
|
||||
|
||||
this.setSecondViewState = function (tabID, state) {
|
||||
let { tab } = this._getTab(tabID);
|
||||
tab.data.secondViewState = state;
|
||||
Zotero.Session.debounceSave();
|
||||
};
|
||||
|
||||
this.init = function () {
|
||||
ReactDOM.render(
|
||||
<TabBar
|
||||
|
|
@ -128,12 +142,24 @@ var Zotero_Tabs = new function () {
|
|||
}
|
||||
else if (tab.type === 'reader') {
|
||||
if (Zotero.Items.exists(tab.data.itemID)) {
|
||||
this.add({
|
||||
type: 'reader-unloaded',
|
||||
title: tab.title,
|
||||
index: i,
|
||||
data: tab.data
|
||||
});
|
||||
if (tab.selected) {
|
||||
Zotero.Reader.open(tab.data.itemID,
|
||||
null,
|
||||
{
|
||||
title: tab.title,
|
||||
openInBackground: !tab.selected,
|
||||
secondViewState: tab.data.secondViewState
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
this.add({
|
||||
type: 'reader-unloaded',
|
||||
title: tab.title,
|
||||
index: i,
|
||||
data: tab.data
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +178,7 @@ var Zotero_Tabs = new function () {
|
|||
* @param {Function} onClose
|
||||
* @return {{ id: string, container: XULElement}} id - tab id, container - a new tab container created in the deck
|
||||
*/
|
||||
this.add = function ({ type, data, title, index, select, onClose }) {
|
||||
this.add = function ({ id, type, data, title, index, select, onClose }) {
|
||||
if (typeof type != 'string') {
|
||||
}
|
||||
if (typeof title != 'string') {
|
||||
|
|
@ -164,7 +190,7 @@ var Zotero_Tabs = new function () {
|
|||
if (onClose !== undefined && typeof onClose != 'function') {
|
||||
throw new Error(`'onClose' should be a function (was ${typeof onClose})`);
|
||||
}
|
||||
var id = 'tab-' + Zotero.Utilities.randomString();
|
||||
id = id || 'tab-' + Zotero.Utilities.randomString();
|
||||
var container = document.createElement('vbox');
|
||||
container.id = id;
|
||||
this.deck.appendChild(container);
|
||||
|
|
@ -251,7 +277,7 @@ var Zotero_Tabs = new function () {
|
|||
closedIDs.push(id);
|
||||
}
|
||||
this._history.push(historyEntry);
|
||||
Zotero.Notifier.trigger('close', 'tab', [closedIDs]);
|
||||
Zotero.Notifier.trigger('close', 'tab', [closedIDs], true);
|
||||
this._update();
|
||||
};
|
||||
|
||||
|
|
@ -321,7 +347,7 @@ var Zotero_Tabs = new function () {
|
|||
* @param {String} id
|
||||
* @param {Boolean} reopening
|
||||
*/
|
||||
this.select = function (id, reopening) {
|
||||
this.select = function (id, reopening, options) {
|
||||
var { tab, tabIndex } = this._getTab(id);
|
||||
if (!tab || tab.id === this._selectedID) {
|
||||
return;
|
||||
|
|
@ -332,10 +358,12 @@ var Zotero_Tabs = new function () {
|
|||
}
|
||||
if (tab.type === 'reader-unloaded') {
|
||||
this.close(tab.id);
|
||||
Zotero.Reader.open(tab.data.itemID, null, {
|
||||
Zotero.Reader.open(tab.data.itemID, options && options.location, {
|
||||
tabID: tab.id,
|
||||
title: tab.title,
|
||||
tabIndex,
|
||||
allowDuplicate: true
|
||||
allowDuplicate: true,
|
||||
secondViewState: tab.data.secondViewState
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -385,6 +413,7 @@ var Zotero_Tabs = new function () {
|
|||
}
|
||||
this.close(tab.id);
|
||||
this.add({
|
||||
id: tab.id,
|
||||
type: 'reader-unloaded',
|
||||
title: tab.title,
|
||||
index: tabIndex,
|
||||
|
|
@ -440,7 +469,7 @@ var Zotero_Tabs = new function () {
|
|||
};
|
||||
|
||||
this._openMenu = function (x, y, id) {
|
||||
var { tabIndex } = this._getTab(id);
|
||||
var { tab, tabIndex } = this._getTab(id);
|
||||
window.Zotero_Tooltip.stop();
|
||||
let menuitem;
|
||||
let popup = document.createElement('menupopup');
|
||||
|
|
@ -497,7 +526,8 @@ var Zotero_Tabs = new function () {
|
|||
var reader = Zotero.Reader.getByTabID(id);
|
||||
if (reader) {
|
||||
this.close(id);
|
||||
Zotero.Reader.open(reader.itemID, null, { openInWindow: true });
|
||||
let { secondViewState } = tab.data;
|
||||
Zotero.Reader.open(reader.itemID, null, { openInWindow: true, secondViewState });
|
||||
}
|
||||
});
|
||||
menupopup.appendChild(menuitem);
|
||||
|
|
@ -505,11 +535,10 @@ var Zotero_Tabs = new function () {
|
|||
menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('tabs.duplicate'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
var { tab, tabIndex } = this._getTab(id);
|
||||
if (tab.data.itemID) {
|
||||
tabIndex++;
|
||||
Zotero.Reader.open(tab.data.itemID, null, { tabIndex, allowDuplicate: true });
|
||||
|
||||
let { secondViewState } = tab.data;
|
||||
Zotero.Reader.open(tab.data.itemID, null, { tabIndex, allowDuplicate: true, secondViewState });
|
||||
}
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
|
|
|
|||
|
|
@ -59,10 +59,8 @@ var Zotero_CSL_Editor = new function() {
|
|||
|
||||
var pageList = document.getElementById('zotero-csl-page-type');
|
||||
var locators = Zotero.Cite.labels;
|
||||
for (let type of locators) {
|
||||
var locator = type;
|
||||
locator = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
|
||||
pageList.appendItem(locator, type);
|
||||
for (let locator of locators) {
|
||||
pageList.appendItem(Zotero.Cite.getLocatorString(locator), locator);
|
||||
}
|
||||
|
||||
pageList.selectedIndex = 0;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ Zotero.Annotations = new function () {
|
|||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 });
|
||||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_INK', { value: 4 });
|
||||
|
||||
Zotero.defineProperty(this, 'DEFAULT_COLOR', { value: '#ffd400' });
|
||||
|
||||
Zotero.defineProperty(this, 'PROPS', {
|
||||
value: ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel', 'sortIndex', 'position'],
|
||||
writable: false
|
||||
|
|
@ -177,7 +179,7 @@ Zotero.Annotations = new function () {
|
|||
o.tags = tags;
|
||||
}
|
||||
|
||||
o.dateModified = item.dateModified;
|
||||
o.dateModified = Zotero.Date.sqlToISO8601(item.dateModified);
|
||||
return o;
|
||||
};
|
||||
|
||||
|
|
@ -231,6 +233,10 @@ Zotero.Annotations = new function () {
|
|||
}
|
||||
}
|
||||
|
||||
// Don't try to select annotation, which would clear an active quick search (at least
|
||||
// until annotations are visible in the items list)
|
||||
saveOptions.skipSelect = true;
|
||||
|
||||
await item.saveTx(saveOptions);
|
||||
|
||||
return item;
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ Zotero.Attachments = new function(){
|
|||
|
||||
|
||||
/**
|
||||
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID', 'singleFile'
|
||||
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'libraryID', 'parentItemID', 'singleFile'
|
||||
* @param {Object} [options.saveOptions] - Options to pass to Zotero.Item::save()
|
||||
* @return {Promise<Zotero.Item>}
|
||||
*/
|
||||
|
|
@ -287,10 +287,19 @@ Zotero.Attachments = new function(){
|
|||
var title = options.title;
|
||||
var contentType = options.contentType;
|
||||
var charset = options.charset;
|
||||
var libraryID = options.libraryID;
|
||||
var parentItemID = options.parentItemID;
|
||||
var saveOptions = options.saveOptions;
|
||||
|
||||
if (!parentItemID) {
|
||||
if (parentItemID) {
|
||||
libraryID = Zotero.Items.getLibraryAndKeyFromID(parentItemID).libraryID;
|
||||
}
|
||||
else if (contentType == 'text/html') {
|
||||
throw new Error("parentItemID not provided");
|
||||
}
|
||||
|
||||
// Webpage snapshots must have parent items
|
||||
if (!parentItemID && contentType == 'text/html') {
|
||||
throw new Error("parentItemID not provided");
|
||||
}
|
||||
|
||||
|
|
@ -299,8 +308,9 @@ Zotero.Attachments = new function(){
|
|||
yield Zotero.DB.executeTransaction(function* () {
|
||||
// Create a new attachment
|
||||
attachmentItem = new Zotero.Item('attachment');
|
||||
let {libraryID, key: parentKey} = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
|
||||
attachmentItem.libraryID = libraryID;
|
||||
if (libraryID) {
|
||||
attachmentItem.libraryID = libraryID;
|
||||
}
|
||||
attachmentItem.setField('title', title);
|
||||
attachmentItem.setField('url', url);
|
||||
attachmentItem.parentID = parentItemID;
|
||||
|
|
@ -563,7 +573,8 @@ Zotero.Attachments = new function(){
|
|||
reject(e);
|
||||
},
|
||||
true,
|
||||
cookieSandbox
|
||||
cookieSandbox,
|
||||
{ allowImages: true }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
@ -1143,67 +1154,82 @@ Zotero.Attachments = new function(){
|
|||
*/
|
||||
this.downloadPDFViaBrowser = async function (url, path, options = {}) {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Downloading file via browser from ${url}`);
|
||||
const onLoadTimeout = 1e3;
|
||||
const onLoadTimeout = Zotero.Prefs.get('downloadPDFViaBrowser.onLoadTimeout');
|
||||
// Technically this is not a download, but the full operation timeout
|
||||
const downloadTimeout = 60e3;
|
||||
const downloadTimeout = Zotero.Prefs.get('downloadPDFViaBrowser.downloadTimeout');
|
||||
let channelBrowser, hiddenBrowser;
|
||||
let hiddenBrowserPDFFoundDeferred = Zotero.Promise.defer();
|
||||
|
||||
var pdfMIMETypeHandler = async (blob, name, _, channel) => {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Sniffing a PDF loaded at ${name}`);
|
||||
|
||||
let isOurPDF = false;
|
||||
// try the browser
|
||||
try {
|
||||
channelBrowser = channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
|
||||
}
|
||||
catch (e) {}
|
||||
if (channelBrowser) {
|
||||
isOurPDF = hiddenBrowser === channelBrowser;
|
||||
}
|
||||
else {
|
||||
// try the document for the load group
|
||||
|
||||
let isOurPDF = false;
|
||||
var pdfMIMETypeHandler = {
|
||||
onStartRequest: function (name, _, channel) {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Sniffing a PDF loaded at ${name}`);
|
||||
// try the browser
|
||||
try {
|
||||
channelBrowser = channel.loadGroup.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
|
||||
channelBrowser = channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
|
||||
}
|
||||
catch(e) {}
|
||||
catch (e) {}
|
||||
if (channelBrowser) {
|
||||
isOurPDF = hiddenBrowser === channelBrowser;
|
||||
}
|
||||
}
|
||||
|
||||
if (isOurPDF) {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Found our PDF at ${name}`);
|
||||
await Zotero.File.putContentsAsync(path, blob);
|
||||
hiddenBrowserPDFFoundDeferred.resolve();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Not our PDF at ${name}`);
|
||||
return false;
|
||||
else {
|
||||
// try the document for the load group
|
||||
try {
|
||||
channelBrowser = channel.loadGroup.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
|
||||
}
|
||||
catch(e) {}
|
||||
if (channelBrowser) {
|
||||
isOurPDF = hiddenBrowser === channelBrowser;
|
||||
}
|
||||
}
|
||||
},
|
||||
onContent: async (blob, name, _, channel) => {
|
||||
if (isOurPDF) {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Found our PDF at ${name}`);
|
||||
await Zotero.File.putContentsAsync(path, blob);
|
||||
hiddenBrowserPDFFoundDeferred.resolve();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Not our PDF at ${name}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
Zotero.MIMETypeHandler.addHandler("application/pdf", pdfMIMETypeHandler, true);
|
||||
let noop = () => 0;
|
||||
Zotero.MIMETypeHandler.addHandlers("application/pdf", pdfMIMETypeHandler, true);
|
||||
function noop() {};
|
||||
hiddenBrowser = Zotero.HTTP.loadDocuments([url], noop, noop, noop, true, options.cookieSandbox);
|
||||
let onLoadTimeoutDeferred = Zotero.Promise.defer();
|
||||
hiddenBrowser.addEventListener("load", async function onLoad() {
|
||||
Zotero.debug(`downloadPDFViaBrowser: Page with potential JS redirect loaded, giving it ${onLoadTimeout}ms to process`);
|
||||
hiddenBrowser.addEventListener("unload", () => {
|
||||
Zotero.debug(`downloadPDFViaBrowser: A JS redirect occurred, short timeout cancelled`);
|
||||
hiddenBrowser.removeEventListener('load', onLoad);
|
||||
});
|
||||
await Zotero.Promise.delay(onLoadTimeout);
|
||||
onLoadTimeoutDeferred.reject(`Loading PDF via browser timed out on the JS challenge page after ${onLoadTimeout}ms`);
|
||||
}, true);
|
||||
let currentUrl = "";
|
||||
hiddenBrowser.addProgressListener({
|
||||
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIWebProgressListener,
|
||||
Components.interfaces.nsISupportsWeakReference]),
|
||||
onProgressChange: noop,
|
||||
onStateChange: noop,
|
||||
onStatusChange: noop,
|
||||
onSecurityChange: noop,
|
||||
async onLocationChange() {
|
||||
let url = hiddenBrowser.contentDocument.location.href;
|
||||
if (currentUrl) {
|
||||
Zotero.debug(`downloadPDFViaBrowser: A JS redirect occurred to ${hiddenBrowser.contentDocument.location.href}`);
|
||||
}
|
||||
currentUrl = url;
|
||||
Zotero.debug(`downloadPDFViaBrowser: Page with potential JS redirect loaded, giving it ${onLoadTimeout}ms to process`);
|
||||
await Zotero.Promise.delay(onLoadTimeout);
|
||||
// If URL changed that means we got redirected and the onLoadTimeout needs to restart
|
||||
if (currentUrl === url && !isOurPDF) {
|
||||
onLoadTimeoutDeferred.reject(new Error(`downloadPDFViaBrowser: Loading PDF via browser timed out on the JS challenge page after ${onLoadTimeout}ms`));
|
||||
}
|
||||
}
|
||||
});
|
||||
await Zotero.Promise.race([
|
||||
onLoadTimeoutDeferred.promise,
|
||||
Zotero.Promise.delay(downloadTimeout).then(() => {
|
||||
if (!hiddenBrowserPDFFoundDeferred.promise.isResolved()) {
|
||||
throw new Error(`Loading PDF via browser timed out after ${downloadTimeout}ms`);
|
||||
if (!isOurPDF) {
|
||||
throw new Error(`downloadPDFViaBrowser: Loading PDF via browser timed out after ${downloadTimeout}ms`);
|
||||
}
|
||||
}),
|
||||
hiddenBrowserPDFFoundDeferred.promise
|
||||
|
|
@ -1213,13 +1239,13 @@ Zotero.Attachments = new function(){
|
|||
try {
|
||||
await OS.File.remove(path, { ignoreAbsent: true });
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e, 1);
|
||||
catch (err) {
|
||||
Zotero.logError(err);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
Zotero.MIMETypeHandler.removeHandler('application/pdf', pdfMIMETypeHandler);
|
||||
Zotero.MIMETypeHandler.removeHandlers('application/pdf', pdfMIMETypeHandler);
|
||||
if (hiddenBrowser) {
|
||||
Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
|
||||
}
|
||||
|
|
@ -1259,13 +1285,14 @@ Zotero.Attachments = new function(){
|
|||
|
||||
this.canFindPDFForItem = function (item) {
|
||||
return item.isRegularItem()
|
||||
&& !item.isFeedItem
|
||||
&& (!!item.getField('DOI') || !!item.getField('url') || !!item.getExtraField('DOI'))
|
||||
&& item.numPDFAttachments() == 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Look for an available PDF for an item and add it as an attachment
|
||||
* Get the PDF resolvers that can be used for a given item based on the available fields
|
||||
*
|
||||
* @param {Zotero.Item} item
|
||||
* @param {String[]} [methods=['doi', 'url', 'oa', 'custom']]
|
||||
|
|
@ -1405,6 +1432,12 @@ Zotero.Attachments = new function(){
|
|||
? elem.getAttribute(attribute)
|
||||
: elem.textContent;
|
||||
if (!val) return [];
|
||||
|
||||
// Handle relative paths
|
||||
val = Services.io.newURI(
|
||||
val, null, Services.io.newURI(url)
|
||||
).spec;
|
||||
|
||||
return [{
|
||||
accessMethod: name,
|
||||
url: val,
|
||||
|
|
@ -2011,7 +2044,7 @@ Zotero.Attachments = new function(){
|
|||
addTriedURL(url);
|
||||
// Backoff loop
|
||||
let tries = 3;
|
||||
while (tries-- >= 0) {
|
||||
while (tries-- > 0) {
|
||||
try {
|
||||
await beforeRequest(url);
|
||||
await this.downloadFile(url, path, options);
|
||||
|
|
@ -2043,14 +2076,22 @@ Zotero.Attachments = new function(){
|
|||
let contentType;
|
||||
let skip = false;
|
||||
let domains = new Set();
|
||||
let redirectLimit = 10;
|
||||
let redirectURLTries = new Map();
|
||||
while (true) {
|
||||
if (redirectLimit == 0) {
|
||||
Zotero.debug("Too many redirects -- stopping");
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
let domain = urlToDomain(nextURL);
|
||||
let noDelay = domains.has(domain);
|
||||
domains.add(domain);
|
||||
|
||||
// Backoff loop
|
||||
let tries = 3;
|
||||
while (tries-- >= 0) {
|
||||
while (tries-- > 0) {
|
||||
try {
|
||||
await beforeRequest(nextURL, noDelay);
|
||||
req = await Zotero.HTTP.request(
|
||||
|
|
@ -2058,7 +2099,9 @@ Zotero.Attachments = new function(){
|
|||
nextURL,
|
||||
{
|
||||
responseType: 'blob',
|
||||
followRedirects: false
|
||||
followRedirects: false,
|
||||
// Use our own error handling
|
||||
errorDelayMax: 0
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -2069,22 +2112,39 @@ Zotero.Attachments = new function(){
|
|||
noDelay = false;
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
break;
|
||||
}
|
||||
afterRequest(nextURL);
|
||||
if (!req) {
|
||||
break;
|
||||
}
|
||||
if ([301, 302, 303, 307].includes(req.status)) {
|
||||
let location = req.getResponseHeader('Location');
|
||||
if (!location) {
|
||||
throw new Error("Location header not provided");
|
||||
}
|
||||
|
||||
let currentURL = nextURL;
|
||||
|
||||
nextURL = Services.io.newURI(nextURL, null, null).resolve(location);
|
||||
if (isTriedURL(nextURL)) {
|
||||
Zotero.debug("Redirect URL has already been tried -- skipping");
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Keep track of tries for each redirect URL, and stop if too many
|
||||
let maxTriesPerRedirectURL = 2;
|
||||
let tries = (redirectURLTries.get(currentURL) || 0) + 1;
|
||||
if (tries > maxTriesPerRedirectURL) {
|
||||
Zotero.debug(`Too many redirects to ${currentURL} -- stopping`);
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
redirectURLTries.set(currentURL, tries);
|
||||
// And keep track of total redirects for this chain
|
||||
redirectLimit--;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -2131,7 +2191,7 @@ Zotero.Attachments = new function(){
|
|||
}
|
||||
|
||||
// If DOI resolves directly to a PDF, save it to disk
|
||||
if (contentType.startsWith('application/pdf')) {
|
||||
if (contentType && contentType.startsWith('application/pdf')) {
|
||||
Zotero.debug("URL resolves directly to PDF");
|
||||
await Zotero.File.putContentsAsync(path, blob);
|
||||
await _enforcePDF(path);
|
||||
|
|
@ -2147,7 +2207,7 @@ Zotero.Attachments = new function(){
|
|||
continue;
|
||||
}
|
||||
if (!url) {
|
||||
Zotero.debug(`No PDF found on ${responseURL}`);
|
||||
Zotero.debug(`No PDF found on ${responseURL || pageURL}`);
|
||||
continue;
|
||||
}
|
||||
if (isTriedURL(url)) {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,110 @@ Zotero.Cite = {
|
|||
/**
|
||||
* Locator labels
|
||||
*/
|
||||
"labels":["page", "book", "chapter", "column", "figure", "folio",
|
||||
"issue", "line", "note", "opus", "paragraph", "part", "section", "sub verbo",
|
||||
"volume", "verse"],
|
||||
"labels": [
|
||||
"act",
|
||||
"appendix",
|
||||
"article-locator",
|
||||
"book",
|
||||
"canon",
|
||||
"chapter",
|
||||
"column",
|
||||
"elocation",
|
||||
"equation",
|
||||
"figure",
|
||||
"folio",
|
||||
"issue",
|
||||
"line",
|
||||
"note",
|
||||
"opus",
|
||||
"page",
|
||||
"paragraph",
|
||||
"part",
|
||||
"rule",
|
||||
"scene",
|
||||
"section",
|
||||
"sub-verbo",
|
||||
"table",
|
||||
//"timestamp",
|
||||
"title-locator",
|
||||
"verse",
|
||||
"volume"
|
||||
],
|
||||
|
||||
_locatorStrings: new Map(),
|
||||
|
||||
/**
|
||||
* Get localized string for locator
|
||||
*
|
||||
* @param {String} locator - Locator name (e.g., 'book')
|
||||
* @return {String} - Localized string (e.g., 'Livre')
|
||||
*/
|
||||
getLocatorString: function (locator) {
|
||||
// 'timestamp' not included in CSL locales
|
||||
if (locator == 'timestamp') {
|
||||
return Zotero.getString('citation.locator.timestamp');
|
||||
}
|
||||
|
||||
// Get the best CSL locale for the current Zotero locale
|
||||
var cslLocale = Zotero.Utilities.Internal.resolveLocale(
|
||||
Zotero.locale,
|
||||
Object.keys(Zotero.Styles.locales)
|
||||
);
|
||||
|
||||
// If locator strings are already cached for the current locale, use that
|
||||
if (this._locatorStrings.has(cslLocale)) {
|
||||
return this._locatorStrings.get(cslLocale).get(locator);
|
||||
}
|
||||
var map = new Map();
|
||||
this._locatorStrings.set(cslLocale, map);
|
||||
|
||||
var localeXML = Zotero.Cite.Locale.get(cslLocale);
|
||||
var parser;
|
||||
if (Zotero.platformMajorVersion > 60) {
|
||||
parser = new DOMParser();
|
||||
}
|
||||
else {
|
||||
parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
}
|
||||
var doc = parser.parseFromString(localeXML, 'text/xml');
|
||||
var englishDoc;
|
||||
// Cache all locators for the current locale
|
||||
for (let locator of this.labels) {
|
||||
let elem = doc.querySelector(`term[name="${locator}"]:not([form="short"]) > single`);
|
||||
if (!elem) {
|
||||
// If locator not found, get from the U.S. English locale
|
||||
if (cslLocale != 'en-US') {
|
||||
Zotero.logError(`Locator '${locator}' not found in ${cslLocale} locale -- trying en-US`);
|
||||
if (!englishDoc) {
|
||||
englishDoc = parser.parseFromString(Zotero.Cite.Locale.get('en-US'), 'text/xml');
|
||||
}
|
||||
elem = englishDoc.querySelector(`term[name="${locator}"]:not([form="short"]) > single`);
|
||||
if (!elem) {
|
||||
Zotero.logError(`Locator '${locator}' not found in en-US locale -- using name`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Zotero.logError(`Locator '${locator}' not found in en-US locale -- using name`);
|
||||
}
|
||||
// If still not found, use the locator name directly
|
||||
if (!elem) {
|
||||
map.set(locator, Zotero.Utilities.capitalize(locator));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// If <single> is empty, use the locator name directly
|
||||
let str = elem.textContent;
|
||||
if (!str) {
|
||||
Zotero.logError(`Locator '${locator}' is empty in ${cslLocale} locale -- using name`);
|
||||
map.set(locator, Zotero.Utilities.capitalize(locator));
|
||||
continue;
|
||||
}
|
||||
map.set(locator, Zotero.Utilities.capitalize(str));
|
||||
}
|
||||
|
||||
return map.get(locator);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove specified item IDs in-place from a citeproc-js bibliography object returned
|
||||
|
|
|
|||
|
|
@ -22261,7 +22261,7 @@ CSL.Output.Formats.prototype.text = {
|
|||
return "\n"+str;
|
||||
},
|
||||
"@display/left-margin": function (state, str) {
|
||||
return str;
|
||||
return str + " ";
|
||||
},
|
||||
"@display/right-inline": function (state, str) {
|
||||
return str;
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
|
|||
}
|
||||
else {
|
||||
var s = new Zotero.Search();
|
||||
s.addCondition('libraryID', 'is', this.ref.libraryID);
|
||||
s.libraryID = this.ref.libraryID;
|
||||
// Library root
|
||||
if (this.isLibrary(true)) {
|
||||
s.addCondition('noChildren', 'true');
|
||||
|
|
@ -379,7 +379,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
|
|||
|
||||
// Create the outer (filter) search
|
||||
var s2 = new Zotero.Search();
|
||||
s2.addCondition('libraryID', 'is', this.ref.libraryID);
|
||||
s2.libraryID = this.ref.libraryID;
|
||||
|
||||
if (this.isTrash()) {
|
||||
s2.addCondition('deleted', 'true');
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ Zotero.HTTPIntegrationClient.Application = function() {
|
|||
this.supportedNotes = ['footnotes'];
|
||||
this.supportsImportExport = false;
|
||||
this.supportsTextInsertion = false;
|
||||
this.supportsCitationMerging = false;
|
||||
this.processorName = "HTTP Integration";
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Application.prototype = {
|
||||
|
|
@ -70,6 +71,7 @@ Zotero.HTTPIntegrationClient.Application.prototype = {
|
|||
this.supportedNotes = result.supportedNotes || this.supportedNotes;
|
||||
this.supportsImportExport = result.supportsImportExport || this.supportsImportExport;
|
||||
this.supportsTextInsertion = result.supportsTextInsertion || this.supportsTextInsertion;
|
||||
this.supportsCitationMerging = result.supportsCitationMerging || this.supportsCitationMerging;
|
||||
this.processorName = result.processorName || this.processorName;
|
||||
return new Zotero.HTTPIntegrationClient.Document(result.documentID, this.processorName);
|
||||
}
|
||||
|
|
@ -168,6 +170,10 @@ Zotero.HTTPIntegrationClient.Field = function(documentID, json) {
|
|||
this._code = json.code;
|
||||
this._text = json.text;
|
||||
this._noteIndex = json.noteIndex;
|
||||
this._adjacent = json.adjacent;
|
||||
if (this._adjacent !== 'undefined') {
|
||||
this.isAdjacentToNextField = this._isAdjacentToNextField;
|
||||
}
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype = {};
|
||||
|
||||
|
|
@ -202,3 +208,6 @@ Zotero.HTTPIntegrationClient.Field.prototype.getNoteIndex = async function() {
|
|||
Zotero.HTTPIntegrationClient.Field.prototype.equals = async function(arg) {
|
||||
return this._id === arg._id;
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype._isAdjacentToNextField = async function() {
|
||||
return this._adjacent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1175,6 +1175,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
|
|||
let item = await Zotero.Attachments.importFromURL({
|
||||
libraryID,
|
||||
url: data.url,
|
||||
referrer: data.referrer,
|
||||
collections: collection ? [collection.id] : undefined,
|
||||
contentType: "application/pdf",
|
||||
cookieSandbox
|
||||
|
|
@ -1234,6 +1235,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
|
|||
await Zotero.Attachments.importFromURL({
|
||||
libraryID,
|
||||
url: data.url,
|
||||
referrer: data.referrer,
|
||||
title,
|
||||
parentItemID: itemID,
|
||||
contentType: "text/html",
|
||||
|
|
@ -1689,9 +1691,8 @@ Zotero.Server.Connector.Ping.prototype = {
|
|||
*/
|
||||
init: function (req) {
|
||||
if (req.method == 'GET') {
|
||||
return [200, "text/html", '<!DOCTYPE html><html><head>' +
|
||||
'<title>Zotero Connector Server is Available</title></head>' +
|
||||
'<body>Zotero Connector Server is Available</body></html>'];
|
||||
return [200, "text/html", '<!DOCTYPE html><html>'
|
||||
+ '<body>Zotero is running</body></html>'];
|
||||
} else {
|
||||
// Store the active URL so it can be used for site-specific Quick Copy
|
||||
if (req.data.activeURL) {
|
||||
|
|
@ -1846,3 +1847,103 @@ Zotero.Server.Connector.IEHack.prototype = {
|
|||
'</head><body></body></html>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP request from the client. Accepts {@link Zotero.HTTP.request} options and returns a minimal response
|
||||
* object with the same form as the one returned from {@link Zotero.Utilities.Translate#request}.
|
||||
*
|
||||
* Accepts:
|
||||
* method - The request method ('GET', 'POST', etc.)
|
||||
* url - The URL to make the request to. Must be an absolute HTTP(S) URL.
|
||||
* options - See Zotero.HTTP.request() documentation. Differences:
|
||||
* - responseType is always set to 'text'
|
||||
* - successCodes is always set to false (non-2xx status codes will not trigger an error)
|
||||
* Returns:
|
||||
* Response code is always 200. Body contains:
|
||||
* status - The response status code, as a number
|
||||
* headers - An object mapping header names to values
|
||||
* body - The response body, as a string
|
||||
*/
|
||||
Zotero.Server.Connector.Request = function () {};
|
||||
|
||||
/**
|
||||
* The list of allowed hosts. Intentionally hardcoded.
|
||||
*/
|
||||
Zotero.Server.Connector.Request.allowedHosts = ['www.worldcat.org'];
|
||||
|
||||
/**
|
||||
* For testing: allow disabling validation so we can make requests to the server.
|
||||
*/
|
||||
Zotero.Server.Connector.Request.enableValidation = false;
|
||||
|
||||
Zotero.Server.Endpoints["/connector/request"] = Zotero.Server.Connector.Request;
|
||||
Zotero.Server.Connector.Request.prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
supportedDataTypes: ["application/json"],
|
||||
|
||||
init: async function (req) {
|
||||
let { method, url, options } = req.data;
|
||||
|
||||
if (typeof method !== 'string' || typeof url !== 'string') {
|
||||
return [400, 'text/plain', 'method and url are required and must be strings'];
|
||||
}
|
||||
|
||||
let uri;
|
||||
try {
|
||||
uri = Services.io.newURI(url);
|
||||
}
|
||||
catch (e) {
|
||||
return [400, 'text/plain', 'Invalid URL'];
|
||||
}
|
||||
|
||||
if (uri.scheme != 'http' && uri.scheme != 'https') {
|
||||
return [400, 'text/plain', 'Unsupported scheme'];
|
||||
}
|
||||
|
||||
if (Zotero.Server.Connector.Request.enableValidation) {
|
||||
if (!Zotero.Server.Connector.Request.allowedHosts.includes(uri.host)) {
|
||||
return [
|
||||
400,
|
||||
'text/plain',
|
||||
'Unsupported URL'
|
||||
];
|
||||
}
|
||||
|
||||
if (!req.headers['User-Agent'] || !req.headers['User-Agent'].startsWith('Mozilla/')) {
|
||||
return [400, 'text/plain', 'Unsupported User-Agent'];
|
||||
}
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.responseType = 'text';
|
||||
options.successCodes = false;
|
||||
|
||||
let xhr;
|
||||
try {
|
||||
xhr = await Zotero.HTTP.request(req.data.method, req.data.url, options);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Zotero.HTTP.BrowserOfflineException) {
|
||||
return [503, 'text/plain', 'Client is offline'];
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
let status = xhr.status;
|
||||
let headers = {};
|
||||
xhr.getAllResponseHeaders()
|
||||
.trim()
|
||||
.split(/[\r\n]+/)
|
||||
.map(line => line.split(': '))
|
||||
.forEach(parts => headers[parts.shift()] = parts.join(': '));
|
||||
let body = xhr.response;
|
||||
|
||||
return [200, 'application/json', JSON.stringify({
|
||||
status,
|
||||
headers,
|
||||
body
|
||||
})];
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -317,10 +317,17 @@ Zotero.CookieSandbox.Observer = new function() {
|
|||
this.trackedInterfaceRequestors.splice(i, 1);
|
||||
this.trackedInterfaceRequestorSandboxes.splice(i, 1);
|
||||
i--;
|
||||
} else if(ir == notificationCallbacks) {
|
||||
// We are tracking this interface requestor
|
||||
trackedBy = this.trackedInterfaceRequestorSandboxes[i];
|
||||
break;
|
||||
} else {
|
||||
let tracked = ir === notificationCallbacks;
|
||||
try {
|
||||
tracked = ir === notificationCallbacks.getInterface(Ci.nsIWebBrowserPersist);
|
||||
} catch (e) { }
|
||||
|
||||
if (tracked) {
|
||||
// We are tracking this interface requestor
|
||||
trackedBy = this.trackedInterfaceRequestorSandboxes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,12 +357,6 @@ Zotero.CookieSandbox.Observer = new function() {
|
|||
notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest);
|
||||
tested = true;
|
||||
} catch(e) {}
|
||||
if(!tested) {
|
||||
try {
|
||||
notificationCallbacks.QueryInterface(Components.interfaces.nsIWebBrowserPersist);
|
||||
tested = true;
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -383,6 +384,10 @@ Zotero.CookieSandbox.Observer = new function() {
|
|||
if(trackedBy) {
|
||||
var cookiesForURI = trackedBy.getCookiesForURI(channel.URI);
|
||||
}
|
||||
|
||||
if (trackedBy && trackedBy.userAgent) {
|
||||
channel.setRequestHeader("User-Agent", trackedBy.userAgent, false);
|
||||
}
|
||||
|
||||
if(!trackedBy || !cookiesForURI) {
|
||||
channel.setRequestHeader("Cookie", "", false);
|
||||
|
|
@ -390,11 +395,7 @@ Zotero.CookieSandbox.Observer = new function() {
|
|||
Zotero.debug("CookieSandbox: Cleared cookies to be sent to "+channelURI, 5);
|
||||
return;
|
||||
}
|
||||
|
||||
if(trackedBy.userAgent) {
|
||||
channel.setRequestHeader("User-Agent", trackedBy.userAgent, false);
|
||||
}
|
||||
|
||||
|
||||
// add cookies to be sent to this domain
|
||||
channel.setRequestHeader("Cookie", Zotero.CookieSandbox.generateCookieString(cookiesForURI), false);
|
||||
Zotero.debug("CookieSandbox: Added cookies for request to "+channelURI, 5);
|
||||
|
|
|
|||
|
|
@ -470,6 +470,7 @@ Zotero.ItemTypes = new function() {
|
|||
case 'case':
|
||||
case 'computerProgram':
|
||||
case 'conferencePaper':
|
||||
case 'dataset':
|
||||
case 'dictionaryEntry':
|
||||
case 'email':
|
||||
case 'encyclopediaArticle':
|
||||
|
|
@ -488,6 +489,7 @@ Zotero.ItemTypes = new function() {
|
|||
case 'preprint':
|
||||
case 'presentation':
|
||||
case 'report':
|
||||
case 'standard':
|
||||
case 'statute':
|
||||
case 'thesis':
|
||||
case 'webpage':
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ Zotero.DataObjectUtilities = {
|
|||
var pred2 = Object.keys(data2);
|
||||
pred2.sort();
|
||||
if (!Zotero.Utilities.arrayEquals(pred1, pred2)) return true;
|
||||
for (let pred in pred1) {
|
||||
for (let pred of pred1) {
|
||||
let vals1 = typeof data1[pred] == 'string' ? [data1[pred]] : data1[pred];
|
||||
let vals2 = (!data2[pred] || data2[pred] === '')
|
||||
? []
|
||||
|
|
|
|||
|
|
@ -133,8 +133,9 @@ Zotero.Feeds = new function() {
|
|||
for (let feedElem of feedElems) {
|
||||
let url = feedElem.getAttribute('xmlUrl');
|
||||
if (!url) url = feedElem.getAttribute('url');
|
||||
let name = feedElem.getAttribute('title');
|
||||
if (!name) name = feedElem.getAttribute('text');
|
||||
let name = feedElem.getAttribute('title')
|
||||
|| feedElem.getAttribute('text')
|
||||
|| Zotero.getString('pane.collections.untitled');
|
||||
if (Zotero.Feeds.existsByURL(url) || registeredUrls.has(url)) {
|
||||
Zotero.debug("Feed Import from OPML: Feed " + name + " : " + url + " already exists. Skipping");
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -153,7 +153,16 @@ Zotero.defineProperty(Zotero.Item.prototype, 'parentItemKey', {
|
|||
Zotero.defineProperty(Zotero.Item.prototype, 'parentItem', {
|
||||
get: function() { return Zotero.Items.get(this.parentID) || undefined; },
|
||||
});
|
||||
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'topLevelItem', {
|
||||
get: function () {
|
||||
var item = this; // eslint-disable-line consistent-this
|
||||
var parentItem;
|
||||
while ((parentItem = item.parentItem)) {
|
||||
item = parentItem;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'firstCreator', {
|
||||
get: function() { return this._firstCreator; }
|
||||
|
|
@ -768,7 +777,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
|
|||
|
||||
if (value !== false && !Zotero.ItemFields.isValidForType(fieldID, itemTypeID)) {
|
||||
let msg = "'" + field + "' is not a valid field for type '"
|
||||
+ Zotero.ItemFields.getName(itemTypeID) + "'";
|
||||
+ Zotero.ItemTypes.getName(itemTypeID) + "'";
|
||||
|
||||
if (loadIn) {
|
||||
Zotero.debug(msg + " -- ignoring value '" + value + "'", 2);
|
||||
|
|
@ -1926,7 +1935,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
|||
authorName || null,
|
||||
text || null,
|
||||
comment || null,
|
||||
color || null,
|
||||
color || Zotero.Annotations.DEFAULT_COLOR,
|
||||
pageLabel || null,
|
||||
sortIndex,
|
||||
position,
|
||||
|
|
@ -2231,6 +2240,10 @@ Zotero.Item.prototype.setNote = function(text) {
|
|||
throw ("updateNote() can only be called on notes and attachments");
|
||||
}
|
||||
|
||||
if (this.isEmbeddedImageAttachment()) {
|
||||
throw new Error("setNote() cannot be called on embedded-image attachments");
|
||||
}
|
||||
|
||||
if (typeof text != 'string') {
|
||||
throw ("text must be a string in Zotero.Item.setNote() (was " + typeof text + ")");
|
||||
}
|
||||
|
|
@ -3713,7 +3726,7 @@ Zotero.Item.prototype.getBestAttachments = Zotero.Promise.coroutine(function* ()
|
|||
|
||||
|
||||
/**
|
||||
* Return state of best attachment
|
||||
* Return state of best attachment (or this item if it's a standalone attachment)
|
||||
*
|
||||
* @return {Promise<Object>} - Promise for object with string 'type' ('none'|'pdf'|'snapshot'|'other')
|
||||
* and boolean 'exists'
|
||||
|
|
@ -3722,7 +3735,9 @@ Zotero.Item.prototype.getBestAttachmentState = async function () {
|
|||
if (this._bestAttachmentState !== null) {
|
||||
return this._bestAttachmentState;
|
||||
}
|
||||
var item = await this.getBestAttachment();
|
||||
var item = this.isAttachment() && this.isTopLevelItem()
|
||||
? this
|
||||
: await this.getBestAttachment();
|
||||
if (!item) {
|
||||
return this._bestAttachmentState = {
|
||||
type: 'none'
|
||||
|
|
@ -3953,6 +3968,29 @@ Zotero.Item.prototype.getAnnotations = function (includeTrashed) {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the item is a PDF attachment that exists on disk and contains
|
||||
* embedded markup annotations.
|
||||
*
|
||||
* @return {Promise<Boolean>}
|
||||
*/
|
||||
Zotero.Item.prototype.hasEmbeddedAnnotations = async function () {
|
||||
if (!this.isPDFAttachment()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = await this.getFilePathAsync();
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let contents = await Zotero.File.getContentsAsync(path);
|
||||
// Check for "markup" annotations per the PDF spec
|
||||
// https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf, p. 390
|
||||
let re = /\s\/Subtype\s+\/(Text|FreeText|Line|Square|Circle|Polygon|PolyLine|Highlight|Underline|Squiggly|StrikeOut|Stamp|Caret|Ink|FileAttachment|Sound|Redact)\s/;
|
||||
return re.test(contents);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Methods dealing with item tags
|
||||
|
|
@ -5189,7 +5227,7 @@ Zotero.Item.prototype.fromJSON = function (json, options = {}) {
|
|||
let parentKey = json.parentItem;
|
||||
this.parentKey = parentKey ? parentKey : false;
|
||||
|
||||
if (!this.isAnnotation()) {
|
||||
if (!this.isAnnotation() && !this.isEmbeddedImageAttachment()) {
|
||||
let note = json.note;
|
||||
this.setNote(note !== undefined ? note : "");
|
||||
}
|
||||
|
|
@ -5408,7 +5446,16 @@ Zotero.Item.prototype.migrateExtraFields = function () {
|
|||
|
||||
try {
|
||||
var { itemType, fields, creators, extra } = Zotero.Utilities.Internal.extractExtraFields(
|
||||
originalExtra, this
|
||||
originalExtra,
|
||||
this,
|
||||
[
|
||||
// Skip 'publisher-place' and 'event-place' for now, since the mappings will be changed
|
||||
// https://github.com/citation-style-language/zotero-bits/issues/6
|
||||
'place',
|
||||
// Skip 'issued' for now, since we don't support date ranges in Date
|
||||
// https://github.com/zotero/zotero/issues/3030
|
||||
'date'
|
||||
]
|
||||
);
|
||||
if (itemType) {
|
||||
let originalType = this.itemTypeID;
|
||||
|
|
|
|||
|
|
@ -76,11 +76,9 @@ Zotero.ItemFields = new function() {
|
|||
var baseFields = yield Zotero.DB.columnQueryAsync(sql);
|
||||
|
||||
for (let field of fields) {
|
||||
let isBaseField = baseFields.includes(field.fieldID);
|
||||
let label = field.label || Zotero.Schema.globalSchemaLocale.fields[field.fieldName];
|
||||
// If string not available, use the field name, except for some base fields that aren't
|
||||
// used in the UI and therefore aren't localized
|
||||
if (!label && !['number', 'type', 'medium'].includes(field.fieldName)) {
|
||||
// If string not available, use the field name
|
||||
if (!label) {
|
||||
Zotero.logError(`Localized string not available for field '${field.fieldName}'`);
|
||||
label = Zotero.Utilities.Internal.camelToTitleCase(field.fieldName);
|
||||
}
|
||||
|
|
@ -347,7 +345,11 @@ Zotero.ItemFields = new function() {
|
|||
|
||||
|
||||
this.isAutocompleteField = function (field) {
|
||||
field = this.getName(field);
|
||||
var fieldName = this.getName(field);
|
||||
if (!fieldName) {
|
||||
Zotero.logError(`Can't check autocomplete for invalid field '${field}'`);
|
||||
return false;
|
||||
}
|
||||
|
||||
var autoCompleteFields = [
|
||||
'journalAbbreviation',
|
||||
|
|
@ -377,7 +379,7 @@ Zotero.ItemFields = new function() {
|
|||
autoCompleteFields = autoCompleteFields.concat(add);
|
||||
}
|
||||
|
||||
return autoCompleteFields.indexOf(field) != -1;
|
||||
return autoCompleteFields.includes(fieldName);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -500,55 +500,68 @@ Zotero.Items = function() {
|
|||
+ "FROM items JOIN itemAnnotations IA USING (itemID) "
|
||||
+ "WHERE libraryID=?" + idSQL;
|
||||
var params = [libraryID];
|
||||
await Zotero.DB.queryAsync(
|
||||
sql,
|
||||
params,
|
||||
{
|
||||
noCache: true,
|
||||
onRow: function (row) {
|
||||
let itemID = row.getResultByIndex(0);
|
||||
|
||||
let item = this._objectCache[itemID];
|
||||
if (!item) {
|
||||
throw new Error("Item " + itemID + " not found");
|
||||
}
|
||||
|
||||
item._parentItemID = row.getResultByIndex(1);
|
||||
var typeID = row.getResultByIndex(2);
|
||||
var type;
|
||||
switch (typeID) {
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_HIGHLIGHT:
|
||||
type = 'highlight';
|
||||
break;
|
||||
|
||||
// TEMP: Fix faulty upgrade from early 6.0 beta
|
||||
// https://github.com/zotero/zotero/issues/3013
|
||||
try {
|
||||
await Zotero.DB.queryAsync(
|
||||
sql,
|
||||
params,
|
||||
{
|
||||
noCache: true,
|
||||
onRow: function (row) {
|
||||
let itemID = row.getResultByIndex(0);
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_NOTE:
|
||||
type = 'note';
|
||||
break;
|
||||
let item = this._objectCache[itemID];
|
||||
if (!item) {
|
||||
throw new Error("Item " + itemID + " not found");
|
||||
}
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_IMAGE:
|
||||
type = 'image';
|
||||
break;
|
||||
item._parentItemID = row.getResultByIndex(1);
|
||||
var typeID = row.getResultByIndex(2);
|
||||
var type;
|
||||
switch (typeID) {
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_HIGHLIGHT:
|
||||
type = 'highlight';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_NOTE:
|
||||
type = 'note';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_IMAGE:
|
||||
type = 'image';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_INK:
|
||||
type = 'ink';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown annotation type id ${typeID}`);
|
||||
}
|
||||
item._annotationType = type;
|
||||
item._annotationAuthorName = row.getResultByIndex(3);
|
||||
item._annotationText = row.getResultByIndex(4);
|
||||
item._annotationComment = row.getResultByIndex(5);
|
||||
item._annotationColor = row.getResultByIndex(6);
|
||||
item._annotationSortIndex = row.getResultByIndex(7);
|
||||
item._annotationIsExternal = !!row.getResultByIndex(8);
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_INK:
|
||||
type = 'ink';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown annotation type id ${typeID}`);
|
||||
}
|
||||
item._annotationType = type;
|
||||
item._annotationAuthorName = row.getResultByIndex(3);
|
||||
item._annotationText = row.getResultByIndex(4);
|
||||
item._annotationComment = row.getResultByIndex(5);
|
||||
item._annotationColor = row.getResultByIndex(6);
|
||||
item._annotationSortIndex = row.getResultByIndex(7);
|
||||
item._annotationIsExternal = !!row.getResultByIndex(8);
|
||||
|
||||
item._loaded.annotation = true;
|
||||
item._clearChanged('annotation');
|
||||
}.bind(this)
|
||||
item._loaded.annotation = true;
|
||||
item._clearChanged('annotation');
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message.includes('no such column: IA.authorName')
|
||||
&& await Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM version WHERE schema='userdata' AND version IN (120, 121)")) {
|
||||
await Zotero.DB.queryAsync("UPDATE version SET version=119 WHERE schema='userdata'");
|
||||
Zotero.crash();
|
||||
}
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1035,6 +1048,29 @@ Zotero.Items = function() {
|
|||
for (let otherItem of otherItems) {
|
||||
let mergedMasterAttachments = new Set();
|
||||
|
||||
let doMerge = async (fromAttachment, toAttachment) => {
|
||||
mergedMasterAttachments.add(toAttachment.id);
|
||||
|
||||
await this.moveChildItems(fromAttachment, toAttachment, true);
|
||||
await this._moveEmbeddedNote(fromAttachment, toAttachment);
|
||||
await this._moveRelations(fromAttachment, toAttachment);
|
||||
|
||||
fromAttachment.deleted = true;
|
||||
await fromAttachment.save();
|
||||
|
||||
// Later on, when processing notes, we'll use this to remap
|
||||
// URLs pointing to the old attachment.
|
||||
remapAttachmentKeys.set(fromAttachment.key, toAttachment.key);
|
||||
|
||||
// Items can only have one replaced item predicate
|
||||
if (!toAttachment.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate)) {
|
||||
toAttachment.addRelation(Zotero.Relations.replacedItemPredicate,
|
||||
Zotero.URI.getItemURI(fromAttachment));
|
||||
}
|
||||
|
||||
await toAttachment.save();
|
||||
};
|
||||
|
||||
for (let otherAttachment of await this.getAsync(otherItem.getAttachments(true))) {
|
||||
if (!otherAttachment.isPDFAttachment()) {
|
||||
continue;
|
||||
|
|
@ -1065,13 +1101,12 @@ Zotero.Items = function() {
|
|||
await otherAttachment.save();
|
||||
continue;
|
||||
}
|
||||
mergedMasterAttachments.add(masterAttachmentID);
|
||||
|
||||
let masterAttachment = await this.getAsync(masterAttachmentID);
|
||||
|
||||
if (masterAttachment.attachmentContentType !== otherAttachment.attachmentContentType) {
|
||||
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
|
||||
+ 'but content types differ - moving');
|
||||
+ 'but content types differ - keeping both');
|
||||
otherAttachment.parentItemID = item.id;
|
||||
await otherAttachment.save();
|
||||
continue;
|
||||
|
|
@ -1080,31 +1115,35 @@ Zotero.Items = function() {
|
|||
if (!((masterAttachment.isImportedAttachment() && otherAttachment.isImportedAttachment())
|
||||
|| (masterAttachment.isLinkedFileAttachment() && otherAttachment.isLinkedFileAttachment()))) {
|
||||
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
|
||||
+ 'but link modes differ - moving');
|
||||
+ 'but link modes differ - keeping both');
|
||||
otherAttachment.parentItemID = item.id;
|
||||
await otherAttachment.save();
|
||||
continue;
|
||||
}
|
||||
|
||||
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key} - merging`);
|
||||
await this.moveChildItems(otherAttachment, masterAttachment, true);
|
||||
await this._moveEmbeddedNote(otherAttachment, masterAttachment);
|
||||
await this._moveRelations(otherAttachment, masterAttachment);
|
||||
|
||||
otherAttachment.deleted = true;
|
||||
await otherAttachment.save();
|
||||
|
||||
// Later on, when processing notes, we'll use this to remap
|
||||
// URLs pointing to the old attachment.
|
||||
remapAttachmentKeys.set(otherAttachment.key, masterAttachment.key);
|
||||
|
||||
// Items can only have one replaced item predicate
|
||||
if (!masterAttachment.getRelationsByPredicate(Zotero.Relations.replacedItemPredicate)) {
|
||||
masterAttachment.addRelation(Zotero.Relations.replacedItemPredicate,
|
||||
Zotero.URI.getItemURI(otherAttachment));
|
||||
// Check whether master and other have embedded annotations
|
||||
// Master yes, other yes -> keep both
|
||||
// Master yes, other no -> keep master
|
||||
// Master no, other yes -> keep other
|
||||
if (await otherAttachment.hasEmbeddedAnnotations()) {
|
||||
if (await masterAttachment.hasEmbeddedAnnotations()) {
|
||||
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
|
||||
+ 'but both have embedded annotations - keeping both');
|
||||
otherAttachment.parentItemID = item.id;
|
||||
await otherAttachment.save();
|
||||
}
|
||||
else {
|
||||
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key}, `
|
||||
+ 'but other has embedded annotations - merging into other');
|
||||
await doMerge(masterAttachment, otherAttachment);
|
||||
otherAttachment.parentItemID = item.id;
|
||||
await otherAttachment.save();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
await masterAttachment.save();
|
||||
Zotero.debug(`Master attachment ${masterAttachment.key} matches ${otherAttachment.key} - merging into master`);
|
||||
await doMerge(otherAttachment, masterAttachment);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1477,6 +1516,7 @@ Zotero.Items = function() {
|
|||
);
|
||||
}
|
||||
Zotero.debug("Emptied " + deleted.length + " item(s) from trash in " + (new Date() - t) + " ms");
|
||||
Zotero.Notifier.trigger('refresh', 'trash', libraryID);
|
||||
}
|
||||
|
||||
return deleted.length;
|
||||
|
|
@ -1709,22 +1749,40 @@ Zotero.Items = function() {
|
|||
|
||||
|
||||
/**
|
||||
* Returns an array of items with children of selected parents removed
|
||||
* Get the top-level items of all passed items
|
||||
*
|
||||
* @param {Zotero.Item[]} items
|
||||
* @return {Zotero.Item[]}
|
||||
*/
|
||||
this.keepParents = function (items) {
|
||||
var parentItems = new Set(
|
||||
items
|
||||
.filter(item => item.isTopLevelItem())
|
||||
.map(item => item.id)
|
||||
this.getTopLevel = function (items) {
|
||||
return [...new Set(items.map(item => item.topLevelItem))];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return an array of items with descendants of selected top-level items removed
|
||||
*
|
||||
* Non-top-level items that aren't descendents of selected items are kept.
|
||||
*
|
||||
* @param {Zotero.Item[]}
|
||||
* @return {Zotero.Item[]}
|
||||
*/
|
||||
this.keepTopLevel = function (items) {
|
||||
var topLevelItems = new Set(
|
||||
items.filter(item => item.isTopLevelItem())
|
||||
);
|
||||
return items.filter(item => {
|
||||
var parentItemID = item.parentItemID;
|
||||
return items.filter((item) => {
|
||||
var topLevelItem = !item.isTopLevelItem() && item.topLevelItem;
|
||||
// Not a child item or not a child of one of the passed items
|
||||
return !parentItemID || !parentItems.has(parentItemID);
|
||||
return !topLevelItem || !topLevelItems.has(topLevelItem);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.keepParents = function (items) {
|
||||
Zotero.debug("Zotero.Items.keepParents() is deprecated -- use Zotero.Items.keepTopLevel() instead");
|
||||
return this.keepTopLevel(items);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
|
|
@ -2011,6 +2069,28 @@ Zotero.Items = function() {
|
|||
}
|
||||
return title.trim();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find attachment items whose paths begin with the passed `pathPrefix` and don't exist on disk
|
||||
*
|
||||
* @param {Number} libraryID
|
||||
* @param {String} pathPrefix
|
||||
* @return {Zotero.Item[]}
|
||||
*/
|
||||
this.findMissingLinkedFiles = async function (libraryID, pathPrefix) {
|
||||
let sql = "SELECT itemID FROM items JOIN itemAttachments USING (itemID) "
|
||||
+ "WHERE itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
+ `AND linkMode=${Zotero.Attachments.LINK_MODE_LINKED_FILE} `
|
||||
+ "AND path LIKE ? ESCAPE '\\' "
|
||||
+ "AND libraryID=?";
|
||||
let ids = await Zotero.DB.columnQueryAsync(sql, [Zotero.DB.escapeSQLExpression(pathPrefix) + '%', libraryID]);
|
||||
let items = await this.getAsync(ids);
|
||||
let missingItems = await Promise.all(
|
||||
items.map(async item => (await item.fileExists() ? false : item))
|
||||
);
|
||||
return missingItems.filter(Boolean);
|
||||
};
|
||||
|
||||
|
||||
Zotero.DataObjects.call(this);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,12 @@ Zotero.defineProperty(Zotero.Search.prototype, 'key', {
|
|||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'name', {
|
||||
get: function() { return this._get('name'); },
|
||||
set: function(val) { return this._set('name', val); }
|
||||
set: function (val) {
|
||||
if (!val) {
|
||||
throw new Error("Saved search name cannot be empty");
|
||||
}
|
||||
return this._set('name', val);
|
||||
}
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'version', {
|
||||
get: function() { return this._get('version'); },
|
||||
|
|
@ -948,7 +953,11 @@ Zotero.Search.idsToTempTable = Zotero.Promise.coroutine(function* (ids) {
|
|||
Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
||||
this._requireData('conditions');
|
||||
|
||||
var sql = 'SELECT itemID FROM items';
|
||||
// TEMP: Match parent attachment for annotation matches
|
||||
// var sql = 'SELECT itemID FROM items';
|
||||
var sql = "SELECT COALESCE(IA.parentItemID, itemID) AS itemID FROM items "
|
||||
+ "LEFT JOIN itemAnnotations IA USING (itemID)";
|
||||
|
||||
var sqlParams = [];
|
||||
// Separate ANY conditions for 'required' condition support
|
||||
var anySQL = '';
|
||||
|
|
@ -957,12 +966,14 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
var conditions = [];
|
||||
|
||||
let lastCondition;
|
||||
for (let condition of Object.values(this._conditions)) {
|
||||
let conditionsToProcess = Object.values(this._conditions);
|
||||
for (let condition of conditionsToProcess) {
|
||||
let name = condition.condition;
|
||||
let conditionData = Zotero.SearchConditions.get(name);
|
||||
|
||||
// Has a table (or 'savedSearch', which doesn't have a table but isn't special)
|
||||
if (conditionData.table || name == 'savedSearch' || name == 'tempTable') {
|
||||
// TEMP: Or 'tag', which needs to match annotation parents
|
||||
if (conditionData.table || name == 'savedSearch' || name == 'tempTable' || name == 'tag') {
|
||||
// For conditions with an inline filter using 'is'/'isNot', combine with last condition
|
||||
// if the same
|
||||
if (lastCondition
|
||||
|
|
@ -1050,6 +1061,39 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
case 'blockEnd':
|
||||
conditions.push({name:'blockEnd'});
|
||||
continue;
|
||||
|
||||
case 'anyField':
|
||||
// We expand this condition to the same underlying set of conditions as 'quicksearch-fields'
|
||||
// (although we don't detect keys or split into quoted and unquoted segments). 'quicksearch-fields'
|
||||
// is expanded in addCondition(), but we can't do that with this condition because we don't want
|
||||
// to save the conditions it expands to in the search object
|
||||
conditionsToProcess.push({ condition: 'blockStart' });
|
||||
conditionsToProcess.push({
|
||||
condition: 'field',
|
||||
operator: condition.operator,
|
||||
value: condition.value,
|
||||
required: false
|
||||
});
|
||||
conditionsToProcess.push({
|
||||
condition: 'tag',
|
||||
operator: condition.operator,
|
||||
value: condition.value,
|
||||
required: false
|
||||
});
|
||||
conditionsToProcess.push({
|
||||
condition: 'note',
|
||||
operator: condition.operator,
|
||||
value: condition.value,
|
||||
required: false
|
||||
});
|
||||
conditionsToProcess.push({
|
||||
condition: 'creator',
|
||||
operator: condition.operator,
|
||||
value: condition.value,
|
||||
required: false
|
||||
});
|
||||
conditionsToProcess.push({ condition: 'blockEnd' });
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error('Unhandled special condition ' + name);
|
||||
|
|
@ -1058,16 +1102,24 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
|
||||
// Exclude deleted items (and their child items) by default
|
||||
let not = deleted ? "" : "NOT ";
|
||||
let op = deleted ? "OR" : "AND";
|
||||
sql += " WHERE ("
|
||||
+ `itemID ${not} IN (SELECT itemID FROM deletedItems) `
|
||||
+ `${op} itemID ${not}IN (SELECT itemID FROM itemNotes `
|
||||
sql += ` WHERE (itemID ${not} IN (`
|
||||
// Deleted items
|
||||
+ "SELECT itemID FROM deletedItems "
|
||||
// Child notes of deleted items
|
||||
+ "UNION SELECT itemID FROM itemNotes "
|
||||
+ "WHERE parentItemID IS NOT NULL AND "
|
||||
+ "parentItemID IN (SELECT itemID FROM deletedItems)) "
|
||||
+ `${op} itemID ${not}IN (SELECT itemID FROM itemAttachments `
|
||||
+ "parentItemID IN (SELECT itemID FROM deletedItems) "
|
||||
// Child attachments of deleted items
|
||||
+ "UNION SELECT itemID FROM itemAttachments "
|
||||
+ "WHERE parentItemID IS NOT NULL AND "
|
||||
+ "parentItemID IN (SELECT itemID FROM deletedItems))"
|
||||
+ ")";
|
||||
+ "parentItemID IN (SELECT itemID FROM deletedItems)"
|
||||
// Annotations of deleted attachments
|
||||
+ "UNION SELECT itemID FROM itemAnnotations "
|
||||
+ "WHERE parentItemID IN (SELECT itemID FROM deletedItems)"
|
||||
// Annotations of attachments of deleted items
|
||||
+ "UNION SELECT itemID FROM itemAnnotations "
|
||||
+ "WHERE parentItemID IN (SELECT itemID FROM itemAttachments WHERE parentItemID IN (SELECT itemID FROM deletedItems))"
|
||||
+ "))";
|
||||
|
||||
if (noChildren){
|
||||
sql += " AND (itemID NOT IN (SELECT itemID FROM itemNotes "
|
||||
|
|
@ -1077,14 +1129,15 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
}
|
||||
|
||||
if (unfiled) {
|
||||
sql += " AND (itemID NOT IN (SELECT itemID FROM collectionItems) "
|
||||
sql += " AND (itemID NOT IN ("
|
||||
+ "SELECT itemID FROM collectionItems "
|
||||
// Exclude children
|
||||
+ "AND itemID NOT IN "
|
||||
+ "(SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL "
|
||||
+ "UNION SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL)"
|
||||
+ ") "
|
||||
+ "UNION SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL "
|
||||
+ "UNION SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL "
|
||||
+ "UNION SELECT itemID FROM itemAnnotations "
|
||||
// Exclude My Publications
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM publicationsItems)";
|
||||
+ "UNION SELECT itemID FROM publicationsItems "
|
||||
+ "))";
|
||||
}
|
||||
|
||||
if (retracted) {
|
||||
|
|
@ -1119,22 +1172,42 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
//
|
||||
// Special table handling
|
||||
//
|
||||
if (condition['table']){
|
||||
switch (condition['table']){
|
||||
default:
|
||||
condSelectSQL += 'itemID '
|
||||
switch (condition['operator']){
|
||||
case 'isNot':
|
||||
case 'doesNotContain':
|
||||
condSelectSQL += 'NOT ';
|
||||
break;
|
||||
}
|
||||
condSelectSQL += 'IN (';
|
||||
selectOpenParens = 1;
|
||||
condSQL += 'SELECT itemID FROM ' +
|
||||
condition['table'] + ' WHERE (';
|
||||
openParens = 1;
|
||||
if (condition.table) {
|
||||
let negationOperators = ['isNot', 'doesNotContain'];
|
||||
let isNegationOperator = negationOperators.includes(condition.operator);
|
||||
|
||||
condSelectSQL += 'itemID '
|
||||
if (isNegationOperator) {
|
||||
condSelectSQL += 'NOT ';
|
||||
}
|
||||
condSelectSQL += 'IN (';
|
||||
selectOpenParens = 1;
|
||||
|
||||
// TEMP: Don't match annotations for negation operators, since it would result in
|
||||
// all parent attachments being returned
|
||||
if (isNegationOperator) {
|
||||
condSelectSQL += "SELECT itemID FROM items WHERE itemTypeID="
|
||||
+ Zotero.ItemTypes.getID('annotation') + " UNION ";
|
||||
}
|
||||
|
||||
switch (condition.name) {
|
||||
// TEMP: Match parent attachments of matching annotations
|
||||
case 'tag':
|
||||
condSQL += "SELECT COALESCE(IAnT.parentItemID, itemID) FROM itemTags "
|
||||
+ "LEFT JOIN itemAnnotations IAnT USING (itemID) WHERE (";
|
||||
break;
|
||||
|
||||
// TEMP: Match parent attachments of matching annotations
|
||||
case 'annotationText':
|
||||
case 'annotationComment':
|
||||
condSQL += `SELECT parentItemID FROM ${condition.table} WHERE (`
|
||||
break;
|
||||
|
||||
default:
|
||||
condSQL += `SELECT itemID FROM ${condition.table} WHERE (`;
|
||||
}
|
||||
|
||||
openParens = 1;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -1709,7 +1782,11 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
|
||||
// Add on quicksearch conditions
|
||||
if (quicksearchSQLSet) {
|
||||
sql = "SELECT itemID FROM items WHERE itemID IN (" + sql + ") "
|
||||
// TEMP: Match parent attachments for annotations
|
||||
//sql = "SELECT itemID FROM items WHERE itemID IN (" + sql + ") "
|
||||
sql = "SELECT COALESCE(IAn.parentItemID, itemID) AS itemID FROM items "
|
||||
+ "LEFT JOIN itemAnnotations IAn USING (itemID) "
|
||||
+ "WHERE itemID IN (" + sql + ") "
|
||||
+ "AND ((" + quicksearchSQLSet.join(') AND (') + "))";
|
||||
|
||||
for (var k=0; k<quicksearchParamsSet.length; k++) {
|
||||
|
|
|
|||
|
|
@ -451,6 +451,18 @@ Zotero.SearchConditions = new function(){
|
|||
+ "'section','seriesNumber','issue')"),
|
||||
template: true // mark for special handling
|
||||
},
|
||||
|
||||
{
|
||||
name: 'anyField',
|
||||
operators: {
|
||||
is: true,
|
||||
isNot: true,
|
||||
contains: true,
|
||||
doesNotContain: true,
|
||||
beginsWith: true
|
||||
},
|
||||
special: false
|
||||
},
|
||||
|
||||
{
|
||||
name: 'datefield',
|
||||
|
|
@ -652,6 +664,10 @@ Zotero.SearchConditions = new function(){
|
|||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
_standardConditions.sort(function(a, b) {
|
||||
// Sort Any Field to the top
|
||||
if (a.name == 'anyField') {
|
||||
return -1;
|
||||
}
|
||||
return collation.compareString(1, a.localized, b.localized);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -163,9 +163,6 @@ Zotero.Tags = new function() {
|
|||
if (libraryID) {
|
||||
sql += "JOIN items USING (itemID) WHERE libraryID = ? ";
|
||||
params.push(libraryID);
|
||||
// TEMP: Don't show annotation tags in tag selector
|
||||
sql += "AND itemTypeID != ? ";
|
||||
params.push(Zotero.ItemTypes.getID('annotation'));
|
||||
}
|
||||
else {
|
||||
sql += "WHERE 1 ";
|
||||
|
|
@ -174,7 +171,11 @@ Zotero.Tags = new function() {
|
|||
if (libraryID) {
|
||||
throw new Error("tmpTable and libraryID are mutually exclusive");
|
||||
}
|
||||
sql += "AND itemID IN (SELECT itemID FROM " + tmpTable + ") ";
|
||||
sql += "AND itemID IN (SELECT itemID FROM " + tmpTable;
|
||||
// TEMP: Match parent attachments for annotation tags
|
||||
sql += " UNION SELECT itemID FROM itemAnnotations WHERE parentItemID IN "
|
||||
+ "(SELECT itemID FROM " + tmpTable + ")";
|
||||
sql += ") ";
|
||||
}
|
||||
if (types && types.length) {
|
||||
sql += "AND type IN (" + new Array(types.length).fill('?').join(', ') + ") ";
|
||||
|
|
|
|||
|
|
@ -909,39 +909,6 @@ Zotero.DataDirectory = {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check for an existing pipe from other running versions of Zotero pointing at the same data
|
||||
// directory, and skip migration if found
|
||||
try {
|
||||
let foundPipe = yield Zotero.IPC.pipeExists();
|
||||
if (foundPipe) {
|
||||
Zotero.debug("Found existing pipe -- skipping migration");
|
||||
|
||||
if (!automatic) {
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
let index = ps.confirmEx(null,
|
||||
Zotero.getString('dataDir.migration.failure.title'),
|
||||
Zotero.getString('dataDir.migration.failure.full.firefoxOpen'),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.tryAgain'),
|
||||
Zotero.getString('general.tryLater'),
|
||||
null, null, {}
|
||||
);
|
||||
|
||||
if (index == 0) {
|
||||
return this.checkForMigration(newDir, newDir);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error checking for pipe -- skipping migration:\n\n" + e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there are other profiles pointing to the old directory, make sure we can edit the prefs.js
|
||||
// file before doing anything, or else we risk orphaning a 4.0 installation
|
||||
try {
|
||||
|
|
@ -1043,10 +1010,6 @@ Zotero.DataDirectory = {
|
|||
// Set data directory again
|
||||
Zotero.debug("Using new data directory " + newDir);
|
||||
this._cache(newDir);
|
||||
// Tell Zotero for Firefox in connector mode to reload and find the new data directory
|
||||
if (Zotero.isStandalone) {
|
||||
Zotero.IPC.broadcast('reinit');
|
||||
}
|
||||
|
||||
// At least the database was copied, but other things failed
|
||||
if (errors.length) {
|
||||
|
|
|
|||
|
|
@ -1303,7 +1303,6 @@ Zotero.DBConnection.prototype._checkException = async function (e) {
|
|||
* @return {Boolean} - True if recovered, false if not
|
||||
*/
|
||||
Zotero.DBConnection.prototype._handleCorruptionMarker = async function () {
|
||||
var storage = Services.storage;
|
||||
var file = this._dbPath;
|
||||
var fileName = OS.Path.basename(file);
|
||||
var backupFile = this._dbPath + '.bak';
|
||||
|
|
@ -1315,26 +1314,38 @@ Zotero.DBConnection.prototype._handleCorruptionMarker = async function () {
|
|||
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);
|
||||
let damagedFile = this._dbPath + '.damaged';
|
||||
damagedFile = await Zotero.File.moveToUnique(file, damagedFile);
|
||||
let damagedFile;
|
||||
|
||||
// If database file exists, move it to .damaged
|
||||
if (await OS.File.exists(file)) {
|
||||
this._debug('Saving damaged DB file with .damaged extension', 1);
|
||||
damagedFile = this._dbPath + '.damaged';
|
||||
damagedFile = await Zotero.File.moveToUnique(file, damagedFile);
|
||||
}
|
||||
// If it doesn't exist, assume we already showed a warning and moved it
|
||||
else {
|
||||
this._debug(`Database file '${fileName}' doesn't exist!`);
|
||||
}
|
||||
|
||||
// Create new main database
|
||||
this._connection = storage.openDatabase(file);
|
||||
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: file
|
||||
}));
|
||||
|
||||
if (await OS.File.exists(corruptMarker)) {
|
||||
await OS.File.remove(corruptMarker);
|
||||
}
|
||||
|
||||
Zotero.alert(
|
||||
null,
|
||||
Zotero.getString('startupError', Zotero.appName),
|
||||
Zotero.getString(
|
||||
'db.dbCorruptedNoBackup',
|
||||
[Zotero.appName, fileName, OS.Path.basename(damagedFile)]
|
||||
)
|
||||
);
|
||||
if (damagedFile) {
|
||||
Zotero.alert(
|
||||
null,
|
||||
Zotero.getString('startupError', Zotero.appName),
|
||||
Zotero.getString(
|
||||
'db.dbCorruptedNoBackup',
|
||||
[Zotero.appName, fileName, OS.Path.basename(damagedFile)]
|
||||
)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -384,12 +384,12 @@ Zotero.Duplicates.prototype._findDuplicates = Zotero.Promise.coroutine(function*
|
|||
for (let i = 0; i < aCreatorRows.length; i++) {
|
||||
let aCreatorRow = aCreatorRows[i];
|
||||
let aLastName = aCreatorRow.lastName;
|
||||
let aFirstInitial = aCreatorRow.firstInitial;
|
||||
let aFirstInitial = aCreatorRow.firstInitial || "";
|
||||
|
||||
for (let j = 0; j < bCreatorRows.length; j++) {
|
||||
let bCreatorRow = bCreatorRows[j];
|
||||
let bLastName = bCreatorRow.lastName;
|
||||
let bFirstInitial = bCreatorRow.firstInitial;
|
||||
let bFirstInitial = bCreatorRow.firstInitial || "";
|
||||
|
||||
if (aLastName === bLastName && aFirstInitial === bFirstInitial) {
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
|
||||
|
||||
import FilePicker from 'zotero/modules/filePicker';
|
||||
|
||||
// Note: TinyMCE is automatically doing some meaningless corrections to
|
||||
// note-editor produced HTML. Which might result to more
|
||||
// conflicts, especially in group libraries
|
||||
|
|
@ -43,9 +45,6 @@ const DOWNLOADED_IMAGE_TYPE = [
|
|||
'image/png'
|
||||
];
|
||||
|
||||
// Schema version here has to be the same as in note-editor!
|
||||
const SCHEMA_VERSION = 8;
|
||||
|
||||
class EditorInstance {
|
||||
constructor() {
|
||||
this.instanceID = Zotero.Utilities.randomString();
|
||||
|
|
@ -89,6 +88,76 @@ class EditorInstance {
|
|||
return doc.execCommand(command, ui, value);
|
||||
};
|
||||
|
||||
// Translate note HTML into Markdown, for setting it as text/plain in clipboard (on text copy/drag)
|
||||
this._iframeWindow.wrappedJSObject.zoteroTranslateToMarkdown = (html) => {
|
||||
let item = new Zotero.Item('note');
|
||||
item.libraryID = this._item.libraryID;
|
||||
item.setNote(html);
|
||||
let text = '';
|
||||
var translation = new Zotero.Translate.Export;
|
||||
translation.noWait = true;
|
||||
translation.setItems([item]);
|
||||
translation.setTranslator(Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN);
|
||||
translation.setHandler("done", (obj, worked) => {
|
||||
if (worked) {
|
||||
text = obj.string.replace(/\r\n/g, '\n');
|
||||
}
|
||||
});
|
||||
translation.translate();
|
||||
return text;
|
||||
};
|
||||
|
||||
this._iframeWindow.wrappedJSObject.zoteroCopyImage = async (dataURL) => {
|
||||
let parts = dataURL.split(',');
|
||||
if (!parts[0].includes('base64')) {
|
||||
return;
|
||||
}
|
||||
let mime = parts[0].match(/:(.*?);/)[1];
|
||||
let bstr = atob(parts[1]);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
let imgTools = Components.classes["@mozilla.org/image/tools;1"]
|
||||
.getService(Components.interfaces.imgITools);
|
||||
let transferable = Components.classes['@mozilla.org/widget/transferable;1']
|
||||
.createInstance(Components.interfaces.nsITransferable);
|
||||
let clipboardService = Components.classes['@mozilla.org/widget/clipboard;1']
|
||||
.getService(Components.interfaces.nsIClipboard);
|
||||
let imgPtr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
|
||||
.createInstance(Components.interfaces.nsISupportsInterfacePointer);
|
||||
imgPtr.data = imgTools.decodeImageFromArrayBuffer(u8arr.buffer, mime);
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor(mime);
|
||||
transferable.setTransferData(mime, imgPtr, 0);
|
||||
clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
|
||||
};
|
||||
|
||||
this._iframeWindow.wrappedJSObject.zoteroSaveImageAs = async (dataURL) => {
|
||||
let parts = dataURL.split(',');
|
||||
if (!parts[0].includes('base64')) {
|
||||
return;
|
||||
}
|
||||
let mime = parts[0].match(/:(.*?);/)[1];
|
||||
let bstr = atob(parts[1]);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
let ext = Zotero.MIME.getPrimaryExtension(mime, '');
|
||||
let fp = new FilePicker();
|
||||
fp.init(this._iframeWindow, Zotero.getString('noteEditor.saveImageAs'), fp.modeSave);
|
||||
fp.appendFilters(fp.filterImages);
|
||||
fp.defaultString = Zotero.getString('fileTypes.image').toLowerCase() + '.' + ext;
|
||||
let rv = await fp.show();
|
||||
if (rv === fp.returnOK || rv === fp.returnReplace) {
|
||||
let outputPath = fp.file;
|
||||
await OS.File.writeAtomic(outputPath, u8arr);
|
||||
}
|
||||
};
|
||||
|
||||
this._iframeWindow.addEventListener('message', this._messageHandler);
|
||||
this._iframeWindow.addEventListener('error', (event) => {
|
||||
Zotero.logError(event.error);
|
||||
|
|
@ -697,6 +766,9 @@ class EditorInstance {
|
|||
}
|
||||
menuitem.setAttribute('checked', item.checked);
|
||||
menuitem.addEventListener('command', () => {
|
||||
if (item.name === 'insertImage') {
|
||||
return this._iframeWindow.eval('openImageFilePicker()');
|
||||
}
|
||||
this._postMessage({
|
||||
action: 'contextMenuAction',
|
||||
ctxAction: item.name,
|
||||
|
|
@ -1193,10 +1265,12 @@ class EditorInstance {
|
|||
* Create note from annotations
|
||||
*
|
||||
* @param {Zotero.Item[]} annotations
|
||||
* @param {Integer} parentID Creates standalone note if not provided
|
||||
* @param {Object} options
|
||||
* @param {Integer} options.parentID - Creates standalone note if not provided
|
||||
* @param {Integer} options.collectionID - Only valid if parentID not provided
|
||||
* @returns {Promise<Zotero.Item>}
|
||||
*/
|
||||
static async createNoteFromAnnotations(annotations, parentID) {
|
||||
static async createNoteFromAnnotations(annotations, { parentID, collectionID } = {}) {
|
||||
if (!annotations.length) {
|
||||
throw new Error("No annotations provided");
|
||||
}
|
||||
|
|
@ -1217,7 +1291,12 @@ class EditorInstance {
|
|||
|
||||
let note = new Zotero.Item('note');
|
||||
note.libraryID = annotations[0].libraryID;
|
||||
note.parentID = parentID;
|
||||
if (parentID) {
|
||||
note.parentID = parentID;
|
||||
}
|
||||
else if (collectionID) {
|
||||
note.addToCollection(collectionID);
|
||||
}
|
||||
await note.saveTx();
|
||||
let editorInstance = new EditorInstance();
|
||||
editorInstance._item = note;
|
||||
|
|
@ -1239,10 +1318,61 @@ class EditorInstance {
|
|||
html += '\n';
|
||||
|
||||
await editorInstance.importImages(jsonAnnotations);
|
||||
let { html: serializedHTML, citationItems } = Zotero.EditorInstanceUtilities.serializeAnnotations(jsonAnnotations, true);
|
||||
html += serializedHTML;
|
||||
|
||||
let multipleParentParent = false;
|
||||
let lastParentParentID;
|
||||
let lastParentID;
|
||||
// Group annotations per attachment
|
||||
let groups = [];
|
||||
for (let i = 0; i < annotations.length; i++) {
|
||||
let annotation = annotations[i];
|
||||
let jsonAnnotation = jsonAnnotations[i];
|
||||
let parentParentID = annotation.parentItem.parentID;
|
||||
let parentID = annotation.parentID;
|
||||
if (groups.length) {
|
||||
if (parentParentID !== lastParentParentID) {
|
||||
// Multiple top level regular items detected, allow including their titles
|
||||
multipleParentParent = true;
|
||||
}
|
||||
}
|
||||
if (!groups.length || parentID !== lastParentID) {
|
||||
groups.push({
|
||||
parentTitle: annotation.parentItem.getDisplayTitle(),
|
||||
parentParentID,
|
||||
parentParentTitle: annotation.parentItem.parentItem && annotation.parentItem.parentItem.getDisplayTitle(),
|
||||
jsonAnnotations: [jsonAnnotation]
|
||||
});
|
||||
}
|
||||
else {
|
||||
let group = groups[groups.length - 1];
|
||||
group.jsonAnnotations.push(jsonAnnotation);
|
||||
}
|
||||
lastParentParentID = parentParentID;
|
||||
lastParentID = parentID;
|
||||
}
|
||||
let citationItems = [];
|
||||
lastParentParentID = null;
|
||||
for (let group of groups) {
|
||||
if (multipleParentParent && group.parentParentTitle && lastParentParentID !== group.parentParentID) {
|
||||
html += `<h2>${group.parentParentTitle}</h2>\n`;
|
||||
}
|
||||
lastParentParentID = group.parentParentID;
|
||||
// If attachment doesn't have a parent or there are more attachments with the same parent, show attachment title
|
||||
if (!group.parentParentID || groups.filter(x => x.parentParentID === group.parentParentID).length > 1) {
|
||||
html += `<h3>${group.parentTitle}</h3>\n`;
|
||||
}
|
||||
let { html: _html, citationItems: _citationItems } = Zotero.EditorInstanceUtilities.serializeAnnotations(group.jsonAnnotations, true);
|
||||
html += _html + '\n';
|
||||
for (let _citationItem of _citationItems) {
|
||||
if (!citationItems.find(item => item.uris.some(uri => _citationItem.uris.includes(uri)))) {
|
||||
citationItems.push(_citationItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
citationItems = encodeURIComponent(JSON.stringify(citationItems));
|
||||
html = `<div data-citation-items="${citationItems}" data-schema-version="${SCHEMA_VERSION}">${html}</div>`;
|
||||
// Note: Update schema version only if using new features
|
||||
let schemaVersion = 8;
|
||||
html = `<div data-citation-items="${citationItems}" data-schema-version="${schemaVersion}">${html}</div>`;
|
||||
note.setNote(html);
|
||||
await note.saveTx();
|
||||
return note;
|
||||
|
|
@ -1268,7 +1398,8 @@ class EditorInstanceUtilities {
|
|||
|
||||
if (!annotation.text
|
||||
&& !annotation.comment
|
||||
&& !annotation.imageAttachmentKey) {
|
||||
&& !annotation.imageAttachmentKey
|
||||
|| annotation.type === 'ink') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1359,16 +1490,24 @@ class EditorInstanceUtilities {
|
|||
template = '<p>{{image}}<br/>{{citation}} {{comment}}</p>';
|
||||
}
|
||||
|
||||
Zotero.debug('Using note template:');
|
||||
Zotero.debug(template);
|
||||
|
||||
template = template.replace(
|
||||
/(<blockquote>[^<>]*?)({{highlight}})([\s\S]*?<\/blockquote>)/g,
|
||||
(match, p1, p2, p3) => p1 + "{{highlight quotes='false'}}" + p3
|
||||
);
|
||||
|
||||
let vars = {
|
||||
color: annotation.color || '',
|
||||
highlight: (attrs) => attrs.quotes === 'true' ? quotedHighlightHTML : highlightHTML,
|
||||
// Include quotation marks by default, but allow to disable with `quotes='false'`
|
||||
highlight: (attrs) => attrs.quotes === 'false' ? highlightHTML : quotedHighlightHTML,
|
||||
comment: commentHTML,
|
||||
citation: citationHTML,
|
||||
image: imageHTML,
|
||||
tags: (attrs) => (annotation.tags && annotation.tags.map(tag => tag.name) || []).join(attrs.join || ' ')
|
||||
};
|
||||
Zotero.debug('Using note template:');
|
||||
Zotero.debug(template);
|
||||
|
||||
let templateHTML = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||
// Remove some spaces at the end of paragraph
|
||||
templateHTML = templateHTML.replace(/([\s]*)(<\/p)/g, '$2');
|
||||
|
|
@ -1529,5 +1668,4 @@ class EditorInstanceUtilities {
|
|||
}
|
||||
|
||||
Zotero.EditorInstance = EditorInstance;
|
||||
Zotero.EditorInstance.SCHEMA_VERSION = SCHEMA_VERSION;
|
||||
Zotero.EditorInstanceUtilities = new EditorInstanceUtilities();
|
||||
|
|
|
|||
|
|
@ -1086,7 +1086,31 @@ Zotero.File = new function(){
|
|||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Normalize to a Unix-style path, replacing backslashes (interpreted as
|
||||
* separators only on Windows) with forward slashes (interpreted as
|
||||
* separators everywhere)
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
*/
|
||||
this.normalizeToUnix = function (path) {
|
||||
// If we're on Windows, we need to normalize first and then replace
|
||||
// the slashes, because OS.Path.normalize won't handle forward slashes
|
||||
// correctly. Otherwise, we replace slashes first and *then* normalize.
|
||||
// This should ensure consistent behavior across platforms.
|
||||
if (Zotero.isWin) {
|
||||
let normalized = OS.Path.normalize(path);
|
||||
return normalized.replace(/\\/g, '/');
|
||||
}
|
||||
else {
|
||||
let replaced = path.replace(/\\/g, '/');
|
||||
return OS.Path.normalize(replaced);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -1096,8 +1120,8 @@ Zotero.File = new function(){
|
|||
if (typeof dir != 'string') throw new Error("dir must be a string");
|
||||
if (typeof file != 'string') throw new Error("file must be a string");
|
||||
|
||||
dir = OS.Path.normalize(dir).replace(/\\/g, "/");
|
||||
file = OS.Path.normalize(file).replace(/\\/g, "/");
|
||||
dir = this.normalizeToUnix(dir);
|
||||
file = this.normalizeToUnix(file);
|
||||
// Normalize D:\ vs. D:\foo
|
||||
if (dir != file && !dir.endsWith('/')) {
|
||||
dir += '/';
|
||||
|
|
|
|||
|
|
@ -381,8 +381,8 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
}
|
||||
|
||||
if (!charset) {
|
||||
Zotero.logError(`Item ${itemID} didn't have a charset`);
|
||||
return false;
|
||||
Zotero.debug(`Item ${itemID} doesn't have a charset set -- using 'utf-8'`);
|
||||
charset = 'utf-8';
|
||||
}
|
||||
|
||||
var maxLength = Zotero.Prefs.get('fulltext.textMaxLength');
|
||||
|
|
|
|||
|
|
@ -166,46 +166,48 @@ Zotero.HTTP = new function() {
|
|||
if (e.xmlhttp.status == 503 && await _checkRetry(e.xmlhttp)) {
|
||||
continue;
|
||||
}
|
||||
// Don't retry if errorDelayMax is 0
|
||||
if (options.errorDelayMax === 0) {
|
||||
throw e;
|
||||
}
|
||||
// Automatically retry other 5xx errors by default
|
||||
if (options.errorDelayMax !== 0) {
|
||||
if (!errorDelayGenerator) {
|
||||
// Keep trying for up to an hour
|
||||
errorDelayGenerator = Zotero.Utilities.Internal.delayGenerator(
|
||||
options.errorDelayIntervals || _errorDelayIntervals,
|
||||
options.errorDelayMax !== undefined
|
||||
? options.errorDelayMax
|
||||
: _errorDelayMax
|
||||
);
|
||||
if (!errorDelayGenerator) {
|
||||
// Keep trying for up to an hour
|
||||
errorDelayGenerator = Zotero.Utilities.Internal.delayGenerator(
|
||||
options.errorDelayIntervals || _errorDelayIntervals,
|
||||
options.errorDelayMax !== undefined
|
||||
? options.errorDelayMax
|
||||
: _errorDelayMax
|
||||
);
|
||||
}
|
||||
let delayPromise = errorDelayGenerator.next().value;
|
||||
let keepGoing;
|
||||
// Provide caller with a callback to cancel while waiting to retry
|
||||
if (options.cancellerReceiver) {
|
||||
let resolve;
|
||||
let reject;
|
||||
let cancelPromise = new Zotero.Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = function () {
|
||||
rej(new Zotero.HTTP.CancelledException);
|
||||
};
|
||||
});
|
||||
options.cancellerReceiver(reject);
|
||||
try {
|
||||
keepGoing = await Promise.race([delayPromise, cancelPromise]);
|
||||
}
|
||||
let delayPromise = errorDelayGenerator.next().value;
|
||||
let keepGoing;
|
||||
// Provide caller with a callback to cancel while waiting to retry
|
||||
if (options.cancellerReceiver) {
|
||||
let resolve;
|
||||
let reject;
|
||||
let cancelPromise = new Zotero.Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = function () {
|
||||
rej(new Zotero.HTTP.CancelledException);
|
||||
};
|
||||
});
|
||||
options.cancellerReceiver(reject);
|
||||
try {
|
||||
keepGoing = await Promise.race([delayPromise, cancelPromise]);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("Request cancelled");
|
||||
throw e;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
keepGoing = await delayPromise;
|
||||
}
|
||||
if (!keepGoing) {
|
||||
Zotero.logError("Failed too many times");
|
||||
catch (e) {
|
||||
Zotero.debug("Request cancelled");
|
||||
throw e;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
keepGoing = await delayPromise;
|
||||
}
|
||||
if (!keepGoing) {
|
||||
Zotero.logError("Failed too many times");
|
||||
throw e;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1217,9 +1219,10 @@ Zotero.HTTP = new function() {
|
|||
* @param {Boolean} dontDelete Don't delete the hidden browser upon completion; calling function
|
||||
* must call deleteHiddenBrowser itself.
|
||||
* @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
|
||||
* @param {Object} [docShellPrefs] See Zotero.Browser.createHiddenBrowser
|
||||
* @return {browser} Hidden browser used for loading
|
||||
*/
|
||||
this.loadDocuments = function (urls, processor, onDone, onError, dontDelete, cookieSandbox) {
|
||||
this.loadDocuments = function (urls, processor, onDone, onError, dontDelete, cookieSandbox, docShellPrefs={}) {
|
||||
// (Approximately) how many seconds to wait if the document is left in the loading state and
|
||||
// pageshow is called before we call pageshow with an incomplete document
|
||||
const LOADING_STATE_TIMEOUT = 120;
|
||||
|
|
@ -1342,6 +1345,9 @@ Zotero.HTTP = new function() {
|
|||
currentURL = 0;
|
||||
for(var i=0; i<urls.length; i++) {
|
||||
let hiddenBrowser = Zotero.Browser.createHiddenBrowser();
|
||||
for (let pref in docShellPrefs) {
|
||||
hiddenBrowser.docShell[pref] = docShellPrefs[pref];
|
||||
}
|
||||
if (cookieSandbox) {
|
||||
cookieSandbox.attachToBrowser(hiddenBrowser);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ const NOTE_CITATION_PLACEHOLDER_LINK = 'https://www.zotero.org/?';
|
|||
|
||||
const TEMPLATE_VERSION = 1;
|
||||
|
||||
const MENDELEY_URI_RE = /^http:\/\/www\.mendeley\.com\/documents\/\?uuid=(.*)/;
|
||||
|
||||
|
||||
Zotero.Integration = new function() {
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
|
@ -104,7 +106,6 @@ Zotero.Integration = new function() {
|
|||
this.deletePipe = function(pipe) {
|
||||
try {
|
||||
if(pipe.exists()) {
|
||||
Zotero.IPC.safePipeWrite(pipe, "Zotero shutdown\n");
|
||||
pipe.remove(false);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -505,6 +506,7 @@ Zotero.Integration = new function() {
|
|||
session._removeCodeFields = {};
|
||||
session._deleteFields = {};
|
||||
session._bibliographyFields = [];
|
||||
session._shouldMerge = false;
|
||||
|
||||
if (dataString == EXPORTED_DOCUMENT_MARKER) {
|
||||
Zotero.Integration.currentSession = session;
|
||||
|
|
@ -798,7 +800,8 @@ Zotero.Integration.Interface.prototype.addEditBibliography = Zotero.Promise.coro
|
|||
* @return {Promise}
|
||||
*/
|
||||
Zotero.Integration.Interface.prototype.refresh = async function() {
|
||||
await this._session.init(true, false)
|
||||
await this._session.init(true, false);
|
||||
this._session._shouldMerge = true;
|
||||
|
||||
this._session.reload = this._session.reload || this._session.data.prefs.delayCitationUpdates;
|
||||
await this._session.updateFromDocument(FORCE_CITATIONS_REGENERATE);
|
||||
|
|
@ -1037,6 +1040,10 @@ Zotero.Integration.Session.prototype.updateFromDocument = Zotero.Promise.corouti
|
|||
this.progressBar.start();
|
||||
if (forceCitations) {
|
||||
this.regenAll = true;
|
||||
// See Session.restoreProcessorState() for a comment
|
||||
if (!Zotero.Prefs.get('cite.useCiteprocRs')) {
|
||||
this.reload = true;
|
||||
}
|
||||
}
|
||||
yield this._processFields();
|
||||
try {
|
||||
|
|
@ -1067,15 +1074,24 @@ Zotero.Integration.Session.prototype._processFields = async function () {
|
|||
if (!this._fields) {
|
||||
throw new Error("_processFields called without fetching fields first");
|
||||
}
|
||||
|
||||
|
||||
let adjacentCitations = [];
|
||||
for (var i = 0; i < this._fields.length; i++) {
|
||||
let field = await Zotero.Integration.Field.loadExisting(this._fields[i]);
|
||||
if (field.type === INTEGRATION_TYPE_ITEM) {
|
||||
var noteIndex = await field.getNoteIndex(),
|
||||
data = await field.unserialize(),
|
||||
citation = new Zotero.Integration.Citation(field, data, noteIndex);
|
||||
|
||||
await this.addCitation(i, noteIndex, citation);
|
||||
|
||||
if (this._shouldMerge && typeof field.isAdjacentToNextField === 'function' && await field.isAdjacentToNextField()) {
|
||||
adjacentCitations.push(citation);
|
||||
this._deleteFields[i] = true;
|
||||
continue;
|
||||
}
|
||||
await this.addCitation(i, noteIndex, citation, adjacentCitations);
|
||||
if (adjacentCitations.length) {
|
||||
adjacentCitations = [];
|
||||
}
|
||||
} else if (field.type === INTEGRATION_TYPE_BIBLIOGRAPHY) {
|
||||
if (this.ignoreEmptyBibliography && (await field.getText()).trim() === "") {
|
||||
this._removeCodeFields[i] = true;
|
||||
|
|
@ -1957,9 +1973,17 @@ Zotero.Integration.Session.prototype.importDocument = async function() {
|
|||
/**
|
||||
* Adds a citation to the arrays representing the document
|
||||
*/
|
||||
Zotero.Integration.Session.prototype.addCitation = async function (index, noteIndex, citation) {
|
||||
Zotero.Integration.Session.prototype.addCitation = async function (index, noteIndex, citation, adjacentCitations=[]) {
|
||||
index = parseInt(index, 10);
|
||||
|
||||
if (adjacentCitations.length) {
|
||||
Zotero.debug(`Merging adjacent citations ${adjacentCitations.map(c => c.citationID)} to citation ${citation.citationID}`);
|
||||
for (let adjacentCitation of adjacentCitations) {
|
||||
citation.mergeCitation(adjacentCitation);
|
||||
}
|
||||
this.updateIndices[index] = true;
|
||||
}
|
||||
|
||||
var action = await citation.loadItemData();
|
||||
|
||||
if (action == Zotero.Integration.REMOVE_CODE) {
|
||||
|
|
@ -2157,6 +2181,12 @@ Zotero.Integration.Session.prototype.restoreProcessorState = function() {
|
|||
citations.push(this.citationsByIndex[i]);
|
||||
}
|
||||
}
|
||||
if (!Zotero.Prefs.get('cite.useCiteprocRs')) {
|
||||
// Due to a bug in citeproc-js there are disambiguation issues after changing items in Zotero library
|
||||
// and rebuilding the processor state, so we reinitialize the processor altogether
|
||||
let style = Zotero.Styles.get(this.data.style.styleID);
|
||||
this.style = style.getCiteProc(this.data.style.locale, this.outputFormat, this.data.prefs.automaticJournalAbbreviations);
|
||||
}
|
||||
this.style.rebuildProcessorState(citations, this.outputFormat, uncited);
|
||||
}
|
||||
|
||||
|
|
@ -2564,7 +2594,7 @@ Zotero.Integration.URIMap.prototype.getURIsForItemID = function(id) {
|
|||
/**
|
||||
* Gets Zotero item for a given set of URIs
|
||||
*/
|
||||
Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = Zotero.Promise.coroutine(function* (uris) {
|
||||
Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = async function (uris) {
|
||||
var zoteroItem = false;
|
||||
var needUpdate = false;
|
||||
var embeddedItem = false;;
|
||||
|
|
@ -2579,43 +2609,94 @@ Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = Zotero.Promise.corout
|
|||
|
||||
// Next try getting URI directly
|
||||
try {
|
||||
zoteroItem = yield Zotero.URI.getURIItem(uri);
|
||||
if(zoteroItem) {
|
||||
// Ignore items in the trash
|
||||
if(zoteroItem.deleted) {
|
||||
zoteroItem = false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
var replacer = await Zotero.URI.getURIItem(uri);
|
||||
if (replacer && !replacer.deleted) {
|
||||
zoteroItem = replacer;
|
||||
break;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// Try merged item mapping
|
||||
var replacer = yield Zotero.Relations.getByPredicateAndObject(
|
||||
var replacer = await Zotero.Relations.getByPredicateAndObject(
|
||||
'item', Zotero.Relations.replacedItemPredicate, uri
|
||||
);
|
||||
if (replacer.length && !replacer[0].deleted) {
|
||||
zoteroItem = replacer[0];
|
||||
break;
|
||||
}
|
||||
|
||||
if(zoteroItem) break;
|
||||
// Check if it's a mendeley URI and if we have imported the item
|
||||
let m = MENDELEY_URI_RE.exec(uri);
|
||||
if (m) {
|
||||
replacer = await Zotero.Relations.getByPredicateAndObject(
|
||||
'item', 'mendeleyDB:documentUUID', m[1]
|
||||
);
|
||||
if (replacer.length) {
|
||||
if (!replacer[0].deleted) {
|
||||
zoteroItem = replacer[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If not blocked by user having pressed skip in this session,
|
||||
// or user having checked the checkbox to not be prompted about this,
|
||||
// or user having imported their library with the new version of importer
|
||||
else if (!(this.session.dontPromptForMendeley
|
||||
|| Zotero.Prefs.get('integration.dontPromptMendeleyImport')
|
||||
|| await Zotero.DB.valueQueryAsync("SELECT value FROM settings WHERE setting='mendeleyImport' AND key='version'")
|
||||
)) {
|
||||
// Prompt user to (re)import their mendeley database which might make us recognize
|
||||
// these items
|
||||
let checkbox = {};
|
||||
let result = Zotero.Prompt.confirm({
|
||||
title: Zotero.getString('integration.mendeleyImport.title'),
|
||||
text: Zotero.getString('integration.mendeleyImport.description', [Zotero.appName]),
|
||||
button0: Zotero.getString('integration.mendeleyImport.openImporter'),
|
||||
button1: Zotero.getString('general.skip'),
|
||||
button2: Zotero.getString('general.moreInformation'),
|
||||
checkLabel: Zotero.getString('general.dontAskAgain'),
|
||||
checkbox
|
||||
});
|
||||
if (result === 0) {
|
||||
setTimeout(
|
||||
() => Zotero.getMainWindow().Zotero_File_Interface.showImportWizard(
|
||||
{
|
||||
pageID: 'mendeley-online-explanation',
|
||||
relinkOnly: true
|
||||
}
|
||||
)
|
||||
);
|
||||
throw new Zotero.Exception.UserCancelled("Importing mendeley citations");
|
||||
}
|
||||
else if (result == 1) {
|
||||
this.session.dontPromptForMendeley = true;
|
||||
}
|
||||
else {
|
||||
Zotero.launchURL("https://www.zotero.org/support/kb/mendeley_import#using_mendeley_citations");
|
||||
}
|
||||
if (checkbox.value) {
|
||||
Zotero.Prefs.set('integration.dontPromptMendeleyImport', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if(zoteroItem) {
|
||||
if (zoteroItem) {
|
||||
// make sure URI is up to date (in case user just began syncing)
|
||||
var newURI = Zotero.URI.getItemURI(zoteroItem);
|
||||
if(newURI != uris[i]) {
|
||||
uris[i] = newURI;
|
||||
if (!uris.includes(newURI)) {
|
||||
uris.push(newURI);
|
||||
needUpdate = true;
|
||||
}
|
||||
// cache uris
|
||||
this.itemIDURIs[zoteroItem.id] = uris;
|
||||
} else if(embeddedItem) {
|
||||
this.add(zoteroItem.id, uris)
|
||||
}
|
||||
else if (embeddedItem) {
|
||||
return [embeddedItem, false];
|
||||
}
|
||||
|
||||
return [zoteroItem, needUpdate];
|
||||
});
|
||||
};
|
||||
|
||||
Zotero.Integration.Field = class {
|
||||
constructor(field, rawCode) {
|
||||
|
|
@ -2623,7 +2704,7 @@ Zotero.Integration.Field = class {
|
|||
throw new Error("Trying to instantiate Integration.Field with Integration.Field, not doc field");
|
||||
}
|
||||
for (let func of Zotero.Integration.Field.INTERFACE) {
|
||||
if (!(func in this)) {
|
||||
if (!(func in this) && (func in field)) {
|
||||
this[func] = field[func].bind(field);
|
||||
}
|
||||
}
|
||||
|
|
@ -2682,7 +2763,7 @@ Zotero.Integration.Field = class {
|
|||
}
|
||||
};
|
||||
Zotero.Integration.Field.INTERFACE = ['delete', 'removeCode', 'select', 'setText',
|
||||
'getText', 'setCode', 'getCode', 'equals', 'getNoteIndex'];
|
||||
'getText', 'setCode', 'getCode', 'equals', 'getNoteIndex', 'isAdjacentToNextField'];
|
||||
|
||||
/**
|
||||
* Load existing field in document and return correct instance of field type
|
||||
|
|
@ -2794,8 +2875,13 @@ Zotero.Integration.CitationField = class extends Zotero.Integration.Field {
|
|||
}
|
||||
|
||||
// for update from Zotero 2.1 or earlier
|
||||
if(citationItem.uri) {
|
||||
citationItem.uris = citationItem.uri;
|
||||
if (citationItem.uri) {
|
||||
if (Array.isArray(citationItem.uris)) {
|
||||
citationItem.uris.push(citationItem.uri);
|
||||
}
|
||||
else {
|
||||
citationItem.uris = [citationItem.uri];
|
||||
}
|
||||
delete citationItem.uri;
|
||||
}
|
||||
}
|
||||
|
|
@ -2862,7 +2948,7 @@ Zotero.Integration.CitationField = class extends Zotero.Integration.Field {
|
|||
Zotero.Integration.currentDoc.activate();
|
||||
var result = await Zotero.Integration.currentSession.displayAlert(msg, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO_CANCEL);
|
||||
if (result == 0) { // Cancel
|
||||
return new Zotero.Exception.UserCancelled("corrupt citation resolution");
|
||||
throw new Zotero.Exception.UserCancelled("corrupt citation resolution");
|
||||
} else if (result == 1) { // No
|
||||
return false;
|
||||
} else { // Yes
|
||||
|
|
@ -2921,6 +3007,23 @@ Zotero.Integration.Citation = class {
|
|||
this._field = citationField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge citation items and remove duplicates, unless the items have different
|
||||
* @param citation {Citation}
|
||||
*/
|
||||
mergeCitation(citation) {
|
||||
let items = this.citationItems.concat(citation.citationItems);
|
||||
let addedItems = new Set();
|
||||
this.citationItems = []
|
||||
for (let item of items) {
|
||||
if (addedItems.has(item.id)) {
|
||||
continue;
|
||||
}
|
||||
addedItems.add(item.id);
|
||||
this.citationItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load citation item data
|
||||
* @param {Boolean} [promptToReselect=true] - will throw a MissingItemException if false
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ Zotero.Intl = new function () {
|
|||
|
||||
Components.utils.import("resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
bundle = Services.strings.createBundle('chrome://zotero/locale/zotero.properties');
|
||||
// Exposed for tests
|
||||
this._bundle = bundle = Services.strings.createBundle('chrome://zotero/locale/zotero.properties');
|
||||
intlProps = Services.strings.createBundle('chrome://zotero/locale/mozilla/intl.properties');
|
||||
|
||||
[pluralFormGet, pluralFormNumForms] = PluralForm.makeGetter(parseInt(getIntlProp('pluralRule', 1)));
|
||||
|
|
@ -105,6 +106,9 @@ Zotero.Intl = new function () {
|
|||
}
|
||||
l10n = bundle.formatStringFromName(name, params, params.length);
|
||||
}
|
||||
else if (this.strings[name]) {
|
||||
return this.strings[name];
|
||||
}
|
||||
else {
|
||||
l10n = bundle.GetStringFromName(name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,79 +27,6 @@ Zotero.IPC = new function() {
|
|||
var _libc, _libcPath, _instancePipe, _user32, open, write, close;
|
||||
|
||||
/**
|
||||
* Initialize pipe for communication with connector
|
||||
*/
|
||||
this.init = function() {
|
||||
if(!Zotero.isWin) { // no pipe support on Fx 3.6
|
||||
_instancePipe = _getPipeDirectory();
|
||||
if(!_instancePipe.exists()) {
|
||||
_instancePipe.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
|
||||
}
|
||||
_instancePipe.append(Zotero.instanceID);
|
||||
|
||||
Zotero.IPC.Pipe.initPipeListener(_instancePipe, this.parsePipeInput);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses input received via instance pipe
|
||||
*/
|
||||
this.parsePipeInput = function(msgs) {
|
||||
for (let msg of msgs.split("\n")) {
|
||||
if(!msg) continue;
|
||||
Zotero.debug('IPC: Received "'+msg+'"');
|
||||
|
||||
/*
|
||||
* The below messages coordinate switching Zotero for Firefox from extension mode to
|
||||
* connector mode without restarting after Zotero Standalone has been launched. The
|
||||
* dance typically proceeds as follows:
|
||||
*
|
||||
* 1. SA sends a releaseLock message to Z4Fx that tells it to release its lock.
|
||||
* 2. Z4Fx releases its lock and sends a lockReleased message to SA.
|
||||
* 3. Z4Fx restarts in connector mode. Once it's ready for an IPC command, it sends
|
||||
* a checkInitComplete message to SA.
|
||||
* 4. Once SA finishes initializing, or immediately after a checkInitComplete message
|
||||
* has been received if it is already initialized, SA sends an initComplete message
|
||||
* to Z4Fx.
|
||||
*/
|
||||
if(msg.substr(0, 11) === "releaseLock") {
|
||||
// Standalone sends this to the Firefox extension to tell the Firefox extension to
|
||||
// release its lock on the Zotero database
|
||||
if(!Zotero.isConnector && (msg.length === 11 ||
|
||||
msg.substr(12) === Zotero.DataDirectory.getDatabase())) {
|
||||
switchConnectorMode(true);
|
||||
}
|
||||
} else if(msg === "lockReleased") {
|
||||
// The Firefox extension sends this to Standalone to let Standalone know that it has
|
||||
// released its lock
|
||||
Zotero.onDBLockReleased();
|
||||
} else if(msg === "checkInitComplete") {
|
||||
// The Firefox extension sends this to Standalone to tell Standalone to send an
|
||||
// initComplete message when it is fully initialized
|
||||
if(Zotero.initialized) {
|
||||
Zotero.IPC.broadcast("initComplete");
|
||||
} else {
|
||||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
var _loadObserver = function() {
|
||||
Zotero.IPC.broadcast("initComplete");
|
||||
observerService.removeObserver(_loadObserver, "zotero-loaded");
|
||||
};
|
||||
observerService.addObserver(_loadObserver, "zotero-loaded", false);
|
||||
}
|
||||
} else if(msg === "initComplete") {
|
||||
// Standalone sends this to the Firefox extension to let the Firefox extension
|
||||
// know that Standalone has fully initialized and it should pull the list of
|
||||
// translators
|
||||
Zotero.initComplete();
|
||||
}
|
||||
else if (msg == "reinit") {
|
||||
if (Zotero.isConnector) {
|
||||
reinit(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes safely to a file, avoiding blocking.
|
||||
|
|
@ -135,165 +62,6 @@ Zotero.IPC = new function() {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message to all other Zotero instances
|
||||
*/
|
||||
this.broadcast = function(msg) {
|
||||
if(Zotero.isWin) { // communicate via WM_COPYDATA method
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
// communicate via message window
|
||||
var user32 = ctypes.open("user32.dll");
|
||||
|
||||
/* http://msdn.microsoft.com/en-us/library/ms633499%28v=vs.85%29.aspx
|
||||
* HWND WINAPI FindWindow(
|
||||
* __in_opt LPCTSTR lpClassName,
|
||||
* __in_opt LPCTSTR lpWindowName
|
||||
* );
|
||||
*/
|
||||
var FindWindow = user32.declare("FindWindowW", ctypes.winapi_abi, ctypes.int32_t,
|
||||
ctypes.jschar.ptr, ctypes.jschar.ptr);
|
||||
|
||||
/* http://msdn.microsoft.com/en-us/library/ms633539%28v=vs.85%29.aspx
|
||||
* BOOL WINAPI SetForegroundWindow(
|
||||
* __in HWND hWnd
|
||||
* );
|
||||
*/
|
||||
var SetForegroundWindow = user32.declare("SetForegroundWindow", ctypes.winapi_abi,
|
||||
ctypes.bool, ctypes.int32_t);
|
||||
|
||||
/*
|
||||
* LRESULT WINAPI SendMessage(
|
||||
* __in HWND hWnd,
|
||||
* __in UINT Msg,
|
||||
* __in WPARAM wParam,
|
||||
* __in LPARAM lParam
|
||||
* );
|
||||
*/
|
||||
var SendMessage = user32.declare("SendMessageW", ctypes.winapi_abi, ctypes.uintptr_t,
|
||||
ctypes.int32_t, ctypes.unsigned_int, ctypes.voidptr_t, ctypes.voidptr_t);
|
||||
|
||||
/* http://msdn.microsoft.com/en-us/library/ms649010%28v=vs.85%29.aspx
|
||||
* typedef struct tagCOPYDATASTRUCT {
|
||||
* ULONG_PTR dwData;
|
||||
* DWORD cbData;
|
||||
* PVOID lpData;
|
||||
* } COPYDATASTRUCT, *PCOPYDATASTRUCT;
|
||||
*/
|
||||
var COPYDATASTRUCT = ctypes.StructType("COPYDATASTRUCT", [
|
||||
{"dwData":ctypes.voidptr_t},
|
||||
{"cbData":ctypes.uint32_t},
|
||||
{"lpData":ctypes.voidptr_t}
|
||||
]);
|
||||
|
||||
// Aurora/Nightly are always named "Firefox" in
|
||||
// application.ini
|
||||
const appNames = ["Firefox", "Zotero"];
|
||||
|
||||
// Different from Zotero.appName; this corresponds to the
|
||||
// name in application.ini
|
||||
const myAppName = Services.appinfo.name;
|
||||
|
||||
for (let appName of appNames) {
|
||||
// don't send messages to ourself
|
||||
if(appName === myAppName) continue;
|
||||
|
||||
var thWnd = FindWindow(appName+"MessageWindow", null);
|
||||
if(thWnd) {
|
||||
Zotero.debug('IPC: Broadcasting "'+msg+'" to window "'+appName+'MessageWindow"');
|
||||
|
||||
// allocate message
|
||||
var data = ctypes.char.array()('firefox.exe -silent -ZoteroIPC "'+msg.replace('"', '""', "g")+'"\x00C:\\');
|
||||
var dataSize = data.length*data.constructor.size;
|
||||
|
||||
// create new COPYDATASTRUCT
|
||||
var cds = new COPYDATASTRUCT();
|
||||
cds.dwData = null;
|
||||
cds.cbData = dataSize;
|
||||
cds.lpData = data.address();
|
||||
|
||||
// send COPYDATASTRUCT
|
||||
var success = SendMessage(thWnd, 0x004A /** WM_COPYDATA **/, null, cds.address());
|
||||
|
||||
user32.close();
|
||||
return !!success;
|
||||
}
|
||||
}
|
||||
|
||||
user32.close();
|
||||
return false;
|
||||
} else { // communicate via pipes
|
||||
// look for other Zotero instances
|
||||
var pipes = [];
|
||||
var pipeDir = _getPipeDirectory();
|
||||
if(pipeDir.exists()) {
|
||||
var dirEntries = pipeDir.directoryEntries;
|
||||
while (dirEntries.hasMoreElements()) {
|
||||
var pipe = dirEntries.getNext().QueryInterface(Ci.nsIFile);
|
||||
if(pipe.leafName[0] !== "." && (!_instancePipe || !pipe.equals(_instancePipe))) {
|
||||
pipes.push(pipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!pipes.length) return false;
|
||||
var success = false;
|
||||
for (let pipe of pipes) {
|
||||
Zotero.debug('IPC: Trying to broadcast "'+msg+'" to instance '+pipe.leafName);
|
||||
|
||||
var defunct = false;
|
||||
|
||||
if(pipe.isFile()) {
|
||||
// not actually a pipe
|
||||
if(pipe.isDirectory()) {
|
||||
// not a file, so definitely defunct
|
||||
defunct = true;
|
||||
} else {
|
||||
// check to see whether the size exceeds a certain threshold that we find
|
||||
// reasonable for the queue, and if not, delete the pipe, because it's
|
||||
// probably just a file that wasn't deleted on shutdown and is now
|
||||
// accumulating vast amounts of data
|
||||
defunct = pipe.fileSize > 1024;
|
||||
}
|
||||
}
|
||||
|
||||
if(!defunct) {
|
||||
// Try to write to the pipe for 100 ms
|
||||
var time = Date.now(), timeout = time+100, wroteToPipe;
|
||||
do {
|
||||
wroteToPipe = Zotero.IPC.safePipeWrite(pipe, msg+"\n");
|
||||
} while(Date.now() < timeout && !wroteToPipe);
|
||||
if (wroteToPipe) Zotero.debug('IPC: Pipe took '+(Date.now()-time)+' ms to become available');
|
||||
success = success || wroteToPipe;
|
||||
defunct = !wroteToPipe;
|
||||
}
|
||||
|
||||
if(defunct) {
|
||||
Zotero.debug('IPC: Removing defunct pipe '+pipe.leafName);
|
||||
try {
|
||||
pipe.remove(true);
|
||||
} catch(e) {};
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get directory containing Zotero pipes
|
||||
*/
|
||||
function _getPipeDirectory() {
|
||||
var dir = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
|
||||
dir.append("pipes");
|
||||
return dir;
|
||||
}
|
||||
|
||||
this.pipeExists = Zotero.Promise.coroutine(function* () {
|
||||
var dir = _getPipeDirectory().path;
|
||||
return (yield OS.File.exists(dir)) && !(yield Zotero.File.directoryIsEmpty(dir));
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the path to libc as a string
|
||||
*/
|
||||
|
|
@ -359,19 +127,8 @@ Zotero.IPC.Pipe = new function() {
|
|||
this.initPipeListener = function(file, callback) {
|
||||
Zotero.debug("IPC: Initializing pipe at "+file.path);
|
||||
|
||||
// determine type of pipe
|
||||
if(!_pipeClass) {
|
||||
var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
|
||||
.getService(Components.interfaces.nsIVersionComparator);
|
||||
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
|
||||
getService(Components.interfaces.nsIXULAppInfo);
|
||||
if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) { // Gecko 5
|
||||
_pipeClass = Zotero.IPC.Pipe.DeferredOpen;
|
||||
}
|
||||
}
|
||||
|
||||
// make new pipe
|
||||
new _pipeClass(file, callback);
|
||||
new Zotero.IPC.Pipe.DeferredOpen(file, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -395,7 +152,7 @@ Zotero.IPC.Pipe = new function() {
|
|||
* Adds a shutdown listener for a pipe that writes "Zotero shutdown\n" to the pipe and then
|
||||
* deletes it
|
||||
*/
|
||||
this.writeShutdownMessage = function(pipe, file) {
|
||||
this.remove = function(pipe, file) {
|
||||
// Make sure pipe actually exists
|
||||
if(!file.exists()) {
|
||||
Zotero.debug("IPC: Not closing pipe "+file.path+": already deleted");
|
||||
|
|
@ -425,7 +182,7 @@ Zotero.IPC.Pipe.DeferredOpen = function(file, callback) {
|
|||
this._initPump();
|
||||
|
||||
// add shutdown listener
|
||||
Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, this, file));
|
||||
Zotero.addShutdownListener(Zotero.IPC.Pipe.remove.bind(null, this, file));
|
||||
}
|
||||
|
||||
Zotero.IPC.Pipe.DeferredOpen.prototype = {
|
||||
|
|
|
|||
|
|
@ -55,20 +55,22 @@ Zotero.MIMETypeHandler = new function () {
|
|||
_observers = [];
|
||||
|
||||
// Install styles from the Cite preferences
|
||||
this.addHandler("application/vnd.citationstyles.style+xml", async function (blob, origin) {
|
||||
let win = Services.wm.getMostRecentWindow("zotero:basicViewer");
|
||||
var data = await Zotero.Utilities.Internal.blobToText(blob);
|
||||
try {
|
||||
await Zotero.Styles.install(data, origin, true);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
(new Zotero.Exception.Alert("styles.install.unexpectedError",
|
||||
origin, "styles.install.title", e)).present();
|
||||
}
|
||||
// Close styles page in basic viewer after installing a style
|
||||
if (win) {
|
||||
win.close();
|
||||
this.addHandlers("application/vnd.citationstyles.style+xml", {
|
||||
onContent: async function (blob, origin) {
|
||||
let win = Services.wm.getMostRecentWindow("zotero:basicViewer");
|
||||
var data = await Zotero.Utilities.Internal.blobToText(blob);
|
||||
try {
|
||||
await Zotero.Styles.install(data, origin, true);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
(new Zotero.Exception.Alert("styles.install.unexpectedError",
|
||||
origin, "styles.install.title", e)).present();
|
||||
}
|
||||
// Close styles page in basic viewer after installing a style
|
||||
if (win) {
|
||||
win.close();
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
};
|
||||
|
|
@ -76,17 +78,25 @@ Zotero.MIMETypeHandler = new function () {
|
|||
/**
|
||||
* Adds a handler to handle a specific MIME type
|
||||
* @param {String} type MIME type to handle
|
||||
* @param {Function} fn Function to call to handle type - fn(string, uri)
|
||||
* @param {Object} handlers
|
||||
* - handlers.onContent - function to call when content is received
|
||||
* - handlers.onStartRequest - function to call when response for content type is first received
|
||||
* @param {Boolean} ignoreContentDisposition If true, ignores the Content-Disposition header,
|
||||
* which is often used to force a file to download rather than let it be handled by the web
|
||||
* browser
|
||||
*/
|
||||
this.addHandler = function (type, fn, ignoreContentDisposition) {
|
||||
this.addHandlers = function (type, handlers, ignoreContentDisposition) {
|
||||
if (typeof handlers == 'function') {
|
||||
handlers = {
|
||||
onContent: handlers
|
||||
};
|
||||
Zotero.debug('MIMETypeHandler.addHandler: second parameter function is deprecated. Pass an object', 1)
|
||||
}
|
||||
if (_typeHandlers[type]) {
|
||||
_typeHandlers[type].push(fn);
|
||||
_typeHandlers[type].push(handlers);
|
||||
}
|
||||
else {
|
||||
_typeHandlers[type] = [fn];
|
||||
_typeHandlers[type] = [handlers];
|
||||
}
|
||||
if (ignoreContentDisposition) {
|
||||
_ignoreContentDispositionTypes.add(type);
|
||||
|
|
@ -96,19 +106,19 @@ Zotero.MIMETypeHandler = new function () {
|
|||
/**
|
||||
* Removes a handler for a specific MIME type
|
||||
* @param {String} type MIME type to handle
|
||||
* @param {Function} handler Function handler to remove
|
||||
* @param {Object} handlers Function handlers to remove
|
||||
*/
|
||||
this.removeHandler = function (type, handler) {
|
||||
this.removeHandlers = function (type, handlers) {
|
||||
// If no handler specified or this is the last handler for the type
|
||||
// stop monitoring the content type completely.
|
||||
if (!handler || _typeHandlers[type] && _typeHandlers[type].length <= 1) {
|
||||
if (!handlers || _typeHandlers[type] && _typeHandlers[type].length <= 1) {
|
||||
delete _typeHandlers[type];
|
||||
_ignoreContentDispositionTypes.delete(type);
|
||||
}
|
||||
else if (_typeHandlers[type]) {
|
||||
var i = _typeHandlers[type].indexOf(handler);
|
||||
var i = _typeHandlers[type].indexOf(handlers);
|
||||
if (i != -1) {
|
||||
_typeHandlers.splice(i, 1);
|
||||
_typeHandlers[type].splice(i, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -214,10 +224,27 @@ Zotero.MIMETypeHandler = new function () {
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when the request is started; we ignore this
|
||||
* Called when the request is started
|
||||
*/
|
||||
_StreamListener.prototype.onStartRequest = function(channel, context) {}
|
||||
|
||||
_StreamListener.prototype.onStartRequest = async function(channel, context) {
|
||||
try {
|
||||
if (!_typeHandlers[this._contentType]) return;
|
||||
for (let handlers of _typeHandlers[this._contentType]) {
|
||||
if (!handlers.onStartRequest) continue;
|
||||
let maybePromise = handlers.onStartRequest(
|
||||
this._request.name ? this._request.name : null,
|
||||
this._contentType,
|
||||
channel
|
||||
);
|
||||
if (maybePromise && maybePromise.then) {
|
||||
maybePromise = await maybePromise;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there's data available; we collect this data and keep it until the request is
|
||||
|
|
@ -259,14 +286,16 @@ Zotero.MIMETypeHandler = new function () {
|
|||
|
||||
var handled = false;
|
||||
try {
|
||||
for (let handler of _typeHandlers[this._contentType]) {
|
||||
let maybePromise = handler(
|
||||
if (!_typeHandlers[this._contentType]) return;
|
||||
for (let handlers of _typeHandlers[this._contentType]) {
|
||||
if (!handlers.onContent) continue;
|
||||
let maybePromise = handlers.onContent(
|
||||
blob,
|
||||
this._request.name ? this._request.name : null,
|
||||
this._contentType,
|
||||
channel
|
||||
);
|
||||
if (maybePromise.then) {
|
||||
if (maybePromise && maybePromise.then) {
|
||||
maybePromise = await maybePromise;
|
||||
}
|
||||
handled = handled || maybePromise;
|
||||
|
|
|
|||
|
|
@ -271,6 +271,10 @@ class PDFWorker {
|
|||
}));
|
||||
|
||||
let path = await attachment.getFilePathAsync();
|
||||
let fileSize = (await OS.File.stat(path)).size;
|
||||
if (fileSize > Math.pow(2, 31) - 1) {
|
||||
throw new Error(`The file "${path}" is too large`);
|
||||
}
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
|
||||
|
|
@ -341,6 +345,10 @@ class PDFWorker {
|
|||
|
||||
async processCitaviAnnotations(pdfPath, citaviAnnotations, isPriority, password) {
|
||||
return this._enqueue(async () => {
|
||||
let fileSize = (await OS.File.stat(pdfPath)).size;
|
||||
if (fileSize > Math.pow(2, 31) - 1) {
|
||||
throw new Error(`The file "${pdfPath}" is too large`);
|
||||
}
|
||||
let buf = await OS.File.read(pdfPath, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
try {
|
||||
|
|
@ -371,6 +379,10 @@ class PDFWorker {
|
|||
*/
|
||||
async processMendeleyAnnotations(pdfPath, mendeleyAnnotations, isPriority, password) {
|
||||
return this._enqueue(async () => {
|
||||
let fileSize = (await OS.File.stat(pdfPath)).size;
|
||||
if (fileSize > Math.pow(2, 31) - 1) {
|
||||
throw new Error(`The file "${pdfPath}" is too large`);
|
||||
}
|
||||
let buf = await OS.File.read(pdfPath, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
try {
|
||||
|
|
@ -410,6 +422,162 @@ class PDFWorker {
|
|||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete pages from PDF attachment
|
||||
*
|
||||
* @param {Integer} itemID Attachment item id
|
||||
* @param {Array} pageIndexes
|
||||
* @param {Boolean} [isPriority]
|
||||
* @param {String} [password]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async deletePages(itemID, pageIndexes, isPriority, password) {
|
||||
return this._enqueue(async () => {
|
||||
let attachment = await Zotero.Items.getAsync(itemID);
|
||||
|
||||
Zotero.debug(`Deleting [${pageIndexes.join(', ')}] pages for item ${attachment.libraryKey}`);
|
||||
let t = new Date();
|
||||
|
||||
if (!attachment.isPDFAttachment()) {
|
||||
throw new Error('Item must be a PDF attachment');
|
||||
}
|
||||
|
||||
let annotations = attachment
|
||||
.getAnnotations()
|
||||
.map(annotation => ({
|
||||
id: annotation.id,
|
||||
position: JSON.parse(annotation.annotationPosition)
|
||||
}));
|
||||
|
||||
let path = await attachment.getFilePathAsync();
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
|
||||
try {
|
||||
var { buf: modifiedBuf } = await this._query('deletePages', {
|
||||
buf, pageIndexes, password
|
||||
}, [buf]);
|
||||
}
|
||||
catch (e) {
|
||||
let error = new Error(`Worker 'deletePages' failed: ${JSON.stringify({ error: e.message })}`);
|
||||
try {
|
||||
error.name = JSON.parse(e.message).name;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
Zotero.logError(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Delete annotations from deleted pages
|
||||
let ids = [];
|
||||
for (let i = annotations.length - 1; i >= 0; i--) {
|
||||
let { id, position } = annotations[i];
|
||||
if (pageIndexes.includes(position.pageIndex)) {
|
||||
ids.push(id);
|
||||
annotations.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (ids.length) {
|
||||
await Zotero.Items.erase(ids);
|
||||
}
|
||||
|
||||
// Shift page index for other annotations
|
||||
ids = [];
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
let rows = await Zotero.DB.queryAsync('SELECT itemID, position FROM itemAnnotations WHERE parentItemID=?', itemID);
|
||||
for (let { itemID, position } of rows) {
|
||||
try {
|
||||
position = JSON.parse(position);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
continue;
|
||||
}
|
||||
// Find the count of deleted pages before the current annotation page
|
||||
let shift = pageIndexes.reduce((prev, cur) => cur < position.pageIndex ? prev + 1 : prev, 0);
|
||||
if (shift > 0) {
|
||||
position.pageIndex -= shift;
|
||||
position = JSON.stringify(position);
|
||||
await Zotero.DB.queryAsync('UPDATE itemAnnotations SET position=? WHERE itemID=?', [position, itemID]);
|
||||
ids.push(itemID);
|
||||
}
|
||||
}
|
||||
});
|
||||
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('item');
|
||||
let loadedObjects = objectsClass.getLoaded();
|
||||
for (let object of loadedObjects) {
|
||||
if (ids.includes(object.id)) {
|
||||
await object.reload(null, true);
|
||||
}
|
||||
}
|
||||
await Zotero.Notifier.trigger('modify', 'item', ids, {});
|
||||
|
||||
await OS.File.writeAtomic(path, new Uint8Array(modifiedBuf));
|
||||
let mtime = Math.floor(await attachment.attachmentModificationTime / 1000);
|
||||
attachment.attachmentLastProcessedModificationTime = mtime;
|
||||
await attachment.saveTx({
|
||||
skipAll: true
|
||||
});
|
||||
|
||||
Zotero.debug(`Deleted pages for item ${attachment.libraryKey} in ${new Date() - t} ms`);
|
||||
}, isPriority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate pages in PDF attachment
|
||||
*
|
||||
* @param {Integer} itemID Attachment item id
|
||||
* @param {Array} pageIndexes
|
||||
* @param {Integer} degrees 90, 180, 270
|
||||
* @param {Boolean} [isPriority]
|
||||
* @param {String} [password]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async rotatePages(itemID, pageIndexes, degrees, isPriority, password) {
|
||||
return this._enqueue(async () => {
|
||||
let attachment = await Zotero.Items.getAsync(itemID);
|
||||
|
||||
Zotero.debug(`Rotating [${pageIndexes.join(', ')}] pages for item ${attachment.libraryKey}`);
|
||||
let t = new Date();
|
||||
|
||||
if (!attachment.isPDFAttachment()) {
|
||||
throw new Error('Item must be a PDF attachment');
|
||||
}
|
||||
|
||||
let path = await attachment.getFilePathAsync();
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
|
||||
try {
|
||||
var { buf: modifiedBuf } = await this._query('rotatePages', {
|
||||
buf, pageIndexes, degrees, password
|
||||
}, [buf]);
|
||||
}
|
||||
catch (e) {
|
||||
let error = new Error(`Worker 'rotatePages' failed: ${JSON.stringify({ error: e.message })}`);
|
||||
try {
|
||||
error.name = JSON.parse(e.message).name;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
Zotero.logError(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
await OS.File.writeAtomic(path, new Uint8Array(modifiedBuf));
|
||||
let mtime = Math.floor(await attachment.attachmentModificationTime / 1000);
|
||||
attachment.attachmentLastProcessedModificationTime = mtime;
|
||||
await attachment.saveTx({
|
||||
skipAll: true
|
||||
});
|
||||
|
||||
Zotero.debug(`Rotated pages for item ${attachment.libraryKey} in ${new Date() - t} ms`);
|
||||
}, isPriority);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.PDFWorker = new PDFWorker();
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Zotero.Prefs = new function(){
|
|||
if (!fromVersion) {
|
||||
fromVersion = 0;
|
||||
}
|
||||
var toVersion = 5;
|
||||
var toVersion = 6;
|
||||
if (fromVersion < toVersion) {
|
||||
for (var i = fromVersion + 1; i <= toVersion; i++) {
|
||||
switch (i) {
|
||||
|
|
@ -85,6 +85,24 @@ Zotero.Prefs = new function(){
|
|||
case 5:
|
||||
this.clear('extensions.spellcheck.inline.max-misspellings', true);
|
||||
break;
|
||||
|
||||
// If the note Quick Copy setting was set to Markdown + Rich Text but in a
|
||||
// non-default way (e.g., because of whitespace differences), clear the pref
|
||||
// to pick up the new app-link options
|
||||
case 6:
|
||||
var o = this.get('export.noteQuickCopy.setting');
|
||||
try {
|
||||
o = JSON.parse(o);
|
||||
if (o.mode == 'export'
|
||||
&& o.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
this.clear('export.noteQuickCopy.setting');
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
this.clear('export.noteQuickCopy.setting');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.set('prefVersion', toVersion);
|
||||
|
|
@ -230,6 +248,7 @@ Zotero.Prefs = new function(){
|
|||
Zotero.setFontSize(
|
||||
Zotero.getActiveZoteroPane().document.getElementById('zotero-pane')
|
||||
);
|
||||
Zotero.setFontSize(Zotero.getActiveZoteroPane().document.getElementById('zotero-context-pane'));
|
||||
Zotero.getActiveZoteroPane().collectionsView && Zotero.getActiveZoteroPane().collectionsView.updateFontSize();
|
||||
Zotero.getActiveZoteroPane().itemsView && Zotero.getActiveZoteroPane().itemsView.updateFontSize();
|
||||
}],
|
||||
|
|
@ -358,12 +377,13 @@ Zotero.Prefs = new function(){
|
|||
delete _observersBySymbol[symbol];
|
||||
|
||||
var [name, handler] = obs;
|
||||
var i = obs.indexOf(handler);
|
||||
var handlers = _observers[name];
|
||||
var i = handlers.indexOf(handler);
|
||||
if (i == -1) {
|
||||
Zotero.debug("Handler was not registered for preference " + name, 2);
|
||||
return;
|
||||
}
|
||||
obs.splice(i, 1);
|
||||
handlers.splice(i, 1);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
87
chrome/content/zotero/xpcom/prompt.js
Normal file
87
chrome/content/zotero/xpcom/prompt.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2022 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
Zotero.Prompt = {
|
||||
BUTTON_TITLE_OK: Services.prompt.BUTTON_TITLE_OK,
|
||||
BUTTON_TITLE_CANCEL: Services.prompt.BUTTON_TITLE_CANCEL,
|
||||
BUTTON_TITLE_YES: Services.prompt.BUTTON_TITLE_YES,
|
||||
BUTTON_TITLE_NO: Services.prompt.BUTTON_TITLE_NO,
|
||||
BUTTON_TITLE_SAVE: Services.prompt.BUTTON_TITLE_SAVE,
|
||||
BUTTON_TITLE_DONT_SAVE: Services.prompt.BUTTON_TITLE_DONT_SAVE,
|
||||
BUTTON_TITLE_REVERT: Services.prompt.BUTTON_TITLE_REVERT,
|
||||
|
||||
/**
|
||||
* A wrapper around XPCOM's Services.prompt.confirmEx()
|
||||
* but with a friendlier interface.
|
||||
*
|
||||
* Button text can use special static variables from
|
||||
* Zotero.Prompt
|
||||
*
|
||||
* @param options
|
||||
* - {mozIDOMWindowProxy} window - The parent window or null.
|
||||
* - {String} title - Text to appear in the title of the dialog.
|
||||
* - {String} text - Text to appear in the body of the dialog.
|
||||
* - {String|Number} button0 - Button 0 text
|
||||
* - {String|Number} button1 - Button 1 text
|
||||
* - {String|Number} button2 - Button 2 text
|
||||
* - {String} checkLabel - Text to appear with the checkbox.
|
||||
* - {Object} checkbox - Contains the initial checked state of the
|
||||
* checkbox when this method is called and the final checked
|
||||
* state after this method returns. Either {} or { value: true/false }.
|
||||
* - {Number} defaultButton - The index of default button. 0 by default
|
||||
* - {Boolean} delayButtons - Make the buttons initially disabled and enable them after some period
|
||||
* so that the user doesn't click through the dialog without reading it.
|
||||
* @returns {Number} The index of the button pressed.
|
||||
*/
|
||||
confirm(options = {}) {
|
||||
let { window: win, title, text, button0, button1, button2, checkLabel, checkbox, defaultButton, delayButtons } = options;
|
||||
if (!win) win = null;
|
||||
if (!title) throw new Error('`title` is required');
|
||||
if (!text) throw new Error('`text` is required');
|
||||
if (!button0 && !button1 && !button2) {
|
||||
throw new Error('At least one button is required');
|
||||
}
|
||||
if (checkLabel && (!checkbox || typeof checkbox != 'object')) {
|
||||
throw new Error('`checkLabel` provided without `checkbox` option');
|
||||
}
|
||||
let flags = delayButtons ? Services.prompt.BUTTON_DELAY_ENABLE : 0;
|
||||
if (typeof button0 == 'number') flags += Services.prompt.BUTTON_POS_0 * button0;
|
||||
else if (typeof button0 == 'string') flags += Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING;
|
||||
if (typeof button1 == 'number') flags += Services.prompt.BUTTON_POS_1 * button1;
|
||||
else if (typeof button1 == 'string') flags += Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING;
|
||||
if (typeof button2 == 'number') flags += Services.prompt.BUTTON_POS_2 * button2;
|
||||
else if (typeof button2 == 'string') flags += Services.prompt.BUTTON_POS_2 * Services.prompt.BUTTON_TITLE_IS_STRING;
|
||||
if (defaultButton) flags += defaultButton == 1 ? Services.prompt.BUTTON_POS_1_DEFAULT : Services.prompt.BUTTON_POS_2_DEFAULT;
|
||||
return Services.prompt.confirmEx(
|
||||
win, title, text, flags,
|
||||
typeof button0 == 'number' ? null : button0,
|
||||
typeof button1 == 'number' ? null : button1,
|
||||
typeof button2 == 'number' ? null : button2,
|
||||
checkLabel, checkbox
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -23,6 +23,8 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Components.utils.importGlobalProperties(["URL"]);
|
||||
|
||||
/**
|
||||
* A singleton to handle URL rewriting proxies
|
||||
* @namespace
|
||||
|
|
@ -138,6 +140,8 @@ Zotero.Proxies = new function() {
|
|||
* @type String
|
||||
*/
|
||||
this.proxyToProper = function(url, onlyReturnIfProxied) {
|
||||
// make sure url has a trailing slash
|
||||
url = new URL(url).href;
|
||||
for (let proxy of Zotero.Proxies.proxies) {
|
||||
if(proxy.regexp) {
|
||||
var m = proxy.regexp.exec(url);
|
||||
|
|
@ -173,16 +177,18 @@ Zotero.Proxies = new function() {
|
|||
* Check the url for potential proxies and deproxify, providing a scheme to build
|
||||
* a proxy object.
|
||||
*
|
||||
* @param URL
|
||||
* @param url
|
||||
* @returns {Object} Unproxied url to proxy object
|
||||
*/
|
||||
this.getPotentialProxies = function(URL) {
|
||||
this.getPotentialProxies = function(url) {
|
||||
// make sure url has a trailing slash
|
||||
url = new URL(url).href;
|
||||
var urlToProxy = {};
|
||||
// If it's a known proxied URL just return it
|
||||
if (Zotero.Proxies.transparent) {
|
||||
for (var proxy of Zotero.Proxies.proxies) {
|
||||
if (proxy.regexp) {
|
||||
var m = proxy.regexp.exec(URL);
|
||||
var m = proxy.regexp.exec(url);
|
||||
if (m) {
|
||||
let proper = proxy.toProper(m);
|
||||
urlToProxy[proper] = proxy.toJSON();
|
||||
|
|
@ -191,12 +197,12 @@ Zotero.Proxies = new function() {
|
|||
}
|
||||
}
|
||||
}
|
||||
urlToProxy[URL] = null;
|
||||
urlToProxy[url] = null;
|
||||
|
||||
// if there is a subdomain that is also a TLD, also test against URI with the domain
|
||||
// dropped after the TLD
|
||||
// (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
|
||||
var m = /^(https?:\/\/)([^\/]+)/i.exec(URL);
|
||||
var m = /^(https?:\/\/)([^\/]+)/i.exec(url);
|
||||
if (m) {
|
||||
// First, drop the 0- if it exists (this is an III invention)
|
||||
var host = m[2];
|
||||
|
|
@ -211,16 +217,15 @@ Zotero.Proxies = new function() {
|
|||
for (let i=0; i < hostnameParts.length; i++) {
|
||||
let parts = hostnameParts[i];
|
||||
// If hostnameParts has two entries, then the second one is with replaced hyphens
|
||||
let dotsToHyphens = i == 1;
|
||||
// skip the lowest level subdomain, domain and TLD
|
||||
for (let j=1; j<parts.length-2; j++) {
|
||||
// if a part matches a TLD, everything up to it is probably the true URL
|
||||
if (TLDS[parts[j].toLowerCase()]) {
|
||||
var properHost = parts.slice(0, j+1).join(".");
|
||||
// protocol + properHost + /path
|
||||
var properURL = m[1]+properHost+URL.substr(m[0].length);
|
||||
var properURL = m[1]+properHost+url.substr(m[0].length);
|
||||
var proxyHost = parts.slice(j+1).join('.');
|
||||
urlToProxy[properURL] = {scheme: m[1] + '%h.' + proxyHost + '/%p', dotsToHyphens};
|
||||
urlToProxy[properURL] = {scheme: m[1] + '%h.' + proxyHost + '/%p'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -249,10 +254,6 @@ Zotero.Proxy.prototype._loadFromRow = function (row) {
|
|||
this.multiHost = row.scheme && row.scheme.indexOf('%h') != -1 || !!row.multiHost;
|
||||
this.autoAssociate = !!row.autoAssociate;
|
||||
this.scheme = row.scheme;
|
||||
// Database query results will throw as this option is only present when the proxy comes along with the translator
|
||||
if ('dotsToHyphens' in row) {
|
||||
this.dotsToHyphens = !!row.dotsToHyphens;
|
||||
}
|
||||
|
||||
if (this.scheme) {
|
||||
this.compileRegexp();
|
||||
|
|
@ -263,7 +264,7 @@ Zotero.Proxy.prototype.toJSON = function() {
|
|||
if (!this.scheme) {
|
||||
throw Error('Cannot convert proxy to JSON - no scheme');
|
||||
}
|
||||
return {id: this.id, scheme: this.scheme, dotsToHyphens: this.dotsToHyphens};
|
||||
return {id: this.id, scheme: this.scheme};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -456,6 +457,8 @@ Zotero.Proxy.prototype.erase = Zotero.Promise.coroutine(function* () {
|
|||
*/
|
||||
Zotero.Proxy.prototype.toProper = function(m) {
|
||||
if (!Array.isArray(m)) {
|
||||
// make sure url has a trailing slash
|
||||
m = new URL(m).href;
|
||||
let match = this.regexp.exec(m);
|
||||
if (!match) {
|
||||
return m
|
||||
|
|
@ -463,16 +466,16 @@ Zotero.Proxy.prototype.toProper = function(m) {
|
|||
m = match;
|
||||
}
|
||||
}
|
||||
let scheme = this.scheme.indexOf('https') == -1 ? 'http://' : 'https://';
|
||||
let protocol = m[0].indexOf('https') == -1 ? 'http://' : 'https://';
|
||||
if(this.multiHost) {
|
||||
var properURL = scheme+m[this.parameters.indexOf("%h")+1]+"/";
|
||||
var properURL = protocol+m[this.parameters.indexOf("%h")+1]+"/";
|
||||
} else {
|
||||
var properURL = scheme+this.hosts[0]+"/";
|
||||
var properURL = protocol+this.hosts[0]+"/";
|
||||
}
|
||||
|
||||
// Replace `-` with `.` in https to support EZProxy HttpsHyphens.
|
||||
// Potentially troublesome with domains that contain dashes
|
||||
if (this.dotsToHyphens) {
|
||||
if (protocol.includes('https')) {
|
||||
properURL = properURL.replace(/-/g, '.');
|
||||
}
|
||||
|
||||
|
|
@ -507,7 +510,7 @@ Zotero.Proxy.prototype.toProxy = function(uri) {
|
|||
var param = this.parameters[i];
|
||||
var value = "";
|
||||
if(param == "%h") {
|
||||
value = this.dotsToHyphens ? uri.hostPort.replace(/-/g, '.') : uri.hostPort;
|
||||
value = uri.scheme === 'https' ? uri.hostPort.replace(/-/g, '.') : uri.hostPort;
|
||||
} else if(param == "%p") {
|
||||
value = uri.pathQueryRef.substr(1);
|
||||
} else if(param == "%d") {
|
||||
|
|
|
|||
|
|
@ -269,6 +269,9 @@ Zotero.QuickCopy = new function() {
|
|||
// Allow to reuse items array
|
||||
translation.setItems(items.slice());
|
||||
translation.setTranslator(format.id);
|
||||
if (format.options) {
|
||||
translation.setDisplayOptions(format.options);
|
||||
}
|
||||
translation.setHandler("done", callback);
|
||||
translation.translate();
|
||||
return true;
|
||||
|
|
@ -322,14 +325,19 @@ Zotero.QuickCopy = new function() {
|
|||
var _loadNoteOutputFormat = async function () {
|
||||
var format = Zotero.Prefs.get("export.noteQuickCopy.setting");
|
||||
format = Zotero.QuickCopy.unserializeSetting(format);
|
||||
// If virtual "Markdown + Rich Text" translator is selected, preload Note Markdown and
|
||||
// Note HTML translators
|
||||
if (format.id == Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
await _preloadFormat({ mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN });
|
||||
await _preloadFormat({ mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML });
|
||||
return;
|
||||
|
||||
// Always preload Note Markdown and Note HTML translators. They're both needed for note
|
||||
// dragging if the format is "Markdown + Rich Text", HTML is needed for note dragging if
|
||||
// the format is "HTML", and they're both needed for copying or dragging from the note
|
||||
// editor, which uses `noWait`.
|
||||
await _preloadFormat({ mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN });
|
||||
await _preloadFormat({ mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML });
|
||||
|
||||
// If there's another format, preload it for note item dragging
|
||||
if (format.id != Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT
|
||||
&& format.id != Zotero.Translators.TRANSLATOR_ID_NOTE_HTML) {
|
||||
await _preloadFormat(format);
|
||||
}
|
||||
return _preloadFormat(format);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
import FilePicker from 'zotero/modules/filePicker';
|
||||
|
||||
class ReaderInstance {
|
||||
constructor() {
|
||||
this.pdfStateFileName = '.zotero-pdf-state';
|
||||
|
|
@ -50,7 +52,48 @@ class ReaderInstance {
|
|||
}
|
||||
}
|
||||
|
||||
async open({ itemID, state, location }) {
|
||||
getSecondViewState() {
|
||||
let state = this._iframeWindow.wrappedJSObject.getSecondViewState();
|
||||
return state ? JSON.parse(JSON.stringify(state)) : undefined;
|
||||
}
|
||||
|
||||
async migrateMendeleyColors(libraryID, annotations) {
|
||||
let colorMap = new Map();
|
||||
colorMap.set('#fff5ad', '#ffd400');
|
||||
colorMap.set('#ffb5b6', '#ff6666');
|
||||
colorMap.set('#bae2ff', '#2ea8e5');
|
||||
colorMap.set('#d3c2ff', '#a28ae5');
|
||||
colorMap.set('#dcffb0', '#5fb236');
|
||||
let updatedAnnotations = [];
|
||||
for (let annotation of annotations) {
|
||||
let color = colorMap.get(annotation.color);
|
||||
if (color) {
|
||||
annotation.color = color;
|
||||
updatedAnnotations.push(annotation);
|
||||
}
|
||||
}
|
||||
if (!updatedAnnotations.length) {
|
||||
return false;
|
||||
}
|
||||
Zotero.debug('Migrating Mendeley colors');
|
||||
let notifierQueue = new Zotero.Notifier.Queue();
|
||||
try {
|
||||
for (let annotation of updatedAnnotations) {
|
||||
let { id: key, color } = annotation;
|
||||
let item = Zotero.Items.getByLibraryAndKey(libraryID, key);
|
||||
if (item && item.isEditable()) {
|
||||
item.annotationColor = color;
|
||||
await item.saveTx({ skipDateModifiedUpdate: true, notifierQueue });
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
await Zotero.Notifier.commit(notifierQueue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async open({ itemID, state, location, secondViewState }) {
|
||||
let { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
|
||||
let library = Zotero.Libraries.get(libraryID);
|
||||
await library.waitForDataLoad('item');
|
||||
|
|
@ -62,12 +105,29 @@ class ReaderInstance {
|
|||
this.state = state;
|
||||
this._itemID = item.id;
|
||||
// Set `ReaderTab` title as fast as possible
|
||||
this.updateTitle();
|
||||
await this.updateTitle();
|
||||
let path = await item.getFilePathAsync();
|
||||
// Check file size, otherwise we get uncatchable error:
|
||||
// JavaScript error: resource://gre/modules/osfile/osfile_native.jsm, line 60: RangeError: invalid array length
|
||||
// See more https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
|
||||
let fileSize = (await OS.File.stat(path)).size;
|
||||
// Max ArrayBuffer size before fx89 is 2GB-1 bytes
|
||||
if (fileSize > Math.pow(2, 31) - 1) {
|
||||
throw new Error(`The file "${path}" is too large`);
|
||||
}
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
let annotationItems = item.getAnnotations();
|
||||
let annotations = (await Promise.all(annotationItems.map(x => this._getAnnotation(x)))).filter(x => x);
|
||||
|
||||
// TODO: Remove after some time
|
||||
// Migrate Mendeley colors to Zotero PDF reader colors
|
||||
let migrated = await this.migrateMendeleyColors(libraryID, annotations);
|
||||
if (migrated) {
|
||||
annotationItems = item.getAnnotations();
|
||||
annotations = (await Promise.all(annotationItems.map(x => this._getAnnotation(x)))).filter(x => x);
|
||||
}
|
||||
|
||||
this.annotationItemIDs = annotationItems.map(x => x.id);
|
||||
state = state || await this._getState();
|
||||
this._postMessage({
|
||||
|
|
@ -75,6 +135,7 @@ class ReaderInstance {
|
|||
buf,
|
||||
annotations,
|
||||
state,
|
||||
secondViewState,
|
||||
location,
|
||||
readOnly: this._isReadOnly(),
|
||||
authorName: item.library.libraryType === 'group' ? Zotero.Users.getCurrentName() : '',
|
||||
|
|
@ -83,46 +144,62 @@ class ReaderInstance {
|
|||
sidebarOpen: this._sidebarOpen,
|
||||
bottomPlaceholderHeight: this._bottomPlaceholderHeight,
|
||||
rtl: Zotero.rtl,
|
||||
fontSize: Zotero.Prefs.get('fontSize'),
|
||||
localizedStrings: {
|
||||
...Zotero.Intl.getPrefixedStrings('general.'),
|
||||
...Zotero.Intl.getPrefixedStrings('pdfReader.')
|
||||
}
|
||||
}, [buf]);
|
||||
// Set title once again, because `ReaderWindow` isn't loaded the first time
|
||||
this.updateTitle();
|
||||
await this.updateTitle();
|
||||
|
||||
this._prefObserverIDs = [
|
||||
Zotero.Prefs.registerObserver('fontSize', this._handleFontSizePrefChange),
|
||||
Zotero.Prefs.registerObserver('tabs.title.reader', this._handleTabTitlePrefChange)
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uninit() {
|
||||
if (this._prefObserverIDs) {
|
||||
this._prefObserverIDs.forEach(id => Zotero.Prefs.unregisterObserver(id));
|
||||
}
|
||||
}
|
||||
|
||||
get itemID() {
|
||||
return this._itemID;
|
||||
}
|
||||
|
||||
updateTitle() {
|
||||
async updateTitle() {
|
||||
let type = Zotero.Prefs.get('tabs.title.reader');
|
||||
let item = Zotero.Items.get(this._itemID);
|
||||
let title = item.getDisplayTitle();
|
||||
let readerTitle = item.getDisplayTitle();
|
||||
let parentItem = item.parentItem;
|
||||
if (parentItem) {
|
||||
let parts = [];
|
||||
let displayTitle = parentItem.getDisplayTitle();
|
||||
if (displayTitle) {
|
||||
parts.push(displayTitle);
|
||||
// If type is "filename", then readerTitle already has it
|
||||
if (parentItem && type !== 'filename') {
|
||||
let attachment = await parentItem.getBestAttachment();
|
||||
if (attachment && attachment.id === this._itemID) {
|
||||
let parts = [];
|
||||
let creator = parentItem.getField('firstCreator');
|
||||
let year = parentItem.getField('year');
|
||||
let title = parentItem.getDisplayTitle();
|
||||
// If creator is missing fall back to titleCreatorYear
|
||||
if (type === 'creatorYearTitle' && creator) {
|
||||
parts = [creator, year, title];
|
||||
}
|
||||
else if (type === 'title') {
|
||||
parts = [title];
|
||||
}
|
||||
// If type is titleCreatorYear, or is missing, or another type falls back
|
||||
else {
|
||||
parts = [title, creator, year];
|
||||
}
|
||||
readerTitle = parts.filter(x => x).join(' - ');
|
||||
}
|
||||
|
||||
let firstCreator = parentItem.getField('firstCreator');
|
||||
if (firstCreator) {
|
||||
parts.push(firstCreator);
|
||||
}
|
||||
|
||||
let year = parentItem.getField('year');
|
||||
if (year) {
|
||||
parts.push(year);
|
||||
}
|
||||
|
||||
title = parts.join(' - ');
|
||||
}
|
||||
|
||||
this._title = title;
|
||||
this._setTitleValue(title);
|
||||
this._title = readerTitle;
|
||||
this._setTitleValue(readerTitle);
|
||||
}
|
||||
|
||||
async setAnnotations(items) {
|
||||
|
|
@ -201,6 +278,14 @@ class ReaderInstance {
|
|||
isZoomPageHeightActive() {
|
||||
return this._iframeWindow.eval('PDFViewerApplication.pdfViewer.currentScaleValue === "page-fit"');
|
||||
}
|
||||
|
||||
isSplitVerticallyActive() {
|
||||
return this._iframeWindow.wrappedJSObject.getSplitType() === 'vertical';
|
||||
}
|
||||
|
||||
isSplitHorizontallyActive() {
|
||||
return this._iframeWindow.wrappedJSObject.getSplitType() === 'horizontal';
|
||||
}
|
||||
|
||||
allowNavigateFirstPage() {
|
||||
return this._iframeWindow.eval('PDFViewerApplication.pdfViewer.currentPageNumber > 1');
|
||||
|
|
@ -249,6 +334,33 @@ class ReaderInstance {
|
|||
);
|
||||
return !index;
|
||||
}
|
||||
|
||||
promptToDeletePages(num) {
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
|
||||
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
|
||||
let index = ps.confirmEx(
|
||||
null,
|
||||
Zotero.getString('pdfReader.promptDeletePages.title'),
|
||||
Zotero.getString(
|
||||
'pdfReader.promptDeletePages.text',
|
||||
new Intl.NumberFormat().format(num),
|
||||
num
|
||||
),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.continue'),
|
||||
null, null, null, {}
|
||||
);
|
||||
return !index;
|
||||
}
|
||||
|
||||
async reload(data) {
|
||||
let item = Zotero.Items.get(this._itemID);
|
||||
let path = await item.getFilePathAsync();
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
this._postMessage({ action: 'reload', buf, data }, [buf]);
|
||||
}
|
||||
|
||||
async menuCmd(cmd) {
|
||||
if (cmd === 'transferFromPDF') {
|
||||
|
|
@ -281,6 +393,24 @@ class ReaderInstance {
|
|||
}
|
||||
return;
|
||||
}
|
||||
else if (cmd === 'rotateLeft') {
|
||||
await this._rotateCurrentPage(270);
|
||||
return;
|
||||
}
|
||||
else if (cmd === 'rotateRight') {
|
||||
await this._rotateCurrentPage(90);
|
||||
return;
|
||||
}
|
||||
else if (cmd === 'rotate180') {
|
||||
await this._rotateCurrentPage(180);
|
||||
return;
|
||||
}
|
||||
else if (cmd === 'splitVertically') {
|
||||
this._splitVertically();
|
||||
}
|
||||
else if (cmd === 'splitHorizontally') {
|
||||
this._splitHorizontally();
|
||||
}
|
||||
|
||||
let data = {
|
||||
action: 'menuCmd',
|
||||
|
|
@ -317,8 +447,8 @@ class ReaderInstance {
|
|||
// If exporting with virtual "Markdown + Rich Text" translator, call Note Markdown
|
||||
// and Note HTML translators instead
|
||||
if (format.id === Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT) {
|
||||
let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN };
|
||||
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML };
|
||||
let markdownFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_MARKDOWN, options: format.markdownOptions };
|
||||
let htmlFormat = { mode: 'export', id: Zotero.Translators.TRANSLATOR_ID_NOTE_HTML, options: format.htmlOptions };
|
||||
Zotero.QuickCopy.getContentFromItems(items, markdownFormat, (obj, worked) => {
|
||||
if (!worked) {
|
||||
return;
|
||||
|
|
@ -369,6 +499,54 @@ class ReaderInstance {
|
|||
);
|
||||
return !index;
|
||||
};
|
||||
|
||||
this._iframeWindow.wrappedJSObject.zoteroCopyImage = async (dataURL) => {
|
||||
let parts = dataURL.split(',');
|
||||
if (!parts[0].includes('base64')) {
|
||||
return;
|
||||
}
|
||||
let bstr = atob(parts[1]);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
let imgTools = Components.classes["@mozilla.org/image/tools;1"]
|
||||
.getService(Components.interfaces.imgITools);
|
||||
let transferable = Components.classes['@mozilla.org/widget/transferable;1']
|
||||
.createInstance(Components.interfaces.nsITransferable);
|
||||
let clipboardService = Components.classes['@mozilla.org/widget/clipboard;1']
|
||||
.getService(Components.interfaces.nsIClipboard);
|
||||
let imgPtr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
|
||||
.createInstance(Components.interfaces.nsISupportsInterfacePointer);
|
||||
let mimeType = `image/png`;
|
||||
imgPtr.data = imgTools.decodeImageFromArrayBuffer(u8arr.buffer, mimeType);
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor(mimeType);
|
||||
transferable.setTransferData(mimeType, imgPtr, 0);
|
||||
clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
|
||||
};
|
||||
|
||||
this._iframeWindow.wrappedJSObject.zoteroSaveImageAs = async (dataURL) => {
|
||||
let fp = new FilePicker();
|
||||
fp.init(this._iframeWindow, Zotero.getString('pdfReader.saveImageAs'), fp.modeSave);
|
||||
fp.appendFilter("PNG", "*.png");
|
||||
fp.defaultString = Zotero.getString('fileTypes.image').toLowerCase() + '.png';
|
||||
let rv = await fp.show();
|
||||
if (rv === fp.returnOK || rv === fp.returnReplace) {
|
||||
let outputPath = fp.file;
|
||||
let parts = dataURL.split(',');
|
||||
if (parts[0].includes('base64')) {
|
||||
let bstr = atob(parts[1]);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
await OS.File.writeAtomic(outputPath, u8arr);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async _setState(state) {
|
||||
|
|
@ -424,6 +602,14 @@ class ReaderInstance {
|
|||
|| item.parentItem && item.parentItem.deleted;
|
||||
}
|
||||
|
||||
_handleFontSizePrefChange = () => {
|
||||
this._postMessage({ action: 'setFontSize', fontSize: Zotero.Prefs.get('fontSize') });
|
||||
};
|
||||
|
||||
_handleTabTitlePrefChange = async () => {
|
||||
await this.updateTitle();
|
||||
};
|
||||
|
||||
_dataURLtoBlob(dataurl) {
|
||||
let parts = dataurl.split(',');
|
||||
let mime = parts[0].match(/:(.*?);/)[1];
|
||||
|
|
@ -444,6 +630,33 @@ class ReaderInstance {
|
|||
return `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect shape-rendering="geometricPrecision" fill="${fill}" stroke-width="2" x="2" y="2" stroke="${stroke}" width="12" height="12" rx="3"/></svg>`;
|
||||
}
|
||||
|
||||
async _rotateCurrentPage(degrees) {
|
||||
let pageIndex = this._iframeWindow.wrappedJSObject.PDFViewerApplication.pdfViewer.currentPageNumber - 1;
|
||||
this._postMessage({ action: 'reloading' });
|
||||
await Zotero.PDFWorker.rotatePages(this._itemID, [pageIndex], degrees, true);
|
||||
await this.reload({ rotatedPageIndexes: [pageIndex] });
|
||||
}
|
||||
|
||||
_splitVertically() {
|
||||
if (this.isSplitVerticallyActive()) {
|
||||
this._iframeWindow.wrappedJSObject.unsplitView();
|
||||
}
|
||||
else {
|
||||
this._iframeWindow.wrappedJSObject.splitView();
|
||||
}
|
||||
setTimeout(() => this._updateSecondViewState(), 500);
|
||||
}
|
||||
|
||||
_splitHorizontally() {
|
||||
if (this.isSplitHorizontallyActive()) {
|
||||
this._iframeWindow.wrappedJSObject.unsplitView();
|
||||
}
|
||||
else {
|
||||
this._iframeWindow.wrappedJSObject.splitView(true);
|
||||
}
|
||||
setTimeout(() => this._updateSecondViewState(), 500);
|
||||
}
|
||||
|
||||
_openTagsPopup(item, selector) {
|
||||
let menupopup = this._window.document.createElement('menupopup');
|
||||
menupopup.className = 'tags-popup';
|
||||
|
|
@ -462,7 +675,7 @@ class ReaderInstance {
|
|||
}
|
||||
}
|
||||
|
||||
_openPagePopup(data) {
|
||||
_openPagePopup(data, secondView) {
|
||||
let popup = this._window.document.createElement('menupopup');
|
||||
this._popupset.appendChild(popup);
|
||||
popup.addEventListener('popuphidden', function () {
|
||||
|
|
@ -483,14 +696,14 @@ class ReaderInstance {
|
|||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.zoomIn'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomIn' });
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomIn' }, [], secondView);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Zoom out
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.zoomOut'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomOut' });
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomOut' }, [], secondView);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Zoom 'Auto'
|
||||
|
|
@ -499,7 +712,7 @@ class ReaderInstance {
|
|||
menuitem.setAttribute('type', 'checkbox');
|
||||
menuitem.setAttribute('checked', data.isZoomAuto);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomAuto' });
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomAuto' }, [], secondView);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Zoom 'Page Width'
|
||||
|
|
@ -508,7 +721,7 @@ class ReaderInstance {
|
|||
menuitem.setAttribute('type', 'checkbox');
|
||||
menuitem.setAttribute('checked', data.isZoomPageWidth);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomPageWidth' });
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomPageWidth' }, [], secondView);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Zoom 'Page Height'
|
||||
|
|
@ -517,17 +730,33 @@ class ReaderInstance {
|
|||
menuitem.setAttribute('type', 'checkbox');
|
||||
menuitem.setAttribute('checked', data.isZoomPageHeight);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomPageHeight' });
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'zoomPageHeight' }, [], secondView);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Separator
|
||||
popup.appendChild(this._window.document.createElement('menuseparator'));
|
||||
// Split Horizontally
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.splitHorizontally'));
|
||||
menuitem.setAttribute('type', 'checkbox');
|
||||
menuitem.setAttribute('checked', this.isSplitHorizontallyActive());
|
||||
menuitem.addEventListener('command', () => this._splitHorizontally());
|
||||
popup.appendChild(menuitem);
|
||||
// Split Vertically
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.splitVertically'));
|
||||
menuitem.setAttribute('type', 'checkbox');
|
||||
menuitem.setAttribute('checked', this.isSplitVerticallyActive());
|
||||
menuitem.addEventListener('command', () => this._splitVertically());
|
||||
popup.appendChild(menuitem);
|
||||
// Separator
|
||||
popup.appendChild(this._window.document.createElement('menuseparator'));
|
||||
// Next page
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.nextPage'));
|
||||
menuitem.setAttribute('disabled', !data.enableNextPage);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'nextPage' });
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'nextPage' }, [], secondView);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Previous page
|
||||
|
|
@ -535,7 +764,7 @@ class ReaderInstance {
|
|||
menuitem.setAttribute('label', Zotero.getString('pdfReader.previousPage'));
|
||||
menuitem.setAttribute('disabled', !data.enablePrevPage);
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'prevPage' });
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'prevPage' }, [], secondView);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
popup.openPopupAtScreen(data.x, data.y, true);
|
||||
|
|
@ -614,6 +843,26 @@ class ReaderInstance {
|
|||
}
|
||||
// Separator
|
||||
popup.appendChild(this._window.document.createElement('menuseparator'));
|
||||
|
||||
if (data.enableImageOptions) {
|
||||
// Copy Image
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.copyImage'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'copyImage', data });
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Save Image As…
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.saveImageAs'));
|
||||
menuitem.addEventListener('command', () => {
|
||||
this._postMessage({ action: 'popupCmd', cmd: 'saveImageAs', data });
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Separator
|
||||
popup.appendChild(this._window.document.createElement('menuseparator'));
|
||||
}
|
||||
|
||||
// Delete
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('general.delete'));
|
||||
|
|
@ -661,6 +910,64 @@ class ReaderInstance {
|
|||
popup.openPopup(element, 'after_start', 0, 0, true);
|
||||
}
|
||||
|
||||
_openThumbnailPopup(data) {
|
||||
let popup = this._window.document.createElement('menupopup');
|
||||
this._popupset.appendChild(popup);
|
||||
popup.addEventListener('popuphidden', function () {
|
||||
popup.remove();
|
||||
});
|
||||
let menuitem;
|
||||
// Rotate Left
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.rotateLeft'));
|
||||
menuitem.setAttribute('disabled', this._isReadOnly());
|
||||
menuitem.addEventListener('command', async () => {
|
||||
this._postMessage({ action: 'reloading' });
|
||||
await Zotero.PDFWorker.rotatePages(this._itemID, data.pageIndexes, 270, true);
|
||||
await this.reload({ rotatedPageIndexes: data.pageIndexes });
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Rotate Right
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.rotateRight'));
|
||||
menuitem.setAttribute('disabled', this._isReadOnly());
|
||||
menuitem.addEventListener('command', async () => {
|
||||
this._postMessage({ action: 'reloading' });
|
||||
await Zotero.PDFWorker.rotatePages(this._itemID, data.pageIndexes, 90, true);
|
||||
await this.reload({ rotatedPageIndexes: data.pageIndexes });
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Rotate 180
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('pdfReader.rotate180'));
|
||||
menuitem.setAttribute('disabled', this._isReadOnly());
|
||||
menuitem.addEventListener('command', async () => {
|
||||
this._postMessage({ action: 'reloading' });
|
||||
await Zotero.PDFWorker.rotatePages(this._itemID, data.pageIndexes, 180, true);
|
||||
await this.reload({ rotatedPageIndexes: data.pageIndexes });
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
// Separator
|
||||
popup.appendChild(this._window.document.createElement('menuseparator'));
|
||||
// Delete
|
||||
menuitem = this._window.document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', Zotero.getString('general.delete'));
|
||||
menuitem.setAttribute('disabled', this._isReadOnly());
|
||||
menuitem.addEventListener('command', async () => {
|
||||
if (this.promptToDeletePages(data.pageIndexes.length)) {
|
||||
this._postMessage({ action: 'reloading' });
|
||||
try {
|
||||
await Zotero.PDFWorker.deletePages(this._itemID, data.pageIndexes, true);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
await this.reload();
|
||||
}
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
popup.openPopupAtScreen(data.x, data.y, true);
|
||||
}
|
||||
|
||||
_openSelectorPopup(data) {
|
||||
let popup = this._window.document.createElement('menupopup');
|
||||
this._popupset.appendChild(popup);
|
||||
|
|
@ -683,25 +990,52 @@ class ReaderInstance {
|
|||
popup.openPopupAtScreen(data.x, data.y, true);
|
||||
}
|
||||
|
||||
async _postMessage(message, transfer) {
|
||||
async _postMessage(message, transfer, secondView) {
|
||||
await this._waitForReader();
|
||||
this._iframeWindow.postMessage({ itemID: this._itemID, message }, this._iframeWindow.origin, transfer);
|
||||
this._iframeWindow.postMessage({ itemID: this._itemID, message, secondView }, this._iframeWindow.origin, transfer);
|
||||
}
|
||||
|
||||
_updateSecondViewState() {
|
||||
if (this.tabID) {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.Zotero_Tabs.setSecondViewState(this.tabID, this.getSecondViewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleMessage = async (event) => {
|
||||
let message;
|
||||
let secondViewIframeWindow = this._iframeWindow.document.getElementById('secondViewIframe');
|
||||
if (secondViewIframeWindow) {
|
||||
secondViewIframeWindow = secondViewIframeWindow.contentWindow;
|
||||
}
|
||||
try {
|
||||
if (event.source !== this._iframeWindow) {
|
||||
if (event.source !== this._iframeWindow
|
||||
&& event.source !== secondViewIframeWindow) {
|
||||
return;
|
||||
}
|
||||
// Clone data to avoid the dead object error when the window is closed
|
||||
let data = JSON.parse(JSON.stringify(event.data));
|
||||
let { secondView } = data;
|
||||
// Filter messages coming from previous reader instances,
|
||||
// except for `setAnnotation` to still allow saving it
|
||||
if (data.itemID !== this._itemID && data.message.action !== 'setAnnotation') {
|
||||
return;
|
||||
}
|
||||
message = data.message;
|
||||
|
||||
if (secondView) {
|
||||
switch (message.action) {
|
||||
case 'openPagePopup': break;
|
||||
case 'setState': {
|
||||
this._updateSecondViewState();
|
||||
return;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.action) {
|
||||
case 'initialized': {
|
||||
this._resolveInitPromise();
|
||||
|
|
@ -725,25 +1059,22 @@ class ReaderInstance {
|
|||
saveOptions.notifierData.autoSyncDelay = Zotero.Notes.AUTO_SYNC_DELAY;
|
||||
}
|
||||
|
||||
// Note: annotation.image is always saved separately from the rest
|
||||
// of annotation properties
|
||||
|
||||
let item = Zotero.Items.getByLibraryAndKey(attachment.libraryID, annotation.key);
|
||||
// Save image for read-only annotation.
|
||||
if (item
|
||||
&& !item.isEditable()
|
||||
&& annotation.image
|
||||
&& !await Zotero.Annotations.hasCacheImage(item)
|
||||
) {
|
||||
// If annotation isn't editable, only save image to cache.
|
||||
// This is the only case when saving can be triggered for non-editable annotation
|
||||
if (annotation.image && item && !item.isEditable()) {
|
||||
let blob = this._dataURLtoBlob(annotation.image);
|
||||
await Zotero.Annotations.saveCacheImage(item, blob);
|
||||
continue;
|
||||
}
|
||||
|
||||
let savedAnnotation = await Zotero.Annotations.saveFromJSON(attachment, annotation, saveOptions);
|
||||
if (annotation.image && !await Zotero.Annotations.hasCacheImage(savedAnnotation)) {
|
||||
let blob = this._dataURLtoBlob(annotation.image);
|
||||
await Zotero.Annotations.saveCacheImage(savedAnnotation, blob);
|
||||
// Save annotation, and save image to cache
|
||||
else {
|
||||
// Delete authorName to prevent setting annotationAuthorName unnecessarily
|
||||
delete annotation.authorName;
|
||||
let savedAnnotation = await Zotero.Annotations.saveFromJSON(attachment, annotation, saveOptions);
|
||||
if (annotation.image) {
|
||||
let blob = this._dataURLtoBlob(annotation.image);
|
||||
await Zotero.Annotations.saveCacheImage(savedAnnotation, blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -772,6 +1103,19 @@ class ReaderInstance {
|
|||
}
|
||||
return;
|
||||
}
|
||||
// Save rendered image when annotation isn't modified
|
||||
case 'saveImage': {
|
||||
let { annotation } = message;
|
||||
let { image, id: key } = annotation;
|
||||
let attachment = Zotero.Items.get(this._itemID);
|
||||
let libraryID = attachment.libraryID;
|
||||
let item = Zotero.Items.getByLibraryAndKey(attachment.libraryID, key);
|
||||
if (item) {
|
||||
let blob = this._dataURLtoBlob(image);
|
||||
await Zotero.Annotations.saveCacheImage({ libraryID, key }, blob);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'setState': {
|
||||
let { state } = message;
|
||||
await this._setState(state);
|
||||
|
|
@ -788,7 +1132,7 @@ class ReaderInstance {
|
|||
return;
|
||||
}
|
||||
case 'openPagePopup': {
|
||||
this._openPagePopup(message.data);
|
||||
this._openPagePopup(message.data, secondView);
|
||||
return;
|
||||
}
|
||||
case 'openAnnotationPopup': {
|
||||
|
|
@ -799,6 +1143,14 @@ class ReaderInstance {
|
|||
this._openColorPopup(message.data);
|
||||
return;
|
||||
}
|
||||
case 'openThumbnailPopup': {
|
||||
this._openThumbnailPopup(message.data);
|
||||
return;
|
||||
}
|
||||
case 'openSelectorPopup': {
|
||||
this._openSelectorPopup(message.data);
|
||||
return;
|
||||
}
|
||||
case 'closePopup': {
|
||||
// Note: This currently only closes tags popup when annotations are
|
||||
// disappearing from pdf-reader sidebar
|
||||
|
|
@ -847,18 +1199,17 @@ class ReaderInstance {
|
|||
return;
|
||||
}
|
||||
case 'focusSplitButton': {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.document.getElementById('zotero-tb-toggle-item-pane').focus();
|
||||
if (this instanceof ReaderTab) {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.document.getElementById('zotero-tb-toggle-item-pane').focus();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'focusContextPane': {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
if (!this._window.ZoteroContextPane.focus()) {
|
||||
this.focusFirst();
|
||||
}
|
||||
if (this instanceof ReaderWindow || !this._window.ZoteroContextPane.focus()) {
|
||||
this.focusFirst();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -925,7 +1276,7 @@ class ReaderInstance {
|
|||
}
|
||||
|
||||
class ReaderTab extends ReaderInstance {
|
||||
constructor({ itemID, title, sidebarWidth, sidebarOpen, bottomPlaceholderHeight, index, background }) {
|
||||
constructor({ itemID, title, sidebarWidth, sidebarOpen, bottomPlaceholderHeight, index, tabID, background }) {
|
||||
super();
|
||||
this._itemID = itemID;
|
||||
this._sidebarWidth = sidebarWidth;
|
||||
|
|
@ -934,6 +1285,7 @@ class ReaderTab extends ReaderInstance {
|
|||
this._showItemPaneToggle = true;
|
||||
this._window = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
let { id, container } = this._window.Zotero_Tabs.add({
|
||||
id: tabID,
|
||||
type: 'reader',
|
||||
title: title || '',
|
||||
index,
|
||||
|
|
@ -968,20 +1320,24 @@ class ReaderTab extends ReaderInstance {
|
|||
// events in PDF reader iframe when mouse up happens over another iframe
|
||||
// i.e. note-editor. There should be a better way to solve this
|
||||
this._window.addEventListener('pointerup', (event) => {
|
||||
if (this._window.Zotero_Tabs.selectedID === this.tabID
|
||||
&& this._iframeWindow
|
||||
&& event.target
|
||||
&& event.target.closest
|
||||
&& !event.target.closest('#outerContainer')) {
|
||||
let evt = new this._iframeWindow.CustomEvent('mouseup', { bubbles: false });
|
||||
evt.clientX = event.clientX;
|
||||
evt.clientY = event.clientY;
|
||||
this._iframeWindow.dispatchEvent(evt);
|
||||
try {
|
||||
if (this._window.Zotero_Tabs.selectedID === this.tabID
|
||||
&& this._iframeWindow
|
||||
&& event.target
|
||||
&& event.target.closest
|
||||
&& !event.target.closest('#outerContainer')) {
|
||||
let evt = new this._iframeWindow.CustomEvent('mouseup', { bubbles: false });
|
||||
evt.clientX = event.clientX;
|
||||
evt.clientY = event.clientY;
|
||||
this._iframeWindow.dispatchEvent(evt);
|
||||
|
||||
evt = new this._iframeWindow.CustomEvent('pointerup', { bubbles: false });
|
||||
evt.clientX = event.clientX;
|
||||
evt.clientY = event.clientY;
|
||||
this._iframeWindow.dispatchEvent(evt);
|
||||
evt = new this._iframeWindow.CustomEvent('pointerup', { bubbles: false });
|
||||
evt.clientX = event.clientX;
|
||||
evt.clientY = event.clientY;
|
||||
this._iframeWindow.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1080,6 +1436,8 @@ class ReaderWindow extends ReaderInstance {
|
|||
this._window.document.getElementById('view-menuitem-zoom-auto').setAttribute('checked', this.isZoomAutoActive());
|
||||
this._window.document.getElementById('view-menuitem-zoom-page-width').setAttribute('checked', this.isZoomPageWidthActive());
|
||||
this._window.document.getElementById('view-menuitem-zoom-page-height').setAttribute('checked', this.isZoomPageHeightActive());
|
||||
this._window.document.getElementById('view-menuitem-split-vertically').setAttribute('checked', this.isSplitVerticallyActive());
|
||||
this._window.document.getElementById('view-menuitem-split-horizontally').setAttribute('checked', this.isSplitHorizontallyActive());
|
||||
}
|
||||
|
||||
_onGoMenuOpen() {
|
||||
|
|
@ -1139,7 +1497,7 @@ class Reader {
|
|||
await Zotero.uiReadyPromise;
|
||||
Zotero.Session.state.windows
|
||||
.filter(x => x.type == 'reader' && Zotero.Items.exists(x.itemID))
|
||||
.forEach(x => this.open(x.itemID, null, { title: x.title, openInWindow: true }));
|
||||
.forEach(x => this.open(x.itemID, null, { title: x.title, openInWindow: true, secondViewState: x.secondViewState }));
|
||||
}
|
||||
|
||||
_loadSidebarState() {
|
||||
|
|
@ -1199,6 +1557,7 @@ class Reader {
|
|||
for (let id of ids) {
|
||||
let reader = Zotero.Reader.getByTabID(id);
|
||||
if (reader) {
|
||||
reader.uninit();
|
||||
this._readers.splice(this._readers.indexOf(reader), 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1261,7 +1620,12 @@ class Reader {
|
|||
getWindowStates() {
|
||||
return this._readers
|
||||
.filter(r => r instanceof ReaderWindow)
|
||||
.map(r => ({ type: 'reader', itemID: r._itemID, title: r._title }));
|
||||
.map(r => ({
|
||||
type: 'reader',
|
||||
itemID: r._itemID,
|
||||
title: r._title,
|
||||
secondViewState: r.getSecondViewState()
|
||||
}));
|
||||
}
|
||||
|
||||
async openURI(itemURI, location, options) {
|
||||
|
|
@ -1270,11 +1634,24 @@ class Reader {
|
|||
await this.open(item.id, location, options);
|
||||
}
|
||||
|
||||
async open(itemID, location, { title, tabIndex, openInBackground, openInWindow, allowDuplicate } = {}) {
|
||||
async open(itemID, location, { title, tabIndex, tabID, openInBackground, openInWindow, allowDuplicate, secondViewState } = {}) {
|
||||
this._loadSidebarState();
|
||||
this.triggerAnnotationsImportCheck(itemID);
|
||||
let reader;
|
||||
|
||||
// If duplicating is not allowed, and no reader instance is loaded for itemID,
|
||||
// try to find an unloaded tab and select it. Zotero.Reader.open will then be called again
|
||||
if (!allowDuplicate && !this._readers.find(r => r._itemID === itemID)) {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
let existingTabID = win.Zotero_Tabs.getTabIDByItemID(itemID);
|
||||
if (existingTabID) {
|
||||
win.Zotero_Tabs.select(existingTabID, false, { location });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (openInWindow) {
|
||||
reader = this._readers.find(r => r._itemID === itemID && (r instanceof ReaderWindow));
|
||||
}
|
||||
|
|
@ -1298,7 +1675,7 @@ class Reader {
|
|||
bottomPlaceholderHeight: this._bottomPlaceholderHeight
|
||||
});
|
||||
this._readers.push(reader);
|
||||
if (!(await reader.open({ itemID, location }))) {
|
||||
if (!(await reader.open({ itemID, location, secondViewState }))) {
|
||||
return;
|
||||
}
|
||||
Zotero.Session.debounceSave();
|
||||
|
|
@ -1312,13 +1689,14 @@ class Reader {
|
|||
itemID,
|
||||
title,
|
||||
index: tabIndex,
|
||||
tabID,
|
||||
background: openInBackground,
|
||||
sidebarWidth: this._sidebarWidth,
|
||||
sidebarOpen: this._sidebarOpen,
|
||||
bottomPlaceholderHeight: this._bottomPlaceholderHeight
|
||||
});
|
||||
this._readers.push(reader);
|
||||
if (!(await reader.open({ itemID, location }))) {
|
||||
if (!(await reader.open({ itemID, location, secondViewState }))) {
|
||||
return;
|
||||
}
|
||||
reader.onChangeSidebarWidth = (width) => {
|
||||
|
|
@ -1340,6 +1718,7 @@ class Reader {
|
|||
if (!openInBackground) {
|
||||
reader._window.focus();
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -716,6 +716,8 @@ Zotero.Retractions = {
|
|||
|
||||
_valueToKey: function (type, value) {
|
||||
if (type == this.TYPE_DOI) {
|
||||
// DOIs are case-insensitive
|
||||
value = value.toLowerCase();
|
||||
return Zotero.Utilities.Internal.sha1(value);
|
||||
}
|
||||
return value;
|
||||
|
|
@ -1003,9 +1005,10 @@ Zotero.Retractions = {
|
|||
// https://retractionwatch.com/retraction-watch-database-user-guide/retraction-watch-database-user-guide-appendix-b-reasons/
|
||||
_reasonDescriptions: {
|
||||
"Author Unresponsive": "Author(s) lack of communication after prior contact by Journal, Publisher or other original Authors",
|
||||
"Bias Issues or Lack of Balance": "Failure to maintain objectivity in the presentation or analysis of information",
|
||||
"Breach of Policy by Author": "A violation of the Journal, Publisher or Institutional accepted practices by the author",
|
||||
"Breach of Policy by Third Party": "A violation of the Journal, Publisher or Institutional accepted practices by a person or company/institution not the authors",
|
||||
"Cites Prior Retracted Work": "A retracted item is used in citations or referencing",
|
||||
"Cites Retracted Work": "A retracted item is used in citations or referencing",
|
||||
"Civil Proceedings": "Non-criminal litigation arising from the publication of the original article or the related notice(s)",
|
||||
"Complaints about Author": "Allegations made strictly about the author without respect to the original article",
|
||||
"Complaints about Company/Institution": "Allegations made strictly about the author’s affiliation(s) without respect to the original article",
|
||||
|
|
@ -1028,6 +1031,7 @@ Zotero.Retractions = {
|
|||
"Duplication of Data": "Also known as “self-plagiarism”. Used when the all or part of the data from an item written by one or all authors of the original article, are repeated in the original article without appropriate citation.",
|
||||
"Duplication of Image": "Also known as “self-plagiarism”. Used when an image from an item written by one or all authors of the original article is repeated in the original article without appropriate citation.",
|
||||
"Duplication of Text": "Also known as “self-plagiarism”. Used when sections of text from an item written by one or all authors of the original article, are repeated in the original article without appropriate citation.",
|
||||
"Duplicate Publication through Error by Journal/Publisher": "Used when a Journal or Publisher incorrectly publishes the same article more than once. Differs from Duplication of Article, which is typically due to dual submission by the article’s authors.",
|
||||
"Error by Journal/Publisher": "A mistake attributed to a Journal Editor or Publisher",
|
||||
"Error by Third Party": "A mistake attributed to a person or other, who is not an author or representative of the Journal or Publisher",
|
||||
"Error in Analyses": "A mistake made in the evaluation of the data or calculations",
|
||||
|
|
@ -1057,8 +1061,7 @@ Zotero.Retractions = {
|
|||
"Lack of Approval from Author": "Failure to obtain agreement from original author(s)",
|
||||
"Lack of Approval from Company/Institution": "Failure to obtain agreement from original author(s)",
|
||||
"Lack of Approval from Third Party": "Failure to obtain agreement from original author(s)",
|
||||
"Lack Of Balance/Bias Issues": "Failure to maintain objectivity in the presentation or analysis of information",
|
||||
"Lack of IRB/IACUC Approval": "Failure to obtain consent from the institutional ethical review board overseeing human or animal experimentation",
|
||||
"Lack of IRB/IACUC Approval": "Failure to obtain consent from the institutional ethical review board overseeing human or animal experimentation prior to initiation of study, or failure to provide proof of such",
|
||||
"Legal Reasons/Legal Threats": "Actions taken to avoid or foster litigation",
|
||||
"Manipulation of Images": "The changing of the presentation of an image by reversal, rotation or similar action",
|
||||
"Manipulation of Results": "The changing of the presentation of results which may lead to conclusions not otherwise warranted",
|
||||
|
|
@ -1078,24 +1081,31 @@ Zotero.Retractions = {
|
|||
"Objections by Author(s)": "A complaint by any of the original authors or refusal to agree actions taken by the Journal or Publisher",
|
||||
"Objections by Company/Institution": "A complaint by any of the original authors’ affiliation(s) or refusal by same to agree actions taken by the Journal or Publisher",
|
||||
"Objections by Third Party": "A complaint by any person, company or institution not of the original authors, or refusal by same to agree actions taken by the Journal or Publisher",
|
||||
"Original Data not Provided": "The original data or images for the published study is no longer available or is not given to the editorial staff.",
|
||||
"Paper Mill": "A business that creates and sells research papers where the buyer(s) claim authorship; the business is generally unacknowledged as a contributor.",
|
||||
"Plagiarism of Article": "Used when an entire published item, or undefined sections of it, and not written by one or all authors of the original article, are repeated in the original article without appropriate citation.",
|
||||
"Plagiarism of Data": "Used when the all or part of the data from an item not written by one or all authors of the original article, are repeated in the original article without appropriate citation.",
|
||||
"Plagiarism of Image": "Used when an image from an item not written by one or all authors of the original article is repeated in the original article without appropriate citation.",
|
||||
"Plagiarism of Text": "Used when sections of text from an item not written by one or all authors of the original article, are repeated in the original article without appropriate citation.",
|
||||
"Publishing Ban": "A Journal or Publisher states that no manuscripts will be acceptance from one or all the authors of the original article. It can be for a limited time, or indefinitely.",
|
||||
"Randomly Generated Content": "Text or data that was created via a randomizing algorithm such as Mathgen or Scigen",
|
||||
"Results Not Reproducible": "Experiments conducted, using the same materials and methods, that fail to replicate the finding of the original article",
|
||||
"Retract and Replace": "The permanent change of an item to a non-citable status, with a subsequent republication by the same journal after substantial changes to the item",
|
||||
"Rogue Editor": "Applies to fraudulent editor (i.e. non-existent or with false credential); applies to those lists as “Guest Editor” as well;",
|
||||
"Sabotage of Materials": "An intentional action to surreptitiously change or contaminate experimental ingredients in order to artificially change the experimental outcome",
|
||||
"Sabotage of Methods": "An intentional action to surreptitiously change or contaminate experimental instruments or tools in order to artificially change the experimental outcome",
|
||||
"Salami Slicing": "The publication of several articles by using the same (small) dataset, but by breaking it into sections, with the intent of exploiting a limited data set for the production of several published works This does not apply to large multi-group studies such as the Framingham Heart Study.",
|
||||
"Temporary Removal": "An original article is removed from the Journal’s publishing platform for an undefined period of time, after which, if returned to the publishing platform, it appears with minimal or no substantial changes",
|
||||
"Transfer of Copyright/Ownership": "Articles removed from a publishing platform due to change in the Copyright/Ownership of the article",
|
||||
"Unreliable Data": "The accuracy or validity of the data is questionable",
|
||||
"Unreliable Image": "The accuracy or validity of the image is questionable",
|
||||
"Unreliable Results": "The accuracy or validity of the results is questionable",
|
||||
"Updated to Correction": "A prior notice has been changed to the status of a Correction",
|
||||
"Updated to Retraction": "A prior notice has been changed to the status of a Retraction",
|
||||
"Upgrade/Update of Prior Notice": "Either a change to or affirmation of a prior notice",
|
||||
Withdrawal: "The original article is removed from access on the Journal’s publishing platform."
|
||||
Withdrawal: "The original article is removed from access on the Journal’s publishing platform.",
|
||||
"Withdrawn (out of date)": "The article has been retracted as part of the journal’s normal process of keeping guidelines or reviews current for professional use.",
|
||||
"Withdrawn to Publish in Different Journal": "Journal/Publisher removed an article from one journal platform to publish in a different journal platform.",
|
||||
},
|
||||
|
||||
_fixedResults: [
|
||||
|
|
|
|||
|
|
@ -2337,6 +2337,7 @@ Zotero.Schema = new function(){
|
|||
var styleUpdates = xmlhttp.responseXML.getElementsByTagName('style');
|
||||
|
||||
_showRepositoryMessage(xmlhttp.responseXML);
|
||||
_checkRepositoryPrefs(xmlhttp.responseXML);
|
||||
|
||||
if (!translatorUpdates.length && !styleUpdates.length){
|
||||
await Zotero.DB.executeTransaction(async function (conn) {
|
||||
|
|
@ -2491,6 +2492,59 @@ Zotero.Schema = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for remotely set preferences
|
||||
*
|
||||
* Format:
|
||||
*
|
||||
* <prefs>
|
||||
* <prefset version="1">
|
||||
* <pref name="extensions.zotero.import.mendeleyUseOAuth" value="true" />
|
||||
* </prefset>
|
||||
* </prefs>
|
||||
*
|
||||
* Prefsets must be in increasing order
|
||||
*
|
||||
* @param {XMLDocument} responseXML - Response document from repository
|
||||
*/
|
||||
function _checkRepositoryPrefs(responseXML) {
|
||||
const allowedPrefs = new Set([
|
||||
'extensions.zotero.import.mendeleyUseOAuth'
|
||||
]);
|
||||
try {
|
||||
var prefsElem = responseXML.querySelector('prefs');
|
||||
if (!prefsElem) {
|
||||
return;
|
||||
}
|
||||
var prefsets = prefsElem.querySelectorAll('prefset');
|
||||
for (let prefset of prefsets) {
|
||||
let version = parseInt(prefset.getAttribute('version'));
|
||||
let pref = prefset.querySelector('pref');
|
||||
if (!pref) {
|
||||
Zotero.logError('<pref> not found within <prefset>');
|
||||
continue;
|
||||
}
|
||||
if (version <= Zotero.Prefs.get('prefVersion.remote')) {
|
||||
break;
|
||||
}
|
||||
let name = pref.getAttribute('name');
|
||||
if (!allowedPrefs.has(name)) {
|
||||
Zotero.logError(`${name} cannot be set remotely`);
|
||||
continue;
|
||||
}
|
||||
let value = pref.getAttribute('value');
|
||||
if (value == "true") value = true;
|
||||
if (value == "false") value = false;
|
||||
Zotero.Prefs.set(name, value, true);
|
||||
Zotero.Prefs.set('prefVersion.remote', version);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the interval between repository queries
|
||||
*
|
||||
|
|
@ -3412,6 +3466,18 @@ Zotero.Schema = new function(){
|
|||
}
|
||||
}
|
||||
|
||||
else if (i == 121) {
|
||||
let datasetItemTypeID = await Zotero.DB.valueQueryAsync("SELECT itemTypeID FROM itemTypes WHERE typeName='dataset'");
|
||||
let numberFieldID = await Zotero.DB.valueQueryAsync("SELECT fieldID FROM fields WHERE fieldName='number'");
|
||||
if (datasetItemTypeID && numberFieldID) {
|
||||
await Zotero.DB.queryAsync("DELETE FROM itemData WHERE fieldID=? AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=?)", [numberFieldID, datasetItemTypeID]);
|
||||
await Zotero.DB.queryAsync("DELETE FROM itemTypeFields WHERE itemTypeID=? AND fieldID=?", [datasetItemTypeID, numberFieldID]);
|
||||
}
|
||||
}
|
||||
|
||||
// TEMP: When adding 122, check whether IA.authorName fix in items.js::_loadAnnotations()
|
||||
// can be updated due to update steps being indempodent
|
||||
|
||||
// If breaking compatibility or doing anything dangerous, clear minorUpdateFrom
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.SingleFile = {
|
||||
Zotero.SingleFile = Object.assign(Zotero.SingleFile || {}, {
|
||||
// These are defaults from SingleFile
|
||||
// Located in: zotero/resources/SingleFile/extension/core/bg/config.js
|
||||
CONFIG: {
|
||||
|
|
@ -32,7 +32,9 @@ Zotero.SingleFile = {
|
|||
removeUnusedFonts: true,
|
||||
removeFrames: true,
|
||||
removeImports: true,
|
||||
removeScripts: true,
|
||||
blockScripts: true,
|
||||
blockVideos: true,
|
||||
blockAudios: true,
|
||||
compressHTML: true,
|
||||
compressCSS: false,
|
||||
loadDeferredImages: true,
|
||||
|
|
@ -40,6 +42,7 @@ Zotero.SingleFile = {
|
|||
loadDeferredImagesBlockCookies: false,
|
||||
loadDeferredImagesBlockStorage: false,
|
||||
loadDeferredImagesKeepZoomLevel: false,
|
||||
loadDeferredImagesNativeTimeout: true, // See https://github.com/gildas-lormeau/SingleFile/issues/1026
|
||||
filenameTemplate: "{page-title} ({date-iso} {time-locale}).html",
|
||||
infobarTemplate: "",
|
||||
includeInfobar: false,
|
||||
|
|
@ -58,8 +61,6 @@ Zotero.SingleFile = {
|
|||
progressBarEnabled: true,
|
||||
maxResourceSizeEnabled: false,
|
||||
maxResourceSize: 10,
|
||||
removeAudioSrc: true,
|
||||
removeVideoSrc: true,
|
||||
displayInfobar: true,
|
||||
displayStats: false,
|
||||
backgroundSave: true,
|
||||
|
|
@ -95,4 +96,4 @@ Zotero.SingleFile = {
|
|||
insertSingleFileComment: true,
|
||||
blockMixedContent: false,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue