Improve PDF reader
This commit is contained in:
parent
875e9f674f
commit
a89f7e8ec7
12 changed files with 668 additions and 636 deletions
|
@ -11,13 +11,17 @@
|
|||
]>
|
||||
|
||||
<window
|
||||
windowtype="zotero:viewer"
|
||||
id="pdf-reader"
|
||||
windowtype="zotero:reader"
|
||||
orient="vertical"
|
||||
width="1300"
|
||||
height="800"
|
||||
persist="screenX screenY width height"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
>
|
||||
<script type="application/javascript">
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
</script>
|
||||
<!-- TODO: Localize -->
|
||||
<menubar>
|
||||
<menu label="File">
|
||||
|
@ -56,11 +60,16 @@
|
|||
</menubar>
|
||||
|
||||
<hbox flex="1">
|
||||
<vbox id="zotero-viewer" flex="3">
|
||||
<browser tooltip="viewerTooltip" type="content" primary="true" transparent="transparent" src="" id="viewer"
|
||||
<vbox id="zotero-reader" flex="3">
|
||||
<browser id="reader"
|
||||
tooltip="readerTooltip"
|
||||
type="content"
|
||||
primary="true"
|
||||
transparent="transparent"
|
||||
src="resource://zotero/pdf-reader/viewer.html"
|
||||
flex="1"/>
|
||||
<popupset>
|
||||
<tooltip id="viewerTooltip" onpopupshowing="return fillTooltip(this);"/>
|
||||
<tooltip id="readerTooltip" 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'); }"
|
||||
|
@ -71,28 +80,27 @@
|
|||
<menupopup id="colorPopup"/>
|
||||
</popupset>
|
||||
</vbox>
|
||||
<splitter id="zotero-viewer-splitter"
|
||||
<splitter id="zotero-reader-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">
|
||||
<vbox flex="0" id="zotero-reader-note-sidebar" width="350" hidden="true">
|
||||
<vbox id="zotero-reader-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"
|
||||
<vbox id="zotero-reader-sidebar-container" flex="1" style="overflow:auto;" hidden="true">
|
||||
<zoteronoteeditor id="zotero-reader-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;"/>
|
||||
oncommand="document.getElementById('zotero-reader-sidebar-container').hidden = true;document.getElementById('zotero-reader-sidebar-cover').hidden = false;"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<script src="include.js"/>
|
||||
<script src="viewer.js"/>
|
||||
</window>
|
|
@ -1,66 +0,0 @@
|
|||
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);
|
|
@ -32,7 +32,7 @@ Zotero.Annotations = new function () {
|
|||
Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 });
|
||||
|
||||
|
||||
this.toJSON = function (item) {
|
||||
this.toJSON = async function (item) {
|
||||
var o = {};
|
||||
o.key = item.key;
|
||||
o.type = item.annotationType;
|
||||
|
@ -44,7 +44,13 @@ Zotero.Annotations = new function () {
|
|||
o.text = item.annotationText;
|
||||
}
|
||||
else if (o.type == 'image') {
|
||||
o.imageURL = item.annotationImageURL;
|
||||
var attachments = item.getAttachments();
|
||||
if (attachments.length) {
|
||||
let imageAttachment = Zotero.Items.get(attachments[0]);
|
||||
if (imageAttachment) {
|
||||
o.image = await imageAttachment.attachmentDataURI;
|
||||
}
|
||||
}
|
||||
}
|
||||
o.comment = item.annotationComment;
|
||||
o.pageLabel = item.annotationPageLabel;
|
||||
|
|
|
@ -3332,6 +3332,31 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentText', {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Return dataURI of attachment content
|
||||
*
|
||||
* @return {Promise<String>} - A promise for attachment dataURI or empty string if unavailable
|
||||
*/
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'attachmentDataURI', {
|
||||
get: async function () {
|
||||
if (!this.isAttachment()) {
|
||||
throw new Error("'attachmentDataURI' is only valid for attachments");
|
||||
}
|
||||
let path = await this.getFilePathAsync();
|
||||
if (!path || !(await OS.File.exists(path))) {
|
||||
return '';
|
||||
}
|
||||
let buf = await OS.File.read(path, {});
|
||||
let bytes = new Uint8Array(buf);
|
||||
let binary = '';
|
||||
let len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return 'data:' + this.attachmentContentType + ';base64,' + btoa(binary);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Returns child attachments of this item
|
||||
|
@ -3549,34 +3574,6 @@ Zotero.defineProperty(Zotero.Item.prototype, 'annotationImageAttachment', {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* @property {String} annotationImageURL
|
||||
*/
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'annotationImageURL', {
|
||||
get: function () {
|
||||
if (!this.isImageAnnotation()) {
|
||||
throw new Error("'annotationImageURL' is only valid for image annotations");
|
||||
}
|
||||
var attachments = this.getAttachments();
|
||||
if (!attachments.length) {
|
||||
throw new Error("No attachments found for image annotation");
|
||||
}
|
||||
|
||||
var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(attachments[0]);
|
||||
var url = 'zotero://attachment/';
|
||||
if (libraryID == Zotero.Libraries.userLibraryID) {
|
||||
url += 'library';
|
||||
}
|
||||
else {
|
||||
url += Zotero.URI.getLibraryPath(libraryID);
|
||||
}
|
||||
url += '/items/' + key;
|
||||
|
||||
return url;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Determine if an item is an annotation
|
||||
*
|
||||
|
|
|
@ -190,7 +190,7 @@ class EditorInstance {
|
|||
this.onNavigate(uri, { position });
|
||||
}
|
||||
else {
|
||||
await Zotero.Viewer.openURI(uri, { position });
|
||||
await Zotero.Reader.openURI(uri, { position });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
const CMAPS_URL = 'resource://zotero/pdf.js/cmaps/';
|
||||
const CMAPS_URL = 'resource://zotero/pdf-reader/cmaps/';
|
||||
|
||||
class PDFWorker {
|
||||
constructor() {
|
||||
|
|
610
chrome/content/zotero/xpcom/reader.js
Normal file
610
chrome/content/zotero/xpcom/reader.js
Normal file
|
@ -0,0 +1,610 @@
|
|||
let PDFStates = {};
|
||||
|
||||
class ReaderWindow {
|
||||
constructor() {
|
||||
this.annotationItemIds = [];
|
||||
this._instanceID = Zotero.Utilities.randomString();
|
||||
this._window = null;
|
||||
this._iframeWindow = null;
|
||||
this._itemID = null;
|
||||
this._state = null;
|
||||
this._prevHistory = [];
|
||||
this._nextHistory = [];
|
||||
this._isReaderInitialized = false;
|
||||
}
|
||||
|
||||
init() {
|
||||
let win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
if (!win) return;
|
||||
|
||||
this._window = win.open(
|
||||
'chrome://zotero/content/reader.xul', '', 'chrome,resizable'
|
||||
);
|
||||
|
||||
this._window.addEventListener('DOMContentLoaded', (event) => {
|
||||
this._window.addEventListener('dragover', this._handleDragOver, true);
|
||||
this._window.addEventListener('drop', this._handleDrop, true);
|
||||
this._window.addEventListener('keypress', this._handleKeyPress);
|
||||
|
||||
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._postMessage(data);
|
||||
};
|
||||
|
||||
let readerIframe = this._window.document.getElementById('reader');
|
||||
if (!(readerIframe && readerIframe.contentWindow && readerIframe.contentWindow.document === event.target)) {
|
||||
return;
|
||||
}
|
||||
let editor = this._window.document.getElementById('zotero-reader-editor');
|
||||
editor.navigateHandler = async (uri, location) => {
|
||||
let item = await Zotero.URI.getURIItem(uri);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (item.id === this._itemID) {
|
||||
this.navigate(location);
|
||||
}
|
||||
else {
|
||||
await this.open({
|
||||
itemID: item.id,
|
||||
location
|
||||
});
|
||||
}
|
||||
};
|
||||
this._iframeWindow = this._window.document.getElementById('reader').contentWindow;
|
||||
this._iframeWindow.addEventListener('message', this._handleMessage);
|
||||
});
|
||||
}
|
||||
|
||||
async open({ itemID, state, location, skipHistory }) {
|
||||
if (itemID === this._itemID) {
|
||||
return false;
|
||||
}
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
let path = await item.getFilePathAsync();
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
if (this._itemID && !skipHistory) {
|
||||
this._prevHistory.push({
|
||||
itemID: this._itemID,
|
||||
state: this._state
|
||||
});
|
||||
this._nextHistory = [];
|
||||
}
|
||||
this._itemID = item.id;
|
||||
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;
|
||||
// TODO: Remove when fixed
|
||||
item._loaded.childItems = true;
|
||||
let ids = item.getAnnotations();
|
||||
let annotations = (await Promise.all(ids.map(id => this._getAnnotation(id)))).filter(x => x);
|
||||
this.annotationItemIds = ids;
|
||||
state = state || PDFStates[this._itemID];
|
||||
this._state = state;
|
||||
this._postMessage({
|
||||
action: 'open',
|
||||
buf,
|
||||
annotations,
|
||||
state,
|
||||
location,
|
||||
enablePrev: !!this._prevHistory.length,
|
||||
enableNext: !!this._nextHistory.length
|
||||
}, [buf]);
|
||||
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;
|
||||
}
|
||||
|
||||
async setAnnotations(ids) {
|
||||
let annotations = [];
|
||||
for (let id of ids) {
|
||||
let annotation = await this._getAnnotation(id);
|
||||
if (annotation) {
|
||||
annotations.push(annotation);
|
||||
}
|
||||
}
|
||||
if (annotations.length) {
|
||||
let data = { action: 'setAnnotations', annotations };
|
||||
this._postMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
unsetAnnotations(keys) {
|
||||
let data = { action: 'unsetAnnotations', ids: keys };
|
||||
this._postMessage(data);
|
||||
}
|
||||
|
||||
async navigate(location) {
|
||||
this._postMessage({ action: 'navigate', location });
|
||||
}
|
||||
|
||||
close() {
|
||||
this._window.close();
|
||||
}
|
||||
|
||||
_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 });
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Pass sidebar state to the responsible pdf-reader button
|
||||
_toggleNoteSidebar(isToggled) {
|
||||
let splitter = this._window.document.getElementById('zotero-reader-splitter');
|
||||
let sidebar = this._window.document.getElementById('zotero-reader-note-sidebar');
|
||||
if (isToggled) {
|
||||
splitter.hidden = false;
|
||||
sidebar.hidden = false;
|
||||
}
|
||||
else {
|
||||
splitter.hidden = true;
|
||||
sidebar.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
_getColorIcon(color, selected) {
|
||||
let stroke = selected ? 'lightgray' : 'transparent';
|
||||
let fill = '%23' + color.slice(1);
|
||||
return `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect shape-rendering="geometricPrecision" fill="${fill}" stroke-width="2" x="2" y="2" stroke="${stroke}" width="12" height="12" rx="3"/></svg>`;
|
||||
}
|
||||
|
||||
_openAnnotationPopup(x, y, annotationId, colors, 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: annotationId
|
||||
};
|
||||
this._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';
|
||||
menuitem.setAttribute('image', this._getColorIcon(color[1], color[1] === selectedColor));
|
||||
menuitem.addEventListener('command', () => {
|
||||
let data = {
|
||||
action: 'popupCmd',
|
||||
cmd: 'setAnnotationColor',
|
||||
id: annotationId,
|
||||
color: color[1]
|
||||
};
|
||||
this._postMessage(data);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
popup.openPopupAtScreen(x, y, true);
|
||||
}
|
||||
|
||||
_openColorPopup(x, y, colors, 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';
|
||||
menuitem.setAttribute('image', this._getColorIcon(color[1], color[1] === selectedColor));
|
||||
menuitem.addEventListener('command', () => {
|
||||
let data = {
|
||||
action: 'popupCmd',
|
||||
cmd: 'setColor',
|
||||
color: color[1]
|
||||
};
|
||||
this._postMessage(data);
|
||||
});
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
popup.openPopupAtScreen(x, y, true);
|
||||
}
|
||||
|
||||
async _postMessage(message, transfer) {
|
||||
await this._waitForReader();
|
||||
this._iframeWindow.postMessage({ itemId: this._itemID, message }, this._iframeWindow.origin, transfer);
|
||||
}
|
||||
|
||||
_handleMessage = async (event) => {
|
||||
let message;
|
||||
try {
|
||||
if (event.source !== this._iframeWindow) {
|
||||
return;
|
||||
}
|
||||
// Clone data to avoid the dead object error when the window is closed
|
||||
let data = JSON.parse(JSON.stringify(event.data));
|
||||
// Filter messages coming from previous reader instances,
|
||||
// except for `setAnnotation` to still allow saving it
|
||||
if (data.itemId !== this._itemID && data.message.action !== 'setAnnotation') {
|
||||
return;
|
||||
}
|
||||
Zotero.debug('Received message from pdf-reader iframe: ' + JSON.stringify(data));
|
||||
message = data.message;
|
||||
switch (message.action) {
|
||||
case 'navigatePrev': {
|
||||
let prev = this._prevHistory.pop();
|
||||
if (prev) {
|
||||
this._nextHistory.push({
|
||||
itemID: this._itemID,
|
||||
state: this._state
|
||||
});
|
||||
this.open({ itemID: prev.itemID, state: prev.state, skipHistory: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'navigateNext': {
|
||||
let next = this._nextHistory.pop();
|
||||
if (next) {
|
||||
this._prevHistory.push({
|
||||
itemID: this._itemID,
|
||||
state: this._state
|
||||
});
|
||||
this.open({ itemID: next.itemID, state: next.state, skipHistory: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'setAnnotation': {
|
||||
let attachment = Zotero.Items.get(data.itemId);
|
||||
let { annotation } = message;
|
||||
annotation.key = annotation.id;
|
||||
let saveOptions = {
|
||||
notifierData: {
|
||||
instanceID: this._instanceID
|
||||
}
|
||||
};
|
||||
let savedAnnotation = await Zotero.Annotations.saveFromJSON(attachment, annotation, saveOptions);
|
||||
if (annotation.image) {
|
||||
let blob = this._dataURLtoBlob(annotation.image);
|
||||
let attachmentIds = savedAnnotation.getAttachments();
|
||||
if (attachmentIds.length) {
|
||||
let attachment = Zotero.Items.get(attachmentIds[0]);
|
||||
let path = await attachment.getFilePathAsync();
|
||||
await Zotero.File.putContentsAsync(path, blob);
|
||||
await Zotero.Sync.Storage.Local.updateSyncStates([attachment], 'to_upload');
|
||||
Zotero.Notifier.trigger('modify', 'item', attachment.id, { instanceID: this._instanceID });
|
||||
}
|
||||
else {
|
||||
await Zotero.Attachments.importEmbeddedImage({
|
||||
blob,
|
||||
parentItemID: savedAnnotation.id,
|
||||
saveOptions
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'deleteAnnotations': {
|
||||
let { ids: keys } = message;
|
||||
let attachment = Zotero.Items.get(this._itemID);
|
||||
let libraryID = attachment.libraryID;
|
||||
for (let key of keys) {
|
||||
let annotation = Zotero.Items.getByLibraryAndKey(libraryID, key);
|
||||
// A small check, as we are receiving a list of item keys from a less secure code
|
||||
if (annotation && annotation.isAnnotation() && annotation.parentID === this._itemID) {
|
||||
this.annotationItemIds = this.annotationItemIds.filter(id => id !== annotation.id);
|
||||
await annotation.eraseTx();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'setState': {
|
||||
let { state } = message;
|
||||
PDFStates[this._itemID] = state;
|
||||
this._state = state;
|
||||
return;
|
||||
}
|
||||
case 'openTagsPopup': {
|
||||
let { id: key, x, y } = message;
|
||||
let attachment = Zotero.Items.get(this._itemID);
|
||||
let libraryID = attachment.libraryID;
|
||||
let annotation = Zotero.Items.getByLibraryAndKey(libraryID, key);
|
||||
if (annotation) {
|
||||
this._window.document.getElementById('tags').item = annotation;
|
||||
this._window.document.getElementById('tagsPopup').openPopupAtScreen(x, y, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'openAnnotationPopup': {
|
||||
let { x, y, id, colors, selectedColor } = message;
|
||||
this._openAnnotationPopup(x, y, id, colors, selectedColor);
|
||||
return;
|
||||
}
|
||||
case 'openColorPopup': {
|
||||
let { x, y, colors, selectedColor } = message;
|
||||
this._openColorPopup(x, y, colors, selectedColor);
|
||||
return;
|
||||
}
|
||||
case 'openUrl': {
|
||||
let { url } = message;
|
||||
let win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
if (win) {
|
||||
win.ZoteroPane.loadURI(url);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'import': {
|
||||
Zotero.debug('Importing PDF annotations');
|
||||
let item = Zotero.Items.get(this._itemID);
|
||||
Zotero.PDFImport.import(item);
|
||||
return;
|
||||
}
|
||||
case 'importDismiss': {
|
||||
Zotero.debug('Dismiss PDF annotations');
|
||||
return;
|
||||
}
|
||||
case 'save': {
|
||||
Zotero.debug('Exporting PDF');
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
zp.exportPDF(this._itemID);
|
||||
return;
|
||||
}
|
||||
case 'toggleNoteSidebar': {
|
||||
let { isToggled } = message;
|
||||
this._toggleNoteSidebar(isToggled);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this._postMessage({
|
||||
action: 'error',
|
||||
message: `An error occured during '${message ? message.action : ''}'`,
|
||||
moreInfo: {
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
fileName: e.fileName,
|
||||
lineNumber: e.lineNumber
|
||||
}
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
_handleDragOver = (event) => {
|
||||
if (event.dataTransfer.getData('zotero/item')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
_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 = this._window.document.getElementById('zotero-reader-sidebar-cover');
|
||||
let container = this._window.document.getElementById('zotero-reader-sidebar-container');
|
||||
let splitter = this._window.document.getElementById('zotero-reader-splitter');
|
||||
|
||||
cover.hidden = true;
|
||||
container.hidden = false;
|
||||
splitter.hidden = false;
|
||||
|
||||
let editor = this._window.document.getElementById('zotero-reader-editor');
|
||||
let notebox = this._window.document.getElementById('zotero-reader-note-sidebar');
|
||||
editor.mode = 'edit';
|
||||
notebox.hidden = false;
|
||||
editor.item = item;
|
||||
}
|
||||
else if (item.isAttachment() && item.attachmentContentType === 'application/pdf') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.open({ itemID: item.id });
|
||||
}
|
||||
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();
|
||||
this.open({ itemID: attachment.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleKeyPress = (event) => {
|
||||
if ((Zotero.isMac && event.metaKey || event.ctrlKey)
|
||||
&& !event.shiftKey && !event.altKey && event.key === 'w') {
|
||||
this._window.close();
|
||||
}
|
||||
}
|
||||
|
||||
async _waitForReader() {
|
||||
if (this._isReaderInitialized) {
|
||||
return;
|
||||
}
|
||||
let n = 0;
|
||||
while (!this._iframeWindow || !this._iframeWindow.eval('window.isReady')) {
|
||||
if (n >= 500) {
|
||||
throw new Error('Waiting for reader failed');
|
||||
}
|
||||
await Zotero.Promise.delay(10);
|
||||
n++;
|
||||
}
|
||||
this._isReaderInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return item JSON in the pdf-reader ready format
|
||||
* @param itemID
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
async _getAnnotation(itemID) {
|
||||
try {
|
||||
let item = Zotero.Items.get(itemID);
|
||||
if (!item || !item.isAnnotation()) {
|
||||
return null;
|
||||
}
|
||||
// TODO: Remve when fixed
|
||||
item._loaded.childItems = true;
|
||||
item = await Zotero.Annotations.toJSON(item);
|
||||
item.id = item.key;
|
||||
item.image = item.image;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Reader {
|
||||
constructor() {
|
||||
this._readerWindows = [];
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'reader');
|
||||
}
|
||||
|
||||
notify(event, type, ids, extraData) {
|
||||
// Listen for the parent item, PDF attachment and its annotation items updates
|
||||
for (let readerWindow of this._readerWindows) {
|
||||
if (event === 'delete') {
|
||||
let disappearedIds = readerWindow.annotationItemIds.filter(x => ids.includes(x));
|
||||
if (disappearedIds.length) {
|
||||
let keys = disappearedIds.map(id => extraData[id].key);
|
||||
readerWindow.unsetAnnotations(keys);
|
||||
}
|
||||
if (ids.includes(readerWindow._itemID)) {
|
||||
readerWindow.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
let item = Zotero.Items.get(readerWindow._itemID);
|
||||
// TODO: Remove when fixed
|
||||
item._loaded.childItems = true;
|
||||
let annotationItemIds = item.getAnnotations();
|
||||
readerWindow.annotationItemIds = annotationItemIds;
|
||||
let affectedAnnotationIds = annotationItemIds.filter(annotationID => {
|
||||
let annotation = Zotero.Items.get(annotationID);
|
||||
let imageAttachmentID = null;
|
||||
annotation._loaded.childItems = true;
|
||||
let annotationAttachments = annotation.getAttachments();
|
||||
if (annotationAttachments.length) {
|
||||
imageAttachmentID = annotationAttachments[0];
|
||||
}
|
||||
return (
|
||||
ids.includes(annotationID) && !(extraData[annotationID]
|
||||
&& extraData[annotationID].instanceID === readerWindow._instanceID)
|
||||
|| ids.includes(imageAttachmentID) && !(extraData[imageAttachmentID]
|
||||
&& extraData[imageAttachmentID].instanceID === readerWindow._instanceID)
|
||||
);
|
||||
});
|
||||
if (affectedAnnotationIds.length) {
|
||||
readerWindow.setAnnotations(affectedAnnotationIds);
|
||||
}
|
||||
// Update title if the PDF attachment or the parent item changes
|
||||
if (ids.includes(readerWindow._itemID) || ids.includes(item.parentItemID)) {
|
||||
readerWindow.updateTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getReaderWindow(itemID) {
|
||||
return this._readerWindows.find(v => v._itemID === itemID);
|
||||
}
|
||||
|
||||
async openURI(itemURI, location) {
|
||||
let item = await Zotero.URI.getURIItem(itemURI);
|
||||
if (!item) return;
|
||||
this.open(item.id, location);
|
||||
}
|
||||
|
||||
async open(itemID, location) {
|
||||
let reader = this._getReaderWindow(itemID);
|
||||
if (reader) {
|
||||
if (location) {
|
||||
reader.navigate(location);
|
||||
}
|
||||
}
|
||||
else {
|
||||
reader = new ReaderWindow();
|
||||
reader.init();
|
||||
if (!(await reader.open({ itemID, location }))) {
|
||||
return;
|
||||
}
|
||||
this._readerWindows.push(reader);
|
||||
reader._window.addEventListener('unload', () => {
|
||||
this._readerWindows.splice(this._readerWindows.indexOf(reader), 1);
|
||||
});
|
||||
}
|
||||
reader._window.focus();
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Reader = new Reader();
|
|
@ -1,523 +0,0 @@
|
|||
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();
|
|
@ -4232,7 +4232,7 @@ var ZoteroPane = new function()
|
|||
});
|
||||
|
||||
this.viewPDF = function (itemID) {
|
||||
Zotero.Viewer.open(itemID);
|
||||
Zotero.Reader.open(itemID);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ const xpcomFilesLocal = [
|
|||
'noteBackups',
|
||||
'notifier',
|
||||
'openPDF',
|
||||
'viewer',
|
||||
'reader',
|
||||
'progressQueue',
|
||||
'progressQueueDialog',
|
||||
'quickCopy',
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 7aefe43059843f2b065f1ca9630d1bb48e08d4c3
|
||||
Subproject commit acd34e852d38768c8f31e59aa7238d4d934081eb
|
|
@ -30,7 +30,7 @@ async function getPDFReader(signatures) {
|
|||
}
|
||||
|
||||
if (updated) {
|
||||
await fs.copy('./pdf-reader/build/zotero', './build/resource/pdf.js');
|
||||
await fs.copy('./pdf-reader/build/zotero', './build/resource/pdf-reader');
|
||||
}
|
||||
|
||||
const t2 = Date.now();
|
||||
|
|
Loading…
Reference in a new issue