Compare commits
8 Commits
master
...
5.0.34.6-h
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ea29c5cb10 | ||
![]() |
12c9e55472 | ||
![]() |
380ee9c0ed | ||
![]() |
4ed33b4da9 | ||
![]() |
ff9ee48e0f | ||
![]() |
e577009154 | ||
![]() |
246d7af3eb | ||
![]() |
2edf2641a2 |
3
.babelrc
3
.babelrc
|
@ -5,6 +5,7 @@
|
|||
"ignore": [
|
||||
"resource/require.js",
|
||||
"chrome/content/zotero/include.js",
|
||||
"resource/tinymce/tinymce.js",
|
||||
"chrome/content/zotero/xpcom/citeproc.js",
|
||||
"resource/csl-validator.js",
|
||||
"resource/react.js",
|
||||
|
@ -24,4 +25,4 @@
|
|||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
build
|
||||
.signatures.json
|
||||
tmp
|
||||
.signatures.json
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,24 +1,18 @@
|
|||
[submodule "translators"]
|
||||
path = translators
|
||||
url = git://github.com/zotero/translators.git
|
||||
branch = master
|
||||
[submodule "chrome/content/zotero/locale/csl"]
|
||||
path = chrome/content/zotero/locale/csl
|
||||
url = git://github.com/citation-style-language/locales.git
|
||||
branch = master
|
||||
[submodule "styles"]
|
||||
path = styles
|
||||
url = git://github.com/zotero/bundled-styles.git
|
||||
branch = master
|
||||
[submodule "test/resource/chai"]
|
||||
path = test/resource/chai
|
||||
url = https://github.com/chaijs/chai.git
|
||||
branch = master
|
||||
[submodule "test/resource/mocha"]
|
||||
path = test/resource/mocha
|
||||
url = https://github.com/mochajs/mocha.git
|
||||
branch = master
|
||||
[submodule "test/resource/chai-as-promised"]
|
||||
path = test/resource/chai-as-promised
|
||||
url = https://github.com/domenic/chai-as-promised.git
|
||||
branch = master
|
||||
|
|
|
@ -10,6 +10,7 @@ env:
|
|||
global:
|
||||
secure: "NxvkbZ7/Op7BTGQRR3C4q8lLoO29f8WtyNN27NSH7AO3H0vBr1Vp5xO8gn+H2qHEug5HvM+YrZ/xAkNXaZVbOInmBmKVMxqVvdpKp9JM1Amf+gzsXWQphfySvs6iqzyP6cwU/jspdvX/WSakgU5v7PWXxtUIaKxdANt6Rw7W+Pc="
|
||||
matrix:
|
||||
- FX_VERSION="54.0"
|
||||
- FX_VERSION="52.0.3"
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
@ -25,13 +26,17 @@ install:
|
|||
- if [ $FX_VERSION = "52.0.3" ]; then
|
||||
wget -O tarball "https://archive.mozilla.org/pub/firefox/tinderbox-builds/mozilla-release-linux64-add-on-devel/1491732920/firefox-52.0.3.en-US.linux-x86_64-add-on-devel.tar.bz2";
|
||||
fi
|
||||
- if [ $FX_VERSION = "54.0" ]; then
|
||||
wget -O tarball "https://archive.mozilla.org/pub/firefox/tinderbox-builds/mozilla-release-linux64-add-on-devel/1496944705/firefox-54.0.en-US.linux-x86_64-add-on-devel.tar.bz2";
|
||||
fi
|
||||
- tar xf tarball
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- npm i
|
||||
- npm run build
|
||||
- if [[ $TRAVIS_REPO_SLUG = "zotero/zotero" &&
|
||||
- if [[ $FX_VERSION = "54.0" &&
|
||||
$TRAVIS_REPO_SLUG = "zotero/zotero" &&
|
||||
($TRAVIS_BRANCH = "master" || $TRAVIS_BRANCH = *-hotfix) &&
|
||||
$TRAVIS_PULL_REQUEST = "false" ]]; then
|
||||
mkdir build-zip;
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
## 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. Keeping product discussions in the Zotero Forums allows the entire Zotero community to participate, including domain experts that can address many questions better than Zotero developers.
|
||||
In order to keep product discussions open to as many people as possible, Zotero does not use GitHub Issues for bug reports or feature requests. Please use the [Zotero Forums](https://forums.zotero.org) to report problems and suggest changes.
|
||||
|
||||
For confirmed bugs or agreed-upon changes, Zotero developers will create new issues in the relevant repositories.
|
||||
For confirmed bugs or agreed-upon changes, new issues will be created in the relevant repositories on GitHub by Zotero developers.
|
||||
|
||||
## Working with Zotero Code
|
||||
|
||||
|
|
7
COPYING
7
COPYING
|
@ -1,11 +1,8 @@
|
|||
Zotero is Copyright © 2018 Corporation for Digital Scholarship,
|
||||
Vienna, Virginia, USA http://digitalscholar.org
|
||||
|
||||
Copyright © 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
|
||||
Zotero is Copyright © 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
|
||||
Roy Rosenzweig Center for History and New Media, George Mason University,
|
||||
Fairfax, Virginia, USA http://zotero.org
|
||||
|
||||
The Corporation for Digital Scholarship distributes the Zotero source code
|
||||
The Roy Rosenzweig Center for History and New Media distributes the Zotero source code
|
||||
under the GNU Affero General Public License, version 3 (AGPLv3). The full text
|
||||
of this license is given below.
|
||||
|
||||
|
|
|
@ -14,10 +14,6 @@ body[multiline="true"] {
|
|||
width: 800px;
|
||||
}
|
||||
|
||||
#quick-format-dialog.progress-bar #quick-format-deck {
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
#quick-format-search {
|
||||
background: white;
|
||||
-moz-appearance: searchfield;
|
||||
|
|
|
@ -33,6 +33,6 @@ textbox
|
|||
|
||||
|
||||
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
@media (min-resolution: 1.5dppx) {
|
||||
.creator-type-label > image { list-style-image: url('chrome://zotero/skin/mac/arrow-down@2x.png'); }
|
||||
}
|
||||
|
|
|
@ -491,7 +491,7 @@ treechildren::-moz-tree-image {
|
|||
|
||||
|
||||
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
@media (min-resolution: 1.5dppx) {
|
||||
.zotero-tb-button,.zotero-tb-button:first-child,.zotero-tb-button:last-child { background: url("chrome://zotero/skin/mac/menubutton-end@2x.png") right center/auto 24px no-repeat; }
|
||||
.zotero-tb-button > .toolbarbutton-icon { background: url("chrome://zotero/skin/mac/menubutton-start@2x.png") left center/auto 24px no-repeat; }
|
||||
.zotero-tb-button:-moz-window-inactive > .toolbarbutton-icon { background: url("chrome://zotero/skin/mac/menubutton-start-inactive-window@2x.png") left center/auto 24px no-repeat; }
|
||||
|
|
|
@ -173,7 +173,7 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
|
|||
}
|
||||
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
@media (min-resolution: 1.5dppx) {
|
||||
#zotero-pane .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
}
|
||||
|
@ -228,14 +228,8 @@ tab {
|
|||
#zotero-prefs .numberbox-input-box{
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
#zotero-prefs #noteFontSize {
|
||||
min-width: 3.8em;
|
||||
}
|
||||
|
||||
#zotero-pane splitter {
|
||||
border: 0;
|
||||
/* Grippy icon missing anyway */
|
||||
#zotero-pane splitter{
|
||||
width: 6px;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,3 @@
|
|||
:root {
|
||||
--theme-border-color: #cecece;
|
||||
}
|
||||
|
||||
/* Hide horrible blue effect for menu bar and toolbar */
|
||||
#navigator-toolbox {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#zotero-pane #zotero-toolbar {
|
||||
-moz-appearance: none !important;
|
||||
margin-top: -3px;
|
||||
border-bottom-color: var(--theme-border-color);
|
||||
}
|
||||
|
||||
/*
|
||||
As of Fx36, the built-in styles don't properly handle a menu-button within combined buttons.
|
||||
|
||||
|
@ -191,7 +176,7 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
|
|||
/* End toolbar buttons */
|
||||
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
@media (min-resolution: 1.5dppx) {
|
||||
#zotero-toolbar .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
}
|
||||
|
@ -236,44 +221,13 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
|
|||
padding-left: 2px;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]),
|
||||
#zotero-items-splitter:not([state=collapsed]),
|
||||
#zotero-tags-splitter:not([state=collapsed]) {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
/* Positive z-index positions the splitter on top of its siblings and makes
|
||||
it clickable on both sides. */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]),
|
||||
#zotero-items-splitter:not([state=collapsed]):not([orient=vertical]),
|
||||
#zotero-tags-splitter:not([state=collapsed]) {
|
||||
border-inline-end: 1px solid var(--theme-border-color);
|
||||
min-width: 0;
|
||||
width: 3px;
|
||||
margin-inline-start: -3px;
|
||||
}
|
||||
|
||||
#zotero-tags-splitter:not([state=collapsed]),
|
||||
#zotero-items-splitter:not([state=collapsed])[orient=vertical] {
|
||||
border-block-end: 1px solid var(--theme-border-color);
|
||||
min-height: 0;
|
||||
height: 3px;
|
||||
margin-block-start: -3px;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter > grippy,
|
||||
#zotero-items-splitter > grippy,
|
||||
#zotero-tags-splitter > grippy {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-items-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-tags-splitter:not([state=collapsed]) > grippy {
|
||||
display: none;
|
||||
#zotero-toolbar:-moz-system-metric(windows-compositor) {
|
||||
-moz-appearance: none !important;
|
||||
background-image: -moz-linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
|
||||
background-color: rgb(207, 219, 236) !important;
|
||||
border-width: 0 0 1px 0;
|
||||
border-style: solid;
|
||||
border-color: #818790;
|
||||
}
|
||||
|
||||
#zotero-collections-tree, #zotero-items-tree, #zotero-view-item {
|
||||
|
@ -288,10 +242,6 @@ toolbar:not([id="nav-bar"]) #zotero-toolbar-buttons separator {
|
|||
-moz-border-left-colors: none;
|
||||
}
|
||||
|
||||
treechildren::-moz-tree-twisty {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* Undo tree row spacing change in Fx25 on Windows */
|
||||
#zotero-collections-tree treechildren::-moz-tree-row,
|
||||
#zotero-items-tree treechildren::-moz-tree-row,
|
||||
|
@ -299,8 +249,8 @@ treechildren::-moz-tree-twisty {
|
|||
height: 1.6em;
|
||||
}
|
||||
|
||||
tree {
|
||||
border-width: 0;
|
||||
#zotero-collections-tree {
|
||||
border-width: 0 1px 1px 0;
|
||||
}
|
||||
|
||||
/* Restore row highlighting on drag over, though I'm not sure how we're losing it to begin with. */
|
||||
|
@ -308,31 +258,15 @@ tree {
|
|||
background-color: Highlight;
|
||||
}
|
||||
|
||||
#zotero-tag-selector groupbox {
|
||||
-moz-appearance: none;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#tags-box {
|
||||
padding-top: 0.1em;
|
||||
padding-left: 0.05em;
|
||||
}
|
||||
|
||||
#tags-box button {
|
||||
margin: .04em 0 0 .15em !important;
|
||||
}
|
||||
|
||||
#zotero-editpane-tabs spacer {
|
||||
border: 0;
|
||||
#zotero-items-tree {
|
||||
border-width: 0 1px;
|
||||
}
|
||||
|
||||
#zotero-view-item {
|
||||
padding: 0 !important;
|
||||
-moz-appearance: none;
|
||||
background-color: -moz-field;
|
||||
border-width: 1px 0 0 0;
|
||||
border-color: var(--theme-border-color);
|
||||
border-width: 1px 0 0 1px;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox > tabs {
|
||||
|
@ -341,7 +275,10 @@ tree {
|
|||
|
||||
#zotero-item-pane-groupbox {
|
||||
-moz-appearance: none !important;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
border-width: 0 0 0 1px;
|
||||
border-color: #818790;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
#zotero-editpane-item-box > scrollbox, #zotero-view-item > tabpanel > vbox,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
button {
|
||||
font-family: Segoe UI, sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
|
@ -146,47 +146,31 @@ var Zotero_File_Interface_Bibliography = new function() {
|
|||
if(_io.useEndnotes && _io.useEndnotes == 1) document.getElementById("displayAs").selectedIndex = 1;
|
||||
let dialog = document.getElementById("zotero-doc-prefs-dialog");
|
||||
dialog.setAttribute('title', `${Zotero.clientName} - ${dialog.getAttribute('title')}`);
|
||||
|
||||
if (document.getElementById("formatUsing-groupbox")) {
|
||||
if (["Field", "ReferenceMark"].includes(_io.primaryFieldType)) {
|
||||
if(_io.fieldType == "Bookmark") document.getElementById("formatUsing").selectedIndex = 1;
|
||||
var formatOption = (_io.primaryFieldType == "ReferenceMark" ? "referenceMarks" : "fields");
|
||||
document.getElementById("fields").label =
|
||||
Zotero.getString("integration."+formatOption+".label");
|
||||
document.getElementById("fields-caption").textContent =
|
||||
Zotero.getString("integration."+formatOption+".caption");
|
||||
document.getElementById("fields-file-format-notice").textContent =
|
||||
Zotero.getString("integration."+formatOption+".fileFormatNotice");
|
||||
document.getElementById("bookmarks-file-format-notice").textContent =
|
||||
Zotero.getString("integration.fields.fileFormatNotice");
|
||||
} else {
|
||||
document.getElementById("formatUsing-groupbox").style.display = "none";
|
||||
_io.fieldType = _io.primaryFieldType;
|
||||
}
|
||||
}
|
||||
if(document.getElementById("formatUsing")) {
|
||||
if(_io.fieldType == "Bookmark") document.getElementById("formatUsing").selectedIndex = 1;
|
||||
var formatOption = (_io.primaryFieldType == "ReferenceMark" ? "referenceMarks" : "fields");
|
||||
document.getElementById("fields").label =
|
||||
Zotero.getString("integration."+formatOption+".label");
|
||||
document.getElementById("fields-caption").textContent =
|
||||
Zotero.getString("integration."+formatOption+".caption");
|
||||
document.getElementById("fields-file-format-notice").textContent =
|
||||
Zotero.getString("integration."+formatOption+".fileFormatNotice");
|
||||
document.getElementById("bookmarks-file-format-notice").textContent =
|
||||
Zotero.getString("integration.fields.fileFormatNotice");
|
||||
}
|
||||
if(document.getElementById("automaticJournalAbbreviations-checkbox")) {
|
||||
if(_io.automaticJournalAbbreviations === undefined) {
|
||||
_io.automaticJournalAbbreviations = Zotero.Prefs.get("cite.automaticJournalAbbreviations");
|
||||
}
|
||||
if(document.getElementById("automaticJournalAbbreviations-checkbox")) {
|
||||
if(_io.automaticJournalAbbreviations === undefined) {
|
||||
_io.automaticJournalAbbreviations = Zotero.Prefs.get("cite.automaticJournalAbbreviations");
|
||||
}
|
||||
if(_io.automaticJournalAbbreviations) {
|
||||
document.getElementById("automaticJournalAbbreviations-checkbox").checked = true;
|
||||
}
|
||||
|
||||
document.getElementById("automaticCitationUpdates-checkbox").checked = !_io.delayCitationUpdates;
|
||||
if(_io.automaticJournalAbbreviations) {
|
||||
document.getElementById("automaticJournalAbbreviations-checkbox").checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// set style to false, in case this is cancelled
|
||||
_io.style = false;
|
||||
});
|
||||
|
||||
this.openHelpLink = function() {
|
||||
var url = "https://www.zotero.org/support/word_processor_plugin_usage";
|
||||
var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator)
|
||||
.getMostRecentWindow("navigator:browser");
|
||||
Zotero.launchURL(url);
|
||||
};
|
||||
|
||||
/*
|
||||
* Called when locale is changed
|
||||
|
@ -208,18 +192,22 @@ var Zotero_File_Interface_Bibliography = new function() {
|
|||
//
|
||||
// For integrationDocPrefs.xul
|
||||
//
|
||||
if (isDocPrefs) {
|
||||
// update status of displayAs box based on style class
|
||||
|
||||
// update status of displayAs box based on style class
|
||||
if(document.getElementById("displayAs-groupbox")) {
|
||||
var isNote = selectedStyleObj.class == "note";
|
||||
var multipleNotesSupported = _io.supportedNotes.length > 1;
|
||||
document.getElementById("displayAs-groupbox").hidden = !isNote || !multipleNotesSupported;
|
||||
document.getElementById("displayAs-groupbox").hidden = !isNote;
|
||||
|
||||
// update status of formatUsing box based on style class
|
||||
if(isNote) document.getElementById("formatUsing").selectedIndex = 0;
|
||||
document.getElementById("bookmarks").disabled = isNote;
|
||||
document.getElementById("bookmarks-caption").disabled = isNote;
|
||||
|
||||
// update status of displayAs box based on style class
|
||||
if(document.getElementById("formatUsing")) {
|
||||
if(isNote) document.getElementById("formatUsing").selectedIndex = 0;
|
||||
document.getElementById("bookmarks").disabled = isNote;
|
||||
document.getElementById("bookmarks-caption").disabled = isNote;
|
||||
}
|
||||
}
|
||||
|
||||
// update status of displayAs box based on style class
|
||||
if(document.getElementById("automaticJournalAbbreviations-vbox")) {
|
||||
document.getElementById("automaticJournalAbbreviations-vbox").hidden =
|
||||
!selectedStyleObj.usesAbbreviation;
|
||||
}
|
||||
|
@ -278,7 +266,6 @@ var Zotero_File_Interface_Bibliography = new function() {
|
|||
}
|
||||
_io.useEndnotes = document.getElementById("displayAs").selectedIndex;
|
||||
_io.fieldType = (document.getElementById("formatUsing").selectedIndex == 0 ? _io.primaryFieldType : _io.secondaryFieldType);
|
||||
_io.delayCitationUpdates = !document.getElementById("automaticCitationUpdates-checkbox").checked;
|
||||
}
|
||||
|
||||
// remember style and locale if user selected these explicitly
|
||||
|
@ -296,7 +283,8 @@ var Zotero_File_Interface_Bibliography = new function() {
|
|||
document.documentElement.getButton('cancel').click();
|
||||
var win = Zotero.Utilities.Internal.openPreferences('zotero-prefpane-cite', { tab: 'styles-tab' });
|
||||
if (isDocPrefs) {
|
||||
Zotero.Utilities.Internal.activate(win);
|
||||
// TODO: Move activate() code elsewhere
|
||||
Zotero.Integration.activate(win);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -239,7 +239,6 @@
|
|||
if (this.displayURL) {
|
||||
var urlSpec = this.item.getField('url');
|
||||
urlField.setAttribute('value', urlSpec);
|
||||
urlField.setAttribute('tooltiptext', urlSpec);
|
||||
urlField.setAttribute('hidden', false);
|
||||
if (this.clickableLink) {
|
||||
urlField.onclick = function (event) {
|
||||
|
@ -260,17 +259,10 @@
|
|||
if (this.displayAccessed) {
|
||||
this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
|
||||
+ Zotero.getString('punctuation.colon');
|
||||
let val = this.item.getField('accessDate');
|
||||
if (val) {
|
||||
val = Zotero.Date.sqlToDate(val, true);
|
||||
}
|
||||
if (val) {
|
||||
this._id("accessed").value = val.toLocaleString();
|
||||
accessed.hidden = false;
|
||||
}
|
||||
else {
|
||||
accessed.hidden = true;
|
||||
}
|
||||
this._id("accessed").value = Zotero.Date.sqlToDate(
|
||||
this.item.getField('accessDate'), true
|
||||
).toLocaleString();
|
||||
accessed.hidden = false;
|
||||
}
|
||||
else {
|
||||
accessed.hidden = true;
|
||||
|
|
|
@ -1087,17 +1087,12 @@
|
|||
<method name="changeTypeTo">
|
||||
<parameter name="itemTypeID"/>
|
||||
<parameter name="menu"/>
|
||||
<body><![CDATA[
|
||||
return (async function () {
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (itemTypeID == this.item.itemTypeID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.saveOnEdit) {
|
||||
await this.blurOpenField();
|
||||
await this.item.saveTx();
|
||||
}
|
||||
|
||||
var fieldsToDelete = this.item.getFieldsNotInType(itemTypeID, true);
|
||||
|
||||
// Special cases handled below
|
||||
|
@ -1154,15 +1149,15 @@
|
|||
|
||||
if (this.saveOnEdit) {
|
||||
// See note in transformText()
|
||||
await this.blurOpenField();
|
||||
await this.item.saveTx();
|
||||
this.blurOpenField().then(() => this.item.saveTx());
|
||||
}
|
||||
else {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
if (this.eventHandlers['itemtypechange'] && this.eventHandlers['itemtypechange'].length) {
|
||||
this.eventHandlers['itemtypechange'].forEach(f => f.bind(this)());
|
||||
var self = this;
|
||||
this.eventHandlers['itemtypechange'].forEach(function (f) f.bind(self)());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1174,8 +1169,8 @@
|
|||
}
|
||||
|
||||
return false;
|
||||
}.bind(this))();
|
||||
]]></body>
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
|
@ -1274,7 +1269,6 @@
|
|||
var valueElement = document.createElement("label");
|
||||
}
|
||||
|
||||
valueElement.setAttribute('id', `itembox-field-value-${fieldName}`);
|
||||
valueElement.setAttribute('fieldname', fieldName);
|
||||
valueElement.setAttribute('flex', 1);
|
||||
|
||||
|
@ -1314,10 +1308,7 @@
|
|||
if (date) {
|
||||
// If no time, interpret as local, not UTC
|
||||
if (Zotero.Date.isSQLDate(valueText)) {
|
||||
// Add time to avoid showing previous day if date is in
|
||||
// DST (including the current date at 00:00:00) and we're
|
||||
// in standard time
|
||||
date = Zotero.Date.sqlToDate(valueText + ' 12:00:00');
|
||||
date = Zotero.Date.sqlToDate(valueText);
|
||||
valueText = date.toLocaleDateString();
|
||||
}
|
||||
else {
|
||||
|
@ -1430,7 +1421,6 @@
|
|||
return (async function () {
|
||||
Zotero.debug(`Showing editor for ${elem.getAttribute('fieldname')}`);
|
||||
|
||||
var label = Zotero.getAncestorByTagName(elem, 'row').querySelector('label');
|
||||
var lastTabIndex = this._lastTabIndex = parseInt(elem.getAttribute('ztabindex'));
|
||||
|
||||
// If a field is open, hide it before selecting the new field, which might
|
||||
|
@ -1495,7 +1485,6 @@
|
|||
}
|
||||
|
||||
var t = document.createElement("textbox");
|
||||
t.setAttribute('id', `itembox-field-textbox-${fieldName}`);
|
||||
t.setAttribute('value', value);
|
||||
t.setAttribute('fieldname', fieldName);
|
||||
t.setAttribute('ztabindex', tabindex);
|
||||
|
@ -1552,9 +1541,6 @@
|
|||
var box = elem.parentNode;
|
||||
box.replaceChild(t, elem);
|
||||
|
||||
// Associate textbox with label
|
||||
label.setAttribute('control', t.getAttribute('id'));
|
||||
|
||||
// Prevent error when clicking between a changed field
|
||||
// and another -- there's probably a better way
|
||||
if (!t.select) {
|
||||
|
@ -1628,8 +1614,8 @@
|
|||
textbox.getAttribute('fieldname').split('-');
|
||||
|
||||
if (stayFocused) {
|
||||
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex'));
|
||||
this._tabDirection = false;
|
||||
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
|
||||
this._tabDirection = 1;
|
||||
}
|
||||
|
||||
var creator = Zotero.Creators.get(creatorID);
|
||||
|
@ -1781,7 +1767,6 @@
|
|||
return (async function () {
|
||||
Zotero.debug(`Hiding editor for ${textbox.getAttribute('fieldname')}`);
|
||||
|
||||
var label = Zotero.getAncestorByTagName(textbox, 'row').querySelector('label');
|
||||
this._lastTabIndex = -1;
|
||||
|
||||
// Prevent autocomplete breakage in Firefox 3
|
||||
|
@ -1972,9 +1957,6 @@
|
|||
var box = textbox.parentNode;
|
||||
box.replaceChild(elem, textbox);
|
||||
|
||||
// Disassociate textbox from label
|
||||
label.setAttribute('control', elem.getAttribute('id'));
|
||||
|
||||
if (this.saveOnEdit) {
|
||||
await this.item.saveTx();
|
||||
}
|
||||
|
|
|
@ -152,7 +152,28 @@
|
|||
let id = relatedItem.id;
|
||||
let icon = document.createElement("image");
|
||||
icon.className = "zotero-box-icon";
|
||||
icon.setAttribute('src', relatedItem.getImageSrc());
|
||||
let type = Zotero.ItemTypes.getName(relatedItem.itemTypeID);
|
||||
if (type=='attachment')
|
||||
{
|
||||
switch (relatedItem.attaachmentLinkMode) {
|
||||
case Zotero.Attachments.LINK_MODE_LINKED_URL:
|
||||
type += '-web-link';
|
||||
break;
|
||||
|
||||
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
|
||||
type += '-snapshot';
|
||||
break;
|
||||
|
||||
case Zotero.Attachments.LINK_MODE_LINKED_FILE:
|
||||
type += '-link';
|
||||
break;
|
||||
|
||||
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
|
||||
type += '-file';
|
||||
break;
|
||||
}
|
||||
}
|
||||
icon.setAttribute('src','chrome://zotero/skin/treeitem-' + type + '.png');
|
||||
|
||||
var label = document.createElement("label");
|
||||
label.className = "zotero-box-label";
|
||||
|
|
|
@ -410,6 +410,15 @@
|
|||
}
|
||||
switch (event.type) {
|
||||
case 'keydown':
|
||||
// Intercept and manually trigger redo for Cmd-Shift-Z,
|
||||
// which keeps it from toggling the Zotero pane instead
|
||||
if (Zotero.isMac && event.metaKey && event.shiftKey && !event.ctrlKey
|
||||
&& !event.altKey && event.keyCode == 90) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.redo();
|
||||
return;
|
||||
}
|
||||
// Handle forward-delete, which doesn't register as a keypress
|
||||
// when a selection is cleared
|
||||
if (event.which == event.DOM_VK_DELETE) {
|
||||
|
@ -600,12 +609,19 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<method name="redo">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._editor.undoManager.redo();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearUndo">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._editor) {
|
||||
this._editor.undoManager.clear();
|
||||
this._editor.undoManager.add();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
|
@ -691,16 +707,10 @@
|
|||
}
|
||||
if (self._value) {
|
||||
self.value = self._value;
|
||||
|
||||
// Prevent undoing to empty note after initialization
|
||||
self._editor.undoManager.clear();
|
||||
self._editor.undoManager.add();
|
||||
}
|
||||
if (self._focus) {
|
||||
setTimeout(function () {
|
||||
self._iframe.focus();
|
||||
self._editor.focus();
|
||||
});
|
||||
self._iframe.focus();
|
||||
self._editor.focus();
|
||||
self._focus = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,8 @@ var Zotero_DownloadOverlay = new function() {
|
|||
try {
|
||||
if (item && item.getFile()) {
|
||||
timer.cancel();
|
||||
Zotero.RecognizePDF.recognizeItems([item]);
|
||||
var recognizer = new win.Zotero_RecognizePDF.ItemRecognizer();
|
||||
recognizer.recognizeItems([item]);
|
||||
}
|
||||
} catch(e) { dump(e.toSource()) };
|
||||
}, 1000, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
|
||||
|
@ -144,7 +145,15 @@ var Zotero_DownloadOverlay = new function() {
|
|||
// to happen automatically
|
||||
if(zoteroSelected) document.getElementById('rememberChoice').selected = false;
|
||||
document.getElementById('rememberChoice').disabled = zoteroSelected;
|
||||
document.getElementById('zotero-recognizePDF').disabled = !zoteroSelected;
|
||||
|
||||
// disable recognizePDF checkbox as necessary
|
||||
if(!Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
document.getElementById('zotero-noPDFTools-description').hidden = !zoteroSelected;
|
||||
document.getElementById('zotero-recognizePDF').disabled = true;
|
||||
window.sizeToContent();
|
||||
} else {
|
||||
document.getElementById('zotero-recognizePDF').disabled = !zoteroSelected;
|
||||
}
|
||||
|
||||
Zotero_DownloadOverlay.updateLibraryNote();
|
||||
};
|
||||
|
@ -203,6 +212,9 @@ var Zotero_DownloadOverlay = new function() {
|
|||
recognizePDF.label = Zotero.getString("pane.items.menu.recognizePDF");
|
||||
recognizePDF.hidden = false;
|
||||
recognizePDF.disabled = true;
|
||||
if(!Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
recognizePDF.checked = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
<vbox style="margin-left: 15px">
|
||||
<description id="zotero-saveToLibrary-description" style="font: small-caption; font-weight: normal" hidden="true">&zotero.downloadManager.saveToLibrary.description;</description>
|
||||
<checkbox id="zotero-recognizePDF" hidden="true" persist="checked" disabled="true"/>
|
||||
<description style="margin-left: 20px; font: small-caption; font-weight: normal" id="zotero-noPDFTools-description" hidden="true">&zotero.downloadManager.noPDFTools.description;</description>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</radiogroup>
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm")
|
||||
|
||||
/****Zotero_File_Exporter****
|
||||
**
|
||||
* A class to handle exporting of items, collections, or the entire library
|
||||
|
@ -208,119 +206,13 @@ var Zotero_File_Interface = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
this.getMendeleyDirectory = function () {
|
||||
Components.classes["@mozilla.org/net/osfileconstantsservice;1"]
|
||||
.getService(Components.interfaces.nsIOSFileConstantsService)
|
||||
.init();
|
||||
var path = OS.Constants.Path.homeDir;
|
||||
if (Zotero.isMac) {
|
||||
path = OS.Path.join(path, 'Library', 'Application Support', 'Mendeley Desktop');
|
||||
}
|
||||
else if (Zotero.isWin) {
|
||||
path = OS.Path.join(path, 'AppData', 'Local', 'Mendeley Ltd', 'Mendeley Desktop');
|
||||
}
|
||||
else if (Zotero.isLinux) {
|
||||
path = OS.Path.join(path, '.local', 'share', 'data', 'Mendeley Ltd.', 'Mendeley Desktop');
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid platform");
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
|
||||
this.findMendeleyDatabases = async function () {
|
||||
var dbs = [];
|
||||
try {
|
||||
var dir = this.getMendeleyDirectory();
|
||||
if (!await OS.File.exists(dir)) {
|
||||
Zotero.debug(`${dir} does not exist`);
|
||||
return dbs;
|
||||
}
|
||||
await Zotero.File.iterateDirectory(dir, function* (iterator) {
|
||||
while (true) {
|
||||
let entry = yield iterator.next();
|
||||
if (entry.isDir) continue;
|
||||
// online.sqlite, counterintuitively, is the default database before you sign in
|
||||
if (entry.name == 'online.sqlite' || entry.name.endsWith('@www.mendeley.com.sqlite')) {
|
||||
dbs.push({
|
||||
name: entry.name,
|
||||
path: entry.path,
|
||||
lastModified: null,
|
||||
size: null
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < dbs.length; i++) {
|
||||
let dbPath = OS.Path.join(dir, dbs[i].name);
|
||||
let info = await OS.File.stat(dbPath);
|
||||
dbs[i].size = info.size;
|
||||
dbs[i].lastModified = info.lastModificationDate;
|
||||
}
|
||||
dbs.sort((a, b) => {
|
||||
return b.lastModified - a.lastModified;
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
return dbs;
|
||||
};
|
||||
|
||||
|
||||
this.showImportWizard = function () {
|
||||
var libraryID = Zotero.Libraries.userLibraryID;
|
||||
try {
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
libraryID = zp.getSelectedLibraryID();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
var args = {
|
||||
libraryID
|
||||
};
|
||||
args.wrappedJSObject = args;
|
||||
|
||||
Services.ww.openWindow(null, "chrome://zotero/content/import/importWizard.xul",
|
||||
"importFile", "chrome,dialog=yes,centerscreen,width=600,height=400", args);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates Zotero.Translate instance and shows file picker for file import
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {nsIFile|string|null} [options.file=null] - File to import, or none to show a filepicker
|
||||
* @param {Boolean} [options.addToLibraryRoot=false]
|
||||
* @param {Boolean} [options.createNewCollection=true] - Put items in a new collection
|
||||
* @param {Function} [options.onBeforeImport] - Callback to receive translation object, useful
|
||||
* for displaying progress in a different way. This also causes an error to be throw
|
||||
* instead of shown in the main window.
|
||||
*/
|
||||
this.importFile = Zotero.Promise.coroutine(function* (options = {}) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options == 'string' || options instanceof Components.interfaces.nsIFile) {
|
||||
Zotero.debug("WARNING: importFile() now takes a single options object -- update your code");
|
||||
options = {
|
||||
file: options,
|
||||
createNewCollection: arguments[1]
|
||||
};
|
||||
}
|
||||
|
||||
var file = options.file ? Zotero.File.pathToFile(options.file) : null;
|
||||
var createNewCollection = options.createNewCollection;
|
||||
var addToLibraryRoot = options.addToLibraryRoot;
|
||||
var onBeforeImport = options.onBeforeImport;
|
||||
|
||||
if (createNewCollection === undefined && !addToLibraryRoot) {
|
||||
this.importFile = Zotero.Promise.coroutine(function* (file, createNewCollection) {
|
||||
if(createNewCollection === undefined) {
|
||||
createNewCollection = true;
|
||||
}
|
||||
else if (!createNewCollection) {
|
||||
} else if(!createNewCollection) {
|
||||
try {
|
||||
if (!ZoteroPane.collectionsView.editable) {
|
||||
ZoteroPane.collectionsView.selectLibrary(null);
|
||||
|
@ -328,40 +220,32 @@ var Zotero_File_Interface = new function() {
|
|||
} catch(e) {}
|
||||
}
|
||||
|
||||
var defaultNewCollectionPrefix = Zotero.getString("fileInterface.imported");
|
||||
|
||||
var translation;
|
||||
// Check if the file is an SQLite database
|
||||
var sample = yield Zotero.File.getSample(file.path);
|
||||
if (file.path == Zotero.DataDirectory.getDatabase()) {
|
||||
// Blacklist the current Zotero database, which would cause a hang
|
||||
}
|
||||
else if (Zotero.MIME.sniffForMIMEType(sample) == 'application/x-sqlite3') {
|
||||
// Mendeley import doesn't use the real translation architecture, but we create a
|
||||
// translation object with the same interface
|
||||
translation = yield _getMendeleyTranslation();
|
||||
translation.createNewCollection = createNewCollection;
|
||||
defaultNewCollectionPrefix = Zotero.getString(
|
||||
'fileInterface.appImportCollection', 'Mendeley'
|
||||
);
|
||||
}
|
||||
else if (file.path.endsWith('@www.mendeley.com.sqlite')
|
||||
|| file.path.endsWith('online.sqlite')) {
|
||||
// Keep in sync with importWizard.js
|
||||
throw new Error('Encrypted Mendeley database');
|
||||
}
|
||||
|
||||
if (!translation) {
|
||||
translation = new Zotero.Translate.Import();
|
||||
var translation = new Zotero.Translate.Import();
|
||||
if (!file) {
|
||||
let translators = yield translation.getTranslators();
|
||||
const nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(window, Zotero.getString("fileInterface.import"), nsIFilePicker.modeOpen);
|
||||
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
translators.sort((a, b) => collation.compareString(1, a.label, b.label))
|
||||
for (let translator of translators) {
|
||||
fp.appendFilter(translator.label, "*." + translator.target);
|
||||
}
|
||||
|
||||
var rv = fp.show();
|
||||
if (rv !== nsIFilePicker.returnOK && rv !== nsIFilePicker.returnReplace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file = fp.file;
|
||||
}
|
||||
|
||||
translation.setLocation(file);
|
||||
return _finishImport({
|
||||
translation,
|
||||
createNewCollection,
|
||||
addToLibraryRoot,
|
||||
defaultNewCollectionPrefix,
|
||||
onBeforeImport
|
||||
});
|
||||
yield _finishImport(translation, createNewCollection);
|
||||
});
|
||||
|
||||
|
||||
|
@ -389,10 +273,7 @@ var Zotero_File_Interface = new function() {
|
|||
}
|
||||
} catch(e) {}
|
||||
|
||||
yield _finishImport({
|
||||
translation,
|
||||
createNewCollection: false
|
||||
});
|
||||
yield _finishImport(translation, false);
|
||||
|
||||
// Select imported items
|
||||
try {
|
||||
|
@ -406,36 +287,17 @@ var Zotero_File_Interface = new function() {
|
|||
});
|
||||
|
||||
|
||||
var _finishImport = Zotero.Promise.coroutine(function* (options) {
|
||||
var t = performance.now();
|
||||
|
||||
var translation = options.translation;
|
||||
var addToLibraryRoot = options.addToLibraryRoot;
|
||||
var createNewCollection = options.createNewCollection;
|
||||
var defaultNewCollectionPrefix = options.defaultNewCollectionPrefix;
|
||||
var onBeforeImport = options.onBeforeImport;
|
||||
|
||||
if (addToLibraryRoot && createNewCollection) {
|
||||
throw new Error("Can't add to library root and create new collection");
|
||||
}
|
||||
|
||||
var showProgressWindow = !onBeforeImport;
|
||||
|
||||
var _finishImport = Zotero.Promise.coroutine(function* (translation, createNewCollection) {
|
||||
let translators = yield translation.getTranslators();
|
||||
|
||||
// Unrecognized file
|
||||
if (!translators.length) {
|
||||
if (onBeforeImport) {
|
||||
yield onBeforeImport(false);
|
||||
}
|
||||
|
||||
let ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
|
||||
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
|
||||
let index = ps.confirmEx(
|
||||
|
||||
if(!translators.length) {
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
var index = ps.confirmEx(
|
||||
null,
|
||||
Zotero.getString('general.error'),
|
||||
"",
|
||||
Zotero.getString("fileInterface.unsupportedFormat"),
|
||||
buttonFlags,
|
||||
null,
|
||||
|
@ -443,27 +305,17 @@ var Zotero_File_Interface = new function() {
|
|||
null, null, {}
|
||||
);
|
||||
if (index == 1) {
|
||||
Zotero.launchURL("https://www.zotero.org/support/kb/importing");
|
||||
ZoteroPane_Local.loadURI("http://zotero.org/support/kb/importing");
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
var libraryID = Zotero.Libraries.userLibraryID;
|
||||
var importCollection = null;
|
||||
|
||||
let importCollection = null, libraryID = Zotero.Libraries.userLibraryID;
|
||||
try {
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
libraryID = zp.getSelectedLibraryID();
|
||||
if (addToLibraryRoot) {
|
||||
yield zp.collectionsView.selectLibrary(libraryID);
|
||||
}
|
||||
else if (!createNewCollection) {
|
||||
importCollection = zp.getSelectedCollection();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
libraryID = ZoteroPane.getSelectedLibraryID();
|
||||
importCollection = ZoteroPane.getSelectedCollection();
|
||||
} catch(e) {}
|
||||
|
||||
if(createNewCollection) {
|
||||
// Create a new collection to take imported items
|
||||
let collectionName;
|
||||
|
@ -478,9 +330,8 @@ var Zotero_File_Interface = new function() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
collectionName = defaultNewCollectionPrefix + " " + (new Date()).toLocaleString();
|
||||
} else {
|
||||
collectionName = Zotero.getString("fileInterface.imported")+" "+(new Date()).toLocaleString();
|
||||
}
|
||||
importCollection = new Zotero.Collection;
|
||||
importCollection.libraryID = libraryID;
|
||||
|
@ -491,29 +342,22 @@ var Zotero_File_Interface = new function() {
|
|||
translation.setTranslator(translators[0]);
|
||||
|
||||
// Show progress popup
|
||||
var progressWin;
|
||||
var progress;
|
||||
if (showProgressWindow) {
|
||||
progressWin = new Zotero.ProgressWindow({
|
||||
closeOnClick: false
|
||||
});
|
||||
progressWin.changeHeadline(Zotero.getString('fileInterface.importing'));
|
||||
let icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
|
||||
progress = new progressWin.ItemProgress(
|
||||
icon, translation.path ? OS.Path.basename(translation.path) : translators[0].label
|
||||
);
|
||||
progressWin.show();
|
||||
|
||||
translation.setHandler("itemDone", function () {
|
||||
progress.setProgress(translation.getProgress());
|
||||
});
|
||||
|
||||
yield Zotero.Promise.delay(0);
|
||||
}
|
||||
else {
|
||||
yield onBeforeImport(translation);
|
||||
}
|
||||
var progressWin = new Zotero.ProgressWindow({
|
||||
closeOnClick: false
|
||||
});
|
||||
progressWin.changeHeadline(Zotero.getString('fileInterface.importing'));
|
||||
var icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
|
||||
let progress = new progressWin.ItemProgress(
|
||||
icon, translation.path ? OS.Path.basename(translation.path) : translators[0].label
|
||||
);
|
||||
progressWin.show();
|
||||
|
||||
translation.setHandler("itemDone", function () {
|
||||
progress.setProgress(translation.getProgress());
|
||||
});
|
||||
|
||||
yield Zotero.Promise.delay(0);
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
yield translation.translate({
|
||||
|
@ -521,10 +365,6 @@ var Zotero_File_Interface = new function() {
|
|||
collections: importCollection ? [importCollection.id] : null
|
||||
});
|
||||
} catch(e) {
|
||||
if (!showProgressWindow) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
progressWin.close();
|
||||
Zotero.logError(e);
|
||||
Zotero.alert(
|
||||
|
@ -532,61 +372,25 @@ var Zotero_File_Interface = new function() {
|
|||
Zotero.getString('general.error'),
|
||||
Zotero.getString("fileInterface.importError")
|
||||
);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
var numItems = translation.newItems.length;
|
||||
|
||||
// Show popup on completion
|
||||
if (showProgressWindow) {
|
||||
progressWin.changeHeadline(Zotero.getString('fileInterface.importComplete'));
|
||||
let icon;
|
||||
if (numItems == 1) {
|
||||
icon = translation.newItems[0].getImageSrc();
|
||||
}
|
||||
else {
|
||||
icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
|
||||
}
|
||||
let text = Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems);
|
||||
progress.setIcon(icon);
|
||||
progress.setText(text);
|
||||
// For synchronous translators, which don't update progress
|
||||
progress.setProgress(100);
|
||||
progressWin.startCloseTimer(5000);
|
||||
var numItems = translation.newItems.length;
|
||||
progressWin.changeHeadline(Zotero.getString('fileInterface.importComplete'));
|
||||
if (numItems == 1) {
|
||||
var icon = translation.newItems[0].getImageSrc();
|
||||
}
|
||||
|
||||
Zotero.debug(`Imported ${numItems} item(s) in ${performance.now() - t} ms`);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
var _getMendeleyTranslation = async function () {
|
||||
if (true) {
|
||||
Components.utils.import("chrome://zotero/content/import/mendeley/mendeleyImport.js");
|
||||
}
|
||||
// TEMP: Load uncached from ~/zotero-client for development
|
||||
else {
|
||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
||||
let file = FileUtils.getDir("Home", []);
|
||||
file = OS.Path.join(
|
||||
file.path,
|
||||
'zotero-client', 'chrome', 'content', 'zotero', 'import', 'mendeley', 'mendeleyImport.js'
|
||||
);
|
||||
let fileURI = OS.Path.toFileURI(file);
|
||||
let xmlhttp = await Zotero.HTTP.request(
|
||||
'GET',
|
||||
fileURI,
|
||||
{
|
||||
dontCache: true,
|
||||
responseType: 'text'
|
||||
}
|
||||
);
|
||||
eval(xmlhttp.response);
|
||||
var icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png';
|
||||
}
|
||||
return new Zotero_Import_Mendeley();
|
||||
}
|
||||
|
||||
var text = Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems);
|
||||
progress.setIcon(icon);
|
||||
progress.setText(text);
|
||||
// For synchronous translators, which don't update progress
|
||||
progress.setProgress(100);
|
||||
progressWin.startCloseTimer(5000);
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a bibliography from a collection or saved search
|
||||
|
|
|
@ -1,332 +0,0 @@
|
|||
var Zotero_Import_Wizard = {
|
||||
_wizard: null,
|
||||
_dbs: null,
|
||||
_file: null,
|
||||
_translation: null,
|
||||
|
||||
|
||||
init: async function () {
|
||||
this._wizard = document.getElementById('import-wizard');
|
||||
|
||||
var dbs = await Zotero_File_Interface.findMendeleyDatabases();
|
||||
if (dbs.length) {
|
||||
document.getElementById('radio-import-source-mendeley').hidden = false;
|
||||
}
|
||||
|
||||
// If no existing collections or non-trash items in the library, don't create a new
|
||||
// collection by default
|
||||
var args = window.arguments[0].wrappedJSObject;
|
||||
if (args && args.libraryID) {
|
||||
let sql = "SELECT ROWID FROM collections WHERE libraryID=?1 "
|
||||
+ "UNION "
|
||||
+ "SELECT ROWID FROM items WHERE libraryID=?1 "
|
||||
// Not in trash
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
// And not a child item (which doesn't necessarily show up in the trash)
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL) "
|
||||
+ "LIMIT 1";
|
||||
if (!await Zotero.DB.valueQueryAsync(sql, args.libraryID)) {
|
||||
document.getElementById('create-collection-checkbox').removeAttribute('checked');
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Translators.init(); // async
|
||||
},
|
||||
|
||||
|
||||
onModeChosen: async function () {
|
||||
var wizard = this._wizard;
|
||||
|
||||
var mode = document.getElementById('import-source').selectedItem.id;
|
||||
try {
|
||||
switch (mode) {
|
||||
case 'radio-import-source-file':
|
||||
await this.chooseFile();
|
||||
break;
|
||||
|
||||
case 'radio-import-source-mendeley':
|
||||
this._dbs = await Zotero_File_Interface.findMendeleyDatabases();
|
||||
// This shouldn't happen, because we only show the wizard if there are databases
|
||||
if (!this._dbs.length) {
|
||||
throw new Error("No databases found");
|
||||
}
|
||||
this._populateFileList(this._dbs);
|
||||
document.getElementById('file-options-header').textContent
|
||||
= Zotero.getString('fileInterface.chooseAppDatabaseToImport', 'Mendeley')
|
||||
wizard.goTo('page-file-list');
|
||||
wizard.canRewind = true;
|
||||
this._enableCancel();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown mode ${mode}`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this._onDone(
|
||||
Zotero.getString('general.error'),
|
||||
Zotero.getString('fileInterface.importError'),
|
||||
true
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
goToStart: function () {
|
||||
this._wizard.goTo('page-start');
|
||||
this._wizard.canAdvance = true;
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
chooseFile: async function (translation) {
|
||||
var translation = new Zotero.Translate.Import();
|
||||
var translators = await translation.getTranslators();
|
||||
const nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(window, Zotero.getString("fileInterface.import"), nsIFilePicker.modeOpen);
|
||||
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
|
||||
// Add Mendeley DB, which isn't a translator
|
||||
var mendeleyFilter = {
|
||||
label: "Mendeley Database", // TODO: Localize
|
||||
target: "*.sqlite"
|
||||
};
|
||||
var filters = [...translators];
|
||||
filters.push(mendeleyFilter);
|
||||
|
||||
filters.sort((a, b) => collation.compareString(1, a.label, b.label));
|
||||
for (let filter of filters) {
|
||||
fp.appendFilter(filter.label, "*." + filter.target);
|
||||
}
|
||||
|
||||
var rv = fp.show();
|
||||
if (rv !== nsIFilePicker.returnOK && rv !== nsIFilePicker.returnReplace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Zotero.debug(`File is ${fp.file.path}`);
|
||||
|
||||
this._file = fp.file.path;
|
||||
this._wizard.canAdvance = true;
|
||||
this._wizard.goTo('page-options');
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* When a file is clicked on in the file list
|
||||
*/
|
||||
onFileSelected: async function () {
|
||||
var index = document.getElementById('file-list').selectedIndex;
|
||||
if (index != -1) {
|
||||
this._file = this._dbs[index].path;
|
||||
this._wizard.canAdvance = true;
|
||||
}
|
||||
else {
|
||||
this._file = null;
|
||||
this._wizard.canAdvance = false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* When the user clicks "Other…" to choose a file not in the list
|
||||
*/
|
||||
chooseMendeleyDB: async function () {
|
||||
document.getElementById('file-list').selectedIndex = -1;
|
||||
const nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(window, Zotero.getString('fileInterface.import'), nsIFilePicker.modeOpen);
|
||||
fp.appendFilter("Mendeley Database", "*.sqlite"); // TODO: Localize
|
||||
var rv = fp.show();
|
||||
if (rv != nsIFilePicker.returnOK) {
|
||||
return false;
|
||||
}
|
||||
this._file = fp.file.path;
|
||||
this._wizard.canAdvance = true;
|
||||
this._wizard.advance();
|
||||
},
|
||||
|
||||
|
||||
onOptionsShown: function () {
|
||||
|
||||
},
|
||||
|
||||
|
||||
onImportStart: async function () {
|
||||
if (!this._file) {
|
||||
let index = document.getElementById('file-list').selectedIndex;
|
||||
this._file = this._dbs[index].path;
|
||||
}
|
||||
this._disableCancel();
|
||||
this._wizard.canRewind = false;
|
||||
this._wizard.canAdvance = false;
|
||||
await this.doImport({
|
||||
createNewCollection: document.getElementById('create-collection-checkbox').hasAttribute('checked')
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onBeforeImport: async function (translation) {
|
||||
// Unrecognized translator
|
||||
if (!translation) {
|
||||
// Allow error dialog to be displayed, and then close window
|
||||
setTimeout(function () {
|
||||
window.close();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._translation = translation;
|
||||
|
||||
// Switch to progress pane
|
||||
this._wizard.goTo('page-progress');
|
||||
var pm = document.getElementById('import-progressmeter');
|
||||
|
||||
translation.setHandler('itemDone', function () {
|
||||
pm.value = translation.getProgress();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
doImport: async function (options) {
|
||||
try {
|
||||
let result = await Zotero_File_Interface.importFile({
|
||||
file: this._file,
|
||||
onBeforeImport: this.onBeforeImport.bind(this),
|
||||
addToLibraryRoot: !options.createNewCollection
|
||||
});
|
||||
|
||||
// Cancelled by user or due to error
|
||||
if (!result) {
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let numItems = this._translation.newItems.length;
|
||||
this._onDone(
|
||||
Zotero.getString('fileInterface.importComplete'),
|
||||
Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems)
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message == 'Encrypted Mendeley database') {
|
||||
let url = 'https://www.zotero.org/support/kb/mendeley_import';
|
||||
this._onDone(
|
||||
Zotero.getString('general.error'),
|
||||
// TODO: Localize
|
||||
`The selected Mendeley database cannot be read, likely because it is encrypted. `
|
||||
+ `See <a href="${url}" class="text-link">How do I import a Mendeley library `
|
||||
+ `into Zotero?</a> for more information.`
|
||||
);
|
||||
}
|
||||
else {
|
||||
this._onDone(
|
||||
Zotero.getString('general.error'),
|
||||
Zotero.getString('fileInterface.importError'),
|
||||
true
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
reportError: function () {
|
||||
Zotero.getActiveZoteroPane().reportErrors();
|
||||
window.close();
|
||||
},
|
||||
|
||||
|
||||
_populateFileList: async function (files) {
|
||||
var listbox = document.getElementById('file-list');
|
||||
|
||||
// Remove existing entries
|
||||
var items = listbox.getElementsByTagName('listitem');
|
||||
for (let item of items) {
|
||||
listbox.removeChild(item);
|
||||
}
|
||||
|
||||
for (let file of files) {
|
||||
let li = document.createElement('listitem');
|
||||
|
||||
let name = document.createElement('listcell');
|
||||
// Simply filenames
|
||||
let nameStr = file.name
|
||||
.replace(/\.sqlite$/, '')
|
||||
.replace(/@www\.mendeley\.com$/, '');
|
||||
if (nameStr == 'online') {
|
||||
nameStr = Zotero.getString('dataDir.default', 'online.sqlite');
|
||||
}
|
||||
name.setAttribute('label', nameStr + ' ');
|
||||
li.appendChild(name);
|
||||
|
||||
let lastModified = document.createElement('listcell');
|
||||
lastModified.setAttribute('label', file.lastModified.toLocaleString() + ' ');
|
||||
li.appendChild(lastModified);
|
||||
|
||||
let size = document.createElement('listcell');
|
||||
size.setAttribute(
|
||||
'label',
|
||||
Zotero.getString('general.nMegabytes', (file.size / 1024 / 1024).toFixed(1)) + ' '
|
||||
);
|
||||
li.appendChild(size);
|
||||
|
||||
listbox.appendChild(li);
|
||||
}
|
||||
|
||||
if (files.length == 1) {
|
||||
listbox.selectedIndex = 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_enableCancel: function () {
|
||||
this._wizard.getButton('cancel').disabled = false;
|
||||
},
|
||||
|
||||
|
||||
_disableCancel: function () {
|
||||
this._wizard.getButton('cancel').disabled = true;
|
||||
},
|
||||
|
||||
|
||||
_onDone: function (label, description, showReportErrorButton) {
|
||||
var wizard = this._wizard;
|
||||
wizard.getPageById('page-done').setAttribute('label', label);
|
||||
|
||||
var xulElem = document.getElementById('result-description');
|
||||
var htmlElem = document.getElementById('result-description-html');
|
||||
|
||||
if (description.includes('href')) {
|
||||
htmlElem.innerHTML = description;
|
||||
Zotero.Utilities.Internal.updateHTMLInXUL(htmlElem);
|
||||
xulElem.hidden = true;
|
||||
htmlElem.setAttribute('display', 'block');
|
||||
}
|
||||
else {
|
||||
xulElem.textContent = description;
|
||||
xulElem.hidden = false;
|
||||
htmlElem.setAttribute('display', 'none');
|
||||
}
|
||||
document.getElementById('result-description')
|
||||
|
||||
if (showReportErrorButton) {
|
||||
let button = document.getElementById('result-report-error');
|
||||
button.setAttribute('label', Zotero.getString('errorReport.reportError'));
|
||||
button.hidden = false;
|
||||
}
|
||||
|
||||
// When done, move to last page and allow closing
|
||||
wizard.canAdvance = true;
|
||||
wizard.goTo('page-done');
|
||||
wizard.canRewind = false;
|
||||
}
|
||||
};
|
|
@ -1,75 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/importWizard.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<wizard id="import-wizard"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
title="&zotero.import;"
|
||||
onload="Zotero_Import_Wizard.init()">
|
||||
|
||||
<script src="../include.js"/>
|
||||
<script src="../fileInterface.js"/>
|
||||
<script src="importWizard.js"/>
|
||||
|
||||
<wizardpage pageid="page-start"
|
||||
label="&zotero.import.whereToImportFrom;"
|
||||
next="page-options"
|
||||
onpageadvanced="Zotero_Import_Wizard.onModeChosen(); return false;">
|
||||
<radiogroup id="import-source">
|
||||
<radio id="radio-import-source-file" label="&zotero.import.source.file;"/>
|
||||
<radio id="radio-import-source-mendeley" label="Mendeley" hidden="true"/>
|
||||
</radiogroup>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-file-list"
|
||||
next="page-options"
|
||||
onpagerewound="return Zotero_Import_Wizard.goToStart()">
|
||||
<description id="file-options-header"/>
|
||||
<listbox id="file-list" onselect="Zotero_Import_Wizard.onFileSelected()">
|
||||
<listhead>
|
||||
<listheader label="&zotero.import.database;"/>
|
||||
<listheader label="&zotero.import.lastModified;"/>
|
||||
<listheader label="&zotero.import.size;"/>
|
||||
</listhead>
|
||||
|
||||
<listcols>
|
||||
<listcol flex="1"/>
|
||||
<listcol/>
|
||||
<listcol/>
|
||||
</listcols>
|
||||
</listbox>
|
||||
<hbox>
|
||||
<button label="&zotero.general.other;" oncommand="Zotero_Import_Wizard.chooseMendeleyDB()"/>
|
||||
</hbox>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-options"
|
||||
label="&zotero.general.options;"
|
||||
next="page-progress"
|
||||
onpageshow="Zotero_Import_Wizard.onOptionsShown()"
|
||||
onpagerewound="return Zotero_Import_Wizard.goToStart()"
|
||||
onpageadvanced="Zotero_Import_Wizard.onImportStart()">
|
||||
<checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" />
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-progress"
|
||||
label="&zotero.import.importing;"
|
||||
onpageshow="document.getElementById('import-wizard').canRewind = false;"
|
||||
next="page-done">
|
||||
<progressmeter id="import-progressmeter" mode="determined"/>
|
||||
</wizardpage>
|
||||
|
||||
<wizardpage pageid="page-done">
|
||||
<description id="result-description"/>
|
||||
<html:div id="result-description-html"/>
|
||||
<hbox>
|
||||
<button id="result-report-error"
|
||||
oncommand="Zotero_Import_Wizard.reportError()"
|
||||
hidden="true"/>
|
||||
</hbox>
|
||||
</wizardpage>
|
||||
</wizard>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,102 +0,0 @@
|
|||
var map = {
|
||||
83: {
|
||||
itemTypes: {
|
||||
Bill: "bill",
|
||||
Book: "book",
|
||||
BookSection: "bookSection",
|
||||
Case: "case",
|
||||
ComputerProgram: "computerProgram",
|
||||
ConferenceProceedings: "conferencePaper",
|
||||
EncyclopediaArticle: "encyclopediaArticle",
|
||||
Film: "film",
|
||||
Generic: "document",
|
||||
JournalArticle: "journalArticle",
|
||||
MagazineArticle: "magazineArticle",
|
||||
NewspaperArticle: "newspaperArticle",
|
||||
Patent: "patent",
|
||||
Report: "report",
|
||||
Statute: "statute",
|
||||
TelevisionBroadcast: "tvBroadcast",
|
||||
Thesis: "thesis",
|
||||
WebPage: "webpage",
|
||||
WorkingPaper: "report"
|
||||
},
|
||||
fields: {
|
||||
id: "",
|
||||
uuid: "",
|
||||
reviewedArticle: "",
|
||||
revisionNumber: "",
|
||||
publisher: "publisher",
|
||||
reprintEdition: "",
|
||||
series: "seriesTitle",
|
||||
seriesNumber: "seriesNumber",
|
||||
sections: "section",
|
||||
seriesEditor: "creator[seriesEditor]", // falls back to editor if necessary
|
||||
owner: "",
|
||||
pages: "func[pages]",
|
||||
month: "", // handled explicitly
|
||||
originalPublication: "",
|
||||
publication: "publicationTitle",
|
||||
publicLawNumber: "publicLawNumber",
|
||||
pmid: "extra[PMID]",
|
||||
sourceType: "",
|
||||
session: "session",
|
||||
shortTitle: "shortTitle",
|
||||
volume: "volume",
|
||||
year: "", // handled explicitly
|
||||
userType: "type",
|
||||
country: "place[country]",
|
||||
dateAccessed: "accessDate",
|
||||
committee: "committee",
|
||||
counsel: "creator[counsel]",
|
||||
doi: "DOI",
|
||||
edition: "edition",
|
||||
day: "", // handled explicitly
|
||||
department: "",
|
||||
citationKey: "citationKey", // put in Extra
|
||||
city: "place[city]",
|
||||
chapter: "",
|
||||
codeSection: "section",
|
||||
codeVolume: "codeVolume",
|
||||
code: "code",
|
||||
codeNumber: "codeNumber",
|
||||
issue: "issue",
|
||||
language: "language",
|
||||
isbn: "ISBN",
|
||||
issn: "ISSN",
|
||||
length: "",
|
||||
medium: "medium",
|
||||
lastUpdate: "",
|
||||
legalStatus: "legalStatus",
|
||||
hideFromMendeleyWebIndex: "",
|
||||
institution: "publisher",
|
||||
genre: "genre",
|
||||
internationalTitle: "",
|
||||
internationalUserType: "",
|
||||
internationalAuthor: "",
|
||||
internationalNumber: "",
|
||||
deletionPending: "",
|
||||
favourite: "", // tag?
|
||||
confirmed: "", // tag?
|
||||
deduplicated: "",
|
||||
read: "", // tag?
|
||||
type: "", // item type handled separately
|
||||
title: "title",
|
||||
privacy: "",
|
||||
applicationNumber: "applicationNumber",
|
||||
arxivId: "extra[arXiv]",
|
||||
advisor: "",
|
||||
articleColumn: "",
|
||||
modified: "func[fromUnixtime:dateModified]",
|
||||
abstract: "abstractNote",
|
||||
added: "func[fromUnixtime:dateAdded]",
|
||||
note: "func[note]",
|
||||
importer: ""
|
||||
},
|
||||
creatorTypes: {
|
||||
DocumentAuthor: "author",
|
||||
DocumentEditor: "editor",
|
||||
DocumentTranslator: "translator"
|
||||
}
|
||||
}
|
||||
};
|
|
@ -59,8 +59,10 @@ var Zotero_Citation_Dialog = new function () {
|
|||
this.listItemSelected = listItemSelected;
|
||||
this.up = up;
|
||||
this.down = down;
|
||||
this.add = add;
|
||||
this.remove = remove;
|
||||
this.setSortToggle = setSortToggle;
|
||||
this.citationSortUnsort = citationSortUnsort;
|
||||
this.confirmRegenerate = confirmRegenerate;
|
||||
this.accept = accept;
|
||||
this.cancel = cancel;
|
||||
|
@ -371,13 +373,13 @@ var Zotero_Citation_Dialog = new function () {
|
|||
/*
|
||||
* Adds an item to the multipleSources list
|
||||
*/
|
||||
this.add = Zotero.Promise.coroutine(function* (first_item) {
|
||||
function add(first_item) {
|
||||
|
||||
var pos, len;
|
||||
var item = itemsView.getSelectedItems()[0]; // treeview from xpcom/itemTreeView.js
|
||||
|
||||
if (!item) {
|
||||
yield sortCitation();
|
||||
sortCitation();
|
||||
_updateAccept();
|
||||
_updatePreview();
|
||||
return;
|
||||
|
@ -410,11 +412,11 @@ var Zotero_Citation_Dialog = new function () {
|
|||
_citationList.ensureElementIsVisible(selectionNode);
|
||||
|
||||
// allow user to press OK
|
||||
selectionNode = yield sortCitation(selectionNode);
|
||||
selectionNode = sortCitation(selectionNode);
|
||||
_citationList.selectItem(selectionNode);
|
||||
_updateAccept();
|
||||
_updatePreview();
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Deletes a citation from the multipleSources list
|
||||
|
@ -444,11 +446,11 @@ var Zotero_Citation_Dialog = new function () {
|
|||
/*
|
||||
* Sorts preview citations, if preview is open.
|
||||
*/
|
||||
this.citationSortUnsort = Zotero.Promise.coroutine(function* () {
|
||||
function citationSortUnsort() {
|
||||
setSortToggle();
|
||||
yield sortCitation();
|
||||
sortCitation();
|
||||
_updatePreview();
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the current sort toggle state persistently on the citation.
|
||||
|
@ -466,7 +468,7 @@ var Zotero_Citation_Dialog = new function () {
|
|||
/*
|
||||
* Sorts the list of citations
|
||||
*/
|
||||
var sortCitation = Zotero.Promise.coroutine(function* (scrollToItem) {
|
||||
function sortCitation(scrollToItem) {
|
||||
if(!_sortCheckbox) return scrollToItem;
|
||||
if(!_sortCheckbox.checked) {
|
||||
io.citation.properties.unsorted = true;
|
||||
|
@ -483,7 +485,7 @@ var Zotero_Citation_Dialog = new function () {
|
|||
|
||||
// run preview function to re-sort, if it hasn't already been
|
||||
// run
|
||||
yield io.sort();
|
||||
io.sort();
|
||||
|
||||
// add items back to list
|
||||
scrollToItem = null;
|
||||
|
@ -500,7 +502,7 @@ var Zotero_Citation_Dialog = new function () {
|
|||
|
||||
if(scrollToItem) _citationList.ensureElementIsVisible(scrollToItem);
|
||||
return scrollToItem;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Ask whether to modifiy the preview
|
||||
|
|
|
@ -74,8 +74,8 @@ var Zotero_Bibliography_Dialog = new function () {
|
|||
if(selectedItemIDs.length) {
|
||||
for (let itemID of selectedItemIDs) {
|
||||
var itemIndexToSelect = false;
|
||||
for(var i in bibEditInterface.bib[0].entry_ids) {
|
||||
if(bibEditInterface.bib[0].entry_ids[i].indexOf(itemID) !== -1) {
|
||||
for(var i in bibEditInterface.bibliography[0].entry_ids) {
|
||||
if(bibEditInterface.bibliography[0].entry_ids[i].indexOf(itemID) !== -1) {
|
||||
itemIndexToSelect = i;
|
||||
continue;
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ var Zotero_Bibliography_Dialog = new function () {
|
|||
*/
|
||||
function _getSelectedListItemIDs() {
|
||||
return Array.from(_itemList.selectedItems)
|
||||
.map(item => bibEditInterface.bib[0].entry_ids[item.value][0]);
|
||||
.map(item => bibEditInterface.bibliography[0].entry_ids[item.value][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -287,8 +287,8 @@ var Zotero_Bibliography_Dialog = new function () {
|
|||
|
||||
editor.readonly = index === undefined;
|
||||
if(index !== undefined) {
|
||||
var itemID = bibEditInterface.bib[0].entry_ids[index];
|
||||
editor.value = bibEditInterface.bib[1][index];
|
||||
var itemID = bibEditInterface.bibliography[0].entry_ids[index];
|
||||
editor.value = bibEditInterface.bibliography[1][index];
|
||||
_lastSelectedIndex = index;
|
||||
_lastSelectedItemID = itemID;
|
||||
_lastSelectedValue = editor.value;
|
||||
|
@ -304,7 +304,7 @@ var Zotero_Bibliography_Dialog = new function () {
|
|||
* loads items from itemSet
|
||||
*/
|
||||
function _loadItems() {
|
||||
var itemIDs = bibEditInterface.bib[0].entry_ids;
|
||||
var itemIDs = bibEditInterface.bibliography[0].entry_ids;
|
||||
var items = itemIDs.map(itemID => Zotero.Cite.getItem(itemID[0]));
|
||||
|
||||
// delete all existing items from list
|
||||
|
|
|
@ -31,11 +31,10 @@
|
|||
<dialog
|
||||
id="zotero-doc-prefs-dialog"
|
||||
orient="vertical"
|
||||
buttons="accept,cancel,help"
|
||||
buttons="accept,cancel"
|
||||
title="&zotero.integration.docPrefs.title;"
|
||||
onload="Zotero_File_Interface_Bibliography.init();"
|
||||
ondialogaccept="Zotero_File_Interface_Bibliography.acceptSelection();"
|
||||
ondialoghelp="Zotero_File_Interface_Bibliography.openHelpLink();"
|
||||
onclose="document.documentElement.cancelDialog(); event.preventDefault(); event.stopPropagation();"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
persist="screenX screenY"
|
||||
|
@ -53,7 +52,7 @@
|
|||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="locale-box">
|
||||
<groupbox>
|
||||
<hbox align="center">
|
||||
<caption label="&zotero.bibliography.locale.label;"/>
|
||||
<menulist id="locale-menu" oncommand="Zotero_File_Interface_Bibliography.localeChanged(this.selectedItem.value)"/>
|
||||
|
@ -68,7 +67,7 @@
|
|||
</radiogroup>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="formatUsing-groupbox">
|
||||
<groupbox>
|
||||
<caption label="&zotero.integration.prefs.formatUsing.label;"/>
|
||||
|
||||
<radiogroup id="formatUsing" orient="vertical">
|
||||
|
@ -85,10 +84,5 @@
|
|||
<checkbox id="automaticJournalAbbreviations-checkbox" label="&zotero.integration.prefs.automaticJournalAbbeviations.label;"/>
|
||||
<description class="radioDescription">&zotero.integration.prefs.automaticJournalAbbeviations.caption;</description>
|
||||
</vbox>
|
||||
|
||||
<vbox id="automaticCitationUpdates-vbox">
|
||||
<checkbox id="automaticCitationUpdates-checkbox" label="&zotero.integration.prefs.automaticCitationUpdates.label;" tooltiptext="&zotero.integration.prefs.automaticCitationUpdates.tooltip;"/>
|
||||
<description class="radioDescription">&zotero.integration.prefs.automaticCitationUpdates.description;</description>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2018 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var Zotero_ProgressBar = new function () {
|
||||
var initialized, io;
|
||||
|
||||
/**
|
||||
* Pre-initialization, when the dialog has loaded but has not yet appeared
|
||||
*/
|
||||
this.onDOMContentLoaded = function(event) {
|
||||
if(event.target === document) {
|
||||
initialized = true;
|
||||
io = window.arguments[0].wrappedJSObject;
|
||||
if (io.onLoad) {
|
||||
io.onLoad(_onProgress);
|
||||
}
|
||||
|
||||
// Only hide chrome on Windows or Mac
|
||||
if(Zotero.isMac) {
|
||||
document.documentElement.setAttribute("drawintitlebar", true);
|
||||
} else if(Zotero.isWin) {
|
||||
document.documentElement.setAttribute("hidechrome", true);
|
||||
}
|
||||
|
||||
new WindowDraggingElement(document.getElementById("quick-format-dialog"), window);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Center the window
|
||||
*/
|
||||
this.onLoad = function(event) {
|
||||
if(event.target !== document) return;
|
||||
// make sure we are visible
|
||||
window.focus();
|
||||
window.setTimeout(function() {
|
||||
var targetX = Math.floor(-window.outerWidth/2 + (window.screen.width / 2));
|
||||
var targetY = Math.floor(-window.outerHeight/2 + (window.screen.height / 2));
|
||||
Zotero.debug("Moving window to "+targetX+", "+targetY);
|
||||
window.moveTo(targetX, targetY);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when progress changes
|
||||
*/
|
||||
function _onProgress(percent) {
|
||||
var meter = document.getElementById("quick-format-progress-meter");
|
||||
if(percent === null) {
|
||||
meter.mode = "undetermined";
|
||||
} else {
|
||||
meter.mode = "determined";
|
||||
meter.value = Math.round(percent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes windows
|
||||
* @constructor
|
||||
*/
|
||||
var Resizer = function(panel, targetWidth, targetHeight, pixelsPerStep, stepsPerSecond) {
|
||||
this.panel = panel;
|
||||
this.curWidth = panel.clientWidth;
|
||||
this.curHeight = panel.clientHeight;
|
||||
this.difX = (targetWidth ? targetWidth - this.curWidth : 0);
|
||||
this.difY = (targetHeight ? targetHeight - this.curHeight : 0);
|
||||
this.step = 0;
|
||||
this.steps = Math.ceil(Math.max(Math.abs(this.difX), Math.abs(this.difY))/pixelsPerStep);
|
||||
this.timeout = (1000/stepsPerSecond);
|
||||
|
||||
var me = this;
|
||||
this._animateCallback = function() { me.animate() };
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs a step of the animation
|
||||
*/
|
||||
Resizer.prototype.animate = function() {
|
||||
if(this.stopped) return;
|
||||
this.step++;
|
||||
this.panel.sizeTo(this.curWidth+Math.round(this.step*this.difX/this.steps),
|
||||
this.curHeight+Math.round(this.step*this.difY/this.steps));
|
||||
if(this.step !== this.steps) {
|
||||
window.setTimeout(this._animateCallback, this.timeout);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Halts resizing
|
||||
*/
|
||||
Resizer.prototype.stop = function() {
|
||||
this.stopped = true;
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded", Zotero_ProgressBar.onDOMContentLoaded, false);
|
||||
window.addEventListener("load", Zotero_ProgressBar.onLoad, false);
|
|
@ -1,51 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2018 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
-->
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/browser.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/integration.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/integration.css" type="text/css"?>
|
||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<window
|
||||
id="quick-format-dialog"
|
||||
class="progress-bar"
|
||||
orient="vertical"
|
||||
title="&zotero.progress.title;"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
persist="screenX screenY">
|
||||
|
||||
<script src="../include.js"/>
|
||||
<script src="windowDraggingUtils.js" type="text/javascript"/>
|
||||
<script src="progressBar.js" type="text/javascript"/>
|
||||
|
||||
<box orient="horizontal" id="quick-format-entry">
|
||||
<deck id="quick-format-deck" selectedIndex="0" flex="1">
|
||||
<progressmeter id="quick-format-progress-meter" mode="undetermined" value="0" flex="1"/>
|
||||
</deck>
|
||||
</box>
|
||||
</window>
|
|
@ -179,7 +179,6 @@ var Zotero_QuickFormat = new function () {
|
|||
*/
|
||||
function _getCurrentEditorTextNode() {
|
||||
var selection = qfiWindow.getSelection();
|
||||
if (!selection) return false;
|
||||
var range = selection.getRangeAt(0);
|
||||
|
||||
var node = range.startContainer;
|
||||
|
@ -712,7 +711,7 @@ var Zotero_QuickFormat = new function () {
|
|||
/**
|
||||
* Converts the selected item to a bubble
|
||||
*/
|
||||
var _bubbleizeSelected = Zotero.Promise.coroutine(function* () {
|
||||
function _bubbleizeSelected() {
|
||||
if(!referenceBox.hasChildNodes() || !referenceBox.selectedItem) return false;
|
||||
|
||||
var citationItem = {"id":referenceBox.selectedItem.getAttribute("zotero-item")};
|
||||
|
@ -735,11 +734,11 @@ var Zotero_QuickFormat = new function () {
|
|||
node.nodeValue = "";
|
||||
var bubble = _insertBubble(citationItem, node);
|
||||
_clearEntryList();
|
||||
yield _previewAndSort();
|
||||
_previewAndSort();
|
||||
_refocusQfe();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignores clicks (for use on separators in the rich list box)
|
||||
|
@ -903,13 +902,13 @@ var Zotero_QuickFormat = new function () {
|
|||
/**
|
||||
* Generates the preview and sorts citations
|
||||
*/
|
||||
var _previewAndSort = Zotero.Promise.coroutine(function* () {
|
||||
function _previewAndSort() {
|
||||
var shouldKeepSorted = keepSorted.hasAttribute("checked"),
|
||||
editorShowing = showEditor.hasAttribute("checked");
|
||||
if(!shouldKeepSorted && !editorShowing) return;
|
||||
|
||||
_updateCitationObject();
|
||||
yield io.sort();
|
||||
io.sort();
|
||||
if(shouldKeepSorted) {
|
||||
// means we need to resort citations
|
||||
_clearCitation();
|
||||
|
@ -921,7 +920,7 @@ var Zotero_QuickFormat = new function () {
|
|||
|
||||
_moveCursorToEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the citation properties panel for a given bubble
|
||||
|
@ -1072,7 +1071,7 @@ var Zotero_QuickFormat = new function () {
|
|||
/**
|
||||
* Handle return or escape
|
||||
*/
|
||||
var _onQuickSearchKeyPress = Zotero.Promise.coroutine(function* (event) {
|
||||
function _onQuickSearchKeyPress(event) {
|
||||
// Prevent hang if another key is pressed after Enter
|
||||
// https://forums.zotero.org/discussion/59157/
|
||||
if (accepted) {
|
||||
|
@ -1084,7 +1083,7 @@ var Zotero_QuickFormat = new function () {
|
|||
var keyCode = event.keyCode;
|
||||
if (keyCode === event.DOM_VK_RETURN) {
|
||||
event.preventDefault();
|
||||
if(!(yield _bubbleizeSelected()) && !_getEditorContent()) {
|
||||
if(!_bubbleizeSelected() && !_getEditorContent()) {
|
||||
_accept();
|
||||
}
|
||||
} else if(keyCode === event.DOM_VK_TAB || event.charCode === 59 /* ; */) {
|
||||
|
@ -1163,7 +1162,7 @@ var Zotero_QuickFormat = new function () {
|
|||
} else {
|
||||
_resetSearchTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dummy element to make dragging work
|
||||
|
@ -1191,7 +1190,7 @@ var Zotero_QuickFormat = new function () {
|
|||
/**
|
||||
* Replaces the dummy element with a node to make dropping work
|
||||
*/
|
||||
var _onBubbleDrop = Zotero.Promise.coroutine(function* (event) {
|
||||
function _onBubbleDrop(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
|
@ -1209,9 +1208,9 @@ var Zotero_QuickFormat = new function () {
|
|||
keepSorted.removeAttribute("checked");
|
||||
}
|
||||
|
||||
yield _previewAndSort();
|
||||
_previewAndSort();
|
||||
_moveCursorToEnd();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a click on a bubble
|
||||
|
@ -1334,7 +1333,7 @@ var Zotero_QuickFormat = new function () {
|
|||
pane.selectItem(id);
|
||||
|
||||
// Pull window to foreground
|
||||
Zotero.Utilities.Internal.activate(pane.document.defaultView);
|
||||
Zotero.Integration.activate(pane.document.defaultView);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
var ZoteroItemPane = new function() {
|
||||
var _lastItem, _itemBox, _notesLabel, _notesButton, _notesList, _tagsBox, _relatedBox;
|
||||
var _selectedNoteID;
|
||||
var _translationTarget;
|
||||
var _noteIDs;
|
||||
|
||||
|
@ -224,8 +223,6 @@ var ZoteroItemPane = new function() {
|
|||
|
||||
|
||||
this.onNoteSelected = function (item, editable) {
|
||||
_selectedNoteID = item.id;
|
||||
|
||||
// If an external note window is open for this item, don't show the editor
|
||||
if (ZoteroPane.findNoteWindow(item.id)) {
|
||||
this.showNoteWindowMessage();
|
||||
|
@ -261,20 +258,20 @@ var ZoteroItemPane = new function() {
|
|||
* Select the parent item and open the note editor
|
||||
*/
|
||||
this.openNoteWindow = async function () {
|
||||
var selectedNote = Zotero.Items.get(_selectedNoteID);
|
||||
|
||||
var noteEditor = document.getElementById('zotero-note-editor');
|
||||
var item = noteEditor.item;
|
||||
// We don't want to show the note in two places, since it causes unnecessary UI updates
|
||||
// and can result in weird bugs where note content gets lost.
|
||||
//
|
||||
// If this is a child note, select the parent
|
||||
if (selectedNote.parentID) {
|
||||
await ZoteroPane.selectItem(selectedNote.parentID);
|
||||
if (item.parentID) {
|
||||
await ZoteroPane.selectItem(item.parentID);
|
||||
}
|
||||
// Otherwise, hide note and replace with a message that we're editing externally
|
||||
else {
|
||||
this.showNoteWindowMessage();
|
||||
}
|
||||
ZoteroPane.openNoteWindow(selectedNote.id);
|
||||
ZoteroPane.openNoteWindow(item.id);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b8c370c8a978790d2aeefa302f05f3bfb1478e75
|
||||
Subproject commit 15396c0c18b768f0837015091bae9c931d4dd56b
|
|
@ -206,20 +206,14 @@ var Zotero_Lookup = new function () {
|
|||
}
|
||||
|
||||
this.toggleProgress = function(on) {
|
||||
// In Firefox 52.6.0, progressmeters burn CPU at idle on Linux when undetermined, even
|
||||
// if they're hidden. (Being hidden is enough on macOS.)
|
||||
var mode = on ? 'undetermined' : 'determined';
|
||||
|
||||
//single line
|
||||
var txtBox = document.getElementById("zotero-lookup-textbox");
|
||||
txtBox.style.opacity = on ? 0.5 : 1;
|
||||
txtBox.disabled = !!on;
|
||||
var p1 = document.getElementById("zotero-lookup-progress");
|
||||
p1.mode = mode;
|
||||
document.getElementById("zotero-lookup-progress").setAttribute("collapsed", !on);
|
||||
|
||||
//multiline
|
||||
document.getElementById("zotero-lookup-multiline-textbox").disabled = !!on;
|
||||
var p2 = document.getElementById("zotero-lookup-multiline-progress");
|
||||
p2.mode = mode;
|
||||
document.getElementById("zotero-lookup-multiline-progress").setAttribute("collapsed", !on);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,18 @@ async function onLoad() {
|
|||
|
||||
if (itemID) {
|
||||
var ref = await Zotero.Items.getAsync(itemID);
|
||||
|
||||
// If loading new or different note, disable undo while we repopulate the text field
|
||||
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
|
||||
// undo content from another note into the current one.
|
||||
let clearUndo = noteEditor.item ? noteEditor.item.id != itemID : false;
|
||||
|
||||
noteEditor.item = ref;
|
||||
|
||||
if (clearUndo) {
|
||||
noteEditor.clearUndo();
|
||||
}
|
||||
|
||||
document.title = ref.getNoteTitle();
|
||||
}
|
||||
else {
|
||||
|
@ -59,7 +70,6 @@ async function onLoad() {
|
|||
noteEditor.refresh();
|
||||
}
|
||||
|
||||
noteEditor.focus();
|
||||
notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item', 'noteWindow');
|
||||
}
|
||||
|
||||
|
@ -85,9 +95,12 @@ function onUnload() {
|
|||
|
||||
var NotifyCallback = {
|
||||
notify: function(action, type, ids){
|
||||
if (noteEditor.item && ids.includes(noteEditor.item.id)) {
|
||||
if (noteEditor.item && ids.indexOf(noteEditor.item.id) != -1) {
|
||||
// If the document title hasn't yet been set, reset undo so
|
||||
// undoing to empty isn't possible
|
||||
var noteTitle = noteEditor.item.getNoteTitle();
|
||||
if (!document.title && noteTitle != '') {
|
||||
noteEditor.clearUndo();
|
||||
document.title = noteTitle;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
title="&zotero.progress.title;" width="550" height="230"
|
||||
id="zotero-progress">
|
||||
<vbox style="padding:10px" flex="1">
|
||||
<label id="label" control="progress-indicator" value=""/>
|
||||
<label id="label" control="progress-indicator" value="&zotero.recognizePDF.recognizing.label;"/>
|
||||
<hbox align="center">
|
||||
<progressmeter id="progress-indicator" mode="determined" flex="1"/>
|
||||
<button id="cancel-button" label="&zotero.general.cancel;"/>
|
||||
<button id="minimize-button" label="&zotero.general.minimize;"/>
|
||||
<button id="close-button" label="&zotero.general.close;"/>
|
||||
<button id="cancel-button" label="&zotero.recognizePDF.cancel.label;"/>
|
||||
</hbox>
|
||||
<tree flex="1" id="tree" hidecolumnpicker="true">
|
||||
<treecols>
|
|
@ -42,28 +42,6 @@ Zotero_Preferences.Advanced = {
|
|||
},
|
||||
|
||||
|
||||
updateTranslators: Zotero.Promise.coroutine(function* () {
|
||||
var updated = yield Zotero.Schema.updateFromRepository(Zotero.Schema.REPO_UPDATE_MANUAL);
|
||||
var button = document.getElementById('updateButton');
|
||||
if (button) {
|
||||
if (updated===-1) {
|
||||
var label = Zotero.getString('zotero.preferences.update.upToDate');
|
||||
}
|
||||
else if (updated) {
|
||||
var label = Zotero.getString('zotero.preferences.update.updated');
|
||||
}
|
||||
else {
|
||||
var label = Zotero.getString('zotero.preferences.update.error');
|
||||
}
|
||||
button.setAttribute('label', label);
|
||||
|
||||
if (updated && Zotero_Preferences.Cite) {
|
||||
yield Zotero_Preferences.Cite.refreshStylesList();
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
migrateDataDirectory: Zotero.Promise.coroutine(function* () {
|
||||
var currentDir = Zotero.DataDirectory.dir;
|
||||
var defaultDir = Zotero.DataDirectory.defaultDir;
|
||||
|
|
|
@ -37,16 +37,12 @@
|
|||
onpaneload="Zotero_Preferences.Advanced.init()"
|
||||
helpTopic="advanced">
|
||||
<preferences>
|
||||
<preference id="pref-automaticScraperUpdates" name="extensions.zotero.automaticScraperUpdates" type="bool"/>
|
||||
<preference id="pref-reportTranslationFailure" name="extensions.zotero.reportTranslationFailure" type="bool"/>
|
||||
|
||||
<preference id="pref-baseAttachmentPath" name="extensions.zotero.baseAttachmentPath" type="string"/>
|
||||
<preference id="pref-useDataDir" name="extensions.zotero.useDataDir" type="bool"/>
|
||||
<preference id="pref-dataDir" name="extensions.zotero.dataDir" type="string"/>
|
||||
<preference id="pref-debug-output-enableAfterRestart" name="extensions.zotero.debug.store" type="bool"/>
|
||||
<preference id="pref-openURL-resolver" name="extensions.zotero.openURL.resolver" type="string"/>
|
||||
<preference id="pref-openURL-version" name="extensions.zotero.openURL.version" type="string"/>
|
||||
|
||||
<preference id="pref-keys-openZotero" name="extensions.zotero.keys.openZotero" type="string"/>
|
||||
<preference id="pref-keys-saveToZotero" name="extensions.zotero.keys.saveToZotero" type="string"/>
|
||||
<preference id="pref-keys-library" name="extensions.zotero.keys.library" type="string"/>
|
||||
|
@ -78,14 +74,6 @@
|
|||
<groupbox id="zotero-prefpane-advanced-miscellaneous">
|
||||
<caption label="&zotero.preferences.miscellaneous;"/>
|
||||
|
||||
<hbox align="center">
|
||||
<checkbox label="&zotero.preferences.autoUpdate;" preference="pref-automaticScraperUpdates"/>
|
||||
<button id="updateButton" style="margin-top:0" label="&zotero.preferences.updateNow;"
|
||||
oncommand="Zotero_Preferences.Advanced.updateTranslators()"/>
|
||||
</hbox>
|
||||
|
||||
<checkbox label="&zotero.preferences.reportTranslationFailure;" preference="pref-reportTranslationFailure"/>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.bibliography.locale.label;"/>
|
||||
<menulist id="locale-menu"
|
||||
|
|
|
@ -26,48 +26,22 @@
|
|||
"use strict";
|
||||
|
||||
Zotero_Preferences.Cite = {
|
||||
wordPluginIDs: new Set([
|
||||
'zoteroOpenOfficeIntegration@zotero.org',
|
||||
'zoteroMacWordIntegration@zotero.org',
|
||||
'zoteroWinWordIntegration@zotero.org'
|
||||
]),
|
||||
|
||||
init: Zotero.Promise.coroutine(function* () {
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
this.updateWordProcessorInstructions();
|
||||
yield this.refreshStylesList();
|
||||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Determines if any word processors are disabled and if so, shows a message in the pref pane
|
||||
* Determines if there are word processors, and if not, enables no word processor message
|
||||
*/
|
||||
updateWordProcessorInstructions: async function () {
|
||||
var someDisabled = false;
|
||||
await new Promise(function(resolve) {
|
||||
AddonManager.getAllAddons(function(addons) {
|
||||
for (let addon of addons) {
|
||||
if (Zotero_Preferences.Cite.wordPluginIDs.has(addon.id) && addon.userDisabled) {
|
||||
someDisabled = true;
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
if (someDisabled) {
|
||||
document.getElementById("wordProcessors-somePluginsDisabled").hidden = undefined;
|
||||
updateWordProcessorInstructions: function () {
|
||||
if(document.getElementById("wordProcessors").childNodes.length == 2) {
|
||||
document.getElementById("wordProcessors-noWordProcessorPluginsInstalled").hidden = undefined;
|
||||
}
|
||||
if(Zotero.isStandalone) {
|
||||
document.getElementById("wordProcessors-getWordProcessorPlugins").hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
enableWordPlugins: function () {
|
||||
AddonManager.getAllAddons(function(addons) {
|
||||
for (let addon of addons) {
|
||||
if (Zotero_Preferences.Cite.wordPluginIDs.has(addon.id) && addon.userDisabled) {
|
||||
addon.userDisabled = false;
|
||||
}
|
||||
}
|
||||
return Zotero.Utilities.Internal.quit(true);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -96,15 +96,11 @@
|
|||
</groupbox>
|
||||
</tabpanel>
|
||||
<tabpanel orient="vertical" id="wordProcessors">
|
||||
<vbox id="wordProcessors-somePluginsDisabled" hidden="true">
|
||||
<label style="font-weight: bold; margin-top: 1em; text-align: center">Some word processor plugins are disabled.</label>
|
||||
<hbox pack="center" style="margin-bottom: 2em">
|
||||
<button id="wordProcessors-enablePlugins"
|
||||
label="Enable Plugins and Restart Zotero"
|
||||
oncommand="Zotero_Preferences.Cite.enableWordPlugins()"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<label id="wordProcessors-noWordProcessorPluginsInstalled" width="45em" hidden="true">
|
||||
&zotero.preferences.cite.wordProcessors.noWordProcessorPluginsInstalled;
|
||||
</label>
|
||||
<checkbox label="&zotero.preferences.cite.wordProcessors.useClassicAddCitationDialog;" preference="pref-cite-useClassicAddCitationDialog"/>
|
||||
<label id="wordProcessors-getWordProcessorPlugins" class="zotero-text-link" href="&zotero.preferences.cite.wordProcessors.getPlugins.url;" value="&zotero.preferences.cite.wordProcessors.getPlugins;"/>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
|
|
|
@ -25,9 +25,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
Zotero_Preferences.General = {
|
||||
init: function () {
|
||||
// JS-based strings
|
||||
|
@ -39,89 +36,27 @@ Zotero_Preferences.General = {
|
|||
}
|
||||
|
||||
document.getElementById('noteFontSize').value = Zotero.Prefs.get('note.fontSize');
|
||||
|
||||
this._updateFileHandlerUI();
|
||||
},
|
||||
|
||||
//
|
||||
// File handlers
|
||||
//
|
||||
chooseFileHandler: function (type) {
|
||||
var pref = this._getFileHandlerPref(type);
|
||||
var currentPath = Zotero.Prefs.get(pref);
|
||||
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
if (currentPath) {
|
||||
fp.displayDirectory = Zotero.File.pathToFile(OS.Path.dirname(currentPath));
|
||||
}
|
||||
fp.init(
|
||||
window,
|
||||
Zotero.getString('zotero.preferences.chooseApplication'),
|
||||
nsIFilePicker.modeOpen
|
||||
);
|
||||
fp.appendFilters(nsIFilePicker.filterApps);
|
||||
if (fp.show() != nsIFilePicker.returnOK) {
|
||||
this._updateFileHandlerUI();
|
||||
return false;
|
||||
}
|
||||
var newPath = OS.Path.normalize(fp.file.path);
|
||||
this.setFileHandler(type, newPath);
|
||||
},
|
||||
|
||||
setFileHandler: function (type, handler) {
|
||||
var pref = this._getFileHandlerPref(type);
|
||||
if (handler) {
|
||||
Zotero.Prefs.set(pref, handler);
|
||||
}
|
||||
else {
|
||||
Zotero.Prefs.clear(pref);
|
||||
}
|
||||
this._updateFileHandlerUI();
|
||||
},
|
||||
|
||||
_updateFileHandlerUI: function () {
|
||||
var handler = Zotero.Prefs.get('fileHandler.pdf');
|
||||
var menulist = document.getElementById('fileHandler-pdf');
|
||||
var customMenuItem = document.getElementById('fileHandler-custom');
|
||||
if (handler) {
|
||||
let icon;
|
||||
try {
|
||||
let fph = Services.io.getProtocolHandler("file")
|
||||
.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
|
||||
let urlspec = fph.getURLSpecFromFile(Zotero.File.pathToFile(handler));
|
||||
icon = "moz-icon://" + urlspec + "?size=16";
|
||||
updateTranslators: Zotero.Promise.coroutine(function* () {
|
||||
var updated = yield Zotero.Schema.updateFromRepository(Zotero.Schema.REPO_UPDATE_MANUAL);
|
||||
var button = document.getElementById('updateButton');
|
||||
if (button) {
|
||||
if (updated===-1) {
|
||||
var label = Zotero.getString('zotero.preferences.update.upToDate');
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
let handlerFilename = OS.Path.basename(handler);
|
||||
if (Zotero.isMac) {
|
||||
handlerFilename = handlerFilename.replace(/\.app$/, '');
|
||||
}
|
||||
customMenuItem.setAttribute('label', handlerFilename);
|
||||
if (icon) {
|
||||
customMenuItem.className = 'menuitem-iconic';
|
||||
customMenuItem.setAttribute('image', icon);
|
||||
else if (updated) {
|
||||
var label = Zotero.getString('zotero.preferences.update.updated');
|
||||
}
|
||||
else {
|
||||
customMenuItem.className = '';
|
||||
var label = Zotero.getString('zotero.preferences.update.error');
|
||||
}
|
||||
button.setAttribute('label', label);
|
||||
|
||||
if (updated && Zotero_Preferences.Cite) {
|
||||
yield Zotero_Preferences.Cite.refreshStylesList();
|
||||
}
|
||||
customMenuItem.hidden = false;
|
||||
menulist.selectedIndex = 0;
|
||||
}
|
||||
else {
|
||||
customMenuItem.hidden = true;
|
||||
menulist.selectedIndex = 1;
|
||||
}
|
||||
},
|
||||
|
||||
_getFileHandlerPref: function (type) {
|
||||
if (type != 'pdf') {
|
||||
throw new Error(`Unknown file type ${type}`);
|
||||
}
|
||||
return 'fileHandler.pdf';
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -33,12 +33,10 @@
|
|||
<preferences id="zotero-prefpane-general-preferences">
|
||||
<preference id="pref-fontSize" name="extensions.zotero.fontSize" type="string"/>
|
||||
<preference id="pref-layout" name="extensions.zotero.layout" type="string"/>
|
||||
|
||||
<preference id="pref-automaticScraperUpdates" name="extensions.zotero.automaticScraperUpdates" type="bool"/>
|
||||
<preference id="pref-reportTranslationFailure" name="extensions.zotero.reportTranslationFailure" type="bool"/>
|
||||
<preference id="pref-automaticSnapshots" name="extensions.zotero.automaticSnapshots" type="bool"/>
|
||||
<preference id="pref-downloadAssociatedFiles" name="extensions.zotero.downloadAssociatedFiles" type="bool"/>
|
||||
<preference id="pref-autoRecognizeFiles" name="extensions.zotero.autoRecognizeFiles" type="bool"/>
|
||||
<preference id="pref-autoRenameFiles" name="extensions.zotero.autoRenameFiles" type="bool"/>
|
||||
|
||||
<preference id="pref-automaticTags" name="extensions.zotero.automaticTags" type="bool"/>
|
||||
<preference id="pref-trashAutoEmptyDays" name="extensions.zotero.trashAutoEmptyDays" type="int"/>
|
||||
|
||||
|
@ -110,33 +108,20 @@
|
|||
</grid>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-file-handling-groupbox">
|
||||
<caption label="&zotero.preferences.fileHandling;"/>
|
||||
<groupbox id="zotero-prefpane-miscellaneous-groupbox">
|
||||
<caption label="&zotero.preferences.miscellaneous;"/>
|
||||
|
||||
<hbox align="center">
|
||||
<checkbox label="&zotero.preferences.autoUpdate;" preference="pref-automaticScraperUpdates"/>
|
||||
<button id="updateButton" style="margin-top:0" label="&zotero.preferences.updateNow;"
|
||||
oncommand="Zotero_Preferences.General.updateTranslators()"/>
|
||||
</hbox>
|
||||
|
||||
<checkbox label="&zotero.preferences.reportTranslationFailure;" preference="pref-reportTranslationFailure"/>
|
||||
<checkbox id="automaticSnapshots-checkbox"
|
||||
label="&zotero.preferences.automaticSnapshots;"
|
||||
preference="pref-automaticSnapshots"/>
|
||||
<checkbox label="&zotero.preferences.downloadAssociatedFiles;" preference="pref-downloadAssociatedFiles"/>
|
||||
<checkbox label="&zotero.preferences.autoRecognizeFiles;" preference="pref-autoRecognizeFiles"/>
|
||||
<checkbox label="&zotero.preferences.autoRenameFiles;" preference="pref-autoRenameFiles"/>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.fileHandler.openPDFsUsing;" control="file-handler-pdf"/>
|
||||
<menulist id="fileHandler-pdf" class="fileHandler-menu">
|
||||
<menupopup>
|
||||
<menuitem id="fileHandler-custom"/>
|
||||
<menuitem label="&zotero.preferences.fileHandler.systemDefault;"
|
||||
oncommand="Zotero_Preferences.General.setFileHandler('pdf', false)"/>
|
||||
<menuitem label="&zotero.preferences.custom;"
|
||||
oncommand="Zotero_Preferences.General.chooseFileHandler('pdf')"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-miscellaneous-groupbox">
|
||||
<caption label="&zotero.preferences.miscellaneous;"/>
|
||||
|
||||
<checkbox label="&zotero.preferences.automaticTags;" preference="pref-automaticTags"/>
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.trashAutoEmptyDaysPre;"/>
|
||||
|
|
|
@ -33,10 +33,264 @@ Zotero_Preferences.Search = {
|
|||
document.getElementById('fulltext-clearIndex').setAttribute('label',
|
||||
Zotero.getString('zotero.preferences.search.clearIndex')
|
||||
+ Zotero.getString('punctuation.ellipsis'));
|
||||
this.updatePDFToolsStatus();
|
||||
|
||||
this.updateIndexStats();
|
||||
|
||||
// Quick hack to support install prompt from PDF recognize option
|
||||
var io = window.arguments[0];
|
||||
if (io.action && io.action == 'pdftools-install') {
|
||||
this.checkPDFToolsDownloadVersion();
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Update window according to installation status for PDF tools
|
||||
* (e.g. status line, install/update button, etc.)
|
||||
*/
|
||||
updatePDFToolsStatus: function () {
|
||||
var converterIsRegistered = Zotero.Fulltext.pdfConverterIsRegistered();
|
||||
var infoIsRegistered = Zotero.Fulltext.pdfInfoIsRegistered();
|
||||
|
||||
var converterStatusLabel = document.getElementById('pdfconverter-status');
|
||||
var infoStatusLabel = document.getElementById('pdfinfo-status');
|
||||
var requiredLabel = document.getElementById('pdftools-required');
|
||||
var updateButton = document.getElementById('pdftools-update-button');
|
||||
var documentationLink = document.getElementById('pdftools-documentation-link');
|
||||
var settingsBox = document.getElementById('pdftools-settings');
|
||||
|
||||
// If we haven't already generated the required and documentation messages
|
||||
if (!converterIsRegistered && !requiredLabel.hasChildNodes()) {
|
||||
|
||||
// Xpdf link
|
||||
var str = Zotero.getString('zotero.preferences.search.pdf.toolsRequired',
|
||||
[Zotero.Fulltext.pdfConverterName, Zotero.Fulltext.pdfInfoName,
|
||||
'<a href="' + Zotero.Fulltext.pdfToolsURL + '">'
|
||||
+ Zotero.Fulltext.pdfToolsName + '</a>']);
|
||||
var parts = Zotero.Utilities.parseMarkup(str);
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
var part = parts[i];
|
||||
if (part.type == 'text') {
|
||||
var elem = document.createTextNode(part.text);
|
||||
}
|
||||
else if (part.type == 'link') {
|
||||
var elem = document.createElement('label');
|
||||
elem.setAttribute('value', part.text);
|
||||
elem.setAttribute('class', 'zotero-text-link');
|
||||
for (var key in part.attributes) {
|
||||
elem.setAttribute(key, part.attributes[key]);
|
||||
|
||||
if (key == 'href') {
|
||||
elem.setAttribute('tooltiptext', part.attributes[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
requiredLabel.appendChild(elem);
|
||||
}
|
||||
|
||||
requiredLabel.appendChild(document.createTextNode(' '
|
||||
+ Zotero.getString('zotero.preferences.search.pdf.automaticInstall')));
|
||||
|
||||
// Documentation link
|
||||
var link = '<a href="http://www.zotero.org/documentation/pdf_fulltext_indexing">'
|
||||
+ Zotero.getString('zotero.preferences.search.pdf.documentationLink')
|
||||
+ '</a>';
|
||||
var str = Zotero.getString('zotero.preferences.search.pdf.advancedUsers', link);
|
||||
var parts = Zotero.Utilities.parseMarkup(str);
|
||||
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
var part = parts[i];
|
||||
if (part.type == 'text') {
|
||||
var elem = document.createTextNode(part.text);
|
||||
}
|
||||
else if (part.type == 'link') {
|
||||
var elem = document.createElement('label');
|
||||
elem.setAttribute('value', part.text);
|
||||
elem.setAttribute('class', 'zotero-text-link');
|
||||
for (var key in part.attributes) {
|
||||
elem.setAttribute(key, part.attributes[key]);
|
||||
|
||||
if (key == 'href') {
|
||||
elem.setAttribute('tooltiptext', part.attributes[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
documentationLink.appendChild(elem);
|
||||
}
|
||||
}
|
||||
|
||||
// converter status line
|
||||
var prefix = 'zotero.preferences.search.pdf.tool';
|
||||
if (converterIsRegistered) {
|
||||
var version = Zotero.Fulltext.pdfConverterVersion;
|
||||
str = Zotero.getString(prefix + 'Registered',
|
||||
Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
|
||||
[Zotero.Fulltext.pdfConverterName, version]));
|
||||
}
|
||||
else {
|
||||
str = Zotero.getString(prefix + 'NotRegistered',
|
||||
[Zotero.Fulltext.pdfConverterFileName]);
|
||||
}
|
||||
converterStatusLabel.setAttribute('value', str);
|
||||
|
||||
// pdfinfo status line
|
||||
if (infoIsRegistered) {
|
||||
var version = Zotero.Fulltext.pdfInfoVersion;
|
||||
str = Zotero.getString(prefix + 'Registered',
|
||||
Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
|
||||
[Zotero.Fulltext.pdfInfoName, version]));
|
||||
}
|
||||
else {
|
||||
str = Zotero.getString(prefix + 'NotRegistered',
|
||||
[Zotero.Fulltext.pdfInfoFileName]);
|
||||
}
|
||||
infoStatusLabel.setAttribute('value', str);
|
||||
|
||||
str = converterIsRegistered ?
|
||||
Zotero.getString('general.checkForUpdate') :
|
||||
Zotero.getString('zotero.preferences.search.pdf.checkForInstaller');
|
||||
updateButton.setAttribute('label', str);
|
||||
|
||||
requiredLabel.setAttribute('hidden', converterIsRegistered);
|
||||
documentationLink.setAttribute('hidden', converterIsRegistered);
|
||||
settingsBox.setAttribute('hidden', !converterIsRegistered);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* Check available versions of PDF tools from server and prompt for installation
|
||||
* if a newer version is available
|
||||
*/
|
||||
checkPDFToolsDownloadVersion: Zotero.Promise.coroutine(function* () {
|
||||
try {
|
||||
var latestVersion = yield Zotero.Fulltext.getLatestPDFToolsVersion();
|
||||
|
||||
var converterIsRegistered = Zotero.Fulltext.pdfConverterIsRegistered();
|
||||
var infoIsRegistered = Zotero.Fulltext.pdfInfoIsRegistered();
|
||||
var bothRegistered = converterIsRegistered && infoIsRegistered;
|
||||
|
||||
// On Windows, install if not installed or anything other than 3.02a
|
||||
if (Zotero.isWin) {
|
||||
var converterVersionAvailable = !converterIsRegistered
|
||||
|| Zotero.Fulltext.pdfConverterVersion != '3.02a';
|
||||
var infoVersionAvailable = !infoIsRegistered
|
||||
|| Zotero.Fulltext.pdfInfoVersion != '3.02a';
|
||||
var bothAvailable = converterVersionAvailable && infoVersionAvailable;
|
||||
}
|
||||
// Install if not installed, version unknown, outdated, or
|
||||
// Xpdf 3.02/3.04 (to upgrade to Poppler),
|
||||
else {
|
||||
var converterVersionAvailable = (!converterIsRegistered ||
|
||||
Zotero.Fulltext.pdfConverterVersion == 'UNKNOWN'
|
||||
|| latestVersion > Zotero.Fulltext.pdfConverterVersion
|
||||
|| (!latestVersion.startsWith('3.02')
|
||||
&& Zotero.Fulltext.pdfConverterVersion.startsWith('3.02'))
|
||||
|| (!latestVersion.startsWith('3.02') && latestVersion != '3.04'
|
||||
&& Zotero.Fulltext.pdfConverterVersion == '3.04'));
|
||||
var infoVersionAvailable = (!infoIsRegistered ||
|
||||
Zotero.Fulltext.pdfInfoVersion == 'UNKNOWN'
|
||||
|| latestVersion > Zotero.Fulltext.pdfInfoVersion
|
||||
|| (!latestVersion.startsWith('3.02')
|
||||
&& Zotero.Fulltext.pdfInfoVersion.startsWith('3.02'))
|
||||
|| (!latestVersion.startsWith('3.02') && latestVersion != '3.04'
|
||||
&& Zotero.Fulltext.pdfInfoVersion == '3.04'));
|
||||
var bothAvailable = converterVersionAvailable && infoVersionAvailable;
|
||||
}
|
||||
|
||||
// Up to date -- disable update button
|
||||
if (!converterVersionAvailable && !infoVersionAvailable) {
|
||||
var button = document.getElementById('pdftools-update-button');
|
||||
button.setAttribute('label', Zotero.getString('zotero.preferences.update.upToDate'));
|
||||
button.setAttribute('disabled', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// New version available -- display update prompt
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
|
||||
createInstance(Components.interfaces.nsIPromptService);
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
|
||||
|
||||
var msg = Zotero.getString('zotero.preferences.search.pdf.available'
|
||||
+ ((converterIsRegistered || infoIsRegistered) ? 'Updates' : 'Downloads'),
|
||||
[Zotero.platform, 'zotero.org']) + '\n\n';
|
||||
|
||||
if (converterVersionAvailable) {
|
||||
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
|
||||
[Zotero.Fulltext.pdfConverterName, latestVersion]);
|
||||
msg += '- ' + tvp + '\n';
|
||||
}
|
||||
if (infoVersionAvailable) {
|
||||
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
|
||||
[Zotero.Fulltext.pdfInfoName, latestVersion]);
|
||||
msg += '- ' + tvp + '\n';
|
||||
}
|
||||
msg += '\n';
|
||||
msg += Zotero.getString('zotero.preferences.search.pdf.zoteroCanInstallVersion'
|
||||
+ (bothAvailable ? 's' : ''));
|
||||
|
||||
var index = ps.confirmEx(null,
|
||||
converterIsRegistered ?
|
||||
Zotero.getString('general.updateAvailable') : '',
|
||||
msg,
|
||||
buttonFlags,
|
||||
converterIsRegistered ?
|
||||
Zotero.getString('general.upgrade') :
|
||||
Zotero.getString('general.install'),
|
||||
null, null, null, {});
|
||||
|
||||
if (index != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('pdftools-update-button').disabled = true;
|
||||
var str = Zotero.getString('zotero.preferences.search.pdf.downloading');
|
||||
document.getElementById('pdftools-update-button').setAttribute('label', str);
|
||||
|
||||
if (converterVersionAvailable) {
|
||||
yield Zotero.Fulltext.downloadPDFTool('converter', latestVersion)
|
||||
.catch(function (e) {
|
||||
Zotero.logError(e);
|
||||
throw new Error("Error downloading pdftotext");
|
||||
});
|
||||
}
|
||||
if (infoVersionAvailable) {
|
||||
yield Zotero.Fulltext.downloadPDFTool('info', latestVersion)
|
||||
.catch(function (e) {
|
||||
Zotero.logError(e);
|
||||
throw new Error("Error downloading pdfinfo");
|
||||
});
|
||||
}
|
||||
this.updatePDFToolsStatus();
|
||||
}
|
||||
catch (e) {
|
||||
this.onPDFToolsDownloadError(e);
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
onPDFToolsDownloadError: function (e) {
|
||||
if (e == 404) {
|
||||
var str = Zotero.getString('zotero.preferences.search.pdf.toolDownloadsNotAvailable',
|
||||
Zotero.Fulltext.pdfToolsName) + ' '
|
||||
+ Zotero.getString('zotero.preferences.search.pdf.viewManualInstructions');
|
||||
}
|
||||
else {
|
||||
Components.utils.reportError(e);
|
||||
var str = Zotero.getString('zotero.preferences.search.pdf.toolsDownloadError', Zotero.Fulltext.pdfToolsName)
|
||||
+ ' ' + Zotero.getString('zotero.preferences.search.pdf.tryAgainOrViewManualInstructions');
|
||||
}
|
||||
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.createInstance(Components.interfaces.nsIPromptService);
|
||||
ps.alert(
|
||||
null,
|
||||
Zotero.getString('pane.item.attachments.PDF.installTools.title'),
|
||||
str
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
updateIndexStats: Zotero.Promise.coroutine(function* () {
|
||||
var stats = yield Zotero.Fulltext.getIndexStats();
|
||||
document.getElementById('fulltext-stats-indexed').
|
||||
|
|
|
@ -49,10 +49,34 @@
|
|||
<textbox size="10" preference="pref-fulltext-textMaxLength"/>
|
||||
<label value="(&zotero.preferences.default; 500000)"/>
|
||||
</hbox>
|
||||
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="pdftools-box">
|
||||
<caption label="&zotero.preferences.search.pdfIndexing;"/>
|
||||
|
||||
<label id="pdfconverter-status"/>
|
||||
<separator class="thin"/>
|
||||
|
||||
<hbox align="center">
|
||||
<label id="pdfinfo-status"/>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<!-- This doesn't wrap without an explicit width -->
|
||||
<label id="pdftools-required" width="45em" hidden="true"/>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<hbox>
|
||||
<button id="pdftools-update-button" flex="1" oncommand="Zotero_Preferences.Search.checkPDFToolsDownloadVersion()"/>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<!-- This doesn't wrap without an explicit width -->
|
||||
<label id="pdftools-documentation-link" width="45em" hidden="true"/>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<hbox id="pdftools-settings" align="center" hidden="true">
|
||||
<label value="&zotero.preferences.fulltext.pdfMaxPages;"/>
|
||||
<textbox size="5" preference="pref-fulltext-pdfmaxpages"/>
|
||||
<label value="(&zotero.preferences.default; 100)"/>
|
||||
|
|
|
@ -398,6 +398,7 @@ Zotero_Preferences.Sync = {
|
|||
|
||||
onStorageSettingsKeyPress: Zotero.Promise.coroutine(function* (event) {
|
||||
if (event.keyCode == 13) {
|
||||
yield this.onStorageSettingsChange();
|
||||
yield this.verifyStorageServer();
|
||||
}
|
||||
}),
|
||||
|
@ -479,11 +480,6 @@ Zotero_Preferences.Sync = {
|
|||
|
||||
|
||||
verifyStorageServer: Zotero.Promise.coroutine(function* () {
|
||||
// onchange weirdly isn't triggered when clicking straight from a field to the button,
|
||||
// so we have to trigger this here (and we don't trigger it for Enter in
|
||||
// onStorageSettingsKeyPress()).
|
||||
yield this.onStorageSettingsChange();
|
||||
|
||||
Zotero.debug("Verifying storage");
|
||||
|
||||
var verifyButton = document.getElementById("storage-verify");
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
|
||||
<window id="zotero-progress-meter-window"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
windowtype="alert:alert">
|
||||
|
||||
<vbox id="zotero-progress-text-box" flex="1">
|
||||
<label/>
|
||||
<progressmeter/>
|
||||
</vbox>
|
||||
</window>
|
943
chrome/content/zotero/recognizePDF.js
Normal file
943
chrome/content/zotero/recognizePDF.js
Normal file
|
@ -0,0 +1,943 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Tools for automatically retrieving a citation for the given PDF
|
||||
*/
|
||||
|
||||
/**
|
||||
* Front end for recognizing PDFs
|
||||
* @namespace
|
||||
*/
|
||||
var Zotero_RecognizePDF = new function() {
|
||||
var _progressWindow, _progressIndicator;
|
||||
|
||||
/**
|
||||
* Checks whether a given PDF could theoretically be recognized
|
||||
* @returns {Boolean} True if the PDF can be recognized, false if it cannot be
|
||||
*/
|
||||
this.canRecognize = function(/**Zotero.Item*/ item) {
|
||||
return item.attachmentContentType
|
||||
&& item.attachmentContentType == "application/pdf"
|
||||
&& item.isTopLevelItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves metadata for the PDF(s) selected in the Zotero Pane, placing the PDFs as a children
|
||||
* of the new items
|
||||
*/
|
||||
this.recognizeSelected = function() {
|
||||
var installed = ZoteroPane_Local.checkPDFConverter();
|
||||
if (!installed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var items = ZoteroPane_Local.getSelectedItems();
|
||||
if (!items) return;
|
||||
var itemRecognizer = new Zotero_RecognizePDF.ItemRecognizer();
|
||||
itemRecognizer.recognizeItems(items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves metadata for a PDF and saves it as an item
|
||||
*
|
||||
* @param {nsIFile} file The PDF file to retrieve metadata for
|
||||
* @param {Integer} libraryID The library in which to save the PDF
|
||||
* @param {Function} stopCheckCallback Function that returns true if the
|
||||
* process is to be interrupted
|
||||
* @return {Promise} A promise resolved when PDF metadata has been retrieved
|
||||
*/
|
||||
this.recognize = Zotero.Promise.coroutine(function* (file, libraryID, stopCheckCallback) {
|
||||
const MAX_PAGES = 15;
|
||||
var me = this;
|
||||
|
||||
var lines = yield _extractText(file, MAX_PAGES);
|
||||
// Look for DOI - Use only first 80 lines to avoid catching article references
|
||||
var allText = lines.join("\n"),
|
||||
firstChunk = lines.slice(0,80).join('\n'),
|
||||
doi = Zotero.Utilities.cleanDOI(firstChunk),
|
||||
promise;
|
||||
Zotero.debug(allText);
|
||||
|
||||
if(!doi) {
|
||||
// Look for a JSTOR stable URL, which can be converted to a DOI by prepending 10.2307
|
||||
doi = firstChunk.match(/www.\jstor\.org\/stable\/(\S+)/i);
|
||||
if(doi) {
|
||||
doi = Zotero.Utilities.cleanDOI(
|
||||
doi[1].indexOf('10.') == 0 ? doi[1] : '10.2307/' + doi[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var newItem;
|
||||
if (doi) {
|
||||
// Look up DOI
|
||||
Zotero.debug("RecognizePDF: Found DOI: "+doi);
|
||||
|
||||
var translateDOI = new Zotero.Translate.Search();
|
||||
translateDOI.setTranslator("11645bd1-0420-45c1-badb-53fb41eeb753");
|
||||
translateDOI.setSearch({"itemType":"journalArticle", "DOI":doi});
|
||||
try {
|
||||
newItem = yield _promiseTranslate(translateDOI, libraryID);
|
||||
return newItem;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("RecognizePDF: " + e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Zotero.debug("RecognizePDF: No DOI found in text");
|
||||
}
|
||||
|
||||
// Look for ISBNs if no DOI
|
||||
var isbns = _findISBNs(allText);
|
||||
if (isbns.length) {
|
||||
Zotero.debug("RecognizePDF: Found ISBNs: " + isbns);
|
||||
|
||||
var translate = new Zotero.Translate.Search();
|
||||
translate.setSearch({"itemType":"book", "ISBN":isbns[0]});
|
||||
try {
|
||||
newItem = yield _promiseTranslate(translate, libraryID);
|
||||
return newItem;
|
||||
}
|
||||
catch (e) {
|
||||
// If no DOI or ISBN, query Google Scholar
|
||||
Zotero.debug("RecognizePDF: " + e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Zotero.debug("RecognizePDF: No ISBN found in text");
|
||||
}
|
||||
|
||||
return this.GSFullTextSearch.findItem(lines, libraryID, stopCheckCallback);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get text from a PDF
|
||||
* @param {nsIFile} file PDF
|
||||
* @param {Number} pages Number of pages to extract
|
||||
* @return {Promise}
|
||||
*/
|
||||
function _extractText(file, pages) {
|
||||
var cacheFile = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
|
||||
cacheFile.append("recognizePDFcache.txt");
|
||||
if(cacheFile.exists()) {
|
||||
cacheFile.remove(false);
|
||||
}
|
||||
|
||||
var {exec, args} = Zotero.Fulltext.getPDFConverterExecAndArgs();
|
||||
args.push('-enc', 'UTF-8', '-nopgbrk', '-layout', '-l', pages, file.path, cacheFile.path);
|
||||
|
||||
Zotero.debug("RecognizePDF: Running " + exec.path + " " + args.map(arg => "'" + arg + "'").join(" "));
|
||||
|
||||
return Zotero.Utilities.Internal.exec(exec, args).then(function() {
|
||||
if(!cacheFile.exists()) {
|
||||
throw new Zotero.Exception.Alert("recognizePDF.couldNotRead");
|
||||
}
|
||||
|
||||
try {
|
||||
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
inputStream.init(cacheFile, 0x01, 0o664, 0);
|
||||
try {
|
||||
var intlStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
intlStream.init(inputStream, "UTF-8", 65535,
|
||||
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
intlStream.QueryInterface(Components.interfaces.nsIUnicharLineInputStream);
|
||||
|
||||
// get the lines in this sample
|
||||
var lines = [], str = {};
|
||||
while(intlStream.readLine(str)) {
|
||||
var line = str.value.trim();
|
||||
if(line) lines.push(line);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
} finally {
|
||||
cacheFile.remove(false);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}, function() {
|
||||
throw new Zotero.Exception.Alert("recognizePDF.couldNotRead");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach appropriate handlers to a Zotero.Translate instance and begin translation
|
||||
* @return {Promise}
|
||||
*/
|
||||
var _promiseTranslate = Zotero.Promise.coroutine(function* (translate, libraryID) {
|
||||
translate.setHandler("select", function(translate, items, callback) {
|
||||
for(var i in items) {
|
||||
var obj = {};
|
||||
obj[i] = items[i];
|
||||
callback(obj);
|
||||
return;
|
||||
}
|
||||
});
|
||||
/*translate.setHandler("done", function(translate, success) {
|
||||
if(success && translate.newItems.length) {
|
||||
deferred.resolve(translate.newItems[0]);
|
||||
} else {
|
||||
deferred.reject(translate.translator && translate.translator.length
|
||||
? "Translation with " + translate.translator.map(t => t.label) + " failed"
|
||||
: "Could not find a translator for given search item"
|
||||
);
|
||||
}
|
||||
});*/
|
||||
var newItems = yield translate.translate({
|
||||
libraryID,
|
||||
saveAttachments: false
|
||||
});
|
||||
if (newItems.length) {
|
||||
return newItems[0];
|
||||
}
|
||||
throw new Error("No items found");
|
||||
});
|
||||
|
||||
/**
|
||||
* Search ISBNs in text
|
||||
* @private
|
||||
* @return {String[]} Array of ISBNs
|
||||
*/
|
||||
function _findISBNs(x) {
|
||||
if(typeof(x) != "string") {
|
||||
throw "findISBNs: argument must be a string";
|
||||
}
|
||||
var isbns = [];
|
||||
|
||||
// Match lines saying "isbn: " or "ISBN-10:" or similar, consider m-dashes and n-dashes as well
|
||||
var pattern = /(SBN|sbn)[ \u2014\u2013\u2012-]?(10|13)?[: ]*([0-9X][0-9X \u2014\u2013\u2012-]+)/g;
|
||||
var match;
|
||||
|
||||
while (match = pattern.exec(x)) {
|
||||
var isbn = match[3];
|
||||
isbn = isbn.replace(/[ \u2014\u2013\u2012-]/g, '');
|
||||
if(isbn.length==20 || isbn.length==26) {
|
||||
// Handle the case of two isbns (e.g. paper+hardback) next to each other
|
||||
isbns.push(isbn.slice(0,isbn.length/2), isbn.slice(isbn.length/2));
|
||||
} else if(isbn.length==23) {
|
||||
// Handle the case of two isbns (10+13) next to each other
|
||||
isbns.push(isbn.slice(0,10), isbn.slice(10));
|
||||
} else if(isbn.length==10 || isbn.length==13) {
|
||||
isbns.push(isbn);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate ISBNs
|
||||
var validIsbns = [], cleanISBN;
|
||||
for (var i =0; i < isbns.length; i++) {
|
||||
cleanISBN = Zotero.Utilities.cleanISBN(isbns[i]);
|
||||
if(cleanISBN) validIsbns.push(cleanISBN);
|
||||
}
|
||||
return validIsbns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Handles UI, etc. for recognizing multiple items
|
||||
*/
|
||||
this.ItemRecognizer = function () {
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
this.ItemRecognizer.prototype = {
|
||||
"_stopped": false,
|
||||
"_itemsTotal": 0,
|
||||
"_progressWindow": null,
|
||||
"_progressIndicator": null,
|
||||
|
||||
/**
|
||||
* Retreives metadata for the PDF items passed, displaying a progress dialog during conversion
|
||||
* and placing the PDFs as a children of the new items
|
||||
* @param {Zotero.Item[]} items
|
||||
*/
|
||||
"recognizeItems": function(items) {
|
||||
var me = this;
|
||||
this._items = items.slice();
|
||||
this._itemTotal = items.length;
|
||||
|
||||
_progressWindow = this._progressWindow = window.openDialog("chrome://zotero/content/pdfProgress.xul", "", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen");
|
||||
this._progressWindow.addEventListener("pageshow", function() { me._onWindowLoaded() }, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Halts recognition of PDFs
|
||||
*/
|
||||
"stop": function() {
|
||||
this._stopped = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Halts recognition and closes window
|
||||
*/
|
||||
"close": function() {
|
||||
this.stop();
|
||||
this._progressWindow.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the progress window has been opened; adds items to the tree and begins recognizing
|
||||
* @param
|
||||
*/
|
||||
"_onWindowLoaded": function() {
|
||||
// populate progress window
|
||||
var treechildren = this._progressWindow.document.getElementById("treechildren");
|
||||
this._rowIDs = [];
|
||||
for(var i in this._items) {
|
||||
var treeitem = this._progressWindow.document.createElement('treeitem');
|
||||
var treerow = this._progressWindow.document.createElement('treerow');
|
||||
this._rowIDs.push(this._items[i].id);
|
||||
|
||||
var treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("id", "item-"+this._items[i].id+"-icon");
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("label", this._items[i].getField("title"));
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("id", "item-"+this._items[i].id+"-title");
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treeitem.appendChild(treerow);
|
||||
treechildren.appendChild(treeitem);
|
||||
}
|
||||
|
||||
var me = this;
|
||||
|
||||
this._progressWindow.document.getElementById("tree").addEventListener(
|
||||
"dblclick", function(event) { me._onDblClick(event, this); });
|
||||
|
||||
this._cancelHandler = function() { me.stop() };
|
||||
this._keypressCancelHandler = function(e) {
|
||||
if(e.keyCode === KeyEvent.DOM_VK_ESCAPE) me.stop();
|
||||
};
|
||||
|
||||
_progressIndicator = this._progressIndicator = this._progressWindow.document.getElementById("progress-indicator");
|
||||
this._progressWindow.document.getElementById("cancel-button")
|
||||
.addEventListener("command", this._cancelHandler, false);
|
||||
// Also cancel if the user presses Esc
|
||||
this._progressWindow.addEventListener("keypress", this._keypressCancelHandler);
|
||||
this._progressWindow.addEventListener("close", this._cancelHandler, false);
|
||||
Zotero_RecognizePDF.GSFullTextSearch.resetQueryLimit();
|
||||
return this._recognizeItem();
|
||||
},
|
||||
|
||||
/**
|
||||
* Shifts an item off of this._items and recognizes it, then calls itself again if there are more
|
||||
* @private
|
||||
*/
|
||||
"_recognizeItem": Zotero.Promise.coroutine(function* () {
|
||||
const SUCCESS_IMAGE = "chrome://zotero/skin/tick.png";
|
||||
const FAILURE_IMAGE = "chrome://zotero/skin/cross.png";
|
||||
const LOADING_IMAGE = "chrome://global/skin/icons/loading_16.png";
|
||||
|
||||
if(!this._items.length) {
|
||||
this._done();
|
||||
return;
|
||||
}
|
||||
|
||||
// Order here matters. Otherwise we may show an incorrect label
|
||||
if(this._stopped) {
|
||||
this._done(true);
|
||||
return;
|
||||
}
|
||||
|
||||
this._progressIndicator.value = (this._itemTotal-this._items.length)/this._itemTotal*100;
|
||||
|
||||
var item = this._items.shift(),
|
||||
itemIcon = this._progressWindow.document.getElementById("item-"+item.id+"-icon"),
|
||||
itemTitle = this._progressWindow.document.getElementById("item-"+item.id+"-title"),
|
||||
rowNumber = this._rowIDs.indexOf(item.id);
|
||||
itemIcon.setAttribute("src", LOADING_IMAGE);
|
||||
itemTitle.setAttribute("label", "");
|
||||
|
||||
var file = item.getFile(), me = this;
|
||||
|
||||
try {
|
||||
if (file) {
|
||||
let newItem = yield Zotero_RecognizePDF.recognize(
|
||||
file,
|
||||
item.libraryID,
|
||||
() => this._stopped
|
||||
);
|
||||
|
||||
// If already stopped, delete
|
||||
if (this._stopped) {
|
||||
yield Zotero.Items.erase(newItem.id);
|
||||
throw new Zotero.Exception.Alert('recognizePDF.stopped');
|
||||
}
|
||||
|
||||
// put new item in same collections as the old one
|
||||
let itemCollections = item.getCollections();
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
for (let i = 0; i < itemCollections.length; i++) {
|
||||
let collection = Zotero.Collections.get(itemCollections[i]);
|
||||
yield collection.addItem(newItem.id);
|
||||
}
|
||||
|
||||
// put old item as a child of the new item
|
||||
item.parentID = newItem.id;
|
||||
yield item.save();
|
||||
});
|
||||
|
||||
itemTitle.setAttribute("label", newItem.getField("title"));
|
||||
itemIcon.setAttribute("src", SUCCESS_IMAGE);
|
||||
this._rowIDs[rowNumber] = newItem.id;
|
||||
|
||||
return this._recognizeItem();
|
||||
}
|
||||
else {
|
||||
throw new Zotero.Exception.Alert("recognizePDF.fileNotFound");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
||||
itemTitle.setAttribute(
|
||||
"label",
|
||||
e instanceof Zotero.Exception.Alert
|
||||
? e.message
|
||||
: Zotero.getString("recognizePDF.error")
|
||||
);
|
||||
itemIcon.setAttribute("src", FAILURE_IMAGE);
|
||||
|
||||
// Don't show "completed" label if stopped on last item
|
||||
if (this._stopped && !this._items.length) {
|
||||
this._done(true);
|
||||
} else {
|
||||
return this._recognizeItem();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// scroll to this item
|
||||
this._progressWindow.document.getElementById("tree").treeBoxObject.scrollToRow(
|
||||
Math.max(0, this._itemTotal - this._items.length - 4)
|
||||
);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Cleans up after items are recognized, disabling the cancel button and
|
||||
* making the progress window close on blur.
|
||||
* @param {Boolean} cancelled Whether the process was cancelled
|
||||
*/
|
||||
"_done": function(cancelled) {
|
||||
this._progressIndicator.value = 100;
|
||||
// Switch out cancel for close
|
||||
var cancelButton = this._progressWindow.document.getElementById("cancel-button"),
|
||||
me = this;
|
||||
cancelButton.label = Zotero.getString("recognizePDF.close.label");
|
||||
cancelButton.removeEventListener("command", this._cancelHandler, false);
|
||||
cancelButton.addEventListener("command", function() { me.close() }, false);
|
||||
this._progressWindow.removeEventListener("keypress", this._keypressCancelHandler);
|
||||
this._progressWindow.addEventListener("keypress", function() { me.close() });
|
||||
|
||||
if(Zotero.isMac) {
|
||||
// On MacOS X, the windows are not always on top, so we hide them on
|
||||
// blur to avoid clutter
|
||||
this._setCloseTimer();
|
||||
}
|
||||
this._progressWindow.document.getElementById("label").value =
|
||||
cancelled ? Zotero.getString("recognizePDF.cancelled.label")
|
||||
: Zotero.getString("recognizePDF.complete.label");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a timer after which the window will close automatically. If the
|
||||
* window is refocused, clear the timer and do not attempt to auto-close
|
||||
* any more
|
||||
* @private
|
||||
*/
|
||||
"_setCloseTimer": function() {
|
||||
var me = this, win = this._progressWindow;
|
||||
var focusListener = function() {
|
||||
if(!win.zoteroCloseTimeoutID) return;
|
||||
|
||||
win.clearTimeout(win.zoteroCloseTimeoutID);
|
||||
delete win.zoteroCloseTimeoutID;
|
||||
|
||||
win.removeEventListener('blur', blurListener, false);
|
||||
win.removeEventListener('focus', focusListener, false);
|
||||
};
|
||||
var blurListener = function() {
|
||||
// Close window after losing focus for 5 seconds
|
||||
win.zoteroCloseTimeoutID = win.setTimeout(function() { win.close() }, 5000);
|
||||
// Prevent auto-close if we gain focus again
|
||||
win.addEventListener("focus", focusListener, false);
|
||||
};
|
||||
win.addEventListener("blur", blurListener, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus items in Zotero library when double-clicking them in the Retrieve
|
||||
* metadata window.
|
||||
* @param {Event} event
|
||||
* @param {tree} tree XUL tree object
|
||||
* @private
|
||||
*/
|
||||
"_onDblClick": function(event, tree) {
|
||||
if (event && tree && event.type == "dblclick") {
|
||||
var itemID = this._rowIDs[tree.treeBoxObject.getRowAt(event.clientX, event.clientY)];
|
||||
if(!itemID) return;
|
||||
|
||||
// Get the right window. In tab mode, it's the container window
|
||||
var lastWin = (window.ZoteroTab ? window.ZoteroTab.containerWindow : window);
|
||||
|
||||
if (lastWin.ZoteroOverlay) {
|
||||
lastWin.ZoteroOverlay.toggleDisplay(true);
|
||||
}
|
||||
|
||||
lastWin.ZoteroPane.selectItem(itemID, false, true);
|
||||
lastWin.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton for querying Google Scholar. Ensures that all queries are
|
||||
* sequential and respect the delay inbetween queries.
|
||||
* @namespace
|
||||
*/
|
||||
this.GSFullTextSearch = new function() {
|
||||
const GOOGLE_SCHOLAR_QUERY_DELAY = 2000; // In ms
|
||||
var queryLimitReached = false,
|
||||
inProgress = false,
|
||||
queue = [],
|
||||
stopCheckCallback; // As long as we process one query at a time, this is ok
|
||||
// Load nsICookieManager2
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
var cookieService = Services.cookies;
|
||||
|
||||
/**
|
||||
* Reset "Query Limit Reached" flag, so that we attempt to query Google again
|
||||
*/
|
||||
this.resetQueryLimit = function() {
|
||||
queryLimitReached = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue up item for Google Scholar query
|
||||
* @param {String[]} lines Lines of text to use for full-text query
|
||||
* @param {Integer | null} libraryID Library to save the item to
|
||||
* @param {Function} stopCheckCallback Function that returns true if the
|
||||
* process is to be interrupted
|
||||
* @return {Promise} A promise resolved when PDF metadata has been retrieved
|
||||
*/
|
||||
this.findItem = function(lines, libraryID, stopCheckCallback) {
|
||||
if(!inProgress && queryLimitReached) {
|
||||
// There's no queue, so we can reject immediately
|
||||
return Zotero.Promise.reject(new Zotero.Exception.Alert("recognizePDF.limit"));
|
||||
}
|
||||
|
||||
var deferred = Zotero.Promise.defer();
|
||||
queue.push({
|
||||
deferred: deferred,
|
||||
lines: lines,
|
||||
libraryID: libraryID,
|
||||
stopCheckCallback: stopCheckCallback
|
||||
});
|
||||
_processQueue();
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Process Google Scholar queue
|
||||
* @private
|
||||
* @param {Boolean} proceed Whether we should pop the next item off the queue
|
||||
* This should not be true unless being called after processing
|
||||
* another item
|
||||
*/
|
||||
function _processQueue(proceed) {
|
||||
if(inProgress && !proceed) return; //only one at a time
|
||||
|
||||
if(!queue.length) {
|
||||
inProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
inProgress = true;
|
||||
if(queryLimitReached) {
|
||||
// Irreversibly blocked. Reject remaining items in queue
|
||||
var item;
|
||||
while(item = queue.shift()) {
|
||||
item.deferred.reject(new Zotero.Exception.Alert("recognizePDF.limit"));
|
||||
}
|
||||
_processQueue(true); // Wrap it up
|
||||
} else {
|
||||
var item = queue.shift();
|
||||
|
||||
stopCheckCallback = item.stopCheckCallback;
|
||||
if(stopCheckCallback && stopCheckCallback()) {
|
||||
item.deferred.reject(new Zotero.Exception.Alert('recognizePDF.stopped'));
|
||||
_processQueue(true);
|
||||
return;
|
||||
}
|
||||
|
||||
item.deferred.resolve(
|
||||
Zotero.Promise.try(function () {
|
||||
var lines = getGoodLines(item.lines);
|
||||
return queryGoogle(lines, item.libraryID, 3); // Try querying 3 times
|
||||
})
|
||||
.finally(function() { _processQueue(true); })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select lines that are good candidates for Google Scholar query
|
||||
* @private
|
||||
* @param {String[]} lines
|
||||
* @return {String[]}
|
||||
*/
|
||||
function getGoodLines(lines) {
|
||||
// Use only first column from multi-column lines
|
||||
const lineRe = /^[\s_]*([^\s]+(?: [^\s_]+)+)/;
|
||||
var cleanedLines = [], cleanedLineLengths = [];
|
||||
for(var i=0; i<lines.length && cleanedLines.length<100; i++) {
|
||||
var m = lineRe.exec(
|
||||
lines[i]
|
||||
// Replace non-breaking spaces
|
||||
.replace(/\xA0/g, ' ')
|
||||
);
|
||||
if(m && m[1].split(' ').length > 3) {
|
||||
cleanedLines.push(m[1]);
|
||||
cleanedLineLengths.push(m[1].length);
|
||||
}
|
||||
}
|
||||
|
||||
// Get (not quite) median length
|
||||
var lineLengthsLength = cleanedLineLengths.length;
|
||||
if(lineLengthsLength < 20
|
||||
|| cleanedLines[0] === "This is a digital copy of a book that was preserved for generations on library shelves before it was carefully scanned by Google as part of a project") {
|
||||
throw new Zotero.Exception.Alert("recognizePDF.noOCR");
|
||||
}
|
||||
|
||||
var sortedLengths = cleanedLineLengths.sort(),
|
||||
medianLength = sortedLengths[Math.floor(lineLengthsLength/2)];
|
||||
|
||||
// Pick lines within 6 chars of the median (this is completely arbitrary)
|
||||
var goodLines = [],
|
||||
uBound = medianLength + 6,
|
||||
lBound = medianLength - 6;
|
||||
for (var i=0; i<lineLengthsLength; i++) {
|
||||
if(cleanedLineLengths[i] > lBound && cleanedLineLengths[i] < uBound) {
|
||||
// Strip quotation marks so they don't mess up search query quoting
|
||||
var line = cleanedLines[i].replace('"', '');
|
||||
goodLines.push(line);
|
||||
}
|
||||
}
|
||||
return goodLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query Google Scholar
|
||||
* @private
|
||||
* @param {String[]} goodLines
|
||||
* @param {Integer | null} libraryID
|
||||
* @param {Integer} tries Number of queries to attempt before giving up
|
||||
* @return {Promise} A promise resolved when PDF metadata has been retrieved
|
||||
*/
|
||||
var queryGoogle = Zotero.Promise.coroutine(function* (goodLines, libraryID, tries) {
|
||||
if(tries <= 0) throw new Zotero.Exception.Alert("recognizePDF.noMatches");
|
||||
|
||||
// Take the relevant parts of some lines (exclude hyphenated word)
|
||||
var queryString = "", queryStringWords = 0, nextLine = 0;
|
||||
while(queryStringWords < 25) {
|
||||
if(!goodLines.length) throw new Zotero.Exception.Alert("recognizePDF.noMatches");
|
||||
|
||||
var words = goodLines.splice(nextLine, 1)[0].split(/\s+/);
|
||||
// Try to avoid picking adjacent strings so the odds of them appearing in another
|
||||
// document quoting our document is low. Every 7th line is a magic value
|
||||
nextLine = (nextLine + 7) % goodLines.length;
|
||||
|
||||
// Get rid of first and last words
|
||||
words.shift();
|
||||
words.pop();
|
||||
// Make sure there are no long words (probably OCR mistakes)
|
||||
var skipLine = false;
|
||||
for(var i=0; i<words.length; i++) {
|
||||
if(words[i].length > 20) {
|
||||
skipLine = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Add words to query
|
||||
if(!skipLine && words.length) {
|
||||
queryStringWords += words.length;
|
||||
queryString += '"'+words.join(" ")+'" ';
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("RecognizePDF: Query string " + queryString);
|
||||
|
||||
var url = "https://scholar.google.com/scholar?q="+encodeURIComponent(queryString)+"&hl=en&lr=&btnG=Search",
|
||||
delay = GOOGLE_SCHOLAR_QUERY_DELAY - (Date.now() - Zotero.HTTP.lastGoogleScholarQueryTime);
|
||||
|
||||
// Delay
|
||||
if (delay > 0) {
|
||||
yield Zotero.Promise.delay(delay);
|
||||
}
|
||||
Zotero.HTTP.lastGoogleScholarQueryTime = Date.now();
|
||||
try {
|
||||
let xmlhttp = yield Zotero.HTTP.request("GET", url, { "responseType": "document" })
|
||||
.then(
|
||||
function (xmlhttp) {
|
||||
return _checkCaptchaOK(xmlhttp, 3);
|
||||
},
|
||||
function (e) {
|
||||
return _checkCaptchaError(e, 3);
|
||||
}
|
||||
);
|
||||
|
||||
let doc = xmlhttp.response,
|
||||
deferred = Zotero.Promise.defer(),
|
||||
translate = new Zotero.Translate.Web();
|
||||
|
||||
translate.setTranslator("57a00950-f0d1-4b41-b6ba-44ff0fc30289");
|
||||
translate.setDocument(Zotero.HTTP.wrapDocument(doc, url));
|
||||
translate.setHandler("translators", function(translate, detected) {
|
||||
if(detected.length) {
|
||||
deferred.resolve(_promiseTranslate(translate, libraryID));
|
||||
} else {
|
||||
deferred.resolve(Zotero.Promise.try(function() {
|
||||
return queryGoogle(goodLines, libraryID, tries-1);
|
||||
}));
|
||||
}
|
||||
});
|
||||
translate.getTranslators();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
catch (e) {
|
||||
if(e.name == "recognizePDF.limit") {
|
||||
queryLimitReached = true;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Check for CAPTCHA on a page with HTTP 200 status
|
||||
* @private
|
||||
* @param {XMLHttpRequest} xmlhttp
|
||||
* @param {Integer} tries Number of queries to attempt before giving up
|
||||
* @return {Promise} A promise resolved when PDF metadata has been retrieved
|
||||
*/
|
||||
function _checkCaptchaOK(xmlhttp, tries) {
|
||||
if(stopCheckCallback && stopCheckCallback()) {
|
||||
throw new Zotero.Exception.Alert('recognizePDF.stopped');
|
||||
}
|
||||
|
||||
Zotero.debug("RecognizePDF: (" + xmlhttp.status + ") Got page with title " + xmlhttp.response.title);
|
||||
|
||||
if(Zotero.Utilities.xpath(xmlhttp.response, "//form[@action='Captcha']").length) {
|
||||
Zotero.debug("RecognizePDF: Found CAPTCHA on page.");
|
||||
return _solveCaptcha(xmlhttp, tries);
|
||||
}
|
||||
return xmlhttp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for CAPTCHA on an error page. Handle 403 and 503 pages
|
||||
* @private
|
||||
* @param {Zotero.HTTP.UnexpectedStatusException} e HTTP response error object
|
||||
* @param {Integer} tries Number of queries to attempt before giving up
|
||||
* @param {Boolean} dontClearCookies Whether to attempt to clear cookies in
|
||||
* in order to get CAPTCHA to show up
|
||||
* @return {Promise} A promise resolved when PDF metadata has been retrieved
|
||||
*/
|
||||
var _checkCaptchaError = Zotero.Promise.coroutine(function* (e, tries, dontClearCookies) {
|
||||
if(stopCheckCallback && stopCheckCallback()) {
|
||||
throw new Zotero.Exception.Alert('recognizePDF.stopped');
|
||||
}
|
||||
|
||||
Zotero.debug("RecognizePDF: Checking for CAPTCHA on Google Scholar error page (" + e.status + ")");
|
||||
|
||||
// Check for captcha on error page
|
||||
if(e instanceof Zotero.HTTP.UnexpectedStatusException
|
||||
&& (e.status == 403 || e.status == 503) && e.xmlhttp.response) {
|
||||
if(_extractCaptchaFormData(e.xmlhttp.response)) {
|
||||
Zotero.debug("RecognizePDF: CAPTCHA found");
|
||||
return _solveCaptcha(e.xmlhttp, tries);
|
||||
} else if(!dontClearCookies && e.xmlhttp.channel) { // Make sure we can obtain original URL
|
||||
// AFAICT, for 403 errors, GS just says "sorry, try later",
|
||||
// but if you clear cookies, you get a CAPTCHA
|
||||
Zotero.debug("RecognizePDF: No CAPTCHA detected on page. Clearing cookies.");
|
||||
if(!_clearGSCookies(e.xmlhttp.channel.originalURI.host)) {
|
||||
//user said no or no cookies removed
|
||||
throw new Zotero.Exception.Alert('recognizePDF.limit');
|
||||
}
|
||||
// Redo GET request
|
||||
Zotero.debug("RecognizePDF: Reloading page after clearing cookies.");
|
||||
return Zotero.HTTP.request(
|
||||
"GET", e.xmlhttp.channel.originalURI.spec, { "responseType": "document" }
|
||||
)
|
||||
.then(
|
||||
function (xmlhttp) {
|
||||
return _checkCaptchaOK(xmlhttp, tries);
|
||||
},
|
||||
function (e) {
|
||||
return _checkCaptchaError(e, tries, true); // Don't try this again
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Zotero.debug("RecognizePDF: Google Scholar returned an unexpected page"
|
||||
+ " with status " + e.status);
|
||||
throw new Zotero.Exception.Alert('recognizePDF.limit');
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
|
||||
/**
|
||||
* Prompt user to enter CPATCHA
|
||||
* @private
|
||||
* @param {XMLHttpRequest} xmlhttp
|
||||
* @param {Integer} [tries] Number of queries to attempt before giving up
|
||||
* @return {Promise} A promise resolved when PDF metadata has been retrieved
|
||||
*/
|
||||
function _solveCaptcha(xmlhttp, tries) {
|
||||
var doc = xmlhttp.response;
|
||||
|
||||
if(tries === undefined) tries = 3;
|
||||
|
||||
if(!tries) {
|
||||
Zotero.debug("RecognizePDF: Failed to solve CAPTCHA after multiple attempts.");
|
||||
throw new Zotero.Exception.Alert('recognizePDF.limit');
|
||||
}
|
||||
|
||||
tries--;
|
||||
var formData = doc && _extractCaptchaFormData(doc);
|
||||
if(!formData) {
|
||||
Zotero.debug("RecognizePDF: Could not find CAPTCHA on page.");
|
||||
throw new Zotero.Exception.Alert('recognizePDF.limit');
|
||||
}
|
||||
|
||||
var io = { dataIn: {
|
||||
title: Zotero.getString("recognizePDF.captcha.title"),
|
||||
description: Zotero.getString("recognizePDF.captcha.description"),
|
||||
imgUrl: formData.img
|
||||
}};
|
||||
|
||||
_progressWindow.openDialog("chrome://zotero/content/captcha.xul", "",
|
||||
"chrome,modal,resizable=no,centerscreen", io);
|
||||
|
||||
if(!io.dataOut) {
|
||||
Zotero.debug("RecognizePDF: No CAPTCHA entered");
|
||||
throw new Zotero.Exception.Alert('recognizePDF.limit');
|
||||
}
|
||||
|
||||
Zotero.debug('RecognizePDF: User entered "' + io.dataOut.captcha + '" for CAPTCHA');
|
||||
formData.input.captcha = io.dataOut.captcha;
|
||||
var url = '', prop;
|
||||
for(prop in formData.input) {
|
||||
url += '&' + encodeURIComponent(prop) + '='
|
||||
+ encodeURIComponent(formData.input[prop]);
|
||||
}
|
||||
|
||||
url = formData.action + '?' + url.substr(1);
|
||||
|
||||
return Zotero.HTTP.promise("GET", url, {"responseType":"document"})
|
||||
.then(function(xmlhttp) {
|
||||
return _checkCaptchaOK(xmlhttp, tries);
|
||||
},
|
||||
function(e) {
|
||||
return _checkCaptchaError(e, tries);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract CAPTCHA form-related data from the CAPTCHA page
|
||||
* @private
|
||||
* @param {Document} doc DOM document object for the CAPTCHA page
|
||||
* @return {Object} Object containing data describing CAPTCHA form
|
||||
*/
|
||||
function _extractCaptchaFormData(doc) {
|
||||
var formData = {};
|
||||
|
||||
var img = doc.getElementsByTagName('img')[0];
|
||||
if(!img) return;
|
||||
formData.img = img.src;
|
||||
|
||||
var form = doc.forms[0];
|
||||
if(!form) return;
|
||||
|
||||
formData.action = form.action;
|
||||
formData.input = {};
|
||||
var inputs = form.getElementsByTagName('input');
|
||||
for(var i=0, n=inputs.length; i<n; i++) {
|
||||
if(!inputs[i].name) continue;
|
||||
formData.input[inputs[i].name] = inputs[i].value;
|
||||
}
|
||||
|
||||
formData.continue = "https://scholar.google.com";
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Google cookies to get the CAPTCHA page to appear
|
||||
* @private
|
||||
* @param {String} host Host of the Google Scholar page (in case it's proxied)
|
||||
* @return {Boolean} Whether any cookies were cleared
|
||||
*/
|
||||
function _clearGSCookies(host) {
|
||||
/* There don't seem to be any negative effects of deleting GDSESS
|
||||
if(!Zotero.isStandalone) {
|
||||
//ask user first
|
||||
var response = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService)
|
||||
.confirm(null, "Clear Google Scholar cookies?",
|
||||
"Google Scholar is attempting to block further queries. We can "
|
||||
+ "clear certain cookies and try again. This may affect some "
|
||||
+ "temporary Google preferences or it may log you out. May we clear"
|
||||
+ " your Google Scholar cookies?");
|
||||
if(!response) return;
|
||||
}*/
|
||||
|
||||
var removed = false, cookies = cookieService.getCookiesFromHost(host);
|
||||
while(cookies.hasMoreElements()) {
|
||||
var cookie = cookies.getNext().QueryInterface(Components.interfaces.nsICookie2);
|
||||
if(["GDSESS", "PREF"].indexOf(cookie.name) !== -1) { // GDSESS doesn't seem to always be enough
|
||||
Zotero.debug("RecognizePDF: Removing cookie " + cookie.name + " for host "
|
||||
+ cookie.host + " and path " + cookie.path);
|
||||
cookieService.remove(cookie.host, cookie.name, cookie.path, false);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!removed) {
|
||||
Zotero.debug("RecognizePDF: No cookies removed");
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2018 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Tools for automatically retrieving a citation for the given PDF
|
||||
*/
|
||||
|
||||
/**
|
||||
* Front end for recognizing PDFs
|
||||
* @namespace
|
||||
*/
|
||||
|
||||
var Zotero_RecognizePDF_Dialog = new function () {
|
||||
const SUCCESS_IMAGE = 'chrome://zotero/skin/tick.png';
|
||||
const FAILURE_IMAGE = 'chrome://zotero/skin/cross.png';
|
||||
const LOADING_IMAGE = 'chrome://zotero/skin/arrow_refresh.png';
|
||||
|
||||
let _progressWindow = null;
|
||||
let _progressIndicator = null;
|
||||
let _rowIDs = [];
|
||||
|
||||
this.open = function() {
|
||||
if (_progressWindow) {
|
||||
_progressWindow.focus();
|
||||
return;
|
||||
}
|
||||
_progressWindow = window.openDialog('chrome://zotero/content/recognizePDFDialog.xul', '', 'chrome,close=yes,resizable=yes,dependent,dialog,centerscreen');
|
||||
_progressWindow.addEventListener('pageshow', _onWindowLoaded.bind(this), false);
|
||||
};
|
||||
|
||||
function close() {
|
||||
_progressWindow.close();
|
||||
}
|
||||
|
||||
function _getImageByStatus(status) {
|
||||
if (status === Zotero.RecognizePDF.ROW_PROCESSING) {
|
||||
return LOADING_IMAGE;
|
||||
}
|
||||
else if (status === Zotero.RecognizePDF.ROW_FAILED) {
|
||||
return FAILURE_IMAGE;
|
||||
}
|
||||
else if (status === Zotero.RecognizePDF.ROW_SUCCEEDED) {
|
||||
return SUCCESS_IMAGE;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function _rowToTreeItem(row) {
|
||||
let treeitem = _progressWindow.document.createElement('treeitem');
|
||||
treeitem.setAttribute('id', 'item-' + row.id);
|
||||
|
||||
let treerow = _progressWindow.document.createElement('treerow');
|
||||
|
||||
let treecell = _progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute('id', 'item-' + row.id + '-icon');
|
||||
treecell.setAttribute('src', _getImageByStatus(row.status));
|
||||
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = _progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute('label', row.fileName);
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = _progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute('id', 'item-' + row.id + '-title');
|
||||
treecell.setAttribute('label', row.message);
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treeitem.appendChild(treerow);
|
||||
return treeitem;
|
||||
}
|
||||
|
||||
function _onWindowLoaded() {
|
||||
let rows = Zotero.RecognizePDF.getRows();
|
||||
_rowIDs = [];
|
||||
let treechildren = _progressWindow.document.getElementById('treechildren');
|
||||
|
||||
for (let row of rows) {
|
||||
_rowIDs.push(row.id);
|
||||
let treeitem = _rowToTreeItem(row);
|
||||
treechildren.appendChild(treeitem);
|
||||
}
|
||||
|
||||
_progressWindow.document.getElementById('tree').addEventListener('dblclick',
|
||||
function (event) {
|
||||
_onDblClick(event, this);
|
||||
}
|
||||
);
|
||||
|
||||
_progressIndicator = _progressWindow.document.getElementById('progress-indicator');
|
||||
_progressWindow.document.getElementById('cancel-button')
|
||||
.addEventListener('command', function () {
|
||||
close();
|
||||
Zotero.RecognizePDF.cancel();
|
||||
}, false);
|
||||
|
||||
_progressWindow.document.getElementById('minimize-button')
|
||||
.addEventListener('command', function () {
|
||||
close();
|
||||
}, false);
|
||||
|
||||
_progressWindow.document.getElementById('close-button')
|
||||
.addEventListener('command', function () {
|
||||
close();
|
||||
Zotero.RecognizePDF.cancel();
|
||||
}, false);
|
||||
|
||||
_progressWindow.addEventListener('keypress', function (e) {
|
||||
if (e.keyCode === KeyEvent.DOM_VK_ESCAPE) {
|
||||
// If done processing, Esc is equivalent to Close rather than Minimize
|
||||
if (Zotero.RecognizePDF.getTotal() == Zotero.RecognizePDF.getProcessedTotal()) {
|
||||
Zotero.RecognizePDF.cancel();
|
||||
}
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
_progressWindow.addEventListener('unload', function () {
|
||||
Zotero.RecognizePDF.removeListener('rowadded');
|
||||
Zotero.RecognizePDF.removeListener('rowupdated');
|
||||
Zotero.RecognizePDF.removeListener('rowdeleted');
|
||||
_progressWindow = null;
|
||||
_progressIndicator = null;
|
||||
_rowIDs = [];
|
||||
});
|
||||
|
||||
_updateProgress();
|
||||
|
||||
Zotero.RecognizePDF.addListener('rowadded', function (row) {
|
||||
_rowIDs.push(row.id);
|
||||
let treeitem = _rowToTreeItem(row);
|
||||
treechildren.appendChild(treeitem);
|
||||
_updateProgress();
|
||||
});
|
||||
|
||||
Zotero.RecognizePDF.addListener('rowupdated', function (row) {
|
||||
let itemIcon = _progressWindow.document.getElementById('item-' + row.id + '-icon');
|
||||
let itemTitle = _progressWindow.document.getElementById('item-' + row.id + '-title');
|
||||
|
||||
itemIcon.setAttribute('src', _getImageByStatus(row.status));
|
||||
itemTitle.setAttribute('label', row.message);
|
||||
_updateProgress();
|
||||
});
|
||||
|
||||
Zotero.RecognizePDF.addListener('rowdeleted', function (row) {
|
||||
_rowIDs.splice(_rowIDs.indexOf(row.id), 1);
|
||||
let treeitem = _progressWindow.document.getElementById('item-' + row.id);
|
||||
treeitem.parentNode.removeChild(treeitem);
|
||||
_updateProgress();
|
||||
});
|
||||
}
|
||||
|
||||
function _updateProgress() {
|
||||
if (!_progressWindow) return;
|
||||
let total = Zotero.RecognizePDF.getTotal();
|
||||
let processed = Zotero.RecognizePDF.getProcessedTotal();
|
||||
_progressIndicator.value = processed * 100 / total;
|
||||
if (processed === total) {
|
||||
_progressWindow.document.getElementById("cancel-button").hidden = true;
|
||||
_progressWindow.document.getElementById("minimize-button").hidden = true;
|
||||
_progressWindow.document.getElementById("close-button").hidden = false;
|
||||
_progressWindow.document.getElementById("label").value = Zotero.getString('recognizePDF.complete.label');
|
||||
}
|
||||
else {
|
||||
_progressWindow.document.getElementById("cancel-button").hidden = false;
|
||||
_progressWindow.document.getElementById("minimize-button").hidden = false;
|
||||
_progressWindow.document.getElementById("close-button").hidden = true;
|
||||
_progressWindow.document.getElementById("label").value = Zotero.getString('recognizePDF.recognizing.label');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus items in Zotero library when double-clicking them in the Retrieve
|
||||
* metadata window.
|
||||
* @param {Event} event
|
||||
* @param {tree} tree XUL tree object
|
||||
* @private
|
||||
*/
|
||||
async function _onDblClick(event, tree) {
|
||||
if (event && tree && event.type === 'dblclick') {
|
||||
let itemID = _rowIDs[tree.treeBoxObject.getRowAt(event.clientX, event.clientY)];
|
||||
if (!itemID) return;
|
||||
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
if (!item) return;
|
||||
|
||||
if (item.parentItemID) itemID = item.parentItemID;
|
||||
|
||||
if (window.ZoteroOverlay) {
|
||||
window.ZoteroOverlay.toggleDisplay(true);
|
||||
}
|
||||
|
||||
window.ZoteroPane.selectItem(itemID, false, true);
|
||||
window.focus();
|
||||
}
|
||||
}
|
||||
};
|
|
@ -139,15 +139,6 @@ const ZoteroStandalone = new function() {
|
|||
|
||||
|
||||
this.updateQuickCopyOptions = function () {
|
||||
var selected = false;
|
||||
try {
|
||||
selected = Zotero.getActiveZoteroPane()
|
||||
.getSelectedItems()
|
||||
.filter(item => item.isRegularItem())
|
||||
.length;
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
var format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
|
||||
format = Zotero.QuickCopy.unserializeSetting(format);
|
||||
|
||||
|
@ -155,18 +146,16 @@ const ZoteroStandalone = new function() {
|
|||
var copyBibliography = document.getElementById('menu_copyBibliography');
|
||||
var copyExport = document.getElementById('menu_copyExport');
|
||||
|
||||
copyCitation.hidden = !selected || format.mode != 'bibliography';
|
||||
copyBibliography.hidden = !selected || format.mode != 'bibliography';
|
||||
copyExport.hidden = !selected || format.mode != 'export';
|
||||
copyCitation.hidden = format.mode != 'bibliography';
|
||||
copyBibliography.hidden = format.mode != 'bibliography';
|
||||
copyExport.hidden = format.mode != 'export';
|
||||
if (format.mode == 'export') {
|
||||
try {
|
||||
let obj = Zotero.Translators.get(format.id);
|
||||
copyExport.label = Zotero.getString('quickCopy.copyAs', obj.label);
|
||||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof Zotero.Exception.UnloadedDataException && e.dataType == 'translators')) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
Zotero.logError(e);
|
||||
copyExport.hidden = true;
|
||||
}
|
||||
}
|
||||
|
@ -281,8 +270,6 @@ ZoteroStandalone.DebugOutput = {
|
|||
submit: function () {
|
||||
// 'Zotero' isn't defined yet when this function is created, so do it inline
|
||||
return Zotero.Promise.coroutine(function* () {
|
||||
Zotero.debug("Submitting debug output");
|
||||
|
||||
Components.utils.import("resource://zotero/config.js");
|
||||
|
||||
var url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1";
|
||||
|
|
|
@ -52,9 +52,9 @@ var Zotero_Tag_Color_Chooser = new function() {
|
|||
colorPicker.setAttribute('tileWidth', 24);
|
||||
colorPicker.setAttribute('tileHeight', 24);
|
||||
colorPicker.colors = [
|
||||
'#FF6666', '#FF8C19', '#999999',
|
||||
'#5FB236', '#009980', '#2EA8E5',
|
||||
'#576DD9', '#A28AE5', '#A6507B'
|
||||
'#990000', '#CC9933', '#FF9900',
|
||||
'#FFCC00', '#007439', '#1049A9',
|
||||
'#9999FF', '#CC66CC', '#993399'
|
||||
];
|
||||
|
||||
var maxTags = document.getElementById('max-tags');
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
for (let type of types) {
|
||||
var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
|
||||
var baseFields = {};
|
||||
for (let fieldID of fieldIDs) {
|
||||
for (let fieldID in fieldIDs) {
|
||||
if (baseMappedFields.includes(fieldID)) {
|
||||
baseFields[fieldID] = Zotero.ItemFields.getBaseIDFromTypeAndField(type.id, fieldID);
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ Zotero.API = {
|
|||
return 'groups/' + Zotero.Groups.getGroupIDFromLibraryID(libraryID);
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid type '${type}'`);
|
||||
throw new Error(`Invalid type '${type}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -176,18 +176,14 @@ Zotero.Attachments = new function(){
|
|||
|
||||
|
||||
/**
|
||||
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID', 'singleFile'
|
||||
* @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID'
|
||||
* @return {Promise<Zotero.Item>}
|
||||
*/
|
||||
this.importSnapshotFromFile = Zotero.Promise.coroutine(function* (options) {
|
||||
Zotero.debug('Importing snapshot from file');
|
||||
|
||||
var file = Zotero.File.pathToFile(options.file);
|
||||
// TODO: Fix main filename when copying directory, though in that case it's probably
|
||||
// from our own export and already clean
|
||||
var fileName = options.singleFile
|
||||
? Zotero.File.getValidFileName(file.leafName)
|
||||
: file.leafName;
|
||||
var fileName = file.leafName;
|
||||
var url = options.url;
|
||||
var title = options.title;
|
||||
var contentType = options.contentType;
|
||||
|
@ -198,7 +194,7 @@ Zotero.Attachments = new function(){
|
|||
throw new Error("parentItemID not provided");
|
||||
}
|
||||
|
||||
var attachmentItem, itemID, destDir, newPath;
|
||||
var attachmentItem, itemID, destDir, newFile;
|
||||
try {
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
// Create a new attachment
|
||||
|
@ -221,23 +217,13 @@ Zotero.Attachments = new function(){
|
|||
var storageDir = Zotero.getStorageDirectory();
|
||||
destDir = this.getStorageDirectory(attachmentItem);
|
||||
yield OS.File.removeDir(destDir.path);
|
||||
newPath = OS.Path.join(destDir.path, fileName);
|
||||
// Copy single file to new directory
|
||||
if (options.singleFile) {
|
||||
yield this.createDirectoryForItem(attachmentItem);
|
||||
yield OS.File.copy(file.path, newPath);
|
||||
}
|
||||
// Copy entire parent directory (for HTML snapshots)
|
||||
else {
|
||||
file.parent.copyTo(storageDir, destDir.leafName);
|
||||
}
|
||||
file.parent.copyTo(storageDir, destDir.leafName);
|
||||
|
||||
// Point to copied file
|
||||
newFile = destDir.clone();
|
||||
newFile.append(file.leafName);
|
||||
}.bind(this));
|
||||
yield _postProcessFile(
|
||||
attachmentItem,
|
||||
Zotero.File.pathToFile(newPath),
|
||||
contentType,
|
||||
charset
|
||||
);
|
||||
yield _postProcessFile(attachmentItem, newFile, contentType, charset);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
@ -259,18 +245,8 @@ Zotero.Attachments = new function(){
|
|||
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Integer} options.libraryID
|
||||
* @param {String} options.url
|
||||
* @param {Integer} [options.parentItemID]
|
||||
* @param {Integer[]} [options.collections]
|
||||
* @param {String} [options.title]
|
||||
* @param {String} [options.fileBaseName]
|
||||
* @param {Boolean} [options.renameIfAllowedType=false]
|
||||
* @param {String} [options.contentType]
|
||||
* @param {String} [options.referrer]
|
||||
* @param {CookieSandbox} [options.cookieSandbox]
|
||||
* @param {Object} [options.saveOptions]
|
||||
* @param {Object} options - 'libraryID', 'url', 'parentItemID', 'collections', 'title',
|
||||
* 'fileBaseName', 'contentType', 'cookieSandbox', 'saveOptions'
|
||||
* @return {Promise<Zotero.Item>} - A promise for the created attachment item
|
||||
*/
|
||||
this.importFromURL = Zotero.Promise.coroutine(function* (options) {
|
||||
|
@ -280,9 +256,7 @@ Zotero.Attachments = new function(){
|
|||
var collections = options.collections;
|
||||
var title = options.title;
|
||||
var fileBaseName = options.fileBaseName;
|
||||
var renameIfAllowedType = options.renameIfAllowedType;
|
||||
var contentType = options.contentType;
|
||||
var referrer = options.referrer;
|
||||
var cookieSandbox = options.cookieSandbox;
|
||||
var saveOptions = options.saveOptions;
|
||||
|
||||
|
@ -312,7 +286,7 @@ Zotero.Attachments = new function(){
|
|||
if (channel.responseStatus < 200 || channel.responseStatus >= 400) {
|
||||
reject(new Error("Invalid response " + channel.responseStatus + " "
|
||||
+ channel.responseStatusText + " for '" + url + "'"));
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
@ -344,11 +318,6 @@ Zotero.Attachments = new function(){
|
|||
|
||||
// Save using remote web browser persist
|
||||
var externalHandlerImport = Zotero.Promise.coroutine(function* (contentType) {
|
||||
// Rename attachment
|
||||
if (renameIfAllowedType && !fileBaseName && this.getRenamedFileTypes().includes(contentType)) {
|
||||
let parentItem = Zotero.Items.get(parentItemID);
|
||||
fileBaseName = this.getFileBaseNameFromItem(parentItem);
|
||||
}
|
||||
if (fileBaseName) {
|
||||
let ext = _getExtensionFromURL(url, contentType);
|
||||
var fileName = fileBaseName + (ext != '' ? '.' + ext : '');
|
||||
|
@ -378,12 +347,7 @@ Zotero.Attachments = new function(){
|
|||
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
|
||||
.createInstance(Components.interfaces.nsIURL);
|
||||
nsIURL.spec = url;
|
||||
var headers = {};
|
||||
if (referrer) {
|
||||
headers.Referer = referrer;
|
||||
}
|
||||
Zotero.Utilities.Internal.saveURI(wbp, nsIURL, tmpFile, headers);
|
||||
|
||||
Zotero.Utilities.Internal.saveURI(wbp, nsIURL, tmpFile);
|
||||
|
||||
yield deferred.promise;
|
||||
let sample = yield Zotero.File.getContentsAsync(tmpFile, null, 1000);
|
||||
|
@ -423,8 +387,6 @@ Zotero.Attachments = new function(){
|
|||
attachmentItem.attachmentPath = 'storage:' + fileName;
|
||||
var itemID = yield attachmentItem.save(saveOptions);
|
||||
|
||||
Zotero.Fulltext.queueItem(attachmentItem);
|
||||
|
||||
// DEBUG: Does this fail if 'storage' is symlinked to another drive?
|
||||
destDir = this.getStorageDirectory(attachmentItem).path;
|
||||
yield OS.File.move(tmpDir, destDir);
|
||||
|
@ -444,6 +406,16 @@ Zotero.Attachments = new function(){
|
|||
throw e;
|
||||
}
|
||||
|
||||
// We don't have any way of knowing that the file is flushed to disk,
|
||||
// so we just wait a second before indexing and hope for the best.
|
||||
// We'll index it later if it fails. (This may not be necessary.)
|
||||
//
|
||||
// If this is removed, the afterEach() delay in the server_connector /connector/saveSnapshot
|
||||
// tests can also be removed.
|
||||
setTimeout(function () {
|
||||
Zotero.Fulltext.indexItems([attachmentItem.id]);
|
||||
}, 1000);
|
||||
|
||||
return attachmentItem;
|
||||
}.bind(this));
|
||||
|
||||
|
@ -681,8 +653,6 @@ Zotero.Attachments = new function(){
|
|||
attachmentItem.attachmentPath = 'storage:' + fileName;
|
||||
var itemID = yield attachmentItem.save();
|
||||
|
||||
Zotero.Fulltext.queueItem(attachmentItem);
|
||||
|
||||
// DEBUG: Does this fail if 'storage' is symlinked to another drive?
|
||||
destDir = this.getStorageDirectory(attachmentItem).path;
|
||||
yield OS.File.move(tmpDir, destDir);
|
||||
|
@ -707,6 +677,21 @@ Zotero.Attachments = new function(){
|
|||
throw e;
|
||||
}
|
||||
|
||||
// We don't have any way of knowing that the file is flushed to disk,
|
||||
// so we just wait a second before indexing and hope for the best.
|
||||
// We'll index it later if it fails. (This may not be necessary.)
|
||||
if (contentType == 'application/pdf') {
|
||||
setTimeout(function () {
|
||||
Zotero.Fulltext.indexPDF(attachmentItem.getFilePath(), attachmentItem.id);
|
||||
}, 1000);
|
||||
}
|
||||
else if (Zotero.MIME.isTextType(contentType)) {
|
||||
// wbp.saveDocument consumes the document context (in Zotero.Utilities.Internal.saveDocument)
|
||||
// Seems like a mozilla bug, but nothing on bugtracker.
|
||||
// Either way, we don't rely on Zotero.Fulltext.indexDocument here anymore
|
||||
yield Zotero.Fulltext.indexItems(attachmentItem.id, true, true);
|
||||
}
|
||||
|
||||
return attachmentItem;
|
||||
});
|
||||
|
||||
|
@ -812,30 +797,6 @@ Zotero.Attachments = new function(){
|
|||
}
|
||||
|
||||
|
||||
this.getRenamedFileTypes = function () {
|
||||
try {
|
||||
var types = Zotero.Prefs.get('autoRenameFiles.fileTypes');
|
||||
return types ? types.split(',') : [];
|
||||
}
|
||||
catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.getRenamedFileBaseNameIfAllowedType = async function (parentItem, file) {
|
||||
var types = this.getRenamedFileTypes();
|
||||
var contentType = file.endsWith('.pdf')
|
||||
// Don't bother reading file if there's a .pdf extension
|
||||
? 'application/pdf'
|
||||
: await Zotero.MIME.getMIMETypeFromFile(file);
|
||||
if (!types.includes(contentType)) {
|
||||
return false;
|
||||
}
|
||||
return this.getFileBaseNameFromItem(parentItem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create directory for attachment files within storage directory
|
||||
*
|
||||
|
@ -1129,75 +1090,13 @@ Zotero.Attachments = new function(){
|
|||
|
||||
|
||||
/**
|
||||
* Move attachment item, including file, to another library
|
||||
*/
|
||||
this.moveAttachmentToLibrary = async function (attachment, libraryID, parentItemID) {
|
||||
if (attachment.libraryID == libraryID) {
|
||||
throw new Error("Attachment is already in library " + libraryID);
|
||||
}
|
||||
|
||||
Zotero.DB.requireTransaction();
|
||||
|
||||
var newAttachment = attachment.clone(libraryID);
|
||||
if (attachment.isImportedAttachment()) {
|
||||
// Attachment path isn't copied over by clone() if libraryID is different
|
||||
newAttachment.attachmentPath = attachment.attachmentPath;
|
||||
}
|
||||
if (parentItemID) {
|
||||
newAttachment.parentID = parentItemID;
|
||||
}
|
||||
await newAttachment.save();
|
||||
|
||||
// Move files over if they exist
|
||||
var oldDir;
|
||||
var newDir;
|
||||
if (newAttachment.isImportedAttachment()) {
|
||||
oldDir = this.getStorageDirectory(attachment).path;
|
||||
if (await OS.File.exists(oldDir)) {
|
||||
newDir = this.getStorageDirectory(newAttachment).path;
|
||||
// Target directory shouldn't exist, but remove it if it does
|
||||
//
|
||||
// Testing for directories in OS.File, used by removeDir(), is broken on Travis,
|
||||
// so use nsIFile
|
||||
if (Zotero.automatedTest) {
|
||||
let nsIFile = Zotero.File.pathToFile(newDir);
|
||||
if (nsIFile.exists()) {
|
||||
nsIFile.remove(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
await OS.File.removeDir(newDir, { ignoreAbsent: true });
|
||||
}
|
||||
await OS.File.move(oldDir, newDir);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await attachment.erase();
|
||||
}
|
||||
catch (e) {
|
||||
// Move files back if old item can't be deleted
|
||||
if (newAttachment.isImportedAttachment()) {
|
||||
try {
|
||||
await OS.File.move(newDir, oldDir);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
return newAttachment.id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Copy attachment item, including file, to another library
|
||||
* Copy attachment item, including files, to another library
|
||||
*/
|
||||
this.copyAttachmentToLibrary = Zotero.Promise.coroutine(function* (attachment, libraryID, parentItemID) {
|
||||
var linkMode = attachment.attachmentLinkMode;
|
||||
|
||||
if (attachment.libraryID == libraryID) {
|
||||
throw new Error("Attachment is already in library " + libraryID);
|
||||
throw ("Attachment is already in library " + libraryID);
|
||||
}
|
||||
|
||||
Zotero.DB.requireTransaction();
|
||||
|
|
|
@ -16,13 +16,13 @@ Zotero.Cite = {
|
|||
* Remove specified item IDs in-place from a citeproc-js bibliography object returned
|
||||
* by makeBibliography()
|
||||
* @param {bib} citeproc-js bibliography object
|
||||
* @param {Set} itemsToRemove Set of items to remove
|
||||
* @param {Array} itemsToRemove Array of items to remove
|
||||
*/
|
||||
"removeFromBibliography":function(bib, itemsToRemove) {
|
||||
var removeItems = [];
|
||||
for(let i in bib[0].entry_ids) {
|
||||
for(let j in bib[0].entry_ids[i]) {
|
||||
if(itemsToRemove.has(`${bib[0].entry_ids[i][j]}`)) {
|
||||
if(itemsToRemove[bib[0].entry_ids[i][j]]) {
|
||||
removeItems.push(i);
|
||||
break;
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ Zotero.Cite = {
|
|||
var sessionID = id.substr(0, slashIndex),
|
||||
session = Zotero.Integration.sessions[sessionID],
|
||||
item;
|
||||
if (session) {
|
||||
if(session) {
|
||||
item = session.embeddedZoteroItems[id.substr(slashIndex+1)];
|
||||
}
|
||||
|
||||
|
@ -315,104 +315,6 @@ Zotero.Cite = {
|
|||
} else {
|
||||
return Zotero.Items.get(id);
|
||||
}
|
||||
},
|
||||
|
||||
extraToCSL: function (extra) {
|
||||
return extra.replace(/^([A-Za-z \-]+)(:\s*.+)/gm, function (_, field, value) {
|
||||
var originalField = field;
|
||||
var field = field.toLowerCase().replace(/ /g, '-');
|
||||
// Fields from https://aurimasv.github.io/z2csl/typeMap.xml
|
||||
switch (field) {
|
||||
// Standard fields
|
||||
case 'abstract':
|
||||
case 'accessed':
|
||||
case 'annote':
|
||||
case 'archive':
|
||||
case 'archive-place':
|
||||
case 'author':
|
||||
case 'authority':
|
||||
case 'call-number':
|
||||
case 'chapter-number':
|
||||
case 'citation-label':
|
||||
case 'citation-number':
|
||||
case 'collection-editor':
|
||||
case 'collection-number':
|
||||
case 'collection-title':
|
||||
case 'composer':
|
||||
case 'container':
|
||||
case 'container-author':
|
||||
case 'container-title':
|
||||
case 'container-title-short':
|
||||
case 'dimensions':
|
||||
case 'director':
|
||||
case 'edition':
|
||||
case 'editor':
|
||||
case 'editorial-director':
|
||||
case 'event':
|
||||
case 'event-date':
|
||||
case 'event-place':
|
||||
case 'first-reference-note-number':
|
||||
case 'genre':
|
||||
case 'illustrator':
|
||||
case 'interviewer':
|
||||
case 'issue':
|
||||
case 'issued':
|
||||
case 'jurisdiction':
|
||||
case 'keyword':
|
||||
case 'language':
|
||||
case 'locator':
|
||||
case 'medium':
|
||||
case 'note':
|
||||
case 'number':
|
||||
case 'number-of-pages':
|
||||
case 'number-of-volumes':
|
||||
case 'original-author':
|
||||
case 'original-date':
|
||||
case 'original-publisher':
|
||||
case 'original-publisher-place':
|
||||
case 'original-title':
|
||||
case 'page':
|
||||
case 'page-first':
|
||||
case 'publisher':
|
||||
case 'publisher-place':
|
||||
case 'recipient':
|
||||
case 'references':
|
||||
case 'reviewed-author':
|
||||
case 'reviewed-title':
|
||||
case 'scale':
|
||||
case 'section':
|
||||
case 'source':
|
||||
case 'status':
|
||||
case 'submitted':
|
||||
case 'title':
|
||||
case 'title-short':
|
||||
case 'translator':
|
||||
case 'version':
|
||||
case 'volume':
|
||||
case 'year-suffix':
|
||||
break;
|
||||
|
||||
// Uppercase fields
|
||||
case 'doi':
|
||||
case 'isbn':
|
||||
case 'issn':
|
||||
case 'pmcid':
|
||||
case 'pmid':
|
||||
case 'url':
|
||||
field = field.toUpperCase();
|
||||
break;
|
||||
|
||||
// Weirdo
|
||||
case 'archive-location':
|
||||
field = 'archive_location';
|
||||
break;
|
||||
|
||||
// Don't change other lines
|
||||
default:
|
||||
field = originalField;
|
||||
}
|
||||
return field + value;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -596,7 +498,7 @@ Zotero.Cite.System.prototype = {
|
|||
/**
|
||||
* citeproc-js system function for getting items
|
||||
* See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#retrieveitem
|
||||
* @param {String|Integer} item - Item ID, or string item for embedded citations
|
||||
* @param {String|Integer} Item ID, or string item for embedded citations
|
||||
* @return {Object} citeproc-js item
|
||||
*/
|
||||
"retrieveItem":function retrieveItem(item) {
|
||||
|
@ -607,10 +509,10 @@ Zotero.Cite.System.prototype = {
|
|||
} else if(typeof item === "string" && (slashIndex = item.indexOf("/")) !== -1) {
|
||||
// is an embedded item
|
||||
var sessionID = item.substr(0, slashIndex);
|
||||
var session = Zotero.Integration.sessions[sessionID];
|
||||
var session = Zotero.Integration.sessions[sessionID]
|
||||
if(session) {
|
||||
var embeddedCitation = session.embeddedItems[item.substr(slashIndex+1)];
|
||||
if (embeddedCitation) {
|
||||
if(embeddedCitation) {
|
||||
embeddedCitation.id = item;
|
||||
return embeddedCitation;
|
||||
}
|
||||
|
@ -624,7 +526,7 @@ Zotero.Cite.System.prototype = {
|
|||
}
|
||||
|
||||
if(!zoteroItem) {
|
||||
throw new Error("Zotero.Cite.System.retrieveItem called on non-item "+item);
|
||||
throw "Zotero.Cite.System.retrieveItem called on non-item "+item;
|
||||
}
|
||||
|
||||
var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -76,14 +76,6 @@ Object.defineProperty(Zotero.CollectionTreeView.prototype, "selectedTreeRow", {
|
|||
});
|
||||
|
||||
|
||||
Object.defineProperty(Zotero.CollectionTreeView.prototype, 'window', {
|
||||
get: function () {
|
||||
return this._ownerDocument.defaultView;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Called by the tree itself
|
||||
*/
|
||||
|
@ -95,13 +87,6 @@ Zotero.CollectionTreeView.prototype.setTree = Zotero.Promise.coroutine(function*
|
|||
}
|
||||
this._treebox = treebox;
|
||||
|
||||
if (!this._ownerDocument) {
|
||||
try {
|
||||
this._ownerDocument = treebox.treeBody.ownerDocument;
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
// Add a keypress listener for expand/collapse
|
||||
var tree = this._treebox.treeBody.parentNode;
|
||||
tree.addEventListener('keypress', function(event) {
|
||||
|
@ -1621,7 +1606,6 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
|
|||
if (dataType == 'zotero/item') {
|
||||
var ids = data;
|
||||
var items = Zotero.Items.get(ids);
|
||||
items = Zotero.Items.keepParents(items);
|
||||
var skip = true;
|
||||
for (let item of items) {
|
||||
// Can only drag top-level items
|
||||
|
@ -1829,7 +1813,6 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
|
|||
if (treeRow.ref.libraryID != draggedCollection.libraryID) {
|
||||
// Disallow if linked collection already exists
|
||||
if (yield draggedCollection.getLinkedCollection(treeRow.ref.libraryID, true)) {
|
||||
Zotero.debug("Linked collection already exists in library");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1841,7 +1824,6 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
|
|||
// If this is allowed in the future for the root collection,
|
||||
// need to allow drag only to root
|
||||
if (yield descendent.getLinkedCollection(treeRow.ref.libraryID, true)) {
|
||||
Zotero.debug("Linked subcollection already exists in library");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2133,7 +2115,6 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
}
|
||||
|
||||
if (targetTreeRow.isPublications()) {
|
||||
items = Zotero.Items.keepParents(items);
|
||||
let io = this._treebox.treeBody.ownerDocument.defaultView
|
||||
.ZoteroPane.showPublicationsWizard(items);
|
||||
if (!io) {
|
||||
|
@ -2205,7 +2186,9 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
}
|
||||
*/
|
||||
|
||||
let lastWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
let lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
|
@ -2244,23 +2227,24 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
}
|
||||
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
|
||||
var targetLibraryID = targetTreeRow.ref.libraryID;
|
||||
|
||||
if (targetTreeRow.isCollection()) {
|
||||
var parentCollectionID = targetTreeRow.ref.id;
|
||||
}
|
||||
else {
|
||||
var parentCollectionID = false;
|
||||
}
|
||||
var addedItems = [];
|
||||
|
||||
for (var i=0; i<data.length; i++) {
|
||||
var file = data[i];
|
||||
|
||||
if (dataType == 'text/x-moz-url') {
|
||||
var url = data[i];
|
||||
let item;
|
||||
|
||||
if (url.indexOf('file:///') == 0) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
// If dragging currently loaded page, only convert to
|
||||
// file if not an HTML document
|
||||
if (win.content.location.href != url ||
|
||||
|
@ -2278,7 +2262,9 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
|
||||
// Still string, so remote URL
|
||||
if (typeof file == 'string') {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack', null, row); // TODO: don't do this
|
||||
continue;
|
||||
}
|
||||
|
@ -2287,13 +2273,13 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
}
|
||||
|
||||
if (dropEffect == 'link') {
|
||||
item = yield Zotero.Attachments.linkFromFile({
|
||||
yield Zotero.Attachments.linkFromFile({
|
||||
file: file,
|
||||
collections: parentCollectionID ? [parentCollectionID] : undefined
|
||||
});
|
||||
}
|
||||
else {
|
||||
item = yield Zotero.Attachments.importFromFile({
|
||||
yield Zotero.Attachments.importFromFile({
|
||||
file: file,
|
||||
libraryID: targetLibraryID,
|
||||
collections: parentCollectionID ? [parentCollectionID] : undefined
|
||||
|
@ -2308,12 +2294,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
addedItems.push(item);
|
||||
}
|
||||
|
||||
// Automatically retrieve metadata for PDFs
|
||||
Zotero.RecognizePDF.autoRecognizeItems(addedItems);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2017 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a HTTP-based integration interface for Zotero. The actual
|
||||
* heavy lifting occurs in the connector and/or wherever the connector delegates the heavy
|
||||
* lifting to.
|
||||
*/
|
||||
Zotero.HTTPIntegrationClient = {
|
||||
deferredResponse: null,
|
||||
sendCommandPromise: Zotero.Promise.resolve(),
|
||||
sendCommand: async function(command, args=[]) {
|
||||
let payload = JSON.stringify({command, arguments: args});
|
||||
function sendCommand() {
|
||||
Zotero.HTTPIntegrationClient.deferredResponse = Zotero.Promise.defer();
|
||||
Zotero.HTTPIntegrationClient.sendResponse.apply(Zotero.HTTPIntegrationClient,
|
||||
[200, 'application/json', payload]);
|
||||
return Zotero.HTTPIntegrationClient.deferredResponse.promise;
|
||||
}
|
||||
// Force issued commands to occur sequentially, since these are really just
|
||||
// a sequence of HTTP requests and responses.
|
||||
// We might want to consider something better later, but this has the advantage of
|
||||
// being easy to interface with as a Client, as you don't need SSE or WS.
|
||||
if (command != 'Document.complete') {
|
||||
Zotero.HTTPIntegrationClient.sendCommandPromise =
|
||||
Zotero.HTTPIntegrationClient.sendCommandPromise.then(sendCommand, sendCommand);
|
||||
} else {
|
||||
await Zotero.HTTPIntegrationClient.sendCommandPromise;
|
||||
sendCommand();
|
||||
}
|
||||
return Zotero.HTTPIntegrationClient.sendCommandPromise;
|
||||
}
|
||||
};
|
||||
|
||||
Zotero.HTTPIntegrationClient.Application = function() {
|
||||
this.primaryFieldType = "Http";
|
||||
this.secondaryFieldType = "Http";
|
||||
this.outputFormat = 'html';
|
||||
this.supportedNotes = ['footnotes'];
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Application.prototype = {
|
||||
getActiveDocument: async function() {
|
||||
let result = await Zotero.HTTPIntegrationClient.sendCommand('Application.getActiveDocument');
|
||||
this.outputFormat = result.outputFormat || this.outputFormat;
|
||||
this.supportedNotes = result.supportedNotes || this.supportedNotes;
|
||||
return new Zotero.HTTPIntegrationClient.Document(result.documentID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* See integrationTests.js
|
||||
*/
|
||||
Zotero.HTTPIntegrationClient.Document = function(documentID) {
|
||||
this._documentID = documentID;
|
||||
};
|
||||
for (let method of ["activate", "canInsertField", "displayAlert", "getDocumentData",
|
||||
"setDocumentData", "setBibliographyStyle"]) {
|
||||
Zotero.HTTPIntegrationClient.Document.prototype[method] = async function() {
|
||||
return Zotero.HTTPIntegrationClient.sendCommand("Document."+method,
|
||||
[this._documentID].concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
}
|
||||
|
||||
// @NOTE Currently unused, prompts are done using the connector
|
||||
Zotero.HTTPIntegrationClient.Document.prototype._displayAlert = async function(dialogText, icon, buttons) {
|
||||
var ps = Services.prompt;
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
|
||||
switch (buttons) {
|
||||
case DIALOG_BUTTONS_OK:
|
||||
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK; break;
|
||||
case DIALOG_BUTTONS_OK_CANCEL:
|
||||
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.STD_OK_CANCEL_BUTTONS; break;
|
||||
case DIALOG_BUTTONS_YES_NO:
|
||||
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.STD_YES_NO_BUTTONS; break;
|
||||
case DIALOG_BUTTONS_YES_NO_CANCEL:
|
||||
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_YES +
|
||||
ps.BUTTON_POS_1 * ps.BUTTON_TITLE_NO +
|
||||
ps.BUTTON_POS_2 * ps.BUTTON_TITLE_CANCEL; break;
|
||||
}
|
||||
|
||||
var result = ps.confirmEx(
|
||||
null,
|
||||
"Zotero",
|
||||
dialogText,
|
||||
buttonFlags,
|
||||
null, null, null,
|
||||
null,
|
||||
{}
|
||||
);
|
||||
|
||||
switch (buttons) {
|
||||
default:
|
||||
break;
|
||||
case DIALOG_BUTTONS_OK_CANCEL:
|
||||
case DIALOG_BUTTONS_YES_NO:
|
||||
result = (result+1)%2; break;
|
||||
case DIALOG_BUTTONS_YES_NO_CANCEL:
|
||||
result = result == 0 ? 2 : result == 2 ? 0 : 1; break;
|
||||
}
|
||||
await this.activate();
|
||||
return result;
|
||||
}
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.cleanup = async function() {};
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.cursorInField = async function(fieldType) {
|
||||
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.cursorInField", [this._documentID, fieldType]);
|
||||
if (!retVal) return null;
|
||||
return new Zotero.HTTPIntegrationClient.Field(this._documentID, retVal);
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.insertField = async function(fieldType, noteType) {
|
||||
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.insertField", [this._documentID, fieldType, parseInt(noteType) || 0]);
|
||||
return new Zotero.HTTPIntegrationClient.Field(this._documentID, retVal);
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.getFields = async function(fieldType) {
|
||||
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.getFields", [this._documentID, fieldType]);
|
||||
return retVal.map(field => new Zotero.HTTPIntegrationClient.Field(this._documentID, field));
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.convert = async function(fields, fieldType, noteTypes) {
|
||||
fields = fields.map((f) => f._id);
|
||||
await Zotero.HTTPIntegrationClient.sendCommand("Field.convert", [this._documentID, fields, fieldType, noteTypes]);
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.complete = async function() {
|
||||
Zotero.HTTPIntegrationClient.inProgress = false;
|
||||
Zotero.HTTPIntegrationClient.sendCommand("Document.complete", [this._documentID]);
|
||||
};
|
||||
|
||||
/**
|
||||
* See integrationTests.js
|
||||
*/
|
||||
Zotero.HTTPIntegrationClient.Field = function(documentID, json) {
|
||||
this._documentID = documentID;
|
||||
this._id = json.id;
|
||||
this._code = json.code;
|
||||
this._text = json.text;
|
||||
this._noteIndex = json.noteIndex;
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype = {};
|
||||
|
||||
for (let method of ["delete", "select", "removeCode"]) {
|
||||
Zotero.HTTPIntegrationClient.Field.prototype[method] = async function() {
|
||||
return Zotero.HTTPIntegrationClient.sendCommand("Field."+method,
|
||||
[this._documentID, this._id].concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
}
|
||||
Zotero.HTTPIntegrationClient.Field.prototype.getText = async function() {
|
||||
return this._text;
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype.setText = async function(text, isRich) {
|
||||
// The HTML will be stripped by Google Docs and and since we're
|
||||
// caching this value, we need to strip it ourselves
|
||||
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
var doc = parser.parseFromString(text, "text/html");
|
||||
this._text = doc.documentElement.textContent;
|
||||
return Zotero.HTTPIntegrationClient.sendCommand("Field.setText", [this._documentID, this._id, text, true]);
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype.getCode = async function() {
|
||||
return this._code;
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype.setCode = async function(code) {
|
||||
this._code = code;
|
||||
return Zotero.HTTPIntegrationClient.sendCommand("Field.setCode", [this._documentID, this._id, code]);
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype.getNoteIndex = async function() {
|
||||
return this._noteIndex;
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Field.prototype.equals = async function(arg) {
|
||||
return this._id === arg._id;
|
||||
};
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2017 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds integration endpoints related to doc integration via HTTP/connector.
|
||||
*
|
||||
* document/execCommand initiates an integration command and responds with the
|
||||
* next request for the http client (e.g. 'Application.getDocument').
|
||||
* The client should respond to document/respond with the payload and expect
|
||||
* another response with the next request, until it receives 'Document.complete'
|
||||
* at which point the integration transaction is considered complete.
|
||||
*/
|
||||
Zotero.Server.Endpoints['/connector/document/execCommand'] = function() {};
|
||||
Zotero.Server.Endpoints['/connector/document/execCommand'].prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
supportedDataTypes: ["application/json"],
|
||||
permitBookmarklet: true,
|
||||
init: function(data, sendResponse) {
|
||||
if (Zotero.HTTPIntegrationClient.inProgress) {
|
||||
// This will focus the last integration window if present
|
||||
Zotero.Integration.execCommand('http', data.command, data.docId);
|
||||
sendResponse(503, 'text/plain', 'Integration transaction is already in progress')
|
||||
return;
|
||||
}
|
||||
Zotero.HTTPIntegrationClient.inProgress = true;
|
||||
Zotero.HTTPIntegrationClient.sendResponse = sendResponse;
|
||||
Zotero.Integration.execCommand('http', data.command, data.docId);
|
||||
},
|
||||
};
|
||||
|
||||
Zotero.Server.Endpoints['/connector/document/respond'] = function() {};
|
||||
Zotero.Server.Endpoints['/connector/document/respond'].prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
supportedDataTypes: ["application/json"],
|
||||
permitBookmarklet: true,
|
||||
|
||||
init: function(data, sendResponse) {
|
||||
data = JSON.parse(data);
|
||||
if (data && data.error) {
|
||||
// Apps Script stack is a JSON object
|
||||
if (typeof data.stack != "string") {
|
||||
data.stack = JSON.stringify(data.stack);
|
||||
}
|
||||
Zotero.HTTPIntegrationClient.deferredResponse.reject(data);
|
||||
} else {
|
||||
Zotero.HTTPIntegrationClient.deferredResponse.resolve(data);
|
||||
}
|
||||
Zotero.HTTPIntegrationClient.sendResponse = sendResponse;
|
||||
}
|
||||
};
|
||||
|
||||
// For managing macOS integration and progress window focus
|
||||
Zotero.Server.Endpoints['/connector/sendToBack'] = function() {};
|
||||
Zotero.Server.Endpoints['/connector/sendToBack'].prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
supportedDataTypes: ["application/json"],
|
||||
permitBookmarklet: true,
|
||||
init: function() {
|
||||
Zotero.Utilities.Internal.sendToBack();
|
||||
},
|
||||
};
|
|
@ -459,44 +459,44 @@ Zotero.ItemTypes = new function() {
|
|||
|
||||
// HiDPI images available
|
||||
case 'attachment-link':
|
||||
case 'attachment-pdf':
|
||||
case 'attachment-web-link':
|
||||
case 'artwork':
|
||||
case 'audioRecording':
|
||||
case 'bill':
|
||||
case 'blogPost':
|
||||
case 'book':
|
||||
case 'bookSection':
|
||||
case 'case':
|
||||
case 'computerProgram':
|
||||
case 'dictionaryEntry':
|
||||
case 'email':
|
||||
case 'encyclopediaArticle':
|
||||
case 'film':
|
||||
case 'forumPost':
|
||||
case 'hearing':
|
||||
case 'instantMessage':
|
||||
case 'interview':
|
||||
case 'journalArticle':
|
||||
case 'letter':
|
||||
case 'magazineArticle':
|
||||
case 'manuscript':
|
||||
case 'newspaperArticle':
|
||||
case 'note':
|
||||
case 'patent':
|
||||
case 'presentation':
|
||||
case 'report':
|
||||
case 'statute':
|
||||
case 'thesis':
|
||||
case 'webpage':
|
||||
return "chrome://zotero/skin/treeitem-" + itemType + suffix + ".png";
|
||||
|
||||
// No HiDPI images available
|
||||
case 'attachment-snapshot':
|
||||
case 'attachment-pdf':
|
||||
case 'blogPost':
|
||||
case 'case':
|
||||
case 'conferencePaper':
|
||||
case 'dictionaryEntry':
|
||||
case 'email':
|
||||
case 'encyclopediaArticle':
|
||||
case 'hearing':
|
||||
case 'manuscript':
|
||||
case 'map':
|
||||
case 'patent':
|
||||
case 'podcast':
|
||||
case 'presentation':
|
||||
case 'radioBroadcast':
|
||||
case 'statute':
|
||||
case 'thesis':
|
||||
case 'tvBroadcast':
|
||||
case 'videoRecording':
|
||||
return "chrome://zotero/skin/treeitem-" + itemType + ".png";
|
||||
|
|
|
@ -698,7 +698,8 @@ Zotero.Collection.prototype.fromJSON = function (json) {
|
|||
this.name = json.name;
|
||||
this.parentKey = json.parentCollection ? json.parentCollection : false;
|
||||
|
||||
this.setRelations(json.relations || {});
|
||||
// TODO
|
||||
//this.setRelations(json.relations);
|
||||
}
|
||||
|
||||
|
||||
|
@ -712,7 +713,7 @@ Zotero.Collection.prototype.toJSON = function (options = {}) {
|
|||
|
||||
obj.name = this.name;
|
||||
obj.parentCollection = this.parentKey ? this.parentKey : false;
|
||||
obj.relations = this.getRelations();
|
||||
obj.relations = {}; // TEMP
|
||||
|
||||
return this._postToJSON(env);
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
Zotero.DataCache = {
|
||||
|
||||
};
|
|
@ -285,7 +285,7 @@ Zotero.DataObject.prototype._setParentKey = function(key) {
|
|||
/**
|
||||
* Returns all relations of the object
|
||||
*
|
||||
* @return {Object} - Object with predicates as keys and arrays of values
|
||||
* @return {Object} - Object with predicates as keys and arrays of URIs as values
|
||||
*/
|
||||
Zotero.DataObject.prototype.getRelations = function () {
|
||||
this._requireData('relations');
|
||||
|
@ -410,7 +410,7 @@ Zotero.DataObject.prototype.setRelations = function (newRelations) {
|
|||
|
||||
// Limit predicates to letters and colons for now
|
||||
for (let p in newRelations) {
|
||||
if (!/^[a-z]+:[a-z]+$/i.test(p)) {
|
||||
if (!/[a-z]+:[a-z]+/.test(p)) {
|
||||
throw new Error(`Invalid relation predicate '${p}'`);
|
||||
}
|
||||
}
|
||||
|
@ -1249,19 +1249,15 @@ Zotero.DataObject.prototype._finalizeErase = Zotero.Promise.coroutine(function*
|
|||
});
|
||||
|
||||
|
||||
Zotero.DataObject.prototype.toResponseJSON = function (options = {}) {
|
||||
Zotero.DataObject.prototype.toResponseJSON = function (options) {
|
||||
// TODO: library block?
|
||||
|
||||
var json = {
|
||||
return {
|
||||
key: this.key,
|
||||
version: this.version,
|
||||
meta: {},
|
||||
data: this.toJSON(options)
|
||||
};
|
||||
if (options.version) {
|
||||
json.version = json.data.version = options.version;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -234,9 +234,14 @@ Zotero.DataObjectUtilities = {
|
|||
},
|
||||
|
||||
_conditionsChanged: function (data1, data2) {
|
||||
if (!data2 || data1.length != data2.length) return true;
|
||||
for (let i = 0; i < data1.length; i++) {
|
||||
if (!Zotero.Searches.conditionEquals(data1[i], data2[i])) {
|
||||
if (!data2) return true;
|
||||
var pred1 = Object.keys(data1);
|
||||
pred1.sort();
|
||||
var pred2 = Object.keys(data2);
|
||||
pred2.sort();
|
||||
if (!Zotero.Utilities.arrayEquals(pred1, pred2)) return false;
|
||||
for (let i in pred1) {
|
||||
if (!Zotero.Utilities.arrayEquals(pred1[i], pred2[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,10 +111,6 @@ Zotero.defineProperty(Zotero.Feed.prototype, 'isFeed', {
|
|||
value: true
|
||||
});
|
||||
|
||||
Zotero.defineProperty(Zotero.Feed.prototype, 'allowsLinkedFiles', {
|
||||
value: false
|
||||
});
|
||||
|
||||
Zotero.defineProperty(Zotero.Feed.prototype, 'libraryTypes', {
|
||||
value: Object.freeze(Zotero.Feed._super.prototype.libraryTypes.concat(['feed']))
|
||||
});
|
||||
|
|
|
@ -86,10 +86,6 @@ Zotero.defineProperty(Zotero.Group.prototype, 'id', {
|
|||
set: function(v) { return this.groupID = v; }
|
||||
});
|
||||
|
||||
Zotero.defineProperty(Zotero.Group.prototype, 'allowsLinkedFiles', {
|
||||
value: false
|
||||
});
|
||||
|
||||
// Create accessors
|
||||
(function() {
|
||||
let accessors = ['name', 'description', 'version'];
|
||||
|
|
|
@ -145,9 +145,6 @@ Zotero.defineProperty(Zotero.Item.prototype, 'parentItemKey', {
|
|||
get: function() { return this.parentKey; },
|
||||
set: function(val) { return this.parentKey = val; }
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'parentItem', {
|
||||
get: function() { return Zotero.Items.get(this.parentID) || undefined; },
|
||||
});
|
||||
|
||||
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'firstCreator', {
|
||||
|
@ -1051,7 +1048,8 @@ Zotero.Item.prototype.setCreator = function (orderIndex, data) {
|
|||
var msg = "Creator type '" + Zotero.CreatorTypes.getName(data.creatorTypeID) + "' "
|
||||
+ "isn't valid for " + Zotero.ItemTypes.getName(itemTypeID)
|
||||
+ " -- changing to primary creator";
|
||||
Zotero.warn(msg);
|
||||
Zotero.debug(msg, 2);
|
||||
Components.utils.reportError(msg);
|
||||
data.creatorTypeID = Zotero.CreatorTypes.getPrimaryIDForType(itemTypeID);
|
||||
}
|
||||
|
||||
|
@ -1643,7 +1641,8 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
|||
let noteText = this._noteText ? this._noteText : '';
|
||||
// Add <div> wrapper if not present
|
||||
if (!noteText.match(/^<div class="zotero-note znv[0-9]+">[\s\S]*<\/div>$/)) {
|
||||
noteText = Zotero.Notes.notePrefix + noteText + Zotero.Notes.noteSuffix;
|
||||
// Keep consistent with getNote()
|
||||
noteText = '<div class="zotero-note znv1">' + noteText + '</div>';
|
||||
}
|
||||
|
||||
let params = [
|
||||
|
@ -2121,15 +2120,6 @@ Zotero.Item.prototype.numAttachments = function (includeTrashed) {
|
|||
}
|
||||
|
||||
|
||||
Zotero.Item.prototype.numNonHTMLFileAttachments = function () {
|
||||
this._requireData('childItems');
|
||||
return this.getAttachments()
|
||||
.map(itemID => Zotero.Items.get(itemID))
|
||||
.filter(item => item.isFileAttachment() && item.attachmentContentType != 'text/html')
|
||||
.length;
|
||||
};
|
||||
|
||||
|
||||
Zotero.Item.prototype.getFile = function () {
|
||||
Zotero.debug("Zotero.Item.prototype.getFile() is deprecated -- use getFilePath[Async]()", 2);
|
||||
|
||||
|
@ -2442,67 +2432,102 @@ Zotero.Item.prototype.fileExistsCached = function () {
|
|||
* -2 - Error renaming
|
||||
* false - Attachment file not found
|
||||
*/
|
||||
Zotero.Item.prototype.renameAttachmentFile = async function (newName, overwrite = false, unique = false) {
|
||||
var origPath = await this.getFilePathAsync();
|
||||
Zotero.Item.prototype.renameAttachmentFile = Zotero.Promise.coroutine(function* (newName, overwrite=false, unique=false) {
|
||||
var origPath = yield this.getFilePathAsync();
|
||||
if (!origPath) {
|
||||
Zotero.debug("Attachment file not found in renameAttachmentFile()", 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
let origName = OS.Path.basename(origPath);
|
||||
if (this.isImportedAttachment()) {
|
||||
var origModDate = (await OS.File.stat(origPath)).lastModificationDate;
|
||||
}
|
||||
var origName = OS.Path.basename(origPath);
|
||||
var origModDate = (yield OS.File.stat(origPath)).lastModificationDate;
|
||||
|
||||
// No change
|
||||
newName = Zotero.File.getValidFileName(newName);
|
||||
|
||||
// Ignore if no change
|
||||
if (origName === newName) {
|
||||
Zotero.debug("Filename has not changed");
|
||||
return true;
|
||||
}
|
||||
|
||||
var parentDir = OS.Path.dirname(origPath);
|
||||
var destPath = OS.Path.join(parentDir, newName);
|
||||
var destName = OS.Path.basename(destPath);
|
||||
// Get root + extension, if there is one
|
||||
var pos = destName.lastIndexOf('.');
|
||||
if (pos > 0) {
|
||||
var root = destName.substr(0, pos);
|
||||
var ext = destName.substr(pos + 1);
|
||||
}
|
||||
else {
|
||||
var root = destName;
|
||||
}
|
||||
|
||||
// Update mod time and clear hash so the file syncs
|
||||
// TODO: use an integer counter instead of mod time for change detection
|
||||
// Update mod time first, because it may fail for read-only files on Windows
|
||||
if (this.isImportedAttachment()) {
|
||||
await OS.File.setDates(origPath, null, null);
|
||||
yield OS.File.setDates(origPath, null, null);
|
||||
var result;
|
||||
var incr = 0;
|
||||
while (true) {
|
||||
// If filename already exists, add a numeric suffix to the end of the root, before
|
||||
// the extension if there is one
|
||||
if (incr) {
|
||||
if (ext) {
|
||||
destName = root + ' ' + (incr + 1) + '.' + ext;
|
||||
}
|
||||
else {
|
||||
destName = root + ' ' + (incr + 1);
|
||||
}
|
||||
destPath = OS.Path.join(parentDir, destName);
|
||||
}
|
||||
|
||||
try {
|
||||
result = yield OS.File.move(origPath, destPath, { noOverwrite: !overwrite })
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof OS.File.Error) {
|
||||
if (e.becauseExists) {
|
||||
// Increment number to create unique suffix
|
||||
if (unique) {
|
||||
incr++;
|
||||
continue;
|
||||
}
|
||||
// If no overwriting or making unique and file exists, return -1
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
newName = await Zotero.File.rename(
|
||||
origPath,
|
||||
newName,
|
||||
{
|
||||
overwrite,
|
||||
unique
|
||||
}
|
||||
);
|
||||
let destPath = OS.Path.join(OS.Path.dirname(origPath), newName);
|
||||
|
||||
await this.relinkAttachmentFile(destPath);
|
||||
yield this.relinkAttachmentFile(destPath);
|
||||
|
||||
if (this.isImportedAttachment()) {
|
||||
this.attachmentSyncedHash = null;
|
||||
this.attachmentSyncState = "to_upload";
|
||||
await this.saveTx({ skipAll: true });
|
||||
yield this.saveTx({ skipAll: true });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
||||
// Restore original modification date in case we managed to change it
|
||||
if (this.isImportedAttachment()) {
|
||||
try {
|
||||
OS.File.setDates(origPath, null, origModDate);
|
||||
} catch (e) {
|
||||
Zotero.debug(e, 2);
|
||||
}
|
||||
try {
|
||||
OS.File.setDates(origPath, null, origModDate);
|
||||
} catch (e) {
|
||||
Zotero.debug(e, 2);
|
||||
}
|
||||
|
||||
Zotero.debug(e);
|
||||
Components.utils.reportError(e);
|
||||
return -2;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
|
@ -2811,7 +2836,7 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentFilename', {
|
|||
if (!path) {
|
||||
return '';
|
||||
}
|
||||
var prefixedPath = path.match(/^(?:attachments|storage):(.*)$/);
|
||||
var prefixedPath = path.match(/^(?:attachments|storage):(.+)$/);
|
||||
if (prefixedPath) {
|
||||
return prefixedPath[1];
|
||||
}
|
||||
|
@ -3115,6 +3140,7 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentHash', {
|
|||
*
|
||||
* - Currently works on HTML, PDF and plaintext attachments
|
||||
* - Paragraph breaks will be lost in PDF content
|
||||
* - For PDFs, will return empty string if Zotero.Fulltext.pdfConverterIsRegistered() is false
|
||||
*
|
||||
* @return {Promise<String>} - A promise for attachment text or empty string if unavailable
|
||||
*/
|
||||
|
@ -3168,6 +3194,10 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentText', {
|
|||
}
|
||||
|
||||
if (reindex) {
|
||||
if (!Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
Zotero.debug("PDF converter is unavailable -- returning empty .attachmentText", 3);
|
||||
return '';
|
||||
}
|
||||
yield Zotero.Fulltext.indexItems(this.id, false);
|
||||
}
|
||||
|
||||
|
@ -3987,89 +4017,6 @@ Zotero.Item.prototype.clone = function (libraryID, options = {}) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Zotero.Item} item
|
||||
* @param {Integer} libraryID
|
||||
* @return {Zotero.Item} - New item
|
||||
*/
|
||||
Zotero.Item.prototype.moveToLibrary = async function (libraryID, onSkippedAttachment) {
|
||||
if (!this.isEditable) {
|
||||
throw new Error("Can't move item in read-only library");
|
||||
}
|
||||
var library = Zotero.Libraries.get(libraryID);
|
||||
Zotero.debug("Moving item to " + library.name);
|
||||
if (!library.editable) {
|
||||
throw new Error("Can't move item to read-only library");
|
||||
}
|
||||
var filesEditable = library.filesEditable;
|
||||
var allowsLinkedFiles = library.allowsLinkedFiles;
|
||||
|
||||
var newItem = await Zotero.DB.executeTransaction(async function () {
|
||||
// Create new clone item in target library
|
||||
var newItem = this.clone(libraryID);
|
||||
var newItemID = await newItem.save({
|
||||
skipSelect: true
|
||||
});
|
||||
|
||||
if (this.isNote()) {
|
||||
// Delete old item
|
||||
await this.erase();
|
||||
return newItem;
|
||||
}
|
||||
|
||||
// For regular items, add child items
|
||||
|
||||
// Child notes
|
||||
var noteIDs = this.getNotes();
|
||||
var notes = Zotero.Items.get(noteIDs);
|
||||
for (let note of notes) {
|
||||
let newNote = note.clone(libraryID);
|
||||
newNote.parentID = newItemID;
|
||||
await newNote.save({
|
||||
skipSelect: true
|
||||
});
|
||||
}
|
||||
|
||||
// Child attachments
|
||||
var attachmentIDs = this.getAttachments();
|
||||
var attachments = Zotero.Items.get(attachmentIDs);
|
||||
for (let attachment of attachments) {
|
||||
let linkMode = attachment.attachmentLinkMode;
|
||||
|
||||
// Skip linked files if not allowed in destination
|
||||
if (!allowsLinkedFiles && linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
Zotero.debug("Target library doesn't support linked files -- skipping attachment");
|
||||
if (onSkippedAttachment) {
|
||||
await onSkippedAttachment(attachment);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip files if not allowed in destination
|
||||
if (!filesEditable && linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
Zotero.debug("Target library doesn't allow file editing -- skipping attachment");
|
||||
if (onSkippedAttachment) {
|
||||
await onSkippedAttachment(attachment);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
await Zotero.Attachments.moveAttachmentToLibrary(
|
||||
attachment, libraryID, newItemID
|
||||
);
|
||||
}
|
||||
|
||||
return newItem;
|
||||
}.bind(this));
|
||||
|
||||
// Delete old item. Do this outside of a transaction so we don't leave stranded files
|
||||
// in the target library if deleting fails.
|
||||
await this.eraseTx();
|
||||
|
||||
return newItem;
|
||||
};
|
||||
|
||||
|
||||
Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
|
||||
Zotero.DB.requireTransaction();
|
||||
|
||||
|
@ -4194,9 +4141,6 @@ Zotero.Item.prototype.fromJSON = function (json) {
|
|||
case 'mtime':
|
||||
// Handled below
|
||||
case 'collections':
|
||||
case 'parentItem':
|
||||
case 'deleted':
|
||||
case 'inPublications':
|
||||
break;
|
||||
|
||||
case 'accessDate':
|
||||
|
@ -4225,6 +4169,15 @@ Zotero.Item.prototype.fromJSON = function (json) {
|
|||
this[field] = val;
|
||||
break;
|
||||
|
||||
case 'parentItem':
|
||||
this.parentKey = val;
|
||||
break;
|
||||
|
||||
case 'deleted':
|
||||
case 'inPublications':
|
||||
this[field] = !!val;
|
||||
break;
|
||||
|
||||
case 'creators':
|
||||
this.setCreators(json.creators);
|
||||
break;
|
||||
|
@ -4312,13 +4265,6 @@ Zotero.Item.prototype.fromJSON = function (json) {
|
|||
let note = json.note;
|
||||
this.setNote(note !== undefined ? note : "");
|
||||
}
|
||||
|
||||
// Update boolean fields that might not be present in JSON
|
||||
['deleted', 'inPublications'].forEach(field => {
|
||||
if (json[field] || this[field]) {
|
||||
this[field] = !!json[field];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -260,11 +260,6 @@ Zotero.ItemFields = new function() {
|
|||
throw new Error("Invalid field '" + baseField + '" for base field');
|
||||
}
|
||||
|
||||
// If field isn't a base field, return it if it's valid for the type
|
||||
if (!this.isBaseField(baseFieldID)) {
|
||||
return this.isValidForType(baseFieldID, itemTypeID) ? baseFieldID : false;
|
||||
}
|
||||
|
||||
return _baseTypeFields[itemTypeID][baseFieldID];
|
||||
}
|
||||
|
||||
|
|
|
@ -315,21 +315,7 @@ Zotero.Items = function() {
|
|||
item._clearChanged('itemData');
|
||||
|
||||
// Display titles
|
||||
try {
|
||||
item.updateDisplayTitle()
|
||||
}
|
||||
catch (e) {
|
||||
// A few item types need creators to be loaded. Instead of making
|
||||
// updateDisplayTitle() async and loading conditionally, just catch the error
|
||||
// and load on demand
|
||||
if (e instanceof Zotero.Exception.UnloadedDataException) {
|
||||
yield item.loadDataType('creators');
|
||||
item.updateDisplayTitle()
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
item.updateDisplayTitle()
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -429,31 +415,21 @@ Zotero.Items = function() {
|
|||
|
||||
// Convert non-HTML notes on-the-fly
|
||||
if (note !== "") {
|
||||
if (typeof note == 'number') {
|
||||
note = '' + note;
|
||||
}
|
||||
if (typeof note == 'string') {
|
||||
if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
|
||||
note = Zotero.Utilities.htmlSpecialChars(note);
|
||||
note = Zotero.Notes.notePrefix + '<p>'
|
||||
+ note.replace(/\n/g, '</p><p>')
|
||||
.replace(/\t/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
+ '</p>' + Zotero.Notes.noteSuffix;
|
||||
note = note.replace(/<p>\s*<\/p>/g, '<p> </p>');
|
||||
notesToUpdate.push([item.id, note]);
|
||||
}
|
||||
|
||||
// Don't include <div> wrapper when returning value
|
||||
let startLen = note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)[0].length;
|
||||
let endLen = 6; // "</div>".length
|
||||
note = note.substr(startLen, note.length - startLen - endLen);
|
||||
}
|
||||
// Clear null notes
|
||||
else {
|
||||
note = '';
|
||||
notesToUpdate.push([item.id, '']);
|
||||
if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
|
||||
note = Zotero.Utilities.htmlSpecialChars(note);
|
||||
note = Zotero.Notes.notePrefix + '<p>'
|
||||
+ note.replace(/\n/g, '</p><p>')
|
||||
.replace(/\t/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
+ '</p>' + Zotero.Notes.noteSuffix;
|
||||
note = note.replace(/<p>\s*<\/p>/g, '<p> </p>');
|
||||
notesToUpdate.push([item.id, note]);
|
||||
}
|
||||
|
||||
// Don't include <div> wrapper when returning value
|
||||
let startLen = note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)[0].length;
|
||||
let endLen = 6; // "</div>".length
|
||||
note = note.substr(startLen, note.length - startLen - endLen);
|
||||
}
|
||||
|
||||
item._noteText = note ? note : '';
|
||||
|
@ -749,8 +725,6 @@ Zotero.Items = function() {
|
|||
|
||||
|
||||
this.merge = function (item, otherItems) {
|
||||
Zotero.debug("Merging items");
|
||||
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
var otherItemIDs = [];
|
||||
var itemURI = Zotero.URI.getItemURI(item);
|
||||
|
@ -774,10 +748,7 @@ Zotero.Items = function() {
|
|||
}
|
||||
|
||||
// Add relations to master
|
||||
let oldRelations = otherItem.getRelations();
|
||||
for (let pred in oldRelations) {
|
||||
oldRelations[pred].forEach(obj => item.addRelation(pred, obj));
|
||||
}
|
||||
item.setRelations(otherItem.getRelations());
|
||||
|
||||
// Remove merge-tracking relations from other item, so that there aren't two
|
||||
// subjects for a given deleted object
|
||||
|
@ -1205,25 +1176,6 @@ Zotero.Items = function() {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of items with children of selected parents removed
|
||||
*
|
||||
* @return {Zotero.Item[]}
|
||||
*/
|
||||
this.keepParents = function (items) {
|
||||
var parentItems = new Set(
|
||||
items
|
||||
.filter(item => item.isTopLevelItem())
|
||||
.map(item => item.id)
|
||||
);
|
||||
return items.filter(item => {
|
||||
var parentItemID = item.parentItemID;
|
||||
// Not a child item or not a child of one of the passed items
|
||||
return !parentItemID || !parentItems.has(parentItemID);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate SQL to retrieve firstCreator field
|
||||
*
|
||||
|
@ -1464,7 +1416,6 @@ Zotero.Items = function() {
|
|||
return title.replace(/^[\[\'\"](.*)[\'\"\]]?$/, '$1')
|
||||
}
|
||||
|
||||
|
||||
Zotero.DataObjects.call(this);
|
||||
|
||||
return this;
|
||||
|
|
|
@ -190,10 +190,6 @@ Zotero.defineProperty(Zotero.Library.prototype, 'hasTrash', {
|
|||
value: true
|
||||
});
|
||||
|
||||
Zotero.defineProperty(Zotero.Library.prototype, 'allowsLinkedFiles', {
|
||||
value: true
|
||||
});
|
||||
|
||||
// Create other accessors
|
||||
(function() {
|
||||
let accessors = ['editable', 'filesEditable', 'storageVersion', 'archived'];
|
||||
|
|
|
@ -32,8 +32,7 @@ Zotero.Relations = new function () {
|
|||
|
||||
this._namespaces = {
|
||||
dc: 'http://purl.org/dc/elements/1.1/',
|
||||
owl: 'http://www.w3.org/2002/07/owl#',
|
||||
mendeleyDB: 'http://zotero.org/namespaces/mendeleyDB#'
|
||||
owl: 'http://www.w3.org/2002/07/owl#'
|
||||
};
|
||||
|
||||
var _types = ['collection', 'item'];
|
||||
|
@ -147,7 +146,7 @@ Zotero.Relations = new function () {
|
|||
* @return {Object[]} - An array of objects with a Zotero.DataObject as 'subject'
|
||||
* and a predicate string as 'predicate'
|
||||
*/
|
||||
this.getByObject = Zotero.Promise.coroutine(function* (objectType, object) {
|
||||
this.getByObject = function (objectType, object) {
|
||||
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
|
||||
var predicateIDs = [];
|
||||
var o = _subjectPredicatesByObject[objectType]
|
||||
|
@ -157,16 +156,13 @@ Zotero.Relations = new function () {
|
|||
}
|
||||
var toReturn = [];
|
||||
for (let predicateID in o) {
|
||||
for (let subjectID of o[predicateID]) {
|
||||
var subject = yield objectsClass.getAsync(subjectID);
|
||||
toReturn.push({
|
||||
subject: subject,
|
||||
predicate: Zotero.RelationPredicates.getName(predicateID)
|
||||
});
|
||||
};
|
||||
o[predicateID].forEach(subjectID => toReturn.push({
|
||||
subject: objectsClass.get(subjectID),
|
||||
predicate: Zotero.RelationPredicates.getName(predicateID)
|
||||
}));
|
||||
}
|
||||
return toReturn;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.updateUser = Zotero.Promise.coroutine(function* (fromUserID, toUserID) {
|
||||
|
@ -183,9 +179,9 @@ Zotero.Relations = new function () {
|
|||
let objects = yield Zotero.DB.columnQueryAsync(
|
||||
sql, 'http://zotero.org/users/' + fromUserID + '/%'
|
||||
);
|
||||
Zotero.DB.addCurrentCallback("commit", function* () {
|
||||
Zotero.DB.addCurrentCallback("commit", function () {
|
||||
for (let object of objects) {
|
||||
let subPrefs = yield this.getByObject(type, object);
|
||||
let subPrefs = this.getByObject(type, object);
|
||||
let newObject = object.replace(
|
||||
new RegExp("^http://zotero.org/users/" + fromUserID + "/(.*)"),
|
||||
"http://zotero.org/users/" + toUserID + "/$1"
|
||||
|
@ -269,4 +265,4 @@ Zotero.Relations = new function () {
|
|||
}
|
||||
throw ("Invalid namespace in URI '" + uri + "' in Zotero.Relations._getPrefixAndValue()");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -338,9 +338,7 @@ Zotero.SearchConditions = new function(){
|
|||
doesNotContain: true
|
||||
},
|
||||
table: 'itemNotes',
|
||||
// Exclude note prefix and suffix
|
||||
field: `SUBSTR(note, ${1 + Zotero.Notes.notePrefix.length}, `
|
||||
+ `LENGTH(note) - ${Zotero.Notes.notePrefix.length + Zotero.Notes.noteSuffix.length})`
|
||||
field: 'note'
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -350,9 +348,7 @@ Zotero.SearchConditions = new function(){
|
|||
doesNotContain: true
|
||||
},
|
||||
table: 'items',
|
||||
// Exclude note prefix and suffix
|
||||
field: `SUBSTR(note, ${1 + Zotero.Notes.notePrefix.length}, `
|
||||
+ `LENGTH(note) - ${Zotero.Notes.notePrefix.length + Zotero.Notes.noteSuffix.length})`
|
||||
field: 'note'
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
@ -96,13 +96,6 @@ Zotero.Searches = function() {
|
|||
}
|
||||
|
||||
|
||||
this.conditionEquals = function (data1, data2) {
|
||||
return data1.condition === data2.condition
|
||||
&& data1.operator === data2.operator
|
||||
&& data1.value === data2.value;
|
||||
},
|
||||
|
||||
|
||||
this._loadConditions = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
|
||||
var sql = "SELECT savedSearchID, searchConditionID, condition, operator, value, required "
|
||||
+ "FROM savedSearches LEFT JOIN savedSearchConditions USING (savedSearchID) "
|
||||
|
|
|
@ -758,13 +758,12 @@ Zotero.Tags = new function() {
|
|||
* @return {Q Promise} A Q promise for a data: URL for a PNG
|
||||
*/
|
||||
this.generateItemsListImage = function (colors, extraImage) {
|
||||
var multiplier = Zotero.hiDPI ? 2 : 1;
|
||||
var multiplier = (extraImage && extraImage.indexOf('2x') != -1) ? 2 : 1;
|
||||
|
||||
var swatchWidth = 8 * multiplier;
|
||||
var separator = 3 * multiplier;
|
||||
var extraImageSeparator = 1 * multiplier;
|
||||
var extraImageWidth = 16 * multiplier;
|
||||
var extraImageHeight = 16 * multiplier;
|
||||
var canvasHeight = 16 * multiplier;
|
||||
var swatchHeight = 8 * multiplier;
|
||||
var prependExtraImage = true;
|
||||
|
@ -832,7 +831,7 @@ Zotero.Tags = new function() {
|
|||
|
||||
// When extra image has loaded, draw it
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, x, 0, extraImageWidth, extraImageHeight);
|
||||
ctx.drawImage(img, x, 0);
|
||||
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Zotero.Promise.resolve(dataURI);
|
||||
|
@ -853,7 +852,7 @@ Zotero.Tags = new function() {
|
|||
// for the composite image once it's ready
|
||||
return _itemsListExtraImagePromises[extraImage]
|
||||
.then(function (img) {
|
||||
ctx.drawImage(img, x, 0, extraImageWidth, extraImageHeight);
|
||||
ctx.drawImage(img, x, 0);
|
||||
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Zotero.Promise.resolve(dataURI);
|
||||
|
|
|
@ -182,23 +182,6 @@ Zotero.DataDirectory = {
|
|||
|
||||
dataDir = this.defaultDir;
|
||||
|
||||
// If there's already a profile pointing to the default location, use a different
|
||||
// data directory named after the profile, as long as one either doesn't exist yet or
|
||||
// one does and it contains a database
|
||||
try {
|
||||
if ((yield Zotero.Profile.findOtherProfilesUsingDataDirectory(dataDir, false)).length) {
|
||||
let profileName = OS.Path.basename(Zotero.Profile.dir).match(/[^.]+\.(.+)/)[1];
|
||||
let newDataDir = this.defaultDir + ' ' + profileName;
|
||||
if (!(yield OS.File.exists(newDataDir))
|
||||
|| (yield OS.File.exists(OS.Path.join(newDataDir, dbFilename)))) {
|
||||
dataDir = newDataDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
// Check for ~/Zotero/zotero.sqlite
|
||||
let dbFile = OS.Path.join(dataDir, dbFilename);
|
||||
if (yield OS.File.exists(dbFile)) {
|
||||
|
@ -255,7 +238,35 @@ Zotero.DataDirectory = {
|
|||
// Read in prefs
|
||||
let prefsFile = OS.Path.join(profileDir, "prefs.js");
|
||||
if (yield OS.File.exists(prefsFile)) {
|
||||
let prefs = yield Zotero.Profile.readPrefsFromFile(prefsFile);
|
||||
// build sandbox
|
||||
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
|
||||
Components.utils.evalInSandbox(
|
||||
"var prefs = {};"+
|
||||
"function user_pref(key, val) {"+
|
||||
"prefs[key] = val;"+
|
||||
"}"
|
||||
, sandbox);
|
||||
|
||||
(yield Zotero.File.getContentsAsync(prefsFile))
|
||||
.split(/\n/)
|
||||
.filter((line) => {
|
||||
// Strip comments
|
||||
return !line.startsWith('#')
|
||||
// Only process lines in our pref branch
|
||||
&& line.includes(ZOTERO_CONFIG.PREF_BRANCH);
|
||||
})
|
||||
// Process each line individually
|
||||
.forEach((line) => {
|
||||
try {
|
||||
Zotero.debug("Processing " + line);
|
||||
Components.utils.evalInSandbox(line, sandbox);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error processing prefs line: " + line);
|
||||
}
|
||||
});
|
||||
|
||||
var prefs = sandbox.prefs;
|
||||
|
||||
// Check for data dir pref
|
||||
if (prefs['extensions.zotero.dataDir'] && prefs['extensions.zotero.useDataDir']) {
|
||||
|
|
|
@ -296,13 +296,13 @@ Zotero.Date = new function(){
|
|||
|
||||
// Parse 'yesterday'/'today'/'tomorrow'
|
||||
var lc = (string + '').toLowerCase();
|
||||
if (lc == 'yesterday' || (Zotero.isClient && lc === Zotero.getString('date.yesterday'))) {
|
||||
if (lc == 'yesterday' || (Zotero.getString && lc === Zotero.getString('date.yesterday'))) {
|
||||
string = Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60*60*24)).substr(0, 10); // no 'this' for translator sandbox
|
||||
}
|
||||
else if (lc == 'today' || (Zotero.isClient && lc == Zotero.getString('date.today'))) {
|
||||
else if (lc == 'today' || (Zotero.getString && lc == Zotero.getString('date.today'))) {
|
||||
string = Zotero.Date.dateToSQL(new Date()).substr(0, 10);
|
||||
}
|
||||
else if (lc == 'tomorrow' || (Zotero.isClient && lc == Zotero.getString('date.tomorrow'))) {
|
||||
else if (lc == 'tomorrow' || (Zotero.getString && lc == Zotero.getString('date.tomorrow'))) {
|
||||
string = Zotero.Date.dateToSQL(new Date(Date.now() + 1000*60*60*24)).substr(0, 10);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
// the same database is accessed simultaneously by multiple Zotero instances.
|
||||
const DB_LOCK_EXCLUSIVE = true;
|
||||
|
||||
Zotero.DBConnection = function(dbNameOrPath) {
|
||||
if (!dbNameOrPath) {
|
||||
Zotero.DBConnection = function(dbName) {
|
||||
if (!dbName) {
|
||||
throw ('DB name not provided in Zotero.DBConnection()');
|
||||
}
|
||||
|
||||
|
@ -70,18 +70,8 @@ Zotero.DBConnection = function(dbNameOrPath) {
|
|||
return Zotero.Date.toUnixTimestamp(d);
|
||||
});
|
||||
|
||||
// Absolute path to DB
|
||||
if (dbNameOrPath.startsWith('/') || (Zotero.isWin && dbNameOrPath.includes('\\'))) {
|
||||
this._dbName = OS.Path.basename(dbNameOrPath).replace(/\.sqlite$/, '');
|
||||
this._dbPath = dbNameOrPath;
|
||||
this._externalDB = true;
|
||||
}
|
||||
// DB name in data directory
|
||||
else {
|
||||
this._dbName = dbNameOrPath;
|
||||
this._dbPath = Zotero.DataDirectory.getDatabase(dbNameOrPath);
|
||||
this._externalDB = false;
|
||||
}
|
||||
// Private members
|
||||
this._dbName = dbName;
|
||||
this._shutdown = false;
|
||||
this._connection = null;
|
||||
this._transactionID = null;
|
||||
|
@ -101,14 +91,6 @@ Zotero.DBConnection = function(dbNameOrPath) {
|
|||
this._dbIsCorrupt = null
|
||||
|
||||
this._transactionPromise = null;
|
||||
|
||||
if (dbNameOrPath == 'zotero') {
|
||||
this.IncompatibleVersionException = function (msg, dbClientVersion) {
|
||||
this.message = msg;
|
||||
this.dbClientVersion = dbClientVersion;
|
||||
}
|
||||
this.IncompatibleVersionException.prototype = Object.create(Error.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
@ -123,7 +105,7 @@ Zotero.DBConnection = function(dbNameOrPath) {
|
|||
* @return void
|
||||
*/
|
||||
Zotero.DBConnection.prototype.test = function () {
|
||||
return this._getConnectionAsync().then(() => {});
|
||||
return this._getConnectionAsync().return();
|
||||
}
|
||||
|
||||
Zotero.DBConnection.prototype.getAsyncStatement = Zotero.Promise.coroutine(function* (sql) {
|
||||
|
@ -503,7 +485,7 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
|
|||
// Run begin callbacks
|
||||
for (var i=0; i<this._callbacks.begin.length; i++) {
|
||||
if (this._callbacks.begin[i]) {
|
||||
this._callbacks.begin[i](id);
|
||||
this._callbacks.begin[i]();
|
||||
}
|
||||
}
|
||||
var conn = this._getConnection(options) || (yield this._getConnectionAsync(options));
|
||||
|
@ -534,13 +516,13 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
|
|||
// Run temporary commit callbacks
|
||||
var f;
|
||||
while (f = this._callbacks.current.commit.shift()) {
|
||||
yield Zotero.Promise.resolve(f(id));
|
||||
yield Zotero.Promise.resolve(f());
|
||||
}
|
||||
|
||||
// Run commit callbacks
|
||||
for (var i=0; i<this._callbacks.commit.length; i++) {
|
||||
if (this._callbacks.commit[i]) {
|
||||
yield this._callbacks.commit[i](id);
|
||||
yield this._callbacks.commit[i]();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,13 +549,13 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
|
|||
// Run temporary commit callbacks
|
||||
var f;
|
||||
while (f = this._callbacks.current.rollback.shift()) {
|
||||
yield Zotero.Promise.resolve(f(id));
|
||||
yield Zotero.Promise.resolve(f());
|
||||
}
|
||||
|
||||
// Run rollback callbacks
|
||||
for (var i=0; i<this._callbacks.rollback.length; i++) {
|
||||
if (this._callbacks.rollback[i]) {
|
||||
yield Zotero.Promise.resolve(this._callbacks.rollback[i](id));
|
||||
yield Zotero.Promise.resolve(this._callbacks.rollback[i]());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -836,10 +818,9 @@ Zotero.DBConnection.prototype.logQuery = function (sql, params = [], options) {
|
|||
}
|
||||
|
||||
|
||||
Zotero.DBConnection.prototype.tableExists = Zotero.Promise.coroutine(function* (table, db) {
|
||||
Zotero.DBConnection.prototype.tableExists = Zotero.Promise.coroutine(function* (table) {
|
||||
yield this._getConnectionAsync();
|
||||
var prefix = db ? db + '.' : '';
|
||||
var sql = `SELECT COUNT(*) FROM ${prefix}sqlite_master WHERE type='table' AND tbl_name=?`;
|
||||
var sql = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name=?";
|
||||
var count = yield this.valueQueryAsync(sql, [table]);
|
||||
return !!count;
|
||||
});
|
||||
|
@ -898,7 +879,7 @@ Zotero.DBConnection.prototype.vacuum = function () {
|
|||
// TEMP
|
||||
Zotero.DBConnection.prototype.info = Zotero.Promise.coroutine(function* () {
|
||||
var info = {};
|
||||
var pragmas = ['auto_vacuum', 'cache_size', 'main.locking_mode', 'page_size'];
|
||||
var pragmas = ['auto_vacuum', 'cache_size', 'locking_mode', 'page_size'];
|
||||
for (let p of pragmas) {
|
||||
info[p] = yield Zotero.DB.valueQueryAsync(`PRAGMA ${p}`);
|
||||
}
|
||||
|
@ -913,13 +894,9 @@ Zotero.DBConnection.prototype.integrityCheck = Zotero.Promise.coroutine(function
|
|||
|
||||
|
||||
Zotero.DBConnection.prototype.checkException = function (e) {
|
||||
if (this._externalDB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e.message.includes(this.DB_CORRUPTION_STRING)) {
|
||||
// Write corrupt marker to data directory
|
||||
var file = Zotero.File.pathToFile(this._dbPath + '.is.corrupt');
|
||||
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'is.corrupt'));
|
||||
Zotero.File.putContents(file, '');
|
||||
|
||||
this._dbIsCorrupt = true;
|
||||
|
@ -970,11 +947,6 @@ Zotero.DBConnection.prototype.closeDatabase = Zotero.Promise.coroutine(function*
|
|||
|
||||
|
||||
Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function* (suffix, force) {
|
||||
if (this.skipBackup || this._externalDB || Zotero.skipLoading) {
|
||||
this._debug("Skipping backup of database '" + this._dbName + "'", 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
var storageService = Components.classes["@mozilla.org/storage/service;1"]
|
||||
.getService(Components.interfaces.mozIStorageService);
|
||||
|
||||
|
@ -1008,21 +980,27 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
|
|||
});
|
||||
|
||||
try {
|
||||
let corruptMarker = Zotero.File.pathToFile(this._dbPath + '.is.corrupt');
|
||||
var corruptMarker = Zotero.File.pathToFile(
|
||||
Zotero.DataDirectory.getDatabase(this._dbName, 'is.corrupt')
|
||||
);
|
||||
|
||||
if (this._dbIsCorrupt || corruptMarker.exists()) {
|
||||
if (this.skipBackup || Zotero.skipLoading) {
|
||||
this._debug("Skipping backup of database '" + this._dbName + "'", 1);
|
||||
return false;
|
||||
}
|
||||
else if (this._dbIsCorrupt || corruptMarker.exists()) {
|
||||
this._debug("Database '" + this._dbName + "' is marked as corrupt -- skipping backup", 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
let file = this._dbPath;
|
||||
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
|
||||
|
||||
// For standard backup, make sure last backup is old enough to replace
|
||||
if (!suffix && !force) {
|
||||
let backupFile = this._dbPath + '.bak';
|
||||
if (yield OS.File.exists(backupFile)) {
|
||||
let currentDBTime = (yield OS.File.stat(file.path)).lastModificationDate;
|
||||
let lastBackupTime = (yield OS.File.stat(backupFile)).lastModificationDate;
|
||||
var backupFile = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'bak'));
|
||||
if (yield OS.File.exists(backupFile.path)) {
|
||||
var currentDBTime = (yield OS.File.stat(file.path)).lastModificationDate;
|
||||
var lastBackupTime = (yield OS.File.stat(backupFile.path)).lastModificationDate;
|
||||
if (currentDBTime == lastBackupTime) {
|
||||
Zotero.debug("Database '" + this._dbName + "' hasn't changed -- skipping backup");
|
||||
return;
|
||||
|
@ -1043,7 +1021,7 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
|
|||
|
||||
// Copy via a temporary file so we don't run into disk space issues
|
||||
// after deleting the old backup file
|
||||
var tmpFile = this._dbPath + '.tmp';
|
||||
var tmpFile = Zotero.DataDirectory.getDatabase(this._dbName, 'tmp');
|
||||
if (yield OS.File.exists(tmpFile)) {
|
||||
try {
|
||||
yield OS.File.remove(tmpFile);
|
||||
|
@ -1060,21 +1038,18 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
|
|||
// the lock is lost
|
||||
try {
|
||||
if (DB_LOCK_EXCLUSIVE) {
|
||||
yield this.queryAsync("PRAGMA main.locking_mode=NORMAL", false, { inBackup: true });
|
||||
yield this.queryAsync("PRAGMA locking_mode=NORMAL", false, { inBackup: true });
|
||||
}
|
||||
storageService.backupDatabaseFile(
|
||||
Zotero.File.pathToFile(file),
|
||||
OS.Path.basename(tmpFile),
|
||||
Zotero.File.pathToFile(file).parent
|
||||
);
|
||||
storageService.backupDatabaseFile(file, OS.Path.basename(tmpFile), file.parent);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(e);
|
||||
Components.utils.reportError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
if (DB_LOCK_EXCLUSIVE) {
|
||||
yield this.queryAsync("PRAGMA main.locking_mode=EXCLUSIVE", false, { inBackup: true });
|
||||
yield this.queryAsync("PRAGMA locking_mode=EXCLUSIVE", false, { inBackup: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1105,7 +1080,7 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
|
|||
// Special backup
|
||||
if (!suffix && numBackups > 1) {
|
||||
// Remove oldest backup file
|
||||
let targetFile = this._dbPath + '.' + (numBackups - 1) + '.bak';
|
||||
var targetFile = Zotero.DataDirectory.getDatabase(this._dbName, (numBackups - 1) + '.bak');
|
||||
if (yield OS.File.exists(targetFile)) {
|
||||
yield OS.File.remove(targetFile);
|
||||
}
|
||||
|
@ -1115,8 +1090,12 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
|
|||
var targetNum = i;
|
||||
var sourceNum = targetNum - 1;
|
||||
|
||||
let targetFile = this._dbPath + '.' + targetNum + '.bak';
|
||||
let sourceFile = this._dbPath + '.' + (sourceNum ? sourceNum + '.bak' : 'bak')
|
||||
var targetFile = Zotero.DataDirectory.getDatabase(
|
||||
this._dbName, targetNum + '.bak'
|
||||
);
|
||||
var sourceFile = Zotero.DataDirectory.getDatabase(
|
||||
this._dbName, sourceNum ? sourceNum + '.bak' : 'bak'
|
||||
);
|
||||
|
||||
if (!(yield OS.File.exists(sourceFile))) {
|
||||
continue;
|
||||
|
@ -1128,7 +1107,9 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function
|
|||
}
|
||||
}
|
||||
|
||||
let backupFile = this._dbPath + '.' + (suffix ? suffix + '.' : '') + 'bak';
|
||||
var backupFile = Zotero.DataDirectory.getDatabase(
|
||||
this._dbName, (suffix ? suffix + '.' : '') + 'bak'
|
||||
);
|
||||
|
||||
// Remove old backup file
|
||||
if (yield OS.File.exists(backupFile)) {
|
||||
|
@ -1165,11 +1146,11 @@ Zotero.DBConnection.prototype._getConnection = function (options) {
|
|||
/*
|
||||
* Retrieve a link to the data store asynchronously
|
||||
*/
|
||||
Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
||||
Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(function* (options) {
|
||||
// If a backup is in progress, wait until it's done
|
||||
if (this._backupPromise && this._backupPromise.isPending() && (!options || !options.inBackup)) {
|
||||
Zotero.debug("Waiting for database backup to complete", 2);
|
||||
await this._backupPromise;
|
||||
yield this._backupPromise;
|
||||
}
|
||||
|
||||
if (this._connection) {
|
||||
|
@ -1180,50 +1161,48 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
|||
}
|
||||
|
||||
this._debug("Asynchronously opening database '" + this._dbName + "'");
|
||||
Zotero.debug(this._dbPath);
|
||||
|
||||
// Get the storage service
|
||||
var store = Components.classes["@mozilla.org/storage/service;1"].
|
||||
getService(Components.interfaces.mozIStorageService);
|
||||
|
||||
var file = this._dbPath;
|
||||
var backupFile = this._dbPath + '.bak';
|
||||
var fileName = OS.Path.basename(file);
|
||||
var corruptMarker = this._dbPath + '.is.corrupt';
|
||||
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
|
||||
var backupFile = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'bak'));
|
||||
|
||||
var fileName = this._dbName + '.sqlite';
|
||||
|
||||
catchBlock: try {
|
||||
if (await OS.File.exists(corruptMarker)) {
|
||||
var corruptMarker = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName, 'is.corrupt'));
|
||||
if (corruptMarker.exists()) {
|
||||
throw new Error(this.DB_CORRUPTION_STRING);
|
||||
}
|
||||
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: file
|
||||
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: file.path
|
||||
}));
|
||||
}
|
||||
catch (e) {
|
||||
// Don't deal with corrupted external dbs
|
||||
if (this._externalDB) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
Zotero.logError(e);
|
||||
|
||||
if (e.message.includes(this.DB_CORRUPTION_STRING)) {
|
||||
this._debug(`Database file '${fileName}' corrupted`, 1);
|
||||
this._debug("Database file '" + file.leafName + "' corrupted", 1);
|
||||
|
||||
// No backup file! Eek!
|
||||
if (!await OS.File.exists(backupFile)) {
|
||||
if (!backupFile.exists()) {
|
||||
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';
|
||||
var damagedFile = Zotero.File.pathToFile(
|
||||
Zotero.DataDirectory.getDatabase(this._dbName, 'damaged')
|
||||
);
|
||||
Zotero.moveToUnique(file, damagedFile);
|
||||
|
||||
// Create new main database
|
||||
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
|
||||
this._connection = store.openDatabase(file);
|
||||
|
||||
if (await OS.File.exists(corruptMarker)) {
|
||||
await OS.File.remove(corruptMarker);
|
||||
if (corruptMarker.exists()) {
|
||||
corruptMarker.remove(null);
|
||||
}
|
||||
|
||||
Zotero.alert(
|
||||
|
@ -1236,21 +1215,24 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
|||
|
||||
// Save damaged file
|
||||
this._debug('Saving damaged DB file with .damaged extension', 1);
|
||||
let damagedFile = this._dbPath + '.damaged';
|
||||
var damagedFile = Zotero.File.pathToFile(
|
||||
Zotero.DataDirectory.getDatabase(this._dbName, 'damaged')
|
||||
);
|
||||
Zotero.moveToUnique(file, damagedFile);
|
||||
|
||||
// Test the backup file
|
||||
try {
|
||||
Zotero.debug("Asynchronously opening DB connection");
|
||||
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: backupFile
|
||||
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: backupFile.path
|
||||
}));
|
||||
}
|
||||
// Can't open backup either
|
||||
catch (e) {
|
||||
// Create new main database
|
||||
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: file
|
||||
var file = Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(this._dbName));
|
||||
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: file.path
|
||||
}));
|
||||
|
||||
Zotero.alert(
|
||||
|
@ -1259,8 +1241,8 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
|||
Zotero.getString('db.dbRestoreFailed', fileName)
|
||||
);
|
||||
|
||||
if (await OS.File.exists(corruptMarker)) {
|
||||
await OS.File.remove(corruptMarker);
|
||||
if (corruptMarker.exists()) {
|
||||
corruptMarker.remove(null);
|
||||
}
|
||||
|
||||
break catchBlock;
|
||||
|
@ -1271,7 +1253,7 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
|||
// Copy backup file to main DB file
|
||||
this._debug("Restoring database '" + this._dbName + "' from backup file", 1);
|
||||
try {
|
||||
await OS.File.copy(backupFile, file);
|
||||
backupFile.copyTo(backupFile.parent, fileName);
|
||||
}
|
||||
catch (e) {
|
||||
// TODO: deal with low disk space
|
||||
|
@ -1279,7 +1261,8 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
|||
}
|
||||
|
||||
// Open restored database
|
||||
this._connection = await Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
var file = OS.Path.join(Zotero.DataDirectory.dir, fileName);
|
||||
this._connection = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
|
||||
path: file
|
||||
}));
|
||||
this._debug('Database restored', 1);
|
||||
|
@ -1288,13 +1271,13 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
|||
Zotero.getString('general.warning'),
|
||||
Zotero.getString('db.dbRestored', [
|
||||
fileName,
|
||||
Zotero.Date.getFileDateString(Zotero.File.pathToFile(backupFile)),
|
||||
Zotero.Date.getFileTimeString(Zotero.File.pathToFile(backupFile))
|
||||
Zotero.Date.getFileDateString(backupFile),
|
||||
Zotero.Date.getFileTimeString(backupFile)
|
||||
])
|
||||
);
|
||||
|
||||
if (await OS.File.exists(corruptMarker)) {
|
||||
await OS.File.remove(corruptMarker);
|
||||
if (corruptMarker.exists()) {
|
||||
corruptMarker.remove(null);
|
||||
}
|
||||
|
||||
break catchBlock;
|
||||
|
@ -1304,36 +1287,44 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) {
|
|||
throw (e);
|
||||
}
|
||||
|
||||
if (!this._externalDB) {
|
||||
if (DB_LOCK_EXCLUSIVE) {
|
||||
await this.queryAsync("PRAGMA main.locking_mode=EXCLUSIVE");
|
||||
}
|
||||
else {
|
||||
await this.queryAsync("PRAGMA main.locking_mode=NORMAL");
|
||||
}
|
||||
|
||||
// Set page cache size to 8MB
|
||||
let pageSize = await this.valueQueryAsync("PRAGMA page_size");
|
||||
let cacheSize = 8192000 / pageSize;
|
||||
await this.queryAsync("PRAGMA cache_size=" + cacheSize);
|
||||
|
||||
// Enable foreign key checks
|
||||
await this.queryAsync("PRAGMA foreign_keys=true");
|
||||
|
||||
// Register idle observer for DB backup
|
||||
Zotero.Schema.schemaUpdatePromise.then(() => {
|
||||
Zotero.debug("Initializing DB backup idle observer");
|
||||
var idleService = Components.classes["@mozilla.org/widget/idleservice;1"]
|
||||
.getService(Components.interfaces.nsIIdleService);
|
||||
idleService.addIdleObserver(this, 300);
|
||||
});
|
||||
if (DB_LOCK_EXCLUSIVE) {
|
||||
yield this.queryAsync("PRAGMA locking_mode=EXCLUSIVE");
|
||||
}
|
||||
else {
|
||||
yield this.queryAsync("PRAGMA locking_mode=NORMAL");
|
||||
}
|
||||
|
||||
// Set page cache size to 8MB
|
||||
var pageSize = yield this.valueQueryAsync("PRAGMA page_size");
|
||||
var cacheSize = 8192000 / pageSize;
|
||||
yield this.queryAsync("PRAGMA cache_size=" + cacheSize);
|
||||
|
||||
// Enable foreign key checks
|
||||
yield this.queryAsync("PRAGMA foreign_keys=true");
|
||||
|
||||
// Register idle observer for DB backup
|
||||
Zotero.Schema.schemaUpdatePromise.then(() => {
|
||||
Zotero.debug("Initializing DB backup idle observer");
|
||||
var idleService = Components.classes["@mozilla.org/widget/idleservice;1"]
|
||||
.getService(Components.interfaces.nsIIdleService);
|
||||
idleService.addIdleObserver(this, 300);
|
||||
});
|
||||
|
||||
return this._connection;
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Zotero.DBConnection.prototype._debug = function (str, level) {
|
||||
var prefix = this._dbName == 'zotero' ? '' : '[' + this._dbName + '] ';
|
||||
Zotero.debug(prefix + str, level);
|
||||
}
|
||||
|
||||
|
||||
// Initialize main database connection
|
||||
Zotero.DB = new Zotero.DBConnection('zotero');
|
||||
|
||||
Zotero.DB.IncompatibleVersionException = function (msg, dbClientVersion) {
|
||||
this.message = msg;
|
||||
this.dbClientVersion = dbClientVersion;
|
||||
}
|
||||
Zotero.DB.IncompatibleVersionException.prototype = Object.create(Error.prototype);
|
||||
|
|
|
@ -427,82 +427,6 @@ Zotero.File = new function(){
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Rename file within its parent directory
|
||||
*
|
||||
* @param {String} file - File path
|
||||
* @param {String} newName
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.overwrite=false] - Overwrite file if one exists
|
||||
* @param {Boolean} [options.unique=false] - Add suffix to create unique filename if necessary
|
||||
* @return {String|false} - New filename, or false if destination file exists and `overwrite` not set
|
||||
*/
|
||||
this.rename = async function (file, newName, options = {}) {
|
||||
var overwrite = options.overwrite || false;
|
||||
var unique = options.unique || false;
|
||||
|
||||
var origPath = file;
|
||||
var origName = OS.Path.basename(origPath);
|
||||
newName = Zotero.File.getValidFileName(newName);
|
||||
|
||||
// Ignore if no change
|
||||
if (origName === newName) {
|
||||
Zotero.debug("Filename has not changed");
|
||||
return origName;
|
||||
}
|
||||
|
||||
var parentDir = OS.Path.dirname(origPath);
|
||||
var destPath = OS.Path.join(parentDir, newName);
|
||||
var destName = OS.Path.basename(destPath);
|
||||
// Get root + extension, if there is one
|
||||
var pos = destName.lastIndexOf('.');
|
||||
if (pos > 0) {
|
||||
var root = destName.substr(0, pos);
|
||||
var ext = destName.substr(pos + 1);
|
||||
}
|
||||
else {
|
||||
var root = destName;
|
||||
}
|
||||
|
||||
var incr = 0;
|
||||
while (true) {
|
||||
// If filename already exists, add a numeric suffix to the end of the root, before
|
||||
// the extension if there is one
|
||||
if (incr) {
|
||||
if (ext) {
|
||||
destName = root + ' ' + (incr + 1) + '.' + ext;
|
||||
}
|
||||
else {
|
||||
destName = root + ' ' + (incr + 1);
|
||||
}
|
||||
destPath = OS.Path.join(parentDir, destName);
|
||||
}
|
||||
|
||||
try {
|
||||
Zotero.debug(`Renaming ${origPath} to ${OS.Path.basename(destPath)}`);
|
||||
Zotero.debug(destPath);
|
||||
await OS.File.move(origPath, destPath, { noOverwrite: !overwrite })
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof OS.File.Error) {
|
||||
if (e.becauseExists) {
|
||||
// Increment number to create unique suffix
|
||||
if (unique) {
|
||||
incr++;
|
||||
continue;
|
||||
}
|
||||
// No overwriting or making unique and file exists
|
||||
return false;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return destName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete a file if it exists, asynchronously
|
||||
*
|
||||
|
|
|
@ -24,8 +24,17 @@
|
|||
*/
|
||||
|
||||
Zotero.Fulltext = Zotero.FullText = new function(){
|
||||
const CACHE_FILE = '.zotero-ft-cache';
|
||||
|
||||
this.pdfConverterIsRegistered = pdfConverterIsRegistered;
|
||||
this.pdfInfoIsRegistered = pdfInfoIsRegistered;
|
||||
this.isCachedMIMEType = isCachedMIMEType;
|
||||
|
||||
this.pdfToolsDownloadBaseURL = ZOTERO_CONFIG.PDF_TOOLS_URL;
|
||||
this.__defineGetter__("pdfToolsName", function() { return 'Xpdf'; });
|
||||
this.__defineGetter__("pdfToolsURL", function() { return 'http://www.foolabs.com/xpdf/'; });
|
||||
this.__defineGetter__("pdfConverterName", function() { return 'pdftotext'; });
|
||||
this.__defineGetter__("pdfInfoName", function() { return 'pdfinfo'; });
|
||||
this.__defineGetter__("pdfConverterCacheFile", function () { return '.zotero-ft-cache'; });
|
||||
this.__defineGetter__("pdfInfoCacheFile", function () { return '.zotero-ft-info'; });
|
||||
|
||||
|
@ -51,10 +60,16 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
const kWbClassHiraganaLetter = 5;
|
||||
const kWbClassHWKatakanaLetter = 6;
|
||||
const kWbClassThaiLetter = 7;
|
||||
|
||||
|
||||
var _pdfConverterVersion = null;
|
||||
var _pdfConverterFileName = null;
|
||||
var _pdfConverterScript = null; // nsIFile of hidden window script on Windows
|
||||
var _pdfConverter = null; // nsIFile to executable
|
||||
var _pdfInfoVersion = null;
|
||||
var _pdfInfoFileName = null;
|
||||
var _pdfInfoScript = null; // nsIFile of redirection script
|
||||
var _pdfInfo = null; // nsIFile to executable
|
||||
var _pdfData = null;
|
||||
|
||||
var _idleObserverIsRegistered = false;
|
||||
var _idleObserverDelay = 30;
|
||||
|
@ -69,34 +84,22 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
|
||||
this.decoder = Components.classes["@mozilla.org/intl/utf8converterservice;1"].
|
||||
getService(Components.interfaces.nsIUTF8ConverterService);
|
||||
|
||||
let pdfConverterFileName = "pdftotext";
|
||||
let pdfInfoFileName = "pdfinfo";
|
||||
|
||||
if (Zotero.isWin) {
|
||||
pdfConverterFileName += '.exe';
|
||||
pdfInfoFileName += '.exe';
|
||||
}
|
||||
|
||||
let dir = FileUtils.getDir('AChrom', []).parent;
|
||||
|
||||
_pdfData = dir.clone();
|
||||
_pdfData.append('poppler-data');
|
||||
_pdfData = _pdfData.path;
|
||||
|
||||
_pdfConverter = dir.clone();
|
||||
_pdfInfo = dir.clone();
|
||||
|
||||
if(Zotero.isMac) {
|
||||
_pdfConverter = _pdfConverter.parent;
|
||||
_pdfConverter.append('MacOS');
|
||||
|
||||
_pdfInfo = _pdfInfo.parent;
|
||||
_pdfInfo.append('MacOS');
|
||||
}
|
||||
|
||||
_pdfConverter.append(pdfConverterFileName);
|
||||
_pdfInfo.append(pdfInfoFileName);
|
||||
var platform = Zotero.platform.replace(/ /g, '-');
|
||||
_pdfConverterFileName = this.pdfConverterName + '-' + platform;
|
||||
_pdfInfoFileName = this.pdfInfoName + '-' + platform;
|
||||
if (Zotero.isWin) {
|
||||
_pdfConverterFileName += '.exe';
|
||||
_pdfInfoFileName += '.exe';
|
||||
}
|
||||
|
||||
this.__defineGetter__("pdfConverterFileName", function() { return _pdfConverterFileName; });
|
||||
this.__defineGetter__("pdfConverterVersion", function() { return _pdfConverterVersion; });
|
||||
this.__defineGetter__("pdfInfoFileName", function() { return _pdfInfoFileName; });
|
||||
this.__defineGetter__("pdfInfoVersion", function() { return _pdfInfoVersion; });
|
||||
|
||||
yield this.registerPDFTool('converter');
|
||||
yield this.registerPDFTool('info');
|
||||
|
||||
Zotero.uiReadyPromise.delay(30000).then(() => {
|
||||
this.registerContentProcessor();
|
||||
|
@ -131,22 +134,6 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
});
|
||||
|
||||
|
||||
this.setPDFConverterPath = function(path) {
|
||||
_pdfConverter = Zotero.File.pathToFile(path);
|
||||
};
|
||||
|
||||
|
||||
this.setPDFInfoPath = function(path) {
|
||||
_pdfInfo = Zotero.File.pathToFile(path);
|
||||
|
||||
};
|
||||
|
||||
|
||||
this.setPDFDataPath = function(path) {
|
||||
_pdfData = path;
|
||||
};
|
||||
|
||||
|
||||
this.getLibraryVersion = function (libraryID) {
|
||||
if (!libraryID) throw new Error("libraryID not provided");
|
||||
return Zotero.DB.valueQueryAsync(
|
||||
|
@ -210,12 +197,279 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
}
|
||||
|
||||
|
||||
this.getLatestPDFToolsVersion = Zotero.Promise.coroutine(function* () {
|
||||
if (Zotero.isWin) {
|
||||
return "3.02a";
|
||||
}
|
||||
|
||||
// Find latest version for this platform
|
||||
var url = Zotero.Fulltext.pdfToolsDownloadBaseURL + 'latest.json';
|
||||
var xmlhttp = yield Zotero.HTTP.request("GET", url, { responseType: "json" });
|
||||
var json = xmlhttp.response;
|
||||
|
||||
var platform = Zotero.platform.replace(/\s/g, '-');
|
||||
var version = json[platform] || json['default'];
|
||||
|
||||
Zotero.debug("Latest PDF tools version for " + platform + " is " + version);
|
||||
|
||||
return version;
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Download and install latest PDF tool
|
||||
*/
|
||||
this.downloadPDFTool = Zotero.Promise.coroutine(function* (tool, version) {
|
||||
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
|
||||
if (tool == 'converter') {
|
||||
var fileName = this.pdfConverterFileName;
|
||||
}
|
||||
else {
|
||||
var fileName = this.pdfInfoFileName;
|
||||
}
|
||||
|
||||
var spec = this.pdfToolsDownloadBaseURL + version + "/" + fileName;
|
||||
var uri = ioService.newURI(spec, null, null);
|
||||
var tmpFile = OS.Path.join(Zotero.getTempDirectory().path, fileName);
|
||||
|
||||
yield Zotero.File.download(uri, tmpFile);
|
||||
|
||||
var fileInfo = yield OS.File.stat(tmpFile);
|
||||
|
||||
// Delete if too small, since a 404 might not be detected above
|
||||
if (fileInfo.size < 50000) {
|
||||
let msg = tmpFile + " is too small -- deleting";
|
||||
Zotero.logError(msg);
|
||||
try {
|
||||
yield OS.File.remove(tmpFile);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
var scriptExt = _getScriptExtension();
|
||||
// On Windows, write out script to hide pdftotext console window
|
||||
// TEMP: disabled
|
||||
if (false && tool == 'converter') {
|
||||
if (Zotero.isWin) {
|
||||
let content = yield Zotero.File.getContentsFromURLAsync(
|
||||
'resource://zotero/hide.' + scriptExt
|
||||
);
|
||||
var tmpScriptFile = OS.Path.join(
|
||||
Zotero.getTempDirectory().path,
|
||||
'pdftotext.' + scriptExt
|
||||
);
|
||||
yield Zotero.File.putContentsAsync(tmpScriptFile, content);
|
||||
}
|
||||
}
|
||||
// Write out output redirection script for pdfinfo
|
||||
// TEMP: disabled on Windows
|
||||
else if (!Zotero.isWin && tool == 'info') {
|
||||
let content = yield Zotero.File.getContentsFromURLAsync(
|
||||
'resource://zotero/redirect.' + scriptExt
|
||||
);
|
||||
var tmpScriptFile = OS.Path.join(
|
||||
Zotero.getTempDirectory().path,
|
||||
'pdfinfo.' + scriptExt
|
||||
);
|
||||
yield Zotero.File.putContentsAsync(tmpScriptFile, content);
|
||||
}
|
||||
|
||||
// Set permissions to 755
|
||||
if (Zotero.isMac || Zotero.isLinux) {
|
||||
yield OS.File.setPermissions(tmpFile, {
|
||||
unixMode: 0o755
|
||||
});
|
||||
if (tmpScriptFile) {
|
||||
yield OS.File.setPermissions(tmpScriptFile, {
|
||||
unixMode: 0o755
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var destDir = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
|
||||
// Move redirect script and executable into data dir
|
||||
if (tmpScriptFile) {
|
||||
yield OS.File.move(
|
||||
tmpScriptFile,
|
||||
OS.Path.join(destDir.path, OS.Path.basename(tmpScriptFile))
|
||||
);
|
||||
}
|
||||
yield OS.File.move(tmpFile, OS.Path.join(destDir.path, fileName));
|
||||
|
||||
// Write the version number to a file
|
||||
var versionFile = destDir.clone();
|
||||
versionFile.append(fileName + '.version');
|
||||
// TEMP
|
||||
if (Zotero.isWin) {
|
||||
version = '3.02a';
|
||||
}
|
||||
yield Zotero.File.putContentsAsync(versionFile, version + '');
|
||||
|
||||
yield Zotero.Fulltext.registerPDFTool(tool);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Looks for pdftotext-{platform}[.exe] in the Zotero data directory
|
||||
*
|
||||
* {platform} is navigator.platform, with spaces replaced by hyphens
|
||||
* e.g. "Win32", "Linux-i686", "MacPPC", "MacIntel", etc.
|
||||
*/
|
||||
this.registerPDFTool = Zotero.Promise.coroutine(function* (tool) {
|
||||
var errMsg = false;
|
||||
var exec = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
|
||||
|
||||
switch (tool) {
|
||||
case 'converter':
|
||||
var toolName = this.pdfConverterName;
|
||||
var fileName = _pdfConverterFileName;
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
var toolName = this.pdfInfoName;
|
||||
var fileName = _pdfInfoFileName;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid PDF tool type '" + tool + "' in Zotero.Fulltext.registerPDFTool()");
|
||||
}
|
||||
|
||||
exec.append(fileName);
|
||||
if (!exec.exists()) {
|
||||
exec = null;
|
||||
errMsg = fileName + ' not found';
|
||||
}
|
||||
|
||||
if (!exec) {
|
||||
if (tool == 'converter') {
|
||||
Zotero.debug(errMsg + ' -- PDF indexing disabled');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var versionFile = exec.parent;
|
||||
versionFile.append(fileName + '.version');
|
||||
if (versionFile.exists()) {
|
||||
try {
|
||||
var version = (yield Zotero.File.getSample(versionFile)).split(/[\r\n\s]/)[0];
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
}
|
||||
if (!version) {
|
||||
var version = 'UNKNOWN';
|
||||
}
|
||||
|
||||
// If scripts exist, use those instead
|
||||
switch (tool) {
|
||||
case 'converter':
|
||||
// TEMP: disabled
|
||||
if (false && Zotero.isWin) {
|
||||
var script = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
|
||||
script.append('pdftotext.' + _getScriptExtension())
|
||||
if (script.exists()) {
|
||||
Zotero.debug(script.leafName + " registered");
|
||||
_pdfConverterScript = script;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
// Modified 3.02 version doesn't use redirection script
|
||||
if (version.startsWith('3.02')) break;
|
||||
|
||||
var script = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
|
||||
// TEMP: disabled on Win
|
||||
if (!Zotero.isWin) {
|
||||
script.append('pdfinfo.' + _getScriptExtension())
|
||||
// The redirection script is necessary to run pdfinfo
|
||||
if (!script.exists()) {
|
||||
Zotero.debug(script.leafName + " not found -- PDF statistics disabled");
|
||||
return false;
|
||||
}
|
||||
Zotero.debug(toolName + " redirection script registered");
|
||||
_pdfInfoScript = script;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (tool) {
|
||||
case 'converter':
|
||||
_pdfConverter = exec;
|
||||
_pdfConverterVersion = version;
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
_pdfInfo = exec;
|
||||
_pdfInfoVersion = version;
|
||||
break;
|
||||
}
|
||||
|
||||
Zotero.debug(toolName + ' version ' + version + ' registered');
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Unregister and delete PDF tools
|
||||
*
|
||||
* Used only for tests
|
||||
*/
|
||||
this.uninstallPDFTools = Zotero.Promise.coroutine(function* () {
|
||||
Zotero.debug("Uninstalling PDF tools");
|
||||
|
||||
if (_pdfConverter) {
|
||||
yield Zotero.File.removeIfExists(_pdfConverter.path);
|
||||
yield Zotero.File.removeIfExists(_pdfConverter.path + ".version");
|
||||
}
|
||||
if (_pdfInfo) {
|
||||
yield Zotero.File.removeIfExists(_pdfInfo.path);
|
||||
yield Zotero.File.removeIfExists(_pdfInfo.path + ".version");
|
||||
}
|
||||
if (_pdfConverterScript) yield Zotero.File.removeIfExists(_pdfConverterScript.path);
|
||||
if (_pdfInfoScript) yield Zotero.File.removeIfExists(_pdfInfoScript.path);
|
||||
|
||||
_pdfConverter = null;
|
||||
_pdfInfo = null;
|
||||
_pdfInfoScript = null;
|
||||
});
|
||||
|
||||
|
||||
function pdfConverterIsRegistered() {
|
||||
return !!_pdfConverter;
|
||||
}
|
||||
|
||||
|
||||
function pdfInfoIsRegistered() {
|
||||
return !!_pdfInfo;
|
||||
}
|
||||
|
||||
|
||||
this.getPDFConverterExecAndArgs = function () {
|
||||
if (!this.pdfConverterIsRegistered()) {
|
||||
throw new Error("PDF converter is not registered");
|
||||
}
|
||||
|
||||
if (_pdfConverterScript) {
|
||||
return {
|
||||
exec: _pdfConverterScript,
|
||||
args: [_pdfConverter.path]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
exec: _pdfConverter,
|
||||
args: ['-datadir', _pdfData]
|
||||
args: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
@ -261,10 +515,6 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
var indexString = Zotero.Promise.coroutine(function* (text, charset, itemID, stats, version, synced) {
|
||||
var words = this.semanticSplitter(text, charset);
|
||||
|
||||
while (Zotero.DB.inTransaction()) {
|
||||
yield Zotero.DB.waitForTransaction('indexString()');
|
||||
}
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
this.clearItemWords(itemID, true);
|
||||
yield indexWords(itemID, words, stats, version, synced);
|
||||
|
@ -424,6 +674,11 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
* @return {Promise}
|
||||
*/
|
||||
this.indexPDF = Zotero.Promise.coroutine(function* (filePath, itemID, allPages) {
|
||||
if (!_pdfConverter) {
|
||||
Zotero.debug("PDF tools are not installed -- skipping indexing");
|
||||
return false;
|
||||
}
|
||||
|
||||
var maxPages = Zotero.Prefs.get('fulltext.pdfMaxPages');
|
||||
if (maxPages == 0) {
|
||||
return false;
|
||||
|
@ -442,21 +697,38 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
var infoFilePath = OS.Path.join(parentDirPath, this.pdfInfoCacheFile);
|
||||
var cacheFilePath = OS.Path.join(parentDirPath, this.pdfConverterCacheFile);
|
||||
|
||||
|
||||
var args = [filePath, infoFilePath];
|
||||
|
||||
try {
|
||||
yield Zotero.Utilities.Internal.exec(_pdfInfo, args);
|
||||
var totalPages = yield getTotalPagesFromFile(itemID);
|
||||
// Modified 3.02 version that can output a text file directly
|
||||
if (_pdfInfo && _pdfInfoVersion.startsWith('3.02')) {
|
||||
let args = [filePath, infoFilePath];
|
||||
|
||||
try {
|
||||
yield Zotero.Utilities.Internal.exec(_pdfInfo, args);
|
||||
var totalPages = yield getTotalPagesFromFile(itemID);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("Error running pdfinfo");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("Error running " + _pdfInfo.path, 1);
|
||||
Zotero.logError(e);
|
||||
// Use redirection script
|
||||
else if (_pdfInfoScript) {
|
||||
let args = [_pdfInfo.path, filePath, infoFilePath];
|
||||
|
||||
try {
|
||||
yield Zotero.Utilities.Internal.exec(_pdfInfoScript, args);
|
||||
var totalPages = yield getTotalPagesFromFile(itemID);
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
Zotero.debug("Error running pdfinfo", 1);
|
||||
Zotero.debug(e, 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Zotero.debug(this.pdfInfoName + " is not available");
|
||||
}
|
||||
|
||||
|
||||
var {exec, args} = this.getPDFConverterExecAndArgs();
|
||||
args.push('-nopgbrk');
|
||||
args.push('-enc', 'UTF-8', '-nopgbrk');
|
||||
|
||||
if (allPages) {
|
||||
if (totalPages) {
|
||||
|
@ -473,8 +745,9 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
yield Zotero.Utilities.Internal.exec(exec, args);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("Error running " + exec.path, 1);
|
||||
Zotero.logError(e);
|
||||
Components.utils.reportError(e);
|
||||
Zotero.debug("Error running pdftotext", 1);
|
||||
Zotero.debug(e, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -527,61 +800,22 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
yield indexFile(path, item.attachmentContentType, item.attachmentCharset, itemID, complete);
|
||||
}
|
||||
catch (e) {
|
||||
if (ignoreErrors) {
|
||||
if (ignoreErrors) {
|
||||
try {
|
||||
yield indexFile(path, item.attachmentContentType, item.attachmentCharset, itemID, complete);
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError("Error indexing " + path);
|
||||
Zotero.logError(e);
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield indexFile(path, item.attachmentContentType, item.attachmentCharset, itemID, complete);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// TEMP: Temporary mechanism to serialize indexing of new attachments
|
||||
//
|
||||
// This should instead save the itemID to a table that's read by the content processor
|
||||
var _queue = [];
|
||||
var _indexing = false;
|
||||
var _nextIndexTime;
|
||||
var _indexDelay = 5000;
|
||||
var _indexInterval = 500;
|
||||
this.queueItem = function (item) {
|
||||
// Don't index files in the background during tests
|
||||
if (Zotero.test) return;
|
||||
|
||||
_queue.push(item.id);
|
||||
_nextIndexTime = Date.now() + _indexDelay;
|
||||
setTimeout(() => {
|
||||
_processNextItem()
|
||||
}, _indexDelay);
|
||||
};
|
||||
|
||||
async function _processNextItem() {
|
||||
if (!_queue.length) return;
|
||||
// Another _processNextItem() was scheduled
|
||||
if (Date.now() < _nextIndexTime) return;
|
||||
// If indexing is already running, _processNextItem() will be called when it's done
|
||||
if (_indexing) return;
|
||||
_indexing = true;
|
||||
var itemID = _queue.shift();
|
||||
try {
|
||||
await Zotero.Fulltext.indexItems([itemID], false, true);
|
||||
}
|
||||
finally {
|
||||
_indexing = false;
|
||||
}
|
||||
setTimeout(() => {
|
||||
_processNextItem();
|
||||
}, _indexInterval);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Full-text content syncing
|
||||
//
|
||||
|
@ -820,8 +1054,6 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
* Start the idle observer for the background content processor
|
||||
*/
|
||||
this.registerContentProcessor = function () {
|
||||
// Don't start idle observer during tests
|
||||
if (Zotero.test) return;
|
||||
if (!Zotero.Prefs.get('sync.fulltext.enabled')) return;
|
||||
|
||||
if (!_idleObserverIsRegistered) {
|
||||
|
@ -1392,12 +1624,7 @@ Zotero.Fulltext = Zotero.FullText = new function(){
|
|||
* Item must be a non-web-link attachment that isn't already fully indexed
|
||||
*/
|
||||
this.canReindex = Zotero.Promise.coroutine(function* (item) {
|
||||
if (item.isAttachment()
|
||||
&& item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
let contentType = item.attachmentContentType;
|
||||
if (!contentType || contentType != 'application/pdf' && !Zotero.MIME.isTextType(contentType)) {
|
||||
return false;
|
||||
}
|
||||
if (item.isAttachment() && item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
switch (yield this.getIndexedState(item)) {
|
||||
case this.INDEX_STATE_UNAVAILABLE:
|
||||
case this.INDEX_STATE_UNINDEXED:
|
||||
|
|
|
@ -12,8 +12,6 @@ Zotero.HTTP = new function() {
|
|||
this.UnexpectedStatusException = function(xmlhttp, msg) {
|
||||
this.xmlhttp = xmlhttp;
|
||||
this.status = xmlhttp.status;
|
||||
this.channelStatus = null;
|
||||
this.responseStatus = null;
|
||||
this.channel = xmlhttp.channel;
|
||||
this.message = msg;
|
||||
this.stack = new Error().stack;
|
||||
|
@ -35,24 +33,6 @@ Zotero.HTTP = new function() {
|
|||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
}
|
||||
|
||||
// If the connection failed, try to find out what really happened
|
||||
if (!this.status) {
|
||||
try {
|
||||
if (xmlhttp.channel.status) {
|
||||
this.channelStatus = xmlhttp.channel.status;
|
||||
Zotero.debug("Channel status was " + this.channelStatus, 2);
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
try {
|
||||
if (xmlhttp.channel.responseStatus) {
|
||||
this.responseStatus = xmlhttp.channel.responseStatus;
|
||||
Zotero.debug("Response status was " + this.responseStatus, 2);
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
};
|
||||
this.UnexpectedStatusException.prototype = Object.create(Error.prototype);
|
||||
this.UnexpectedStatusException.prototype.is4xx = function () {
|
||||
|
@ -268,18 +248,6 @@ Zotero.HTTP = new function() {
|
|||
xmlhttp.onloadend = function() {
|
||||
var status = xmlhttp.status;
|
||||
|
||||
// If an invalid HTTP response (e.g., NS_ERROR_INVALID_CONTENT_ENCODING) includes a
|
||||
// 4xx or 5xx HTTP response code, swap it in, since it might be enough info to do
|
||||
// what we need (e.g., verify a 404 from a WebDAV server)
|
||||
try {
|
||||
if (!status && xmlhttp.channel.responseStatus >= 400) {
|
||||
Zotero.warn(`Overriding status for invalid response for ${dispURL} `
|
||||
+ `(${xmlhttp.channel.status})`);
|
||||
status = xmlhttp.channel.responseStatus;
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
if (options.successCodes) {
|
||||
var success = options.successCodes.indexOf(status) != -1;
|
||||
}
|
||||
|
@ -296,13 +264,13 @@ Zotero.HTTP = new function() {
|
|||
|
||||
if(success) {
|
||||
Zotero.debug("HTTP " + method + " " + dispURL
|
||||
+ " succeeded with " + status);
|
||||
+ " succeeded with " + xmlhttp.status);
|
||||
if (options.debug) {
|
||||
Zotero.debug(xmlhttp.responseText);
|
||||
}
|
||||
deferred.resolve(xmlhttp);
|
||||
} else {
|
||||
let msg = "HTTP " + method + " " + dispURL + " failed with status code " + status;
|
||||
let msg = "HTTP " + method + " " + dispURL + " failed with status code " + xmlhttp.status;
|
||||
if (!xmlhttp.responseType && xmlhttp.responseText) {
|
||||
msg += ":\n\n" + xmlhttp.responseText;
|
||||
}
|
||||
|
@ -1056,9 +1024,6 @@ Zotero.HTTP = new function() {
|
|||
}
|
||||
|
||||
let secInfo = channel.securityInfo;
|
||||
let msg;
|
||||
let dialogButtonText;
|
||||
let dialogButtonCallback;
|
||||
if (secInfo instanceof Ci.nsITransportSecurityInfo) {
|
||||
secInfo.QueryInterface(Ci.nsITransportSecurityInfo);
|
||||
if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
|
||||
|
@ -1087,15 +1052,13 @@ Zotero.HTTP = new function() {
|
|||
== Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
|
||||
msg = Zotero.getString('sync.error.sslConnectionError');
|
||||
}
|
||||
if (msg) {
|
||||
throw new Zotero.HTTP.SecurityException(
|
||||
msg,
|
||||
{
|
||||
dialogButtonText,
|
||||
dialogButtonCallback
|
||||
}
|
||||
);
|
||||
}
|
||||
throw new Zotero.HTTP.SecurityException(
|
||||
msg,
|
||||
{
|
||||
dialogButtonText,
|
||||
dialogButtonCallback
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -67,18 +67,22 @@ Zotero.ItemTreeView.prototype.regularOnly = false;
|
|||
Zotero.ItemTreeView.prototype.expandAll = false;
|
||||
Zotero.ItemTreeView.prototype.collapseAll = false;
|
||||
|
||||
Object.defineProperty(Zotero.ItemTreeView.prototype, 'window', {
|
||||
get: function () {
|
||||
return this._ownerDocument.defaultView;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Called by the tree itself
|
||||
*/
|
||||
Zotero.ItemTreeView.prototype.setTree = async function (treebox) {
|
||||
try {
|
||||
Zotero.debug("Setting tree for " + this.collectionTreeRow.id + " items view " + this.id);
|
||||
var start = Date.now();
|
||||
// Try to set the window document if not yet set
|
||||
if (treebox && !this._ownerDocument) {
|
||||
try {
|
||||
this._ownerDocument = treebox.treeBody.ownerDocument;
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (this._treebox) {
|
||||
if (this._needsSort) {
|
||||
this.sort();
|
||||
|
@ -86,39 +90,28 @@ Zotero.ItemTreeView.prototype.setTree = async function (treebox) {
|
|||
return;
|
||||
}
|
||||
|
||||
var start = Date.now();
|
||||
|
||||
Zotero.debug("Setting tree for " + this.collectionTreeRow.id + " items view " + this.id);
|
||||
|
||||
if (!treebox) {
|
||||
Zotero.debug("Treebox not passed in setTree()", 2);
|
||||
return;
|
||||
}
|
||||
this._treebox = treebox;
|
||||
|
||||
if (!this._ownerDocument) {
|
||||
try {
|
||||
this._ownerDocument = treebox.treeBody.ownerDocument;
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
if (!this._ownerDocument) {
|
||||
Zotero.debug("No owner document in setTree()", 2);
|
||||
return;
|
||||
}
|
||||
Zotero.debug("No owner document in setTree()", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
this._treebox = treebox;
|
||||
this.setSortColumn();
|
||||
|
||||
if (this.window.ZoteroPane) {
|
||||
this.window.ZoteroPane.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
|
||||
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
|
||||
this._ownerDocument.defaultView.ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
|
||||
}
|
||||
|
||||
if (Zotero.locked) {
|
||||
Zotero.debug("Zotero is locked -- not loading items tree", 2);
|
||||
|
||||
if (this.window.ZoteroPane) {
|
||||
this.window.ZoteroPane.clearItemsPaneMessage();
|
||||
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
|
||||
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -472,7 +465,7 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
|
|||
|
||||
// Clear My Publications intro text on a refresh with items
|
||||
if (this.collectionTreeRow.isPublications() && this.rowCount) {
|
||||
this.window.ZoteroPane.clearItemsPaneMessage();
|
||||
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
|
||||
}
|
||||
|
||||
yield this.runListeners('refresh');
|
||||
|
@ -720,7 +713,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
|
|||
}
|
||||
}
|
||||
else if (collectionTreeRow.isFeed()) {
|
||||
this.window.ZoteroPane.updateReadLabel();
|
||||
this._ownerDocument.defaultView.ZoteroPane.updateReadLabel();
|
||||
}
|
||||
// If not a search, process modifications manually
|
||||
else {
|
||||
|
@ -1689,7 +1682,7 @@ Zotero.ItemTreeView.prototype.sort = function (itemIDs) {
|
|||
* Show intro text in middle pane for some views when no items
|
||||
*/
|
||||
Zotero.ItemTreeView.prototype._updateIntroText = function() {
|
||||
if (!this.window.ZoteroPane) {
|
||||
if (!this._ownerDocument.defaultView.ZoteroPane) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1788,7 +1781,7 @@ Zotero.ItemTreeView.prototype._updateIntroText = function() {
|
|||
}
|
||||
|
||||
if (this._introText || this._introText === null) {
|
||||
this.window.ZoteroPane.clearItemsPaneMessage();
|
||||
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
|
||||
this._introText = false;
|
||||
}
|
||||
};
|
||||
|
@ -1848,9 +1841,9 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
|
|||
// No parent -- it's not here
|
||||
|
||||
// Clear the quick search and tag selection and try again (once)
|
||||
if (!noRecurse && this.window.ZoteroPane) {
|
||||
let cleared1 = yield this.window.ZoteroPane.clearQuicksearch();
|
||||
let cleared2 = this.window.ZoteroPane.clearTagSelection();
|
||||
if (!noRecurse && this._ownerDocument.defaultView.ZoteroPane_Local) {
|
||||
let cleared1 = yield this._ownerDocument.defaultView.ZoteroPane_Local.clearQuicksearch();
|
||||
let cleared2 = this._ownerDocument.defaultView.ZoteroPane_Local.clearTagSelection();
|
||||
if (cleared1 || cleared2) {
|
||||
return this.selectItem(id, expand, true);
|
||||
}
|
||||
|
@ -3218,7 +3211,9 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
|
|||
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
|
||||
// Disallow drop into read-only libraries
|
||||
if (!collectionTreeRow.editable) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
}
|
||||
|
@ -3236,29 +3231,34 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
|
|||
var parentCollectionID = collectionTreeRow.ref.id;
|
||||
}
|
||||
|
||||
let addedItems = [];
|
||||
var notifierQueue = new Zotero.Notifier.Queue;
|
||||
try {
|
||||
// If there's a single file being added to a parent, automatic renaming is enabled,
|
||||
// and there are no other non-HTML attachments, we'll rename the file as long as it's
|
||||
// an allowed type. The dragged data could be a URL, so we don't yet know the file type.
|
||||
// This should be kept in sync with ZoteroPane.addAttachmentFromDialog().
|
||||
let renameIfAllowedType = false;
|
||||
let parentItem;
|
||||
if (parentItemID && data.length == 1 && Zotero.Prefs.get('autoRenameFiles')) {
|
||||
let numExistingFileAttachments;
|
||||
if (parentItemID) {
|
||||
parentItem = Zotero.Items.get(parentItemID);
|
||||
if (!parentItem.numNonHTMLFileAttachments()) {
|
||||
renameIfAllowedType = true;
|
||||
}
|
||||
numExistingFileAttachments = parentItem.getAttachments()
|
||||
.map(itemID => Zotero.Items.get(itemID))
|
||||
.filter(item => item.isFileAttachment())
|
||||
.length;
|
||||
}
|
||||
|
||||
for (var i=0; i<data.length; i++) {
|
||||
var file = data[i];
|
||||
|
||||
let fileBaseName;
|
||||
// If only one item is being dragged and it's the only attachment, run
|
||||
// "Rename File from Parent Metadata" automatically
|
||||
if (data.length == 1 && parentItem && !numExistingFileAttachments) {
|
||||
fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
|
||||
}
|
||||
|
||||
if (dataType == 'text/x-moz-url') {
|
||||
var url = data[i];
|
||||
if (url.indexOf('file:///') == 0) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
// If dragging currently loaded page, only convert to
|
||||
// file if not an HTML document
|
||||
if (win.content.location.href != url ||
|
||||
|
@ -3276,17 +3276,18 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
|
|||
|
||||
// Still string, so remote URL
|
||||
if (typeof file == 'string') {
|
||||
let item;
|
||||
if (parentItemID) {
|
||||
if (!collectionTreeRow.filesEditable) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.displayCannotEditLibraryFilesMessage();
|
||||
return;
|
||||
}
|
||||
item = yield Zotero.Attachments.importFromURL({
|
||||
yield Zotero.Attachments.importFromURL({
|
||||
libraryID: targetLibraryID,
|
||||
url,
|
||||
renameIfAllowedType,
|
||||
fileBaseName,
|
||||
parentItemID,
|
||||
saveOptions: {
|
||||
notifierQueue
|
||||
|
@ -3294,11 +3295,10 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
|
|||
});
|
||||
}
|
||||
else {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
item = yield win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack'); // TODO: don't do this
|
||||
}
|
||||
if (item) {
|
||||
addedItems.push(item);
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack'); // TODO: don't do this
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -3306,38 +3306,8 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
|
|||
// Otherwise file, so fall through
|
||||
}
|
||||
|
||||
file = file.path;
|
||||
|
||||
// Rename file if it's an allowed type
|
||||
let fileBaseName = false;
|
||||
if (renameIfAllowedType) {
|
||||
fileBaseName = yield Zotero.Attachments.getRenamedFileBaseNameIfAllowedType(
|
||||
parentItem, file
|
||||
);
|
||||
}
|
||||
|
||||
let item;
|
||||
if (dropEffect == 'link') {
|
||||
// Rename linked file, with unique suffix if necessary
|
||||
try {
|
||||
if (fileBaseName) {
|
||||
let ext = Zotero.File.getExtension(file);
|
||||
let newName = yield Zotero.File.rename(
|
||||
file,
|
||||
fileBaseName + (ext ? '.' + ext : ''),
|
||||
{
|
||||
unique: true
|
||||
}
|
||||
);
|
||||
// Update path in case the name was changed to be unique
|
||||
file = OS.Path.join(OS.Path.dirname(file), newName);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
item = yield Zotero.Attachments.linkFromFile({
|
||||
yield Zotero.Attachments.linkFromFile({
|
||||
file,
|
||||
parentItemID,
|
||||
collections: parentCollectionID ? [parentCollectionID] : undefined,
|
||||
|
@ -3347,13 +3317,15 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
|
|||
});
|
||||
}
|
||||
else {
|
||||
if (file.endsWith(".lnk")) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.displayCannotAddShortcutMessage(file);
|
||||
if (file.leafName.endsWith(".lnk")) {
|
||||
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
let win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.displayCannotAddShortcutMessage(file.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
item = yield Zotero.Attachments.importFromFile({
|
||||
yield Zotero.Attachments.importFromFile({
|
||||
file,
|
||||
fileBaseName,
|
||||
libraryID: targetLibraryID,
|
||||
|
@ -3366,27 +3338,18 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
|
|||
// If moving, delete original file
|
||||
if (dragData.dropEffect == 'move') {
|
||||
try {
|
||||
yield OS.File.remove(file);
|
||||
file.remove(false);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error deleting original file " + file + " after drag");
|
||||
Components.utils.reportError("Error deleting original file " + file.path + " after drag");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item) {
|
||||
addedItems.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
yield Zotero.Notifier.commit(notifierQueue);
|
||||
}
|
||||
|
||||
// Automatically retrieve metadata for PDFs
|
||||
if (!parentItemID) {
|
||||
Zotero.RecognizePDF.autoRecognizeItems(addedItems);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
Zotero.MIME = new function(){
|
||||
this.isTextType = isTextType;
|
||||
this.getPrimaryExtension = getPrimaryExtension;
|
||||
this.sniffForMIMEType = sniffForMIMEType;
|
||||
this.sniffForBinary = sniffForBinary;
|
||||
this.hasNativeHandler = hasNativeHandler;
|
||||
this.hasInternalHandler = hasInternalHandler;
|
||||
|
@ -47,9 +48,8 @@ Zotero.MIME = new function(){
|
|||
["\uFFFDPNG", 'image/png', 0],
|
||||
["JFIF", 'image/jpeg'],
|
||||
["FLV", "video/x-flv", 0],
|
||||
["\u0000\u0000\u0001\u0000", "image/vnd.microsoft.icon", 0],
|
||||
["\u0053\u0051\u004C\u0069\u0074\u0065\u0020\u0066"
|
||||
+ "\u006F\u0072\u006D\u0061\u0074\u0020\u0033\u0000", "application/x-sqlite3", 0]
|
||||
["\u0000\u0000\u0001\u0000", "image/vnd.microsoft.icon", 0]
|
||||
|
||||
];
|
||||
|
||||
var _extensions = {
|
||||
|
@ -103,6 +103,10 @@ Zotero.MIME = new function(){
|
|||
var _nativeMIMETypes = {
|
||||
'text/html': true,
|
||||
'text/css': true,
|
||||
'image/jpeg': true,
|
||||
'image/gif': true,
|
||||
'image/png': true,
|
||||
'image/svg+xml': true,
|
||||
'text/xml': true,
|
||||
'application/xhtml+xml': true,
|
||||
'application/xml': true,
|
||||
|
@ -228,12 +232,12 @@ Zotero.MIME = new function(){
|
|||
/*
|
||||
* Searches string for magic numbers
|
||||
*/
|
||||
this.sniffForMIMEType = function (str) {
|
||||
for (let i in _snifferEntries) {
|
||||
let match = false;
|
||||
function sniffForMIMEType(str){
|
||||
for (var i in _snifferEntries){
|
||||
var match = false;
|
||||
// If an offset is defined, match only from there
|
||||
if (_snifferEntries[i][2] != undefined) {
|
||||
if (str.substr(_snifferEntries[i][2]).indexOf(_snifferEntries[i][0]) == 0) {
|
||||
if (typeof _snifferEntries[i][2] != 'undefined') {
|
||||
if (str.substr(i[2]).indexOf(_snifferEntries[i][0]) == 0) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
@ -274,7 +278,7 @@ Zotero.MIME = new function(){
|
|||
* ext is an optional file extension hint if data sniffing is unsuccessful
|
||||
*/
|
||||
this.getMIMETypeFromData = function (str, ext){
|
||||
var mimeType = this.sniffForMIMEType(str);
|
||||
var mimeType = sniffForMIMEType(str);
|
||||
if (mimeType){
|
||||
Zotero.debug('Detected MIME type ' + mimeType);
|
||||
return mimeType;
|
||||
|
|
|
@ -32,7 +32,7 @@ Zotero.Notifier = new function(){
|
|||
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash',
|
||||
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key'
|
||||
];
|
||||
var _transactionID = false;
|
||||
var _inTransaction;
|
||||
var _queue = {};
|
||||
|
||||
|
||||
|
@ -106,7 +106,7 @@ Zotero.Notifier = new function(){
|
|||
* - New events and types should be added to the order arrays in commit()
|
||||
**/
|
||||
this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) {
|
||||
if (_transactionID && !force) {
|
||||
if (_inTransaction && !force) {
|
||||
return this.queue(event, type, ids, extraData);
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,7 @@ Zotero.Notifier = new function(){
|
|||
queue = queue._queue;
|
||||
}
|
||||
else {
|
||||
if (!_transactionID) {
|
||||
if (!_inTransaction) {
|
||||
throw new Error("Can't queue event outside of a transaction");
|
||||
}
|
||||
queue = _queue;
|
||||
|
@ -278,11 +278,11 @@ Zotero.Notifier = new function(){
|
|||
*
|
||||
* Note: Be sure the matching commit() gets called (e.g. in a finally{...} block) or
|
||||
* notifications will break until Firefox is restarted or commit(true)/reset() is called manually
|
||||
*
|
||||
* @param {String} [transactionID]
|
||||
*/
|
||||
this.begin = function (transactionID = true) {
|
||||
_transactionID = transactionID;
|
||||
this.begin = function () {
|
||||
if (!_inTransaction) {
|
||||
_inTransaction = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -291,9 +291,8 @@ Zotero.Notifier = new function(){
|
|||
*
|
||||
* @param {Zotero.Notifier.Queue|Zotero.Notifier.Queue[]} [queues] - One or more queues to use
|
||||
* instead of the internal queue
|
||||
* @param {String} [transactionID]
|
||||
*/
|
||||
this.commit = Zotero.Promise.coroutine(function* (queues, transactionID = true) {
|
||||
this.commit = Zotero.Promise.coroutine(function* (queues) {
|
||||
if (queues) {
|
||||
if (!Array.isArray(queues)) {
|
||||
queues = [queues];
|
||||
|
@ -309,7 +308,7 @@ Zotero.Notifier = new function(){
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (!_transactionID) {
|
||||
else if (!_inTransaction) {
|
||||
throw new Error("Can't commit outside of transaction");
|
||||
}
|
||||
else {
|
||||
|
@ -376,7 +375,7 @@ Zotero.Notifier = new function(){
|
|||
}
|
||||
|
||||
if (!queues) {
|
||||
this.reset(transactionID);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
if (totals) {
|
||||
|
@ -408,13 +407,10 @@ Zotero.Notifier = new function(){
|
|||
/*
|
||||
* Reset the event queue
|
||||
*/
|
||||
this.reset = function (transactionID = true) {
|
||||
if (transactionID != _transactionID) {
|
||||
return;
|
||||
}
|
||||
this.reset = function () {
|
||||
//Zotero.debug("Resetting notifier event queue");
|
||||
_queue = {};
|
||||
_transactionID = false;
|
||||
_inTransaction = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2018 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
https://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.OpenPDF = {
|
||||
openToPage: async function (path, page) {
|
||||
var handler = Zotero.Prefs.get("fileHandler.pdf");
|
||||
var opened = false;
|
||||
if (Zotero.isMac) {
|
||||
if (handler.includes('Preview')) {
|
||||
this._openWithPreview(path, page);
|
||||
}
|
||||
else if (handler.includes('Skim')) {
|
||||
this._openWithSkim(path, page);
|
||||
}
|
||||
else if (handler.includes('PDF Expert')) {
|
||||
this._openWithPDFExpert(path, page);
|
||||
}
|
||||
else {
|
||||
// Try to detect default app
|
||||
handler = this._getPDFHandlerName();
|
||||
Zotero.debug(`Handler is ${handler}`);
|
||||
if (handler && handler == 'Skim') {
|
||||
this._openWithSkim(path, page);
|
||||
}
|
||||
else if (handler && handler == 'PDF Expert') {
|
||||
this._openWithPDFExpert(path, page);
|
||||
}
|
||||
// Fall back to Preview
|
||||
else {
|
||||
this._openWithPreview(path, page);
|
||||
}
|
||||
}
|
||||
opened = true;
|
||||
}
|
||||
else if (Zotero.isWin) {
|
||||
handler = handler || this._getPDFHandlerWindows();
|
||||
// Include flags to open the PDF on a given page in various apps
|
||||
//
|
||||
// Adobe Acrobat: http://partners.adobe.com/public/developer/en/acrobat/PDFOpenParameters.pdf
|
||||
// PDF-XChange: http://help.tracker-software.com/eu/default.aspx?pageid=PDFXView25:command_line_options
|
||||
let args = ['/A', 'page=' + page, path];
|
||||
Zotero.Utilities.Internal.exec(handler, args);
|
||||
opened = true;
|
||||
}
|
||||
else if (Zotero.isLinux) {
|
||||
if (handler.includes('evince') || handler.includes('okular')) {
|
||||
this._openWithEvinceOrOkular(handler, path, page);
|
||||
opened = true;
|
||||
}
|
||||
else {
|
||||
let handler = await this._getPDFHandlerLinux();
|
||||
if (handler.includes('evince') || handler.includes('okular')) {
|
||||
this._openWithEvinceOrOkular(handler, path, page);
|
||||
opened = true;
|
||||
}
|
||||
// Fall back to okular and then evince if unknown handler
|
||||
else if (await OS.File.exists('/usr/bin/okular')) {
|
||||
this._openWithEvinceOrOkular('/usr/bin/okular', path, page);
|
||||
opened = true;
|
||||
}
|
||||
else if (await OS.File.exists('/usr/bin/evince')) {
|
||||
this._openWithEvinceOrOkular('/usr/bin/evince', path, page);
|
||||
opened = true;
|
||||
}
|
||||
else {
|
||||
Zotero.debug("No handler found");
|
||||
}
|
||||
}
|
||||
}
|
||||
return opened;
|
||||
},
|
||||
|
||||
_getPDFHandlerName: function () {
|
||||
var handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
|
||||
.getService(Ci.nsIHandlerService);
|
||||
var handlers = handlerService.enumerate();
|
||||
var handler;
|
||||
while (handlers.hasMoreElements()) {
|
||||
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
|
||||
if (handlerInfo.type == 'application/pdf') {
|
||||
handler = handlerInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!handler) {
|
||||
// We can't get the name of the system default handler unless we add an entry
|
||||
Zotero.debug("Default handler not found -- adding default entry");
|
||||
let mimeService = Components.classes["@mozilla.org/mime;1"]
|
||||
.getService(Components.interfaces.nsIMIMEService);
|
||||
let mimeInfo = mimeService.getFromTypeAndExtension("application/pdf", "");
|
||||
mimeInfo.preferredAction = 4;
|
||||
mimeInfo.alwaysAskBeforeHandling = false;
|
||||
handlerService.store(mimeInfo);
|
||||
|
||||
// And once we do that, we can get the name (but not the path, unfortunately)
|
||||
let handlers = handlerService.enumerate();
|
||||
while (handlers.hasMoreElements()) {
|
||||
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
|
||||
if (handlerInfo.type == 'application/pdf') {
|
||||
handler = handlerInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handler) {
|
||||
Zotero.debug(`Default handler is ${handler.defaultDescription}`);
|
||||
return handler.defaultDescription;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
//
|
||||
// Mac
|
||||
//
|
||||
_openWithPreview: async function (filePath, page) {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', 'Preview', filePath]);
|
||||
// Go to page using AppleScript
|
||||
let args = [
|
||||
'-e', 'tell app "Preview" to activate',
|
||||
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
];
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
},
|
||||
|
||||
_openWithSkim: async function (filePath, page) {
|
||||
// Escape double-quotes in path
|
||||
var quoteRE = /"/g;
|
||||
filePath = filePath.replace(quoteRE, '\\"');
|
||||
let filename = OS.Path.basename(filePath).replace(quoteRE, '\\"');
|
||||
let args = [
|
||||
'-e', 'tell app "Skim" to activate',
|
||||
'-e', `tell app "Skim" to open "${filePath}"`
|
||||
];
|
||||
args.push('-e', `tell document "${filename}" of application "Skim" to go to page ${page}`);
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
},
|
||||
|
||||
_openWithPDFExpert: async function (filePath, page) {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', 'PDF Expert', filePath]);
|
||||
// Go to page using AppleScript (same as Preview)
|
||||
let args = [
|
||||
'-e', 'tell app "PDF Expert" to activate',
|
||||
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
];
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
},
|
||||
|
||||
//
|
||||
// Windows
|
||||
//
|
||||
/**
|
||||
* Get path to default pdf reader application on windows
|
||||
* @return {string} Path to default pdf reader application
|
||||
*
|
||||
* From getPDFReader() in ZotFile (GPL)
|
||||
* https://github.com/jlegewie/zotfile/blob/master/chrome/content/zotfile/utils.js
|
||||
*/
|
||||
_getPDFHandlerWindows: function () {
|
||||
var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
|
||||
.createInstance(Components.interfaces.nsIWindowsRegKey);
|
||||
// Get handler for PDFs
|
||||
var tryKeys = [
|
||||
{
|
||||
root: wrk.ROOT_KEY_CURRENT_USER,
|
||||
path: 'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.pdf\\UserChoice',
|
||||
value: 'Progid'
|
||||
},
|
||||
{
|
||||
root: wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
path: '.pdf',
|
||||
value: ''
|
||||
}
|
||||
];
|
||||
var progId;
|
||||
for (let i = 0; !progId && i < tryKeys.length; i++) {
|
||||
try {
|
||||
wrk.open(
|
||||
tryKeys[i].root,
|
||||
tryKeys[i].path,
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
progId = wrk.readStringValue(tryKeys[i].value);
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (!progId) {
|
||||
wrk.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get version specific handler, if it exists
|
||||
try {
|
||||
wrk.open(
|
||||
wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
progId + '\\CurVer',
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
progId = wrk.readStringValue('') || progId;
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
// Get command
|
||||
var success = false;
|
||||
tryKeys = [
|
||||
progId + '\\shell\\Read\\command',
|
||||
progId + '\\shell\\Open\\command'
|
||||
];
|
||||
for (let i = 0; !success && i < tryKeys.length; i++) {
|
||||
try {
|
||||
wrk.open(
|
||||
wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
tryKeys[i],
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
success = true;
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
wrk.close();
|
||||
return;
|
||||
}
|
||||
|
||||
var command = wrk.readStringValue('').match(/^(?:".+?"|[^"]\S+)/);
|
||||
|
||||
wrk.close();
|
||||
|
||||
if (!command) return;
|
||||
return command[0].replace(/"/g, '');
|
||||
},
|
||||
|
||||
//
|
||||
// Linux
|
||||
//
|
||||
_getPDFHandlerLinux: async function () {
|
||||
var name = this._getPDFHandlerName();
|
||||
switch (name.toLowerCase()) {
|
||||
case 'okular':
|
||||
return `/usr/bin/${name}`;
|
||||
|
||||
// It's "Document Viewer" on stock Ubuntu
|
||||
case 'document viewer':
|
||||
case 'evince':
|
||||
return `/usr/bin/evince`;
|
||||
}
|
||||
|
||||
// TODO: Try to get default from mimeapps.list, etc., in case system default is okular
|
||||
// or evince somewhere other than /usr/bin
|
||||
var homeDir = OS.Constants.Path.homeDir;
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
_openWithEvinceOrOkular: function (appPath, filePath, page) {
|
||||
var args = ['-p', page, filePath];
|
||||
Zotero.Utilities.Internal.exec(appPath, args);
|
||||
}
|
||||
}
|
|
@ -129,12 +129,10 @@ Zotero.Profile = {
|
|||
/**
|
||||
* Find other profile directories (for this app or the other app) using the given data directory
|
||||
*
|
||||
* @param {String} dataDir
|
||||
* @param {Boolean} [includeOtherApps=false] - Check Firefox profiles
|
||||
* @return {String[]}
|
||||
*/
|
||||
findOtherProfilesUsingDataDirectory: Zotero.Promise.coroutine(function* (dataDir, includeOtherApps = true) {
|
||||
let otherAppProfiles = includeOtherApps ? (yield this._findOtherAppProfiles()) : [];
|
||||
findOtherProfilesUsingDataDirectory: Zotero.Promise.coroutine(function* (dataDir) {
|
||||
let otherAppProfiles = yield this._findOtherAppProfiles();
|
||||
let otherProfiles = (yield this._findOtherProfiles()).concat(otherAppProfiles);
|
||||
|
||||
// First get profiles pointing at this directory
|
||||
|
@ -240,38 +238,6 @@ Zotero.Profile = {
|
|||
},
|
||||
|
||||
|
||||
readPrefsFromFile: async function (prefsFile) {
|
||||
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
|
||||
Components.utils.evalInSandbox(
|
||||
"var prefs = {};"+
|
||||
"function user_pref(key, val) {"+
|
||||
"prefs[key] = val;"+
|
||||
"}"
|
||||
, sandbox);
|
||||
|
||||
(await Zotero.File.getContentsAsync(prefsFile))
|
||||
.split(/\n/)
|
||||
.filter((line) => {
|
||||
// Strip comments
|
||||
return !line.startsWith('#')
|
||||
// Only process lines in our pref branch
|
||||
&& line.includes(ZOTERO_CONFIG.PREF_BRANCH);
|
||||
})
|
||||
// Process each line individually
|
||||
.forEach((line) => {
|
||||
try {
|
||||
Zotero.debug("Processing " + line);
|
||||
Components.utils.evalInSandbox(line, sandbox);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error processing prefs line: " + line);
|
||||
}
|
||||
});
|
||||
|
||||
return sandbox.prefs;
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
|
|
|
@ -154,9 +154,6 @@ Zotero.ProgressWindow = function(options = {}) {
|
|||
_progressWindow.addEventListener("mouseover", _onMouseOver, false);
|
||||
_progressWindow.addEventListener("mouseout", _onMouseOut, false);
|
||||
_progressWindow.addEventListener("mouseup", _onMouseUp, false);
|
||||
_window.addEventListener('close', () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
_windowLoading = true;
|
||||
|
||||
|
@ -284,10 +281,7 @@ Zotero.ProgressWindow = function(options = {}) {
|
|||
|
||||
try {
|
||||
_progressWindow.close();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
} catch(ex) {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -321,11 +321,7 @@ Zotero.Proxy.prototype.compileRegexp = function() {
|
|||
})
|
||||
|
||||
// now replace with regexp fragment in reverse order
|
||||
if (this.scheme.includes('://')) {
|
||||
re = "^"+Zotero.Utilities.quotemeta(this.scheme)+"$";
|
||||
} else {
|
||||
re = "^https?"+Zotero.Utilities.quotemeta('://'+this.scheme)+"$";
|
||||
}
|
||||
var re = "^"+Zotero.Utilities.quotemeta(this.scheme)+"$";
|
||||
for(var i=this.parameters.length-1; i>=0; i--) {
|
||||
var param = this.parameters[i];
|
||||
re = re.replace(Zotero_Proxy_schemeParameterRegexps[param], "$1"+parametersToCheck[param]);
|
||||
|
|
|
@ -1,681 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2018 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.RecognizePDF = new function () {
|
||||
const OFFLINE_RECHECK_DELAY = 60 * 1000;
|
||||
const MAX_PAGES = 5;
|
||||
const UNRECOGNIZE_TIMEOUT = 86400 * 1000;
|
||||
|
||||
this.ROW_QUEUED = 1;
|
||||
this.ROW_PROCESSING = 2;
|
||||
this.ROW_FAILED = 3;
|
||||
this.ROW_SUCCEEDED = 4;
|
||||
|
||||
let _newItems = new WeakMap();
|
||||
|
||||
let _listeners = {};
|
||||
let _rows = [];
|
||||
let _queue = [];
|
||||
let _queueProcessing = false;
|
||||
|
||||
/**
|
||||
* Add listener
|
||||
* @param name Event name
|
||||
* @param callback
|
||||
*/
|
||||
this.addListener = function (name, callback) {
|
||||
_listeners[name] = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove listener
|
||||
* @param name Event name
|
||||
*/
|
||||
this.removeListener = function (name) {
|
||||
delete _listeners[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether a given PDF could theoretically be recognized
|
||||
* @param {Zotero.Item} item
|
||||
* @return {Boolean} True if the PDF can be recognized, false if it cannot be
|
||||
*/
|
||||
this.canRecognize = function (item) {
|
||||
return item.attachmentContentType
|
||||
&& item.attachmentContentType === 'application/pdf'
|
||||
&& item.isTopLevelItem();
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds items to the queue and starts processing it
|
||||
* @param items {Zotero.Item}
|
||||
*/
|
||||
this.recognizeItems = function (items) {
|
||||
for (let item of items) {
|
||||
_addItem(item);
|
||||
}
|
||||
_processQueue();
|
||||
};
|
||||
|
||||
|
||||
this.autoRecognizeItems = function (items) {
|
||||
if (!Zotero.Prefs.get('autoRecognizeFiles')) return;
|
||||
|
||||
var pdfs = items.filter((item) => {
|
||||
return item
|
||||
&& item.isFileAttachment()
|
||||
&& item.attachmentContentType == 'application/pdf';
|
||||
});
|
||||
if (!pdfs.length) {
|
||||
return;
|
||||
}
|
||||
this.recognizeItems(pdfs);
|
||||
var win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
if (win) {
|
||||
win.Zotero_RecognizePDF_Dialog.open();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all rows
|
||||
* @return {Array}
|
||||
*/
|
||||
this.getRows = function () {
|
||||
return _rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns rows count
|
||||
* @return {Number}
|
||||
*/
|
||||
this.getTotal = function () {
|
||||
return _rows.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns processed rows count
|
||||
* @return {Number}
|
||||
*/
|
||||
this.getProcessedTotal = function () {
|
||||
return _rows.filter(row => row.status > Zotero.RecognizePDF.ROW_PROCESSING).length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop processing items
|
||||
*/
|
||||
this.cancel = function () {
|
||||
_queue = [];
|
||||
_rows = [];
|
||||
if (_listeners['empty']) {
|
||||
_listeners['empty']();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.canUnrecognize = function (item) {
|
||||
var { dateModified } = _newItems.get(item) || {};
|
||||
// Item must have been recognized recently, must not have been modified since it was
|
||||
// created, and must have only one attachment and no other children
|
||||
if (!dateModified
|
||||
|| Zotero.Date.sqlToDate(dateModified, true) < new Date() - UNRECOGNIZE_TIMEOUT
|
||||
|| item.dateModified != dateModified
|
||||
|| item.numAttachments(true) != 1
|
||||
|| item.numChildren(true) != 1) {
|
||||
_newItems.delete(item);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Child attachment must be not be in trash and must be a PDF
|
||||
var attachments = Zotero.Items.get(item.getAttachments());
|
||||
if (!attachments.length || attachments[0].attachmentContentType != 'application/pdf') {
|
||||
_newItems.delete(item);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
this.unrecognize = async function (item) {
|
||||
var { originalTitle, originalFilename } = _newItems.get(item);
|
||||
var attachment = Zotero.Items.get(item.getAttachments()[0]);
|
||||
|
||||
try {
|
||||
let currentFilename = attachment.attachmentFilename;
|
||||
if (currentFilename != originalFilename) {
|
||||
let renamed = await attachment.renameAttachmentFile(originalFilename);
|
||||
if (renamed) {
|
||||
attachment.setField('title', originalTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
return Zotero.DB.executeTransaction(async function () {
|
||||
let collections = item.getCollections();
|
||||
attachment.parentItemID = null
|
||||
attachment.setCollections(collections);
|
||||
await attachment.save();
|
||||
|
||||
await item.erase();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
this.report = async function (item, description) {
|
||||
var attachment = Zotero.Items.get(item.getAttachments()[0]);
|
||||
var filePath = attachment.getFilePath();
|
||||
if (!filePath || !await OS.File.exists(filePath)) {
|
||||
throw new Error("File not found when reporting metadata");
|
||||
}
|
||||
|
||||
var version = Zotero.version;
|
||||
var json = await extractJSON(filePath, MAX_PAGES);
|
||||
var metadata = item.toJSON();
|
||||
|
||||
var data = { description, version, json, metadata };
|
||||
var uri = ZOTERO_CONFIG.RECOGNIZE_URL + 'report';
|
||||
return Zotero.HTTP.request(
|
||||
"POST",
|
||||
uri,
|
||||
{
|
||||
successCodes: [200, 204],
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add item for processing
|
||||
* @param item
|
||||
* @return {null}
|
||||
*/
|
||||
function _addItem(item) {
|
||||
for (let row of _rows) {
|
||||
if (row.id === item.id) {
|
||||
if (row.status > Zotero.RecognizePDF.ROW_PROCESSING) {
|
||||
_deleteRow(row.id);
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let row = {
|
||||
id: item.id,
|
||||
status: Zotero.RecognizePDF.ROW_QUEUED,
|
||||
fileName: item.getField('title'),
|
||||
message: ''
|
||||
};
|
||||
|
||||
_rows.unshift(row);
|
||||
_queue.unshift(item.id);
|
||||
|
||||
if (_listeners['rowadded']) {
|
||||
_listeners['rowadded'](row);
|
||||
}
|
||||
|
||||
if (_listeners['nonempty'] && _rows.length === 1) {
|
||||
_listeners['nonempty']();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update row status and message
|
||||
* @param itemID
|
||||
* @param status
|
||||
* @param message
|
||||
*/
|
||||
function _updateRow(itemID, status, message) {
|
||||
for (let row of _rows) {
|
||||
if (row.id === itemID) {
|
||||
row.status = status;
|
||||
row.message = message;
|
||||
if (_listeners['rowupdated']) {
|
||||
_listeners['rowupdated']({
|
||||
id: row.id,
|
||||
status,
|
||||
message: message || ''
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete row
|
||||
* @param itemID
|
||||
*/
|
||||
function _deleteRow(itemID) {
|
||||
for (let i = 0; i < _rows.length; i++) {
|
||||
let row = _rows[i];
|
||||
if (row.id === itemID) {
|
||||
_rows.splice(i, 1);
|
||||
if (_listeners['rowdeleted']) {
|
||||
_listeners['rowdeleted']({
|
||||
id: row.id
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers queue processing and returns when all items in the queue are processed
|
||||
* @return {Promise}
|
||||
*/
|
||||
async function _processQueue() {
|
||||
await Zotero.Schema.schemaUpdatePromise;
|
||||
|
||||
if (_queueProcessing) return;
|
||||
_queueProcessing = true;
|
||||
|
||||
while (1) {
|
||||
if (Zotero.HTTP.browserIsOffline()) {
|
||||
await Zotero.Promise.delay(OFFLINE_RECHECK_DELAY);
|
||||
continue;
|
||||
}
|
||||
|
||||
let itemID = _queue.shift();
|
||||
if (!itemID) break;
|
||||
|
||||
_updateRow(itemID, Zotero.RecognizePDF.ROW_PROCESSING, Zotero.getString('general.processing'));
|
||||
|
||||
try {
|
||||
let newItem = await _processItem(itemID);
|
||||
|
||||
if (newItem) {
|
||||
_updateRow(itemID, Zotero.RecognizePDF.ROW_SUCCEEDED, newItem.getField('title'));
|
||||
}
|
||||
else {
|
||||
_updateRow(itemID, Zotero.RecognizePDF.ROW_FAILED, Zotero.getString('recognizePDF.noMatches'));
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
||||
_updateRow(
|
||||
itemID,
|
||||
Zotero.RecognizePDF.ROW_FAILED,
|
||||
e instanceof Zotero.Exception.Alert
|
||||
? e.message
|
||||
: Zotero.getString('recognizePDF.error')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_queueProcessing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the item and places it as a children of the new item
|
||||
* @param itemID
|
||||
* @return {Promise}
|
||||
*/
|
||||
async function _processItem(itemID) {
|
||||
let attachment = await Zotero.Items.getAsync(itemID);
|
||||
|
||||
if (!attachment || attachment.parentItemID) {
|
||||
throw new Zotero.Exception.Alert('recognizePDF.error');
|
||||
}
|
||||
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
var selectParent = false;
|
||||
if (zp) {
|
||||
let selected = zp.getSelectedItems();
|
||||
if (selected.length) {
|
||||
// If only the PDF was selected, select the parent when we're done
|
||||
selectParent = selected.length == 1 && selected[0] == attachment;
|
||||
}
|
||||
}
|
||||
|
||||
let parentItem = await _recognize(attachment);
|
||||
if (!parentItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Put new item in same collections as the old one
|
||||
let collections = attachment.getCollections();
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
if (collections.length) {
|
||||
for (let collectionID of collections) {
|
||||
parentItem.addToCollection(collectionID);
|
||||
}
|
||||
await parentItem.save();
|
||||
}
|
||||
|
||||
// Put old item as a child of the new item
|
||||
attachment.parentID = parentItem.id;
|
||||
await attachment.save();
|
||||
});
|
||||
|
||||
var originalTitle = attachment.getField('title');
|
||||
var path = attachment.getFilePath();
|
||||
var originalFilename = OS.Path.basename(path);
|
||||
|
||||
// Rename attachment file to match new metadata
|
||||
if (Zotero.Prefs.get('autoRenameFiles')) {
|
||||
let ext = Zotero.File.getExtension(path);
|
||||
let fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
|
||||
let newName = fileBaseName + (ext ? '.' + ext : '');
|
||||
let result = await attachment.renameAttachmentFile(newName, false, true);
|
||||
if (result !== true) {
|
||||
throw new Error("Error renaming " + path);
|
||||
}
|
||||
// Rename attachment title
|
||||
attachment.setField('title', newName);
|
||||
await attachment.saveTx();
|
||||
}
|
||||
|
||||
try {
|
||||
zp = Zotero.getActiveZoteroPane();
|
||||
if (zp) {
|
||||
if (selectParent) {
|
||||
await zp.selectItem(parentItem.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
_newItems.set(
|
||||
parentItem,
|
||||
{
|
||||
originalTitle,
|
||||
originalFilename,
|
||||
dateModified: parentItem.dateModified
|
||||
}
|
||||
);
|
||||
return parentItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get json from a PDF
|
||||
* @param {String} filePath PDF file path
|
||||
* @param {Number} pages Number of pages to extract
|
||||
* @return {Promise}
|
||||
*/
|
||||
async function extractJSON(filePath, pages) {
|
||||
let cacheFile = Zotero.getTempDirectory();
|
||||
cacheFile.append("recognizePDFcache.txt");
|
||||
if (cacheFile.exists()) {
|
||||
cacheFile.remove(false);
|
||||
}
|
||||
|
||||
let {exec, args} = Zotero.Fulltext.getPDFConverterExecAndArgs();
|
||||
args.push('-json', '-l', pages, filePath, cacheFile.path);
|
||||
|
||||
Zotero.debug("RecognizePDF: Running " + exec.path + " " + args.map(arg => "'" + arg + "'").join(" "));
|
||||
|
||||
try {
|
||||
await Zotero.Utilities.Internal.exec(exec, args);
|
||||
let content = await Zotero.File.getContentsAsync(cacheFile.path);
|
||||
Zotero.debug("RecognizePDF: Extracted JSON:");
|
||||
Zotero.debug(content);
|
||||
cacheFile.remove(false);
|
||||
return JSON.parse(content);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
try {
|
||||
cacheFile.remove(false);
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
throw new Zotero.Exception.Alert("recognizePDF.couldNotRead");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach appropriate handlers to a Zotero.Translate instance and begin translation
|
||||
* @return {Promise}
|
||||
*/
|
||||
async function _promiseTranslate(translate, libraryID) {
|
||||
translate.setHandler('select', function (translate, items, callback) {
|
||||
for (let i in items) {
|
||||
let obj = {};
|
||||
obj[i] = items[i];
|
||||
callback(obj);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
let newItems = await translate.translate({
|
||||
libraryID,
|
||||
saveAttachments: false
|
||||
});
|
||||
if (newItems.length) {
|
||||
return newItems[0];
|
||||
}
|
||||
throw new Error('No items found');
|
||||
}
|
||||
|
||||
async function _query(json) {
|
||||
// TODO: Use main API URL for recognizer server
|
||||
//let uri = Zotero.Prefs.get("api.url") || ZOTERO_CONFIG.API_URL;
|
||||
let uri = Zotero.Prefs.get("api.url") || ZOTERO_CONFIG.RECOGNIZE_URL;
|
||||
|
||||
if (!uri.endsWith('/')) {
|
||||
uri += '/';
|
||||
}
|
||||
|
||||
uri += 'recognize';
|
||||
|
||||
let client = Zotero.Sync.Runner.getAPIClient();
|
||||
|
||||
let req = await client.makeRequest(
|
||||
'POST',
|
||||
uri,
|
||||
{
|
||||
successCodes: [200],
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(json),
|
||||
noAPIKey: true
|
||||
}
|
||||
);
|
||||
|
||||
return JSON.parse(req.responseText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves metadata for a PDF and saves it as an item
|
||||
* @param {Zotero.Item} item
|
||||
* @return {Promise}
|
||||
*/
|
||||
async function _recognize(item) {
|
||||
let filePath = await item.getFilePath();
|
||||
|
||||
if (!filePath || !await OS.File.exists(filePath)) throw new Zotero.Exception.Alert('recognizePDF.fileNotFound');
|
||||
|
||||
let json = await extractJSON(filePath, MAX_PAGES);
|
||||
|
||||
let containingTextPages = 0;
|
||||
|
||||
for(let page of json.pages) {
|
||||
if(page[2].length) {
|
||||
containingTextPages++;
|
||||
}
|
||||
}
|
||||
|
||||
if(!containingTextPages) {
|
||||
throw new Zotero.Exception.Alert('recognizePDF.noOCR');
|
||||
}
|
||||
|
||||
let libraryID = item.libraryID;
|
||||
|
||||
let res = await _query(json);
|
||||
if (!res) return null;
|
||||
|
||||
if (res.arxiv) {
|
||||
Zotero.debug('RecognizePDF: Getting metadata by arXiv');
|
||||
let translate = new Zotero.Translate.Search();
|
||||
translate.setIdentifier({arXiv: res.arxiv});
|
||||
let translators = await translate.getTranslators();
|
||||
translate.setTranslator(translators);
|
||||
|
||||
try {
|
||||
let newItem = await _promiseTranslate(translate, libraryID);
|
||||
if (!newItem.abstractNote && res.abstract) {
|
||||
newItem.setField('abstractNote', res.abstract);
|
||||
}
|
||||
if (!newItem.language && res.language) {
|
||||
newItem.setField('language', res.language);
|
||||
}
|
||||
newItem.saveTx();
|
||||
return newItem;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('RecognizePDF: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
if (res.doi) {
|
||||
Zotero.debug('RecognizePDF: Getting metadata by DOI');
|
||||
let translate = new Zotero.Translate.Search();
|
||||
translate.setIdentifier({
|
||||
DOI: res.doi
|
||||
});
|
||||
let translators = await translate.getTranslators();
|
||||
translate.setTranslator(translators);
|
||||
|
||||
try {
|
||||
let newItem = await _promiseTranslate(translate, libraryID);
|
||||
if (!newItem.abstractNote && res.abstract) {
|
||||
newItem.setField('abstractNote', res.abstract);
|
||||
}
|
||||
if (!newItem.language && res.language) {
|
||||
newItem.setField('language', res.language);
|
||||
}
|
||||
newItem.saveTx();
|
||||
return newItem;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('RecognizePDF: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
if (res.isbn) {
|
||||
Zotero.debug('RecognizePDF: Getting metadata by ISBN');
|
||||
let translate = new Zotero.Translate.Search();
|
||||
translate.setSearch({'itemType': 'book', 'ISBN': res.isbn});
|
||||
try {
|
||||
let translatedItems = await translate.translate({
|
||||
libraryID: false,
|
||||
saveAttachments: false
|
||||
});
|
||||
Zotero.debug('RecognizePDF: Translated items:');
|
||||
Zotero.debug(translatedItems);
|
||||
if (translatedItems.length) {
|
||||
let newItem = new Zotero.Item;
|
||||
newItem.libraryID = libraryID;
|
||||
// Convert tags to automatic. For other items this is done automatically in
|
||||
// translate.js for other items, but for ISBNs we just get the data
|
||||
// (libraryID=false) and do the saving manually.
|
||||
translatedItems[0].tags = translatedItems[0].tags.map(tag => {
|
||||
if (typeof tag == 'string') {
|
||||
return {
|
||||
tag,
|
||||
type: 1
|
||||
};
|
||||
}
|
||||
tag.type = 1;
|
||||
return tag;
|
||||
});
|
||||
newItem.fromJSON(translatedItems[0]);
|
||||
if (!newItem.abstractNote && res.abstract) {
|
||||
newItem.setField('abstractNote', res.abstract);
|
||||
}
|
||||
if (!newItem.language && res.language) {
|
||||
newItem.setField('language', res.language);
|
||||
}
|
||||
newItem.saveTx();
|
||||
return newItem;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('RecognizePDF: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
if (res.title) {
|
||||
let type = 'journalArticle';
|
||||
|
||||
if (res.type === 'book-chapter') {
|
||||
type = 'bookSection';
|
||||
}
|
||||
|
||||
let newItem = new Zotero.Item(type);
|
||||
newItem.libraryID = libraryID;
|
||||
newItem.setField('title', res.title);
|
||||
|
||||
let creators = [];
|
||||
for (let author of res.authors) {
|
||||
creators.push({
|
||||
firstName: author.firstName,
|
||||
lastName: author.lastName,
|
||||
creatorType: 'author'
|
||||
})
|
||||
}
|
||||
|
||||
newItem.setCreators(creators);
|
||||
|
||||
if (res.abstract) newItem.setField('abstractNote', res.abstract);
|
||||
if (res.year) newItem.setField('date', res.year);
|
||||
if (res.pages) newItem.setField('pages', res.pages);
|
||||
if (res.volume) newItem.setField('volume', res.volume);
|
||||
if (res.url) newItem.setField('url', res.url);
|
||||
if (res.language) newItem.setField('language', res.language);
|
||||
|
||||
if (type === 'journalArticle') {
|
||||
if (res.issue) newItem.setField('issue', res.issue);
|
||||
if (res.ISSN) newItem.setField('issn', res.issn);
|
||||
if (res.container) newItem.setField('publicationTitle', res.container);
|
||||
}
|
||||
else if (type === 'bookSection') {
|
||||
if (res.container) newItem.setField('bookTitle', res.container);
|
||||
if (res.publisher) newItem.setField('publisher', res.publisher);
|
||||
}
|
||||
|
||||
newItem.setField('libraryCatalog', 'Zotero');
|
||||
|
||||
await newItem.saveTx();
|
||||
return newItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -744,29 +744,16 @@ Zotero.Schema = new function(){
|
|||
index[id].extract = true;
|
||||
}
|
||||
|
||||
let sql = "SELECT rowid, fileName, metadataJSON FROM translatorCache";
|
||||
let rows = yield Zotero.DB.queryAsync(sql);
|
||||
let sql = "SELECT metadataJSON FROM translatorCache";
|
||||
let dbCache = yield Zotero.DB.columnQueryAsync(sql);
|
||||
// If there's anything in the cache, see what we actually need to extract
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let json = rows[i].metadataJSON;
|
||||
let metadata;
|
||||
try {
|
||||
metadata = JSON.parse(json);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(json, 1);
|
||||
|
||||
// If JSON is invalid, clear from cache
|
||||
yield Zotero.DB.queryAsync(
|
||||
"DELETE FROM translatorCache WHERE rowid=?",
|
||||
rows[i].rowid
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let id = metadata.translatorID;
|
||||
if (index[id] && index[id].lastUpdated <= metadata.lastUpdated) {
|
||||
index[id].extract = false;
|
||||
if (dbCache) {
|
||||
for (let i = 0; i < dbCache.length; i++) {
|
||||
let metadata = JSON.parse(dbCache[i]);
|
||||
let id = metadata.translatorID;
|
||||
if (index[id] && index[id].lastUpdated <= metadata.lastUpdated) {
|
||||
index[id].extract = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -801,8 +788,9 @@ Zotero.Schema = new function(){
|
|||
catch (e) {
|
||||
if (e instanceof OS.File.Error && e.becauseExists) {
|
||||
// Could overwrite automatically, but we want to log this
|
||||
Zotero.warn("Overwriting translator with same filename '"
|
||||
+ entry.fileName + "'");
|
||||
let msg = "Overwriting translator with same filename '" + entry.fileName + "'";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
yield OS.File.move(tmpFile, destFile);
|
||||
}
|
||||
else {
|
||||
|
@ -964,8 +952,10 @@ Zotero.Schema = new function(){
|
|||
catch (e) {
|
||||
if (e instanceof OS.File.Error && e.becauseExists) {
|
||||
// Could overwrite automatically, but we want to log this
|
||||
Zotero.warn("Overwriting " + modeType + " with same filename "
|
||||
+ "'" + fileName + "'", 1);
|
||||
let msg = "Overwriting " + modeType + " with same filename "
|
||||
+ "'" + fileName + "'";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
yield OS.File.copy(entry.path, destFile);
|
||||
}
|
||||
else {
|
||||
|
@ -1546,13 +1536,8 @@ Zotero.Schema = new function(){
|
|||
return false;
|
||||
}
|
||||
if (dbVersion > schemaVersion) {
|
||||
let dbClientVersion = yield Zotero.DB.valueQueryAsync(
|
||||
"SELECT value FROM settings WHERE setting='client' AND key='lastCompatibleVersion'"
|
||||
);
|
||||
throw new Zotero.DB.IncompatibleVersionException(
|
||||
`Zotero '${schema}' DB version (${dbVersion}) is newer than SQL file (${schemaVersion})`,
|
||||
dbClientVersion
|
||||
);
|
||||
throw new Error("Zotero '" + schema + "' DB version (" + dbVersion
|
||||
+ ") is newer than SQL file (" + schemaVersion + ")");
|
||||
}
|
||||
let sql = yield _getSchemaSQL(schema);
|
||||
yield Zotero.DB.executeSQLFile(sql);
|
||||
|
@ -1635,6 +1620,116 @@ Zotero.Schema = new function(){
|
|||
var translatorUpdates = xmlhttp.responseXML.getElementsByTagName('translator');
|
||||
var styleUpdates = xmlhttp.responseXML.getElementsByTagName('style');
|
||||
|
||||
var updatePDFTools = function () {
|
||||
// No updates for PPC
|
||||
if (Zotero.platform == 'MacPPC') return;
|
||||
|
||||
let pdfToolsUpdates = xmlhttp.responseXML.getElementsByTagName('pdftools');
|
||||
if (pdfToolsUpdates.length) {
|
||||
let availableVersion = pdfToolsUpdates[0].getAttribute('version');
|
||||
let installInfo = false;
|
||||
let installConverter = false;
|
||||
|
||||
// Don't auto-install if not installed
|
||||
if (!Zotero.Fulltext.pdfInfoIsRegistered() && !Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TEMP
|
||||
if (Zotero.isWin) {
|
||||
if (Zotero.Fulltext.pdfInfoIsRegistered()) {
|
||||
if (Zotero.Fulltext.pdfInfoVersion != '3.02a') {
|
||||
installInfo = true;
|
||||
}
|
||||
}
|
||||
// Install missing component if one is installed
|
||||
else if (Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
installInfo = true;
|
||||
}
|
||||
if (Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
if (Zotero.Fulltext.pdfConverterVersion != '3.02a') {
|
||||
installConverter = true;
|
||||
}
|
||||
}
|
||||
// Install missing component if one is installed
|
||||
else if (Zotero.Fulltext.pdfInfoIsRegistered()) {
|
||||
installConverter = true;
|
||||
}
|
||||
availableVersion = '3.02';
|
||||
}
|
||||
else {
|
||||
if (Zotero.Fulltext.pdfInfoIsRegistered()) {
|
||||
let currentVersion = Zotero.Fulltext.pdfInfoVersion;
|
||||
if (currentVersion < availableVersion || currentVersion.startsWith('3.02')
|
||||
|| currentVersion == 'UNKNOWN') {
|
||||
installInfo = true;
|
||||
}
|
||||
}
|
||||
// Install missing component if one is installed
|
||||
else if (Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
installInfo = true;
|
||||
}
|
||||
if (Zotero.Fulltext.pdfConverterIsRegistered()) {
|
||||
let currentVersion = Zotero.Fulltext.pdfConverterVersion;
|
||||
if (currentVersion < availableVersion || currentVersion.startsWith('3.02')
|
||||
|| currentVersion == 'UNKNOWN') {
|
||||
installConverter = true;
|
||||
}
|
||||
}
|
||||
// Install missing component if one is installed
|
||||
else if (Zotero.Fulltext.pdfInfoIsRegistered()) {
|
||||
installConverter = true;
|
||||
}
|
||||
}
|
||||
|
||||
let prefKey = 'pdfToolsInstallError';
|
||||
let lastTry = 0, delay = 43200000; // half a day, so doubles to a day initially
|
||||
try {
|
||||
[lastTry, delay] = Zotero.Prefs.get(prefKey).split(';');
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
// Allow an additional minute, since repo updates might not be exact
|
||||
if (Date.now() < (parseInt(lastTry) + parseInt(delay) - 60000)) {
|
||||
Zotero.debug("Now enough time since last PDF tools installation failure -- skipping", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
var checkResult = function (success) {
|
||||
if (success) {
|
||||
try {
|
||||
Zotero.Prefs.clear(prefKey);
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
else {
|
||||
// Keep doubling delay, to a max of 1 week
|
||||
Zotero.Prefs.set(prefKey, Date.now() + ";" + Math.min(delay * 2, 7*24*60*60*1000));
|
||||
|
||||
let msg = "Error downloading PDF tool";
|
||||
Zotero.debug(msg, 1);
|
||||
throw new Error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
if (installConverter && installInfo) {
|
||||
Zotero.Fulltext.downloadPDFTool('converter', availableVersion, function (success) {
|
||||
checkResult(success);
|
||||
Zotero.Fulltext.downloadPDFTool('info', availableVersion, checkResult);
|
||||
});
|
||||
}
|
||||
else if (installConverter) {
|
||||
Zotero.Fulltext.downloadPDFTool('converter', availableVersion, checkResult);
|
||||
}
|
||||
else if (installInfo) {
|
||||
Zotero.Fulltext.downloadPDFTool('info', availableVersion, checkResult);
|
||||
}
|
||||
else {
|
||||
Zotero.debug("PDF tools are up to date");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!translatorUpdates.length && !styleUpdates.length){
|
||||
await Zotero.DB.executeTransaction(function* (conn) {
|
||||
// Store the timestamp provided by the server
|
||||
|
@ -1648,6 +1743,7 @@ Zotero.Schema = new function(){
|
|||
if (!force) {
|
||||
_setRepositoryTimer(ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL);
|
||||
}
|
||||
updatePDFTools();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1681,6 +1777,8 @@ Zotero.Schema = new function(){
|
|||
});
|
||||
}
|
||||
|
||||
updatePDFTools();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
@ -2416,31 +2514,6 @@ Zotero.Schema = new function(){
|
|||
yield Zotero.DB.queryAsync("DELETE FROM relationPredicates WHERE predicate='dc:isReplacedBy'");
|
||||
}
|
||||
|
||||
else if (i == 100) {
|
||||
let userID = yield Zotero.DB.valueQueryAsync("SELECT value FROM settings WHERE setting='account' AND key='userID'");
|
||||
let predicateID = yield Zotero.DB.valueQueryAsync("SELECT predicateID FROM relationPredicates WHERE predicate='dc:relation'");
|
||||
if (userID && predicateID) {
|
||||
let rows = yield Zotero.DB.queryAsync("SELECT itemID, object FROM items JOIN itemRelations IR USING (itemID) WHERE libraryID=? AND predicateID=?", [1, predicateID]);
|
||||
for (let row of rows) {
|
||||
let matches = row.object.match(/^http:\/\/zotero.org\/users\/(\d+)\/items\/([A-Z0-9]+)$/);
|
||||
if (matches) {
|
||||
// Wrong libraryID
|
||||
if (matches[1] != userID) {
|
||||
yield Zotero.DB.queryAsync(`UPDATE OR REPLACE itemRelations SET object='http://zotero.org/users/${userID}/items/${matches[2]}' WHERE itemID=? AND predicateID=?`, [row.itemID, predicateID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (i == 101) {
|
||||
Components.utils.import("chrome://zotero/content/import/mendeley/mendeleyImport.js");
|
||||
let importer = new Zotero_Import_Mendeley();
|
||||
if (yield importer.hasImportedFiles()) {
|
||||
yield importer.queueFileCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// If breaking compatibility or doing anything dangerous, clear minorUpdateFrom
|
||||
}
|
||||
|
||||
|
|
|
@ -28,15 +28,12 @@ Zotero.Server = new function() {
|
|||
this.responseCodes = {
|
||||
200:"OK",
|
||||
201:"Created",
|
||||
204:"No Content",
|
||||
300:"Multiple Choices",
|
||||
400:"Bad Request",
|
||||
404:"Not Found",
|
||||
409:"Conflict",
|
||||
412:"Precondition Failed",
|
||||
500:"Internal Server Error",
|
||||
501:"Not Implemented",
|
||||
503:"Service Unavailable",
|
||||
501:"Method Not Implemented",
|
||||
504:"Gateway Timeout"
|
||||
};
|
||||
|
||||
|
@ -243,18 +240,17 @@ Zotero.Server.DataListener.prototype._headerFinished = function() {
|
|||
const hostRe = /[\r\n]Host: *(localhost|127\.0\.0\.1)(:[0-9]+)?[\r\n]/i;
|
||||
const contentTypeRe = /[\r\n]Content-Type: *([^ \r\n]+)/i;
|
||||
|
||||
const originRe = /[\r\n]Origin: *([^ \r\n]+)/i;
|
||||
var m = originRe.exec(this.header);
|
||||
if (m) {
|
||||
this.origin = m[1];
|
||||
}
|
||||
else {
|
||||
const bookmarkletRe = /[\r\n]Zotero-Bookmarklet: *([^ \r\n]+)/i;
|
||||
var m = bookmarkletRe.exec(this.header);
|
||||
if (m) this.origin = "https://www.zotero.org";
|
||||
}
|
||||
|
||||
if (!Zotero.isServer) {
|
||||
if(!Zotero.isServer) {
|
||||
const originRe = /[\r\n]Origin: *([^ \r\n]+)/i;
|
||||
var m = originRe.exec(this.header);
|
||||
if(m) {
|
||||
this.origin = m[1];
|
||||
} else {
|
||||
const bookmarkletRe = /[\r\n]Zotero-Bookmarklet: *([^ \r\n]+)/i;
|
||||
var m = bookmarkletRe.exec(this.header);
|
||||
if(m) this.origin = "https://www.zotero.org";
|
||||
}
|
||||
|
||||
// Make sure the Host header is set to localhost/127.0.0.1 to prevent DNS rebinding attacks
|
||||
if (!hostRe.exec(this.header)) {
|
||||
this._requestFinished(this._generateResponse(400, "text/plain", "Invalid Host header\n"));
|
||||
|
@ -333,46 +329,21 @@ Zotero.Server.DataListener.prototype._bodyData = function() {
|
|||
/**
|
||||
* Generates the response to an HTTP request
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype._generateResponse = function (status, contentTypeOrHeaders, body) {
|
||||
Zotero.Server.DataListener.prototype._generateResponse = function(status, contentType, body) {
|
||||
var response = "HTTP/1.0 "+status+" "+Zotero.Server.responseCodes[status]+"\r\n";
|
||||
|
||||
// Translation server
|
||||
if (Zotero.isServer) {
|
||||
// Add CORS headers if Origin header matches the allowed origins
|
||||
if (this.origin) {
|
||||
let allowedOrigins = Zotero.Prefs.get('httpServer.allowedOrigins')
|
||||
.split(/, */).filter(x => x);
|
||||
let allAllowed = allowedOrigins.includes('*');
|
||||
if (allAllowed || allowedOrigins.includes(this.origin)) {
|
||||
response += "Access-Control-Allow-Origin: " + (allAllowed ? '*' : this.origin) + "\r\n";
|
||||
response += "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n";
|
||||
response += "Access-Control-Allow-Headers: Content-Type\r\n";
|
||||
response += "Access-Control-Expose-Headers: Link\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Client
|
||||
else {
|
||||
if(!Zotero.isServer) {
|
||||
response += "X-Zotero-Version: "+Zotero.version+"\r\n";
|
||||
response += "X-Zotero-Connector-API-Version: "+CONNECTOR_API_VERSION+"\r\n";
|
||||
|
||||
if (this.origin === ZOTERO_CONFIG.BOOKMARKLET_ORIGIN ||
|
||||
if(this.origin === ZOTERO_CONFIG.BOOKMARKLET_ORIGIN ||
|
||||
this.origin === ZOTERO_CONFIG.HTTP_BOOKMARKLET_ORIGIN) {
|
||||
response += "Access-Control-Allow-Origin: " + this.origin + "\r\n";
|
||||
response += "Access-Control-Allow-Origin: "+this.origin+"\r\n";
|
||||
response += "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n";
|
||||
response += "Access-Control-Allow-Headers: Content-Type,X-Zotero-Connector-API-Version,X-Zotero-Version\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (contentTypeOrHeaders) {
|
||||
if (typeof contentTypeOrHeaders == 'string') {
|
||||
contentTypeOrHeaders = {
|
||||
'Content-Type': contentTypeOrHeaders
|
||||
};
|
||||
}
|
||||
for (let header in contentTypeOrHeaders) {
|
||||
response += `${header}: ${contentTypeOrHeaders[header]}\r\n`;
|
||||
}
|
||||
if(contentType) {
|
||||
response += "Content-Type: "+contentType+"\r\n";
|
||||
}
|
||||
|
||||
if(body) {
|
||||
|
@ -447,12 +418,10 @@ Zotero.Server.DataListener.prototype._processEndpoint = Zotero.Promise.coroutine
|
|||
}
|
||||
|
||||
// set up response callback
|
||||
var sendResponseCallback = function (code, contentTypeOrHeaders, arg, options) {
|
||||
this._requestFinished(
|
||||
this._generateResponse(code, contentTypeOrHeaders, arg),
|
||||
options
|
||||
);
|
||||
}.bind(this);
|
||||
var me = this;
|
||||
var sendResponseCallback = function(code, contentType, arg) {
|
||||
me._requestFinished(me._generateResponse(code, contentType, arg));
|
||||
}
|
||||
|
||||
// Pass to endpoint
|
||||
//
|
||||
|
@ -522,7 +491,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = Zotero.Promise.coroutine
|
|||
/*
|
||||
* returns HTTP data from a request
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype._requestFinished = function (response, options) {
|
||||
Zotero.Server.DataListener.prototype._requestFinished = function(response) {
|
||||
if(this._responseSent) {
|
||||
Zotero.debug("Request already finished; not sending another response");
|
||||
return;
|
||||
|
@ -540,19 +509,8 @@ Zotero.Server.DataListener.prototype._requestFinished = function (response, opti
|
|||
try {
|
||||
intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
|
||||
|
||||
// Filter logged response
|
||||
if (Zotero.Debug.enabled) {
|
||||
let maxLogLength = 2000;
|
||||
let str = response;
|
||||
if (options && options.logFilter) {
|
||||
str = options.logFilter(str);
|
||||
}
|
||||
if (str.length > maxLogLength) {
|
||||
str = str.substr(0, maxLogLength) + `\u2026 (${response.length} chars)`;
|
||||
}
|
||||
Zotero.debug(str, 5);
|
||||
}
|
||||
|
||||
// write response
|
||||
Zotero.debug(response, 5);
|
||||
intlStream.writeString(response);
|
||||
} finally {
|
||||
intlStream.close();
|
||||
|
|
|
@ -27,47 +27,35 @@ const CONNECTOR_API_VERSION = 2;
|
|||
Zotero.Server.Connector = {
|
||||
_waitingForSelection: {},
|
||||
|
||||
getSaveTarget: function (allowReadOnly) {
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
var library = null;
|
||||
var collection = null;
|
||||
var editable = null;
|
||||
|
||||
if (zp && zp.collectionsView) {
|
||||
if (zp.collectionsView.editable || allowReadOnly) {
|
||||
library = Zotero.Libraries.get(zp.getSelectedLibraryID());
|
||||
collection = zp.getSelectedCollection();
|
||||
editable = zp.collectionsView.editable;
|
||||
}
|
||||
// If not editable, switch to My Library if it exists and is editable
|
||||
else {
|
||||
let userLibrary = Zotero.Libraries.userLibrary;
|
||||
if (userLibrary && userLibrary.editable) {
|
||||
Zotero.debug("Save target isn't editable -- switching to My Library");
|
||||
|
||||
// Don't wait for this, because we don't want to slow down all conenctor
|
||||
// requests by making this function async
|
||||
zp.collectionsView.selectByID(userLibrary.treeViewID);
|
||||
|
||||
library = userLibrary;
|
||||
collection = null;
|
||||
editable = true;
|
||||
}
|
||||
}
|
||||
getSaveTarget: function () {
|
||||
var zp = Zotero.getActiveZoteroPane(),
|
||||
library = null,
|
||||
collection = null,
|
||||
editable = true;
|
||||
try {
|
||||
library = Zotero.Libraries.get(zp.getSelectedLibraryID());
|
||||
collection = zp.getSelectedCollection();
|
||||
editable = zp.collectionsView.editable;
|
||||
}
|
||||
else {
|
||||
catch (e) {
|
||||
let id = Zotero.Prefs.get('lastViewedFolder');
|
||||
if (id) {
|
||||
({ library, collection, editable } = this.resolveTarget(id));
|
||||
if (!editable && !allowReadOnly) {
|
||||
let userLibrary = Zotero.Libraries.userLibrary;
|
||||
if (userLibrary && userLibrary.editable) {
|
||||
Zotero.debug("Save target isn't editable -- switching to My Library");
|
||||
let treeViewID = userLibrary.treeViewID;
|
||||
Zotero.Prefs.set('lastViewedFolder', treeViewID);
|
||||
({ library, collection, editable } = this.resolveTarget(treeViewID));
|
||||
let type = id[0];
|
||||
Zotero.debug(type);
|
||||
id = parseInt(('' + id).substr(1));
|
||||
|
||||
switch (type) {
|
||||
case 'L':
|
||||
library = Zotero.Libraries.get(id);
|
||||
editable = library.editable;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
collection = Zotero.Collections.get(id);
|
||||
library = collection.library;
|
||||
editable = collection.editable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,246 +63,15 @@ Zotero.Server.Connector = {
|
|||
// (which should never be the case anymore)
|
||||
if (!library) {
|
||||
let userLibrary = Zotero.Libraries.userLibrary;
|
||||
if (userLibrary && userLibrary.editable) {
|
||||
if (userLibrary) {
|
||||
library = userLibrary;
|
||||
}
|
||||
}
|
||||
|
||||
return { library, collection, editable };
|
||||
},
|
||||
|
||||
resolveTarget: function (targetID) {
|
||||
var library;
|
||||
var collection;
|
||||
var editable;
|
||||
|
||||
var type = targetID[0];
|
||||
var id = parseInt(('' + targetID).substr(1));
|
||||
|
||||
switch (type) {
|
||||
case 'L':
|
||||
library = Zotero.Libraries.get(id);
|
||||
editable = library.editable;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
collection = Zotero.Collections.get(id);
|
||||
library = collection.library;
|
||||
editable = collection.editable;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported target type '${type}'`);
|
||||
}
|
||||
|
||||
return { library, collection, editable };
|
||||
}
|
||||
};
|
||||
Zotero.Server.Connector.Data = {};
|
||||
|
||||
Zotero.Server.Connector.SessionManager = {
|
||||
_sessions: new Map(),
|
||||
|
||||
get: function (id) {
|
||||
return this._sessions.get(id);
|
||||
},
|
||||
|
||||
create: function (id, action, requestData) {
|
||||
// Legacy connector
|
||||
if (!id) {
|
||||
Zotero.debug("No session id provided by client", 2);
|
||||
id = Zotero.Utilities.randomString();
|
||||
}
|
||||
if (this._sessions.has(id)) {
|
||||
throw new Error(`Session ID ${id} exists`);
|
||||
}
|
||||
Zotero.debug("Creating connector save session " + id);
|
||||
var session = new Zotero.Server.Connector.SaveSession(id, action, requestData);
|
||||
this._sessions.set(id, session);
|
||||
this.gc();
|
||||
return session;
|
||||
},
|
||||
|
||||
gc: function () {
|
||||
// Delete sessions older than 10 minutes, or older than 1 minute if more than 10 sessions
|
||||
var ttl = this._sessions.size >= 10 ? 60 : 600;
|
||||
var deleteBefore = new Date() - ttl * 1000;
|
||||
|
||||
for (let session of this._sessions) {
|
||||
if (session.created < deleteBefore) {
|
||||
this._session.delete(session.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Zotero.Server.Connector.SaveSession = function (id, action, requestData) {
|
||||
this.id = id;
|
||||
this.created = new Date();
|
||||
this._action = action;
|
||||
this._requestData = requestData;
|
||||
this._items = new Set();
|
||||
};
|
||||
|
||||
Zotero.Server.Connector.SaveSession.prototype.addItem = async function (item) {
|
||||
return this.addItems([item]);
|
||||
};
|
||||
|
||||
Zotero.Server.Connector.SaveSession.prototype.addItems = async function (items) {
|
||||
for (let item of items) {
|
||||
this._items.add(item);
|
||||
}
|
||||
|
||||
// Update the items with the current target data, in case it changed since the save began
|
||||
await this._updateItems(items);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the target data for this session and update any items that have already been saved
|
||||
*/
|
||||
Zotero.Server.Connector.SaveSession.prototype.update = async function (targetID, tags) {
|
||||
var previousTargetID = this._currentTargetID;
|
||||
this._currentTargetID = targetID;
|
||||
this._currentTags = tags || "";
|
||||
|
||||
// Select new destination in collections pane
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
if (zp && zp.collectionsView) {
|
||||
await zp.collectionsView.selectByID(targetID);
|
||||
}
|
||||
// If window is closed, select target collection re-open
|
||||
else {
|
||||
Zotero.Prefs.set('lastViewedFolder', targetID);
|
||||
}
|
||||
|
||||
// If moving from a non-filesEditable library to a filesEditable library, resave from
|
||||
// original data, since there might be files that weren't saved or were removed
|
||||
if (previousTargetID && previousTargetID != targetID) {
|
||||
let { library: oldLibrary } = Zotero.Server.Connector.resolveTarget(previousTargetID);
|
||||
let { library: newLibrary } = Zotero.Server.Connector.resolveTarget(targetID);
|
||||
if (oldLibrary != newLibrary && !oldLibrary.filesEditable && newLibrary.filesEditable) {
|
||||
Zotero.debug("Resaving items to filesEditable library");
|
||||
if (this._action == 'saveItems' || this._action == 'saveSnapshot') {
|
||||
// Delete old items
|
||||
for (let item of this._items) {
|
||||
await item.eraseTx();
|
||||
}
|
||||
let actionUC = Zotero.Utilities.capitalize(this._action);
|
||||
let newItems = await Zotero.Server.Connector[actionUC].prototype[this._action](
|
||||
targetID, this._requestData
|
||||
);
|
||||
// saveSnapshot only returns a single item
|
||||
if (this._action == 'saveSnapshot') {
|
||||
newItems = [newItems];
|
||||
}
|
||||
this._items = new Set(newItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this._updateItems(this._items);
|
||||
|
||||
// If a single item was saved, select it (or its parent, if it now has one)
|
||||
if (zp && zp.collectionsView && this._items.size == 1) {
|
||||
let item = Array.from(this._items)[0];
|
||||
item = item.isTopLevelItem() ? item : item.parentItem;
|
||||
// Don't select if in trash
|
||||
if (!item.deleted) {
|
||||
await zp.selectItem(item.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the passed items with the current target and tags
|
||||
*/
|
||||
Zotero.Server.Connector.SaveSession.prototype._updateItems = Zotero.serial(async function (items) {
|
||||
if (items.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var { library, collection, editable } = Zotero.Server.Connector.resolveTarget(this._currentTargetID);
|
||||
var libraryID = library.libraryID;
|
||||
|
||||
var tags = this._currentTags.trim();
|
||||
tags = tags ? tags.split(/\s*,\s*/) : [];
|
||||
|
||||
Zotero.debug("Updating items for connector save session " + this.id);
|
||||
|
||||
for (let item of items) {
|
||||
let newLibrary = Zotero.Libraries.get(library.libraryID);
|
||||
|
||||
if (item.libraryID != libraryID) {
|
||||
let newItem = await item.moveToLibrary(libraryID);
|
||||
// Replace item in session
|
||||
this._items.delete(item);
|
||||
this._items.add(newItem);
|
||||
}
|
||||
|
||||
// If the item is now a child item (e.g., from Retrieve Metadata for PDF), update the
|
||||
// parent item instead
|
||||
if (!item.isTopLevelItem()) {
|
||||
item = item.parentItem;
|
||||
}
|
||||
// Skip deleted items
|
||||
if (!Zotero.Items.exists(item.id)) {
|
||||
Zotero.debug(`Item ${item.id} in save session no longer exists`);
|
||||
continue;
|
||||
}
|
||||
// Keep automatic tags
|
||||
let originalTags = item.getTags().filter(tag => tag.type == 1);
|
||||
item.setTags(originalTags.concat(tags));
|
||||
item.setCollections(collection ? [collection.id] : []);
|
||||
await item.saveTx();
|
||||
}
|
||||
|
||||
this._updateRecents();
|
||||
});
|
||||
|
||||
|
||||
Zotero.Server.Connector.SaveSession.prototype._updateRecents = function () {
|
||||
var targetID = this._currentTargetID;
|
||||
try {
|
||||
let numRecents = 7;
|
||||
let recents = Zotero.Prefs.get('recentSaveTargets') || '[]';
|
||||
recents = JSON.parse(recents);
|
||||
// If there's already a target from this session in the list, update it
|
||||
for (let recent of recents) {
|
||||
if (recent.sessionID == this.id) {
|
||||
recent.id = targetID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If a session is found with the same target, move it to the end without changing
|
||||
// the sessionID. This could be the current session that we updated above or a different
|
||||
// one. (We need to leave the old sessionID for the same target or we'll end up removing
|
||||
// the previous target from the history if it's changed in the current one.)
|
||||
let pos = recents.findIndex(r => r.id == targetID);
|
||||
if (pos != -1) {
|
||||
recents = [
|
||||
...recents.slice(0, pos),
|
||||
...recents.slice(pos + 1),
|
||||
recents[pos]
|
||||
];
|
||||
}
|
||||
// Otherwise just add this one to the end
|
||||
else {
|
||||
recents = recents.concat([{
|
||||
id: targetID,
|
||||
sessionID: this.id
|
||||
}]);
|
||||
}
|
||||
recents = recents.slice(-1 * numRecents);
|
||||
Zotero.Prefs.set('recentSaveTargets', JSON.stringify(recents));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.Prefs.clear('recentSaveTargets');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Zotero.Server.Connector.AttachmentProgressManager = new function() {
|
||||
var attachmentsInProgress = new WeakMap(),
|
||||
attachmentProgress = {},
|
||||
|
@ -415,6 +172,7 @@ Zotero.Server.Connector.GetTranslators.prototype = {
|
|||
*/
|
||||
Zotero.Server.Connector.Detect = function() {};
|
||||
Zotero.Server.Endpoints["/connector/detect"] = Zotero.Server.Connector.Detect;
|
||||
Zotero.Server.Connector.Data = {};
|
||||
Zotero.Server.Connector.Detect.prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
supportedDataTypes: ["application/json"],
|
||||
|
@ -512,8 +270,6 @@ Zotero.Server.Connector.SavePage.prototype = {
|
|||
*/
|
||||
init: function(url, data, sendResponseCallback) {
|
||||
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
|
||||
|
||||
// Shouldn't happen as long as My Library exists
|
||||
if (!library.editable) {
|
||||
Zotero.logError("Can't add item to read-only library " + library.name);
|
||||
return sendResponseCallback(500, "application/json", JSON.stringify({ libraryEditable: false }));
|
||||
|
@ -600,9 +356,9 @@ Zotero.Server.Connector.SavePage.prototype = {
|
|||
* Returns:
|
||||
* 201 response code with item in body.
|
||||
*/
|
||||
Zotero.Server.Connector.SaveItems = function() {};
|
||||
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItems;
|
||||
Zotero.Server.Connector.SaveItems.prototype = {
|
||||
Zotero.Server.Connector.SaveItem = function() {};
|
||||
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItem;
|
||||
Zotero.Server.Connector.SaveItem.prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
supportedDataTypes: ["application/json"],
|
||||
permitBookmarklet: true,
|
||||
|
@ -611,103 +367,69 @@ Zotero.Server.Connector.SaveItems.prototype = {
|
|||
* Either loads HTML into a hidden browser and initiates translation, or saves items directly
|
||||
* to the database
|
||||
*/
|
||||
init: Zotero.Promise.coroutine(function* (requestData) {
|
||||
var data = requestData.data;
|
||||
init: Zotero.Promise.coroutine(function* (options) {
|
||||
var data = options.data;
|
||||
|
||||
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
|
||||
var libraryID = library.libraryID;
|
||||
var targetID = collection ? collection.treeViewID : library.treeViewID;
|
||||
|
||||
try {
|
||||
var session = Zotero.Server.Connector.SessionManager.create(
|
||||
data.sessionID,
|
||||
'saveItems',
|
||||
requestData
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
return [409, "application/json", JSON.stringify({ error: "SESSION_EXISTS" })];
|
||||
}
|
||||
yield session.update(targetID);
|
||||
|
||||
// Shouldn't happen as long as My Library exists
|
||||
if (!library.editable) {
|
||||
Zotero.logError("Can't add item to read-only library " + library.name);
|
||||
return [500, "application/json", JSON.stringify({ libraryEditable: false })];
|
||||
}
|
||||
|
||||
return new Zotero.Promise((resolve) => {
|
||||
try {
|
||||
this.saveItems(
|
||||
targetID,
|
||||
requestData,
|
||||
function (topLevelItems) {
|
||||
resolve([201, "application/json", JSON.stringify({items: topLevelItems})]);
|
||||
}
|
||||
)
|
||||
// Add items to session once all attachments have been saved
|
||||
.then(function (items) {
|
||||
session.addItems(items);
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
resolve(500);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
saveItems: async function (target, requestData, onTopLevelItemsDone) {
|
||||
var { library, collection, editable } = Zotero.Server.Connector.resolveTarget(target);
|
||||
|
||||
var data = requestData.data;
|
||||
var cookieSandbox = data.uri
|
||||
? new Zotero.CookieSandbox(
|
||||
null,
|
||||
data.uri,
|
||||
data.detailedCookies ? "" : data.cookie || "",
|
||||
requestData.headers["User-Agent"]
|
||||
options.headers["User-Agent"]
|
||||
)
|
||||
: null;
|
||||
if (cookieSandbox && data.detailedCookies) {
|
||||
if(cookieSandbox && data.detailedCookies) {
|
||||
cookieSandbox.addCookiesFromHeader(data.detailedCookies);
|
||||
}
|
||||
|
||||
for (let item of data.items) {
|
||||
Zotero.Server.Connector.AttachmentProgressManager.add(item.attachments);
|
||||
for(var i=0; i<data.items.length; i++) {
|
||||
Zotero.Server.Connector.AttachmentProgressManager.add(data.items[i].attachments);
|
||||
}
|
||||
|
||||
var proxy = data.proxy && new Zotero.Proxy(data.proxy);
|
||||
|
||||
// Save items
|
||||
let proxy = data.proxy && new Zotero.Proxy(data.proxy);
|
||||
// save items
|
||||
var itemSaver = new Zotero.Translate.ItemSaver({
|
||||
libraryID: library.libraryID,
|
||||
libraryID,
|
||||
collections: collection ? [collection.id] : undefined,
|
||||
attachmentMode: Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD,
|
||||
forceTagType: 1,
|
||||
referrer: data.uri,
|
||||
cookieSandbox,
|
||||
proxy
|
||||
});
|
||||
return itemSaver.saveItems(
|
||||
data.items,
|
||||
Zotero.Server.Connector.AttachmentProgressManager.onProgress,
|
||||
function () {
|
||||
// Remove attachments from item.attachments that aren't being saved. We have to
|
||||
// clone the items so that we don't mutate the data stored in the session.
|
||||
var savedItems = [...data.items.map(item => Object.assign({}, item))];
|
||||
for (let item of savedItems) {
|
||||
item.attachments = item.attachments
|
||||
.filter(attachment => {
|
||||
return Zotero.Server.Connector.AttachmentProgressManager.has(attachment);
|
||||
});
|
||||
try {
|
||||
var deferred = Zotero.Promise.defer();
|
||||
itemSaver.saveItems(
|
||||
data.items,
|
||||
Zotero.Server.Connector.AttachmentProgressManager.onProgress,
|
||||
function() {
|
||||
// Remove attachments not being saved from item.attachments
|
||||
for(var i=0; i<data.items.length; i++) {
|
||||
var item = data.items[i];
|
||||
for(var j=0; j<item.attachments.length; j++) {
|
||||
if(!Zotero.Server.Connector.AttachmentProgressManager.has(item.attachments[j])) {
|
||||
item.attachments.splice(j--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve([201, "application/json", JSON.stringify({items: data.items})]);
|
||||
}
|
||||
if (onTopLevelItemsDone) {
|
||||
onTopLevelItemsDone(savedItems);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
return 500;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -730,84 +452,69 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
|
|||
/**
|
||||
* Save snapshot
|
||||
*/
|
||||
init: async function (requestData) {
|
||||
var data = requestData.data;
|
||||
init: Zotero.Promise.coroutine(function* (options) {
|
||||
var data = options.data;
|
||||
|
||||
Zotero.Server.Connector.Data[data["url"]] = "<html>"+data["html"]+"</html>";
|
||||
|
||||
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
|
||||
var targetID = collection ? collection.treeViewID : library.treeViewID;
|
||||
var libraryID = library.libraryID;
|
||||
|
||||
try {
|
||||
var session = Zotero.Server.Connector.SessionManager.create(
|
||||
data.sessionID,
|
||||
'saveSnapshot',
|
||||
requestData
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
return [409, "application/json", JSON.stringify({ error: "SESSION_EXISTS" })];
|
||||
}
|
||||
await session.update(collection ? collection.treeViewID : library.treeViewID);
|
||||
|
||||
// Shouldn't happen as long as My Library exists
|
||||
if (!library.editable) {
|
||||
Zotero.logError("Can't add item to read-only library " + library.name);
|
||||
return [500, "application/json", JSON.stringify({ libraryEditable: false })];
|
||||
}
|
||||
|
||||
try {
|
||||
let item = await this.saveSnapshot(targetID, requestData);
|
||||
await session.addItem(item);
|
||||
// determine whether snapshot can be saved
|
||||
var filesEditable;
|
||||
if (libraryID) {
|
||||
let group = Zotero.Groups.getByLibraryID(libraryID);
|
||||
filesEditable = group.filesEditable;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
return 500;
|
||||
else {
|
||||
filesEditable = true;
|
||||
}
|
||||
|
||||
return 201;
|
||||
},
|
||||
|
||||
saveSnapshot: async function (target, requestData) {
|
||||
var { library, collection, editable } = Zotero.Server.Connector.resolveTarget(target);
|
||||
var libraryID = library.libraryID;
|
||||
var data = requestData.data;
|
||||
|
||||
var cookieSandbox = data.url
|
||||
? new Zotero.CookieSandbox(
|
||||
null,
|
||||
data.url,
|
||||
data.detailedCookies ? "" : data.cookie || "",
|
||||
requestData.headers["User-Agent"]
|
||||
options.headers["User-Agent"]
|
||||
)
|
||||
: null;
|
||||
if (cookieSandbox && data.detailedCookies) {
|
||||
if(cookieSandbox && data.detailedCookies) {
|
||||
cookieSandbox.addCookiesFromHeader(data.detailedCookies);
|
||||
}
|
||||
|
||||
if (data.pdf && library.filesEditable) {
|
||||
let item = await Zotero.Attachments.importFromURL({
|
||||
libraryID,
|
||||
url: data.url,
|
||||
collections: collection ? [collection.id] : undefined,
|
||||
contentType: "application/pdf",
|
||||
cookieSandbox
|
||||
});
|
||||
if (data.pdf && filesEditable) {
|
||||
delete Zotero.Server.Connector.Data[data.url];
|
||||
|
||||
// Automatically recognize PDF
|
||||
Zotero.RecognizePDF.autoRecognizeItems([item]);
|
||||
|
||||
return item;
|
||||
try {
|
||||
yield Zotero.Attachments.importFromURL({
|
||||
libraryID,
|
||||
url: data.url,
|
||||
collections: collection ? [collection.id] : undefined,
|
||||
contentType: "application/pdf",
|
||||
cookieSandbox
|
||||
});
|
||||
return 201;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
return new Zotero.Promise((resolve, reject) => {
|
||||
Zotero.Server.Connector.Data[data.url] = "<html>" + data.html + "</html>";
|
||||
else {
|
||||
let deferred = Zotero.Promise.defer();
|
||||
Zotero.HTTP.loadDocuments(
|
||||
["zotero://connector/" + encodeURIComponent(data.url)],
|
||||
async function (doc) {
|
||||
Zotero.Promise.coroutine(function* (doc) {
|
||||
delete Zotero.Server.Connector.Data[data.url];
|
||||
|
||||
try {
|
||||
// Create new webpage item
|
||||
let item = new Zotero.Item("webpage");
|
||||
// create new webpage item
|
||||
var item = new Zotero.Item("webpage");
|
||||
item.libraryID = libraryID;
|
||||
item.setField("title", doc.title);
|
||||
item.setField("url", data.url);
|
||||
|
@ -815,29 +522,28 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
|
|||
if (collection) {
|
||||
item.setCollections([collection.id]);
|
||||
}
|
||||
var itemID = await item.saveTx();
|
||||
var itemID = yield item.saveTx();
|
||||
|
||||
// Save snapshot
|
||||
if (library.filesEditable && !data.skipSnapshot) {
|
||||
await Zotero.Attachments.importFromDocument({
|
||||
// save snapshot
|
||||
if (filesEditable && !data.skipSnapshot) {
|
||||
yield Zotero.Attachments.importFromDocument({
|
||||
document: doc,
|
||||
parentItemID: itemID
|
||||
});
|
||||
}
|
||||
|
||||
resolve(item);
|
||||
deferred.resolve(201);
|
||||
} catch(e) {
|
||||
Zotero.debug(e, 1);
|
||||
deferred.resolve(500);
|
||||
throw e;
|
||||
}
|
||||
catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
cookieSandbox
|
||||
}),
|
||||
null, null, false, cookieSandbox
|
||||
);
|
||||
});
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -874,66 +580,6 @@ Zotero.Server.Connector.SelectItems.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Accepts:
|
||||
* sessionID - A session ID previously passed to /saveItems
|
||||
* target - A treeViewID (L1, C23, etc.) for the library or collection to save to
|
||||
* tags - A string of tags separated by commas
|
||||
*
|
||||
* Returns:
|
||||
* 200 response on successful change
|
||||
* 400 on error with 'error' property in JSON
|
||||
*/
|
||||
Zotero.Server.Connector.UpdateSession = function() {};
|
||||
Zotero.Server.Endpoints["/connector/updateSession"] = Zotero.Server.Connector.UpdateSession;
|
||||
Zotero.Server.Connector.UpdateSession.prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
supportedDataTypes: ["application/json"],
|
||||
permitBookmarklet: true,
|
||||
|
||||
init: async function (requestData) {
|
||||
var data = requestData.data
|
||||
|
||||
if (!data.sessionID) {
|
||||
return [400, "application/json", JSON.stringify({ error: "SESSION_ID_NOT_PROVIDED" })];
|
||||
}
|
||||
|
||||
var session = Zotero.Server.Connector.SessionManager.get(data.sessionID);
|
||||
if (!session) {
|
||||
Zotero.debug("Can't find session " + data.sessionID, 1);
|
||||
return [400, "application/json", JSON.stringify({ error: "SESSION_NOT_FOUND" })];
|
||||
}
|
||||
|
||||
// Parse treeViewID
|
||||
var [type, id] = [data.target[0], parseInt(data.target.substr(1))];
|
||||
var tags = data.tags;
|
||||
|
||||
if (type == 'C') {
|
||||
let collection = await Zotero.Collections.getAsync(id);
|
||||
if (!collection) {
|
||||
return [400, "application/json", JSON.stringify({ error: "COLLECTION_NOT_FOUND" })];
|
||||
}
|
||||
}
|
||||
|
||||
await session.update(data.target, tags);
|
||||
|
||||
return [200, "application/json", JSON.stringify({})];
|
||||
}
|
||||
};
|
||||
|
||||
Zotero.Server.Connector.DelaySync = function () {};
|
||||
Zotero.Server.Endpoints["/connector/delaySync"] = Zotero.Server.Connector.DelaySync;
|
||||
Zotero.Server.Connector.DelaySync.prototype = {
|
||||
supportedMethods: ["POST"],
|
||||
|
||||
init: async function (requestData) {
|
||||
Zotero.Sync.Runner.delaySync(10000);
|
||||
return [204];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets progress for an attachment that is currently being saved
|
||||
*
|
||||
|
@ -974,44 +620,25 @@ Zotero.Server.Connector.Import.prototype = {
|
|||
supportedDataTypes: '*',
|
||||
permitBookmarklet: false,
|
||||
|
||||
init: async function (requestData) {
|
||||
init: Zotero.Promise.coroutine(function* (options) {
|
||||
let translate = new Zotero.Translate.Import();
|
||||
translate.setString(requestData.data);
|
||||
let translators = await translate.getTranslators();
|
||||
translate.setString(options.data);
|
||||
let translators = yield translate.getTranslators();
|
||||
if (!translators || !translators.length) {
|
||||
return 400;
|
||||
}
|
||||
translate.setTranslator(translators[0]);
|
||||
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
|
||||
var libraryID = library.libraryID;
|
||||
|
||||
// Shouldn't happen as long as My Library exists
|
||||
if (!library.editable) {
|
||||
Zotero.logError("Can't import into read-only library " + library.name);
|
||||
return [500, "application/json", JSON.stringify({ libraryEditable: false })];
|
||||
}
|
||||
|
||||
try {
|
||||
var session = Zotero.Server.Connector.SessionManager.create(requestData.query.session);
|
||||
}
|
||||
catch (e) {
|
||||
return [409, "application/json", JSON.stringify({ error: "SESSION_EXISTS" })];
|
||||
}
|
||||
await session.update(collection ? collection.treeViewID : library.treeViewID);
|
||||
|
||||
let items = await translate.translate({
|
||||
libraryID,
|
||||
collections: collection ? [collection.id] : null,
|
||||
forceTagType: 1,
|
||||
// Import translation skips selection by default, so force it to occur
|
||||
saveOptions: {
|
||||
skipSelect: false
|
||||
}
|
||||
let items = yield translate.translate({
|
||||
libraryID: library.libraryID,
|
||||
collections: collection ? [collection.id] : null
|
||||
});
|
||||
session.addItems(items);
|
||||
|
||||
return [201, "application/json", JSON.stringify(items)];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1028,11 +655,9 @@ Zotero.Server.Connector.InstallStyle.prototype = {
|
|||
supportedDataTypes: '*',
|
||||
permitBookmarklet: false,
|
||||
|
||||
init: Zotero.Promise.coroutine(function* (requestData) {
|
||||
init: Zotero.Promise.coroutine(function* (options) {
|
||||
try {
|
||||
var styleName = yield Zotero.Styles.install(
|
||||
requestData.data, requestData.query.origin || null, true
|
||||
);
|
||||
var styleName = yield Zotero.Styles.install(options.data, options.query.origin || null, true);
|
||||
} catch (e) {
|
||||
return [400, "text/plain", e.message];
|
||||
}
|
||||
|
@ -1092,7 +717,7 @@ Zotero.Server.Connector.GetSelectedCollection.prototype = {
|
|||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
init: function(postData, sendResponseCallback) {
|
||||
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget(true);
|
||||
var { library, collection, editable } = Zotero.Server.Connector.getSaveTarget();
|
||||
var response = {
|
||||
libraryID: library.libraryID,
|
||||
libraryName: library.name,
|
||||
|
@ -1100,6 +725,9 @@ Zotero.Server.Connector.GetSelectedCollection.prototype = {
|
|||
editable
|
||||
};
|
||||
|
||||
response.libraryName = library.name;
|
||||
response.libraryEditable = library.editable;
|
||||
|
||||
if(collection && collection.id) {
|
||||
response.id = collection.id;
|
||||
response.name = collection.name;
|
||||
|
@ -1108,64 +736,7 @@ Zotero.Server.Connector.GetSelectedCollection.prototype = {
|
|||
response.name = response.libraryName;
|
||||
}
|
||||
|
||||
// Get list of editable libraries and collections
|
||||
var collections = [];
|
||||
var originalLibraryID = library.libraryID;
|
||||
for (let library of Zotero.Libraries.getAll()) {
|
||||
if (!library.editable) continue;
|
||||
|
||||
// Add recent: true for recent targets
|
||||
|
||||
collections.push(
|
||||
{
|
||||
id: library.treeViewID,
|
||||
name: library.name,
|
||||
level: 0
|
||||
},
|
||||
...Zotero.Collections.getByLibrary(library.libraryID, true).map(c => ({
|
||||
id: c.treeViewID,
|
||||
name: c.name,
|
||||
level: c.level + 1 || 1 // Added by Zotero.Collections._getByContainer()
|
||||
}))
|
||||
);
|
||||
}
|
||||
response.targets = collections;
|
||||
|
||||
// Mark recent targets
|
||||
try {
|
||||
let recents = Zotero.Prefs.get('recentSaveTargets');
|
||||
if (recents) {
|
||||
recents = new Set(JSON.parse(recents).map(o => o.id));
|
||||
for (let target of response.targets) {
|
||||
if (recents.has(target.id)) {
|
||||
target.recent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.Prefs.clear('recentSaveTargets');
|
||||
}
|
||||
|
||||
sendResponseCallback(
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify(response),
|
||||
{
|
||||
// Filter out collection names in debug output
|
||||
logFilter: function (str) {
|
||||
try {
|
||||
let json = JSON.parse(str.match(/^{"libraryID"[^]+/m)[0]);
|
||||
json.targets.forEach(t => t.name = "\u2026");
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
sendResponseCallback(200, "application/json", JSON.stringify(response));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1188,7 +759,7 @@ Zotero.Server.Connector.GetClientHostnames.prototype = {
|
|||
/**
|
||||
* Returns a 200 response to say the server is alive
|
||||
*/
|
||||
init: Zotero.Promise.coroutine(function* (requestData) {
|
||||
init: Zotero.Promise.coroutine(function* (options) {
|
||||
try {
|
||||
var hostnames = yield Zotero.Proxies.DNS.getHostnames();
|
||||
} catch(e) {
|
||||
|
@ -1264,108 +835,8 @@ Zotero.Server.Connector.Ping.prototype = {
|
|||
response.prefs.reportActiveURL = true;
|
||||
}
|
||||
|
||||
this.versionWarning(req);
|
||||
|
||||
return [200, 'application/json', JSON.stringify(response)];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Warn on outdated connector version
|
||||
*
|
||||
* We can remove this once the connector checks and warns on its own and most people are on
|
||||
* a version that does that.
|
||||
*/
|
||||
versionWarning: function (req) {
|
||||
try {
|
||||
if (!Zotero.Prefs.get('showConnectorVersionWarning')) return;
|
||||
if (!req.headers) return;
|
||||
|
||||
var minVersion = ZOTERO_CONFIG.CONNECTOR_MIN_VERSION;
|
||||
var appName = ZOTERO_CONFIG.CLIENT_NAME;
|
||||
var domain = ZOTERO_CONFIG.DOMAIN_NAME;
|
||||
var origin = req.headers.Origin;
|
||||
|
||||
var browser;
|
||||
var message;
|
||||
var showDownloadButton = false;
|
||||
if (origin && origin.startsWith('safari-extension')) {
|
||||
browser = 'safari';
|
||||
message = `An update is available for the ${appName} Connector for Safari.\n\n`
|
||||
+ 'You can upgrade from the Extensions pane of the Safari preferences.';
|
||||
}
|
||||
else if (origin && origin.startsWith('chrome-extension')) {
|
||||
browser = 'chrome';
|
||||
message = `An update is available for the ${appName} Connector for Chrome.\n\n`
|
||||
+ `You can upgrade to the latest version from ${domain}.`;
|
||||
showDownloadButton = true;
|
||||
}
|
||||
else if (req.headers['User-Agent'] && req.headers['User-Agent'].includes('Firefox/')) {
|
||||
browser = 'firefox';
|
||||
message = `An update is available for the ${appName} Connector for Firefox.\n\n`
|
||||
+ `You can upgrade to the latest version from ${domain}.`;
|
||||
showDownloadButton = true;
|
||||
}
|
||||
else {
|
||||
Zotero.debug("Unknown browser");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Zotero.Server.Connector['skipVersionWarning-' + browser]) return;
|
||||
|
||||
var version = req.headers['X-Zotero-Version'];
|
||||
if (!version || version == '4.999.0') return;
|
||||
|
||||
// If connector is up to date, bail
|
||||
if (Services.vc.compare(version, minVersion) >= 0) return;
|
||||
|
||||
var showNextPref = `nextConnectorVersionWarning.${browser}`;
|
||||
var showNext = Zotero.Prefs.get(showNextPref);
|
||||
if (showNext && new Date() < new Date(showNext * 1000)) return;
|
||||
|
||||
// Don't show again for this browser until restart
|
||||
Zotero.Server.Connector['skipVersionWarning-' + browser] = true;
|
||||
var ps = Services.prompt;
|
||||
var buttonFlags;
|
||||
if (showDownloadButton) {
|
||||
buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
|
||||
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
|
||||
}
|
||||
else {
|
||||
buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK;
|
||||
}
|
||||
setTimeout(function () {
|
||||
var dontShow = {};
|
||||
var index = ps.confirmEx(null,
|
||||
Zotero.getString('general.updateAvailable'),
|
||||
message,
|
||||
buttonFlags,
|
||||
showDownloadButton ? Zotero.getString('general.upgrade') : null,
|
||||
showDownloadButton ? Zotero.getString('general.notNow') : null,
|
||||
null,
|
||||
"Don\u0027t show again for a month",
|
||||
dontShow
|
||||
);
|
||||
|
||||
var nextShowDays;
|
||||
if (dontShow.value) {
|
||||
nextShowDays = 30;
|
||||
}
|
||||
// Don't show again for at least a day, even after a restart
|
||||
else {
|
||||
nextShowDays = 1;
|
||||
}
|
||||
Zotero.Prefs.set(showNextPref, Math.round(Date.now() / 1000) + 86400 * nextShowDays);
|
||||
|
||||
if (showDownloadButton && index == 0) {
|
||||
Zotero.launchURL(ZOTERO_CONFIG.CONNECTORS_URL);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1413,7 +884,7 @@ Zotero.Server.Connector.IncompatibleVersion.prototype = {
|
|||
sendResponseCallback(404);
|
||||
if(Zotero.Server.Connector.IncompatibleVersion._errorShown) return;
|
||||
|
||||
Zotero.Utilities.Internal.activate();
|
||||
Zotero.Integration.activate();
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
|
||||
createInstance(Components.interfaces.nsIPromptService);
|
||||
ps.alert(null,
|
|
@ -687,7 +687,7 @@ Zotero.Sync.Storage.Local = {
|
|||
|
||||
var filename = item.attachmentFilename;
|
||||
if (!filename) {
|
||||
Zotero.debug("Empty filename for item " + item.key, 2);
|
||||
throw new Error("Empty path for item " + item.key);
|
||||
}
|
||||
// Don't save Windows aliases
|
||||
if (filename.endsWith('.lnk')) {
|
||||
|
|
|
@ -224,6 +224,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
|
||||
try {
|
||||
var req = yield Zotero.HTTP.request("OPTIONS", this.rootURI);
|
||||
this._checkResponse(req);
|
||||
|
||||
Zotero.debug("WebDAV credentials cached");
|
||||
this._cachedCredentials = true;
|
||||
|
@ -594,22 +595,30 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
}
|
||||
|
||||
// Test whether URL is WebDAV-enabled
|
||||
var req = yield Zotero.HTTP.request(
|
||||
"OPTIONS",
|
||||
uri,
|
||||
{
|
||||
successCodes: [200, 404],
|
||||
requestObserver: function (req) {
|
||||
if (req.channel) {
|
||||
channel = req.channel;
|
||||
}
|
||||
if (options.onRequest) {
|
||||
options.onRequest(req);
|
||||
}
|
||||
},
|
||||
debug: true
|
||||
try {
|
||||
var req = yield Zotero.HTTP.request(
|
||||
"OPTIONS",
|
||||
uri,
|
||||
{
|
||||
successCodes: [200, 404],
|
||||
requestObserver: function (req) {
|
||||
if (req.channel) {
|
||||
channel = req.channel;
|
||||
}
|
||||
if (options.onRequest) {
|
||||
options.onRequest(req);
|
||||
}
|
||||
},
|
||||
debug: true
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||
this._checkResponse(e.xmlhttp, e.channel);
|
||||
}
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
|
||||
Zotero.debug(req.getAllResponseHeaders());
|
||||
|
||||
|
@ -857,7 +866,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
break;
|
||||
|
||||
case "NONEXISTENT_FILE_NOT_MISSING":
|
||||
errorTitle = Zotero.getString('sync.storage.error.webdav.serverConfig.title');
|
||||
var errorTitle = Zotero.getString('sync.storage.error.webdav.serverConfig.title');
|
||||
errorMsg = Zotero.getString('sync.storage.error.webdav.nonexistentFileNotMissing');
|
||||
break;
|
||||
|
||||
|
@ -868,25 +877,18 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
var e;
|
||||
if (errorMsg) {
|
||||
e = {
|
||||
message: errorMsg,
|
||||
// Prevent Report Errors button for known errors
|
||||
dialogButtonText: null
|
||||
};
|
||||
Zotero.logError(errorMsg);
|
||||
}
|
||||
else {
|
||||
e = err;
|
||||
Zotero.logError(err);
|
||||
// TEMP
|
||||
if (!errorMsg) {
|
||||
errorMsg = err;
|
||||
}
|
||||
|
||||
Zotero.logError(errorMsg);
|
||||
|
||||
if (!skipSuccessMessage) {
|
||||
if (!errorTitle) {
|
||||
errorTitle = Zotero.getString("general.error");
|
||||
var errorTitle = Zotero.getString("general.error");
|
||||
}
|
||||
Zotero.Utilities.Internal.errorPrompt(errorTitle, e);
|
||||
promptService.alert(window, errorTitle, errorMsg);
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
|
@ -1124,6 +1126,8 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
throw e;
|
||||
}
|
||||
|
||||
this._checkResponse(req);
|
||||
|
||||
// mod_speling can return 300s for 404s with base name matches
|
||||
if (req.status == 404 || req.status == 300) {
|
||||
return false;
|
||||
|
@ -1458,6 +1462,87 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Checks for an invalid SSL certificate and throws a nice error
|
||||
*/
|
||||
_checkResponse: function (req, channel) {
|
||||
if (req.status != 0) return;
|
||||
|
||||
// Check if the error we encountered is really an SSL error
|
||||
// Logic borrowed from https://developer.mozilla.org/en-US/docs/How_to_check_the_security_state_of_an_XMLHTTPRequest_over_SSL
|
||||
// http://mxr.mozilla.org/mozilla-central/source/security/nss/lib/ssl/sslerr.h
|
||||
// http://mxr.mozilla.org/mozilla-central/source/security/nss/lib/util/secerr.h
|
||||
var secErrLimit = Ci.nsINSSErrorsService.NSS_SEC_ERROR_LIMIT - Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
|
||||
var secErr = Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (channel.status & 0xffff);
|
||||
var sslErrLimit = Ci.nsINSSErrorsService.NSS_SSL_ERROR_LIMIT - Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
|
||||
var sslErr = Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (channel.status & 0xffff);
|
||||
if( (secErr < 0 || secErr > secErrLimit) && (sslErr < 0 || sslErr > sslErrLimit) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var secInfo = channel.securityInfo;
|
||||
if (secInfo instanceof Ci.nsITransportSecurityInfo) {
|
||||
secInfo.QueryInterface(Ci.nsITransportSecurityInfo);
|
||||
if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE) == Ci.nsIWebProgressListener.STATE_IS_INSECURE) {
|
||||
var host = 'host';
|
||||
try {
|
||||
host = channel.URI.host;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
}
|
||||
|
||||
var msg = Zotero.getString('sync.storage.error.webdav.sslCertificateError', host);
|
||||
// In Standalone, provide cert_override.txt instructions and a
|
||||
// button to open the Zotero profile directory
|
||||
if (Zotero.isStandalone) {
|
||||
msg += "\n\n" + Zotero.getString('sync.storage.error.webdav.seeCertOverrideDocumentation');
|
||||
var buttonText = Zotero.getString('general.openDocumentation');
|
||||
var func = function () {
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
zp.loadURI("https://www.zotero.org/support/kb/cert_override", { shiftKey: true });
|
||||
};
|
||||
}
|
||||
// In Firefox display a button to load the WebDAV URL
|
||||
else {
|
||||
msg += "\n\n" + Zotero.getString('sync.storage.error.webdav.loadURLForMoreInfo');
|
||||
var buttonText = Zotero.getString('sync.storage.error.webdav.loadURL');
|
||||
var func = function () {
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
zp.loadURI(channel.URI.spec, { shiftKey: true });
|
||||
};
|
||||
}
|
||||
|
||||
var e = new Zotero.Error(
|
||||
msg,
|
||||
0,
|
||||
{
|
||||
dialogButtonText: buttonText,
|
||||
dialogButtonCallback: func
|
||||
}
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
|
||||
var msg = Zotero.getString('sync.storage.error.webdav.sslConnectionError', host) +
|
||||
Zotero.getString('sync.storage.error.webdav.loadURLForMoreInfo');
|
||||
var e = new Zotero.Error(
|
||||
msg,
|
||||
0,
|
||||
{
|
||||
dialogButtonText: Zotero.getString('sync.storage.error.webdav.loadURL'),
|
||||
dialogButtonCallback: function () {
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
zp.loadURI(channel.URI.spec, { shiftKey: true });
|
||||
}
|
||||
}
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_throwFriendlyError: function (method, url, status) {
|
||||
throw new Error(
|
||||
Zotero.getString('sync.storage.error.webdav.requestError', [status, method])
|
||||
|
|
|
@ -820,7 +820,7 @@ Zotero.Sync.APIClient.prototype = {
|
|||
|
||||
_checkBackoff: function (xmlhttp) {
|
||||
var backoff = xmlhttp.getResponseHeader("Backoff");
|
||||
if (backoff && parseInt(backoff) == backoff) {
|
||||
if (backoff && Number.isInteger(backoff)) {
|
||||
// TODO: Update status?
|
||||
this.caller.pause(backoff * 1000);
|
||||
}
|
||||
|
@ -831,7 +831,7 @@ Zotero.Sync.APIClient.prototype = {
|
|||
var retryAfter = xmlhttp.getResponseHeader("Retry-After");
|
||||
var delay;
|
||||
if (!retryAfter) return false;
|
||||
if (parseInt(retryAfter) != retryAfter) {
|
||||
if (!Number.isInteger(retryAfter)) {
|
||||
Zotero.logError(`Invalid Retry-After delay ${retryAfter}`);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,6 @@ Zotero.Sync.Data.Engine = function (options) {
|
|||
}
|
||||
|
||||
this.apiClient = options.apiClient;
|
||||
this.userID = options.userID;
|
||||
this.libraryID = options.libraryID;
|
||||
this.library = Zotero.Libraries.get(options.libraryID);
|
||||
this.libraryTypeID = this.library.libraryTypeID;
|
||||
|
@ -1691,14 +1690,22 @@ Zotero.Sync.Data.Engine.prototype._fullSync = Zotero.Promise.coroutine(function*
|
|||
for (let key in results.versions) {
|
||||
let version = results.versions[key];
|
||||
let obj = objectsClass.getByLibraryAndKey(this.libraryID, key);
|
||||
// If object is already at or above latest version, skip. Local version can be
|
||||
// higher because, as explained in _uploadObjects(), we upload items in batches
|
||||
// and only get the last version to record in the database.
|
||||
// If object already at latest version, skip
|
||||
let localVersion = localVersions[key];
|
||||
if (localVersion && localVersion >= version) {
|
||||
if (localVersion && localVersion === version) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This should never happen
|
||||
if (localVersion > version) {
|
||||
Zotero.logError(`Local version of ${objectType} ${this.libraryID}/${key} `
|
||||
+ `is later than remote! (${localVersion} > ${version})`);
|
||||
// Delete cache version if it's there
|
||||
yield Zotero.Sync.Data.Local.deleteCacheObjectVersions(
|
||||
objectType, this.libraryID, key, localVersion, localVersion
|
||||
);
|
||||
}
|
||||
|
||||
if (obj) {
|
||||
Zotero.debug(`${ObjectType} ${obj.libraryKey} is older than remote version`);
|
||||
}
|
||||
|
@ -1917,12 +1924,6 @@ Zotero.Sync.Data.Engine.prototype._handleUploadError = Zotero.Promise.coroutine(
|
|||
return this.UPLOAD_RESULT_OBJECT_CONFLICT;
|
||||
}
|
||||
}
|
||||
else if (e.name == "ZoteroUploadRestartError") {
|
||||
return this.UPLOAD_RESULT_RESTART;
|
||||
}
|
||||
else if (e.name == "ZoteroUploadCancelError") {
|
||||
return this.UPLOAD_RESULT_CANCEL;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
|
||||
|
@ -1982,48 +1983,6 @@ Zotero.Sync.Data.Engine.prototype._checkObjectUploadError = Zotero.Promise.corou
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (code == 403) {
|
||||
// If we get a 403 for a local group attachment, check the group permissions to confirm
|
||||
// that we no longer have file-editing access and prompt to reset local group files
|
||||
if (objectType == 'item') {
|
||||
let item = Zotero.Items.getByLibraryAndKey(this.libraryID, key);
|
||||
if (this.library.libraryType == 'group' && item.isFileAttachment()) {
|
||||
let reset = false;
|
||||
let groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
|
||||
let info = yield this.apiClient.getGroup(groupID);
|
||||
if (info) {
|
||||
Zotero.debug(info);
|
||||
let { editable, filesEditable } = Zotero.Groups.getPermissionsFromJSON(
|
||||
info.data, this.userID
|
||||
);
|
||||
// If we do still have file-editing access, something else went wrong,
|
||||
// and we should just fail without resetting
|
||||
if (!filesEditable) {
|
||||
let index = Zotero.Sync.Storage.Utilities.showFileWriteAccessLostPrompt(
|
||||
null, this.library
|
||||
);
|
||||
|
||||
let e = new Error(message);
|
||||
if (index === 0) {
|
||||
let group = Zotero.Groups.get(groupID);
|
||||
group.filesEditable = false;
|
||||
yield group.saveTx();
|
||||
|
||||
yield Zotero.Sync.Data.Local.resetUnsyncedLibraryFiles(this.libraryID);
|
||||
e.name = "ZoteroUploadRestartError";
|
||||
}
|
||||
else {
|
||||
e.name = "ZoteroUploadCancelError";
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Zotero.logError("Couldn't get metadata for group " + groupID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// This shouldn't happen, because the upload request includes a library version and should
|
||||
// prevent an outdated upload before the object version is checked. If it does, we need to
|
||||
// do a full sync. This error is checked in handleUploadError().
|
||||
|
|
|
@ -239,8 +239,6 @@ Zotero.Sync.Data.Local = {
|
|||
|
||||
|
||||
_libraryHasUnsyncedFiles: Zotero.Promise.coroutine(function* (libraryID) {
|
||||
// TODO: Check for modified file attachment items, which also can't be uploaded
|
||||
// (and which are corrected by resetUnsyncedLibraryFiles())
|
||||
yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID);
|
||||
return !!(yield Zotero.Sync.Storage.Local.getFilesToUpload(libraryID)).length;
|
||||
}),
|
||||
|
@ -255,9 +253,42 @@ Zotero.Sync.Data.Local = {
|
|||
}
|
||||
|
||||
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(libraryID)) {
|
||||
let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
|
||||
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
|
||||
|
||||
// New/modified objects
|
||||
let ids = yield this.getUnsynced(objectType, libraryID);
|
||||
yield this._resetObjects(libraryID, objectType, ids);
|
||||
let ids = yield Zotero.Sync.Data.Local.getUnsynced(objectType, libraryID);
|
||||
let keys = ids.map(id => objectsClass.getLibraryAndKeyFromID(id).key);
|
||||
let cacheVersions = yield this.getLatestCacheObjectVersions(objectType, libraryID, keys);
|
||||
let toDelete = [];
|
||||
for (let key of keys) {
|
||||
let obj = objectsClass.getByLibraryAndKey(libraryID, key);
|
||||
|
||||
// If object is in cache, overwrite with pristine data
|
||||
if (cacheVersions[key]) {
|
||||
let json = yield this.getCacheObject(objectType, libraryID, key, cacheVersions[key]);
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
yield this._saveObjectFromJSON(obj, json, {});
|
||||
}.bind(this));
|
||||
}
|
||||
// Otherwise, erase
|
||||
else {
|
||||
toDelete.push(objectsClass.getIDFromLibraryAndKey(libraryID, key));
|
||||
}
|
||||
}
|
||||
if (toDelete.length) {
|
||||
yield objectsClass.erase(
|
||||
toDelete,
|
||||
{
|
||||
skipEditCheck: true,
|
||||
skipDeleteLog: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Deleted objects
|
||||
keys = yield Zotero.Sync.Data.Local.getDeleted(objectType, libraryID);
|
||||
yield this.removeObjectsFromDeleteLog(objectType, libraryID, keys);
|
||||
}
|
||||
|
||||
// Mark library for full sync
|
||||
|
@ -274,62 +305,13 @@ Zotero.Sync.Data.Local = {
|
|||
*
|
||||
* _libraryHasUnsyncedFiles(), which checks for updated files, must be called first.
|
||||
*/
|
||||
resetUnsyncedLibraryFiles: async function (libraryID) {
|
||||
// Reset unsynced file attachments
|
||||
var itemIDs = await Zotero.Sync.Data.Local.getUnsynced('item', libraryID);
|
||||
var toReset = [];
|
||||
resetUnsyncedLibraryFiles: Zotero.Promise.coroutine(function* (libraryID) {
|
||||
var itemIDs = yield Zotero.Sync.Storage.Local.getFilesToUpload(libraryID);
|
||||
for (let itemID of itemIDs) {
|
||||
let item = Zotero.Items.get(itemID);
|
||||
if (item.isFileAttachment()) {
|
||||
toReset.push(item.id);
|
||||
}
|
||||
yield item.deleteAttachmentFile();
|
||||
}
|
||||
await this._resetObjects(libraryID, 'item', toReset);
|
||||
|
||||
// Delete unsynced files
|
||||
var itemIDs = await Zotero.Sync.Storage.Local.getFilesToUpload(libraryID);
|
||||
for (let itemID of itemIDs) {
|
||||
let item = Zotero.Items.get(itemID);
|
||||
await item.deleteAttachmentFile();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_resetObjects: async function (libraryID, objectType, ids) {
|
||||
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
|
||||
|
||||
var keys = ids.map(id => objectsClass.getLibraryAndKeyFromID(id).key);
|
||||
var cacheVersions = await this.getLatestCacheObjectVersions(objectType, libraryID, keys);
|
||||
var toDelete = [];
|
||||
for (let key of keys) {
|
||||
let obj = objectsClass.getByLibraryAndKey(libraryID, key);
|
||||
|
||||
// If object is in cache, overwrite with pristine data
|
||||
if (cacheVersions[key]) {
|
||||
let json = await this.getCacheObject(objectType, libraryID, key, cacheVersions[key]);
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
await this._saveObjectFromJSON(obj, json, {});
|
||||
}.bind(this));
|
||||
}
|
||||
// Otherwise, erase
|
||||
else {
|
||||
toDelete.push(objectsClass.getIDFromLibraryAndKey(libraryID, key));
|
||||
}
|
||||
}
|
||||
if (toDelete.length) {
|
||||
await objectsClass.erase(
|
||||
toDelete,
|
||||
{
|
||||
skipEditCheck: true,
|
||||
skipDeleteLog: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Deleted objects
|
||||
keys = await Zotero.Sync.Data.Local.getDeleted(objectType, libraryID);
|
||||
await this.removeObjectsFromDeleteLog(objectType, libraryID, keys);
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
getSkippedLibraries: function () {
|
||||
|
@ -794,7 +776,6 @@ Zotero.Sync.Data.Local = {
|
|||
|
||||
// Skip objects with unmet dependencies
|
||||
if (objectType == 'item' || objectType == 'collection') {
|
||||
// Missing parent collection or item
|
||||
let parentProp = 'parent' + objectType[0].toUpperCase() + objectType.substr(1);
|
||||
let parentKey = jsonData[parentProp];
|
||||
if (parentKey) {
|
||||
|
@ -816,37 +797,17 @@ Zotero.Sync.Data.Local = {
|
|||
}
|
||||
}
|
||||
|
||||
// Missing collection -- this could happen if the collection was deleted
|
||||
// locally and an item in it was modified remotely
|
||||
if (objectType == 'item' && jsonData.collections) {
|
||||
let error;
|
||||
for (let key of jsonData.collections) {
|
||||
let collection = Zotero.Collections.getByLibraryAndKey(libraryID, key);
|
||||
if (!collection) {
|
||||
error = new Error(`Collection ${libraryID}/${key} not found `
|
||||
+ `-- skipping item`);
|
||||
error.name = "ZoteroMissingObjectError";
|
||||
Zotero.debug(error.message);
|
||||
results.push({
|
||||
key: objectKey,
|
||||
processed: false,
|
||||
error,
|
||||
retry: false
|
||||
});
|
||||
|
||||
// If the collection is in the delete log, the deletion will upload
|
||||
// after downloads are done. Otherwise, we somehow missed
|
||||
// downloading it and should add it to the queue to try again.
|
||||
if (!(yield this.getDateDeleted('collection', libraryID, key))) {
|
||||
yield this.addObjectsToSyncQueue('collection', libraryID, [key]);
|
||||
}
|
||||
break;
|
||||
/*if (objectType == 'item') {
|
||||
for (let j = 0; j < jsonData.collections.length; i++) {
|
||||
let parentKey = jsonData.collections[j];
|
||||
let parentCollection = Zotero.Collections.getByLibraryAndKey(
|
||||
libraryID, parentKey, { noCache: true }
|
||||
);
|
||||
if (!parentCollection) {
|
||||
// ???
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// Errors have to be thrown in order to roll back the transaction, so catch those here
|
||||
|
@ -1095,12 +1056,6 @@ Zotero.Sync.Data.Local = {
|
|||
Zotero.debug(json, 1);
|
||||
throw new Error("Missing 'version' property in JSON");
|
||||
}
|
||||
if (json.version === 0) {
|
||||
Zotero.debug(json, 1);
|
||||
// TODO: Fix tests so this doesn't happen
|
||||
Zotero.warn("'version' cannot be 0 in cache JSON");
|
||||
//throw new Error("'version' cannot be 0 in cache JSON");
|
||||
}
|
||||
// If direct data object passed, wrap in fake response object
|
||||
return json.data === undefined ? {
|
||||
key: json.key,
|
||||
|
|
|
@ -69,8 +69,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
|
||||
var _enabled = false;
|
||||
var _autoSyncTimer;
|
||||
var _delaySyncUntil;
|
||||
var _delayPromises = [];
|
||||
var _firstInSession = true;
|
||||
var _syncInProgress = false;
|
||||
var _stopping = false;
|
||||
|
@ -129,6 +127,9 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
try {
|
||||
yield Zotero.Notifier.trigger('start', 'sync', []);
|
||||
|
||||
// Purge deleted objects so they don't cause sync errors (e.g., long tags)
|
||||
yield Zotero.purgeDataObjects(true);
|
||||
|
||||
let apiKey = yield _getAPIKey();
|
||||
if (!apiKey) {
|
||||
throw new Zotero.Error("API key not set", Zotero.Error.ERROR_API_KEY_NOT_SET);
|
||||
|
@ -141,45 +142,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
|
||||
this.updateIcons('animate');
|
||||
|
||||
// If a delay is set (e.g., from the connector target selector), wait to sync
|
||||
while (_delaySyncUntil && new Date() < _delaySyncUntil) {
|
||||
this.setSyncStatus(Zotero.getString('sync.status.waiting'));
|
||||
let delay = _delaySyncUntil - new Date();
|
||||
Zotero.debug(`Waiting ${delay} ms to sync`);
|
||||
yield Zotero.Promise.delay(delay);
|
||||
}
|
||||
|
||||
// If paused, wait until we're done
|
||||
while (true) {
|
||||
if (_delayPromises.some(p => p.isPending())) {
|
||||
this.setSyncStatus(Zotero.getString('sync.status.waiting'));
|
||||
Zotero.debug("Syncing is paused -- waiting to sync");
|
||||
yield Zotero.Promise.all(_delayPromises);
|
||||
// If more were added, continue
|
||||
if (_delayPromises.some(p => p.isPending())) {
|
||||
continue;
|
||||
}
|
||||
_delayPromises = [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// purgeDataObjects() starts a transaction, so if there's an active one then show a
|
||||
// nice message and wait until there's not. Another transaction could still start
|
||||
// before purgeDataObjects() and result in a wait timeout, but this should reduce the
|
||||
// frequency of that.
|
||||
while (Zotero.DB.inTransaction()) {
|
||||
this.setSyncStatus(Zotero.getString('sync.status.waiting'));
|
||||
Zotero.debug("Transaction in progress -- waiting to sync");
|
||||
yield Zotero.DB.waitForTransaction('sync');
|
||||
_stopCheck();
|
||||
}
|
||||
|
||||
this.setSyncStatus(Zotero.getString('sync.status.preparing'));
|
||||
|
||||
// Purge deleted objects so they don't cause sync errors (e.g., long tags)
|
||||
yield Zotero.purgeDataObjects(true);
|
||||
|
||||
let client = this.getAPIClient({ apiKey });
|
||||
let keyInfo = yield this.checkAccess(client, options);
|
||||
|
||||
|
@ -200,7 +162,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
}
|
||||
|
||||
let engineOptions = {
|
||||
userID: keyInfo.userID,
|
||||
apiClient: client,
|
||||
caller: this.caller,
|
||||
setStatus: this.setSyncStatus.bind(this),
|
||||
|
@ -909,20 +870,11 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
|
||||
// Implements nsITimerCallback
|
||||
var callback = {
|
||||
notify: async function (timer) {
|
||||
notify: function (timer) {
|
||||
if (!_getAPIKey()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a delay is set (e.g., from the connector target selector), wait to sync.
|
||||
// We do this in sync() too for manual syncs, but no need to start spinning if
|
||||
// it's just an auto-sync.
|
||||
while (_delaySyncUntil && new Date() < _delaySyncUntil) {
|
||||
let delay = _delaySyncUntil - new Date();
|
||||
Zotero.debug(`Waiting ${delay} ms to start auto-sync`);
|
||||
await Zotero.Promise.delay(delay);
|
||||
}
|
||||
|
||||
if (Zotero.locked) {
|
||||
Zotero.debug('Zotero is locked -- skipping auto-sync', 4);
|
||||
return;
|
||||
|
@ -969,26 +921,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
}
|
||||
|
||||
|
||||
this.delaySync = function (ms) {
|
||||
_delaySyncUntil = new Date(Date.now() + ms);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delay syncs until the returned function is called
|
||||
*
|
||||
* @return {Function} - Resolve function
|
||||
*/
|
||||
this.delayIndefinite = function () {
|
||||
var resolve;
|
||||
var promise = new Zotero.Promise(function () {
|
||||
resolve = arguments[0];
|
||||
});
|
||||
_delayPromises.push(promise);
|
||||
return resolve;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Trigger updating of the main sync icon, the sync error icon, and
|
||||
* library-specific sync error icons across all windows
|
||||
|
@ -1114,133 +1046,95 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
else if (e.name && e.name == 'ZoteroObjectUploadError') {
|
||||
let { code, data, objectType, object } = e;
|
||||
|
||||
if (code == 413) {
|
||||
// Collection name too long
|
||||
if (objectType == 'collection' && data && data.value) {
|
||||
e.message = Zotero.getString('sync.error.collectionTooLong', [data.value]);
|
||||
// Tag too long
|
||||
if (code == 413 && objectType == 'item') {
|
||||
if (data && data.tag !== undefined) {
|
||||
// Show long tag fixer and handle result
|
||||
e.dialogButtonText = Zotero.getString('general.fix');
|
||||
e.dialogButtonCallback = Zotero.Promise.coroutine(function* () {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
// Open long tag fixer for every long tag in every editable library we're syncing
|
||||
var editableLibraries = options.libraries
|
||||
.filter(x => Zotero.Libraries.get(x).editable);
|
||||
for (let libraryID of editableLibraries) {
|
||||
let oldTagIDs = yield Zotero.Tags.getLongTagsInLibrary(libraryID);
|
||||
for (let oldTagID of oldTagIDs) {
|
||||
let oldTag = Zotero.Tags.getName(oldTagID);
|
||||
let dataOut = { result: null };
|
||||
lastWin.openDialog(
|
||||
'chrome://zotero/content/longTagFixer.xul',
|
||||
'',
|
||||
'chrome,modal,centerscreen',
|
||||
oldTag,
|
||||
dataOut
|
||||
);
|
||||
// If dialog was cancelled, stop
|
||||
if (!dataOut.result) {
|
||||
return;
|
||||
}
|
||||
switch (dataOut.result.op) {
|
||||
case 'split':
|
||||
for (let libraryID of editableLibraries) {
|
||||
let itemIDs = yield Zotero.Tags.getTagItems(libraryID, oldTagID);
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
for (let itemID of itemIDs) {
|
||||
let item = yield Zotero.Items.getAsync(itemID);
|
||||
for (let tag of dataOut.result.tags) {
|
||||
item.addTag(tag);
|
||||
}
|
||||
item.removeTag(oldTag);
|
||||
yield item.save();
|
||||
}
|
||||
yield Zotero.Tags.purge(oldTagID);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
for (let libraryID of editableLibraries) {
|
||||
let itemIDs = yield Zotero.Tags.getTagItems(libraryID, oldTagID);
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
for (let itemID of itemIDs) {
|
||||
let item = yield Zotero.Items.getAsync(itemID);
|
||||
item.replaceTag(oldTag, dataOut.result.tag);
|
||||
yield item.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
for (let libraryID of editableLibraries) {
|
||||
yield Zotero.Tags.removeFromLibrary(libraryID, oldTagID);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.restartSync = true;
|
||||
});
|
||||
}
|
||||
// Note too long
|
||||
else if (object.isNote() || object.isAttachment()) {
|
||||
// Throw an error that adds a button for selecting the item to the sync error dialog
|
||||
if (e.message.includes('<img src="data:image')) {
|
||||
// TODO: Localize
|
||||
e.message = "Notes with embedded images cannot currently be synced to "
|
||||
+ `${ZOTERO_CONFIG.DOMAIN_NAME}.`
|
||||
}
|
||||
|
||||
e.dialogButtonText = Zotero.getString('pane.collections.showCollectionInLibrary');
|
||||
e.dialogButtonText = Zotero.getString('pane.items.showItemInLibrary');
|
||||
e.dialogButtonCallback = () => {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.collectionsView.selectCollection(object.id);
|
||||
win.ZoteroPane.selectItem(object.id);
|
||||
};
|
||||
}
|
||||
else if (objectType == 'item') {
|
||||
// Tag too long
|
||||
if (data && data.tag !== undefined) {
|
||||
// Show long tag fixer and handle result
|
||||
e.dialogButtonText = Zotero.getString('general.fix');
|
||||
e.dialogButtonCallback = Zotero.Promise.coroutine(function* () {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
// Open long tag fixer for every long tag in every editable library we're syncing
|
||||
var editableLibraries = options.libraries
|
||||
.filter(x => Zotero.Libraries.get(x).editable);
|
||||
for (let libraryID of editableLibraries) {
|
||||
let oldTagIDs = yield Zotero.Tags.getLongTagsInLibrary(libraryID);
|
||||
for (let oldTagID of oldTagIDs) {
|
||||
let oldTag = Zotero.Tags.getName(oldTagID);
|
||||
let dataOut = { result: null };
|
||||
lastWin.openDialog(
|
||||
'chrome://zotero/content/longTagFixer.xul',
|
||||
'',
|
||||
'chrome,modal,centerscreen',
|
||||
oldTag,
|
||||
dataOut
|
||||
);
|
||||
// If dialog was cancelled, stop
|
||||
if (!dataOut.result) {
|
||||
return;
|
||||
}
|
||||
switch (dataOut.result.op) {
|
||||
case 'split':
|
||||
for (let libraryID of editableLibraries) {
|
||||
let itemIDs = yield Zotero.Tags.getTagItems(libraryID, oldTagID);
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
for (let itemID of itemIDs) {
|
||||
let item = yield Zotero.Items.getAsync(itemID);
|
||||
for (let tag of dataOut.result.tags) {
|
||||
item.addTag(tag);
|
||||
}
|
||||
item.removeTag(oldTag);
|
||||
yield item.save();
|
||||
}
|
||||
yield Zotero.Tags.purge(oldTagID);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
for (let libraryID of editableLibraries) {
|
||||
let itemIDs = yield Zotero.Tags.getTagItems(libraryID, oldTagID);
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
for (let itemID of itemIDs) {
|
||||
let item = yield Zotero.Items.getAsync(itemID);
|
||||
item.replaceTag(oldTag, dataOut.result.tag);
|
||||
yield item.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
for (let libraryID of editableLibraries) {
|
||||
yield Zotero.Tags.removeFromLibrary(libraryID, oldTagID);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.restartSync = true;
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Note too long
|
||||
if (object.isNote() || object.isAttachment()) {
|
||||
// Throw an error that adds a button for selecting the item to the sync error dialog
|
||||
if (e.message.includes('<img src="data:image')) {
|
||||
e.message = Zotero.getString('sync.error.noteEmbeddedImage');
|
||||
}
|
||||
else if (e.message.match(/^Note '.*' too long for item/)) {
|
||||
e.message = Zotero.getString(
|
||||
'sync.error.noteTooLong',
|
||||
Zotero.Utilities.ellipsize(object.getNoteTitle(), 40)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Field or creator too long
|
||||
else if (data && data.field) {
|
||||
e.message = (data.field == 'creator'
|
||||
? Zotero.getString(
|
||||
'sync.error.creatorTooLong',
|
||||
[data.value]
|
||||
)
|
||||
: Zotero.getString(
|
||||
'sync.error.fieldTooLong',
|
||||
[data.field, data.value]
|
||||
))
|
||||
+ '\n\n'
|
||||
+ Zotero.getString(
|
||||
'sync.error.reportSiteIssuesToForums',
|
||||
Zotero.clientName
|
||||
);
|
||||
}
|
||||
|
||||
// Include "Show Item in Library" button
|
||||
e.dialogButtonText = Zotero.getString('pane.items.showItemInLibrary');
|
||||
e.dialogButtonCallback = () => {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.selectItem(object.id);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If not a background sync, show dialog immediately
|
||||
if (!options.background && e.dialogButtonCallback) {
|
||||
|
|
|
@ -1292,15 +1292,12 @@ Zotero.Translate.Base.prototype = {
|
|||
|
||||
this._currentState = "translate";
|
||||
|
||||
this._sessionID = options.sessionID;
|
||||
this._libraryID = options.libraryID;
|
||||
if (options.collections && !Array.isArray(options.collections)) {
|
||||
throw new Error("'collections' must be an array");
|
||||
}
|
||||
this._collections = options.collections;
|
||||
this._saveAttachments = options.saveAttachments === undefined || options.saveAttachments;
|
||||
this._forceTagType = options.forceTagType;
|
||||
this._saveOptions = options.saveOptions;
|
||||
|
||||
this._savingAttachments = [];
|
||||
this._savingItems = 0;
|
||||
|
@ -1343,15 +1340,11 @@ Zotero.Translate.Base.prototype = {
|
|||
Zotero.Promise.resolve(this.translator[0])
|
||||
.then(function (translator) {
|
||||
this.translator[0] = translator;
|
||||
this._loadTranslator(translator)
|
||||
.then(() => this._translateTranslatorLoaded())
|
||||
.catch(e => deferred.reject(e));
|
||||
this._loadTranslator(translator).then(() => this._translateTranslatorLoaded());
|
||||
}.bind(this));
|
||||
}
|
||||
else {
|
||||
this._loadTranslator(this.translator[0])
|
||||
.then(() => this._translateTranslatorLoaded())
|
||||
.catch(e => deferred.reject(e));
|
||||
this._loadTranslator(this.translator[0]).then(() => this._translateTranslatorLoaded());
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
|
@ -1886,17 +1879,14 @@ Zotero.Translate.Base.prototype = {
|
|||
*/
|
||||
_attr: function (selector, attr, index) {
|
||||
if (typeof arguments[0] == 'string') {
|
||||
var docOrElem = this.document;
|
||||
var doc = this.document;
|
||||
}
|
||||
// Document or element passed as first argument
|
||||
// Support legacy polyfill signature
|
||||
else {
|
||||
// TODO: Warn if Document rather than Element is passed once we drop 4.0 translator
|
||||
// support
|
||||
[docOrElem, selector, attr, index] = arguments;
|
||||
this._debug("WARNING: attr() no longer requires a document as the first argument");
|
||||
[doc, selector, attr, index] = arguments;
|
||||
}
|
||||
var elem = index
|
||||
? docOrElem.querySelectorAll(selector).item(index)
|
||||
: docOrElem.querySelector(selector);
|
||||
var elem = index ? doc.querySelectorAll(selector).item(index) : doc.querySelector(selector);
|
||||
return elem ? elem.getAttribute(attr) : null;
|
||||
},
|
||||
|
||||
|
@ -1905,17 +1895,14 @@ Zotero.Translate.Base.prototype = {
|
|||
*/
|
||||
_text: function (selector, index) {
|
||||
if (typeof arguments[0] == 'string') {
|
||||
var docOrElem = this.document;
|
||||
var doc = this.document;
|
||||
}
|
||||
// Document or element passed as first argument
|
||||
// Support legacy polyfill signature
|
||||
else {
|
||||
// TODO: Warn if Document rather than Element is passed once we drop 4.0 translator
|
||||
// support
|
||||
[docOrElem, selector, index] = arguments;
|
||||
this._debug("WARNING: text() no longer requires a document as the first argument");
|
||||
[doc, selector, attr, index] = arguments;
|
||||
}
|
||||
var elem = index
|
||||
? docOrElem.querySelectorAll(selector).item(index)
|
||||
: docOrElem.querySelector(selector);
|
||||
var elem = index ? doc.querySelectorAll(selector).item(index) : doc.querySelector(selector);
|
||||
return elem ? elem.textContent : null;
|
||||
},
|
||||
|
||||
|
@ -1941,21 +1928,29 @@ Zotero.Translate.Base.prototype = {
|
|||
* Generates a string from an exception
|
||||
* @param {String|Exception} error
|
||||
*/
|
||||
_generateErrorString: function (error) {
|
||||
var errorString = error;
|
||||
if (error.stack && error) {
|
||||
errorString += "\n\n" + error.stack;
|
||||
"_generateErrorString":function(error) {
|
||||
var errorString = "";
|
||||
if(typeof(error) == "string") {
|
||||
errorString = "\nthrown exception => "+error;
|
||||
} else {
|
||||
var haveStack = false;
|
||||
for(var i in error) {
|
||||
if(typeof(error[i]) != "object") {
|
||||
if(i === "stack") haveStack = true;
|
||||
errorString += "\n"+i+' => '+error[i];
|
||||
}
|
||||
}
|
||||
errorString += "\nstring => "+error.toString();
|
||||
if(!haveStack && error.stack) {
|
||||
// In case the stack is not enumerable
|
||||
errorString += "\nstack => "+error.stack.toString();
|
||||
}
|
||||
}
|
||||
if (this.path) {
|
||||
errorString += `\nurl => ${this.path}`;
|
||||
}
|
||||
if (Zotero.Prefs.get("downloadAssociatedFiles")) {
|
||||
errorString += "\ndownloadAssociatedFiles => true";
|
||||
}
|
||||
if (Zotero.Prefs.get("automaticSnapshots")) {
|
||||
errorString += "\nautomaticSnapshots => true";
|
||||
}
|
||||
return errorString;
|
||||
|
||||
errorString += "\nurl => "+this.path
|
||||
+ "\ndownloadAssociatedFiles => "+Zotero.Prefs.get("downloadAssociatedFiles")
|
||||
+ "\nautomaticSnapshots => "+Zotero.Prefs.get("automaticSnapshots");
|
||||
return errorString.substr(1);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2087,7 +2082,6 @@ Zotero.Translate.Web.prototype._prepareTranslation = Zotero.Promise.method(funct
|
|||
collections: this._collections,
|
||||
attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")],
|
||||
forceTagType: 1,
|
||||
sessionID: this._sessionID,
|
||||
cookieSandbox: this._cookieSandbox,
|
||||
proxy: this._proxy,
|
||||
baseURI: this.location
|
||||
|
@ -2379,15 +2373,11 @@ Zotero.Translate.Import.prototype._prepareTranslation = Zotero.Promise.method(fu
|
|||
this._itemSaver = new Zotero.Translate.ItemSaver({
|
||||
libraryID: this._libraryID,
|
||||
collections: this._collections,
|
||||
forceTagType: this._forceTagType,
|
||||
attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
|
||||
baseURI,
|
||||
saveOptions: Object.assign(
|
||||
{
|
||||
skipSelect: true
|
||||
},
|
||||
this._saveOptions || {}
|
||||
)
|
||||
saveOptions: {
|
||||
skipSelect: true
|
||||
}
|
||||
});
|
||||
this.newItems = [];
|
||||
this.newCollections = [];
|
||||
|
@ -2595,7 +2585,6 @@ Zotero.Translate.Search.prototype = new Zotero.Translate.Base();
|
|||
Zotero.Translate.Search.prototype.type = "search";
|
||||
Zotero.Translate.Search.prototype._entryFunctionSuffix = "Search";
|
||||
Zotero.Translate.Search.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Search);
|
||||
Zotero.Translate.Search.prototype.ERROR_NO_RESULTS = "No items returned from any translator";
|
||||
|
||||
/**
|
||||
* @borrows Zotero.Translate.Web#setCookieSandbox
|
||||
|
@ -2636,12 +2625,6 @@ Zotero.Translate.Search.prototype.setIdentifier = function (identifier) {
|
|||
contextObject: "rft_id=info:pmid/" + identifier.PMID
|
||||
};
|
||||
}
|
||||
else if (identifier.arXiv) {
|
||||
search = {
|
||||
itemType: "journalArticle",
|
||||
arXiv: identifier.arXiv
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new Error("Unrecognized identifier");
|
||||
}
|
||||
|
@ -2679,8 +2662,7 @@ Zotero.Translate.Search.prototype.complete = function(returnValue, error) {
|
|||
&& !this._savingItems
|
||||
//length is 0 only when translate was called without translators
|
||||
&& this.translator.length) {
|
||||
Zotero.debug("Translate: Could not find a result using " + this.translator[0].label
|
||||
+ (this.translator.length > 1 ? " -- trying next translator" : ""), 3);
|
||||
Zotero.debug("Translate: Could not find a result using "+this.translator[0].label, 3);
|
||||
if(error) Zotero.debug(this._generateErrorString(error), 3);
|
||||
if(this.translator.length > 1) {
|
||||
this.translator.shift();
|
||||
|
@ -2691,8 +2673,7 @@ Zotero.Translate.Search.prototype.complete = function(returnValue, error) {
|
|||
});
|
||||
return;
|
||||
} else {
|
||||
Zotero.debug("No more translators to try");
|
||||
error = this.ERROR_NO_RESULTS;
|
||||
error = "No items returned from any translator";
|
||||
returnValue = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -368,16 +368,7 @@ Zotero.Translate.DOMWrapper = new function() {
|
|||
*/
|
||||
Zotero.Translate.SandboxManager = function(sandboxLocation) {
|
||||
// sandboxLocation = Components.classes["@mozilla.org/systemprincipal;1"].createInstance(Components.interfaces.nsIPrincipal);
|
||||
var sandbox = this.sandbox = new Components.utils.Sandbox(
|
||||
sandboxLocation,
|
||||
{
|
||||
wantComponents: false,
|
||||
wantGlobalProperties: [
|
||||
'atob',
|
||||
'XMLHttpRequest'
|
||||
]
|
||||
}
|
||||
);
|
||||
var sandbox = this.sandbox = new Components.utils.Sandbox(sandboxLocation, {wantComponents:false, wantGlobalProperties:["XMLHttpRequest"]});
|
||||
this.sandbox.Zotero = {};
|
||||
|
||||
// import functions missing from global scope into Fx sandbox
|
||||
|
|
|
@ -54,7 +54,6 @@ Zotero.Translate.ItemSaver = function(options) {
|
|||
this.attachmentMode = Zotero.Libraries.get(this._libraryID).filesEditable ? options.attachmentMode :
|
||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
|
||||
this._forceTagType = options.forceTagType;
|
||||
this._referrer = options.referrer;
|
||||
this._cookieSandbox = options.cookieSandbox;
|
||||
this._proxy = options.proxy;
|
||||
|
||||
|
@ -142,7 +141,6 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
for (let i=0; i<specialFields.notes.length; i++) {
|
||||
yield this._saveNote(specialFields.notes[i], myID);
|
||||
}
|
||||
item.notes = specialFields.notes;
|
||||
}
|
||||
|
||||
// handle attachments
|
||||
|
@ -636,7 +634,6 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
title,
|
||||
fileBaseName,
|
||||
contentType: mimeType,
|
||||
referrer: this._referrer,
|
||||
cookieSandbox: this._cookieSandbox,
|
||||
collections: !parentItemID ? this._collections : undefined
|
||||
});
|
||||
|
|
|
@ -68,7 +68,7 @@ Zotero.Translators = new function() {
|
|||
_cache = {"import":[], "export":[], "web":[], "webWithTargetAll":[], "search":[]};
|
||||
_translators = {};
|
||||
|
||||
var sql = "SELECT rowid, fileName, metadataJSON, lastModifiedTime FROM translatorCache";
|
||||
var sql = "SELECT fileName, metadataJSON, lastModifiedTime FROM translatorCache";
|
||||
var dbCacheResults = yield Zotero.DB.queryAsync(sql);
|
||||
var dbCache = {};
|
||||
for (let i = 0; i < dbCacheResults.length; i++) {
|
||||
|
@ -116,22 +116,9 @@ Zotero.Translators = new function() {
|
|||
|
||||
// Get JSON from cache if possible
|
||||
if (memCacheJSON || dbCacheEntry) {
|
||||
try {
|
||||
var translator = Zotero.Translators.load(
|
||||
memCacheJSON || dbCacheEntry.metadataJSON, path
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(memCacheJSON || dbCacheEntry.metadataJSON, 1);
|
||||
|
||||
// If JSON is invalid, clear from cache
|
||||
yield Zotero.DB.queryAsync(
|
||||
"DELETE FROM translatorCache WHERE fileName=?",
|
||||
fileName
|
||||
);
|
||||
continue;
|
||||
}
|
||||
var translator = Zotero.Translators.load(
|
||||
memCacheJSON || dbCacheEntry.metadataJSON, path
|
||||
);
|
||||
}
|
||||
// Otherwise, load from file
|
||||
else {
|
||||
|
@ -211,8 +198,7 @@ Zotero.Translators = new function() {
|
|||
for (let fileName in dbCache) {
|
||||
if (!filesInCache[fileName]) {
|
||||
yield Zotero.DB.queryAsync(
|
||||
"DELETE FROM translatorCache WHERE rowid=?",
|
||||
dbCache[fileName].rowid
|
||||
"DELETE FROM translatorCache WHERE fileName = ?", fileName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user