| Index: chrome/browser/resources/history.js
|
| diff --git a/chrome/browser/resources/history.js b/chrome/browser/resources/history.js
|
| index b37495fe3074fbb4194239ab2e81eb9e9e5ab330..2c70f571f58bd1c48bf27411e1a0344157c69103 100644
|
| --- a/chrome/browser/resources/history.js
|
| +++ b/chrome/browser/resources/history.js
|
| @@ -32,10 +32,30 @@ var historyView;
|
| var localStrings;
|
| var pageState;
|
| var deleteQueue = [];
|
| -var deleteInFlight = false;
|
| var selectionAnchor = -1;
|
| -var id2checkbox = [];
|
| +var activePage = null;
|
| +
|
| +const MenuButton = cr.ui.MenuButton;
|
| +const Command = cr.ui.Command;
|
| +const Menu = cr.ui.Menu;
|
| +
|
| +function createDropDownBgImage(canvasName, colorSpec) {
|
| + var ctx = document.getCSSCanvasContext('2d', canvasName, 6, 4);
|
| + ctx.fillStyle = ctx.strokeStyle = colorSpec;
|
| + ctx.beginPath();
|
| + ctx.moveTo(0, 0);
|
| + ctx.lineTo(6, 0);
|
| + ctx.lineTo(3, 3);
|
| + ctx.closePath();
|
| + ctx.fill();
|
| + ctx.stroke();
|
| + return ctx;
|
| +}
|
|
|
| +// Create the canvases to be used as the drop down button background images.
|
| +var arrow = createDropDownBgImage('drop-down-arrow', 'hsl(214, 91%, 85%)');
|
| +var hoverArrow = createDropDownBgImage('drop-down-arrow-hover', '#6A86DE');
|
| +var activeArrow = createDropDownBgImage('drop-down-arrow-active', 'white');
|
|
|
| ///////////////////////////////////////////////////////////////////////////////
|
| // Page:
|
| @@ -49,6 +69,7 @@ function Page(result, continued, model, id) {
|
| this.model_ = model;
|
| this.title_ = result.title;
|
| this.url_ = result.url;
|
| + this.domain_ = this.getDomainFromURL_(this.url_);
|
| this.starred_ = result.starred;
|
| this.snippet_ = result.snippet || "";
|
| this.id_ = id;
|
| @@ -75,63 +96,101 @@ function Page(result, continued, model, id) {
|
|
|
| // Page, Public: --------------------------------------------------------------
|
| /**
|
| - * @return {DOMObject} Gets the DOM representation of the page
|
| - * for use in browse results.
|
| + * Returns a dom structure for a browse page result or a search page result.
|
| + * @param {boolean} Flag to indicate if result is a search result.
|
| + * @return {Element} The dom structure.
|
| */
|
| -Page.prototype.getBrowseResultDOM = function() {
|
| - var node = createElementWithClassName('div', 'entry');
|
| +Page.prototype.getResultDOM = function(searchResultFlag) {
|
| + var node = createElementWithClassName('li', 'entry');
|
| var time = createElementWithClassName('div', 'time');
|
| - if (this.model_.getEditMode()) {
|
| - var checkbox = document.createElement('input');
|
| - checkbox.type = "checkbox";
|
| - checkbox.name = this.id_;
|
| - checkbox.time = this.time.toString();
|
| - checkbox.addEventListener("click", checkboxClicked, false);
|
| - id2checkbox[this.id_] = checkbox;
|
| - time.appendChild(checkbox);
|
| + var entryBox = createElementWithClassName('label', 'entry-box');
|
| + var domain = createElementWithClassName('div', 'domain');
|
| +
|
| + var dropDown = createElementWithClassName('button', 'drop-down');
|
| + dropDown.value = 'Open action menu';
|
| + dropDown.title = localStrings.getString('actionMenuDescription');
|
| + dropDown.setAttribute('menu', '#action-menu');
|
| + cr.ui.decorate(dropDown, MenuButton);
|
| +
|
| + // Checkbox is always created, but only visible on hover & when checked.
|
| + var checkbox = document.createElement('input');
|
| + checkbox.type = 'checkbox';
|
| + checkbox.id = 'checkbox-' + this.id_;
|
| + checkbox.time = this.time.getTime();
|
| + checkbox.addEventListener('click', checkboxClicked);
|
| + time.appendChild(checkbox);
|
| +
|
| + // Keep track of the drop down that triggered the menu, so we know
|
| + // which element to apply the command to.
|
| + // TODO(dubroy): Ideally we'd use 'activate', but MenuButton swallows it.
|
| + var self = this;
|
| + var setActivePage = function(e) {
|
| + activePage = self;
|
| + };
|
| + dropDown.addEventListener('mousedown', setActivePage);
|
| + dropDown.addEventListener('focus', setActivePage);
|
| +
|
| + domain.style.backgroundImage =
|
| + 'url(chrome://favicon/' + encodeURIForCSS(this.url_) + ')';
|
| + domain.textContent = this.domain_;
|
| +
|
| + // Clicking anywhere in the entryBox will check/uncheck the checkbox.
|
| + entryBox.setAttribute('for', checkbox.id);
|
| + entryBox.addEventListener('mousedown', entryBoxMousedown, false);
|
| +
|
| + // Prevent clicks on the drop down from affecting the checkbox.
|
| + dropDown.addEventListener('click', function(e) { e.preventDefault(); });
|
| +
|
| + // We use a wrapper div so that the entry contents will be shinkwrapped.
|
| + entryBox.appendChild(time);
|
| + entryBox.appendChild(domain);
|
| + entryBox.appendChild(this.getTitleDOM_());
|
| + entryBox.appendChild(dropDown);
|
| +
|
| + // Let the entryBox be styled appropriately when it contains keyboard focus.
|
| + entryBox.addEventListener('focus', function() {
|
| + this.classList.add('contains-focus');
|
| + }, true);
|
| + entryBox.addEventListener('blur', function() {
|
| + this.classList.remove('contains-focus');
|
| + }, true);
|
| +
|
| + node.appendChild(entryBox);
|
| +
|
| + if (searchResultFlag) {
|
| + time.textContent = this.dateShort;
|
| + var snippet = createElementWithClassName('div', 'snippet');
|
| + this.addHighlightedText_(snippet,
|
| + this.snippet_,
|
| + this.model_.getSearchText());
|
| + node.appendChild(snippet);
|
| + } else {
|
| + time.appendChild(document.createTextNode(this.dateTimeOfDay));
|
| }
|
| - time.appendChild(document.createTextNode(this.dateTimeOfDay));
|
| - node.appendChild(time);
|
| - node.appendChild(this.getTitleDOM_());
|
| +
|
| + if (typeof this.domNode_ != 'undefined') {
|
| + console.error('Already generated node for page.');
|
| + }
|
| + this.domNode_ = node;
|
| +
|
| return node;
|
| };
|
|
|
| +// Page, private: -------------------------------------------------------------
|
| /**
|
| - * @return {DOMObject} Gets the DOM representation of the page for
|
| - * use in search results.
|
| + * Extracts and returns the domain (and subdomains) from a URL.
|
| + * @param {string} The url
|
| + * @return (string) The domain. An empty string is returned if no domain can
|
| + * be found.
|
| */
|
| -Page.prototype.getSearchResultDOM = function() {
|
| - var row = createElementWithClassName('tr', 'entry');
|
| - var datecell = createElementWithClassName('td', 'time');
|
| - datecell.appendChild(document.createTextNode(this.dateShort));
|
| - row.appendChild(datecell);
|
| - if (this.model_.getEditMode()) {
|
| - var checkbox = document.createElement('input');
|
| - checkbox.type = "checkbox";
|
| - checkbox.name = this.id_;
|
| - checkbox.time = this.time.toString();
|
| - checkbox.addEventListener("click", checkboxClicked, false);
|
| - id2checkbox[this.id_] = checkbox;
|
| - datecell.appendChild(checkbox);
|
| - }
|
| -
|
| - var titleCell = document.createElement('td');
|
| - titleCell.valign = 'top';
|
| - titleCell.appendChild(this.getTitleDOM_());
|
| - var snippet = createElementWithClassName('div', 'snippet');
|
| - this.addHighlightedText_(snippet,
|
| - this.snippet_,
|
| - this.model_.getSearchText());
|
| - titleCell.appendChild(snippet);
|
| - row.appendChild(titleCell);
|
| -
|
| - return row;
|
| +Page.prototype.getDomainFromURL_ = function(url) {
|
| + var domain = url.replace(/^.+:\/\//, '').match(/[^/]+/);
|
| + return domain ? domain[0] : '';
|
| };
|
|
|
| -// Page, private: -------------------------------------------------------------
|
| /**
|
| - * Add child text nodes to a node such that occurrences of the spcified text is
|
| - * highligted.
|
| + * Add child text nodes to a node such that occurrences of the specified text is
|
| + * highlighted.
|
| * @param {Node} node The node under which new text nodes will be made as
|
| * children.
|
| * @param {string} content Text to be added beneath |node| as one or more
|
| @@ -163,16 +222,16 @@ Page.prototype.addHighlightedText_ = function(node, content, highlightText) {
|
| * @return {DOMObject} DOM representation for the title block.
|
| */
|
| Page.prototype.getTitleDOM_ = function() {
|
| - var node = document.createElement('div');
|
| - node.className = 'title';
|
| + var node = createElementWithClassName('div', 'title');
|
| var link = document.createElement('a');
|
| link.href = this.url_;
|
| -
|
| - link.style.backgroundImage =
|
| - 'url(chrome://favicon/' + encodeURIForCSS(this.url_) + ')';
|
| link.id = "id-" + this.id_;
|
| - this.addHighlightedText_(link, this.title_, this.model_.getSearchText());
|
|
|
| + // Add a tooltip, since it might be ellipsized.
|
| + // TODO(dubroy): Find a way to show the tooltip only when necessary.
|
| + link.title = this.title_;
|
| +
|
| + this.addHighlightedText_(link, this.title_, this.model_.getSearchText());
|
| node.appendChild(link);
|
|
|
| if (this.starred_) {
|
| @@ -183,6 +242,26 @@ Page.prototype.getTitleDOM_ = function() {
|
| return node;
|
| };
|
|
|
| +/**
|
| + * Launch a search for more history entries from the same domain.
|
| + */
|
| +Page.prototype.showMoreFromSite_ = function() {
|
| + setSearch(this.domain_);
|
| +};
|
| +
|
| +/**
|
| + * Remove a single entry from the history.
|
| + */
|
| +Page.prototype.removeFromHistory_ = function() {
|
| + var self = this;
|
| + var onSuccessCallback = function() {
|
| + removeEntryFromView(self.domNode_);
|
| + };
|
| + queueURLsForDeletion(this.time, [this.url_], onSuccessCallback);
|
| + deleteNextInQueue();
|
| +};
|
| +
|
| +
|
| // Page, private, static: -----------------------------------------------------
|
|
|
| /**
|
| @@ -208,8 +287,6 @@ Page.pregQuote_ = function(str) {
|
| */
|
| function HistoryModel() {
|
| this.clearModel_();
|
| - this.setEditMode(false);
|
| - this.view_;
|
| }
|
|
|
| // HistoryModel, Public: ------------------------------------------------------
|
| @@ -337,21 +414,6 @@ HistoryModel.prototype.getNumberedRange = function(start, end) {
|
| return this.pages_.slice(start, end);
|
| };
|
|
|
| -/**
|
| - * @return {boolean} Whether we are in edit mode where history items can be
|
| - * deleted
|
| - */
|
| -HistoryModel.prototype.getEditMode = function() {
|
| - return this.editMode_;
|
| -};
|
| -
|
| -/**
|
| - * @param {boolean} edit_mode Control whether we are in edit mode.
|
| - */
|
| -HistoryModel.prototype.setEditMode = function(edit_mode) {
|
| - this.editMode_ = edit_mode;
|
| -};
|
| -
|
| // HistoryModel, Private: -----------------------------------------------------
|
| HistoryModel.prototype.clearModel_ = function() {
|
| this.inFlight_ = false; // Whether a query is inflight.
|
| @@ -360,7 +422,6 @@ HistoryModel.prototype.clearModel_ = function() {
|
| this.pages_ = []; // Date-sorted list of pages.
|
| this.last_id_ = 0;
|
| selectionAnchor = -1;
|
| - id2checkbox = [];
|
|
|
| // The page that the view wants to see - we only fetch slightly past this
|
| // point. If the view requests a page that we don't have data for, we try
|
| @@ -389,7 +450,7 @@ HistoryModel.prototype.updateSearch_ = function(finished) {
|
| } else {
|
| // If we can't fill the requested page, ask for more data unless a request
|
| // is still in-flight.
|
| - if (!this.inFlight_ && !this.canFillPage_(this.requestedPage_)) {
|
| + if (!this.canFillPage_(this.requestedPage_) && !this.inFlight_) {
|
| this.getSearchResults_(this.searchDepth_ + 1);
|
| }
|
|
|
| @@ -470,12 +531,9 @@ function HistoryView(model) {
|
| window.onresize = function() {
|
| self.updateEntryAnchorWidth_();
|
| };
|
| - this.updateEditControls_();
|
| - this.editButtonTd_.hidden = true;
|
|
|
| - this.boundUpdateRemoveButton_ = function(e) {
|
| - return self.updateRemoveButton_(e);
|
| - };
|
| + $('clear-browsing-data').addEventListener('click', openClearBrowsingData);
|
| + $('remove-selected').addEventListener('click', removeItems);
|
| }
|
|
|
| // HistoryView, public: -------------------------------------------------------
|
| @@ -489,35 +547,7 @@ HistoryView.prototype.setSearch = function(term, opt_page) {
|
| this.pageIndex_ = parseInt(opt_page || 0, 10);
|
| window.scrollTo(0, 0);
|
| this.model_.setSearchText(term, this.pageIndex_);
|
| - this.updateEditControls_();
|
| - pageState.setUIState(this.model_.getEditMode(), term, this.pageIndex_);
|
| -};
|
| -
|
| -/**
|
| - * Controls edit mode where history can be deleted.
|
| - * @param {boolean} edit_mode Whether to enable edit mode.
|
| - */
|
| -HistoryView.prototype.setEditMode = function(edit_mode) {
|
| - this.model_.setEditMode(edit_mode);
|
| - pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(),
|
| - this.pageIndex_);
|
| -};
|
| -
|
| -/**
|
| - * Toggles the edit mode and triggers UI update.
|
| - */
|
| -HistoryView.prototype.toggleEditMode = function() {
|
| - var editMode = !this.model_.getEditMode();
|
| - this.setEditMode(editMode);
|
| - this.updateEditControls_();
|
| -};
|
| -
|
| -/**
|
| - * @return {boolean} Whether we are in edit mode where history items can be
|
| - * deleted
|
| - */
|
| -HistoryView.prototype.getEditMode = function() {
|
| - return this.model_.getEditMode();
|
| + pageState.setUIState(term, this.pageIndex_);
|
| };
|
|
|
| /**
|
| @@ -525,6 +555,7 @@ HistoryView.prototype.getEditMode = function() {
|
| */
|
| HistoryView.prototype.reload = function() {
|
| this.model_.reload();
|
| + this.updateRemoveButton();
|
| };
|
|
|
| /**
|
| @@ -536,8 +567,7 @@ HistoryView.prototype.setPage = function(page) {
|
| this.pageIndex_ = parseInt(page, 10);
|
| window.scrollTo(0, 0);
|
| this.model_.requestPage(page);
|
| - pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(),
|
| - this.pageIndex_);
|
| + pageState.setUIState(this.model_.getSearchText(), this.pageIndex_);
|
| };
|
|
|
| /**
|
| @@ -555,6 +585,14 @@ HistoryView.prototype.onModelReady = function() {
|
| this.displayResults_();
|
| };
|
|
|
| +/**
|
| + * Enables or disables the 'Remove selected items' button as appropriate.
|
| + */
|
| +HistoryView.prototype.updateRemoveButton = function() {
|
| + var anyChecked = document.querySelector('.entry input:checked') != null;
|
| + $('remove-selected').disabled = !anyChecked;
|
| +}
|
| +
|
| // HistoryView, private: ------------------------------------------------------
|
| /**
|
| * Clear the results in the view. Since we add results piecemeal, we need
|
| @@ -579,54 +617,62 @@ HistoryView.prototype.setPageRendered_ = function(page) {
|
| * Update the page with results.
|
| */
|
| HistoryView.prototype.displayResults_ = function() {
|
| - // Hide the Edit Button if there are no history results to display.
|
| - this.editButtonTd_.hidden = !this.model_.getSize();
|
| -
|
| var results = this.model_.getNumberedRange(
|
| this.pageIndex_ * RESULTS_PER_PAGE,
|
| this.pageIndex_ * RESULTS_PER_PAGE + RESULTS_PER_PAGE);
|
|
|
| if (this.model_.getSearchText()) {
|
| - var resultTable = createElementWithClassName('table', 'results');
|
| - resultTable.cellSpacing = 0;
|
| - resultTable.cellPadding = 0;
|
| - resultTable.border = 0;
|
| -
|
| + var searchResults = createElementWithClassName('ol', 'search-results');
|
| for (var i = 0, page; page = results[i]; i++) {
|
| if (!page.isRendered) {
|
| - resultTable.appendChild(page.getSearchResultDOM());
|
| + searchResults.appendChild(page.getResultDOM(true));
|
| this.setPageRendered_(page);
|
| }
|
| }
|
| - this.resultDiv_.appendChild(resultTable);
|
| + this.resultDiv_.appendChild(searchResults);
|
| } else {
|
| + var resultsFragment = document.createDocumentFragment();
|
| var lastTime = Math.infinity;
|
| + var dayResults;
|
| for (var i = 0, page; page = results[i]; i++) {
|
| if (page.isRendered) {
|
| continue;
|
| }
|
| // Break across day boundaries and insert gaps for browsing pauses.
|
| + // Create a dayResults element to contain results for each day
|
| var thisTime = page.time.getTime();
|
|
|
| if ((i == 0 && page.continued) || !page.continued) {
|
| - var day = createElementWithClassName('div', 'day');
|
| + var day = createElementWithClassName('h2', 'day');
|
| day.appendChild(document.createTextNode(page.dateRelativeDay));
|
| -
|
| if (i == 0 && page.continued) {
|
| day.appendChild(document.createTextNode(' ' +
|
| localStrings.getString('cont')));
|
| }
|
|
|
| - this.resultDiv_.appendChild(day);
|
| + // If there is an existing dayResults element, append it.
|
| + if (dayResults) {
|
| + resultsFragment.appendChild(dayResults);
|
| + }
|
| + resultsFragment.appendChild(day);
|
| + dayResults = createElementWithClassName('ol', 'day-results');
|
| } else if (lastTime - thisTime > BROWSING_GAP_TIME) {
|
| - this.resultDiv_.appendChild(createElementWithClassName('div', 'gap'));
|
| + if (dayResults) {
|
| + dayResults.appendChild(createElementWithClassName('li', 'gap'));
|
| + }
|
| }
|
| lastTime = thisTime;
|
| -
|
| // Add entry.
|
| - this.resultDiv_.appendChild(page.getBrowseResultDOM());
|
| - this.setPageRendered_(page);
|
| + if (dayResults) {
|
| + dayResults.appendChild(page.getResultDOM(false));
|
| + this.setPageRendered_(page);
|
| + }
|
| }
|
| + // Add final dayResults element.
|
| + if (dayResults) {
|
| + resultsFragment.appendChild(dayResults);
|
| + }
|
| + this.resultDiv_.appendChild(resultsFragment);
|
| }
|
|
|
| this.displaySummaryBar_();
|
| @@ -648,58 +694,6 @@ HistoryView.prototype.displaySummaryBar_ = function() {
|
| };
|
|
|
| /**
|
| - * Update the widgets related to edit mode.
|
| - */
|
| -HistoryView.prototype.updateEditControls_ = function() {
|
| - // Display a button (looking like a link) to enable/disable edit mode.
|
| - var oldButton = this.editButtonTd_.firstChild;
|
| - var editMode = this.model_.getEditMode();
|
| - var button = createElementWithClassName('button', 'edit-button');
|
| - button.onclick = toggleEditMode;
|
| - button.textContent = localStrings.getString(editMode ?
|
| - 'doneediting' : 'edithistory');
|
| - this.editButtonTd_.replaceChild(button, oldButton);
|
| -
|
| - this.editingControlsDiv_.textContent = '';
|
| -
|
| - if (editMode) {
|
| - // Button to delete the selected items.
|
| - button = document.createElement('button');
|
| - button.onclick = removeItems;
|
| - button.textContent = localStrings.getString('removeselected');
|
| - button.disabled = true;
|
| - this.editingControlsDiv_.appendChild(button);
|
| - this.removeButton_ = button;
|
| -
|
| - // Button that opens up the clear browsing data dialog.
|
| - button = document.createElement('button');
|
| - button.onclick = openClearBrowsingData;
|
| - button.textContent = localStrings.getString('clearallhistory');
|
| - this.editingControlsDiv_.appendChild(button);
|
| -
|
| - // Listen for clicks in the page to sync the disabled state.
|
| - document.addEventListener('click', this.boundUpdateRemoveButton_);
|
| - } else {
|
| - this.removeButton_ = null;
|
| - document.removeEventListener('click', this.boundUpdateRemoveButton_);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Updates the disabled state of the remove button when in editing mode.
|
| - * @param {!Event} e The click event object.
|
| - * @private
|
| - */
|
| -HistoryView.prototype.updateRemoveButton_ = function(e) {
|
| - if (e.target.tagName != 'INPUT')
|
| - return;
|
| -
|
| - var anyChecked = document.querySelector('.entry input:checked') != null;
|
| - if (this.removeButton_)
|
| - this.removeButton_.disabled = !anyChecked;
|
| -};
|
| -
|
| -/**
|
| * Update the pagination tools.
|
| */
|
| HistoryView.prototype.displayNavBar_ = function() {
|
| @@ -732,8 +726,7 @@ HistoryView.prototype.createPageNav_ = function(page, name) {
|
| anchor = document.createElement('a');
|
| anchor.className = 'page-navigation';
|
| anchor.textContent = name;
|
| - var hashString = PageState.getHashString(this.model_.getEditMode(),
|
| - this.model_.getSearchText(), page);
|
| + var hashString = PageState.getHashString(this.model_.getSearchText(), page);
|
| var link = 'chrome://history/' + (hashString ? '#' + hashString : '');
|
| anchor.href = link;
|
| anchor.onclick = function() {
|
| @@ -755,23 +748,24 @@ HistoryView.prototype.updateEntryAnchorWidth_ = function() {
|
| return;
|
|
|
| // Create new CSS rules and add them last to the last stylesheet.
|
| - if (!this.entryAnchorRule_) {
|
| - var styleSheets = document.styleSheets;
|
| - var styleSheet = styleSheets[styleSheets.length - 1];
|
| - var rules = styleSheet.cssRules;
|
| - var createRule = function(selector) {
|
| - styleSheet.insertRule(selector + '{}', rules.length);
|
| - return rules[rules.length - 1];
|
| - };
|
| - this.entryAnchorRule_ = createRule('.entry .title > a');
|
| - // The following rule needs to be more specific to have higher priority.
|
| - this.entryAnchorStarredRule_ = createRule('.entry .title.starred > a');
|
| - }
|
| -
|
| - var anchorMaxWith = titleElement.offsetWidth;
|
| - this.entryAnchorRule_.style.maxWidth = anchorMaxWith + 'px';
|
| - // Adjust by the width of star plus its margin.
|
| - this.entryAnchorStarredRule_.style.maxWidth = anchorMaxWith - 23 + 'px';
|
| + // TODO(jochen): The following code does not work due to WebKit bug #32309
|
| + // if (!this.entryAnchorRule_) {
|
| + // var styleSheets = document.styleSheets;
|
| + // var styleSheet = styleSheets[styleSheets.length - 1];
|
| + // var rules = styleSheet.cssRules;
|
| + // var createRule = function(selector) {
|
| + // styleSheet.insertRule(selector + '{}', rules.length);
|
| + // return rules[rules.length - 1];
|
| + // };
|
| + // this.entryAnchorRule_ = createRule('.entry .title > a');
|
| + // // The following rule needs to be more specific to have higher priority.
|
| + // this.entryAnchorStarredRule_ = createRule('.entry .title.starred > a');
|
| + // }
|
| + //
|
| + // var anchorMaxWith = titleElement.offsetWidth;
|
| + // this.entryAnchorRule_.style.maxWidth = anchorMaxWith + 'px';
|
| + // // Adjust by the width of star plus its margin.
|
| + // this.entryAnchorStarredRule_.style.maxWidth = anchorMaxWith - 23 + 'px';
|
| };
|
|
|
| ///////////////////////////////////////////////////////////////////////////////
|
| @@ -841,13 +835,12 @@ PageState.prototype.getHashData = function() {
|
| * @param {string} term The current search string.
|
| * @param {string} page The page currently being viewed.
|
| */
|
| -PageState.prototype.setUIState = function(editMode, term, page) {
|
| +PageState.prototype.setUIState = function(term, page) {
|
| // Make sure the form looks pretty.
|
| document.forms[0].term.value = term;
|
| var currentHash = this.getHashData();
|
| - if (Boolean(currentHash.e) != editMode || currentHash.q != term ||
|
| - currentHash.p != page) {
|
| - window.location.hash = PageState.getHashString(editMode, term, page);
|
| + if (currentHash.q != term || currentHash.p != page) {
|
| + window.location.hash = PageState.getHashString(term, page);
|
| }
|
| };
|
|
|
| @@ -857,11 +850,8 @@ PageState.prototype.setUIState = function(editMode, term, page) {
|
| * @param {string} page The page currently being viewed.
|
| * @return {string} The string to be used in a hash.
|
| */
|
| -PageState.getHashString = function(editMode, term, page) {
|
| +PageState.getHashString = function(term, page) {
|
| var newHash = [];
|
| - if (editMode) {
|
| - newHash.push('e=1');
|
| - }
|
| if (term) {
|
| newHash.push('q=' + encodeURIComponent(term));
|
| }
|
| @@ -887,12 +877,9 @@ function load() {
|
|
|
| // Create default view.
|
| var hashData = pageState.getHashData();
|
| - if (Boolean(hashData.e)) {
|
| - historyView.toggleEditMode();
|
| - }
|
| historyView.setSearch(hashData.q, hashData.p);
|
|
|
| - // Add handlers to HTML elements.
|
| + // Setup click handlers.
|
| $('history-section').onclick = function () {
|
| setSearch('');
|
| return false;
|
| @@ -901,6 +888,15 @@ function load() {
|
| setSearch(this.term.value);
|
| return false;
|
| };
|
| +
|
| + $('remove-page').addEventListener('activate', function(e) {
|
| + activePage.removeFromHistory_();
|
| + activePage = null;
|
| + });
|
| + $('more-from-site').addEventListener('activate', function(e) {
|
| + activePage.showMoreFromSite_();
|
| + activePage = null;
|
| + });
|
| }
|
|
|
| /**
|
| @@ -926,24 +922,16 @@ function setPage(page) {
|
| }
|
|
|
| /**
|
| - * TODO(glen): Get rid of this function.
|
| - * Toggles edit mode.
|
| - */
|
| -function toggleEditMode() {
|
| - if (historyView) {
|
| - historyView.toggleEditMode();
|
| - historyView.reload();
|
| - }
|
| -}
|
| -
|
| -/**
|
| * Delete the next item in our deletion queue.
|
| */
|
| function deleteNextInQueue() {
|
| - if (!deleteInFlight && deleteQueue.length) {
|
| - deleteInFlight = true;
|
| + if (deleteQueue.length > 0) {
|
| + // Call the native function to remove history entries.
|
| + // First arg is a time in seconds (passed as String) identifying the day.
|
| + // Remaining args are URLs of history entries from that day to delete.
|
| + var timeInSeconds = Math.floor(deleteQueue[0].date.getTime() / 1000);
|
| chrome.send('removeURLsOnOneDay',
|
| - [String(deleteQueue[0])].concat(deleteQueue[1]));
|
| + [String(timeInSeconds)].concat(deleteQueue[0].urls));
|
| }
|
| }
|
|
|
| @@ -956,52 +944,68 @@ function openClearBrowsingData() {
|
| }
|
|
|
| /**
|
| + * Queue a set of URLs from the same day for deletion.
|
| + * @param {Date} date A date indicating the day the URLs were visited.
|
| + * @param {Array} urls Array of URLs from the same day to be deleted.
|
| + * @param {Function} opt_callback An optional callback to be executed when
|
| + * the deletion is complete.
|
| + */
|
| +function queueURLsForDeletion(date, urls, opt_callback) {
|
| + deleteQueue.push({ 'date': date, 'urls': urls, 'callback': opt_callback });
|
| +}
|
| +
|
| +function reloadHistory() {
|
| + historyView.reload();
|
| +}
|
| +
|
| +/**
|
| * Collect IDs from checked checkboxes and send to Chrome for deletion.
|
| */
|
| function removeItems() {
|
| - var checkboxes = document.getElementsByTagName('input');
|
| - var ids = [];
|
| + var checked = document.querySelectorAll(
|
| + 'input[type=checkbox]:checked:not([disabled])');
|
| + var urls = [];
|
| var disabledItems = [];
|
| var queue = [];
|
| var date = new Date();
|
| - for (var i = 0; i < checkboxes.length; i++) {
|
| - if (checkboxes[i].type == 'checkbox' && checkboxes[i].checked &&
|
| - !checkboxes[i].disabled) {
|
| - var cbDate = new Date(checkboxes[i].time);
|
| - if (date.getFullYear() != cbDate.getFullYear() ||
|
| - date.getMonth() != cbDate.getMonth() ||
|
| - date.getDate() != cbDate.getDate()) {
|
| - if (ids.length > 0) {
|
| - queue.push(date.valueOf() / 1000);
|
| - queue.push(ids);
|
| - }
|
| - ids = [];
|
| - date = cbDate;
|
| +
|
| + for (var i = 0; i < checked.length; i++) {
|
| + var checkbox = checked[i];
|
| + var cbDate = new Date(checkbox.time);
|
| + if (date.getFullYear() != cbDate.getFullYear() ||
|
| + date.getMonth() != cbDate.getMonth() ||
|
| + date.getDate() != cbDate.getDate()) {
|
| + if (urls.length > 0) {
|
| + queue.push([date, urls]);
|
| }
|
| - var link = $('id-' + checkboxes[i].name);
|
| - checkboxes[i].disabled = true;
|
| - link.style.textDecoration = 'line-through';
|
| - disabledItems.push(checkboxes[i]);
|
| - ids.push(link.href);
|
| + urls = [];
|
| + date = cbDate;
|
| }
|
| + var link = checkbox.parentNode.parentNode.querySelector('a');
|
| + checkbox.disabled = true;
|
| + link.classList.add('to-be-removed');
|
| + disabledItems.push(checkbox);
|
| + urls.push(link.href);
|
| }
|
| - if (ids.length > 0) {
|
| - queue.push(date.valueOf() / 1000);
|
| - queue.push(ids);
|
| + if (urls.length > 0) {
|
| + queue.push([date, urls]);
|
| }
|
| - if (queue.length > 0) {
|
| - if (confirm(localStrings.getString('deletewarning'))) {
|
| - deleteQueue = deleteQueue.concat(queue);
|
| - deleteNextInQueue();
|
| - historyView.removeButton_.disabled = true;
|
| - } else {
|
| - // If the remove is cancelled, return the checkboxes to their
|
| - // enabled, non-line-through state.
|
| - for (var i = 0; i < disabledItems.length; i++) {
|
| - var link = $('id-' + disabledItems[i].name);
|
| - disabledItems[i].disabled = false;
|
| - link.style.textDecoration = '';
|
| - }
|
| + if (checked.length > 0 && confirm(localStrings.getString('deletewarning'))) {
|
| + for (var i = 0; i < queue.length; i++) {
|
| + // Reload the page when the final entry has been deleted.
|
| + var callback = i == 0 ? reloadHistory : null;
|
| +
|
| + queueURLsForDeletion(queue[i][0], queue[i][1], callback);
|
| + }
|
| + deleteNextInQueue();
|
| + } else {
|
| + // If the remove is cancelled, return the checkboxes to their
|
| + // enabled, non-line-through state.
|
| + for (var i = 0; i < disabledItems.length; i++) {
|
| + var checkbox = disabledItems[i];
|
| + var link = checkbox.parentNode.parentNode.querySelector('a');
|
| + checkbox.disabled = false;
|
| + link.classList.remove('to-be-removed');
|
| }
|
| }
|
| return false;
|
| @@ -1011,18 +1015,65 @@ function removeItems() {
|
| * Toggle state of checkbox and handle Shift modifier.
|
| */
|
| function checkboxClicked(event) {
|
| + var id = Number(this.id.slice("checkbox-".length));
|
| if (event.shiftKey && (selectionAnchor != -1)) {
|
| var checked = this.checked;
|
| // Set all checkboxes from the anchor up to the clicked checkbox to the
|
| // state of the clicked one.
|
| - var begin = Math.min(this.name, selectionAnchor);
|
| - var end = Math.max(this.name, selectionAnchor);
|
| + var begin = Math.min(id, selectionAnchor);
|
| + var end = Math.max(id, selectionAnchor);
|
| for (var i = begin; i <= end; i++) {
|
| - id2checkbox[i].checked = checked;
|
| + var checkbox = document.querySelector('#checkbox-' + i);
|
| + if (checkbox)
|
| + checkbox.checked = checked;
|
| }
|
| }
|
| - selectionAnchor = this.name;
|
| - this.focus();
|
| + selectionAnchor = id;
|
| +
|
| + historyView.updateRemoveButton();
|
| +}
|
| +
|
| +function entryBoxMousedown(event) {
|
| + // Prevent text selection when shift-clicking to select multiple entries.
|
| + if (event.shiftKey) {
|
| + event.preventDefault();
|
| + }
|
| +}
|
| +
|
| +function removeNode(node) {
|
| + node.classList.add('fade-out'); // Trigger CSS fade out animation.
|
| +
|
| + // Delete the node when the animation is complete.
|
| + node.addEventListener('webkitTransitionEnd', function() {
|
| + node.parentNode.removeChild(node);
|
| + });
|
| +}
|
| +
|
| +/**
|
| + * Removes a single entry from the view. Also removes gaps before and after
|
| + * entry if necessary.
|
| + */
|
| +function removeEntryFromView(entry) {
|
| + var nextEntry = entry.nextSibling;
|
| + var previousEntry = entry.previousSibling;
|
| +
|
| + removeNode(entry);
|
| +
|
| + // if there is no previous entry, and the next entry is a gap, remove it
|
| + if (!previousEntry && nextEntry && nextEntry.className == 'gap') {
|
| + removeNode(nextEntry);
|
| + }
|
| +
|
| + // if there is no next entry, and the previous entry is a gap, remove it
|
| + if (!nextEntry && previousEntry && previousEntry.className == 'gap') {
|
| + removeNode(previousEntry);
|
| + }
|
| +
|
| + // if both the next and previous entries are gaps, remove one
|
| + if (nextEntry && nextEntry.className == 'gap' &&
|
| + previousEntry && previousEntry.className == 'gap') {
|
| + removeNode(nextEntry);
|
| + }
|
| }
|
|
|
| ///////////////////////////////////////////////////////////////////////////////
|
| @@ -1038,13 +1089,14 @@ function historyResult(info, results) {
|
| * Our history system calls this function when a deletion has finished.
|
| */
|
| function deleteComplete() {
|
| - window.console.log('Delete complete');
|
| - deleteInFlight = false;
|
| - if (deleteQueue.length > 2) {
|
| - deleteQueue = deleteQueue.slice(2);
|
| + if (deleteQueue.length > 0) {
|
| + // Remove the successfully deleted entry from the queue.
|
| + if (deleteQueue[0].callback)
|
| + deleteQueue[0].callback.apply();
|
| + deleteQueue.splice(0, 1);
|
| deleteNextInQueue();
|
| } else {
|
| - deleteQueue = [];
|
| + console.error('Received deleteComplete but queue is empty.');
|
| }
|
| }
|
|
|
| @@ -1054,20 +1106,28 @@ function deleteComplete() {
|
| */
|
| function deleteFailed() {
|
| window.console.log('Delete failed');
|
| +
|
| // The deletion failed - try again later.
|
| - deleteInFlight = false;
|
| + // TODO(dubroy): We should probably give up at some point.
|
| setTimeout(deleteNextInQueue, 500);
|
| }
|
|
|
| /**
|
| - * We're called when something is deleted (either by us or by someone
|
| - * else).
|
| + * Called when the history is deleted by someone else.
|
| */
|
| function historyDeleted() {
|
| window.console.log('History deleted');
|
| var anyChecked = document.querySelector('.entry input:checked') != null;
|
| - if (!(historyView.getEditMode() && anyChecked))
|
| + // Reload the page, unless the user has any items checked.
|
| + // TODO(dubroy): We should just reload the page & restore the checked items.
|
| + if (!anyChecked)
|
| historyView.reload();
|
| }
|
|
|
| +// Add handlers to HTML elements.
|
| document.addEventListener('DOMContentLoaded', load);
|
| +
|
| +// This event lets us enable and disable menu items before the menu is shown.
|
| +document.addEventListener('canExecute', function(e) {
|
| + e.canExecute = true;
|
| +});
|
|
|