locateMenu.js (19203B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 27 28 /* 29 * This object contains the various functions for the interface 30 */ 31 var Zotero_LocateMenu = new function() { 32 XPCOMUtils.defineLazyServiceGetter(this, "ios", "@mozilla.org/network/io-service;1", "nsIIOService"); 33 34 /** 35 * Clear and build the locate menu 36 */ 37 this.buildLocateMenu = function() { 38 var locateMenu = document.getElementById('zotero-tb-locate-menu'); 39 40 // clear menu 41 while(locateMenu.childElementCount > 0) { 42 locateMenu.removeChild(locateMenu.firstChild); 43 } 44 45 var selectedItems = _getSelectedItems(); 46 47 if(selectedItems.length) { 48 _addViewOptions(locateMenu, selectedItems, true, true); 49 50 var availableEngines = _getAvailableLocateEngines(selectedItems); 51 // add engines that are available for selected items 52 if(availableEngines.length) { 53 Zotero_LocateMenu.addLocateEngines(locateMenu, availableEngines, null, true); 54 } 55 } else { 56 // add "no items selected" 57 menuitem = _createMenuItem(Zotero.getString("pane.item.selected.zero"), "no-items-selected"); 58 locateMenu.appendChild(menuitem); 59 menuitem.disabled = true; 60 } 61 62 // add separator at end if necessary 63 if(locateMenu.lastChild.tagName !== "menuseparator") { 64 locateMenu.appendChild(document.createElement("menuseparator")); 65 } 66 67 // add installable locate menus, if there are any 68 if(window.Zotero_Browser) { 69 var installableLocateEngines = _getInstallableLocateEngines(); 70 } else { 71 var installableLocateEngines = []; 72 } 73 74 if(installableLocateEngines.length) { 75 for (let locateEngine of installableLocateEngines) { 76 var menuitem = document.createElement("menuitem"); 77 menuitem.setAttribute("label", locateEngine.label); 78 menuitem.setAttribute("class", "menuitem-iconic"); 79 menuitem.setAttribute("image", locateEngine.image); 80 menuitem.zoteroLocateInfo = locateEngine; 81 menuitem.addEventListener("command", _addLocateEngine, false); 82 83 locateMenu.appendChild(menuitem); 84 } 85 } 86 87 var menuitem = document.createElement("menuitem"); 88 menuitem = _createMenuItem(Zotero.getString("locate.manageLocateEngines"), "zotero-manage-locate-menu"); 89 menuitem.addEventListener("command", _openLocateEngineManager, false); 90 locateMenu.appendChild(menuitem); 91 } 92 93 /** 94 * Clear the bottom part of the context menu and add locate options 95 * @param {menupopup} menu The menu to add context menu items to 96 * @param {Boolean} showIcons Whether menu items should have associated icons 97 * @return {Promise} 98 */ 99 this.buildContextMenu = Zotero.Promise.coroutine(function* (menu, showIcons) { 100 // get selected items 101 var selectedItems = _getSelectedItems(); 102 103 // if no items selected or >20 items selected, stop now 104 if(!selectedItems.length || selectedItems.length > 20) return; 105 106 // add view options 107 yield _addViewOptions(menu, selectedItems, showIcons); 108 109 /*// look for locate engines 110 var availableEngines = _getAvailableLocateEngines(selectedItems); 111 if(availableEngines.length) { 112 // if locate engines are available, make a new submenu 113 var submenu = document.createElement("menu"); 114 submenu.setAttribute("zotero-locate", "true"); 115 submenu.setAttribute("label", Zotero.getString("locate.locateEngines")); 116 117 // add locate engines to the submenu 118 _addLocateEngines(submenuPopup, availableEngines, true); 119 120 submenu.appendChild(submenuPopup); 121 menu.appendChild(submenu); 122 }*/ 123 }); 124 125 function _addViewOption(selectedItems, optionName, optionObject, showIcons) { 126 var menuitem = _createMenuItem(Zotero.getString("locate."+optionName+".label"), 127 null, Zotero.getString("locate."+optionName+".tooltip")); 128 if(showIcons) { 129 menuitem.setAttribute("class", "menuitem-iconic"); 130 menuitem.style.listStyleImage = "url('"+optionObject.icon+"')"; 131 } 132 menuitem.setAttribute("zotero-locate", "true"); 133 134 menuitem.addEventListener("command", function(event) { 135 optionObject.handleItems(selectedItems, event); 136 }, false) 137 return menuitem; 138 } 139 140 /** 141 * Add view options to a menu 142 * @param {menupopup} locateMenu The menu to add menu items to 143 * @param {Zotero.Item[]} selectedItems The items to create view options based upon 144 * @param {Boolean} showIcons Whether menu items should have associated icons 145 * @param {Boolean} addExtraOptions Whether to add options that start with "_" below the separator 146 */ 147 var _addViewOptions = Zotero.Promise.coroutine(function* (locateMenu, selectedItems, showIcons, addExtraOptions) { 148 var optionsToShow = {}; 149 150 // check which view options are available 151 for (let item of selectedItems) { 152 for(var viewOption in ViewOptions) { 153 if(!optionsToShow[viewOption]) { 154 optionsToShow[viewOption] = yield ViewOptions[viewOption].canHandleItem(item); 155 } 156 } 157 } 158 159 // add available view options to menu 160 var lastNode = locateMenu.hasChildNodes() ? locateMenu.firstChild : null; 161 var haveOptions = false; 162 for(var viewOption in optionsToShow) { 163 if(viewOption[0] === "_" || !optionsToShow[viewOption]) continue; 164 locateMenu.insertBefore(_addViewOption(selectedItems, viewOption, 165 ViewOptions[viewOption], showIcons), lastNode); 166 haveOptions = true; 167 } 168 169 if(haveOptions) { 170 var sep = document.createElement("menuseparator"); 171 sep.setAttribute("zotero-locate", "true"); 172 locateMenu.insertBefore(sep, lastNode); 173 } 174 175 if(addExtraOptions) { 176 for (let viewOption in optionsToShow) { 177 if(viewOption[0] !== "_" || !optionsToShow[viewOption]) continue; 178 locateMenu.insertBefore(_addViewOption(selectedItems, viewOption.substr(1), 179 ViewOptions[viewOption], showIcons), lastNode); 180 } 181 } 182 }); 183 184 /** 185 * Get available locate engines that can handle a set of items 186 * @param {Zotero.Item[]} selectedItems The items to look or locate engines for 187 * @return {Zotero.LocateManater.LocateEngine[]} An array of locate engines capable of handling 188 * the given items 189 */ 190 function _getAvailableLocateEngines(selectedItems) { 191 // check for custom locate engines 192 var customEngines = Zotero.LocateManager.getVisibleEngines(); 193 var availableEngines = []; 194 195 // check which engines can translate an item 196 for (let engine of customEngines) { 197 // require a submission for at least one selected item 198 for (let item of selectedItems) { 199 if(engine.getItemSubmission(item)) { 200 availableEngines.push(engine); 201 break; 202 } 203 } 204 } 205 206 return availableEngines; 207 } 208 209 /** 210 * Add locate engine options to a menu 211 * @param {menupopup} menu The menu to add menu items to 212 * @param {Zotero.LocateManager.LocateEngine[]} engines The list of engines to add to the menu 213 * @param {Function|null} items Function to call to locate items 214 * @param {Boolean} showIcons Whether menu items should have associated icons 215 */ 216 this.addLocateEngines = function(menu, engines, locateFn, showIcons) { 217 if(!locateFn) { 218 locateFn = this.locateItem; 219 } 220 221 for (let engine of engines) { 222 var menuitem = _createMenuItem(engine.name, null, engine.description); 223 menuitem.setAttribute("class", "menuitem-iconic"); 224 menuitem.setAttribute("image", engine.icon); 225 menu.appendChild(menuitem); 226 menuitem.addEventListener("command", locateFn, false); 227 } 228 } 229 230 /** 231 * Create a new menuitem XUL element 232 */ 233 function _createMenuItem( label, id, tooltiptext ) { 234 var menuitem = document.createElement("menuitem"); 235 menuitem.setAttribute("label", label); 236 if(id) menuitem.setAttribute("id", id); 237 if(tooltiptext) menuitem.setAttribute("tooltiptext", tooltiptext); 238 239 return menuitem; 240 } 241 242 /** 243 * Get any locate engines that can be installed from the current page 244 */ 245 function _getInstallableLocateEngines() { 246 var locateEngines = []; 247 if(!window.Zotero_Browser || !window.Zotero_Browser.tabbrowser) return locateEngines; 248 249 var links = Zotero_Browser.tabbrowser.selectedBrowser.contentDocument.getElementsByTagName("link"); 250 for (let link of links) { 251 if(!link.getAttribute) continue; 252 var rel = link.getAttribute("rel"); 253 if(rel && rel === "search") { 254 var type = link.getAttribute("type"); 255 if(type && type === "application/x-openurl-opensearchdescription+xml") { 256 var label = link.getAttribute("title"); 257 if(label) { 258 if(Zotero.LocateManager.getEngineByName(label)) { 259 label = 'Update "'+label+'"'; 260 } else { 261 label = 'Add "'+label+'"'; 262 } 263 } else { 264 label = 'Add Locate Engine'; 265 } 266 267 locateEngines.push({'label':label, 268 'href':link.getAttribute("href"), 269 'image':Zotero_Browser.tabbrowser.selectedTab.image}); 270 } 271 } 272 } 273 274 return locateEngines; 275 } 276 277 /** 278 * Locate selected items 279 */ 280 this.locateItem = function(event, selectedItems) { 281 if(!selectedItems) { 282 selectedItems = _getSelectedItems(); 283 } 284 285 // find selected engine 286 var selectedEngine = Zotero.LocateManager.getEngineByName(event.target.label); 287 if(!selectedEngine) throw "Selected locate engine not found"; 288 289 var urls = []; 290 var postDatas = []; 291 for (let item of selectedItems) { 292 var submission = selectedEngine.getItemSubmission(item); 293 if(submission) { 294 urls.push(submission.uri.spec); 295 postDatas.push(submission.postData); 296 } 297 } 298 299 Zotero.debug("Loading using "+selectedEngine.name); 300 Zotero.debug(urls); 301 ZoteroPane_Local.loadURI(urls, event, postDatas); 302 } 303 304 /** 305 * Add a new locate engine 306 */ 307 function _addLocateEngine(event) { 308 Zotero.LocateManager.addEngine(event.target.zoteroLocateInfo.href, 309 Components.interfaces.nsISearchEngine.TYPE_OPENSEARCH, 310 event.target.zoteroLocateInfo.image, false); 311 } 312 313 /** 314 * Open the locate manager 315 */ 316 function _openLocateEngineManager(event) { 317 window.openDialog('chrome://zotero/content/locateManager.xul', 318 'Zotero Locate Engine Manager', 319 'chrome,centerscreen' 320 ); 321 } 322 323 /** 324 * Get the first 50 selected items 325 */ 326 function _getSelectedItems() { 327 var allSelectedItems = ZoteroPane_Local.getSelectedItems(); 328 var selectedItems = []; 329 while(selectedItems.length < 50 && allSelectedItems.length) { 330 var item = allSelectedItems.shift(); 331 if(!item.isNote()) selectedItems.push(item); 332 } 333 return selectedItems; 334 } 335 336 var ViewOptions = {}; 337 338 /** 339 * "View PDF" option 340 * 341 * Should appear only when the item is a PDF, or a linked or attached file or web attachment is 342 * a PDF 343 */ 344 ViewOptions.pdf = new function() { 345 this.icon = "chrome://zotero/skin/treeitem-attachment-pdf.png"; 346 this._mimeTypes = ["application/pdf"]; 347 348 this.canHandleItem = function (item) { 349 return _getFirstAttachmentWithMIMEType(item, this._mimeTypes).then((item) => !!item); 350 } 351 352 this.handleItems = Zotero.Promise.coroutine(function* (items, event) { 353 var attachments = []; 354 for (let item of items) { 355 var attachment = yield _getFirstAttachmentWithMIMEType(item, this._mimeTypes); 356 if(attachment) attachments.push(attachment.id); 357 } 358 359 ZoteroPane_Local.viewAttachment(attachments, event); 360 }); 361 362 var _getFirstAttachmentWithMIMEType = Zotero.Promise.coroutine(function* (item, mimeTypes) { 363 var attachments = item.isAttachment() ? [item] : (yield item.getBestAttachments()); 364 for (let i = 0; i < attachments.length; i++) { 365 let attachment = attachments[i]; 366 if (mimeTypes.indexOf(attachment.attachmentContentType) !== -1 367 && attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) { 368 return attachment; 369 } 370 } 371 return false; 372 }); 373 }; 374 375 /** 376 * "View Online" option 377 * 378 * Should appear only when an item or an attachment has a URL 379 */ 380 ViewOptions.online = new function() { 381 this.icon = "chrome://zotero/skin/locate-view-online.png"; 382 383 this.canHandleItem = function (item) { 384 return _getURL(item).then((val) => val !== false); 385 } 386 this.handleItems = Zotero.Promise.coroutine(function* (items, event) { 387 var urls = yield Zotero.Promise.all(items.map(item => _getURL(item))); 388 ZoteroPane_Local.loadURI(urls.filter(url => !!url), event); 389 }); 390 391 var _getURL = Zotero.Promise.coroutine(function* (item) { 392 // try url field for item and for attachments 393 var urlField = item.getField('url'); 394 if(urlField) { 395 var uri; 396 try { 397 uri = Zotero_LocateMenu.ios.newURI(urlField, null, null); 398 if(uri && uri.host && uri.scheme !== 'file') return urlField; 399 } catch(e) {}; 400 } 401 402 if(item.isRegularItem()) { 403 var attachments = item.getAttachments(); 404 if(attachments) { 405 // look through url fields for non-file:/// attachments 406 for (let attachment of Zotero.Items.get(attachments)) { 407 var urlField = attachment.getField('url'); 408 if(urlField) return urlField; 409 } 410 411 } 412 } 413 414 // if no url field, try DOI field 415 var doi = item.getField('DOI'); 416 if(doi && typeof doi === "string") { 417 doi = Zotero.Utilities.cleanDOI(doi); 418 if(doi) { 419 return "http://dx.doi.org/" + encodeURIComponent(doi); 420 } 421 } 422 423 return false; 424 }); 425 }; 426 427 /** 428 * "View Snapshot" option 429 * 430 * Should appear only when the item is a PDF, or a linked or attached file or web attachment is 431 * a snapshot 432 */ 433 ViewOptions.snapshot = new function() { 434 this.icon = "chrome://zotero/skin/treeitem-attachment-snapshot.png"; 435 this._mimeTypes = ["text/html", "application/xhtml+xml"]; 436 this.canHandleItem = ViewOptions.pdf.canHandleItem; 437 this.handleItems = ViewOptions.pdf.handleItems; 438 }; 439 440 /** 441 * "View File" option 442 * 443 * Should appear only when an item or a linked or attached file or web attachment does not 444 * satisfy the conditions for "View PDF" or "View Snapshot" 445 */ 446 ViewOptions.file = new function() { 447 this.icon = "chrome://zotero/skin/treeitem-attachment-file.png"; 448 449 this.canHandleItem = function (item) { 450 return _getFile(item).then((item) => !!item); 451 } 452 453 this.handleItems = Zotero.Promise.coroutine(function* (items, event) { 454 var attachments = []; 455 for (let item of items) { 456 var attachment = yield _getFile(item); 457 if(attachment) attachments.push(attachment.id); 458 } 459 460 ZoteroPane_Local.viewAttachment(attachments, event); 461 }); 462 463 var _getFile = Zotero.Promise.coroutine(function* (item) { 464 var attachments = item.isAttachment() ? [item] : (yield item.getBestAttachments()); 465 for (let i = 0; i < attachments.length; i++) { 466 let attachment = attachments[i]; 467 if (!(yield ViewOptions.snapshot.canHandleItem(attachment)) 468 && !(yield ViewOptions.pdf.canHandleItem(attachment)) 469 && attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) { 470 return attachment; 471 } 472 } 473 return false; 474 }); 475 }; 476 477 /** 478 * "Open in External Viewer" option 479 * 480 * Should appear only when an item or a linked or attached file or web attachment can be 481 * viewed by an internal non-native handler and "launchNonNativeFiles" pref is disabled 482 */ 483 ViewOptions.externalViewer = new function() { 484 this.icon = "chrome://zotero/skin/locate-external-viewer.png"; 485 this.useExternalViewer = true; 486 487 this.canHandleItem = Zotero.Promise.coroutine(function* (item) { 488 return (this.useExternalViewer ^ Zotero.Prefs.get('launchNonNativeFiles')) 489 && (yield _getBestNonNativeAttachment(item)); 490 }); 491 492 this.handleItems = Zotero.Promise.coroutine(function* (items, event) { 493 var attachments = []; 494 for (let item of items) { 495 var attachment = yield _getBestNonNativeAttachment(item); 496 if(attachment) attachments.push(attachment.id); 497 } 498 499 ZoteroPane_Local.viewAttachment(attachments, event, false, this.useExternalViewer); 500 }); 501 502 var _getBestNonNativeAttachment = Zotero.Promise.coroutine(function* (item) { 503 var attachments = item.isAttachment() ? [item] : (yield item.getBestAttachments()); 504 for (let i = 0; i < attachments.length; i++) { 505 let attachment = attachments[i]; 506 if(attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) { 507 var path = yield attachment.getFilePathAsync(); 508 if (path) { 509 var ext = Zotero.File.getExtension(Zotero.File.pathToFile(path)); 510 if(!attachment.attachmentContentType || 511 Zotero.MIME.hasNativeHandler(attachment.attachmentContentType, ext) || 512 !Zotero.MIME.hasInternalHandler(attachment.attachmentContentType, ext)) { 513 return false; 514 } 515 return attachment; 516 } 517 } 518 } 519 return false; 520 }); 521 }; 522 523 /** 524 * "Open in Internal Viewer" option 525 * 526 * Should appear only when an item or a linked or attached file or web attachment can be 527 * viewed by an internal non-native handler and "launchNonNativeFiles" pref is enabled 528 */ 529 ViewOptions.internalViewer = new function() { 530 this.icon = "chrome://zotero/skin/locate-internal-viewer.png"; 531 this.useExternalViewer = false; 532 this.canHandleItem = ViewOptions.externalViewer.canHandleItem; 533 this.handleItems = ViewOptions.externalViewer.handleItems; 534 }; 535 536 /** 537 * "Show File" option 538 * 539 * Should appear only when an item is a file or web attachment, or has a linked or attached 540 * file or web attachment 541 */ 542 ViewOptions.showFile = new function() { 543 this.icon = "chrome://zotero/skin/locate-show-file.png"; 544 this.useExternalViewer = true; 545 546 this.canHandleItem = function (item) { 547 return _getBestFile(item).then(item => !!item); 548 } 549 550 this.handleItems = Zotero.Promise.coroutine(function* (items, event) { 551 for (let item of items) { 552 var attachment = yield _getBestFile(item); 553 if(attachment) { 554 ZoteroPane_Local.showAttachmentInFilesystem(attachment.id); 555 } 556 } 557 }); 558 559 var _getBestFile = Zotero.Promise.coroutine(function* (item) { 560 if(item.isAttachment()) { 561 if(item.attachmentLinkMode === Zotero.Attachments.LINK_MODE_LINKED_URL) return false; 562 return item; 563 } else { 564 return yield item.getBestAttachment(); 565 } 566 }); 567 }; 568 569 /** 570 * "Library Lookup" Option 571 * 572 * Should appear only for regular items 573 */ 574 ViewOptions._libraryLookup = new function() { 575 this.icon = "chrome://zotero/skin/locate-library-lookup.png"; 576 this.canHandleItem = function (item) { return Zotero.Promise.resolve(item.isRegularItem()); }; 577 this.handleItems = Zotero.Promise.method(function (items, event) { 578 var urls = []; 579 for (let item of items) { 580 if(!item.isRegularItem()) continue; 581 var url = Zotero.OpenURL.resolve(item); 582 if(url) urls.push(url); 583 } 584 ZoteroPane_Local.loadURI(urls, event); 585 }); 586 }; 587 }