www

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

zoterosearch.xml (31565B)


      1 <?xml version="1.0"?>
      2 <!--
      3     ***** BEGIN LICENSE BLOCK *****
      4     
      5     Copyright © 2009 Center for History and New Media
      6                      George Mason University, Fairfax, Virginia, USA
      7                      http://zotero.org
      8     
      9     This file is part of Zotero.
     10     
     11     Zotero is free software: you can redistribute it and/or modify
     12     it under the terms of the GNU Affero General Public License as published by
     13     the Free Software Foundation, either version 3 of the License, or
     14     (at your option) any later version.
     15     
     16     Zotero is distributed in the hope that it will be useful,
     17     but WITHOUT ANY WARRANTY; without even the implied warranty of
     18     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19     GNU Affero General Public License for more details.
     20     
     21     You should have received a copy of the GNU Affero General Public License
     22     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     23     
     24     ***** END LICENSE BLOCK *****
     25 -->
     26 
     27 <!DOCTYPE window [
     28 	<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
     29 	%zoteroDTD;
     30 	<!ENTITY % searchboxDTD SYSTEM "chrome://zotero/locale/searchbox.dtd">
     31 	%searchboxDTD;
     32 ]>
     33 
     34 <bindings xmlns="http://www.mozilla.org/xbl"
     35 		  xmlns:xbl="http://www.mozilla.org/xbl"
     36 		  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     37 	
     38 	<binding id="search-box">
     39 		<resources>
     40 			<stylesheet src="chrome://zotero/skin/bindings/search.css"/>
     41 		</resources>
     42 		
     43 		<implementation>
     44 			<property name="groups"/>
     45 			
     46 			<field name="searchRef"/>
     47 			<property name="search" onget="return this.searchRef;">
     48 				<setter>
     49 					<![CDATA[
     50 						this.searchRef = val;
     51 						
     52 						var libraryMenu = this.id('libraryMenu');
     53 						var libraries = Zotero.Libraries.getAll();
     54 						Zotero.Utilities.Internal.buildLibraryMenu(
     55 							libraryMenu, libraries, this.searchRef.libraryID
     56 						);
     57 						if (this.searchRef.id) {
     58 							libraryMenu.disabled = true;
     59 						}
     60 						this.updateLibrary();
     61 						
     62 						
     63 						var conditionsBox = this.id('conditions');
     64 						while(conditionsBox.hasChildNodes())
     65 							conditionsBox.removeChild(conditionsBox.firstChild);
     66 						
     67 						var conditions = this.search.getConditions();
     68 						for (let id in conditions) {
     69 							let condition = conditions[id];
     70 							// Checkboxes
     71 							switch (condition.condition) {
     72 								case 'recursive':
     73 								case 'noChildren':
     74 								case 'includeParentsAndChildren':
     75 									let checkbox = condition.condition + 'Checkbox';
     76 									this.id(checkbox).setAttribute('condition', id);
     77 									this.id(checkbox).checked = condition.operator == 'true';
     78 									continue;
     79 							}
     80 							
     81 							if(condition.condition == 'joinMode') {
     82 								this.id('joinModeMenu').setAttribute('condition', id);
     83 								this.id('joinModeMenu').value = condition.operator;
     84 							}
     85 							else {
     86 								this.addCondition(condition);
     87 							}
     88 						}
     89 					]]>
     90 				</setter>
     91 			</property>
     92 			
     93 			<method name="addCondition">
     94 				<parameter name="ref"/>
     95 				<body>
     96 					<![CDATA[
     97 						var conditionsBox = this.id('conditions');
     98 						var condition = document.createElement('zoterosearchcondition');
     99 						condition.setAttribute('flex','1');
    100 						
    101 						conditionsBox.appendChild(condition);
    102 						
    103 						// Default to an empty 'title' condition
    104 						if (!ref) {
    105 							ref = this.search.getCondition(this.search.addCondition("title","contains",""))
    106 						}
    107 						
    108 						condition.initWithParentAndCondition(this, ref);
    109 						
    110 						if (conditionsBox.childNodes.length == 2){
    111 							conditionsBox.childNodes[0].enableRemoveButton();
    112 						}
    113 						else if (conditionsBox.childNodes.length == 1){
    114 							conditionsBox.childNodes[0].disableRemoveButton();
    115 						}
    116 					]]>
    117 				</body>
    118 			</method>
    119 			
    120 			<method name="removeCondition">
    121 				<parameter name="id"/>
    122 				<body>
    123 					<![CDATA[
    124 						var conditionsBox = this.id('conditions');
    125 						
    126 						this.search.removeCondition(id);
    127 						
    128 						for (var i = 0, len=conditionsBox.childNodes.length; i < len; i++){
    129 							if (conditionsBox.childNodes[i].conditionID == id){
    130 								conditionsBox.removeChild(conditionsBox.childNodes[i]);
    131 								break;
    132 							}
    133 						}
    134 						
    135 						if (conditionsBox.childNodes.length == 1){
    136 							conditionsBox.childNodes[0].disableRemoveButton();
    137 						}
    138 					]]>
    139 				</body>
    140 			</method>
    141 			
    142 			<method name="updateLibrary">
    143 				<body><![CDATA[
    144 					var menu = this.id('libraryMenu');
    145 					var libraryID = parseInt(menu.selectedItem.value);
    146 					
    147 				 	if (this.onLibraryChange) {
    148 				 		this.onLibraryChange(libraryID);
    149 					}
    150 					if (!this.searchRef.id) {
    151 						this.searchRef.libraryID = libraryID;
    152 					}
    153 					
    154 					[...this.id('conditions').childNodes].forEach(x => x.onLibraryChange());
    155 				]]></body>
    156 			</method>
    157 			
    158 			<method name="updateJoinMode">
    159 				<body>
    160 					<![CDATA[
    161 						var menu = this.id('joinModeMenu');
    162 						if(menu.hasAttribute('condition'))
    163 							this.search.updateCondition(menu.getAttribute('condition'),'joinMode',menu.value,null);
    164 						else
    165 							menu.setAttribute('condition', this.search.addCondition('joinMode',menu.value,null));
    166 					]]>
    167 				</body>
    168 			</method>
    169 			
    170 			<method name="updateCheckbox">
    171 				<parameter name="condition"/>
    172 				<body>
    173 					<![CDATA[
    174 						var checkbox = this.id(condition + 'Checkbox');
    175 						var value = checkbox.checked ? 'true' : 'false';
    176 						if(checkbox.hasAttribute('condition'))
    177 						{
    178 							this.search.updateCondition(checkbox.getAttribute('condition'),
    179 								condition, value, null);
    180 						}
    181 						else
    182 						{
    183 							checkbox.setAttribute('condition',
    184 								this.search.addCondition(condition, value, null));
    185 						}
    186 					]]>
    187 				</body>
    188 			</method>
    189 			
    190 			<!-- Calls updateSearch() on all search conditions -->
    191 			<method name="updateSearch">
    192 				<body>
    193 					<![CDATA[
    194 						var conditionsBox = this.id('conditions');
    195 						if (conditionsBox.hasChildNodes()) {
    196 							for(var i = 0, len=conditionsBox.childNodes.length; i < len; i++) {
    197 								conditionsBox.childNodes[i].updateSearch();
    198 							}
    199 						}
    200 					]]>
    201 				</body>
    202 			</method>
    203 			
    204 			<method name="handleKeyPress">
    205 				<parameter name="event"/>
    206 				<body>
    207 				<![CDATA[
    208 					switch (event.keyCode) {
    209 						case event.DOM_VK_RETURN:
    210 							this.active = true;
    211 							
    212 							if (event.shiftKey) {
    213 								this.addCondition();
    214 							}
    215 							else {
    216 								this.doCommand();
    217 							}
    218 							break;
    219 					}
    220 				]]>
    221 				</body>
    222 			</method>
    223 			
    224 			<method name="id">
    225 				<parameter name="id"/>
    226 				<body>
    227 					<![CDATA[
    228 						return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
    229 					]]>
    230 				</body>
    231 			</method>
    232 		</implementation>
    233 		
    234 		<content>
    235 			<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    236 					id="search-box" flex="1" onkeypress="document.getBindingParent(this).handleKeyPress(event)">
    237 				<hbox align="center">
    238 					<label value="&zotero.search.searchInLibrary;" control="libraryMenu"/>
    239 					<menulist id="libraryMenu" oncommand="document.getBindingParent(this).updateLibrary();">
    240 						<menupopup/>
    241 					</menulist>
    242 				</hbox>
    243 				<groupbox xbl:inherits="flex">
    244 					<caption align="center">
    245 						<label value="&zotero.search.joinMode.prefix;"/>
    246 						<menulist id="joinModeMenu" oncommand="document.getBindingParent(this).updateJoinMode();">
    247 							<menupopup>
    248 								<menuitem label="&zotero.search.joinMode.any;" value="any"/>
    249 								<menuitem label="&zotero.search.joinMode.all;" value="all" selected="true"/>
    250 							</menupopup>
    251 						</menulist>
    252 						<label value="&zotero.search.joinMode.suffix;"/>
    253 					</caption>
    254 					<vbox id="conditions"/>
    255 				</groupbox>
    256 				<hbox>
    257 					<checkbox id="recursiveCheckbox" label="&zotero.search.recursive.label;" oncommand="document.getBindingParent(this).updateCheckbox('recursive');"/>
    258 					<checkbox id="noChildrenCheckbox" label="&zotero.search.noChildren;" oncommand="document.getBindingParent(this).updateCheckbox('noChildren');"/>
    259 				</hbox>
    260 				<hbox>
    261 					<checkbox id="includeParentsAndChildrenCheckbox" label="&zotero.search.includeParentsAndChildren;" oncommand="document.getBindingParent(this).updateCheckbox('includeParentsAndChildren');"/>
    262 				</hbox>
    263 			</vbox>
    264 		</content>
    265 	</binding>
    266 	
    267 	
    268 	
    269 	<binding id="search-condition">
    270 		<resources>
    271 			<stylesheet src="chrome://zotero/skin/bindings/search.css"/>
    272 		</resources>
    273 		
    274 		<implementation>
    275 			<field name="conditionID"/>
    276 			<field name="selectedCondition"/>
    277 			<field name="mode"/>
    278 			<field name="selectedOperator"/>
    279 			<field name="value"/>
    280 			<field name="parent"/>
    281 			<field name="dontupdate"/>
    282 			<constructor>
    283 				<![CDATA[
    284 					var operators = [
    285 						'is',
    286 						'isNot',
    287 						'beginsWith',
    288 						'contains',
    289 						'doesNotContain',
    290 						'isLessThan',
    291 						'isGreaterThan',
    292 						'isBefore',
    293 						'isAfter',
    294 						'isInTheLast'
    295 					];
    296 					var operatorsList = this.id('operatorsmenu');
    297 					
    298 					// Build operator menu
    299 					for (let operator of operators) {
    300 						operatorsList.appendItem(
    301 							Zotero.getString('searchOperator.' + operator),
    302 							operator
    303 						);
    304 					}
    305 					
    306 					// Build conditions menu
    307 					var conditionsMenu = this.id('conditionsmenu');
    308 					var moreConditionsMenu = this.id('more-conditions-menu');
    309 					var conditions = Zotero.SearchConditions.getStandardConditions();
    310 					
    311 					var lastInsertPos = 0;
    312 					
    313 					for (let condition of conditions) {
    314 						if (this.isPrimaryCondition(condition.name)) {
    315 							var menuitem = conditionsMenu.insertItemAt(
    316 								lastInsertPos++, condition.localized, condition.name
    317 							);
    318 						}
    319 						else {
    320 							var menuitem = moreConditionsMenu.appendItem(
    321 								condition.localized, condition.name
    322 							);
    323 						}
    324 						
    325 						var baseFields = null;
    326 						try {
    327 							baseFields = Zotero.ItemFields.getTypeFieldsFromBase(condition.name);
    328 						}
    329 						catch (e) {}
    330 						
    331 						// Add tooltip, building it if it doesn't exist
    332 						if (baseFields) {
    333 							if (!this.id(condition.name + '-tooltip')) {
    334 								var fieldName = null;
    335 								try {
    336 									fieldName = Zotero.ItemFields.getLocalizedString(null, condition.name);
    337 								}
    338 								catch (e) {}
    339 								
    340 								if (fieldName) {
    341 									var localized = [fieldName];
    342 								}
    343 								else {
    344 									var localized = [];
    345 								}
    346 								
    347 								for (let baseField of baseFields) {
    348 									var str = Zotero.SearchConditions.getLocalizedName(
    349 										Zotero.ItemFields.getName(baseField)
    350 									);
    351 									
    352 									if (localized.indexOf(str) == -1) {
    353 										localized.push(str);
    354 									}
    355 								}
    356 								localized.sort();
    357 								
    358 								var tt = document.createElement('tooltip');
    359 								tt.setAttribute('id', condition.name + '-tooltip');
    360 								tt.setAttribute('orient', 'vertical');
    361 								tt.setAttribute('noautohide', true);
    362 								
    363 								var grid = document.createElement('grid');
    364 								
    365 								var columns = document.createElement('columns');
    366 								var col1 = document.createElement('column');
    367 								var col2 = document.createElement('column');
    368 								columns.appendChild(col1);
    369 								columns.appendChild(col2);
    370 								
    371 								var rows = document.createElement('rows');
    372 								
    373 								var fieldRow = document.createElement('row');
    374 								var label = document.createElement('label');
    375 								label.setAttribute('value', Zotero.getString('searchConditions.tooltip.fields'));
    376 								fieldRow.appendChild(label);
    377 								var vbox = document.createElement('vbox');
    378 								for (let str of localized) {
    379 									var label = document.createElement('label')
    380 									label.setAttribute('value', str);
    381 									vbox.appendChild(label);
    382 								}
    383 								fieldRow.appendChild(vbox);
    384 								
    385 								rows.appendChild(fieldRow);
    386 								
    387 								grid.appendChild(rows);
    388 								tt.appendChild(grid);
    389 								
    390 								this.id('condition-tooltips').appendChild(tt);
    391 							}
    392 							
    393 							menuitem.setAttribute('tooltip', condition.name + '-tooltip');
    394 						}
    395 					}
    396 					conditionsMenu.selectedIndex = 0;
    397 				]]>
    398 			</constructor>
    399 			
    400 			<method name="isPrimaryCondition">
    401 				<parameter name="condition"/>
    402 				<body><![CDATA[
    403 				switch (condition) {
    404 				case 'collection':
    405 				case 'creator':
    406 				case 'title':
    407 				case 'date':
    408 				case 'dateAdded':
    409 				case 'dateModified':
    410 				case 'itemType':
    411 				case 'fileTypeID':
    412 				case 'publicationTitle':
    413 				case 'tag':
    414 				case 'note':
    415 				case 'childNote':
    416 				case 'fulltextContent':
    417 					return true;
    418 				}
    419 				
    420 				return false;
    421 				]]></body>
    422 			</method>
    423 			
    424 			<method name="onConditionSelected">
    425 				<parameter name="conditionName"/>
    426 				<parameter name="reload"/>
    427 				<body><![CDATA[
    428 					var conditionsMenu = this.id('conditionsmenu');
    429 					var operatorsList = this.id('operatorsmenu');
    430 					
    431 					// Skip if no condition or correct condition already selected
    432 					if (!conditionName || (conditionName == this.selectedCondition && !reload)) {
    433 						return;
    434 					}
    435 					
    436 					this.selectedCondition = conditionName;
    437 					this.selectedOperator = operatorsList.value;
    438 					
    439 					var condition = Zotero.SearchConditions.get(conditionName);
    440 					var operators = condition.operators;
    441 					
    442 					conditionsMenu.value = conditionName;
    443 					
    444 					// Parent state isn't set automatically for submenu selections
    445 					if (!this.isPrimaryCondition(conditionName)) {
    446 						conditionsMenu.selectedIndex = -1;
    447 						conditionsMenu.setAttribute(
    448 							'label',
    449 							Zotero.SearchConditions.getLocalizedName(conditionName)
    450 						);
    451 					}
    452 					
    453 					this.updateSubmenuCheckboxes(conditionsMenu);
    454 					
    455 					// Display appropriate operators for condition
    456 					var selectThis;
    457 					for(var i = 0, len = operatorsList.firstChild.childNodes.length; i < len; i++)
    458 					{
    459 						var val = operatorsList.firstChild.childNodes[i].getAttribute('value');
    460 						var hidden = !operators[val];
    461 						operatorsList.firstChild.childNodes[i].setAttribute('hidden', hidden);
    462 						if (!hidden && (selectThis == null || this.selectedOperator == val))
    463 						{
    464 							selectThis = i;
    465 						}
    466 					}
    467 					operatorsList.selectedIndex = selectThis;
    468 					
    469 					// Generate drop-down menu instead of textbox for certain conditions
    470 					switch (conditionName) {
    471 						case 'collection':
    472 							var rows = [];
    473 							
    474 							var libraryID = this.parent.search.libraryID;
    475 							
    476 							// Add collections
    477 							let cols = Zotero.Collections.getByLibrary(libraryID, true);
    478 							for (let col of cols) {
    479 								// Indent subcollections
    480 								var indent = '';
    481 								if (col.level) {
    482 									for (let j = 1; j < col.level; j++) {
    483 										indent += '    ';
    484 									}
    485 									indent += '- ';
    486 								}
    487 								rows.push({
    488 									name: indent + col.name,
    489 									value: 'C' + col.key,
    490 									image: Zotero.Collection.prototype.treeViewImage
    491 								});
    492 							}
    493 							
    494 							// Add saved searches
    495 							let searches = Zotero.Searches.getByLibrary(libraryID);
    496 							for (let search of searches) {
    497 								if (search.id != this.parent.search.id) {
    498 									rows.push({
    499 										name: search.name,
    500 										value: 'S' + search.key,
    501 										image: Zotero.Search.prototype.treeViewImage
    502 									});
    503 								}
    504 							}
    505 							this.createValueMenu(rows);
    506 							break;
    507 						
    508 						case 'itemType':
    509 							var rows = Zotero.ItemTypes.getTypes().map(type => ({
    510 								name: Zotero.ItemTypes.getLocalizedString(type.id),
    511 								value: type.name
    512 							}));
    513 							
    514 							// Sort by localized name
    515 							var collation = Zotero.getLocaleCollation();
    516 							rows.sort((a, b) => collation.compareString(1, a.name, b.name));
    517 							
    518 							this.createValueMenu(rows);
    519 							break;
    520 						
    521 						case 'fileTypeID':
    522 							var rows = Zotero.FileTypes.getTypes().map(type => ({
    523 								name: Zotero.getString('fileTypes.' + type.name),
    524 								value: type.id
    525 							}));
    526 							
    527 							// Sort by localized name
    528 							var collation = Zotero.getLocaleCollation();
    529 							rows.sort((a, b) => collation.compareString(1, a.name, b.name));
    530 							
    531 							this.createValueMenu(rows);
    532 							break;
    533 						
    534 						default:
    535 							if (operatorsList.value=='isInTheLast')
    536 							{
    537 								this.id('value-date-age').value = this.value;
    538 							}
    539 							
    540 							// Textbox
    541 							else {
    542 								// If switching from menu to textbox, clear value
    543 								if (this.id('valuefield').hidden){
    544 									this.id('valuefield').value = '';
    545 								}
    546 								// If switching between textbox conditions, get loaded value for new one
    547 								else {
    548 									this.id('valuefield').value = this.value;
    549 								}
    550 								
    551 								// Update field drop-down if applicable
    552 								this.id('valuefield').update(conditionName, this.mode);
    553 							}
    554 					}
    555 					
    556 					this.onOperatorSelected();
    557 				]]></body>
    558 			</method>
    559 			<method name="onOperatorSelected">
    560 				<body>
    561 					<![CDATA[
    562 					var operatorsList = this.id('operatorsmenu');
    563 					
    564 					// Drop-down menu
    565 					if (this.selectedCondition == 'collection'
    566 							|| this.selectedCondition == 'itemType'
    567 							|| this.selectedCondition == 'fileTypeID') {
    568 						this.id('valuefield').hidden = true;
    569 						this.id('valuemenu').hidden = false;
    570 						this.id('value-date-age').hidden = true;
    571 					}
    572 					
    573 					// Textbox + units dropdown for isInTheLast operator
    574 					else if (operatorsList.value=='isInTheLast')
    575 					{
    576 						// If switching from text field, clear value
    577 						if (this.id('value-date-age').hidden){
    578 							this.value = '';
    579 						}
    580 						this.id('valuefield').hidden = true;
    581 						this.id('valuemenu').hidden = true;
    582 						this.id('value-date-age').hidden = false;
    583 					}
    584 					
    585 					// Textbox
    586 					else
    587 					{
    588 						// If switching from date age, clear value
    589 						if (this.id('valuefield').hidden){
    590 							this.value = '';
    591 						}
    592 						this.id('valuefield').hidden = false;
    593 						this.id('valuemenu').hidden = true;
    594 						this.id('value-date-age').hidden = true;
    595 					}
    596 					]]>
    597 				</body>
    598 			</method>
    599 			<method name="createValueMenu">
    600 				<parameter name="rows"/>
    601 				<body>
    602 					<![CDATA[
    603 						while (this.id('valuemenu').hasChildNodes()){
    604 							this.id('valuemenu').removeChild(this.id('valuemenu').firstChild);
    605 						}
    606 						
    607 						for (let row of rows) {
    608 							let menuitem = this.id('valuemenu').appendItem(row.name, row.value);
    609 							if (row.image) {
    610 								menuitem.className = 'menuitem-iconic';
    611 								menuitem.setAttribute('image', row.image);
    612 							}
    613 						}
    614 						this.id('valuemenu').selectedIndex = 0;
    615 						
    616 						if (this.value)
    617 						{
    618 							this.id('valuemenu').value = this.value;
    619 						}
    620 					]]>
    621 				</body>
    622 			</method>
    623 			<method name="initWithParentAndCondition">
    624 				<parameter name="parent"/>
    625 				<parameter name="condition"/>
    626 				<body><![CDATA[
    627 					this.parent = parent;
    628 					this.conditionID = condition['id'];
    629 					var menu = this.id('conditionsmenu');
    630 					
    631 					if(this.parent.search)
    632 					{
    633 						this.dontupdate = true;	//so that the search doesn't get updated while we are creating controls.
    634 						var prefix = '';
    635 						
    636 						// Handle special conditions
    637 						switch (condition.condition) {
    638 							case 'savedSearch':
    639 								prefix = 'S';
    640 								break;
    641 							
    642 							case 'collection':
    643 								prefix = 'C';
    644 								break;
    645 						}
    646 						
    647 						// Map certain conditions to other menu items
    648 						let uiCondition = condition.condition;
    649 						switch (condition.condition) {
    650 							case 'savedSearch':
    651 								uiCondition = 'collection';
    652 								break;
    653 						}
    654 						
    655 						menu.setAttribute('value', uiCondition);
    656 						
    657 						// Convert datetimes from UTC to localtime
    658 						if ((condition['condition']=='accessDate' ||
    659 								condition['condition']=='dateAdded' ||
    660 								condition['condition']=='dateModified') &&
    661 								Zotero.Date.isSQLDateTime(condition['value'])){
    662 							
    663 							condition['value'] =
    664 								Zotero.Date.dateToSQL(Zotero.Date.sqlToDate(condition['value'], true));
    665 						}
    666 						
    667 						this.mode = condition['mode'];
    668 						this.id('operatorsmenu').value = condition['operator'];
    669 						this.value = prefix +
    670 							(condition.value ? condition.value : '');
    671 
    672 						this.dontupdate = false;
    673 					}
    674 					
    675 					this.onConditionSelected(menu.value);
    676 					
    677 					this.id('conditionsmenu').focus();
    678 				]]></body>
    679 			</method>
    680 			<!-- Gets the value from the UI and updates the associated condition on the Zotero.Search object -->
    681 			<method name="updateSearch">
    682 				<body>
    683 					<![CDATA[
    684 						if(this.parent && this.parent.search && !this.dontupdate)
    685 						{
    686 							var condition = this.selectedCondition;
    687 							var operator = this.id('operatorsmenu').value;
    688 							
    689 							// Regular text field
    690 							if (!this.id('valuefield').hidden)
    691 							{
    692 								var value = this.id('valuefield').value;
    693 								
    694 								// Convert datetimes to UTC before saving
    695 								switch (condition) {
    696 									case 'accessDate':
    697 									case 'dateAdded':
    698 									case 'dateModified':
    699 										if (Zotero.Date.isSQLDateTime(value)) {
    700 											var value = Zotero.Date.dateToSQL(Zotero.Date.sqlToDate(value), true);
    701 										}
    702 								}
    703 								
    704 								// Append mode to condition
    705 								if (this.id('valuefield').mode){
    706 									condition += '/' + this.id('valuefield').mode;
    707 								}
    708 							}
    709 							
    710 							// isInTheLast operator
    711 							else if (!this.id('value-date-age').hidden)
    712 							{
    713 								var value = this.id('value-date-age').value;
    714 							}
    715 							
    716 							// Handle special C1234 and S5678 form for
    717 							// collections and searches
    718 							else if (condition == 'collection') {
    719 								var letter = this.id('valuemenu').value.substr(0,1);
    720 								if (letter=='C')
    721 								{
    722 									condition = 'collection';
    723 								}
    724 								else if (letter=='S')
    725 								{
    726 									condition = 'savedSearch';
    727 								}
    728 								var value = this.id('valuemenu').value.substr(1);
    729 							}
    730 							
    731 							// Regular drop-down menu
    732 							else
    733 							{
    734 								var value = this.id('valuemenu').value;
    735 							}
    736 							this.parent.search.updateCondition(this.conditionID, condition, operator, value);
    737 						}
    738 					]]>
    739 				</body>
    740 			</method>
    741 			
    742 			<method name="updateSubmenuCheckboxes">
    743 			<parameter name="menu"/>
    744 			<body><![CDATA[
    745 				for (let i = 0; i < menu.itemCount; i++) {
    746 					let item = menu.getItemAtIndex(i);
    747 					if (item.localName == 'menuitem') {
    748 						if (item.getAttribute('value') == this.selectedCondition) {
    749 							item.setAttribute('checked', true);
    750 						}
    751 						else {
    752 							item.removeAttribute('checked');
    753 						}
    754 					}
    755 					else {
    756 						this.updateSubmenuCheckboxes(item);
    757 					}
    758 				}
    759 			]]></body>
    760 			</method>
    761 			
    762 			<method name="revealSelectedCondition">
    763 			<parameter name="menu"/>
    764 			<body><![CDATA[
    765 				if (!this.selectedCondition || this.isPrimaryCondition(this.selectedCondition)) {
    766 					return;
    767 				}
    768 				
    769 				if (!menu) {
    770 					menu = this.id('conditionsmenu');
    771 				}
    772 				for (let i = 0; i < menu.itemCount; i++) {
    773 					let item = menu.getItemAtIndex(i);
    774 					if (item.localName == 'menuitem') {
    775 						if (item.getAttribute('value') == this.selectedCondition) {
    776 							menu.open = true;
    777 							return true;
    778 						}
    779 					}
    780 					else {
    781 						var opened = this.revealSelectedCondition(item);
    782 						if (opened) {
    783 							return true;
    784 						}
    785 					}
    786 				}
    787 				
    788 				return false;
    789 			]]></body></method>
    790 			
    791 			<method name="onLibraryChange">
    792 				<body><![CDATA[
    793 					switch (this.selectedCondition) {
    794 					case 'collection':
    795 						this.onConditionSelected(this.selectedCondition, true);
    796 						break;
    797 					}
    798 				]]></body>
    799 			</method>
    800 			
    801 			<method name="onRemoveClicked">
    802 				<body>
    803 					<![CDATA[
    804 						if (this.parent){
    805 							this.parent.removeCondition(this.conditionID);
    806 							window.sizeToContent()
    807 						}
    808 					]]>
    809 				</body>
    810 			</method>
    811 			<method name="onAddClicked">
    812 				<body>
    813 					<![CDATA[
    814 						if (this.parent){
    815 							this.parent.addCondition();
    816 							window.sizeToContent();
    817 						}
    818 					]]>
    819 				</body>
    820 			</method>
    821 			<method name="disableRemoveButton">
    822 				<body>
    823 					<![CDATA[
    824 						var button = this.id("remove");
    825 						button.setAttribute('disabled', true);
    826 						button.removeAttribute('onclick');
    827 					]]>
    828 				</body>
    829 			</method>
    830 			<method name="enableRemoveButton">
    831 				<body>
    832 					<![CDATA[
    833 						var button = this.id("remove");
    834 						button.setAttribute('disabled', false);
    835 						button.setAttribute('onclick', "document.getBindingParent(this).onRemoveClicked(event)");
    836 					]]>
    837 				</body>
    838 			</method>
    839 			<method name="id">
    840 				<parameter name="id"/>
    841 				<body>
    842 					<![CDATA[
    843 						var elems = document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id);
    844 						return elems[0] ? elems[0] : false;
    845 					]]>
    846 				</body>
    847 			</method>
    848 		</implementation>
    849 		
    850 		<content>
    851 			<xul:hbox id="search-condition" xbl:inherits="flex">
    852 				<xul:popupset id="condition-tooltips"/>
    853 				
    854 				<xul:menulist id="conditionsmenu" oncommand="document.getBindingParent(this).onConditionSelected(event.target.value); event.stopPropagation()">
    855 					<xul:menupopup onpopupshown="document.getBindingParent(this).revealSelectedCondition()">
    856 						<xul:menu id="more-conditions-menu" label="&zotero.general.more;">
    857 							<xul:menupopup/>
    858 						</xul:menu>
    859 					</xul:menupopup>
    860 				</xul:menulist>
    861 				<xul:menulist id="operatorsmenu" oncommand="document.getBindingParent(this).onOperatorSelected(); event.stopPropagation()">
    862 					<xul:menupopup/>
    863 				</xul:menulist>
    864 				<xul:zoterosearchtextbox id="valuefield" flex="1"/>
    865 				<xul:menulist id="valuemenu" flex="1" hidden="true">
    866 					<xul:menupopup/>
    867 				</xul:menulist>
    868 				<xul:zoterosearchagefield id="value-date-age" hidden="true" flex="1"/>
    869 				<xul:label id="remove" class="zotero-clicky zotero-clicky-minus" value="-" onclick="document.getBindingParent(this).onRemoveClicked(event)"/>
    870 				<xul:label id="add" class="zotero-clicky zotero-clicky-plus" value="+" onclick="document.getBindingParent(this).onAddClicked(event)"/>
    871 			</xul:hbox>
    872 		</content>
    873 	</binding>
    874 	
    875 	
    876 	
    877 	<binding id="search-textbox">
    878 		<resources>
    879 			<stylesheet src="chrome://zotero/skin/bindings/search.css"/>
    880 		</resources>
    881 		
    882 		<implementation>
    883 			<property name="value"
    884 				onget="return document.getAnonymousNodes(this)[0].value"
    885 				onset="document.getAnonymousNodes(this)[0].setAttribute('value', val); return val"/>
    886 			<property name="mode">
    887 				<getter>
    888 					<![CDATA[
    889 						if (this.getAttribute('hasOptions')!='true'){
    890 							return false;
    891 						}
    892 						
    893 						var button = this.id('textbox-button');
    894 						var menu = this.id(button.popup);
    895 						
    896 						var selectedIndex = -1;
    897 						for (var i=0; i<menu.childNodes.length; i++){
    898 							if (menu.childNodes[i].getAttribute('checked')=='true'){
    899 								selectedIndex = i;
    900 								break;
    901 							}
    902 						}
    903 						switch (button.popup){
    904 							case 'textbox-fulltext-menu':
    905 								switch (selectedIndex){
    906 									case 0:
    907 										return false;
    908 									
    909 									case 1:
    910 										return 'phraseBinary';
    911 									
    912 									case 2:
    913 										return 'regexp';
    914 									
    915 									case 3:
    916 										return 'regexpCS';
    917 								}
    918 								break;
    919 						}
    920 						
    921 						throw('Invalid search textbox popup');
    922 					]]>
    923 				</getter>
    924 			</property>
    925 			<method name="update">
    926 				<parameter name="condition"/>
    927 				<parameter name="mode"/>
    928 				<body>
    929 					<![CDATA[
    930 						var button = this.id('textbox-button');
    931 						
    932 						switch (condition){
    933 							case 'fulltextContent':
    934 								button.popup = 'textbox-fulltext-menu';
    935 								button.setAttribute('popup', 'textbox-fulltext-menu');
    936 								var menu = this.id(button.popup);
    937 								this.setAttribute('hasOptions', true);
    938 								
    939 								var selectedIndex = 0;
    940 								if (mode){
    941 									switch (mode){
    942 										case 'phrase':
    943 											selectedIndex = 0;
    944 											break;
    945 										
    946 										case 'phraseBinary':
    947 											selectedIndex = 1;
    948 											break;
    949 										
    950 										case 'regexp':
    951 											selectedIndex = 2;
    952 											break;
    953 										
    954 										case 'regexpCS':
    955 											selectedIndex = 3;
    956 											break;
    957 									}
    958 								}
    959 								menu.childNodes[selectedIndex].setAttribute('checked', true);
    960 								break;
    961 								
    962 							default:
    963 								this.setAttribute('hasOptions', false);
    964 								
    965 								// Set textbox to autocomplete mode
    966 								switch (condition)
    967 								{
    968 									// Skip autocomplete for these fields
    969 									case 'date':
    970 									case 'note':
    971 									case 'extra':
    972 										break;
    973 									
    974 									default:
    975 									
    976 										var textbox = document.getAnonymousNodes(this)[0];
    977 										textbox.setAttribute('type', 'autocomplete');
    978 										textbox.setAttribute('autocompletesearch', 'zotero');
    979 										textbox.setAttribute('timeout', '250');
    980 										
    981 										var autocompleteParams = {
    982 											fieldName: condition
    983 										};
    984 										if (condition == 'creator') {
    985 											autocompleteParams.fieldMode = 2;
    986 										}
    987 										textbox.setAttribute(
    988 											'autocompletesearchparam',
    989 											JSON.stringify(autocompleteParams)
    990 										);
    991 								}
    992 						}
    993 						
    994 						if (!autocompleteParams) {
    995 							var textbox = document.getAnonymousNodes(this)[0];
    996 							textbox.removeAttribute('type');
    997 						}
    998 					]]>
    999 				</body>
   1000 			</method>
   1001 			<method name="id">
   1002 				<parameter name="id"/>
   1003 				<body>
   1004 					<![CDATA[
   1005 						return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
   1006 					]]>
   1007 				</body>
   1008 			</method>
   1009 		</implementation>
   1010 		
   1011 		<content>
   1012 			<xul:textbox id="search-textbox" xbl:inherits="hasOptions,flex">
   1013 				<xul:popupset>
   1014 					<xul:menupopup id="textbox-fulltext-menu">
   1015 						<xul:menuitem type="radio" label="&zotero.search.textModes.phrase;"/>
   1016 						<xul:menuitem type="radio" label="&zotero.search.textModes.phraseBinary;"/>
   1017 						<xul:menuitem type="radio" label="&zotero.search.textModes.regexp;"/>
   1018 						<xul:menuitem type="radio" label="&zotero.search.textModes.regexpCS;"/>
   1019 					</xul:menupopup>
   1020 				</xul:popupset>
   1021 				
   1022 				<xul:toolbarbutton id="textbox-button" type="menu"/>
   1023 			</xul:textbox>
   1024 		</content>
   1025 	</binding>
   1026 	
   1027 	
   1028 	
   1029 	<binding id="search-in-the-last">
   1030 		<resources>
   1031 			<stylesheet src="chrome://zotero/skin/bindings/search.css"/>
   1032 		</resources>
   1033 		
   1034 		<implementation>
   1035 			<property name="value">
   1036 				<getter>
   1037 					<![CDATA[
   1038 						var menulist = document.getAnonymousNodes(this)[0].firstChild.nextSibling;
   1039 						
   1040 						return document.getAnonymousNodes(this)[0].firstChild.value + ' ' +
   1041 							menulist.firstChild.childNodes[menulist.selectedIndex].getAttribute('value')
   1042 					]]>
   1043 				</getter>
   1044 				<setter>
   1045 					<![CDATA[
   1046 						var [num, units] = val.split(' ');
   1047 						document.getAnonymousNodes(this)[0].firstChild.setAttribute('value', num);
   1048 						
   1049 						var menulist = document.getAnonymousNodes(this)[0].firstChild.nextSibling;
   1050 						var menupopup = menulist.firstChild;
   1051 						
   1052 						var selectThis = 0;
   1053 						for (var i=0; i<menupopup.childNodes.length; i++){
   1054 							if (menupopup.childNodes[i].value == units)
   1055 							{
   1056 								selectThis = i;
   1057 								break;
   1058 							}
   1059 						}
   1060 						menulist.selectedIndex = selectThis;
   1061 						
   1062 						return val;
   1063 					]]>
   1064 				</setter>
   1065 			</property>
   1066 			<method name="id">
   1067 				<parameter name="id"/>
   1068 				<body>
   1069 					<![CDATA[
   1070 						return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
   1071 					]]>
   1072 				</body>
   1073 			</method>
   1074 		</implementation>
   1075 		
   1076 		<content>
   1077 			<xul:hbox id="search-in-the-last" flex="1">
   1078 				<xul:textbox flex="1"/>
   1079 				<xul:menulist>
   1080 					<xul:menupopup flex="1">
   1081 						<xul:menuitem label="&zotero.search.date.units.days;" value="days" selected="true"/>
   1082 						<xul:menuitem label="&zotero.search.date.units.months;" value="months"/>
   1083 						<xul:menuitem label="&zotero.search.date.units.years;" value="years"/>
   1084 					</xul:menupopup>
   1085 				</xul:menulist>
   1086 			</xul:hbox>
   1087 		</content>
   1088 	</binding>
   1089 </bindings>