www

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

attachmentbox.xml (18747B)


      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 bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
     28 <!-- <!DOCTYPE bindings SYSTEM "chrome://zotero/locale/attachmentbox.dtd"> -->
     29 
     30 <bindings xmlns="http://www.mozilla.org/xbl"
     31 		  xmlns:xbl="http://www.mozilla.org/xbl"
     32 		  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     33 	
     34 	<binding id="attachment-box">
     35 		<resources>
     36 			<stylesheet src="chrome://zotero/skin/bindings/attachmentbox.css"/>
     37 			<stylesheet src="chrome://zotero-platform/content/attachmentbox.css"/>
     38 		</resources>
     39 		
     40 		<implementation>
     41 			<!--
     42 				Public properties
     43 			-->
     44 			<field name="editable">false</field>
     45 			<field name="clickableLink">false</field>
     46 			<field name="displayButton">false</field>
     47 			<field name="displayNote">false</field>
     48 			
     49 			<field name="buttonCaption"/>
     50 			<field name="clickHandler"/>
     51 			
     52 			<!-- Modes are predefined settings groups for particular tasks -->
     53 			<field name="_mode">"view"</field>
     54 			<property name="mode" onget="return this._mode;">
     55 				<setter>
     56 				<![CDATA[
     57 					Zotero.debug("Setting mode to '" + val + "'");
     58 					
     59 					this.editable = false;
     60 					this.synchronous = false;
     61 					this.displayURL = false;
     62 					this.displayFileName = false;
     63 					this.clickableLink = false;
     64 					this.displayAccessed = false;
     65 					this.displayPages = false;
     66 					this.displayDateModified = false;
     67 					this.displayIndexed = false;
     68 					this.displayNote = false;
     69 					this.displayNoteIfEmpty = false;
     70 					
     71 					switch (val) {
     72 						case 'view':
     73 							this.displayURL = true;
     74 							this.displayFileName = true;
     75 							this.clickableLink = true;
     76 							this.displayAccessed = true;
     77 							this.displayPages = true;
     78 							this.displayIndexed = true;
     79 							this.displayNote = true;
     80 							this.displayDateModified = true;
     81 							break;
     82 						
     83 						case 'edit':
     84 							this.editable = true;
     85 							this.displayURL = true;
     86 							this.displayFileName = true;
     87 							this.clickableLink = true;
     88 							this.displayAccessed = true;
     89 							this.displayPages = true;
     90 							this.displayIndexed = true;
     91 							this.displayNote = true;
     92 							this.displayNoteIfEmpty = true;
     93 							this.displayDateModified = true;
     94 							break;
     95 						
     96 						case 'merge':
     97 							this.synchronous = true;
     98 							this.displayURL = true;
     99 							this.displayFileName = true;
    100 							this.displayAccessed = true;
    101 							this.displayNote = true;
    102 							this.displayDateModified = true;
    103 							break;
    104 						
    105 						case 'mergeedit':
    106 							this.synchronous = true;
    107 							this.editable = true;
    108 							this.displayURL = true;
    109 							this.displayFileName = true;
    110 							this.displayAccessed = true;
    111 							this.displayNote = true;
    112 							// Notes aren't currently editable in mergeedit pane
    113 							this.displayNoteIfEmpty = false;
    114 							this.displayDateModified = true;
    115 							break;
    116 						
    117 						case 'filemerge':
    118 							this.synchronous = true;
    119 							this.displayURL = true;
    120 							this.displayFileName = true;
    121 							this.displayDateModified = true;
    122 							break;
    123 						
    124 						default:
    125 							throw ("Invalid mode '" + val + "' in attachmentbox.xml");
    126 					}
    127 					
    128 					this._mode = val;
    129 					document.getAnonymousNodes(this)[0].setAttribute('mode', val);
    130 				]]>
    131 				</setter>
    132 			</property>
    133 			
    134 			<field name="_item"/>
    135 			<property name="item" onget="return this._item;">
    136 				<setter><![CDATA[
    137 					if (!(val instanceof Zotero.Item)) {
    138 						throw new Error("'item' must be a Zotero.Item");
    139 					}
    140 					this._item = val;
    141 					this.refresh();
    142 				]]></setter>
    143 			</property>
    144 			
    145 			<!-- Methods -->
    146 			
    147 			<constructor>
    148 			<![CDATA[
    149 				this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentbox');
    150 			]]>
    151 			</constructor>
    152 			
    153 			<destructor>
    154 			<![CDATA[
    155 				Zotero.Notifier.unregisterObserver(this._notifierID);
    156 			]]>
    157 			</destructor>
    158 			
    159 			<method name="notify">
    160 				<parameter name="event"/>
    161 				<parameter name="type"/>
    162 				<parameter name="ids"/>
    163 				<parameter name="extraData"/>
    164 				<body><![CDATA[
    165 					if (event != 'modify' || !this.item || !this.item.id) return;
    166 					for (let id of ids) {
    167 						if (id != this.item.id) {
    168 							continue;
    169 						}
    170 						
    171 						var noteEditor = this._id('attachment-note-editor')
    172 						if (extraData && extraData[id]
    173 								&& extraData[id].noteEditorID == noteEditor.instanceID) {
    174 							//Zotero.debug("Skipping notification from current attachment note field");
    175 							continue;
    176 						}
    177 						
    178 						this.refresh();
    179 						break;
    180 					}
    181 				]]></body>
    182 			</method>
    183 			
    184 			<method name="refresh">
    185 				<body><![CDATA[
    186 					Zotero.debug('Refreshing attachment box');
    187 					
    188 					var attachmentBox = document.getAnonymousNodes(this)[0];
    189 					var title = this._id('title');
    190 					var fileNameRow = this._id('fileNameRow');
    191 					var urlField = this._id('url');
    192 					var accessed = this._id('accessedRow');
    193 					var pagesRow = this._id('pagesRow');
    194 					var dateModifiedRow = this._id('dateModifiedRow');
    195 					var indexStatusRow = this._id('indexStatusRow');
    196 					var selectButton = this._id('select-button');
    197 					
    198 					// DEBUG: this is annoying -- we really want to use an abstracted
    199 					// version of createValueElement() from itemPane.js
    200 					// (ideally in an XBL binding)
    201 					
    202 					// Wrap title to multiple lines if necessary
    203 					while (title.hasChildNodes()) {
    204 						title.removeChild(title.firstChild);
    205 					}
    206 					var val = this.item.getField('title');
    207 					
    208 					if (typeof val != 'string') {
    209 						val += "";
    210 					}
    211 					
    212 					var firstSpace = val.indexOf(" ");
    213 					// Crop long uninterrupted text, and use value attribute for empty field
    214 					if ((firstSpace == -1 && val.length > 29 ) || firstSpace > 29 || val === "") {
    215 						title.setAttribute('crop', 'end');
    216 						title.setAttribute('value', val);
    217 					}
    218 					// Create a <description> element, essentially
    219 					else {
    220 						title.removeAttribute('value');
    221 						title.appendChild(document.createTextNode(val));
    222 					}
    223 					
    224 					if (this.editable) {
    225 						title.className = 'zotero-clicky';
    226 						
    227 						// For the time being, use a silly little popup
    228 						title.addEventListener('click', this.editTitle, false);
    229 					}
    230 					
    231 					var isImportedURL = this.item.attachmentLinkMode ==
    232 											Zotero.Attachments.LINK_MODE_IMPORTED_URL;
    233 					
    234 					// Metadata for URL's
    235 					if (this.item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL
    236 							|| isImportedURL) {
    237 						
    238 						// URL
    239 						if (this.displayURL) {
    240 							var urlSpec = this.item.getField('url');
    241 							urlField.setAttribute('value', urlSpec);
    242 							urlField.setAttribute('tooltiptext', urlSpec);
    243 							urlField.setAttribute('hidden', false);
    244 							if (this.clickableLink) {
    245 								 urlField.onclick = function (event) {
    246 									ZoteroPane_Local.loadURI(this.value, event)
    247 								};
    248 								urlField.className = 'zotero-text-link';
    249 							}
    250 							else {
    251 								urlField.className = '';
    252 							}
    253 							urlField.hidden = false;
    254 						}
    255 						else {
    256 							urlField.hidden = true;
    257 						}
    258 						
    259 						// Access date
    260 						if (this.displayAccessed) {
    261 							this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
    262 								+ Zotero.getString('punctuation.colon');
    263 							let val = this.item.getField('accessDate');
    264 							if (val) {
    265 								val = Zotero.Date.sqlToDate(val, true);
    266 							}
    267 							if (val) {
    268 								this._id("accessed").value = val.toLocaleString();
    269 								accessed.hidden = false;
    270 							}
    271 							else {
    272 								accessed.hidden = true;
    273 							}
    274 						}
    275 						else {
    276 							accessed.hidden = true;
    277 						}
    278 					}
    279 					// Metadata for files
    280 					else {
    281 						urlField.hidden = true;
    282 						accessed.hidden = true;
    283 					}
    284 					
    285 					if (this.item.attachmentLinkMode
    286 								!= Zotero.Attachments.LINK_MODE_LINKED_URL
    287 							&& this.displayFileName) {
    288 						var fileName = this.item.attachmentFilename;
    289 						
    290 						if (fileName) {
    291 							this._id("fileName-label").value = Zotero.getString('pane.item.attachments.filename')
    292 								+ Zotero.getString('punctuation.colon');
    293 							this._id("fileName").value = fileName;
    294 							fileNameRow.hidden = false;
    295 						}
    296 						else {
    297 							fileNameRow.hidden = true;
    298 						}
    299 					}
    300 					else {
    301 						fileNameRow.hidden = true;
    302 					}
    303 					
    304 					// Page count
    305 					if (this.displayPages) {
    306 						Zotero.Fulltext.getPages(this.item.id)
    307 						.tap(() => Zotero.Promise.check(this.item))
    308 						.then(function (pages) {
    309 							pages = pages ? pages.total : null;
    310 							if (pages) {
    311 								this._id("pages-label").value = Zotero.getString('itemFields.pages')
    312 									+ Zotero.getString('punctuation.colon');
    313 								this._id("pages").value = pages;
    314 								pagesRow.hidden = false;
    315 							}
    316 							else {
    317 								pagesRow.hidden = true;
    318 							}
    319 						}.bind(this));
    320 					}
    321 					else {
    322 						pagesRow.hidden = true;
    323 					}
    324 					
    325 					if (this.displayDateModified) {
    326 						this._id("dateModified-label").value = Zotero.getString('itemFields.dateModified')
    327 							+ Zotero.getString('punctuation.colon');
    328 						// Conflict resolution uses a modal window, so promises won't work, but
    329 						// the sync process passes in the file mod time as dateModified
    330 						if (this.synchronous) {
    331 							this._id("dateModified").value = Zotero.Date.sqlToDate(
    332 								this.item.getField('dateModified'), true
    333 							).toLocaleString();
    334 							dateModifiedRow.hidden = false;
    335 						}
    336 						else {
    337 							this.item.attachmentModificationTime
    338 							.tap(() => Zotero.Promise.check(this._id))
    339 							.then(function (mtime) {
    340 								if (mtime) {
    341 									this._id("dateModified").value = new Date(mtime).toLocaleString();
    342 								}
    343 								dateModifiedRow.hidden = !mtime;
    344 							}.bind(this));
    345 						}
    346 					}
    347 					else {
    348 						dateModifiedRow.hidden = true;
    349 					}
    350 					
    351 					// Full-text index information
    352 					if (this.displayIndexed) {
    353 						this.updateItemIndexedState()
    354 						.tap(() => Zotero.Promise.check(this.item))
    355 						.then(function () {
    356 							indexStatusRow.hidden = false;
    357 						});
    358 					}
    359 					else {
    360 						indexStatusRow.hidden = true;
    361 					}
    362 					
    363 					// Note editor
    364 					var noteEditor = this._id('attachment-note-editor');
    365 					if (this.displayNote && (this.displayNoteIfEmpty || this.item.getNote() != '')) {
    366 						noteEditor.linksOnTop = true;
    367 						noteEditor.hidden = false;
    368 						
    369 						// Don't make note editable (at least for now)
    370 						if (this.mode == 'merge' || this.mode == 'mergeedit') {
    371 							noteEditor.mode = 'merge';
    372 							noteEditor.displayButton = false;
    373 						}
    374 						else {
    375 							noteEditor.mode = this.mode;
    376 						}
    377 						noteEditor.parent = null;
    378 						noteEditor.item = this.item;
    379 					}
    380 					else {
    381 						noteEditor.hidden = true;
    382 					}
    383 					
    384 					if (this.displayButton) {
    385 						selectButton.label = this.buttonCaption;
    386 						selectButton.hidden = false;
    387 						selectButton.setAttribute('oncommand',
    388 							'document.getBindingParent(this).clickHandler(this)');
    389 					}
    390 					else {
    391 						selectButton.hidden = true;
    392 					}
    393 				]]></body>
    394 			</method>
    395 			
    396 			
    397 			<method name="editTitle">
    398 				<body>
    399 				<![CDATA[
    400 					return Zotero.spawn(function* () {
    401 						var item = document.getBindingParent(this).item;
    402 						var oldTitle = item.getField('title');
    403 						
    404 						var nsIPS = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
    405 								.getService(Components.interfaces.nsIPromptService);
    406 						
    407 						var newTitle = { value: oldTitle };
    408 						var checkState = { value: Zotero.Prefs.get('lastRenameAssociatedFile') };
    409 						
    410 						while (true) {
    411 							// Don't show "Rename associated file" option for
    412 							// linked URLs
    413 							if (item.attachmentLinkMode ==
    414 									Zotero.Attachments.LINK_MODE_LINKED_URL) {
    415 								var result = nsIPS.prompt(
    416 									window,
    417 									'',
    418 									Zotero.getString('pane.item.attachments.rename.title'),
    419 									newTitle,
    420 									null,
    421 									{}
    422 								);
    423 								
    424 								// If they hit cancel or left it blank
    425 								if (!result || !newTitle.value) {
    426 									return;
    427 								}
    428 								
    429 								break;
    430 							}
    431 							
    432 							var result = nsIPS.prompt(
    433 								window,
    434 								'',
    435 								Zotero.getString('pane.item.attachments.rename.title'),
    436 								newTitle,
    437 								Zotero.getString('pane.item.attachments.rename.renameAssociatedFile'),
    438 								checkState
    439 							);
    440 							
    441 							// If they hit cancel or left it blank
    442 							if (!result || !newTitle.value) {
    443 								return;
    444 							}
    445 							
    446 							Zotero.Prefs.set('lastRenameAssociatedFile', checkState.value);
    447 							
    448 							// Rename associated file
    449 							if (checkState.value) {
    450 								var newFilename = newTitle.value.trim();
    451 								if (newFilename.search(/\.\w{1,10}$/) == -1) {
    452 									// User did not specify extension. Use current
    453 									var oldExt = item.getFilename().match(/\.\w{1,10}$/);
    454 									if (oldExt) newFilename += oldExt[0];
    455 								}
    456 								var renamed = yield item.renameAttachmentFile(newFilename);
    457 								if (renamed == -1) {
    458 									var confirmed = nsIPS.confirm(
    459 										window,
    460 										'',
    461 										newFilename + ' exists. Overwrite existing file?'
    462 									);
    463 									if (!confirmed) {
    464 										// If they said not to overwrite existing file,
    465 										// start again
    466 										continue;
    467 									}
    468 									
    469 									// Force overwrite, but make sure we check that this doesn't fail
    470 									renamed = yield item.renameAttachmentFile(newFilename, true);
    471 								}
    472 								
    473 								if (renamed == -2) {
    474 									nsIPS.alert(
    475 										window,
    476 										Zotero.getString('general.error'),
    477 										Zotero.getString('pane.item.attachments.rename.error')
    478 									);
    479 									return;
    480 								}
    481 								else if (!renamed) {
    482 									nsIPS.alert(
    483 										window,
    484 										Zotero.getString('pane.item.attachments.fileNotFound.title'),
    485 										Zotero.getString('pane.item.attachments.fileNotFound.text')
    486 									);
    487 								}
    488 							}
    489 							
    490 							break;
    491 						}
    492 						
    493 						if (newTitle.value != oldTitle) {
    494 							item.setField('title', newTitle.value);
    495 							yield item.saveTx();
    496 						}
    497 					}.bind(this));
    498 				]]>
    499 				</body>
    500 			</method>
    501 			
    502 			
    503 			<method name="onViewClick">
    504 				<parameter name="event"/>
    505 				<body>
    506 				<![CDATA[
    507 					ZoteroPane_Local.viewAttachment(this.item.id, event, !this.editable);
    508 				]]>
    509 				</body>
    510 			</method>
    511 			
    512 			
    513 			<method name="onShowClick">
    514 				<parameter name="event"/>
    515 				<body>
    516 				<![CDATA[
    517 					ZoteroPane_Local.showAttachmentInFilesystem(this.item.id, event.originalTarget, !this.editable);
    518 				]]>
    519 				</body>
    520 			</method>
    521 			
    522 			
    523 			<!--
    524 			 Update Indexed: (Yes|No|Partial) line
    525 			-->
    526 			<method name="updateItemIndexedState">
    527 				<body><![CDATA[
    528 					return Zotero.spawn(function* () {
    529 						var indexStatus = this._id('index-status');
    530 						var reindexButton = this._id('reindex');
    531 						
    532 						var status = yield Zotero.Fulltext.getIndexedState(this.item)
    533 							.tap(() => Zotero.Promise.check(this.item));
    534 						var str = 'fulltext.indexState.';
    535 						switch (status) {
    536 							case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
    537 								str += 'unavailable';
    538 								break;
    539 							case Zotero.Fulltext.INDEX_STATE_UNINDEXED:
    540 								str = 'general.no';
    541 								break;
    542 							case Zotero.Fulltext.INDEX_STATE_PARTIAL:
    543 								str += 'partial';
    544 								break;
    545 							case Zotero.Fulltext.INDEX_STATE_QUEUED:
    546 								str += 'queued';
    547 								break;
    548 							case Zotero.Fulltext.INDEX_STATE_INDEXED:
    549 								str = 'general.yes';
    550 								break;
    551 						}
    552 						this._id("index-status-label").value = Zotero.getString('fulltext.indexState.indexed')
    553 							+ Zotero.getString('punctuation.colon');
    554 						indexStatus.value = Zotero.getString(str);
    555 						
    556 						// Reindex button tooltip (string stored in zotero.properties)
    557 						var str = Zotero.getString('pane.items.menu.reindexItem');
    558 						reindexButton.setAttribute('tooltiptext', str);
    559 						
    560 						var show = false;
    561 						if (this.editable) {
    562 							show = yield Zotero.Fulltext.canReindex(this.item)
    563 								.tap(() => Zotero.Promise.check(this.item));
    564 						}
    565 						
    566 						if (show) {
    567 							reindexButton.setAttribute('hidden', false);
    568 						}
    569 						else {
    570 							reindexButton.setAttribute('hidden', true);
    571 						}
    572 					}, this);
    573 				]]></body>
    574 			</method>
    575 			
    576 			
    577 			<method name="_id">
    578 				<parameter name="id"/>
    579 				<body>
    580 				<![CDATA[
    581 					return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
    582 				]]>
    583 				</body>
    584 			</method>
    585 		</implementation>
    586 		
    587 		<content>
    588 			<vbox id="attachment-box" flex="1" orient="vertical"
    589 				 	xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    590 				<vbox id="metadata">
    591 					<label id="title"/>
    592 					<label id="url" crop="end"
    593 						ondragstart="var dt = event.dataTransfer; dt.setData('text/x-moz-url', this.value); dt.setData('text/uri-list', this.value); dt.setData('text/plain', this.value);"/>
    594 					<grid>
    595 						<columns>
    596 							<column/>
    597 							<column flex="1"/>
    598 						</columns>
    599 						<rows>
    600 							<row id="fileNameRow">
    601 								<label id="fileName-label"/>
    602 								<label id="fileName" crop="end"/>
    603 							</row>
    604 							<row id="accessedRow">
    605 								<label id="accessed-label"/>
    606 								<label id="accessed"/>
    607 							</row>
    608 							<row id="pagesRow">
    609 								<label id="pages-label"/>
    610 								<label id="pages"/>
    611 							</row>
    612 							<row id="dateModifiedRow" hidden="true">
    613 								<label id="dateModified-label"/>
    614 								<label id="dateModified"/>
    615 							</row>
    616 							<row id="indexStatusRow">
    617 								<label id="index-status-label"/>
    618 								<hbox>
    619 									<label id="index-status"/>
    620 									<image id="reindex" onclick="this.hidden = true; setTimeout(function () { ZoteroPane_Local.reindexItem(); }, 50)"/>
    621 								</hbox>
    622 							</row>
    623 						</rows>
    624 					</grid>
    625 				</vbox>
    626 				
    627 				<zoteronoteeditor id="attachment-note-editor" notitle="1" flex="1"/>
    628 				
    629 				<button id="select-button" hidden="true"/>
    630 			</vbox>
    631 		</content>
    632 	</binding>
    633 </bindings>