Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 Polymer({ | 5 Polymer({ |
| 6 is: 'history-list', | 6 is: 'history-list', |
| 7 | 7 |
| 8 behaviors: [HistoryListBehavior], | |
| 9 | |
| 10 properties: { | 8 properties: { |
| 11 // The search term for the current query. Set when the query returns. | 9 // The search term for the current query. Set when the query returns. |
| 12 searchedTerm: { | 10 searchedTerm: { |
| 13 type: String, | 11 type: String, |
| 14 value: '', | 12 value: '', |
| 15 }, | 13 }, |
| 16 | 14 |
| 17 resultLoadingDisabled_: { | 15 resultLoadingDisabled_: { |
| 18 type: Boolean, | 16 type: Boolean, |
| 19 value: false, | 17 value: false, |
| 20 }, | 18 }, |
| 21 | 19 |
| 20 /** | |
| 21 * Indexes into historyData_ of selected items. | |
| 22 * @type {!Set<number>} | |
| 23 */ | |
| 24 selectedItems: { | |
| 25 type: Object, | |
| 26 value: /** @return {!Set<string>} */ function() { | |
| 27 return new Set(); | |
| 28 }, | |
| 29 }, | |
| 30 | |
| 22 // An array of history entries in reverse chronological order. | 31 // An array of history entries in reverse chronological order. |
| 23 historyData_: Array, | 32 historyData_: Array, |
| 24 | 33 |
| 25 lastFocused_: Object, | 34 lastFocused_: Object, |
| 26 | 35 |
| 27 querying: Boolean, | 36 lastSelectedIndex: Number, |
| 37 | |
| 38 /** @type {!QueryState} */ | |
| 39 queryState: Object, | |
| 40 | |
| 41 /** | |
| 42 * @private {?{ | |
| 43 * index: number, | |
| 44 * item: !HistoryEntry, | |
| 45 * path: string, | |
| 46 * target: !HTMLElement | |
| 47 * }} | |
| 48 */ | |
| 49 actionMenuModel_: Object, | |
| 28 }, | 50 }, |
| 29 | 51 |
| 30 listeners: { | 52 listeners: { |
| 53 'history-checkbox-select': 'onItemSelected_', | |
| 54 'open-menu': 'onOpenMenu_', | |
| 31 'remove-bookmark-stars': 'removeBookmarkStars_', | 55 'remove-bookmark-stars': 'removeBookmarkStars_', |
| 32 'open-menu': 'onOpenMenu_', | |
| 33 }, | 56 }, |
| 34 | 57 |
| 35 /** @override */ | 58 /** @override */ |
| 36 attached: function() { | 59 attached: function() { |
| 37 // It is possible (eg, when middle clicking the reload button) for all other | 60 // It is possible (eg, when middle clicking the reload button) for all other |
| 38 // resize events to fire before the list is attached and can be measured. | 61 // resize events to fire before the list is attached and can be measured. |
| 39 // Adding another resize here ensures it will get sized correctly. | 62 // Adding another resize here ensures it will get sized correctly. |
| 40 /** @type {IronListElement} */ (this.$['infinite-list']).notifyResize(); | 63 /** @type {IronListElement} */ (this.$['infinite-list']).notifyResize(); |
| 41 this.$['infinite-list'].scrollTarget = this; | 64 this.$['infinite-list'].scrollTarget = this; |
| 42 this.$['scroll-threshold'].scrollTarget = this; | 65 this.$['scroll-threshold'].scrollTarget = this; |
| 43 }, | 66 }, |
| 44 | 67 |
| 68 ///////////////////////////////////////////////////////////////////////////// | |
| 69 // Public methods: | |
| 70 | |
| 45 /** | 71 /** |
| 46 * Remove bookmark star for history items with matching URLs. | 72 * @param {HistoryQuery} info An object containing information about the |
| 47 * @param {{detail: !string}} e | 73 * query. |
| 48 * @private | 74 * @param {!Array<!HistoryEntry>} results A list of results. |
| 49 */ | 75 */ |
| 50 removeBookmarkStars_: function(e) { | 76 historyResult: function(info, results) { |
| 51 var url = e.detail; | 77 this.initializeResults_(info, results); |
| 78 this.closeMenu_(); | |
| 52 | 79 |
| 53 if (this.historyData_ === undefined) | 80 if (info.term && !this.queryState.incremental) { |
| 54 return; | 81 Polymer.IronA11yAnnouncer.requestAvailability(); |
| 82 this.fire('iron-announce', { | |
| 83 text: | |
| 84 md_history.HistoryItem.searchResultsTitle(results.length, info.term) | |
| 85 }); | |
| 86 } | |
| 55 | 87 |
| 56 for (var i = 0; i < this.historyData_.length; i++) { | 88 this.addNewResults(results, this.queryState.incremental, info.finished); |
| 57 if (this.historyData_[i].url == url) | |
| 58 this.set('historyData_.' + i + '.starred', false); | |
| 59 } | |
| 60 }, | 89 }, |
| 61 | 90 |
| 62 /** | 91 /** |
| 63 * Adds the newly updated history results into historyData_. Adds new fields | 92 * Adds the newly updated history results into historyData_. Adds new fields |
| 64 * for each result. | 93 * for each result. |
| 65 * @param {!Array<!HistoryEntry>} historyResults The new history results. | 94 * @param {!Array<!HistoryEntry>} historyResults The new history results. |
| 66 * @param {boolean} incremental Whether the result is from loading more | 95 * @param {boolean} incremental Whether the result is from loading more |
| 67 * history, or a new search/list reload. | 96 * history, or a new search/list reload. |
| 68 * @param {boolean} finished True if there are no more results available and | 97 * @param {boolean} finished True if there are no more results available and |
| 69 * result loading should be disabled. | 98 * result loading should be disabled. |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 87 this.push.apply(this, results); | 116 this.push.apply(this, results); |
| 88 } else { | 117 } else { |
| 89 // The first time we receive data, use set() to ensure the iron-list is | 118 // The first time we receive data, use set() to ensure the iron-list is |
| 90 // initialized correctly. | 119 // initialized correctly. |
| 91 this.set('historyData_', results); | 120 this.set('historyData_', results); |
| 92 } | 121 } |
| 93 | 122 |
| 94 this.resultLoadingDisabled_ = finished; | 123 this.resultLoadingDisabled_ = finished; |
| 95 }, | 124 }, |
| 96 | 125 |
| 126 historyDeleted: function() { | |
| 127 // Do not reload the list when there are items checked. | |
| 128 if (this.getSelectedItemCount() > 0) | |
| 129 return; | |
| 130 | |
| 131 // Reload the list with current search state. | |
| 132 this.fire('query-history', false); | |
| 133 }, | |
| 134 | |
| 135 /** | |
| 136 * Set the selection status for an item at a particular index. | |
| 137 * @param {number} index | |
| 138 * @param {boolean} selected | |
| 139 */ | |
| 140 changeSelection: function(index, selected) { | |
| 141 this.set('historyData_.' + index + '.selected', selected); | |
|
calamity
2017/02/21 02:40:06
nit: Can be private.
tsergeant
2017/02/21 23:24:13
Done.
| |
| 142 if (selected) | |
| 143 this.selectedItems.add(index); | |
| 144 else | |
| 145 this.selectedItems.delete(index); | |
| 146 }, | |
| 147 | |
| 148 /** | |
| 149 * Deselect each item in |selectedItems|. | |
| 150 */ | |
| 151 unselectAllItems: function() { | |
| 152 this.selectedItems.forEach(function(index) { | |
| 153 this.changeSelection(index, false); | |
| 154 }.bind(this)); | |
| 155 | |
| 156 assert(this.selectedItems.size == 0); | |
| 157 }, | |
| 158 | |
| 159 /** @return {number} */ | |
| 160 getSelectedItemCount: function() { | |
| 161 return this.selectedItems.size; | |
| 162 }, | |
| 163 | |
| 164 /** | |
| 165 * Delete all the currently selected history items. Will prompt the user with | |
| 166 * a dialog to confirm that the deletion should be performed. | |
| 167 */ | |
| 168 deleteSelectedWithPrompt: function() { | |
| 169 if (!this.canDeleteHistory_()) | |
| 170 return; | |
| 171 | |
| 172 var browserService = md_history.BrowserService.getInstance(); | |
| 173 browserService.recordAction('RemoveSelected'); | |
| 174 if (this.queryState.searchTerm != '') | |
| 175 browserService.recordAction('SearchResultRemove'); | |
| 176 this.$.dialog.get().showModal(); | |
| 177 | |
| 178 // TODO(dbeam): remove focus flicker caused by showModal() + focus(). | |
| 179 this.$$('.action-button').focus(); | |
| 180 }, | |
| 181 | |
| 182 /** | |
| 183 * Performs a request to the backend to delete all selected items. If | |
| 184 * successful, removes them from the view. Does not prompt the user before | |
| 185 * deleting -- see deleteSelectedWithPrompt for a version of this method which | |
| 186 * does prompt. | |
| 187 */ | |
| 188 deleteSelected: function() { | |
|
calamity
2017/02/21 02:40:06
nit: private-able.
tsergeant
2017/02/21 23:24:13
Done.
| |
| 189 var toBeRemoved = | |
| 190 Array.from(this.selectedItems.values()).map(function(index) { | |
| 191 return this.get('historyData_.' + index); | |
| 192 }.bind(this)); | |
| 193 | |
| 194 md_history.BrowserService.getInstance() | |
| 195 .deleteItems(toBeRemoved) | |
| 196 .then(function(items) { | |
| 197 this.removeItemsByIndex(Array.from(this.selectedItems)); | |
| 198 this.fire('unselect-all'); | |
| 199 }.bind(this)); | |
| 200 }, | |
| 201 | |
| 202 /** | |
| 203 * Remove all |indices| from the history list. Uses notifySplices to send a | |
| 204 * single large notification to Polymer, rather than many small notifications, | |
| 205 * which greatly improves performance. | |
| 206 * @param {!Array<number>} indices | |
| 207 * @private | |
| 208 */ | |
| 209 removeItemsByIndex: function(indices) { | |
|
calamity
2017/02/21 02:40:06
nit: private-able.
tsergeant
2017/02/21 23:24:12
Done.
| |
| 210 var splices = []; | |
| 211 indices.sort(function(a, b) { | |
| 212 // Sort in reverse numerical order. | |
| 213 return b - a; | |
| 214 }); | |
| 215 indices.forEach(function(index) { | |
| 216 var item = this.historyData_.splice(index, 1); | |
| 217 splices.push({ | |
| 218 index: index, | |
| 219 removed: [item], | |
| 220 addedCount: 0, | |
| 221 object: this.historyData_, | |
| 222 type: 'splice' | |
| 223 }); | |
| 224 }.bind(this)); | |
| 225 this.notifySplices('historyData_', splices); | |
| 226 }, | |
| 227 | |
| 228 ///////////////////////////////////////////////////////////////////////////// | |
| 229 // Event listeners: | |
| 230 | |
| 231 /** @private */ | |
| 232 onDialogConfirmTap_: function() { | |
| 233 md_history.BrowserService.getInstance().recordAction( | |
| 234 'ConfirmRemoveSelected'); | |
| 235 | |
| 236 this.deleteSelected(); | |
| 237 var dialog = assert(this.$.dialog.getIfExists()); | |
| 238 dialog.close(); | |
| 239 }, | |
| 240 | |
| 241 /** @private */ | |
| 242 onDialogCancelTap_: function() { | |
| 243 md_history.BrowserService.getInstance().recordAction( | |
| 244 'CancelRemoveSelected'); | |
| 245 | |
| 246 var dialog = assert(this.$.dialog.getIfExists()); | |
| 247 dialog.close(); | |
| 248 }, | |
| 249 | |
| 250 /** | |
| 251 * Remove bookmark star for history items with matching URLs. | |
| 252 * @param {{detail: !string}} e | |
| 253 * @private | |
| 254 */ | |
| 255 removeBookmarkStars_: function(e) { | |
| 256 var url = e.detail; | |
| 257 | |
| 258 if (this.historyData_ === undefined) | |
| 259 return; | |
| 260 | |
| 261 for (var i = 0; i < this.historyData_.length; i++) { | |
| 262 if (this.historyData_[i].url == url) | |
| 263 this.set('historyData_.' + i + '.starred', false); | |
| 264 } | |
| 265 }, | |
| 266 | |
| 97 /** | 267 /** |
| 98 * Called when the page is scrolled to near the bottom of the list. | 268 * Called when the page is scrolled to near the bottom of the list. |
| 99 * @private | 269 * @private |
| 100 */ | 270 */ |
| 101 loadMoreData_: function() { | 271 loadMoreData_: function() { |
|
calamity
2017/02/21 02:40:06
While you're here, you may as well make this and t
tsergeant
2017/02/22 01:54:27
Done, oops
| |
| 102 if (this.resultLoadingDisabled_ || this.querying) | 272 if (this.resultLoadingDisabled_ || this.queryState.querying) |
| 103 return; | 273 return; |
| 104 | 274 |
| 105 this.fire('query-history', true); | 275 this.fire('query-history', true); |
| 106 }, | 276 }, |
| 107 | 277 |
| 108 /** | 278 /** |
| 109 * Ensure that the item is visible in the scroll pane when its menu is | 279 * Open the overflow menu and ensure that the item is visible in the scroll |
| 110 * opened (it is possible to open off-screen items using keyboard shortcuts). | 280 * pane when its menu is opened (it is possible to open off-screen items using |
| 111 * @param {Event} e | 281 * keyboard shortcuts). |
| 282 * @param {{detail: { | |
| 283 * index: number, item: !HistoryEntry, | |
| 284 * path: string, target: !HTMLElement | |
| 285 * }}} e | |
| 112 * @private | 286 * @private |
| 113 */ | 287 */ |
| 114 onOpenMenu_: function(e) { | 288 onOpenMenu_: function(e) { |
| 115 var index = e.detail.index; | 289 var index = e.detail.index; |
| 116 var list = /** @type {IronListElement} */ (this.$['infinite-list']); | 290 var list = /** @type {IronListElement} */ (this.$['infinite-list']); |
| 117 if (index < list.firstVisibleIndex || index > list.lastVisibleIndex) | 291 if (index < list.firstVisibleIndex || index > list.lastVisibleIndex) |
| 118 list.scrollToIndex(index); | 292 list.scrollToIndex(index); |
| 293 | |
| 294 var target = e.detail.target; | |
| 295 this.actionMenuModel_ = e.detail; | |
| 296 var menu = /** @type {CrSharedMenuElement} */ this.$.sharedMenu.get(); | |
| 297 menu.showAt(target); | |
| 119 }, | 298 }, |
| 120 | 299 |
| 121 /** | 300 /** |
| 301 * Closes the overflow menu. | |
| 302 * @private | |
| 303 */ | |
| 304 closeMenu_: function() { | |
|
calamity
2017/02/21 02:40:06
I don't think this is hooked to any events.
tsergeant
2017/02/21 23:24:12
Moved up into the new Private methods section
| |
| 305 var menu = this.$.sharedMenu.getIfExists(); | |
| 306 if (menu && menu.open) { | |
| 307 this.actionMenuModel_ = null; | |
| 308 menu.close(); | |
| 309 } | |
| 310 }, | |
| 311 | |
| 312 /** @private */ | |
| 313 onMoreFromSiteTap_: function() { | |
| 314 md_history.BrowserService.getInstance().recordAction( | |
| 315 'EntryMenuShowMoreFromSite'); | |
| 316 | |
| 317 var menu = assert(this.$.sharedMenu.getIfExists()); | |
| 318 this.fire('change-query', {search: this.actionMenuModel_.item.domain}); | |
| 319 this.actionMenuModel_ = null; | |
| 320 this.closeMenu_(); | |
| 321 }, | |
| 322 | |
| 323 /** @private */ | |
| 324 onRemoveFromHistoryTap_: function() { | |
| 325 var browserService = md_history.BrowserService.getInstance(); | |
| 326 browserService.recordAction('EntryMenuRemoveFromHistory'); | |
| 327 var menu = assert(this.$.sharedMenu.getIfExists()); | |
| 328 var itemData = this.actionMenuModel_; | |
| 329 browserService.deleteItems([itemData.item]).then(function(items) { | |
| 330 // This unselect-all resets the toolbar when deleting a selected item | |
| 331 // and clears selection state which can be invalid if items move | |
| 332 // around during deletion. | |
| 333 // TODO(tsergeant): Make this automatic based on observing list | |
| 334 // modifications. | |
| 335 this.fire('unselect-all'); | |
| 336 this.removeItemsByIndex([itemData.index]); | |
| 337 | |
| 338 var index = itemData.index; | |
| 339 if (index == undefined) | |
| 340 return; | |
| 341 | |
| 342 var browserService = md_history.BrowserService.getInstance(); | |
| 343 browserService.recordHistogram( | |
| 344 'HistoryPage.RemoveEntryPosition', | |
| 345 Math.min(index, UMA_MAX_BUCKET_VALUE), UMA_MAX_BUCKET_VALUE); | |
| 346 if (index <= UMA_MAX_SUBSET_BUCKET_VALUE) { | |
| 347 browserService.recordHistogram( | |
| 348 'HistoryPage.RemoveEntryPositionSubset', index, | |
| 349 UMA_MAX_SUBSET_BUCKET_VALUE); | |
| 350 } | |
| 351 }.bind(this)); | |
| 352 this.closeMenu_(); | |
| 353 }, | |
| 354 | |
| 355 /** | |
| 356 * @param {Event} e | |
| 357 * @private | |
| 358 */ | |
| 359 onItemSelected_: function(e) { | |
| 360 var index = e.detail.index; | |
| 361 var indices = []; | |
| 362 | |
| 363 // Handle shift selection. Change the selection state of all items between | |
| 364 // |path| and |lastSelected| to the selection state of |item|. | |
| 365 if (e.detail.shiftKey && this.lastSelectedIndex != undefined) { | |
| 366 for (var i = Math.min(index, this.lastSelectedIndex); | |
| 367 i <= Math.max(index, this.lastSelectedIndex); i++) { | |
| 368 indices.push(i); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 if (indices.length == 0) | |
| 373 indices.push(index); | |
| 374 | |
| 375 var selected = !this.selectedItems.has(index); | |
| 376 | |
| 377 indices.forEach(function(index) { | |
| 378 this.changeSelection(index, selected); | |
| 379 }.bind(this)); | |
| 380 | |
| 381 this.lastSelectedIndex = index; | |
| 382 }, | |
| 383 | |
| 384 ///////////////////////////////////////////////////////////////////////////// | |
| 385 // Template helpers: | |
| 386 | |
| 387 /** | |
| 122 * Check whether the time difference between the given history item and the | 388 * Check whether the time difference between the given history item and the |
| 123 * next one is large enough for a spacer to be required. | 389 * next one is large enough for a spacer to be required. |
| 124 * @param {HistoryEntry} item | 390 * @param {HistoryEntry} item |
| 125 * @param {number} index The index of |item| in |historyData_|. | 391 * @param {number} index The index of |item| in |historyData_|. |
| 126 * @param {number} length The length of |historyData_|. | 392 * @param {number} length The length of |historyData_|. |
| 127 * @return {boolean} Whether or not time gap separator is required. | 393 * @return {boolean} Whether or not time gap separator is required. |
| 128 * @private | 394 * @private |
| 129 */ | 395 */ |
| 130 needsTimeGap_: function(item, index, length) { | 396 needsTimeGap_: function(item, index, length) { |
| 131 return md_history.HistoryItem.needsTimeGap( | 397 if (index >= length - 1 || length == 0) |
| 132 this.historyData_, index, this.searchedTerm); | 398 return false; |
| 399 | |
| 400 var currentItem = this.historyData_[index]; | |
| 401 var nextItem = this.historyData_[index + 1]; | |
| 402 | |
| 403 if (this.searchedTerm) | |
| 404 return currentItem.dateShort != nextItem.dateShort; | |
| 405 | |
| 406 return currentItem.time - nextItem.time > BROWSING_GAP_TIME && | |
| 407 currentItem.dateRelativeDay == nextItem.dateRelativeDay; | |
| 133 }, | 408 }, |
| 134 | 409 |
| 135 /** | 410 /** |
| 136 * True if the given item is the beginning of a new card. | 411 * True if the given item is the beginning of a new card. |
| 137 * @param {HistoryEntry} item | 412 * @param {HistoryEntry} item |
| 138 * @param {number} i Index of |item| within |historyData_|. | 413 * @param {number} i Index of |item| within |historyData_|. |
| 139 * @param {number} length | 414 * @param {number} length |
| 140 * @return {boolean} | 415 * @return {boolean} |
| 141 * @private | 416 * @private |
| 142 */ | 417 */ |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 158 */ | 433 */ |
| 159 isCardEnd_: function(item, i, length) { | 434 isCardEnd_: function(item, i, length) { |
| 160 if (length == 0 || i > length - 1) | 435 if (length == 0 || i > length - 1) |
| 161 return false; | 436 return false; |
| 162 return i == length - 1 || | 437 return i == length - 1 || |
| 163 this.historyData_[i].dateRelativeDay != | 438 this.historyData_[i].dateRelativeDay != |
| 164 this.historyData_[i + 1].dateRelativeDay; | 439 this.historyData_[i + 1].dateRelativeDay; |
| 165 }, | 440 }, |
| 166 | 441 |
| 167 /** | 442 /** |
| 168 * @param {number} index | 443 * @param {number} historyDataLength |
| 444 * @return {boolean} | |
| 445 * @private | |
| 446 */ | |
| 447 hasResults_: function(historyDataLength) { | |
| 448 return historyDataLength > 0; | |
| 449 }, | |
| 450 | |
| 451 /** | |
| 452 * @param {string} searchedTerm | |
| 453 * @param {boolean} isLoading | |
| 169 * @return {string} | 454 * @return {string} |
| 170 * @private | 455 * @private |
| 171 */ | 456 */ |
| 172 pathForItem_: function(index) { | 457 noResultsMessage_: function(searchedTerm, isLoading) { |
| 173 return 'historyData_.' + index; | 458 if (isLoading) |
| 459 return ''; | |
| 460 | |
| 461 var messageId = searchedTerm !== '' ? 'noSearchResults' : 'noResults'; | |
| 462 return loadTimeData.getString(messageId); | |
| 463 }, | |
| 464 | |
| 465 /** @private */ | |
| 466 canDeleteHistory_: function() { | |
| 467 return loadTimeData.getBoolean('allowDeletingHistory'); | |
| 468 }, | |
| 469 | |
| 470 /** | |
| 471 * @param {HistoryQuery} info | |
| 472 * @param {!Array<HistoryEntry>} results | |
| 473 * @private | |
| 474 */ | |
| 475 initializeResults_: function(info, results) { | |
| 476 if (results.length == 0) | |
| 477 return; | |
| 478 | |
| 479 var currentDate = results[0].dateRelativeDay; | |
| 480 | |
| 481 for (var i = 0; i < results.length; i++) { | |
| 482 // Sets the default values for these fields to prevent undefined types. | |
| 483 results[i].selected = false; | |
| 484 results[i].readableTimestamp = | |
| 485 info.term == '' ? results[i].dateTimeOfDay : results[i].dateShort; | |
| 486 | |
| 487 if (results[i].dateRelativeDay != currentDate) { | |
| 488 currentDate = results[i].dateRelativeDay; | |
| 489 } | |
| 490 } | |
| 174 }, | 491 }, |
| 175 }); | 492 }); |
| OLD | NEW |