diff --git a/chrome/content/zotero/locateMenu.js b/chrome/content/zotero/locateMenu.js index e324b9e4f5..e12a7e236f 100644 --- a/chrome/content/zotero/locateMenu.js +++ b/chrome/content/zotero/locateMenu.js @@ -31,6 +31,112 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); var Zotero_LocateMenu = new function() { XPCOMUtils.defineLazyServiceGetter(this, "ios", "@mozilla.org/network/io-service;1", "nsIIOService"); + /** + * Clear and build the locate menu + */ + this.buildLocateMenu = function() { + var locateMenu = document.getElementById('zotero-tb-locate-menu'); + + // clear menu + while(locateMenu.childElementCount > 0) { + locateMenu.removeChild(locateMenu.firstChild); + } + + var selectedItems = [item for each(item in ZoteroPane.getSelectedItems()) if(!item.isNote())]; + + if(selectedItems.length) { + var optionsToShow = {}; + + // check which view options are available + for each(var item in selectedItems) { + for(var viewOption in ViewOptions) { + if(!optionsToShow[viewOption]) { + Zotero.debug("testing "+viewOption); + optionsToShow[viewOption] = ViewOptions[viewOption].canHandleItem(item); + } + } + } + + // add available view options to menu + for(var viewOption in optionsToShow) { + if(!optionsToShow[viewOption]) continue; + + var menuitem = _createMenuItem(Zotero.getString("locate."+viewOption+".label"), + null, Zotero.getString("locate."+viewOption+".tooltip")); + menuitem.setAttribute("class", "menuitem-iconic"); + Zotero.debug("icon is "+ViewOptions[viewOption].icon); + menuitem.setAttribute("image", ViewOptions[viewOption].icon); + locateMenu.appendChild(menuitem); + + let myViewOption = viewOption; + menuitem.addEventListener("command", function(event) { + ViewOptions[myViewOption].handleItems(selectedItems, event); + }, false) + } + + // check for custom locate engines + var customEngines = Zotero.LocateManager.getVisibleEngines(); + if(customEngines.length) { + locateMenu.appendChild(document.createElement("menuseparator")); + + // add engines to menu + for each(var engine in customEngines) { + // require a submission for at least one selected item + var canSubmit = false; + for each(var item in selectedItems) { + if(engine.getItemSubmission(item)) { + canSubmit = true; + break; + } + } + + if(canSubmit) { + var menuitem = _createMenuItem(engine.name, null, engine.description); + menuitem.setAttribute("class", "menuitem-iconic"); + menuitem.setAttribute("image", engine.icon); + locateMenu.appendChild(menuitem); + menuitem.addEventListener("command", _locateItem, false); + } + } + } + } else { + // add "no items selected" + menuitem = _createMenuItem(Zotero.getString("pane.item.selected.zero"), "no-items-selected"); + locateMenu.appendChild(menuitem); + menuitem.disabled = true; + } + + // add installable locate menus, if there are any + if(window.Zotero_Browser) { + var installableLocateEngines = _getInstallableLocateEngines(); + } else { + var installableLocateEngines = []; + } + + if(installableLocateEngines.length) { + locateMenu.appendChild(document.createElement("menuseparator")); + + for each(var locateEngine in installableLocateEngines) { + var menuitem = document.createElement("menuitem"); + menuitem.setAttribute("label", locateEngine.label); + menuitem.setAttribute("class", "menuitem-iconic"); + menuitem.setAttribute("image", locateEngine.image); + menuitem.zoteroLocateInfo = locateEngine; + menuitem.addEventListener("command", _addLocateEngine, false); + + locateMenu.appendChild(menuitem); + } + } + + // add manage menu item + locateMenu.appendChild(document.createElement("menuseparator")); + + var menuitem = document.createElement("menuitem"); + menuitem = _createMenuItem(Zotero.getString("locate.manageLocateEngines"), "zotero-manage-locate-menu"); + menuitem.addEventListener("command", _openLocateEngineManager, false); + locateMenu.appendChild(menuitem); + } + /** * Create a new menuitem XUL element */ @@ -43,58 +149,6 @@ var Zotero_LocateMenu = new function() { return menuitem; } - /** - * Get snapshot IDs for items - */ - function _getSnapshotIDs(items) { - var ids = []; - for each(var item in items) { - if(item.isNote()) continue; - var snapID = (item.isAttachment() ? item.id : item.getBestAttachment()); - if(!snapID) continue; - var spec = Zotero.Items.get(snapID).getLocalFileURL(); - if(!spec) continue; - var uri = Zotero_LocateMenu.ios.newURI(spec, null, null); - if(!uri || uri.scheme != 'file') continue; - ids.push(snapID); - } - return ids; - } - - /** - * Get URLs for items - */ - function _getURLs(items, includeDOIs) { - var urls = []; - for each(var item in items) { - if(item.isNote()) continue; - var url = null; - - // try url field - var urlField = item.getField('url'); - if(urlField) { - var uri = Zotero_LocateMenu.ios.newURI(urlField, null, null); - if(uri && (uri.host || !uri.scheme !== 'file')) { - url = urlField; - } - } - - if(includeDOIs) { - if(!url) { - // try DOI field - var doi = item.getField('DOI'); - if(doi && typeof doi === "string") { - doi = Zotero.Utilities.cleanDOI(doi); - if(doi) url = "http://dx.doi.org/" + encodeURIComponent(doi); - } - } - } - - if(url) urls.push(url); - } - return urls; - } - /** * Get any locate engines that can be installed from the current page */ @@ -129,158 +183,11 @@ var Zotero_LocateMenu = new function() { return locateEngines; } - - /** - * Clear and build the locate menu - */ - this.buildLocateMenu = function() { - var locateMenu = document.getElementById('zotero-tb-locate-menu'); - - // clear menu - while(locateMenu.childElementCount > 0) { - locateMenu.removeChild(locateMenu.firstChild); - } - - var selectedItems = [item for each(item in ZoteroPane.getSelectedItems()) if(!item.isNote())]; - - if(selectedItems.length) { - // get snapshot IDs and URLs - var allURLs = _getURLs(selectedItems, true); - var realURLs = _getURLs(selectedItems); - - if(selectedItems.length == 1 && _getSnapshotIDs(selectedItems).length) { - // add view snapshot - var menuitem = _createMenuItem(Zotero.getString("locate.snapshot.label"), - "zotero-locate-snapshot", Zotero.getString("locate.snapshot.tooltip")); - locateMenu.appendChild(menuitem); - menuitem.addEventListener("command", this.openItemSnapshot, false); - } - - if(allURLs.length) { - // add view online - var menuitem = _createMenuItem(Zotero.getString("locate.online.label"), - "zotero-locate-online", Zotero.getString("locate.online.tooltip")); - locateMenu.appendChild(menuitem); - menuitem.addEventListener("command", this.openItemURL, false); - } - - // add library lookup to regular items - var regularItems = [item for each(item in selectedItems) if(item.isRegularItem())]; - if(regularItems.length) { - var menuitem = _createMenuItem(Zotero.getString("locate.libraryLookup.label"), - "zotero-locate-service-openurl", Zotero.getString("locate.libraryLookup.tooltip")); - locateMenu.appendChild(menuitem); - menuitem.addEventListener("command", this.lookupItem, false); - } - - // add wayback if there are real URLs - if(realURLs.length) { - var menuitem = _createMenuItem(Zotero.getString("locate.waybackMachine.label"), - "zotero-locate-service-wayback", Zotero.getString("locate.waybackMachine.tooltip")); - locateMenu.appendChild(menuitem); - menuitem.addEventListener("command", this.waybackItem, false); - } - - var customEngines = Zotero.LocateManager.getVisibleEngines(); - if(customEngines.length) { - locateMenu.appendChild(document.createElement("menuseparator")); - - // add engines to menu - for each(var engine in customEngines) { - // require a submission for at least one selected item - var canSubmit = false; - for each(var item in selectedItems) { - if(engine.getItemSubmission(item)) { - canSubmit = true; - break; - } - } - - if(canSubmit) { - var menuitem = _createMenuItem(engine.name, null, engine.description); - menuitem.setAttribute("class", "menuitem-iconic"); - menuitem.setAttribute("image", engine.icon); - locateMenu.appendChild(menuitem); - menuitem.addEventListener("command", this.locateItem, false); - } - } - } - } else { - // add "no items selected" - menuitem = _createMenuItem(Zotero.getString("pane.item.selected.zero"), "no-items-selected"); - locateMenu.appendChild(menuitem); - menuitem.disabled = true; - } - - // add installable locate menus, if there are any - if(window.Zotero_Browser) { - var installableLocateEngines = _getInstallableLocateEngines(); - } else { - var installableLocateEngines = []; - } - - if(installableLocateEngines.length) { - locateMenu.appendChild(document.createElement("menuseparator")); - - for each(var locateEngine in installableLocateEngines) { - var menuitem = document.createElement("menuitem"); - menuitem.setAttribute("label", locateEngine.label); - menuitem.setAttribute("image", locateEngine.image); - menuitem.zoteroLocateInfo = locateEngine; - menuitem.addEventListener("command", this.addLocateEngine, false); - - locateMenu.appendChild(menuitem); - } - } - - // add manage menu item - locateMenu.appendChild(document.createElement("menuseparator")); - - var menuitem = document.createElement("menuitem"); - menuitem = _createMenuItem(Zotero.getString("locate.manageLocateEngines"), "zotero-manage-locate-menu"); - menuitem.addEventListener("command", this.openLocateEngineManager, false); - locateMenu.appendChild(menuitem); - } - - /** - * Open snapshots for selected items - */ - this.openItemSnapshot = function(event) { - ZoteroPane.viewAttachment(_getSnapshotIDs(ZoteroPane.getSelectedItems())[0], event); - } - - /** - * Open URLs for selected items - */ - this.openItemURL = function(event) { - ZoteroPane.loadURI(_getURLs(ZoteroPane.getSelectedItems(), true), event); - } - - /** - * Perform library lookup of selected items - */ - this.lookupItem = function(event) { - var urls = []; - for each(var item in ZoteroPane.getSelectedItems()) { - if(!item.isRegularItem()) continue; - var url = Zotero.OpenURL.resolve(item); - if(url) urls.push(url); - } - ZoteroPane.loadURI(urls, event); - } - - /** - * Perform library lookup of selected items - */ - this.waybackItem = function(event) { - ZoteroPane.loadURI(["http://web.archive.org/web/*/"+url - for each(url in _getURLs(ZoteroPane.getSelectedItems()))], event); - } /** * Locate selected items */ - this.locateItem = function(event) { + function _locateItem(event) { var selectedItems = ZoteroPane.getSelectedItems(); // find selected engine @@ -303,7 +210,7 @@ var Zotero_LocateMenu = new function() { /** * Add a new locate engine */ - this.addLocateEngine = function(event) { + function _addLocateEngine(event) { Zotero.LocateManager.addEngine(event.target.zoteroLocateInfo.href, Components.interfaces.nsISearchEngine.TYPE_OPENSEARCH, event.target.zoteroLocateInfo.image, false); @@ -312,10 +219,247 @@ var Zotero_LocateMenu = new function() { /** * Open the locate manager */ - this.openLocateEngineManager = function(event) { + function _openLocateEngineManager(event) { window.openDialog('chrome://zotero/content/locateManager.xul', 'Zotero Locate Engine Manager', 'chrome,centerscreen' ); } -} + + var ViewOptions = {}; + + /** + * "View Online" option + * + * Should appear only when an item or an attachment has a URL + */ + ViewOptions.online = new function() { + this.icon = "chrome://zotero/skin/locate-view-online.png"; + this.canHandleItem = function(item) _getURL(item) !== false; + + this.handleItems = function(items, event) { + ZoteroPane.loadURI([_getURL(item) for each(item in items)], event); + } + + function _getURL(item) { + // try url field for item and for attachments + var urlFields = [item.getField('url')]; + if(item.isRegularItem()) { + var attachments = item.getAttachments(); + if(attachments) { + urlFields = urlFields.concat([attachment.getField('url') + for each(attachment in Zotero.Items.get(attachments))]); + } + } + + // look through url fields for non-file:/// attachments + for each(var urlField in urlFields) { + try { + Zotero.debug(urlField); + var uri = Zotero_LocateMenu.ios.newURI(urlField, null, null); + if(uri && uri.host && uri.scheme !== 'file') { + return urlField; + } + } catch(e) {}; + } + + // if no url field, try DOI field + var doi = item.getField('DOI'); + if(doi && typeof doi === "string") { + doi = Zotero.Utilities.cleanDOI(doi); + if(doi) { + return "http://dx.doi.org/" + encodeURIComponent(doi); + } + } + + return false; + } + }; + + /** + * "View PDF" option + * + * Should appear only when the item is a PDF, or a linked or attached file or web attachment is + * a PDF + */ + ViewOptions.pdf = new function() { + this.icon = "chrome://zotero/skin/treeitem-attachment-pdf.png"; + this._mimeTypes = ["application/pdf"]; + this.canHandleItem = function(item) !!_getFirstAttachmentWithMIMEType(item, this._mimeTypes); + + this.handleItems = function(items, event) { + for each(var item in items) { + var attachment = _getFirstAttachmentWithMIMEType(item, this._mimeTypes); + if(attachment) { + ZoteroPane.viewAttachment(attachment.id, event); + return; + } + } + } + + function _getFirstAttachmentWithMIMEType(item, mimeTypes) { + var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments())); + for each(var attachment in attachments) { + if(mimeTypes.indexOf(attachment.attachmentMIMEType) !== -1 + && item.linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) return attachment; + } + return false; + } + }; + + /** + * "View Snapshot" option + * + * Should appear only when the item is a PDF, or a linked or attached file or web attachment is + * a snapshot + */ + ViewOptions.snapshot = new function() { + this.icon = "chrome://zotero/skin/treeitem-attachment-snapshot.png"; + this._mimeTypes = ["text/html", "application/xhtml+xml"]; + this.canHandleItem = ViewOptions.pdf.canHandleItem; + this.handleItems = ViewOptions.pdf.handleItems; + }; + + /** + * "View File" option + * + * Should appear only when an item or a linked or attached file or web attachment does not + * satisfy the conditions for "View PDF" or "View Snapshot" + */ + ViewOptions.file = new function() { + this.icon = "chrome://zotero/skin/treeitem-attachment-file.png"; + this.canHandleItem = function(item) !!_getFile(item); + + this.handleItems = function(items, event) { + for each(var item in items) { + var attachment = _getFile(item); + if(attachment) { + ZoteroPane.viewAttachment(attachment.id, event); + return; + } + } + } + + function _getFile(item) { + var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments())); + for each(var attachment in attachments) { + if(!ViewOptions.snapshot.canHandleItem(attachment) + && !ViewOptions.pdf.canHandleItem(attachment) + && item.linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) { + return attachment; + } + } + return false; + } + }; + + /** + * "Open in External Viewer" option + * + * Should appear only when an item or a linked or attached file or web attachment can be + * viewed by an internal non-native handler and "launchNonNativeFiles" pref is disabled + */ + ViewOptions.externalViewer = new function() { + this.icon = "chrome://zotero/skin/locate-external-viewer.png"; + this.useExternalViewer = true; + + this.canHandleItem = function(item) { + return (this.useExternalViewer ^ Zotero.Prefs.get('launchNonNativeFiles')) + && _getBestNonNativeAttachment(item); + } + + this.handleItems = function(items, event) { + for each(var item in items) { + var attachment = _getBestNonNativeAttachment(item); + if(attachment) { + ZoteroPane.viewAttachment(attachment.id, event, false, this.useExternalViewer); + return; + } + } + } + + function _getBestNonNativeAttachment(item) { + var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments())); + for each(var attachment in attachments) { + if(attachment.linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) { + var file = attachment.getFile(); + if(file) { + var ext = Zotero.File.getExtension(file); + if(!attachment.attachmentMIMEType) continue; + if(!Zotero.MIME.hasNativeHandler(attachment.attachmentMIMEType, ext) && + Zotero.MIME.hasInternalHandler(attachment.attachmentMIMEType, ext)) { + return attachment; + } + } + } + } + return false; + } + }; + + /** + * "Open in Internal Viewer" option + * + * Should appear only when an item or a linked or attached file or web attachment can be + * viewed by an internal non-native handler and "launchNonNativeFiles" pref is enabled + */ + ViewOptions.internalViewer = new function() { + this.icon = "chrome://zotero/skin/locate-internal-viewer.png"; + this.useExternalViewer = false; + this.canHandleItem = ViewOptions.externalViewer.canHandleItem; + this.handleItems = ViewOptions.externalViewer.handleItems; + }; + + /** + * "Show File" option + * + * Should appear only when an item is a file or web attachment, or has a linked or attached + * file or web attachment + */ + ViewOptions.showFile = new function() { + this.icon = "chrome://zotero/skin/locate-show-file.png"; + this.useExternalViewer = true; + + this.canHandleItem = function(item) { + return !!_getBestFile(item); + } + + this.handleItems = function(items, event) { + for each(var item in items) { + var attachment = _getBestFile(item); + if(attachment) { + ZoteroPane.showAttachmentInFilesystem(attachment.id); + return; + } + } + } + + function _getBestFile(item) { + if(item.isAttachment()) { + if(item.linkMode === Zotero.Attachments.LINK_MODE_LINKED_URL) return false + return item; + } else { + return Zotero.Items.get(item.getBestAttachment()); + } + } + }; + + /** + * "Library Lookup" Option + * + * Should appear only for regular items + */ + ViewOptions.libraryLookup = new function() { + this.icon = "chrome://zotero/skin/locate-library-lookup.png"; + this.canHandleItem = function(item) item.isRegularItem(); + this.handleItems = function(items, event) { + var urls = []; + for each(var item in items) { + if(!item.isRegularItem()) continue; + var url = Zotero.OpenURL.resolve(item); + if(url) urls.push(url); + } + ZoteroPane.loadURI(urls, event); + } + }; +} \ No newline at end of file diff --git a/chrome/content/zotero/tab.js b/chrome/content/zotero/tab.js index eeffc84af0..786d6eaeca 100644 --- a/chrome/content/zotero/tab.js +++ b/chrome/content/zotero/tab.js @@ -81,6 +81,10 @@ var ZoteroTab = new function() var listener = function(event) { if(event.target !== tab) return; window.gBrowser.tabContainer.removeEventListener("TabSelect", listener, false); + if(!Zotero || !Zotero.initialized) { + ZoteroPane.displayStartupError(true); + return; + } ZoteroPane.init(); ZoteroPane.makeVisible(); } diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index b3fe540c42..20612dd8ba 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -3195,7 +3195,7 @@ Zotero.Item.prototype.getAttachments = function(includeTrashed) { if (!this.id) { return []; } - + var sql = "SELECT A.itemID, value AS title FROM itemAttachments A " + "NATURAL JOIN items I LEFT JOIN itemData ID " + "ON (fieldID=110 AND A.itemID=ID.itemID) " @@ -3250,6 +3250,20 @@ Zotero.Item.prototype.getBestAttachment = function() { if (!this.isRegularItem()) { throw ("getBestAttachment() can only be called on regular items"); } + return this.getBestAttachments()[0]; +} + +/* + * Looks for attachment in the following order: oldest PDF attachment matching parent URL, + * oldest non-PDF attachment matching parent URL, oldest PDF attachment not matching URL, + * old non-PDF attachment not matching URL + * + * @return {Array} itemIDs for attachments + */ +Zotero.Item.prototype.getBestAttachments = function() { + if (!this.isRegularItem()) { + throw ("getBestAttachments() can only be called on regular items"); + } var url = this.getField('url'); @@ -3259,7 +3273,7 @@ Zotero.Item.prototype.getBestAttachment = function() { + "WHERE sourceItemID=? AND linkMode NOT IN (?) " + "AND IA.itemID NOT IN (SELECT itemID FROM deletedItems) " + "ORDER BY value=? DESC, mimeType='application/pdf' DESC, dateAdded ASC"; - return Zotero.DB.valueQuery(sql, [this.id, Zotero.Attachments.LINK_MODE_LINKED_URL, url]); + return Zotero.DB.columnQuery(sql, [this.id, Zotero.Attachments.LINK_MODE_LINKED_URL, url]); } diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 667c361195..92ddb47972 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -3029,7 +3029,7 @@ var ZoteroPane = new function() } - function viewAttachment(itemID, event, noLocateOnMissing) { + function viewAttachment(itemID, event, noLocateOnMissing, forceExternalViewer) { var attachment = Zotero.Items.get(itemID); if (!attachment.isAttachment()) { throw ("Item " + itemID + " is not an attachment in ZoteroPane.viewAttachment()"); @@ -3042,22 +3042,24 @@ var ZoteroPane = new function() var file = attachment.getFile(); if (file) { - var mimeType = attachment.getAttachmentMIMEType(); - // If no MIME type specified, try to detect again (I guess in case - // we've gotten smarter since the file was imported?) - if (!mimeType) { - var mimeType = Zotero.MIME.getMIMETypeFromFile(file); + if(forceExternalViewer !== undefined) { + var externalViewer = forceExternalViewer; + } else { + var mimeType = attachment.getAttachmentMIMEType(); + // If no MIME type specified, try to detect again (I guess in case + // we've gotten smarter since the file was imported?) + if (!mimeType) { + mimeType = Zotero.MIME.getMIMETypeFromFile(file); + + // TODO: update DB with new info + } + var ext = Zotero.File.getExtension(file); - - // TODO: update DB with new info + var externalViewer = Zotero.isStandalone || (!Zotero.MIME.hasNativeHandler(mimeType, ext) && + (!Zotero.MIME.hasInternalHandler(mimeType, ext) || Zotero.Prefs.get('launchNonNativeFiles'))); } - var ext = Zotero.File.getExtension(file); - var isNative = Zotero.MIME.hasNativeHandler(mimeType, ext); - var internal = Zotero.MIME.hasInternalHandler(mimeType, ext); - if (isNative || - (internal && !Zotero.Prefs.get('launchNonNativeFiles'))) { - + if (!forceExternalViewer) { var url = 'zotero://attachment/' + itemID + '/'; this.loadURI(url, event, { attachmentID: itemID}); } diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul index ebf2336c5b..5a0396c683 100644 --- a/chrome/content/zotero/zoteroPane.xul +++ b/chrome/content/zotero/zoteroPane.xul @@ -43,7 +43,7 @@