Improve PDF import/export
This commit is contained in:
parent
e99f76b40c
commit
76e77d4422
12 changed files with 328 additions and 511 deletions
|
@ -1,168 +0,0 @@
|
|||
class PDFExport {
|
||||
constructor() {
|
||||
this._queue = [];
|
||||
this._queueProcessing = false;
|
||||
this._processingItemID = null;
|
||||
this._progressQueue = Zotero.ProgressQueues.create({
|
||||
id: 'pdf-export',
|
||||
title: 'pdfExport.title',
|
||||
columns: [
|
||||
'recognizePDF.pdfName.label',
|
||||
'pdfImport.annotations.label'
|
||||
]
|
||||
});
|
||||
|
||||
this._progressQueue.addListener('cancel', () => {
|
||||
this._queue = [];
|
||||
});
|
||||
}
|
||||
|
||||
hasAnnotations(item) {
|
||||
item._loaded.childItems = true;
|
||||
return item.isAttachment() && item.getAnnotations().length;
|
||||
}
|
||||
|
||||
canExport(item) {
|
||||
if (this.hasAnnotations(item)) {
|
||||
return true;
|
||||
}
|
||||
else if (item.isRegularItem()) {
|
||||
let ids = item.getAttachments();
|
||||
for (let id of ids) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (this.hasAnnotations(attachment)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers queue processing and returns when all items in the queue are processed
|
||||
* @return {Promise}
|
||||
*/
|
||||
async _processQueue() {
|
||||
// await Zotero.Schema.schemaUpdatePromise;
|
||||
|
||||
if (this._queueProcessing) return;
|
||||
this._queueProcessing = true;
|
||||
|
||||
while (1) {
|
||||
let data = this._queue.pop();
|
||||
if (!data) break;
|
||||
|
||||
let { itemID, path } = data;
|
||||
|
||||
this._processingItemID = itemID;
|
||||
|
||||
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_PROCESSING, Zotero.getString('general.processing'));
|
||||
|
||||
try {
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
|
||||
if (!item) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
let num = await this._exportItemAnnotations(item, path);
|
||||
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_SUCCEEDED, num);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
||||
this._progressQueue.updateRow(
|
||||
itemID,
|
||||
Zotero.ProgressQueue.ROW_FAILED,
|
||||
e instanceof Zotero.Exception.Alert
|
||||
? e.message
|
||||
: Zotero.getString('general.error')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._queueProcessing = false;
|
||||
this._processingItemID = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds items to the queue and triggers processing
|
||||
* @param {Zotero.Item[]} items
|
||||
*/
|
||||
async export(items) {
|
||||
let pdfItems = [];
|
||||
|
||||
if (!Array.isArray(items)) {
|
||||
items = [items];
|
||||
}
|
||||
|
||||
for (let item of items) {
|
||||
if (this.hasAnnotations(item)) {
|
||||
pdfItems.push(item);
|
||||
}
|
||||
else if (item.isRegularItem()) {
|
||||
let ids = item.getAttachments();
|
||||
for (let id of ids) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (this.hasAnnotations(attachment)) {
|
||||
pdfItems.push(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let item of pdfItems) {
|
||||
if (
|
||||
this._processingItemID === item.id ||
|
||||
this._queue.find(x => x.itemID === item.id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
this._queue.unshift({ itemID: item.id });
|
||||
this._progressQueue.addRow(item);
|
||||
}
|
||||
await this._processQueue();
|
||||
}
|
||||
|
||||
async exportToPath(item, path, isPriority) {
|
||||
if (isPriority) {
|
||||
this._queue.push({ itemID: item.id, path });
|
||||
}
|
||||
else {
|
||||
this._queue.unshift({ itemID: item.id, path });
|
||||
}
|
||||
this._progressQueue.addRow(item);
|
||||
await this._processQueue();
|
||||
}
|
||||
|
||||
async _exportItemAnnotations(item, path) {
|
||||
let ids = item.getAnnotations();
|
||||
|
||||
let annotations = [];
|
||||
for (let id of ids) {
|
||||
try {
|
||||
annotations.push(Zotero.Annotations.toJSON(Zotero.Items.get(id)));
|
||||
} catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
annotations.id = annotations.key;
|
||||
// annotations.image = annotations.imageURL;
|
||||
|
||||
for (let annotation of annotations) {
|
||||
delete annotation.key;
|
||||
for (let key in annotation) {
|
||||
annotation[key] = annotation[key] || '';
|
||||
}
|
||||
|
||||
annotation.authorName = '';
|
||||
}
|
||||
|
||||
await Zotero.PDFWorker.writeAnnotations(item.id, annotations, path);
|
||||
return annotations.length;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.PDFExport = new PDFExport();
|
|
@ -1,167 +0,0 @@
|
|||
// TODO: Import ToC
|
||||
|
||||
class PdfImport {
|
||||
constructor() {
|
||||
this._queue = [];
|
||||
this._queueProcessing = false;
|
||||
this._processingItemID = null;
|
||||
this._progressQueue = Zotero.ProgressQueues.create({
|
||||
id: 'pdf-import',
|
||||
title: 'pdfImport.title',
|
||||
columns: [
|
||||
'recognizePDF.pdfName.label',
|
||||
'pdfImport.annotations.label'
|
||||
]
|
||||
});
|
||||
|
||||
this._progressQueue.addListener('cancel', () => {
|
||||
this._queue = [];
|
||||
});
|
||||
}
|
||||
|
||||
isPDFAttachment(item) {
|
||||
return item.isAttachment() && item.attachmentContentType === 'application/pdf';
|
||||
}
|
||||
|
||||
canImport(item) {
|
||||
if (this.isPDFAttachment(item)) {
|
||||
return true;
|
||||
}
|
||||
else if (item.isRegularItem()) {
|
||||
let ids = item.getAttachments();
|
||||
for (let id of ids) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (this.isPDFAttachment(attachment)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers queue processing and returns when all items in the queue are processed
|
||||
* @return {Promise}
|
||||
*/
|
||||
async _processQueue() {
|
||||
// await Zotero.Schema.schemaUpdatePromise;
|
||||
|
||||
if (this._queueProcessing) return;
|
||||
this._queueProcessing = true;
|
||||
|
||||
while (1) {
|
||||
let itemID = this._queue.pop();
|
||||
if (!itemID) break;
|
||||
|
||||
this._processingItemID = itemID;
|
||||
|
||||
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_PROCESSING, Zotero.getString('general.processing'));
|
||||
|
||||
try {
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
|
||||
if (!item) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
let num = await this._importItemAnnotations(item);
|
||||
this._progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_SUCCEEDED, num);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
||||
this._progressQueue.updateRow(
|
||||
itemID,
|
||||
Zotero.ProgressQueue.ROW_FAILED,
|
||||
e instanceof Zotero.Exception.Alert
|
||||
? e.message
|
||||
: Zotero.getString('general.error')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._queueProcessing = false;
|
||||
this._processingItemID = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds items to the queue and triggers processing
|
||||
* @param {Zotero.Item[]} items
|
||||
*/
|
||||
async import(items, isPriority) {
|
||||
let pdfItems = [];
|
||||
|
||||
if (!Array.isArray(items)) {
|
||||
items = [items];
|
||||
}
|
||||
|
||||
for (let item of items) {
|
||||
if (this.isPDFAttachment(item)) {
|
||||
pdfItems.push(item);
|
||||
}
|
||||
else if (item.isRegularItem()) {
|
||||
let ids = item.getAttachments();
|
||||
for (let id of ids) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (this.isPDFAttachment(attachment)) {
|
||||
pdfItems.push(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let item of pdfItems) {
|
||||
if (
|
||||
this._processingItemID === item.id ||
|
||||
this._queue.includes(item.id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
this._queue.unshift(item.id);
|
||||
this._progressQueue.addRow(item);
|
||||
}
|
||||
await this._processQueue();
|
||||
}
|
||||
|
||||
similarAnnotions(annotation1, annotation2) {
|
||||
return (annotation1.position.pageIndex === annotation2.position.pageIndex &&
|
||||
JSON.stringify(annotation1.position.rects) === JSON.stringify(annotation2.position.rects));
|
||||
}
|
||||
|
||||
async _importItemAnnotations(item) {
|
||||
if (!item.isAttachment() || item.attachmentContentType !== 'application/pdf') {
|
||||
throw new Error('Not a valid PDF attachment');
|
||||
}
|
||||
|
||||
// TODO: Remove when fixed
|
||||
item._loaded.childItems = true;
|
||||
let ids = item.getAnnotations();
|
||||
let existingAnnotations = [];
|
||||
for (let id of ids) {
|
||||
try {
|
||||
existingAnnotations.push(Zotero.Annotations.toJSON(Zotero.Items.get(id)));
|
||||
} catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
let annotations = await Zotero.PDFWorker.readAnnotations(item.id);
|
||||
annotations = annotations.filter(x => ['highlight', 'note'].includes(x.type));
|
||||
|
||||
let num = 0;
|
||||
for (let annotation of annotations) {
|
||||
annotation.comment = annotation.comment || '';
|
||||
if (existingAnnotations.some(existingAnnotation => this.similarAnnotions(existingAnnotation, annotation))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Utilize the saved Zotero item key for deduplication
|
||||
annotation.key = Zotero.DataObjectUtilities.generateKey();
|
||||
let annotationItem = await Zotero.Annotations.saveFromJSON(item, annotation);
|
||||
num++;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.PDFImport = new PdfImport();
|
283
chrome/content/zotero/xpcom/pdfWorker/manager.js
Normal file
283
chrome/content/zotero/xpcom/pdfWorker/manager.js
Normal file
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
http://digitalscholar.org/
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
const WORKER_URL = 'chrome://zotero/content/xpcom/pdfWorker/worker.js';
|
||||
const CMAPS_URL = 'resource://zotero/pdf-reader/cmaps/';
|
||||
|
||||
class PDFWorker {
|
||||
constructor() {
|
||||
this._worker = null;
|
||||
this._lastPromiseID = 0;
|
||||
this._waitingPromises = {};
|
||||
this._queue = [];
|
||||
this._processingQueue = false;
|
||||
}
|
||||
|
||||
async _processQueue() {
|
||||
this._init();
|
||||
if (this._processingQueue) {
|
||||
return;
|
||||
}
|
||||
this._processingQueue = true;
|
||||
let item;
|
||||
while ((item = this._queue.shift())) {
|
||||
if (item) {
|
||||
let [fn, resolve, reject] = item;
|
||||
try {
|
||||
resolve(await fn());
|
||||
}
|
||||
catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._processingQueue = false;
|
||||
}
|
||||
|
||||
async _enqueue(fn, isPriority) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isPriority) {
|
||||
this._queue.unshift([fn, resolve, reject]);
|
||||
}
|
||||
else {
|
||||
this._queue.push([fn, resolve, reject]);
|
||||
}
|
||||
this._processQueue();
|
||||
});
|
||||
}
|
||||
|
||||
async _query(action, data, transfer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._lastPromiseID++;
|
||||
this._waitingPromises[this._lastPromiseID] = { resolve, reject };
|
||||
this._worker.postMessage({ id: this._lastPromiseID, action, data }, transfer);
|
||||
});
|
||||
}
|
||||
|
||||
_init() {
|
||||
if (this._worker) return;
|
||||
this._worker = new Worker(WORKER_URL);
|
||||
this._worker.addEventListener('message', async (event) => {
|
||||
let message = event.data;
|
||||
// console.log(event.data)
|
||||
if (message.responseId) {
|
||||
let { resolve, reject } = this._waitingPromises[message.responseId];
|
||||
delete this._waitingPromises[message.responseId];
|
||||
if (message.data) {
|
||||
resolve(message.data);
|
||||
}
|
||||
else {
|
||||
let err = new Error(message.error.message);
|
||||
Object.assign(err, message.error);
|
||||
reject(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (message.id) {
|
||||
let respData = null;
|
||||
try {
|
||||
if (message.action === 'FetchBuiltInCMap') {
|
||||
let response = await Zotero.HTTP.request(
|
||||
'GET',
|
||||
CMAPS_URL + event.data.data.name + '.bcmap',
|
||||
{ responseType: 'arraybuffer' }
|
||||
);
|
||||
respData = {
|
||||
compressionType: 1,
|
||||
cMapData: new Uint8Array(response.response)
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Failed to fetch CMap data:');
|
||||
Zotero.debug(e);
|
||||
}
|
||||
this._worker.postMessage({ responseId: event.data.id, data: respData });
|
||||
}
|
||||
});
|
||||
this._worker.addEventListener('error', (event) => {
|
||||
Zotero.debug('PDF Web Worker error:');
|
||||
Zotero.debug(event);
|
||||
});
|
||||
}
|
||||
|
||||
isPDFAttachment(item) {
|
||||
return item.isAttachment() && item.attachmentContentType === 'application/pdf';
|
||||
}
|
||||
|
||||
canImport(item) {
|
||||
if (this.isPDFAttachment(item)) {
|
||||
return true;
|
||||
}
|
||||
else if (item.isRegularItem()) {
|
||||
let ids = item.getAttachments();
|
||||
for (let id of ids) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (this.isPDFAttachment(attachment)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export attachment with annotations to specified path
|
||||
*
|
||||
* @param {Integer} itemID
|
||||
* @param {String} path
|
||||
* @param {Boolean} isPriority
|
||||
* @param {String} password
|
||||
* @returns {Promise<Integer>} Number of written annotations
|
||||
*/
|
||||
async export(itemID, path, isPriority, password) {
|
||||
return this._enqueue(async () => {
|
||||
let attachment = await Zotero.Items.getAsync(itemID);
|
||||
if (!this.isPDFAttachment(attachment)) {
|
||||
throw new Error('not a valid attachment');
|
||||
}
|
||||
let ids = attachment.getAnnotations();
|
||||
let annotations = [];
|
||||
for (let id of ids) {
|
||||
let item = await Zotero.Items.getAsync(id);
|
||||
annotations.push({
|
||||
id: item.key,
|
||||
type: item.annotationType,
|
||||
authorName: Zotero.Users.getName(item.createdByUserID) || '',
|
||||
comment: item.annotationComment || '',
|
||||
color: item.annotationColor,
|
||||
position: JSON.parse(item.annotationPosition),
|
||||
dateModified: item.dateModified
|
||||
});
|
||||
}
|
||||
let attachmentPath = await attachment.getFilePath();
|
||||
let buf = await OS.File.read(attachmentPath, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
let res = await this._query('export', { buf, annotations, password }, [buf]);
|
||||
await OS.File.writeAtomic(path, new Uint8Array(res.buf));
|
||||
return annotations.length;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export children PDF attachments with annotations
|
||||
*
|
||||
* @param {Zotero.Item} item
|
||||
* @param {String} directory
|
||||
*/
|
||||
async exportParent(item, directory) {
|
||||
if (!item.isRegularItem()) {
|
||||
throw new Error('regular item not provided');
|
||||
}
|
||||
if (!directory) {
|
||||
throw new Error('\'directory\' not provided');
|
||||
}
|
||||
let promises = [];
|
||||
let ids = item.getAttachments();
|
||||
for (let id of ids) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (this.isPDFAttachment(attachment)) {
|
||||
let path = OS.Path.join(directory, attachment.attachmentFilename);
|
||||
promises.push(this.export(id, path));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import annotations from PDF attachment
|
||||
*
|
||||
* @param {Integer} itemID
|
||||
* @param {Boolean} save Save imported annotations, or otherwise just return the number of importable annotations
|
||||
* @param {Boolean} isPriority
|
||||
* @param {String} password
|
||||
* @returns {Promise<Integer>} Number of annotations
|
||||
*/
|
||||
async import(itemID, save, isPriority, password) {
|
||||
return this._enqueue(async () => {
|
||||
let attachment = await Zotero.Items.getAsync(itemID);
|
||||
if (!this.isPDFAttachment(attachment)) {
|
||||
throw new Error('not a valid PDF attachment');
|
||||
}
|
||||
// TODO: Remove when fixed
|
||||
attachment._loaded.childItems = true;
|
||||
let ids = attachment.getAnnotations();
|
||||
let existingAnnotations = [];
|
||||
for (let id of ids) {
|
||||
let item = await Zotero.Items.getAsync(id);
|
||||
existingAnnotations.push({
|
||||
id: item.key,
|
||||
type: item.annotationType,
|
||||
comment: item.annotationComment || '',
|
||||
position: JSON.parse(item.annotationPosition)
|
||||
});
|
||||
}
|
||||
let path = await attachment.getFilePath();
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
let res = await this._query('import', { buf, existingAnnotations, password }, [buf]);
|
||||
let annotations = res.annotations;
|
||||
if (save) {
|
||||
for (let annotation of annotations) {
|
||||
// TODO: Utilize the saved Zotero item key for deduplication. Newer annotation modificaiton date wins
|
||||
annotation.key = Zotero.DataObjectUtilities.generateKey();
|
||||
await Zotero.Annotations.saveFromJSON(attachment, annotation);
|
||||
}
|
||||
Zotero.PDF.hasUnmachedAnnotations[itemID] = false;
|
||||
}
|
||||
else {
|
||||
Zotero.PDF.hasUnmachedAnnotations[itemID] = !!annotations.length;
|
||||
}
|
||||
for (let readerWindow of Zotero.Reader._readerWindows) {
|
||||
if (readerWindow._itemID === itemID) {
|
||||
readerWindow.toggleImportPrompt(!!Zotero.PDF.hasUnmachedAnnotations[itemID]);
|
||||
}
|
||||
}
|
||||
Zotero.PDF.dateChecked[itemID] = Zotero.Date.dateToISO(new Date());
|
||||
return annotations.length;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Import children PDF attachment annotations
|
||||
*
|
||||
* @param {Zotero.Item} item
|
||||
*/
|
||||
async importParent(item) {
|
||||
if (!item.isRegularItem()) {
|
||||
throw new Error('regular item not provided');
|
||||
}
|
||||
let promises = [];
|
||||
let ids = item.getAttachments();
|
||||
for (let id of ids) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (this.isPDFAttachment(attachment)) {
|
||||
promises.push(this.import(id, true));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.PDFWorker = new PDFWorker();
|
|
@ -1,106 +0,0 @@
|
|||
|
||||
const CMAPS_URL = 'resource://zotero/pdf-reader/cmaps/';
|
||||
|
||||
class PDFWorker {
|
||||
constructor() {
|
||||
this.worker = null;
|
||||
this.promiseId = 0;
|
||||
this.waitingPromises = {};
|
||||
}
|
||||
|
||||
async query(op, data, transfer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.promiseId++;
|
||||
this.waitingPromises[this.promiseId] = {resolve, reject};
|
||||
this.worker.postMessage({id: this.promiseId, op, data}, transfer);
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.worker) return;
|
||||
this.worker = new Worker('chrome://zotero/content/xpcom/pdfWorker/worker.js');
|
||||
|
||||
this.worker.addEventListener('message', async e => {
|
||||
let message = e.data;
|
||||
// console.log(e.data)
|
||||
if (message.responseId) {
|
||||
let { resolve, reject } = this.waitingPromises[message.responseId];
|
||||
if (message.data) {
|
||||
resolve(message.data);
|
||||
}
|
||||
else {
|
||||
reject(message.error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.id) {
|
||||
let respData = null;
|
||||
|
||||
try {
|
||||
if (message.op === 'FetchBuiltInCMap') {
|
||||
let response = await Zotero.HTTP.request(
|
||||
"GET",
|
||||
CMAPS_URL + e.data.data.name + '.bcmap',
|
||||
{responseType: 'arraybuffer'}
|
||||
);
|
||||
respData = {
|
||||
compressionType: 1,
|
||||
cMapData: new Uint8Array(response.response)
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Failed to fetch CMap data:');
|
||||
Zotero.debug(e);
|
||||
}
|
||||
this.worker.postMessage({responseId: e.data.id, data: respData});
|
||||
}
|
||||
});
|
||||
|
||||
this.worker.addEventListener('error', e => {
|
||||
Zotero.debug('PDF Web Worker error:');
|
||||
Zotero.debug(e);
|
||||
});
|
||||
}
|
||||
|
||||
async writeAnnotations(itemID, annotations, path) {
|
||||
Zotero.debug("Writing annotations");
|
||||
this.init();
|
||||
|
||||
|
||||
let password = '';
|
||||
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
let itemFilePath = await item.getFilePath();
|
||||
|
||||
let buf = await OS.File.read(itemFilePath, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
|
||||
let res = await this.query('write', {buf, annotations, password}, [buf]);
|
||||
|
||||
if (!path) {
|
||||
path = itemFilePath;
|
||||
}
|
||||
|
||||
await OS.File.writeAtomic(path, new Uint8Array(res.buf));
|
||||
}
|
||||
|
||||
async readAnnotations(itemID) {
|
||||
this.init();
|
||||
|
||||
let password = '';
|
||||
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
let path = await item.getFilePath();
|
||||
|
||||
let buf = await OS.File.read(path, {});
|
||||
buf = new Uint8Array(buf).buffer;
|
||||
|
||||
let res = await this.query('read', {buf, password}, [buf]);
|
||||
|
||||
return res.annotations;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.PDFWorker = new PDFWorker();
|
|
@ -1,8 +1,12 @@
|
|||
let PDFStates = {};
|
||||
// Temporary stuff
|
||||
Zotero.PDF = {
|
||||
dateChecked: {},
|
||||
hasUnmachedAnnotations: {}
|
||||
};
|
||||
|
||||
class ReaderWindow {
|
||||
constructor() {
|
||||
this.annotationItemIds = [];
|
||||
this.annotationItemIDs = [];
|
||||
this._instanceID = Zotero.Utilities.randomString();
|
||||
this._window = null;
|
||||
this._iframeWindow = null;
|
||||
|
@ -115,7 +119,8 @@ class ReaderWindow {
|
|||
state,
|
||||
location,
|
||||
enablePrev: !!this._prevHistory.length,
|
||||
enableNext: !!this._nextHistory.length
|
||||
enableNext: !!this._nextHistory.length,
|
||||
promptImport: !!Zotero.PDF.hasUnmachedAnnotations[this._itemID]
|
||||
}, [buf]);
|
||||
return true;
|
||||
}
|
||||
|
@ -156,6 +161,10 @@ class ReaderWindow {
|
|||
this._postMessage({ action: 'navigate', location });
|
||||
}
|
||||
|
||||
toggleImportPrompt(enable) {
|
||||
this._postMessage({ action: 'toggleImportPrompt', enable });
|
||||
}
|
||||
|
||||
close() {
|
||||
this._window.close();
|
||||
}
|
||||
|
@ -337,7 +346,7 @@ class ReaderWindow {
|
|||
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);
|
||||
this.annotationItemIDs = this.annotationItemIDs.filter(id => id !== annotation.id);
|
||||
await annotation.eraseTx();
|
||||
}
|
||||
}
|
||||
|
@ -380,8 +389,7 @@ class ReaderWindow {
|
|||
}
|
||||
case 'import': {
|
||||
Zotero.debug('Importing PDF annotations');
|
||||
let item = Zotero.Items.get(this._itemID);
|
||||
Zotero.PDFImport.import(item);
|
||||
Zotero.PDFWorker.import(this._itemID, true, true);
|
||||
return;
|
||||
}
|
||||
case 'importDismiss': {
|
||||
|
@ -534,7 +542,7 @@ class Reader {
|
|||
// 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));
|
||||
let disappearedIds = readerWindow.annotationItemIDs.filter(x => ids.includes(x));
|
||||
if (disappearedIds.length) {
|
||||
let keys = disappearedIds.map(id => extraData[id].key);
|
||||
readerWindow.unsetAnnotations(keys);
|
||||
|
@ -547,9 +555,9 @@ class Reader {
|
|||
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 annotationItemIDs = item.getAnnotations();
|
||||
readerWindow.annotationItemIDs = annotationItemIDs;
|
||||
let affectedAnnotationIds = annotationItemIDs.filter(annotationID => {
|
||||
let annotation = Zotero.Items.get(annotationID);
|
||||
let imageAttachmentID = null;
|
||||
annotation._loaded.childItems = true;
|
||||
|
@ -586,6 +594,7 @@ class Reader {
|
|||
}
|
||||
|
||||
async open(itemID, location) {
|
||||
this.triggerAnnotationsImportCheck(itemID);
|
||||
let reader = this._getReaderWindow(itemID);
|
||||
if (reader) {
|
||||
if (location) {
|
||||
|
@ -605,6 +614,15 @@ class Reader {
|
|||
}
|
||||
reader._window.focus();
|
||||
}
|
||||
|
||||
async triggerAnnotationsImportCheck(itemID) {
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
let mtime = await item.attachmentModificationTime;
|
||||
let dateModified = Zotero.Date.dateToISO(new Date(mtime));
|
||||
if (!Zotero.PDF.dateChecked[itemID] || Zotero.PDF.dateChecked[itemID] < dateModified) {
|
||||
await Zotero.PDFWorker.import(itemID, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Reader = new Reader();
|
||||
|
|
|
@ -2746,8 +2746,7 @@ var ZoteroPane = new function()
|
|||
'createParent',
|
||||
'renameAttachments',
|
||||
'reindexItem',
|
||||
'importAnnotations',
|
||||
'exportAnnotations'
|
||||
'importAnnotations'
|
||||
];
|
||||
|
||||
var m = {};
|
||||
|
@ -2795,8 +2794,7 @@ var ZoteroPane = new function()
|
|||
canRecognize = true,
|
||||
canUnrecognize = true,
|
||||
canRename = true,
|
||||
canImportAnnotations = true,
|
||||
canExportAnnotations = true;
|
||||
canImportAnnotations = true;
|
||||
var canMarkRead = collectionTreeRow.isFeed();
|
||||
var markUnread = true;
|
||||
|
||||
|
@ -2818,14 +2816,10 @@ var ZoteroPane = new function()
|
|||
canUnrecognize = false;
|
||||
}
|
||||
|
||||
if (canImportAnnotations && !Zotero.PDFImport.canImport(item)) {
|
||||
if (canImportAnnotations && !Zotero.PDFWorker.canImport(item)) {
|
||||
canImportAnnotations = false;
|
||||
}
|
||||
|
||||
if (canExportAnnotations && !Zotero.PDFExport.canExport(item)) {
|
||||
canExportAnnotations = false;
|
||||
}
|
||||
|
||||
// Show rename option only if all items are child attachments
|
||||
if (canRename && (!item.isAttachment() || item.isTopLevelItem() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)) {
|
||||
canRename = false;
|
||||
|
@ -2908,10 +2902,6 @@ var ZoteroPane = new function()
|
|||
if (canImportAnnotations) {
|
||||
show.push(m.importAnnotations);
|
||||
}
|
||||
|
||||
if (canExportAnnotations) {
|
||||
show.push(m.exportAnnotations);
|
||||
}
|
||||
}
|
||||
|
||||
// Single item selected
|
||||
|
@ -2981,13 +2971,9 @@ var ZoteroPane = new function()
|
|||
show.push(m.duplicateItem);
|
||||
}
|
||||
|
||||
if (Zotero.PDFImport.canImport(item)) {
|
||||
if (Zotero.PDFWorker.canImport(item)) {
|
||||
show.push(m.importAnnotations);
|
||||
}
|
||||
|
||||
if (Zotero.PDFExport.canExport(item)) {
|
||||
show.push(m.exportAnnotations);
|
||||
}
|
||||
}
|
||||
|
||||
// Update attachment submenu
|
||||
|
@ -4578,16 +4564,16 @@ var ZoteroPane = new function()
|
|||
}
|
||||
};
|
||||
|
||||
this.exportAnnotationsForSelected = async function () {
|
||||
var items = ZoteroPane.getSelectedItems();
|
||||
Zotero.PDFExport.export(items);
|
||||
Zotero.ProgressQueues.get('pdf-export').getDialog().open();
|
||||
};
|
||||
|
||||
this.importAnnotationsForSelected = async function () {
|
||||
var items = ZoteroPane.getSelectedItems();
|
||||
Zotero.PDFImport.import(items);
|
||||
Zotero.ProgressQueues.get('pdf-import').getDialog().open();
|
||||
let items = ZoteroPane.getSelectedItems();
|
||||
for (let item of items) {
|
||||
if (item.isRegularItem()) {
|
||||
Zotero.PDFWorker.importParent(item);
|
||||
}
|
||||
else if (item.isAttachment()) {
|
||||
Zotero.PDFWorker.import(item.id, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.reportMetadataForSelected = async function () {
|
||||
|
@ -4709,7 +4695,7 @@ var ZoteroPane = new function()
|
|||
var rv = await fp.show();
|
||||
if (rv === fp.returnOK || rv === fp.returnReplace) {
|
||||
let outputFile = fp.file;
|
||||
Zotero.PDFExport.exportToPath(item, outputFile, true);
|
||||
await Zotero.PDFWorker.export(item.id, outputFile, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -292,10 +292,7 @@
|
|||
<menuitem class="menuitem-iconic zotero-menuitem-create-parent" oncommand="ZoteroPane_Local.createParentItemsFromSelected();"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-rename-from-parent" oncommand="ZoteroPane_Local.renameSelectedAttachmentsFromParents()"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-reindex" oncommand="ZoteroPane_Local.reindexItem();"/>
|
||||
<!-- <menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="&zotero.items.menu.importAnnotations;" oncommand="ZoteroPane.importAnnotationsForSelected()"/>-->
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="Import annotations" oncommand="ZoteroPane.importAnnotationsForSelected()"/>
|
||||
<!-- <menuitem class="menuitem-iconic zotero-menuitem-export-annotations" label="&zotero.items.menu.exportAnnotations;" oncommand="ZoteroPane.exportAnnotationsForSelected()"/>-->
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-export-annotations" label="Export Annotations" oncommand="ZoteroPane.exportAnnotationsForSelected()"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="&zotero.items.menu.importAnnotations;" oncommand="ZoteroPane.importAnnotationsForSelected()"/>
|
||||
</menupopup>
|
||||
|
||||
<tooltip id="fake-tooltip"/>
|
||||
|
|
|
@ -100,7 +100,6 @@
|
|||
<!ENTITY zotero.items.menu.mergeItems "Merge Items…">
|
||||
<!ENTITY zotero.items.menu.unrecognize "Undo Retrieve Metadata">
|
||||
<!ENTITY zotero.items.menu.reportMetadata "Report Incorrect Metadata">
|
||||
<!ENTITY zotero.items.menu.exportAnnotations "Export Annotations">
|
||||
<!ENTITY zotero.items.menu.importAnnotations "Import Annotations">
|
||||
|
||||
<!ENTITY zotero.duplicatesMerge.versionSelect "Choose the version of the item to use as the master item:">
|
||||
|
|
|
@ -1131,11 +1131,6 @@ recognizePDF.reportMetadata = Report Incorrect Metadata
|
|||
recognizePDF.pdfName.label = PDF Name
|
||||
recognizePDF.itemName.label = Item Name
|
||||
|
||||
pdfExport.title = PDF Annotations Export
|
||||
|
||||
pdfImport.title = PDF Annotations Import
|
||||
pdfImport.annotations.label = Annotations
|
||||
|
||||
rtfScan.openTitle = Select a file to scan
|
||||
rtfScan.scanning.label = Scanning RTF Document…
|
||||
rtfScan.saving.label = Formatting RTF Document…
|
||||
|
|
|
@ -620,24 +620,6 @@
|
|||
margin-top: 1px;
|
||||
}
|
||||
|
||||
#zotero-tb-pq-pdf-export {
|
||||
list-style-image: url(chrome://zotero/skin/pdf-search.png);
|
||||
}
|
||||
|
||||
#zotero-tb-pq-pdf-export .toolbarbutton-icon {
|
||||
width: 18px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
#zotero-tb-pq-pdf-import {
|
||||
list-style-image: url(chrome://zotero/skin/pdf-search.png);
|
||||
}
|
||||
|
||||
#zotero-tb-pq-pdf-import .toolbarbutton-icon {
|
||||
width: 18px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* Sync error icon */
|
||||
#zotero-tb-sync-error {
|
||||
list-style-image: url(chrome://zotero/skin/error.png);
|
||||
|
|
|
@ -47,7 +47,7 @@ const xpcomFilesAll = [
|
|||
'http',
|
||||
'mimeTypeHandler',
|
||||
'openurl',
|
||||
'pdfWorker/transport',
|
||||
'pdfWorker/manager',
|
||||
'ipc',
|
||||
'profile',
|
||||
'progressWindow',
|
||||
|
@ -113,8 +113,6 @@ const xpcomFilesLocal = [
|
|||
'progressQueueDialog',
|
||||
'quickCopy',
|
||||
'recognizePDF',
|
||||
'pdfExport',
|
||||
'pdfImport',
|
||||
'report',
|
||||
'retractions',
|
||||
'router',
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 20b9f8f7a197b809c310db0c94bc7a5d22ed9bd0
|
||||
Subproject commit 9067fc6a9245019b0a4670f8a2b5d81f9f36ad0f
|
Loading…
Reference in a new issue