collectionTreeRow.js (10865B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2015 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 "use strict"; 27 28 Zotero.CollectionTreeRow = function (collectionTreeView, type, ref, level, isOpen) { 29 this.view = collectionTreeView; 30 this.type = type; 31 this.ref = ref; 32 this.level = level || 0 33 this.isOpen = isOpen || false; 34 this.onUnload = null; 35 } 36 37 38 Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () { 39 switch (this.type) { 40 case 'library': 41 case 'group': 42 case 'feed': 43 return 'L' + this.ref.libraryID; 44 45 case 'collection': 46 return 'C' + this.ref.id; 47 48 case 'search': 49 return 'S' + this.ref.id; 50 51 case 'duplicates': 52 return 'D' + this.ref.libraryID; 53 54 case 'unfiled': 55 return 'U' + this.ref.libraryID; 56 57 case 'publications': 58 return 'P' + this.ref.libraryID; 59 60 case 'trash': 61 return 'T' + this.ref.libraryID; 62 63 case 'header': 64 switch (this.ref.id) { 65 case 'group-libraries-header': 66 return "HG"; 67 case 'feed-libraries-header': 68 return "HF"; 69 } 70 break; 71 } 72 73 return ''; 74 }); 75 76 Zotero.CollectionTreeRow.prototype.isLibrary = function (includeGlobal) 77 { 78 if (includeGlobal) { 79 var global = ['library', 'group', 'feed']; 80 return global.indexOf(this.type) != -1; 81 } 82 return this.type == 'library'; 83 } 84 85 Zotero.CollectionTreeRow.prototype.isCollection = function() 86 { 87 return this.type == 'collection'; 88 } 89 90 Zotero.CollectionTreeRow.prototype.isSearch = function() 91 { 92 return this.type == 'search'; 93 } 94 95 Zotero.CollectionTreeRow.prototype.isDuplicates = function () { 96 return this.type == 'duplicates'; 97 } 98 99 Zotero.CollectionTreeRow.prototype.isUnfiled = function () { 100 return this.type == 'unfiled'; 101 } 102 103 Zotero.CollectionTreeRow.prototype.isTrash = function() 104 { 105 return this.type == 'trash'; 106 } 107 108 Zotero.CollectionTreeRow.prototype.isHeader = function () { 109 return this.type == 'header'; 110 } 111 112 Zotero.CollectionTreeRow.prototype.isPublications = function() { 113 return this.type == 'publications'; 114 } 115 116 Zotero.CollectionTreeRow.prototype.isGroup = function() { 117 return this.type == 'group'; 118 } 119 120 Zotero.CollectionTreeRow.prototype.isFeed = function() { 121 return this.type == 'feed'; 122 } 123 124 Zotero.CollectionTreeRow.prototype.isSeparator = function () { 125 return this.type == 'separator'; 126 } 127 128 Zotero.CollectionTreeRow.prototype.isBucket = function() 129 { 130 return this.type == 'bucket'; 131 } 132 133 Zotero.CollectionTreeRow.prototype.isShare = function() 134 { 135 return this.type == 'share'; 136 } 137 138 139 140 // Special 141 Zotero.CollectionTreeRow.prototype.isWithinGroup = function () { 142 return this.ref && !this.isHeader() 143 && Zotero.Libraries.get(this.ref.libraryID).libraryType == 'group'; 144 } 145 146 Zotero.CollectionTreeRow.prototype.isWithinEditableGroup = function () { 147 if (!this.isWithinGroup()) { 148 return false; 149 } 150 var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.ref.libraryID); 151 return Zotero.Groups.get(groupID).editable; 152 } 153 154 Zotero.CollectionTreeRow.prototype.__defineGetter__('editable', function () { 155 if (this.isTrash() || this.isShare() || this.isBucket()) { 156 return false; 157 } 158 if (this.isGroup() || this.isFeed()) { 159 return this.ref.editable; 160 } 161 if (!this.isWithinGroup() || this.isPublications()) { 162 return true; 163 } 164 var libraryID = this.ref.libraryID; 165 if (this.isCollection() || this.isSearch() || this.isDuplicates() || this.isUnfiled()) { 166 var type = Zotero.Libraries.get(libraryID).libraryType; 167 if (type == 'group') { 168 var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID); 169 var group = Zotero.Groups.get(groupID); 170 return group.editable; 171 } 172 throw ("Unknown library type '" + type + "' in Zotero.CollectionTreeRow.editable"); 173 } 174 return false; 175 }); 176 177 Zotero.CollectionTreeRow.prototype.__defineGetter__('filesEditable', function () { 178 if (this.isTrash() || this.isShare() || this.isFeed()) { 179 return false; 180 } 181 if (!this.isWithinGroup() || this.isPublications()) { 182 return true; 183 } 184 var libraryID = this.ref.libraryID; 185 if (this.isGroup()) { 186 return this.ref.filesEditable; 187 } 188 if (this.isCollection() || this.isSearch() || this.isDuplicates() || this.isUnfiled()) { 189 var type = Zotero.Libraries.get(libraryID).libraryType; 190 if (type == 'group') { 191 var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID); 192 var group = Zotero.Groups.get(groupID); 193 return group.filesEditable; 194 } 195 throw ("Unknown library type '" + type + "' in Zotero.CollectionTreeRow.filesEditable"); 196 } 197 return false; 198 }); 199 200 201 Zotero.CollectionTreeRow.visibilityGroups = {'feed': 'feed'}; 202 203 204 Zotero.CollectionTreeRow.prototype.__defineGetter__('visibilityGroup', function() { 205 return Zotero.CollectionTreeRow.visibilityGroups[this.type] || 'default'; 206 }); 207 208 209 Zotero.CollectionTreeRow.prototype.getName = function() 210 { 211 switch (this.type) { 212 case 'library': 213 return Zotero.getString('pane.collections.library'); 214 215 case 'publications': 216 return Zotero.getString('pane.collections.publications'); 217 218 case 'trash': 219 return Zotero.getString('pane.collections.trash'); 220 221 case 'header': 222 return this.ref.label; 223 224 case 'separator': 225 return ""; 226 227 default: 228 return this.ref.name; 229 } 230 } 231 232 Zotero.CollectionTreeRow.prototype.getItems = Zotero.Promise.coroutine(function* () 233 { 234 switch (this.type) { 235 // Fake results if this is a shared library 236 case 'share': 237 return this.ref.getAll(); 238 239 case 'bucket': 240 return this.ref.getItems(); 241 } 242 243 var ids = yield this.getSearchResults(); 244 245 // Filter out items that exist in the items table (where search results come from) but that haven't 246 // yet been registered. This helps prevent unloaded-data crashes when switching collections while 247 // items are being added (e.g., during sync). 248 var len = ids.length; 249 ids = ids.filter(id => Zotero.Items.getLibraryAndKeyFromID(id)); 250 if (len > ids.length) { 251 let diff = len - ids.length; 252 Zotero.debug(`Not showing ${diff} unloaded item${diff != 1 ? 's' : ''}`); 253 } 254 255 if (!ids.length) { 256 return [] 257 } 258 259 return Zotero.Items.getAsync(ids); 260 }); 261 262 Zotero.CollectionTreeRow.prototype.getSearchResults = Zotero.Promise.coroutine(function* (asTempTable) { 263 if (Zotero.CollectionTreeCache.lastTreeRow && Zotero.CollectionTreeCache.lastTreeRow.id !== this.id) { 264 Zotero.CollectionTreeCache.clear(); 265 } 266 267 if(!Zotero.CollectionTreeCache.lastResults) { 268 var s = yield this.getSearchObject(); 269 Zotero.CollectionTreeCache.lastResults = yield s.search(); 270 Zotero.CollectionTreeCache.lastTreeRow = this; 271 } 272 273 if(asTempTable) { 274 if(!Zotero.CollectionTreeCache.lastTempTable) { 275 Zotero.CollectionTreeCache.lastTempTable = yield Zotero.Search.idsToTempTable(Zotero.CollectionTreeCache.lastResults); 276 } 277 return Zotero.CollectionTreeCache.lastTempTable; 278 } 279 return Zotero.CollectionTreeCache.lastResults; 280 }); 281 282 /* 283 * Returns the search object for the currently display 284 * 285 * This accounts for the collection, saved search, quicksearch, tags, etc. 286 */ 287 Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(function* () { 288 if (Zotero.CollectionTreeCache.lastTreeRow && Zotero.CollectionTreeCache.lastTreeRow.id !== this.id) { 289 Zotero.CollectionTreeCache.clear(); 290 } 291 292 if(Zotero.CollectionTreeCache.lastSearch) { 293 return Zotero.CollectionTreeCache.lastSearch; 294 } 295 296 var includeScopeChildren = false; 297 298 // Create/load the inner search 299 if (this.ref instanceof Zotero.Search) { 300 var s = this.ref; 301 } 302 else if (this.isDuplicates()) { 303 var s = yield this.ref.getSearchObject(); 304 let tmpTable; 305 for (let id in s.conditions) { 306 let c = s.conditions[id]; 307 if (c.condition == 'tempTable') { 308 tmpTable = c.value; 309 break; 310 } 311 } 312 // Called by ItemTreeView::unregister() 313 this.onUnload = async function () { 314 await Zotero.DB.queryAsync(`DROP TABLE IF EXISTS ${tmpTable}`); 315 }; 316 } 317 else { 318 var s = new Zotero.Search(); 319 s.addCondition('libraryID', 'is', this.ref.libraryID); 320 // Library root 321 if (this.isLibrary(true)) { 322 s.addCondition('noChildren', 'true'); 323 includeScopeChildren = true; 324 } 325 else if (this.isCollection()) { 326 s.addCondition('noChildren', 'true'); 327 s.addCondition('collectionID', 'is', this.ref.id); 328 if (Zotero.Prefs.get('recursiveCollections')) { 329 s.addCondition('recursive', 'true'); 330 } 331 includeScopeChildren = true; 332 } 333 else if (this.isPublications()) { 334 s.addCondition('publications', 'true'); 335 } 336 else if (this.isTrash()) { 337 s.addCondition('deleted', 'true'); 338 } 339 else { 340 throw new Error('Invalid search mode ' + this.type); 341 } 342 } 343 344 // Create the outer (filter) search 345 var s2 = new Zotero.Search(); 346 s2.addCondition('libraryID', 'is', this.ref.libraryID); 347 348 if (this.isTrash()) { 349 s2.addCondition('deleted', 'true'); 350 } 351 s2.setScope(s, includeScopeChildren); 352 353 if (this.searchText) { 354 var cond = 'quicksearch-' + Zotero.Prefs.get('search.quicksearch-mode'); 355 s2.addCondition(cond, 'contains', this.searchText); 356 } 357 358 if (this.tags){ 359 for (let tag of this.tags) { 360 s2.addCondition('tag', 'is', tag); 361 } 362 } 363 364 Zotero.CollectionTreeCache.lastTreeRow = this; 365 Zotero.CollectionTreeCache.lastSearch = s2; 366 return s2; 367 }); 368 369 370 /** 371 * Returns all the tags used by items in the current view 372 * 373 * @return {Promise<Object[]>} 374 */ 375 Zotero.CollectionTreeRow.prototype.getChildTags = Zotero.Promise.coroutine(function* () { 376 switch (this.type) { 377 // TODO: implement? 378 case 'share': 379 return []; 380 381 case 'bucket': 382 return []; 383 } 384 var results = yield this.getSearchResults(true); 385 return Zotero.Tags.getAllWithinSearchResults(results); 386 }); 387 388 389 Zotero.CollectionTreeRow.prototype.setSearch = function (searchText) { 390 Zotero.CollectionTreeCache.clear(); 391 this.searchText = searchText; 392 } 393 394 Zotero.CollectionTreeRow.prototype.setTags = function (tags) { 395 Zotero.CollectionTreeCache.clear(); 396 this.tags = tags; 397 } 398 399 /* 400 * Returns TRUE if saved search, quicksearch or tag filter 401 */ 402 Zotero.CollectionTreeRow.prototype.isSearchMode = function() { 403 switch (this.type) { 404 case 'search': 405 case 'publications': 406 case 'trash': 407 return true; 408 } 409 410 // Quicksearch 411 if (this.searchText != '') { 412 return true; 413 } 414 415 // Tag filter 416 if (this.tags && this.tags.size) { 417 return true; 418 } 419 }