www

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

addCitationDialog.js (24281B)


      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 var Zotero_Citation_Dialog = new function () {
     27 	// Array value [0] is property name.
     28 	// Array value [1] is default value of property.
     29 	var _preserveData = {
     30 		"prefix":["value", ""],
     31 		"suffix":["value", ""],
     32 		"label":["selectedIndex", 0],
     33 		"locator":["value", ""],
     34 		"suppress-author":["checked", false]
     35 	};
     36 	
     37 	var _accepted = false;
     38 	var _itemData = new Object();
     39 	var _multipleSourcesOn = false;
     40 	var _lastSelected = null;
     41 	var _previewShown = false;
     42 	var _suppressNextTreeSelect = false;
     43 	var _suppressNextListSelect = false;
     44 	var _customHTML = false;
     45 	var _locatorIndexArray = {};
     46 	var _locatorNameArray = {};
     47 	var _autoRegeneratePref;
     48 	var _acceptButton;
     49 	var _multipleSourceButton;
     50 	var _sortCheckbox;
     51 	var _citationList;
     52 	var _originalHTML;
     53 	var serial_number;
     54 	var io;
     55 	
     56 	this.toggleMultipleSources = toggleMultipleSources;
     57 	this.toggleEditor = toggleEditor;
     58 	this.treeItemSelected = treeItemSelected;
     59 	this.listItemSelected = listItemSelected;
     60 	this.up = up;
     61 	this.down = down;
     62 	this.remove = remove;
     63 	this.setSortToggle = setSortToggle;
     64 	this.confirmRegenerate = confirmRegenerate;
     65 	this.accept = accept;
     66 	this.cancel = cancel;
     67 	
     68 	/*
     69 	 * initialize add citation dialog
     70 	 */
     71 	this.load = Zotero.Promise.coroutine(function* () {
     72 		// make sure we are visible
     73 		window.setTimeout(function() {
     74 			var screenX = window.screenX;
     75 			var screenY = window.screenY;
     76 			var xRange = [window.screen.availLeft, window.screen.width-window.outerWidth];
     77 			var yRange = [window.screen.availTop, window.screen.height-window.outerHeight];
     78 			if(screenX < xRange[0] || screenX > xRange[1] || screenY < yRange[0] || screenY > yRange[1]) {
     79 				var targetX = Math.max(Math.min(screenX, xRange[1]), xRange[0]);
     80 				var targetY = Math.max(Math.min(screenY, yRange[1]), yRange[0]);
     81 				Zotero.debug("Moving window to "+targetX+", "+targetY);
     82 				window.moveTo(targetX, targetY);
     83 			}
     84 		}, 0);
     85 		
     86 		document.documentElement.getButton("extra1").label = Zotero.getString("citation.multipleSources");
     87 		document.documentElement.getButton("extra2").label = Zotero.getString("citation.showEditor");
     88 		
     89 		io = window.arguments[0].wrappedJSObject;
     90 		
     91 		// find accept button
     92 		_acceptButton = document.getElementById("zotero-add-citation-dialog").getButton("accept");
     93 		_multipleSourceButton = document.documentElement.getButton("extra1");
     94 		_autoRegeneratePref = Zotero.Prefs.get("integration.autoRegenerate");
     95 		_citationList = document.getElementById("citation-list");
     96 		
     97 		// Manipulated by _addItem().  Discriminates between cite instances
     98 		// based on the same item in the same citation.  Internal throwaway variable,
     99 		// reset each time _multipleSourcesOn is set to true.
    100 		serial_number = 0;
    101 
    102 		// if a style with sortable citations, present checkbox
    103 		if(io.sortable) {
    104 			_sortCheckbox = document.getElementById("keepSorted");
    105 			_sortCheckbox.hidden = false;
    106 			_sortCheckbox.checked = !io.citation.properties.unsorted;
    107 		}
    108 		
    109 		// load locators
    110 		var locators = Zotero.Cite.labels;
    111 		var menu = document.getElementById("label");
    112 		var label_list = document.getElementById("locator-type-popup");
    113 		var i = 0;
    114 		for(var value in locators) {
    115 			var locator = locators[value];
    116 			var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
    117 			// add to list of labels
    118 			var child = document.createElement("menuitem");
    119 			child.setAttribute("value", value);
    120 			child.setAttribute("label", locatorLabel);
    121 			label_list.appendChild(child);
    122 			// add to array
    123 			_locatorIndexArray[locator] = i;
    124 			_locatorNameArray[i] = locator;
    125 			i++;
    126 		}
    127 		menu.selectedIndex = 0;
    128 		
    129 		// load (from selectItemsDialog.js)
    130 		yield doLoad();
    131 		
    132 		// if we already have a citation, load data from it
    133 		document.getElementById('editor').format = "RTF";
    134 		if(io.citation.citationItems.length) {
    135 			if(io.citation.citationItems.length === 1) {
    136 				// single citation
    137 				toggleMultipleSources(false);
    138 				_suppressNextTreeSelect = true;
    139 				
    140 				// DEBUG: When editing a citation before the library data has been loaded (i.e., in
    141 				// Firefox before the pane has been opened), this is the citation id, not the item id,
    142 				// and this fails. It works on subsequent attempts. Since this won't happen in
    143 				// Standalone, we can ignore.
    144 				var id = io.citation.citationItems[0].id;
    145 				let selected = yield collectionsView.selectItem(id);
    146 				
    147 				for(var box in _preserveData) {
    148 					var property = _preserveData[box][0];
    149 					if(io.citation.citationItems[0][box]) {
    150 						if(box === "label") {
    151 							document.getElementById(box)[property] = _locatorIndexArray[io.citation.citationItems[0][box]];
    152 						} else {
    153 							document.getElementById(box)[property] = io.citation.citationItems[0][box];
    154 						}
    155 					}
    156 				}
    157 			} else {
    158 				// multiple citations
    159 				toggleMultipleSources(true);
    160 				var _itemData = {};
    161 				// There is a little thrashing here, with repeated writes and
    162 				// overwrites of node content.  But sticking to the same
    163 				// workflow for all updates (node -> array -> io.citation) makes
    164 				// debugging a little less painful.
    165 				for(var i=0; i<io.citation.citationItems.length; i++) {
    166 					var item = Zotero.Items.get(io.citation.citationItems[i].id);
    167 					if(item) {
    168 						var itemNode = _addItem(item);
    169 						var itemDataID = itemNode.getAttribute("value");
    170 						_itemData[itemDataID] = {};
    171 						for(var box in _preserveData) {
    172 							var domBox = document.getElementById(box);
    173 							var property = _preserveData[box][0];
    174 							if("undefined" !== typeof io.citation.citationItems[i][box]) {
    175 								if(box === "label") {
    176 									domBox[property] = _locatorIndexArray[io.citation.citationItems[i][box]];
    177 								} else {
    178 									domBox[property] = io.citation.citationItems[i][box];
    179 								}
    180 							} else {
    181 								domBox[property] = _preserveData[box][1];
    182 							}
    183 						}
    184 						_itemSelected(itemDataID, true);
    185 					}
    186 				}
    187 				for (var box in _preserveData) {
    188 					document.getElementById(box).disabled = true;
    189 				}
    190 			}
    191 			
    192 			// show user-editable edited citation
    193 			if(io.citation.properties.custom) {
    194 				toggleEditor(io.citation.properties.custom);
    195 				delete io.citation.properties.custom;
    196 			}
    197 			
    198 			_updateAccept();
    199 		} else {
    200 			toggleMultipleSources(false);
    201 		}
    202 	});
    203 	
    204 	/*
    205 	 * turn on/off multiple sources item list
    206 	 */
    207 	function toggleMultipleSources(mode) {
    208 		if (mode === false || mode === true) {
    209 			_multipleSourcesOn = !mode;
    210 		}
    211 		_multipleSourcesOn = !_multipleSourcesOn;
    212 		var popup = document.defaultView;
    213 		var dialog = document.getElementById("zotero-add-citation-dialog");
    214 		if (dialog.getAttribute("height") == 1) {
    215 			 popup.sizeToContent();
    216 		}
    217 		if(_multipleSourcesOn) {
    218 			_multipleSourceButton.label = Zotero.getString("citation.singleSource");
    219 			document.getElementById("multiple-sources").setAttribute("hidden", false);
    220 			if(dialog.getAttribute("width") <= 600) {
    221 				popup.resizeTo(750, dialog.getAttribute("height"));
    222 			}
    223 			//popup.moveBy((600 - 750)/2, 0);
    224 
    225 			serial_number = 0;
    226 
    227 
    228 			// The mode is forced only when run from load(), in which case
    229 			// the adding of items is done separately.
    230 			if (mode !== true) {
    231 				this.add(true);
    232 			}
    233 		} else {
    234 			_multipleSourceButton.label = Zotero.getString("citation.multipleSources");
    235 			document.getElementById("multiple-sources").setAttribute("hidden", true);
    236 			//popup.resizeTo(600, dialog.getAttribute("height"));
    237 			//popup.moveBy((750 - 600)/2, 0);
    238 			
    239 			// enable all fields
    240 			for(var box in _preserveData) {
    241 				document.getElementById(box).disabled = false;
    242 			}
    243 			
    244 			var itemID = false;
    245 			if (_citationList.selectedIndex > -1) {
    246 				var itemDataID = _citationList.getSelectedItem(0).getAttribute("value");
    247 				itemID = itemDataID.slice(0, itemDataID.indexOf(":"));
    248 			}
    249 
    250 			// delete item list
    251 			_itemData = new Object();
    252 			
    253 			// delete all items
    254 			_clearCitationList();
    255 
    256 			// refresh
    257 			if (itemID) {
    258 				collectionsView.selectItem(itemID);
    259 			}
    260 			_updateAccept();
    261 			_updatePreview();
    262 		}
    263 	}
    264 	
    265 	/*
    266 	 * called when an item in the item selection tree is clicked
    267 	 */
    268 	function treeItemSelected() {
    269 		if(_suppressNextTreeSelect) {
    270 			_suppressNextTreeSelect = false;
    271 			_updateAccept();
    272 			return;
    273 		}
    274 		var items = itemsView.getSelectedItems(true); // treeview from xpcom/itemTreeView.js
    275 		var itemID = (items.length ? items[0] : false);
    276 		
    277 		if(_multipleSourcesOn) {
    278 			// We can safely use itemID here, because none of these operations
    279 			// affect selected items; this is all about the tree and navigation.
    280 
    281 			// turn off highlight in selected item list
    282 			_suppressNextListSelect = true;
    283 			document.getElementById("citation-list").selectedIndex = -1;
    284 
    285 			// disable all fields
    286 
    287 			for(var box in _preserveData) {
    288 				document.getElementById(box).disabled = true;
    289 			}
    290 
    291 			// disable adding nothing
    292 			document.getElementById("add").disabled = !itemID;
    293 			document.getElementById("remove").disabled = true;
    294 			document.getElementById("up").disabled = true;
    295 			document.getElementById("down").disabled = true;
    296 		} else {
    297 			for(var box in _preserveData) {
    298 				document.getElementById(box).disabled = !itemID;
    299 			}
    300 			_updateAccept();
    301 			_updatePreview();
    302 		}
    303 	}
    304 	
    305 	/*
    306 	 * called when an item in the selected items list is clicked
    307 	 */
    308 	function listItemSelected() {
    309 		if(_suppressNextListSelect) {
    310 			_suppressNextListSelect = false;
    311 			_updateAccept();
    312 			return;
    313 		}
    314 		var selectedListItem = _citationList.getSelectedItem(0);
    315 		var selectedListIndex = _citationList.selectedIndex;
    316 		var itemDataID = (selectedListItem ? selectedListItem.getAttribute("value") : false);
    317 		_itemSelected(itemDataID);
    318 		// turn off highlight in item tree
    319 		_suppressNextTreeSelect = true;
    320 		document.getElementById("zotero-items-tree").view.selection.clearSelection();
    321 		document.getElementById("remove").disabled = !itemDataID;
    322 		document.getElementById("add").disabled = true;
    323 		_configListPosition(!itemDataID, selectedListIndex);
    324 	}
    325 	
    326 	function _configListPosition(flag, selectedListIndex) {
    327 		if (selectedListIndex > 0) {
    328 			document.getElementById("up").disabled = flag;
    329 		} else {
    330 			document.getElementById("up").disabled = true;
    331 		}
    332 		if (-1 < selectedListIndex && selectedListIndex < (_citationList.getRowCount() - 1)) {
    333 			document.getElementById("down").disabled = flag;
    334 		} else {
    335 			document.getElementById("down").disabled = true;
    336 		}
    337 	}
    338 
    339 	function _move(direction) {
    340 		// automatically uncheck sorted checkbox if user is rearranging citation
    341 		if(_sortCheckbox && _sortCheckbox.checked) {
    342 			_sortCheckbox.checked = false;
    343 			setSortToggle();
    344 		}
    345 		
    346 		var insertBeforeItem;
    347 		var selectedListItem = _citationList.getSelectedItem(0);
    348 		var selectedListIndex = _citationList.selectedIndex;
    349 		var itemDataID = selectedListItem.getAttribute("value");
    350 		if (direction === -1) {
    351 			insertBeforeItem = selectedListItem.previousSibling;
    352 		} else {
    353 			insertBeforeItem = selectedListItem.nextSibling.nextSibling;
    354 		}
    355 		var listItem = _citationList.removeChild(selectedListItem);
    356 		_citationList.insertBefore(listItem, insertBeforeItem);
    357 		_citationList.selectedIndex = (selectedListIndex + direction);
    358 		_itemSelected(itemDataID);
    359 		_updatePreview();
    360 		_configListPosition(false, (selectedListIndex + direction));
    361 	}
    362 
    363 	function up() {
    364 		_move(-1);
    365 	}
    366 
    367 	function down() {
    368 		_move(1);
    369 	}
    370 
    371 	/*
    372 	 * Adds an item to the multipleSources list
    373 	 */
    374 	this.add = Zotero.Promise.coroutine(function* (first_item) {
    375 		
    376 		var pos, len;
    377 		var item = itemsView.getSelectedItems()[0]; // treeview from xpcom/itemTreeView.js
    378 		
    379 		if (!item) {
    380 			yield sortCitation();
    381 			_updateAccept();
    382 			_updatePreview();
    383 			return;
    384 		}
    385 
    386 		// Add to selection list and generate a new itemDataID for this cite.
    387 		var selectionNode = _addItem(item);
    388 		var itemDataID = selectionNode.getAttribute("value");
    389 		document.getElementById("add").disabled = !itemDataID;
    390 
    391 		// Save existing locator and affix field content, if any.
    392 		if (first_item) {
    393 			_itemSelected(itemDataID, true);
    394 		} else {
    395 			_itemSelected();
    396 			// set to defaults
    397 			for(var box in _preserveData) {
    398 				var property = _preserveData[box][0];
    399 				var default_value = _preserveData[box][1];
    400 				document.getElementById(box)[property] = default_value;
    401 			}
    402 			// Save default locator and affix element values to this multi-item.
    403 			_itemSelected(itemDataID, true);
    404 		}
    405 
    406 		for(var box in _preserveData) {
    407 			document.getElementById(box).disabled = true;
    408 		}
    409 
    410 		_citationList.ensureElementIsVisible(selectionNode);
    411 
    412 		// allow user to press OK
    413 		selectionNode = yield sortCitation(selectionNode);
    414 		_citationList.selectItem(selectionNode);
    415 		_updateAccept();
    416 		_updatePreview();
    417 	});
    418 	
    419 	/*
    420 	 * Deletes a citation from the multipleSources list
    421 	 */
    422 	function remove() {
    423 		var selectedListItem = _citationList.getSelectedItem(0);
    424 		var selectedListIndex = _citationList.selectedIndex;
    425 		var itemDataID = selectedListItem.getAttribute("value");
    426 		
    427 		// remove from _itemData
    428 		delete _itemData[itemDataID];
    429 		_itemData[itemDataID] = undefined;
    430 		_lastSelected = null;
    431 		
    432 		// remove from list
    433 		_citationList.removeChild(selectedListItem);
    434 		
    435 		if (selectedListIndex >= _citationList.getRowCount()) {
    436 			selectedListIndex = _citationList.getRowCount() - 1;
    437 		}
    438 		_citationList.selectedIndex = selectedListIndex;
    439 
    440 		_updateAccept();
    441 		_updatePreview();
    442 	}
    443 	
    444 	/*
    445 	 * Sorts preview citations, if preview is open.
    446 	 */
    447 	this.citationSortUnsort = Zotero.Promise.coroutine(function* () {
    448 		setSortToggle();
    449 		yield sortCitation();
    450 		_updatePreview();
    451 	});
    452 
    453 	/*
    454 	 * Sets the current sort toggle state persistently on the citation.
    455 	 */
    456 	function setSortToggle() {
    457 		if(!_sortCheckbox) return;
    458 		if(!_sortCheckbox.checked) {
    459 			io.citation.properties.unsorted = true;
    460 		} else {
    461 			io.citation.properties.unsorted = false;
    462 		}
    463 		return;
    464 	}
    465 
    466 	/*
    467 	 * Sorts the list of citations
    468  	 */
    469 	var sortCitation = Zotero.Promise.coroutine(function* (scrollToItem) {
    470  		if(!_sortCheckbox) return scrollToItem;
    471  		if(!_sortCheckbox.checked) {
    472  			io.citation.properties.unsorted = true;
    473  			return scrollToItem;
    474  		}
    475 		var scrollToItemID = false;
    476 		if (scrollToItem) {
    477 			scrollToItemID = scrollToItem.getAttribute("value");
    478 		}
    479 		_getCitation();
    480 		
    481 		// delete all existing items from list
    482 		_clearCitationList();
    483 		
    484 		// run preview function to re-sort, if it hasn't already been
    485 		// run
    486 		yield io.sort();
    487 		
    488 		// add items back to list
    489 		scrollToItem = null;
    490 		for(var i=0; i<io.citation.sortedItems.length; i++) {
    491 			var itemID = io.citation.sortedItems[i][0].id;
    492 			var itemDataID = io.citation.sortedItems[i][1].tmpItemDataID;
    493 			var item = Zotero.Items.get(itemID);
    494 			// Don't increment serial_number, and use the
    495 			// existing itemDataID stored on the item in sortedItems
    496 			var itemNode = _addItem(item, itemDataID);
    497 			if(itemDataID == scrollToItemID) _citationList.selectedIndex = i;
    498 			if(scrollToItemID && itemDataID == scrollToItemID) scrollToItem = itemNode;
    499 		}
    500 		
    501 		if(scrollToItem) _citationList.ensureElementIsVisible(scrollToItem);
    502 		return scrollToItem;
    503 	});
    504 	
    505 	/*
    506 	 * Ask whether to modifiy the preview
    507 	 */
    508 	function confirmRegenerate(focusShifted) {
    509 		if(document.getElementById('editor').value == _originalHTML || _originalHTML === undefined) {
    510 			// no changes; just update without asking
    511 			_updatePreview();
    512 			return;
    513 		}
    514 		
    515 		if(_autoRegeneratePref == -1) {
    516 			if(focusShifted) {	// only ask after onchange event; oninput is too
    517 								// frequent for this to be worthwhile
    518 				var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
    519 												.getService(Components.interfaces.nsIPromptService);
    520 				
    521 				var saveBehavior = { value: false };
    522 				var regenerate = promptService.confirmEx(
    523 					this.window,
    524 					Zotero.getString('integration.regenerate.title'),
    525 					Zotero.getString('integration.regenerate.body'),
    526 					promptService.STD_YES_NO_BUTTONS,
    527 					null, null, null,
    528 					Zotero.getString('integration.regenerate.saveBehavior'),
    529 					saveBehavior
    530 				);
    531 				
    532 				if(saveBehavior.value) {
    533 					_autoRegeneratePref = (regenerate == 0 ? 1 : 0);
    534 					Zotero.Prefs.set("integration.autoRegenerate", _autoRegeneratePref);
    535 				}
    536 				
    537 				if(regenerate == 0) {
    538 					_updatePreview();
    539 				}
    540 			}
    541 		} else if(_autoRegeneratePref == 1) {
    542 			_updatePreview();
    543 		}
    544 	}
    545 	
    546 	/*
    547 	 * Shows the edit pane
    548 	 */
    549 	function toggleEditor(text) {
    550 		var warning = document.getElementById('zotero-editor-warning');
    551 		var editor = document.getElementById('editor');
    552 		warning.hidden = _previewShown;
    553 		editor.hidden = _previewShown;
    554 		_previewShown = !_previewShown;
    555 		
    556 		if(_previewShown) {
    557 			document.documentElement.getButton("extra2").label = Zotero.getString("citation.hideEditor");		
    558 			if (!text && _customHTML) {
    559 				text = _customHTML;
    560 			}
    561 			if(text) {
    562 				io.preview().then(function(preview) {
    563 					_originalHTML = preview;
    564 					editor.value = text;
    565 				}).done();
    566 			} else {
    567 				_updatePreview();
    568 			}
    569 		} else {
    570 			if (editor.initialized) {
    571 				if (editor.value) {
    572 					_customHTML = editor.value;
    573 				}
    574 			}
    575 			document.documentElement.getButton("extra2").label = Zotero.getString("citation.showEditor");		
    576 		}
    577 	}
    578 	
    579 	/*
    580 	 * called when accept button is clicked
    581 	 */
    582 	function accept() {
    583 		if(_accepted) return true;
    584 
    585 		_getCitation();
    586 		var isCustom = _previewShown && io.citation.citationItems.length	// if a citation is selected
    587 				&& _originalHTML
    588 				&& document.getElementById('editor').value != _originalHTML	// and citation has been edited
    589 		
    590 		if(isCustom) {	
    591 			var citation = document.getElementById('editor').value;
    592 			if(Zotero.Utilities.trim(citation) == "") {				
    593 				var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
    594 							.getService(Components.interfaces.nsIPromptService);
    595 				var insert = promptService.confirm(window,
    596 					Zotero.getString("integration.emptyCitationWarning.title"),
    597 					Zotero.getString("integration.emptyCitationWarning.body"));
    598 				if(!insert) return false;
    599 			}
    600 			io.citation.properties.custom = citation;
    601 		}
    602 		
    603 		io.accept();
    604 		_accepted = true;
    605 		return true;
    606 	}
    607 	
    608 	/*
    609 	 * called when cancel button is clicked
    610 	 */
    611 	function cancel() {
    612 		if(_accepted) return true;
    613 		io.citation.citationItems = new Array();
    614 
    615 		io.accept();
    616 		_accepted = true;
    617 		return true;
    618 	}
    619 	
    620 	/*
    621 	 * Updates the contents of the preview pane
    622 	 */
    623 	function _updatePreview() {
    624 		if(_previewShown) {
    625 			var editor = document.getElementById('editor');
    626 			_getCitation();
    627 			
    628 			editor.readonly = !io.citation.citationItems.length;
    629 			if(io.citation.citationItems.length) {
    630 				io.preview().then(function(preview) {
    631 					editor.value = preview;
    632 					
    633 					if (editor.initialized) {
    634 						_originalHTML = editor.value;
    635 					}
    636 					else {
    637 						editor.onInit(() => _originalHTML = editor.value);
    638 					}
    639 				});
    640 			} else {
    641 				editor.value = "";
    642 				_originalHTML = "";
    643 			}
    644 		}
    645 	}
    646 	
    647 	/*
    648 	 * Controls whether the accept (OK) button should be enabled
    649 	 */
    650 	function _updateAccept() {
    651 		if(_multipleSourcesOn) {
    652 			_acceptButton.disabled = !_citationList.getRowCount();
    653 			// To prevent accidental data loss, do not allow change to
    654 			// single citation mode if multiple items are in selection
    655 			// list.
    656 			if (_citationList.getRowCount() > 1) {
    657 				_multipleSourceButton.disabled = true;
    658 			} else {
    659 				_multipleSourceButton.disabled = false;
    660 			}
    661 		} else {
    662 			collectionsView.onLoad.addListener(Zotero.Promise.coroutine(function* () {
    663 				if (itemsView) {
    664 					yield itemsView.waitForLoad();
    665 					_acceptButton.disabled = !itemsView.getSelectedItems().length;
    666 				}
    667 			}));
    668 		}
    669 	}
    670 	
    671 	/*
    672 	 * called when an item is selected; if itemDataID is false, disables fields; if
    673 	 * itemDataID is undefined, only updates _itemData array
    674 	 *
    675 	 * Note: This function no longer disables fields.  That operation is
    676 	 * now performed separately by explicit code.
    677 	 */
    678 	function _itemSelected(itemDataID, forceSave) {
    679 
    680 		if (forceSave) {
    681 			_lastSelected = itemDataID;
    682 		}
    683 
    684 		if(_lastSelected && !_itemData[_lastSelected]) {
    685 			_itemData[_lastSelected] = new Object();
    686 		}
    687 		
    688 		for(var box in _preserveData) {
    689 			var domBox = document.getElementById(box);
    690 			var property = _preserveData[box][0];
    691 			
    692 			// save property
    693 			if(_lastSelected) {
    694 				if(property == "label") {
    695 					_itemData[_lastSelected][box] = _locatorNameArray[domBox.selectedIndex];
    696 				} else {
    697 					_itemData[_lastSelected][box] = domBox[property];
    698 				}
    699 			}
    700 			// restore previous property
    701 			if(itemDataID) {
    702 				domBox.disabled = false;
    703 				if(_itemData[itemDataID] && _itemData[itemDataID][box] !== undefined) {
    704 					if(property == "label") {
    705 						domBox[property] = _locatorIndexArray[_itemData[itemDataID][box]];
    706 					} else {
    707 						domBox[property] = _itemData[itemDataID][box];
    708 					}
    709 				}
    710 			}
    711 		}
    712 		
    713 		if(itemDataID !== undefined) _lastSelected = itemDataID;
    714 	}
    715 	
    716 	/*
    717 	 * updates io.citation to reflect selected items
    718 	 */
    719 	function _getCitation() {
    720 		var key;
    721 		io.citation.citationItems = new Array();
    722 		
    723 		// use to map selectedIndexes back to page/paragraph/line
    724 		var locatorTypeElements = document.getElementById("label").getElementsByTagName("menuitem");
    725 		if(_multipleSourcesOn) {
    726 			_itemSelected();		// store locator info
    727 			var listLength = _citationList.getRowCount();
    728 			if(listLength) {
    729 				// generate citationItems
    730 				for(var i=0; i<listLength; i++) {
    731 					var itemDataID = _citationList.getItemAtIndex(i).getAttribute("value");
    732 					var citationItem = {};
    733 					for (key in _itemData[itemDataID]) {
    734 						// label is special everywhere
    735 						if (key === "label") {
    736 							citationItem.label = _locatorNameArray[_itemData[itemDataID].label];
    737 						} else if (_itemData[itemDataID][key]) {
    738 							citationItem[key] = _itemData[itemDataID][key];
    739 						}
    740 					}
    741 					citationItem["tmpItemDataID"] = itemDataID;
    742 					var itemID = itemDataID.slice(0, itemDataID.indexOf(":"));
    743 					citationItem.id = itemID;
    744 					io.citation.citationItems.push(citationItem);
    745 				}
    746 			}
    747 		} else {
    748 			var items = itemsView.getSelectedItems(true); // treeview from xpcom/itemTreeView.js
    749 			
    750 			if(items.length) {
    751 				var citationItem = {};
    752 				citationItem.id = items[0];
    753 				for(var box in _preserveData) {
    754 					var property = _preserveData[box][0];
    755 					if(box == "label") {
    756 						citationItem[box] = _locatorNameArray[document.getElementById(box).selectedIndex];
    757 					} else {
    758 						var prop = document.getElementById(box)[property];
    759 						if(prop !== "" && prop !== false) citationItem[box] = prop;
    760 					}
    761 				}
    762 				
    763 				if(!citationItem["locator"]) {
    764 					delete citationItem["locator"];
    765 					delete citationItem["label"];
    766 				}
    767 				
    768 				io.citation.citationItems = [citationItem];
    769 			} else {
    770 				io.citation.citationItems = [];
    771 			}
    772 		}
    773 	}
    774 	
    775 	/*
    776 	 * Add an item to the item list (multiple sources only)
    777 	 */
    778 	function _addItem(item, forceID) {
    779 		var itemNode = document.createElement("listitem");
    780 
    781 		var itemDataID;
    782 		if (!forceID) {
    783 			serial_number += 1;
    784 			itemDataID = item.id + ":" + serial_number;
    785 		} else {
    786 			itemDataID = forceID;
    787 		}
    788 
    789 		itemNode.setAttribute("value", itemDataID);
    790 		itemNode.setAttribute("label", item.getDisplayTitle());
    791 		itemNode.setAttribute("class", "listitem-iconic");
    792 		itemNode.setAttribute("image", item.getImageSrc());
    793 		_citationList.appendChild(itemNode);
    794 		return itemNode;
    795 	}
    796 	
    797 	/*
    798 	 * Removes all items from the multiple sources list
    799 	 */
    800 	function _clearCitationList() {
    801 		while(_citationList.firstChild) _citationList.removeChild(_citationList.firstChild);
    802 	}
    803 }