Introduce PDF reader and note editor
This commit is contained in:
parent
c3ff6eb66e
commit
2543a695e8
36 changed files with 2512 additions and 419 deletions
12
.gitmodules
vendored
12
.gitmodules
vendored
|
@ -29,3 +29,15 @@
|
||||||
[submodule "resource/SingleFile"]
|
[submodule "resource/SingleFile"]
|
||||||
path = resource/SingleFile
|
path = resource/SingleFile
|
||||||
url = https://github.com/gildas-lormeau/SingleFile.git
|
url = https://github.com/gildas-lormeau/SingleFile.git
|
||||||
|
[submodule "pdf-reader"]
|
||||||
|
path = pdf-reader
|
||||||
|
url = https://github.com/zotero/pdf-reader.git
|
||||||
|
branch = master
|
||||||
|
[submodule "pdf-worker"]
|
||||||
|
path = pdf-worker
|
||||||
|
url = https://github.com/zotero/pdf-worker.git
|
||||||
|
branch = master
|
||||||
|
[submodule "zotero-note-editor"]
|
||||||
|
path = zotero-note-editor
|
||||||
|
url = https://github.com/zotero/zotero-note-editor.git
|
||||||
|
branch = master
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<!--
|
<!--
|
||||||
***** BEGIN LICENSE BLOCK *****
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
Copyright © 2009 Center for History and New Media
|
Copyright © 2009 Center for History and New Media
|
||||||
George Mason University, Fairfax, Virginia, USA
|
George Mason University, Fairfax, Virginia, USA
|
||||||
http://zotero.org
|
http://zotero.org
|
||||||
|
|
||||||
This file is part of Zotero.
|
This file is part of Zotero.
|
||||||
|
|
||||||
Zotero is free software: you can redistribute it and/or modify
|
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
|
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
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
Zotero is distributed in the hope that it will be useful,
|
Zotero is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU Affero General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
|
||||||
<binding id="note-editor">
|
<binding id="note-editor">
|
||||||
<resources>
|
<resources>
|
||||||
<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
|
<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
|
||||||
<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
|
<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<implementation>
|
<implementation>
|
||||||
<!--
|
<!--
|
||||||
Public properties
|
Public properties
|
||||||
|
@ -43,57 +43,121 @@
|
||||||
<field name="displayTags">false</field>
|
<field name="displayTags">false</field>
|
||||||
<field name="displayRelated">false</field>
|
<field name="displayRelated">false</field>
|
||||||
<field name="displayButton">false</field>
|
<field name="displayButton">false</field>
|
||||||
|
|
||||||
<field name="buttonCaption"/>
|
<field name="buttonCaption"/>
|
||||||
<field name="parentClickHandler"/>
|
<field name="parentClickHandler"/>
|
||||||
<field name="keyDownHandler"/>
|
<field name="keyDownHandler"/>
|
||||||
<field name="commandHandler"/>
|
<field name="commandHandler"/>
|
||||||
<field name="clickHandler"/>
|
<field name="clickHandler"/>
|
||||||
|
<field name="navigateHandler"/>
|
||||||
|
|
||||||
|
<constructor><![CDATA[
|
||||||
|
this._noteEditorID = Zotero.Utilities.randomString();
|
||||||
|
this._iframe = document.getAnonymousElementByAttribute(this, "anonid", "rt-view1");
|
||||||
|
this._iframe.addEventListener('DOMContentLoaded', (e) => {
|
||||||
|
this._initialized = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getNoteDataSync = () => {
|
||||||
|
return this._editor.getNoteDataSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initEditor = async (state) => {
|
||||||
|
if (this._editor) {
|
||||||
|
this._editor.uninit();
|
||||||
|
}
|
||||||
|
this._editor = new Zotero.NoteEditor();
|
||||||
|
await this._editor.init({
|
||||||
|
state,
|
||||||
|
item: this._item,
|
||||||
|
window: document.getAnonymousElementByAttribute(this, "anonid", "rt-view1").contentWindow,
|
||||||
|
onNavigate: this._navigateHandler,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notify = async (event, type, ids, extraData) => {
|
||||||
|
// Update citations
|
||||||
|
let uris = [];
|
||||||
|
let items = await Zotero.Items.getAsync(ids);
|
||||||
|
for (let item of items) {
|
||||||
|
let uri = Zotero.URI.getItemURI(item);
|
||||||
|
if (uri) {
|
||||||
|
uris.push(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._editor) {
|
||||||
|
await this._editor.updateCitationsForURIs(uris);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.item) return;
|
||||||
|
// Try to use the state from the item save event
|
||||||
|
let id = this.item.id;
|
||||||
|
if (ids.includes(id)) {
|
||||||
|
let state = extraData && extraData[id] && extraData[id].state;
|
||||||
|
if (state) {
|
||||||
|
if (extraData[id].noteEditorID !== this._editor.instanceID) {
|
||||||
|
this.initEditor(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let curValue = this.item.getNote();
|
||||||
|
if (curValue !== this._lastHtmlValue) {
|
||||||
|
this.initEditor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._lastHtmlValue = this.item.getNote();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._id('links-container').hidden = !(this.displayTags && this.displayRelated);
|
||||||
|
this._id('links-box').refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
|
||||||
|
]]></constructor>
|
||||||
|
|
||||||
|
|
||||||
<!-- Modes are predefined settings groups for particular tasks -->
|
<!-- Modes are predefined settings groups for particular tasks -->
|
||||||
<field name="_mode">"view"</field>
|
<field name="_mode">"view"</field>
|
||||||
<property name="mode" onget="return this._mode;">
|
<property name="mode" onget="return this._mode;">
|
||||||
<setter>
|
<setter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
// Duplicate default property settings here
|
// Duplicate default property settings here
|
||||||
this.editable = false;
|
this.editable = false;
|
||||||
this.saveOnEdit = false;
|
this.saveOnEdit = false;
|
||||||
this.displayTags = false;
|
this.displayTags = false;
|
||||||
this.displayRelated = false;
|
this.displayRelated = false;
|
||||||
this.displayButton = false;
|
this.displayButton = false;
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case 'view':
|
case 'view':
|
||||||
case 'merge':
|
case 'merge':
|
||||||
if (this.noteField) {
|
this.editable = false;
|
||||||
this.noteField.onInit(ed => ed.setMode('readonly'));
|
break;
|
||||||
}
|
|
||||||
break;
|
case 'edit':
|
||||||
|
this.editable = true;
|
||||||
case 'edit':
|
this.saveOnEdit = true;
|
||||||
if (this.noteField) {
|
this.parentClickHandler = this.selectParent;
|
||||||
this.noteField.onInit(ed => ed.setMode('design'));
|
this.keyDownHandler = this.handleKeyDown;
|
||||||
}
|
this.commandHandler = this.save;
|
||||||
this.editable = true;
|
this.displayTags = true;
|
||||||
this.saveOnEdit = true;
|
this.displayRelated = true;
|
||||||
this.parentClickHandler = this.selectParent;
|
break;
|
||||||
this.keyDownHandler = this.handleKeyDown;
|
|
||||||
this.commandHandler = this.save;
|
default:
|
||||||
this.displayTags = true;
|
throw ("Invalid mode '" + val + "' in noteeditor.xml");
|
||||||
this.displayRelated = true;
|
}
|
||||||
break;
|
|
||||||
|
this._mode = val;
|
||||||
default:
|
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
||||||
throw ("Invalid mode '" + val + "' in noteeditor.xml");
|
this._id('links-box').mode = val;
|
||||||
}
|
this._id('links-container').hidden = !(this.displayTags && this.displayRelated);
|
||||||
|
this._id('links-box').refresh();
|
||||||
this._mode = val;
|
]]>
|
||||||
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
|
||||||
this._id('links-box').mode = val;
|
|
||||||
]]>
|
|
||||||
</setter>
|
</setter>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<field name="_parentItem"/>
|
<field name="_parentItem"/>
|
||||||
<property name="parentItem" onget="return this._parentItem;">
|
<property name="parentItem" onget="return this._parentItem;">
|
||||||
<setter>
|
<setter>
|
||||||
|
@ -102,31 +166,59 @@
|
||||||
]]>
|
]]>
|
||||||
</setter>
|
</setter>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<field name="_mtime"/>
|
|
||||||
|
|
||||||
<field name="_item"/>
|
<field name="_item"/>
|
||||||
<property name="item" onget="return this._item;">
|
<property name="item" onget="return this._item;">
|
||||||
<setter><![CDATA[
|
<setter><![CDATA[
|
||||||
this._item = val;
|
return (async () => {
|
||||||
// TODO: use clientDateModified instead
|
// `item` field can be set before the constructor is called
|
||||||
this._mtime = val.getField('dateModified');
|
// (which happens in the merge dialog i.e.), therefore we wait for
|
||||||
|
// the initialization
|
||||||
var parentKey = this.item.parentKey;
|
let n = 0;
|
||||||
if (parentKey) {
|
while (!this._initialized && !this._destroyed) {
|
||||||
this.parentItem = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey);
|
if (n >= 1000) {
|
||||||
}
|
throw new Error('Waiting for noteeditor initialization failed');
|
||||||
|
}
|
||||||
this._id('links-box').item = this.item;
|
await Zotero.Promise.delay(10);
|
||||||
|
n++;
|
||||||
this.refresh();
|
}
|
||||||
]]></setter>
|
|
||||||
|
// The binding can also be immediately destrcutred
|
||||||
|
// (which also happens in the marge dialog)
|
||||||
|
if (this._destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!val) this._item = null;
|
||||||
|
if (this._item && this._item.id === val.id) return;
|
||||||
|
|
||||||
|
this._lastHtmlValue = val.getNote();
|
||||||
|
|
||||||
|
this._editor = new Zotero.NoteEditor();
|
||||||
|
this._editor.init({
|
||||||
|
item: val,
|
||||||
|
window: document.getAnonymousElementByAttribute(this, "anonid", "rt-view1").contentWindow,
|
||||||
|
readOnly: !this.editable,
|
||||||
|
onNavigate: this._navigateHandler
|
||||||
|
});
|
||||||
|
|
||||||
|
this._item = val;
|
||||||
|
|
||||||
|
var parentKey = this._item.parentKey;
|
||||||
|
if (parentKey) {
|
||||||
|
this.parentItem = Zotero.Items.getByLibraryAndKey(this._item.libraryID, parentKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._id('links-box').item = this._item;
|
||||||
|
})();
|
||||||
|
]]></setter>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<property name="linksOnTop">
|
<property name="linksOnTop">
|
||||||
<setter>
|
<setter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if(val) {
|
return;
|
||||||
|
if (val) {
|
||||||
var container = this._id('links-container');
|
var container = this._id('links-container');
|
||||||
var parent = container.parentNode;
|
var parent = container.parentNode;
|
||||||
var sib = container.nextSibling;
|
var sib = container.nextSibling;
|
||||||
|
@ -137,266 +229,116 @@
|
||||||
]]>
|
]]>
|
||||||
</setter>
|
</setter>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<property name="note"
|
<property name="navigateHandler">
|
||||||
onget="Zotero.debug('Getting note with .note deprecated -- use .item in zoteronoteeditor'); return this._item"
|
<setter>
|
||||||
onset="Zotero.debug('Setting note with .note deprecated -- use .item in zoteronoteeditor'); this.item = val"/>
|
<![CDATA[
|
||||||
<property name="ref" onget="return this._item" onset="this.item = val"/>
|
if (this._editor) {
|
||||||
|
this._editor.onNavigate = val;
|
||||||
|
}
|
||||||
|
this._navigateHandler = val;
|
||||||
|
]]>
|
||||||
|
</setter>
|
||||||
|
</property>
|
||||||
|
|
||||||
<field name="collection"/>
|
<field name="collection"/>
|
||||||
|
|
||||||
<property name="noteField" onget="return this._id('noteField')" readonly="true"/>
|
|
||||||
<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
|
|
||||||
|
|
||||||
<constructor>
|
|
||||||
<![CDATA[
|
|
||||||
this.instanceID = Zotero.Utilities.randomString();
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
|
|
||||||
]]>
|
|
||||||
</constructor>
|
|
||||||
|
|
||||||
<destructor>
|
<destructor>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
|
this._destroyed = true;
|
||||||
]]>
|
]]>
|
||||||
</destructor>
|
</destructor>
|
||||||
|
|
||||||
<method name="notify">
|
|
||||||
<parameter name="event"/>
|
|
||||||
<parameter name="type"/>
|
|
||||||
<parameter name="ids"/>
|
|
||||||
<parameter name="extraData"/>
|
|
||||||
<body><![CDATA[
|
|
||||||
if (event != 'modify' || !this.item || !this.item.id) return;
|
|
||||||
for (let i = 0; i < ids.length; i++) {
|
|
||||||
let id = ids[i];
|
|
||||||
if (id != this.item.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (extraData && extraData[id] && extraData[id].noteEditorID == this.instanceID) {
|
|
||||||
//Zotero.debug("Skipping notification from current note field");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (this.noteField.changed) {
|
|
||||||
//Zotero.debug("Note has changed since last save -- skipping refresh");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.refresh();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="refresh">
|
|
||||||
<body><![CDATA[
|
|
||||||
Zotero.debug('Refreshing note editor');
|
|
||||||
|
|
||||||
var textbox = this.noteField;
|
|
||||||
var textboxReadOnly = this._id('noteFieldReadOnly');
|
|
||||||
var button = this._id('goButton');
|
|
||||||
|
|
||||||
if (this.editable) {
|
|
||||||
textbox.hidden = false;
|
|
||||||
textboxReadOnly.hidden = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
textbox.hidden = true;
|
|
||||||
textboxReadOnly.hidden = false;
|
|
||||||
textbox = textboxReadOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
//var scrollPos = textbox.inputField.scrollTop;
|
|
||||||
if (this.item) {
|
|
||||||
// For sanity check in save()
|
|
||||||
textbox.setAttribute('itemID', this.item.id);
|
|
||||||
textbox.value = this.item.getNote();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
textbox.value = '';
|
|
||||||
textbox.removeAttribute('itemID');
|
|
||||||
}
|
|
||||||
//textbox.inputField.scrollTop = scrollPos;
|
|
||||||
|
|
||||||
this._id('links-container').hidden = !(this.displayTags && this.displayRelated);
|
|
||||||
this._id('links-box').refresh();
|
|
||||||
|
|
||||||
if (this.keyDownHandler) {
|
|
||||||
textbox.setAttribute('onkeydown',
|
|
||||||
'document.getBindingParent(this).handleKeyDown(event)');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
textbox.removeAttribute('onkeydown');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.commandHandler) {
|
|
||||||
textbox.setAttribute('oncommand',
|
|
||||||
'document.getBindingParent(this).commandHandler()');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
textbox.removeAttribute('oncommand');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.displayButton) {
|
|
||||||
button.label = this.buttonCaption;
|
|
||||||
button.hidden = false;
|
|
||||||
button.setAttribute('oncommand',
|
|
||||||
'document.getBindingParent(this).clickHandler(this)');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
button.hidden = true;
|
|
||||||
}
|
|
||||||
]]></body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="save">
|
<method name="save">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
return Zotero.spawn(function* () {
|
return (async () => {
|
||||||
try {
|
|
||||||
if (this._mode == 'view') {
|
})();
|
||||||
Zotero.debug("Not saving read-only note");
|
]]></body>
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var noteField = this._id('noteField');
|
|
||||||
var value = noteField.value;
|
|
||||||
if (value === null) {
|
|
||||||
Zotero.debug("Note value not available -- not saving", 2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update note
|
|
||||||
if (this.item) {
|
|
||||||
// If note field doesn't match item, abort save and run error handler
|
|
||||||
if (noteField.getAttribute('itemID') != this.item.id) {
|
|
||||||
throw new Error("Note field doesn't match current item");
|
|
||||||
}
|
|
||||||
|
|
||||||
let changed = this.item.setNote(value);
|
|
||||||
if (changed && this.saveOnEdit) {
|
|
||||||
this.noteField.changed = false;
|
|
||||||
yield this.item.saveTx({
|
|
||||||
notifierData: {
|
|
||||||
noteEditorID: this.instanceID
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new note
|
|
||||||
var item = new Zotero.Item('note');
|
|
||||||
if (this.parentItem) {
|
|
||||||
item.libraryID = this.parentItem.libraryID;
|
|
||||||
}
|
|
||||||
item.setNote(value);
|
|
||||||
if (this.parentItem) {
|
|
||||||
item.parentKey = this.parentItem.key;
|
|
||||||
}
|
|
||||||
if (this.saveOnEdit) {
|
|
||||||
var id = yield item.saveTx();
|
|
||||||
|
|
||||||
if (!this.parentItem && this.collection) {
|
|
||||||
this.collection.addItem(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.item = item;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.logError(e);
|
|
||||||
|
|
||||||
if (this.hasAttribute('onerror')) {
|
|
||||||
let fn = new Function("", this.getAttribute('onerror'));
|
|
||||||
fn.call(this)
|
|
||||||
}
|
|
||||||
if (this.onError) {
|
|
||||||
this.onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
]]></body>
|
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<!-- Used to insert a tab manually -->
|
<!-- Used to insert a tab manually -->
|
||||||
<method name="handleKeyDown">
|
<method name="handleKeyDown">
|
||||||
<parameter name="event"/>
|
<parameter name="event"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
var noteField = this._id('noteField');
|
// var noteField = this._id('noteField');
|
||||||
|
//
|
||||||
switch (event.keyCode) {
|
// switch (event.keyCode) {
|
||||||
case 9:
|
// case 9:
|
||||||
// On Shift-Tab, if focus was moved out of the note, focus the element
|
// // On Shift-Tab, if focus was moved out of the note, focus the element
|
||||||
// specified in the 'previousfocus' attribute. We check for focus
|
// // specified in the 'previousfocus' attribute. We check for focus
|
||||||
// because Shift-Tab doesn't and shouldn't move focus out of the note if
|
// // because Shift-Tab doesn't and shouldn't move focus out of the note if
|
||||||
// the cursor is in a list.
|
// // the cursor is in a list.
|
||||||
if (event.shiftKey) {
|
// if (event.shiftKey) {
|
||||||
let id = this.getAttribute('previousfocus');
|
// let id = this.getAttribute('previousfocus');
|
||||||
if (id) {
|
// if (id) {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
if (!noteField.hasFocus()) {
|
// if (!noteField.hasFocus()) {
|
||||||
document.getElementById(id).focus();
|
// document.getElementById(id).focus();
|
||||||
}
|
// }
|
||||||
}, 0);
|
// }, 0);
|
||||||
}
|
// }
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
]]>
|
|
||||||
</body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="focus">
|
|
||||||
<body>
|
|
||||||
<![CDATA[
|
|
||||||
this._id('noteField').focus();
|
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="clearUndo">
|
<method name="focus">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
this._id('noteField').clearUndo();
|
setTimeout(() => {
|
||||||
]]>
|
if (this._iframe && this._iframe.contentWindow) {
|
||||||
|
this._iframe.focus();
|
||||||
|
this._editor.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 500);
|
||||||
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="_id">
|
<method name="_id">
|
||||||
<parameter name="id"/>
|
<parameter name="id"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
</implementation>
|
</implementation>
|
||||||
|
|
||||||
<content>
|
<content>
|
||||||
<xul:vbox xbl:inherits="flex">
|
<xul:vbox xbl:inherits="flex">
|
||||||
<xul:textbox id="noteField" type="styled" mode="note"
|
<xul:iframe anonid="rt-view1" flex="1" overflow="auto" style="width: 100%;margin-right: 5px;border: 0"
|
||||||
timeout="1000" flex="1" hidden="true"/>
|
frameBorder="0" src="resource://zotero/zotero-note-editor/editor.html" type="content"/>
|
||||||
<xul:textbox id="noteFieldReadOnly" type="styled" mode="note"
|
|
||||||
readonly="true" flex="1" hidden="true"/>
|
|
||||||
<xul:hbox id="links-container" hidden="true">
|
<xul:hbox id="links-container" hidden="true">
|
||||||
<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
|
<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
<xul:button id="goButton" hidden="true"/>
|
|
||||||
|
<xul:popupset>
|
||||||
|
<xul:menupopup anonid="editor-menu" id="editor-menu" flex="1">
|
||||||
|
</xul:menupopup>
|
||||||
|
</xul:popupset>
|
||||||
</xul:vbox>
|
</xul:vbox>
|
||||||
|
|
||||||
</content>
|
</content>
|
||||||
</binding>
|
</binding>
|
||||||
|
|
||||||
|
|
||||||
<binding id="links-box">
|
<binding id="links-box">
|
||||||
<implementation>
|
<implementation>
|
||||||
<field name="itemRef"/>
|
<field name="itemRef"/>
|
||||||
<property name="item" onget="return this.itemRef;">
|
<property name="item" onget="return this.itemRef;">
|
||||||
<setter>
|
<setter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
this.itemRef = val;
|
this.itemRef = val;
|
||||||
|
|
||||||
this.id('tags').item = this.item;
|
this.id('tags').item = this.item;
|
||||||
this.id('related').item = this.item;
|
this.id('related').item = this.item;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
@ -406,127 +348,131 @@
|
||||||
<property name="mode">
|
<property name="mode">
|
||||||
<setter>
|
<setter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
this.id('related').mode = val;
|
this.id('related').mode = val;
|
||||||
this.id('tags').mode = val;
|
this.id('tags').mode = val;
|
||||||
]]>
|
]]>
|
||||||
</setter>
|
</setter>
|
||||||
</property>
|
</property>
|
||||||
<field name="_parentItem"/>
|
<field name="_parentItem"/>
|
||||||
<property name="parentItem" onget="return this._parentItem;">
|
<property name="parentItem" onget="return this._parentItem;">
|
||||||
<setter>
|
<setter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
this._parentItem = val;
|
this._parentItem = val;
|
||||||
|
|
||||||
var parentText = this.id('parentText');
|
var parentText = this.id('parentText');
|
||||||
if (parentText.firstChild) {
|
if (parentText.firstChild) {
|
||||||
parentText.removeChild(parentText.firstChild);
|
parentText.removeChild(parentText.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._parentItem && this.getAttribute('notitle') != '1') {
|
if (this._parentItem && this.getAttribute('notitle') != '1') {
|
||||||
this.id('parent-row').hidden = undefined;
|
this.id('parent-row').hidden = undefined;
|
||||||
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
|
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
|
||||||
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
|
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
|
||||||
}
|
}
|
||||||
]]>
|
]]>
|
||||||
</setter>
|
</setter>
|
||||||
</property>
|
</property>
|
||||||
<method name="tagsClick">
|
<method name="tagsClick">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
this.id('tags').reload();
|
this.id('tags').reload();
|
||||||
var x = this.boxObject.screenX;
|
var x = this.boxObject.screenX;
|
||||||
var y = this.boxObject.screenY;
|
var y = this.boxObject.screenY;
|
||||||
this.id('tagsPopup').openPopupAtScreen(x, y, false);
|
this.id('tagsPopup').openPopupAtScreen(x, y, false);
|
||||||
|
|
||||||
// If editable and no existing tags, open new empty row
|
// If editable and no existing tags, open new empty row
|
||||||
var tagsBox = this.id('tags');
|
var tagsBox = this.id('tags');
|
||||||
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
|
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
|
||||||
this.id('tags').newTag();
|
this.id('tags').newTag();
|
||||||
}
|
}
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="refresh">
|
<method name="refresh">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
this.updateTagsSummary();
|
this.updateTagsSummary();
|
||||||
this.updateRelatedSummary();
|
this.updateRelatedSummary();
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="updateTagsSummary">
|
<method name="updateTagsSummary">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
var v = this.id('tags').summary;
|
var v = this.id('tags').summary;
|
||||||
|
|
||||||
if (!v || v == "") {
|
if (!v || v == "") {
|
||||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
|
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
|
||||||
+ Zotero.getString('punctuation.colon');
|
+ Zotero.getString('punctuation.colon');
|
||||||
this.id('tagsClick').value = v;
|
this.id('tagsClick').value = v;
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
<method name="relatedClick">
|
<method name="relatedClick">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
var relatedList = this.item.relatedItems;
|
var relatedList = this.item.relatedItems;
|
||||||
if (relatedList.length > 0) {
|
if (relatedList.length > 0) {
|
||||||
var x = this.boxObject.screenX;
|
var x = this.boxObject.screenX;
|
||||||
var y = this.boxObject.screenY;
|
var y = this.boxObject.screenY;
|
||||||
this.id('relatedPopup').openPopupAtScreen(x, y, false);
|
this.id('relatedPopup').openPopupAtScreen(x, y, false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.id('related').add();
|
this.id('related').add();
|
||||||
}
|
}
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
<method name="updateRelatedSummary">
|
<method name="updateRelatedSummary">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
var v = this.id('related').summary;
|
var v = this.id('related').summary;
|
||||||
|
|
||||||
if (!v || v == "") {
|
if (!v || v == "") {
|
||||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
|
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
|
||||||
+ Zotero.getString('punctuation.colon');
|
+ Zotero.getString('punctuation.colon');
|
||||||
this.id('relatedClick').value = v;
|
this.id('relatedClick').value = v;
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
<method name="parentClick">
|
<method name="parentClick">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if (!this.item || !this.item.id) {
|
if (!this.item || !this.item.id) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('zotero-pane')) {
|
|
||||||
var zp = ZoteroPane;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
|
||||||
.getService(Components.interfaces.nsIWindowMediator);
|
|
||||||
|
|
||||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
|
||||||
|
|
||||||
if (!lastWin) {
|
|
||||||
var lastWin = window.open();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var zp = lastWin.ZoteroPane;
|
if (document.getElementById('zotero-pane')) {
|
||||||
}
|
var zp = ZoteroPane;
|
||||||
|
}
|
||||||
Zotero.spawn(function* () {
|
else {
|
||||||
var parentID = this.item.parentID;
|
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||||
yield zp.clearQuicksearch();
|
.getService(Components.interfaces.nsIWindowMediator);
|
||||||
zp.selectItem(parentID);
|
|
||||||
}, this);
|
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||||
]]>
|
|
||||||
|
if (!lastWin) {
|
||||||
|
var lastWin = window.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastWin.ZoteroOverlay && !lastWin.ZoteroPane.isShowing()) {
|
||||||
|
lastWin.ZoteroOverlay.toggleDisplay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var zp = lastWin.ZoteroPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.spawn(function* () {
|
||||||
|
var parentID = this.item.parentID;
|
||||||
|
yield zp.clearQuicksearch();
|
||||||
|
zp.selectItem(parentID);
|
||||||
|
}, this);
|
||||||
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
<method name="id">
|
<method name="id">
|
||||||
<parameter name="id"/>
|
<parameter name="id"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
@ -541,15 +487,18 @@
|
||||||
<xul:rows>
|
<xul:rows>
|
||||||
<xul:row id="parent-row" hidden="true">
|
<xul:row id="parent-row" hidden="true">
|
||||||
<xul:label id="parentLabel"/>
|
<xul:label id="parentLabel"/>
|
||||||
<xul:label id="parentText" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).parentClick();"/>
|
<xul:label id="parentText" class="zotero-clicky" crop="end"
|
||||||
|
onclick="document.getBindingParent(this).parentClick();"/>
|
||||||
</xul:row>
|
</xul:row>
|
||||||
<xul:row>
|
<xul:row>
|
||||||
<xul:label id="relatedLabel"/>
|
<xul:label id="relatedLabel"/>
|
||||||
<xul:label id="relatedClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).relatedClick();"/>
|
<xul:label id="relatedClick" class="zotero-clicky" crop="end"
|
||||||
|
onclick="document.getBindingParent(this).relatedClick();"/>
|
||||||
</xul:row>
|
</xul:row>
|
||||||
<xul:row>
|
<xul:row>
|
||||||
<xul:label id="tagsLabel"/>
|
<xul:label id="tagsLabel"/>
|
||||||
<xul:label id="tagsClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).tagsClick();"/>
|
<xul:label id="tagsClick" class="zotero-clicky" crop="end"
|
||||||
|
onclick="document.getBindingParent(this).tagsClick();"/>
|
||||||
</xul:row>
|
</xul:row>
|
||||||
</xul:rows>
|
</xul:rows>
|
||||||
</xul:grid>
|
</xul:grid>
|
||||||
|
@ -562,17 +511,17 @@
|
||||||
seems to get triggered by these events for reasons that are less than
|
seems to get triggered by these events for reasons that are less than
|
||||||
clear) so that we can manually refresh the popup if it's open after
|
clear) so that we can manually refresh the popup if it's open after
|
||||||
autocomplete is used to prevent it from becoming unresponsive
|
autocomplete is used to prevent it from becoming unresponsive
|
||||||
|
|
||||||
Note: Code in tagsbox.xml is dependent on the DOM path between the
|
Note: Code in tagsbox.xml is dependent on the DOM path between the
|
||||||
tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
|
tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
|
||||||
-->
|
-->
|
||||||
<xul:menupopup id="tagsPopup" ignorekeys="true"
|
<xul:menupopup id="tagsPopup" ignorekeys="true"
|
||||||
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
|
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
|
||||||
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
|
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
|
||||||
<xul:tagsbox id="tags" flex="1" mode="edit"/>
|
<xul:tagsbox id="tags" flex="1" mode="edit"/>
|
||||||
</xul:menupopup>
|
</xul:menupopup>
|
||||||
</xul:popupset>
|
</xul:popupset>
|
||||||
</xul:vbox>
|
</xul:vbox>
|
||||||
</content>
|
</content>
|
||||||
</binding>
|
</binding>
|
||||||
</bindings>
|
</bindings>
|
||||||
|
|
|
@ -26,9 +26,11 @@
|
||||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
|
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
|
||||||
<?xul-overlay href="chrome://zotero/content/containers/tagSelector.xul"?>
|
<?xul-overlay href="chrome://zotero/content/containers/tagSelector.xul"?>
|
||||||
<?xul-overlay href="chrome://zotero/content/containers/tagsBox.xul"?>
|
<?xul-overlay href="chrome://zotero/content/containers/tagsBox.xul"?>
|
||||||
|
<?xul-overlay href="chrome://zotero/content/containers/noteEditor.xul"?>
|
||||||
|
|
||||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
<script src="chrome://zotero/content/include.js"></script>
|
<script src="chrome://zotero/content/include.js"></script>
|
||||||
|
|
||||||
<script src="tagSelectorContainer.js"></script>
|
<script src="tagSelectorContainer.js"></script>
|
||||||
|
<script src="noteEditorContainer.js"></script>
|
||||||
</overlay>
|
</overlay>
|
35
chrome/content/zotero/containers/noteEditor.xul
Normal file
35
chrome/content/zotero/containers/noteEditor.xul
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.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 *****
|
||||||
|
-->
|
||||||
|
<!DOCTYPE overlay [
|
||||||
|
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
|
||||||
|
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd"> %zoteroDTD;
|
||||||
|
]>
|
||||||
|
|
||||||
|
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
<popupset>
|
||||||
|
<menupopup anonid="editor-menu" id="editor-menu" flex="1"/>
|
||||||
|
</popupset>
|
||||||
|
</overlay>
|
594
chrome/content/zotero/containers/noteEditorContainer.js
Normal file
594
chrome/content/zotero/containers/noteEditorContainer.js
Normal file
|
@ -0,0 +1,594 @@
|
||||||
|
class NoteEditor {
|
||||||
|
constructor() {
|
||||||
|
this.instanceID = Zotero.Utilities.randomString();
|
||||||
|
Zotero.Notes.editorInstances.push(this);
|
||||||
|
Zotero.debug('Creating a new editor instance');
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(options) {
|
||||||
|
this.id = options.item.id;
|
||||||
|
|
||||||
|
this.item = options.item;
|
||||||
|
// this._onNavigate = options.onNavigate;
|
||||||
|
this.saveOnEdit = true;
|
||||||
|
this.state = options.state;
|
||||||
|
this.citations = [];
|
||||||
|
this.disableSaving = false;
|
||||||
|
this._readOnly = options.readOnly;
|
||||||
|
this.window = options.window;
|
||||||
|
|
||||||
|
await this.waitForEditor();
|
||||||
|
// Zotero.Notes.updateURIs(h1);
|
||||||
|
|
||||||
|
// Run Cut/Copy/Paste with chrome privileges
|
||||||
|
this.window.wrappedJSObject.zoteroExecCommand = function (doc, command, ui, value) {
|
||||||
|
// Is that safe enough?
|
||||||
|
if (!['cut', 'copy', 'paste'].includes(command)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return doc.execCommand(command, ui, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.window.addEventListener('message', this.listener);
|
||||||
|
this.quickFormatWindow = null;
|
||||||
|
let data = this.state ? { state: this.state } : { html: this.item.getNote() };
|
||||||
|
this.postMessage({
|
||||||
|
op: 'init', ...data,
|
||||||
|
libraryId: this.item.libraryID,
|
||||||
|
key: this.item.key,
|
||||||
|
readOnly: this._readOnly
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uninit() {
|
||||||
|
this.window.removeEventListener('message', this.listener);
|
||||||
|
let index = Zotero.Notes.editorInstances.indexOf(this);
|
||||||
|
if (index >= 0) {
|
||||||
|
Zotero.Notes.editorInstances.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitForEditor() {
|
||||||
|
let n = 0;
|
||||||
|
while (!this.window) {
|
||||||
|
if (n >= 1000) {
|
||||||
|
throw new Error('Waiting for editor failed ');
|
||||||
|
}
|
||||||
|
await Zotero.Promise.delay(10);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage(message) {
|
||||||
|
this.window.postMessage({ instanceId: this.instanceID, message }, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
listener = async (e) => {
|
||||||
|
if (e.data.instanceId !== this.instanceID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Zotero.debug('Message received from editor ' + e.data.instanceId + ' ' + this.instanceID + ' ' + e.data.message.op);
|
||||||
|
|
||||||
|
let message = e.data.message;
|
||||||
|
|
||||||
|
if (message.op === 'getItemData') {
|
||||||
|
let parent = message.parent;
|
||||||
|
let item = await Zotero.Items.getAsync(message.itemId);
|
||||||
|
if (parent && item && item.parentID) {
|
||||||
|
item = await Zotero.Items.getAsync(item.parentID);
|
||||||
|
}
|
||||||
|
if (item) {
|
||||||
|
let data = {
|
||||||
|
uri: Zotero.URI.getItemURI(item),
|
||||||
|
backupText: this.getBackupStr(item)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.op === 'insertObject') {
|
||||||
|
let { type, data, pos } = message;
|
||||||
|
|
||||||
|
if (type === 'zotero/item') {
|
||||||
|
let ids = data.split(',').map(id => parseInt(id));
|
||||||
|
let citations = [];
|
||||||
|
for (let id of ids) {
|
||||||
|
let item = await Zotero.Items.getAsync(id);
|
||||||
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
citations.push({
|
||||||
|
citationItems: [{
|
||||||
|
uri: Zotero.URI.getItemURI(item),
|
||||||
|
backupText: this.getBackupStr(item)
|
||||||
|
}],
|
||||||
|
properties: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.postMessage({ op: 'insertCitations', citations, pos });
|
||||||
|
}
|
||||||
|
else if (type === 'zotero/annotation') {
|
||||||
|
let annotations = JSON.parse(data);
|
||||||
|
let list = [];
|
||||||
|
for (let annotation of annotations) {
|
||||||
|
let attachmentItem = await Zotero.Items.getAsync(annotation.itemId);
|
||||||
|
if (!attachmentItem) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let citationItem = attachmentItem.parentID && await Zotero.Items.getAsync(attachmentItem.parentID) || attachmentItem;
|
||||||
|
annotation.uri = Zotero.URI.getItemURI(attachmentItem);
|
||||||
|
let citation = {
|
||||||
|
citationItems: [{
|
||||||
|
uri: Zotero.URI.getItemURI(citationItem),
|
||||||
|
backupText: this.getBackupStr(citationItem),
|
||||||
|
locator: annotation.pageLabel
|
||||||
|
}],
|
||||||
|
properties: {}
|
||||||
|
};
|
||||||
|
list.push({ annotation, citation });
|
||||||
|
}
|
||||||
|
this.postMessage({ op: 'insertAnnotationsAndCitations', list, pos });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.op === 'navigate') {
|
||||||
|
if (this._onNavigate) {
|
||||||
|
this._onNavigate(message.uri, { position: message.position });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await Zotero.Viewer.openURI(message.uri, { position: message.position });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.op === 'openURL') {
|
||||||
|
var zp = typeof ZoteroPane !== 'undefined' ? ZoteroPane : window.opener.ZoteroPane;
|
||||||
|
zp.loadURI(message.url);
|
||||||
|
}
|
||||||
|
else if (message.op === 'showInLibrary') {
|
||||||
|
let zp = Zotero.getActiveZoteroPane();
|
||||||
|
if (zp) {
|
||||||
|
let item = await Zotero.URI.getURIItem(message.itemURI);
|
||||||
|
if (item) {
|
||||||
|
zp.selectItems([item.id]);
|
||||||
|
let win = Zotero.getMainWindow();
|
||||||
|
if (win) {
|
||||||
|
win.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.op === 'update') {
|
||||||
|
this.save(message.noteData);
|
||||||
|
}
|
||||||
|
else if (message.op === 'getFormattedCitations') {
|
||||||
|
let formattedCitations = await this.getFormattedCitations(message.citations);
|
||||||
|
for (let newCitation of message.citations) {
|
||||||
|
if (!this.citations.find(citation => citation.id === newCitation.id)) {
|
||||||
|
this.citations.push(newCitation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.postMessage({
|
||||||
|
op: 'setFormattedCitations',
|
||||||
|
formattedCitations
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (message.op === 'quickFormat') {
|
||||||
|
let id = message.id;
|
||||||
|
let citation = message.citation;
|
||||||
|
citation = JSON.parse(JSON.stringify(citation));
|
||||||
|
let availableCitationItems = [];
|
||||||
|
for (let citationItem of citation.citationItems) {
|
||||||
|
let item = await Zotero.URI.getURIItem(citationItem.uri);
|
||||||
|
if (item) {
|
||||||
|
availableCitationItems.push({ ...citationItem, id: item.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
citation.citationItems = availableCitationItems;
|
||||||
|
let libraryID = this.item.libraryID;
|
||||||
|
this.quickFormatDialog(id, citation, [libraryID]);
|
||||||
|
}
|
||||||
|
else if (message.op === 'updateImages') {
|
||||||
|
for (let image of message.added) {
|
||||||
|
let blob = this.dataURLtoBlob(image.dataUrl);
|
||||||
|
let imageAttachment = await Zotero.Attachments.importEmbeddedImage({
|
||||||
|
blob,
|
||||||
|
parentItemID: this.item.id,
|
||||||
|
itemKey: image.attachmentKey,
|
||||||
|
saveOptions: {
|
||||||
|
notifierData: {
|
||||||
|
noteEditorID: this.instanceID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let attachmentItems = this.item.getAttachments().map(id => Zotero.Items.get(id));
|
||||||
|
let abandonedItems = attachmentItems.filter(item => !message.all.includes(item.key));
|
||||||
|
for (let item of abandonedItems) {
|
||||||
|
await item.eraseTx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.op === 'requestImage') {
|
||||||
|
let { attachmentKey } = message;
|
||||||
|
var item = Zotero.Items.getByLibraryAndKey(this.item.libraryID, attachmentKey);
|
||||||
|
if (!item) return;
|
||||||
|
let path = await item.getFilePathAsync();
|
||||||
|
let buf = await OS.File.read(path, {});
|
||||||
|
buf = new Uint8Array(buf).buffer;
|
||||||
|
let dataURL = 'data:' + item.attachmentContentType + ';base64,' + this.arrayBufferToBase64(buf);
|
||||||
|
this.postMessage({
|
||||||
|
op: 'updateImage',
|
||||||
|
attachmentKey,
|
||||||
|
dataUrl: dataURL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (message.op === 'popup') {
|
||||||
|
this.openPopup(message.x, message.y, message.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openPopup(x, y, items) {
|
||||||
|
let popup = document.getElementById('editor-menu');
|
||||||
|
popup.hidePopup();
|
||||||
|
|
||||||
|
while (popup.firstChild) {
|
||||||
|
popup.removeChild(popup.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let item of items) {
|
||||||
|
let menuitem = document.createElement('menuitem');
|
||||||
|
menuitem.setAttribute('value', item[0]);
|
||||||
|
menuitem.setAttribute('label', item[1]);
|
||||||
|
menuitem.addEventListener('command', () => {
|
||||||
|
this.postMessage({
|
||||||
|
op: 'contextMenuAction',
|
||||||
|
ctxAction: item[0],
|
||||||
|
payload: item.payload
|
||||||
|
});
|
||||||
|
});
|
||||||
|
popup.appendChild(menuitem);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.openPopupAtScreen(x, y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(noteData) {
|
||||||
|
if (!noteData) return;
|
||||||
|
let { state, html } = noteData;
|
||||||
|
if (html === undefined) return;
|
||||||
|
try {
|
||||||
|
if (this.disableSaving) {
|
||||||
|
Zotero.debug('Saving is disabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._readOnly) {
|
||||||
|
Zotero.debug('Not saving read-only note');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (html === null) {
|
||||||
|
Zotero.debug('Note value not available -- not saving', 2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Update note
|
||||||
|
if (this.item) {
|
||||||
|
let changed = this.item.setNote(html);
|
||||||
|
if (changed && this.saveOnEdit) {
|
||||||
|
// this.noteField.changed = false;
|
||||||
|
await this.item.saveTx({
|
||||||
|
notifierData: {
|
||||||
|
noteEditorID: this.instanceID,
|
||||||
|
state
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Create a new note
|
||||||
|
var item = new Zotero.Item('note');
|
||||||
|
if (this.parentItem) {
|
||||||
|
item.libraryID = this.parentItem.libraryID;
|
||||||
|
}
|
||||||
|
item.setNote(html);
|
||||||
|
if (this.parentItem) {
|
||||||
|
item.parentKey = this.parentItem.key;
|
||||||
|
}
|
||||||
|
if (this.saveOnEdit) {
|
||||||
|
var id = await item.saveTx();
|
||||||
|
|
||||||
|
if (!this.parentItem && this.collection) {
|
||||||
|
this.collection.addItem(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
if (this.hasAttribute('onerror')) {
|
||||||
|
let fn = new Function('', this.getAttribute('onerror'));
|
||||||
|
fn.call(this)
|
||||||
|
}
|
||||||
|
if (this.onError) {
|
||||||
|
this.onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focus = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getNoteDataSync = () => {
|
||||||
|
if (!this._readOnly && !this.disableSaving && this.window) {
|
||||||
|
return this.window.wrappedJSObject.getDataSync();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the string to go inside a bubble
|
||||||
|
*/
|
||||||
|
_buildBubbleString(citationItem, str) {
|
||||||
|
// Locator
|
||||||
|
if (citationItem.locator) {
|
||||||
|
if (citationItem.label) {
|
||||||
|
// TODO localize and use short forms
|
||||||
|
var label = citationItem.label;
|
||||||
|
}
|
||||||
|
else if (/[\-–,]/.test(citationItem.locator)) {
|
||||||
|
var label = 'pp.';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var label = 'p.';
|
||||||
|
}
|
||||||
|
|
||||||
|
str += ', ' + label + ' ' + citationItem.locator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix
|
||||||
|
if (citationItem.prefix && Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP) {
|
||||||
|
str = citationItem.prefix
|
||||||
|
+ (Zotero.CiteProc.CSL.ENDSWITH_ROMANESQUE_REGEXP.test(citationItem.prefix) ? ' ' : '')
|
||||||
|
+ str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suffix
|
||||||
|
if (citationItem.suffix && Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP) {
|
||||||
|
str += (Zotero.CiteProc.CSL.STARTSWITH_ROMANESQUE_REGEXP.test(citationItem.suffix) ? ' ' : '')
|
||||||
|
+ citationItem.suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCitationsForURIs(uris) {
|
||||||
|
let citations = this.citations
|
||||||
|
.filter(citation => citation.citationItems
|
||||||
|
.some(citationItem => uris.includes(citationItem.uri)));
|
||||||
|
|
||||||
|
if (citations.length) {
|
||||||
|
let formattedCitations = await this.getFormattedCitations(citations);
|
||||||
|
this.postMessage({
|
||||||
|
op: 'setFormattedCitations',
|
||||||
|
formattedCitations
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedCitations = async (citations) => {
|
||||||
|
let formattedCitations = {};
|
||||||
|
for (let citation of citations) {
|
||||||
|
formattedCitations[citation.id] = await this.getFormattedCitation(citation);
|
||||||
|
}
|
||||||
|
return formattedCitations;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedCitation = async (citation) => {
|
||||||
|
let formattedItems = [];
|
||||||
|
for (let citationItem of citation.citationItems) {
|
||||||
|
let item = await Zotero.URI.getURIItem(citationItem.uri);
|
||||||
|
if (item && !item.deleted) {
|
||||||
|
formattedItems.push(this._buildBubbleString(citationItem, this.getBackupStr(item)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let formattedItem = this._buildBubbleString(citationItem, citationItem.backupText);
|
||||||
|
formattedItem = `<span style="color: red;">${formattedItem}</span>`;
|
||||||
|
formattedItems.push(formattedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formattedItems.join(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
getBackupStr(item) {
|
||||||
|
var str = item.getField('firstCreator');
|
||||||
|
|
||||||
|
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
|
||||||
|
if (!str) {
|
||||||
|
str = Zotero.getString('punctuation.openingQMark') + item.getDisplayTitle() + Zotero.getString('punctuation.closingQMark');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date
|
||||||
|
var date = item.getField('date', true, true);
|
||||||
|
if (date && (date = date.substr(0, 4)) !== '0000') {
|
||||||
|
str += ', ' + date;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayBufferToBase64(buffer) {
|
||||||
|
var binary = '';
|
||||||
|
var bytes = new Uint8Array(buffer);
|
||||||
|
var len = bytes.byteLength;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return self.btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataURLtoBlob(dataurl) {
|
||||||
|
let parts = dataurl.split(',');
|
||||||
|
let mime = parts[0].match(/:(.*?);/)[1];
|
||||||
|
if (parts[0].indexOf('base64') !== -1) {
|
||||||
|
let bstr = atob(parts[1]);
|
||||||
|
let n = bstr.length;
|
||||||
|
let u8arr = new Uint8Array(n);
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self.Blob([u8arr], { type: mime });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
quickFormatDialog(id, citationData, filterLibraryIDs) {
|
||||||
|
let that = this;
|
||||||
|
let win;
|
||||||
|
/**
|
||||||
|
* Citation editing functions and propertiesaccessible to quickFormat.js and addCitationDialog.js
|
||||||
|
*/
|
||||||
|
let CI = function (citation, sortable, fieldIndexPromise, citationsByItemIDPromise, previewFn) {
|
||||||
|
this.citation = citation;
|
||||||
|
this.sortable = sortable;
|
||||||
|
this.filterLibraryIDs = filterLibraryIDs;
|
||||||
|
this.disableClassicDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CI.prototype = {
|
||||||
|
/**
|
||||||
|
* Execute a callback with a preview of the given citation
|
||||||
|
* @return {Promise} A promise resolved with the previewed citation string
|
||||||
|
*/
|
||||||
|
preview: function () {
|
||||||
|
Zotero.debug('CI: preview')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the citationItems within citation (depends on this.citation.properties.unsorted)
|
||||||
|
* @return {Promise} A promise resolved with the previewed citation string
|
||||||
|
*/
|
||||||
|
sort: function () {
|
||||||
|
Zotero.debug('CI: sort')
|
||||||
|
return async function () {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept changes to the citation
|
||||||
|
* @param {Function} [progressCallback] A callback to be run when progress has changed.
|
||||||
|
* Receives a number from 0 to 100 indicating current status.
|
||||||
|
*/
|
||||||
|
accept: async function (progressCallback) {
|
||||||
|
Zotero.debug('CI: accept');
|
||||||
|
if (progressCallback) progressCallback(100);
|
||||||
|
|
||||||
|
if (win) {
|
||||||
|
win.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
let citation = {
|
||||||
|
citationItems: this.citation.citationItems,
|
||||||
|
properties: this.citation.properties
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let citationItem of citation.citationItems) {
|
||||||
|
let itm = await Zotero.Items.getAsync(citationItem.id);
|
||||||
|
delete citationItem.id;
|
||||||
|
citationItem.uri = Zotero.URI.getItemURI(itm);
|
||||||
|
citationItem.backupText = that.getBackupStr(itm);
|
||||||
|
}
|
||||||
|
|
||||||
|
let formattedCitation = await that.getFormattedCitation(citation);
|
||||||
|
|
||||||
|
if (this.citation.citationItems.length) {
|
||||||
|
that.postMessage({
|
||||||
|
op: 'setCitation',
|
||||||
|
id, citation, formattedCitation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of items used in the current document
|
||||||
|
* @return {Promise} A promise resolved by the items
|
||||||
|
*/
|
||||||
|
getItems: async function () {
|
||||||
|
Zotero.debug('CI: getItems')
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let Citation = class {
|
||||||
|
constructor(citationField, data, noteIndex) {
|
||||||
|
if (!data) {
|
||||||
|
data = { citationItems: [], properties: {} };
|
||||||
|
}
|
||||||
|
this.citationID = data.citationID;
|
||||||
|
this.citationItems = data.citationItems;
|
||||||
|
this.properties = data.properties;
|
||||||
|
this.properties.noteIndex = noteIndex;
|
||||||
|
|
||||||
|
this._field = citationField;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load citation item data
|
||||||
|
* @param {Boolean} [promptToReselect=true] - will throw a MissingItemException if false
|
||||||
|
* @returns {Promise{Number}}
|
||||||
|
* - Zotero.Integration.NO_ACTION
|
||||||
|
* - Zotero.Integration.UPDATE
|
||||||
|
* - Zotero.Integration.REMOVE_CODE
|
||||||
|
* - Zotero.Integration.DELETE
|
||||||
|
*/
|
||||||
|
loadItemData() {
|
||||||
|
Zotero.debug('Citation: loadItemData');
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleMissingItem(idx) {
|
||||||
|
Zotero.debug('Citation: handleMissingItem');
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepareForEditing() {
|
||||||
|
Zotero.debug('Citation: prepareForEditing');
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
Zotero.debug('Citation: toJSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the citation into CSL code representation
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
serialize() {
|
||||||
|
Zotero.debug('Citation: serialize');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (that.quickFormatWindow) {
|
||||||
|
that.quickFormatWindow.close();
|
||||||
|
that.quickFormatWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let citation = new Citation();
|
||||||
|
citation.citationItems = citationData.citationItems;
|
||||||
|
citation.properties = citationData.properties;
|
||||||
|
let styleID = Zotero.Prefs.get('export.lastStyle');
|
||||||
|
let locale = Zotero.Prefs.get('export.lastLocale');
|
||||||
|
let csl = Zotero.Styles.get(styleID).getCiteProc(locale);
|
||||||
|
var io = new CI(citation, csl.opt.sort_citations);
|
||||||
|
|
||||||
|
|
||||||
|
var allOptions = 'chrome,centerscreen';
|
||||||
|
// without this, Firefox gets raised with our windows under Compiz
|
||||||
|
if (Zotero.isLinux) allOptions += ',dialog=no';
|
||||||
|
// if(options) allOptions += ','+options;
|
||||||
|
|
||||||
|
var mode = (!Zotero.isMac && Zotero.Prefs.get('integration.keepAddCitationDialogRaised')
|
||||||
|
? 'popup' : 'alwaysRaised') + ',resizable=false,centerscreen';
|
||||||
|
|
||||||
|
win = that.quickFormatWindow = Components.classes['@mozilla.org/embedcomp/window-watcher;1']
|
||||||
|
.getService(Components.interfaces.nsIWindowWatcher)
|
||||||
|
.openWindow(null, 'chrome://zotero/content/integration/quickFormat.xul', '', mode, {
|
||||||
|
wrappedJSObject: io
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.NoteEditor = NoteEditor;
|
|
@ -56,6 +56,10 @@ var Zotero_QuickFormat = new function () {
|
||||||
Zotero.debug(`Quick Format received citation:`);
|
Zotero.debug(`Quick Format received citation:`);
|
||||||
Zotero.debug(JSON.stringify(io.citation.toJSON()));
|
Zotero.debug(JSON.stringify(io.citation.toJSON()));
|
||||||
|
|
||||||
|
if (io.disableClassicDialog) {
|
||||||
|
document.getElementById('classic-view').hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Only hide chrome on Windows or Mac
|
// Only hide chrome on Windows or Mac
|
||||||
if(Zotero.isMac) {
|
if(Zotero.isMac) {
|
||||||
document.documentElement.setAttribute("drawintitlebar", true);
|
document.documentElement.setAttribute("drawintitlebar", true);
|
||||||
|
@ -309,6 +313,10 @@ var Zotero_QuickFormat = new function () {
|
||||||
.forEach(feed => s.addCondition("libraryID", "isNot", feed.libraryID));
|
.forEach(feed => s.addCondition("libraryID", "isNot", feed.libraryID));
|
||||||
s.addCondition("quicksearch-titleCreatorYear", "contains", str);
|
s.addCondition("quicksearch-titleCreatorYear", "contains", str);
|
||||||
s.addCondition("itemType", "isNot", "attachment");
|
s.addCondition("itemType", "isNot", "attachment");
|
||||||
|
if (io.filterLibraryIDs) {
|
||||||
|
io.filterLibraryIDs.forEach(id => s.addCondition("libraryID", "is", id));
|
||||||
|
}
|
||||||
|
|
||||||
haveConditions = true;
|
haveConditions = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,25 +259,25 @@ var ZoteroItemPane = new function() {
|
||||||
_selectedNoteID = item.id;
|
_selectedNoteID = item.id;
|
||||||
|
|
||||||
// If an external note window is open for this item, don't show the editor
|
// If an external note window is open for this item, don't show the editor
|
||||||
if (ZoteroPane.findNoteWindow(item.id)) {
|
// if (ZoteroPane.findNoteWindow(item.id)) {
|
||||||
this.showNoteWindowMessage();
|
// this.showNoteWindowMessage();
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
var noteEditor = document.getElementById('zotero-note-editor');
|
var noteEditor = document.getElementById('zotero-note-editor');
|
||||||
|
|
||||||
// If loading new or different note, disable undo while we repopulate the text field
|
// 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
|
// 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.
|
// undo content from another note into the current one.
|
||||||
var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
|
// var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
|
||||||
|
|
||||||
noteEditor.mode = editable ? 'edit' : 'view';
|
noteEditor.mode = editable ? 'edit' : 'view';
|
||||||
noteEditor.parent = null;
|
noteEditor.parent = null;
|
||||||
noteEditor.item = item;
|
noteEditor.item = item;
|
||||||
|
|
||||||
if (clearUndo) {
|
// if (clearUndo) {
|
||||||
noteEditor.clearUndo();
|
// noteEditor.clearUndo();
|
||||||
}
|
// }
|
||||||
|
|
||||||
document.getElementById('zotero-view-note-button').hidden = !editable;
|
document.getElementById('zotero-view-note-button').hidden = !editable;
|
||||||
document.getElementById('zotero-item-pane-content').selectedIndex = 2;
|
document.getElementById('zotero-item-pane-content').selectedIndex = 2;
|
||||||
|
@ -285,7 +285,7 @@ var ZoteroItemPane = new function() {
|
||||||
|
|
||||||
|
|
||||||
this.showNoteWindowMessage = function () {
|
this.showNoteWindowMessage = function () {
|
||||||
ZoteroPane.setItemPaneMessage(Zotero.getString('pane.item.notes.editingInWindow'));
|
// ZoteroPane.setItemPaneMessage(Zotero.getString('pane.item.notes.editingInWindow'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
-->
|
-->
|
||||||
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"
|
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"
|
||||||
previousfocus="zotero-items-tree"
|
previousfocus="zotero-items-tree"
|
||||||
onerror="ZoteroPane.displayErrorMessage(); this.mode = 'view'"/>
|
onerror="return;ZoteroPane.displayErrorMessage(); /*this.mode = 'view'*/"/>
|
||||||
<button id="zotero-view-note-button"
|
<button id="zotero-view-note-button"
|
||||||
label="&zotero.notes.separate;"
|
label="&zotero.notes.separate;"
|
||||||
oncommand="ZoteroItemPane.openNoteWindow()"/>
|
oncommand="ZoteroItemPane.openNoteWindow()"/>
|
||||||
|
|
|
@ -79,7 +79,12 @@ function onUnload() {
|
||||||
Zotero.Notifier.unregisterObserver(notifierUnregisterID);
|
Zotero.Notifier.unregisterObserver(notifierUnregisterID);
|
||||||
|
|
||||||
if (noteEditor.item) {
|
if (noteEditor.item) {
|
||||||
window.opener.ZoteroPane.onNoteWindowClosed(noteEditor.item.id, noteEditor.value);
|
// noteData will be null if noteEditor current editor instance
|
||||||
|
// has disabled saving, which might happen at the time of the initial sync
|
||||||
|
let noteData = JSON.parse(JSON.stringify(noteEditor.getNoteDataSync()));
|
||||||
|
if (noteData) {
|
||||||
|
window.opener.ZoteroPane.onNoteWindowClosed(noteEditor.item.id, noteData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +92,7 @@ var NotifyCallback = {
|
||||||
notify: function(action, type, ids){
|
notify: function(action, type, ids){
|
||||||
if (noteEditor.item && ids.includes(noteEditor.item.id)) {
|
if (noteEditor.item && ids.includes(noteEditor.item.id)) {
|
||||||
var noteTitle = noteEditor.item.getNoteTitle();
|
var noteTitle = noteEditor.item.getNoteTitle();
|
||||||
if (!document.title && noteTitle != '') {
|
document.title = noteTitle;
|
||||||
document.title = noteTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the window name (used for focusing) in case this is a new note
|
// Update the window name (used for focusing) in case this is a new note
|
||||||
window.name = 'zotero-note-' + noteEditor.item.id;
|
window.name = 'zotero-note-' + noteEditor.item.id;
|
||||||
|
|
|
@ -21,6 +21,6 @@
|
||||||
<key id="key_close" key="W" modifiers="accel" command="cmd_close"/>
|
<key id="key_close" key="W" modifiers="accel" command="cmd_close"/>
|
||||||
</keyset>
|
</keyset>
|
||||||
<command id="cmd_close" oncommand="window.close();"/>
|
<command id="cmd_close" oncommand="window.close();"/>
|
||||||
|
|
||||||
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="onError()"/>
|
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="return;onError()"/>
|
||||||
</window>
|
</window>
|
||||||
|
|
66
chrome/content/zotero/viewer.js
Normal file
66
chrome/content/zotero/viewer.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||||
|
|
||||||
|
function handleDragOver(event) {
|
||||||
|
if (event.dataTransfer.getData('zotero/item')) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop(event) {
|
||||||
|
let data;
|
||||||
|
if (!(data = event.dataTransfer.getData('zotero/item'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids = data.split(',').map(id => parseInt(id));
|
||||||
|
let item = Zotero.Items.get(ids[0]);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.isNote()) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
let cover = document.getElementById('zotero-viewer-sidebar-cover');
|
||||||
|
let container = document.getElementById('zotero-viewer-sidebar-container');
|
||||||
|
|
||||||
|
cover.hidden = true;
|
||||||
|
container.hidden = false;
|
||||||
|
|
||||||
|
let editor = document.getElementById('zotero-viewer-editor');
|
||||||
|
let notebox = document.getElementById('zotero-viewer-note-sidebar');
|
||||||
|
editor.mode = 'edit';
|
||||||
|
notebox.hidden = false;
|
||||||
|
editor.item = item;
|
||||||
|
}
|
||||||
|
else if (item.isAttachment() && item.attachmentContentType === 'application/pdf') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
let iframeWindow = document.getElementById('viewer').contentWindow;
|
||||||
|
let url = 'zotero://pdf.js/viewer.html?libraryID=' + item.libraryID + '&key=' + item.key;
|
||||||
|
if (url !== iframeWindow.location.href) {
|
||||||
|
iframeWindow.location = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.isRegularItem()) {
|
||||||
|
let attachments = item.getAttachments();
|
||||||
|
if (attachments.length === 1) {
|
||||||
|
let id = attachments[0];
|
||||||
|
let attachment = Zotero.Items.get(id);
|
||||||
|
if (attachment.attachmentContentType === 'application/pdf') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
let iframeWindow = document.getElementById('viewer').contentWindow;
|
||||||
|
let url = 'zotero://pdf.js/viewer.html?libraryID=' + attachment.libraryID + '&key=' + attachment.key;
|
||||||
|
if (url !== iframeWindow.location.href) {
|
||||||
|
iframeWindow.location = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('dragover', handleDragOver, true);
|
||||||
|
window.addEventListener('drop', handleDrop, true);
|
98
chrome/content/zotero/viewer.xul
Normal file
98
chrome/content/zotero/viewer.xul
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
|
||||||
|
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||||
|
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||||
|
|
||||||
|
<?xul-overlay href="chrome://zotero-platform/content/standalone/menuOverlay.xul"?>
|
||||||
|
|
||||||
|
<!DOCTYPE window [
|
||||||
|
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
|
||||||
|
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd"> %zoteroDTD;
|
||||||
|
]>
|
||||||
|
|
||||||
|
<window
|
||||||
|
windowtype="zotero:viewer"
|
||||||
|
orient="vertical"
|
||||||
|
width="1300"
|
||||||
|
height="800"
|
||||||
|
persist="screenX screenY width height"
|
||||||
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
|
>
|
||||||
|
<!-- TODO: Localize -->
|
||||||
|
<menubar>
|
||||||
|
<menu label="File">
|
||||||
|
<menupopup>
|
||||||
|
<menuitem label="Save As" oncommand="menuCmd('export')"/>
|
||||||
|
<menuitem label="Print" oncommand="menuCmd('print')"/>
|
||||||
|
</menupopup>
|
||||||
|
</menu>
|
||||||
|
<menu label="View">
|
||||||
|
<menupopup>
|
||||||
|
<menuitem label="Switch to Presentation Mode" oncommand="menuCmd('presentationmode')"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="Go to First Page" oncommand="menuCmd('firstpage')"/>
|
||||||
|
<menuitem label="Go to Last Page" oncommand="menuCmd('lastpage')"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="Rotate Clockwise" oncommand="menuCmd('rotatecw')"/>
|
||||||
|
<menuitem label="Rotate Counterclockwise" oncommand="menuCmd('rotateccw')"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="Text Selection Tool" oncommand="menuCmd('switchcursortool_select')"/>
|
||||||
|
<menuitem label="Hand Tool" oncommand="menuCmd('switchcursortool_hand')"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="Vertical Scrolling" oncommand="menuCmd('switchscrollmode_vertical')"/>
|
||||||
|
<menuitem label="Horizontal Scrolling" oncommand="menuCmd('switchscrollmode_horizontal')"/>
|
||||||
|
<menuitem label="Wrapped Scrolling" oncommand="menuCmd('switchscrollmode_wrapped')"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="No Spreads" oncommand="menuCmd('switchspreadmode_none')"/>
|
||||||
|
<menuitem label="Odd Spreads" oncommand="menuCmd('switchspreadmode_odd')"/>
|
||||||
|
<menuitem label="Even Spreads" oncommand="menuCmd('switchspreadmode_even')"/>
|
||||||
|
</menupopup>
|
||||||
|
</menu>
|
||||||
|
<menu label="Tools">
|
||||||
|
<menupopup>
|
||||||
|
<menuitem label="Remove password (not implemented)" oncommand="menuCmd('remove_password')"/>
|
||||||
|
</menupopup>
|
||||||
|
</menu>
|
||||||
|
</menubar>
|
||||||
|
|
||||||
|
<hbox flex="1">
|
||||||
|
<vbox id="zotero-viewer" flex="3">
|
||||||
|
<browser tooltip="viewerTooltip" type="content" primary="true" transparent="transparent" src="" id="viewer"
|
||||||
|
flex="1"/>
|
||||||
|
<popupset>
|
||||||
|
<tooltip id="viewerTooltip" onpopupshowing="return fillTooltip(this);"/>
|
||||||
|
<menupopup id="tagsPopup" ignorekeys="true"
|
||||||
|
style="min-width: 300px;"
|
||||||
|
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'true'); }"
|
||||||
|
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
|
||||||
|
<tagsbox id="tags" flex="1" mode="edit"/>
|
||||||
|
</menupopup>
|
||||||
|
<menupopup id="annotationPopup"/>
|
||||||
|
<menupopup id="colorPopup"/>
|
||||||
|
</popupset>
|
||||||
|
</vbox>
|
||||||
|
<splitter id="zotero-viewer-splitter"
|
||||||
|
hidden="true"
|
||||||
|
resizebefore="closest"
|
||||||
|
resizeafter="closest"
|
||||||
|
collapse="after"
|
||||||
|
orient="horizontal"
|
||||||
|
zotero-persist="state orient" />
|
||||||
|
<vbox flex="0" id="zotero-viewer-note-sidebar" width="350" hidden="true">
|
||||||
|
<vbox id="zotero-viewer-sidebar-cover" flex="1">
|
||||||
|
<label>Drag a note here…</label>
|
||||||
|
</vbox>
|
||||||
|
<vbox id="zotero-viewer-sidebar-container" flex="1" style="overflow:auto;" hidden="true">
|
||||||
|
<zoteronoteeditor id="zotero-viewer-editor" flex="1" notitle="1"
|
||||||
|
previousfocus="zotero-items-tree"
|
||||||
|
onerror="/*this.mode = 'view'*/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button id="zotero-view-note-button" label="Close"
|
||||||
|
oncommand="document.getElementById('zotero-viewer-sidebar-container').hidden = true;document.getElementById('zotero-viewer-sidebar-cover').hidden = false;"/>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
|
</hbox>
|
||||||
|
<script src="include.js"/>
|
||||||
|
<script src="viewer.js"/>
|
||||||
|
</window>
|
|
@ -362,8 +362,8 @@ Zotero.Attachments = new function(){
|
||||||
* @param {Object} [params.saveOptions] - Options to pass to Zotero.Item::save()
|
* @param {Object} [params.saveOptions] - Options to pass to Zotero.Item::save()
|
||||||
* @return {Promise<Zotero.Item>}
|
* @return {Promise<Zotero.Item>}
|
||||||
*/
|
*/
|
||||||
this.importEmbeddedImage = async function ({ blob, parentItemID, saveOptions }) {
|
this.importEmbeddedImage = async function ({ blob, itemKey, parentItemID, saveOptions }) {
|
||||||
Zotero.debug('Importing annotation image');
|
Zotero.debug('Importing note or annotation image');
|
||||||
|
|
||||||
var contentType = blob.type;
|
var contentType = blob.type;
|
||||||
var fileExt;
|
var fileExt;
|
||||||
|
@ -371,6 +371,12 @@ Zotero.Attachments = new function(){
|
||||||
case 'image/png':
|
case 'image/png':
|
||||||
fileExt = 'png';
|
fileExt = 'png';
|
||||||
break;
|
break;
|
||||||
|
case 'image/jpeg':
|
||||||
|
fileExt = 'jpg';
|
||||||
|
break;
|
||||||
|
case 'image/jpg':
|
||||||
|
fileExt = 'jpg';
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported embedded image content type '${contentType}`);
|
throw new Error(`Unsupported embedded image content type '${contentType}`);
|
||||||
|
@ -385,6 +391,11 @@ Zotero.Attachments = new function(){
|
||||||
attachmentItem = new Zotero.Item('attachment');
|
attachmentItem = new Zotero.Item('attachment');
|
||||||
let { libraryID: parentLibraryID } = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
|
let { libraryID: parentLibraryID } = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
|
||||||
attachmentItem.libraryID = parentLibraryID;
|
attachmentItem.libraryID = parentLibraryID;
|
||||||
|
if (itemKey) {
|
||||||
|
// Let it fail if the key already exists
|
||||||
|
attachmentItem.key = itemKey;
|
||||||
|
await attachmentItem.loadPrimaryData();
|
||||||
|
}
|
||||||
attachmentItem.parentID = parentItemID;
|
attachmentItem.parentID = parentItemID;
|
||||||
attachmentItem.attachmentLinkMode = this.LINK_MODE_EMBEDDED_IMAGE;
|
attachmentItem.attachmentLinkMode = this.LINK_MODE_EMBEDDED_IMAGE;
|
||||||
attachmentItem.attachmentPath = 'storage:' + filename;
|
attachmentItem.attachmentPath = 'storage:' + filename;
|
||||||
|
|
|
@ -1936,7 +1936,7 @@ Zotero.Item.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
|
||||||
|
|
||||||
|
|
||||||
Zotero.Item.prototype.isRegularItem = function() {
|
Zotero.Item.prototype.isRegularItem = function() {
|
||||||
return !(this.isNote() || this.isAttachment());
|
return !(this.isNote() || this.isAttachment() || this.isAnnotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,9 @@
|
||||||
|
|
||||||
Zotero.Notes = new function() {
|
Zotero.Notes = new function() {
|
||||||
this.noteToTitle = noteToTitle;
|
this.noteToTitle = noteToTitle;
|
||||||
|
// Currently active editor instances
|
||||||
|
this.editorInstances = [];
|
||||||
|
|
||||||
|
|
||||||
this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 120; });
|
this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 120; });
|
||||||
this.__defineGetter__("defaultNote", function () { return '<div class="zotero-note znv1"></div>'; });
|
this.__defineGetter__("defaultNote", function () { return '<div class="zotero-note znv1"></div>'; });
|
||||||
|
@ -60,6 +63,46 @@ Zotero.Notes = new function() {
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces local URIs for citation and highlight nodes
|
||||||
|
*
|
||||||
|
* Must be called just before the initial sync,
|
||||||
|
* if called later the item version will be increased,
|
||||||
|
* which might be incovenient for the future (better) notes sync
|
||||||
|
*
|
||||||
|
* @param item Note item
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
this.updateURIs = async (item) => {
|
||||||
|
let html = item.getNote();
|
||||||
|
let num = 0;
|
||||||
|
// "uri":"http://zotero.org/users/local/(.+?)/items/(.+?)"
|
||||||
|
let regex = new RegExp(/%22uri%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2Flocal%2F(.+?)%2Fitems%2F(.+?)%22/g);
|
||||||
|
html = html.replace(regex, function (m, g1, g2) {
|
||||||
|
num++;
|
||||||
|
let libraryID = Zotero.URI.getURILibrary('http://zotero.org/users/local/' + g1);
|
||||||
|
let libraryURI = Zotero.URI.getLibraryURI(libraryID);
|
||||||
|
return encodeURIComponent('"uri":"' + libraryURI + '/items/' + g2 + '"');
|
||||||
|
});
|
||||||
|
if (num) {
|
||||||
|
item.setNote(html);
|
||||||
|
// Cut off saving for each editor instance for this item,
|
||||||
|
// to make sure none of the editor instances will concurrently
|
||||||
|
// overwrite our changes
|
||||||
|
this.editorInstances.forEach(editorInstance => {
|
||||||
|
if (editorInstance.item.id === item.id) {
|
||||||
|
editorInstance.disableSaving = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Although, theoretically, a new editor instance with the old data can still
|
||||||
|
// be created while asynchronous `item.saveTx` is in progress, but really unlikely
|
||||||
|
|
||||||
|
// Observer notification will automatically recreate the affected editor instances
|
||||||
|
await item.saveTx();
|
||||||
|
Zotero.debug(`Updated URIs for item ${item.id}: ${num}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof process === 'object' && process + '' === '[object process]'){
|
if (typeof process === 'object' && process + '' === '[object process]'){
|
||||||
|
|
|
@ -330,6 +330,8 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
|
||||||
let newSearchItems = yield this.collectionTreeRow.getItems();
|
let newSearchItems = yield this.collectionTreeRow.getItems();
|
||||||
// TEMP: Hide annotations
|
// TEMP: Hide annotations
|
||||||
newSearchItems = newSearchItems.filter(item => !item.isAnnotation());
|
newSearchItems = newSearchItems.filter(item => !item.isAnnotation());
|
||||||
|
// A temporary workaround to make item tree crash less often
|
||||||
|
newSearchItems = newSearchItems.filter(item => !(item.isAttachment() && item.attachmentLinkMode === Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE));
|
||||||
// Remove notes and attachments if necessary
|
// Remove notes and attachments if necessary
|
||||||
if (this.regularOnly) {
|
if (this.regularOnly) {
|
||||||
newSearchItems = newSearchItems.filter(item => item.isRegularItem());
|
newSearchItems = newSearchItems.filter(item => item.isRegularItem());
|
||||||
|
|
168
chrome/content/zotero/xpcom/pdfExport.js
Normal file
168
chrome/content/zotero/xpcom/pdfExport.js
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
class PDFExport {
|
||||||
|
constructor() {
|
||||||
|
this._queue = [];
|
||||||
|
this._queueProcessing = false;
|
||||||
|
this._processingItemID = null;
|
||||||
|
this._progressQueue = Zotero.ProgressQueues.create({
|
||||||
|
id: 'pdf-export',
|
||||||
|
title: 'pdfExport.title',
|
||||||
|
columns: [
|
||||||
|
'recognizePDF.pdfName.label',
|
||||||
|
'pdfImport.annotations.label'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this._progressQueue.addListener('cancel', () => {
|
||||||
|
this._queue = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAnnotations(item) {
|
||||||
|
item._loaded.childItems = true;
|
||||||
|
return item.isAttachment() && item.getAnnotations().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
canExport(item) {
|
||||||
|
if (this.hasAnnotations(item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (item.isRegularItem()) {
|
||||||
|
let ids = item.getAttachments();
|
||||||
|
for (let id of ids) {
|
||||||
|
let attachment = Zotero.Items.get(id);
|
||||||
|
if (this.hasAnnotations(attachment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers queue processing and returns when all items in the queue are processed
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
async _processQueue() {
|
||||||
|
// await Zotero.Schema.schemaUpdatePromise;
|
||||||
|
|
||||||
|
if (this._queueProcessing) return;
|
||||||
|
this._queueProcessing = true;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
let data = this._queue.pop();
|
||||||
|
if (!data) break;
|
||||||
|
|
||||||
|
let { itemID, path } = data;
|
||||||
|
|
||||||
|
this._processingItemID = itemID;
|
||||||
|
|
||||||
|
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_PROCESSING, Zotero.getString('general.processing'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
let item = await Zotero.Items.getAsync(itemID);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let num = await this._exportItemAnnotations(item, path);
|
||||||
|
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_SUCCEEDED, num);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
|
||||||
|
this._progressQueue.updateRow(
|
||||||
|
itemID,
|
||||||
|
Zotero.ProgressQueue.ROW_FAILED,
|
||||||
|
e instanceof Zotero.Exception.Alert
|
||||||
|
? e.message
|
||||||
|
: Zotero.getString('general.error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._queueProcessing = false;
|
||||||
|
this._processingItemID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds items to the queue and triggers processing
|
||||||
|
* @param {Zotero.Item[]} items
|
||||||
|
*/
|
||||||
|
async export(items) {
|
||||||
|
let pdfItems = [];
|
||||||
|
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
items = [items];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let item of items) {
|
||||||
|
if (this.hasAnnotations(item)) {
|
||||||
|
pdfItems.push(item);
|
||||||
|
}
|
||||||
|
else if (item.isRegularItem()) {
|
||||||
|
let ids = item.getAttachments();
|
||||||
|
for (let id of ids) {
|
||||||
|
let attachment = Zotero.Items.get(id);
|
||||||
|
if (this.hasAnnotations(attachment)) {
|
||||||
|
pdfItems.push(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let item of pdfItems) {
|
||||||
|
if (
|
||||||
|
this._processingItemID === item.id ||
|
||||||
|
this._queue.find(x => x.itemID === item.id)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this._queue.unshift({ itemID: item.id });
|
||||||
|
this._progressQueue.addRow(item);
|
||||||
|
}
|
||||||
|
await this._processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportToPath(item, path, isPriority) {
|
||||||
|
if (isPriority) {
|
||||||
|
this._queue.push({ itemID: item.id, path });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._queue.unshift({ itemID: item.id, path });
|
||||||
|
}
|
||||||
|
this._progressQueue.addRow(item);
|
||||||
|
await this._processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _exportItemAnnotations(item, path) {
|
||||||
|
let ids = item.getAnnotations();
|
||||||
|
|
||||||
|
let annotations = [];
|
||||||
|
for (let id of ids) {
|
||||||
|
try {
|
||||||
|
annotations.push(Zotero.Annotations.toJSON(Zotero.Items.get(id)));
|
||||||
|
} catch (e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations.id = annotations.key;
|
||||||
|
// annotations.image = annotations.imageURL;
|
||||||
|
|
||||||
|
for (let annotation of annotations) {
|
||||||
|
delete annotation.key;
|
||||||
|
for (let key in annotation) {
|
||||||
|
annotation[key] = annotation[key] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
annotation.authorName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
await Zotero.PDFWorker.writeAnnotations(item.id, annotations, path);
|
||||||
|
return annotations.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.PDFExport = new PDFExport();
|
167
chrome/content/zotero/xpcom/pdfImport.js
Normal file
167
chrome/content/zotero/xpcom/pdfImport.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// TODO: Import ToC
|
||||||
|
|
||||||
|
class PdfImport {
|
||||||
|
constructor() {
|
||||||
|
this._queue = [];
|
||||||
|
this._queueProcessing = false;
|
||||||
|
this._processingItemID = null;
|
||||||
|
this._progressQueue = Zotero.ProgressQueues.create({
|
||||||
|
id: 'pdf-import',
|
||||||
|
title: 'pdfImport.title',
|
||||||
|
columns: [
|
||||||
|
'recognizePDF.pdfName.label',
|
||||||
|
'pdfImport.annotations.label'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this._progressQueue.addListener('cancel', () => {
|
||||||
|
this._queue = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isPDFAttachment(item) {
|
||||||
|
return item.isAttachment() && item.attachmentContentType === 'application/pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
canImport(item) {
|
||||||
|
if (this.isPDFAttachment(item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (item.isRegularItem()) {
|
||||||
|
let ids = item.getAttachments();
|
||||||
|
for (let id of ids) {
|
||||||
|
let attachment = Zotero.Items.get(id);
|
||||||
|
if (this.isPDFAttachment(attachment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers queue processing and returns when all items in the queue are processed
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
async _processQueue() {
|
||||||
|
// await Zotero.Schema.schemaUpdatePromise;
|
||||||
|
|
||||||
|
if (this._queueProcessing) return;
|
||||||
|
this._queueProcessing = true;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
let itemID = this._queue.pop();
|
||||||
|
if (!itemID) break;
|
||||||
|
|
||||||
|
this._processingItemID = itemID;
|
||||||
|
|
||||||
|
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_PROCESSING, Zotero.getString('general.processing'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
let item = await Zotero.Items.getAsync(itemID);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let num = await this._importItemAnnotations(item);
|
||||||
|
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_SUCCEEDED, num);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
|
||||||
|
this._progressQueue.updateRow(
|
||||||
|
itemID,
|
||||||
|
Zotero.ProgressQueue.ROW_FAILED,
|
||||||
|
e instanceof Zotero.Exception.Alert
|
||||||
|
? e.message
|
||||||
|
: Zotero.getString('general.error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._queueProcessing = false;
|
||||||
|
this._processingItemID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds items to the queue and triggers processing
|
||||||
|
* @param {Zotero.Item[]} items
|
||||||
|
*/
|
||||||
|
async import(items, isPriority) {
|
||||||
|
let pdfItems = [];
|
||||||
|
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
items = [items];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let item of items) {
|
||||||
|
if (this.isPDFAttachment(item)) {
|
||||||
|
pdfItems.push(item);
|
||||||
|
}
|
||||||
|
else if (item.isRegularItem()) {
|
||||||
|
let ids = item.getAttachments();
|
||||||
|
for (let id of ids) {
|
||||||
|
let attachment = Zotero.Items.get(id);
|
||||||
|
if (this.isPDFAttachment(attachment)) {
|
||||||
|
pdfItems.push(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let item of pdfItems) {
|
||||||
|
if (
|
||||||
|
this._processingItemID === item.id ||
|
||||||
|
this._queue.includes(item.id)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this._queue.unshift(item.id);
|
||||||
|
this._progressQueue.addRow(item);
|
||||||
|
}
|
||||||
|
await this._processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
similarAnnotions(annotation1, annotation2) {
|
||||||
|
return (annotation1.position.pageIndex === annotation2.position.pageIndex &&
|
||||||
|
JSON.stringify(annotation1.position.rects) === JSON.stringify(annotation2.position.rects));
|
||||||
|
}
|
||||||
|
|
||||||
|
async _importItemAnnotations(item) {
|
||||||
|
if (!item.isAttachment() || item.attachmentContentType !== 'application/pdf') {
|
||||||
|
throw new Error('Not a valid PDF attachment');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove when fixed
|
||||||
|
item._loaded.childItems = true;
|
||||||
|
let ids = item.getAnnotations();
|
||||||
|
let existingAnnotations = [];
|
||||||
|
for (let id of ids) {
|
||||||
|
try {
|
||||||
|
existingAnnotations.push(Zotero.Annotations.toJSON(Zotero.Items.get(id)));
|
||||||
|
} catch (e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let annotations = await Zotero.PDFWorker.readAnnotations(item.id);
|
||||||
|
annotations = annotations.filter(x => ['highlight', 'note'].includes(x.type));
|
||||||
|
|
||||||
|
let num = 0;
|
||||||
|
for (let annotation of annotations) {
|
||||||
|
annotation.comment = annotation.comment || '';
|
||||||
|
if (existingAnnotations.some(existingAnnotation => this.similarAnnotions(existingAnnotation, annotation))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Utilize the saved Zotero item key for deduplication
|
||||||
|
annotation.key = Zotero.DataObjectUtilities.generateKey();
|
||||||
|
let annotationItem = await Zotero.Annotations.saveFromJSON(item, annotation);
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.PDFImport = new PdfImport();
|
106
chrome/content/zotero/xpcom/pdfWorker/transport.js
Normal file
106
chrome/content/zotero/xpcom/pdfWorker/transport.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
|
||||||
|
const CMAPS_URL = 'resource://zotero/pdf.js/cmaps/';
|
||||||
|
|
||||||
|
class PDFWorker {
|
||||||
|
constructor() {
|
||||||
|
this.worker = null;
|
||||||
|
this.promiseId = 0;
|
||||||
|
this.waitingPromises = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async query(op, data, transfer) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.promiseId++;
|
||||||
|
this.waitingPromises[this.promiseId] = {resolve, reject};
|
||||||
|
this.worker.postMessage({id: this.promiseId, op, data}, transfer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this.worker) return;
|
||||||
|
this.worker = new Worker('chrome://zotero/content/xpcom/pdfWorker/worker.js');
|
||||||
|
|
||||||
|
this.worker.addEventListener('message', async e => {
|
||||||
|
let message = e.data;
|
||||||
|
// console.log(e.data)
|
||||||
|
if (message.responseId) {
|
||||||
|
let { resolve, reject } = this.waitingPromises[message.responseId];
|
||||||
|
if (message.data) {
|
||||||
|
resolve(message.data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(message.error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.id) {
|
||||||
|
let respData = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (message.op === 'FetchBuiltInCMap') {
|
||||||
|
let response = await Zotero.HTTP.request(
|
||||||
|
"GET",
|
||||||
|
CMAPS_URL + e.data.data.name + '.bcmap',
|
||||||
|
{responseType: 'arraybuffer'}
|
||||||
|
);
|
||||||
|
respData = {
|
||||||
|
compressionType: 1,
|
||||||
|
cMapData: new Uint8Array(response.response)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.debug('Failed to fetch CMap data:');
|
||||||
|
Zotero.debug(e);
|
||||||
|
}
|
||||||
|
this.worker.postMessage({responseId: e.data.id, data: respData});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.worker.addEventListener('error', e => {
|
||||||
|
Zotero.debug('PDF Web Worker error:');
|
||||||
|
Zotero.debug(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeAnnotations(itemID, annotations, path) {
|
||||||
|
Zotero.debug("Writing annotations");
|
||||||
|
this.init();
|
||||||
|
|
||||||
|
|
||||||
|
let password = '';
|
||||||
|
|
||||||
|
let item = await Zotero.Items.getAsync(itemID);
|
||||||
|
let itemFilePath = await item.getFilePath();
|
||||||
|
|
||||||
|
let buf = await OS.File.read(itemFilePath, {});
|
||||||
|
buf = new Uint8Array(buf).buffer;
|
||||||
|
|
||||||
|
let res = await this.query('write', {buf, annotations, password}, [buf]);
|
||||||
|
|
||||||
|
if (!path) {
|
||||||
|
path = itemFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
await OS.File.writeAtomic(path, new Uint8Array(res.buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
async readAnnotations(itemID) {
|
||||||
|
this.init();
|
||||||
|
|
||||||
|
let password = '';
|
||||||
|
|
||||||
|
let item = await Zotero.Items.getAsync(itemID);
|
||||||
|
let path = await item.getFilePath();
|
||||||
|
|
||||||
|
let buf = await OS.File.read(path, {});
|
||||||
|
buf = new Uint8Array(buf).buffer;
|
||||||
|
|
||||||
|
let res = await this.query('read', {buf, password}, [buf]);
|
||||||
|
|
||||||
|
return res.annotations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.PDFWorker = new PDFWorker();
|
523
chrome/content/zotero/xpcom/viewer.js
Normal file
523
chrome/content/zotero/xpcom/viewer.js
Normal file
|
@ -0,0 +1,523 @@
|
||||||
|
let PDFStates = {};
|
||||||
|
|
||||||
|
const COLORS = [
|
||||||
|
['Red', '#ff6666'],
|
||||||
|
['Orange', '#ff8c19'],
|
||||||
|
['Green', '#5fb236'],
|
||||||
|
['Blue', '#2ea8e5'],
|
||||||
|
['Purple', '#a28ae5']
|
||||||
|
];
|
||||||
|
|
||||||
|
class ViewerWindow {
|
||||||
|
constructor() {
|
||||||
|
this._window = null;
|
||||||
|
this._iframeWindow = null;
|
||||||
|
this.popupData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataURLtoBlob(dataurl) {
|
||||||
|
let parts = dataurl.split(',');
|
||||||
|
let mime = parts[0].match(/:(.*?);/)[1];
|
||||||
|
if (parts[0].indexOf('base64') !== -1) {
|
||||||
|
let bstr = atob(parts[1]);
|
||||||
|
let n = bstr.length;
|
||||||
|
let u8arr = new Uint8Array(n);
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new this._iframeWindow.Blob([u8arr], { type: mime });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleNoteSidebar(isToggled) {
|
||||||
|
let splitter = this._window.document.getElementById('zotero-viewer-splitter');
|
||||||
|
let sidebar = this._window.document.getElementById('zotero-viewer-note-sidebar');
|
||||||
|
|
||||||
|
if (isToggled) {
|
||||||
|
splitter.hidden = false;
|
||||||
|
sidebar.hidden = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
splitter.hidden = true;
|
||||||
|
sidebar.hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openAnnotationPopup(x, y, annotationId, selectedColor) {
|
||||||
|
let popup = this._window.document.getElementById('annotationPopup');
|
||||||
|
popup.hidePopup();
|
||||||
|
|
||||||
|
while (popup.firstChild) {
|
||||||
|
popup.removeChild(popup.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
let menuitem = this._window.document.createElement('menuitem');
|
||||||
|
menuitem.setAttribute('label', 'Delete');
|
||||||
|
menuitem.addEventListener('command', () => {
|
||||||
|
let data = {
|
||||||
|
action: 'popupCmd',
|
||||||
|
cmd: 'deleteAnnotation',
|
||||||
|
id: this.popupData.id
|
||||||
|
};
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
});
|
||||||
|
popup.appendChild(menuitem);
|
||||||
|
|
||||||
|
popup.appendChild(this._window.document.createElement('menuseparator'));
|
||||||
|
|
||||||
|
for (let color of COLORS) {
|
||||||
|
menuitem = this._window.document.createElement('menuitem');
|
||||||
|
menuitem.setAttribute('label', color[0]);
|
||||||
|
menuitem.className = 'menuitem-iconic';
|
||||||
|
let stroke = color[1] === selectedColor ? 'lightgray' : 'transparent';
|
||||||
|
let fill = '%23' + color[1].slice(1);
|
||||||
|
menuitem.setAttribute('image', 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><circle shape-rendering="geometricPrecision" fill="' + fill + '" stroke-width="2" stroke="' + stroke + '" cx="8" cy="8" r="6"/></svg>');
|
||||||
|
menuitem.addEventListener('command', () => {
|
||||||
|
let data = {
|
||||||
|
action: 'popupCmd',
|
||||||
|
cmd: 'setAnnotationColor',
|
||||||
|
id: this.popupData.id,
|
||||||
|
color: color[1]
|
||||||
|
};
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
});
|
||||||
|
popup.appendChild(menuitem);
|
||||||
|
}
|
||||||
|
popup.openPopupAtScreen(x, y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
openColorPopup(x, y, selectedColor) {
|
||||||
|
let popup = this._window.document.getElementById('colorPopup');
|
||||||
|
popup.hidePopup();
|
||||||
|
|
||||||
|
while (popup.firstChild) {
|
||||||
|
popup.removeChild(popup.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
let menuitem;
|
||||||
|
|
||||||
|
for (let color of COLORS) {
|
||||||
|
menuitem = this._window.document.createElement('menuitem');
|
||||||
|
menuitem.setAttribute('label', color[0]);
|
||||||
|
menuitem.className = 'menuitem-iconic';
|
||||||
|
let stroke = color[1] === selectedColor ? 'lightgray' : 'transparent';
|
||||||
|
let fill = '%23' + color[1].slice(1);
|
||||||
|
menuitem.setAttribute('image', 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><circle shape-rendering="geometricPrecision" fill="' + fill + '" stroke-width="2" stroke="' + stroke + '" cx="8" cy="8" r="6"/></svg>');
|
||||||
|
menuitem.addEventListener('command', () => {
|
||||||
|
let data = {
|
||||||
|
action: 'popupCmd',
|
||||||
|
cmd: 'setColor',
|
||||||
|
color: color[1]
|
||||||
|
};
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
});
|
||||||
|
popup.appendChild(menuitem);
|
||||||
|
}
|
||||||
|
popup.openPopupAtScreen(x, y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||||
|
if (!win) return;
|
||||||
|
|
||||||
|
this._window = win.open(
|
||||||
|
'chrome://zotero/content/viewer.xul', '', 'chrome,resizable,centerscreen'
|
||||||
|
);
|
||||||
|
|
||||||
|
this._window.addEventListener('DOMContentLoaded', (e) => {
|
||||||
|
this._window.fillTooltip = (tooltip) => {
|
||||||
|
let node = this._window.document.tooltipNode.closest('*[title]');
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip.setAttribute('label', node.getAttribute('title'));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._window.menuCmd = (cmd) => {
|
||||||
|
|
||||||
|
if (cmd === 'export') {
|
||||||
|
let zp = Zotero.getActiveZoteroPane();
|
||||||
|
zp.exportPDF(this.itemID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
action: 'menuCmd',
|
||||||
|
cmd
|
||||||
|
};
|
||||||
|
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewerIframe = this._window.document.getElementById('viewer');
|
||||||
|
if (!(viewerIframe && viewerIframe.contentWindow && viewerIframe.contentWindow.document === e.target)) return;
|
||||||
|
|
||||||
|
let that = this;
|
||||||
|
let editor = this._window.document.getElementById('zotero-viewer-editor');
|
||||||
|
editor.navigateHandler = async function (uri, annotation) {
|
||||||
|
let item = await Zotero.URI.getURIItem(uri);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
that.open(item.id, annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this._iframeWindow = this._window.document.getElementById('viewer').contentWindow;
|
||||||
|
|
||||||
|
// In the iframe `window.performance` is null which firstly makes React to fail,
|
||||||
|
// because it expects `undefined` or `object` type, and secondly pdf.js is hardcoded
|
||||||
|
// to always use performance API
|
||||||
|
// By using the method below the performance API in the iframe appears not immediately,
|
||||||
|
// which can cause problems for scipts trying to access it too early
|
||||||
|
this._iframeWindow.performance = this._window.performance;
|
||||||
|
|
||||||
|
this._iframeWindow.addEventListener('message', async (e) => {
|
||||||
|
// Clone data to avoid the dead object error when the window is closed
|
||||||
|
let data = JSON.parse(JSON.stringify(e.data));
|
||||||
|
|
||||||
|
switch (data.action) {
|
||||||
|
case 'load':
|
||||||
|
this.load(data.libraryID, data.key);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'setAnnotation':
|
||||||
|
|
||||||
|
var item = await Zotero.Items.getAsync(this.itemID);
|
||||||
|
data.annotation.key = data.annotation.id;
|
||||||
|
var annotation = await Zotero.Annotations.saveFromJSON(item, data.annotation);
|
||||||
|
|
||||||
|
if (data.annotation.image) {
|
||||||
|
let blob = this.dataURLtoBlob(data.annotation.image);
|
||||||
|
let attachmentIds = annotation.getAttachments();
|
||||||
|
if (attachmentIds.length) {
|
||||||
|
let attachment = Zotero.Items.get(attachmentIds[0]);
|
||||||
|
var path = await attachment.getFilePathAsync();
|
||||||
|
await Zotero.File.putContentsAsync(path, blob);
|
||||||
|
await Zotero.Sync.Storage.Local.updateSyncStates([attachment], 'to_upload');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let imageAttachment = await Zotero.Attachments.importEmbeddedImage({
|
||||||
|
blob,
|
||||||
|
parentItemID: annotation.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'deleteAnnotations':
|
||||||
|
for (let id of data.ids) {
|
||||||
|
let item = Zotero.Items.getByLibraryAndKey(this.libraryID, id);
|
||||||
|
if (item) {
|
||||||
|
await Zotero.Items.trashTx([item.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'setState':
|
||||||
|
PDFStates[this.itemID] = data.state;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'openTagsPopup':
|
||||||
|
var item = Zotero.Items.getByLibraryAndKey(this.libraryID, data.id);
|
||||||
|
if (item) {
|
||||||
|
this._window.document.getElementById('tags').item = item;
|
||||||
|
this._window.document.getElementById('tagsPopup').openPopupAtScreen(data.x, data.y, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'openAnnotationPopup':
|
||||||
|
this.popupData = data;
|
||||||
|
this.openAnnotationPopup(data.x, data.y, data.id, data.selectedColor);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'openColorPopup':
|
||||||
|
this.popupData = data;
|
||||||
|
this.openColorPopup(data.x, data.y, data.selectedColor);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'openURL':
|
||||||
|
let win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||||
|
if (win) {
|
||||||
|
win.ZoteroPane.loadURI(data.url);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'import':
|
||||||
|
Zotero.debug('Importing PDF annotations');
|
||||||
|
let item1 = Zotero.Items.get(this.itemID);
|
||||||
|
Zotero.PDFImport.import(item1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'importDismiss':
|
||||||
|
Zotero.debug('Dismiss PDF annotations');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'save':
|
||||||
|
Zotero.debug('Exporting PDF');
|
||||||
|
var zp = Zotero.getActiveZoteroPane();
|
||||||
|
zp.exportPDF(this.itemID);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'toggleNoteSidebar':
|
||||||
|
this.toggleNoteSidebar(data.isToggled);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async waitForViewer() {
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
let n = 0;
|
||||||
|
while (!this._iframeWindow || !this._iframeWindow.eval('window.isDocumentReady')) {
|
||||||
|
if (n >= 500) {
|
||||||
|
throw new Error('Waiting for viewer failed');
|
||||||
|
}
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async waitForViewer2() {
|
||||||
|
let n = 0;
|
||||||
|
while (!this._iframeWindow) {
|
||||||
|
if (n >= 50) {
|
||||||
|
throw new Error('Waiting for viewer failed');
|
||||||
|
}
|
||||||
|
await Zotero.Promise.delay(10);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async open(itemID, annotation) {
|
||||||
|
await this.waitForViewer2();
|
||||||
|
|
||||||
|
let item = await Zotero.Items.getAsync(itemID);
|
||||||
|
if (!item) return;
|
||||||
|
let url = 'zotero://pdf.js/viewer.html?libraryID=' + item.libraryID + '&key=' + item.key;
|
||||||
|
if (url !== this._iframeWindow.location.href) {
|
||||||
|
this._iframeWindow.location = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navigate(annotation);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async load(libraryID, key) {
|
||||||
|
let item = await Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
|
||||||
|
this.itemID = item.id;
|
||||||
|
this.libraryID = item.libraryID;
|
||||||
|
|
||||||
|
let title = item.getField('title');
|
||||||
|
let parentItemID = item.parentItemID;
|
||||||
|
if (parentItemID) {
|
||||||
|
let parentItem = await Zotero.Items.getAsync(parentItemID);
|
||||||
|
if (parentItem) {
|
||||||
|
title = parentItem.getField('title');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._window.document.title = title;
|
||||||
|
Zotero.debug('Annots');
|
||||||
|
// TODO: Remove when fixed
|
||||||
|
item._loaded.childItems = true;
|
||||||
|
let ids = item.getAnnotations();
|
||||||
|
let annotations = ids.map(id => this.getAnnotation(id)).filter(x => x);
|
||||||
|
this.annotationIds = ids;
|
||||||
|
Zotero.debug(annotations);
|
||||||
|
let state = PDFStates[this.itemID];
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
action: 'open',
|
||||||
|
libraryID,
|
||||||
|
key,
|
||||||
|
itemId: item.itemID,
|
||||||
|
annotations,
|
||||||
|
state
|
||||||
|
};
|
||||||
|
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTitle() {
|
||||||
|
let item = Zotero.Items.get(this.itemID);
|
||||||
|
let title = item.getField('title');
|
||||||
|
let parentItemID = item.parentItemID;
|
||||||
|
if (parentItemID) {
|
||||||
|
let parentItem = Zotero.Items.get(parentItemID);
|
||||||
|
if (parentItem) {
|
||||||
|
title = parentItem.getField('title');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._window.document.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return item JSON in pdf-reader ready format
|
||||||
|
* @param itemID
|
||||||
|
* @returns {Object|null}
|
||||||
|
*/
|
||||||
|
getAnnotation(itemID) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
let item = Zotero.Items.get(itemID);
|
||||||
|
if (!item || !item.isAnnotation()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
item = Zotero.Annotations.toJSON(item);
|
||||||
|
|
||||||
|
item.id = item.key;
|
||||||
|
item.image = item.imageURL;
|
||||||
|
delete item.key;
|
||||||
|
for (let key in item) {
|
||||||
|
item[key] = item[key] || '';
|
||||||
|
}
|
||||||
|
item.tags = item.tags || [];
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnnotations(ids) {
|
||||||
|
Zotero.debug('set annots')
|
||||||
|
Zotero.debug(ids);
|
||||||
|
let annotations = [];
|
||||||
|
for (let id of ids) {
|
||||||
|
let annotation = this.getAnnotation(id);
|
||||||
|
if (annotation) {
|
||||||
|
annotations.push(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (annotations.length) {
|
||||||
|
let data = { action: 'setAnnotations', annotations };
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsetAnnotations(keys) {
|
||||||
|
Zotero.debug('unset annots')
|
||||||
|
Zotero.debug(keys)
|
||||||
|
let data = { action: 'unsetAnnotations', ids: keys };
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigate(annotation) {
|
||||||
|
if (!annotation) return;
|
||||||
|
await this.waitForViewer();
|
||||||
|
// TODO: Wait until the document is loaded
|
||||||
|
let data = {
|
||||||
|
action: 'navigate',
|
||||||
|
annotationId: annotation.id,
|
||||||
|
position: annotation.position,
|
||||||
|
to: annotation
|
||||||
|
};
|
||||||
|
this._iframeWindow.postMessage(data, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Viewer {
|
||||||
|
constructor() {
|
||||||
|
this._viewerWindows = [];
|
||||||
|
|
||||||
|
this.instanceID = Zotero.Utilities.randomString();
|
||||||
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'viewer');
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(event, type, ids, extraData) {
|
||||||
|
// Listen for the parent item, PDF attachment and its annotation items updates
|
||||||
|
// TODO: Skip events that emerge in the current pdf-reader window
|
||||||
|
Zotero.debug('notification received')
|
||||||
|
Zotero.debug(event)
|
||||||
|
Zotero.debug(type)
|
||||||
|
Zotero.debug(ids)
|
||||||
|
Zotero.debug(extraData)
|
||||||
|
for (let viewerWindow of this._viewerWindows) {
|
||||||
|
if (event === 'delete') {
|
||||||
|
let disappearedIds = viewerWindow.annotationIds.filter(x => ids.includes(x));
|
||||||
|
if (disappearedIds.length) {
|
||||||
|
let keys = disappearedIds.map(id => extraData[id].itemKey);
|
||||||
|
viewerWindow.unsetAnnotations(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ids.includes(viewerWindow.itemID)) {
|
||||||
|
viewerWindow.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Check if any annotation is involved
|
||||||
|
let item = Zotero.Items.get(viewerWindow.itemID);
|
||||||
|
// TODO: Remove when fixed
|
||||||
|
item._loaded.childItems = true;
|
||||||
|
let annotationIds = item.getAnnotations();
|
||||||
|
viewerWindow.annotationIds = annotationIds;
|
||||||
|
let affectedAnnotationIds = annotationIds.filter(x => ids.includes(x));
|
||||||
|
if (affectedAnnotationIds.length) {
|
||||||
|
viewerWindow.setAnnotations(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update title if the PDF attachment or the parent item changes
|
||||||
|
if (ids.includes(viewerWindow.itemID) || ids.includes(item.parentItemID)) {
|
||||||
|
viewerWindow.updateTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getViewerWindow(itemID) {
|
||||||
|
return this._viewerWindows.find(v => v.itemID === itemID);
|
||||||
|
}
|
||||||
|
|
||||||
|
async openURI(itemURI, annotation) {
|
||||||
|
let item = await Zotero.URI.getURIItem(itemURI);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
this.open(item.id, annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(itemID, annotation) {
|
||||||
|
let viewer = this._getViewerWindow(itemID);
|
||||||
|
|
||||||
|
if (viewer) {
|
||||||
|
if (annotation) {
|
||||||
|
viewer.navigate(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
viewer = new ViewerWindow();
|
||||||
|
viewer.init();
|
||||||
|
if (!(await viewer.open(itemID))) return;
|
||||||
|
this._viewerWindows.push(viewer);
|
||||||
|
viewer._window.addEventListener('close', () => {
|
||||||
|
this._viewerWindows.splice(this._viewerWindows.indexOf(viewer), 1);
|
||||||
|
});
|
||||||
|
viewer.navigate(annotation);
|
||||||
|
}
|
||||||
|
viewer._window.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.Viewer = new Viewer();
|
|
@ -2712,6 +2712,8 @@ var ZoteroPane = new function()
|
||||||
'createParent',
|
'createParent',
|
||||||
'renameAttachments',
|
'renameAttachments',
|
||||||
'reindexItem',
|
'reindexItem',
|
||||||
|
'importAnnotations',
|
||||||
|
'exportAnnotations'
|
||||||
];
|
];
|
||||||
|
|
||||||
var m = {};
|
var m = {};
|
||||||
|
@ -2758,7 +2760,9 @@ var ZoteroPane = new function()
|
||||||
canIndex = true,
|
canIndex = true,
|
||||||
canRecognize = true,
|
canRecognize = true,
|
||||||
canUnrecognize = true,
|
canUnrecognize = true,
|
||||||
canRename = true;
|
canRename = true,
|
||||||
|
canImportAnnotations = true,
|
||||||
|
canExportAnnotations = true;
|
||||||
var canMarkRead = collectionTreeRow.isFeed();
|
var canMarkRead = collectionTreeRow.isFeed();
|
||||||
var markUnread = true;
|
var markUnread = true;
|
||||||
|
|
||||||
|
@ -2780,6 +2784,14 @@ var ZoteroPane = new function()
|
||||||
canUnrecognize = false;
|
canUnrecognize = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canImportAnnotations && !Zotero.PDFImport.canImport(item)) {
|
||||||
|
canImportAnnotations = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canExportAnnotations && !Zotero.PDFExport.canExport(item)) {
|
||||||
|
canExportAnnotations = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Show rename option only if all items are child attachments
|
// Show rename option only if all items are child attachments
|
||||||
if (canRename && (!item.isAttachment() || item.isTopLevelItem() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)) {
|
if (canRename && (!item.isAttachment() || item.isTopLevelItem() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)) {
|
||||||
canRename = false;
|
canRename = false;
|
||||||
|
@ -2858,6 +2870,14 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canImportAnnotations) {
|
||||||
|
show.push(m.importAnnotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canExportAnnotations) {
|
||||||
|
show.push(m.exportAnnotations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single item selected
|
// Single item selected
|
||||||
|
@ -2926,6 +2946,14 @@ var ZoteroPane = new function()
|
||||||
else if (!collectionTreeRow.isPublications()) {
|
else if (!collectionTreeRow.isPublications()) {
|
||||||
show.push(m.duplicateItem);
|
show.push(m.duplicateItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Zotero.PDFImport.canImport(item)) {
|
||||||
|
show.push(m.importAnnotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Zotero.PDFExport.canExport(item)) {
|
||||||
|
show.push(m.exportAnnotations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update attachment submenu
|
// Update attachment submenu
|
||||||
|
@ -3516,10 +3544,19 @@ var ZoteroPane = new function()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.onNoteWindowClosed = async function (itemID, noteText) {
|
this.onNoteWindowClosed = async function (itemID, noteData) {
|
||||||
var item = Zotero.Items.get(itemID);
|
var item = Zotero.Items.get(itemID);
|
||||||
item.setNote(noteText);
|
|
||||||
await item.saveTx();
|
if (noteData) {
|
||||||
|
let changed = item.setNote(noteData.html);
|
||||||
|
if (changed) {
|
||||||
|
await item.saveTx({
|
||||||
|
notifierData: {
|
||||||
|
state: noteData.state
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If note is still selected, show the editor again when the note window closes
|
// If note is still selected, show the editor again when the note window closes
|
||||||
var selectedItems = this.getSelectedItems(true);
|
var selectedItems = this.getSelectedItems(true);
|
||||||
|
@ -4000,9 +4037,12 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var launchFile = async function (path, contentType) {
|
var launchFile = async (path, contentType, itemID) => {
|
||||||
// Custom PDF handler
|
// Custom PDF handler
|
||||||
if (contentType === 'application/pdf') {
|
if (contentType === 'application/pdf') {
|
||||||
|
this.viewPDF(itemID);
|
||||||
|
// TODO: Still leave an option to use an external PDF viewer
|
||||||
|
return;
|
||||||
let pdfHandler = Zotero.Prefs.get("fileHandler.pdf");
|
let pdfHandler = Zotero.Prefs.get("fileHandler.pdf");
|
||||||
if (pdfHandler) {
|
if (pdfHandler) {
|
||||||
if (await OS.File.exists(pdfHandler)) {
|
if (await OS.File.exists(pdfHandler)) {
|
||||||
|
@ -4058,7 +4098,7 @@ var ZoteroPane = new function()
|
||||||
let iCloudPath = Zotero.File.getEvictedICloudPath(path);
|
let iCloudPath = Zotero.File.getEvictedICloudPath(path);
|
||||||
if (await OS.File.exists(iCloudPath)) {
|
if (await OS.File.exists(iCloudPath)) {
|
||||||
Zotero.debug("Triggering download of iCloud file");
|
Zotero.debug("Triggering download of iCloud file");
|
||||||
await launchFile(iCloudPath, item.attachmentContentType);
|
await launchFile(iCloudPath, item.attachmentContentType, itemID);
|
||||||
let time = new Date();
|
let time = new Date();
|
||||||
let maxTime = 5000;
|
let maxTime = 5000;
|
||||||
let revealed = false;
|
let revealed = false;
|
||||||
|
@ -4118,7 +4158,7 @@ var ZoteroPane = new function()
|
||||||
if (fileExists && !redownload) {
|
if (fileExists && !redownload) {
|
||||||
Zotero.debug("Opening " + path);
|
Zotero.debug("Opening " + path);
|
||||||
Zotero.Notifier.trigger('open', 'file', item.id);
|
Zotero.Notifier.trigger('open', 'file', item.id);
|
||||||
launchFile(path, item.attachmentContentType);
|
launchFile(path, item.attachmentContentType, item.id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4168,6 +4208,10 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.viewPDF = function (itemID) {
|
||||||
|
Zotero.Viewer.open(itemID);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
|
@ -4482,6 +4526,17 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.exportAnnotationsForSelected = async function () {
|
||||||
|
var items = ZoteroPane.getSelectedItems();
|
||||||
|
Zotero.PDFExport.export(items);
|
||||||
|
Zotero.ProgressQueues.get('pdf-export').getDialog().open();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.importAnnotationsForSelected = async function () {
|
||||||
|
var items = ZoteroPane.getSelectedItems();
|
||||||
|
Zotero.PDFImport.import(items);
|
||||||
|
Zotero.ProgressQueues.get('pdf-import').getDialog().open();
|
||||||
|
};
|
||||||
|
|
||||||
this.reportMetadataForSelected = async function () {
|
this.reportMetadataForSelected = async function () {
|
||||||
let items = ZoteroPane.getSelectedItems();
|
let items = ZoteroPane.getSelectedItems();
|
||||||
|
@ -4563,8 +4618,8 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.createEmptyParent = async function (item) {
|
this.createEmptyParent = async function (item) {
|
||||||
await Zotero.DB.executeTransaction(async function () {
|
await Zotero.DB.executeTransaction(async function () {
|
||||||
// TODO: remove once there are no top-level web attachments
|
// TODO: remove once there are no top-level web attachments
|
||||||
|
@ -4585,7 +4640,27 @@ var ZoteroPane = new function()
|
||||||
await item.save();
|
await item.save();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.exportPDF = async function (itemID) {
|
||||||
|
let item = await Zotero.Items.getAsync(itemID);
|
||||||
|
if (!item || !item.isAttachment()) {
|
||||||
|
throw new Error('Item ' + itemID + ' is not attachment');
|
||||||
|
}
|
||||||
|
let filename = item.attachmentFilename;
|
||||||
|
|
||||||
|
var fp = new FilePicker();
|
||||||
|
fp.init(window, Zotero.getString('styles.editor.save'), fp.modeSave);
|
||||||
|
fp.appendFilter("PDF", "*.pdf");
|
||||||
|
fp.defaultString = filename;
|
||||||
|
|
||||||
|
var rv = await fp.show();
|
||||||
|
if (rv === fp.returnOK || rv === fp.returnReplace) {
|
||||||
|
let outputFile = fp.file;
|
||||||
|
Zotero.PDFExport.exportToPath(item, outputFile, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
this.renameSelectedAttachmentsFromParents = Zotero.Promise.coroutine(function* () {
|
this.renameSelectedAttachmentsFromParents = Zotero.Promise.coroutine(function* () {
|
||||||
// TEMP: fix
|
// TEMP: fix
|
||||||
|
|
|
@ -290,6 +290,10 @@
|
||||||
<menuitem class="menuitem-iconic zotero-menuitem-create-parent" oncommand="ZoteroPane_Local.createParentItemsFromSelected();"/>
|
<menuitem class="menuitem-iconic zotero-menuitem-create-parent" oncommand="ZoteroPane_Local.createParentItemsFromSelected();"/>
|
||||||
<menuitem class="menuitem-iconic zotero-menuitem-rename-from-parent" oncommand="ZoteroPane_Local.renameSelectedAttachmentsFromParents()"/>
|
<menuitem class="menuitem-iconic zotero-menuitem-rename-from-parent" oncommand="ZoteroPane_Local.renameSelectedAttachmentsFromParents()"/>
|
||||||
<menuitem class="menuitem-iconic zotero-menuitem-reindex" oncommand="ZoteroPane_Local.reindexItem();"/>
|
<menuitem class="menuitem-iconic zotero-menuitem-reindex" oncommand="ZoteroPane_Local.reindexItem();"/>
|
||||||
|
<!-- <menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="&zotero.items.menu.importAnnotations;" oncommand="ZoteroPane.importAnnotationsForSelected()"/>-->
|
||||||
|
<menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="Import annotations" oncommand="ZoteroPane.importAnnotationsForSelected()"/>
|
||||||
|
<!-- <menuitem class="menuitem-iconic zotero-menuitem-export-annotations" label="&zotero.items.menu.exportAnnotations;" oncommand="ZoteroPane.exportAnnotationsForSelected()"/>-->
|
||||||
|
<menuitem class="menuitem-iconic zotero-menuitem-export-annotations" label="Export Annotations" oncommand="ZoteroPane.exportAnnotationsForSelected()"/>
|
||||||
</menupopup>
|
</menupopup>
|
||||||
|
|
||||||
<tooltip id="fake-tooltip"/>
|
<tooltip id="fake-tooltip"/>
|
||||||
|
|
|
@ -100,6 +100,8 @@
|
||||||
<!ENTITY zotero.items.menu.mergeItems "Merge Items…">
|
<!ENTITY zotero.items.menu.mergeItems "Merge Items…">
|
||||||
<!ENTITY zotero.items.menu.unrecognize "Undo Retrieve Metadata">
|
<!ENTITY zotero.items.menu.unrecognize "Undo Retrieve Metadata">
|
||||||
<!ENTITY zotero.items.menu.reportMetadata "Report Incorrect Metadata">
|
<!ENTITY zotero.items.menu.reportMetadata "Report Incorrect Metadata">
|
||||||
|
<!ENTITY zotero.items.menu.exportAnnotations "Export Annotations">
|
||||||
|
<!ENTITY zotero.items.menu.importAnnotations "Import Annotations">
|
||||||
|
|
||||||
<!ENTITY zotero.duplicatesMerge.versionSelect "Choose the version of the item to use as the master item:">
|
<!ENTITY zotero.duplicatesMerge.versionSelect "Choose the version of the item to use as the master item:">
|
||||||
<!ENTITY zotero.duplicatesMerge.fieldSelect "Select fields to keep from other versions of the item:">
|
<!ENTITY zotero.duplicatesMerge.fieldSelect "Select fields to keep from other versions of the item:">
|
||||||
|
|
|
@ -1130,6 +1130,11 @@ recognizePDF.reportMetadata = Report Incorrect Metadata
|
||||||
recognizePDF.pdfName.label = PDF Name
|
recognizePDF.pdfName.label = PDF Name
|
||||||
recognizePDF.itemName.label = Item Name
|
recognizePDF.itemName.label = Item Name
|
||||||
|
|
||||||
|
pdfExport.title = PDF Annotations Export
|
||||||
|
|
||||||
|
pdfImport.title = PDF Annotations Import
|
||||||
|
pdfImport.annotations.label = Annotations
|
||||||
|
|
||||||
rtfScan.openTitle = Select a file to scan
|
rtfScan.openTitle = Select a file to scan
|
||||||
rtfScan.scanning.label = Scanning RTF Document…
|
rtfScan.scanning.label = Scanning RTF Document…
|
||||||
rtfScan.saving.label = Formatting RTF Document…
|
rtfScan.saving.label = Formatting RTF Document…
|
||||||
|
|
|
@ -622,6 +622,24 @@
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#zotero-tb-pq-pdf-export {
|
||||||
|
list-style-image: url(chrome://zotero/skin/pdf-search.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
#zotero-tb-pq-pdf-export .toolbarbutton-icon {
|
||||||
|
width: 18px;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#zotero-tb-pq-pdf-import {
|
||||||
|
list-style-image: url(chrome://zotero/skin/pdf-search.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
#zotero-tb-pq-pdf-import .toolbarbutton-icon {
|
||||||
|
width: 18px;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Sync error icon */
|
/* Sync error icon */
|
||||||
#zotero-tb-sync-error {
|
#zotero-tb-sync-error {
|
||||||
list-style-image: url(chrome://zotero/skin/error.png);
|
list-style-image: url(chrome://zotero/skin/error.png);
|
||||||
|
|
|
@ -46,6 +46,11 @@ zoteronoteeditor
|
||||||
-moz-binding: url('chrome://zotero/content/bindings/noteeditor.xml#note-editor');
|
-moz-binding: url('chrome://zotero/content/bindings/noteeditor.xml#note-editor');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoteronoteeditor2
|
||||||
|
{
|
||||||
|
-moz-binding: url('chrome://zotero/content/bindings/noteeditor2.xml#note-editor');
|
||||||
|
}
|
||||||
|
|
||||||
linksbox
|
linksbox
|
||||||
{
|
{
|
||||||
-moz-binding: url('chrome://zotero/content/bindings/noteeditor.xml#links-box');
|
-moz-binding: url('chrome://zotero/content/bindings/noteeditor.xml#links-box');
|
||||||
|
|
|
@ -1038,20 +1038,19 @@ function ZoteroProtocolHandler() {
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
zotero://pdf.js/web/viewer.html
|
zotero://pdf.js/viewer.html
|
||||||
zotero://pdf.js/pdf/1/ABCD5678
|
zotero://pdf.js/pdf/1/ABCD5678
|
||||||
*/
|
*/
|
||||||
var PDFJSExtension = {
|
var PDFJSExtension = {
|
||||||
loadAsChrome: false,
|
loadAsChrome: true,
|
||||||
|
|
||||||
newChannel: function (uri) {
|
newChannel: function (uri) {
|
||||||
return new AsyncChannel(uri, function* () {
|
return new AsyncChannel(uri, function* () {
|
||||||
try {
|
try {
|
||||||
uri = uri.spec;
|
uri = uri.spec;
|
||||||
// Proxy PDF.js files
|
// Proxy PDF.js files
|
||||||
if (uri.startsWith('zotero://pdf.js/web/')
|
if (uri.startsWith('zotero://pdf.js/') && !uri.startsWith('zotero://pdf.js/pdf/')) {
|
||||||
|| uri.startsWith('zotero://pdf.js/build/')) {
|
uri = uri.replace(/zotero:\/\/pdf.js\//, 'resource://zotero/pdf.js/');
|
||||||
uri = uri.replace(/zotero:\/\/pdf.js\//, 'resource://pdf.js/');
|
|
||||||
let newURI = Services.io.newURI(uri, null, null);
|
let newURI = Services.io.newURI(uri, null, null);
|
||||||
return this.getURIInputStream(newURI);
|
return this.getURIInputStream(newURI);
|
||||||
}
|
}
|
||||||
|
@ -1066,7 +1065,7 @@ function ZoteroProtocolHandler() {
|
||||||
|
|
||||||
var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return self._errorChannel("Item not found");
|
return this._errorChannel("Item not found");
|
||||||
}
|
}
|
||||||
var path = yield item.getFilePathAsync();
|
var path = yield item.getFilePathAsync();
|
||||||
if (!path) {
|
if (!path) {
|
||||||
|
@ -1260,6 +1259,16 @@ ZoteroProtocolHandler.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
newURI: function (spec, charset, baseURI) {
|
newURI: function (spec, charset, baseURI) {
|
||||||
|
// A temporary workaround because baseURI.resolve(spec) just returns spec
|
||||||
|
if (baseURI) {
|
||||||
|
if (!spec.includes('://') && baseURI.spec.includes('/pdf.js/')) {
|
||||||
|
let parts = baseURI.spec.split('/');
|
||||||
|
parts.pop();
|
||||||
|
parts.push(spec);
|
||||||
|
spec = parts.join('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Components.classes["@mozilla.org/network/simple-uri-mutator;1"]
|
return Components.classes["@mozilla.org/network/simple-uri-mutator;1"]
|
||||||
.createInstance(Components.interfaces.nsIURIMutator)
|
.createInstance(Components.interfaces.nsIURIMutator)
|
||||||
.setSpec(spec)
|
.setSpec(spec)
|
||||||
|
|
|
@ -47,6 +47,7 @@ const xpcomFilesAll = [
|
||||||
'http',
|
'http',
|
||||||
'mimeTypeHandler',
|
'mimeTypeHandler',
|
||||||
'openurl',
|
'openurl',
|
||||||
|
'pdfWorker/transport',
|
||||||
'ipc',
|
'ipc',
|
||||||
'profile',
|
'profile',
|
||||||
'progressWindow',
|
'progressWindow',
|
||||||
|
@ -105,10 +106,13 @@ const xpcomFilesLocal = [
|
||||||
'mime',
|
'mime',
|
||||||
'notifier',
|
'notifier',
|
||||||
'openPDF',
|
'openPDF',
|
||||||
|
'viewer',
|
||||||
'progressQueue',
|
'progressQueue',
|
||||||
'progressQueueDialog',
|
'progressQueueDialog',
|
||||||
'quickCopy',
|
'quickCopy',
|
||||||
'recognizePDF',
|
'recognizePDF',
|
||||||
|
'pdfExport',
|
||||||
|
'pdfImport',
|
||||||
'report',
|
'report',
|
||||||
'retractions',
|
'retractions',
|
||||||
'router',
|
'router',
|
||||||
|
|
1
pdf-reader
Submodule
1
pdf-reader
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit cb09ac97385b17bad5ef3cb70daa57fb998b309f
|
1
pdf-worker
Submodule
1
pdf-worker
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b855ed86d8f50261a4b5437d5894d32fe5389a67
|
|
@ -5,6 +5,9 @@ const getCopy = require('./copy');
|
||||||
const getJS = require('./js');
|
const getJS = require('./js');
|
||||||
const getSass = require('./sass');
|
const getSass = require('./sass');
|
||||||
const getSymlinks = require('./symlinks');
|
const getSymlinks = require('./symlinks');
|
||||||
|
const getPDFReader = require('./pdf-reader');
|
||||||
|
const getPDFWorker = require('./pdf-worker');
|
||||||
|
const getZoteroNoteEditor = require('./zotero-note-editor');
|
||||||
const { formatDirsForMatcher, getSignatures, writeSignatures, cleanUp, onSuccess, onError} = require('./utils');
|
const { formatDirsForMatcher, getSignatures, writeSignatures, cleanUp, onSuccess, onError} = require('./utils');
|
||||||
const { dirs, symlinkDirs, copyDirs, symlinkFiles, jsFiles, scssFiles, ignoreMask } = require('./config');
|
const { dirs, symlinkDirs, copyDirs, symlinkFiles, jsFiles, scssFiles, ignoreMask } = require('./config');
|
||||||
|
|
||||||
|
@ -27,7 +30,10 @@ if (require.main === module) {
|
||||||
getSass(scssFiles, { ignore: ignoreMask }, signatures),
|
getSass(scssFiles, { ignore: ignoreMask }, signatures),
|
||||||
getSymlinks(symlinks, { nodir: true, ignore: ignoreMask }, signatures),
|
getSymlinks(symlinks, { nodir: true, ignore: ignoreMask }, signatures),
|
||||||
getSymlinks(symlinkDirs, { ignore: ignoreMask }, signatures),
|
getSymlinks(symlinkDirs, { ignore: ignoreMask }, signatures),
|
||||||
cleanUp(signatures)
|
cleanUp(signatures),
|
||||||
|
getPDFReader(signatures),
|
||||||
|
getPDFWorker(signatures),
|
||||||
|
getZoteroNoteEditor(signatures)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await writeSignatures(signatures);
|
await writeSignatures(signatures);
|
||||||
|
|
|
@ -17,6 +17,9 @@ if (require.main === module) {
|
||||||
try {
|
try {
|
||||||
await getClean(path.join(ROOT, 'build'));
|
await getClean(path.join(ROOT, 'build'));
|
||||||
await getClean(path.join(ROOT, '.signatures.json'));
|
await getClean(path.join(ROOT, '.signatures.json'));
|
||||||
|
await getClean(path.join(ROOT, 'pdf-reader/build'));
|
||||||
|
await getClean(path.join(ROOT, 'pdf-worker/build'));
|
||||||
|
await getClean(path.join(ROOT, 'zotero-note-editor/build'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
global.isError = true;
|
global.isError = true;
|
||||||
|
|
61
scripts/pdf-reader.js
Normal file
61
scripts/pdf-reader.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const util = require('util');
|
||||||
|
const exec = util.promisify(require('child_process').exec);
|
||||||
|
const { getSignatures, writeSignatures, onSuccess, onError } = require('./utils');
|
||||||
|
|
||||||
|
async function getPDFReader(signatures) {
|
||||||
|
const t1 = Date.now();
|
||||||
|
|
||||||
|
var { stdout } = await exec('git rev-parse HEAD', { cwd: './pdf-reader/pdf.js' });
|
||||||
|
const PDFJSHash = stdout.trim();
|
||||||
|
|
||||||
|
var { stdout } = await exec('git rev-parse HEAD', { cwd: './pdf-reader' });
|
||||||
|
const PDFReaderHash = stdout.trim();
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
let name = 'pdf-reader/pdf.js';
|
||||||
|
if (!(name in signatures) || signatures[name].hash !== PDFJSHash) {
|
||||||
|
await exec('npm run build:pdf.js', { cwd: './pdf-reader' });
|
||||||
|
signatures[name] = { hash: PDFJSHash };
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = 'pdf-reader';
|
||||||
|
if (!(name in signatures) || signatures[name].hash !== PDFReaderHash) {
|
||||||
|
await exec('npm ci;npm run build:reader', { cwd: './pdf-reader' });
|
||||||
|
signatures[name] = { hash: PDFReaderHash };
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
await fs.copy('./pdf-reader/build/zotero', './build/resource/pdf.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
const t2 = Date.now();
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: 'pdf-reader',
|
||||||
|
count: 1,
|
||||||
|
totalCount: 1,
|
||||||
|
processingTime: t2 - t1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getPDFReader;
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const signatures = await getSignatures();
|
||||||
|
onSuccess(await getPDFReader(signatures));
|
||||||
|
await writeSignatures(signatures);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
process.exitCode = 1;
|
||||||
|
global.isError = true;
|
||||||
|
onError(err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
61
scripts/pdf-worker.js
Normal file
61
scripts/pdf-worker.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const util = require('util');
|
||||||
|
const exec = util.promisify(require('child_process').exec);
|
||||||
|
const { getSignatures, writeSignatures, onSuccess, onError } = require('./utils');
|
||||||
|
|
||||||
|
async function getPDFWorker(signatures) {
|
||||||
|
const t1 = Date.now();
|
||||||
|
|
||||||
|
var { stdout } = await exec('git rev-parse HEAD', { cwd: './pdf-worker/pdf.js' });
|
||||||
|
const PDFJSHash = stdout.trim();
|
||||||
|
|
||||||
|
var { stdout } = await exec('git rev-parse HEAD', { cwd: './pdf-worker' });
|
||||||
|
const PDFWorkerHash = stdout.trim();
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
let name = 'pdf-worker/pdf.js';
|
||||||
|
if (!(name in signatures) || signatures[name].hash !== PDFJSHash) {
|
||||||
|
await exec('npm run build:pdf.js', { cwd: './pdf-worker' });
|
||||||
|
signatures[name] = { hash: PDFJSHash };
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = 'pdf-worker';
|
||||||
|
if (!(name in signatures) || signatures[name].hash !== PDFWorkerHash) {
|
||||||
|
await exec('npm ci;npm run build:worker', { cwd: './pdf-worker' });
|
||||||
|
signatures[name] = { hash: PDFWorkerHash };
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
await fs.copy('./pdf-worker/build/pdf-worker.js', './build/chrome/content/zotero/xpcom/pdfWorker/worker.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
const t2 = Date.now();
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: 'pdf-worker',
|
||||||
|
count: 1,
|
||||||
|
totalCount: 1,
|
||||||
|
processingTime: t2 - t1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getPDFWorker;
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const signatures = await getSignatures();
|
||||||
|
onSuccess(await getPDFWorker(signatures));
|
||||||
|
await writeSignatures(signatures);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
process.exitCode = 1;
|
||||||
|
global.isError = true;
|
||||||
|
onError(err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
50
scripts/zotero-note-editor.js
Normal file
50
scripts/zotero-note-editor.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const util = require('util');
|
||||||
|
const exec = util.promisify(require('child_process').exec);
|
||||||
|
const { getSignatures, writeSignatures, onSuccess, onError } = require('./utils');
|
||||||
|
|
||||||
|
async function getZoteroNoteEditor(signatures) {
|
||||||
|
const t1 = Date.now();
|
||||||
|
|
||||||
|
var { stdout } = await exec('git rev-parse HEAD', { cwd: './zotero-note-editor' });
|
||||||
|
const zoteroNoteEditorHash = stdout.trim();
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
let name = 'zotero-note-editor';
|
||||||
|
if (!(name in signatures) || signatures[name].hash !== zoteroNoteEditorHash) {
|
||||||
|
await exec('npm ci;npm run build', { cwd: './zotero-note-editor' });
|
||||||
|
signatures[name] = { hash: zoteroNoteEditorHash };
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
await fs.copy('./zotero-note-editor/build/zotero', './build/resource/zotero-note-editor');
|
||||||
|
}
|
||||||
|
const t2 = Date.now();
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: 'zotero-note-editor',
|
||||||
|
count: 1,
|
||||||
|
totalCount: 1,
|
||||||
|
processingTime: t2 - t1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getZoteroNoteEditor;
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const signatures = await getSignatures();
|
||||||
|
onSuccess(await getZoteroNoteEditor(signatures));
|
||||||
|
await writeSignatures(signatures);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
process.exitCode = 1;
|
||||||
|
global.isError = true;
|
||||||
|
onError(err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
1
zotero-note-editor
Submodule
1
zotero-note-editor
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 918d6d2787b2abfd90a8f27d96e6a9cba097433e
|
Loading…
Reference in a new issue