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 |