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>