| Index: chrome/tools/test/reference_build/chrome_linux/resources/bookmark_manager/main.html
|
| ===================================================================
|
| --- chrome/tools/test/reference_build/chrome_linux/resources/bookmark_manager/main.html (revision 0)
|
| +++ chrome/tools/test/reference_build/chrome_linux/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>
|
|
|