itemPane.js (11988B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 var ZoteroItemPane = new function() { 27 var _lastItem, _itemBox, _notesLabel, _notesButton, _notesList, _tagsBox, _relatedBox; 28 var _selectedNoteID; 29 var _translationTarget; 30 var _noteIDs; 31 32 this.onLoad = function () { 33 if (!Zotero) { 34 return; 35 } 36 37 // Not in item pane, so skip the introductions 38 // 39 // DEBUG: remove? 40 if (!document.getElementById('zotero-view-tabbox')) { 41 return; 42 } 43 44 _itemBox = document.getElementById('zotero-editpane-item-box'); 45 _notesLabel = document.getElementById('zotero-editpane-notes-label'); 46 _notesButton = document.getElementById('zotero-editpane-notes-add'); 47 _notesList = document.getElementById('zotero-editpane-dynamic-notes'); 48 _tagsBox = document.getElementById('zotero-editpane-tags'); 49 _relatedBox = document.getElementById('zotero-editpane-related'); 50 51 this._unregisterID = Zotero.Notifier.registerObserver(this, ['item'], 'itemPane'); 52 } 53 54 55 this.onUnload = function () { 56 Zotero.Notifier.unregisterObserver(this._unregisterID); 57 }, 58 59 60 /* 61 * Load a top-level item 62 */ 63 this.viewItem = Zotero.Promise.coroutine(function* (item, mode, index) { 64 if (!index) { 65 index = 0; 66 } 67 68 Zotero.debug('Viewing item in pane ' + index); 69 70 switch (index) { 71 case 0: 72 var box = _itemBox; 73 break; 74 75 case 2: 76 var box = _tagsBox; 77 break; 78 79 case 3: 80 var box = _relatedBox; 81 break; 82 } 83 84 // Force blur() when clicking off a textbox to another item in middle 85 // pane, since for some reason it's not being called automatically 86 if (_lastItem && _lastItem != item) { 87 switch (index) { 88 case 0: 89 case 2: 90 yield box.blurOpenField(); 91 // DEBUG: Currently broken 92 //box.scrollToTop(); 93 break; 94 } 95 } 96 97 _lastItem = item; 98 99 var viewBox = document.getElementById('zotero-view-item'); 100 viewBox.classList.remove('no-tabs'); 101 102 if (index == 0) { 103 document.getElementById('zotero-editpane-tabs').setAttribute('hidden', item.isFeedItem); 104 105 if (item.isFeedItem) { 106 viewBox.classList.add('no-tabs'); 107 108 let lastTranslationTarget = Zotero.Prefs.get('feeds.lastTranslationTarget'); 109 if (lastTranslationTarget) { 110 let id = parseInt(lastTranslationTarget.substr(1)); 111 if (lastTranslationTarget[0] == "L") { 112 _translationTarget = Zotero.Libraries.get(id); 113 } 114 else if (lastTranslationTarget[0] == "C") { 115 _translationTarget = Zotero.Collections.get(id); 116 } 117 } 118 if (!_translationTarget) { 119 _translationTarget = Zotero.Libraries.userLibrary; 120 } 121 this.setTranslateButton(); 122 } 123 } 124 else if (index == 1) { 125 var editable = ZoteroPane_Local.canEdit(); 126 _notesButton.hidden = !editable; 127 128 while(_notesList.hasChildNodes()) { 129 _notesList.removeChild(_notesList.firstChild); 130 } 131 132 _noteIDs = new Set(); 133 let notes = yield Zotero.Items.getAsync(item.getNotes()); 134 if (notes.length) { 135 for (var i = 0; i < notes.length; i++) { 136 let note = notes[i]; 137 let id = notes[i].id; 138 139 var icon = document.createElement('image'); 140 icon.className = "zotero-box-icon"; 141 icon.setAttribute('src', `chrome://zotero/skin/treeitem-note${Zotero.hiDPISuffix}.png`); 142 143 var label = document.createElement('label'); 144 label.className = "zotero-box-label"; 145 var title = note.getNoteTitle(); 146 title = title ? title : Zotero.getString('pane.item.notes.untitled'); 147 label.setAttribute('value', title); 148 label.setAttribute('flex','1'); //so that the long names will flex smaller 149 label.setAttribute('crop','end'); 150 151 var box = document.createElement('box'); 152 box.setAttribute('class','zotero-clicky'); 153 box.addEventListener('click', function () { ZoteroPane_Local.selectItem(id); }); 154 box.appendChild(icon); 155 box.appendChild(label); 156 157 if (editable) { 158 var removeButton = document.createElement('label'); 159 removeButton.setAttribute("value","-"); 160 removeButton.setAttribute("class","zotero-clicky zotero-clicky-minus"); 161 removeButton.addEventListener('click', function () { ZoteroItemPane.removeNote(id); }); 162 } 163 164 var row = document.createElement('row'); 165 row.appendChild(box); 166 if (editable) { 167 row.appendChild(removeButton); 168 } 169 170 _notesList.appendChild(row); 171 _noteIDs.add(id); 172 } 173 } 174 175 _updateNoteCount(); 176 return; 177 } 178 179 if (mode) { 180 box.mode = mode; 181 182 if (box.mode == 'view') { 183 box.hideEmptyFields = true; 184 } 185 } 186 else { 187 box.mode = 'edit'; 188 } 189 190 box.item = item; 191 }); 192 193 194 this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) { 195 var viewBox = document.getElementById('zotero-view-item'); 196 // If notes pane is selected, refresh it if any of the notes change or are deleted 197 if (viewBox.selectedIndex == 1 && (action == 'modify' || action == 'delete')) { 198 let refresh = false; 199 if (ids.some(id => _noteIDs.has(id))) { 200 refresh = true; 201 } 202 if (refresh) { 203 yield this.viewItem(_lastItem, null, 1); 204 } 205 } 206 }); 207 208 209 this.blurOpenField = Zotero.Promise.coroutine(function* () { 210 var tabBox = document.getElementById('zotero-view-tabbox'); 211 switch (tabBox.selectedIndex) { 212 case 0: 213 var box = _itemBox; 214 break; 215 216 case 2: 217 var box = _tagsBox; 218 break; 219 } 220 if (box) { 221 yield box.blurOpenField(); 222 } 223 }); 224 225 226 this.onNoteSelected = function (item, editable) { 227 _selectedNoteID = item.id; 228 229 // If an external note window is open for this item, don't show the editor 230 if (ZoteroPane.findNoteWindow(item.id)) { 231 this.showNoteWindowMessage(); 232 return; 233 } 234 235 var noteEditor = document.getElementById('zotero-note-editor'); 236 237 // If loading new or different note, disable undo while we repopulate the text field 238 // so Undo doesn't end up clearing the field. This also ensures that Undo doesn't 239 // undo content from another note into the current one. 240 var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false; 241 242 noteEditor.mode = editable ? 'edit' : 'view'; 243 noteEditor.parent = null; 244 noteEditor.item = item; 245 246 if (clearUndo) { 247 noteEditor.clearUndo(); 248 } 249 250 document.getElementById('zotero-view-note-button').hidden = !editable; 251 document.getElementById('zotero-item-pane-content').selectedIndex = 2; 252 }; 253 254 255 this.showNoteWindowMessage = function () { 256 ZoteroPane.setItemPaneMessage(Zotero.getString('pane.item.notes.editingInWindow')); 257 }; 258 259 260 /** 261 * Select the parent item and open the note editor 262 */ 263 this.openNoteWindow = async function () { 264 var selectedNote = Zotero.Items.get(_selectedNoteID); 265 266 // We don't want to show the note in two places, since it causes unnecessary UI updates 267 // and can result in weird bugs where note content gets lost. 268 // 269 // If this is a child note, select the parent 270 if (selectedNote.parentID) { 271 await ZoteroPane.selectItem(selectedNote.parentID); 272 } 273 // Otherwise, hide note and replace with a message that we're editing externally 274 else { 275 this.showNoteWindowMessage(); 276 } 277 ZoteroPane.openNoteWindow(selectedNote.id); 278 }; 279 280 281 this.addNote = function (popup) { 282 ZoteroPane_Local.newNote(popup, _lastItem.key); 283 } 284 285 286 this.removeNote = function (id) { 287 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 288 .getService(Components.interfaces.nsIPromptService); 289 if (ps.confirm(null, '', Zotero.getString('pane.item.notes.delete.confirm'))) { 290 Zotero.Items.trashTx(id); 291 } 292 } 293 294 295 this.translateSelectedItems = Zotero.Promise.coroutine(function* () { 296 var collectionID = _translationTarget.objectType == 'collection' ? _translationTarget.id : undefined; 297 var items = ZoteroPane_Local.itemsView.getSelectedItems(); 298 for (let item of items) { 299 yield item.translate(_translationTarget.libraryID, collectionID); 300 } 301 }); 302 303 304 this.buildTranslateSelectContextMenu = function (event) { 305 var menu = document.getElementById('zotero-item-addTo-menu'); 306 // Don't trigger rebuilding on nested popupmenu open/close 307 if (event.target != menu) { 308 return; 309 } 310 // Clear previous items 311 while (menu.firstChild) { 312 menu.removeChild(menu.firstChild); 313 } 314 315 let target = Zotero.Prefs.get('feeds.lastTranslationTarget'); 316 if (!target) { 317 target = "L" + Zotero.Libraries.userLibraryID; 318 } 319 320 var libraries = Zotero.Libraries.getAll(); 321 for (let library of libraries) { 322 if (!library.editable || library.libraryType == 'publications') { 323 continue; 324 } 325 Zotero.Utilities.Internal.createMenuForTarget( 326 library, 327 menu, 328 target, 329 function(event, libraryOrCollection) { 330 if (event.target.tagName == 'menu') { 331 Zotero.Promise.coroutine(function* () { 332 // Simulate menuitem flash on OS X 333 if (Zotero.isMac) { 334 event.target.setAttribute('_moz-menuactive', false); 335 yield Zotero.Promise.delay(50); 336 event.target.setAttribute('_moz-menuactive', true); 337 yield Zotero.Promise.delay(50); 338 event.target.setAttribute('_moz-menuactive', false); 339 yield Zotero.Promise.delay(50); 340 event.target.setAttribute('_moz-menuactive', true); 341 } 342 menu.hidePopup(); 343 344 ZoteroItemPane.setTranslationTarget(libraryOrCollection); 345 event.stopPropagation(); 346 })(); 347 } 348 else { 349 ZoteroItemPane.setTranslationTarget(libraryOrCollection); 350 event.stopPropagation(); 351 } 352 } 353 ); 354 } 355 }; 356 357 358 this.setTranslateButton = function() { 359 var label = Zotero.getString('pane.item.addTo', _translationTarget.name); 360 var elem = document.getElementById('zotero-feed-item-addTo-button'); 361 elem.setAttribute('label', label); 362 363 var key = Zotero.Keys.getKeyForCommand('saveToZotero'); 364 365 var tooltip = label 366 + (Zotero.rtl ? ' \u202B' : ' ') + '(' 367 + (Zotero.isMac ? '⇧⌘' : Zotero.getString('general.keys.ctrlShift')) 368 + key + ')'; 369 elem.setAttribute('tooltiptext', tooltip); 370 elem.setAttribute('image', _translationTarget.treeViewImage); 371 }; 372 373 374 this.setTranslationTarget = function(translationTarget) { 375 _translationTarget = translationTarget; 376 Zotero.Prefs.set('feeds.lastTranslationTarget', translationTarget.treeViewID); 377 ZoteroItemPane.setTranslateButton(); 378 }; 379 380 381 this.setReadLabel = function (isRead) { 382 var elem = document.getElementById('zotero-feed-item-toggleRead-button'); 383 var label = Zotero.getString('pane.item.' + (isRead ? 'markAsUnread' : 'markAsRead')); 384 elem.setAttribute('label', label); 385 386 var key = Zotero.Keys.getKeyForCommand('toggleRead'); 387 var tooltip = label + (Zotero.rtl ? ' \u202B' : ' ') + '(' + key + ')' 388 elem.setAttribute('tooltiptext', tooltip); 389 }; 390 391 392 function _updateNoteCount() { 393 var c = _notesList.childNodes.length; 394 395 var str = 'pane.item.notes.count.'; 396 switch (c){ 397 case 0: 398 str += 'zero'; 399 break; 400 case 1: 401 str += 'singular'; 402 break; 403 default: 404 str += 'plural'; 405 break; 406 } 407 408 _notesLabel.value = Zotero.getString(str, [c]); 409 } 410 } 411 412 addEventListener("load", function(e) { ZoteroItemPane.onLoad(e); }, false); 413 addEventListener("unload", function(e) { ZoteroItemPane.onUnload(e); }, false);