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

Side by Side Diff: chrome/browser/resources/ntp/most_visited.js

Issue 1695022: NTP - Refactor the most visited code to uncouple it from the rest of the NTP.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/resources/ntp/most_visited.css ('k') | chrome/browser/resources/ntp/util.js » ('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) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 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 var mostVisitedData = []; 5 // Dependencies that we should remove/formalize:
6 var gotMostVisited = false; 6 // ../shared/js/class_list.js
7 7 // util.js
8 function mostVisitedPages(data, firstRun) { 8 //
9 logEvent('received most visited pages'); 9 // afterTransition
10 10 // chrome.send
11 // We append the class name with the "filler" so that we can style fillers 11 // hideNotification
12 // differently. 12 // isRtl
13 var maxItems = 8; 13 // localStrings
14 data.length = Math.min(maxItems, data.length); 14 // logEvent
15 var len = data.length; 15 // showNotification
16 for (var i = len; i < maxItems; i++) { 16
17 data[i] = {filler: true}; 17
18 var MostVisited = (function() {
19
20 function addPinnedUrl(item, index) {
21 chrome.send('addPinnedURL', [item.url, item.title, item.faviconUrl || '',
22 item.thumbnailUrl || '', String(index)]);
18 } 23 }
19 24
20 mostVisitedData = data; 25 function getItem(el) {
21 renderMostVisited(data); 26 return findAncestorByClass(el, 'thumbnail-container');
22
23 gotMostVisited = true;
24 onDataLoaded();
25
26 // Only show the first run notification if first run.
27 if (firstRun) {
28 showFirstRunNotification();
29 } 27 }
30 } 28
31 function getThumbnailClassName(data) { 29 function updatePinnedDom(el, pinned) {
32 return 'thumbnail-container' +
33 (data.pinned ? ' pinned' : '') +
34 (data.filler ? ' filler' : '');
35 }
36
37 function renderMostVisited(data) {
38 var parent = $('most-visited');
39 var children = parent.children;
40 for (var i = 0; i < data.length; i++) {
41 var d = data[i];
42 var t = children[i];
43
44 // If we have a filler continue
45 var oldClassName = t.className;
46 var newClassName = getThumbnailClassName(d);
47 if (oldClassName != newClassName) {
48 t.className = newClassName;
49 }
50
51 // No need to continue if this is a filler.
52 if (newClassName == 'thumbnail-container filler') {
53 // Make sure the user cannot tab to the filler.
54 t.tabIndex = -1;
55 continue;
56 }
57 // Allow focus.
58 t.tabIndex = 1;
59
60 t.href = d.url;
61 t.querySelector('.pin').title = localStrings.getString(d.pinned ?
62 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
63 t.querySelector('.remove').title =
64 localStrings.getString('removethumbnailtooltip');
65
66 // There was some concern that a malformed malicious URL could cause an XSS
67 // attack but setting style.backgroundImage = 'url(javascript:...)' does
68 // not execute the JavaScript in WebKit.
69
70 var thumbnailUrl = d.thumbnailUrl || 'chrome://thumb/' + d.url;
71 t.querySelector('.thumbnail-wrapper').style.backgroundImage =
72 url(thumbnailUrl);
73 var titleDiv = t.querySelector('.title > div');
74 titleDiv.xtitle = titleDiv.textContent = d.title;
75 var faviconUrl = d.faviconUrl || 'chrome://favicon/' + d.url;
76 titleDiv.style.backgroundImage = url(faviconUrl);
77 titleDiv.dir = d.direction;
78 }
79 }
80
81 var mostVisited = {
82 addPinnedUrl_: function(data, index) {
83 chrome.send('addPinnedURL', [data.url, data.title, data.faviconUrl || '',
84 data.thumbnailUrl || '', String(index)]);
85 },
86 getItem: function(el) {
87 return findAncestorByClass(el, 'thumbnail-container');
88 },
89
90 getHref: function(el) {
91 return el.href;
92 },
93
94 togglePinned: function(el) {
95 var index = this.getThumbnailIndex(el);
96 var data = mostVisitedData[index];
97 data.pinned = !data.pinned;
98 if (data.pinned) {
99 this.addPinnedUrl_(data, index);
100 } else {
101 chrome.send('removePinnedURL', [data.url]);
102 }
103 this.updatePinnedDom_(el, data.pinned);
104 },
105
106 updatePinnedDom_: function(el, pinned) {
107 el.querySelector('.pin').title = localStrings.getString(pinned ? 30 el.querySelector('.pin').title = localStrings.getString(pinned ?
108 'unpinthumbnailtooltip' : 'pinthumbnailtooltip'); 31 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
109 if (pinned) { 32 if (pinned) {
110 addClass(el, 'pinned'); 33 el.classList.add('pinned');
111 } else { 34 } else {
112 removeClass(el, 'pinned'); 35 el.classList.remove('pinned');
113 } 36 }
114 }, 37 }
115 38
116 getThumbnailIndex: function(el) { 39 function getThumbnailIndex(el) {
117 var nodes = el.parentNode.querySelectorAll('.thumbnail-container'); 40 var nodes = el.parentNode.querySelectorAll('.thumbnail-container');
118 return Array.prototype.indexOf.call(nodes, el); 41 return Array.prototype.indexOf.call(nodes, el);
119 }, 42 }
120 43
121 swapPosition: function(source, destination) { 44 function MostVisited(el, useSmallGrid, visible) {
122 var nodes = source.parentNode.querySelectorAll('.thumbnail-container'); 45 this.element = el;
123 var sourceIndex = this.getThumbnailIndex(source); 46 this.useSmallGrid_ = useSmallGrid;
124 var destinationIndex = this.getThumbnailIndex(destination); 47 this.visible_ = visible;
125 swapDomNodes(source, destination); 48
126 49 this.createThumbnails_();
127 var sourceData = mostVisitedData[sourceIndex]; 50 this.applyMostVisitedRects_();
128 this.addPinnedUrl_(sourceData, destinationIndex); 51
129 sourceData.pinned = true; 52 el.addEventListener('click', bind(this.handleClick_, this));
130 this.updatePinnedDom_(source, true); 53 el.addEventListener('keydown', bind(this.handleKeyDown_, this));
131 54
132 var destinationData = mostVisitedData[destinationIndex]; 55 document.addEventListener('DOMContentLoaded',
133 // Only update the destination if it was pinned before. 56 bind(this.ensureSmallGridCorrect, this));
134 if (destinationData.pinned) { 57
135 this.addPinnedUrl_(destinationData, sourceIndex); 58 // DND
59 el.addEventListener('dragstart', bind(this.handleDragStart_, this));
60 el.addEventListener('dragenter', bind(this.handleDragEnter_, this));
61 el.addEventListener('dragover', bind(this.handleDragOver_, this));
62 el.addEventListener('dragleave', bind(this.handleDragLeave_, this));
63 el.addEventListener('drop', bind(this.handleDrop_, this));
64 el.addEventListener('dragend', bind(this.handleDragEnd_, this));
65 el.addEventListener('drag', bind(this.handleDrag_, this));
66 el.addEventListener('mousedown', bind(this.handleMouseDown_, this));
67 }
68
69 MostVisited.prototype = {
70 togglePinned_: function(el) {
71 var index = getThumbnailIndex(el);
72 var item = this.data[index];
73 item.pinned = !item.pinned;
74 if (item.pinned) {
75 addPinnedUrl(item, index);
76 } else {
77 chrome.send('removePinnedURL', [item.url]);
78 }
79 updatePinnedDom(el, item.pinned);
80 },
81
82 swapPosition_: function(source, destination) {
83 var nodes = source.parentNode.querySelectorAll('.thumbnail-container');
84 var sourceIndex = getThumbnailIndex(source);
85 var destinationIndex = getThumbnailIndex(destination);
86 swapDomNodes(source, destination);
87
88 var sourceData = this.data[sourceIndex];
89 addPinnedUrl(sourceData, destinationIndex);
90 sourceData.pinned = true;
91 updatePinnedDom(source, true);
92
93 var destinationData = this.data[destinationIndex];
94 // Only update the destination if it was pinned before.
95 if (destinationData.pinned) {
96 addPinnedUrl(destinationData, sourceIndex);
97 }
98 this.data[destinationIndex] = sourceData;
99 this.data[sourceIndex] = destinationData;
100 },
101
102 blacklist: function(el) {
103 var self = this;
104 var url = el.href;
105 chrome.send('blacklistURLFromMostVisited', [url]);
106
107 el.classList.add('hide');
108
109 // Find the old item.
110 var oldUrls = {};
111 var oldIndex = -1;
112 var oldItem;
113 var data = this.data;
114 for (var i = 0; i < data.length; i++) {
115 if (data[i].url == url) {
116 oldItem = data[i];
117 oldIndex = i;
118 }
119 oldUrls[data[i].url] = true;
120 }
121
122 // Send 'getMostVisitedPages' with a callback since we want to find the
123 // new page and add that in the place of the removed page.
124 chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
125 // Find new item.
126 var newItem;
127 for (var i = 0; i < data.length; i++) {
128 if (!(data[i].url in oldUrls)) {
129 newItem = data[i];
130 break;
131 }
132 }
133
134 if (!newItem) {
135 // If no other page is available to replace the blacklisted item,
136 // we need to reorder items s.t. all filler items are in the rightmost
137 // indices.
138 self.data = data;
139
140 // Replace old item with new item in the most visited data array.
141 } else if (oldIndex != -1) {
142 var oldData = self.data.concat();
143 oldData.splice(oldIndex, 1, newItem);
144 self.data = oldData;
145 el.classList.add('fade-in');
146 }
147
148 // We wrap the title in a <span class=blacklisted-title>. We pass an
149 // empty string to the notifier function and use DOM to insert the real
150 // string.
151 var actionText = localStrings.getString('undothumbnailremove');
152
153 // Show notification and add undo callback function.
154 var wasPinned = oldItem.pinned;
155 showNotification('', actionText, function() {
156 self.removeFromBlackList(url);
157 if (wasPinned) {
158 addPinnedUrl(oldItem, oldIndex);
159 }
160 chrome.send('getMostVisited');
161 });
162
163 // Now change the DOM.
164 var removeText = localStrings.getString('thumbnailremovednotification');
165 var notifySpan = document.querySelector('#notification > span');
166 notifySpan.textContent = removeText;
167
168 // Focus the undo link.
169 var undoLink = document.querySelector(
170 '#notification > .link > [tabindex]');
171 undoLink.focus();
172 });
173 },
174
175 removeFromBlackList: function(url) {
176 chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
177 },
178
179 clearAllBlacklisted: function() {
180 chrome.send('clearMostVisitedURLsBlacklist', []);
181 hideNotification();
182 },
183
184 dirty_: false,
185 invalidate_: function() {
186 this.dirty_ = true;
187 },
188
189 visible_: true,
190 get visible() {
191 return this.visible_;
192 },
193 set visible(visible) {
194 if (this.visible_ != visible) {
195 this.visible_ = visible;
196 this.invalidate_();
197 }
198 },
199
200 useSmallGrid_: false,
201 get useSmallGrid() {
202 return this.useSmallGrid_;
203 },
204 set useSmallGrid(b) {
205 if (this.useSmallGrid_ != b) {
206 this.useSmallGrid_ = b;
207 this.invalidate_();
208 }
209 },
210
211 layout: function() {
212 if (!this.dirty_)
213 return;
214 var d0 = Date.now();
215 this.applyMostVisitedRects_();
216 this.dirty_ = false;
217 logEvent('mostVisited.layout: ' + (Date.now() - d0));
218 },
219
220 createThumbnails_: function() {
221 var singleHtml =
222 '<a class="thumbnail-container filler" tabindex="1">' +
223 '<div class="edit-mode-border">' +
224 '<div class="edit-bar">' +
225 '<div class="pin"></div>' +
226 '<div class="spacer"></div>' +
227 '<div class="remove"></div>' +
228 '</div>' +
229 '<span class="thumbnail-wrapper">' +
230 '<span class="thumbnail"></span>' +
231 '</span>' +
232 '</div>' +
233 '<div class="title">' +
234 '<div></div>' +
235 '</div>' +
236 '</a>';
237 this.element.innerHTML = Array(8 + 1).join(singleHtml);
238 var children = this.element.children;
239 for (var i = 0; i < 8; i++) {
240 children[i].id = 't' + i;
241 }
242 },
243
244 getMostVisitedLayoutRects_: function() {
245 var small = this.useSmallGrid;
246
247 var cols = 4;
248 var rows = 2;
249 var marginWidth = 10;
250 var marginHeight = 7;
251 var borderWidth = 4;
252 var thumbWidth = small ? 150 : 207;
253 var thumbHeight = small ? 93 : 129;
254 var w = thumbWidth + 2 * borderWidth + 2 * marginWidth;
255 var h = thumbHeight + 40 + 2 * marginHeight;
256 var sumWidth = cols * w - 2 * marginWidth;
257
258 var rtl = isRtl();
259 var rects = [];
260
261 if (this.visible) {
262 for (var i = 0; i < rows * cols; i++) {
263 var row = Math.floor(i / cols);
264 var col = i % cols;
265 var left = rtl ? sumWidth - col * w - thumbWidth - 2 * borderWidth :
266 col * w;
267
268 var top = row * h;
269
270 rects[i] = {left: left, top: top};
271 }
272 }
273 return rects;
274 },
275
276 applyMostVisitedRects_: function() {
277 if (this.visible) {
278 var rects = this.getMostVisitedLayoutRects_();
279 var children = this.element.children;
280 for (var i = 0; i < 8; i++) {
281 var t = children[i];
282 t.style.left = rects[i].left + 'px';
283 t.style.top = rects[i].top + 'px';
284 t.style.right = '';
285 var innerStyle = t.firstElementChild.style;
286 innerStyle.left = innerStyle.top = '';
287 }
288 }
289 },
290
291 // Work around for http://crbug.com/25329
292 ensureSmallGridCorrect: function(expected) {
293 if (expected != this.useSmallGrid)
294 this.applyMostVisitedRects_();
295 },
296
297 getRectByIndex_: function(index) {
298 return this.getMostVisitedLayoutRects_()[index];
299 },
300
301 // DND
302
303 currentOverItem_: null,
304 get currentOverItem() {
305 return this.currentOverItem_;
306 },
307 set currentOverItem(item) {
308 var style;
309 if (item != this.currentOverItem_) {
310 if (this.currentOverItem_) {
311 style = this.currentOverItem_.firstElementChild.style;
312 style.left = style.top = '';
313 }
314 this.currentOverItem_ = item;
315
316 if (item) {
317 // Make the drag over item move 15px towards the source. The movement
318 // is done by only moving the edit-mode-border (as in the mocks) and
319 // it is done with relative positioning so that the movement does not
320 // change the drop target.
321 var dragIndex = getThumbnailIndex(this.dragItem_);
322 var overIndex = getThumbnailIndex(item);
323 if (dragIndex == -1 || overIndex == -1) {
324 return;
325 }
326
327 var dragRect = this.getRectByIndex_(dragIndex);
328 var overRect = this.getRectByIndex_(overIndex);
329
330 var x = dragRect.left - overRect.left;
331 var y = dragRect.top - overRect.top;
332 var z = Math.sqrt(x * x + y * y);
333 var z2 = 15;
334 var x2 = x * z2 / z;
335 var y2 = y * z2 / z;
336
337 style = this.currentOverItem_.firstElementChild.style;
338 style.left = x2 + 'px';
339 style.top = y2 + 'px';
340 }
341 }
342 },
343 dragItem_: null,
344 startX_: 0,
345 startY_: 0,
346 startScreenX_: 0,
347 startScreenY_: 0,
348 dragEndTimer_: null,
349
350 isDragging: function() {
351 return !!this.dragItem_;
352 },
353
354 handleDragStart_: function(e) {
355 var thumbnail = getItem(e.target);
356 if (thumbnail) {
357 // Don't set data since HTML5 does not allow setting the name for
358 // url-list. Instead, we just rely on the dragging of link behavior.
359 this.dragItem_ = thumbnail;
360 this.dragItem_.classList.add('dragging');
361 this.dragItem_.style.zIndex = 2;
362 e.dataTransfer.effectAllowed = 'copyLinkMove';
363 }
364 },
365
366 handleDragEnter_: function(e) {
367 if (this.canDropOnElement_(this.currentOverItem)) {
368 e.preventDefault();
369 }
370 },
371
372 handleDragOver_: function(e) {
373 var item = getItem(e.target);
374 this.currentOverItem = item;
375 if (this.canDropOnElement_(item)) {
376 e.preventDefault();
377 e.dataTransfer.dropEffect = 'move';
378 }
379 },
380
381 handleDragLeave_: function(e) {
382 var item = getItem(e.target);
383 if (item) {
384 e.preventDefault();
385 }
386
387 this.currentOverItem = null;
388 },
389
390 handleDrop_: function(e) {
391 var dropTarget = getItem(e.target);
392 if (this.canDropOnElement_(dropTarget)) {
393 dropTarget.style.zIndex = 1;
394 this.swapPosition_(this.dragItem_, dropTarget);
395 // The timeout below is to allow WebKit to see that we turned off
396 // pointer-event before moving the thumbnails so that we can get out of
397 // hover mode.
398 window.setTimeout(bind(function() {
399 this.invalidate_();
400 this.layout();
401 }, this), 10);
402 e.preventDefault();
403 if (this.dragEndTimer_) {
404 window.clearTimeout(this.dragEndTimer_);
405 this.dragEndTimer_ = null;
406 }
407 afterTransition(function() {
408 dropTarget.style.zIndex = '';
409 });
410 }
411 },
412
413 handleDragEnd_: function(e) {
414 var dragItem = this.dragItem_;
415 if (dragItem) {
416 dragItem.style.pointerEvents = '';
417 dragItem.classList.remove('dragging');
418
419 afterTransition(function() {
420 // Delay resetting zIndex to let the animation finish.
421 dragItem.style.zIndex = '';
422 // Same for overflow.
423 dragItem.parentNode.style.overflow = '';
424 });
425
426 this.invalidate_();
427 this.layout();
428 this.dragItem_ = null;
429 }
430 },
431
432 handleDrag_: function(e) {
433 // Moves the drag item making sure that it is not displayed outside the
434 // browser viewport.
435 var item = getItem(e.target);
436 var rect = this.element.getBoundingClientRect();
437 item.style.pointerEvents = 'none';
438
439 var x = this.startX_ + e.screenX - this.startScreenX_;
440 var y = this.startY_ + e.screenY - this.startScreenY_;
441
442 // The position of the item is relative to #most-visited so we need to
443 // subtract that when calculating the allowed position.
444 x = Math.max(x, -rect.left);
445 x = Math.min(x, document.body.clientWidth - rect.left - item.offsetWidth -
446 2);
447 // The shadow is 2px
448 y = Math.max(-rect.top, y);
449 y = Math.min(y, document.body.clientHeight - rect.top -
450 item.offsetHeight - 2);
451
452 // Override right in case of RTL.
453 item.style.right = 'auto';
454 item.style.left = x + 'px';
455 item.style.top = y + 'px';
456 item.style.zIndex = 2;
457 },
458
459 // We listen to mousedown to get the relative position of the cursor for dnd .
460 handleMouseDown_: function(e) {
461 var item = getItem(e.target);
462 if (item) {
463 this.startX_ = item.offsetLeft;
464 this.startY_ = item.offsetTop;
465 this.startScreenX_ = e.screenX;
466 this.startScreenY_ = e.screenY;
467
468 // We don't want to focus the item on mousedown. However, to prevent
469 // focus one has to call preventDefault but this also prevents the drag
470 // and drop (sigh) so we only prevent it when the user is not doing a
471 // left mouse button drag.
472 if (e.button != 0) // LEFT
473 e.preventDefault();
474 }
475 },
476
477 canDropOnElement_: function(el) {
478 return this.dragItem_ && el &&
479 el.classList.contains('thumbnail-container') &&
480 !el.classList.contains('filler');
481 },
482
483
484 /// data
485
486 data_: null,
487 get data() {
488 return this.data_;
489 },
490 set data(data) {
491 // We append the class name with the "filler" so that we can style fillers
492 // differently.
493 var maxItems = 8;
494 data.length = Math.min(maxItems, data.length);
495 var len = data.length;
496 for (var i = len; i < maxItems; i++) {
497 data[i] = {filler: true};
498 }
499
500 // On setting we need to update the items
501 this.data_ = data;
502 this.updateMostVisited_();
503 },
504
505 updateMostVisited_: function() {
506
507 function getThumbnailClassName(item) {
508 return 'thumbnail-container' +
509 (item.pinned ? ' pinned' : '') +
510 (item.filler ? ' filler' : '');
511 }
512
513 var data = this.data;
514 var children = this.element.children;
515 for (var i = 0; i < data.length; i++) {
516 var d = data[i];
517 var t = children[i];
518
519 // If we have a filler continue
520 var oldClassName = t.className;
521 var newClassName = getThumbnailClassName(d);
522 if (oldClassName != newClassName) {
523 t.className = newClassName;
524 }
525
526 // No need to continue if this is a filler.
527 if (newClassName == 'thumbnail-container filler') {
528 // Make sure the user cannot tab to the filler.
529 t.tabIndex = -1;
530 continue;
531 }
532 // Allow focus.
533 t.tabIndex = 1;
534
535 t.href = d.url;
536 t.querySelector('.pin').title = localStrings.getString(d.pinned ?
537 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
538 t.querySelector('.remove').title =
539 localStrings.getString('removethumbnailtooltip');
540
541 // There was some concern that a malformed malicious URL could cause an
542 // XSS attack but setting style.backgroundImage = 'url(javascript:...)'
543 // does not execute the JavaScript in WebKit.
544
545 var thumbnailUrl = d.thumbnailUrl || 'chrome://thumb/' + d.url;
546 t.querySelector('.thumbnail-wrapper').style.backgroundImage =
547 url(thumbnailUrl);
548 var titleDiv = t.querySelector('.title > div');
549 titleDiv.xtitle = titleDiv.textContent = d.title;
550 var faviconUrl = d.faviconUrl || 'chrome://favicon/' + d.url;
551 titleDiv.style.backgroundImage = url(faviconUrl);
552 titleDiv.dir = d.direction;
553 }
554 },
555
556 handleClick_: function(e) {
557 var target = e.target;
558 if (target.classList.contains('pin')) {
559 this.togglePinned_(getItem(target));
560 e.preventDefault();
561 } else if (target.classList.contains('remove')) {
562 this.blacklist(getItem(target));
563 e.preventDefault();
564 }
565 },
566
567 /**
568 * Allow blacklisting most visited site using the keyboard.
569 */
570 handleKeyDown_: function(e) {
571 if (!IS_MAC && e.keyCode == 46 || // Del
572 IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
573 this.blacklist(e.target);
574 }
136 } 575 }
137 mostVisitedData[destinationIndex] = sourceData; 576 };
138 mostVisitedData[sourceIndex] = destinationData; 577
139 }, 578 return MostVisited;
140 579 })();
141 blacklist: function(el) {
142 var self = this;
143 var url = this.getHref(el);
144 chrome.send('blacklistURLFromMostVisited', [url]);
145
146 addClass(el, 'hide');
147
148 // Find the old item.
149 var oldUrls = {};
150 var oldIndex = -1;
151 var oldItem;
152 for (var i = 0; i < mostVisitedData.length; i++) {
153 if (mostVisitedData[i].url == url) {
154 oldItem = mostVisitedData[i];
155 oldIndex = i;
156 }
157 oldUrls[mostVisitedData[i].url] = true;
158 }
159
160 // Send 'getMostVisitedPages' with a callback since we want to find the new
161 // page and add that in the place of the removed page.
162 chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
163 // Find new item.
164 var newItem;
165 for (var i = 0; i < data.length; i++) {
166 if (!(data[i].url in oldUrls)) {
167 newItem = data[i];
168 break;
169 }
170 }
171
172 if (!newItem) {
173 // If no other page is available to replace the blacklisted item,
174 // we need to reorder items s.t. all filler items are in the rightmost
175 // indices.
176 mostVisitedPages(data);
177
178 // Replace old item with new item in the mostVisitedData array.
179 } else if (oldIndex != -1) {
180 mostVisitedData.splice(oldIndex, 1, newItem);
181 mostVisitedPages(mostVisitedData);
182 addClass(el, 'fade-in');
183 }
184
185 // We wrap the title in a <span class=blacklisted-title>. We pass an empty
186 // string to the notifier function and use DOM to insert the real string.
187 var actionText = localStrings.getString('undothumbnailremove');
188
189 // Show notification and add undo callback function.
190 var wasPinned = oldItem.pinned;
191 showNotification('', actionText, function() {
192 self.removeFromBlackList(url);
193 if (wasPinned) {
194 self.addPinnedUrl_(oldItem, oldIndex);
195 }
196 chrome.send('getMostVisited');
197 });
198
199 // Now change the DOM.
200 var removeText = localStrings.getString('thumbnailremovednotification');
201 var notifySpan = document.querySelector('#notification > span');
202 notifySpan.textContent = removeText;
203
204 // Focus the undo link.
205 var undoLink = document.querySelector(
206 '#notification > .link > [tabindex]');
207 undoLink.focus();
208 });
209 },
210
211 removeFromBlackList: function(url) {
212 chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
213 },
214
215 clearAllBlacklisted: function() {
216 chrome.send('clearMostVisitedURLsBlacklist', []);
217 hideNotification();
218 },
219
220 updateDisplayMode: function() {
221 if (!this.dirty_) {
222 return;
223 }
224 updateSimpleSection('most-visited-section', Section.THUMB);
225 },
226
227 dirty_: false,
228
229 invalidate: function() {
230 this.dirty_ = true;
231 },
232
233 layout: function() {
234 if (!this.dirty_) {
235 return;
236 }
237 var d0 = Date.now();
238
239 var mostVisitedElement = $('most-visited');
240 var thumbnails = mostVisitedElement.children;
241 var hidden = !(shownSections & Section.THUMB);
242
243
244 // We set overflow to hidden so that the most visited element does not
245 // "leak" when we hide and show it.
246 if (hidden) {
247 mostVisitedElement.style.overflow = 'hidden';
248 }
249
250 applyMostVisitedRects();
251
252 // Only set overflow to visible if the element is shown.
253 if (!hidden) {
254 afterTransition(function() {
255 mostVisitedElement.style.overflow = '';
256 });
257 }
258
259 this.dirty_ = false;
260
261 logEvent('mostVisited.layout: ' + (Date.now() - d0));
262 },
263
264 getRectByIndex: function(index) {
265 return getMostVisitedLayoutRects()[index];
266 }
267 };
268
269 $('most-visited').addEventListener('click', function(e) {
270 var target = e.target;
271 if (hasClass(target, 'pin')) {
272 mostVisited.togglePinned(mostVisited.getItem(target));
273 e.preventDefault();
274 } else if (hasClass(target, 'remove')) {
275 mostVisited.blacklist(mostVisited.getItem(target));
276 e.preventDefault();
277 }
278 });
279
280 // Allow blacklisting most visited site using the keyboard.
281 $('most-visited').addEventListener('keydown', function(e) {
282 if (!IS_MAC && e.keyCode == 46 || // Del
283 IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
284 mostVisited.blacklist(e.target);
285 }
286 });
287
288 window.addEventListener('load', onDataLoaded);
289
290 window.addEventListener('resize', handleWindowResize);
291
292 // Work around for http://crbug.com/25329
293 function ensureSmallGridCorrect() {
294 if (wasSmallGrid != useSmallGrid()) {
295 applyMostVisitedRects();
296 }
297 }
298 document.addEventListener('DOMContentLoaded', ensureSmallGridCorrect);
299
300 // DnD
301
302 var dnd = {
303 currentOverItem_: null,
304 get currentOverItem() {
305 return this.currentOverItem_;
306 },
307 set currentOverItem(item) {
308 var style;
309 if (item != this.currentOverItem_) {
310 if (this.currentOverItem_) {
311 style = this.currentOverItem_.firstElementChild.style;
312 style.left = style.top = '';
313 }
314 this.currentOverItem_ = item;
315
316 if (item) {
317 // Make the drag over item move 15px towards the source. The movement is
318 // done by only moving the edit-mode-border (as in the mocks) and it is
319 // done with relative positioning so that the movement does not change
320 // the drop target.
321 var dragIndex = mostVisited.getThumbnailIndex(this.dragItem);
322 var overIndex = mostVisited.getThumbnailIndex(item);
323 if (dragIndex == -1 || overIndex == -1) {
324 return;
325 }
326
327 var dragRect = mostVisited.getRectByIndex(dragIndex);
328 var overRect = mostVisited.getRectByIndex(overIndex);
329
330 var x = dragRect.left - overRect.left;
331 var y = dragRect.top - overRect.top;
332 var z = Math.sqrt(x * x + y * y);
333 var z2 = 15;
334 var x2 = x * z2 / z;
335 var y2 = y * z2 / z;
336
337 style = this.currentOverItem_.firstElementChild.style;
338 style.left = x2 + 'px';
339 style.top = y2 + 'px';
340 }
341 }
342 },
343 dragItem: null,
344 startX: 0,
345 startY: 0,
346 startScreenX: 0,
347 startScreenY: 0,
348 dragEndTimer: null,
349
350 handleDragStart: function(e) {
351 var thumbnail = mostVisited.getItem(e.target);
352 if (thumbnail) {
353 // Don't set data since HTML5 does not allow setting the name for
354 // url-list. Instead, we just rely on the dragging of link behavior.
355 this.dragItem = thumbnail;
356 addClass(this.dragItem, 'dragging');
357 this.dragItem.style.zIndex = 2;
358 e.dataTransfer.effectAllowed = 'copyLinkMove';
359 }
360 },
361
362 handleDragEnter: function(e) {
363 if (this.canDropOnElement(this.currentOverItem)) {
364 e.preventDefault();
365 }
366 },
367
368 handleDragOver: function(e) {
369 var item = mostVisited.getItem(e.target);
370 this.currentOverItem = item;
371 if (this.canDropOnElement(item)) {
372 e.preventDefault();
373 e.dataTransfer.dropEffect = 'move';
374 }
375 },
376
377 handleDragLeave: function(e) {
378 var item = mostVisited.getItem(e.target);
379 if (item) {
380 e.preventDefault();
381 }
382
383 this.currentOverItem = null;
384 },
385
386 handleDrop: function(e) {
387 var dropTarget = mostVisited.getItem(e.target);
388 if (this.canDropOnElement(dropTarget)) {
389 dropTarget.style.zIndex = 1;
390 mostVisited.swapPosition(this.dragItem, dropTarget);
391 // The timeout below is to allow WebKit to see that we turned off
392 // pointer-event before moving the thumbnails so that we can get out of
393 // hover mode.
394 window.setTimeout(function() {
395 mostVisited.invalidate();
396 mostVisited.layout();
397 }, 10);
398 e.preventDefault();
399 if (this.dragEndTimer) {
400 window.clearTimeout(this.dragEndTimer);
401 this.dragEndTimer = null;
402 }
403 afterTransition(function() {
404 dropTarget.style.zIndex = '';
405 });
406 }
407 },
408
409 handleDragEnd: function(e) {
410 var dragItem = this.dragItem;
411 if (dragItem) {
412 dragItem.style.pointerEvents = '';
413 removeClass(dragItem, 'dragging');
414
415 afterTransition(function() {
416 // Delay resetting zIndex to let the animation finish.
417 dragItem.style.zIndex = '';
418 // Same for overflow.
419 dragItem.parentNode.style.overflow = '';
420 });
421
422 mostVisited.invalidate();
423 mostVisited.layout();
424 this.dragItem = null;
425 }
426 },
427
428 handleDrag: function(e) {
429 // Moves the drag item making sure that it is not displayed outside the
430 // browser viewport.
431 var item = mostVisited.getItem(e.target);
432 var rect = document.querySelector('#most-visited').getBoundingClientRect();
433 item.style.pointerEvents = 'none';
434
435 var x = this.startX + e.screenX - this.startScreenX;
436 var y = this.startY + e.screenY - this.startScreenY;
437
438 // The position of the item is relative to #most-visited so we need to
439 // subtract that when calculating the allowed position.
440 x = Math.max(x, -rect.left);
441 x = Math.min(x, document.body.clientWidth - rect.left - item.offsetWidth -
442 2);
443 // The shadow is 2px
444 y = Math.max(-rect.top, y);
445 y = Math.min(y, document.body.clientHeight - rect.top - item.offsetHeight -
446 2);
447
448 // Override right in case of RTL.
449 item.style.right = 'auto';
450 item.style.left = x + 'px';
451 item.style.top = y + 'px';
452 item.style.zIndex = 2;
453 },
454
455 // We listen to mousedown to get the relative position of the cursor for dnd.
456 handleMouseDown: function(e) {
457 var item = mostVisited.getItem(e.target);
458 if (item) {
459 this.startX = item.offsetLeft;
460 this.startY = item.offsetTop;
461 this.startScreenX = e.screenX;
462 this.startScreenY = e.screenY;
463
464 // We don't want to focus the item on mousedown. However, to prevent focus
465 // one has to call preventDefault but this also prevents the drag and drop
466 // (sigh) so we only prevent it when the user is not doing a left mouse
467 // button drag.
468 if (e.button != 0) // LEFT
469 e.preventDefault();
470 }
471 },
472
473 canDropOnElement: function(el) {
474 return this.dragItem && el && hasClass(el, 'thumbnail-container') &&
475 !hasClass(el, 'filler');
476 },
477
478 init: function() {
479 var el = $('most-visited');
480 el.addEventListener('dragstart', bind(this.handleDragStart, this));
481 el.addEventListener('dragenter', bind(this.handleDragEnter, this));
482 el.addEventListener('dragover', bind(this.handleDragOver, this));
483 el.addEventListener('dragleave', bind(this.handleDragLeave, this));
484 el.addEventListener('drop', bind(this.handleDrop, this));
485 el.addEventListener('dragend', bind(this.handleDragEnd, this));
486 el.addEventListener('drag', bind(this.handleDrag, this));
487 el.addEventListener('mousedown', bind(this.handleMouseDown, this));
488 }
489 };
490
491 dnd.init();
492
OLDNEW
« no previous file with comments | « chrome/browser/resources/ntp/most_visited.css ('k') | chrome/browser/resources/ntp/util.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698