diff --git a/chrome/content/zotero/bindings/styled-textbox.xml b/chrome/content/zotero/bindings/styled-textbox.xml index 45733f86f..977bb7f76 100644 --- a/chrome/content/zotero/bindings/styled-textbox.xml +++ b/chrome/content/zotero/bindings/styled-textbox.xml @@ -440,7 +440,11 @@ this._changed = true; commandEvent = true; break; - + + case 'ZoteroLinkClick': + ZoteroPane.loadURI(event.value); + break; + default: return; } diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index c802c7515..f4fce70b2 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -51,7 +51,6 @@ var ZoteroPane = new function() this.getSortedItems = getSortedItems; this.getSortField = getSortField; this.getSortDirection = getSortDirection; - this.loadURI = loadURI; this.setItemsPaneMessage = setItemsPaneMessage; this.clearItemsPaneMessage = clearItemsPaneMessage; this.contextPopupShowing = contextPopupShowing; @@ -3188,7 +3187,7 @@ var ZoteroPane = new function() * (e.g. meta-click == new background tab, meta-shift-click == new front tab, * shift-click == new window, no modifier == frontmost tab */ - function loadURI(uris, event) { + this.loadURI = function (uris, event) { if(typeof uris === "string") { uris = [uris]; } @@ -3203,9 +3202,22 @@ var ZoteroPane = new function() if (Zotero.isStandalone) { if(uri.match(/^https?/)) { this.launchURL(uri); - } else { - Zotero.openInViewer(uri); + return; } + + // Handle no-content zotero: URLs (e.g., zotero://select) without opening viewer + if (uri.startsWith('zotero:')) { + let nsIURI = Services.io.newURI(uri, null, null); + let handler = Components.classes["@mozilla.org/network/protocol;1?name=zotero"] + .createInstance(Components.interfaces.nsIProtocolHandler); + let extension = handler.wrappedJSObject.getExtension(nsIURI); + if (extension.noContent) { + extension.doAction(nsIURI); + return; + } + } + + Zotero.openInViewer(uri); return; } diff --git a/components/zotero-protocol-handler.js b/components/zotero-protocol-handler.js index 372e470e1..2970ada2a 100644 --- a/components/zotero-protocol-handler.js +++ b/components/zotero-protocol-handler.js @@ -127,7 +127,7 @@ function ZoteroProtocolHandler() { router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items'); // All items - router.add('library/items', function () { + router.add('library/items/:objectKey', function () { params.libraryID = userLibraryID; }); router.add('groups/:groupID/items'); @@ -796,73 +796,70 @@ function ZoteroProtocolHandler() { * zotero://select/[type]/1234 (not consistent across synced machines) */ var SelectExtension = { - newChannel: function (uri) { - return new AsyncChannel(uri, function* () { - var userLibraryID = Zotero.Libraries.userLibraryID; - - var path = uri.path; - if (!path) { - return 'Invalid URL'; - } - // Strip leading '/' - path = path.substr(1); - var mimeType, content = ''; - - var params = { - objectType: 'item' - }; - var router = new Zotero.Router(params); - - // Item within a collection or search - router.add('library/:scopeObject/:scopeObjectKey/items/:objectKey', function () { - params.libraryID = userLibraryID; - }); - router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items/:objectKey'); - - // All items - router.add('library/items/:objectKey', function () { - params.libraryID = userLibraryID; - }); - router.add('groups/:groupID/items/:objectKey'); - - // Old-style URLs - router.add('item/:id', function () { - var lkh = Zotero.Items.parseLibraryKeyHash(params.id); - if (lkh) { - params.libraryID = lkh.libraryID; - params.objectKey = lkh.key; - } - else { - params.objectID = params.id; - } - delete params.id; - }); - router.run(path); - - try { - Zotero.API.parseParams(params); - var results = yield Zotero.API.getResultsFromParams(params); - } - catch (e) { - Zotero.debug(e, 1); - return e.toString(); - } - - - if (!results.length) { - var msg = "Selected items not found"; - Zotero.debug(msg, 2); - Components.utils.reportError(msg); - return; - } - - var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] - .getService(Components.interfaces.nsIWindowMediator); - var win = wm.getMostRecentWindow("navigator:browser"); - - // TODO: Currently only able to select one item - yield win.ZoteroPane.selectItem(results[0].id); + noContent: true, + + doAction: Zotero.Promise.coroutine(function* (uri) { + var userLibraryID = Zotero.Libraries.userLibraryID; + + var path = uri.path; + if (!path) { + return 'Invalid URL'; + } + // Strip leading '/' + path = path.substr(1); + var mimeType, content = ''; + + var params = { + objectType: 'item' + }; + var router = new Zotero.Router(params); + + // Item within a collection or search + router.add('library/:scopeObject/:scopeObjectKey/items/:objectKey', function () { + params.libraryID = userLibraryID; }); + router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items/:objectKey'); + + // All items + router.add('library/items/:objectKey', function () { + params.libraryID = userLibraryID; + }); + router.add('groups/:groupID/items/:objectKey'); + + // Old-style URLs + router.add('item/:id', function () { + var lkh = Zotero.Items.parseLibraryKeyHash(params.id); + if (lkh) { + params.libraryID = lkh.libraryID; + params.objectKey = lkh.key; + } + else { + params.objectID = params.id; + } + delete params.id; + }); + router.run(path); + + Zotero.API.parseParams(params); + var results = yield Zotero.API.getResultsFromParams(params); + + if (!results.length) { + var msg = "Items not found"; + Zotero.debug(msg, 2); + Components.utils.reportError(msg); + return; + } + + var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator); + var win = wm.getMostRecentWindow("navigator:browser"); + + // TODO: Currently only able to select one item + return win.ZoteroPane.selectItem(results[0].id); + }), + + newChannel: function (uri) { + this.doAction(uri); } }; @@ -1039,6 +1036,22 @@ ZoteroProtocolHandler.prototype = { return false; }, + getExtension: function (uri) { + let uriString = uri; + if (uri instanceof Components.interfaces.nsIURI) { + uriString = uri.spec; + } + uriString = uriString.toLowerCase(); + + for (let extSpec in this._extensions) { + if (uriString.startsWith(extSpec)) { + return this._extensions[extSpec]; + } + } + + return false; + }, + newURI : function(spec, charset, baseURI) { var newURL = Components.classes["@mozilla.org/network/standard-url;1"] .createInstance(Components.interfaces.nsIStandardURL); @@ -1056,53 +1069,49 @@ ZoteroProtocolHandler.prototype = { var newChannel = null; try { - var uriString = uri.spec.toLowerCase(); + let ext = this.getExtension(uri); - for (var extSpec in this._extensions) { - var ext = this._extensions[extSpec]; - - if (uriString.indexOf(extSpec) == 0) { - if (!this._principal) { - if (ext.loadAsChrome) { - var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null); - var chromeChannel = chromeService.newChannel(chromeURI); - - // Cache System Principal from chrome request - // so proxied pages load with chrome privileges - this._principal = chromeChannel.owner; - - var chromeRequest = chromeChannel.QueryInterface(Components.interfaces.nsIRequest); - chromeRequest.cancel(0x804b0002); // BINDING_ABORTED - } - } + if (!ext) { + // Return cancelled channel for unknown paths + // + // These can be in the form zotero://example.com/... -- maybe for "//example.com" URLs? + var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null); + var extChannel = chromeService.newChannel(chromeURI); + var chromeRequest = extChannel.QueryInterface(Components.interfaces.nsIRequest); + chromeRequest.cancel(0x804b0002); // BINDING_ABORTED + return extChannel; + } + + if (!this._principal) { + if (ext.loadAsChrome) { + var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null); + var chromeChannel = chromeService.newChannel(chromeURI); - var extChannel = ext.newChannel(uri); - // Extension returned null, so cancel request - if (!extChannel) { - var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null); - var extChannel = chromeService.newChannel(chromeURI); - var chromeRequest = extChannel.QueryInterface(Components.interfaces.nsIRequest); - chromeRequest.cancel(0x804b0002); // BINDING_ABORTED - } + // Cache System Principal from chrome request + // so proxied pages load with chrome privileges + this._principal = chromeChannel.owner; - // Apply cached principal to extension channel - if (this._principal) { - extChannel.owner = this._principal; - } - - if(!extChannel.originalURI) extChannel.originalURI = uri; - - return extChannel; + var chromeRequest = chromeChannel.QueryInterface(Components.interfaces.nsIRequest); + chromeRequest.cancel(0x804b0002); // BINDING_ABORTED } } - // Return cancelled channel for unknown paths - // - // These can be in the form zotero://example.com/... -- maybe for "//example.com" URLs? - var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null); - var extChannel = chromeService.newChannel(chromeURI); - var chromeRequest = extChannel.QueryInterface(Components.interfaces.nsIRequest); - chromeRequest.cancel(0x804b0002); // BINDING_ABORTED + var extChannel = ext.newChannel(uri); + // Extension returned null, so cancel request + if (!extChannel) { + var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null); + var extChannel = chromeService.newChannel(chromeURI); + var chromeRequest = extChannel.QueryInterface(Components.interfaces.nsIRequest); + chromeRequest.cancel(0x804b0002); // BINDING_ABORTED + } + + // Apply cached principal to extension channel + if (this._principal) { + extChannel.owner = this._principal; + } + + if(!extChannel.originalURI) extChannel.originalURI = uri; + return extChannel; } catch (e) { diff --git a/resource/tinymce/note.html b/resource/tinymce/note.html index 2ad446bc1..83ce1516f 100644 --- a/resource/tinymce/note.html +++ b/resource/tinymce/note.html @@ -57,6 +57,15 @@ zoteroExecCommand(ed.getDoc(), cmd, ui, value); }); }); + + ["ZoteroLinkClick"].forEach(function (command) { + ed.addCommand(command, function (ui, value) { + zoteroHandleEvent({ + type: command, + value + }); + }); + }); }, // More restrictive version of default set, with JS/etc. removed diff --git a/resource/tinymce/plugins/link/plugin.js b/resource/tinymce/plugins/link/plugin.js index e4ca0d55b..1dd4071c3 100644 --- a/resource/tinymce/plugins/link/plugin.js +++ b/resource/tinymce/plugins/link/plugin.js @@ -59,11 +59,14 @@ tinymce.PluginManager.add('link', function(editor) { } function openDetachedWindow(url) { + // Added by Zotero + editor.execCommand('ZoteroLinkClick', false, url); + // Chrome and Webkit has implemented noopener and works correctly with/without popup blocker // Firefox has it implemented noopener but when the popup blocker is activated it doesn't work // Edge has only implemented noreferrer and it seems to remove opener as well // Older IE versions pre IE 11 falls back to a window.open approach - if (!tinymce.Env.ie || tinymce.Env.ie > 10) { + /*if (!tinymce.Env.ie || tinymce.Env.ie > 10) { var link = document.createElement('a'); link.target = '_blank'; link.href = url; @@ -81,7 +84,7 @@ tinymce.PluginManager.add('link', function(editor) { doc.write(''); doc.close(); } - } + }*/ } function gotoLink(a) {