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

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: Proper switch code. 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>
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
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
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 var siteDomain = siteDomainWrapper.appendChild(
720 createElementWithClassName('div', 'site-domain'));
721 var numberOfVisits = createElementWithClassName('span', 'number-visits');
722 numberOfVisits.textContent = loadTimeData.getStringF('numbervisits',
723 domainVisits.length);
724 siteDomain.textContent = domain;
725 siteDomain.appendChild(numberOfVisits);
726 siteResults.appendChild(siteDomainWrapper);
727 var resultsList = siteResults.appendChild(
728 createElementWithClassName('ol', 'site-results'));
729
730 domainVisits[0].addFaviconToElement_(siteDomain);
731
732 siteDomainWrapper.addEventListener('click', toggleHandler);
733 // Collapse until it gets toggled.
734 resultsList.style.height = 0;
735
736 // Add the results for each of the domain.
737 for (var j = 0, visit; visit = domainVisits[j]; j++) {
738 resultsList.appendChild(visit.getResultDOM({}));
739 this.setVisitRendered_(visit);
740 }
741 };
742
743 /**
744 * Groups visits by domain, sorting them by the number of visits.
745 * @param {Array} visits Visits received from the query results.
746 * @param {Element} results Object where the results are added to.
747 * @private
748 */
749 HistoryView.prototype.groupVisitsByDomain_ = function(visits, results) {
750 var visitsByDomain = {};
751 var domains = [];
752
753 // Group the visits into a dictionary and generate a list of domains.
754 for (var i = 0, visit; visit = visits[i]; i++) {
755 var domain = visit.getDomainFromURL_(visit.url_);
756 if (!visitsByDomain[domain]) {
757 visitsByDomain[domain] = [];
758 domains.push(domain);
759 }
760 visitsByDomain[domain].push(visit);
761 }
762 var sortByVisits = function(a, b) {
763 return visitsByDomain[b].length - visitsByDomain[a].length;
764 };
765 domains.sort(sortByVisits);
766
767 for (var i = 0, domain; domain = domains[i]; i++) {
768 this.getGroupedVisitsDOM_(results, domain, visitsByDomain[domain]);
769 }
770 };
771
772 /**
773 * Adds the results grouped by days, grouping them if needed.
774 * @param {Array} visits Visits returned by the query.
775 * @param {Element} parentElement Element to which to add the results to.
776 * @private
777 */
778 HistoryView.prototype.addDayResults_ = function(visits, parentElement) {
779 if (visits.length == 0)
780 return;
781
782 var firstVisit = visits[0];
783 var day = parentElement.appendChild(createElementWithClassName('h3', 'day'));
784 day.appendChild(document.createTextNode(firstVisit.dateRelativeDay));
785 if (firstVisit.continued) {
786 day.appendChild(document.createTextNode(' ' +
787 loadTimeData.getString('cont')));
788 }
789 var dayResults = parentElement.appendChild(
790 createElementWithClassName('ol', 'day-results'));
791
792 if (this.model_.getGroupByDomain()) {
793 this.groupVisitsByDomain_(visits, dayResults);
794 } else {
795 var lastTime;
796
797 for (var i = 0, visit; visit = visits[i]; i++) {
798 // If enough time has passed between visits, indicate a gap in browsing.
799 var thisTime = visit.date.getTime();
800 if (lastTime && lastTime - thisTime > BROWSING_GAP_TIME)
801 dayResults.appendChild(createElementWithClassName('li', 'gap'));
802
803 // Insert the visit into the DOM.
804 dayResults.appendChild(visit.getResultDOM({
805 addTitleFavicon: true
806 }));
807 this.setVisitRendered_(visit);
808
809 lastTime = thisTime;
810 }
811 }
812 };
813
814 /**
632 * Update the page with results. 815 * Update the page with results.
633 * @private 816 * @private
634 */ 817 */
635 HistoryView.prototype.displayResults_ = function() { 818 HistoryView.prototype.displayResults_ = function() {
636 var rangeStart = this.pageIndex_ * RESULTS_PER_PAGE; 819 var rangeStart = this.pageIndex_ * RESULTS_PER_PAGE;
637 var rangeEnd = rangeStart + RESULTS_PER_PAGE; 820 var rangeEnd = rangeStart + RESULTS_PER_PAGE;
638 var results = this.model_.getNumberedRange(rangeStart, rangeEnd); 821 var results = this.model_.getNumberedRange(rangeStart, rangeEnd);
639 822
640 var searchText = this.model_.getSearchText(); 823 var searchText = this.model_.getSearchText();
824 var groupByDomain = this.model_.getGroupByDomain();
825
641 if (searchText) { 826 if (searchText) {
642 // Add a header for the search results, if there isn't already one. 827 // Add a header for the search results, if there isn't already one.
643 if (!this.resultDiv_.querySelector('h3')) { 828 if (!this.resultDiv_.querySelector('h3')) {
644 var header = document.createElement('h3'); 829 var header = document.createElement('h3');
645 header.textContent = loadTimeData.getStringF('searchresultsfor', 830 header.textContent = loadTimeData.getStringF('searchresultsfor',
646 searchText); 831 searchText);
647 this.resultDiv_.appendChild(header); 832 this.resultDiv_.appendChild(header);
648 } 833 }
649 834
650 var searchResults = createElementWithClassName('ol', 'search-results'); 835 var searchResults = createElementWithClassName('ol', 'search-results');
651 if (results.length == 0) { 836 if (results.length == 0) {
652 var noResults = document.createElement('div'); 837 var noResults = document.createElement('div');
653 noResults.textContent = loadTimeData.getString('noresults'); 838 noResults.textContent = loadTimeData.getString('noresults');
654 searchResults.appendChild(noResults); 839 searchResults.appendChild(noResults);
655 } else { 840 } else {
656 for (var i = 0, visit; visit = results[i]; i++) { 841 for (var i = 0, visit; visit = results[i]; i++) {
657 if (!visit.isRendered) { 842 if (!visit.isRendered) {
658 searchResults.appendChild(visit.getResultDOM(true)); 843 searchResults.appendChild(visit.getResultDOM({
844 isSearchResult: true,
845 addTitleFavicon: true
846 }));
659 this.setVisitRendered_(visit); 847 this.setVisitRendered_(visit);
660 } 848 }
661 } 849 }
662 } 850 }
663 this.resultDiv_.appendChild(searchResults); 851 this.resultDiv_.appendChild(searchResults);
664 } else { 852 } else {
853 if (results.length == 0) {
854 var noResults = document.createElement('div');
855 noResults.textContent = loadTimeData.getString('noresultsinterval');
Patrick Dubroy 2013/01/21 09:42:40 This message doesn't make sense for the normal his
Sergiu 2013/01/21 10:38:29 Done, renamed all the strings related to "no resul
856 this.resultDiv_.appendChild(noResults);
857 this.updateNavBar_();
858 return;
859 }
860
665 var resultsFragment = document.createDocumentFragment(); 861 var resultsFragment = document.createDocumentFragment();
666 var lastTime = Math.infinity;
667 var dayResults;
668 862
669 for (var i = 0, visit; visit = results[i]; i++) { 863 var dayStart = 0;
670 if (visit.isRendered) 864 var dayEnd = 0;
671 continue; 865 // Go through all of the visits and process them in chunks of one day.
866 while (dayEnd < results.length) {
867 // Skip over the ones that are already rendered.
868 while (dayStart < results.length && results[dayStart].isRendered)
869 ++dayStart;
870 var dayEnd = dayStart + 1;
871 while (dayEnd < results.length && results[dayEnd].continued)
872 ++dayEnd;
672 873
673 var thisTime = visit.date.getTime(); 874 this.addDayResults_(
875 results.slice(dayStart, dayEnd), resultsFragment, groupByDomain);
876 }
674 877
675 // Break across day boundaries and insert gaps for browsing pauses. 878 // 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); 879 this.resultDiv_.appendChild(resultsFragment);
700 } 880 }
881 this.updateNavBar_();
701 }; 882 };
702 883
703 /** 884 /**
704 * Update the visibility of the page navigation buttons. 885 * Update the visibility of the page navigation buttons.
705 * @private 886 * @private
706 */ 887 */
707 HistoryView.prototype.updateNavBar_ = function() { 888 HistoryView.prototype.updateNavBar_ = function() {
708 $('newest-button').hidden = this.pageIndex_ == 0; 889 $('newest-button').hidden = this.pageIndex_ == 0;
709 $('newer-button').hidden = this.pageIndex_ == 0; 890 $('newer-button').hidden = this.pageIndex_ == 0;
710 $('older-button').hidden = !this.model_.hasMoreResults(); 891 $('older-button').hidden = !this.model_.hasMoreResults();
(...skipping 21 matching lines...) Expand all
732 } 913 }
733 914
734 // TODO(glen): Replace this with a bound method so we don't need 915 // TODO(glen): Replace this with a bound method so we don't need
735 // public model and view. 916 // public model and view.
736 this.checker_ = setInterval((function(state_obj) { 917 this.checker_ = setInterval((function(state_obj) {
737 var hashData = state_obj.getHashData(); 918 var hashData = state_obj.getHashData();
738 if (hashData.q != state_obj.model.getSearchText()) { 919 if (hashData.q != state_obj.model.getSearchText()) {
739 state_obj.view.setSearch(hashData.q, parseInt(hashData.p, 10)); 920 state_obj.view.setSearch(hashData.q, parseInt(hashData.p, 10));
740 } else if (parseInt(hashData.p, 10) != state_obj.view.getPage()) { 921 } else if (parseInt(hashData.p, 10) != state_obj.view.getPage()) {
741 state_obj.view.setPage(hashData.p); 922 state_obj.view.setPage(hashData.p);
923 } else if ((hashData.g == 'true') !=
924 state_obj.view.model_.getGroupByDomain()) {
925 state_obj.view.setGroupByDomain(hashData.g);
742 } 926 }
743 }), 50, this); 927 }), 50, this);
744 } 928 }
745 929
746 /** 930 /**
747 * Holds the singleton instance. 931 * Holds the singleton instance.
748 */ 932 */
749 PageState.instance = null; 933 PageState.instance = null;
750 934
751 /** 935 /**
752 * @return {Object} An object containing parameters from our window hash. 936 * @return {Object} An object containing parameters from our window hash.
753 */ 937 */
754 PageState.prototype.getHashData = function() { 938 PageState.prototype.getHashData = function() {
755 var result = { 939 var result = {
756 e: 0, 940 e: 0,
757 q: '', 941 q: '',
758 p: 0 942 p: 0,
943 g: false
759 }; 944 };
760 945
761 if (!window.location.hash) { 946 if (!window.location.hash)
762 return result; 947 return result;
763 }
764 948
765 var hashSplit = window.location.hash.substr(1).split('&'); 949 var hashSplit = window.location.hash.substr(1).split('&');
766 for (var i = 0; i < hashSplit.length; i++) { 950 for (var i = 0; i < hashSplit.length; i++) {
767 var pair = hashSplit[i].split('='); 951 var pair = hashSplit[i].split('=');
768 if (pair.length > 1) { 952 if (pair.length > 1) {
769 result[pair[0]] = decodeURIComponent(pair[1].replace(/\+/g, ' ')); 953 result[pair[0]] = decodeURIComponent(pair[1].replace(/\+/g, ' '));
770 } 954 }
771 } 955 }
772 956
773 return result; 957 return result;
774 }; 958 };
775 959
776 /** 960 /**
777 * Set the hash to a specified state, this will create an entry in the 961 * 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 962 * session history so the back button cycles through hash states, which
779 * are then picked up by our listener. 963 * are then picked up by our listener.
780 * @param {string} term The current search string. 964 * @param {string} term The current search string.
781 * @param {string} page The page currently being viewed. 965 * @param {number} page The page currently being viewed.
966 * @param {boolean} grouped Whether the results are grouped or not.
782 */ 967 */
783 PageState.prototype.setUIState = function(term, page) { 968 PageState.prototype.setUIState = function(term, page, grouped) {
784 // Make sure the form looks pretty. 969 // Make sure the form looks pretty.
785 $('search-field').value = term; 970 $('search-field').value = term;
786 var currentHash = this.getHashData(); 971 if (grouped) {
787 if (currentHash.q != term || currentHash.p != page) { 972 $('display-filter-sites').checked = true;
788 window.location.hash = PageState.getHashString(term, page); 973 } else {
974 $('display-filter-sites').checked = false;
975 }
976 var hash = this.getHashData();
977 if (hash.q != term || hash.p != page || hash.g != grouped) {
978 window.location.hash = PageState.getHashString(
979 term, page, grouped);
789 } 980 }
790 }; 981 };
791 982
792 /** 983 /**
793 * Static method to get the hash string for a specified state 984 * Static method to get the hash string for a specified state
794 * @param {string} term The current search string. 985 * @param {string} term The current search string.
795 * @param {string} page The page currently being viewed. 986 * @param {number} page The page currently being viewed.
987 * @param {boolean} grouped Whether the results are grouped or not.
796 * @return {string} The string to be used in a hash. 988 * @return {string} The string to be used in a hash.
797 */ 989 */
798 PageState.getHashString = function(term, page) { 990 PageState.getHashString = function(term, page, grouped) {
991 // Omit elements that are empty.
799 var newHash = []; 992 var newHash = [];
800 if (term) { 993
994 if (term)
801 newHash.push('q=' + encodeURIComponent(term)); 995 newHash.push('q=' + encodeURIComponent(term));
802 } 996
803 if (page != undefined) { 997 if (page)
804 newHash.push('p=' + page); 998 newHash.push('p=' + page);
805 } 999
1000 if (grouped)
1001 newHash.push('g=' + grouped);
806 1002
807 return newHash.join('&'); 1003 return newHash.join('&');
808 }; 1004 };
809 1005
810 /////////////////////////////////////////////////////////////////////////////// 1006 ///////////////////////////////////////////////////////////////////////////////
811 // Document Functions: 1007 // Document Functions:
812 /** 1008 /**
813 * Window onload handler, sets up the page. 1009 * Window onload handler, sets up the page.
814 */ 1010 */
815 function load() { 1011 function load() {
(...skipping 17 matching lines...) Expand all
833 1029
834 $('remove-visit').addEventListener('activate', function(e) { 1030 $('remove-visit').addEventListener('activate', function(e) {
835 activeVisit.removeFromHistory_(); 1031 activeVisit.removeFromHistory_();
836 activeVisit = null; 1032 activeVisit = null;
837 }); 1033 });
838 $('more-from-site').addEventListener('activate', function(e) { 1034 $('more-from-site').addEventListener('activate', function(e) {
839 activeVisit.showMoreFromSite_(); 1035 activeVisit.showMoreFromSite_();
840 activeVisit = null; 1036 activeVisit = null;
841 }); 1037 });
842 1038
1039 // Only show the controls if the command line switch is activated.
1040 if (loadTimeData.getBoolean('historyGroupEnabled')) {
1041 $('filter-controls').hidden = false;
1042 }
1043
843 var title = loadTimeData.getString('title'); 1044 var title = loadTimeData.getString('title');
844 uber.invokeMethodOnParent('setTitle', {title: title}); 1045 uber.invokeMethodOnParent('setTitle', {title: title});
845 1046
846 window.addEventListener('message', function(e) { 1047 window.addEventListener('message', function(e) {
847 if (e.data.method == 'frameSelected') 1048 if (e.data.method == 'frameSelected')
848 searchField.focus(); 1049 searchField.focus();
849 }); 1050 });
850 } 1051 }
851 1052
852 /** 1053 /**
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after
1022 removeNode(previousEntry); 1223 removeNode(previousEntry);
1023 } 1224 }
1024 1225
1025 // if both the next and previous entries are gaps, remove one 1226 // if both the next and previous entries are gaps, remove one
1026 if (nextEntry && nextEntry.className == 'gap' && 1227 if (nextEntry && nextEntry.className == 'gap' &&
1027 previousEntry && previousEntry.className == 'gap') { 1228 previousEntry && previousEntry.className == 'gap') {
1028 removeNode(nextEntry); 1229 removeNode(nextEntry);
1029 } 1230 }
1030 } 1231 }
1031 1232
1233 /**
1234 * Toggles an element in the grouped history.
1235 * @param {Element} e The element which was clicked on.
1236 */
1237 function toggleHandler(e) {
1238 var innerResultList = e.currentTarget.parentElement.querySelector(
1239 '.site-results');
1240 var innerArrow = e.currentTarget.parentElement.querySelector(
1241 '.site-domain-arrow');
1242 if (innerArrow.classList.contains('collapse')) {
1243 innerResultList.style.height = 'auto';
1244 // -webkit-transition does not work on height:auto elements so first set
1245 // the height to auto so that it is computed and then set it to the
1246 // computed value in pixels so the transition works properly.
1247 var height = innerResultList.clientHeight;
1248 innerResultList.style.height = height + 'px';
1249 innerArrow.classList.remove('collapse');
1250 innerArrow.classList.add('expand');
1251 } else {
1252 innerResultList.style.height = 0;
1253 innerArrow.classList.remove('expand');
1254 innerArrow.classList.add('collapse');
1255 }
1256 }
1257
1032 /////////////////////////////////////////////////////////////////////////////// 1258 ///////////////////////////////////////////////////////////////////////////////
1033 // Chrome callbacks: 1259 // Chrome callbacks:
1034 1260
1035 /** 1261 /**
1036 * Our history system calls this function with results from searches. 1262 * Our history system calls this function with results from searches.
1037 * @param {Object} info An object containing information about the query. 1263 * @param {Object} info An object containing information about the query.
1038 * @param {Array} results A list of results. 1264 * @param {Array} results A list of results.
1039 */ 1265 */
1040 function historyResult(info, results) { 1266 function historyResult(info, results) {
1041 historyModel.addResults(info, results); 1267 historyModel.addResults(info, results);
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
1079 historyView.reload(); 1305 historyView.reload();
1080 } 1306 }
1081 1307
1082 // Add handlers to HTML elements. 1308 // Add handlers to HTML elements.
1083 document.addEventListener('DOMContentLoaded', load); 1309 document.addEventListener('DOMContentLoaded', load);
1084 1310
1085 // This event lets us enable and disable menu items before the menu is shown. 1311 // This event lets us enable and disable menu items before the menu is shown.
1086 document.addEventListener('canExecute', function(e) { 1312 document.addEventListener('canExecute', function(e) {
1087 e.canExecute = true; 1313 e.canExecute = true;
1088 }); 1314 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698