| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 (function() { | |
| 6 /** @const */ var BookmarkList = bmm.BookmarkList; | |
| 7 /** @const */ var BookmarkTree = bmm.BookmarkTree; | |
| 8 /** @const */ var Command = cr.ui.Command; | |
| 9 /** @const */ var CommandBinding = cr.ui.CommandBinding; | |
| 10 /** @const */ var LinkKind = cr.LinkKind; | |
| 11 /** @const */ var ListItem = cr.ui.ListItem; | |
| 12 /** @const */ var Menu = cr.ui.Menu; | |
| 13 /** @const */ var MenuButton = cr.ui.MenuButton; | |
| 14 /** @const */ var Promise = cr.Promise; | |
| 15 /** @const */ var Splitter = cr.ui.Splitter; | |
| 16 /** @const */ var TreeItem = cr.ui.TreeItem; | |
| 17 | |
| 18 /** | |
| 19 * An array containing the BookmarkTreeNodes that were deleted in the last | |
| 20 * deletion action. This is used for implementing undo. | |
| 21 * @type {Array.<BookmarkTreeNode>} | |
| 22 */ | |
| 23 var lastDeletedNodes; | |
| 24 | |
| 25 /** | |
| 26 * | |
| 27 * Holds the last DOMTimeStamp when mouse pointer hovers on folder in tree | |
| 28 * view. Zero means pointer doesn't hover on folder. | |
| 29 * @type {number} | |
| 30 */ | |
| 31 var lastHoverOnFolderTimeStamp = 0; | |
| 32 | |
| 33 /** | |
| 34 * Holds a function that will undo that last action, if global undo is enabled. | |
| 35 * @type {Function} | |
| 36 */ | |
| 37 var performGlobalUndo; | |
| 38 | |
| 39 /** | |
| 40 * Holds a link controller singleton. Use getLinkController() rarther than | |
| 41 * accessing this variabie. | |
| 42 * @type {LinkController} | |
| 43 */ | |
| 44 var linkController; | |
| 45 | |
| 46 /** | |
| 47 * New Windows are not allowed in Windows 8 metro mode. | |
| 48 */ | |
| 49 var canOpenNewWindows = true; | |
| 50 | |
| 51 /** | |
| 52 * Incognito mode availability can take the following values: , | |
| 53 * - 'enabled' for when both normal and incognito modes are available; | |
| 54 * - 'disabled' for when incognito mode is disabled; | |
| 55 * - 'forced' for when incognito mode is forced (normal mode is unavailable). | |
| 56 */ | |
| 57 var incognitoModeAvailability = 'enabled'; | |
| 58 | |
| 59 /** | |
| 60 * Whether bookmarks can be modified. | |
| 61 * @type {boolean} | |
| 62 */ | |
| 63 var canEdit = true; | |
| 64 | |
| 65 /** | |
| 66 * @type {TreeItem} | |
| 67 * @const | |
| 68 */ | |
| 69 var searchTreeItem = new TreeItem({ | |
| 70 bookmarkId: 'q=' | |
| 71 }); | |
| 72 | |
| 73 /** | |
| 74 * Command shortcut mapping. | |
| 75 * @const | |
| 76 */ | |
| 77 var commandShortcutMap = cr.isMac ? { | |
| 78 'edit': 'Enter', | |
| 79 // On Mac we also allow Meta+Backspace. | |
| 80 'delete': 'U+007F U+0008 Meta-U+0008', | |
| 81 'open-in-background-tab': 'Meta-Enter', | |
| 82 'open-in-new-tab': 'Shift-Meta-Enter', | |
| 83 'open-in-same-window': 'Meta-Down', | |
| 84 'open-in-new-window': 'Shift-Enter', | |
| 85 'rename-folder': 'Enter', | |
| 86 // Global undo is Command-Z. It is not in any menu. | |
| 87 'undo': 'Meta-U+005A', | |
| 88 } : { | |
| 89 'edit': 'F2', | |
| 90 'delete': 'U+007F', | |
| 91 'open-in-background-tab': 'Ctrl-Enter', | |
| 92 'open-in-new-tab': 'Shift-Ctrl-Enter', | |
| 93 'open-in-same-window': 'Enter', | |
| 94 'open-in-new-window': 'Shift-Enter', | |
| 95 'rename-folder': 'F2', | |
| 96 // Global undo is Ctrl-Z. It is not in any menu. | |
| 97 'undo': 'Ctrl-U+005A', | |
| 98 }; | |
| 99 | |
| 100 /** | |
| 101 * Mapping for folder id to suffix of UMA. These names will be appeared | |
| 102 * after "BookmarkManager_NavigateTo_" in UMA dashboard. | |
| 103 * @const | |
| 104 */ | |
| 105 var folderMetricsNameMap = { | |
| 106 '1': 'BookmarkBar', | |
| 107 '2': 'Other', | |
| 108 '3': 'Mobile', | |
| 109 'q=': 'Search', | |
| 110 'subfolder': 'SubFolder', | |
| 111 }; | |
| 112 | |
| 113 /** | |
| 114 * Adds an event listener to a node that will remove itself after firing once. | |
| 115 * @param {!Element} node The DOM node to add the listener to. | |
| 116 * @param {string} name The name of the event listener to add to. | |
| 117 * @param {function(Event)} handler Function called when the event fires. | |
| 118 */ | |
| 119 function addOneShotEventListener(node, name, handler) { | |
| 120 var f = function(e) { | |
| 121 handler(e); | |
| 122 node.removeEventListener(name, f); | |
| 123 }; | |
| 124 node.addEventListener(name, f); | |
| 125 } | |
| 126 | |
| 127 // Get the localized strings from the backend via bookmakrManagerPrivate API. | |
| 128 function loadLocalizedStrings(data) { | |
| 129 // The strings may contain & which we need to strip. | |
| 130 for (var key in data) { | |
| 131 data[key] = data[key].replace(/&/, ''); | |
| 132 } | |
| 133 | |
| 134 loadTimeData.data = data; | |
| 135 i18nTemplate.process(document, loadTimeData); | |
| 136 | |
| 137 searchTreeItem.label = loadTimeData.getString('search'); | |
| 138 searchTreeItem.icon = isRTL() ? 'images/bookmark_manager_search_rtl.png' : | |
| 139 'images/bookmark_manager_search.png'; | |
| 140 } | |
| 141 | |
| 142 /** | |
| 143 * Updates the location hash to reflect the current state of the application. | |
| 144 */ | |
| 145 function updateHash() { | |
| 146 window.location.hash = tree.selectedItem.bookmarkId; | |
| 147 } | |
| 148 | |
| 149 /** | |
| 150 * Navigates to a bookmark ID. | |
| 151 * @param {string} id The ID to navigate to. | |
| 152 * @param {function()} callback Function called when list view loaded or | |
| 153 * displayed specified folder. | |
| 154 */ | |
| 155 function navigateTo(id, callback) { | |
| 156 if (list.parentId == id) { | |
| 157 callback(); | |
| 158 return; | |
| 159 } | |
| 160 | |
| 161 var metricsId = folderMetricsNameMap[id.replace(/^q=.*/, 'q=')] || | |
| 162 folderMetricsNameMap['subfolder']; | |
| 163 chrome.metricsPrivate.recordUserAction( | |
| 164 'BookmarkManager_NavigateTo_' + metricsId); | |
| 165 | |
| 166 addOneShotEventListener(list, 'load', callback); | |
| 167 updateParentId(id); | |
| 168 } | |
| 169 | |
| 170 /** | |
| 171 * Updates the parent ID of the bookmark list and selects the correct tree item. | |
| 172 * @param {string} id The id. | |
| 173 */ | |
| 174 function updateParentId(id) { | |
| 175 // Setting list.parentId fires 'load' event. | |
| 176 list.parentId = id; | |
| 177 | |
| 178 // When tree.selectedItem changed, tree view calls navigatTo() then it | |
| 179 // calls updateHash() when list view displayed specified folder. | |
| 180 tree.selectedItem = bmm.treeLookup[id] || tree.selectedItem; | |
| 181 } | |
| 182 | |
| 183 // Process the location hash. This is called by onhashchange and when the page | |
| 184 // is first loaded. | |
| 185 function processHash() { | |
| 186 var id = window.location.hash.slice(1); | |
| 187 if (!id) { | |
| 188 // If we do not have a hash, select first item in the tree. | |
| 189 id = tree.items[0].bookmarkId; | |
| 190 } | |
| 191 | |
| 192 var valid = false; | |
| 193 if (/^e=/.test(id)) { | |
| 194 id = id.slice(2); | |
| 195 | |
| 196 // If hash contains e=, edit the item specified. | |
| 197 chrome.bookmarks.get(id, function(bookmarkNodes) { | |
| 198 // Verify the node to edit is a valid node. | |
| 199 if (!bookmarkNodes || bookmarkNodes.length != 1) | |
| 200 return; | |
| 201 var bookmarkNode = bookmarkNodes[0]; | |
| 202 | |
| 203 // After the list reloads, edit the desired bookmark. | |
| 204 var editBookmark = function(e) { | |
| 205 var index = list.dataModel.findIndexById(bookmarkNode.id); | |
| 206 if (index != -1) { | |
| 207 var sm = list.selectionModel; | |
| 208 sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index; | |
| 209 scrollIntoViewAndMakeEditable(index); | |
| 210 } | |
| 211 }; | |
| 212 | |
| 213 navigateTo(bookmarkNode.parentId, editBookmark); | |
| 214 }); | |
| 215 | |
| 216 // We handle the two cases of navigating to the bookmark to be edited | |
| 217 // above. Don't run the standard navigation code below. | |
| 218 return; | |
| 219 } else if (/^q=/.test(id)) { | |
| 220 // In case we got a search hash, update the text input and the | |
| 221 // bmm.treeLookup to use the new id. | |
| 222 setSearch(id.slice(2)); | |
| 223 valid = true; | |
| 224 } | |
| 225 | |
| 226 // Navigate to bookmark 'id' (which may be a query of the form q=query). | |
| 227 if (valid) { | |
| 228 updateParentId(id); | |
| 229 } else { | |
| 230 // We need to verify that this is a correct ID. | |
| 231 chrome.bookmarks.get(id, function(items) { | |
| 232 if (items && items.length == 1) | |
| 233 updateParentId(id); | |
| 234 }); | |
| 235 } | |
| 236 } | |
| 237 | |
| 238 // Activate is handled by the open-in-same-window-command. | |
| 239 function handleDoubleClickForList(e) { | |
| 240 if (e.button == 0) | |
| 241 $('open-in-same-window-command').execute(); | |
| 242 } | |
| 243 | |
| 244 // The list dispatches an event when the user clicks on the URL or the Show in | |
| 245 // folder part. | |
| 246 function handleUrlClickedForList(e) { | |
| 247 getLinkController().openUrlFromEvent(e.url, e.originalEvent); | |
| 248 chrome.bookmarkManagerPrivate.recordLaunch(); | |
| 249 } | |
| 250 | |
| 251 function handleSearch(e) { | |
| 252 setSearch(this.value); | |
| 253 } | |
| 254 | |
| 255 /** | |
| 256 * Navigates to the search results for the search text. | |
| 257 * @param {string} searchText The text to search for. | |
| 258 */ | |
| 259 function setSearch(searchText) { | |
| 260 if (searchText) { | |
| 261 // Only update search item if we have a search term. We never want the | |
| 262 // search item to be for an empty search. | |
| 263 delete bmm.treeLookup[searchTreeItem.bookmarkId]; | |
| 264 var id = searchTreeItem.bookmarkId = 'q=' + searchText; | |
| 265 bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem; | |
| 266 } | |
| 267 | |
| 268 var input = $('term'); | |
| 269 // Do not update the input if the user is actively using the text input. | |
| 270 if (document.activeElement != input) | |
| 271 input.value = searchText; | |
| 272 | |
| 273 if (searchText) { | |
| 274 tree.add(searchTreeItem); | |
| 275 tree.selectedItem = searchTreeItem; | |
| 276 } else { | |
| 277 // Go "home". | |
| 278 tree.selectedItem = tree.items[0]; | |
| 279 id = tree.selectedItem.bookmarkId; | |
| 280 } | |
| 281 | |
| 282 // Navigate now and update hash immediately. | |
| 283 navigateTo(id, updateHash); | |
| 284 } | |
| 285 | |
| 286 // Handle the logo button UI. | |
| 287 // When the user clicks the button we should navigate "home" and focus the list. | |
| 288 function handleClickOnLogoButton(e) { | |
| 289 setSearch(''); | |
| 290 $('list').focus(); | |
| 291 } | |
| 292 | |
| 293 /** | |
| 294 * This returns the user visible path to the folder where the bookmark is | |
| 295 * located. | |
| 296 * @param {number} parentId The ID of the parent folder. | |
| 297 * @return {string} The path to the the bookmark, | |
| 298 */ | |
| 299 function getFolder(parentId) { | |
| 300 var parentNode = tree.getBookmarkNodeById(parentId); | |
| 301 if (parentNode) { | |
| 302 var s = parentNode.title; | |
| 303 if (parentNode.parentId != bmm.ROOT_ID) { | |
| 304 return getFolder(parentNode.parentId) + '/' + s; | |
| 305 } | |
| 306 return s; | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 function handleLoadForTree(e) { | |
| 311 processHash(); | |
| 312 } | |
| 313 | |
| 314 function getAllUrls(nodes) { | |
| 315 var urls = []; | |
| 316 | |
| 317 // Adds the node and all its direct children. | |
| 318 function addNodes(node) { | |
| 319 if (node.id == 'new') | |
| 320 return; | |
| 321 | |
| 322 if (node.children) { | |
| 323 node.children.forEach(function(child) { | |
| 324 if (!bmm.isFolder(child)) | |
| 325 urls.push(child.url); | |
| 326 }); | |
| 327 } else { | |
| 328 urls.push(node.url); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 // Get a future promise for the nodes. | |
| 333 var promises = nodes.map(function(node) { | |
| 334 if (bmm.isFolder(node)) | |
| 335 return bmm.loadSubtree(node.id); | |
| 336 // Not a folder so we already have all the data we need. | |
| 337 return new Promise(node); | |
| 338 }); | |
| 339 | |
| 340 var urlsPromise = new Promise(); | |
| 341 | |
| 342 var p = Promise.all.apply(null, promises); | |
| 343 p.addListener(function(nodes) { | |
| 344 nodes.forEach(function(node) { | |
| 345 addNodes(node); | |
| 346 }); | |
| 347 urlsPromise.value = urls; | |
| 348 }); | |
| 349 | |
| 350 return urlsPromise; | |
| 351 } | |
| 352 | |
| 353 /** | |
| 354 * Returns the nodes (non recursive) to use for the open commands. | |
| 355 * @param {HTMLElement} target . | |
| 356 * @return {Array.<BookmarkTreeNode>} . | |
| 357 */ | |
| 358 function getNodesForOpen(target) { | |
| 359 if (target == tree) { | |
| 360 var folderItem = tree.selectedItem; | |
| 361 return folderItem == searchTreeItem ? | |
| 362 list.dataModel.slice() : tree.selectedFolders; | |
| 363 } | |
| 364 var items = list.selectedItems; | |
| 365 return items.length ? items : list.dataModel.slice(); | |
| 366 } | |
| 367 | |
| 368 /** | |
| 369 * Returns a promise that will contain all URLs of all the selected bookmarks | |
| 370 * and the nested bookmarks for use with the open commands. | |
| 371 * @param {HTMLElement} target The target list or tree. | |
| 372 * @return {Promise} . | |
| 373 */ | |
| 374 function getUrlsForOpenCommands(target) { | |
| 375 return getAllUrls(getNodesForOpen(target)); | |
| 376 } | |
| 377 | |
| 378 function notNewNode(node) { | |
| 379 return node.id != 'new'; | |
| 380 } | |
| 381 | |
| 382 /** | |
| 383 * Helper function that updates the canExecute and labels for the open-like | |
| 384 * commands. | |
| 385 * @param {!cr.ui.CanExecuteEvent} e The event fired by the command system. | |
| 386 * @param {!cr.ui.Command} command The command we are currently processing. | |
| 387 * @param {string} singularId The string id of singular form of the menu label. | |
| 388 * @param {string} pluralId The string id of menu label if the singular form is | |
| 389 not used. | |
| 390 * @param {boolean} commandDisabled Whether the menu item should be disabled | |
| 391 no matter what bookmarks are selected. | |
| 392 */ | |
| 393 function updateOpenCommand(e, command, singularId, pluralId, commandDisabled) { | |
| 394 if (singularId) { | |
| 395 // The command label reflects the selection which might not reflect | |
| 396 // how many bookmarks will be opened. For example if you right click an | |
| 397 // empty area in a folder with 1 bookmark the text should still say "all". | |
| 398 var selectedNodes = getSelectedBookmarkNodes(e.target).filter(notNewNode); | |
| 399 var singular = selectedNodes.length == 1 && !bmm.isFolder(selectedNodes[0]); | |
| 400 command.label = loadTimeData.getString(singular ? singularId : pluralId); | |
| 401 } | |
| 402 | |
| 403 if (commandDisabled) { | |
| 404 command.disabled = true; | |
| 405 e.canExecute = false; | |
| 406 return; | |
| 407 } | |
| 408 | |
| 409 getUrlsForOpenCommands(e.target).addListener(function(urls) { | |
| 410 var disabled = !urls.length; | |
| 411 command.disabled = disabled; | |
| 412 e.canExecute = !disabled; | |
| 413 }); | |
| 414 } | |
| 415 | |
| 416 /** | |
| 417 * Calls the backend to figure out if we can paste the clipboard into the active | |
| 418 * folder. | |
| 419 * @param {Function=} opt_f Function to call after the state has been updated. | |
| 420 */ | |
| 421 function updatePasteCommand(opt_f) { | |
| 422 function update(canPaste) { | |
| 423 var organizeMenuCommand = $('paste-from-organize-menu-command'); | |
| 424 var contextMenuCommand = $('paste-from-context-menu-command'); | |
| 425 organizeMenuCommand.disabled = !canPaste; | |
| 426 contextMenuCommand.disabled = !canPaste; | |
| 427 if (opt_f) | |
| 428 opt_f(); | |
| 429 } | |
| 430 // We cannot paste into search view. | |
| 431 if (list.isSearch()) | |
| 432 update(false); | |
| 433 else | |
| 434 chrome.bookmarkManagerPrivate.canPaste(list.parentId, update); | |
| 435 } | |
| 436 | |
| 437 function handleCanExecuteForDocument(e) { | |
| 438 var command = e.command; | |
| 439 switch (command.id) { | |
| 440 case 'import-menu-command': | |
| 441 e.canExecute = canEdit; | |
| 442 break; | |
| 443 case 'export-menu-command': | |
| 444 // We can always execute the export-menu command. | |
| 445 e.canExecute = true; | |
| 446 break; | |
| 447 case 'sort-command': | |
| 448 e.canExecute = !list.isSearch() && list.dataModel.length > 1; | |
| 449 break; | |
| 450 case 'undo-command': | |
| 451 // The global undo command has no visible UI, so always enable it, and | |
| 452 // just make it a no-op if undo is not possible. | |
| 453 e.canExecute = true; | |
| 454 break; | |
| 455 default: | |
| 456 canExecuteForList(e); | |
| 457 break; | |
| 458 } | |
| 459 } | |
| 460 | |
| 461 /** | |
| 462 * Helper function for handling canExecute for the list and the tree. | |
| 463 * @param {!Event} e Can execute event object. | |
| 464 * @param {boolean} isSearch Whether the user is trying to do a command on | |
| 465 * search. | |
| 466 */ | |
| 467 function canExecuteShared(e, isSearch) { | |
| 468 var command = e.command; | |
| 469 var commandId = command.id; | |
| 470 switch (commandId) { | |
| 471 case 'paste-from-organize-menu-command': | |
| 472 case 'paste-from-context-menu-command': | |
| 473 updatePasteCommand(); | |
| 474 break; | |
| 475 | |
| 476 case 'add-new-bookmark-command': | |
| 477 case 'new-folder-command': | |
| 478 e.canExecute = !isSearch && canEdit; | |
| 479 break; | |
| 480 | |
| 481 case 'open-in-new-tab-command': | |
| 482 updateOpenCommand(e, command, 'open_in_new_tab', 'open_all', false); | |
| 483 break; | |
| 484 case 'open-in-background-tab-command': | |
| 485 updateOpenCommand(e, command, '', '', false); | |
| 486 break; | |
| 487 case 'open-in-new-window-command': | |
| 488 updateOpenCommand(e, command, | |
| 489 'open_in_new_window', 'open_all_new_window', | |
| 490 // Disabled when incognito is forced. | |
| 491 incognitoModeAvailability == 'forced' || !canOpenNewWindows); | |
| 492 break; | |
| 493 case 'open-incognito-window-command': | |
| 494 updateOpenCommand(e, command, | |
| 495 'open_incognito', 'open_all_incognito', | |
| 496 // Not available when incognito is disabled. | |
| 497 incognitoModeAvailability == 'disabled'); | |
| 498 break; | |
| 499 | |
| 500 case 'undo-delete-command': | |
| 501 e.canExecute = !!lastDeletedNodes; | |
| 502 break; | |
| 503 } | |
| 504 } | |
| 505 | |
| 506 /** | |
| 507 * Helper function for handling canExecute for the list and document. | |
| 508 * @param {!Event} e Can execute event object. | |
| 509 */ | |
| 510 function canExecuteForList(e) { | |
| 511 var command = e.command; | |
| 512 var commandId = command.id; | |
| 513 | |
| 514 function hasSelected() { | |
| 515 return !!list.selectedItem; | |
| 516 } | |
| 517 | |
| 518 function hasSingleSelected() { | |
| 519 return list.selectedItems.length == 1; | |
| 520 } | |
| 521 | |
| 522 function canCopyItem(item) { | |
| 523 return item.id != 'new'; | |
| 524 } | |
| 525 | |
| 526 function canCopyItems() { | |
| 527 var selectedItems = list.selectedItems; | |
| 528 return selectedItems && selectedItems.some(canCopyItem); | |
| 529 } | |
| 530 | |
| 531 function isSearch() { | |
| 532 return list.isSearch(); | |
| 533 } | |
| 534 | |
| 535 switch (commandId) { | |
| 536 case 'rename-folder-command': | |
| 537 // Show rename if a single folder is selected. | |
| 538 var items = list.selectedItems; | |
| 539 if (items.length != 1) { | |
| 540 e.canExecute = false; | |
| 541 command.hidden = true; | |
| 542 } else { | |
| 543 var isFolder = bmm.isFolder(items[0]); | |
| 544 e.canExecute = isFolder && canEdit; | |
| 545 command.hidden = !isFolder; | |
| 546 } | |
| 547 break; | |
| 548 | |
| 549 case 'edit-command': | |
| 550 // Show the edit command if not a folder. | |
| 551 var items = list.selectedItems; | |
| 552 if (items.length != 1) { | |
| 553 e.canExecute = false; | |
| 554 command.hidden = false; | |
| 555 } else { | |
| 556 var isFolder = bmm.isFolder(items[0]); | |
| 557 e.canExecute = !isFolder && canEdit; | |
| 558 command.hidden = isFolder; | |
| 559 } | |
| 560 break; | |
| 561 | |
| 562 case 'show-in-folder-command': | |
| 563 e.canExecute = isSearch() && hasSingleSelected(); | |
| 564 break; | |
| 565 | |
| 566 case 'delete-command': | |
| 567 case 'cut-command': | |
| 568 e.canExecute = canCopyItems() && canEdit; | |
| 569 break; | |
| 570 | |
| 571 case 'copy-command': | |
| 572 e.canExecute = canCopyItems(); | |
| 573 break; | |
| 574 | |
| 575 case 'open-in-same-window-command': | |
| 576 e.canExecute = hasSelected(); | |
| 577 break; | |
| 578 | |
| 579 default: | |
| 580 canExecuteShared(e, isSearch()); | |
| 581 } | |
| 582 } | |
| 583 | |
| 584 // Update canExecute for the commands when the list is the active element. | |
| 585 function handleCanExecuteForList(e) { | |
| 586 if (e.target != list) return; | |
| 587 canExecuteForList(e); | |
| 588 } | |
| 589 | |
| 590 // Update canExecute for the commands when the tree is the active element. | |
| 591 function handleCanExecuteForTree(e) { | |
| 592 if (e.target != tree) return; | |
| 593 | |
| 594 var command = e.command; | |
| 595 var commandId = command.id; | |
| 596 | |
| 597 function hasSelected() { | |
| 598 return !!e.target.selectedItem; | |
| 599 } | |
| 600 | |
| 601 function isSearch() { | |
| 602 var item = e.target.selectedItem; | |
| 603 return item == searchTreeItem; | |
| 604 } | |
| 605 | |
| 606 function isTopLevelItem() { | |
| 607 return e.target.selectedItem.parentNode == tree; | |
| 608 } | |
| 609 | |
| 610 switch (commandId) { | |
| 611 case 'rename-folder-command': | |
| 612 command.hidden = false; | |
| 613 e.canExecute = hasSelected() && !isTopLevelItem() && canEdit; | |
| 614 break; | |
| 615 | |
| 616 case 'edit-command': | |
| 617 command.hidden = true; | |
| 618 e.canExecute = false; | |
| 619 break; | |
| 620 | |
| 621 case 'delete-command': | |
| 622 case 'cut-command': | |
| 623 e.canExecute = hasSelected() && !isTopLevelItem() && canEdit; | |
| 624 break; | |
| 625 | |
| 626 case 'copy-command': | |
| 627 e.canExecute = hasSelected() && !isTopLevelItem(); | |
| 628 break; | |
| 629 | |
| 630 default: | |
| 631 canExecuteShared(e, isSearch()); | |
| 632 } | |
| 633 } | |
| 634 | |
| 635 /** | |
| 636 * Update the canExecute state of the commands when the selection changes. | |
| 637 * @param {Event} e The change event object. | |
| 638 */ | |
| 639 function updateCommandsBasedOnSelection(e) { | |
| 640 if (e.target == document.activeElement) { | |
| 641 // Paste only needs to be updated when the tree selection changes. | |
| 642 var commandNames = ['copy', 'cut', 'delete', 'rename-folder', 'edit', | |
| 643 'add-new-bookmark', 'new-folder', 'open-in-new-tab', | |
| 644 'open-in-background-tab', 'open-in-new-window', 'open-incognito-window', | |
| 645 'open-in-same-window', 'show-in-folder']; | |
| 646 | |
| 647 if (e.target == tree) { | |
| 648 commandNames.push('paste-from-context-menu', 'paste-from-organize-menu', | |
| 649 'sort'); | |
| 650 } | |
| 651 | |
| 652 commandNames.forEach(function(baseId) { | |
| 653 $(baseId + '-command').canExecuteChange(); | |
| 654 }); | |
| 655 } | |
| 656 } | |
| 657 | |
| 658 function updateEditingCommands() { | |
| 659 var editingCommands = ['cut', 'delete', 'rename-folder', 'edit', | |
| 660 'add-new-bookmark', 'new-folder', 'sort', | |
| 661 'paste-from-context-menu', 'paste-from-organize-menu']; | |
| 662 | |
| 663 chrome.bookmarkManagerPrivate.canEdit(function(result) { | |
| 664 if (result != canEdit) { | |
| 665 canEdit = result; | |
| 666 editingCommands.forEach(function(baseId) { | |
| 667 $(baseId + '-command').canExecuteChange(); | |
| 668 }); | |
| 669 } | |
| 670 }); | |
| 671 } | |
| 672 | |
| 673 function handleChangeForTree(e) { | |
| 674 updateCommandsBasedOnSelection(e); | |
| 675 navigateTo(tree.selectedItem.bookmarkId, updateHash); | |
| 676 } | |
| 677 | |
| 678 function handleOrganizeButtonClick(e) { | |
| 679 updateEditingCommands(); | |
| 680 $('add-new-bookmark-command').canExecuteChange(); | |
| 681 $('new-folder-command').canExecuteChange(); | |
| 682 $('sort-command').canExecuteChange(); | |
| 683 } | |
| 684 | |
| 685 function handleRename(e) { | |
| 686 var item = e.target; | |
| 687 var bookmarkNode = item.bookmarkNode; | |
| 688 chrome.bookmarks.update(bookmarkNode.id, {title: item.label}); | |
| 689 performGlobalUndo = null; // This can't be undone, so disable global undo. | |
| 690 } | |
| 691 | |
| 692 function handleEdit(e) { | |
| 693 var item = e.target; | |
| 694 var bookmarkNode = item.bookmarkNode; | |
| 695 var context = { | |
| 696 title: bookmarkNode.title | |
| 697 }; | |
| 698 if (!bmm.isFolder(bookmarkNode)) | |
| 699 context.url = bookmarkNode.url; | |
| 700 | |
| 701 if (bookmarkNode.id == 'new') { | |
| 702 selectItemsAfterUserAction(list); | |
| 703 | |
| 704 // New page | |
| 705 context.parentId = bookmarkNode.parentId; | |
| 706 chrome.bookmarks.create(context, function(node) { | |
| 707 // A new node was created and will get added to the list due to the | |
| 708 // handler. | |
| 709 var dataModel = list.dataModel; | |
| 710 var index = dataModel.indexOf(bookmarkNode); | |
| 711 dataModel.splice(index, 1); | |
| 712 | |
| 713 // Select new item. | |
| 714 var newIndex = dataModel.findIndexById(node.id); | |
| 715 if (newIndex != -1) { | |
| 716 var sm = list.selectionModel; | |
| 717 list.scrollIndexIntoView(newIndex); | |
| 718 sm.leadIndex = sm.anchorIndex = sm.selectedIndex = newIndex; | |
| 719 } | |
| 720 }); | |
| 721 } else { | |
| 722 // Edit | |
| 723 chrome.bookmarks.update(bookmarkNode.id, context); | |
| 724 } | |
| 725 performGlobalUndo = null; // This can't be undone, so disable global undo. | |
| 726 } | |
| 727 | |
| 728 function handleCancelEdit(e) { | |
| 729 var item = e.target; | |
| 730 var bookmarkNode = item.bookmarkNode; | |
| 731 if (bookmarkNode.id == 'new') { | |
| 732 var dataModel = list.dataModel; | |
| 733 var index = dataModel.findIndexById('new'); | |
| 734 dataModel.splice(index, 1); | |
| 735 } | |
| 736 } | |
| 737 | |
| 738 /** | |
| 739 * Navigates to the folder that the selected item is in and selects it. This is | |
| 740 * used for the show-in-folder command. | |
| 741 */ | |
| 742 function showInFolder() { | |
| 743 var bookmarkNode = list.selectedItem; | |
| 744 if (!bookmarkNode) | |
| 745 return; | |
| 746 var parentId = bookmarkNode.parentId; | |
| 747 | |
| 748 // After the list is loaded we should select the revealed item. | |
| 749 function selectItem() { | |
| 750 var index = list.dataModel.findIndexById(bookmarkNode.id); | |
| 751 if (index == -1) | |
| 752 return; | |
| 753 var sm = list.selectionModel; | |
| 754 sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index; | |
| 755 list.scrollIndexIntoView(index); | |
| 756 } | |
| 757 | |
| 758 var treeItem = bmm.treeLookup[parentId]; | |
| 759 treeItem.reveal(); | |
| 760 | |
| 761 navigateTo(parentId, selectItem); | |
| 762 } | |
| 763 | |
| 764 /** | |
| 765 * @return {!cr.LinkController} The link controller used to open links based on | |
| 766 * user clicks and keyboard actions. | |
| 767 */ | |
| 768 function getLinkController() { | |
| 769 return linkController || | |
| 770 (linkController = new cr.LinkController(loadTimeData)); | |
| 771 } | |
| 772 | |
| 773 /** | |
| 774 * Returns the selected bookmark nodes of the provided tree or list. | |
| 775 * If |opt_target| is not provided or null the active element is used. | |
| 776 * Only call this if the list or the tree is focused. | |
| 777 * @param {BookmarkList|BookmarkTree} opt_target The target list or tree. | |
| 778 * @return {!Array} Array of bookmark nodes. | |
| 779 */ | |
| 780 function getSelectedBookmarkNodes(opt_target) { | |
| 781 return (opt_target || document.activeElement) == tree ? | |
| 782 tree.selectedFolders : list.selectedItems; | |
| 783 } | |
| 784 | |
| 785 /** | |
| 786 * @return {!Array.<string>} An array of the selected bookmark IDs. | |
| 787 */ | |
| 788 function getSelectedBookmarkIds() { | |
| 789 var selectedNodes = getSelectedBookmarkNodes(); | |
| 790 selectedNodes.sort(function(a, b) { return a.index - b.index }); | |
| 791 return selectedNodes.map(function(node) { | |
| 792 return node.id; | |
| 793 }); | |
| 794 } | |
| 795 | |
| 796 /** | |
| 797 * Opens the selected bookmarks. | |
| 798 * @param {LinkKind} kind The kind of link we want to open. | |
| 799 * @param {HTMLElement} opt_eventTarget The target of the user initiated event. | |
| 800 */ | |
| 801 function openBookmarks(kind, opt_eventTarget) { | |
| 802 // If we have selected any folders, we need to find all the bookmarks one | |
| 803 // level down. We use multiple async calls to getSubtree instead of getting | |
| 804 // the whole tree since we would like to minimize the amount of data sent. | |
| 805 | |
| 806 var urlsP = getUrlsForOpenCommands(opt_eventTarget); | |
| 807 urlsP.addListener(function(urls) { | |
| 808 getLinkController().openUrls(urls, kind); | |
| 809 chrome.bookmarkManagerPrivate.recordLaunch(); | |
| 810 }); | |
| 811 } | |
| 812 | |
| 813 /** | |
| 814 * Opens an item in the list. | |
| 815 */ | |
| 816 function openItem() { | |
| 817 var bookmarkNodes = getSelectedBookmarkNodes(); | |
| 818 // If we double clicked or pressed enter on a single folder, navigate to it. | |
| 819 if (bookmarkNodes.length == 1 && bmm.isFolder(bookmarkNodes[0])) { | |
| 820 navigateTo(bookmarkNodes[0].id, updateHash); | |
| 821 } else { | |
| 822 openBookmarks(LinkKind.FOREGROUND_TAB); | |
| 823 } | |
| 824 } | |
| 825 | |
| 826 /** | |
| 827 * Deletes the selected bookmarks. The bookmarks are saved in memory in case | |
| 828 * the user needs to undo the deletion. | |
| 829 */ | |
| 830 function deleteBookmarks() { | |
| 831 var selectedIds = getSelectedBookmarkIds(); | |
| 832 lastDeletedNodes = []; | |
| 833 | |
| 834 function performDelete() { | |
| 835 chrome.bookmarkManagerPrivate.removeTrees(selectedIds); | |
| 836 $('undo-delete-command').canExecuteChange(); | |
| 837 performGlobalUndo = undoDelete; | |
| 838 } | |
| 839 | |
| 840 // First, store information about the bookmarks being deleted. | |
| 841 selectedIds.forEach(function(id) { | |
| 842 chrome.bookmarks.getSubTree(id, function(results) { | |
| 843 lastDeletedNodes.push(results); | |
| 844 | |
| 845 // When all nodes have been saved, perform the deletion. | |
| 846 if (lastDeletedNodes.length === selectedIds.length) | |
| 847 performDelete(); | |
| 848 }); | |
| 849 }); | |
| 850 } | |
| 851 | |
| 852 /** | |
| 853 * Restores a tree of bookmarks under a specified folder. | |
| 854 * @param {BookmarkTreeNode} node The node to restore. | |
| 855 * @param {=string} parentId The ID of the folder to restore under. If not | |
| 856 * specified, the original parentId of the node will be used. | |
| 857 */ | |
| 858 function restoreTree(node, parentId) { | |
| 859 var bookmarkInfo = { | |
| 860 parentId: parentId || node.parentId, | |
| 861 title: node.title, | |
| 862 index: node.index, | |
| 863 url: node.url | |
| 864 }; | |
| 865 | |
| 866 chrome.bookmarks.create(bookmarkInfo, function(result) { | |
| 867 if (!result) { | |
| 868 console.error('Failed to restore bookmark.'); | |
| 869 return; | |
| 870 } | |
| 871 | |
| 872 if (node.children) { | |
| 873 // Restore the children using the new ID for this node. | |
| 874 node.children.forEach(function(child) { | |
| 875 restoreTree(child, result.id); | |
| 876 }); | |
| 877 } | |
| 878 }); | |
| 879 } | |
| 880 | |
| 881 /** | |
| 882 * Restores the last set of bookmarks that was deleted. | |
| 883 */ | |
| 884 function undoDelete() { | |
| 885 lastDeletedNodes.forEach(function(arr) { | |
| 886 arr.forEach(restoreTree); | |
| 887 }); | |
| 888 lastDeletedNodes = null; | |
| 889 $('undo-delete-command').canExecuteChange(); | |
| 890 | |
| 891 // Only a single level of undo is supported, so disable global undo now. | |
| 892 performGlobalUndo = null; | |
| 893 } | |
| 894 | |
| 895 /** | |
| 896 * Computes folder for "Add Page" and "Add Folder". | |
| 897 * @return {string} The id of folder node where we'll create new page/folder. | |
| 898 */ | |
| 899 function computeParentFolderForNewItem() { | |
| 900 if (document.activeElement == tree) | |
| 901 return list.parentId; | |
| 902 var selectedItem = list.selectedItem; | |
| 903 return selectedItem && bmm.isFolder(selectedItem) ? | |
| 904 selectedItem.id : list.parentId; | |
| 905 } | |
| 906 | |
| 907 /** | |
| 908 * Callback for rename folder and edit command. This starts editing for | |
| 909 * selected item. | |
| 910 */ | |
| 911 function editSelectedItem() { | |
| 912 if (document.activeElement == tree) { | |
| 913 tree.selectedItem.editing = true; | |
| 914 } else { | |
| 915 var li = list.getListItem(list.selectedItem); | |
| 916 if (li) | |
| 917 li.editing = true; | |
| 918 } | |
| 919 } | |
| 920 | |
| 921 /** | |
| 922 * Callback for the new folder command. This creates a new folder and starts | |
| 923 * a rename of it. | |
| 924 */ | |
| 925 function newFolder() { | |
| 926 performGlobalUndo = null; // This can't be undone, so disable global undo. | |
| 927 | |
| 928 var parentId = computeParentFolderForNewItem(); | |
| 929 | |
| 930 // Callback is called after tree and list data model updated. | |
| 931 function createFolder(callback) { | |
| 932 chrome.bookmarks.create({ | |
| 933 title: loadTimeData.getString('new_folder_name'), | |
| 934 parentId: parentId | |
| 935 }, callback); | |
| 936 } | |
| 937 | |
| 938 if (document.activeElement == tree) { | |
| 939 createFolder(function(newNode) { | |
| 940 navigateTo(newNode.id, function() { | |
| 941 bmm.treeLookup[newNode.id].editing = true; | |
| 942 }); | |
| 943 }); | |
| 944 return; | |
| 945 } | |
| 946 | |
| 947 function editNewFolderInList() { | |
| 948 createFolder(function() { | |
| 949 var index = list.dataModel.length - 1; | |
| 950 var sm = list.selectionModel; | |
| 951 sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index; | |
| 952 scrollIntoViewAndMakeEditable(index); | |
| 953 }); | |
| 954 } | |
| 955 | |
| 956 navigateTo(parentId, editNewFolderInList); | |
| 957 } | |
| 958 | |
| 959 /** | |
| 960 * Scrolls the list item into view and makes it editable. | |
| 961 * @param {number} index The index of the item to make editable. | |
| 962 */ | |
| 963 function scrollIntoViewAndMakeEditable(index) { | |
| 964 list.scrollIndexIntoView(index); | |
| 965 // onscroll is now dispatched asynchronously so we have to postpone | |
| 966 // the rest. | |
| 967 setTimeout(function() { | |
| 968 var item = list.getListItemByIndex(index); | |
| 969 if (item) | |
| 970 item.editing = true; | |
| 971 }); | |
| 972 } | |
| 973 | |
| 974 /** | |
| 975 * Adds a page to the current folder. This is called by the | |
| 976 * add-new-bookmark-command handler. | |
| 977 */ | |
| 978 function addPage() { | |
| 979 var parentId = computeParentFolderForNewItem(); | |
| 980 | |
| 981 function editNewBookmark() { | |
| 982 var fakeNode = { | |
| 983 title: '', | |
| 984 url: '', | |
| 985 parentId: parentId, | |
| 986 id: 'new' | |
| 987 }; | |
| 988 var dataModel = list.dataModel; | |
| 989 var length = dataModel.length; | |
| 990 dataModel.splice(length, 0, fakeNode); | |
| 991 var sm = list.selectionModel; | |
| 992 sm.anchorIndex = sm.leadIndex = sm.selectedIndex = length; | |
| 993 scrollIntoViewAndMakeEditable(length); | |
| 994 }; | |
| 995 | |
| 996 navigateTo(parentId, editNewBookmark); | |
| 997 } | |
| 998 | |
| 999 /** | |
| 1000 * This function is used to select items after a user action such as paste, drop | |
| 1001 * add page etc. | |
| 1002 * @param {BookmarkList|BookmarkTree} target The target of the user action. | |
| 1003 * @param {=string} opt_selectedTreeId If provided, then select that tree id. | |
| 1004 */ | |
| 1005 function selectItemsAfterUserAction(target, opt_selectedTreeId) { | |
| 1006 // We get one onCreated event per item so we delay the handling until we get | |
| 1007 // no more events coming. | |
| 1008 | |
| 1009 var ids = []; | |
| 1010 var timer; | |
| 1011 | |
| 1012 function handle(id, bookmarkNode) { | |
| 1013 clearTimeout(timer); | |
| 1014 if (opt_selectedTreeId || list.parentId == bookmarkNode.parentId) | |
| 1015 ids.push(id); | |
| 1016 timer = setTimeout(handleTimeout, 50); | |
| 1017 } | |
| 1018 | |
| 1019 function handleTimeout() { | |
| 1020 chrome.bookmarks.onCreated.removeListener(handle); | |
| 1021 chrome.bookmarks.onMoved.removeListener(handle); | |
| 1022 | |
| 1023 if (opt_selectedTreeId && ids.indexOf(opt_selectedTreeId) != -1) { | |
| 1024 var index = ids.indexOf(opt_selectedTreeId); | |
| 1025 if (index != -1 && opt_selectedTreeId in bmm.treeLookup) { | |
| 1026 tree.selectedItem = bmm.treeLookup[opt_selectedTreeId]; | |
| 1027 } | |
| 1028 } else if (target == list) { | |
| 1029 var dataModel = list.dataModel; | |
| 1030 var firstIndex = dataModel.findIndexById(ids[0]); | |
| 1031 var lastIndex = dataModel.findIndexById(ids[ids.length - 1]); | |
| 1032 if (firstIndex != -1 && lastIndex != -1) { | |
| 1033 var selectionModel = list.selectionModel; | |
| 1034 selectionModel.selectedIndex = -1; | |
| 1035 selectionModel.selectRange(firstIndex, lastIndex); | |
| 1036 selectionModel.anchorIndex = selectionModel.leadIndex = lastIndex; | |
| 1037 list.focus(); | |
| 1038 } | |
| 1039 } | |
| 1040 | |
| 1041 list.endBatchUpdates(); | |
| 1042 } | |
| 1043 | |
| 1044 list.startBatchUpdates(); | |
| 1045 | |
| 1046 chrome.bookmarks.onCreated.addListener(handle); | |
| 1047 chrome.bookmarks.onMoved.addListener(handle); | |
| 1048 timer = setTimeout(handleTimeout, 300); | |
| 1049 } | |
| 1050 | |
| 1051 /** | |
| 1052 * Record user action. | |
| 1053 * @param {string} name An user action name. | |
| 1054 */ | |
| 1055 function recordUserAction(name) { | |
| 1056 chrome.metricsPrivate.recordUserAction('BookmarkManager_Command_' + name); | |
| 1057 } | |
| 1058 | |
| 1059 /** | |
| 1060 * The currently selected bookmark, based on where the user is clicking. | |
| 1061 * @return {string} The ID of the currently selected bookmark (could be from | |
| 1062 * tree view or list view). | |
| 1063 */ | |
| 1064 function getSelectedId() { | |
| 1065 if (document.activeElement == tree) | |
| 1066 return tree.selectedItem.bookmarkId; | |
| 1067 var selectedItem = list.selectedItem; | |
| 1068 return selectedItem && bmm.isFolder(selectedItem) ? | |
| 1069 selectedItem.id : tree.selectedItem.bookmarkId; | |
| 1070 } | |
| 1071 | |
| 1072 /** | |
| 1073 * Pastes the copied/cutted bookmark into the right location depending whether | |
| 1074 * if it was called from Organize Menu or from Context Menu. | |
| 1075 * @param {string} id The id of the element being pasted from. | |
| 1076 */ | |
| 1077 function pasteBookmark(id) { | |
| 1078 recordUserAction('Paste'); | |
| 1079 selectItemsAfterUserAction(list); | |
| 1080 chrome.bookmarkManagerPrivate.paste(id, getSelectedBookmarkIds()); | |
| 1081 } | |
| 1082 | |
| 1083 /** | |
| 1084 * Handler for the command event. This is used for context menu of list/tree | |
| 1085 * and organized menu. | |
| 1086 * @param {!Event} e The event object. | |
| 1087 */ | |
| 1088 function handleCommand(e) { | |
| 1089 var command = e.command; | |
| 1090 var commandId = command.id; | |
| 1091 switch (commandId) { | |
| 1092 case 'import-menu-command': | |
| 1093 recordUserAction('Import'); | |
| 1094 chrome.bookmarks.import(); | |
| 1095 break; | |
| 1096 case 'export-menu-command': | |
| 1097 recordUserAction('Export'); | |
| 1098 chrome.bookmarks.export(); | |
| 1099 break; | |
| 1100 case 'undo-command': | |
| 1101 if (performGlobalUndo) { | |
| 1102 recordUserAction('UndoGlobal'); | |
| 1103 performGlobalUndo(); | |
| 1104 } else { | |
| 1105 recordUserAction('UndoNone'); | |
| 1106 } | |
| 1107 break; | |
| 1108 case 'show-in-folder-command': | |
| 1109 recordUserAction('ShowInFolder'); | |
| 1110 showInFolder(); | |
| 1111 break; | |
| 1112 case 'open-in-new-tab-command': | |
| 1113 case 'open-in-background-tab-command': | |
| 1114 recordUserAction('OpenInNewTab'); | |
| 1115 openBookmarks(LinkKind.BACKGROUND_TAB, e.target); | |
| 1116 break; | |
| 1117 case 'open-in-new-window-command': | |
| 1118 recordUserAction('OpenInNewWindow'); | |
| 1119 openBookmarks(LinkKind.WINDOW, e.target); | |
| 1120 break; | |
| 1121 case 'open-incognito-window-command': | |
| 1122 recordUserAction('OpenIncognito'); | |
| 1123 openBookmarks(LinkKind.INCOGNITO, e.target); | |
| 1124 break; | |
| 1125 case 'delete-command': | |
| 1126 recordUserAction('Delete'); | |
| 1127 deleteBookmarks(); | |
| 1128 break; | |
| 1129 case 'copy-command': | |
| 1130 recordUserAction('Copy'); | |
| 1131 chrome.bookmarkManagerPrivate.copy(getSelectedBookmarkIds(), | |
| 1132 updatePasteCommand); | |
| 1133 break; | |
| 1134 case 'cut-command': | |
| 1135 recordUserAction('Cut'); | |
| 1136 chrome.bookmarkManagerPrivate.cut(getSelectedBookmarkIds(), | |
| 1137 updatePasteCommand); | |
| 1138 break; | |
| 1139 case 'paste-from-organize-menu-command': | |
| 1140 pasteBookmark(list.parentId); | |
| 1141 break; | |
| 1142 case 'paste-from-context-menu-command': | |
| 1143 pasteBookmark(getSelectedId()); | |
| 1144 break; | |
| 1145 case 'sort-command': | |
| 1146 recordUserAction('Sort'); | |
| 1147 chrome.bookmarkManagerPrivate.sortChildren(list.parentId); | |
| 1148 break; | |
| 1149 case 'rename-folder-command': | |
| 1150 editSelectedItem(); | |
| 1151 break; | |
| 1152 case 'edit-command': | |
| 1153 recordUserAction('Edit'); | |
| 1154 editSelectedItem(); | |
| 1155 break; | |
| 1156 case 'new-folder-command': | |
| 1157 recordUserAction('NewFolder'); | |
| 1158 newFolder(); | |
| 1159 break; | |
| 1160 case 'add-new-bookmark-command': | |
| 1161 recordUserAction('AddPage'); | |
| 1162 addPage(); | |
| 1163 break; | |
| 1164 case 'open-in-same-window-command': | |
| 1165 recordUserAction('OpenInSame'); | |
| 1166 openItem(); | |
| 1167 break; | |
| 1168 case 'undo-delete-command': | |
| 1169 recordUserAction('UndoDelete'); | |
| 1170 undoDelete(); | |
| 1171 break; | |
| 1172 } | |
| 1173 } | |
| 1174 | |
| 1175 // Execute the copy, cut and paste commands when those events are dispatched by | |
| 1176 // the browser. This allows us to rely on the browser to handle the keyboard | |
| 1177 // shortcuts for these commands. | |
| 1178 function installEventHandlerForCommand(eventName, commandId) { | |
| 1179 function handle(e) { | |
| 1180 if (document.activeElement != list && document.activeElement != tree) | |
| 1181 return; | |
| 1182 var command = $(commandId); | |
| 1183 if (!command.disabled) { | |
| 1184 command.execute(); | |
| 1185 if (e) | |
| 1186 e.preventDefault(); // Prevent the system beep. | |
| 1187 } | |
| 1188 } | |
| 1189 if (eventName == 'paste') { | |
| 1190 // Paste is a bit special since we need to do an async call to see if we | |
| 1191 // can paste because the paste command might not be up to date. | |
| 1192 document.addEventListener(eventName, function(e) { | |
| 1193 updatePasteCommand(handle); | |
| 1194 }); | |
| 1195 } else { | |
| 1196 document.addEventListener(eventName, handle); | |
| 1197 } | |
| 1198 } | |
| 1199 | |
| 1200 function initializeSplitter() { | |
| 1201 var splitter = document.querySelector('.main > .splitter'); | |
| 1202 Splitter.decorate(splitter); | |
| 1203 | |
| 1204 // The splitter persists the size of the left component in the local store. | |
| 1205 if ('treeWidth' in localStorage) | |
| 1206 splitter.previousElementSibling.style.width = localStorage['treeWidth']; | |
| 1207 | |
| 1208 splitter.addEventListener('resize', function(e) { | |
| 1209 localStorage['treeWidth'] = splitter.previousElementSibling.style.width; | |
| 1210 }); | |
| 1211 } | |
| 1212 | |
| 1213 function initializeBookmarkManager() { | |
| 1214 // Sometimes the extension API is not initialized. | |
| 1215 if (!chrome.bookmarks) | |
| 1216 console.error('Bookmarks extension API is not available'); | |
| 1217 | |
| 1218 chrome.bookmarkManagerPrivate.getStrings(loadLocalizedStrings); | |
| 1219 | |
| 1220 bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem; | |
| 1221 | |
| 1222 cr.ui.decorate('menu', Menu); | |
| 1223 cr.ui.decorate('button[menu]', MenuButton); | |
| 1224 cr.ui.decorate('command', Command); | |
| 1225 BookmarkList.decorate(list); | |
| 1226 BookmarkTree.decorate(tree); | |
| 1227 | |
| 1228 list.addEventListener('canceledit', handleCancelEdit); | |
| 1229 list.addEventListener('canExecute', handleCanExecuteForList); | |
| 1230 list.addEventListener('change', updateCommandsBasedOnSelection); | |
| 1231 list.addEventListener('contextmenu', updateEditingCommands); | |
| 1232 list.addEventListener('dblclick', handleDoubleClickForList); | |
| 1233 list.addEventListener('edit', handleEdit); | |
| 1234 list.addEventListener('rename', handleRename); | |
| 1235 list.addEventListener('urlClicked', handleUrlClickedForList); | |
| 1236 | |
| 1237 tree.addEventListener('canExecute', handleCanExecuteForTree); | |
| 1238 tree.addEventListener('change', handleChangeForTree); | |
| 1239 tree.addEventListener('contextmenu', updateEditingCommands); | |
| 1240 tree.addEventListener('rename', handleRename); | |
| 1241 tree.addEventListener('load', handleLoadForTree); | |
| 1242 | |
| 1243 cr.ui.contextMenuHandler.addContextMenuProperty(tree); | |
| 1244 list.contextMenu = $('context-menu'); | |
| 1245 tree.contextMenu = $('context-menu'); | |
| 1246 | |
| 1247 // We listen to hashchange so that we can update the currently shown folder | |
| 1248 // when // the user goes back and forward in the history. | |
| 1249 window.addEventListener('hashchange', processHash); | |
| 1250 | |
| 1251 document.querySelector('.header form').onsubmit = function(e) { | |
| 1252 setSearch($('term').value); | |
| 1253 e.preventDefault(); | |
| 1254 }; | |
| 1255 | |
| 1256 $('term').addEventListener('search', handleSearch); | |
| 1257 | |
| 1258 document.querySelector('.summary > button').addEventListener( | |
| 1259 'click', handleOrganizeButtonClick); | |
| 1260 | |
| 1261 document.querySelector('button.logo').addEventListener( | |
| 1262 'click', handleClickOnLogoButton); | |
| 1263 | |
| 1264 document.addEventListener('canExecute', handleCanExecuteForDocument); | |
| 1265 document.addEventListener('command', handleCommand); | |
| 1266 | |
| 1267 // Listen to copy, cut and paste events and execute the associated commands. | |
| 1268 installEventHandlerForCommand('copy', 'copy-command'); | |
| 1269 installEventHandlerForCommand('cut', 'cut-command'); | |
| 1270 installEventHandlerForCommand('paste', 'paste-from-organize-menu-command'); | |
| 1271 | |
| 1272 // Install shortcuts | |
| 1273 for (var name in commandShortcutMap) { | |
| 1274 $(name + '-command').shortcut = commandShortcutMap[name]; | |
| 1275 } | |
| 1276 | |
| 1277 // Disable almost all commands at startup. | |
| 1278 var commands = document.querySelectorAll('command'); | |
| 1279 for (var i = 0, command; command = commands[i]; ++i) { | |
| 1280 if (command.id != 'import-menu-command' && | |
| 1281 command.id != 'export-menu-command') { | |
| 1282 command.disabled = true; | |
| 1283 } | |
| 1284 } | |
| 1285 | |
| 1286 chrome.bookmarkManagerPrivate.canEdit(function(result) { | |
| 1287 canEdit = result; | |
| 1288 }); | |
| 1289 | |
| 1290 chrome.systemPrivate.getIncognitoModeAvailability(function(result) { | |
| 1291 // TODO(rustema): propagate policy value to the bookmark manager when it | |
| 1292 // changes. | |
| 1293 incognitoModeAvailability = result; | |
| 1294 }); | |
| 1295 | |
| 1296 chrome.bookmarkManagerPrivate.canOpenNewWindows(function(result) { | |
| 1297 canOpenNewWindows = result; | |
| 1298 }); | |
| 1299 | |
| 1300 cr.ui.FocusOutlineManager.forDocument(document); | |
| 1301 initializeSplitter(); | |
| 1302 bmm.addBookmarkModelListeners(); | |
| 1303 dnd.init(selectItemsAfterUserAction); | |
| 1304 tree.reload(); | |
| 1305 } | |
| 1306 | |
| 1307 initializeBookmarkManager(); | |
| 1308 })(); | |
| OLD | NEW |