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 {
|
class ReaderWindow {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.annotationItemIds = [];
|
this.annotationItemIDs = [];
|
||||||
this._instanceID = Zotero.Utilities.randomString();
|
this._instanceID = Zotero.Utilities.randomString();
|
||||||
this._window = null;
|
this._window = null;
|
||||||
this._iframeWindow = null;
|
this._iframeWindow = null;
|
||||||
|
@ -115,7 +119,8 @@ class ReaderWindow {
|
||||||
state,
|
state,
|
||||||
location,
|
location,
|
||||||
enablePrev: !!this._prevHistory.length,
|
enablePrev: !!this._prevHistory.length,
|
||||||
enableNext: !!this._nextHistory.length
|
enableNext: !!this._nextHistory.length,
|
||||||
|
promptImport: !!Zotero.PDF.hasUnmachedAnnotations[this._itemID]
|
||||||
}, [buf]);
|
}, [buf]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -156,6 +161,10 @@ class ReaderWindow {
|
||||||
this._postMessage({ action: 'navigate', location });
|
this._postMessage({ action: 'navigate', location });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleImportPrompt(enable) {
|
||||||
|
this._postMessage({ action: 'toggleImportPrompt', enable });
|
||||||
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this._window.close();
|
this._window.close();
|
||||||
}
|
}
|
||||||
|
@ -337,7 +346,7 @@ class ReaderWindow {
|
||||||
let annotation = Zotero.Items.getByLibraryAndKey(libraryID, key);
|
let annotation = Zotero.Items.getByLibraryAndKey(libraryID, key);
|
||||||
// A small check, as we are receiving a list of item keys from a less secure code
|
// 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) {
|
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();
|
await annotation.eraseTx();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,8 +389,7 @@ class ReaderWindow {
|
||||||
}
|
}
|
||||||
case 'import': {
|
case 'import': {
|
||||||
Zotero.debug('Importing PDF annotations');
|
Zotero.debug('Importing PDF annotations');
|
||||||
let item = Zotero.Items.get(this._itemID);
|
Zotero.PDFWorker.import(this._itemID, true, true);
|
||||||
Zotero.PDFImport.import(item);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'importDismiss': {
|
case 'importDismiss': {
|
||||||
|
@ -534,7 +542,7 @@ class Reader {
|
||||||
// Listen for the parent item, PDF attachment and its annotation items updates
|
// Listen for the parent item, PDF attachment and its annotation items updates
|
||||||
for (let readerWindow of this._readerWindows) {
|
for (let readerWindow of this._readerWindows) {
|
||||||
if (event === 'delete') {
|
if (event === 'delete') {
|
||||||
let disappearedIds = readerWindow.annotationItemIds.filter(x => ids.includes(x));
|
let disappearedIds = readerWindow.annotationItemIDs.filter(x => ids.includes(x));
|
||||||
if (disappearedIds.length) {
|
if (disappearedIds.length) {
|
||||||
let keys = disappearedIds.map(id => extraData[id].key);
|
let keys = disappearedIds.map(id => extraData[id].key);
|
||||||
readerWindow.unsetAnnotations(keys);
|
readerWindow.unsetAnnotations(keys);
|
||||||
|
@ -547,9 +555,9 @@ class Reader {
|
||||||
let item = Zotero.Items.get(readerWindow._itemID);
|
let item = Zotero.Items.get(readerWindow._itemID);
|
||||||
// TODO: Remove when fixed
|
// TODO: Remove when fixed
|
||||||
item._loaded.childItems = true;
|
item._loaded.childItems = true;
|
||||||
let annotationItemIds = item.getAnnotations();
|
let annotationItemIDs = item.getAnnotations();
|
||||||
readerWindow.annotationItemIds = annotationItemIds;
|
readerWindow.annotationItemIDs = annotationItemIDs;
|
||||||
let affectedAnnotationIds = annotationItemIds.filter(annotationID => {
|
let affectedAnnotationIds = annotationItemIDs.filter(annotationID => {
|
||||||
let annotation = Zotero.Items.get(annotationID);
|
let annotation = Zotero.Items.get(annotationID);
|
||||||
let imageAttachmentID = null;
|
let imageAttachmentID = null;
|
||||||
annotation._loaded.childItems = true;
|
annotation._loaded.childItems = true;
|
||||||
|
@ -586,6 +594,7 @@ class Reader {
|
||||||
}
|
}
|
||||||
|
|
||||||
async open(itemID, location) {
|
async open(itemID, location) {
|
||||||
|
this.triggerAnnotationsImportCheck(itemID);
|
||||||
let reader = this._getReaderWindow(itemID);
|
let reader = this._getReaderWindow(itemID);
|
||||||
if (reader) {
|
if (reader) {
|
||||||
if (location) {
|
if (location) {
|
||||||
|
@ -605,6 +614,15 @@ class Reader {
|
||||||
}
|
}
|
||||||
reader._window.focus();
|
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();
|
Zotero.Reader = new Reader();
|
||||||
|
|
|
@ -2746,8 +2746,7 @@ var ZoteroPane = new function()
|
||||||
'createParent',
|
'createParent',
|
||||||
'renameAttachments',
|
'renameAttachments',
|
||||||
'reindexItem',
|
'reindexItem',
|
||||||
'importAnnotations',
|
'importAnnotations'
|
||||||
'exportAnnotations'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var m = {};
|
var m = {};
|
||||||
|
@ -2795,8 +2794,7 @@ var ZoteroPane = new function()
|
||||||
canRecognize = true,
|
canRecognize = true,
|
||||||
canUnrecognize = true,
|
canUnrecognize = true,
|
||||||
canRename = true,
|
canRename = true,
|
||||||
canImportAnnotations = true,
|
canImportAnnotations = true;
|
||||||
canExportAnnotations = true;
|
|
||||||
var canMarkRead = collectionTreeRow.isFeed();
|
var canMarkRead = collectionTreeRow.isFeed();
|
||||||
var markUnread = true;
|
var markUnread = true;
|
||||||
|
|
||||||
|
@ -2818,14 +2816,10 @@ var ZoteroPane = new function()
|
||||||
canUnrecognize = false;
|
canUnrecognize = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canImportAnnotations && !Zotero.PDFImport.canImport(item)) {
|
if (canImportAnnotations && !Zotero.PDFWorker.canImport(item)) {
|
||||||
canImportAnnotations = false;
|
canImportAnnotations = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canExportAnnotations && !Zotero.PDFExport.canExport(item)) {
|
|
||||||
canExportAnnotations = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show rename option only if all items are child attachments
|
// Show rename option only if all items are child attachments
|
||||||
if (canRename && (!item.isAttachment() || item.isTopLevelItem() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)) {
|
if (canRename && (!item.isAttachment() || item.isTopLevelItem() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)) {
|
||||||
canRename = false;
|
canRename = false;
|
||||||
|
@ -2908,10 +2902,6 @@ var ZoteroPane = new function()
|
||||||
if (canImportAnnotations) {
|
if (canImportAnnotations) {
|
||||||
show.push(m.importAnnotations);
|
show.push(m.importAnnotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canExportAnnotations) {
|
|
||||||
show.push(m.exportAnnotations);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single item selected
|
// Single item selected
|
||||||
|
@ -2981,13 +2971,9 @@ var ZoteroPane = new function()
|
||||||
show.push(m.duplicateItem);
|
show.push(m.duplicateItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Zotero.PDFImport.canImport(item)) {
|
if (Zotero.PDFWorker.canImport(item)) {
|
||||||
show.push(m.importAnnotations);
|
show.push(m.importAnnotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Zotero.PDFExport.canExport(item)) {
|
|
||||||
show.push(m.exportAnnotations);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update attachment submenu
|
// 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 () {
|
this.importAnnotationsForSelected = async function () {
|
||||||
var items = ZoteroPane.getSelectedItems();
|
let items = ZoteroPane.getSelectedItems();
|
||||||
Zotero.PDFImport.import(items);
|
for (let item of items) {
|
||||||
Zotero.ProgressQueues.get('pdf-import').getDialog().open();
|
if (item.isRegularItem()) {
|
||||||
|
Zotero.PDFWorker.importParent(item);
|
||||||
|
}
|
||||||
|
else if (item.isAttachment()) {
|
||||||
|
Zotero.PDFWorker.import(item.id, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.reportMetadataForSelected = async function () {
|
this.reportMetadataForSelected = async function () {
|
||||||
|
@ -4709,7 +4695,7 @@ var ZoteroPane = new function()
|
||||||
var rv = await fp.show();
|
var rv = await fp.show();
|
||||||
if (rv === fp.returnOK || rv === fp.returnReplace) {
|
if (rv === fp.returnOK || rv === fp.returnReplace) {
|
||||||
let outputFile = fp.file;
|
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-create-parent" oncommand="ZoteroPane_Local.createParentItemsFromSelected();"/>
|
||||||
<menuitem class="menuitem-iconic zotero-menuitem-rename-from-parent" oncommand="ZoteroPane_Local.renameSelectedAttachmentsFromParents()"/>
|
<menuitem class="menuitem-iconic zotero-menuitem-rename-from-parent" oncommand="ZoteroPane_Local.renameSelectedAttachmentsFromParents()"/>
|
||||||
<menuitem class="menuitem-iconic zotero-menuitem-reindex" oncommand="ZoteroPane_Local.reindexItem();"/>
|
<menuitem class="menuitem-iconic zotero-menuitem-reindex" oncommand="ZoteroPane_Local.reindexItem();"/>
|
||||||
<!-- <menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="&zotero.items.menu.importAnnotations;" oncommand="ZoteroPane.importAnnotationsForSelected()"/>-->
|
<menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="&zotero.items.menu.importAnnotations;" oncommand="ZoteroPane.importAnnotationsForSelected()"/>
|
||||||
<menuitem class="menuitem-iconic zotero-menuitem-import-annotations" label="Import annotations" oncommand="ZoteroPane.importAnnotationsForSelected()"/>
|
|
||||||
<!-- <menuitem class="menuitem-iconic zotero-menuitem-export-annotations" label="&zotero.items.menu.exportAnnotations;" oncommand="ZoteroPane.exportAnnotationsForSelected()"/>-->
|
|
||||||
<menuitem class="menuitem-iconic zotero-menuitem-export-annotations" label="Export Annotations" oncommand="ZoteroPane.exportAnnotationsForSelected()"/>
|
|
||||||
</menupopup>
|
</menupopup>
|
||||||
|
|
||||||
<tooltip id="fake-tooltip"/>
|
<tooltip id="fake-tooltip"/>
|
||||||
|
|
|
@ -100,7 +100,6 @@
|
||||||
<!ENTITY zotero.items.menu.mergeItems "Merge Items…">
|
<!ENTITY zotero.items.menu.mergeItems "Merge Items…">
|
||||||
<!ENTITY zotero.items.menu.unrecognize "Undo Retrieve Metadata">
|
<!ENTITY zotero.items.menu.unrecognize "Undo Retrieve Metadata">
|
||||||
<!ENTITY zotero.items.menu.reportMetadata "Report Incorrect Metadata">
|
<!ENTITY zotero.items.menu.reportMetadata "Report Incorrect Metadata">
|
||||||
<!ENTITY zotero.items.menu.exportAnnotations "Export Annotations">
|
|
||||||
<!ENTITY zotero.items.menu.importAnnotations "Import Annotations">
|
<!ENTITY zotero.items.menu.importAnnotations "Import Annotations">
|
||||||
|
|
||||||
<!ENTITY zotero.duplicatesMerge.versionSelect "Choose the version of the item to use as the master item:">
|
<!ENTITY zotero.duplicatesMerge.versionSelect "Choose the version of the item to use as the master item:">
|
||||||
|
|
|
@ -1131,11 +1131,6 @@ recognizePDF.reportMetadata = Report Incorrect Metadata
|
||||||
recognizePDF.pdfName.label = PDF Name
|
recognizePDF.pdfName.label = PDF Name
|
||||||
recognizePDF.itemName.label = Item Name
|
recognizePDF.itemName.label = Item Name
|
||||||
|
|
||||||
pdfExport.title = PDF Annotations Export
|
|
||||||
|
|
||||||
pdfImport.title = PDF Annotations Import
|
|
||||||
pdfImport.annotations.label = Annotations
|
|
||||||
|
|
||||||
rtfScan.openTitle = Select a file to scan
|
rtfScan.openTitle = Select a file to scan
|
||||||
rtfScan.scanning.label = Scanning RTF Document…
|
rtfScan.scanning.label = Scanning RTF Document…
|
||||||
rtfScan.saving.label = Formatting RTF Document…
|
rtfScan.saving.label = Formatting RTF Document…
|
||||||
|
|
|
@ -620,24 +620,6 @@
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#zotero-tb-pq-pdf-export {
|
|
||||||
list-style-image: url(chrome://zotero/skin/pdf-search.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
#zotero-tb-pq-pdf-export .toolbarbutton-icon {
|
|
||||||
width: 18px;
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zotero-tb-pq-pdf-import {
|
|
||||||
list-style-image: url(chrome://zotero/skin/pdf-search.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
#zotero-tb-pq-pdf-import .toolbarbutton-icon {
|
|
||||||
width: 18px;
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sync error icon */
|
/* Sync error icon */
|
||||||
#zotero-tb-sync-error {
|
#zotero-tb-sync-error {
|
||||||
list-style-image: url(chrome://zotero/skin/error.png);
|
list-style-image: url(chrome://zotero/skin/error.png);
|
||||||
|
|
|
@ -47,7 +47,7 @@ const xpcomFilesAll = [
|
||||||
'http',
|
'http',
|
||||||
'mimeTypeHandler',
|
'mimeTypeHandler',
|
||||||
'openurl',
|
'openurl',
|
||||||
'pdfWorker/transport',
|
'pdfWorker/manager',
|
||||||
'ipc',
|
'ipc',
|
||||||
'profile',
|
'profile',
|
||||||
'progressWindow',
|
'progressWindow',
|
||||||
|
@ -113,8 +113,6 @@ const xpcomFilesLocal = [
|
||||||
'progressQueueDialog',
|
'progressQueueDialog',
|
||||||
'quickCopy',
|
'quickCopy',
|
||||||
'recognizePDF',
|
'recognizePDF',
|
||||||
'pdfExport',
|
|
||||||
'pdfImport',
|
|
||||||
'report',
|
'report',
|
||||||
'retractions',
|
'retractions',
|
||||||
'router',
|
'router',
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 20b9f8f7a197b809c310db0c94bc7a5d22ed9bd0
|
Subproject commit 9067fc6a9245019b0a4670f8a2b5d81f9f36ad0f
|
Loading…
Reference in a new issue