www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

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 }