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

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

Issue 8511055: Remove old history UI, and replace with history2. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Suppress bidichecker test failure. Created 9 years, 1 month 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
« no previous file with comments | « chrome/browser/resources/history.html ('k') | chrome/browser/resources/history2.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 14 matching lines...) Expand all
25 return uri.replace(/\(/g, "\\(").replace(/\)/g, "\\)"); 25 return uri.replace(/\(/g, "\\(").replace(/\)/g, "\\)");
26 } 26 }
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 deleteInFlight = false;
36 var selectionAnchor = -1; 35 var selectionAnchor = -1;
37 var id2checkbox = []; 36 var activePage = null;
38 37
38 const MenuButton = cr.ui.MenuButton;
39 const Command = cr.ui.Command;
40 const Menu = cr.ui.Menu;
41
42 function createDropDownBgImage(canvasName, colorSpec) {
43 var ctx = document.getCSSCanvasContext('2d', canvasName, 6, 4);
44 ctx.fillStyle = ctx.strokeStyle = colorSpec;
45 ctx.beginPath();
46 ctx.moveTo(0, 0);
47 ctx.lineTo(6, 0);
48 ctx.lineTo(3, 3);
49 ctx.closePath();
50 ctx.fill();
51 ctx.stroke();
52 return ctx;
53 }
54
55 // Create the canvases to be used as the drop down button background images.
56 var arrow = createDropDownBgImage('drop-down-arrow', 'hsl(214, 91%, 85%)');
57 var hoverArrow = createDropDownBgImage('drop-down-arrow-hover', '#6A86DE');
58 var activeArrow = createDropDownBgImage('drop-down-arrow-active', 'white');
39 59
40 /////////////////////////////////////////////////////////////////////////////// 60 ///////////////////////////////////////////////////////////////////////////////
41 // Page: 61 // Page:
42 /** 62 /**
43 * Class to hold all the information about an entry in our model. 63 * Class to hold all the information about an entry in our model.
44 * @param {Object} result An object containing the page's data. 64 * @param {Object} result An object containing the page's data.
45 * @param {boolean} continued Whether this page is on the same day as the 65 * @param {boolean} continued Whether this page is on the same day as the
46 * page before it 66 * page before it
47 */ 67 */
48 function Page(result, continued, model, id) { 68 function Page(result, continued, model, id) {
49 this.model_ = model; 69 this.model_ = model;
50 this.title_ = result.title; 70 this.title_ = result.title;
51 this.url_ = result.url; 71 this.url_ = result.url;
72 this.domain_ = this.getDomainFromURL_(this.url_);
52 this.starred_ = result.starred; 73 this.starred_ = result.starred;
53 this.snippet_ = result.snippet || ""; 74 this.snippet_ = result.snippet || "";
54 this.id_ = id; 75 this.id_ = id;
55 76
56 this.changed = false; 77 this.changed = false;
57 78
58 this.isRendered = false; 79 this.isRendered = false;
59 80
60 // All the date information is public so that owners can compare properties of 81 // All the date information is public so that owners can compare properties of
61 // two items easily. 82 // two items easily.
62 83
63 // We get the time in seconds, but we want it in milliseconds. 84 // We get the time in seconds, but we want it in milliseconds.
64 this.time = new Date(result.time * 1000); 85 this.time = new Date(result.time * 1000);
65 86
66 // See comment in BrowsingHistoryHandler::QueryComplete - we won't always 87 // See comment in BrowsingHistoryHandler::QueryComplete - we won't always
67 // get all of these. 88 // get all of these.
68 this.dateRelativeDay = result.dateRelativeDay || ""; 89 this.dateRelativeDay = result.dateRelativeDay || "";
69 this.dateTimeOfDay = result.dateTimeOfDay || ""; 90 this.dateTimeOfDay = result.dateTimeOfDay || "";
70 this.dateShort = result.dateShort || ""; 91 this.dateShort = result.dateShort || "";
71 92
72 // Whether this is the continuation of a previous day. 93 // Whether this is the continuation of a previous day.
73 this.continued = continued; 94 this.continued = continued;
74 } 95 }
75 96
76 // Page, Public: -------------------------------------------------------------- 97 // Page, Public: --------------------------------------------------------------
77 /** 98 /**
78 * @return {DOMObject} Gets the DOM representation of the page 99 * Returns a dom structure for a browse page result or a search page result.
79 * for use in browse results. 100 * @param {boolean} Flag to indicate if result is a search result.
101 * @return {Element} The dom structure.
80 */ 102 */
81 Page.prototype.getBrowseResultDOM = function() { 103 Page.prototype.getResultDOM = function(searchResultFlag) {
82 var node = createElementWithClassName('div', 'entry'); 104 var node = createElementWithClassName('li', 'entry');
83 var time = createElementWithClassName('div', 'time'); 105 var time = createElementWithClassName('div', 'time');
84 if (this.model_.getEditMode()) { 106 var entryBox = createElementWithClassName('label', 'entry-box');
85 var checkbox = document.createElement('input'); 107 var domain = createElementWithClassName('div', 'domain');
86 checkbox.type = "checkbox";
87 checkbox.name = this.id_;
88 checkbox.time = this.time.toString();
89 checkbox.addEventListener("click", checkboxClicked, false);
90 id2checkbox[this.id_] = checkbox;
91 time.appendChild(checkbox);
92 }
93 time.appendChild(document.createTextNode(this.dateTimeOfDay));
94 node.appendChild(time);
95 node.appendChild(this.getTitleDOM_());
96 return node;
97 };
98 108
99 /** 109 var dropDown = createElementWithClassName('button', 'drop-down');
100 * @return {DOMObject} Gets the DOM representation of the page for 110 dropDown.value = 'Open action menu';
101 * use in search results. 111 dropDown.title = localStrings.getString('actionMenuDescription');
102 */ 112 dropDown.setAttribute('menu', '#action-menu');
103 Page.prototype.getSearchResultDOM = function() { 113 cr.ui.decorate(dropDown, MenuButton);
104 var row = createElementWithClassName('tr', 'entry'); 114
105 var datecell = createElementWithClassName('td', 'time'); 115 // Checkbox is always created, but only visible on hover & when checked.
106 datecell.appendChild(document.createTextNode(this.dateShort)); 116 var checkbox = document.createElement('input');
107 row.appendChild(datecell); 117 checkbox.type = 'checkbox';
108 if (this.model_.getEditMode()) { 118 checkbox.id = 'checkbox-' + this.id_;
109 var checkbox = document.createElement('input'); 119 checkbox.time = this.time.getTime();
110 checkbox.type = "checkbox"; 120 checkbox.addEventListener('click', checkboxClicked);
111 checkbox.name = this.id_; 121 time.appendChild(checkbox);
112 checkbox.time = this.time.toString(); 122
113 checkbox.addEventListener("click", checkboxClicked, false); 123 // Keep track of the drop down that triggered the menu, so we know
114 id2checkbox[this.id_] = checkbox; 124 // which element to apply the command to.
115 datecell.appendChild(checkbox); 125 // TODO(dubroy): Ideally we'd use 'activate', but MenuButton swallows it.
126 var self = this;
127 var setActivePage = function(e) {
128 activePage = self;
129 };
130 dropDown.addEventListener('mousedown', setActivePage);
131 dropDown.addEventListener('focus', setActivePage);
132
133 domain.style.backgroundImage =
134 'url(chrome://favicon/' + encodeURIForCSS(this.url_) + ')';
135 domain.textContent = this.domain_;
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.
145 entryBox.appendChild(time);
146 entryBox.appendChild(domain);
147 entryBox.appendChild(this.getTitleDOM_());
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
158 node.appendChild(entryBox);
159
160 if (searchResultFlag) {
161 time.textContent = this.dateShort;
162 var snippet = createElementWithClassName('div', 'snippet');
163 this.addHighlightedText_(snippet,
164 this.snippet_,
165 this.model_.getSearchText());
166 node.appendChild(snippet);
167 } else {
168 time.appendChild(document.createTextNode(this.dateTimeOfDay));
116 } 169 }
117 170
118 var titleCell = document.createElement('td'); 171 if (typeof this.domNode_ != 'undefined') {
119 titleCell.valign = 'top'; 172 console.error('Already generated node for page.');
120 titleCell.appendChild(this.getTitleDOM_()); 173 }
121 var snippet = createElementWithClassName('div', 'snippet'); 174 this.domNode_ = node;
122 this.addHighlightedText_(snippet,
123 this.snippet_,
124 this.model_.getSearchText());
125 titleCell.appendChild(snippet);
126 row.appendChild(titleCell);
127 175
128 return row; 176 return node;
129 }; 177 };
130 178
131 // Page, private: ------------------------------------------------------------- 179 // Page, private: -------------------------------------------------------------
132 /** 180 /**
133 * Add child text nodes to a node such that occurrences of the spcified text is 181 * Extracts and returns the domain (and subdomains) from a URL.
134 * highligted. 182 * @param {string} The url
183 * @return (string) The domain. An empty string is returned if no domain can
184 * be found.
185 */
186 Page.prototype.getDomainFromURL_ = function(url) {
187 var domain = url.replace(/^.+:\/\//, '').match(/[^/]+/);
188 return domain ? domain[0] : '';
189 };
190
191 /**
192 * Add child text nodes to a node such that occurrences of the specified text is
193 * highlighted.
135 * @param {Node} node The node under which new text nodes will be made as 194 * @param {Node} node The node under which new text nodes will be made as
136 * children. 195 * children.
137 * @param {string} content Text to be added beneath |node| as one or more 196 * @param {string} content Text to be added beneath |node| as one or more
138 * text nodes. 197 * text nodes.
139 * @param {string} highlightText Occurences of this text inside |content| will 198 * @param {string} highlightText Occurences of this text inside |content| will
140 * be highlighted. 199 * be highlighted.
141 */ 200 */
142 Page.prototype.addHighlightedText_ = function(node, content, highlightText) { 201 Page.prototype.addHighlightedText_ = function(node, content, highlightText) {
143 var i = 0; 202 var i = 0;
144 if (highlightText) { 203 if (highlightText) {
(...skipping 11 matching lines...) Expand all
156 } 215 }
157 } 216 }
158 if (i < content.length) 217 if (i < content.length)
159 node.appendChild(document.createTextNode(content.slice(i))); 218 node.appendChild(document.createTextNode(content.slice(i)));
160 }; 219 };
161 220
162 /** 221 /**
163 * @return {DOMObject} DOM representation for the title block. 222 * @return {DOMObject} DOM representation for the title block.
164 */ 223 */
165 Page.prototype.getTitleDOM_ = function() { 224 Page.prototype.getTitleDOM_ = function() {
166 var node = document.createElement('div'); 225 var node = createElementWithClassName('div', 'title');
167 node.className = 'title';
168 var link = document.createElement('a'); 226 var link = document.createElement('a');
169 link.href = this.url_; 227 link.href = this.url_;
228 link.id = "id-" + this.id_;
170 229
171 link.style.backgroundImage = 230 // Add a tooltip, since it might be ellipsized.
172 'url(chrome://favicon/' + encodeURIForCSS(this.url_) + ')'; 231 // TODO(dubroy): Find a way to show the tooltip only when necessary.
173 link.id = "id-" + this.id_; 232 link.title = this.title_;
233
174 this.addHighlightedText_(link, this.title_, this.model_.getSearchText()); 234 this.addHighlightedText_(link, this.title_, this.model_.getSearchText());
175
176 node.appendChild(link); 235 node.appendChild(link);
177 236
178 if (this.starred_) { 237 if (this.starred_) {
179 node.className += ' starred'; 238 node.className += ' starred';
180 node.appendChild(createElementWithClassName('div', 'starred')); 239 node.appendChild(createElementWithClassName('div', 'starred'));
181 } 240 }
182 241
183 return node; 242 return node;
184 }; 243 };
185 244
245 /**
246 * Launch a search for more history entries from the same domain.
247 */
248 Page.prototype.showMoreFromSite_ = function() {
249 setSearch(this.domain_);
250 };
251
252 /**
253 * Remove a single entry from the history.
254 */
255 Page.prototype.removeFromHistory_ = function() {
256 var self = this;
257 var onSuccessCallback = function() {
258 removeEntryFromView(self.domNode_);
259 };
260 queueURLsForDeletion(this.time, [this.url_], onSuccessCallback);
261 deleteNextInQueue();
262 };
263
264
186 // Page, private, static: ----------------------------------------------------- 265 // Page, private, static: -----------------------------------------------------
187 266
188 /** 267 /**
189 * Quote a string so it can be used in a regular expression. 268 * Quote a string so it can be used in a regular expression.
190 * @param {string} str The source string 269 * @param {string} str The source string
191 * @return {string} The escaped string 270 * @return {string} The escaped string
192 */ 271 */
193 Page.pregQuote_ = function(str) { 272 Page.pregQuote_ = function(str) {
194 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1"); 273 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
195 }; 274 };
196 275
197 /////////////////////////////////////////////////////////////////////////////// 276 ///////////////////////////////////////////////////////////////////////////////
198 // HistoryModel: 277 // HistoryModel:
199 /** 278 /**
200 * Global container for history data. Future optimizations might include 279 * Global container for history data. Future optimizations might include
201 * allowing the creation of a HistoryModel for each search string, allowing 280 * allowing the creation of a HistoryModel for each search string, allowing
202 * quick flips back and forth between results. 281 * quick flips back and forth between results.
203 * 282 *
204 * 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
205 * 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,
206 * 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
207 * timeframe (day or week) based containers. 286 * timeframe (day or week) based containers.
208 */ 287 */
209 function HistoryModel() { 288 function HistoryModel() {
210 this.clearModel_(); 289 this.clearModel_();
211 this.setEditMode(false);
212 this.view_;
213 } 290 }
214 291
215 // HistoryModel, Public: ------------------------------------------------------ 292 // HistoryModel, Public: ------------------------------------------------------
216 /** 293 /**
217 * 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.
218 * @param {HistoryView} view The view to set our current view to. 295 * @param {HistoryView} view The view to set our current view to.
219 */ 296 */
220 HistoryModel.prototype.setView = function(view) { 297 HistoryModel.prototype.setView = function(view) {
221 this.view_ = view; 298 this.view_ = view;
222 }; 299 };
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 * @return {Array} A list of pages 407 * @return {Array} A list of pages
331 */ 408 */
332 HistoryModel.prototype.getNumberedRange = function(start, end) { 409 HistoryModel.prototype.getNumberedRange = function(start, end) {
333 if (start >= this.getSize()) 410 if (start >= this.getSize())
334 return []; 411 return [];
335 412
336 var end = end > this.getSize() ? this.getSize() : end; 413 var end = end > this.getSize() ? this.getSize() : end;
337 return this.pages_.slice(start, end); 414 return this.pages_.slice(start, end);
338 }; 415 };
339 416
340 /**
341 * @return {boolean} Whether we are in edit mode where history items can be
342 * deleted
343 */
344 HistoryModel.prototype.getEditMode = function() {
345 return this.editMode_;
346 };
347
348 /**
349 * @param {boolean} edit_mode Control whether we are in edit mode.
350 */
351 HistoryModel.prototype.setEditMode = function(edit_mode) {
352 this.editMode_ = edit_mode;
353 };
354
355 // HistoryModel, Private: ----------------------------------------------------- 417 // HistoryModel, Private: -----------------------------------------------------
356 HistoryModel.prototype.clearModel_ = function() { 418 HistoryModel.prototype.clearModel_ = function() {
357 this.inFlight_ = false; // Whether a query is inflight. 419 this.inFlight_ = false; // Whether a query is inflight.
358 this.searchText_ = ''; 420 this.searchText_ = '';
359 this.searchDepth_ = 0; 421 this.searchDepth_ = 0;
360 this.pages_ = []; // Date-sorted list of pages. 422 this.pages_ = []; // Date-sorted list of pages.
361 this.last_id_ = 0; 423 this.last_id_ = 0;
362 selectionAnchor = -1; 424 selectionAnchor = -1;
363 id2checkbox = [];
364 425
365 // 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
366 // 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
367 // to fetch it and call back when we're done. 428 // to fetch it and call back when we're done.
368 this.requestedPage_ = 0; 429 this.requestedPage_ = 0;
369 430
370 this.complete_ = false; 431 this.complete_ = false;
371 432
372 if (this.view_) { 433 if (this.view_) {
373 this.view_.clear_(); 434 this.view_.clear_();
374 } 435 }
375 }; 436 };
376 437
377 /** 438 /**
378 * Figure out if we need to do more searches to fill the currently requested 439 * Figure out if we need to do more searches to fill the currently requested
379 * page. If we think we can fill the page, call the view and let it know 440 * page. If we think we can fill the page, call the view and let it know
380 * we're ready to show something. 441 * we're ready to show something.
381 */ 442 */
382 HistoryModel.prototype.updateSearch_ = function(finished) { 443 HistoryModel.prototype.updateSearch_ = function(finished) {
383 if ((this.searchText_ && this.searchDepth_ >= MAX_SEARCH_DEPTH_MONTHS) || 444 if ((this.searchText_ && this.searchDepth_ >= MAX_SEARCH_DEPTH_MONTHS) ||
384 finished) { 445 finished) {
385 // We have maxed out. There will be no more data. 446 // We have maxed out. There will be no more data.
386 this.complete_ = true; 447 this.complete_ = true;
387 this.view_.onModelReady(); 448 this.view_.onModelReady();
388 this.changed = false; 449 this.changed = false;
389 } else { 450 } else {
390 // If we can't fill the requested page, ask for more data unless a request 451 // If we can't fill the requested page, ask for more data unless a request
391 // is still in-flight. 452 // is still in-flight.
392 if (!this.inFlight_ && !this.canFillPage_(this.requestedPage_)) { 453 if (!this.canFillPage_(this.requestedPage_) && !this.inFlight_) {
393 this.getSearchResults_(this.searchDepth_ + 1); 454 this.getSearchResults_(this.searchDepth_ + 1);
394 } 455 }
395 456
396 // If we have any data for the requested page, show it. 457 // If we have any data for the requested page, show it.
397 if (this.changed && this.haveDataForPage_(this.requestedPage_)) { 458 if (this.changed && this.haveDataForPage_(this.requestedPage_)) {
398 this.view_.onModelReady(); 459 this.view_.onModelReady();
399 this.changed = false; 460 this.changed = false;
400 } 461 }
401 } 462 }
402 }; 463 };
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
463 this.lastDisplayed_ = []; 524 this.lastDisplayed_ = [];
464 525
465 this.model_.setView(this); 526 this.model_.setView(this);
466 527
467 this.currentPages_ = []; 528 this.currentPages_ = [];
468 529
469 var self = this; 530 var self = this;
470 window.onresize = function() { 531 window.onresize = function() {
471 self.updateEntryAnchorWidth_(); 532 self.updateEntryAnchorWidth_();
472 }; 533 };
473 this.updateEditControls_();
474 this.editButtonTd_.hidden = true;
475 534
476 this.boundUpdateRemoveButton_ = function(e) { 535 $('clear-browsing-data').addEventListener('click', openClearBrowsingData);
477 return self.updateRemoveButton_(e); 536 $('remove-selected').addEventListener('click', removeItems);
478 };
479 } 537 }
480 538
481 // HistoryView, public: ------------------------------------------------------- 539 // HistoryView, public: -------------------------------------------------------
482 /** 540 /**
483 * Do a search and optionally view a certain page. 541 * Do a search and optionally view a certain page.
484 * @param {string} term The string to search for. 542 * @param {string} term The string to search for.
485 * @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
486 * setting up initial views, as this triggers a search. 544 * setting up initial views, as this triggers a search.
487 */ 545 */
488 HistoryView.prototype.setSearch = function(term, opt_page) { 546 HistoryView.prototype.setSearch = function(term, opt_page) {
489 this.pageIndex_ = parseInt(opt_page || 0, 10); 547 this.pageIndex_ = parseInt(opt_page || 0, 10);
490 window.scrollTo(0, 0); 548 window.scrollTo(0, 0);
491 this.model_.setSearchText(term, this.pageIndex_); 549 this.model_.setSearchText(term, this.pageIndex_);
492 this.updateEditControls_(); 550 pageState.setUIState(term, this.pageIndex_);
493 pageState.setUIState(this.model_.getEditMode(), term, this.pageIndex_);
494 }; 551 };
495 552
496 /** 553 /**
497 * Controls edit mode where history can be deleted.
498 * @param {boolean} edit_mode Whether to enable edit mode.
499 */
500 HistoryView.prototype.setEditMode = function(edit_mode) {
501 this.model_.setEditMode(edit_mode);
502 pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(),
503 this.pageIndex_);
504 };
505
506 /**
507 * Toggles the edit mode and triggers UI update.
508 */
509 HistoryView.prototype.toggleEditMode = function() {
510 var editMode = !this.model_.getEditMode();
511 this.setEditMode(editMode);
512 this.updateEditControls_();
513 };
514
515 /**
516 * @return {boolean} Whether we are in edit mode where history items can be
517 * deleted
518 */
519 HistoryView.prototype.getEditMode = function() {
520 return this.model_.getEditMode();
521 };
522
523 /**
524 * Reload the current view. 554 * Reload the current view.
525 */ 555 */
526 HistoryView.prototype.reload = function() { 556 HistoryView.prototype.reload = function() {
527 this.model_.reload(); 557 this.model_.reload();
558 this.updateRemoveButton();
528 }; 559 };
529 560
530 /** 561 /**
531 * Switch to a specified page. 562 * Switch to a specified page.
532 * @param {number} page The page we wish to view. 563 * @param {number} page The page we wish to view.
533 */ 564 */
534 HistoryView.prototype.setPage = function(page) { 565 HistoryView.prototype.setPage = function(page) {
535 this.clear_(); 566 this.clear_();
536 this.pageIndex_ = parseInt(page, 10); 567 this.pageIndex_ = parseInt(page, 10);
537 window.scrollTo(0, 0); 568 window.scrollTo(0, 0);
538 this.model_.requestPage(page); 569 this.model_.requestPage(page);
539 pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(), 570 pageState.setUIState(this.model_.getSearchText(), this.pageIndex_);
540 this.pageIndex_);
541 }; 571 };
542 572
543 /** 573 /**
544 * @return {number} The page number being viewed. 574 * @return {number} The page number being viewed.
545 */ 575 */
546 HistoryView.prototype.getPage = function() { 576 HistoryView.prototype.getPage = function() {
547 return this.pageIndex_; 577 return this.pageIndex_;
548 }; 578 };
549 579
550 /** 580 /**
551 * 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
552 * to view. 582 * to view.
553 */ 583 */
554 HistoryView.prototype.onModelReady = function() { 584 HistoryView.prototype.onModelReady = function() {
555 this.displayResults_(); 585 this.displayResults_();
556 }; 586 };
557 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
558 // HistoryView, private: ------------------------------------------------------ 596 // HistoryView, private: ------------------------------------------------------
559 /** 597 /**
560 * 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
561 * 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.
562 */ 600 */
563 HistoryView.prototype.clear_ = function() { 601 HistoryView.prototype.clear_ = function() {
564 this.resultDiv_.textContent = ''; 602 this.resultDiv_.textContent = '';
565 603
566 var pages = this.currentPages_; 604 var pages = this.currentPages_;
567 for (var i = 0; i < pages.length; i++) { 605 for (var i = 0; i < pages.length; i++) {
568 pages[i].isRendered = false; 606 pages[i].isRendered = false;
569 } 607 }
570 this.currentPages_ = []; 608 this.currentPages_ = [];
571 }; 609 };
572 610
573 HistoryView.prototype.setPageRendered_ = function(page) { 611 HistoryView.prototype.setPageRendered_ = function(page) {
574 page.isRendered = true; 612 page.isRendered = true;
575 this.currentPages_.push(page); 613 this.currentPages_.push(page);
576 }; 614 };
577 615
578 /** 616 /**
579 * Update the page with results. 617 * Update the page with results.
580 */ 618 */
581 HistoryView.prototype.displayResults_ = function() { 619 HistoryView.prototype.displayResults_ = function() {
582 // Hide the Edit Button if there are no history results to display.
583 this.editButtonTd_.hidden = !this.model_.getSize();
584
585 var results = this.model_.getNumberedRange( 620 var results = this.model_.getNumberedRange(
586 this.pageIndex_ * RESULTS_PER_PAGE, 621 this.pageIndex_ * RESULTS_PER_PAGE,
587 this.pageIndex_ * RESULTS_PER_PAGE + RESULTS_PER_PAGE); 622 this.pageIndex_ * RESULTS_PER_PAGE + RESULTS_PER_PAGE);
588 623
589 if (this.model_.getSearchText()) { 624 if (this.model_.getSearchText()) {
590 var resultTable = createElementWithClassName('table', 'results'); 625 var searchResults = createElementWithClassName('ol', 'search-results');
591 resultTable.cellSpacing = 0;
592 resultTable.cellPadding = 0;
593 resultTable.border = 0;
594
595 for (var i = 0, page; page = results[i]; i++) { 626 for (var i = 0, page; page = results[i]; i++) {
596 if (!page.isRendered) { 627 if (!page.isRendered) {
597 resultTable.appendChild(page.getSearchResultDOM()); 628 searchResults.appendChild(page.getResultDOM(true));
598 this.setPageRendered_(page); 629 this.setPageRendered_(page);
599 } 630 }
600 } 631 }
601 this.resultDiv_.appendChild(resultTable); 632 this.resultDiv_.appendChild(searchResults);
602 } else { 633 } else {
634 var resultsFragment = document.createDocumentFragment();
603 var lastTime = Math.infinity; 635 var lastTime = Math.infinity;
636 var dayResults;
604 for (var i = 0, page; page = results[i]; i++) { 637 for (var i = 0, page; page = results[i]; i++) {
605 if (page.isRendered) { 638 if (page.isRendered) {
606 continue; 639 continue;
607 } 640 }
608 // Break across day boundaries and insert gaps for browsing pauses. 641 // Break across day boundaries and insert gaps for browsing pauses.
642 // Create a dayResults element to contain results for each day
609 var thisTime = page.time.getTime(); 643 var thisTime = page.time.getTime();
610 644
611 if ((i == 0 && page.continued) || !page.continued) { 645 if ((i == 0 && page.continued) || !page.continued) {
612 var day = createElementWithClassName('div', 'day'); 646 var day = createElementWithClassName('h2', 'day');
613 day.appendChild(document.createTextNode(page.dateRelativeDay)); 647 day.appendChild(document.createTextNode(page.dateRelativeDay));
614
615 if (i == 0 && page.continued) { 648 if (i == 0 && page.continued) {
616 day.appendChild(document.createTextNode(' ' + 649 day.appendChild(document.createTextNode(' ' +
617 localStrings.getString('cont'))); 650 localStrings.getString('cont')));
618 } 651 }
619 652
620 this.resultDiv_.appendChild(day); 653 // If there is an existing dayResults element, append it.
654 if (dayResults) {
655 resultsFragment.appendChild(dayResults);
656 }
657 resultsFragment.appendChild(day);
658 dayResults = createElementWithClassName('ol', 'day-results');
621 } else if (lastTime - thisTime > BROWSING_GAP_TIME) { 659 } else if (lastTime - thisTime > BROWSING_GAP_TIME) {
622 this.resultDiv_.appendChild(createElementWithClassName('div', 'gap')); 660 if (dayResults) {
661 dayResults.appendChild(createElementWithClassName('li', 'gap'));
662 }
623 } 663 }
624 lastTime = thisTime; 664 lastTime = thisTime;
625
626 // Add entry. 665 // Add entry.
627 this.resultDiv_.appendChild(page.getBrowseResultDOM()); 666 if (dayResults) {
628 this.setPageRendered_(page); 667 dayResults.appendChild(page.getResultDOM(false));
668 this.setPageRendered_(page);
669 }
629 } 670 }
671 // Add final dayResults element.
672 if (dayResults) {
673 resultsFragment.appendChild(dayResults);
674 }
675 this.resultDiv_.appendChild(resultsFragment);
630 } 676 }
631 677
632 this.displaySummaryBar_(); 678 this.displaySummaryBar_();
633 this.displayNavBar_(); 679 this.displayNavBar_();
634 this.updateEntryAnchorWidth_(); 680 this.updateEntryAnchorWidth_();
635 }; 681 };
636 682
637 /** 683 /**
638 * Update the summary bar with descriptive text. 684 * Update the summary bar with descriptive text.
639 */ 685 */
640 HistoryView.prototype.displaySummaryBar_ = function() { 686 HistoryView.prototype.displaySummaryBar_ = function() {
641 var searchText = this.model_.getSearchText(); 687 var searchText = this.model_.getSearchText();
642 if (searchText != '') { 688 if (searchText != '') {
643 this.summaryTd_.textContent = localStrings.getStringF('searchresultsfor', 689 this.summaryTd_.textContent = localStrings.getStringF('searchresultsfor',
644 searchText); 690 searchText);
645 } else { 691 } else {
646 this.summaryTd_.textContent = localStrings.getString('history'); 692 this.summaryTd_.textContent = localStrings.getString('history');
647 } 693 }
648 }; 694 };
649 695
650 /** 696 /**
651 * Update the widgets related to edit mode.
652 */
653 HistoryView.prototype.updateEditControls_ = function() {
654 // Display a button (looking like a link) to enable/disable edit mode.
655 var oldButton = this.editButtonTd_.firstChild;
656 var editMode = this.model_.getEditMode();
657 var button = createElementWithClassName('button', 'edit-button');
658 button.onclick = toggleEditMode;
659 button.textContent = localStrings.getString(editMode ?
660 'doneediting' : 'edithistory');
661 this.editButtonTd_.replaceChild(button, oldButton);
662
663 this.editingControlsDiv_.textContent = '';
664
665 if (editMode) {
666 // Button to delete the selected items.
667 button = document.createElement('button');
668 button.onclick = removeItems;
669 button.textContent = localStrings.getString('removeselected');
670 button.disabled = true;
671 this.editingControlsDiv_.appendChild(button);
672 this.removeButton_ = button;
673
674 // Button that opens up the clear browsing data dialog.
675 button = document.createElement('button');
676 button.onclick = openClearBrowsingData;
677 button.textContent = localStrings.getString('clearallhistory');
678 this.editingControlsDiv_.appendChild(button);
679
680 // Listen for clicks in the page to sync the disabled state.
681 document.addEventListener('click', this.boundUpdateRemoveButton_);
682 } else {
683 this.removeButton_ = null;
684 document.removeEventListener('click', this.boundUpdateRemoveButton_);
685 }
686 };
687
688 /**
689 * Updates the disabled state of the remove button when in editing mode.
690 * @param {!Event} e The click event object.
691 * @private
692 */
693 HistoryView.prototype.updateRemoveButton_ = function(e) {
694 if (e.target.tagName != 'INPUT')
695 return;
696
697 var anyChecked = document.querySelector('.entry input:checked') != null;
698 if (this.removeButton_)
699 this.removeButton_.disabled = !anyChecked;
700 };
701
702 /**
703 * Update the pagination tools. 697 * Update the pagination tools.
704 */ 698 */
705 HistoryView.prototype.displayNavBar_ = function() { 699 HistoryView.prototype.displayNavBar_ = function() {
706 this.pageDiv_.textContent = ''; 700 this.pageDiv_.textContent = '';
707 701
708 if (this.pageIndex_ > 0) { 702 if (this.pageIndex_ > 0) {
709 this.pageDiv_.appendChild( 703 this.pageDiv_.appendChild(
710 this.createPageNav_(0, localStrings.getString('newest'))); 704 this.createPageNav_(0, localStrings.getString('newest')));
711 this.pageDiv_.appendChild( 705 this.pageDiv_.appendChild(
712 this.createPageNav_(this.pageIndex_ - 1, 706 this.createPageNav_(this.pageIndex_ - 1,
(...skipping 12 matching lines...) Expand all
725 /** 719 /**
726 * Make a DOM object representation of a page navigation link. 720 * Make a DOM object representation of a page navigation link.
727 * @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
728 * @param {string} name The text content of the link 722 * @param {string} name The text content of the link
729 * @return {HTMLAnchorElement} the pagination link 723 * @return {HTMLAnchorElement} the pagination link
730 */ 724 */
731 HistoryView.prototype.createPageNav_ = function(page, name) { 725 HistoryView.prototype.createPageNav_ = function(page, name) {
732 anchor = document.createElement('a'); 726 anchor = document.createElement('a');
733 anchor.className = 'page-navigation'; 727 anchor.className = 'page-navigation';
734 anchor.textContent = name; 728 anchor.textContent = name;
735 var hashString = PageState.getHashString(this.model_.getEditMode(), 729 var hashString = PageState.getHashString(this.model_.getSearchText(), page);
736 this.model_.getSearchText(), page);
737 var link = 'chrome://history/' + (hashString ? '#' + hashString : ''); 730 var link = 'chrome://history/' + (hashString ? '#' + hashString : '');
738 anchor.href = link; 731 anchor.href = link;
739 anchor.onclick = function() { 732 anchor.onclick = function() {
740 setPage(page); 733 setPage(page);
741 return false; 734 return false;
742 }; 735 };
743 return anchor; 736 return anchor;
744 }; 737 };
745 738
746 /** 739 /**
747 * Updates the CSS rule for the entry anchor. 740 * Updates the CSS rule for the entry anchor.
748 * @private 741 * @private
749 */ 742 */
750 HistoryView.prototype.updateEntryAnchorWidth_ = function() { 743 HistoryView.prototype.updateEntryAnchorWidth_ = function() {
751 // We need to have at least on .title div to be able to calculate the 744 // We need to have at least on .title div to be able to calculate the
752 // desired width of the anchor. 745 // desired width of the anchor.
753 var titleElement = document.querySelector('.entry .title'); 746 var titleElement = document.querySelector('.entry .title');
754 if (!titleElement) 747 if (!titleElement)
755 return; 748 return;
756 749
757 // Create new CSS rules and add them last to the last stylesheet. 750 // Create new CSS rules and add them last to the last stylesheet.
758 if (!this.entryAnchorRule_) { 751 // TODO(jochen): The following code does not work due to WebKit bug #32309
759 var styleSheets = document.styleSheets; 752 // if (!this.entryAnchorRule_) {
760 var styleSheet = styleSheets[styleSheets.length - 1]; 753 // var styleSheets = document.styleSheets;
761 var rules = styleSheet.cssRules; 754 // var styleSheet = styleSheets[styleSheets.length - 1];
762 var createRule = function(selector) { 755 // var rules = styleSheet.cssRules;
763 styleSheet.insertRule(selector + '{}', rules.length); 756 // var createRule = function(selector) {
764 return rules[rules.length - 1]; 757 // styleSheet.insertRule(selector + '{}', rules.length);
765 }; 758 // return rules[rules.length - 1];
766 this.entryAnchorRule_ = createRule('.entry .title > a'); 759 // };
767 // The following rule needs to be more specific to have higher priority. 760 // this.entryAnchorRule_ = createRule('.entry .title > a');
768 this.entryAnchorStarredRule_ = createRule('.entry .title.starred > a'); 761 // // The following rule needs to be more specific to have higher priority.
769 } 762 // this.entryAnchorStarredRule_ = createRule('.entry .title.starred > a');
770 763 // }
771 var anchorMaxWith = titleElement.offsetWidth; 764 //
772 this.entryAnchorRule_.style.maxWidth = anchorMaxWith + 'px'; 765 // var anchorMaxWith = titleElement.offsetWidth;
773 // Adjust by the width of star plus its margin. 766 // this.entryAnchorRule_.style.maxWidth = anchorMaxWith + 'px';
774 this.entryAnchorStarredRule_.style.maxWidth = anchorMaxWith - 23 + 'px'; 767 // // Adjust by the width of star plus its margin.
768 // this.entryAnchorStarredRule_.style.maxWidth = anchorMaxWith - 23 + 'px';
775 }; 769 };
776 770
777 /////////////////////////////////////////////////////////////////////////////// 771 ///////////////////////////////////////////////////////////////////////////////
778 // State object: 772 // State object:
779 /** 773 /**
780 * An 'AJAX-history' implementation. 774 * An 'AJAX-history' implementation.
781 * @param {HistoryModel} model The model we're representing 775 * @param {HistoryModel} model The model we're representing
782 * @param {HistoryView} view The view we're representing 776 * @param {HistoryView} view The view we're representing
783 */ 777 */
784 function PageState(model, view) { 778 function PageState(model, view) {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
834 return result; 828 return result;
835 }; 829 };
836 830
837 /** 831 /**
838 * 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
839 * session history so the back button cycles through hash states, which 833 * session history so the back button cycles through hash states, which
840 * are then picked up by our listener. 834 * are then picked up by our listener.
841 * @param {string} term The current search string. 835 * @param {string} term The current search string.
842 * @param {string} page The page currently being viewed. 836 * @param {string} page The page currently being viewed.
843 */ 837 */
844 PageState.prototype.setUIState = function(editMode, term, page) { 838 PageState.prototype.setUIState = function(term, page) {
845 // Make sure the form looks pretty. 839 // Make sure the form looks pretty.
846 document.forms[0].term.value = term; 840 document.forms[0].term.value = term;
847 var currentHash = this.getHashData(); 841 var currentHash = this.getHashData();
848 if (Boolean(currentHash.e) != editMode || currentHash.q != term || 842 if (currentHash.q != term || currentHash.p != page) {
849 currentHash.p != page) { 843 window.location.hash = PageState.getHashString(term, page);
850 window.location.hash = PageState.getHashString(editMode, term, page);
851 } 844 }
852 }; 845 };
853 846
854 /** 847 /**
855 * Static method to get the hash string for a specified state 848 * Static method to get the hash string for a specified state
856 * @param {string} term The current search string. 849 * @param {string} term The current search string.
857 * @param {string} page The page currently being viewed. 850 * @param {string} page The page currently being viewed.
858 * @return {string} The string to be used in a hash. 851 * @return {string} The string to be used in a hash.
859 */ 852 */
860 PageState.getHashString = function(editMode, term, page) { 853 PageState.getHashString = function(term, page) {
861 var newHash = []; 854 var newHash = [];
862 if (editMode) {
863 newHash.push('e=1');
864 }
865 if (term) { 855 if (term) {
866 newHash.push('q=' + encodeURIComponent(term)); 856 newHash.push('q=' + encodeURIComponent(term));
867 } 857 }
868 if (page != undefined) { 858 if (page != undefined) {
869 newHash.push('p=' + page); 859 newHash.push('p=' + page);
870 } 860 }
871 861
872 return newHash.join('&'); 862 return newHash.join('&');
873 }; 863 };
874 864
875 /////////////////////////////////////////////////////////////////////////////// 865 ///////////////////////////////////////////////////////////////////////////////
876 // Document Functions: 866 // Document Functions:
877 /** 867 /**
878 * Window onload handler, sets up the page. 868 * Window onload handler, sets up the page.
879 */ 869 */
880 function load() { 870 function load() {
881 $('term').focus(); 871 $('term').focus();
882 872
883 localStrings = new LocalStrings(); 873 localStrings = new LocalStrings();
884 historyModel = new HistoryModel(); 874 historyModel = new HistoryModel();
885 historyView = new HistoryView(historyModel); 875 historyView = new HistoryView(historyModel);
886 pageState = new PageState(historyModel, historyView); 876 pageState = new PageState(historyModel, historyView);
887 877
888 // Create default view. 878 // Create default view.
889 var hashData = pageState.getHashData(); 879 var hashData = pageState.getHashData();
890 if (Boolean(hashData.e)) {
891 historyView.toggleEditMode();
892 }
893 historyView.setSearch(hashData.q, hashData.p); 880 historyView.setSearch(hashData.q, hashData.p);
894 881
895 // Add handlers to HTML elements. 882 // Setup click handlers.
896 $('history-section').onclick = function () { 883 $('history-section').onclick = function () {
897 setSearch(''); 884 setSearch('');
898 return false; 885 return false;
899 }; 886 };
900 $('search-form').onsubmit = function () { 887 $('search-form').onsubmit = function () {
901 setSearch(this.term.value); 888 setSearch(this.term.value);
902 return false; 889 return false;
903 }; 890 };
891
892 $('remove-page').addEventListener('activate', function(e) {
893 activePage.removeFromHistory_();
894 activePage = null;
895 });
896 $('more-from-site').addEventListener('activate', function(e) {
897 activePage.showMoreFromSite_();
898 activePage = null;
899 });
904 } 900 }
905 901
906 /** 902 /**
907 * TODO(glen): Get rid of this function. 903 * TODO(glen): Get rid of this function.
908 * Set the history view to a specified page. 904 * Set the history view to a specified page.
909 * @param {String} term The string to search for 905 * @param {String} term The string to search for
910 */ 906 */
911 function setSearch(term) { 907 function setSearch(term) {
912 if (historyView) { 908 if (historyView) {
913 historyView.setSearch(term); 909 historyView.setSearch(term);
914 } 910 }
915 } 911 }
916 912
917 /** 913 /**
918 * TODO(glen): Get rid of this function. 914 * TODO(glen): Get rid of this function.
919 * Set the history view to a specified page. 915 * Set the history view to a specified page.
920 * @param {number} page The page to set the view to. 916 * @param {number} page The page to set the view to.
921 */ 917 */
922 function setPage(page) { 918 function setPage(page) {
923 if (historyView) { 919 if (historyView) {
924 historyView.setPage(page); 920 historyView.setPage(page);
925 } 921 }
926 } 922 }
927 923
928 /** 924 /**
929 * TODO(glen): Get rid of this function.
930 * Toggles edit mode.
931 */
932 function toggleEditMode() {
933 if (historyView) {
934 historyView.toggleEditMode();
935 historyView.reload();
936 }
937 }
938
939 /**
940 * Delete the next item in our deletion queue. 925 * Delete the next item in our deletion queue.
941 */ 926 */
942 function deleteNextInQueue() { 927 function deleteNextInQueue() {
943 if (!deleteInFlight && deleteQueue.length) { 928 if (deleteQueue.length > 0) {
944 deleteInFlight = true; 929 // Call the native function to remove history entries.
930 // First arg is a time in seconds (passed as String) identifying the day.
931 // Remaining args are URLs of history entries from that day to delete.
932 var timeInSeconds = Math.floor(deleteQueue[0].date.getTime() / 1000);
945 chrome.send('removeURLsOnOneDay', 933 chrome.send('removeURLsOnOneDay',
946 [String(deleteQueue[0])].concat(deleteQueue[1])); 934 [String(timeInSeconds)].concat(deleteQueue[0].urls));
947 } 935 }
948 } 936 }
949 937
950 /** 938 /**
951 * Open the clear browsing data dialog. 939 * Open the clear browsing data dialog.
952 */ 940 */
953 function openClearBrowsingData() { 941 function openClearBrowsingData() {
954 chrome.send('clearBrowsingData', []); 942 chrome.send('clearBrowsingData', []);
955 return false; 943 return false;
956 } 944 }
957 945
958 /** 946 /**
947 * Queue a set of URLs from the same day for deletion.
948 * @param {Date} date A date indicating the day the URLs were visited.
949 * @param {Array} urls Array of URLs from the same day to be deleted.
950 * @param {Function} opt_callback An optional callback to be executed when
951 * the deletion is complete.
952 */
953 function queueURLsForDeletion(date, urls, opt_callback) {
954 deleteQueue.push({ 'date': date, 'urls': urls, 'callback': opt_callback });
955 }
956
957 function reloadHistory() {
958 historyView.reload();
959 }
960
961 /**
959 * Collect IDs from checked checkboxes and send to Chrome for deletion. 962 * Collect IDs from checked checkboxes and send to Chrome for deletion.
960 */ 963 */
961 function removeItems() { 964 function removeItems() {
962 var checkboxes = document.getElementsByTagName('input'); 965 var checked = document.querySelectorAll(
963 var ids = []; 966 'input[type=checkbox]:checked:not([disabled])');
967 var urls = [];
964 var disabledItems = []; 968 var disabledItems = [];
965 var queue = []; 969 var queue = [];
966 var date = new Date(); 970 var date = new Date();
967 for (var i = 0; i < checkboxes.length; i++) { 971
968 if (checkboxes[i].type == 'checkbox' && checkboxes[i].checked && 972 for (var i = 0; i < checked.length; i++) {
969 !checkboxes[i].disabled) { 973 var checkbox = checked[i];
970 var cbDate = new Date(checkboxes[i].time); 974 var cbDate = new Date(checkbox.time);
971 if (date.getFullYear() != cbDate.getFullYear() || 975 if (date.getFullYear() != cbDate.getFullYear() ||
972 date.getMonth() != cbDate.getMonth() || 976 date.getMonth() != cbDate.getMonth() ||
973 date.getDate() != cbDate.getDate()) { 977 date.getDate() != cbDate.getDate()) {
974 if (ids.length > 0) { 978 if (urls.length > 0) {
975 queue.push(date.valueOf() / 1000); 979 queue.push([date, urls]);
976 queue.push(ids);
977 }
978 ids = [];
979 date = cbDate;
980 } 980 }
981 var link = $('id-' + checkboxes[i].name); 981 urls = [];
982 checkboxes[i].disabled = true; 982 date = cbDate;
983 link.style.textDecoration = 'line-through';
984 disabledItems.push(checkboxes[i]);
985 ids.push(link.href);
986 } 983 }
984 var link = checkbox.parentNode.parentNode.querySelector('a');
985 checkbox.disabled = true;
986 link.classList.add('to-be-removed');
987 disabledItems.push(checkbox);
988 urls.push(link.href);
987 } 989 }
988 if (ids.length > 0) { 990 if (urls.length > 0) {
989 queue.push(date.valueOf() / 1000); 991 queue.push([date, urls]);
990 queue.push(ids);
991 } 992 }
992 if (queue.length > 0) { 993 if (checked.length > 0 && confirm(localStrings.getString('deletewarning'))) {
993 if (confirm(localStrings.getString('deletewarning'))) { 994 for (var i = 0; i < queue.length; i++) {
994 deleteQueue = deleteQueue.concat(queue); 995 // Reload the page when the final entry has been deleted.
995 deleteNextInQueue(); 996 var callback = i == 0 ? reloadHistory : null;
996 historyView.removeButton_.disabled = true; 997
997 } else { 998 queueURLsForDeletion(queue[i][0], queue[i][1], callback);
998 // If the remove is cancelled, return the checkboxes to their 999 }
999 // enabled, non-line-through state. 1000 deleteNextInQueue();
1000 for (var i = 0; i < disabledItems.length; i++) { 1001 } else {
1001 var link = $('id-' + disabledItems[i].name); 1002 // If the remove is cancelled, return the checkboxes to their
1002 disabledItems[i].disabled = false; 1003 // enabled, non-line-through state.
1003 link.style.textDecoration = ''; 1004 for (var i = 0; i < disabledItems.length; i++) {
1004 } 1005 var checkbox = disabledItems[i];
1006 var link = checkbox.parentNode.parentNode.querySelector('a');
1007 checkbox.disabled = false;
1008 link.classList.remove('to-be-removed');
1005 } 1009 }
1006 } 1010 }
1007 return false; 1011 return false;
1008 } 1012 }
1009 1013
1010 /** 1014 /**
1011 * Toggle state of checkbox and handle Shift modifier. 1015 * Toggle state of checkbox and handle Shift modifier.
1012 */ 1016 */
1013 function checkboxClicked(event) { 1017 function checkboxClicked(event) {
1018 var id = Number(this.id.slice("checkbox-".length));
1014 if (event.shiftKey && (selectionAnchor != -1)) { 1019 if (event.shiftKey && (selectionAnchor != -1)) {
1015 var checked = this.checked; 1020 var checked = this.checked;
1016 // 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
1017 // state of the clicked one. 1022 // state of the clicked one.
1018 var begin = Math.min(this.name, selectionAnchor); 1023 var begin = Math.min(id, selectionAnchor);
1019 var end = Math.max(this.name, selectionAnchor); 1024 var end = Math.max(id, selectionAnchor);
1020 for (var i = begin; i <= end; i++) { 1025 for (var i = begin; i <= end; i++) {
1021 id2checkbox[i].checked = checked; 1026 var checkbox = document.querySelector('#checkbox-' + i);
1027 if (checkbox)
1028 checkbox.checked = checked;
1022 } 1029 }
1023 } 1030 }
1024 selectionAnchor = this.name; 1031 selectionAnchor = id;
1025 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 });
1050 }
1051
1052 /**
1053 * Removes a single entry from the view. Also removes gaps before and after
1054 * entry if necessary.
1055 */
1056 function removeEntryFromView(entry) {
1057 var nextEntry = entry.nextSibling;
1058 var previousEntry = entry.previousSibling;
1059
1060 removeNode(entry);
1061
1062 // if there is no previous entry, and the next entry is a gap, remove it
1063 if (!previousEntry && nextEntry && nextEntry.className == 'gap') {
1064 removeNode(nextEntry);
1065 }
1066
1067 // if there is no next entry, and the previous entry is a gap, remove it
1068 if (!nextEntry && previousEntry && previousEntry.className == 'gap') {
1069 removeNode(previousEntry);
1070 }
1071
1072 // if both the next and previous entries are gaps, remove one
1073 if (nextEntry && nextEntry.className == 'gap' &&
1074 previousEntry && previousEntry.className == 'gap') {
1075 removeNode(nextEntry);
1076 }
1026 } 1077 }
1027 1078
1028 /////////////////////////////////////////////////////////////////////////////// 1079 ///////////////////////////////////////////////////////////////////////////////
1029 // Chrome callbacks: 1080 // Chrome callbacks:
1030 /** 1081 /**
1031 * Our history system calls this function with results from searches. 1082 * Our history system calls this function with results from searches.
1032 */ 1083 */
1033 function historyResult(info, results) { 1084 function historyResult(info, results) {
1034 historyModel.addResults(info, results); 1085 historyModel.addResults(info, results);
1035 } 1086 }
1036 1087
1037 /** 1088 /**
1038 * Our history system calls this function when a deletion has finished. 1089 * Our history system calls this function when a deletion has finished.
1039 */ 1090 */
1040 function deleteComplete() { 1091 function deleteComplete() {
1041 window.console.log('Delete complete'); 1092 if (deleteQueue.length > 0) {
1042 deleteInFlight = false; 1093 // Remove the successfully deleted entry from the queue.
1043 if (deleteQueue.length > 2) { 1094 if (deleteQueue[0].callback)
1044 deleteQueue = deleteQueue.slice(2); 1095 deleteQueue[0].callback.apply();
1096 deleteQueue.splice(0, 1);
1045 deleteNextInQueue(); 1097 deleteNextInQueue();
1046 } else { 1098 } else {
1047 deleteQueue = []; 1099 console.error('Received deleteComplete but queue is empty.');
1048 } 1100 }
1049 } 1101 }
1050 1102
1051 /** 1103 /**
1052 * 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.
1053 * another delete is in-progress). 1105 * another delete is in-progress).
1054 */ 1106 */
1055 function deleteFailed() { 1107 function deleteFailed() {
1056 window.console.log('Delete failed'); 1108 window.console.log('Delete failed');
1109
1057 // The deletion failed - try again later. 1110 // The deletion failed - try again later.
1058 deleteInFlight = false; 1111 // TODO(dubroy): We should probably give up at some point.
1059 setTimeout(deleteNextInQueue, 500); 1112 setTimeout(deleteNextInQueue, 500);
1060 } 1113 }
1061 1114
1062 /** 1115 /**
1063 * We're called when something is deleted (either by us or by someone 1116 * Called when the history is deleted by someone else.
1064 * else).
1065 */ 1117 */
1066 function historyDeleted() { 1118 function historyDeleted() {
1067 window.console.log('History deleted'); 1119 window.console.log('History deleted');
1068 var anyChecked = document.querySelector('.entry input:checked') != null; 1120 var anyChecked = document.querySelector('.entry input:checked') != null;
1069 if (!(historyView.getEditMode() && anyChecked)) 1121 // Reload the page, unless the user has any items checked.
1122 // TODO(dubroy): We should just reload the page & restore the checked items.
1123 if (!anyChecked)
1070 historyView.reload(); 1124 historyView.reload();
1071 } 1125 }
1072 1126
1127 // Add handlers to HTML elements.
1073 document.addEventListener('DOMContentLoaded', load); 1128 document.addEventListener('DOMContentLoaded', load);
1129
1130 // This event lets us enable and disable menu items before the menu is shown.
1131 document.addEventListener('canExecute', function(e) {
1132 e.canExecute = true;
1133 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/history.html ('k') | chrome/browser/resources/history2.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698