| Index: chrome/browser/resources/ntp/most_visited.js
|
| ===================================================================
|
| --- chrome/browser/resources/ntp/most_visited.js (revision 45984)
|
| +++ chrome/browser/resources/ntp/most_visited.js (working copy)
|
| @@ -2,491 +2,578 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| -var mostVisitedData = [];
|
| -var gotMostVisited = false;
|
| +// Dependencies that we should remove/formalize:
|
| +// ../shared/js/class_list.js
|
| +// util.js
|
| +//
|
| +// afterTransition
|
| +// chrome.send
|
| +// hideNotification
|
| +// isRtl
|
| +// localStrings
|
| +// logEvent
|
| +// showNotification
|
|
|
| -function mostVisitedPages(data, firstRun) {
|
| - logEvent('received most visited pages');
|
|
|
| - // We append the class name with the "filler" so that we can style fillers
|
| - // differently.
|
| - var maxItems = 8;
|
| - data.length = Math.min(maxItems, data.length);
|
| - var len = data.length;
|
| - for (var i = len; i < maxItems; i++) {
|
| - data[i] = {filler: true};
|
| - }
|
| +var MostVisited = (function() {
|
|
|
| - mostVisitedData = data;
|
| - renderMostVisited(data);
|
| -
|
| - gotMostVisited = true;
|
| - onDataLoaded();
|
| -
|
| - // Only show the first run notification if first run.
|
| - if (firstRun) {
|
| - showFirstRunNotification();
|
| + function addPinnedUrl(item, index) {
|
| + chrome.send('addPinnedURL', [item.url, item.title, item.faviconUrl || '',
|
| + item.thumbnailUrl || '', String(index)]);
|
| }
|
| -}
|
| -function getThumbnailClassName(data) {
|
| - return 'thumbnail-container' +
|
| - (data.pinned ? ' pinned' : '') +
|
| - (data.filler ? ' filler' : '');
|
| -}
|
|
|
| -function renderMostVisited(data) {
|
| - var parent = $('most-visited');
|
| - var children = parent.children;
|
| - for (var i = 0; i < data.length; i++) {
|
| - var d = data[i];
|
| - var t = children[i];
|
| -
|
| - // If we have a filler continue
|
| - var oldClassName = t.className;
|
| - var newClassName = getThumbnailClassName(d);
|
| - if (oldClassName != newClassName) {
|
| - t.className = newClassName;
|
| - }
|
| -
|
| - // No need to continue if this is a filler.
|
| - if (newClassName == 'thumbnail-container filler') {
|
| - // Make sure the user cannot tab to the filler.
|
| - t.tabIndex = -1;
|
| - continue;
|
| - }
|
| - // Allow focus.
|
| - t.tabIndex = 1;
|
| -
|
| - t.href = d.url;
|
| - t.querySelector('.pin').title = localStrings.getString(d.pinned ?
|
| - 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
|
| - t.querySelector('.remove').title =
|
| - localStrings.getString('removethumbnailtooltip');
|
| -
|
| - // There was some concern that a malformed malicious URL could cause an XSS
|
| - // attack but setting style.backgroundImage = 'url(javascript:...)' does
|
| - // not execute the JavaScript in WebKit.
|
| -
|
| - var thumbnailUrl = d.thumbnailUrl || 'chrome://thumb/' + d.url;
|
| - t.querySelector('.thumbnail-wrapper').style.backgroundImage =
|
| - url(thumbnailUrl);
|
| - var titleDiv = t.querySelector('.title > div');
|
| - titleDiv.xtitle = titleDiv.textContent = d.title;
|
| - var faviconUrl = d.faviconUrl || 'chrome://favicon/' + d.url;
|
| - titleDiv.style.backgroundImage = url(faviconUrl);
|
| - titleDiv.dir = d.direction;
|
| + function getItem(el) {
|
| + return findAncestorByClass(el, 'thumbnail-container');
|
| }
|
| -}
|
|
|
| -var mostVisited = {
|
| - addPinnedUrl_: function(data, index) {
|
| - chrome.send('addPinnedURL', [data.url, data.title, data.faviconUrl || '',
|
| - data.thumbnailUrl || '', String(index)]);
|
| - },
|
| - getItem: function(el) {
|
| - return findAncestorByClass(el, 'thumbnail-container');
|
| - },
|
| -
|
| - getHref: function(el) {
|
| - return el.href;
|
| - },
|
| -
|
| - togglePinned: function(el) {
|
| - var index = this.getThumbnailIndex(el);
|
| - var data = mostVisitedData[index];
|
| - data.pinned = !data.pinned;
|
| - if (data.pinned) {
|
| - this.addPinnedUrl_(data, index);
|
| - } else {
|
| - chrome.send('removePinnedURL', [data.url]);
|
| - }
|
| - this.updatePinnedDom_(el, data.pinned);
|
| - },
|
| -
|
| - updatePinnedDom_: function(el, pinned) {
|
| + function updatePinnedDom(el, pinned) {
|
| el.querySelector('.pin').title = localStrings.getString(pinned ?
|
| 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
|
| if (pinned) {
|
| - addClass(el, 'pinned');
|
| + el.classList.add('pinned');
|
| } else {
|
| - removeClass(el, 'pinned');
|
| + el.classList.remove('pinned');
|
| }
|
| - },
|
| + }
|
|
|
| - getThumbnailIndex: function(el) {
|
| + function getThumbnailIndex(el) {
|
| var nodes = el.parentNode.querySelectorAll('.thumbnail-container');
|
| return Array.prototype.indexOf.call(nodes, el);
|
| - },
|
| + }
|
|
|
| - swapPosition: function(source, destination) {
|
| - var nodes = source.parentNode.querySelectorAll('.thumbnail-container');
|
| - var sourceIndex = this.getThumbnailIndex(source);
|
| - var destinationIndex = this.getThumbnailIndex(destination);
|
| - swapDomNodes(source, destination);
|
| + function MostVisited(el, useSmallGrid, visible) {
|
| + this.element = el;
|
| + this.useSmallGrid_ = useSmallGrid;
|
| + this.visible_ = visible;
|
|
|
| - var sourceData = mostVisitedData[sourceIndex];
|
| - this.addPinnedUrl_(sourceData, destinationIndex);
|
| - sourceData.pinned = true;
|
| - this.updatePinnedDom_(source, true);
|
| + this.createThumbnails_();
|
| + this.applyMostVisitedRects_();
|
|
|
| - var destinationData = mostVisitedData[destinationIndex];
|
| - // Only update the destination if it was pinned before.
|
| - if (destinationData.pinned) {
|
| - this.addPinnedUrl_(destinationData, sourceIndex);
|
| - }
|
| - mostVisitedData[destinationIndex] = sourceData;
|
| - mostVisitedData[sourceIndex] = destinationData;
|
| - },
|
| + el.addEventListener('click', bind(this.handleClick_, this));
|
| + el.addEventListener('keydown', bind(this.handleKeyDown_, this));
|
|
|
| - blacklist: function(el) {
|
| - var self = this;
|
| - var url = this.getHref(el);
|
| - chrome.send('blacklistURLFromMostVisited', [url]);
|
| + document.addEventListener('DOMContentLoaded',
|
| + bind(this.ensureSmallGridCorrect, this));
|
|
|
| - addClass(el, 'hide');
|
| + // DND
|
| + el.addEventListener('dragstart', bind(this.handleDragStart_, this));
|
| + el.addEventListener('dragenter', bind(this.handleDragEnter_, this));
|
| + el.addEventListener('dragover', bind(this.handleDragOver_, this));
|
| + el.addEventListener('dragleave', bind(this.handleDragLeave_, this));
|
| + el.addEventListener('drop', bind(this.handleDrop_, this));
|
| + el.addEventListener('dragend', bind(this.handleDragEnd_, this));
|
| + el.addEventListener('drag', bind(this.handleDrag_, this));
|
| + el.addEventListener('mousedown', bind(this.handleMouseDown_, this));
|
| + }
|
|
|
| - // Find the old item.
|
| - var oldUrls = {};
|
| - var oldIndex = -1;
|
| - var oldItem;
|
| - for (var i = 0; i < mostVisitedData.length; i++) {
|
| - if (mostVisitedData[i].url == url) {
|
| - oldItem = mostVisitedData[i];
|
| - oldIndex = i;
|
| + MostVisited.prototype = {
|
| + togglePinned_: function(el) {
|
| + var index = getThumbnailIndex(el);
|
| + var item = this.data[index];
|
| + item.pinned = !item.pinned;
|
| + if (item.pinned) {
|
| + addPinnedUrl(item, index);
|
| + } else {
|
| + chrome.send('removePinnedURL', [item.url]);
|
| }
|
| - oldUrls[mostVisitedData[i].url] = true;
|
| - }
|
| + updatePinnedDom(el, item.pinned);
|
| + },
|
|
|
| - // Send 'getMostVisitedPages' with a callback since we want to find the new
|
| - // page and add that in the place of the removed page.
|
| - chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
|
| - // Find new item.
|
| - var newItem;
|
| + swapPosition_: function(source, destination) {
|
| + var nodes = source.parentNode.querySelectorAll('.thumbnail-container');
|
| + var sourceIndex = getThumbnailIndex(source);
|
| + var destinationIndex = getThumbnailIndex(destination);
|
| + swapDomNodes(source, destination);
|
| +
|
| + var sourceData = this.data[sourceIndex];
|
| + addPinnedUrl(sourceData, destinationIndex);
|
| + sourceData.pinned = true;
|
| + updatePinnedDom(source, true);
|
| +
|
| + var destinationData = this.data[destinationIndex];
|
| + // Only update the destination if it was pinned before.
|
| + if (destinationData.pinned) {
|
| + addPinnedUrl(destinationData, sourceIndex);
|
| + }
|
| + this.data[destinationIndex] = sourceData;
|
| + this.data[sourceIndex] = destinationData;
|
| + },
|
| +
|
| + blacklist: function(el) {
|
| + var self = this;
|
| + var url = el.href;
|
| + chrome.send('blacklistURLFromMostVisited', [url]);
|
| +
|
| + el.classList.add('hide');
|
| +
|
| + // Find the old item.
|
| + var oldUrls = {};
|
| + var oldIndex = -1;
|
| + var oldItem;
|
| + var data = this.data;
|
| for (var i = 0; i < data.length; i++) {
|
| - if (!(data[i].url in oldUrls)) {
|
| - newItem = data[i];
|
| - break;
|
| + if (data[i].url == url) {
|
| + oldItem = data[i];
|
| + oldIndex = i;
|
| }
|
| + oldUrls[data[i].url] = true;
|
| }
|
|
|
| - if (!newItem) {
|
| - // If no other page is available to replace the blacklisted item,
|
| - // we need to reorder items s.t. all filler items are in the rightmost
|
| - // indices.
|
| - mostVisitedPages(data);
|
| + // Send 'getMostVisitedPages' with a callback since we want to find the
|
| + // new page and add that in the place of the removed page.
|
| + chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
|
| + // Find new item.
|
| + var newItem;
|
| + for (var i = 0; i < data.length; i++) {
|
| + if (!(data[i].url in oldUrls)) {
|
| + newItem = data[i];
|
| + break;
|
| + }
|
| + }
|
|
|
| - // Replace old item with new item in the mostVisitedData array.
|
| - } else if (oldIndex != -1) {
|
| - mostVisitedData.splice(oldIndex, 1, newItem);
|
| - mostVisitedPages(mostVisitedData);
|
| - addClass(el, 'fade-in');
|
| - }
|
| + if (!newItem) {
|
| + // If no other page is available to replace the blacklisted item,
|
| + // we need to reorder items s.t. all filler items are in the rightmost
|
| + // indices.
|
| + self.data = data;
|
|
|
| - // We wrap the title in a <span class=blacklisted-title>. We pass an empty
|
| - // string to the notifier function and use DOM to insert the real string.
|
| - var actionText = localStrings.getString('undothumbnailremove');
|
| + // Replace old item with new item in the most visited data array.
|
| + } else if (oldIndex != -1) {
|
| + var oldData = self.data.concat();
|
| + oldData.splice(oldIndex, 1, newItem);
|
| + self.data = oldData;
|
| + el.classList.add('fade-in');
|
| + }
|
|
|
| - // Show notification and add undo callback function.
|
| - var wasPinned = oldItem.pinned;
|
| - showNotification('', actionText, function() {
|
| - self.removeFromBlackList(url);
|
| - if (wasPinned) {
|
| - self.addPinnedUrl_(oldItem, oldIndex);
|
| - }
|
| - chrome.send('getMostVisited');
|
| + // We wrap the title in a <span class=blacklisted-title>. We pass an
|
| + // empty string to the notifier function and use DOM to insert the real
|
| + // string.
|
| + var actionText = localStrings.getString('undothumbnailremove');
|
| +
|
| + // Show notification and add undo callback function.
|
| + var wasPinned = oldItem.pinned;
|
| + showNotification('', actionText, function() {
|
| + self.removeFromBlackList(url);
|
| + if (wasPinned) {
|
| + addPinnedUrl(oldItem, oldIndex);
|
| + }
|
| + chrome.send('getMostVisited');
|
| + });
|
| +
|
| + // Now change the DOM.
|
| + var removeText = localStrings.getString('thumbnailremovednotification');
|
| + var notifySpan = document.querySelector('#notification > span');
|
| + notifySpan.textContent = removeText;
|
| +
|
| + // Focus the undo link.
|
| + var undoLink = document.querySelector(
|
| + '#notification > .link > [tabindex]');
|
| + undoLink.focus();
|
| });
|
| + },
|
|
|
| - // Now change the DOM.
|
| - var removeText = localStrings.getString('thumbnailremovednotification');
|
| - var notifySpan = document.querySelector('#notification > span');
|
| - notifySpan.textContent = removeText;
|
| + removeFromBlackList: function(url) {
|
| + chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
|
| + },
|
|
|
| - // Focus the undo link.
|
| - var undoLink = document.querySelector(
|
| - '#notification > .link > [tabindex]');
|
| - undoLink.focus();
|
| - });
|
| - },
|
| + clearAllBlacklisted: function() {
|
| + chrome.send('clearMostVisitedURLsBlacklist', []);
|
| + hideNotification();
|
| + },
|
|
|
| - removeFromBlackList: function(url) {
|
| - chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
|
| - },
|
| + dirty_: false,
|
| + invalidate_: function() {
|
| + this.dirty_ = true;
|
| + },
|
|
|
| - clearAllBlacklisted: function() {
|
| - chrome.send('clearMostVisitedURLsBlacklist', []);
|
| - hideNotification();
|
| - },
|
| + visible_: true,
|
| + get visible() {
|
| + return this.visible_;
|
| + },
|
| + set visible(visible) {
|
| + if (this.visible_ != visible) {
|
| + this.visible_ = visible;
|
| + this.invalidate_();
|
| + }
|
| + },
|
|
|
| - updateDisplayMode: function() {
|
| - if (!this.dirty_) {
|
| - return;
|
| - }
|
| - updateSimpleSection('most-visited-section', Section.THUMB);
|
| - },
|
| + useSmallGrid_: false,
|
| + get useSmallGrid() {
|
| + return this.useSmallGrid_;
|
| + },
|
| + set useSmallGrid(b) {
|
| + if (this.useSmallGrid_ != b) {
|
| + this.useSmallGrid_ = b;
|
| + this.invalidate_();
|
| + }
|
| + },
|
|
|
| - dirty_: false,
|
| + layout: function() {
|
| + if (!this.dirty_)
|
| + return;
|
| + var d0 = Date.now();
|
| + this.applyMostVisitedRects_();
|
| + this.dirty_ = false;
|
| + logEvent('mostVisited.layout: ' + (Date.now() - d0));
|
| + },
|
|
|
| - invalidate: function() {
|
| - this.dirty_ = true;
|
| - },
|
| + createThumbnails_: function() {
|
| + var singleHtml =
|
| + '<a class="thumbnail-container filler" tabindex="1">' +
|
| + '<div class="edit-mode-border">' +
|
| + '<div class="edit-bar">' +
|
| + '<div class="pin"></div>' +
|
| + '<div class="spacer"></div>' +
|
| + '<div class="remove"></div>' +
|
| + '</div>' +
|
| + '<span class="thumbnail-wrapper">' +
|
| + '<span class="thumbnail"></span>' +
|
| + '</span>' +
|
| + '</div>' +
|
| + '<div class="title">' +
|
| + '<div></div>' +
|
| + '</div>' +
|
| + '</a>';
|
| + this.element.innerHTML = Array(8 + 1).join(singleHtml);
|
| + var children = this.element.children;
|
| + for (var i = 0; i < 8; i++) {
|
| + children[i].id = 't' + i;
|
| + }
|
| + },
|
|
|
| - layout: function() {
|
| - if (!this.dirty_) {
|
| - return;
|
| - }
|
| - var d0 = Date.now();
|
| + getMostVisitedLayoutRects_: function() {
|
| + var small = this.useSmallGrid;
|
|
|
| - var mostVisitedElement = $('most-visited');
|
| - var thumbnails = mostVisitedElement.children;
|
| - var hidden = !(shownSections & Section.THUMB);
|
| + var cols = 4;
|
| + var rows = 2;
|
| + var marginWidth = 10;
|
| + var marginHeight = 7;
|
| + var borderWidth = 4;
|
| + var thumbWidth = small ? 150 : 207;
|
| + var thumbHeight = small ? 93 : 129;
|
| + var w = thumbWidth + 2 * borderWidth + 2 * marginWidth;
|
| + var h = thumbHeight + 40 + 2 * marginHeight;
|
| + var sumWidth = cols * w - 2 * marginWidth;
|
|
|
| + var rtl = isRtl();
|
| + var rects = [];
|
|
|
| - // We set overflow to hidden so that the most visited element does not
|
| - // "leak" when we hide and show it.
|
| - if (hidden) {
|
| - mostVisitedElement.style.overflow = 'hidden';
|
| - }
|
| + if (this.visible) {
|
| + for (var i = 0; i < rows * cols; i++) {
|
| + var row = Math.floor(i / cols);
|
| + var col = i % cols;
|
| + var left = rtl ? sumWidth - col * w - thumbWidth - 2 * borderWidth :
|
| + col * w;
|
|
|
| - applyMostVisitedRects();
|
| + var top = row * h;
|
|
|
| - // Only set overflow to visible if the element is shown.
|
| - if (!hidden) {
|
| - afterTransition(function() {
|
| - mostVisitedElement.style.overflow = '';
|
| - });
|
| - }
|
| + rects[i] = {left: left, top: top};
|
| + }
|
| + }
|
| + return rects;
|
| + },
|
|
|
| - this.dirty_ = false;
|
| + applyMostVisitedRects_: function() {
|
| + if (this.visible) {
|
| + var rects = this.getMostVisitedLayoutRects_();
|
| + var children = this.element.children;
|
| + for (var i = 0; i < 8; i++) {
|
| + var t = children[i];
|
| + t.style.left = rects[i].left + 'px';
|
| + t.style.top = rects[i].top + 'px';
|
| + t.style.right = '';
|
| + var innerStyle = t.firstElementChild.style;
|
| + innerStyle.left = innerStyle.top = '';
|
| + }
|
| + }
|
| + },
|
|
|
| - logEvent('mostVisited.layout: ' + (Date.now() - d0));
|
| - },
|
| + // Work around for http://crbug.com/25329
|
| + ensureSmallGridCorrect: function(expected) {
|
| + if (expected != this.useSmallGrid)
|
| + this.applyMostVisitedRects_();
|
| + },
|
|
|
| - getRectByIndex: function(index) {
|
| - return getMostVisitedLayoutRects()[index];
|
| - }
|
| -};
|
| + getRectByIndex_: function(index) {
|
| + return this.getMostVisitedLayoutRects_()[index];
|
| + },
|
|
|
| -$('most-visited').addEventListener('click', function(e) {
|
| - var target = e.target;
|
| - if (hasClass(target, 'pin')) {
|
| - mostVisited.togglePinned(mostVisited.getItem(target));
|
| - e.preventDefault();
|
| - } else if (hasClass(target, 'remove')) {
|
| - mostVisited.blacklist(mostVisited.getItem(target));
|
| - e.preventDefault();
|
| - }
|
| -});
|
| + // DND
|
|
|
| -// Allow blacklisting most visited site using the keyboard.
|
| -$('most-visited').addEventListener('keydown', function(e) {
|
| - if (!IS_MAC && e.keyCode == 46 || // Del
|
| - IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
|
| - mostVisited.blacklist(e.target);
|
| - }
|
| -});
|
| + currentOverItem_: null,
|
| + get currentOverItem() {
|
| + return this.currentOverItem_;
|
| + },
|
| + set currentOverItem(item) {
|
| + var style;
|
| + if (item != this.currentOverItem_) {
|
| + if (this.currentOverItem_) {
|
| + style = this.currentOverItem_.firstElementChild.style;
|
| + style.left = style.top = '';
|
| + }
|
| + this.currentOverItem_ = item;
|
|
|
| -window.addEventListener('load', onDataLoaded);
|
| + if (item) {
|
| + // Make the drag over item move 15px towards the source. The movement
|
| + // is done by only moving the edit-mode-border (as in the mocks) and
|
| + // it is done with relative positioning so that the movement does not
|
| + // change the drop target.
|
| + var dragIndex = getThumbnailIndex(this.dragItem_);
|
| + var overIndex = getThumbnailIndex(item);
|
| + if (dragIndex == -1 || overIndex == -1) {
|
| + return;
|
| + }
|
|
|
| -window.addEventListener('resize', handleWindowResize);
|
| + var dragRect = this.getRectByIndex_(dragIndex);
|
| + var overRect = this.getRectByIndex_(overIndex);
|
|
|
| -// Work around for http://crbug.com/25329
|
| -function ensureSmallGridCorrect() {
|
| - if (wasSmallGrid != useSmallGrid()) {
|
| - applyMostVisitedRects();
|
| - }
|
| -}
|
| -document.addEventListener('DOMContentLoaded', ensureSmallGridCorrect);
|
| + var x = dragRect.left - overRect.left;
|
| + var y = dragRect.top - overRect.top;
|
| + var z = Math.sqrt(x * x + y * y);
|
| + var z2 = 15;
|
| + var x2 = x * z2 / z;
|
| + var y2 = y * z2 / z;
|
|
|
| -// DnD
|
| + style = this.currentOverItem_.firstElementChild.style;
|
| + style.left = x2 + 'px';
|
| + style.top = y2 + 'px';
|
| + }
|
| + }
|
| + },
|
| + dragItem_: null,
|
| + startX_: 0,
|
| + startY_: 0,
|
| + startScreenX_: 0,
|
| + startScreenY_: 0,
|
| + dragEndTimer_: null,
|
|
|
| -var dnd = {
|
| - currentOverItem_: null,
|
| - get currentOverItem() {
|
| - return this.currentOverItem_;
|
| - },
|
| - set currentOverItem(item) {
|
| - var style;
|
| - if (item != this.currentOverItem_) {
|
| - if (this.currentOverItem_) {
|
| - style = this.currentOverItem_.firstElementChild.style;
|
| - style.left = style.top = '';
|
| + isDragging: function() {
|
| + return !!this.dragItem_;
|
| + },
|
| +
|
| + handleDragStart_: function(e) {
|
| + var thumbnail = getItem(e.target);
|
| + if (thumbnail) {
|
| + // Don't set data since HTML5 does not allow setting the name for
|
| + // url-list. Instead, we just rely on the dragging of link behavior.
|
| + this.dragItem_ = thumbnail;
|
| + this.dragItem_.classList.add('dragging');
|
| + this.dragItem_.style.zIndex = 2;
|
| + e.dataTransfer.effectAllowed = 'copyLinkMove';
|
| }
|
| - this.currentOverItem_ = item;
|
| + },
|
|
|
| + handleDragEnter_: function(e) {
|
| + if (this.canDropOnElement_(this.currentOverItem)) {
|
| + e.preventDefault();
|
| + }
|
| + },
|
| +
|
| + handleDragOver_: function(e) {
|
| + var item = getItem(e.target);
|
| + this.currentOverItem = item;
|
| + if (this.canDropOnElement_(item)) {
|
| + e.preventDefault();
|
| + e.dataTransfer.dropEffect = 'move';
|
| + }
|
| + },
|
| +
|
| + handleDragLeave_: function(e) {
|
| + var item = getItem(e.target);
|
| if (item) {
|
| - // Make the drag over item move 15px towards the source. The movement is
|
| - // done by only moving the edit-mode-border (as in the mocks) and it is
|
| - // done with relative positioning so that the movement does not change
|
| - // the drop target.
|
| - var dragIndex = mostVisited.getThumbnailIndex(this.dragItem);
|
| - var overIndex = mostVisited.getThumbnailIndex(item);
|
| - if (dragIndex == -1 || overIndex == -1) {
|
| - return;
|
| + e.preventDefault();
|
| + }
|
| +
|
| + this.currentOverItem = null;
|
| + },
|
| +
|
| + handleDrop_: function(e) {
|
| + var dropTarget = getItem(e.target);
|
| + if (this.canDropOnElement_(dropTarget)) {
|
| + dropTarget.style.zIndex = 1;
|
| + this.swapPosition_(this.dragItem_, dropTarget);
|
| + // The timeout below is to allow WebKit to see that we turned off
|
| + // pointer-event before moving the thumbnails so that we can get out of
|
| + // hover mode.
|
| + window.setTimeout(bind(function() {
|
| + this.invalidate_();
|
| + this.layout();
|
| + }, this), 10);
|
| + e.preventDefault();
|
| + if (this.dragEndTimer_) {
|
| + window.clearTimeout(this.dragEndTimer_);
|
| + this.dragEndTimer_ = null;
|
| }
|
| + afterTransition(function() {
|
| + dropTarget.style.zIndex = '';
|
| + });
|
| + }
|
| + },
|
|
|
| - var dragRect = mostVisited.getRectByIndex(dragIndex);
|
| - var overRect = mostVisited.getRectByIndex(overIndex);
|
| + handleDragEnd_: function(e) {
|
| + var dragItem = this.dragItem_;
|
| + if (dragItem) {
|
| + dragItem.style.pointerEvents = '';
|
| + dragItem.classList.remove('dragging');
|
|
|
| - var x = dragRect.left - overRect.left;
|
| - var y = dragRect.top - overRect.top;
|
| - var z = Math.sqrt(x * x + y * y);
|
| - var z2 = 15;
|
| - var x2 = x * z2 / z;
|
| - var y2 = y * z2 / z;
|
| + afterTransition(function() {
|
| + // Delay resetting zIndex to let the animation finish.
|
| + dragItem.style.zIndex = '';
|
| + // Same for overflow.
|
| + dragItem.parentNode.style.overflow = '';
|
| + });
|
|
|
| - style = this.currentOverItem_.firstElementChild.style;
|
| - style.left = x2 + 'px';
|
| - style.top = y2 + 'px';
|
| + this.invalidate_();
|
| + this.layout();
|
| + this.dragItem_ = null;
|
| }
|
| - }
|
| - },
|
| - dragItem: null,
|
| - startX: 0,
|
| - startY: 0,
|
| - startScreenX: 0,
|
| - startScreenY: 0,
|
| - dragEndTimer: null,
|
| + },
|
|
|
| - handleDragStart: function(e) {
|
| - var thumbnail = mostVisited.getItem(e.target);
|
| - if (thumbnail) {
|
| - // Don't set data since HTML5 does not allow setting the name for
|
| - // url-list. Instead, we just rely on the dragging of link behavior.
|
| - this.dragItem = thumbnail;
|
| - addClass(this.dragItem, 'dragging');
|
| - this.dragItem.style.zIndex = 2;
|
| - e.dataTransfer.effectAllowed = 'copyLinkMove';
|
| - }
|
| - },
|
| + handleDrag_: function(e) {
|
| + // Moves the drag item making sure that it is not displayed outside the
|
| + // browser viewport.
|
| + var item = getItem(e.target);
|
| + var rect = this.element.getBoundingClientRect();
|
| + item.style.pointerEvents = 'none';
|
|
|
| - handleDragEnter: function(e) {
|
| - if (this.canDropOnElement(this.currentOverItem)) {
|
| - e.preventDefault();
|
| - }
|
| - },
|
| + var x = this.startX_ + e.screenX - this.startScreenX_;
|
| + var y = this.startY_ + e.screenY - this.startScreenY_;
|
|
|
| - handleDragOver: function(e) {
|
| - var item = mostVisited.getItem(e.target);
|
| - this.currentOverItem = item;
|
| - if (this.canDropOnElement(item)) {
|
| - e.preventDefault();
|
| - e.dataTransfer.dropEffect = 'move';
|
| - }
|
| - },
|
| + // The position of the item is relative to #most-visited so we need to
|
| + // subtract that when calculating the allowed position.
|
| + x = Math.max(x, -rect.left);
|
| + x = Math.min(x, document.body.clientWidth - rect.left - item.offsetWidth -
|
| + 2);
|
| + // The shadow is 2px
|
| + y = Math.max(-rect.top, y);
|
| + y = Math.min(y, document.body.clientHeight - rect.top -
|
| + item.offsetHeight - 2);
|
|
|
| - handleDragLeave: function(e) {
|
| - var item = mostVisited.getItem(e.target);
|
| - if (item) {
|
| - e.preventDefault();
|
| - }
|
| + // Override right in case of RTL.
|
| + item.style.right = 'auto';
|
| + item.style.left = x + 'px';
|
| + item.style.top = y + 'px';
|
| + item.style.zIndex = 2;
|
| + },
|
|
|
| - this.currentOverItem = null;
|
| - },
|
| + // We listen to mousedown to get the relative position of the cursor for dnd.
|
| + handleMouseDown_: function(e) {
|
| + var item = getItem(e.target);
|
| + if (item) {
|
| + this.startX_ = item.offsetLeft;
|
| + this.startY_ = item.offsetTop;
|
| + this.startScreenX_ = e.screenX;
|
| + this.startScreenY_ = e.screenY;
|
|
|
| - handleDrop: function(e) {
|
| - var dropTarget = mostVisited.getItem(e.target);
|
| - if (this.canDropOnElement(dropTarget)) {
|
| - dropTarget.style.zIndex = 1;
|
| - mostVisited.swapPosition(this.dragItem, dropTarget);
|
| - // The timeout below is to allow WebKit to see that we turned off
|
| - // pointer-event before moving the thumbnails so that we can get out of
|
| - // hover mode.
|
| - window.setTimeout(function() {
|
| - mostVisited.invalidate();
|
| - mostVisited.layout();
|
| - }, 10);
|
| - e.preventDefault();
|
| - if (this.dragEndTimer) {
|
| - window.clearTimeout(this.dragEndTimer);
|
| - this.dragEndTimer = null;
|
| + // We don't want to focus the item on mousedown. However, to prevent
|
| + // focus one has to call preventDefault but this also prevents the drag
|
| + // and drop (sigh) so we only prevent it when the user is not doing a
|
| + // left mouse button drag.
|
| + if (e.button != 0) // LEFT
|
| + e.preventDefault();
|
| }
|
| - afterTransition(function() {
|
| - dropTarget.style.zIndex = '';
|
| - });
|
| - }
|
| - },
|
| + },
|
|
|
| - handleDragEnd: function(e) {
|
| - var dragItem = this.dragItem;
|
| - if (dragItem) {
|
| - dragItem.style.pointerEvents = '';
|
| - removeClass(dragItem, 'dragging');
|
| + canDropOnElement_: function(el) {
|
| + return this.dragItem_ && el &&
|
| + el.classList.contains('thumbnail-container') &&
|
| + !el.classList.contains('filler');
|
| + },
|
|
|
| - afterTransition(function() {
|
| - // Delay resetting zIndex to let the animation finish.
|
| - dragItem.style.zIndex = '';
|
| - // Same for overflow.
|
| - dragItem.parentNode.style.overflow = '';
|
| - });
|
|
|
| - mostVisited.invalidate();
|
| - mostVisited.layout();
|
| - this.dragItem = null;
|
| - }
|
| - },
|
| + /// data
|
|
|
| - handleDrag: function(e) {
|
| - // Moves the drag item making sure that it is not displayed outside the
|
| - // browser viewport.
|
| - var item = mostVisited.getItem(e.target);
|
| - var rect = document.querySelector('#most-visited').getBoundingClientRect();
|
| - item.style.pointerEvents = 'none';
|
| + data_: null,
|
| + get data() {
|
| + return this.data_;
|
| + },
|
| + set data(data) {
|
| + // We append the class name with the "filler" so that we can style fillers
|
| + // differently.
|
| + var maxItems = 8;
|
| + data.length = Math.min(maxItems, data.length);
|
| + var len = data.length;
|
| + for (var i = len; i < maxItems; i++) {
|
| + data[i] = {filler: true};
|
| + }
|
|
|
| - var x = this.startX + e.screenX - this.startScreenX;
|
| - var y = this.startY + e.screenY - this.startScreenY;
|
| + // On setting we need to update the items
|
| + this.data_ = data;
|
| + this.updateMostVisited_();
|
| + },
|
|
|
| - // The position of the item is relative to #most-visited so we need to
|
| - // subtract that when calculating the allowed position.
|
| - x = Math.max(x, -rect.left);
|
| - x = Math.min(x, document.body.clientWidth - rect.left - item.offsetWidth -
|
| - 2);
|
| - // The shadow is 2px
|
| - y = Math.max(-rect.top, y);
|
| - y = Math.min(y, document.body.clientHeight - rect.top - item.offsetHeight -
|
| - 2);
|
| + updateMostVisited_: function() {
|
|
|
| - // Override right in case of RTL.
|
| - item.style.right = 'auto';
|
| - item.style.left = x + 'px';
|
| - item.style.top = y + 'px';
|
| - item.style.zIndex = 2;
|
| - },
|
| + function getThumbnailClassName(item) {
|
| + return 'thumbnail-container' +
|
| + (item.pinned ? ' pinned' : '') +
|
| + (item.filler ? ' filler' : '');
|
| + }
|
|
|
| - // We listen to mousedown to get the relative position of the cursor for dnd.
|
| - handleMouseDown: function(e) {
|
| - var item = mostVisited.getItem(e.target);
|
| - if (item) {
|
| - this.startX = item.offsetLeft;
|
| - this.startY = item.offsetTop;
|
| - this.startScreenX = e.screenX;
|
| - this.startScreenY = e.screenY;
|
| + var data = this.data;
|
| + var children = this.element.children;
|
| + for (var i = 0; i < data.length; i++) {
|
| + var d = data[i];
|
| + var t = children[i];
|
|
|
| - // We don't want to focus the item on mousedown. However, to prevent focus
|
| - // one has to call preventDefault but this also prevents the drag and drop
|
| - // (sigh) so we only prevent it when the user is not doing a left mouse
|
| - // button drag.
|
| - if (e.button != 0) // LEFT
|
| - e.preventDefault();
|
| - }
|
| - },
|
| + // If we have a filler continue
|
| + var oldClassName = t.className;
|
| + var newClassName = getThumbnailClassName(d);
|
| + if (oldClassName != newClassName) {
|
| + t.className = newClassName;
|
| + }
|
|
|
| - canDropOnElement: function(el) {
|
| - return this.dragItem && el && hasClass(el, 'thumbnail-container') &&
|
| - !hasClass(el, 'filler');
|
| - },
|
| + // No need to continue if this is a filler.
|
| + if (newClassName == 'thumbnail-container filler') {
|
| + // Make sure the user cannot tab to the filler.
|
| + t.tabIndex = -1;
|
| + continue;
|
| + }
|
| + // Allow focus.
|
| + t.tabIndex = 1;
|
|
|
| - init: function() {
|
| - var el = $('most-visited');
|
| - el.addEventListener('dragstart', bind(this.handleDragStart, this));
|
| - el.addEventListener('dragenter', bind(this.handleDragEnter, this));
|
| - el.addEventListener('dragover', bind(this.handleDragOver, this));
|
| - el.addEventListener('dragleave', bind(this.handleDragLeave, this));
|
| - el.addEventListener('drop', bind(this.handleDrop, this));
|
| - el.addEventListener('dragend', bind(this.handleDragEnd, this));
|
| - el.addEventListener('drag', bind(this.handleDrag, this));
|
| - el.addEventListener('mousedown', bind(this.handleMouseDown, this));
|
| - }
|
| -};
|
| + t.href = d.url;
|
| + t.querySelector('.pin').title = localStrings.getString(d.pinned ?
|
| + 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
|
| + t.querySelector('.remove').title =
|
| + localStrings.getString('removethumbnailtooltip');
|
|
|
| -dnd.init();
|
| + // There was some concern that a malformed malicious URL could cause an
|
| + // XSS attack but setting style.backgroundImage = 'url(javascript:...)'
|
| + // does not execute the JavaScript in WebKit.
|
|
|
| + var thumbnailUrl = d.thumbnailUrl || 'chrome://thumb/' + d.url;
|
| + t.querySelector('.thumbnail-wrapper').style.backgroundImage =
|
| + url(thumbnailUrl);
|
| + var titleDiv = t.querySelector('.title > div');
|
| + titleDiv.xtitle = titleDiv.textContent = d.title;
|
| + var faviconUrl = d.faviconUrl || 'chrome://favicon/' + d.url;
|
| + titleDiv.style.backgroundImage = url(faviconUrl);
|
| + titleDiv.dir = d.direction;
|
| + }
|
| + },
|
| +
|
| + handleClick_: function(e) {
|
| + var target = e.target;
|
| + if (target.classList.contains('pin')) {
|
| + this.togglePinned_(getItem(target));
|
| + e.preventDefault();
|
| + } else if (target.classList.contains('remove')) {
|
| + this.blacklist(getItem(target));
|
| + e.preventDefault();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Allow blacklisting most visited site using the keyboard.
|
| + */
|
| + handleKeyDown_: function(e) {
|
| + if (!IS_MAC && e.keyCode == 46 || // Del
|
| + IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
|
| + this.blacklist(e.target);
|
| + }
|
| + }
|
| + };
|
| +
|
| + return MostVisited;
|
| +})();
|
|
|