blob: f0b26546e812f0d0fa6aaeaa24be2d9c5b593204 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// Copyright (c) 2012 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00005(function() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00006/** @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;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000013/** @const */ var MenuButton = cr.ui.MenuButton;
Torne (Richard Coles)58218062012-11-14 11:43:16 +000014/** @const */ var Promise = cr.Promise;
15/** @const */ var Splitter = cr.ui.Splitter;
16/** @const */ var TreeItem = cr.ui.TreeItem;
17
Torne (Richard Coles)58218062012-11-14 11:43:16 +000018/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +000019 * 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 */
23var lastDeletedNodes;
24
25/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000026 *
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 */
31var lastHoverOnFolderTimeStamp = 0;
32
33/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +000034 * Holds a function that will undo that last action, if global undo is enabled.
35 * @type {Function}
36 */
37var performGlobalUndo;
38
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000039/**
40 * Holds a link controller singleton. Use getLinkController() rarther than
41 * accessing this variabie.
42 * @type {LinkController}
43 */
44var linkController;
Torne (Richard Coles)58218062012-11-14 11:43:16 +000045
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000046/**
47 * New Windows are not allowed in Windows 8 metro mode.
48 */
49var canOpenNewWindows = true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +000050
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000051/**
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 */
57var incognitoModeAvailability = 'enabled';
58
59/**
60 * Whether bookmarks can be modified.
61 * @type {boolean}
62 */
63var canEdit = true;
64
65/**
66 * @type {TreeItem}
67 * @const
68 */
Torne (Richard Coles)58218062012-11-14 11:43:16 +000069var searchTreeItem = new TreeItem({
70 bookmarkId: 'q='
71});
Torne (Richard Coles)58218062012-11-14 11:43:16 +000072
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000073/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000074 * Command shortcut mapping.
75 * @const
76 */
77var 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};
Torne (Richard Coles)58218062012-11-14 11:43:16 +000099
100/**
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100101 * Mapping for folder id to suffix of UMA. These names will be appeared
102 * after "BookmarkManager_NavigateTo_" in UMA dashboard.
103 * @const
104 */
105var folderMetricsNameMap = {
106 '1': 'BookmarkBar',
107 '2': 'Other',
108 '3': 'Mobile',
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100109 'q=': 'Search',
110 'subfolder': 'SubFolder',
111};
112
113/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000114 * 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 */
119function 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000127// Get the localized strings from the backend via bookmakrManagerPrivate API.
128function 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000137 searchTreeItem.label = loadTimeData.getString('search');
138 searchTreeItem.icon = isRTL() ? 'images/bookmark_manager_search_rtl.png' :
139 'images/bookmark_manager_search.png';
140}
141
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000142/**
143 * Updates the location hash to reflect the current state of the application.
144 */
145function 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.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000152 * @param {function()} callback Function called when list view loaded or
153 * displayed specified folder.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000154 */
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000155function navigateTo(id, callback) {
156 if (list.parentId == id) {
157 callback();
158 return;
159 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000160
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100161 var metricsId = folderMetricsNameMap[id.replace(/^q=.*/, 'q=')] ||
162 folderMetricsNameMap['subfolder'];
163 chrome.metricsPrivate.recordUserAction(
164 'BookmarkManager_NavigateTo_' + metricsId);
165
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000166 addOneShotEventListener(list, 'load', callback);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000167 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 */
174function updateParentId(id) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000175 // Setting list.parentId fires 'load' event.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000176 list.parentId = id;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000177
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;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000181}
182
183// Process the location hash. This is called by onhashchange and when the page
184// is first loaded.
185function 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000213 navigateTo(bookmarkNode.parentId, editBookmark);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000214 });
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;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000224 }
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 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000236}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000237
238// Activate is handled by the open-in-same-window-command.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000239function handleDoubleClickForList(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000240 if (e.button == 0)
241 $('open-in-same-window-command').execute();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000242}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000243
244// The list dispatches an event when the user clicks on the URL or the Show in
245// folder part.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000246function handleUrlClickedForList(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000247 getLinkController().openUrlFromEvent(e.url, e.originalEvent);
248 chrome.bookmarkManagerPrivate.recordLaunch();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000249}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000250
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000251function handleSearch(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000252 setSearch(this.value);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000253}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000254
255/**
256 * Navigates to the search results for the search text.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000257 * @param {string} searchText The text to search for.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000258 */
259function 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.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000283 navigateTo(id, updateHash);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000284}
285
286// Handle the logo button UI.
287// When the user clicks the button we should navigate "home" and focus the list.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000288function handleClickOnLogoButton(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000289 setSearch('');
290 $('list').focus();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000291}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000292
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 */
299function getFolder(parentId) {
300 var parentNode = tree.getBookmarkNodeById(parentId);
301 if (parentNode) {
302 var s = parentNode.title;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000303 if (parentNode.parentId != bmm.ROOT_ID) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000304 return getFolder(parentNode.parentId) + '/' + s;
305 }
306 return s;
307 }
308}
309
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000310function handleLoadForTree(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000311 processHash();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000312}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000313
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100314function 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
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100353/**
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100354 * Returns the nodes (non recursive) to use for the open commands.
355 * @param {HTMLElement} target .
356 * @return {Array.<BookmarkTreeNode>} .
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100357 */
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100358function getNodesForOpen(target) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100359 if (target == tree) {
360 var folderItem = tree.selectedItem;
Ben Murdoch558790d2013-07-30 15:19:42 +0100361 return folderItem == searchTreeItem ?
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100362 list.dataModel.slice() : tree.selectedFolders;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000363 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100364 var items = list.selectedItems;
365 return items.length ? items : list.dataModel.slice();
366}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000367
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000368/**
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100369 * 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 */
374function getUrlsForOpenCommands(target) {
375 return getAllUrls(getNodesForOpen(target));
376}
377
378function notNewNode(node) {
379 return node.id != 'new';
380}
381
382/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000383 * 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.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100387 * @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.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000392 */
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100393function updateOpenCommand(e, command, singularId, pluralId, commandDisabled) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100394 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 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000402
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100403 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;
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100413 });
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000414}
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 */
421function updatePasteCommand(opt_f) {
422 function update(canPaste) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000423 var organizeMenuCommand = $('paste-from-organize-menu-command');
424 var contextMenuCommand = $('paste-from-context-menu-command');
425 organizeMenuCommand.disabled = !canPaste;
426 contextMenuCommand.disabled = !canPaste;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000427 if (opt_f)
428 opt_f();
429 }
Ben Murdoch558790d2013-07-30 15:19:42 +0100430 // We cannot paste into search view.
431 if (list.isSearch())
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000432 update(false);
Ben Murdoch558790d2013-07-30 15:19:42 +0100433 else
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000434 chrome.bookmarkManagerPrivate.canPaste(list.parentId, update);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000435}
436
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000437function handleCanExecuteForDocument(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000438 var command = e.command;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000439 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':
Ben Murdoch558790d2013-07-30 15:19:42 +0100448 e.canExecute = !list.isSearch() && list.dataModel.length > 1;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000449 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;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000458 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000459}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000460
461/**
462 * Helper function for handling canExecute for the list and the tree.
463 * @param {!Event} e Can execute event object.
Ben Murdoch558790d2013-07-30 15:19:42 +0100464 * @param {boolean} isSearch Whether the user is trying to do a command on
465 * search.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000466 */
Ben Murdoch558790d2013-07-30 15:19:42 +0100467function canExecuteShared(e, isSearch) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000468 var command = e.command;
469 var commandId = command.id;
470 switch (commandId) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000471 case 'paste-from-organize-menu-command':
472 case 'paste-from-context-menu-command':
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000473 updatePasteCommand();
474 break;
475
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000476 case 'add-new-bookmark-command':
477 case 'new-folder-command':
Ben Murdoch558790d2013-07-30 15:19:42 +0100478 e.canExecute = !isSearch && canEdit;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000479 break;
480
481 case 'open-in-new-tab-command':
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100482 updateOpenCommand(e, command, 'open_in_new_tab', 'open_all', false);
483 break;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000484 case 'open-in-background-tab-command':
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100485 updateOpenCommand(e, command, '', '', false);
486 break;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000487 case 'open-in-new-window-command':
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100488 updateOpenCommand(e, command,
489 'open_in_new_window', 'open_all_new_window',
490 // Disabled when incognito is forced.
491 incognitoModeAvailability == 'forced' || !canOpenNewWindows);
492 break;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000493 case 'open-incognito-window-command':
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100494 updateOpenCommand(e, command,
495 'open_incognito', 'open_all_incognito',
496 // Not available when incognito is disabled.
497 incognitoModeAvailability == 'disabled');
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000498 break;
499
500 case 'undo-delete-command':
501 e.canExecute = !!lastDeletedNodes;
502 break;
503 }
504}
505
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000506/**
507 * Helper function for handling canExecute for the list and document.
508 * @param {!Event} e Can execute event object.
509 */
510function canExecuteForList(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000511 var command = e.command;
512 var commandId = command.id;
513
514 function hasSelected() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000515 return !!list.selectedItem;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000516 }
517
518 function hasSingleSelected() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000519 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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000529 }
530
Ben Murdoch558790d2013-07-30 15:19:42 +0100531 function isSearch() {
532 return list.isSearch();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000533 }
534
535 switch (commandId) {
536 case 'rename-folder-command':
537 // Show rename if a single folder is selected.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000538 var items = list.selectedItems;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000539 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.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000551 var items = list.selectedItems;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000552 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':
Ben Murdoch558790d2013-07-30 15:19:42 +0100563 e.canExecute = isSearch() && hasSingleSelected();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000564 break;
565
566 case 'delete-command':
567 case 'cut-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000568 e.canExecute = canCopyItems() && canEdit;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000569 break;
570
571 case 'copy-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000572 e.canExecute = canCopyItems();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000573 break;
574
575 case 'open-in-same-window-command':
576 e.canExecute = hasSelected();
577 break;
578
579 default:
Ben Murdoch558790d2013-07-30 15:19:42 +0100580 canExecuteShared(e, isSearch());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000581 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000582}
583
584// Update canExecute for the commands when the list is the active element.
585function handleCanExecuteForList(e) {
586 if (e.target != list) return;
587 canExecuteForList(e);
588}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000589
590// Update canExecute for the commands when the tree is the active element.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000591function handleCanExecuteForTree(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000592 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
Ben Murdoch558790d2013-07-30 15:19:42 +0100601 function isSearch() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000602 var item = e.target.selectedItem;
Ben Murdoch558790d2013-07-30 15:19:42 +0100603 return item == searchTreeItem;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000604 }
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:
Ben Murdoch558790d2013-07-30 15:19:42 +0100631 canExecuteShared(e, isSearch());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000632 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000633}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000634
635/**
636 * Update the canExecute state of the commands when the selection changes.
637 * @param {Event} e The change event object.
638 */
639function 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',
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100644 'open-in-background-tab', 'open-in-new-window', 'open-incognito-window',
645 'open-in-same-window', 'show-in-folder'];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000646
647 if (e.target == tree) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000648 commandNames.push('paste-from-context-menu', 'paste-from-organize-menu',
649 'sort');
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000650 }
651
652 commandNames.forEach(function(baseId) {
653 $(baseId + '-command').canExecuteChange();
654 });
655 }
656}
657
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000658function updateEditingCommands() {
659 var editingCommands = ['cut', 'delete', 'rename-folder', 'edit',
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000660 'add-new-bookmark', 'new-folder', 'sort',
661 'paste-from-context-menu', 'paste-from-organize-menu'];
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000662
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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000673function handleChangeForTree(e) {
674 updateCommandsBasedOnSelection(e);
675 navigateTo(tree.selectedItem.bookmarkId, updateHash);
676}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000677
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000678function handleOrganizeButtonClick(e) {
679 updateEditingCommands();
680 $('add-new-bookmark-command').canExecuteChange();
681 $('new-folder-command').canExecuteChange();
682 $('sort-command').canExecuteChange();
683}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000684
685function 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000692function handleEdit(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000693 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.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000726}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000727
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000728function handleCancelEdit(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000729 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 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000736}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000737
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 */
742function showInFolder() {
743 var bookmarkNode = list.selectedItem;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000744 if (!bookmarkNode)
745 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000746 var parentId = bookmarkNode.parentId;
747
748 // After the list is loaded we should select the revealed item.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000749 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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000756 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000757
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000758 var treeItem = bmm.treeLookup[parentId];
759 treeItem.reveal();
760
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000761 navigateTo(parentId, selectItem);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000762}
763
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000764/**
765 * @return {!cr.LinkController} The link controller used to open links based on
766 * user clicks and keyboard actions.
767 */
768function getLinkController() {
769 return linkController ||
770 (linkController = new cr.LinkController(loadTimeData));
771}
772
773/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000774 * 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.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000778 * @return {!Array} Array of bookmark nodes.
779 */
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000780function getSelectedBookmarkNodes(opt_target) {
781 return (opt_target || document.activeElement) == tree ?
782 tree.selectedFolders : list.selectedItems;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000783}
784
785/**
786 * @return {!Array.<string>} An array of the selected bookmark IDs.
787 */
788function getSelectedBookmarkIds() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100789 var selectedNodes = getSelectedBookmarkNodes();
790 selectedNodes.sort(function(a, b) { return a.index - b.index });
791 return selectedNodes.map(function(node) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000792 return node.id;
793 });
794}
795
796/**
797 * Opens the selected bookmarks.
798 * @param {LinkKind} kind The kind of link we want to open.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100799 * @param {HTMLElement} opt_eventTarget The target of the user initiated event.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000800 */
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100801function openBookmarks(kind, opt_eventTarget) {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100802 // 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.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000805
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100806 var urlsP = getUrlsForOpenCommands(opt_eventTarget);
807 urlsP.addListener(function(urls) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000808 getLinkController().openUrls(urls, kind);
809 chrome.bookmarkManagerPrivate.recordLaunch();
810 });
811}
812
813/**
814 * Opens an item in the list.
815 */
816function 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])) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000820 navigateTo(bookmarkNodes[0].id, updateHash);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000821 } 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 */
830function deleteBookmarks() {
831 var selectedIds = getSelectedBookmarkIds();
832 lastDeletedNodes = [];
833
834 function performDelete() {
Ben Murdochca12bfa2013-07-23 11:17:05 +0100835 chrome.bookmarkManagerPrivate.removeTrees(selectedIds);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000836 $('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 */
858function 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 */
884function 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/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000896 * Computes folder for "Add Page" and "Add Folder".
897 * @return {string} The id of folder node where we'll create new page/folder.
898 */
899function 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 */
911function 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/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000922 * Callback for the new folder command. This creates a new folder and starts
923 * a rename of it.
924 */
925function newFolder() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000926 performGlobalUndo = null; // This can't be undone, so disable global undo.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000927
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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000957}
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 */
963function 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 */
978function addPage() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000979 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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000994 };
995
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000996 navigateTo(parentId, editNewBookmark);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000997}
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 */
1005function 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/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001052 * Record user action.
1053 * @param {string} name An user action name.
1054 */
1055function 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 */
1064function 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 */
1077function 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.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001086 * @param {!Event} e The event object.
1087 */
1088function handleCommand(e) {
1089 var command = e.command;
1090 var commandId = command.id;
1091 switch (commandId) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001092 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;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001108 case 'show-in-folder-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001109 recordUserAction('ShowInFolder');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001110 showInFolder();
1111 break;
1112 case 'open-in-new-tab-command':
1113 case 'open-in-background-tab-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001114 recordUserAction('OpenInNewTab');
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001115 openBookmarks(LinkKind.BACKGROUND_TAB, e.target);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001116 break;
1117 case 'open-in-new-window-command':
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001118 recordUserAction('OpenInNewWindow');
1119 openBookmarks(LinkKind.WINDOW, e.target);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001120 break;
1121 case 'open-incognito-window-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001122 recordUserAction('OpenIncognito');
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001123 openBookmarks(LinkKind.INCOGNITO, e.target);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001124 break;
1125 case 'delete-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001126 recordUserAction('Delete');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001127 deleteBookmarks();
1128 break;
1129 case 'copy-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001130 recordUserAction('Copy');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001131 chrome.bookmarkManagerPrivate.copy(getSelectedBookmarkIds(),
1132 updatePasteCommand);
1133 break;
1134 case 'cut-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001135 recordUserAction('Cut');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001136 chrome.bookmarkManagerPrivate.cut(getSelectedBookmarkIds(),
1137 updatePasteCommand);
1138 break;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001139 case 'paste-from-organize-menu-command':
1140 pasteBookmark(list.parentId);
1141 break;
1142 case 'paste-from-context-menu-command':
1143 pasteBookmark(getSelectedId());
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001144 break;
1145 case 'sort-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001146 recordUserAction('Sort');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001147 chrome.bookmarkManagerPrivate.sortChildren(list.parentId);
1148 break;
1149 case 'rename-folder-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001150 editSelectedItem();
1151 break;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001152 case 'edit-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001153 recordUserAction('Edit');
1154 editSelectedItem();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001155 break;
1156 case 'new-folder-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001157 recordUserAction('NewFolder');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001158 newFolder();
1159 break;
1160 case 'add-new-bookmark-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001161 recordUserAction('AddPage');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001162 addPage();
1163 break;
1164 case 'open-in-same-window-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001165 recordUserAction('OpenInSame');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001166 openItem();
1167 break;
1168 case 'undo-delete-command':
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001169 recordUserAction('UndoDelete');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001170 undoDelete();
1171 break;
1172 }
1173}
1174
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001175// 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.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001178function 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 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001188 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001189 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
1200function 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
1213function 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;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001221
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
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001251 document.querySelector('.header form').onsubmit = function(e) {
1252 setSearch($('term').value);
1253 e.preventDefault();
1254 };
1255
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001256 $('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);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001266
1267 // Listen to copy, cut and paste events and execute the associated commands.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001268 installEventHandlerForCommand('copy', 'copy-command');
1269 installEventHandlerForCommand('cut', 'cut-command');
1270 installEventHandlerForCommand('paste', 'paste-from-organize-menu-command');
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001271
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001272 // 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;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001288 });
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001289
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
Ben Murdochbb1529c2013-08-08 10:24:53 +01001300 cr.ui.FocusOutlineManager.forDocument(document);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001301 initializeSplitter();
1302 bmm.addBookmarkModelListeners();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001303 dnd.init(selectItemsAfterUserAction);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001304 tree.reload();
1305}
1306
1307initializeBookmarkManager();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001308})();