www

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

noteeditor.xml (18106B)


      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 <bindings 	xmlns="http://www.mozilla.org/xbl"
     28 			xmlns:xbl="http://www.mozilla.org/xbl"
     29 			xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     30 	
     31 	<binding id="note-editor">
     32 		<resources>
     33 			<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
     34 			<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
     35 		</resources>
     36 		
     37 		<implementation>
     38 			<!--
     39 				Public properties
     40 			-->
     41 			<field name="editable">false</field>
     42 			<field name="saveOnEdit">false</field>
     43 			<field name="displayTags">false</field>
     44 			<field name="displayRelated">false</field>
     45 			<field name="displayButton">false</field>
     46 			
     47 			<field name="buttonCaption"/>
     48 			<field name="parentClickHandler"/>
     49 			<field name="keyDownHandler"/>
     50 			<field name="commandHandler"/>
     51 			<field name="clickHandler"/>
     52 			
     53 			<!-- Modes are predefined settings groups for particular tasks -->
     54 			<field name="_mode">"view"</field>
     55 			<property name="mode" onget="return this._mode;">
     56 				<setter>
     57 				<![CDATA[
     58 					// Duplicate default property settings here
     59 					this.editable = false;
     60 					this.saveOnEdit = false;
     61 					this.displayTags = false;
     62 					this.displayRelated = false;
     63 					this.displayButton = false;
     64 					
     65 					switch (val) {
     66 						case 'view':
     67 						case 'merge':
     68 							// If there's an existing editor, mark it as read-only. This allows for
     69 							// disabling an existing editable note (e.g., if there's a save error).
     70 							if (this.noteField) {
     71 								this.noteField.onInit(ed => ed.setMode('readonly'));
     72 							}
     73 							break;
     74 						
     75 						case 'edit':
     76 							this.editable = true;
     77 							this.saveOnEdit = true;
     78 							this.parentClickHandler = this.selectParent;
     79 							this.keyDownHandler = this.handleKeyDown;
     80 							this.commandHandler = this.save;
     81 							this.displayTags = true;
     82 							this.displayRelated = true;
     83 							break;
     84 						
     85 						default:
     86 							throw ("Invalid mode '" + val + "' in noteeditor.xml");
     87 					}
     88 					
     89 					this._mode = val;
     90 					document.getAnonymousNodes(this)[0].setAttribute('mode', val);
     91 					this._id('links-box').mode = val;
     92 				]]>
     93 				</setter>
     94 			</property>
     95 			
     96 			<field name="_parentItem"/>
     97 			<property name="parentItem" onget="return this._parentItem;">
     98 				<setter>
     99 					<![CDATA[
    100 						this._parentItem = this._id('links-box').parentItem = val;
    101 					]]>
    102 				</setter>
    103 			</property>
    104 			
    105 			<field name="_mtime"/>
    106 			
    107 			<field name="_item"/>
    108 			<property name="item" onget="return this._item;">
    109 				<setter><![CDATA[
    110 					this._item = val;
    111 					// TODO: use clientDateModified instead
    112 					this._mtime = val.getField('dateModified');
    113 					
    114 					var parentKey = this.item.parentKey;
    115 					if (parentKey) {
    116 						this.parentItem = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey);
    117 					}
    118 					
    119 					this._id('links-box').item = this.item;
    120 					
    121 					this.refresh();
    122 				]]></setter>
    123 			</property>
    124 			
    125 			<property name="linksOnTop">
    126 				<setter>
    127 					<![CDATA[
    128 						if(val) {
    129 							var container = this._id('links-container');
    130 							var parent = container.parentNode;
    131 							var sib = container.nextSibling;
    132 							while (parent.firstChild !== container) {
    133 								parent.insertBefore(parent.removeChild(parent.firstChild), sib);
    134 							}
    135 						}
    136 					]]>
    137 				</setter>
    138 			</property>
    139 			
    140 			<property name="note"
    141 				onget="Zotero.debug('Getting note with .note deprecated -- use .item in zoteronoteeditor'); return this._item"
    142 				onset="Zotero.debug('Setting note with .note deprecated -- use .item in zoteronoteeditor'); this.item = val"/>
    143 			<property name="ref" onget="return this._item" onset="this.item = val"/>
    144 			
    145 			<field name="collection"/>
    146 			
    147 			<property name="noteField" onget="return this._id('noteField')" readonly="true"/>
    148 			<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
    149 			
    150 			<constructor>
    151 			<![CDATA[
    152 				this.instanceID = Zotero.Utilities.randomString();
    153 				this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
    154 			]]>
    155 			</constructor>
    156 			
    157 			<destructor>
    158 			<![CDATA[
    159 				Zotero.Notifier.unregisterObserver(this._notifierID);
    160 			]]>
    161 			</destructor>
    162 			
    163 			<method name="notify">
    164 				<parameter name="event"/>
    165 				<parameter name="type"/>
    166 				<parameter name="ids"/>
    167 				<parameter name="extraData"/>
    168 				<body><![CDATA[
    169 					if (event != 'modify' || !this.item || !this.item.id) return;
    170 					for (let i = 0; i < ids.length; i++) {
    171 						let id = ids[i];
    172 						if (id != this.item.id) {
    173 							continue;
    174 						}
    175 						if (extraData && extraData[id] && extraData[id].noteEditorID == this.instanceID) {
    176 							//Zotero.debug("Skipping notification from current note field");
    177 							continue;
    178 						}
    179 						if (this.noteField.changed) {
    180 							//Zotero.debug("Note has changed since last save -- skipping refresh");
    181 							return;
    182 						}
    183 						this.refresh();
    184 						break;
    185 					}
    186 				]]></body>
    187 			</method>
    188 			
    189 			<method name="refresh">
    190 				<body><![CDATA[
    191 					Zotero.debug('Refreshing note editor');
    192 					
    193 					var textbox = this.noteField;
    194 					var textboxReadOnly = this._id('noteFieldReadOnly');
    195 					var button = this._id('goButton');
    196 					
    197 					if (this.editable) {
    198 						textbox.hidden = false;
    199 						textboxReadOnly.hidden = true;
    200 					}
    201 					else {
    202 						textbox.hidden = true;
    203 						textboxReadOnly.hidden = false;
    204 						textbox = textboxReadOnly;
    205 					}
    206 					
    207 					//var scrollPos = textbox.inputField.scrollTop;
    208 					if (this.item) {
    209 						// For sanity check in save()
    210 						textbox.setAttribute('itemID', this.item.id);
    211 						textbox.value = this.item.getNote();
    212 					}
    213 					else {
    214 						textbox.value = '';
    215 						textbox.removeAttribute('itemID');
    216 					}
    217 					//textbox.inputField.scrollTop = scrollPos;
    218 					
    219 					this._id('links-container').hidden = !(this.displayTags && this.displayRelated);
    220 					this._id('links-box').refresh();
    221 					
    222 					if (this.keyDownHandler) {
    223 						textbox.setAttribute('onkeydown',
    224 							'document.getBindingParent(this).handleKeyDown(event)');
    225 					}
    226 					else {
    227 						textbox.removeAttribute('onkeydown');
    228 					}
    229 					
    230 					if (this.commandHandler) {
    231 						textbox.setAttribute('oncommand',
    232 							'document.getBindingParent(this).commandHandler()');
    233 					}
    234 					else {
    235 						textbox.removeAttribute('oncommand');
    236 					}
    237 					
    238 					if (this.displayButton) {
    239 						button.label = this.buttonCaption;
    240 						button.hidden = false;
    241 						button.setAttribute('oncommand',
    242 							'document.getBindingParent(this).clickHandler(this)');
    243 					}
    244 					else {
    245 						button.hidden = true;
    246 					}
    247 				]]></body>
    248 			</method>
    249 			
    250 			<method name="save">
    251 				<body><![CDATA[
    252 					return Zotero.spawn(function* () {
    253 						try {
    254 							if (this._mode == 'view') {
    255 								Zotero.debug("Not saving read-only note");
    256 								return;
    257 							}
    258 							
    259 							var noteField = this._id('noteField');
    260 							var value = noteField.value;
    261 							if (value === null) {
    262 								Zotero.debug("Note value not available -- not saving", 2);
    263 								return;
    264 							}
    265 							
    266 							// Update note
    267 							if (this.item) {
    268 								// If note field doesn't match item, abort save and run error handler
    269 								if (noteField.getAttribute('itemID') != this.item.id) {
    270 									throw new Error("Note field doesn't match current item");
    271 								}
    272 								
    273 								let changed = this.item.setNote(value);
    274 								if (changed && this.saveOnEdit) {
    275 									this.noteField.changed = false;
    276 									yield this.item.saveTx({
    277 										notifierData: {
    278 											noteEditorID: this.instanceID
    279 										}
    280 									});
    281 								}
    282 								return;
    283 							}
    284 							
    285 							// Create new note
    286 							var item = new Zotero.Item('note');
    287 							if (this.parentItem) {
    288 								item.libraryID = this.parentItem.libraryID;
    289 							}
    290 							item.setNote(value);
    291 							if (this.parentItem) {
    292 								item.parentKey = this.parentItem.key;
    293 							}
    294 							if (this.saveOnEdit) {
    295 								var id = yield item.saveTx();
    296 								
    297 								if (!this.parentItem && this.collection) {
    298 									this.collection.addItem(id);
    299 								}
    300 							}
    301 							
    302 							this.item = item;
    303 						}
    304 						catch (e) {
    305 							Zotero.logError(e);
    306 							
    307 							if (this.hasAttribute('onerror')) {
    308 								let fn = new Function("", this.getAttribute('onerror'));
    309 								fn.call(this)
    310 							}
    311 							if (this.onError) {
    312 								this.onError(e);
    313 							}
    314 						}
    315 					}.bind(this));
    316 				]]></body>
    317 			</method>
    318 			
    319 			<!-- Used to insert a tab manually -->
    320 			<method name="handleKeyDown">
    321 				<parameter name="event"/>
    322 				<body>
    323 				<![CDATA[
    324 					switch (event.keyCode) {
    325 						case 9:
    326 							if (event.ctrlKey || event.altKey) {
    327 								return;
    328 							}
    329 							
    330 							event.stopPropagation();
    331 							event.preventDefault();
    332 							
    333 							// On shift-tab, focus the element specified in
    334 							// the 'previousfocus' attribute
    335 							if (event.shiftKey) {
    336 								let id = this.getAttribute('previousfocus');
    337 								if (id) {
    338 									setTimeout(function () {
    339 										document.getElementById(id).focus();
    340 									}, 0);
    341 								}
    342 								return;
    343 							}
    344 							
    345 							// Insert tab manually
    346 							//
    347 							// From http://kb.mozillazine.org/Inserting_text_at_cursor
    348 							try {
    349 								var command = "cmd_insertText";
    350 								var controller = document.commandDispatcher.getControllerForCommand(command);
    351 								if (controller && controller.isCommandEnabled(command)) {
    352 									controller = controller.QueryInterface(Components.interfaces.nsICommandController);
    353 									var params = Components.classes["@mozilla.org/embedcomp/command-params;1"]
    354 										.createInstance(Components.interfaces.nsICommandParams);
    355 									params.setStringValue("state_data", "\t");
    356 									controller.doCommandWithParams(command, params);
    357 								}
    358 							}
    359 							catch (e) {
    360 								Zotero.debug("Can't do cmd_insertText!\n" + e, 1);
    361 							}
    362 							
    363 							// DEBUG: is there a better way to prevent blur()?
    364 							setTimeout(function() { event.target.focus(); }, 1);
    365 							break;
    366 					}
    367 				]]>
    368 				</body>
    369 			</method>
    370 			
    371 			<method name="focus">
    372 				<body>
    373 					<![CDATA[
    374 						this._id('noteField').focus();
    375 					]]>
    376 				</body>
    377 			</method>
    378 			
    379 			<method name="clearUndo">
    380 				<body>
    381 				<![CDATA[
    382 					this._id('noteField').clearUndo();
    383 				]]>
    384 				</body>
    385 			</method>
    386 			
    387 			<method name="_id">
    388 				<parameter name="id"/>
    389 				<body>
    390 					<![CDATA[
    391 						return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
    392 					]]>
    393 				</body>
    394 			</method>
    395 		</implementation>
    396 		
    397 		<content>
    398 			<xul:vbox xbl:inherits="flex">
    399 				<xul:textbox id="noteField" type="styled" mode="note"
    400 					timeout="1000" flex="1" hidden="true"/>
    401 				<xul:textbox id="noteFieldReadOnly" type="styled" mode="note"
    402 					readonly="true" flex="1" hidden="true"/>
    403 				<xul:hbox id="links-container" hidden="true">
    404 					<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
    405 				</xul:hbox>
    406 				<xul:button id="goButton" hidden="true"/>
    407 			</xul:vbox>
    408 		</content>
    409 	</binding>
    410 	
    411 	
    412 	<binding id="links-box">
    413 		<implementation> 
    414 			<field name="itemRef"/>
    415 			<property name="item" onget="return this.itemRef;">
    416 				<setter>
    417 					<![CDATA[
    418 						this.itemRef = val;
    419 						
    420 						this.id('tags').item = this.item;
    421 						this.id('related').item = this.item;
    422 						this.refresh();
    423 					]]>
    424 				</setter>
    425 			</property>
    426 			<property name="mode">
    427 				<setter>
    428 				<![CDATA[
    429 					this.id('related').mode = val;
    430 					this.id('tags').mode = val;
    431 				]]>
    432 				</setter>
    433 			</property>
    434 			<field name="_parentItem"/>
    435 			<property name="parentItem" onget="return this._parentItem;">
    436 				<setter>
    437 				<![CDATA[
    438 					this._parentItem = val;
    439 					
    440 					var parentText = this.id('parentText');
    441 					if (parentText.firstChild) {
    442 						parentText.removeChild(parentText.firstChild);
    443 					}
    444 					
    445 					if (this._parentItem && this.getAttribute('notitle') != '1') {
    446 						this.id('parent-row').hidden = undefined;
    447 						this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
    448 						parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
    449 					}
    450 				]]>
    451 				</setter>
    452 			</property>
    453 			<method name="tagsClick">
    454 				<body><![CDATA[
    455 					this.id('tags').reload();
    456 					var x = this.boxObject.screenX;
    457 					var y = this.boxObject.screenY;
    458 					this.id('tagsPopup').openPopupAtScreen(x, y, false);
    459 					
    460 					// If editable and no existing tags, open new empty row
    461 					var tagsBox = this.id('tags');
    462 					if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
    463 						this.id('tags').newTag();
    464 					}
    465 				]]></body>
    466 			</method>
    467 			
    468 			<method name="refresh">
    469 				<body><![CDATA[
    470 					this.updateTagsSummary();
    471 					this.updateRelatedSummary();
    472 				]]></body>
    473 			</method>
    474 			
    475 			<method name="updateTagsSummary">
    476 				<body><![CDATA[
    477 					var v = this.id('tags').summary;
    478 					
    479 					if (!v || v == "") {
    480 						v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
    481 					}
    482 					
    483 					this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
    484 						+ Zotero.getString('punctuation.colon');
    485 					this.id('tagsClick').value = v;
    486 				]]></body>
    487 			</method>
    488 			<method name="relatedClick">
    489 				<body><![CDATA[
    490 					var relatedList = this.item.relatedItems;
    491 					if (relatedList.length > 0) {
    492 						var x = this.boxObject.screenX;
    493 						var y = this.boxObject.screenY;
    494 						this.id('relatedPopup').openPopupAtScreen(x, y, false);
    495 					}
    496 					else {
    497 						this.id('related').add();
    498 					}
    499 				]]></body>
    500 			</method>
    501 			<method name="updateRelatedSummary">
    502 				<body><![CDATA[
    503 					var v = this.id('related').summary;
    504 					
    505 					if (!v || v == "") {
    506 						v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
    507 					}
    508 					
    509 					this.id('relatedLabel').value = Zotero.getString('itemFields.related')
    510 						+ Zotero.getString('punctuation.colon');
    511 					this.id('relatedClick').value = v;
    512 				]]></body>
    513 			</method>
    514 			<method name="parentClick">
    515 				<body>
    516 				<![CDATA[
    517 					if (!this.item || !this.item.id) {
    518 						return;
    519 					}
    520 					
    521 					if (document.getElementById('zotero-pane')) {
    522 						var zp = ZoteroPane;
    523 					}
    524 					else {
    525 						var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
    526 									   .getService(Components.interfaces.nsIWindowMediator);
    527 						
    528 						var lastWin = wm.getMostRecentWindow("navigator:browser");
    529 						
    530 						if (!lastWin) {
    531 							var lastWin = window.open();
    532 						}
    533 						
    534 						if (lastWin.ZoteroOverlay && !lastWin.ZoteroPane.isShowing()) {
    535 							lastWin.ZoteroOverlay.toggleDisplay(true);
    536 						}
    537 						
    538 						var zp = lastWin.ZoteroPane;
    539 					}
    540 					
    541 					Zotero.spawn(function* () {
    542 						var parentID = this.item.parentID;
    543 						yield zp.clearQuicksearch();
    544 						zp.selectItem(parentID);
    545 					}, this);
    546 				]]>
    547 				</body>
    548 			</method>
    549 			<method name="id">
    550 				<parameter name="id"/>
    551 				<body>
    552 					<![CDATA[
    553 						return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
    554 					]]>
    555 				</body>
    556 			</method>
    557 		</implementation>
    558 		<content>
    559 			<xul:vbox xbl:inherits="flex">
    560 				<xul:grid>
    561 					<xul:columns>
    562 						<xul:column/>
    563 						<xul:column flex="1"/>
    564 					</xul:columns>
    565 					<xul:rows>
    566 						<xul:row id="parent-row" hidden="true">
    567 							<xul:label id="parentLabel"/>
    568 							<xul:label id="parentText" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).parentClick();"/>
    569 						</xul:row>
    570 						<xul:row>
    571 							<xul:label id="relatedLabel"/>
    572 							<xul:label id="relatedClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).relatedClick();"/>
    573 						</xul:row>
    574 						<xul:row>
    575 							<xul:label id="tagsLabel"/>
    576 							<xul:label id="tagsClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).tagsClick();"/>
    577 						</xul:row>
    578 					</xul:rows>
    579 				</xul:grid>
    580 				<xul:popupset>
    581 					<xul:menupopup id="relatedPopup" width="300" onpopupshowing="this.firstChild.refresh();">
    582 						<xul:relatedbox id="related" flex="1"/>
    583 					</xul:menupopup>
    584 					<!-- The onpopup* stuff is an ugly hack to keep track of when the
    585 					popup is open (and not the descendent autocomplete popup, which also
    586 					seems to get triggered by these events for reasons that are less than
    587 					clear) so that we can manually refresh the popup if it's open after
    588 					autocomplete is used to prevent it from becoming unresponsive
    589 					
    590 					Note: Code in tagsbox.xml is dependent on the DOM path between the
    591 					tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
    592 					-->
    593 					<xul:menupopup id="tagsPopup" ignorekeys="true"
    594 							onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
    595 							onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
    596 						<xul:tagsbox id="tags" flex="1" mode="edit"/>
    597 					</xul:menupopup>
    598 				</xul:popupset>
    599 			</xul:vbox>
    600 		</content>
    601 	</binding>
    602 </bindings>