blob: fefe8a6628c8c4562e768983afc2fad6ed5cb831 [file] [log] [blame]
tsergeant77365182017-05-05 04:02:331// Copyright 2017 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
tsergeant2db36262017-05-15 02:47:535/**
6 * @fileoverview Element which shows context menus and handles keyboard
7 * shortcuts.
8 */
9cr.define('bookmarks', function() {
tsergeant77365182017-05-05 04:02:3310
tsergeant2db36262017-05-15 02:47:5311 var CommandManager = Polymer({
12 is: 'bookmarks-command-manager',
tsergeant77365182017-05-05 04:02:3313
tsergeant2db36262017-05-15 02:47:5314 behaviors: [
15 bookmarks.StoreClient,
16 ],
17
18 properties: {
19 /** @private {!Array<Command>} */
20 menuCommands_: {
21 type: Array,
22 value: function() {
23 return [
24 Command.EDIT,
tsergeantb7253e92017-07-04 02:59:2225 Command.COPY_URL,
tsergeant2db36262017-05-15 02:47:5326 Command.DELETE,
27 // <hr>
28 Command.OPEN_NEW_TAB,
29 Command.OPEN_NEW_WINDOW,
30 Command.OPEN_INCOGNITO,
31 ];
32 },
tsergeant13a466462017-05-15 01:21:0333 },
tsergeant2db36262017-05-15 02:47:5334
tsergeant2437f992017-06-13 23:54:2935 /** @private {Set<string>} */
calamity64e2012a2017-06-21 09:57:1436 menuIds_: {
37 type: Object,
38 observer: 'onMenuIdsChanged_',
39 },
40
41 /** @private */
42 hasAnySublabel_: {
43 type: Boolean,
44 reflectToAttribute: true,
45 },
tsergeant2437f992017-06-13 23:54:2946
47 /** @private */
48 globalCanEdit_: Boolean,
tsergeant13a466462017-05-15 01:21:0349 },
50
tsergeant7fb9e13f2017-06-26 06:35:1951 /** @private {?Function} */
52 confirmOpenCallback_: null,
53
tsergeant2db36262017-05-15 02:47:5354 attached: function() {
55 assert(CommandManager.instance_ == null);
56 CommandManager.instance_ = this;
tsergeant77365182017-05-05 04:02:3357
tsergeant2437f992017-06-13 23:54:2958 this.watch('globalCanEdit_', function(state) {
59 return state.prefs.canEdit;
60 });
61 this.updateFromStore();
62
tsergeant2db36262017-05-15 02:47:5363 /** @private {function(!Event)} */
64 this.boundOnOpenItemMenu_ = this.onOpenItemMenu_.bind(this);
65 document.addEventListener('open-item-menu', this.boundOnOpenItemMenu_);
tsergeantfad224f2017-05-05 04:42:0966
calamityefe477352017-06-07 06:44:5867 /** @private {function()} */
68 this.boundOnCommandUndo_ = function() {
69 this.handle(Command.UNDO, new Set());
70 }.bind(this);
71 document.addEventListener('command-undo', this.boundOnCommandUndo_);
72
tsergeant2db36262017-05-15 02:47:5373 /** @private {function(!Event)} */
74 this.boundOnKeydown_ = this.onKeydown_.bind(this);
75 document.addEventListener('keydown', this.boundOnKeydown_);
tsergeantfad224f2017-05-05 04:42:0976
tsergeant0292e51a2017-06-16 03:44:3577 /** @private {Object<Command, cr.ui.KeyboardShortcutList>} */
tsergeant2db36262017-05-15 02:47:5378 this.shortcuts_ = {};
tsergeant0292e51a2017-06-16 03:44:3579
80 this.addShortcut_(Command.EDIT, 'F2', 'Enter');
tsergeant0292e51a2017-06-16 03:44:3581 this.addShortcut_(Command.DELETE, 'Delete', 'Delete Backspace');
82
83 this.addShortcut_(Command.OPEN, 'Enter', 'Meta|ArrowDown Meta|o');
84 this.addShortcut_(Command.OPEN_NEW_TAB, 'Ctrl|Enter', 'Meta|Enter');
85 this.addShortcut_(Command.OPEN_NEW_WINDOW, 'Shift|Enter');
86
87 this.addShortcut_(Command.UNDO, 'Ctrl|z', 'Meta|z');
88 this.addShortcut_(Command.REDO, 'Ctrl|y Ctrl|Shift|Z', 'Meta|Shift|Z');
tsergeant679159f2017-06-16 06:58:4189
90 this.addShortcut_(Command.SELECT_ALL, 'Ctrl|a', 'Meta|a');
91 this.addShortcut_(Command.DESELECT_ALL, 'Escape');
tsergeantb7253e92017-07-04 02:59:2292
93 this.addShortcut_(Command.CUT, 'Ctrl|x', 'Meta|x');
94 this.addShortcut_(Command.COPY, 'Ctrl|c', 'Meta|c');
95 this.addShortcut_(Command.PASTE, 'Ctrl|v', 'Meta|v');
tsergeant2db36262017-05-15 02:47:5396 },
tsergeant77365182017-05-05 04:02:3397
tsergeant2db36262017-05-15 02:47:5398 detached: function() {
99 CommandManager.instance_ = null;
100 document.removeEventListener('open-item-menu', this.boundOnOpenItemMenu_);
calamityefe477352017-06-07 06:44:58101 document.removeEventListener('command-undo', this.boundOnCommandUndo_);
tsergeant2db36262017-05-15 02:47:53102 document.removeEventListener('keydown', this.boundOnKeydown_);
103 },
tsergeant77365182017-05-05 04:02:33104
tsergeant2db36262017-05-15 02:47:53105 /**
106 * Display the command context menu at (|x|, |y|) in window co-ordinates.
tsergeanta274e0412017-06-16 05:22:28107 * Commands will execute on |items| if given, or on the currently selected
108 * items.
tsergeant2db36262017-05-15 02:47:53109 * @param {number} x
110 * @param {number} y
tsergeanta274e0412017-06-16 05:22:28111 * @param {Set<string>=} items
tsergeant2db36262017-05-15 02:47:53112 */
tsergeanta274e0412017-06-16 05:22:28113 openCommandMenuAtPosition: function(x, y, items) {
114 this.menuIds_ = items || this.getState().selection.items;
calamity57b2e8fd2017-06-29 18:46:58115
tsergeant9b9aa15f2017-06-22 03:22:27116 var dropdown =
117 /** @type {!CrActionMenuElement} */ (this.$.dropdown.get());
118 // Ensure that the menu is fully rendered before trying to position it.
119 Polymer.dom.flush();
calamity57b2e8fd2017-06-29 18:46:58120 bookmarks.DialogFocusManager.getInstance().showDialog(
121 dropdown, function() {
122 dropdown.showAtPosition({top: y, left: x});
123 });
tsergeant2db36262017-05-15 02:47:53124 },
tsergeant77365182017-05-05 04:02:33125
tsergeant2db36262017-05-15 02:47:53126 /**
127 * Display the command context menu positioned to cover the |target|
128 * element. Commands will execute on the currently selected items.
129 * @param {!Element} target
130 */
131 openCommandMenuAtElement: function(target) {
132 this.menuIds_ = this.getState().selection.items;
calamity57b2e8fd2017-06-29 18:46:58133
tsergeant9b9aa15f2017-06-22 03:22:27134 var dropdown =
135 /** @type {!CrActionMenuElement} */ (this.$.dropdown.get());
136 // Ensure that the menu is fully rendered before trying to position it.
137 Polymer.dom.flush();
calamity57b2e8fd2017-06-29 18:46:58138 bookmarks.DialogFocusManager.getInstance().showDialog(
139 dropdown, function() {
140 dropdown.showAt(target);
141 });
tsergeant2db36262017-05-15 02:47:53142 },
tsergeant77365182017-05-05 04:02:33143
tsergeant2db36262017-05-15 02:47:53144 closeCommandMenu: function() {
tsergeant4707d172017-06-05 05:47:02145 this.menuIds_ = new Set();
tsergeant9b9aa15f2017-06-22 03:22:27146 /** @type {!CrActionMenuElement} */ (this.$.dropdown.get()).close();
tsergeant2db36262017-05-15 02:47:53147 },
tsergeant77365182017-05-05 04:02:33148
tsergeant2db36262017-05-15 02:47:53149 ////////////////////////////////////////////////////////////////////////////
150 // Command handlers:
tsergeant77365182017-05-05 04:02:33151
tsergeant2db36262017-05-15 02:47:53152 /**
153 * Determine if the |command| can be executed with the given |itemIds|.
154 * Commands which appear in the context menu should be implemented
155 * separately using `isCommandVisible_` and `isCommandEnabled_`.
156 * @param {Command} command
157 * @param {!Set<string>} itemIds
158 * @return {boolean}
159 */
160 canExecute: function(command, itemIds) {
tsergeantb7253e92017-07-04 02:59:22161 var state = this.getState();
tsergeant6c5ad90a2017-05-19 14:12:34162 switch (command) {
163 case Command.OPEN:
164 return itemIds.size > 0;
calamity2d4b5502017-05-29 03:57:58165 case Command.UNDO:
166 case Command.REDO:
tsergeant2437f992017-06-13 23:54:29167 return this.globalCanEdit_;
tsergeant679159f2017-06-16 06:58:41168 case Command.SELECT_ALL:
169 case Command.DESELECT_ALL:
170 return true;
tsergeantb7253e92017-07-04 02:59:22171 case Command.COPY:
172 return itemIds.size > 0;
173 case Command.CUT:
174 return itemIds.size > 0 &&
175 !this.containsMatchingNode_(itemIds, function(node) {
176 return !bookmarks.util.canEditNode(state, node.id);
177 });
178 case Command.PASTE:
179 return state.search.term == '' &&
180 bookmarks.util.canReorderChildren(state, state.selectedFolder);
tsergeant6c5ad90a2017-05-19 14:12:34181 default:
182 return this.isCommandVisible_(command, itemIds) &&
183 this.isCommandEnabled_(command, itemIds);
184 }
tsergeant2db36262017-05-15 02:47:53185 },
tsergeant13a466462017-05-15 01:21:03186
tsergeant2db36262017-05-15 02:47:53187 /**
188 * @param {Command} command
189 * @param {!Set<string>} itemIds
190 * @return {boolean} True if the command should be visible in the context
191 * menu.
192 */
193 isCommandVisible_: function(command, itemIds) {
194 switch (command) {
195 case Command.EDIT:
tsergeant2437f992017-06-13 23:54:29196 return itemIds.size == 1 && this.globalCanEdit_;
tsergeantb7253e92017-07-04 02:59:22197 case Command.COPY_URL:
tsergeant2437f992017-06-13 23:54:29198 return this.isSingleBookmark_(itemIds);
tsergeant2db36262017-05-15 02:47:53199 case Command.DELETE:
tsergeant2437f992017-06-13 23:54:29200 return itemIds.size > 0 && this.globalCanEdit_;
tsergeant2db36262017-05-15 02:47:53201 case Command.OPEN_NEW_TAB:
202 case Command.OPEN_NEW_WINDOW:
203 case Command.OPEN_INCOGNITO:
204 return itemIds.size > 0;
205 default:
206 return false;
tsergeantf1ffc892017-05-05 07:43:43207 }
tsergeant2db36262017-05-15 02:47:53208 },
tsergeantf1ffc892017-05-05 07:43:43209
tsergeant2db36262017-05-15 02:47:53210 /**
211 * @param {Command} command
212 * @param {!Set<string>} itemIds
213 * @return {boolean} True if the command should be clickable in the context
214 * menu.
215 */
216 isCommandEnabled_: function(command, itemIds) {
217 switch (command) {
tsergeant2437f992017-06-13 23:54:29218 case Command.EDIT:
219 case Command.DELETE:
220 var state = this.getState();
221 return !this.containsMatchingNode_(itemIds, function(node) {
222 return !bookmarks.util.canEditNode(state, node.id);
223 });
tsergeant2db36262017-05-15 02:47:53224 case Command.OPEN_NEW_TAB:
225 case Command.OPEN_NEW_WINDOW:
tsergeant2db36262017-05-15 02:47:53226 return this.expandUrls_(itemIds).length > 0;
tsergeant4707d172017-06-05 05:47:02227 case Command.OPEN_INCOGNITO:
228 return this.expandUrls_(itemIds).length > 0 &&
229 this.getState().prefs.incognitoAvailability !=
230 IncognitoAvailability.DISABLED;
tsergeant2db36262017-05-15 02:47:53231 default:
232 return true;
233 }
234 },
tsergeant13a466462017-05-15 01:21:03235
tsergeant2db36262017-05-15 02:47:53236 /**
237 * @param {Command} command
238 * @param {!Set<string>} itemIds
239 */
240 handle: function(command, itemIds) {
tsergeant0292e51a2017-06-16 03:44:35241 var state = this.getState();
tsergeant2db36262017-05-15 02:47:53242 switch (command) {
243 case Command.EDIT:
244 var id = Array.from(itemIds)[0];
245 /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
tsergeant0292e51a2017-06-16 03:44:35246 .showEditDialog(state.nodes[id]);
tsergeant2db36262017-05-15 02:47:53247 break;
tsergeantb7253e92017-07-04 02:59:22248 case Command.COPY_URL:
tsergeant2db36262017-05-15 02:47:53249 case Command.COPY:
250 var idList = Array.from(itemIds);
251 chrome.bookmarkManagerPrivate.copy(idList, function() {
tsergeantb7253e92017-07-04 02:59:22252 var labelPromise;
253 if (command == Command.COPY_URL) {
254 labelPromise =
255 Promise.resolve(loadTimeData.getString('toastUrlCopied'));
256 } else {
257 labelPromise = cr.sendWithPromise(
258 'getPluralString', 'toastItemsCopied', idList.length);
259 }
260
261 this.showTitleToast_(
262 labelPromise, state.nodes[idList[0]].title, false);
263 }.bind(this));
tsergeant2db36262017-05-15 02:47:53264 break;
265 case Command.DELETE:
calamityefe477352017-06-07 06:44:58266 var idList = Array.from(this.minimizeDeletionSet_(itemIds));
tsergeant0292e51a2017-06-16 03:44:35267 var title = state.nodes[idList[0]].title;
calamitye0917642017-06-09 07:34:35268 var labelPromise = cr.sendWithPromise(
269 'getPluralString', 'toastItemsDeleted', idList.length);
calamityefe477352017-06-07 06:44:58270 chrome.bookmarkManagerPrivate.removeTrees(idList, function() {
tsergeantb7253e92017-07-04 02:59:22271 this.showTitleToast_(labelPromise, title, true);
calamityefe477352017-06-07 06:44:58272 }.bind(this));
tsergeant2db36262017-05-15 02:47:53273 break;
calamity2d4b5502017-05-29 03:57:58274 case Command.UNDO:
275 chrome.bookmarkManagerPrivate.undo();
calamityefe477352017-06-07 06:44:58276 bookmarks.ToastManager.getInstance().hide();
calamity2d4b5502017-05-29 03:57:58277 break;
278 case Command.REDO:
279 chrome.bookmarkManagerPrivate.redo();
280 break;
tsergeant2db36262017-05-15 02:47:53281 case Command.OPEN_NEW_TAB:
282 case Command.OPEN_NEW_WINDOW:
283 case Command.OPEN_INCOGNITO:
284 this.openUrls_(this.expandUrls_(itemIds), command);
285 break;
tsergeant6c5ad90a2017-05-19 14:12:34286 case Command.OPEN:
287 var isFolder = itemIds.size == 1 &&
288 this.containsMatchingNode_(itemIds, function(node) {
289 return !node.url;
290 });
291 if (isFolder) {
292 var folderId = Array.from(itemIds)[0];
tsergeant0292e51a2017-06-16 03:44:35293 this.dispatch(
294 bookmarks.actions.selectFolder(folderId, state.nodes));
tsergeant6c5ad90a2017-05-19 14:12:34295 } else {
296 this.openUrls_(this.expandUrls_(itemIds), command);
297 }
298 break;
tsergeant679159f2017-06-16 06:58:41299 case Command.SELECT_ALL:
300 var displayedIds = bookmarks.util.getDisplayedList(state);
301 this.dispatch(bookmarks.actions.selectAll(displayedIds, state));
302 break;
303 case Command.DESELECT_ALL:
304 this.dispatch(bookmarks.actions.deselectItems());
305 break;
tsergeantb7253e92017-07-04 02:59:22306 case Command.CUT:
307 chrome.bookmarkManagerPrivate.cut(Array.from(itemIds));
308 break;
309 case Command.PASTE:
310 var selectedFolder = state.selectedFolder;
311 var selectedItems = state.selection.items;
312 chrome.bookmarkManagerPrivate.paste(
313 selectedFolder, Array.from(selectedItems));
314 break;
tsergeant6c5ad90a2017-05-19 14:12:34315 default:
316 assert(false);
tsergeant2db36262017-05-15 02:47:53317 }
318 },
tsergeant13a466462017-05-15 01:21:03319
tsergeant6c3a6df2017-06-06 23:53:02320 /**
tsergeant0292e51a2017-06-16 03:44:35321 * @param {!Event} e
tsergeant6c3a6df2017-06-06 23:53:02322 * @param {!Set<string>} itemIds
323 * @return {boolean} True if the event was handled, triggering a keyboard
324 * shortcut.
325 */
326 handleKeyEvent: function(e, itemIds) {
327 for (var commandName in this.shortcuts_) {
328 var shortcut = this.shortcuts_[commandName];
tsergeant0292e51a2017-06-16 03:44:35329 if (shortcut.matchesEvent(e) && this.canExecute(commandName, itemIds)) {
tsergeant6c3a6df2017-06-06 23:53:02330 this.handle(commandName, itemIds);
331
332 e.stopPropagation();
333 e.preventDefault();
334 return true;
335 }
336 }
337
338 return false;
339 },
340
tsergeant2db36262017-05-15 02:47:53341 ////////////////////////////////////////////////////////////////////////////
342 // Private functions:
343
344 /**
tsergeant0292e51a2017-06-16 03:44:35345 * Register a keyboard shortcut for a command.
346 * @param {Command} command Command that the shortcut will trigger.
347 * @param {string} shortcut Keyboard shortcut, using the syntax of
348 * cr/ui/command.js.
349 * @param {string=} macShortcut If set, enables a replacement shortcut for
350 * Mac.
351 */
352 addShortcut_: function(command, shortcut, macShortcut) {
353 var shortcut = (cr.isMac && macShortcut) ? macShortcut : shortcut;
354 this.shortcuts_[command] = new cr.ui.KeyboardShortcutList(shortcut);
355 },
356
357 /**
tsergeant2db36262017-05-15 02:47:53358 * Minimize the set of |itemIds| by removing any node which has an ancestor
359 * node already in the set. This ensures that instead of trying to delete
360 * both a node and its descendant, we will only try to delete the topmost
361 * node, preventing an error in the bookmarkManagerPrivate.removeTrees API
362 * call.
363 * @param {!Set<string>} itemIds
364 * @return {!Set<string>}
365 */
366 minimizeDeletionSet_: function(itemIds) {
367 var minimizedSet = new Set();
368 var nodes = this.getState().nodes;
369 itemIds.forEach(function(itemId) {
370 var currentId = itemId;
371 while (currentId != ROOT_NODE_ID) {
372 currentId = assert(nodes[currentId].parentId);
373 if (itemIds.has(currentId))
374 return;
375 }
376 minimizedSet.add(itemId);
tsergeant13a466462017-05-15 01:21:03377 });
tsergeant2db36262017-05-15 02:47:53378 return minimizedSet;
379 },
tsergeant13a466462017-05-15 01:21:03380
tsergeant2db36262017-05-15 02:47:53381 /**
tsergeant7fb9e13f2017-06-26 06:35:19382 * Open the given |urls| in response to a |command|. May show a confirmation
383 * dialog before opening large numbers of URLs.
tsergeant2db36262017-05-15 02:47:53384 * @param {!Array<string>} urls
385 * @param {Command} command
386 * @private
387 */
388 openUrls_: function(urls, command) {
389 assert(
tsergeant6c5ad90a2017-05-19 14:12:34390 command == Command.OPEN || command == Command.OPEN_NEW_TAB ||
tsergeant2db36262017-05-15 02:47:53391 command == Command.OPEN_NEW_WINDOW ||
392 command == Command.OPEN_INCOGNITO);
tsergeant13a466462017-05-15 01:21:03393
tsergeant2db36262017-05-15 02:47:53394 if (urls.length == 0)
tsergeantfad224f2017-05-05 04:42:09395 return;
tsergeantfad224f2017-05-05 04:42:09396
tsergeant7fb9e13f2017-06-26 06:35:19397 var openUrlsCallback = function() {
398 var incognito = command == Command.OPEN_INCOGNITO;
399 if (command == Command.OPEN_NEW_WINDOW || incognito) {
400 chrome.windows.create({url: urls, incognito: incognito});
401 } else {
402 if (command == Command.OPEN)
403 chrome.tabs.create({url: urls.shift(), active: true});
404 urls.forEach(function(url) {
405 chrome.tabs.create({url: url, active: false});
406 });
407 }
408 };
409
410 if (urls.length <= OPEN_CONFIRMATION_LIMIT) {
411 openUrlsCallback();
412 return;
tsergeant2db36262017-05-15 02:47:53413 }
tsergeant7fb9e13f2017-06-26 06:35:19414
415 this.confirmOpenCallback_ = openUrlsCallback;
416 var dialog = this.$.openDialog.get();
417 dialog.querySelector('.body').textContent =
418 loadTimeData.getStringF('openDialogBody', urls.length);
calamity57b2e8fd2017-06-29 18:46:58419
420 bookmarks.DialogFocusManager.getInstance().showDialog(
421 this.$.openDialog.get());
tsergeant2db36262017-05-15 02:47:53422 },
tsergeant77365182017-05-05 04:02:33423
tsergeant2db36262017-05-15 02:47:53424 /**
425 * Returns all URLs in the given set of nodes and their immediate children.
426 * Note that these will be ordered by insertion order into the |itemIds|
427 * set, and that it is possible to duplicate a URL by passing in both the
428 * parent ID and child ID.
429 * @param {!Set<string>} itemIds
430 * @return {!Array<string>}
431 * @private
432 */
433 expandUrls_: function(itemIds) {
434 var urls = [];
435 var nodes = this.getState().nodes;
tsergeant13a466462017-05-15 01:21:03436
tsergeant2db36262017-05-15 02:47:53437 itemIds.forEach(function(id) {
438 var node = nodes[id];
439 if (node.url) {
440 urls.push(node.url);
441 } else {
442 node.children.forEach(function(childId) {
443 var childNode = nodes[childId];
444 if (childNode.url)
445 urls.push(childNode.url);
446 });
447 }
448 });
tsergeant13a466462017-05-15 01:21:03449
tsergeant2db36262017-05-15 02:47:53450 return urls;
451 },
452
453 /**
454 * @param {!Set<string>} itemIds
455 * @param {function(BookmarkNode):boolean} predicate
456 * @return {boolean} True if any node in |itemIds| returns true for
457 * |predicate|.
458 */
459 containsMatchingNode_: function(itemIds, predicate) {
460 var nodes = this.getState().nodes;
461
462 return Array.from(itemIds).some(function(id) {
463 return predicate(nodes[id]);
464 });
465 },
466
467 /**
tsergeant2437f992017-06-13 23:54:29468 * @param {!Set<string>} itemIds
469 * @return {boolean} True if |itemIds| is a single bookmark (non-folder)
470 * node.
471 */
472 isSingleBookmark_: function(itemIds) {
473 return itemIds.size == 1 &&
474 this.containsMatchingNode_(itemIds, function(node) {
475 return !!node.url;
476 });
477 },
478
479 /**
tsergeant2db36262017-05-15 02:47:53480 * @param {Command} command
481 * @return {string}
482 * @private
483 */
484 getCommandLabel_: function(command) {
485 var multipleNodes = this.menuIds_.size > 1 ||
486 this.containsMatchingNode_(this.menuIds_, function(node) {
487 return !node.url;
488 });
489 var label;
490 switch (command) {
491 case Command.EDIT:
tsergeant4707d172017-06-05 05:47:02492 if (this.menuIds_.size != 1)
tsergeant2db36262017-05-15 02:47:53493 return '';
494
495 var id = Array.from(this.menuIds_)[0];
496 var itemUrl = this.getState().nodes[id].url;
497 label = itemUrl ? 'menuEdit' : 'menuRename';
498 break;
tsergeantb7253e92017-07-04 02:59:22499 case Command.COPY_URL:
tsergeant2db36262017-05-15 02:47:53500 label = 'menuCopyURL';
501 break;
502 case Command.DELETE:
503 label = 'menuDelete';
504 break;
505 case Command.OPEN_NEW_TAB:
506 label = multipleNodes ? 'menuOpenAllNewTab' : 'menuOpenNewTab';
507 break;
508 case Command.OPEN_NEW_WINDOW:
509 label = multipleNodes ? 'menuOpenAllNewWindow' : 'menuOpenNewWindow';
510 break;
511 case Command.OPEN_INCOGNITO:
512 label = multipleNodes ? 'menuOpenAllIncognito' : 'menuOpenIncognito';
513 break;
514 }
515
516 return loadTimeData.getString(assert(label));
517 },
518
519 /**
520 * @param {Command} command
calamity64e2012a2017-06-21 09:57:14521 * @return {string}
522 * @private
523 */
524 getCommandSublabel_: function(command) {
525 var multipleNodes = this.menuIds_.size > 1 ||
526 this.containsMatchingNode_(this.menuIds_, function(node) {
527 return !node.url;
528 });
529 switch (command) {
530 case Command.OPEN_NEW_TAB:
531 var urls = this.expandUrls_(this.menuIds_);
532 return multipleNodes && urls.length > 0 ? String(urls.length) : '';
533 default:
534 return '';
535 }
536 },
537
538 /** @private */
539 onMenuIdsChanged_: function() {
540 if (!this.menuIds_)
541 return;
542
543 this.hasAnySublabel_ = this.menuCommands_.some(function(command) {
544 return this.getCommandSublabel_(command) != '';
545 }.bind(this));
546 },
547
548 /**
549 * @param {Command} command
tsergeant2db36262017-05-15 02:47:53550 * @return {boolean}
551 * @private
552 */
tsergeant2437f992017-06-13 23:54:29553 showDividerAfter_: function(command, itemIds) {
554 return command == Command.DELETE &&
555 (this.globalCanEdit_ || this.isSingleBookmark_(itemIds));
tsergeant2db36262017-05-15 02:47:53556 },
tsergeantb7253e92017-07-04 02:59:22557
558 /**
559 * Show a toast with a bookmark |title| inserted into a label, with the
560 * title ellipsised if necessary.
561 * @param {!Promise<string>} labelPromise Promise which resolves with the
562 * label for the toast.
563 * @param {string} title Bookmark title to insert.
564 * @param {boolean} canUndo If true, shows an undo button in the toast.
565 * @private
566 */
567 showTitleToast_: function(labelPromise, title, canUndo) {
568 labelPromise.then(function(label) {
569 var pieces = loadTimeData.getSubstitutedStringPieces(label, title)
570 .map(function(p) {
571 // Make the bookmark name collapsible.
572 p.collapsible = !!p.arg;
573 return p;
574 });
575
576 bookmarks.ToastManager.getInstance().showForStringPieces(
577 pieces, canUndo);
578 });
579 },
580
581 ////////////////////////////////////////////////////////////////////////////
582 // Event handlers:
583
584 /**
585 * @param {Event} e
586 * @private
587 */
588 onOpenItemMenu_: function(e) {
589 if (e.detail.targetElement) {
590 this.openCommandMenuAtElement(e.detail.targetElement);
591 } else {
592 this.openCommandMenuAtPosition(e.detail.x, e.detail.y);
593 }
594 },
595
596 /**
597 * @param {Event} e
598 * @private
599 */
600 onCommandClick_: function(e) {
601 this.handle(
602 e.currentTarget.getAttribute('command'), assert(this.menuIds_));
603 this.closeCommandMenu();
604 },
605
606 /**
607 * @param {!Event} e
608 * @private
609 */
610 onKeydown_: function(e) {
611 var selection = this.getState().selection.items;
612 if (e.target == document.body)
613 this.handleKeyEvent(e, selection);
614 },
615
616 /**
617 * Close the menu on mousedown so clicks can propagate to the underlying UI.
618 * This allows the user to right click the list while a context menu is
619 * showing and get another context menu.
620 * @param {Event} e
621 * @private
622 */
623 onMenuMousedown_: function(e) {
624 if (e.path[0] != this.$.dropdown.getIfExists())
625 return;
626
627 this.closeCommandMenu();
628 },
629
630 /** @private */
631 onOpenCancelTap_: function() {
632 this.$.openDialog.get().cancel();
633 },
634
635 /** @private */
636 onOpenConfirmTap_: function() {
637 this.confirmOpenCallback_();
638 this.$.openDialog.get().close();
639 },
tsergeant2db36262017-05-15 02:47:53640 });
641
642 /** @private {bookmarks.CommandManager} */
643 CommandManager.instance_ = null;
644
645 /** @return {!bookmarks.CommandManager} */
646 CommandManager.getInstance = function() {
647 return assert(CommandManager.instance_);
648 };
649
650 return {
651 CommandManager: CommandManager,
652 };
tsergeant77365182017-05-05 04:02:33653});