OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 /////////////////////////////////////////////////////////////////////////////// | 5 /////////////////////////////////////////////////////////////////////////////// |
6 // Globals: | 6 // Globals: |
7 var RESULTS_PER_PAGE = 150; | 7 var RESULTS_PER_PAGE = 150; |
8 var MAX_SEARCH_DEPTH_MONTHS = 18; | 8 var MAX_SEARCH_DEPTH_MONTHS = 18; |
9 | 9 |
10 // Amount of time between pageviews that we consider a 'break' in browsing, | 10 // Amount of time between pageviews that we consider a 'break' in browsing, |
(...skipping 16 matching lines...) Expand all Loading... |
27 | 27 |
28 // TODO(glen): Get rid of these global references, replace with a controller | 28 // TODO(glen): Get rid of these global references, replace with a controller |
29 // or just make the classes own more of the page. | 29 // or just make the classes own more of the page. |
30 var historyModel; | 30 var historyModel; |
31 var historyView; | 31 var historyView; |
32 var localStrings; | 32 var localStrings; |
33 var pageState; | 33 var pageState; |
34 var deleteQueue = []; | 34 var deleteQueue = []; |
35 var selectionAnchor = -1; | 35 var selectionAnchor = -1; |
36 var activePage = null; | 36 var activePage = null; |
37 var idToCheckbox = []; | |
38 | 37 |
39 const MenuButton = cr.ui.MenuButton; | 38 const MenuButton = cr.ui.MenuButton; |
40 const Command = cr.ui.Command; | 39 const Command = cr.ui.Command; |
41 const Menu = cr.ui.Menu; | 40 const Menu = cr.ui.Menu; |
42 | 41 |
43 function createDropDownBgImage(canvasName, colorSpec) { | 42 function createDropDownBgImage(canvasName, colorSpec) { |
44 var ctx = document.getCSSCanvasContext('2d', canvasName, 6, 4); | 43 var ctx = document.getCSSCanvasContext('2d', canvasName, 6, 4); |
45 ctx.fillStyle = ctx.strokeStyle = colorSpec; | 44 ctx.fillStyle = ctx.strokeStyle = colorSpec; |
46 ctx.beginPath(); | 45 ctx.beginPath(); |
47 ctx.moveTo(0, 0); | 46 ctx.moveTo(0, 0); |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
97 | 96 |
98 // Page, Public: -------------------------------------------------------------- | 97 // Page, Public: -------------------------------------------------------------- |
99 /** | 98 /** |
100 * Returns a dom structure for a browse page result or a search page result. | 99 * Returns a dom structure for a browse page result or a search page result. |
101 * @param {boolean} Flag to indicate if result is a search result. | 100 * @param {boolean} Flag to indicate if result is a search result. |
102 * @return {Element} The dom structure. | 101 * @return {Element} The dom structure. |
103 */ | 102 */ |
104 Page.prototype.getResultDOM = function(searchResultFlag) { | 103 Page.prototype.getResultDOM = function(searchResultFlag) { |
105 var node = createElementWithClassName('li', 'entry'); | 104 var node = createElementWithClassName('li', 'entry'); |
106 var time = createElementWithClassName('div', 'time'); | 105 var time = createElementWithClassName('div', 'time'); |
107 var entryBox = createElementWithClassName('div', 'entry-box'); | 106 var entryBox = createElementWithClassName('label', 'entry-box'); |
108 var domain = createElementWithClassName('div', 'domain'); | 107 var domain = createElementWithClassName('div', 'domain'); |
| 108 |
109 var dropDown = createElementWithClassName('button', 'drop-down'); | 109 var dropDown = createElementWithClassName('button', 'drop-down'); |
110 dropDown.value = 'Open action menu'; | 110 dropDown.value = 'Open action menu'; |
111 dropDown.title = localStrings.getString('actionMenuDescription'); | 111 dropDown.title = localStrings.getString('actionMenuDescription'); |
112 dropDown.setAttribute('menu', '#action-menu'); | 112 dropDown.setAttribute('menu', '#action-menu'); |
113 cr.ui.decorate(dropDown, MenuButton); | 113 cr.ui.decorate(dropDown, MenuButton); |
114 | 114 |
| 115 // Checkbox is always created, but only visible on hover & when checked. |
| 116 var checkbox = document.createElement('input'); |
| 117 checkbox.type = 'checkbox'; |
| 118 checkbox.id = 'checkbox-' + this.id_; |
| 119 checkbox.time = this.time.getTime(); |
| 120 checkbox.addEventListener('click', checkboxClicked); |
| 121 time.appendChild(checkbox); |
| 122 |
115 // Keep track of the drop down that triggered the menu, so we know | 123 // Keep track of the drop down that triggered the menu, so we know |
116 // which element to apply the command to. | 124 // which element to apply the command to. |
117 // TODO(dubroy): Ideally we'd use 'activate', but MenuButton swallows it. | 125 // TODO(dubroy): Ideally we'd use 'activate', but MenuButton swallows it. |
118 var self = this; | 126 var self = this; |
119 var setActivePage = function(e) { | 127 var setActivePage = function(e) { |
120 activePage = self; | 128 activePage = self; |
121 }; | 129 }; |
122 dropDown.addEventListener('mousedown', setActivePage); | 130 dropDown.addEventListener('mousedown', setActivePage); |
123 dropDown.addEventListener('focus', setActivePage); | 131 dropDown.addEventListener('focus', setActivePage); |
124 | 132 |
125 domain.style.backgroundImage = | 133 domain.style.backgroundImage = |
126 'url(chrome://favicon/' + encodeURIForCSS(this.url_) + ')'; | 134 'url(chrome://favicon/' + encodeURIForCSS(this.url_) + ')'; |
127 domain.textContent = this.domain_; | 135 domain.textContent = this.domain_; |
128 // Use a wrapper div so that the entry contents will be shinkwrapped. | 136 |
| 137 // Clicking anywhere in the entryBox will check/uncheck the checkbox. |
| 138 entryBox.setAttribute('for', checkbox.id); |
| 139 entryBox.addEventListener('mousedown', entryBoxMousedown, false); |
| 140 |
| 141 // Prevent clicks on the drop down from affecting the checkbox. |
| 142 dropDown.addEventListener('click', function(e) { e.preventDefault(); }); |
| 143 |
| 144 // We use a wrapper div so that the entry contents will be shinkwrapped. |
129 entryBox.appendChild(time); | 145 entryBox.appendChild(time); |
130 entryBox.appendChild(domain); | 146 entryBox.appendChild(domain); |
131 entryBox.appendChild(this.getTitleDOM_()); | 147 entryBox.appendChild(this.getTitleDOM_()); |
132 entryBox.appendChild(dropDown); | 148 entryBox.appendChild(dropDown); |
| 149 |
| 150 // Let the entryBox be styled appropriately when it contains keyboard focus. |
| 151 entryBox.addEventListener('focus', function() { |
| 152 this.classList.add('contains-focus'); |
| 153 }, true); |
| 154 entryBox.addEventListener('blur', function() { |
| 155 this.classList.remove('contains-focus'); |
| 156 }, true); |
| 157 |
133 node.appendChild(entryBox); | 158 node.appendChild(entryBox); |
134 | 159 |
135 if (searchResultFlag) { | 160 if (searchResultFlag) { |
136 time.textContent = this.dateShort; | 161 time.textContent = this.dateShort; |
137 var snippet = createElementWithClassName('div', 'snippet'); | 162 var snippet = createElementWithClassName('div', 'snippet'); |
138 this.addHighlightedText_(snippet, | 163 this.addHighlightedText_(snippet, |
139 this.snippet_, | 164 this.snippet_, |
140 this.model_.getSearchText()); | 165 this.model_.getSearchText()); |
141 node.appendChild(snippet); | 166 node.appendChild(snippet); |
142 } else { | 167 } else { |
143 if (this.model_.getEditMode()) { | |
144 var checkbox = document.createElement('input'); | |
145 checkbox.type = 'checkbox'; | |
146 checkbox.name = this.id_; | |
147 checkbox.time = this.time.toString(); | |
148 checkbox.addEventListener("click", checkboxClicked); | |
149 idToCheckbox[this.id_] = checkbox; | |
150 time.appendChild(checkbox); | |
151 } | |
152 time.appendChild(document.createTextNode(this.dateTimeOfDay)); | 168 time.appendChild(document.createTextNode(this.dateTimeOfDay)); |
153 } | 169 } |
154 | 170 |
155 if (typeof this.domNode_ != 'undefined') { | 171 if (typeof this.domNode_ != 'undefined') { |
156 console.error('Already generated node for page.'); | 172 console.error('Already generated node for page.'); |
157 } | 173 } |
158 this.domNode_ = node; | 174 this.domNode_ = node; |
159 | 175 |
160 return node; | 176 return node; |
161 }; | 177 }; |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
227 }; | 243 }; |
228 | 244 |
229 /** | 245 /** |
230 * Launch a search for more history entries from the same domain. | 246 * Launch a search for more history entries from the same domain. |
231 */ | 247 */ |
232 Page.prototype.showMoreFromSite_ = function() { | 248 Page.prototype.showMoreFromSite_ = function() { |
233 setSearch(this.domain_); | 249 setSearch(this.domain_); |
234 }; | 250 }; |
235 | 251 |
236 /** | 252 /** |
237 * Remove the page from the history. | 253 * Remove a single entry from the history. |
238 */ | 254 */ |
239 Page.prototype.removeFromHistory_ = function() { | 255 Page.prototype.removeFromHistory_ = function() { |
240 var self = this; | 256 var self = this; |
241 var onSuccessCallback = function() { | 257 var onSuccessCallback = function() { |
242 removeEntryFromView(self.domNode_); | 258 removeEntryFromView(self.domNode_); |
243 }; | 259 }; |
244 queueURLsForDeletion(this.time, [this.url_], onSuccessCallback); | 260 queueURLsForDeletion(this.time, [this.url_], onSuccessCallback); |
245 deleteNextInQueue(); | 261 deleteNextInQueue(); |
246 }; | 262 }; |
247 | 263 |
(...skipping 16 matching lines...) Expand all Loading... |
264 * allowing the creation of a HistoryModel for each search string, allowing | 280 * allowing the creation of a HistoryModel for each search string, allowing |
265 * quick flips back and forth between results. | 281 * quick flips back and forth between results. |
266 * | 282 * |
267 * The history model is based around pages, and only fetching the data to | 283 * The history model is based around pages, and only fetching the data to |
268 * fill the currently requested page. This is somewhat dependent on the view, | 284 * fill the currently requested page. This is somewhat dependent on the view, |
269 * and so future work may wish to change history model to operate on | 285 * and so future work may wish to change history model to operate on |
270 * timeframe (day or week) based containers. | 286 * timeframe (day or week) based containers. |
271 */ | 287 */ |
272 function HistoryModel() { | 288 function HistoryModel() { |
273 this.clearModel_(); | 289 this.clearModel_(); |
274 this.setEditMode(false); | |
275 this.view_; | |
276 } | 290 } |
277 | 291 |
278 // HistoryModel, Public: ------------------------------------------------------ | 292 // HistoryModel, Public: ------------------------------------------------------ |
279 /** | 293 /** |
280 * Sets our current view that is called when the history model changes. | 294 * Sets our current view that is called when the history model changes. |
281 * @param {HistoryView} view The view to set our current view to. | 295 * @param {HistoryView} view The view to set our current view to. |
282 */ | 296 */ |
283 HistoryModel.prototype.setView = function(view) { | 297 HistoryModel.prototype.setView = function(view) { |
284 this.view_ = view; | 298 this.view_ = view; |
285 }; | 299 }; |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
393 * @return {Array} A list of pages | 407 * @return {Array} A list of pages |
394 */ | 408 */ |
395 HistoryModel.prototype.getNumberedRange = function(start, end) { | 409 HistoryModel.prototype.getNumberedRange = function(start, end) { |
396 if (start >= this.getSize()) | 410 if (start >= this.getSize()) |
397 return []; | 411 return []; |
398 | 412 |
399 var end = end > this.getSize() ? this.getSize() : end; | 413 var end = end > this.getSize() ? this.getSize() : end; |
400 return this.pages_.slice(start, end); | 414 return this.pages_.slice(start, end); |
401 }; | 415 }; |
402 | 416 |
403 /** | |
404 * @return {boolean} Whether we are in edit mode where history items can be | |
405 * deleted | |
406 */ | |
407 HistoryModel.prototype.getEditMode = function() { | |
408 return this.editMode_; | |
409 }; | |
410 | |
411 /** | |
412 * @param {boolean} edit_mode Control whether we are in edit mode. | |
413 */ | |
414 HistoryModel.prototype.setEditMode = function(edit_mode) { | |
415 this.editMode_ = edit_mode; | |
416 }; | |
417 | |
418 // HistoryModel, Private: ----------------------------------------------------- | 417 // HistoryModel, Private: ----------------------------------------------------- |
419 HistoryModel.prototype.clearModel_ = function() { | 418 HistoryModel.prototype.clearModel_ = function() { |
420 this.inFlight_ = false; // Whether a query is inflight. | 419 this.inFlight_ = false; // Whether a query is inflight. |
421 this.searchText_ = ''; | 420 this.searchText_ = ''; |
422 this.searchDepth_ = 0; | 421 this.searchDepth_ = 0; |
423 this.pages_ = []; // Date-sorted list of pages. | 422 this.pages_ = []; // Date-sorted list of pages. |
424 this.last_id_ = 0; | 423 this.last_id_ = 0; |
425 selectionAnchor = -1; | 424 selectionAnchor = -1; |
426 idToCheckbox = []; | |
427 | 425 |
428 // The page that the view wants to see - we only fetch slightly past this | 426 // The page that the view wants to see - we only fetch slightly past this |
429 // point. If the view requests a page that we don't have data for, we try | 427 // point. If the view requests a page that we don't have data for, we try |
430 // to fetch it and call back when we're done. | 428 // to fetch it and call back when we're done. |
431 this.requestedPage_ = 0; | 429 this.requestedPage_ = 0; |
432 | 430 |
433 this.complete_ = false; | 431 this.complete_ = false; |
434 | 432 |
435 if (this.view_) { | 433 if (this.view_) { |
436 this.view_.clear_(); | 434 this.view_.clear_(); |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
526 this.lastDisplayed_ = []; | 524 this.lastDisplayed_ = []; |
527 | 525 |
528 this.model_.setView(this); | 526 this.model_.setView(this); |
529 | 527 |
530 this.currentPages_ = []; | 528 this.currentPages_ = []; |
531 | 529 |
532 var self = this; | 530 var self = this; |
533 window.onresize = function() { | 531 window.onresize = function() { |
534 self.updateEntryAnchorWidth_(); | 532 self.updateEntryAnchorWidth_(); |
535 }; | 533 }; |
536 self.updateEditControls_(); | |
537 | 534 |
538 this.boundUpdateRemoveButton_ = function(e) { | 535 $('clear-browsing-data').addEventListener('click', openClearBrowsingData); |
539 return self.updateRemoveButton_(e); | 536 $('remove-selected').addEventListener('click', removeItems); |
540 }; | |
541 } | 537 } |
542 | 538 |
543 // HistoryView, public: ------------------------------------------------------- | 539 // HistoryView, public: ------------------------------------------------------- |
544 /** | 540 /** |
545 * Do a search and optionally view a certain page. | 541 * Do a search and optionally view a certain page. |
546 * @param {string} term The string to search for. | 542 * @param {string} term The string to search for. |
547 * @param {number} opt_page The page we wish to view, only use this for | 543 * @param {number} opt_page The page we wish to view, only use this for |
548 * setting up initial views, as this triggers a search. | 544 * setting up initial views, as this triggers a search. |
549 */ | 545 */ |
550 HistoryView.prototype.setSearch = function(term, opt_page) { | 546 HistoryView.prototype.setSearch = function(term, opt_page) { |
551 this.pageIndex_ = parseInt(opt_page || 0, 10); | 547 this.pageIndex_ = parseInt(opt_page || 0, 10); |
552 window.scrollTo(0, 0); | 548 window.scrollTo(0, 0); |
553 this.model_.setSearchText(term, this.pageIndex_); | 549 this.model_.setSearchText(term, this.pageIndex_); |
554 if (term) { | 550 pageState.setUIState(term, this.pageIndex_); |
555 this.setEditMode(false); | |
556 } | |
557 this.updateEditControls_(); | |
558 pageState.setUIState(this.model_.getEditMode(), term, this.pageIndex_); | |
559 }; | 551 }; |
560 | 552 |
561 /** | 553 /** |
562 * Controls edit mode where history can be deleted. | |
563 * @param {boolean} edit_mode Whether to enable edit mode. | |
564 */ | |
565 HistoryView.prototype.setEditMode = function(edit_mode) { | |
566 this.model_.setEditMode(edit_mode); | |
567 pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(), | |
568 this.pageIndex_); | |
569 }; | |
570 | |
571 /** | |
572 * Toggles the edit mode and triggers UI update. | |
573 */ | |
574 HistoryView.prototype.toggleEditMode = function() { | |
575 var editMode = !this.model_.getEditMode(); | |
576 this.setEditMode(editMode); | |
577 this.updateEditControls_(); | |
578 }; | |
579 | |
580 /** | |
581 * @return {boolean} Whether we are in edit mode where history items can be | |
582 * deleted | |
583 */ | |
584 HistoryView.prototype.getEditMode = function() { | |
585 return this.model_.getEditMode(); | |
586 }; | |
587 | |
588 /** | |
589 * Reload the current view. | 554 * Reload the current view. |
590 */ | 555 */ |
591 HistoryView.prototype.reload = function() { | 556 HistoryView.prototype.reload = function() { |
592 this.model_.reload(); | 557 this.model_.reload(); |
| 558 this.updateRemoveButton(); |
593 }; | 559 }; |
594 | 560 |
595 /** | 561 /** |
596 * Switch to a specified page. | 562 * Switch to a specified page. |
597 * @param {number} page The page we wish to view. | 563 * @param {number} page The page we wish to view. |
598 */ | 564 */ |
599 HistoryView.prototype.setPage = function(page) { | 565 HistoryView.prototype.setPage = function(page) { |
600 this.clear_(); | 566 this.clear_(); |
601 this.pageIndex_ = parseInt(page, 10); | 567 this.pageIndex_ = parseInt(page, 10); |
602 window.scrollTo(0, 0); | 568 window.scrollTo(0, 0); |
603 this.model_.requestPage(page); | 569 this.model_.requestPage(page); |
604 pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(), | 570 pageState.setUIState(this.model_.getSearchText(), this.pageIndex_); |
605 this.pageIndex_); | |
606 }; | 571 }; |
607 | 572 |
608 /** | 573 /** |
609 * @return {number} The page number being viewed. | 574 * @return {number} The page number being viewed. |
610 */ | 575 */ |
611 HistoryView.prototype.getPage = function() { | 576 HistoryView.prototype.getPage = function() { |
612 return this.pageIndex_; | 577 return this.pageIndex_; |
613 }; | 578 }; |
614 | 579 |
615 /** | 580 /** |
616 * Callback for the history model to let it know that it has data ready for us | 581 * Callback for the history model to let it know that it has data ready for us |
617 * to view. | 582 * to view. |
618 */ | 583 */ |
619 HistoryView.prototype.onModelReady = function() { | 584 HistoryView.prototype.onModelReady = function() { |
620 this.displayResults_(); | 585 this.displayResults_(); |
621 }; | 586 }; |
622 | 587 |
| 588 /** |
| 589 * Enables or disables the 'Remove selected items' button as appropriate. |
| 590 */ |
| 591 HistoryView.prototype.updateRemoveButton = function() { |
| 592 var anyChecked = document.querySelector('.entry input:checked') != null; |
| 593 $('remove-selected').disabled = !anyChecked; |
| 594 } |
| 595 |
623 // HistoryView, private: ------------------------------------------------------ | 596 // HistoryView, private: ------------------------------------------------------ |
624 /** | 597 /** |
625 * Clear the results in the view. Since we add results piecemeal, we need | 598 * Clear the results in the view. Since we add results piecemeal, we need |
626 * to clear them out when we switch to a new page or reload. | 599 * to clear them out when we switch to a new page or reload. |
627 */ | 600 */ |
628 HistoryView.prototype.clear_ = function() { | 601 HistoryView.prototype.clear_ = function() { |
629 this.resultDiv_.textContent = ''; | 602 this.resultDiv_.textContent = ''; |
630 | 603 |
631 var pages = this.currentPages_; | 604 var pages = this.currentPages_; |
632 for (var i = 0; i < pages.length; i++) { | 605 for (var i = 0; i < pages.length; i++) { |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
714 var searchText = this.model_.getSearchText(); | 687 var searchText = this.model_.getSearchText(); |
715 if (searchText != '') { | 688 if (searchText != '') { |
716 this.summaryTd_.textContent = localStrings.getStringF('searchresultsfor', | 689 this.summaryTd_.textContent = localStrings.getStringF('searchresultsfor', |
717 searchText); | 690 searchText); |
718 } else { | 691 } else { |
719 this.summaryTd_.textContent = localStrings.getString('history'); | 692 this.summaryTd_.textContent = localStrings.getString('history'); |
720 } | 693 } |
721 }; | 694 }; |
722 | 695 |
723 /** | 696 /** |
724 * Update the widgets related to edit mode. | |
725 */ | |
726 HistoryView.prototype.updateEditControls_ = function() { | |
727 // Display a button (looking like a link) to enable/disable edit mode. | |
728 var oldButton = this.editButtonTd_.firstChild; | |
729 if (this.model_.getSearchText()) { | |
730 this.editButtonTd_.replaceChild(document.createElement('p'), oldButton); | |
731 this.editingControlsDiv_.textContent = ''; | |
732 return; | |
733 } | |
734 | |
735 var editMode = this.model_.getEditMode(); | |
736 var button = createElementWithClassName('button', 'edit-button'); | |
737 button.onclick = toggleEditMode; | |
738 button.textContent = localStrings.getString(editMode ? | |
739 'doneediting' : 'edithistory'); | |
740 this.editButtonTd_.replaceChild(button, oldButton); | |
741 | |
742 this.editingControlsDiv_.textContent = ''; | |
743 | |
744 if (editMode) { | |
745 // Button to delete the selected items. | |
746 button = document.createElement('button'); | |
747 button.onclick = removeItems; | |
748 button.textContent = localStrings.getString('removeselected'); | |
749 button.disabled = true; | |
750 this.editingControlsDiv_.appendChild(button); | |
751 this.removeButton_ = button; | |
752 | |
753 // Button that opens up the clear browsing data dialog. | |
754 button = document.createElement('button'); | |
755 button.onclick = openClearBrowsingData; | |
756 button.textContent = localStrings.getString('clearallhistory'); | |
757 this.editingControlsDiv_.appendChild(button); | |
758 | |
759 // Listen for clicks in the page to sync the disabled state. | |
760 document.addEventListener('click', this.boundUpdateRemoveButton_); | |
761 } else { | |
762 this.removeButton_ = null; | |
763 document.removeEventListener('click', this.boundUpdateRemoveButton_); | |
764 } | |
765 }; | |
766 | |
767 /** | |
768 * Updates the disabled state of the remove button when in editing mode. | |
769 * @param {!Event} e The click event object. | |
770 * @private | |
771 */ | |
772 HistoryView.prototype.updateRemoveButton_ = function(e) { | |
773 if (e.target.tagName != 'INPUT') | |
774 return; | |
775 | |
776 var anyChecked = document.querySelector('.entry input:checked') != null; | |
777 if (this.removeButton_) | |
778 this.removeButton_.disabled = !anyChecked; | |
779 }; | |
780 | |
781 /** | |
782 * Update the pagination tools. | 697 * Update the pagination tools. |
783 */ | 698 */ |
784 HistoryView.prototype.displayNavBar_ = function() { | 699 HistoryView.prototype.displayNavBar_ = function() { |
785 this.pageDiv_.textContent = ''; | 700 this.pageDiv_.textContent = ''; |
786 | 701 |
787 if (this.pageIndex_ > 0) { | 702 if (this.pageIndex_ > 0) { |
788 this.pageDiv_.appendChild( | 703 this.pageDiv_.appendChild( |
789 this.createPageNav_(0, localStrings.getString('newest'))); | 704 this.createPageNav_(0, localStrings.getString('newest'))); |
790 this.pageDiv_.appendChild( | 705 this.pageDiv_.appendChild( |
791 this.createPageNav_(this.pageIndex_ - 1, | 706 this.createPageNav_(this.pageIndex_ - 1, |
(...skipping 12 matching lines...) Expand all Loading... |
804 /** | 719 /** |
805 * Make a DOM object representation of a page navigation link. | 720 * Make a DOM object representation of a page navigation link. |
806 * @param {number} page The page index the navigation element should link to | 721 * @param {number} page The page index the navigation element should link to |
807 * @param {string} name The text content of the link | 722 * @param {string} name The text content of the link |
808 * @return {HTMLAnchorElement} the pagination link | 723 * @return {HTMLAnchorElement} the pagination link |
809 */ | 724 */ |
810 HistoryView.prototype.createPageNav_ = function(page, name) { | 725 HistoryView.prototype.createPageNav_ = function(page, name) { |
811 anchor = document.createElement('a'); | 726 anchor = document.createElement('a'); |
812 anchor.className = 'page-navigation'; | 727 anchor.className = 'page-navigation'; |
813 anchor.textContent = name; | 728 anchor.textContent = name; |
814 var hashString = PageState.getHashString(this.model_.getEditMode(), | 729 var hashString = PageState.getHashString(this.model_.getSearchText(), page); |
815 this.model_.getSearchText(), page); | |
816 var link = 'chrome://history2/' + (hashString ? '#' + hashString : ''); | 730 var link = 'chrome://history2/' + (hashString ? '#' + hashString : ''); |
817 anchor.href = link; | 731 anchor.href = link; |
818 anchor.onclick = function() { | 732 anchor.onclick = function() { |
819 setPage(page); | 733 setPage(page); |
820 return false; | 734 return false; |
821 }; | 735 }; |
822 return anchor; | 736 return anchor; |
823 }; | 737 }; |
824 | 738 |
825 /** | 739 /** |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
914 return result; | 828 return result; |
915 }; | 829 }; |
916 | 830 |
917 /** | 831 /** |
918 * Set the hash to a specified state, this will create an entry in the | 832 * Set the hash to a specified state, this will create an entry in the |
919 * session history so the back button cycles through hash states, which | 833 * session history so the back button cycles through hash states, which |
920 * are then picked up by our listener. | 834 * are then picked up by our listener. |
921 * @param {string} term The current search string. | 835 * @param {string} term The current search string. |
922 * @param {string} page The page currently being viewed. | 836 * @param {string} page The page currently being viewed. |
923 */ | 837 */ |
924 PageState.prototype.setUIState = function(editMode, term, page) { | 838 PageState.prototype.setUIState = function(term, page) { |
925 // Make sure the form looks pretty. | 839 // Make sure the form looks pretty. |
926 document.forms[0].term.value = term; | 840 document.forms[0].term.value = term; |
927 var currentHash = this.getHashData(); | 841 var currentHash = this.getHashData(); |
928 if (Boolean(currentHash.e) != editMode || currentHash.q != term || | 842 if (currentHash.q != term || currentHash.p != page) { |
929 currentHash.p != page) { | 843 window.location.hash = PageState.getHashString(term, page); |
930 window.location.hash = PageState.getHashString(editMode, term, page); | |
931 } | 844 } |
932 }; | 845 }; |
933 | 846 |
934 /** | 847 /** |
935 * Static method to get the hash string for a specified state | 848 * Static method to get the hash string for a specified state |
936 * @param {string} term The current search string. | 849 * @param {string} term The current search string. |
937 * @param {string} page The page currently being viewed. | 850 * @param {string} page The page currently being viewed. |
938 * @return {string} The string to be used in a hash. | 851 * @return {string} The string to be used in a hash. |
939 */ | 852 */ |
940 PageState.getHashString = function(editMode, term, page) { | 853 PageState.getHashString = function(term, page) { |
941 var newHash = []; | 854 var newHash = []; |
942 if (editMode) { | |
943 newHash.push('e=1'); | |
944 } | |
945 if (term) { | 855 if (term) { |
946 newHash.push('q=' + encodeURIComponent(term)); | 856 newHash.push('q=' + encodeURIComponent(term)); |
947 } | 857 } |
948 if (page != undefined) { | 858 if (page != undefined) { |
949 newHash.push('p=' + page); | 859 newHash.push('p=' + page); |
950 } | 860 } |
951 | 861 |
952 return newHash.join('&'); | 862 return newHash.join('&'); |
953 }; | 863 }; |
954 | 864 |
955 /////////////////////////////////////////////////////////////////////////////// | 865 /////////////////////////////////////////////////////////////////////////////// |
956 // Document Functions: | 866 // Document Functions: |
957 /** | 867 /** |
958 * Window onload handler, sets up the page. | 868 * Window onload handler, sets up the page. |
959 */ | 869 */ |
960 function load() { | 870 function load() { |
961 $('term').focus(); | 871 $('term').focus(); |
962 | 872 |
963 localStrings = new LocalStrings(); | 873 localStrings = new LocalStrings(); |
964 historyModel = new HistoryModel(); | 874 historyModel = new HistoryModel(); |
965 historyView = new HistoryView(historyModel); | 875 historyView = new HistoryView(historyModel); |
966 pageState = new PageState(historyModel, historyView); | 876 pageState = new PageState(historyModel, historyView); |
967 | 877 |
968 // Create default view. | 878 // Create default view. |
969 var hashData = pageState.getHashData(); | 879 var hashData = pageState.getHashData(); |
970 if (Boolean(hashData.e)) { | |
971 historyView.toggleEditMode(); | |
972 } | |
973 historyView.setSearch(hashData.q, hashData.p); | 880 historyView.setSearch(hashData.q, hashData.p); |
974 | 881 |
975 // Setup click handlers. | 882 // Setup click handlers. |
976 $('history-section').onclick = function () { | 883 $('history-section').onclick = function () { |
977 setSearch(''); | 884 setSearch(''); |
978 return false; | 885 return false; |
979 }; | 886 }; |
980 $('search-form').onsubmit = function () { | 887 $('search-form').onsubmit = function () { |
981 setSearch(this.term.value); | 888 setSearch(this.term.value); |
982 return false; | 889 return false; |
(...skipping 25 matching lines...) Expand all Loading... |
1008 * Set the history view to a specified page. | 915 * Set the history view to a specified page. |
1009 * @param {number} page The page to set the view to. | 916 * @param {number} page The page to set the view to. |
1010 */ | 917 */ |
1011 function setPage(page) { | 918 function setPage(page) { |
1012 if (historyView) { | 919 if (historyView) { |
1013 historyView.setPage(page); | 920 historyView.setPage(page); |
1014 } | 921 } |
1015 } | 922 } |
1016 | 923 |
1017 /** | 924 /** |
1018 * TODO(glen): Get rid of this function. | |
1019 * Toggles edit mode. | |
1020 */ | |
1021 function toggleEditMode() { | |
1022 if (historyView) { | |
1023 historyView.toggleEditMode(); | |
1024 historyView.reload(); | |
1025 } | |
1026 } | |
1027 | |
1028 /** | |
1029 * Delete the next item in our deletion queue. | 925 * Delete the next item in our deletion queue. |
1030 */ | 926 */ |
1031 function deleteNextInQueue() { | 927 function deleteNextInQueue() { |
1032 if (deleteQueue.length > 0) { | 928 if (deleteQueue.length > 0) { |
1033 // Call the native function to remove history entries. | 929 // Call the native function to remove history entries. |
1034 // First arg is a time in seconds (passed as String) identifying the day. | 930 // First arg is a time in seconds (passed as String) identifying the day. |
1035 // Remaining args are URLs of history entries from that day to delete. | 931 // Remaining args are URLs of history entries from that day to delete. |
1036 var timeInSeconds = Math.floor(deleteQueue[0].date.getTime() / 1000); | 932 var timeInSeconds = Math.floor(deleteQueue[0].date.getTime() / 1000); |
1037 chrome.send('removeURLsOnOneDay', | 933 chrome.send('removeURLsOnOneDay', |
1038 [String(timeInSeconds)].concat(deleteQueue[0].urls)); | 934 [String(timeInSeconds)].concat(deleteQueue[0].urls)); |
(...skipping 12 matching lines...) Expand all Loading... |
1051 * Queue a set of URLs from the same day for deletion. | 947 * Queue a set of URLs from the same day for deletion. |
1052 * @param {Date} date A date indicating the day the URLs were visited. | 948 * @param {Date} date A date indicating the day the URLs were visited. |
1053 * @param {Array} urls Array of URLs from the same day to be deleted. | 949 * @param {Array} urls Array of URLs from the same day to be deleted. |
1054 * @param {Function} opt_callback An optional callback to be executed when | 950 * @param {Function} opt_callback An optional callback to be executed when |
1055 * the deletion is complete. | 951 * the deletion is complete. |
1056 */ | 952 */ |
1057 function queueURLsForDeletion(date, urls, opt_callback) { | 953 function queueURLsForDeletion(date, urls, opt_callback) { |
1058 deleteQueue.push({ 'date': date, 'urls': urls, 'callback': opt_callback }); | 954 deleteQueue.push({ 'date': date, 'urls': urls, 'callback': opt_callback }); |
1059 } | 955 } |
1060 | 956 |
| 957 function reloadHistory() { |
| 958 historyView.reload(); |
| 959 } |
| 960 |
1061 /** | 961 /** |
1062 * Collect IDs from checked checkboxes and send to Chrome for deletion. | 962 * Collect IDs from checked checkboxes and send to Chrome for deletion. |
1063 */ | 963 */ |
1064 function removeItems() { | 964 function removeItems() { |
1065 var checked = document.querySelectorAll( | 965 var checked = document.querySelectorAll( |
1066 'input[type=checkbox]:checked:not([disabled])'); | 966 'input[type=checkbox]:checked:not([disabled])'); |
1067 var urls = []; | 967 var urls = []; |
1068 var disabledItems = []; | 968 var disabledItems = []; |
1069 var queue = []; | 969 var queue = []; |
1070 var date = new Date(); | 970 var date = new Date(); |
1071 | 971 |
1072 for (var i = 0; i < checked.length; i++) { | 972 for (var i = 0; i < checked.length; i++) { |
1073 var checkbox = checked[i]; | 973 var checkbox = checked[i]; |
1074 var cbDate = new Date(checkbox.time); | 974 var cbDate = new Date(checkbox.time); |
1075 if (date.getFullYear() != cbDate.getFullYear() || | 975 if (date.getFullYear() != cbDate.getFullYear() || |
1076 date.getMonth() != cbDate.getMonth() || | 976 date.getMonth() != cbDate.getMonth() || |
1077 date.getDate() != cbDate.getDate()) { | 977 date.getDate() != cbDate.getDate()) { |
1078 if (urls.length > 0) { | 978 if (urls.length > 0) { |
1079 queue.push([date, urls]); | 979 queue.push([date, urls]); |
1080 } | 980 } |
1081 urls = []; | 981 urls = []; |
1082 date = cbDate; | 982 date = cbDate; |
1083 } | 983 } |
1084 var link = $('id-' + checkbox.name); | 984 var link = checkbox.parentNode.parentNode.querySelector('a'); |
1085 checkbox.disabled = true; | 985 checkbox.disabled = true; |
1086 link.classList.add('to-be-removed'); | 986 link.classList.add('to-be-removed'); |
1087 disabledItems.push(checkbox); | 987 disabledItems.push(checkbox); |
1088 urls.push(link.href); | 988 urls.push(link.href); |
1089 } | 989 } |
1090 if (urls.length > 0) { | 990 if (urls.length > 0) { |
1091 queue.push([date, urls]); | 991 queue.push([date, urls]); |
1092 } | 992 } |
1093 if (checked.length > 0 && confirm(localStrings.getString('deletewarning'))) { | 993 if (checked.length > 0 && confirm(localStrings.getString('deletewarning'))) { |
1094 for (var i = 0; i < queue.length; i++) { | 994 for (var i = 0; i < queue.length; i++) { |
1095 queueURLsForDeletion(queue[i][0], queue[i][1]); | 995 // Reload the page when the final entry has been deleted. |
| 996 var callback = i == 0 ? reloadHistory : null; |
| 997 |
| 998 queueURLsForDeletion(queue[i][0], queue[i][1], callback); |
1096 } | 999 } |
1097 deleteNextInQueue(); | 1000 deleteNextInQueue(); |
1098 } else { | 1001 } else { |
1099 // If the remove is cancelled, return the checkboxes to their | 1002 // If the remove is cancelled, return the checkboxes to their |
1100 // enabled, non-line-through state. | 1003 // enabled, non-line-through state. |
1101 for (var i = 0; i < disabledItems.length; i++) { | 1004 for (var i = 0; i < disabledItems.length; i++) { |
1102 var link = $('id-' + disabledItems[i].name); | 1005 var checkbox = disabledItems[i]; |
1103 disabledItems[i].disabled = false; | 1006 var link = checkbox.parentNode.parentNode.querySelector('a'); |
| 1007 checkbox.disabled = false; |
1104 link.classList.remove('to-be-removed'); | 1008 link.classList.remove('to-be-removed'); |
1105 } | 1009 } |
1106 } | 1010 } |
1107 return false; | 1011 return false; |
1108 } | 1012 } |
1109 | 1013 |
1110 /** | 1014 /** |
1111 * Toggle state of checkbox and handle Shift modifier. | 1015 * Toggle state of checkbox and handle Shift modifier. |
1112 */ | 1016 */ |
1113 function checkboxClicked(event) { | 1017 function checkboxClicked(event) { |
| 1018 var id = Number(this.id.slice("checkbox-".length)); |
1114 if (event.shiftKey && (selectionAnchor != -1)) { | 1019 if (event.shiftKey && (selectionAnchor != -1)) { |
1115 var checked = this.checked; | 1020 var checked = this.checked; |
1116 // Set all checkboxes from the anchor up to the clicked checkbox to the | 1021 // Set all checkboxes from the anchor up to the clicked checkbox to the |
1117 // state of the clicked one. | 1022 // state of the clicked one. |
1118 var begin = Math.min(this.name, selectionAnchor); | 1023 var begin = Math.min(id, selectionAnchor); |
1119 var end = Math.max(this.name, selectionAnchor); | 1024 var end = Math.max(id, selectionAnchor); |
1120 for (var i = begin; i <= end; i++) { | 1025 for (var i = begin; i <= end; i++) { |
1121 idToCheckbox[i].checked = checked; | 1026 var checkbox = document.querySelector('#checkbox-' + i); |
| 1027 if (checkbox) |
| 1028 checkbox.checked = checked; |
1122 } | 1029 } |
1123 } | 1030 } |
1124 selectionAnchor = this.name; | 1031 selectionAnchor = id; |
1125 this.focus(); | 1032 |
| 1033 historyView.updateRemoveButton(); |
| 1034 } |
| 1035 |
| 1036 function entryBoxMousedown(event) { |
| 1037 // Prevent text selection when shift-clicking to select multiple entries. |
| 1038 if (event.shiftKey) { |
| 1039 event.preventDefault(); |
| 1040 } |
| 1041 } |
| 1042 |
| 1043 function removeNode(node) { |
| 1044 node.classList.add('fade-out'); // Trigger CSS fade out animation. |
| 1045 |
| 1046 // Delete the node when the animation is complete. |
| 1047 node.addEventListener('webkitTransitionEnd', function() { |
| 1048 node.parentNode.removeChild(node); |
| 1049 }); |
1126 } | 1050 } |
1127 | 1051 |
1128 /** | 1052 /** |
1129 * Removes a single entry from the view. Also removes gaps before and after | 1053 * Removes a single entry from the view. Also removes gaps before and after |
1130 * entry if necessary. | 1054 * entry if necessary. |
1131 */ | 1055 */ |
1132 function removeEntryFromView(entry) { | 1056 function removeEntryFromView(entry) { |
1133 var nextEntry = entry.nextSibling; | 1057 var nextEntry = entry.nextSibling; |
1134 var previousEntry = entry.previousSibling; | 1058 var previousEntry = entry.previousSibling; |
1135 entry.parentNode.removeChild(entry); | 1059 |
| 1060 removeNode(entry); |
1136 | 1061 |
1137 // if there is no previous entry, and the next entry is a gap, remove it | 1062 // if there is no previous entry, and the next entry is a gap, remove it |
1138 if (!previousEntry && nextEntry && nextEntry.className == 'gap') { | 1063 if (!previousEntry && nextEntry && nextEntry.className == 'gap') { |
1139 nextEntry.parentNode.removeChild(nextEntry); | 1064 removeNode(nextEntry); |
1140 } | 1065 } |
1141 | 1066 |
1142 // if there is no next entry, and the previous entry is a gap, remove it | 1067 // if there is no next entry, and the previous entry is a gap, remove it |
1143 if (!nextEntry && previousEntry && previousEntry.className == 'gap') { | 1068 if (!nextEntry && previousEntry && previousEntry.className == 'gap') { |
1144 previousEntry.parentNode.removeChild(previousEntry); | 1069 removeNode(previousEntry); |
1145 } | 1070 } |
1146 | 1071 |
1147 // if both the next and previous entries are gaps, remove one | 1072 // if both the next and previous entries are gaps, remove one |
1148 if (nextEntry && nextEntry.className == 'gap' && | 1073 if (nextEntry && nextEntry.className == 'gap' && |
1149 previousEntry && previousEntry.className == 'gap') { | 1074 previousEntry && previousEntry.className == 'gap') { |
1150 nextEntry.parentNode.removeChild(nextEntry); | 1075 removeNode(nextEntry); |
1151 } | 1076 } |
1152 } | 1077 } |
1153 | 1078 |
1154 /////////////////////////////////////////////////////////////////////////////// | 1079 /////////////////////////////////////////////////////////////////////////////// |
1155 // Chrome callbacks: | 1080 // Chrome callbacks: |
1156 /** | 1081 /** |
1157 * Our history system calls this function with results from searches. | 1082 * Our history system calls this function with results from searches. |
1158 */ | 1083 */ |
1159 function historyResult(info, results) { | 1084 function historyResult(info, results) { |
1160 historyModel.addResults(info, results); | 1085 historyModel.addResults(info, results); |
1161 } | 1086 } |
1162 | 1087 |
1163 /** | 1088 /** |
1164 * Our history system calls this function when a deletion has finished. | 1089 * Our history system calls this function when a deletion has finished. |
1165 */ | 1090 */ |
1166 function deleteComplete() { | 1091 function deleteComplete() { |
1167 if (deleteQueue.length > 0) { | 1092 if (deleteQueue.length > 0) { |
1168 // Remove the successfully deleted entry from the queue. | 1093 // Remove the successfully deleted entry from the queue. |
1169 if (deleteQueue[0].callback) | 1094 if (deleteQueue[0].callback) |
1170 deleteQueue[0].callback(); | 1095 deleteQueue[0].callback.apply(); |
1171 deleteQueue.splice(0, 1); | 1096 deleteQueue.splice(0, 1); |
1172 deleteNextInQueue(); | 1097 deleteNextInQueue(); |
1173 } else { | 1098 } else { |
1174 console.error('Received deleteComplete but queue is empty.'); | 1099 console.error('Received deleteComplete but queue is empty.'); |
1175 } | 1100 } |
1176 } | 1101 } |
1177 | 1102 |
1178 /** | 1103 /** |
1179 * Our history system calls this function if a delete is not ready (e.g. | 1104 * Our history system calls this function if a delete is not ready (e.g. |
1180 * another delete is in-progress). | 1105 * another delete is in-progress). |
1181 */ | 1106 */ |
1182 function deleteFailed() { | 1107 function deleteFailed() { |
1183 window.console.log('Delete failed'); | 1108 window.console.log('Delete failed'); |
1184 | 1109 |
1185 // The deletion failed - try again later. | 1110 // The deletion failed - try again later. |
1186 // TODO(dubroy): We should probably give up at some point. | 1111 // TODO(dubroy): We should probably give up at some point. |
1187 setTimeout(deleteNextInQueue, 500); | 1112 setTimeout(deleteNextInQueue, 500); |
1188 } | 1113 } |
1189 | 1114 |
1190 /** | 1115 /** |
1191 * Called when the history is deleted by someone else. | 1116 * Called when the history is deleted by someone else. |
1192 */ | 1117 */ |
1193 function historyDeleted() { | 1118 function historyDeleted() { |
1194 window.console.log('History deleted'); | 1119 window.console.log('History deleted'); |
1195 var anyChecked = document.querySelector('.entry input:checked') != null; | 1120 var anyChecked = document.querySelector('.entry input:checked') != null; |
1196 // Reload the page, unless the user is actively editing. | 1121 // Reload the page, unless the user has any items checked. |
1197 // TODO(dubroy): We should just reload the page & restore the checked items. | 1122 // TODO(dubroy): We should just reload the page & restore the checked items. |
1198 if (!(historyView.getEditMode() && anyChecked)) | 1123 if (!anyChecked) |
1199 historyView.reload(); | 1124 historyView.reload(); |
1200 } | 1125 } |
1201 | 1126 |
1202 // Add handlers to HTML elements. | 1127 // Add handlers to HTML elements. |
1203 document.addEventListener('DOMContentLoaded', load); | 1128 document.addEventListener('DOMContentLoaded', load); |
1204 | 1129 |
1205 // This event lets us enable and disable menu items before the menu is shown. | 1130 // This event lets us enable and disable menu items before the menu is shown. |
1206 document.addEventListener('canExecute', function(e) { | 1131 document.addEventListener('canExecute', function(e) { |
1207 e.canExecute = true; | 1132 e.canExecute = true; |
1208 }); | 1133 }); |
OLD | NEW |