Unify attachment opening code and support EPUB/snapshot note links (#3705)
This commit is contained in:
parent
b1333d0e9d
commit
a7d59a90d3
11 changed files with 696 additions and 465 deletions
|
@ -27,9 +27,6 @@
|
|||
// eslint-disable-next-line no-undef
|
||||
class AttachmentPreview extends XULElementBase {
|
||||
static fileTypeMap = {
|
||||
'application/pdf': 'pdf',
|
||||
'application/epub+zip': 'epub',
|
||||
'text/html': 'snapshot',
|
||||
// TODO: support video and audio
|
||||
// 'video/mp4': 'video',
|
||||
// 'video/webm': 'video',
|
||||
|
@ -111,6 +108,9 @@
|
|||
}
|
||||
|
||||
get previewType() {
|
||||
if (this._item?.attachmentReaderType) {
|
||||
return this._item.attachmentReaderType;
|
||||
}
|
||||
let contentType = this._item?.attachmentContentType;
|
||||
if (!contentType) {
|
||||
return "file";
|
||||
|
|
|
@ -370,11 +370,6 @@ var Zotero_LocateMenu = new function() {
|
|||
snapshot: "zotero-menuitem-attachments-snapshot",
|
||||
multiple: "zotero-menuitem-new-tab",
|
||||
};
|
||||
const attachmentTypes = {
|
||||
"application/pdf": "pdf",
|
||||
"application/epub+zip": "epub",
|
||||
"text/html": "snapshot",
|
||||
};
|
||||
this._attachmentType = "multiple";
|
||||
Object.defineProperty(this, "className", {
|
||||
get: () => (alternateWindowBehavior ? "zotero-menuitem-new-window" : classNames[this._attachmentType]),
|
||||
|
@ -404,7 +399,7 @@ var Zotero_LocateMenu = new function() {
|
|||
this._attachmentType = "multiple";
|
||||
}
|
||||
else if (attachment) {
|
||||
this._attachmentType = attachmentTypes[attachment.attachmentContentType];
|
||||
this._attachmentType = attachment.attachmentReaderType;
|
||||
}
|
||||
return !!attachment;
|
||||
};
|
||||
|
|
|
@ -1914,7 +1914,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
|||
if (!parentItem.isFileAttachment()) {
|
||||
throw new Error("Annotation parent must be a file attachment");
|
||||
}
|
||||
if (!['application/pdf', 'application/epub+zip', 'text/html'].includes(parentItem.attachmentContentType)) {
|
||||
if (!parentItem.attachmentReaderType) {
|
||||
throw new Error("Annotation parent must be a PDF, EPUB, or HTML snapshot");
|
||||
}
|
||||
let type = this._getLatestField('annotationType');
|
||||
|
@ -3103,6 +3103,25 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentContentType', {
|
|||
});
|
||||
|
||||
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'attachmentReaderType', {
|
||||
get() {
|
||||
if (!this.isFileAttachment()) {
|
||||
return undefined;
|
||||
}
|
||||
switch (this.attachmentContentType) {
|
||||
case 'application/pdf':
|
||||
return 'pdf';
|
||||
case 'application/epub+zip':
|
||||
return 'epub';
|
||||
case 'text/html':
|
||||
return 'snapshot';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Zotero.Item.prototype.getAttachmentCharset = function() {
|
||||
Zotero.debug("getAttachmentCharset() deprecated -- use .attachmentCharset");
|
||||
return this.attachmentCharset;
|
||||
|
|
501
chrome/content/zotero/xpcom/fileHandlers.js
Normal file
501
chrome/content/zotero/xpcom/fileHandlers.js
Normal file
|
@ -0,0 +1,501 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2018 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
https://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/* eslint-disable array-element-newline */
|
||||
|
||||
Zotero.FileHandlers = {
|
||||
async open(item, params) {
|
||||
let { location, openInWindow = false } = params || {};
|
||||
|
||||
let path = await item.getFilePathAsync();
|
||||
if (!path) {
|
||||
Zotero.warn(`File not found: ${item.attachmentPath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
Zotero.debug('Opening ' + path);
|
||||
|
||||
let readerType = item.attachmentReaderType;
|
||||
|
||||
// Not a file that we/external readers handle with page number support -
|
||||
// just open it with the system handler
|
||||
if (!readerType) {
|
||||
Zotero.debug('No associated reader type -- launching default application');
|
||||
Zotero.launchFile(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
let handler = Zotero.Prefs.get(`fileHandler.${readerType}`);
|
||||
if (!handler) {
|
||||
Zotero.debug('No external handler for ' + readerType + ' -- opening in Zotero');
|
||||
await Zotero.Reader.open(item.id, location, {
|
||||
openInWindow,
|
||||
allowDuplicate: openInWindow
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
let systemHandler = this._getSystemHandler(item.attachmentContentType);
|
||||
|
||||
if (handler === 'system') {
|
||||
handler = systemHandler;
|
||||
Zotero.debug(`System handler is ${handler}`);
|
||||
}
|
||||
else {
|
||||
Zotero.debug(`Custom handler is ${handler}`);
|
||||
}
|
||||
|
||||
let handlers;
|
||||
if (this._mockHandlers) {
|
||||
handlers = this._mockHandlers[readerType];
|
||||
}
|
||||
else if (Zotero.isMac) {
|
||||
handlers = this._handlersMac[readerType];
|
||||
}
|
||||
else if (Zotero.isWin) {
|
||||
handlers = this._handlersWin[readerType];
|
||||
}
|
||||
else if (Zotero.isLinux) {
|
||||
handlers = this._handlersLinux[readerType];
|
||||
}
|
||||
|
||||
let page = location?.position?.pageIndex ?? undefined;
|
||||
// Add 1 to page index for external readers
|
||||
if (page !== undefined && parseInt(page) == page) {
|
||||
page = parseInt(page) + 1;
|
||||
}
|
||||
|
||||
// If there are handlers for this platform and this reader type...
|
||||
if (handlers) {
|
||||
// First try to open with the custom handler
|
||||
try {
|
||||
for (let [i, { name, open }] of handlers.entries()) {
|
||||
if (name.test(handler)) {
|
||||
Zotero.debug('Opening with handler ' + i);
|
||||
await open(handler, { filePath: path, location, page });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
// If we get here, we don't have special handling for the custom
|
||||
// handler that the user has set. If we have a location, we really
|
||||
// want to open with something we know how to pass a page number to,
|
||||
// so we'll see if we know how to do that for the system handler.
|
||||
if (location) {
|
||||
try {
|
||||
if (systemHandler && handler !== systemHandler) {
|
||||
Zotero.debug(`Custom handler did not match -- falling back to system handler ${systemHandler}`);
|
||||
handler = systemHandler;
|
||||
for (let [i, { name, open }] of handlers.entries()) {
|
||||
if (name.test(handler)) {
|
||||
Zotero.debug('Opening with handler ' + i);
|
||||
await open(handler, { filePath: path, location, page });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
// And lastly, the fallback handler for this platform/reader type,
|
||||
// if we have one
|
||||
let fallback = handlers.find(h => h.fallback);
|
||||
if (fallback) {
|
||||
try {
|
||||
Zotero.debug('Opening with fallback');
|
||||
await fallback.open(null, { filePath: path, location, page });
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
// Don't log error if fallback fails
|
||||
// Just move on and try system handler
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Opening handler without page number");
|
||||
|
||||
handler = handler || systemHandler;
|
||||
if (handler) {
|
||||
if (Zotero.isMac) {
|
||||
try {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', handler, path]);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (await OS.File.exists(handler)) {
|
||||
Zotero.debug(`Opening with handler ${handler}`);
|
||||
Zotero.launchFileWithApplication(path, handler);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
Zotero.logError(`${handler} not found`);
|
||||
}
|
||||
|
||||
Zotero.debug('Launching file normally');
|
||||
Zotero.launchFile(path);
|
||||
return true;
|
||||
},
|
||||
|
||||
_handlersMac: {
|
||||
pdf: [
|
||||
{
|
||||
name: /Preview/,
|
||||
fallback: true,
|
||||
async open(appPath, { filePath, page }) {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', "Preview", filePath]);
|
||||
if (page !== undefined) {
|
||||
// Go to page using AppleScript
|
||||
let args = [
|
||||
'-e', 'tell app "Preview" to activate',
|
||||
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
];
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: /Adobe Acrobat/,
|
||||
async open(appPath, { page }) {
|
||||
if (page !== undefined) {
|
||||
// Go to page using AppleScript
|
||||
let args = [
|
||||
'-e', `tell app "${appPath}" to activate`,
|
||||
'-e', 'tell app "System Events" to keystroke "n" using {command down, shift down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
];
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: /Skim/,
|
||||
async open(appPath, { filePath, page }) {
|
||||
// Escape double-quotes in path
|
||||
var quoteRE = /"/g;
|
||||
filePath = filePath.replace(quoteRE, '\\"');
|
||||
let args = [
|
||||
'-e', `tell app "${appPath}" to activate`,
|
||||
'-e', `tell app "${appPath}" to open "${filePath}"`
|
||||
];
|
||||
if (page !== undefined) {
|
||||
let filename = OS.Path.basename(filePath)
|
||||
.replace(quoteRE, '\\"');
|
||||
args.push('-e', `tell document "${filename}" of application "${appPath}" to go to page ${page}`);
|
||||
}
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: /PDF Expert/,
|
||||
async open(appPath, { page }) {
|
||||
// Go to page using AppleScript (same as Preview)
|
||||
let args = [
|
||||
'-e', `tell app "${appPath}" to activate`
|
||||
];
|
||||
if (page !== undefined) {
|
||||
args.push(
|
||||
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
);
|
||||
}
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
}
|
||||
},
|
||||
],
|
||||
epub: [
|
||||
{
|
||||
name: /Calibre/i,
|
||||
async open(appPath, { filePath, location }) {
|
||||
if (!appPath.endsWith('ebook-viewer.app')) {
|
||||
appPath += '/Contents/ebook-viewer.app';
|
||||
}
|
||||
let args = ['-a', appPath, filePath];
|
||||
if (location?.position?.value) {
|
||||
args.push('--args', '--open-at=' + location.position.value);
|
||||
}
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', args);
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
_handlersWin: {
|
||||
pdf: [
|
||||
{
|
||||
name: new RegExp(''), // Match any handler
|
||||
async open(appPath, { filePath, page }) {
|
||||
let args = [filePath];
|
||||
if (page !== undefined) {
|
||||
// Include flags to open the PDF on a given page in various apps
|
||||
//
|
||||
// Adobe Acrobat: http://partners.adobe.com/public/developer/en/acrobat/PDFOpenParameters.pdf
|
||||
// PDF-XChange: http://help.tracker-software.com/eu/default.aspx?pageid=PDFXView25:command_line_options
|
||||
args.unshift('/A', 'page=' + page);
|
||||
}
|
||||
await Zotero.Utilities.Internal.exec(appPath, args);
|
||||
}
|
||||
}
|
||||
],
|
||||
epub: [
|
||||
{
|
||||
name: /Calibre/i,
|
||||
async open(appPath, { filePath, location }) {
|
||||
if (appPath.toLowerCase().endsWith('calibre.exe')) {
|
||||
appPath = appPath.slice(0, -11) + 'ebook-viewer.exe';
|
||||
}
|
||||
let args = [filePath];
|
||||
if (location?.position?.value) {
|
||||
args.push('--open-at=' + location.position.value);
|
||||
}
|
||||
await Zotero.Utilities.Internal.exec(appPath, args);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
_handlersLinux: {
|
||||
pdf: [
|
||||
{
|
||||
name: /evince|okular/i,
|
||||
fallback: true,
|
||||
async open(appPath, { filePath, page }) {
|
||||
if (appPath) {
|
||||
switch (appPath.toLowerCase()) {
|
||||
case 'okular':
|
||||
appPath = '/usr/bin/okular';
|
||||
|
||||
// It's "Document Viewer" on stock Ubuntu
|
||||
case 'document viewer':
|
||||
case 'evince':
|
||||
appPath = '/usr/bin/evince';
|
||||
}
|
||||
}
|
||||
else if (await OS.File.exists('/usr/bin/okular')) {
|
||||
appPath = '/usr/bin/okular';
|
||||
}
|
||||
else if (await OS.File.exists('/usr/bin/evince')) {
|
||||
appPath = '/usr/bin/evince';
|
||||
}
|
||||
else {
|
||||
throw new Error('No PDF reader found');
|
||||
}
|
||||
|
||||
// TODO: Try to get default from mimeapps.list, etc., in case system default is okular
|
||||
// or evince somewhere other than /usr/bin
|
||||
|
||||
let args = [filePath];
|
||||
if (page !== undefined) {
|
||||
args.unshift('-p', page);
|
||||
}
|
||||
await Zotero.Utilities.Internal.exec(appPath, args);
|
||||
}
|
||||
}
|
||||
],
|
||||
epub: [
|
||||
{
|
||||
name: /calibre/i,
|
||||
async open(appPath, { filePath, location }) {
|
||||
if (appPath.toLowerCase().endsWith('calibre')) {
|
||||
appPath = appPath.slice(0, -7) + 'ebook-viewer';
|
||||
}
|
||||
let args = [filePath];
|
||||
if (location?.position?.value) {
|
||||
args.push('--open-at=' + location.position.value);
|
||||
}
|
||||
await Zotero.Utilities.Internal.exec(appPath, args);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
_getSystemHandler(mimeType) {
|
||||
if (Zotero.isWin) {
|
||||
return this._getSystemHandlerWin(mimeType);
|
||||
}
|
||||
else {
|
||||
return this._getSystemHandlerPOSIX(mimeType);
|
||||
}
|
||||
},
|
||||
|
||||
_getSystemHandlerWin(mimeType) {
|
||||
// Based on getPDFReader() in ZotFile (GPL)
|
||||
// https://github.com/jlegewie/zotfile/blob/a6c9e02e17b60cbc1f9bb4062486548d9ef583e3/chrome/content/zotfile/utils.js
|
||||
|
||||
var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
|
||||
.createInstance(Components.interfaces.nsIWindowsRegKey);
|
||||
// Get handler
|
||||
var extension = Zotero.MIME.getPrimaryExtension(mimeType);
|
||||
var tryKeys = [
|
||||
{
|
||||
root: wrk.ROOT_KEY_CURRENT_USER,
|
||||
path: `Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.${extension}\\UserChoice`,
|
||||
value: 'Progid'
|
||||
},
|
||||
{
|
||||
root: wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
path: `.${extension}`,
|
||||
value: ''
|
||||
}
|
||||
];
|
||||
var progId;
|
||||
for (let key of tryKeys) {
|
||||
try {
|
||||
wrk.open(key.root, key.path, wrk.ACCESS_READ);
|
||||
progId = wrk.readStringValue(key.value);
|
||||
if (progId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (!progId) {
|
||||
wrk.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get version specific handler, if it exists
|
||||
try {
|
||||
wrk.open(
|
||||
wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
progId + '\\CurVer',
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
progId = wrk.readStringValue('') || progId;
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
// Get command
|
||||
var success = false;
|
||||
tryKeys = [
|
||||
progId + '\\shell\\Read\\command',
|
||||
progId + '\\shell\\Open\\command'
|
||||
];
|
||||
for (let key of tryKeys) {
|
||||
try {
|
||||
wrk.open(
|
||||
wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
key,
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
wrk.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var command = wrk.readStringValue('').match(/^(?:".+?"|[^"]\S+)/);
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
wrk.close();
|
||||
|
||||
if (!command) return false;
|
||||
return command[0].replace(/"/g, '');
|
||||
},
|
||||
|
||||
_getSystemHandlerPOSIX(mimeType) {
|
||||
var handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
|
||||
.getService(Ci.nsIHandlerService);
|
||||
var handlers = handlerService.enumerate();
|
||||
var handler;
|
||||
while (handlers.hasMoreElements()) {
|
||||
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
|
||||
if (handlerInfo.type == mimeType) {
|
||||
handler = handlerInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!handler) {
|
||||
// We can't get the name of the system default handler unless we add an entry
|
||||
Zotero.debug("Default handler not found -- adding default entry");
|
||||
let mimeService = Components.classes["@mozilla.org/mime;1"]
|
||||
.getService(Components.interfaces.nsIMIMEService);
|
||||
let mimeInfo = mimeService.getFromTypeAndExtension(mimeType, "");
|
||||
mimeInfo.preferredAction = 4;
|
||||
mimeInfo.alwaysAskBeforeHandling = false;
|
||||
handlerService.store(mimeInfo);
|
||||
|
||||
// And once we do that, we can get the name (but not the path, unfortunately)
|
||||
let handlers = handlerService.enumerate();
|
||||
while (handlers.hasMoreElements()) {
|
||||
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
|
||||
if (handlerInfo.type == mimeType) {
|
||||
handler = handlerInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handler) {
|
||||
Zotero.debug(`Default handler is ${handler.defaultDescription}`);
|
||||
return handler.defaultDescription;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Zotero.OpenPDF = {
|
||||
openToPage: async function (pathOrItem, page, annotationKey) {
|
||||
Zotero.warn('Zotero.OpenPDF.openToPage() is deprecated -- use Zotero.FileHandlers.open()');
|
||||
if (typeof pathOrItem === 'string') {
|
||||
throw new Error('Zotero.OpenPDF.openToPage() requires an item -- update your code!');
|
||||
}
|
||||
|
||||
await Zotero.FileHandlers.open(pathOrItem, {
|
||||
location: {
|
||||
annotationID: annotationKey,
|
||||
position: {
|
||||
pageIndex: page,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,353 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2018 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
https://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/* eslint-disable array-element-newline */
|
||||
|
||||
Zotero.OpenPDF = {
|
||||
openToPage: async function (pathOrItem, page, annotationKey) {
|
||||
var handler = Zotero.Prefs.get("fileHandler.pdf");
|
||||
|
||||
var path;
|
||||
if (pathOrItem == 'string') {
|
||||
Zotero.logError("Zotero.OpenPDF.openToPage() now takes a Zotero.Item rather than a path "
|
||||
+ "-- please update your code");
|
||||
path = pathOrItem;
|
||||
}
|
||||
else {
|
||||
let item = pathOrItem;
|
||||
let library = Zotero.Libraries.get(item.libraryID);
|
||||
// Zotero PDF reader
|
||||
if (!handler) {
|
||||
let location = {
|
||||
annotationID: annotationKey,
|
||||
pageIndex: page && page - 1
|
||||
};
|
||||
await Zotero.Reader.open(item.id, location);
|
||||
return true;
|
||||
}
|
||||
|
||||
path = await item.getFilePathAsync();
|
||||
if (!path) {
|
||||
Zotero.warn(`${path} not found`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var opened = false;
|
||||
|
||||
if (handler != 'system') {
|
||||
Zotero.debug(`Custom handler is ${handler}`);
|
||||
}
|
||||
|
||||
if (Zotero.isMac) {
|
||||
if (!this._openWithHandlerMac(handler, path, page)) {
|
||||
// Try to detect default app
|
||||
handler = this._getPDFHandlerName();
|
||||
if (!this._openWithHandlerMac(handler, path, page)) {
|
||||
// Fall back to Preview
|
||||
this._openWithPreview(path, page);
|
||||
}
|
||||
}
|
||||
opened = true;
|
||||
}
|
||||
else if (Zotero.isWin) {
|
||||
if (handler == 'system') {
|
||||
handler = this._getPDFHandlerWindows();
|
||||
if (handler) {
|
||||
Zotero.debug(`Default handler is ${handler}`);
|
||||
}
|
||||
}
|
||||
if (handler) {
|
||||
// Include flags to open the PDF on a given page in various apps
|
||||
//
|
||||
// Adobe Acrobat: http://partners.adobe.com/public/developer/en/acrobat/PDFOpenParameters.pdf
|
||||
// PDF-XChange: http://help.tracker-software.com/eu/default.aspx?pageid=PDFXView25:command_line_options
|
||||
let args = ['/A', 'page=' + page, path];
|
||||
Zotero.Utilities.Internal.exec(handler, args);
|
||||
opened = true;
|
||||
}
|
||||
else {
|
||||
Zotero.debug("No handler found");
|
||||
}
|
||||
}
|
||||
else if (Zotero.isLinux) {
|
||||
if (handler == 'system') {
|
||||
handler = await this._getPDFHandlerLinux();
|
||||
if (handler) {
|
||||
Zotero.debug(`Resolved handler is ${handler}`);
|
||||
}
|
||||
}
|
||||
if (handler && (handler.includes('evince') || handler.includes('okular'))) {
|
||||
this._openWithEvinceOrOkular(handler, path, page);
|
||||
opened = true;
|
||||
}
|
||||
// Fall back to okular and then evince if unknown handler
|
||||
else if (await OS.File.exists('/usr/bin/okular')) {
|
||||
this._openWithEvinceOrOkular('/usr/bin/okular', path, page);
|
||||
opened = true;
|
||||
}
|
||||
else if (await OS.File.exists('/usr/bin/evince')) {
|
||||
this._openWithEvinceOrOkular('/usr/bin/evince', path, page);
|
||||
opened = true;
|
||||
}
|
||||
else {
|
||||
Zotero.debug("No handler found");
|
||||
}
|
||||
}
|
||||
return opened;
|
||||
},
|
||||
|
||||
_getPDFHandlerName: function () {
|
||||
var handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
|
||||
.getService(Ci.nsIHandlerService);
|
||||
var handlers = handlerService.enumerate();
|
||||
var handler;
|
||||
while (handlers.hasMoreElements()) {
|
||||
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
|
||||
if (handlerInfo.type == 'application/pdf') {
|
||||
handler = handlerInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!handler) {
|
||||
// We can't get the name of the system default handler unless we add an entry
|
||||
Zotero.debug("Default handler not found -- adding default entry");
|
||||
let mimeService = Components.classes["@mozilla.org/mime;1"]
|
||||
.getService(Components.interfaces.nsIMIMEService);
|
||||
let mimeInfo = mimeService.getFromTypeAndExtension("application/pdf", "");
|
||||
mimeInfo.preferredAction = 4;
|
||||
mimeInfo.alwaysAskBeforeHandling = false;
|
||||
handlerService.store(mimeInfo);
|
||||
|
||||
// And once we do that, we can get the name (but not the path, unfortunately)
|
||||
let handlers = handlerService.enumerate();
|
||||
while (handlers.hasMoreElements()) {
|
||||
let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
|
||||
if (handlerInfo.type == 'application/pdf') {
|
||||
handler = handlerInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handler) {
|
||||
Zotero.debug(`Default handler is ${handler.defaultDescription}`);
|
||||
return handler.defaultDescription;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
//
|
||||
// Mac
|
||||
//
|
||||
_openWithHandlerMac: function (handler, path, page) {
|
||||
if (!handler) {
|
||||
return false;
|
||||
}
|
||||
if (handler.includes('Preview')) {
|
||||
this._openWithPreview(path, page);
|
||||
return true;
|
||||
}
|
||||
if (handler.includes('Adobe Acrobat')) {
|
||||
this._openWithAcrobat(handler, path, page);
|
||||
return true;
|
||||
}
|
||||
if (handler.includes('Skim')) {
|
||||
this._openWithSkim(handler, path, page);
|
||||
return true;
|
||||
}
|
||||
if (handler.includes('PDF Expert')) {
|
||||
this._openWithPDFExpert(handler, path, page);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_openWithPreview: async function (filePath, page) {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', "Preview", filePath]);
|
||||
// Go to page using AppleScript
|
||||
let args = [
|
||||
'-e', 'tell app "Preview" to activate',
|
||||
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
];
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
},
|
||||
|
||||
_openWithAcrobat: async function (appPath, filePath, page) {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', appPath, filePath]);
|
||||
// Go to page using AppleScript
|
||||
let args = [
|
||||
'-e', `tell app "${appPath}" to activate`,
|
||||
'-e', 'tell app "System Events" to keystroke "n" using {command down, shift down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
];
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
},
|
||||
|
||||
_openWithSkim: async function (appPath, filePath, page) {
|
||||
// Escape double-quotes in path
|
||||
var quoteRE = /"/g;
|
||||
filePath = filePath.replace(quoteRE, '\\"');
|
||||
let filename = OS.Path.basename(filePath).replace(quoteRE, '\\"');
|
||||
let args = [
|
||||
'-e', `tell app "${appPath}" to activate`,
|
||||
'-e', `tell app "${appPath}" to open "${filePath}"`
|
||||
];
|
||||
args.push('-e', `tell document "${filename}" of application "${appPath}" to go to page ${page}`);
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
},
|
||||
|
||||
_openWithPDFExpert: async function (appPath, filePath, page) {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', appPath, filePath]);
|
||||
// Go to page using AppleScript (same as Preview)
|
||||
let args = [
|
||||
'-e', `tell app "${appPath}" to activate`,
|
||||
'-e', 'tell app "System Events" to keystroke "g" using {option down, command down}',
|
||||
'-e', `tell app "System Events" to keystroke "${page}"`,
|
||||
'-e', 'tell app "System Events" to keystroke return'
|
||||
];
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args);
|
||||
},
|
||||
|
||||
//
|
||||
// Windows
|
||||
//
|
||||
/**
|
||||
* Get path to default pdf reader application on windows
|
||||
*
|
||||
* From getPDFReader() in ZotFile (GPL)
|
||||
* https://github.com/jlegewie/zotfile/blob/master/chrome/content/zotfile/utils.js
|
||||
*
|
||||
* @return {String|false} - Path to default pdf reader application, or false if none
|
||||
*/
|
||||
_getPDFHandlerWindows: function () {
|
||||
var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
|
||||
.createInstance(Components.interfaces.nsIWindowsRegKey);
|
||||
// Get handler for PDFs
|
||||
var tryKeys = [
|
||||
{
|
||||
root: wrk.ROOT_KEY_CURRENT_USER,
|
||||
path: 'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.pdf\\UserChoice',
|
||||
value: 'Progid'
|
||||
},
|
||||
{
|
||||
root: wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
path: '.pdf',
|
||||
value: ''
|
||||
}
|
||||
];
|
||||
var progId;
|
||||
for (let i = 0; !progId && i < tryKeys.length; i++) {
|
||||
try {
|
||||
wrk.open(
|
||||
tryKeys[i].root,
|
||||
tryKeys[i].path,
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
progId = wrk.readStringValue(tryKeys[i].value);
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (!progId) {
|
||||
wrk.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get version specific handler, if it exists
|
||||
try {
|
||||
wrk.open(
|
||||
wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
progId + '\\CurVer',
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
progId = wrk.readStringValue('') || progId;
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
// Get command
|
||||
var success = false;
|
||||
tryKeys = [
|
||||
progId + '\\shell\\Read\\command',
|
||||
progId + '\\shell\\Open\\command'
|
||||
];
|
||||
for (let i = 0; !success && i < tryKeys.length; i++) {
|
||||
try {
|
||||
wrk.open(
|
||||
wrk.ROOT_KEY_CLASSES_ROOT,
|
||||
tryKeys[i],
|
||||
wrk.ACCESS_READ
|
||||
);
|
||||
success = true;
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
wrk.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var command = wrk.readStringValue('').match(/^(?:".+?"|[^"]\S+)/);
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
wrk.close();
|
||||
|
||||
if (!command) return false;
|
||||
return command[0].replace(/"/g, '');
|
||||
},
|
||||
|
||||
//
|
||||
// Linux
|
||||
//
|
||||
_getPDFHandlerLinux: async function () {
|
||||
var name = this._getPDFHandlerName();
|
||||
switch (name.toLowerCase()) {
|
||||
case 'okular':
|
||||
return `/usr/bin/${name}`;
|
||||
|
||||
// It's "Document Viewer" on stock Ubuntu
|
||||
case 'document viewer':
|
||||
case 'evince':
|
||||
return `/usr/bin/evince`;
|
||||
}
|
||||
|
||||
// TODO: Try to get default from mimeapps.list, etc., in case system default is okular
|
||||
// or evince somewhere other than /usr/bin
|
||||
var homeDir = OS.Constants.Path.homeDir;
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
_openWithEvinceOrOkular: function (appPath, filePath, page) {
|
||||
var args = ['-p', page, filePath];
|
||||
Zotero.Utilities.Internal.exec(appPath, args);
|
||||
}
|
||||
}
|
|
@ -48,11 +48,9 @@ class ReaderInstance {
|
|||
this._pendingWriteStateTimeout = null;
|
||||
this._pendingWriteStateFunction = null;
|
||||
|
||||
switch (this._item.attachmentContentType) {
|
||||
case 'application/pdf': this._type = 'pdf'; break;
|
||||
case 'application/epub+zip': this._type = 'epub'; break;
|
||||
case 'text/html': this._type = 'snapshot'; break;
|
||||
default: throw new Error('Unsupported attachment type');
|
||||
this._type = this._item.attachmentReaderType;
|
||||
if (!this._type) {
|
||||
throw new Error('Unsupported attachment type');
|
||||
}
|
||||
|
||||
return new Proxy(this, {
|
||||
|
@ -93,7 +91,7 @@ class ReaderInstance {
|
|||
}
|
||||
|
||||
getSecondViewState() {
|
||||
let state = this._iframeWindow.wrappedJSObject.getSecondViewState();
|
||||
let state = this._iframeWindow?.wrappedJSObject?.getSecondViewState?.();
|
||||
return state ? JSON.parse(JSON.stringify(state)) : undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -998,6 +998,13 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
file.launch();
|
||||
}
|
||||
catch (e) {
|
||||
// macOS only: if there's no associated application, launch() will throw, but
|
||||
// the OS will show a dialog asking the user to choose an application. We don't
|
||||
// want to show the Firefox dialog in that case.
|
||||
if (Zotero.isMac && file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug(e, 2);
|
||||
Zotero.debug("launch() not supported -- trying fallback executable", 2);
|
||||
|
||||
|
@ -1013,18 +1020,19 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
Zotero.debug("Launching via executable failed -- passing to loadUrl()");
|
||||
Zotero.debug("Launching via executable failed -- passing to loadURI()");
|
||||
|
||||
// If nsIFile.launch() isn't available and the fallback
|
||||
// executable doesn't exist, we just let the Firefox external
|
||||
// helper app window handle it
|
||||
var nsIFPH = Components.classes["@mozilla.org/network/protocol;1?name=file"]
|
||||
.getService(Components.interfaces.nsIFileProtocolHandler);
|
||||
var uri = nsIFPH.newFileURI(file);
|
||||
var uri = Services.io.newFileURI(file);
|
||||
|
||||
var nsIEPS = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"].
|
||||
getService(Components.interfaces.nsIExternalProtocolService);
|
||||
nsIEPS.loadUrl(uri);
|
||||
nsIEPS.loadURI(
|
||||
uri,
|
||||
Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4908,64 +4908,15 @@ var ZoteroPane = new function()
|
|||
await item.saveTx();
|
||||
}
|
||||
|
||||
if (['application/pdf', 'application/epub+zip', 'text/html'].includes(contentType)) {
|
||||
let type;
|
||||
if (contentType === 'application/pdf') {
|
||||
type = 'pdf';
|
||||
}
|
||||
else if (contentType === 'application/epub+zip') {
|
||||
type = 'epub';
|
||||
}
|
||||
else {
|
||||
type = 'snapshot';
|
||||
}
|
||||
let handler = Zotero.Prefs.get('fileHandler.' + type);
|
||||
|
||||
// Zotero PDF reader
|
||||
if (!handler) {
|
||||
let openInWindow = Zotero.Prefs.get('openReaderInNewWindow');
|
||||
let useAlternateWindowBehavior = event?.shiftKey || extraData?.forceAlternateWindowBehavior;
|
||||
if (useAlternateWindowBehavior) {
|
||||
openInWindow = !openInWindow;
|
||||
}
|
||||
await Zotero.Reader.open(
|
||||
item.id,
|
||||
extraData && extraData.location,
|
||||
{
|
||||
openInWindow,
|
||||
allowDuplicate: openInWindow
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Try to open external PDF reader to page number if specified
|
||||
// TODO: Implement for EPUBs if readers support it
|
||||
else if (type == 'pdf') {
|
||||
let pageIndex = extraData?.location?.position?.pageIndex;
|
||||
if (pageIndex !== undefined) {
|
||||
await Zotero.OpenPDF.openToPage(
|
||||
item,
|
||||
parseInt(pageIndex) + 1
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Custom PDF handler
|
||||
// TODO: Remove this and unify with Zotero.OpenPDF
|
||||
if (handler != 'system') {
|
||||
try {
|
||||
if (await OS.File.exists(handler)) {
|
||||
Zotero.launchFileWithApplication(path, handler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
Zotero.logError(`${handler} not found -- launching file normally`);
|
||||
}
|
||||
let openInWindow = Zotero.Prefs.get('openReaderInNewWindow');
|
||||
let useAlternateWindowBehavior = event?.shiftKey || extraData?.forceAlternateWindowBehavior;
|
||||
if (useAlternateWindowBehavior) {
|
||||
openInWindow = !openInWindow;
|
||||
}
|
||||
Zotero.launchFile(path);
|
||||
await Zotero.FileHandlers.open(item, {
|
||||
location: extraData?.location,
|
||||
openInWindow,
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < itemIDs.length; i++) {
|
||||
|
|
|
@ -1135,7 +1135,7 @@ function ZoteroProtocolHandler() {
|
|||
* Also supports ZotFile format:
|
||||
* zotero://open-pdf/[libraryID]_[key]/[page]
|
||||
*/
|
||||
var OpenPDFExtension = {
|
||||
var OpenExtension = {
|
||||
noContent: true,
|
||||
|
||||
doAction: async function (uri) {
|
||||
|
@ -1145,8 +1145,7 @@ function ZoteroProtocolHandler() {
|
|||
if (!uriPath) {
|
||||
return 'Invalid URL';
|
||||
}
|
||||
uriPath = uriPath.substr('//open-pdf/'.length);
|
||||
var mimeType, content = '';
|
||||
uriPath = uriPath.replace(/^\/\/open(-pdf)?\//, '');
|
||||
|
||||
var params = {
|
||||
objectType: 'item'
|
||||
|
@ -1174,11 +1173,7 @@ function ZoteroProtocolHandler() {
|
|||
|
||||
Zotero.API.parseParams(params);
|
||||
var results = await Zotero.API.getResultsFromParams(params);
|
||||
var page = params.page;
|
||||
if (parseInt(page) != page) {
|
||||
page = null;
|
||||
}
|
||||
var annotation = params.annotation;
|
||||
var { annotation, page, cfi, sel } = params;
|
||||
|
||||
if (!results.length) {
|
||||
Zotero.warn(`No item found for ${uriPath}`);
|
||||
|
@ -1198,32 +1193,43 @@ function ZoteroProtocolHandler() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!path.toLowerCase().endsWith('.pdf')
|
||||
&& Zotero.MIME.sniffForMIMEType(await Zotero.File.getSample(path)) != 'application/pdf') {
|
||||
Zotero.warn(`${path} is not a PDF`);
|
||||
return;
|
||||
try {
|
||||
if (page) {
|
||||
await Zotero.FileHandlers.open(item, {
|
||||
location: {
|
||||
position: {
|
||||
pageIndex: page
|
||||
},
|
||||
annotationID: annotation
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (cfi) {
|
||||
await Zotero.FileHandlers.open(item, {
|
||||
location: {
|
||||
position: {
|
||||
type: 'FragmentSelector',
|
||||
conformsTo: 'http://www.idpf.org/epub/linking/cfi/epub-cfi.html',
|
||||
value: cfi
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (sel) {
|
||||
await Zotero.FileHandlers.open(item, {
|
||||
location: {
|
||||
position: {
|
||||
type: 'CssSelector',
|
||||
value: sel
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
var opened = false;
|
||||
if (page || annotation) {
|
||||
try {
|
||||
opened = await Zotero.OpenPDF.openToPage(item, page, annotation);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// If something went wrong, just open PDF without page
|
||||
if (!opened) {
|
||||
Zotero.debug("Launching PDF without page number");
|
||||
let zp = Zotero.getActiveZoteroPane();
|
||||
// TODO: Open pane if closed (macOS)
|
||||
if (zp) {
|
||||
zp.viewAttachment([item.id]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Zotero.Notifier.trigger('open', 'file', item.id);
|
||||
},
|
||||
|
||||
|
@ -1241,7 +1247,8 @@ function ZoteroProtocolHandler() {
|
|||
this._extensions[ZOTERO_SCHEME + "://debug"] = DebugExtension;
|
||||
this._extensions[ZOTERO_SCHEME + "://connector"] = ConnectorExtension;
|
||||
this._extensions[ZOTERO_SCHEME + "://pdf.js"] = PDFJSExtension;
|
||||
this._extensions[ZOTERO_SCHEME + "://open-pdf"] = OpenPDFExtension;
|
||||
this._extensions[ZOTERO_SCHEME + "://open"] = OpenExtension;
|
||||
this._extensions[ZOTERO_SCHEME + "://open-pdf"] = OpenExtension;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ const xpcomFilesLocal = [
|
|||
'locateManager',
|
||||
'mime',
|
||||
'notifier',
|
||||
'openPDF',
|
||||
'fileHandlers',
|
||||
'plugins',
|
||||
'reader',
|
||||
'progressQueue',
|
||||
|
|
105
test/tests/fileHandlersTest.js
Normal file
105
test/tests/fileHandlersTest.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
describe("Zotero.FileHandlers", () => {
|
||||
describe("open()", () => {
|
||||
var win;
|
||||
|
||||
function clearPrefs() {
|
||||
Zotero.Prefs.clear('fileHandler.pdf');
|
||||
Zotero.Prefs.clear('fileHandler.epub');
|
||||
Zotero.Prefs.clear('fileHandler.snapshot');
|
||||
Zotero.Prefs.clear('openReaderInNewWindow');
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
clearPrefs();
|
||||
win = await loadZoteroPane();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
clearPrefs();
|
||||
delete Zotero.FileHandlers._mockHandlers;
|
||||
for (let reader of Zotero.Reader._readers) {
|
||||
reader.close();
|
||||
}
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
win.close();
|
||||
});
|
||||
|
||||
it("should open a PDF internally when no handler is set", async function () {
|
||||
let pdf = await importFileAttachment('wonderland_short.pdf');
|
||||
await Zotero.FileHandlers.open(pdf, {
|
||||
location: { position: { pageIndex: 2 } }
|
||||
});
|
||||
assert.ok(Zotero.Reader.getByTabID(win.Zotero_Tabs.selectedID));
|
||||
});
|
||||
|
||||
it("should open a PDF in a new window when no handler is set and openInWindow is passed", async function () {
|
||||
let pdf = await importFileAttachment('wonderland_short.pdf');
|
||||
await Zotero.FileHandlers.open(pdf, {
|
||||
location: { position: { pageIndex: 2 } },
|
||||
openInWindow: true
|
||||
});
|
||||
assert.notOk(Zotero.Reader.getByTabID(win.Zotero_Tabs.selectedID));
|
||||
assert.isNotEmpty(Zotero.Reader.getWindowStates());
|
||||
});
|
||||
|
||||
it("should use matching handler", async function () {
|
||||
let pdf = await importFileAttachment('wonderland_short.pdf');
|
||||
let wasRun = false;
|
||||
let readerOpenSpy = sinon.spy(Zotero.Reader, 'open');
|
||||
Zotero.FileHandlers._mockHandlers = {
|
||||
pdf: [
|
||||
{
|
||||
name: /mock/,
|
||||
async open() {
|
||||
wasRun = true;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
Zotero.Prefs.set('fileHandler.pdf', 'mock');
|
||||
|
||||
await Zotero.FileHandlers.open(pdf);
|
||||
assert.isTrue(wasRun);
|
||||
assert.isFalse(readerOpenSpy.called);
|
||||
assert.notOk(Zotero.Reader.getByTabID(win.Zotero_Tabs.selectedID));
|
||||
assert.isEmpty(Zotero.Reader.getWindowStates());
|
||||
|
||||
readerOpenSpy.restore();
|
||||
});
|
||||
|
||||
it("should fall back to fallback handler when location is passed", async function () {
|
||||
let pdf = await importFileAttachment('wonderland_short.pdf');
|
||||
let wasRun = false;
|
||||
let readerOpenSpy = sinon.spy(Zotero.Reader, 'open');
|
||||
Zotero.FileHandlers._mockHandlers = {
|
||||
pdf: [
|
||||
{
|
||||
name: /mock/,
|
||||
fallback: true,
|
||||
async open(appPath) {
|
||||
assert.notOk(appPath); // appPath won't be set when called as fallback
|
||||
wasRun = true;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Set our custom handler to something nonexistent,
|
||||
// and stub the system handler to something nonexistent as well
|
||||
Zotero.Prefs.set('fileHandler.pdf', 'some nonexistent tool');
|
||||
let getSystemHandlerStub = sinon.stub(Zotero.FileHandlers, '_getSystemHandler');
|
||||
getSystemHandlerStub.returns('some other nonexistent tool');
|
||||
|
||||
await Zotero.FileHandlers.open(pdf, { location: {} });
|
||||
assert.isTrue(wasRun);
|
||||
assert.isFalse(readerOpenSpy.called);
|
||||
assert.notOk(Zotero.Reader.getByTabID(win.Zotero_Tabs.selectedID));
|
||||
assert.isEmpty(Zotero.Reader.getWindowStates());
|
||||
|
||||
readerOpenSpy.restore();
|
||||
getSystemHandlerStub.restore();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue