mirror of
https://github.com/zotero/zotero.git
synced 2026-10-11 03:38:25 +00:00
Compare commits
436 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cddd989159 | ||
|
|
d6418c443f | ||
|
|
ffb1cc0c94 | ||
|
|
dd98c5b2b1 | ||
|
|
d05fd69fc2 | ||
|
|
0633f0191b | ||
|
|
72850b49dc | ||
|
|
32e50d89be | ||
|
|
9c9936a88a | ||
|
|
16ea4da9b2 | ||
|
|
9e4986abf5 | ||
|
|
a8a691fddb | ||
|
|
e19ab9b30b | ||
|
|
8f3f3c9b86 | ||
|
|
117bc45ed4 | ||
|
|
4db311eb46 | ||
|
|
fb148497ba | ||
|
|
6f506bbc3d | ||
|
|
78865ed2ef | ||
|
|
0ad55a6a70 | ||
|
|
4f7583e1c0 | ||
|
|
48c7c60bf1 | ||
|
|
266060dc48 | ||
|
|
9515912830 | ||
|
|
078626be82 | ||
|
|
4b61954e52 | ||
|
|
0d6678a735 | ||
|
|
2b9455670f | ||
|
|
1b3caadd20 | ||
|
|
c9381f307c | ||
|
|
c9c7805cdf | ||
|
|
3e12f3f20b | ||
|
|
2f0d41c0cb | ||
|
|
00e63f0f8a | ||
|
|
8096e22f40 | ||
|
|
e59e6cdcd8 | ||
|
|
fcc6cc17a9 | ||
|
|
8992efc869 | ||
|
|
01ebeb1834 | ||
|
|
bed256318a | ||
|
|
860e901240 | ||
|
|
9ce8855fa4 | ||
|
|
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 |
420 changed files with 19110 additions and 7711 deletions
17
.github/workflows/ci.yml
vendored
17
.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,13 +53,16 @@ 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: |
|
||||
env.ACT != 'true'
|
||||
&& github.repository == 'zotero/zotero'
|
||||
&& github.event_name == 'push'
|
||||
&& (github.ref == 'refs/heads/master' || endsWith(github.ref, '-hotfix') || github.ref == 'refs/heads/gh-actions-ci-test')
|
||||
&& (github.ref == 'refs/heads/6.0' || endsWith(github.ref, '-hotfix') || github.ref == 'refs/heads/gh-actions-ci-test')
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
|
@ -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 = [];
|
||||
|
|
@ -1097,13 +1138,19 @@ var Scaffold = new function () {
|
|||
string = fix2028(Zotero.Utilities.varDump(string));
|
||||
}
|
||||
|
||||
if (output.value) output.value += "\n";
|
||||
output.value += Zotero.Utilities.lpad(date.getHours(), '0', 2)
|
||||
// Put off actually building the log message and appending it to the console until the next animation frame
|
||||
// so as not to slow down translation with repeated layout recalculations triggered by appending text
|
||||
// and accessing scrollHeight
|
||||
// requestAnimationFrame() callbacks are guaranteed to be called in the order they were set
|
||||
requestAnimationFrame(() => {
|
||||
if (output.value) output.value += "\n";
|
||||
output.value += Zotero.Utilities.lpad(date.getHours(), '0', 2)
|
||||
+ ":" + Zotero.Utilities.lpad(date.getMinutes(), '0', 2)
|
||||
+ ":" + Zotero.Utilities.lpad(date.getSeconds(), '0', 2)
|
||||
+ " " + string.replace(/\n/g, "\n ");
|
||||
// move to end
|
||||
output.inputField.scrollTop = output.inputField.scrollHeight;
|
||||
// move to end
|
||||
output.inputField.scrollTop = output.inputField.scrollHeight;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1258,6 +1305,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 +1528,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 +1635,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 +1784,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 +1961,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 +1973,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 +2018,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ async function doAccept() {
|
|||
);
|
||||
|
||||
// If we successfully created a parent, return it
|
||||
if (newItems) {
|
||||
if (newItems.length) {
|
||||
io.dataOut = { parent: newItems[0] };
|
||||
window.close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -39,7 +40,7 @@ var Zotero_Lookup = new function () {
|
|||
* @param textBox {HTMLElement} - Textbox containing identifiers
|
||||
* @param childItem {Zotero.Item|false} - Child item (optional)
|
||||
* @param toggleProgress {function} - Callback to toggle progress on/off
|
||||
* @returns {Promise<boolean>}
|
||||
* @returns {Promise<Zotero.Item[]>}
|
||||
*/
|
||||
this.addItemsFromIdentifier = async function (textBox, childItem, toggleProgress) {
|
||||
var identifiers = Zotero.Utilities.extractIdentifiers(textBox.value);
|
||||
|
|
@ -78,11 +79,28 @@ var Zotero_Lookup = new function () {
|
|||
}
|
||||
}
|
||||
|
||||
let newItems = false;
|
||||
toggleProgress(true);
|
||||
|
||||
await Zotero.Promise.all(identifiers.map(async (identifier) => {
|
||||
var translate = new Zotero.Translate.Search();
|
||||
// Group PubMed IDs into batches of 200
|
||||
//
|
||||
// Up to 10,000 ids can apparently be passed in a single request, but 200 is the recommended
|
||||
// limit for GET, which we currently use, and passing batches of 200 seems...fine.
|
||||
//
|
||||
// https://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.id
|
||||
if (identifiers.length && identifiers[0].PMID) {
|
||||
let chunkSize = 200;
|
||||
let newIdentifiers = [];
|
||||
for (let i = 0; i < identifiers.length; i += chunkSize) {
|
||||
newIdentifiers.push({
|
||||
PMID: identifiers.slice(i, i + chunkSize).map(x => x.PMID)
|
||||
});
|
||||
}
|
||||
identifiers = newIdentifiers;
|
||||
}
|
||||
|
||||
let newItems = [];
|
||||
for (let identifier of identifiers) {
|
||||
let translate = new Zotero.Translate.Search();
|
||||
translate.setIdentifier(identifier);
|
||||
|
||||
// be lenient about translators
|
||||
|
|
@ -90,20 +108,20 @@ var Zotero_Lookup = new function () {
|
|||
translate.setTranslator(translators);
|
||||
|
||||
try {
|
||||
newItems = await translate.translate({
|
||||
newItems.push(...await translate.translate({
|
||||
libraryID,
|
||||
collections,
|
||||
saveAttachments: !childItem
|
||||
});
|
||||
}));
|
||||
}
|
||||
// Continue with other ids on failure
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
toggleProgress(false);
|
||||
if (!newItems) {
|
||||
if (!newItems.length) {
|
||||
Zotero.alert(
|
||||
window,
|
||||
Zotero.getString("lookup.failure.title"),
|
||||
|
|
@ -125,7 +143,7 @@ var Zotero_Lookup = new function () {
|
|||
on => Zotero_Lookup.toggleProgress(on)
|
||||
);
|
||||
|
||||
if (newItems) {
|
||||
if (newItems.length) {
|
||||
document.getElementById("zotero-lookup-panel").hidePopup();
|
||||
}
|
||||
return false;
|
||||
|
|
@ -134,6 +152,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 +223,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);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import FilePicker from 'zotero/modules/filePicker';
|
||||
|
||||
|
||||
(async function () {
|
||||
// Create schema
|
||||
var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
|
||||
|
|
@ -77,11 +77,12 @@ import FilePicker from 'zotero/modules/filePicker';
|
|||
resultElem.innerHTML = '<p>Failed.</p>';
|
||||
} else {
|
||||
let schemaFile = Zotero.File.pathToFile(fp.file);
|
||||
schemaFile.append("connectorTypeSchemaData.js");
|
||||
schemaFile.append("zoteroTypeSchemaData.js");
|
||||
await Zotero.File.putContentsAsync(
|
||||
schemaFile,
|
||||
`Zotero.Connector_Types.schema = ${JSON.stringify(schema)}`
|
||||
`var ZOTERO_TYPE_SCHEMA = ${JSON.stringify(schema)};\n\n`
|
||||
+ "if (typeof module !== 'undefined') {\n\tmodule.exports = ZOTERO_TYPE_SCHEMA;\n}\n"
|
||||
);
|
||||
resultElem.innerHTML = `<p>Wrote ${schemaFile} successfully.</p>`;
|
||||
resultElem.innerHTML = `<p>Wrote ${schemaFile.path} successfully.</p>`;
|
||||
}
|
||||
})();
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ Zotero.Annotations = new function () {
|
|||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_NOTE', { value: 2 });
|
||||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 });
|
||||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_INK', { value: 4 });
|
||||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_UNDERLINE', { value: 5 });
|
||||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_TEXT', { value: 6 });
|
||||
|
||||
Zotero.defineProperty(this, 'DEFAULT_COLOR', { value: '#ffd400' });
|
||||
|
||||
Zotero.defineProperty(this, 'PROPS', {
|
||||
value: ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel', 'sortIndex', 'position'],
|
||||
|
|
@ -135,7 +139,7 @@ Zotero.Annotations = new function () {
|
|||
}
|
||||
}
|
||||
o.readOnly = o.isExternal || !isAuthor;
|
||||
if (o.type == 'highlight') {
|
||||
if (['highlight', 'underline'].includes(o.type)) {
|
||||
o.text = item.annotationText;
|
||||
}
|
||||
else if (['image', 'ink'].includes(o.type)) {
|
||||
|
|
@ -177,7 +181,7 @@ Zotero.Annotations = new function () {
|
|||
o.tags = tags;
|
||||
}
|
||||
|
||||
o.dateModified = item.dateModified;
|
||||
o.dateModified = Zotero.Date.sqlToISO8601(item.dateModified);
|
||||
return o;
|
||||
};
|
||||
|
||||
|
|
@ -211,7 +215,7 @@ Zotero.Annotations = new function () {
|
|||
item._requireData('annotationDeferred');
|
||||
item.annotationType = json.type;
|
||||
item.annotationAuthorName = json.authorName || '';
|
||||
if (json.type == 'highlight') {
|
||||
if (['highlight', 'underline'].includes(json.type)) {
|
||||
item.annotationText = json.text;
|
||||
}
|
||||
item.annotationIsExternal = !!json.isExternal;
|
||||
|
|
@ -231,6 +235,10 @@ Zotero.Annotations = new function () {
|
|||
}
|
||||
}
|
||||
|
||||
// Don't try to select annotation, which would clear an active quick search (at least
|
||||
// until annotations are visible in the items list)
|
||||
saveOptions.skipSelect = true;
|
||||
|
||||
await item.saveTx(saveOptions);
|
||||
|
||||
return item;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Attachments = new function(){
|
||||
Zotero.Attachments = new function () {
|
||||
// Keep in sync with Zotero.Schema.integrityCheck() and this.linkModeToName()
|
||||
this.LINK_MODE_IMPORTED_FILE = 0;
|
||||
this.LINK_MODE_IMPORTED_URL = 1;
|
||||
|
|
@ -270,7 +270,7 @@ Zotero.Attachments = new function(){
|
|||
|
||||
|
||||
/**
|
||||
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID', 'singleFile'
|
||||
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'libraryID', 'parentItemID', 'singleFile'
|
||||
* @param {Object} [options.saveOptions] - Options to pass to Zotero.Item::save()
|
||||
* @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
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ var CSL = {
|
|||
"cmt.": "comment",
|
||||
"dec.": "decision",
|
||||
"dept.": "department",
|
||||
"div.": "division",
|
||||
"ex.": "example",
|
||||
"fig.": "figure",
|
||||
"fld.": "field",
|
||||
|
|
@ -170,7 +169,6 @@ var CSL = {
|
|||
"comment": "cmt.",
|
||||
"decision": "dec.",
|
||||
"department": "dept.",
|
||||
"division": "div.",
|
||||
"example": "ex.",
|
||||
"figure": "fig.",
|
||||
"field": "fld.",
|
||||
|
|
@ -219,7 +217,6 @@ var CSL = {
|
|||
"cmt": "comment",
|
||||
"dec": "decision",
|
||||
"dept": "department",
|
||||
"div": "division",
|
||||
"ex": "example",
|
||||
"fig": "figure",
|
||||
"fld": "field",
|
||||
|
|
@ -725,33 +722,61 @@ var CSL = {
|
|||
|
||||
NAME_VARIABLES: [
|
||||
"author",
|
||||
"chair",
|
||||
"collection-editor",
|
||||
"compiler",
|
||||
"composer",
|
||||
"container-author",
|
||||
"contributor",
|
||||
"curator",
|
||||
"director",
|
||||
"editor",
|
||||
"editor-translator",
|
||||
"editorial-director",
|
||||
"executive-producer",
|
||||
"guest",
|
||||
"host",
|
||||
"illustrator",
|
||||
"interviewer",
|
||||
"narrator",
|
||||
"organizer",
|
||||
"original-author",
|
||||
"performer",
|
||||
"producer",
|
||||
"recipient",
|
||||
"reviewed-author",
|
||||
"script-writer",
|
||||
"series-creator",
|
||||
"translator",
|
||||
"commenter"
|
||||
],
|
||||
CREATORS: [
|
||||
"author",
|
||||
"chair",
|
||||
"collection-editor",
|
||||
"compiler",
|
||||
"composer",
|
||||
"container-author",
|
||||
"contributor",
|
||||
"curator",
|
||||
"director",
|
||||
"editor",
|
||||
"editor-translator",
|
||||
"editorial-director",
|
||||
"executive-producer",
|
||||
"guest",
|
||||
"host",
|
||||
"illustrator",
|
||||
"interviewer",
|
||||
"narrator",
|
||||
"organizer",
|
||||
"original-author",
|
||||
"performer",
|
||||
"producer",
|
||||
"recipient",
|
||||
"reviewed-author",
|
||||
"script-writer",
|
||||
"series-creator",
|
||||
"translator",
|
||||
"commenter"
|
||||
],
|
||||
|
|
@ -768,10 +793,13 @@ var CSL = {
|
|||
"number",
|
||||
"number-of-pages",
|
||||
"number-of-volumes",
|
||||
"part-number",
|
||||
"printing-number",
|
||||
"section",
|
||||
"supplement-number",
|
||||
"version",
|
||||
"volume",
|
||||
// "section", ??? add this?
|
||||
"supplement",
|
||||
"supplement", // maybe deprecated this? supplement-number should serve this purpose in standard CSL.
|
||||
"citation-number"
|
||||
],
|
||||
//var x = new Array();
|
||||
|
|
@ -1476,6 +1504,9 @@ var CSL = {
|
|||
},
|
||||
|
||||
INIT_JURISDICTION_MACROS: function (state, Item, item, macroName) {
|
||||
if (Item["best-jurisdiction"]) {
|
||||
return true;
|
||||
}
|
||||
if (!state.sys.retrieveStyleModule || !CSL.MODULE_MACROS[macroName] || !Item.jurisdiction) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1495,6 +1526,7 @@ var CSL = {
|
|||
}
|
||||
}
|
||||
}
|
||||
var jurisdictionList = state.getJurisdictionList(Item.jurisdiction);
|
||||
if (state.opt.parallel.enable) {
|
||||
if (!state.parallel) {
|
||||
state.parallel = new CSL.Parallel(state);
|
||||
|
|
@ -1505,7 +1537,7 @@ var CSL = {
|
|||
var jurisdiction = jurisdictionList[i];
|
||||
if (item) {
|
||||
if (state.juris[jurisdiction] && !item["best-jurisdiction"] && state.juris[jurisdiction].types.locator) {
|
||||
item["best-jurisdiction"] = jurisdiction;
|
||||
Item["best-jurisdiction"] = jurisdiction;
|
||||
}
|
||||
}
|
||||
if(state.juris[jurisdiction] && state.juris[jurisdiction].types[Item.type]) {
|
||||
|
|
@ -10032,7 +10064,7 @@ CSL.Node.date = {
|
|||
|
||||
CSL.Node["date-part"] = {
|
||||
build: function (state, target) {
|
||||
var func, pos, len, first_date, value, value_end, real, have_collapsed, invoked, precondition, known_year, bc, ad, bc_end, ad_end, ready, curr, dcurr, number, num, formatter, item;
|
||||
var func, pos, len, first_date, value, value_end, real, have_collapsed, invoked, precondition, known_year, bc, ad, bc_end, ad_end, ready, curr, dcurr, number, num, formatter, item, blob;
|
||||
if (!this.strings.form) {
|
||||
this.strings.form = "long";
|
||||
}
|
||||
|
|
@ -10175,7 +10207,7 @@ CSL.Node["date-part"] = {
|
|||
// and we reach this block, then we can combine the dates
|
||||
// to a string, run minimial-two, and output the trailing
|
||||
// year right here. No impact on other functionality.
|
||||
|
||||
|
||||
if (state.opt["year-range-format"]
|
||||
&& state.opt["year-range-format"] !== "expanded"
|
||||
&& !state.tmp.date_object.day
|
||||
|
|
@ -10183,7 +10215,7 @@ CSL.Node["date-part"] = {
|
|||
&& !state.tmp.date_object.season
|
||||
&& this.strings.name === "year"
|
||||
&& value && value_end) {
|
||||
|
||||
|
||||
// second argument adjusts collapse as required for years
|
||||
// See OSCOLA section 1.3.2
|
||||
value_end = state.fun.year_mangler(value + "-" + value_end, true);
|
||||
|
|
@ -10193,13 +10225,21 @@ CSL.Node["date-part"] = {
|
|||
last_string_output = value_end;
|
||||
state.dateput.append(value_end, this);
|
||||
if (first_date) {
|
||||
state.dateput.current.value().blobs[0].strings.prefix = "";
|
||||
blob = state.dateput.current.value().blobs[0];
|
||||
if (blob) {
|
||||
blob.strings.prefix = "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
last_string_output = value;
|
||||
state.output.append(value, this);
|
||||
curr = state.output.current.value();
|
||||
curr.blobs[(curr.blobs.length - 1)].strings.suffix = "";
|
||||
blob = curr.blobs[(curr.blobs.length - 1)];
|
||||
if (blob) {
|
||||
blob.strings.suffix = "";
|
||||
}
|
||||
|
||||
if (this.strings["range-delimiter"]) {
|
||||
state.output.append(this.strings["range-delimiter"]);
|
||||
} else {
|
||||
|
|
@ -10234,7 +10274,10 @@ CSL.Node["date-part"] = {
|
|||
last_string_output = value_end;
|
||||
state.dateput.append(value_end, this);
|
||||
if (first_date) {
|
||||
state.dateput.current.value().blobs[0].strings.prefix = "";
|
||||
blob = state.dateput.current.value().blobs[0];
|
||||
if (blob) {
|
||||
blob.strings.prefix = "";
|
||||
}
|
||||
}
|
||||
if (bc) {
|
||||
last_string_output = bc;
|
||||
|
|
@ -10326,8 +10369,6 @@ CSL.Node["date-part"] = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*global CSL: true */
|
||||
|
||||
CSL.Node["else-if"] = {
|
||||
|
|
@ -11752,12 +11793,12 @@ CSL.Node["alternative-text"] = {
|
|||
|
||||
CSL.NameOutput = function(state, Item, item) {
|
||||
this.debug = false;
|
||||
this.state = state;
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(1)");
|
||||
this.state.sys.print("(1)");
|
||||
}
|
||||
//SNIP-END
|
||||
this.state = state;
|
||||
this.Item = Item;
|
||||
this.item = item;
|
||||
this.nameset_base = 0;
|
||||
|
|
@ -11911,21 +11952,21 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(2)");
|
||||
this.state.sys.print("(2)");
|
||||
}
|
||||
//SNIP-END
|
||||
// util_names_etalconfig.js
|
||||
this.getEtAlConfig();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(3)");
|
||||
this.state.sys.print("(3)");
|
||||
}
|
||||
//SNIP-END
|
||||
// util_names_divide.js
|
||||
this.divideAndTransliterateNames();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(4)");
|
||||
this.state.sys.print("(4)");
|
||||
}
|
||||
//SNIP-END
|
||||
// util_names_truncate.js
|
||||
|
|
@ -11933,13 +11974,13 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
this.truncatePersonalNameLists();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(5)");
|
||||
this.state.sys.print("(5)");
|
||||
}
|
||||
//SNIP-END
|
||||
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(6)");
|
||||
this.state.sys.print("(6)");
|
||||
}
|
||||
//SNIP-END
|
||||
// util_names_disambig.js
|
||||
|
|
@ -11949,7 +11990,7 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
this.constrainNames();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(7)");
|
||||
this.state.sys.print("(7)");
|
||||
}
|
||||
//SNIP-END
|
||||
// form="count"
|
||||
|
|
@ -11963,25 +12004,25 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(8)");
|
||||
this.state.sys.print("(8)");
|
||||
}
|
||||
//SNIP-END
|
||||
this.setEtAlParameters();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(9)");
|
||||
this.state.sys.print("(9)");
|
||||
}
|
||||
//SNIP-END
|
||||
this.setCommonTerm(this.requireMatch);
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(10)");
|
||||
this.state.sys.print("(10)");
|
||||
}
|
||||
//SNIP-END
|
||||
this.renderAllNames();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(11)");
|
||||
this.state.sys.print("(11)");
|
||||
}
|
||||
//SNIP-END
|
||||
var blob_list = [];
|
||||
|
|
@ -11995,7 +12036,7 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
} else {
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(11a)");
|
||||
this.state.sys.print("(11a)");
|
||||
}
|
||||
//SNIP-END
|
||||
for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) {
|
||||
|
|
@ -12003,7 +12044,7 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(11b)");
|
||||
this.state.sys.print("(11b)");
|
||||
}
|
||||
//SNIP-END
|
||||
if (this.institutions[v].length) {
|
||||
|
|
@ -12015,13 +12056,13 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(11c)");
|
||||
this.state.sys.print("(11c)");
|
||||
}
|
||||
//SNIP-END
|
||||
var varblob = this.joinFreetersAndInstitutionSets([this.freeters[v], institutions]);
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(11d)");
|
||||
this.state.sys.print("(11d)");
|
||||
}
|
||||
//SNIP-END
|
||||
}
|
||||
|
|
@ -12034,7 +12075,7 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(11e)");
|
||||
this.state.sys.print("(11e)");
|
||||
}
|
||||
//SNIP-END
|
||||
if (this.common_term) {
|
||||
|
|
@ -12043,14 +12084,14 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(12)");
|
||||
this.state.sys.print("(12)");
|
||||
}
|
||||
//SNIP-END
|
||||
this.state.output.openLevel("empty");
|
||||
this.state.output.current.value().strings.delimiter = this.state.inheritOpt(this.names, "delimiter", "names-delimiter");
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(13)");
|
||||
this.state.sys.print("(13)");
|
||||
}
|
||||
//SNIP-END
|
||||
for (i = 0, ilen = blob_list.length; i < ilen; i += 1) {
|
||||
|
|
@ -12062,20 +12103,20 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(14)");
|
||||
this.state.sys.print("(14)");
|
||||
}
|
||||
//SNIP-END
|
||||
this.state.output.closeLevel("empty");
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(15)");
|
||||
this.state.sys.print("(15)");
|
||||
}
|
||||
//SNIP-END
|
||||
var blob = this.state.output.pop();
|
||||
this.state.tmp.name_node.top = blob;
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(16)");
|
||||
this.state.sys.print("(16)");
|
||||
}
|
||||
//SNIP-END
|
||||
|
||||
|
|
@ -12092,14 +12133,14 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(17)");
|
||||
this.state.sys.print("(17)");
|
||||
}
|
||||
//SNIP-END
|
||||
// Also used in CSL.Util.substituteEnd (which could do with
|
||||
// some cleanup at this writing).
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(18)");
|
||||
this.state.sys.print("(18)");
|
||||
}
|
||||
//SNIP-END
|
||||
if (variables[0] !== "authority") {
|
||||
|
|
@ -12164,7 +12205,7 @@ CSL.NameOutput.prototype.outputNames = function () {
|
|||
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("(19)");
|
||||
this.state.sys.print("(19)");
|
||||
}
|
||||
//SNIP-END
|
||||
};
|
||||
|
|
@ -12256,7 +12297,7 @@ CSL.NameOutput.prototype._collapseAuthor = function () {
|
|||
this.state.registry.authorstrings[this.Item.id] = mystr;
|
||||
} else if (!this.state.tmp.just_looking
|
||||
&& !this.state.tmp.suppress_decorations && ((this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length) || this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter)) {
|
||||
// XX1 print("RENDER: "+this.Item.id);
|
||||
// XX1 this.state.sys.print("RENDER: "+this.Item.id);
|
||||
mystr = "";
|
||||
myqueue = this.state.tmp.name_node.top.blobs.slice(-1)[0].blobs;
|
||||
oldchars = this.state.tmp.offset_characters;
|
||||
|
|
@ -12265,7 +12306,7 @@ CSL.NameOutput.prototype._collapseAuthor = function () {
|
|||
}
|
||||
if (mystr === this.state.tmp.last_primary_names_string) {
|
||||
if (this.item["suppress-author"] || (this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length)) {
|
||||
// XX1 print(" CUT!");
|
||||
// XX1 this.state.sys.print(" CUT!");
|
||||
this.state.tmp.name_node.top.blobs.pop();
|
||||
this.state.tmp.name_node.children = [];
|
||||
// If popped, avoid side-effects on character counting: we're only interested
|
||||
|
|
@ -12277,7 +12318,7 @@ CSL.NameOutput.prototype._collapseAuthor = function () {
|
|||
this.state.tmp.use_cite_group_delimiter = true;
|
||||
}
|
||||
} else {
|
||||
// XX1 print("remembering: "+mystr);
|
||||
// XX1 this.state.sys.print("remembering: "+mystr);
|
||||
this.state.tmp.last_primary_names_string = mystr;
|
||||
// XXXXX A little more precision would be nice.
|
||||
// This will clobber variable="author editor" as well as variable="author".
|
||||
|
|
@ -20879,7 +20920,7 @@ CSL.Util.outputNumericField = function(state, varname, itemID) {
|
|||
CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, masterStyling.strings.prefix, null, masterStyling, `${num.particle}${num.value}`);
|
||||
if (num.collapsible) {
|
||||
var blob;
|
||||
if (num.value.match(/^[1-9][0-9]*$/)) {
|
||||
if (num.value.match(/^[1-9][0-9]*$/) && Number.isSafeInteger(parseInt(num.value, 10))) {
|
||||
blob = new CSL.NumericBlob(state, num.particle, parseInt(num.value, 10), numStyling, itemID);
|
||||
} else {
|
||||
blob = new CSL.NumericBlob(state, num.particle, num.value, numStyling, itemID);
|
||||
|
|
@ -20913,7 +20954,7 @@ CSL.Util.outputNumericField = function(state, varname, itemID) {
|
|||
CSL.Util.PageRangeMangler = {};
|
||||
|
||||
CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
|
||||
var rangerex, pos, len, stringify, listify, expand, minimize, minimize_internal, chicago, lst, m, b, e, ret, begin, end, ret_func;
|
||||
var rangerex, pos, len, stringify, listify, expand, minimize, minimize_internal, chicago15, chicago16, lst, m, b, e, ret, begin, end, ret_func;
|
||||
|
||||
var range_delimiter = state.getTerm(rangeType + "-range-delimiter");
|
||||
|
||||
|
|
@ -21021,7 +21062,7 @@ CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
|
|||
return ret.join("");
|
||||
};
|
||||
|
||||
chicago = function (lst) {
|
||||
chicago15 = function (lst) {
|
||||
len = lst.length;
|
||||
for (pos = 1; pos < len; pos += 2) {
|
||||
if ("object" === typeof lst[pos]) {
|
||||
|
|
@ -21041,6 +21082,31 @@ CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
|
|||
return stringify(lst);
|
||||
};
|
||||
|
||||
chicago16 = function (lst) {
|
||||
len = lst.length;
|
||||
for (pos = 1; pos < len; pos += 2) {
|
||||
if ("object" === typeof lst[pos]) {
|
||||
m = lst[pos];
|
||||
begin = parseInt(m[1], 10);
|
||||
end = parseInt(m[3], 10);
|
||||
e = "" + end;
|
||||
if (begin > 100 && begin % 100) {
|
||||
for (var i = 2; i < e.length; i++) {
|
||||
var divisor = Math.pow(10, i);
|
||||
if (Math.floor(begin / divisor) === Math.floor(end / divisor)) {
|
||||
m[3] = "" + (end % divisor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m[2].slice(1) === m[0]) {
|
||||
m[2] = range_delimiter;
|
||||
}
|
||||
}
|
||||
return stringify(lst);
|
||||
};
|
||||
|
||||
//
|
||||
// The top-level option handlers.
|
||||
//
|
||||
|
|
@ -21070,7 +21136,15 @@ CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
|
|||
};
|
||||
} else if (state.opt[rangeType + "-range-format"] === "chicago") {
|
||||
ret_func = function (str) {
|
||||
return sniff(str, chicago);
|
||||
return sniff(str, chicago15);
|
||||
};
|
||||
} else if (state.opt[rangeType + "-range-format"] === "chicago-15") {
|
||||
ret_func = function (str) {
|
||||
return sniff(str, chicago15);
|
||||
};
|
||||
} else if (state.opt[rangeType + "-range-format"] === "chicago-16") {
|
||||
ret_func = function (str) {
|
||||
return sniff(str, chicago16);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -21668,7 +21742,7 @@ CSL.Output.Formatters = (function () {
|
|||
var rexNameStr = "(?:[-\\s]*<\\/*(?:span\s+class=\"no(?:case|decor)\"|i|sc|b|sub|sup)>[-\\s]*|[-\\s]+)";
|
||||
var nameDoppel = new CSL.Doppeler(rexNameStr);
|
||||
|
||||
var wordDoppel = new CSL.Doppeler("(?:[\u0020\u00A0\u2000-\u200B\u205F\u3000]+)");
|
||||
var wordDoppel = new CSL.Doppeler("(?:[\u00A0\u0020\u00A0\u2000-\u200B\u205F\u3000]+)");
|
||||
|
||||
/**
|
||||
* INTERNAL
|
||||
|
|
@ -21941,7 +22015,6 @@ CSL.Output.Formatters = (function () {
|
|||
quoteState: [],
|
||||
capitaliseWords: function(str, i, followingTag) {
|
||||
if (str.trim()) {
|
||||
var words = str.split(/[ \u00A0]+/);
|
||||
var wordle = wordDoppel.split(str);
|
||||
var words = wordle.strings;
|
||||
for (var j=0,jlen=words.length;j<jlen;j++) {
|
||||
|
|
@ -21949,17 +22022,23 @@ CSL.Output.Formatters = (function () {
|
|||
if (!word) {
|
||||
continue;
|
||||
}
|
||||
if (word.length > 1 && !CSL.toLocaleLowerCase.call(state, word).match(config.skipWordsRex)) {
|
||||
let lcase = CSL.toLocaleLowerCase.call(state, word);
|
||||
let capitalize = false;
|
||||
if (word.length > 1 && !lcase.match(config.skipWordsRex)) {
|
||||
// Capitalize every word that is not a stop-word
|
||||
words[j] = _capitalise.call(state, words[j]);
|
||||
capitalize = true;
|
||||
} else if (j === (words.length - 1) && followingTag === "-") {
|
||||
words[j] = _capitalise.call(state, words[j]);
|
||||
capitalize = true;
|
||||
} else if (config.isFirst) {
|
||||
// Capitalize first word, even if a stop-word
|
||||
words[j] = _capitalise.call(state, words[j]);
|
||||
capitalize = true;
|
||||
} else if (config.afterPunct) {
|
||||
// Capitalize after punctuation
|
||||
words[j] = _capitalise.call(state, words[j]);
|
||||
capitalize = true;
|
||||
}
|
||||
// Don't capitalize if word already contains capitalization
|
||||
if (capitalize && word === lcase) {
|
||||
words[j] = _capitalise.call(state, word);
|
||||
}
|
||||
config.afterPunct = false;
|
||||
config.isFirst = false;
|
||||
|
|
@ -21989,18 +22068,22 @@ CSL.Output.Formatters = (function () {
|
|||
var config = {
|
||||
quoteState: [],
|
||||
capitaliseWords: function(str) {
|
||||
var words = str.split(" ");
|
||||
var wordle = wordDoppel.split(str);
|
||||
var words = wordle.strings;
|
||||
for (var i=0,ilen=words.length;i<ilen;i++) {
|
||||
var word = words[i];
|
||||
if (word) {
|
||||
if (config.isFirst) {
|
||||
words[i] = _capitalise.call(state, word);
|
||||
// Don't capitalize if word already contains capitalization
|
||||
if (word === CSL.toLocaleLowerCase.call(state, word)) {
|
||||
words[i] = _capitalise.call(state, word);
|
||||
}
|
||||
config.isFirst = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return words.join(" ");
|
||||
return wordDoppel.join(wordle);
|
||||
},
|
||||
skipWordsRex: null,
|
||||
tagState: [],
|
||||
|
|
@ -22020,14 +22103,18 @@ CSL.Output.Formatters = (function () {
|
|||
var config = {
|
||||
quoteState: [],
|
||||
capitaliseWords: function(str) {
|
||||
var words = str.split(" ");
|
||||
var wordle = wordDoppel.split(str);
|
||||
var words = wordle.strings;
|
||||
for (var i=0,ilen=words.length;i<ilen;i++) {
|
||||
var word = words[i];
|
||||
if (word) {
|
||||
words[i] = _capitalise.call(state, word);
|
||||
// Don't capitalize if word already contains capitalization
|
||||
if (word === CSL.toLocaleLowerCase.call(state, word)) {
|
||||
words[i] = _capitalise.call(state, word);
|
||||
}
|
||||
}
|
||||
}
|
||||
return words.join(" ");
|
||||
return wordDoppel.join(wordle);
|
||||
},
|
||||
skipWordsRex: null,
|
||||
tagState: [],
|
||||
|
|
@ -22261,7 +22348,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;
|
||||
|
|
@ -22630,7 +22717,7 @@ CSL.Output.Formats.prototype.latex = {
|
|||
return text;
|
||||
},
|
||||
"bibstart": "\\begin{thebibliography}{4}",
|
||||
"bibend": "\end{thebibliography}",
|
||||
"bibend": "\\end{thebibliography}",
|
||||
"@font-style/italic": "{\\em %%STRING%%}",
|
||||
"@font-style/oblique": false,
|
||||
"@font-style/normal": false,
|
||||
|
|
@ -23535,6 +23622,11 @@ CSL.Registry.NameReg = function (state) {
|
|||
|
||||
set_keys = function (state, itemid, nameobj) {
|
||||
pkey = strip_periods(nameobj.family);
|
||||
|
||||
if (state.opt["demote-non-dropping-particle"] === "never" && nameobj["non-dropping-particle"] && nameobj["family"]) {
|
||||
pkey = `${pkey} ${nameobj["non-dropping-particle"]}`;
|
||||
}
|
||||
|
||||
skey = strip_periods(nameobj.given);
|
||||
// Drop lowercase suffixes (such as et al.) from given name field
|
||||
// for disambiguation purposes.
|
||||
|
|
@ -23746,12 +23838,10 @@ CSL.Registry.NameReg = function (state) {
|
|||
&& pos !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// A hack. Safe if the name object is used only here, for disambiguation purposes.
|
||||
// TEMP: Disabled due to https://github.com/Juris-M/citeproc-js/issues/179
|
||||
/*if (state.opt["demote-non-dropping-particle"] === "never" && nameobj["non-dropping-particle"] && nameobj["family"]) {
|
||||
nameobj["family"] = nameobj["non-dropping-particle"] + " " + nameobj["family"];
|
||||
}*/
|
||||
/*
|
||||
*/
|
||||
|
||||
//CSL.debug("INS");
|
||||
set_keys(this.state, "" + item_id, nameobj);
|
||||
|
|
@ -23853,7 +23943,7 @@ CSL.Disambiguation.prototype.run = function(akey) {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("[A] === RUN ===");
|
||||
this.state.sys.print("[A] === RUN ===");
|
||||
}
|
||||
//SNIP-END
|
||||
this.akey = akey;
|
||||
|
|
@ -23867,7 +23957,7 @@ CSL.Disambiguation.prototype.runDisambig = function () {
|
|||
var ismax;
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("[C] === runDisambig() ===");
|
||||
this.state.sys.print("[C] === runDisambig() ===");
|
||||
}
|
||||
//SNIP-END
|
||||
this.initGivens = true;
|
||||
|
|
@ -23896,7 +23986,7 @@ CSL.Disambiguation.prototype.scanItems = function (list) {
|
|||
var pos, len, otherItem;
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("[2] === scanItems() ===");
|
||||
this.state.sys.print("[2] === scanItems() ===");
|
||||
}
|
||||
//SNIP-END
|
||||
|
||||
|
|
@ -23915,15 +24005,15 @@ CSL.Disambiguation.prototype.scanItems = function (list) {
|
|||
//SNIP-START
|
||||
if (this.debug) {
|
||||
if (pos > 1) {
|
||||
print(" -----------");
|
||||
this.state.sys.print(" -----------");
|
||||
}
|
||||
}
|
||||
//SNIP-END
|
||||
if (this.ItemCite === otherItemCite) {
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" [CLASH]--> "+this.Item.id+": "+this.ItemCite);
|
||||
print(" "+otherItem.id+": "+otherItemCite);
|
||||
this.state.sys.print(" [CLASH]--> "+this.Item.id+": "+this.ItemCite);
|
||||
this.state.sys.print(" "+otherItem.id+": "+otherItemCite);
|
||||
}
|
||||
//SNIP-END
|
||||
clashes += 1;
|
||||
|
|
@ -23931,8 +24021,8 @@ CSL.Disambiguation.prototype.scanItems = function (list) {
|
|||
} else {
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" [clear]--> "+this.Item.id+": "+this.ItemCite);
|
||||
print(" "+otherItem.id+": "+otherItemCite);
|
||||
this.state.sys.print(" [clear]--> "+this.Item.id+": "+this.ItemCite);
|
||||
this.state.sys.print(" "+otherItem.id+": "+otherItemCite);
|
||||
}
|
||||
//SNIP-END
|
||||
this.nonpartners.push(otherItem);
|
||||
|
|
@ -23958,9 +24048,9 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("[3] == disNames() ==");
|
||||
//print(" partners: "+[this.partners[i].id for (i in this.partners)].join(", "));
|
||||
//print(" nonpartners: "+[this.nonpartners[i].id for (i in this.nonpartners)].join(", "));
|
||||
this.state.sys.print("[3] == disNames() ==");
|
||||
//this.state.sys.print(" partners: "+[this.partners[i].id for (i in this.partners)].join(", "));
|
||||
//this.state.sys.print(" nonpartners: "+[this.nonpartners[i].id for (i in this.nonpartners)].join(", "));
|
||||
}
|
||||
//SNIP-END
|
||||
|
||||
|
|
@ -23988,8 +24078,8 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
this.captureStepToBase();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" ** RESOLUTION [a]: lone partner, one nonpartner");
|
||||
print(" registering "+this.partners[0].id+" and "+this.nonpartners[0].id);
|
||||
this.state.sys.print(" ** RESOLUTION [a]: lone partner, one nonpartner");
|
||||
this.state.sys.print(" registering "+this.partners[0].id+" and "+this.nonpartners[0].id);
|
||||
}
|
||||
//SNIP-END
|
||||
this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase);
|
||||
|
|
@ -23999,8 +24089,8 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
this.captureStepToBase();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" ** RESOLUTION [b]: lone partner, unknown number of remaining nonpartners");
|
||||
print(" registering "+this.partners[0].id);
|
||||
this.state.sys.print(" ** RESOLUTION [b]: lone partner, unknown number of remaining nonpartners");
|
||||
this.state.sys.print(" registering "+this.partners[0].id);
|
||||
}
|
||||
//SNIP-END
|
||||
this.state.registry.registerAmbigToken(this.akey, "" + this.partners[0].id, this.betterbase);
|
||||
|
|
@ -24012,8 +24102,8 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
this.captureStepToBase();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" ** RESOLUTION [c]: lone nonpartner, unknown number of partners remaining");
|
||||
print(" registering "+this.nonpartners[0].id);
|
||||
this.state.sys.print(" ** RESOLUTION [c]: lone nonpartner, unknown number of partners remaining");
|
||||
this.state.sys.print(" registering "+this.nonpartners[0].id);
|
||||
}
|
||||
//SNIP-END
|
||||
this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase);
|
||||
|
|
@ -24023,7 +24113,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
this.captureStepToBase();
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" ** RESOLUTION [d]: better result, but no entries safe to register");
|
||||
this.state.sys.print(" ** RESOLUTION [d]: better result, but no entries safe to register");
|
||||
}
|
||||
//SNIP-END
|
||||
this.lists[this.listpos] = [this.betterbase, this.partners];
|
||||
|
|
@ -24031,7 +24121,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
} else {
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" ** RESOLUTION [e]: no improvement, and clashes remain");
|
||||
this.state.sys.print(" ** RESOLUTION [e]: no improvement, and clashes remain");
|
||||
}
|
||||
//SNIP-END
|
||||
if (ismax) {
|
||||
|
|
@ -24040,7 +24130,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
if (this.modeindex === this.modes.length - 1) {
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" (registering clashing entries because we've run out of options)");
|
||||
this.state.sys.print(" (registering clashing entries because we've run out of options)");
|
||||
}
|
||||
//SNIP-END
|
||||
for (var i = 0, ilen = this.partners.length; i < ilen; i += 1) {
|
||||
|
|
@ -24055,7 +24145,7 @@ CSL.Disambiguation.prototype.disNames = function (ismax) {
|
|||
CSL.Disambiguation.prototype.disExtraText = function () {
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("[3] === disExtraText ==");
|
||||
this.state.sys.print("[3] === disExtraText ==");
|
||||
}
|
||||
//SNIP-END
|
||||
|
||||
|
|
@ -24117,7 +24207,7 @@ CSL.Disambiguation.prototype.disYears = function () {
|
|||
var pos, len, tokens, token;
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("[3] === disYears ==");
|
||||
this.state.sys.print("[3] === disYears ==");
|
||||
}
|
||||
//SNIP-END
|
||||
tokens = [];
|
||||
|
|
@ -24153,7 +24243,7 @@ CSL.Disambiguation.prototype.disYears = function () {
|
|||
CSL.Disambiguation.prototype.incrementDisambig = function () {
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("\n[1] === incrementDisambig() ===");
|
||||
this.state.sys.print("\n[1] === incrementDisambig() ===");
|
||||
}
|
||||
//SNIP-END
|
||||
if (this.initGivens) {
|
||||
|
|
@ -24217,20 +24307,20 @@ CSL.Disambiguation.prototype.incrementDisambig = function () {
|
|||
}
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" ------------------");
|
||||
print(" incremented values");
|
||||
print(" ------------------");
|
||||
print(" | gnameset: "+this.gnameset);
|
||||
print(" | gname: "+this.gname);
|
||||
print(" | names value: "+this.base.names[this.gnameset]);
|
||||
this.state.sys.print(" ------------------");
|
||||
this.state.sys.print(" incremented values");
|
||||
this.state.sys.print(" ------------------");
|
||||
this.state.sys.print(" | gnameset: "+this.gnameset);
|
||||
this.state.sys.print(" | gname: "+this.gname);
|
||||
this.state.sys.print(" | names value: "+this.base.names[this.gnameset]);
|
||||
if (this.base.givens.length) {
|
||||
print(" | givens value: "+this.base.givens[this.gnameset][this.gname]);
|
||||
this.state.sys.print(" | givens value: "+this.base.givens[this.gnameset][this.gname]);
|
||||
} else {
|
||||
print(" | givens value: nil");
|
||||
this.state.sys.print(" | givens value: nil");
|
||||
}
|
||||
print(" | namesetsMax: "+this.namesetsMax);
|
||||
print(" | namesMax: "+this.namesMax);
|
||||
print(" | givensMax: "+this.givensMax);
|
||||
this.state.sys.print(" | namesetsMax: "+this.namesetsMax);
|
||||
this.state.sys.print(" | namesMax: "+this.namesMax);
|
||||
this.state.sys.print(" | givensMax: "+this.givensMax);
|
||||
}
|
||||
//SNIP-END
|
||||
if (("number" !== typeof this.namesetsMax || this.namesetsMax === -1 || this.gnameset === this.namesetsMax)
|
||||
|
|
@ -24241,7 +24331,7 @@ CSL.Disambiguation.prototype.incrementDisambig = function () {
|
|||
maxed = true;
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print(" MAXED");
|
||||
this.state.sys.print(" MAXED");
|
||||
}
|
||||
//SNIP-END
|
||||
}
|
||||
|
|
@ -24256,7 +24346,7 @@ CSL.Disambiguation.prototype.initVars = function (akey) {
|
|||
var i, ilen, myIds, myItemBundles, myItems;
|
||||
//SNIP-START
|
||||
if (this.debug) {
|
||||
print("[B] === initVars() ===");
|
||||
this.state.sys.print("[B] === initVars() ===");
|
||||
}
|
||||
//SNIP-END
|
||||
this.lists = [];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1135,7 +1135,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
|
|||
}
|
||||
|
||||
try {
|
||||
let item = await this.saveSnapshot(targetID, requestData);
|
||||
var item = await this.saveSnapshot(targetID, requestData);
|
||||
await session.addItem(item);
|
||||
}
|
||||
catch (e) {
|
||||
|
|
@ -1143,9 +1143,15 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
|
|||
return 500;
|
||||
}
|
||||
|
||||
let attachments = [];
|
||||
let hasAttachments = !item.isAttachment() && item.getAttachments().length;
|
||||
if (hasAttachments) {
|
||||
attachments = [{mimeType: "text/html", title: data.title, url: data.url}];
|
||||
}
|
||||
|
||||
return [201,
|
||||
"application/json",
|
||||
JSON.stringify({ saveSingleFile: !data.skipSnapshot && !data.pdf })];
|
||||
JSON.stringify({ saveSingleFile: !data.skipSnapshot && !data.pdf && data.singleFile, attachments })];
|
||||
},
|
||||
|
||||
/*
|
||||
|
|
@ -1175,6 +1181,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
|
|||
let item = await Zotero.Attachments.importFromURL({
|
||||
libraryID,
|
||||
url: data.url,
|
||||
referrer: data.referrer,
|
||||
collections: collection ? [collection.id] : undefined,
|
||||
contentType: "application/pdf",
|
||||
cookieSandbox
|
||||
|
|
@ -1234,6 +1241,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 +1697,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 +1853,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':
|
||||
|
|
|
|||
|
|
@ -117,10 +117,10 @@ for (let name of ['deleted']) {
|
|||
val = !!val;
|
||||
var oldVal = this._getLatestField(name);
|
||||
if (oldVal == val) {
|
||||
Zotero.debug(Zotero.Utilities.capitalize(name)
|
||||
+ ` state hasn't changed for ${this._objectType} ${this.id}`);
|
||||
Zotero.debug(`Field '${name}' hasn't changed`);
|
||||
return;
|
||||
}
|
||||
Zotero.debug(`Field '${name}' has changed from '${oldVal}' to '${val}'`, 4);
|
||||
this._markFieldChange(name, val);
|
||||
}
|
||||
});
|
||||
|
|
@ -987,6 +987,11 @@ Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env)
|
|||
|
||||
env.isNew = !this.id;
|
||||
|
||||
if (!this.hasChanged()) {
|
||||
Zotero.debug(this._ObjectType + ' ' + this.id + ' has not changed', 4);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!env.options.skipEditCheck) {
|
||||
if (!this.isEditable()) {
|
||||
throw new Error("Cannot edit " + this._objectType + " in library "
|
||||
|
|
@ -999,11 +1004,6 @@ Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env)
|
|||
throw new Error("Cannot add " + this._objectType + " to a " + targetLib.libraryType + " library");
|
||||
}
|
||||
|
||||
if (!this.hasChanged()) {
|
||||
Zotero.debug(this._ObjectType + ' ' + this.id + ' has not changed', 4);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Undo registerObject() on failure
|
||||
if (env.isNew) {
|
||||
var func = function () {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -1897,7 +1906,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
|||
throw new Error("Annotation parent must be a file attachment");
|
||||
}
|
||||
if (parentItem.attachmentContentType != 'application/pdf') {
|
||||
throw new Error("Annotation parent must be a PDF");
|
||||
//throw new Error("Annotation parent must be a PDF");
|
||||
}
|
||||
let type = this._getLatestField('annotationType');
|
||||
let typeID = Zotero.Annotations[`ANNOTATION_TYPE_${type.toUpperCase()}`];
|
||||
|
|
@ -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 + ")");
|
||||
}
|
||||
|
|
@ -3396,7 +3409,7 @@ Zotero.Item.prototype.getAttachmentLastPageIndex = function () {
|
|||
|
||||
var id = this._getLastPageIndexSettingKey();
|
||||
var val = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, id);
|
||||
if (val !== null && (typeof val != 'number' || val != parseInt(val))) {
|
||||
if (this.isPDFAttachment() && val !== null && (typeof val != 'number' || val != parseInt(val))) {
|
||||
Zotero.logError(`Setting contains an invalid attachment page index ('${val}') -- discarding`);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -3408,7 +3421,7 @@ Zotero.Item.prototype.setAttachmentLastPageIndex = async function (val) {
|
|||
throw new Error("setAttachmentLastPageIndex() can only be called on file attachments");
|
||||
}
|
||||
|
||||
if (typeof val != 'number' || val != parseInt(val)) {
|
||||
if (this.isPDFAttachment() && (typeof val != 'number' || val != parseInt(val))) {
|
||||
Zotero.debug(val, 2);
|
||||
throw new Error(`setAttachmentLastPageIndex() must be passed an integer`);
|
||||
}
|
||||
|
|
@ -3713,7 +3726,7 @@ Zotero.Item.prototype.getBestAttachments = Zotero.Promise.coroutine(function* ()
|
|||
|
||||
|
||||
/**
|
||||
* Return state of best attachment
|
||||
* Return state of best attachment (or this item if it's a standalone attachment)
|
||||
*
|
||||
* @return {Promise<Object>} - Promise for object with string 'type' ('none'|'pdf'|'snapshot'|'other')
|
||||
* 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'
|
||||
|
|
@ -3800,7 +3815,7 @@ for (let name of ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel',
|
|||
if (currentType && currentType != value) {
|
||||
throw new Error("Cannot change annotation type");
|
||||
}
|
||||
if (!['highlight', 'note', 'image', 'ink'].includes(value)) {
|
||||
if (!['highlight', 'underline', 'note', 'text', 'image', 'ink'].includes(value)) {
|
||||
let e = new Error(`Unknown annotation type '${value}'`);
|
||||
e.name = "ZoteroInvalidDataError";
|
||||
throw e;
|
||||
|
|
@ -3808,8 +3823,8 @@ for (let name of ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel',
|
|||
break;
|
||||
}
|
||||
case 'text':
|
||||
if (this._getLatestField('annotationType') != 'highlight') {
|
||||
throw new Error("'annotationText' can only be set for highlight annotations");
|
||||
if (!['highlight', 'underline'].includes(this._getLatestField('annotationType'))) {
|
||||
throw new Error("'annotationText' can only be set for highlight and underline annotations");
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -3823,7 +3838,7 @@ for (let name of ['type', 'authorName', 'text', 'comment', 'color', 'pageLabel',
|
|||
break;
|
||||
|
||||
case 'sortIndex':
|
||||
if (!/^\d{5}\|\d{6}\|\d{5}$/.test(value)) {
|
||||
if (!/^(\d{5}\|\d{6}\|\d{5}|\d{5}\|\d{8}|\d{8})$/.test(value)) {
|
||||
throw new Error(`Invalid sortIndex '${value}'`);
|
||||
}
|
||||
break;
|
||||
|
|
@ -3949,10 +3964,35 @@ Zotero.Item.prototype.getAnnotations = function (includeTrashed) {
|
|||
}
|
||||
var ids = rows.map(row => row.itemID);
|
||||
this._annotations[cacheKey] = ids;
|
||||
return Zotero.Items.get(ids);
|
||||
return Zotero.Items.get(ids)
|
||||
// Filter out underline and text annotations in Zotero 6
|
||||
.filter(x => !['underline', 'text'].includes(x.annotationType));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the item is a PDF attachment that exists on disk and contains
|
||||
* embedded markup annotations.
|
||||
*
|
||||
* @return {Promise<Boolean>}
|
||||
*/
|
||||
Zotero.Item.prototype.hasEmbeddedAnnotations = async function () {
|
||||
if (!this.isPDFAttachment()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = await this.getFilePathAsync();
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let contents = await Zotero.File.getContentsAsync(path);
|
||||
// Check for "markup" annotations per the PDF spec
|
||||
// https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf, p. 390
|
||||
let re = /\s\/Subtype\s+\/(Text|FreeText|Line|Square|Circle|Polygon|PolyLine|Highlight|Underline|Squiggly|StrikeOut|Stamp|Caret|Ink|FileAttachment|Sound|Redact)\s/;
|
||||
return re.test(contents);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Methods dealing with item tags
|
||||
|
|
@ -5009,7 +5049,6 @@ Zotero.Item.prototype.fromJSON = function (json, options = {}) {
|
|||
// Annotation fields
|
||||
//
|
||||
case 'annotationType':
|
||||
case 'annotationType':
|
||||
case 'annotationAuthorName':
|
||||
case 'annotationText':
|
||||
case 'annotationComment':
|
||||
|
|
@ -5189,7 +5228,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 : "");
|
||||
}
|
||||
|
|
@ -5288,7 +5327,7 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
|
|||
let type = this.annotationType;
|
||||
obj.annotationType = type;
|
||||
obj.annotationAuthorName = this.annotationAuthorName || '';
|
||||
if (type == 'highlight') {
|
||||
if (['highlight', 'underline'].includes(type)) {
|
||||
obj.annotationText = this.annotationText || '';
|
||||
}
|
||||
obj.annotationComment = this.annotationComment || '';
|
||||
|
|
@ -5408,7 +5447,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,76 @@ 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_UNDERLINE:
|
||||
type = 'underline';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_NOTE:
|
||||
type = 'note';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_TEXT:
|
||||
type = 'text';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_IMAGE:
|
||||
type = 'image';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_INK:
|
||||
type = 'ink';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown annotation type id ${typeID}`);
|
||||
}
|
||||
item._annotationType = type;
|
||||
item._annotationAuthorName = row.getResultByIndex(3);
|
||||
item._annotationText = row.getResultByIndex(4);
|
||||
item._annotationComment = row.getResultByIndex(5);
|
||||
item._annotationColor = row.getResultByIndex(6);
|
||||
item._annotationSortIndex = row.getResultByIndex(7);
|
||||
item._annotationIsExternal = !!row.getResultByIndex(8);
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_INK:
|
||||
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 +1056,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 +1109,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 +1123,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 +1524,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 +1757,40 @@ Zotero.Items = function() {
|
|||
|
||||
|
||||
/**
|
||||
* Returns an array of items with children of selected parents removed
|
||||
* Get the top-level items of all passed items
|
||||
*
|
||||
* @param {Zotero.Item[]} items
|
||||
* @return {Zotero.Item[]}
|
||||
*/
|
||||
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 +2077,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++) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.SearchConditions = new function(){
|
||||
Zotero.SearchConditions = new function () {
|
||||
this.get = get;
|
||||
this.getStandardConditions = getStandardConditions;
|
||||
this.hasOperator = hasOperator;
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
* Functions for reading files
|
||||
* @namespace
|
||||
*/
|
||||
Zotero.File = new function(){
|
||||
Zotero.File = new function () {
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
|
|
@ -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 += '/';
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Fulltext = Zotero.FullText = new function(){
|
||||
Zotero.Fulltext = Zotero.FullText = new function () {
|
||||
this.isCachedMIMEType = isCachedMIMEType;
|
||||
|
||||
this.__defineGetter__("pdfConverterCacheFile", function () { return '.zotero-ft-cache'; });
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,13 @@ const EXPORTED_DOCUMENT_MARKER = "ZOTERO_TRANSFER_DOCUMENT";
|
|||
|
||||
const NOTE_CITATION_PLACEHOLDER_LINK = 'https://www.zotero.org/?';
|
||||
|
||||
const TEMPLATE_VERSION = 1;
|
||||
const TEMPLATE_VERSIONS = {
|
||||
MacWord16: 2,
|
||||
WinWord: 1,
|
||||
OpenOffice: 1
|
||||
};
|
||||
|
||||
const MENDELEY_URI_RE = /^http:\/\/www\.mendeley\.com\/documents\/\?uuid=(.*)/;
|
||||
|
||||
|
||||
Zotero.Integration = new function() {
|
||||
|
|
@ -104,7 +110,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;
|
||||
|
|
@ -140,8 +145,8 @@ Zotero.Integration = new function() {
|
|||
* @returns {Boolean} true if integration operation should be cancelled
|
||||
*/
|
||||
this.warnOutdatedTemplate = function (agent, templateVersion) {
|
||||
const validAgents = new Set(['OpenOffice', 'MacWord2016', 'MacWord16', 'WinWord']);
|
||||
if (!validAgents.has(agent) || templateVersion >= TEMPLATE_VERSION) return false;
|
||||
const expectedTemplateVersion = TEMPLATE_VERSIONS[agent];
|
||||
if (typeof expectedTemplateVersion == 'undefined' || templateVersion >= expectedTemplateVersion) return false;
|
||||
const daysToIgnore = 30;
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const updateTemplateDelayedOn = Zotero.Prefs.get('integration.updateTemplateDelayedOn');
|
||||
|
|
@ -505,6 +510,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 +804,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 +1044,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 +1078,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 +1977,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 +2185,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 +2598,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 +2613,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 +2708,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 +2767,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 +2879,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 +2952,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 +3011,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 = {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.MIME = new function(){
|
||||
Zotero.MIME = new function () {
|
||||
this.isTextType = isTextType;
|
||||
this.getPrimaryExtension = getPrimaryExtension;
|
||||
this.sniffForBinary = sniffForBinary;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -132,8 +142,10 @@ Zotero.MIMETypeHandler = new function () {
|
|||
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
try {
|
||||
// Get the main directive of the Content-Type header
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#syntax
|
||||
var contentType = channel.getResponseHeader("Content-Type").split(';')[0].toLowerCase();
|
||||
// remove content-disposition headers for EndNote, etc.
|
||||
var contentType = channel.getResponseHeader("Content-Type").toLowerCase();
|
||||
for (let handledType of _ignoreContentDispositionTypes) {
|
||||
if (contentType.startsWith(handledType)) {
|
||||
channel.setResponseHeader("Content-Disposition", "inline", false);
|
||||
|
|
@ -214,10 +226,27 @@ Zotero.MIMETypeHandler = new function () {
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when the request is started; we ignore this
|
||||
* Called when the request is started
|
||||
*/
|
||||
_StreamListener.prototype.onStartRequest = function(channel, context) {}
|
||||
|
||||
_StreamListener.prototype.onStartRequest = async function(channel, context) {
|
||||
try {
|
||||
if (!_typeHandlers[this._contentType]) return;
|
||||
for (let handlers of _typeHandlers[this._contentType]) {
|
||||
if (!handlers.onStartRequest) continue;
|
||||
let maybePromise = handlers.onStartRequest(
|
||||
this._request.name ? this._request.name : null,
|
||||
this._contentType,
|
||||
channel
|
||||
);
|
||||
if (maybePromise && maybePromise.then) {
|
||||
maybePromise = await maybePromise;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there's data available; we collect this data and keep it until the request is
|
||||
|
|
@ -259,14 +288,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;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
Zotero.Notifier = new function(){
|
||||
Zotero.Notifier = new function () {
|
||||
// Options that apply to an entire event, not a specific object
|
||||
this.EVENT_LEVEL_OPTIONS = ['autoSyncDelay', 'skipAutoSync'];
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
Zotero.Prefs = new function(){
|
||||
Zotero.Prefs = new function () {
|
||||
// Privileged methods
|
||||
this.get = get;
|
||||
this.set = set;
|
||||
|
|
@ -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") {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue