Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(682)

Side by Side Diff: chrome/browser/resources/history/history.js

Issue 11975053: History: Add option to group visits by domain (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Replace images with characters and minor fix. Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 <include src="../uber/uber_utils.js"> 5 <include src="../uber/uber_utils.js">
6 6
7 /////////////////////////////////////////////////////////////////////////////// 7 ///////////////////////////////////////////////////////////////////////////////
8 // Globals: 8 // Globals:
9 /** @const */ var RESULTS_PER_PAGE = 150; 9 /** @const */ var RESULTS_PER_PAGE = 150;
10 10
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
63 this.dateShort = result.dateShort || ''; 63 this.dateShort = result.dateShort || '';
64 64
65 // Whether this is the continuation of a previous day. 65 // Whether this is the continuation of a previous day.
66 this.continued = continued; 66 this.continued = continued;
67 } 67 }
68 68
69 // Visit, public: ------------------------------------------------------------- 69 // Visit, public: -------------------------------------------------------------
70 70
71 /** 71 /**
72 * Returns a dom structure for a browse page result or a search page result. 72 * Returns a dom structure for a browse page result or a search page result.
73 * @param {boolean} searchResultFlag Indicates whether the result is a search 73 * @param {Object} propertyBag A bag of configuration properties, false by
74 * result or not. 74 * default:
75 * <ul>
James Hawkins 2013/01/22 17:27:05 We don't really use the JSDocs to generate API pag
76 * <li>isSearchResult: Whether or not the result is a search result.</li>
77 * <li>addTitleFavicon: Whether or not the favicon should be added.</li>
78 * </ul>
75 * @return {Node} A DOM node to represent the history entry or search result. 79 * @return {Node} A DOM node to represent the history entry or search result.
76 */ 80 */
77 Visit.prototype.getResultDOM = function(searchResultFlag) { 81 Visit.prototype.getResultDOM = function(propertyBag) {
82 var isSearchResult = propertyBag.isSearchResult || false;
83 var addTitleFavicon = propertyBag.addTitleFavicon || false;
78 var node = createElementWithClassName('li', 'entry'); 84 var node = createElementWithClassName('li', 'entry');
79 var time = createElementWithClassName('div', 'time'); 85 var time = createElementWithClassName('div', 'time');
80 var entryBox = createElementWithClassName('label', 'entry-box'); 86 var entryBox = createElementWithClassName('label', 'entry-box');
81 var domain = createElementWithClassName('div', 'domain'); 87 var domain = createElementWithClassName('div', 'domain');
82 88
83 var dropDown = createElementWithClassName('button', 'drop-down'); 89 var dropDown = createElementWithClassName('button', 'drop-down');
84 dropDown.value = 'Open action menu'; 90 dropDown.value = 'Open action menu';
85 dropDown.title = loadTimeData.getString('actionMenuDescription'); 91 dropDown.title = loadTimeData.getString('actionMenuDescription');
86 dropDown.setAttribute('menu', '#action-menu'); 92 dropDown.setAttribute('menu', '#action-menu');
87 cr.ui.decorate(dropDown, MenuButton); 93 cr.ui.decorate(dropDown, MenuButton);
(...skipping 18 matching lines...) Expand all
106 112
107 domain.textContent = this.getDomainFromURL_(this.url_); 113 domain.textContent = this.getDomainFromURL_(this.url_);
108 114
109 // Clicking anywhere in the entryBox will check/uncheck the checkbox. 115 // Clicking anywhere in the entryBox will check/uncheck the checkbox.
110 entryBox.setAttribute('for', checkbox.id); 116 entryBox.setAttribute('for', checkbox.id);
111 entryBox.addEventListener('mousedown', entryBoxMousedown); 117 entryBox.addEventListener('mousedown', entryBoxMousedown);
112 118
113 // Prevent clicks on the drop down from affecting the checkbox. 119 // Prevent clicks on the drop down from affecting the checkbox.
114 dropDown.addEventListener('click', function(e) { e.preventDefault(); }); 120 dropDown.addEventListener('click', function(e) { e.preventDefault(); });
115 121
116 // We use a wrapper div so that the entry contents will be shinkwrapped. 122 // We use a wrapper div so that the entry contents will be shrinkwrapped.
117 entryBox.appendChild(time); 123 entryBox.appendChild(time);
118 entryBox.appendChild(this.getTitleDOM_()); 124 entryBox.appendChild(this.getTitleDOM_(addTitleFavicon));
119 entryBox.appendChild(domain); 125 entryBox.appendChild(domain);
120 entryBox.appendChild(dropDown); 126 entryBox.appendChild(dropDown);
121 127
122 // Let the entryBox be styled appropriately when it contains keyboard focus. 128 // Let the entryBox be styled appropriately when it contains keyboard focus.
123 entryBox.addEventListener('focus', function() { 129 entryBox.addEventListener('focus', function() {
124 this.classList.add('contains-focus'); 130 this.classList.add('contains-focus');
125 }, true); 131 }, true);
126 entryBox.addEventListener('blur', function() { 132 entryBox.addEventListener('blur', function() {
127 this.classList.remove('contains-focus'); 133 this.classList.remove('contains-focus');
128 }, true); 134 }, true);
129 135
130 node.appendChild(entryBox); 136 node.appendChild(entryBox);
131 137
132 if (searchResultFlag) { 138 if (isSearchResult) {
133 time.appendChild(document.createTextNode(this.dateShort)); 139 time.appendChild(document.createTextNode(this.dateShort));
134 var snippet = createElementWithClassName('div', 'snippet'); 140 var snippet = createElementWithClassName('div', 'snippet');
135 this.addHighlightedText_(snippet, 141 this.addHighlightedText_(snippet,
136 this.snippet_, 142 this.snippet_,
137 this.model_.getSearchText()); 143 this.model_.getSearchText());
138 node.appendChild(snippet); 144 node.appendChild(snippet);
139 } else { 145 } else {
140 time.appendChild(document.createTextNode(this.dateTimeOfDay)); 146 time.appendChild(document.createTextNode(this.dateTimeOfDay));
141 } 147 }
142 148
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
184 var b = document.createElement('b'); 190 var b = document.createElement('b');
185 b.textContent = content.substring(match.index, i); 191 b.textContent = content.substring(match.index, i);
186 node.appendChild(b); 192 node.appendChild(b);
187 } 193 }
188 } 194 }
189 if (i < content.length) 195 if (i < content.length)
190 node.appendChild(document.createTextNode(content.slice(i))); 196 node.appendChild(document.createTextNode(content.slice(i)));
191 }; 197 };
192 198
193 /** 199 /**
194 * @return {DOMObject} DOM representation for the title block. 200 * Returns the DOM element containing a link on the title of the URL for the
201 * current visit. Optionally sets the favicon as well.
202 * @param {boolean} addFavicon Whether to add a favicon or not.
203 * @return {Element} DOM representation for the title block.
195 * @private 204 * @private
196 */ 205 */
197 Visit.prototype.getTitleDOM_ = function() { 206 Visit.prototype.getTitleDOM_ = function(addFavicon) {
198 var node = createElementWithClassName('div', 'title'); 207 var node = createElementWithClassName('div', 'title');
199 node.style.backgroundImage = getFaviconImageSet(this.url_); 208 if (addFavicon) {
200 node.style.backgroundSize = '16px'; 209 node.style.backgroundImage = getFaviconImageSet(this.url_);
210 node.style.backgroundSize = '16px';
211 }
201 212
202 var link = document.createElement('a'); 213 var link = document.createElement('a');
203 link.href = this.url_; 214 link.href = this.url_;
204 link.id = 'id-' + this.id_; 215 link.id = 'id-' + this.id_;
205 link.target = '_top'; 216 link.target = '_top';
206 217
207 // Add a tooltip, since it might be ellipsized. 218 // Add a tooltip, since it might be ellipsized.
208 // TODO(dubroy): Find a way to show the tooltip only when necessary. 219 // TODO(dubroy): Find a way to show the tooltip only when necessary.
209 link.title = this.title_; 220 link.title = this.title_;
210 221
211 this.addHighlightedText_(link, this.title_, this.model_.getSearchText()); 222 this.addHighlightedText_(link, this.title_, this.model_.getSearchText());
212 node.appendChild(link); 223 node.appendChild(link);
213 224
214 if (this.starred_) { 225 if (this.starred_) {
215 var star = createElementWithClassName('div', 'starred'); 226 var star = createElementWithClassName('div', 'starred');
216 node.appendChild(star); 227 node.appendChild(star);
217 star.addEventListener('click', this.starClicked_.bind(this)); 228 star.addEventListener('click', this.starClicked_.bind(this));
218 } 229 }
219 230
220 return node; 231 return node;
221 }; 232 };
222 233
223 /** 234 /**
235 * Set the favicon for an element.
236 * @param {Element} el The DOM element to which to add the icon.
237 * @private
238 */
239 Visit.prototype.addFaviconToElement_ = function(el) {
240 el.style.backgroundImage = getFaviconImageSet(this.url_);
241 };
242
243 /**
224 * Launch a search for more history entries from the same domain. 244 * Launch a search for more history entries from the same domain.
225 * @private 245 * @private
226 */ 246 */
227 Visit.prototype.showMoreFromSite_ = function() { 247 Visit.prototype.showMoreFromSite_ = function() {
228 setSearch(this.getDomainFromURL_(this.url_)); 248 setSearch(this.getDomainFromURL_(this.url_));
229 }; 249 };
230 250
231 /** 251 /**
232 * Remove a single entry from the history. 252 * Remove a single entry from the history.
233 * @private 253 * @private
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
301 * up an initial view, use #requestPage otherwise. 321 * up an initial view, use #requestPage otherwise.
302 */ 322 */
303 HistoryModel.prototype.setSearchText = function(searchText, opt_page) { 323 HistoryModel.prototype.setSearchText = function(searchText, opt_page) {
304 this.clearModel_(); 324 this.clearModel_();
305 this.searchText_ = searchText; 325 this.searchText_ = searchText;
306 this.requestedPage_ = opt_page ? opt_page : 0; 326 this.requestedPage_ = opt_page ? opt_page : 0;
307 this.queryHistory_(); 327 this.queryHistory_();
308 }; 328 };
309 329
310 /** 330 /**
331 * Clear the search text.
332 */
333 HistoryModel.prototype.clearSearchText = function() {
334 this.searchText_ = '';
335 };
336
337 /**
311 * Reload our model with the current parameters. 338 * Reload our model with the current parameters.
312 */ 339 */
313 HistoryModel.prototype.reload = function() { 340 HistoryModel.prototype.reload = function() {
341 // Save user-visible state, clear the model, and restore the state.
314 var search = this.searchText_; 342 var search = this.searchText_;
315 var page = this.requestedPage_; 343 var page = this.requestedPage_;
344 var groupByDomain = this.groupByDomain_;
345
316 this.clearModel_(); 346 this.clearModel_();
317 this.searchText_ = search; 347 this.searchText_ = search;
318 this.requestedPage_ = page; 348 this.requestedPage_ = page;
349 this.groupByDomain_ = groupByDomain;
319 this.queryHistory_(); 350 this.queryHistory_();
320 }; 351 };
321 352
322 /** 353 /**
323 * @return {string} The current search text. 354 * @return {string} The current search text.
324 */ 355 */
325 HistoryModel.prototype.getSearchText = function() { 356 HistoryModel.prototype.getSearchText = function() {
326 return this.searchText_; 357 return this.searchText_;
327 }; 358 };
328 359
(...skipping 12 matching lines...) Expand all
341 * Receiver for history query. 372 * Receiver for history query.
342 * @param {Object} info An object containing information about the query. 373 * @param {Object} info An object containing information about the query.
343 * @param {Array} results A list of results. 374 * @param {Array} results A list of results.
344 */ 375 */
345 HistoryModel.prototype.addResults = function(info, results) { 376 HistoryModel.prototype.addResults = function(info, results) {
346 $('loading-spinner').hidden = true; 377 $('loading-spinner').hidden = true;
347 this.inFlight_ = false; 378 this.inFlight_ = false;
348 this.isQueryFinished_ = info.finished; 379 this.isQueryFinished_ = info.finished;
349 this.queryCursor_ = info.cursor; 380 this.queryCursor_ = info.cursor;
350 381
351 // If there are no results, or they're not for the current search term, 382 // If the results are not for the current search term there's nothing more
James Hawkins 2013/01/22 17:27:05 nit: "term, there's"
352 // there's nothing more to do. 383 // to do.
353 if (!results || !results.length || info.term != this.searchText_) 384 if (info.term != this.searchText_)
354 return; 385 return;
355 386
356 // If necessary, sort the results from newest to oldest. 387 // If necessary, sort the results from newest to oldest.
357 if (!results.sorted) 388 if (!results.sorted)
358 results.sort(function(a, b) { return b.time - a.time; }); 389 results.sort(function(a, b) { return b.time - a.time; });
359 390
360 var lastVisit = this.visits_.slice(-1)[0]; 391 var lastVisit = this.visits_.slice(-1)[0];
361 var lastDay = lastVisit ? lastVisit.dateRelativeDay : null; 392 var lastDay = lastVisit ? lastVisit.dateRelativeDay : null;
362 393
363 for (var i = 0, thisResult; thisResult = results[i]; i++) { 394 for (var i = 0, thisResult; thisResult = results[i]; i++) {
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
408 439
409 // HistoryModel, Private: ----------------------------------------------------- 440 // HistoryModel, Private: -----------------------------------------------------
410 441
411 /** 442 /**
412 * Clear the history model. 443 * Clear the history model.
413 * @private 444 * @private
414 */ 445 */
415 HistoryModel.prototype.clearModel_ = function() { 446 HistoryModel.prototype.clearModel_ = function() {
416 this.inFlight_ = false; // Whether a query is inflight. 447 this.inFlight_ = false; // Whether a query is inflight.
417 this.searchText_ = ''; 448 this.searchText_ = '';
449 // Flag to show that the results are grouped by domain or not.
450 this.groupByDomain_ = false;
418 451
419 this.visits_ = []; // Date-sorted list of visits (most recent first). 452 this.visits_ = []; // Date-sorted list of visits (most recent first).
420 this.last_id_ = 0; 453 this.last_id_ = 0;
421 selectionAnchor = -1; 454 selectionAnchor = -1;
422 455
423 // The page that the view wants to see - we only fetch slightly past this 456 // The page that the view wants to see - we only fetch slightly past this
424 // point. If the view requests a page that we don't have data for, we try 457 // point. If the view requests a page that we don't have data for, we try
425 // to fetch it and call back when we're done. 458 // to fetch it and call back when we're done.
426 this.requestedPage_ = 0; 459 this.requestedPage_ = 0;
427 460
(...skipping 10 matching lines...) Expand all
438 // visit to a URL on any day. 471 // visit to a URL on any day.
439 this.urlsFromLastSeenDay_ = {}; 472 this.urlsFromLastSeenDay_ = {};
440 473
441 if (this.view_) 474 if (this.view_)
442 this.view_.clear_(); 475 this.view_.clear_();
443 }; 476 };
444 477
445 /** 478 /**
446 * Figure out if we need to do more queries to fill the currently requested 479 * Figure out if we need to do more queries to fill the currently requested
447 * page. If we think we can fill the page, call the view and let it know 480 * page. If we think we can fill the page, call the view and let it know
448 * we're ready to show something. 481 * we're ready to show something. This only applies to the daily time-based
482 * view.
449 * @private 483 * @private
450 */ 484 */
451 HistoryModel.prototype.updateSearch_ = function() { 485 HistoryModel.prototype.updateSearch_ = function() {
452 var doneLoading = 486 var doneLoading = this.isQueryFinished_ ||
453 this.canFillPage_(this.requestedPage_) || this.isQueryFinished_; 487 this.canFillPage_(this.requestedPage_);
454 488
455 // Try to fetch more results if the current page isn't full. 489 // Try to fetch more results if the results are not grouped by domain and
490 // the current page isn't full.
456 if (!doneLoading && !this.inFlight_) 491 if (!doneLoading && !this.inFlight_)
457 this.queryHistory_(); 492 this.queryHistory_();
458 493
459 // If we have any data for the requested page, show it. 494 // Show the result or a message if no results were returned.
460 if (this.changed && this.haveDataForPage_(this.requestedPage_)) { 495 this.view_.onModelReady();
461 this.view_.onModelReady();
462 this.changed = false;
463 }
464 }; 496 };
465 497
466 /** 498 /**
467 * Query for history, either for a search or time-based browsing. 499 * Query for history, either for a search or time-based browsing.
468 * @private 500 * @private
469 */ 501 */
470 HistoryModel.prototype.queryHistory_ = function() { 502 HistoryModel.prototype.queryHistory_ = function() {
471 var endTime = 0; 503 var endTime = 0;
472 504 // Do the time-based search.
473 // If there are already some visits, pick up the previous query where it 505 // If there are already some visits, pick up the previous query where it
474 // left off. 506 // left off.
475 if (this.visits_.length > 0) { 507 if (this.visits_.length > 0) {
476 var lastVisit = this.visits_.slice(-1)[0]; 508 var lastVisit = this.visits_.slice(-1)[0];
477 endTime = lastVisit.date.getTime(); 509 endTime = lastVisit.date.getTime();
478 cursor = this.queryCursor_; 510 cursor = this.queryCursor_;
479 } 511 }
480 512
481 $('loading-spinner').hidden = false; 513 $('loading-spinner').hidden = false;
482 this.inFlight_ = true; 514 this.inFlight_ = true;
(...skipping 14 matching lines...) Expand all
497 /** 529 /**
498 * Check to see if we have data to fill the given page. 530 * Check to see if we have data to fill the given page.
499 * @param {number} page The page number. 531 * @param {number} page The page number.
500 * @return {boolean} Whether we have data to fill the page. 532 * @return {boolean} Whether we have data to fill the page.
501 * @private 533 * @private
502 */ 534 */
503 HistoryModel.prototype.canFillPage_ = function(page) { 535 HistoryModel.prototype.canFillPage_ = function(page) {
504 return ((page + 1) * RESULTS_PER_PAGE <= this.getSize()); 536 return ((page + 1) * RESULTS_PER_PAGE <= this.getSize());
505 }; 537 };
506 538
539 /**
540 * Enables or disables grouping by domain.
541 * @param {boolean} groupByDomain New groupByDomain_ value.
542 */
543 HistoryModel.prototype.setGroupByDomain = function(groupByDomain) {
544 this.groupByDomain_ = groupByDomain;
545 };
546
547 /**
548 * Gets whether we are grouped by domain.
549 * @return {boolean} Whether the results are grouped by domain.
550 */
551 HistoryModel.prototype.getGroupByDomain = function() {
552 return this.groupByDomain_;
553 };
554
507 /////////////////////////////////////////////////////////////////////////////// 555 ///////////////////////////////////////////////////////////////////////////////
508 // HistoryView: 556 // HistoryView:
509 557
510 /** 558 /**
511 * Functions and state for populating the page with HTML. This should one-day 559 * Functions and state for populating the page with HTML. This should one-day
512 * contain the view and use event handlers, rather than pushing HTML out and 560 * contain the view and use event handlers, rather than pushing HTML out and
513 * getting called externally. 561 * getting called externally.
514 * @param {HistoryModel} model The model backing this view. 562 * @param {HistoryModel} model The model backing this view.
515 * @constructor 563 * @constructor
516 */ 564 */
(...skipping 18 matching lines...) Expand all
535 // Add handlers for the page navigation buttons at the bottom. 583 // Add handlers for the page navigation buttons at the bottom.
536 $('newest-button').addEventListener('click', function() { 584 $('newest-button').addEventListener('click', function() {
537 self.setPage(0); 585 self.setPage(0);
538 }); 586 });
539 $('newer-button').addEventListener('click', function() { 587 $('newer-button').addEventListener('click', function() {
540 self.setPage(self.pageIndex_ - 1); 588 self.setPage(self.pageIndex_ - 1);
541 }); 589 });
542 $('older-button').addEventListener('click', function() { 590 $('older-button').addEventListener('click', function() {
543 self.setPage(self.pageIndex_ + 1); 591 self.setPage(self.pageIndex_ + 1);
544 }); 592 });
593
594 $('display-filter-sites').addEventListener('click', function(e) {
595 self.setGroupByDomain($('display-filter-sites').checked);
596 });
545 } 597 }
546 598
547 // HistoryView, public: ------------------------------------------------------- 599 // HistoryView, public: -------------------------------------------------------
548 /** 600 /**
549 * Do a search and optionally view a certain page. 601 * Do a search and optionally view a certain page.
550 * @param {string} term The string to search for. 602 * @param {string} term The string to search for.
551 * @param {number} opt_page The page we wish to view, only use this for 603 * @param {number} opt_page The page we wish to view, only use this for
552 * setting up initial views, as this triggers a search. 604 * setting up initial views, as this triggers a search.
553 */ 605 */
554 HistoryView.prototype.setSearch = function(term, opt_page) { 606 HistoryView.prototype.setSearch = function(term, opt_page) {
555 this.pageIndex_ = parseInt(opt_page || 0, 10); 607 this.pageIndex_ = parseInt(opt_page || 0, 10);
556 window.scrollTo(0, 0); 608 window.scrollTo(0, 0);
557 this.model_.setSearchText(term, this.pageIndex_); 609 this.model_.setSearchText(term, this.pageIndex_);
558 pageState.setUIState(term, this.pageIndex_); 610 pageState.setUIState(term, this.pageIndex_, this.model_.getGroupByDomain());
559 }; 611 };
560 612
561 /** 613 /**
614 * Enable or disable results as being grouped by domain.
615 * @param {boolean} groupedByDomain Whether to group by domain or not.
616 */
617 HistoryView.prototype.setGroupByDomain = function(groupedByDomain) {
618 // Group by domain is not currently supported for search results, so reset
619 // the search term if there was one.
620 this.model_.clearSearchText();
621 this.model_.setGroupByDomain(groupedByDomain);
622 this.model_.reload();
623 pageState.setUIState(this.model_.getSearchText(),
624 this.pageIndex_,
625 this.model_.getGroupByDomain());
626 };
627
628 /**
562 * Reload the current view. 629 * Reload the current view.
563 */ 630 */
564 HistoryView.prototype.reload = function() { 631 HistoryView.prototype.reload = function() {
565 this.model_.reload(); 632 this.model_.reload();
566 this.updateRemoveButton(); 633 this.updateRemoveButton();
567 }; 634 };
568 635
569 /** 636 /**
570 * Switch to a specified page. 637 * Switch to a specified page.
571 * @param {number} page The page we wish to view. 638 * @param {number} page The page we wish to view.
572 */ 639 */
573 HistoryView.prototype.setPage = function(page) { 640 HistoryView.prototype.setPage = function(page) {
574 this.clear_(); 641 this.clear_();
575 this.pageIndex_ = parseInt(page, 10); 642 this.pageIndex_ = parseInt(page, 10);
576 window.scrollTo(0, 0); 643 window.scrollTo(0, 0);
577 this.model_.requestPage(page); 644 this.model_.requestPage(page);
578 pageState.setUIState(this.model_.getSearchText(), this.pageIndex_); 645 pageState.setUIState(this.model_.getSearchText(),
646 this.pageIndex_,
647 this.model_.getGroupByDomain());
579 }; 648 };
580 649
581 /** 650 /**
582 * @return {number} The page number being viewed. 651 * @return {number} The page number being viewed.
583 */ 652 */
584 HistoryView.prototype.getPage = function() { 653 HistoryView.prototype.getPage = function() {
585 return this.pageIndex_; 654 return this.pageIndex_;
586 }; 655 };
587 656
588 /** 657 /**
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
622 * Record that the given visit has been rendered. 691 * Record that the given visit has been rendered.
623 * @param {Visit} visit The visit that was rendered. 692 * @param {Visit} visit The visit that was rendered.
624 * @private 693 * @private
625 */ 694 */
626 HistoryView.prototype.setVisitRendered_ = function(visit) { 695 HistoryView.prototype.setVisitRendered_ = function(visit) {
627 visit.isRendered = true; 696 visit.isRendered = true;
628 this.currentVisits_.push(visit); 697 this.currentVisits_.push(visit);
629 }; 698 };
630 699
631 /** 700 /**
701 * This function generates and adds the grouped visits DOM for a certain
James Hawkins 2013/01/22 17:27:05 s/This function g/G/
702 * domain. This includes the clickable arrow and domain name and the visit
703 * entries for that domain.
704 * @param {Element} results DOM object to which to add the elements.
705 * @param {string} domain Current domain name.
706 * @param {Array} domainVisits Array of visits for this domain.
707 * @private
708 */
709 HistoryView.prototype.getGroupedVisitsDOM_ = function(
710 results, domain, domainVisits) {
711 // Add a new domain entry.
712 var siteResults = results.appendChild(
713 createElementWithClassName('li', 'site-entry'));
714 // Make a wrapper that will contain the arrow, the favicon and the domain.
715 var siteDomainWrapper = siteResults.appendChild(
716 createElementWithClassName('div', 'site-domain-wrapper'));
717 var siteArrow = siteDomainWrapper.appendChild(
718 createElementWithClassName('div', 'site-domain-arrow collapse'));
719 siteArrow.textContent = 'â–º';
720 var siteDomain = siteDomainWrapper.appendChild(
721 createElementWithClassName('div', 'site-domain'));
722 var numberOfVisits = createElementWithClassName('span', 'number-visits');
723 numberOfVisits.textContent = loadTimeData.getStringF('numbervisits',
724 domainVisits.length);
725 siteDomain.textContent = domain;
726 siteDomain.appendChild(numberOfVisits);
727 siteResults.appendChild(siteDomainWrapper);
728 var resultsList = siteResults.appendChild(
729 createElementWithClassName('ol', 'site-results'));
730
731 domainVisits[0].addFaviconToElement_(siteDomain);
732
733 siteDomainWrapper.addEventListener('click', toggleHandler);
734 // Collapse until it gets toggled.
735 resultsList.style.height = 0;
736
737 // Add the results for each of the domain.
738 for (var j = 0, visit; visit = domainVisits[j]; j++) {
739 resultsList.appendChild(visit.getResultDOM({}));
740 this.setVisitRendered_(visit);
741 }
742 };
743
744 /**
745 * Groups visits by domain, sorting them by the number of visits.
746 * @param {Array} visits Visits received from the query results.
747 * @param {Element} results Object where the results are added to.
748 * @private
749 */
750 HistoryView.prototype.groupVisitsByDomain_ = function(visits, results) {
751 var visitsByDomain = {};
752 var domains = [];
753
754 // Group the visits into a dictionary and generate a list of domains.
755 for (var i = 0, visit; visit = visits[i]; i++) {
756 var domain = visit.getDomainFromURL_(visit.url_);
757 if (!visitsByDomain[domain]) {
758 visitsByDomain[domain] = [];
759 domains.push(domain);
760 }
761 visitsByDomain[domain].push(visit);
762 }
763 var sortByVisits = function(a, b) {
764 return visitsByDomain[b].length - visitsByDomain[a].length;
765 };
766 domains.sort(sortByVisits);
767
768 for (var i = 0, domain; domain = domains[i]; i++) {
James Hawkins 2013/01/22 17:27:05 nit: No braces for single-line blocks.
769 this.getGroupedVisitsDOM_(results, domain, visitsByDomain[domain]);
770 }
771 };
772
773 /**
774 * Adds the results grouped by days, grouping them if needed.
775 * @param {Array} visits Visits returned by the query.
776 * @param {Element} parentElement Element to which to add the results to.
777 * @private
778 */
779 HistoryView.prototype.addDayResults_ = function(visits, parentElement) {
780 if (visits.length == 0)
781 return;
782
783 var firstVisit = visits[0];
784 var day = parentElement.appendChild(createElementWithClassName('h3', 'day'));
785 day.appendChild(document.createTextNode(firstVisit.dateRelativeDay));
786 if (firstVisit.continued) {
787 day.appendChild(document.createTextNode(' ' +
788 loadTimeData.getString('cont')));
789 }
790 var dayResults = parentElement.appendChild(
791 createElementWithClassName('ol', 'day-results'));
792
793 if (this.model_.getGroupByDomain()) {
794 this.groupVisitsByDomain_(visits, dayResults);
795 } else {
796 var lastTime;
797
798 for (var i = 0, visit; visit = visits[i]; i++) {
799 // If enough time has passed between visits, indicate a gap in browsing.
800 var thisTime = visit.date.getTime();
801 if (lastTime && lastTime - thisTime > BROWSING_GAP_TIME)
802 dayResults.appendChild(createElementWithClassName('li', 'gap'));
803
804 // Insert the visit into the DOM.
805 dayResults.appendChild(visit.getResultDOM({
806 addTitleFavicon: true
807 }));
808 this.setVisitRendered_(visit);
809
810 lastTime = thisTime;
811 }
812 }
813 };
814
815 /**
632 * Update the page with results. 816 * Update the page with results.
633 * @private 817 * @private
634 */ 818 */
635 HistoryView.prototype.displayResults_ = function() { 819 HistoryView.prototype.displayResults_ = function() {
636 var rangeStart = this.pageIndex_ * RESULTS_PER_PAGE; 820 var rangeStart = this.pageIndex_ * RESULTS_PER_PAGE;
637 var rangeEnd = rangeStart + RESULTS_PER_PAGE; 821 var rangeEnd = rangeStart + RESULTS_PER_PAGE;
638 var results = this.model_.getNumberedRange(rangeStart, rangeEnd); 822 var results = this.model_.getNumberedRange(rangeStart, rangeEnd);
639 823
640 var searchText = this.model_.getSearchText(); 824 var searchText = this.model_.getSearchText();
825 var groupByDomain = this.model_.getGroupByDomain();
826
641 if (searchText) { 827 if (searchText) {
642 // Add a header for the search results, if there isn't already one. 828 // Add a header for the search results, if there isn't already one.
643 if (!this.resultDiv_.querySelector('h3')) { 829 if (!this.resultDiv_.querySelector('h3')) {
644 var header = document.createElement('h3'); 830 var header = document.createElement('h3');
645 header.textContent = loadTimeData.getStringF('searchresultsfor', 831 header.textContent = loadTimeData.getStringF('searchresultsfor',
646 searchText); 832 searchText);
647 this.resultDiv_.appendChild(header); 833 this.resultDiv_.appendChild(header);
648 } 834 }
649 835
650 var searchResults = createElementWithClassName('ol', 'search-results'); 836 var searchResults = createElementWithClassName('ol', 'search-results');
651 if (results.length == 0) { 837 if (results.length == 0) {
652 var noResults = document.createElement('div'); 838 var noSearchResults = document.createElement('div');
653 noResults.textContent = loadTimeData.getString('noresults'); 839 noSearchResults.textContent = loadTimeData.getString('nosearchresults');
654 searchResults.appendChild(noResults); 840 searchResults.appendChild(noSearchResults);
655 } else { 841 } else {
656 for (var i = 0, visit; visit = results[i]; i++) { 842 for (var i = 0, visit; visit = results[i]; i++) {
657 if (!visit.isRendered) { 843 if (!visit.isRendered) {
658 searchResults.appendChild(visit.getResultDOM(true)); 844 searchResults.appendChild(visit.getResultDOM({
845 isSearchResult: true,
846 addTitleFavicon: true
847 }));
659 this.setVisitRendered_(visit); 848 this.setVisitRendered_(visit);
660 } 849 }
661 } 850 }
662 } 851 }
663 this.resultDiv_.appendChild(searchResults); 852 this.resultDiv_.appendChild(searchResults);
664 } else { 853 } else {
854 if (results.length == 0) {
855 var noResults = document.createElement('div');
856 noResults.textContent = loadTimeData.getString('noresults');
857 this.resultDiv_.appendChild(noResults);
858 this.updateNavBar_();
859 return;
860 }
861
665 var resultsFragment = document.createDocumentFragment(); 862 var resultsFragment = document.createDocumentFragment();
666 var lastTime = Math.infinity;
667 var dayResults;
668 863
669 for (var i = 0, visit; visit = results[i]; i++) { 864 var dayStart = 0;
670 if (visit.isRendered) 865 var dayEnd = 0;
671 continue; 866 // Go through all of the visits and process them in chunks of one day.
867 while (dayEnd < results.length) {
868 // Skip over the ones that are already rendered.
869 while (dayStart < results.length && results[dayStart].isRendered)
870 ++dayStart;
871 var dayEnd = dayStart + 1;
872 while (dayEnd < results.length && results[dayEnd].continued)
873 ++dayEnd;
672 874
673 var thisTime = visit.date.getTime(); 875 this.addDayResults_(
876 results.slice(dayStart, dayEnd), resultsFragment, groupByDomain);
877 }
674 878
675 // Break across day boundaries and insert gaps for browsing pauses. 879 // Add all the days and their visits to the page.
676 // Create a dayResults element to contain results for each day.
677 if ((i == 0 && visit.continued) || !visit.continued) {
678 // It's the first visit of the day, or the day is continued from
679 // the previous page. Create a header for the day on the current page.
680 var day = createElementWithClassName('h3', 'day');
681 day.appendChild(document.createTextNode(visit.dateRelativeDay));
682 if (visit.continued) {
683 day.appendChild(document.createTextNode(' ' +
684 loadTimeData.getString('cont')));
685 }
686
687 resultsFragment.appendChild(day);
688 dayResults = createElementWithClassName('ol', 'day-results');
689 resultsFragment.appendChild(dayResults);
690 } else if (dayResults && lastTime - thisTime > BROWSING_GAP_TIME) {
691 dayResults.appendChild(createElementWithClassName('li', 'gap'));
692 }
693 lastTime = thisTime;
694
695 // Add the entry to the appropriate day.
696 dayResults.appendChild(visit.getResultDOM(false));
697 this.setVisitRendered_(visit);
698 }
699 this.resultDiv_.appendChild(resultsFragment); 880 this.resultDiv_.appendChild(resultsFragment);
700 } 881 }
882 this.updateNavBar_();
701 }; 883 };
702 884
703 /** 885 /**
704 * Update the visibility of the page navigation buttons. 886 * Update the visibility of the page navigation buttons.
705 * @private 887 * @private
706 */ 888 */
707 HistoryView.prototype.updateNavBar_ = function() { 889 HistoryView.prototype.updateNavBar_ = function() {
708 $('newest-button').hidden = this.pageIndex_ == 0; 890 $('newest-button').hidden = this.pageIndex_ == 0;
709 $('newer-button').hidden = this.pageIndex_ == 0; 891 $('newer-button').hidden = this.pageIndex_ == 0;
710 $('older-button').hidden = !this.model_.hasMoreResults(); 892 $('older-button').hidden = !this.model_.hasMoreResults();
(...skipping 21 matching lines...) Expand all
732 } 914 }
733 915
734 // TODO(glen): Replace this with a bound method so we don't need 916 // TODO(glen): Replace this with a bound method so we don't need
735 // public model and view. 917 // public model and view.
736 this.checker_ = setInterval((function(state_obj) { 918 this.checker_ = setInterval((function(state_obj) {
737 var hashData = state_obj.getHashData(); 919 var hashData = state_obj.getHashData();
738 if (hashData.q != state_obj.model.getSearchText()) { 920 if (hashData.q != state_obj.model.getSearchText()) {
739 state_obj.view.setSearch(hashData.q, parseInt(hashData.p, 10)); 921 state_obj.view.setSearch(hashData.q, parseInt(hashData.p, 10));
740 } else if (parseInt(hashData.p, 10) != state_obj.view.getPage()) { 922 } else if (parseInt(hashData.p, 10) != state_obj.view.getPage()) {
741 state_obj.view.setPage(hashData.p); 923 state_obj.view.setPage(hashData.p);
924 } else if ((hashData.g == 'true') !=
925 state_obj.view.model_.getGroupByDomain()) {
926 state_obj.view.setGroupByDomain(hashData.g);
742 } 927 }
743 }), 50, this); 928 }), 50, this);
744 } 929 }
745 930
746 /** 931 /**
747 * Holds the singleton instance. 932 * Holds the singleton instance.
748 */ 933 */
749 PageState.instance = null; 934 PageState.instance = null;
750 935
751 /** 936 /**
752 * @return {Object} An object containing parameters from our window hash. 937 * @return {Object} An object containing parameters from our window hash.
753 */ 938 */
754 PageState.prototype.getHashData = function() { 939 PageState.prototype.getHashData = function() {
755 var result = { 940 var result = {
756 e: 0, 941 e: 0,
757 q: '', 942 q: '',
758 p: 0 943 p: 0,
944 g: false
759 }; 945 };
760 946
761 if (!window.location.hash) { 947 if (!window.location.hash)
762 return result; 948 return result;
763 }
764 949
765 var hashSplit = window.location.hash.substr(1).split('&'); 950 var hashSplit = window.location.hash.substr(1).split('&');
766 for (var i = 0; i < hashSplit.length; i++) { 951 for (var i = 0; i < hashSplit.length; i++) {
767 var pair = hashSplit[i].split('='); 952 var pair = hashSplit[i].split('=');
768 if (pair.length > 1) { 953 if (pair.length > 1) {
769 result[pair[0]] = decodeURIComponent(pair[1].replace(/\+/g, ' ')); 954 result[pair[0]] = decodeURIComponent(pair[1].replace(/\+/g, ' '));
770 } 955 }
771 } 956 }
772 957
773 return result; 958 return result;
774 }; 959 };
775 960
776 /** 961 /**
777 * Set the hash to a specified state, this will create an entry in the 962 * Set the hash to a specified state, this will create an entry in the
778 * session history so the back button cycles through hash states, which 963 * session history so the back button cycles through hash states, which
779 * are then picked up by our listener. 964 * are then picked up by our listener.
780 * @param {string} term The current search string. 965 * @param {string} term The current search string.
781 * @param {string} page The page currently being viewed. 966 * @param {number} page The page currently being viewed.
967 * @param {boolean} grouped Whether the results are grouped or not.
782 */ 968 */
783 PageState.prototype.setUIState = function(term, page) { 969 PageState.prototype.setUIState = function(term, page, grouped) {
784 // Make sure the form looks pretty. 970 // Make sure the form looks pretty.
785 $('search-field').value = term; 971 $('search-field').value = term;
786 var currentHash = this.getHashData(); 972 if (grouped) {
James Hawkins 2013/01/22 17:27:05 nit: No braces for single-line blocks.
787 if (currentHash.q != term || currentHash.p != page) { 973 $('display-filter-sites').checked = true;
788 window.location.hash = PageState.getHashString(term, page); 974 } else {
975 $('display-filter-sites').checked = false;
976 }
977 var hash = this.getHashData();
978 if (hash.q != term || hash.p != page || hash.g != grouped) {
979 window.location.hash = PageState.getHashString(
980 term, page, grouped);
789 } 981 }
790 }; 982 };
791 983
792 /** 984 /**
793 * Static method to get the hash string for a specified state 985 * Static method to get the hash string for a specified state
794 * @param {string} term The current search string. 986 * @param {string} term The current search string.
795 * @param {string} page The page currently being viewed. 987 * @param {number} page The page currently being viewed.
988 * @param {boolean} grouped Whether the results are grouped or not.
796 * @return {string} The string to be used in a hash. 989 * @return {string} The string to be used in a hash.
797 */ 990 */
798 PageState.getHashString = function(term, page) { 991 PageState.getHashString = function(term, page, grouped) {
992 // Omit elements that are empty.
799 var newHash = []; 993 var newHash = [];
800 if (term) { 994
995 if (term)
801 newHash.push('q=' + encodeURIComponent(term)); 996 newHash.push('q=' + encodeURIComponent(term));
802 } 997
803 if (page != undefined) { 998 if (page)
804 newHash.push('p=' + page); 999 newHash.push('p=' + page);
805 } 1000
1001 if (grouped)
1002 newHash.push('g=' + grouped);
806 1003
807 return newHash.join('&'); 1004 return newHash.join('&');
808 }; 1005 };
809 1006
810 /////////////////////////////////////////////////////////////////////////////// 1007 ///////////////////////////////////////////////////////////////////////////////
811 // Document Functions: 1008 // Document Functions:
812 /** 1009 /**
813 * Window onload handler, sets up the page. 1010 * Window onload handler, sets up the page.
814 */ 1011 */
815 function load() { 1012 function load() {
(...skipping 17 matching lines...) Expand all
833 1030
834 $('remove-visit').addEventListener('activate', function(e) { 1031 $('remove-visit').addEventListener('activate', function(e) {
835 activeVisit.removeFromHistory_(); 1032 activeVisit.removeFromHistory_();
836 activeVisit = null; 1033 activeVisit = null;
837 }); 1034 });
838 $('more-from-site').addEventListener('activate', function(e) { 1035 $('more-from-site').addEventListener('activate', function(e) {
839 activeVisit.showMoreFromSite_(); 1036 activeVisit.showMoreFromSite_();
840 activeVisit = null; 1037 activeVisit = null;
841 }); 1038 });
842 1039
1040 // Only show the controls if the command line switch is activated.
1041 if (loadTimeData.getBoolean('groupByDomain')) {
1042 $('filter-controls').hidden = false;
1043 }
1044
843 var title = loadTimeData.getString('title'); 1045 var title = loadTimeData.getString('title');
844 uber.invokeMethodOnParent('setTitle', {title: title}); 1046 uber.invokeMethodOnParent('setTitle', {title: title});
845 1047
846 window.addEventListener('message', function(e) { 1048 window.addEventListener('message', function(e) {
847 if (e.data.method == 'frameSelected') 1049 if (e.data.method == 'frameSelected')
848 searchField.focus(); 1050 searchField.focus();
849 }); 1051 });
850 } 1052 }
851 1053
852 /** 1054 /**
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
905 1107
906 function reloadHistory() { 1108 function reloadHistory() {
907 historyView.reload(); 1109 historyView.reload();
908 } 1110 }
909 1111
910 /** 1112 /**
911 * Click handler for the 'Remove selected items' button. 1113 * Click handler for the 'Remove selected items' button.
912 * Collect IDs from the checked checkboxes and send to Chrome for deletion. 1114 * Collect IDs from the checked checkboxes and send to Chrome for deletion.
913 */ 1115 */
914 function removeItems() { 1116 function removeItems() {
915 var checked = document.querySelectorAll( 1117 var checked = $('results-display').querySelectorAll(
916 'input[type=checkbox]:checked:not([disabled])'); 1118 'input[type=checkbox]:checked:not([disabled])');
917 var urls = []; 1119 var urls = [];
918 var disabledItems = []; 1120 var disabledItems = [];
919 var queue = []; 1121 var queue = [];
920 var date = new Date(); 1122 var date = new Date();
921 1123
922 for (var i = 0; i < checked.length; i++) { 1124 for (var i = 0; i < checked.length; i++) {
923 var checkbox = checked[i]; 1125 var checkbox = checked[i];
924 var cbDate = new Date(checkbox.time); 1126 var cbDate = new Date(checkbox.time);
925 if (date.getFullYear() != cbDate.getFullYear() || 1127 if (date.getFullYear() != cbDate.getFullYear() ||
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
1022 removeNode(previousEntry); 1224 removeNode(previousEntry);
1023 } 1225 }
1024 1226
1025 // if both the next and previous entries are gaps, remove one 1227 // if both the next and previous entries are gaps, remove one
1026 if (nextEntry && nextEntry.className == 'gap' && 1228 if (nextEntry && nextEntry.className == 'gap' &&
1027 previousEntry && previousEntry.className == 'gap') { 1229 previousEntry && previousEntry.className == 'gap') {
1028 removeNode(nextEntry); 1230 removeNode(nextEntry);
1029 } 1231 }
1030 } 1232 }
1031 1233
1234 /**
1235 * Toggles an element in the grouped history.
1236 * @param {Element} e The element which was clicked on.
1237 */
1238 function toggleHandler(e) {
1239 var innerResultList = e.currentTarget.parentElement.querySelector(
1240 '.site-results');
1241 var innerArrow = e.currentTarget.parentElement.querySelector(
1242 '.site-domain-arrow');
1243 if (innerArrow.classList.contains('collapse')) {
1244 innerResultList.style.height = 'auto';
1245 // -webkit-transition does not work on height:auto elements so first set
1246 // the height to auto so that it is computed and then set it to the
1247 // computed value in pixels so the transition works properly.
1248 var height = innerResultList.clientHeight;
1249 innerResultList.style.height = height + 'px';
1250 innerArrow.classList.remove('collapse');
1251 innerArrow.classList.add('expand');
1252 } else {
1253 innerResultList.style.height = 0;
1254 innerArrow.classList.remove('expand');
1255 innerArrow.classList.add('collapse');
1256 }
1257 }
1258
1032 /////////////////////////////////////////////////////////////////////////////// 1259 ///////////////////////////////////////////////////////////////////////////////
1033 // Chrome callbacks: 1260 // Chrome callbacks:
1034 1261
1035 /** 1262 /**
1036 * Our history system calls this function with results from searches. 1263 * Our history system calls this function with results from searches.
1037 * @param {Object} info An object containing information about the query. 1264 * @param {Object} info An object containing information about the query.
1038 * @param {Array} results A list of results. 1265 * @param {Array} results A list of results.
1039 */ 1266 */
1040 function historyResult(info, results) { 1267 function historyResult(info, results) {
1041 historyModel.addResults(info, results); 1268 historyModel.addResults(info, results);
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
1079 historyView.reload(); 1306 historyView.reload();
1080 } 1307 }
1081 1308
1082 // Add handlers to HTML elements. 1309 // Add handlers to HTML elements.
1083 document.addEventListener('DOMContentLoaded', load); 1310 document.addEventListener('DOMContentLoaded', load);
1084 1311
1085 // This event lets us enable and disable menu items before the menu is shown. 1312 // This event lets us enable and disable menu items before the menu is shown.
1086 document.addEventListener('canExecute', function(e) { 1313 document.addEventListener('canExecute', function(e) {
1087 e.canExecute = true; 1314 e.canExecute = true;
1088 }); 1315 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698