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 | <window | ||||||
| 		windowtype="zotero:viewer" | 		id="pdf-reader" | ||||||
|  | 		windowtype="zotero:reader" | ||||||
| 		orient="vertical" | 		orient="vertical" | ||||||
| 		width="1300" | 		width="1300" | ||||||
| 		height="800" | 		height="800" | ||||||
| 		persist="screenX screenY width height" | 		persist="screenX screenY width height" | ||||||
| 		xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" | 		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 --> | 	<!-- TODO: Localize --> | ||||||
| 	<menubar> | 	<menubar> | ||||||
| 		<menu label="File"> | 		<menu label="File"> | ||||||
|  | @ -56,11 +60,16 @@ | ||||||
| 	</menubar> | 	</menubar> | ||||||
| 
 | 
 | ||||||
| 	<hbox flex="1"> | 	<hbox flex="1"> | ||||||
| 		<vbox id="zotero-viewer" flex="3"> | 		<vbox id="zotero-reader" flex="3"> | ||||||
| 			<browser tooltip="viewerTooltip" type="content" primary="true" transparent="transparent" src="" id="viewer" | 			<browser id="reader" | ||||||
|  | 					 tooltip="readerTooltip" | ||||||
|  | 					 type="content" | ||||||
|  | 					 primary="true" | ||||||
|  | 					 transparent="transparent" | ||||||
|  | 					 src="resource://zotero/pdf-reader/viewer.html" | ||||||
| 					 flex="1"/> | 					 flex="1"/> | ||||||
| 			<popupset> | 			<popupset> | ||||||
| 				<tooltip id="viewerTooltip" onpopupshowing="return fillTooltip(this);"/> | 				<tooltip id="readerTooltip" onpopupshowing="return fillTooltip(this);"/> | ||||||
| 				<menupopup id="tagsPopup" ignorekeys="true" | 				<menupopup id="tagsPopup" ignorekeys="true" | ||||||
| 						   style="min-width: 300px;" | 						   style="min-width: 300px;" | ||||||
| 						   onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'true'); }" | 						   onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'true'); }" | ||||||
|  | @ -71,28 +80,27 @@ | ||||||
| 				<menupopup id="colorPopup"/> | 				<menupopup id="colorPopup"/> | ||||||
| 			</popupset> | 			</popupset> | ||||||
| 		</vbox> | 		</vbox> | ||||||
| 		<splitter id="zotero-viewer-splitter" | 		<splitter id="zotero-reader-splitter" | ||||||
| 				  hidden="true" | 				  hidden="true" | ||||||
| 				  resizebefore="closest" | 				  resizebefore="closest" | ||||||
| 				  resizeafter="closest" | 				  resizeafter="closest" | ||||||
| 				  collapse="after" | 				  collapse="after" | ||||||
| 				  orient="horizontal" | 				  orient="horizontal" | ||||||
| 				  zotero-persist="state orient" /> | 				  zotero-persist="state orient" /> | ||||||
| 		<vbox flex="0" id="zotero-viewer-note-sidebar" width="350" hidden="true"> | 		<vbox flex="0" id="zotero-reader-note-sidebar" width="350" hidden="true"> | ||||||
| 			<vbox id="zotero-viewer-sidebar-cover" flex="1"> | 			<vbox id="zotero-reader-sidebar-cover" flex="1"> | ||||||
| 				<label>Drag a note here…</label> | 				<label>Drag a note here…</label> | ||||||
| 			</vbox> | 			</vbox> | ||||||
| 			<vbox id="zotero-viewer-sidebar-container" flex="1" style="overflow:auto;" hidden="true"> | 			<vbox id="zotero-reader-sidebar-container" flex="1" style="overflow:auto;" hidden="true"> | ||||||
| 				<zoteronoteeditor id="zotero-viewer-editor" flex="1" notitle="1" | 				<zoteronoteeditor id="zotero-reader-editor" flex="1" notitle="1" | ||||||
| 								   previousfocus="zotero-items-tree" | 								   previousfocus="zotero-items-tree" | ||||||
| 								   onerror="/*this.mode = 'view'*/" | 								   onerror="/*this.mode = 'view'*/" | ||||||
| 				/> | 				/> | ||||||
| 
 | 
 | ||||||
| 				<button id="zotero-view-note-button" label="Close" | 				<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> | ||||||
| 		</vbox> | 		</vbox> | ||||||
| 	</hbox> | 	</hbox> | ||||||
| 	<script src="include.js"/> | 	<script src="include.js"/> | ||||||
| 	<script src="viewer.js"/> |  | ||||||
| </window> | </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 }); | 	Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 }); | ||||||
| 	 | 	 | ||||||
| 	 | 	 | ||||||
| 	this.toJSON = function (item) { | 	this.toJSON = async function (item) { | ||||||
| 		var o = {}; | 		var o = {}; | ||||||
| 		o.key = item.key; | 		o.key = item.key; | ||||||
| 		o.type = item.annotationType; | 		o.type = item.annotationType; | ||||||
|  | @ -44,7 +44,13 @@ Zotero.Annotations = new function () { | ||||||
| 			o.text = item.annotationText; | 			o.text = item.annotationText; | ||||||
| 		} | 		} | ||||||
| 		else if (o.type == 'image') { | 		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.comment = item.annotationComment; | ||||||
| 		o.pageLabel = item.annotationPageLabel; | 		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 |  * 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 |  * Determine if an item is an annotation | ||||||
|  * |  * | ||||||
|  |  | ||||||
|  | @ -190,7 +190,7 @@ class EditorInstance { | ||||||
| 					this.onNavigate(uri, { position }); | 					this.onNavigate(uri, { position }); | ||||||
| 				} | 				} | ||||||
| 				else { | 				else { | ||||||
| 					await Zotero.Viewer.openURI(uri, { position }); | 					await Zotero.Reader.openURI(uri, { position }); | ||||||
| 				} | 				} | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| 
 | 
 | ||||||
| const CMAPS_URL = 'resource://zotero/pdf.js/cmaps/'; | const CMAPS_URL = 'resource://zotero/pdf-reader/cmaps/'; | ||||||
| 
 | 
 | ||||||
| class PDFWorker { | class PDFWorker { | ||||||
| 	constructor() { | 	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) { | 	this.viewPDF = function (itemID) { | ||||||
| 		Zotero.Viewer.open(itemID); | 		Zotero.Reader.open(itemID); | ||||||
| 	}; | 	}; | ||||||
| 	 | 	 | ||||||
| 	 | 	 | ||||||
|  |  | ||||||
|  | @ -108,7 +108,7 @@ const xpcomFilesLocal = [ | ||||||
| 	'noteBackups', | 	'noteBackups', | ||||||
| 	'notifier', | 	'notifier', | ||||||
| 	'openPDF', | 	'openPDF', | ||||||
| 	'viewer', | 	'reader', | ||||||
| 	'progressQueue', | 	'progressQueue', | ||||||
| 	'progressQueueDialog', | 	'progressQueueDialog', | ||||||
| 	'quickCopy', | 	'quickCopy', | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| Subproject commit 7aefe43059843f2b065f1ca9630d1bb48e08d4c3 | Subproject commit acd34e852d38768c8f31e59aa7238d4d934081eb | ||||||
|  | @ -30,7 +30,7 @@ async function getPDFReader(signatures) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (updated) { | 	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(); | 	const t2 = Date.now(); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Martynas Bagdonas
				Martynas Bagdonas