Index: chrome/browser/resources/enhanced_bookmark_manager/js/bmm/bookmark_list.js |
diff --git a/chrome/browser/resources/enhanced_bookmark_manager/js/bmm/bookmark_list.js b/chrome/browser/resources/enhanced_bookmark_manager/js/bmm/bookmark_list.js |
deleted file mode 100644 |
index 9d491c69ff5a947e45d8fc60ccdd017ceaaea1c2..0000000000000000000000000000000000000000 |
--- a/chrome/browser/resources/enhanced_bookmark_manager/js/bmm/bookmark_list.js |
+++ /dev/null |
@@ -1,566 +0,0 @@ |
-// Copyright 2013 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-// TODO(arv): Now that this is driven by a data model, implement a data model |
-// that handles the loading and the events from the bookmark backend. |
- |
-cr.define('bmm', function() { |
- var List = cr.ui.List; |
- var ListItem = cr.ui.ListItem; |
- var ArrayDataModel = cr.ui.ArrayDataModel; |
- var ContextMenuButton = cr.ui.ContextMenuButton; |
- |
- var list; |
- |
- /** |
- * Basic array data model for use with bookmarks. |
- * @param {!Array.<!BookmarkTreeNode>} items The bookmark items. |
- * @constructor |
- * @extends {ArrayDataModel} |
- */ |
- function BookmarksArrayDataModel(items) { |
- ArrayDataModel.call(this, items); |
- } |
- |
- BookmarksArrayDataModel.prototype = { |
- __proto__: ArrayDataModel.prototype, |
- |
- /** |
- * Finds the index of the bookmark with the given ID. |
- * @param {string} id The ID of the bookmark node to find. |
- * @return {number} The index of the found node or -1 if not found. |
- */ |
- findIndexById: function(id) { |
- for (var i = 0; i < this.length; i++) { |
- if (this.item(i).id == id) |
- return i; |
- } |
- return -1; |
- } |
- }; |
- |
- /** |
- * Removes all children and appends a new child. |
- * @param {!Node} parent The node to remove all children from. |
- * @param {!Node} newChild The new child to append. |
- */ |
- function replaceAllChildren(parent, newChild) { |
- var n; |
- while ((n = parent.lastChild)) { |
- parent.removeChild(n); |
- } |
- parent.appendChild(newChild); |
- } |
- |
- /** |
- * Creates a new bookmark list. |
- * @param {Object=} opt_propertyBag Optional properties. |
- * @constructor |
- * @extends {HTMLButtonElement} |
- */ |
- var BookmarkList = cr.ui.define('list'); |
- |
- BookmarkList.prototype = { |
- __proto__: List.prototype, |
- |
- /** @override */ |
- decorate: function() { |
- List.prototype.decorate.call(this); |
- this.addEventListener('mousedown', this.handleMouseDown_); |
- |
- // HACK(arv): http://crbug.com/40902 |
- window.addEventListener('resize', this.redraw.bind(this)); |
- |
- // We could add the ContextMenuButton in the BookmarkListItem but it slows |
- // down redraws a lot so we do this on mouseovers instead. |
- this.addEventListener('mouseover', this.handleMouseOver_.bind(this)); |
- |
- bmm.list = this; |
- }, |
- |
- createItem: function(bookmarkNode) { |
- return new BookmarkListItem(bookmarkNode); |
- }, |
- |
- parentId_: '', |
- |
- /** |
- * Reloads the list from the bookmarks backend. |
- */ |
- reload: function() { |
- var parentId = this.parentId; |
- |
- var callback = this.handleBookmarkCallback_.bind(this); |
- this.loading_ = true; |
- |
- if (!parentId) |
- callback([]); |
- else if (/^q=/.test(parentId)) |
- chrome.bookmarks.search(parentId.slice(2), callback); |
- else |
- chrome.bookmarks.getChildren(parentId, callback); |
- }, |
- |
- /** |
- * Callback function for loading items. |
- * @param {Array.<!BookmarkTreeNode>} items The loaded items. |
- * @private |
- */ |
- handleBookmarkCallback_: function(items) { |
- if (!items) { |
- // Failed to load bookmarks. Most likely due to the bookmark being |
- // removed. |
- cr.dispatchSimpleEvent(this, 'invalidId'); |
- this.loading_ = false; |
- return; |
- } |
- |
- this.dataModel = new BookmarksArrayDataModel(items); |
- |
- this.loading_ = false; |
- this.fixWidth_(); |
- cr.dispatchSimpleEvent(this, 'load'); |
- }, |
- |
- /** |
- * The bookmark node that the list is currently displaying. If we are |
- * currently displaying search this returns null. |
- * @type {BookmarkTreeNode} |
- */ |
- get bookmarkNode() { |
- if (this.isSearch()) |
- return null; |
- var treeItem = bmm.treeLookup[this.parentId]; |
- return treeItem && treeItem.bookmarkNode; |
- }, |
- |
- /** |
- * @return {boolean} Whether we are currently showing search results. |
- */ |
- isSearch: function() { |
- return this.parentId_[0] == 'q'; |
- }, |
- |
- /** |
- * Handles mouseover on the list so that we can add the context menu button |
- * lazily. |
- * @private |
- * @param {!Event} e The mouseover event object. |
- */ |
- handleMouseOver_: function(e) { |
- var el = e.target; |
- while (el && el.parentNode != this) { |
- el = el.parentNode; |
- } |
- |
- if (el && el.parentNode == this && |
- !el.editing && |
- !(el.lastChild instanceof ContextMenuButton)) { |
- el.appendChild(new ContextMenuButton); |
- } |
- }, |
- |
- /** |
- * Dispatches an urlClicked event which is used to open URLs in new |
- * tabs etc. |
- * @private |
- * @param {string} url The URL that was clicked. |
- * @param {!Event} originalEvent The original click event object. |
- */ |
- dispatchUrlClickedEvent_: function(url, originalEvent) { |
- var event = new Event('urlClicked', {bubbles: true}); |
- event.url = url; |
- event.originalEvent = originalEvent; |
- this.dispatchEvent(event); |
- }, |
- |
- /** |
- * Handles mousedown events so that we can prevent the auto scroll as |
- * necessary. |
- * @private |
- * @param {!MouseEvent} e The mousedown event object. |
- */ |
- handleMouseDown_: function(e) { |
- if (e.button == 1) { |
- // WebKit no longer fires click events for middle clicks so we manually |
- // listen to mouse up to dispatch a click event. |
- this.addEventListener('mouseup', this.handleMiddleMouseUp_); |
- |
- // When the user does a middle click we need to prevent the auto scroll |
- // in case the user is trying to middle click to open a bookmark in a |
- // background tab. |
- // We do not do this in case the target is an input since middle click |
- // is also paste on Linux and we don't want to break that. |
- if (e.target.tagName != 'INPUT') |
- e.preventDefault(); |
- } |
- }, |
- |
- /** |
- * WebKit no longer dispatches click events for middle clicks so we need |
- * to emulate it. |
- * @private |
- * @param {!MouseEvent} e The mouse up event object. |
- */ |
- handleMiddleMouseUp_: function(e) { |
- this.removeEventListener('mouseup', this.handleMiddleMouseUp_); |
- if (e.button == 1) { |
- var el = e.target; |
- while (el.parentNode != this) { |
- el = el.parentNode; |
- } |
- var node = el.bookmarkNode; |
- if (node && !bmm.isFolder(node)) |
- this.dispatchUrlClickedEvent_(node.url, e); |
- } |
- }, |
- |
- // Bookmark model update callbacks |
- handleBookmarkChanged: function(id, changeInfo) { |
- var dataModel = this.dataModel; |
- var index = dataModel.findIndexById(id); |
- if (index != -1) { |
- var bookmarkNode = this.dataModel.item(index); |
- bookmarkNode.title = changeInfo.title; |
- if ('url' in changeInfo) |
- bookmarkNode.url = changeInfo['url']; |
- |
- dataModel.updateIndex(index); |
- } |
- }, |
- |
- handleChildrenReordered: function(id, reorderInfo) { |
- if (this.parentId == id) { |
- // We create a new data model with updated items in the right order. |
- var dataModel = this.dataModel; |
- var items = {}; |
- for (var i = this.dataModel.length - 1; i >= 0; i--) { |
- var bookmarkNode = dataModel.item(i); |
- items[bookmarkNode.id] = bookmarkNode; |
- } |
- var newArray = []; |
- for (var i = 0; i < reorderInfo.childIds.length; i++) { |
- newArray[i] = items[reorderInfo.childIds[i]]; |
- newArray[i].index = i; |
- } |
- |
- this.dataModel = new BookmarksArrayDataModel(newArray); |
- } |
- }, |
- |
- handleCreated: function(id, bookmarkNode) { |
- if (this.parentId == bookmarkNode.parentId) |
- this.dataModel.splice(bookmarkNode.index, 0, bookmarkNode); |
- }, |
- |
- handleMoved: function(id, moveInfo) { |
- if (moveInfo.parentId == this.parentId || |
- moveInfo.oldParentId == this.parentId) { |
- |
- var dataModel = this.dataModel; |
- |
- if (moveInfo.oldParentId == moveInfo.parentId) { |
- // Reorder within this folder |
- |
- this.startBatchUpdates(); |
- |
- var bookmarkNode = this.dataModel.item(moveInfo.oldIndex); |
- this.dataModel.splice(moveInfo.oldIndex, 1); |
- this.dataModel.splice(moveInfo.index, 0, bookmarkNode); |
- |
- this.endBatchUpdates(); |
- } else { |
- if (moveInfo.oldParentId == this.parentId) { |
- // Move out of this folder |
- |
- var index = dataModel.findIndexById(id); |
- if (index != -1) |
- dataModel.splice(index, 1); |
- } |
- |
- if (moveInfo.parentId == this.parentId) { |
- // Move to this folder |
- var self = this; |
- chrome.bookmarks.get(id, function(bookmarkNodes) { |
- var bookmarkNode = bookmarkNodes[0]; |
- dataModel.splice(bookmarkNode.index, 0, bookmarkNode); |
- }); |
- } |
- } |
- } |
- }, |
- |
- handleRemoved: function(id, removeInfo) { |
- var dataModel = this.dataModel; |
- var index = dataModel.findIndexById(id); |
- if (index != -1) |
- dataModel.splice(index, 1); |
- }, |
- |
- /** |
- * Workaround for http://crbug.com/40902 |
- * @private |
- */ |
- fixWidth_: function() { |
- var list = bmm.list; |
- if (this.loading_ || !list) |
- return; |
- |
- // The width of the list is wrong after its content has changed. |
- // Fortunately the reported offsetWidth is correct so we can detect the |
- //incorrect width. |
- if (list.offsetWidth != list.parentNode.clientWidth - list.offsetLeft) { |
- // Set the width to the correct size. This causes the relayout. |
- list.style.width = list.parentNode.clientWidth - list.offsetLeft + 'px'; |
- // Remove the temporary style.width in a timeout. Once the timer fires |
- // the size should not change since we already fixed the width. |
- window.setTimeout(function() { |
- list.style.width = ''; |
- }, 0); |
- } |
- } |
- }; |
- |
- /** |
- * The ID of the bookmark folder we are displaying. |
- * @type {string} |
- */ |
- cr.defineProperty(BookmarkList, 'parentId', cr.PropertyKind.JS, |
- function() { |
- this.reload(); |
- }); |
- |
- /** |
- * The contextMenu property. |
- * @type {cr.ui.Menu} |
- */ |
- cr.ui.contextMenuHandler.addContextMenuProperty(BookmarkList); |
- |
- /** |
- * Creates a new bookmark list item. |
- * @param {!BookmarkTreeNode} bookmarkNode The bookmark node this represents. |
- * @constructor |
- * @extends {cr.ui.ListItem} |
- */ |
- function BookmarkListItem(bookmarkNode) { |
- var el = cr.doc.createElement('div'); |
- el.bookmarkNode = bookmarkNode; |
- BookmarkListItem.decorate(el); |
- return el; |
- } |
- |
- /** |
- * Decorates an element as a bookmark list item. |
- * @param {!HTMLElement} el The element to decorate. |
- */ |
- BookmarkListItem.decorate = function(el) { |
- el.__proto__ = BookmarkListItem.prototype; |
- el.decorate(); |
- }; |
- |
- BookmarkListItem.prototype = { |
- __proto__: ListItem.prototype, |
- |
- /** @override */ |
- decorate: function() { |
- ListItem.prototype.decorate.call(this); |
- |
- var bookmarkNode = this.bookmarkNode; |
- |
- this.draggable = true; |
- |
- var labelEl = this.ownerDocument.createElement('div'); |
- labelEl.className = 'label'; |
- labelEl.textContent = bookmarkNode.title; |
- |
- var urlEl = this.ownerDocument.createElement('div'); |
- urlEl.className = 'url'; |
- |
- if (bmm.isFolder(bookmarkNode)) { |
- this.className = 'folder'; |
- } else { |
- labelEl.style.backgroundImage = getFaviconImageSet(bookmarkNode.url); |
- labelEl.style.backgroundSize = '16px'; |
- urlEl.textContent = bookmarkNode.url; |
- } |
- |
- this.appendChild(labelEl); |
- this.appendChild(urlEl); |
- |
- // Initially the ContextMenuButton was added here but it slowed down |
- // rendering a lot so it is now added using mouseover. |
- }, |
- |
- /** |
- * The ID of the bookmark folder we are currently showing or loading. |
- * @type {string} |
- */ |
- get bookmarkId() { |
- return this.bookmarkNode.id; |
- }, |
- |
- /** |
- * Whether the user is currently able to edit the list item. |
- * @type {boolean} |
- */ |
- get editing() { |
- return this.hasAttribute('editing'); |
- }, |
- set editing(editing) { |
- var oldEditing = this.editing; |
- if (oldEditing == editing) |
- return; |
- |
- var url = this.bookmarkNode.url; |
- var title = this.bookmarkNode.title; |
- var isFolder = bmm.isFolder(this.bookmarkNode); |
- var listItem = this; |
- var labelEl = this.firstChild; |
- var urlEl = labelEl.nextSibling; |
- var labelInput, urlInput; |
- |
- // Handles enter and escape which trigger reset and commit respectively. |
- function handleKeydown(e) { |
- // Make sure that the tree does not handle the key. |
- e.stopPropagation(); |
- |
- // Calling list.focus blurs the input which will stop editing the list |
- // item. |
- switch (e.keyIdentifier) { |
- case 'U+001B': // Esc |
- labelInput.value = title; |
- if (!isFolder) |
- urlInput.value = url; |
- // fall through |
- cr.dispatchSimpleEvent(listItem, 'canceledit', true); |
- case 'Enter': |
- if (listItem.parentNode) |
- listItem.parentNode.focus(); |
- } |
- } |
- |
- function handleBlur(e) { |
- // When the blur event happens we do not know who is getting focus so we |
- // delay this a bit since we want to know if the other input got focus |
- // before deciding if we should exit edit mode. |
- var doc = e.target.ownerDocument; |
- window.setTimeout(function() { |
- var activeElement = doc.hasFocus() && doc.activeElement; |
- if (activeElement != urlInput && activeElement != labelInput) { |
- listItem.editing = false; |
- } |
- }, 50); |
- } |
- |
- var doc = this.ownerDocument; |
- if (editing) { |
- this.setAttribute('editing', ''); |
- this.draggable = false; |
- |
- labelInput = doc.createElement('input'); |
- labelInput.placeholder = |
- loadTimeData.getString('name_input_placeholder'); |
- replaceAllChildren(labelEl, labelInput); |
- labelInput.value = title; |
- |
- if (!isFolder) { |
- urlInput = doc.createElement('input'); |
- urlInput.type = 'url'; |
- urlInput.required = true; |
- urlInput.placeholder = |
- loadTimeData.getString('url_input_placeholder'); |
- |
- // We also need a name for the input for the CSS to work. |
- urlInput.name = '-url-input-' + cr.createUid(); |
- replaceAllChildren(urlEl, urlInput); |
- urlInput.value = url; |
- } |
- |
- function stopPropagation(e) { |
- e.stopPropagation(); |
- } |
- |
- var eventsToStop = |
- ['mousedown', 'mouseup', 'contextmenu', 'dblclick', 'paste']; |
- eventsToStop.forEach(function(type) { |
- labelInput.addEventListener(type, stopPropagation); |
- }); |
- labelInput.addEventListener('keydown', handleKeydown); |
- labelInput.addEventListener('blur', handleBlur); |
- cr.ui.limitInputWidth(labelInput, this, 100, 0.5); |
- labelInput.focus(); |
- labelInput.select(); |
- |
- if (!isFolder) { |
- eventsToStop.forEach(function(type) { |
- urlInput.addEventListener(type, stopPropagation); |
- }); |
- urlInput.addEventListener('keydown', handleKeydown); |
- urlInput.addEventListener('blur', handleBlur); |
- cr.ui.limitInputWidth(urlInput, this, 200, 0.5); |
- } |
- |
- } else { |
- // Check that we have a valid URL and if not we do not change the |
- // editing mode. |
- if (!isFolder) { |
- var urlInput = this.querySelector('.url input'); |
- var newUrl = urlInput.value; |
- if (!newUrl) { |
- cr.dispatchSimpleEvent(this, 'canceledit', true); |
- return; |
- } |
- |
- if (!urlInput.validity.valid) { |
- // WebKit does not do URL fix up so we manually test if prepending |
- // 'http://' would make the URL valid. |
- // https://bugs.webkit.org/show_bug.cgi?id=29235 |
- urlInput.value = 'http://' + newUrl; |
- if (!urlInput.validity.valid) { |
- // still invalid |
- urlInput.value = newUrl; |
- |
- // In case the item was removed before getting here we should |
- // not alert. |
- if (listItem.parentNode) { |
- // Select the item again. |
- var dataModel = this.parentNode.dataModel; |
- var index = dataModel.indexOf(this.bookmarkNode); |
- var sm = this.parentNode.selectionModel; |
- sm.selectedIndex = sm.leadIndex = sm.anchorIndex = index; |
- |
- alert(loadTimeData.getString('invalid_url')); |
- } |
- urlInput.focus(); |
- urlInput.select(); |
- return; |
- } |
- newUrl = 'http://' + newUrl; |
- } |
- urlEl.textContent = this.bookmarkNode.url = newUrl; |
- } |
- |
- this.removeAttribute('editing'); |
- this.draggable = true; |
- |
- labelInput = this.querySelector('.label input'); |
- var newLabel = labelInput.value; |
- labelEl.textContent = this.bookmarkNode.title = newLabel; |
- |
- if (isFolder) { |
- if (newLabel != title) { |
- cr.dispatchSimpleEvent(this, 'rename', true); |
- } |
- } else if (newLabel != title || newUrl != url) { |
- cr.dispatchSimpleEvent(this, 'edit', true); |
- } |
- } |
- } |
- }; |
- |
- return { |
- BookmarkList: BookmarkList, |
- list: list |
- }; |
-}); |