Index: resources/bookmark_manager/main.html |
=================================================================== |
--- resources/bookmark_manager/main.html (revision 0) |
+++ resources/bookmark_manager/main.html (revision 0) |
@@ -0,0 +1,1587 @@ |
+<!DOCTYPE html> |
+<html i18n-values="dir:textdirection"> |
+<!-- |
+ |
+Copyright (c) 2010 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. |
+ |
+ |
+This is work in progress: |
+ |
+Favicons: chrome-extension: is not allowed to access chrome://favicon. We need |
+to whitelist it or expose a way to get the data URI for the favicon (slow and |
+sucky). |
+ |
+Favicon of bmm does not work. No icon is showed. |
+ |
+--> |
+<head> |
+<title i18n-content="title"></title> |
+ |
+<link rel="stylesheet" href="css/list.css"> |
+<link rel="stylesheet" href="css/tree.css"> |
+<link rel="stylesheet" href="css/menu.css"> |
+<link rel="stylesheet" href="css/bmm.css"> |
+ |
+<script src="css/tree.css.js"></script> |
+<script src="css/bmm.css.js"></script> |
+ |
+<script src="js/cr.js"></script> |
+<script src="js/cr/event.js"></script> |
+<script src="js/cr/eventtarget.js"></script> |
+<script src="js/cr/promise.js"></script> |
+<script src="js/cr/ui.js"></script> |
+<script src="js/cr/ui/listselectionmodel.js"></script> |
+<script src="js/cr/ui/listitem.js"></script> |
+<script src="js/cr/ui/list.js"></script> |
+<script src="js/cr/ui/tree.js"></script> |
+<script src="js/cr/ui/command.js"></script> |
+<script src="js/cr/ui/menuitem.js"></script> |
+<script src="js/cr/ui/menu.js"></script> |
+<script src="js/cr/ui/menubutton.js"></script> |
+<script src="js/cr/ui/contextmenuhandler.js"></script> |
+ |
+<script src="js/util.js"></script> |
+<script src="js/localstrings.js"></script> |
+<script src="js/i18ntemplate.js"></script> |
+ |
+<script src="js/bmm/treeiterator.js"></script> |
+<script src="js/bmm.js"></script> |
+<script src="js/bmm/bookmarklist.js"></script> |
+<script src="js/bmm/bookmarktree.js"></script> |
+ |
+<script> |
+ |
+// Sometimes the extension API is not initialized. |
+if (!chrome.bookmarks) |
+ window.location.reload(); |
+ |
+// Allow platform specific CSS rules. |
+if (cr.isMac) |
+ document.documentElement.setAttribute('os', 'mac'); |
+ |
+</script> |
+</head> |
+<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize"> |
+ |
+<div class="header"> |
+ <button onclick="resetSearch()" class="logo" tabindex=3></button> |
+ <div> |
+ <form onsubmit="setSearch(this.term.value); return false;" |
+ class="form"> |
+ <input type="text" id="term" tabindex=1 autofocus> |
+ <input type="submit" i18n-values=".value:search_button" tabindex=1> |
+ </form> |
+ <div class=toolbar> |
+ <button menu="#organize-menu" tabindex="-1" i18n-content="organize_menu"></button> |
+ <button menu="#tools-menu" tabindex="-1" i18n-content="tools_menu"></button> |
+ </div> |
+ </div> |
+</div> |
+ |
+<div class=main> |
+ <div id=tree-container> |
+ <tree id=tree tabindex=2></tree> |
+ </div> |
+ <list id=list tabindex=2></list> |
+</div> |
+ |
+ |
+<script> |
+ |
+const BookmarkList = bmm.BookmarkList; |
+const BookmarkTree = bmm.BookmarkTree; |
+const ListItem = cr.ui.ListItem; |
+const TreeItem = cr.ui.TreeItem; |
+ |
+/** |
+ * The id of the bookmark root. |
+ * @type {number} |
+ */ |
+const ROOT_ID = '0'; |
+ |
+var bookmarkCache = { |
+ /** |
+ * This returns a reference to the bookmark node that is cached by the tree |
+ * or list. Use this funciton when we need to update the local cachea after |
+ * changes. It only returns bookmarks that are used by the tree and/or the |
+ * list. |
+ * @param {string} The ID of the bookmark that we want to get. |
+ * @return {BookmarkTreeNode} |
+ */ |
+ getById: function(id) { |
+ var el = bmm.treeLookup[id] || bmm.listLookup[id]; |
+ return el && el.bookmarkNode; |
+ }, |
+ |
+ /** |
+ * Removes the cached item from both the list and tree lookups. |
+ */ |
+ remove: function(id) { |
+ delete bmm.listLookup[id]; |
+ |
+ var treeItem = bmm.treeLookup[id]; |
+ if (treeItem) { |
+ var items = treeItem.items; // is an HTMLCollection |
+ for (var i = 0, item; item = items[i]; i++) { |
+ var bookmarkNode = item.bookmarkNode; |
+ delete bmm.treeLookup[bookmarkNode.id]; |
+ } |
+ delete bmm.treeLookup[id]; |
+ } |
+ }, |
+ |
+ /** |
+ * Updates the underlying bookmark node for the tree items and list items by |
+ * querying the bookmark backend. |
+ * @param {string} id The id of the node to update the children for. |
+ * @param {Function=} opt_f A funciton to call when done. |
+ */ |
+ updateChildren: function(id, opt_f) { |
+ function updateItem(bookmarkNode) { |
+ var treeItem = bmm.treeLookup[bookmarkNode.id]; |
+ if (treeItem) { |
+ treeItem.bookmarkNode = bookmarkNode; |
+ } |
+ var listItem = bmm.listLookup[bookmarkNode.id]; |
+ if (listItem) { |
+ listItem.bookmarkNode = bookmarkNode; |
+ } |
+ } |
+ |
+ chrome.bookmarks.getChildren(id, function(children) { |
+ children.forEach(updateItem); |
+ |
+ if (opt_f) |
+ opt_f(children); |
+ }); |
+ } |
+}; |
+ |
+</script> |
+<script> |
+ |
+BookmarkList.decorate(list); |
+ |
+var searchTreeItem = new TreeItem({ |
+ label: 'Search', |
+ icon: 'images/bookmark_manager_search.png', |
+ bookmarkId: 'q=' |
+}); |
+bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem; |
+ |
+var recentTreeItem = new TreeItem({ |
+ label: 'Recent', |
+ icon: 'images/bookmark_manager_recent.png', |
+ bookmarkId: 'recent' |
+}); |
+bmm.treeLookup[recentTreeItem.bookmarkId] = recentTreeItem; |
+ |
+BookmarkTree.decorate(tree); |
+ |
+tree.addEventListener('change', function() { |
+ navigateTo(tree.selectedItem.bookmarkId); |
+}); |
+ |
+/** |
+ * Navigates to a bookmark ID. |
+ * @param {string} id The ID to navigate to. |
+ */ |
+function navigateTo(id) { |
+ console.info('navigateTo', window.location.hash, id); |
+ // Update the location hash using a timer to prevent reentrancy. This is how |
+ // often we add history entries and the time here is a bit arbitrary but was |
+ // picked as the smallest time a human perceives as instant. |
+ clearTimeout(navigateTo.timer_); |
+ navigateTo.timer_ = setTimeout(function() { |
+ window.location.hash = tree.selectedItem.bookmarkId; |
+ }, 300); |
+ updateParentId(id); |
+} |
+ |
+/** |
+ * Updates the parent ID of the bookmark list and selects the correct tree item. |
+ * @param {string} id The id. |
+ */ |
+function updateParentId(id) { |
+ list.parentId = id; |
+ if (id in bmm.treeLookup) |
+ tree.selectedItem = bmm.treeLookup[id]; |
+} |
+ |
+// We listen to hashchange so that we can update the currently shown folder when |
+// the user goes back and forward in the history. |
+window.onhashchange = function(e) { |
+ var id = window.location.hash.slice(1); |
+ |
+ var valid = false; |
+ |
+ // In case we got a search hash update the text input and the bmm.treeLookup |
+ // to use the new id. |
+ if (/^q=/.test(id)) { |
+ delete bmm.treeLookup[searchTreeItem.bookmarkId]; |
+ $('term').value = id.slice(2); |
+ searchTreeItem.bookmarkId = id; |
+ bmm.treeLookup[id] = searchTreeItem; |
+ valid = true; |
+ } else if (id == 'recent') { |
+ valid = true; |
+ } |
+ |
+ if (valid) { |
+ updateParentId(id); |
+ } else { |
+ // We need to verify that this is a correct ID. |
+ chrome.bookmarks.get(id, function(items) { |
+ if (items && items.length == 1) |
+ updateParentId(id); |
+ }); |
+ } |
+}; |
+ |
+list.addEventListener('activate', function(e) { |
+ var bookmarkNodes = getSelectedBookmarkNodes(); |
+ |
+ // If we double clicked or pressed enter on a single folder navigate to it. |
+ if (bookmarkNodes.length == 1 && bmm.isFolder(bookmarkNodes[0])) { |
+ navigateTo(bookmarkNodes[0].id); |
+ } else { |
+ var command = $('open-in-new-tab-command'); |
+ command.execute(); |
+ } |
+}); |
+ |
+// The list dispatches an event when the user clicks on the URL or the Show in |
+// folder part. |
+list.addEventListener('urlClicked', function(e) { |
+ openUrls([e.url], e.kind); |
+}); |
+ |
+/** |
+ * Timer id used for delaying find-as-you-type |
+ */ |
+var inputDelayTimer; |
+ |
+// Capture input changes to the search term input element and delay searching |
+// for 250ms to reduce flicker. |
+$('term').oninput = function(e) { |
+ clearTimeout(inputDelayTimer); |
+ inputDelayTimer = setTimeout(function() { |
+ setSearch($('term').value); |
+ }, 250); |
+}; |
+ |
+/** |
+ * Navigates to the search results for the search text. |
+ * @para {string} searchText The text to search for. |
+ */ |
+function setSearch(searchText) { |
+ navigateTo('q=' + searchText); |
+} |
+ |
+/** |
+ * Clears the search. |
+ */ |
+function resetSearch() { |
+ $('term').value = ''; |
+ setSearch(''); |
+ $('term').focus(); |
+} |
+ |
+/** |
+ * Called when the title of a bookmark changes. |
+ * @param {string} id |
+ * @param {!Object} changeInfo |
+ */ |
+function handleBookmarkChanged(id, changeInfo) { |
+ // console.log('handleBookmarkChanged', id, changeInfo); |
+ list.handleBookmarkChanged(id, changeInfo); |
+ tree.handleBookmarkChanged(id, changeInfo); |
+} |
+ |
+/** |
+ * Callback for when the user reorders by title. |
+ * @param {string} id The id of the bookmark folder that was reordered. |
+ * @param {!Object} reorderInfo The information about how the items where |
+ * reordered. |
+ */ |
+function handleChildrenReordered(id, reorderInfo) { |
+ // console.info('handleChildrenReordered', id, reorderInfo); |
+ list.handleChildrenReordered(id, reorderInfo); |
+ tree.handleChildrenReordered(id, reorderInfo); |
+ bookmarkCache.updateChildren(id); |
+} |
+ |
+/** |
+ * Callback for when a bookmark node is created. |
+ * @param {string} id The id of the newly created bookmark node. |
+ * @param {!Object} bookmarkNode The new bookmark node. |
+ */ |
+function handleCreated(id, bookmarkNode) { |
+ // console.info('handleCreated', id, bookmarkNode); |
+ list.handleCreated(id, bookmarkNode); |
+ tree.handleCreated(id, bookmarkNode); |
+ bookmarkCache.updateChildren(bookmarkNode.parentId); |
+} |
+ |
+function handleMoved(id, moveInfo) { |
+ // console.info('handleMoved', id, moveInfo); |
+ list.handleMoved(id, moveInfo); |
+ tree.handleMoved(id, moveInfo); |
+ |
+ bookmarkCache.updateChildren(moveInfo.parentId); |
+ if (moveInfo.parentId != moveInfo.oldParentId) |
+ bookmarkCache.updateChildren(moveInfo.oldParentId); |
+} |
+ |
+function handleRemoved(id, removeInfo) { |
+ // console.info('handleRemoved', id, removeInfo); |
+ list.handleRemoved(id, removeInfo); |
+ tree.handleRemoved(id, removeInfo); |
+ |
+ bookmarkCache.updateChildren(removeInfo.parentId); |
+ bookmarkCache.remove(id); |
+} |
+ |
+function handleImportBegan() { |
+ chrome.bookmarks.onCreated.removeListener(handleCreated); |
+} |
+ |
+function handleImportEnded() { |
+ chrome.bookmarks.onCreated.addListener(handleCreated); |
+ var p = bmm.loadTree(); |
+ p.addListener(function(node) { |
+ var otherBookmarks = node.children[1].children; |
+ var importedFolder = otherBookmarks[otherBookmarks.length - 1]; |
+ var importId = importedFolder.id; |
+ tree.insertSubtree(importedFolder); |
+ navigateTo(importId) |
+ }); |
+} |
+ |
+/** |
+ * Adds the listeners for the bookmark model change events. |
+ */ |
+function addBookmarkModelListeners() { |
+ chrome.bookmarks.onChanged.addListener(handleBookmarkChanged); |
+ chrome.bookmarks.onChildrenReordered.addListener(handleChildrenReordered); |
+ chrome.bookmarks.onCreated.addListener(handleCreated); |
+ chrome.bookmarks.onMoved.addListener(handleMoved); |
+ chrome.bookmarks.onRemoved.addListener(handleRemoved); |
+ chrome.experimental.bookmarkManager.onImportBegan.addListener( |
+ handleImportBegan); |
+ chrome.experimental.bookmarkManager.onImportEnded.addListener( |
+ handleImportEnded); |
+} |
+ |
+/** |
+ * This returns the user visible path to the folder where the bookmark is |
+ * located. |
+ * @param {number} parentId The ID of the parent folder. |
+ * @return {string} The path to the the bookmark, |
+ */ |
+function getFolder(parentId) { |
+ var parentNode = tree.getBookmarkNodeById(parentId); |
+ if (parentNode) { |
+ var s = parentNode.title; |
+ if (parentNode.parentId != ROOT_ID) { |
+ return getFolder(parentNode.parentId) + '/' + s; |
+ } |
+ return s; |
+ } |
+} |
+ |
+tree.addEventListener('load', function(e) { |
+ // Add hard coded tree items |
+ tree.add(recentTreeItem); |
+ tree.add(searchTreeItem); |
+ |
+ // Now we can select a tree item. |
+ var hash = window.location.hash.slice(1); |
+ if (!hash) { |
+ // If we do not have a hash select first item in the tree. |
+ hash = tree.items[0].bookmarkId; |
+ } |
+ |
+ if (/^q=/.test(hash)) |
+ $('term').value = hash.slice(2); |
+ navigateTo(hash); |
+}); |
+ |
+tree.buildTree(); |
+addBookmarkModelListeners(); |
+ |
+var dnd = { |
+ DND_EFFECT_COPY: 'copy', |
+ DND_EFFECT_MOVE: cr.isMac ? 'move' : 'copy', // http://crbug.com/14654 |
+ |
+ dragData: null, |
+ |
+ getBookmarkElement: function(el) { |
+ while (el && !el.bookmarkNode) { |
+ el = el.parentNode; |
+ } |
+ return el; |
+ }, |
+ |
+ // If we are over the list and the list is showing recent or search result |
+ // we cannot drop. |
+ isOverRecentOrSearch: function(overElement) { |
+ return (list.isRecent() || list.isSearch()) && list.contains(overElement); |
+ }, |
+ |
+ checkEvery_: function(f, overBookmarkNode, overElement) { |
+ return this.dragData.elements.every(function(element) { |
+ return f.call(this, element, overBookmarkNode, overElement); |
+ }, this); |
+ }, |
+ |
+ /** |
+ * @return {boolean} Whether we are currently dragging any folders. |
+ */ |
+ isDraggingFolders: function() { |
+ return !!this.dragData && this.dragData.elements.some(function(node) { |
+ return !node.url; |
+ }); |
+ }, |
+ |
+ /** |
+ * This is a first pass wether we can drop the dragged items. |
+ * |
+ * @param {!BookmarkTreeNode} overBookmarkNode The bookmark that we are |
+ * currently dragging over. |
+ * @param {!HTMLElement} overElement The element that we are currently |
+ * dragging over. |
+ * @return {boolean} If this returns false then we know we should not drop |
+ * the items. If it returns true we still have to call canDropOn, |
+ * canDropAbove and canDropBelow. |
+ */ |
+ canDrop: function(overBookmarkNode, overElement) { |
+ var dragData = this.dragData; |
+ if (!dragData) |
+ return false; |
+ |
+ if (this.isOverRecentOrSearch(overElement)) |
+ return false; |
+ |
+ if (!dragData.sameProfile) |
+ return true; |
+ |
+ return this.checkEvery_(this.canDrop_, overBookmarkNode, overElement); |
+ }, |
+ |
+ /** |
+ * Helper for canDrop that only checks one bookmark node. |
+ * @private |
+ */ |
+ canDrop_: function(dragNode, overBookmarkNode, overElement) { |
+ var dragId = dragNode.id; |
+ |
+ if (overBookmarkNode.id == dragId) |
+ return false; |
+ |
+ // If we are dragging a folder we cannot drop it on any of its descendants |
+ var dragBookmarkItem = bmm.treeLookup[dragId]; |
+ var dragBookmarkNode = dragBookmarkItem && dragBookmarkItem.bookmarkNode; |
+ if (dragBookmarkNode && bmm.contains(dragBookmarkNode, overBookmarkNode)) { |
+ return false; |
+ } |
+ |
+ return true; |
+ }, |
+ |
+ /** |
+ * Whether we can drop the dragged items above the drop target. |
+ * |
+ * @param {!BookmarkTreeNode} overBookmarkNode The bookmark that we are |
+ * currently dragging over. |
+ * @param {!HTMLElement} overElement The element that we are currently |
+ * dragging over. |
+ * @return {boolean} Whether we can drop the dragged items above the drop |
+ * target. |
+ */ |
+ canDropAbove: function(overBookmarkNode, overElement) { |
+ if (overElement instanceof BookmarkList) |
+ return false; |
+ |
+ // We cannot drop between Bookmarks bar and Other bookmarks |
+ if (overBookmarkNode.parentId == ROOT_ID) |
+ return false; |
+ |
+ var isOverTreeItem = overElement instanceof TreeItem; |
+ |
+ // We can only drop between items in the tree if we have any folders. |
+ if (isOverTreeItem && !this.isDraggingFolders()) |
+ return false; |
+ |
+ if (!this.dragData.sameProfile) |
+ return this.isDraggingFolders() || !isOverTreeItem; |
+ |
+ return this.checkEvery_(this.canDropAbove_, overBookmarkNode, overElement); |
+ }, |
+ |
+ /** |
+ * Helper for canDropAbove that only checks one bookmark node. |
+ * @private |
+ */ |
+ canDropAbove_: function(dragNode, overBookmarkNode, overElement) { |
+ var dragId = dragNode.id; |
+ |
+ // We cannot drop above if the item below is already in the drag source |
+ var previousElement = overElement.previousElementSibling; |
+ if (previousElement && |
+ previousElement.bookmarkId == dragId) |
+ return false; |
+ |
+ return true; |
+ }, |
+ |
+ /** |
+ * Whether we can drop the dragged items below the drop target. |
+ * |
+ * @param {!BookmarkTreeNode} overBookmarkNode The bookmark that we are |
+ * currently dragging over. |
+ * @param {!HTMLElement} overElement The element that we are currently |
+ * dragging over. |
+ * @return {boolean} Whether we can drop the dragged items below the drop |
+ * target. |
+ */ |
+ canDropBelow: function(overBookmarkNode, overElement) { |
+ if (overElement instanceof BookmarkList) |
+ return false; |
+ |
+ // We cannot drop between Bookmarks bar and Other bookmarks |
+ if (overBookmarkNode.parentId == ROOT_ID) |
+ return false; |
+ |
+ // We can only drop between items in the tree if we have any folders. |
+ if (!this.isDraggingFolders() && overElement instanceof TreeItem) |
+ return false; |
+ |
+ var isOverTreeItem = overElement instanceof TreeItem; |
+ |
+ // Don't allow dropping below an expanded tree item since it is confusing |
+ // to the user anyway. |
+ if (isOverTreeItem && overElement.expanded) |
+ return false; |
+ |
+ if (!this.dragData.sameProfile) |
+ return this.isDraggingFolders() || !isOverTreeItem; |
+ |
+ return this.checkEvery_(this.canDropBelow_, overBookmarkNode, overElement); |
+ }, |
+ |
+ /** |
+ * Helper for canDropBelow that only checks one bookmark node. |
+ * @private |
+ */ |
+ canDropBelow_: function(dragNode, overBookmarkNode, overElement) { |
+ var dragId = dragNode.id; |
+ |
+ // We cannot drop below if the item below is already in the drag source |
+ var nextElement = overElement.nextElementSibling; |
+ if (nextElement && |
+ nextElement.bookmarkId == dragId) |
+ return false; |
+ |
+ return true; |
+ }, |
+ |
+ /** |
+ * Whether we can drop the dragged items on the drop target. |
+ * |
+ * @param {!BookmarkTreeNode} overBookmarkNode The bookmark that we are |
+ * currently dragging over. |
+ * @param {!HTMLElement} overElement The element that we are currently |
+ * dragging over. |
+ * @return {boolean} Whether we can drop the dragged items on the drop |
+ * target. |
+ */ |
+ canDropOn: function(overBookmarkNode, overElement) { |
+ // We can only drop on a folder. |
+ if (!bmm.isFolder(overBookmarkNode)) |
+ return false; |
+ |
+ if (!this.dragData.sameProfile) |
+ return true; |
+ |
+ return this.checkEvery_(this.canDropOn_, overBookmarkNode, overElement); |
+ }, |
+ |
+ /** |
+ * Helper for canDropOn that only checks one bookmark node. |
+ * @private |
+ */ |
+ canDropOn_: function(dragNode, overBookmarkNode, overElement) { |
+ var dragId = dragNode.id; |
+ |
+ if (overElement instanceof BookmarkList) { |
+ // We are trying to drop an item after the last item in the list. This |
+ // is allowed if the item is different from the last item in the list |
+ var listItems = list.items; |
+ var len = listItems.length; |
+ if (len == 0 || |
+ listItems[len - 1].bookmarkId != dragId) { |
+ return true; |
+ } |
+ } |
+ |
+ // Cannot drop on current parent. |
+ if (overBookmarkNode.id == dragNode.parentId) |
+ return false; |
+ |
+ return true; |
+ }, |
+ |
+ /** |
+ * Callback for the dragstart event. |
+ * @param {Event} e The dragstart event. |
+ */ |
+ handleDragStart: function(e) { |
+ // console.log(e.type); |
+ |
+ // Determine the selected bookmarks. |
+ var target = e.target; |
+ var draggedItems = []; |
+ if (target instanceof ListItem) { |
+ // Use selected items. |
+ draggedItems = target.parentNode.selectedItems; |
+ } else if (target instanceof TreeItem) { |
+ draggedItems.push(target); |
+ } |
+ |
+ // We manage starting the drag by using the extension API. |
+ e.preventDefault(); |
+ |
+ if (draggedItems.length) { |
+ // If we are dragging a single link we can do the *Link* effect, otherwise |
+ // we only allow copy and move. |
+ var effectAllowed; |
+ if (draggedItems.length == 1 && |
+ !bmm.isFolder(draggedItems[0].bookmarkNode)) { |
+ effectAllowed = 'copyMoveLink'; |
+ } else { |
+ effectAllowed = 'copyMove'; |
+ } |
+ e.dataTransfer.effectAllowed = effectAllowed; |
+ |
+ var ids = draggedItems.map(function(el) { |
+ return el.bookmarkId; |
+ }); |
+ |
+ chrome.experimental.bookmarkManager.startDrag(ids); |
+ } |
+ }, |
+ |
+ handleDragEnter: function(e) { |
+ // console.log(e.type); |
+ |
+ e.preventDefault(); |
+ }, |
+ |
+ /** |
+ * Calback for the dragover event. |
+ * @param {Event} e The dragover event. |
+ */ |
+ handleDragOver: function(e) { |
+ // console.log(e.type); |
+ |
+ // The default operation is to allow dropping links etc to do navigation. |
+ // We never want to do that for the bookmark manager. |
+ e.preventDefault(); |
+ |
+ if (!this.dragData) |
+ return; |
+ |
+ var overElement = this.getBookmarkElement(e.target); |
+ if (!overElement && e.target == list) |
+ overElement = list; |
+ |
+ if (!overElement) |
+ return; |
+ |
+ var overBookmarkNode = overElement.bookmarkNode; |
+ |
+ if (!this.canDrop(overBookmarkNode, overElement)) |
+ return; |
+ |
+ var bookmarkNode = overElement.bookmarkNode; |
+ |
+ var canDropAbove = this.canDropAbove(overBookmarkNode, overElement); |
+ var canDropOn = this.canDropOn(overBookmarkNode, overElement); |
+ var canDropBelow = this.canDropBelow(overBookmarkNode, overElement); |
+ |
+ if (!canDropAbove && !canDropOn && !canDropBelow) |
+ return; |
+ |
+ // Now we know that we can drop. Determine if we will drop above, on or |
+ // below based on mouse position etc. |
+ |
+ var dropPos; |
+ |
+ e.dataTransfer.dropEffect = this.dragData.sameProfile ? |
+ this.DND_EFFECT_MOVE : this.DND_EFFECT_COPY; |
+ |
+ var rect; |
+ if (overElement instanceof TreeItem) { |
+ // We only want the rect of the row representing the item and not |
+ // its children |
+ rect = overElement.rowElement.getBoundingClientRect(); |
+ } else { |
+ rect = overElement.getBoundingClientRect(); |
+ } |
+ |
+ var dy = e.clientY - rect.top; |
+ var yRatio = dy / rect.height; |
+ |
+ // above |
+ if (canDropAbove && |
+ (yRatio <= .25 || yRatio <= .5 && !(canDropBelow && canDropOn))) { |
+ dropPos = 'above'; |
+ |
+ // below |
+ } else if (canDropBelow && |
+ (yRatio > .75 || yRatio > .5 && !(canDropAbove && canDropOn))) { |
+ dropPos = 'below'; |
+ |
+ // on |
+ } else if (canDropOn) { |
+ dropPos = 'on'; |
+ |
+ // none |
+ } else { |
+ // No drop can happen. Exit now. |
+ e.dataTransfer.dropEffect = 'none'; |
+ return; |
+ } |
+ |
+ function cloneClientRect(rect) { |
+ var newRect = {}; |
+ for (var key in rect) { |
+ newRect[key] = rect[key]; |
+ } |
+ return newRect; |
+ } |
+ |
+ // If we are dropping above or below a tree item adjust the width so |
+ // that it is clearer where the item will be dropped. |
+ if ((dropPos == 'above' || dropPos == 'below') && |
+ overElement instanceof TreeItem) { |
+ // ClientRect is read only so clone in into a read-write object. |
+ rect = cloneClientRect(rect); |
+ var rtl = getComputedStyle(overElement).direction == 'rtl'; |
+ var labelElement = overElement.labelElement; |
+ var labelRect = labelElement.getBoundingClientRect(); |
+ if (rtl) { |
+ rect.width = labelRect.left + labelRect.width - rect.left; |
+ } else { |
+ rect.left = labelRect.left; |
+ rect.width -= rect.left |
+ } |
+ } |
+ |
+ var overlayType = dropPos; |
+ |
+ // If we are dropping on a list we want to show a overlay drop line after |
+ // the last element |
+ if (overElement instanceof BookmarkList) { |
+ overlayType = 'below'; |
+ |
+ // Get the rect of the last list item. |
+ var items = overElement.items; |
+ var length = items.length; |
+ if (length) { |
+ dropPos = 'below'; |
+ overElement = items[length - 1]; |
+ rect = overElement.getBoundingClientRect(); |
+ } else { |
+ // If there are no items, collapse the height of the rect |
+ rect = cloneClientRect(rect); |
+ rect.height = 0; |
+ // We do not use bottom so we don't care to adjust it. |
+ } |
+ } |
+ |
+ this.showDropOverlay_(rect, overlayType); |
+ |
+ this.dropDestination = { |
+ dropPos: dropPos, |
+ relatedNode: overElement.bookmarkNode |
+ }; |
+ }, |
+ |
+ /** |
+ * Shows and positions the drop marker overlay. |
+ * @param {ClientRect} targetRect The drop target rect |
+ * @param {string} overlayType The position relative to the target rect. |
+ * @private |
+ */ |
+ showDropOverlay_: function(targetRect, overlayType) { |
+ window.clearTimeout(this.hideDropOverlayTimer_); |
+ var overlay = $('drop-overlay'); |
+ if (overlayType == 'on') { |
+ overlay.className = ''; |
+ overlay.style.top = targetRect.top + 'px'; |
+ overlay.style.height = targetRect.height + 'px'; |
+ } else { |
+ overlay.className = 'line'; |
+ overlay.style.height = ''; |
+ } |
+ overlay.style.width = targetRect.width + 'px'; |
+ overlay.style.left = targetRect.left + 'px'; |
+ overlay.style.display = 'block'; |
+ |
+ if (overlayType != 'on') { |
+ var overlayRect = overlay.getBoundingClientRect(); |
+ if (overlayType == 'above') { |
+ overlay.style.top = targetRect.top - overlayRect.height / 2 + 'px'; |
+ } else { |
+ overlay.style.top = targetRect.top + targetRect.height - |
+ overlayRect.height / 2 + 'px'; |
+ } |
+ } |
+ }, |
+ |
+ /** |
+ * Hides the drop overlay element. |
+ * @private |
+ */ |
+ hideDropOverlay_: function() { |
+ // Hide the overlay in a timeout to reduce flickering as we move between |
+ // valid drop targets. |
+ window.clearTimeout(this.hideDropOverlayTimer_); |
+ this.hideDropOverlayTimer_ = window.setTimeout(function() { |
+ $('drop-overlay').style.display = ''; |
+ }, 100); |
+ }, |
+ |
+ handleDragLeave: function(e) { |
+ // console.log(e.type); |
+ |
+ this.hideDropOverlay_(); |
+ }, |
+ |
+ handleDrop: function(e) { |
+ // console.log(e.type); |
+ |
+ if (this.dropDestination && this.dragData) { |
+ var dropPos = this.dropDestination.dropPos; |
+ var relatedNode = this.dropDestination.relatedNode; |
+ var parentId = dropPos == 'on' ? relatedNode.id : relatedNode.parentId; |
+ |
+ var index; |
+ if (dropPos == 'above') |
+ index = relatedNode.index; |
+ else if (dropPos == 'below') |
+ index = relatedNode.index + 1; |
+ |
+ if (index != undefined) |
+ chrome.experimental.bookmarkManager.drop(parentId, index); |
+ else |
+ chrome.experimental.bookmarkManager.drop(parentId); |
+ |
+ // TODO(arv): Select the newly dropped items. |
+ } |
+ this.dropDestination = null; |
+ this.hideDropOverlay_(); |
+ }, |
+ |
+ handleDrag: function(e) { |
+ // console.log(e.type); |
+ }, |
+ |
+ handleDragEnd: function(e) { |
+ // console.log(e.type); |
+ |
+ var self = this; |
+ // Chromium Win incorrectly fires the dragend event before the drop event. |
+ // http://code.google.com/p/chromium/issues/detail?id=31292 |
+ window.setTimeout(function() { |
+ self.dragData = null; |
+ }, 1) |
+ }, |
+ |
+ handleChromeDragEnter: function(dragData) { |
+ this.dragData = dragData; |
+ }, |
+ |
+ init: function() { |
+ document.addEventListener('dragstart', cr.bind(this.handleDragStart, this)); |
+ document.addEventListener('dragenter', cr.bind(this.handleDragEnter, this)); |
+ document.addEventListener('dragover', cr.bind(this.handleDragOver, this)); |
+ document.addEventListener('dragleave', cr.bind(this.handleDragLeave, this)); |
+ document.addEventListener('drop', cr.bind(this.handleDrop, this)); |
+ document.addEventListener('dragend', cr.bind(this.handleDragEnd, this)); |
+ document.addEventListener('drag', cr.bind(this.handleDrag, this)); |
+ |
+ chrome.experimental.bookmarkManager.onDragEnter.addListener(cr.bind( |
+ this.handleChromeDragEnter, this)); |
+ } |
+ |
+}; |
+ |
+dnd.init(); |
+ |
+</script> |
+ |
+<!-- Organize menu --> |
+<command i18n-values=".label:rename_folder" id="rename-folder-command"></command> |
+<command i18n-values=".label:edit" id="edit-command"></command> |
+<command i18n-values=".label:delete" id="delete-command"></command> |
+<command i18n-values=".label:show_in_folder" id="show-in-folder-command"></command> |
+<command i18n-values=".label:cut" id="cut-command"></command> |
+<command i18n-values=".label:copy" id="copy-command"></command> |
+<command i18n-values=".label:paste" id="paste-command"></command> |
+<command i18n-values=".label:sort" id="sort-command"></command> |
+<command i18n-values=".label:add_new_bookmark" id="add-new-bookmark-command"></command> |
+<command i18n-values=".label:new_folder" id="new-folder-command"></command> |
+ |
+<!-- Tools menu --> |
+<command i18n-values=".label:import_menu" id="import-menu-command"></command> |
+<command i18n-values=".label:export_menu" id="export-menu-command"></command> |
+ |
+<!-- open * are handled in canExecute handler --> |
+<command id="open-in-new-tab-command"></command> |
+<command id="open-in-new-window-command"></command> |
+<command id="open-incognito-window-command"></command> |
+ |
+<!-- TODO(arv): I think the commands might be better created in code? --> |
+ |
+<menu id="organize-menu"> |
+ <button command="#rename-folder-command"></button> |
+ <button command="#edit-command"></button> |
+ <button command="#delete-command"></button> |
+ <button command="#show-in-folder-command"></button> |
+ <hr> |
+ <button command="#cut-command"></button> |
+ <button command="#copy-command"></button> |
+ <button command="#paste-command"></button> |
+ <hr> |
+ <button command="#sort-command"></button> |
+ <hr> |
+ <button command="#add-new-bookmark-command"></button> |
+ <button command="#new-folder-command"></button> |
+</menu> |
+ |
+<menu id="tools-menu"> |
+ <button command="#import-menu-command"></button> |
+ <button command="#export-menu-command"></button> |
+</menu> |
+ |
+<menu id="context-menu"> |
+ <button command="#open-in-new-tab-command"></button> |
+ <button command="#open-in-new-window-command"></button> |
+ <button command="#open-incognito-window-command"></button> |
+ <hr> |
+ <button command="#rename-folder-command"></button> |
+ <button command="#edit-command"></button> |
+ <button command="#delete-command"></button> |
+ <button command="#show-in-folder-command"></button> |
+ <hr> |
+ <button command="#cut-command"></button> |
+ <button command="#copy-command"></button> |
+ <button command="#paste-command"></button> |
+ <hr> |
+ <button command="#add-new-bookmark-command"></button> |
+ <button command="#new-folder-command"></button> |
+</menu> |
+ |
+<script> |
+ |
+// Commands |
+ |
+const Command = cr.ui.Command; |
+const CommandBinding = cr.ui.CommandBinding; |
+const Menu = cr.ui.Menu; |
+const MenuButton = cr.ui.MenuButton; |
+ |
+cr.ui.decorate('menu', Menu); |
+cr.ui.decorate('button[menu]', MenuButton); |
+cr.ui.decorate('command', Command); |
+ |
+cr.ui.contextMenuHandler.addContextMenuProperty(tree); |
+list.contextMenu = $('context-menu'); |
+tree.contextMenu = $('context-menu'); |
+ |
+/** |
+ * Helper function that updates the canExecute and labels for the open like |
+ * commands. |
+ * @param {!cr.ui.CanExecuteEvent} e The event fired by the command system. |
+ * @param {!cr.ui.Command} command The command we are currently precessing. |
+ */ |
+function updateOpenCommands(e, command) { |
+ var selectedItem = e.target.selectedItem; |
+ var selectionCount; |
+ if (e.target == tree) |
+ selectionCount = selectedItem ? 1 : 0; |
+ else |
+ selectionCount = e.target.selectedItems.length; |
+ |
+ var isFolder = selectionCount == 1 && |
+ selectedItem.bookmarkNode && |
+ bmm.isFolder(selectedItem.bookmarkNode); |
+ var multiple = selectionCount != 1 || isFolder; |
+ |
+ function hasBookmarks(node) { |
+ var it = new bmm.TreeIterator(node); |
+ while (it.moveNext()) { |
+ if (!bmm.isFolder(it.current)) |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ switch (command.id) { |
+ case 'open-in-new-tab-command': |
+ command.label = localStrings.getString(multiple ? |
+ 'open_all' : 'open_in_new_tab'); |
+ break; |
+ |
+ case 'open-in-new-window-command': |
+ command.label = localStrings.getString(multiple ? |
+ 'open_all_new_window' : 'open_in_new_window'); |
+ break; |
+ case 'open-incognito-window-command': |
+ command.label = localStrings.getString(multiple ? |
+ 'open_all_incognito' : 'open_incognito'); |
+ break; |
+ } |
+ e.canExecute = selectionCount > 0 && !!selectedItem.bookmarkNode; |
+ if (isFolder && e.canExecute) { |
+ // We need to get all the bookmark items in this tree. If the tree does not |
+ // contain any non-folders we need to disable the command. |
+ var p = bmm.loadSubtree(selectedItem.bookmarkId); |
+ p.addListener(function(node) { |
+ command.disabled = !node || !hasBookmarks(node); |
+ }); |
+ } |
+} |
+ |
+/** |
+ * Calls the backend to figure out if we can paste the clipboard into the active |
+ * folder. |
+ * @param {Function=} opt_f Function to call after the state has been |
+ * updated. |
+ */ |
+function updatePasteCommand(opt_f) { |
+ function update(canPaste) { |
+ var command = $('paste-command'); |
+ command.disabled = !canPaste; |
+ if (opt_f) |
+ opt_f(); |
+ } |
+ // We cannot paste into search and recent view. |
+ if (list.isSearch() || list.isRecent()) { |
+ update(false); |
+ } else { |
+ chrome.experimental.bookmarkManager.canPaste(list.parentId, update); |
+ } |
+} |
+ |
+// We can always execute the import-menu and export-menu commands. |
+document.addEventListener('canExecute', function(e) { |
+ var command = e.command; |
+ var commandId = command.id; |
+ if (commandId == 'import-menu-command' || commandId == 'export-menu-command') { |
+ e.canExecute = true; |
+ } |
+}); |
+ |
+// Update canExecute for the commands when the list is the active element. |
+list.addEventListener('canExecute', function(e) { |
+ if (e.target != list) return; |
+ |
+ var command = e.command; |
+ var commandId = command.id; |
+ |
+ function hasSelected() { |
+ return !!e.target.selectedItem; |
+ } |
+ |
+ function hasSingleSelected() { |
+ return e.target.selectedItems.length == 1; |
+ } |
+ |
+ function isRecentOrSearch() { |
+ return list.isRecent() || list.isSearch(); |
+ } |
+ |
+ switch (commandId) { |
+ case 'rename-folder-command': |
+ // Show rename if a single folder is selected |
+ var items = e.target.selectedItems; |
+ if (items.length != 1) { |
+ e.canExecute = false; |
+ command.hidden = true; |
+ } else { |
+ var isFolder = bmm.isFolder(items[0].bookmarkNode); |
+ e.canExecute = isFolder; |
+ command.hidden = !isFolder; |
+ } |
+ break; |
+ |
+ case 'edit-command': |
+ // Show the edit command if not a folder |
+ var items = e.target.selectedItems; |
+ if (items.length != 1) { |
+ e.canExecute = false; |
+ command.hidden = false; |
+ } else { |
+ var isFolder = bmm.isFolder(items[0].bookmarkNode); |
+ e.canExecute = !isFolder; |
+ command.hidden = isFolder; |
+ } |
+ break; |
+ |
+ case 'show-in-folder-command': |
+ e.canExecute = isRecentOrSearch() && hasSingleSelected(); |
+ break; |
+ |
+ case 'delete-command': |
+ case 'cut-command': |
+ case 'copy-command': |
+ e.canExecute = hasSelected(); |
+ break; |
+ |
+ case 'paste-command': |
+ updatePasteCommand(); |
+ break; |
+ |
+ case 'sort-command': |
+ case 'add-new-bookmark-command': |
+ case 'new-folder-command': |
+ e.canExecute = !isRecentOrSearch(); |
+ break; |
+ |
+ case 'open-in-new-tab-command': |
+ case 'open-in-new-window-command': |
+ case 'open-incognito-window-command': |
+ updateOpenCommands(e, command); |
+ break; |
+ } |
+}); |
+ |
+// Update canExecute for the commands when the tree is the active element. |
+tree.addEventListener('canExecute', function(e) { |
+ if (e.target != tree) return; |
+ |
+ var command = e.command; |
+ var commandId = command.id; |
+ |
+ function hasSelected() { |
+ return !!e.target.selectedItem; |
+ } |
+ |
+ function isRecentOrSearch() { |
+ var item = e.target.selectedItem; |
+ return item == recentTreeItem || item == searchTreeItem; |
+ } |
+ |
+ function isTopLevelItem() { |
+ return e.target.selectedItem.parentNode == tree; |
+ } |
+ |
+ switch (commandId) { |
+ case 'rename-folder-command': |
+ command.hidden = false; |
+ e.canExecute = hasSelected() && !isTopLevelItem(); |
+ break; |
+ |
+ case 'edit-command': |
+ command.hidden = true; |
+ e.canExecute = false; |
+ break; |
+ |
+ case 'delete-command': |
+ case 'cut-command': |
+ case 'copy-command': |
+ e.canExecute = hasSelected() && !isTopLevelItem(); |
+ break; |
+ |
+ case 'paste-command': |
+ updatePasteCommand(); |
+ break; |
+ |
+ case 'sort-command': |
+ case 'add-new-bookmark-command': |
+ case 'new-folder-command': |
+ e.canExecute = !isRecentOrSearch(); |
+ break; |
+ |
+ case 'open-in-new-tab-command': |
+ case 'open-in-new-window-command': |
+ case 'open-incognito-window-command': |
+ updateOpenCommands(e, command); |
+ break; |
+ } |
+}); |
+ |
+/** |
+ * Update the canExecute state of the commands when the selection changes. |
+ * @param {Event} e The change event object. |
+ */ |
+function updateCommandsBasedOnSelection(e) { |
+ if (e.target == document.activeElement) { |
+ // Paste only needs to updated when the tree selection changes. |
+ var commandNames = ['copy', 'cut', 'delete', 'rename-folder', 'edit', |
+ 'add-new-bookmark', 'new-folder', 'open-in-new-tab', |
+ 'open-in-new-window', 'open-incognito-window']; |
+ |
+ if (e.target == tree) { |
+ commandNames.push('paste', 'show-in-folder', 'sort'); |
+ } |
+ |
+ commandNames.forEach(function(baseId) { |
+ $(baseId + '-command').canExecuteChange(); |
+ }); |
+ } |
+} |
+ |
+list.addEventListener('change', updateCommandsBasedOnSelection); |
+tree.addEventListener('change', updateCommandsBasedOnSelection); |
+ |
+document.addEventListener('command', function(e) { |
+ var command = e.command; |
+ var commandId = command.id; |
+ console.log(command.id, 'executed', 'on', e.target); |
+ if (commandId == 'import-menu-command') { |
+ chrome.experimental.bookmarkManager.import(); |
+ } else if (command.id == 'export-menu-command') { |
+ chrome.experimental.bookmarkManager.export(); |
+ } |
+}); |
+ |
+function handleRename(e) { |
+ var item = e.target; |
+ var bookmarkNode = item.bookmarkNode; |
+ chrome.bookmarks.update(bookmarkNode.id, {title: item.label}); |
+} |
+ |
+tree.addEventListener('rename', handleRename); |
+list.addEventListener('rename', handleRename); |
+ |
+list.addEventListener('edit', function(e) { |
+ var item = e.target; |
+ var bookmarkNode = item.bookmarkNode; |
+ var context = { |
+ title: bookmarkNode.title |
+ }; |
+ if (!bmm.isFolder(bookmarkNode)) |
+ context.url = bookmarkNode.url; |
+ |
+ if (bookmarkNode.id == 'new') { |
+ // New page |
+ context.parentId = bookmarkNode.parentId; |
+ chrome.bookmarks.create(context, function(node) { |
+ list.remove(item); |
+ list.selectedItem = bmm.listLookup[node.id]; |
+ }); |
+ } else { |
+ // Edit |
+ chrome.bookmarks.update(bookmarkNode.id, context); |
+ } |
+}); |
+ |
+list.addEventListener('canceledit', function(e) { |
+ var item = e.target; |
+ var bookmarkNode = item.bookmarkNode; |
+ if (bookmarkNode.id == 'new') { |
+ list.remove(item); |
+ list.selectionModel.leadItem = list.lastChild; |
+ list.selectionModel.anchorItem = list.lastChild; |
+ list.focus(); |
+ } |
+}); |
+ |
+/** |
+ * Navigates to the folder that the selected item is in and selects it. This is |
+ * used for the show-in-folder command. |
+ */ |
+function showInFolder() { |
+ var bookmarkId = list.selectedItem.bookmarkNode.id; |
+ var parentId = list.selectedItem.bookmarkNode.parentId; |
+ |
+ // After the list is loaded we should select the revealed item. |
+ var f = function(e) { |
+ var item = bmm.listLookup[bookmarkId]; |
+ if (item) { |
+ list.selectionModel.leadItem = item; |
+ item.selected = true; |
+ } |
+ list.removeEventListener('load', f); |
+ } |
+ list.addEventListener('load', f); |
+ var treeItem = bmm.treeLookup[parentId]; |
+ treeItem.reveal(); |
+ |
+ navigateTo(parentId); |
+} |
+ |
+/** |
+ * Opens URLs in new tab, window or incognito mode. |
+ * @param {!Array.<string>} urls The URLs to open. |
+ * @param {string} kind The kind is either 'tab', 'window', or 'incognito'. |
+ */ |
+function openUrls(urls, kind) { |
+ if (urls.length < 1) |
+ return; |
+ |
+ if (urls.length > 15) { |
+ if (!confirm(localStrings.getStringF('should_open_all', urls.length))) |
+ return; |
+ } |
+ |
+ // Fix '#124' URLs since open those in a new window does not work. We prepend |
+ // the base URL when we encounter those. |
+ var base = window.location.href.split('#')[0]; |
+ urls = urls.map(function(url) { |
+ return url[0] == '#' ? base + url : url; |
+ }); |
+ |
+ // Incognito mode is not yet supported by the extensions APIs. |
+ // http://code.google.com/p/chromium/issues/detail?id=12658 |
+ if (kind == 'window') { |
+ chrome.windows.create({url: urls[0]}, function(window) { |
+ urls.forEach(function(url, i) { |
+ if (i > 0) |
+ chrome.tabs.create({url: url, windowId: window.id, selected: false}); |
+ }); |
+ }); |
+ } else if (kind == 'tab') { |
+ urls.forEach(function(url, i) { |
+ chrome.tabs.create({url: url, selected: !i}); |
+ }); |
+ } else { |
+ window.location.href = urls[0]; |
+ } |
+} |
+ |
+/** |
+ * Returns the selected bookmark nodes of the active element. Only call this |
+ * if the list or the tree is focused. |
+ * @return {!Array} Array of bookmark nodes. |
+ */ |
+function getSelectedBookmarkNodes() { |
+ if (document.activeElement == list) { |
+ return list.selectedItems.map(function(item) { |
+ return item.bookmarkNode; |
+ }); |
+ } else if (document.activeElement == tree) { |
+ return [tree.selectedItem.bookmarkNode]; |
+ } else { |
+ throw Error('getSelectedBookmarkNodes called when wrong element focused.'); |
+ } |
+} |
+ |
+/** |
+ * @return {!Array.<string>} An array of the selected bookmark IDs. |
+ */ |
+function getSelectedBookmarkIds() { |
+ return getSelectedBookmarkNodes().map(function(node) { |
+ return node.id; |
+ }); |
+} |
+ |
+/** |
+ * Opens the selected bookmarks. |
+ */ |
+function openBookmarks(kind) { |
+ // If we have selected any folders we need to find all items recursively. |
+ // We can do several async calls to getChildren but instead we do a single |
+ // call to getTree and only add the subtrees of the selected items. |
+ |
+ var urls = []; |
+ var idMap = {}; |
+ |
+ // Traverses the tree until it finds a node tree that should be added. Then |
+ // we switch over to use addNodes. We could merge these two functions into |
+ // one but that would make the code less readable. |
+ function traverseNodes(node) { |
+ // This is not using the iterator since it uses breadth first search. |
+ if (node.id in idMap) { |
+ addNodes(node); |
+ } else if (node.children) { |
+ for (var i = 0; i < node.children.length; i++) { |
+ traverseNodes(node.children[i]); |
+ } |
+ } |
+ } |
+ |
+ // Adds the node and all the descendants |
+ function addNodes(node) { |
+ var it = new bmm.TreeIterator(node); |
+ while (it.moveNext()) { |
+ var n = it.current; |
+ if (!bmm.isFolder(n)) |
+ urls.push(n.url); |
+ } |
+ } |
+ |
+ var nodes = getSelectedBookmarkNodes(); |
+ |
+ // Create a map for simpler lookup later. |
+ nodes.forEach(function(node) { |
+ idMap[node.id] = true; |
+ }); |
+ var p = bmm.loadTree(); |
+ p.addListener(function(node) { |
+ traverseNodes(node); |
+ openUrls(urls, kind); |
+ }); |
+} |
+ |
+/** |
+ * Deletes the selected bookmarks. |
+ */ |
+function deleteBookmarks() { |
+ getSelectedBookmarkIds().forEach(function(id) { |
+ chrome.bookmarks.removeTree(id); |
+ }); |
+} |
+ |
+/** |
+ * Callback for the new folder command. This creates a new folder and starts |
+ * a rename of it. |
+ */ |
+function newFolder() { |
+ var parentId = list.parentId; |
+ var isTree = document.activeElement == tree; |
+ chrome.bookmarks.create({ |
+ title: localStrings.getString('new_folder_name'), |
+ parentId: parentId |
+ }, function(newNode) { |
+ // We need to do this in a timeout to be able to focus the newly created |
+ // item. |
+ setTimeout(function() { |
+ var newItem = isTree ? bmm.treeLookup[newNode.id] : |
+ bmm.listLookup[newNode.id]; |
+ document.activeElement.selectedItem = newItem; |
+ newItem.editing = true; |
+ }); |
+ }); |
+} |
+ |
+/** |
+ * Adds a page to the current folder. This is called by the |
+ * add-new-bookmark-command handler. |
+ */ |
+function addPage() { |
+ var parentId = list.parentId; |
+ var fakeNode = { |
+ title: '', |
+ url: '', |
+ parentId: parentId, |
+ id: 'new' |
+ }; |
+ var newListItem = bmm.createListItem(fakeNode, false); |
+ list.add(newListItem); |
+ list.selectedItem = newListItem; |
+ newListItem.editing = true; |
+} |
+ |
+/** |
+ * Handler for the command event. This is used both for the tree and the list. |
+ * @param {!Event} e The event object. |
+ */ |
+function handleCommand(e) { |
+ var command = e.command; |
+ var commandId = command.id; |
+ switch (commandId) { |
+ case 'show-in-folder-command': |
+ showInFolder(); |
+ break; |
+ case 'open-in-new-tab-command': |
+ openBookmarks('tab'); |
+ break; |
+ case 'open-in-new-window-command': |
+ openBookmarks('window'); |
+ break; |
+ case 'open-in-new-incognito-command': |
+ openBookmarks('incognito'); |
+ break; |
+ case 'delete-command': |
+ deleteBookmarks(); |
+ break; |
+ case 'copy-command': |
+ chrome.experimental.bookmarkManager.copy(getSelectedBookmarkIds()); |
+ break; |
+ case 'cut-command': |
+ chrome.experimental.bookmarkManager.cut(getSelectedBookmarkIds()); |
+ break; |
+ case 'paste-command': |
+ chrome.experimental.bookmarkManager.paste(list.parentId); |
+ break; |
+ case 'sort-command': |
+ chrome.experimental.bookmarkManager.sortChildren(list.parentId); |
+ break; |
+ case 'rename-folder-command': |
+ case 'edit-command': |
+ document.activeElement.selectedItem.editing = true; |
+ break; |
+ case 'new-folder-command': |
+ newFolder(); |
+ break; |
+ case 'add-new-bookmark-command': |
+ addPage(); |
+ break; |
+ } |
+} |
+ |
+// TODO(arv): Move shortcut to HTML? |
+ |
+// Meta+Backspace on Mac, Del on other platforms. |
+$('delete-command').shortcut = cr.isMac ? 'U+0008-meta' : 'U+007F'; |
+ |
+list.addEventListener('command', handleCommand); |
+tree.addEventListener('command', handleCommand); |
+ |
+// Execute the copy, cut and paste commands when those events are dispatched by |
+// the browser. This allows us to rely on the browser to handle the keyboard |
+// shortcuts for these commands. |
+(function() { |
+ function handle(id) { |
+ return function(e) { |
+ var command = $(id); |
+ if (!command.disabled) { |
+ command.execute(); |
+ e.preventDefault(); // Prevent the system beep |
+ } |
+ }; |
+ } |
+ |
+ // Listen to copy, cut and paste events and execute the associated commands. |
+ document.addEventListener('copy', handle('copy-command')); |
+ document.addEventListener('cut', handle('cut-command')); |
+ |
+ var pasteHandler = handle('paste-command'); |
+ document.addEventListener('paste', function(e) { |
+ // Paste is a bit special since we need to do an async call to see if we can |
+ // paste because the paste command might not be up to date. |
+ updatePasteCommand(pasteHandler); |
+ }); |
+})(); |
+ |
+/** |
+ * The local strings object which is used to do the translation. |
+ * @type {!LocalStrings} |
+ */ |
+var localStrings = new LocalStrings; |
+ |
+// Get the localized strings from the backend. |
+chrome.experimental.bookmarkManager.getStrings(function setTemplateData(data) { |
+ // The strings may contain & which we need to strip. |
+ for (var key in data) { |
+ data[key] = data[key].replace(/&/, ''); |
+ } |
+ |
+ localStrings.templateData = data; |
+ i18nTemplate.process(document, data); |
+}); |
+ |
+</script> |
+ |
+<div id="drop-overlay"></div> |
+ |
+</body> |
+</html> |