blob: 95d77a6758bc4d4a67d12277a696c58ebf70ec48 [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,
25 Command.COPY,
26 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
tsergeant2db36262017-05-15 02:47:5351 attached: function() {
52 assert(CommandManager.instance_ == null);
53 CommandManager.instance_ = this;
tsergeant77365182017-05-05 04:02:3354
tsergeant2437f992017-06-13 23:54:2955 this.watch('globalCanEdit_', function(state) {
56 return state.prefs.canEdit;
57 });
58 this.updateFromStore();
59
tsergeant2db36262017-05-15 02:47:5360 /** @private {function(!Event)} */
61 this.boundOnOpenItemMenu_ = this.onOpenItemMenu_.bind(this);
62 document.addEventListener('open-item-menu', this.boundOnOpenItemMenu_);
tsergeantfad224f2017-05-05 04:42:0963
calamityefe477352017-06-07 06:44:5864 /** @private {function()} */
65 this.boundOnCommandUndo_ = function() {
66 this.handle(Command.UNDO, new Set());
67 }.bind(this);
68 document.addEventListener('command-undo', this.boundOnCommandUndo_);
69
tsergeant2db36262017-05-15 02:47:5370 /** @private {function(!Event)} */
71 this.boundOnKeydown_ = this.onKeydown_.bind(this);
72 document.addEventListener('keydown', this.boundOnKeydown_);
tsergeantfad224f2017-05-05 04:42:0973
tsergeant0292e51a2017-06-16 03:44:3574 /** @private {Object<Command, cr.ui.KeyboardShortcutList>} */
tsergeant2db36262017-05-15 02:47:5375 this.shortcuts_ = {};
tsergeant0292e51a2017-06-16 03:44:3576
77 this.addShortcut_(Command.EDIT, 'F2', 'Enter');
78 this.addShortcut_(Command.COPY, 'Ctrl|c', 'Meta|c');
79 this.addShortcut_(Command.DELETE, 'Delete', 'Delete Backspace');
80
81 this.addShortcut_(Command.OPEN, 'Enter', 'Meta|ArrowDown Meta|o');
82 this.addShortcut_(Command.OPEN_NEW_TAB, 'Ctrl|Enter', 'Meta|Enter');
83 this.addShortcut_(Command.OPEN_NEW_WINDOW, 'Shift|Enter');
84
85 this.addShortcut_(Command.UNDO, 'Ctrl|z', 'Meta|z');
86 this.addShortcut_(Command.REDO, 'Ctrl|y Ctrl|Shift|Z', 'Meta|Shift|Z');
tsergeant679159f2017-06-16 06:58:4187
88 this.addShortcut_(Command.SELECT_ALL, 'Ctrl|a', 'Meta|a');
89 this.addShortcut_(Command.DESELECT_ALL, 'Escape');
tsergeant2db36262017-05-15 02:47:5390 },
tsergeant77365182017-05-05 04:02:3391
tsergeant2db36262017-05-15 02:47:5392 detached: function() {
93 CommandManager.instance_ = null;
94 document.removeEventListener('open-item-menu', this.boundOnOpenItemMenu_);
calamityefe477352017-06-07 06:44:5895 document.removeEventListener('command-undo', this.boundOnCommandUndo_);
tsergeant2db36262017-05-15 02:47:5396 document.removeEventListener('keydown', this.boundOnKeydown_);
97 },
tsergeant77365182017-05-05 04:02:3398
tsergeant2db36262017-05-15 02:47:5399 /**
100 * Display the command context menu at (|x|, |y|) in window co-ordinates.
tsergeanta274e0412017-06-16 05:22:28101 * Commands will execute on |items| if given, or on the currently selected
102 * items.
tsergeant2db36262017-05-15 02:47:53103 * @param {number} x
104 * @param {number} y
tsergeanta274e0412017-06-16 05:22:28105 * @param {Set<string>=} items
tsergeant2db36262017-05-15 02:47:53106 */
tsergeanta274e0412017-06-16 05:22:28107 openCommandMenuAtPosition: function(x, y, items) {
108 this.menuIds_ = items || this.getState().selection.items;
tsergeant9b9aa15f2017-06-22 03:22:27109 var dropdown =
110 /** @type {!CrActionMenuElement} */ (this.$.dropdown.get());
111 // Ensure that the menu is fully rendered before trying to position it.
112 Polymer.dom.flush();
113 dropdown.showAtPosition({top: y, left: x});
tsergeant2db36262017-05-15 02:47:53114 },
tsergeant77365182017-05-05 04:02:33115
tsergeant2db36262017-05-15 02:47:53116 /**
117 * Display the command context menu positioned to cover the |target|
118 * element. Commands will execute on the currently selected items.
119 * @param {!Element} target
120 */
121 openCommandMenuAtElement: function(target) {
122 this.menuIds_ = this.getState().selection.items;
tsergeant9b9aa15f2017-06-22 03:22:27123 var dropdown =
124 /** @type {!CrActionMenuElement} */ (this.$.dropdown.get());
125 // Ensure that the menu is fully rendered before trying to position it.
126 Polymer.dom.flush();
127 dropdown.showAt(target);
tsergeant2db36262017-05-15 02:47:53128 },
tsergeant77365182017-05-05 04:02:33129
tsergeant2db36262017-05-15 02:47:53130 closeCommandMenu: function() {
tsergeant4707d172017-06-05 05:47:02131 this.menuIds_ = new Set();
tsergeant9b9aa15f2017-06-22 03:22:27132 /** @type {!CrActionMenuElement} */ (this.$.dropdown.get()).close();
tsergeant2db36262017-05-15 02:47:53133 },
tsergeant77365182017-05-05 04:02:33134
tsergeant2db36262017-05-15 02:47:53135 ////////////////////////////////////////////////////////////////////////////
136 // Command handlers:
tsergeant77365182017-05-05 04:02:33137
tsergeant2db36262017-05-15 02:47:53138 /**
139 * Determine if the |command| can be executed with the given |itemIds|.
140 * Commands which appear in the context menu should be implemented
141 * separately using `isCommandVisible_` and `isCommandEnabled_`.
142 * @param {Command} command
143 * @param {!Set<string>} itemIds
144 * @return {boolean}
145 */
146 canExecute: function(command, itemIds) {
tsergeant6c5ad90a2017-05-19 14:12:34147 switch (command) {
148 case Command.OPEN:
149 return itemIds.size > 0;
calamity2d4b5502017-05-29 03:57:58150 case Command.UNDO:
151 case Command.REDO:
tsergeant2437f992017-06-13 23:54:29152 return this.globalCanEdit_;
tsergeant679159f2017-06-16 06:58:41153 case Command.SELECT_ALL:
154 case Command.DESELECT_ALL:
155 return true;
tsergeant6c5ad90a2017-05-19 14:12:34156 default:
157 return this.isCommandVisible_(command, itemIds) &&
158 this.isCommandEnabled_(command, itemIds);
159 }
tsergeant2db36262017-05-15 02:47:53160 },
tsergeant13a466462017-05-15 01:21:03161
tsergeant2db36262017-05-15 02:47:53162 /**
163 * @param {Command} command
164 * @param {!Set<string>} itemIds
165 * @return {boolean} True if the command should be visible in the context
166 * menu.
167 */
168 isCommandVisible_: function(command, itemIds) {
169 switch (command) {
170 case Command.EDIT:
tsergeant2437f992017-06-13 23:54:29171 return itemIds.size == 1 && this.globalCanEdit_;
tsergeant2db36262017-05-15 02:47:53172 case Command.COPY:
tsergeant2437f992017-06-13 23:54:29173 return this.isSingleBookmark_(itemIds);
tsergeant2db36262017-05-15 02:47:53174 case Command.DELETE:
tsergeant2437f992017-06-13 23:54:29175 return itemIds.size > 0 && this.globalCanEdit_;
tsergeant2db36262017-05-15 02:47:53176 case Command.OPEN_NEW_TAB:
177 case Command.OPEN_NEW_WINDOW:
178 case Command.OPEN_INCOGNITO:
179 return itemIds.size > 0;
180 default:
181 return false;
tsergeantf1ffc892017-05-05 07:43:43182 }
tsergeant2db36262017-05-15 02:47:53183 },
tsergeantf1ffc892017-05-05 07:43:43184
tsergeant2db36262017-05-15 02:47:53185 /**
186 * @param {Command} command
187 * @param {!Set<string>} itemIds
188 * @return {boolean} True if the command should be clickable in the context
189 * menu.
190 */
191 isCommandEnabled_: function(command, itemIds) {
192 switch (command) {
tsergeant2437f992017-06-13 23:54:29193 case Command.EDIT:
194 case Command.DELETE:
195 var state = this.getState();
196 return !this.containsMatchingNode_(itemIds, function(node) {
197 return !bookmarks.util.canEditNode(state, node.id);
198 });
tsergeant2db36262017-05-15 02:47:53199 case Command.OPEN_NEW_TAB:
200 case Command.OPEN_NEW_WINDOW:
tsergeant2db36262017-05-15 02:47:53201 return this.expandUrls_(itemIds).length > 0;
tsergeant4707d172017-06-05 05:47:02202 case Command.OPEN_INCOGNITO:
203 return this.expandUrls_(itemIds).length > 0 &&
204 this.getState().prefs.incognitoAvailability !=
205 IncognitoAvailability.DISABLED;
tsergeant2db36262017-05-15 02:47:53206 default:
207 return true;
208 }
209 },
tsergeant13a466462017-05-15 01:21:03210
tsergeant2db36262017-05-15 02:47:53211 /**
212 * @param {Command} command
213 * @param {!Set<string>} itemIds
214 */
215 handle: function(command, itemIds) {
tsergeant0292e51a2017-06-16 03:44:35216 var state = this.getState();
tsergeant2db36262017-05-15 02:47:53217 switch (command) {
218 case Command.EDIT:
219 var id = Array.from(itemIds)[0];
220 /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
tsergeant0292e51a2017-06-16 03:44:35221 .showEditDialog(state.nodes[id]);
tsergeant2db36262017-05-15 02:47:53222 break;
223 case Command.COPY:
224 var idList = Array.from(itemIds);
225 chrome.bookmarkManagerPrivate.copy(idList, function() {
calamityefe477352017-06-07 06:44:58226 bookmarks.ToastManager.getInstance().show(
227 loadTimeData.getString('toastUrlCopied'), false);
tsergeant2db36262017-05-15 02:47:53228 });
229 break;
230 case Command.DELETE:
calamityefe477352017-06-07 06:44:58231 var idList = Array.from(this.minimizeDeletionSet_(itemIds));
tsergeant0292e51a2017-06-16 03:44:35232 var title = state.nodes[idList[0]].title;
calamitye0917642017-06-09 07:34:35233 var labelPromise = cr.sendWithPromise(
234 'getPluralString', 'toastItemsDeleted', idList.length);
calamityefe477352017-06-07 06:44:58235 chrome.bookmarkManagerPrivate.removeTrees(idList, function() {
236 labelPromise.then(function(label) {
calamitye0917642017-06-09 07:34:35237 var pieces = loadTimeData.getSubstitutedStringPieces(label, title)
238 .map(function(p) {
239 // Make the bookmark name collapsible.
240 p.collapsible = !!p.arg;
241 return p;
242 });
243 bookmarks.ToastManager.getInstance().showForStringPieces(
244 pieces, true);
245 }.bind(this));
calamityefe477352017-06-07 06:44:58246 }.bind(this));
tsergeant2db36262017-05-15 02:47:53247 break;
calamity2d4b5502017-05-29 03:57:58248 case Command.UNDO:
249 chrome.bookmarkManagerPrivate.undo();
calamityefe477352017-06-07 06:44:58250 bookmarks.ToastManager.getInstance().hide();
calamity2d4b5502017-05-29 03:57:58251 break;
252 case Command.REDO:
253 chrome.bookmarkManagerPrivate.redo();
254 break;
tsergeant2db36262017-05-15 02:47:53255 case Command.OPEN_NEW_TAB:
256 case Command.OPEN_NEW_WINDOW:
257 case Command.OPEN_INCOGNITO:
258 this.openUrls_(this.expandUrls_(itemIds), command);
259 break;
tsergeant6c5ad90a2017-05-19 14:12:34260 case Command.OPEN:
261 var isFolder = itemIds.size == 1 &&
262 this.containsMatchingNode_(itemIds, function(node) {
263 return !node.url;
264 });
265 if (isFolder) {
266 var folderId = Array.from(itemIds)[0];
tsergeant0292e51a2017-06-16 03:44:35267 this.dispatch(
268 bookmarks.actions.selectFolder(folderId, state.nodes));
tsergeant6c5ad90a2017-05-19 14:12:34269 } else {
270 this.openUrls_(this.expandUrls_(itemIds), command);
271 }
272 break;
tsergeant679159f2017-06-16 06:58:41273 case Command.SELECT_ALL:
274 var displayedIds = bookmarks.util.getDisplayedList(state);
275 this.dispatch(bookmarks.actions.selectAll(displayedIds, state));
276 break;
277 case Command.DESELECT_ALL:
278 this.dispatch(bookmarks.actions.deselectItems());
279 break;
tsergeant6c5ad90a2017-05-19 14:12:34280 default:
281 assert(false);
tsergeant2db36262017-05-15 02:47:53282 }
283 },
tsergeant13a466462017-05-15 01:21:03284
tsergeant6c3a6df2017-06-06 23:53:02285 /**
tsergeant0292e51a2017-06-16 03:44:35286 * @param {!Event} e
tsergeant6c3a6df2017-06-06 23:53:02287 * @param {!Set<string>} itemIds
288 * @return {boolean} True if the event was handled, triggering a keyboard
289 * shortcut.
290 */
291 handleKeyEvent: function(e, itemIds) {
292 for (var commandName in this.shortcuts_) {
293 var shortcut = this.shortcuts_[commandName];
tsergeant0292e51a2017-06-16 03:44:35294 if (shortcut.matchesEvent(e) && this.canExecute(commandName, itemIds)) {
tsergeant6c3a6df2017-06-06 23:53:02295 this.handle(commandName, itemIds);
296
297 e.stopPropagation();
298 e.preventDefault();
299 return true;
300 }
301 }
302
303 return false;
304 },
305
tsergeant2db36262017-05-15 02:47:53306 ////////////////////////////////////////////////////////////////////////////
307 // Private functions:
308
309 /**
tsergeant0292e51a2017-06-16 03:44:35310 * Register a keyboard shortcut for a command.
311 * @param {Command} command Command that the shortcut will trigger.
312 * @param {string} shortcut Keyboard shortcut, using the syntax of
313 * cr/ui/command.js.
314 * @param {string=} macShortcut If set, enables a replacement shortcut for
315 * Mac.
316 */
317 addShortcut_: function(command, shortcut, macShortcut) {
318 var shortcut = (cr.isMac && macShortcut) ? macShortcut : shortcut;
319 this.shortcuts_[command] = new cr.ui.KeyboardShortcutList(shortcut);
320 },
321
322 /**
tsergeant2db36262017-05-15 02:47:53323 * Minimize the set of |itemIds| by removing any node which has an ancestor
324 * node already in the set. This ensures that instead of trying to delete
325 * both a node and its descendant, we will only try to delete the topmost
326 * node, preventing an error in the bookmarkManagerPrivate.removeTrees API
327 * call.
328 * @param {!Set<string>} itemIds
329 * @return {!Set<string>}
330 */
331 minimizeDeletionSet_: function(itemIds) {
332 var minimizedSet = new Set();
333 var nodes = this.getState().nodes;
334 itemIds.forEach(function(itemId) {
335 var currentId = itemId;
336 while (currentId != ROOT_NODE_ID) {
337 currentId = assert(nodes[currentId].parentId);
338 if (itemIds.has(currentId))
339 return;
340 }
341 minimizedSet.add(itemId);
tsergeant13a466462017-05-15 01:21:03342 });
tsergeant2db36262017-05-15 02:47:53343 return minimizedSet;
344 },
tsergeant13a466462017-05-15 01:21:03345
tsergeant2db36262017-05-15 02:47:53346 /**
347 * @param {!Array<string>} urls
348 * @param {Command} command
349 * @private
350 */
351 openUrls_: function(urls, command) {
352 assert(
tsergeant6c5ad90a2017-05-19 14:12:34353 command == Command.OPEN || command == Command.OPEN_NEW_TAB ||
tsergeant2db36262017-05-15 02:47:53354 command == Command.OPEN_NEW_WINDOW ||
355 command == Command.OPEN_INCOGNITO);
tsergeant13a466462017-05-15 01:21:03356
tsergeant2db36262017-05-15 02:47:53357 if (urls.length == 0)
tsergeantfad224f2017-05-05 04:42:09358 return;
tsergeantfad224f2017-05-05 04:42:09359
tsergeant2db36262017-05-15 02:47:53360 var incognito = command == Command.OPEN_INCOGNITO;
361 if (command == Command.OPEN_NEW_WINDOW || incognito) {
362 chrome.windows.create({url: urls, incognito: incognito});
363 } else {
tsergeant6c5ad90a2017-05-19 14:12:34364 if (command == Command.OPEN)
365 chrome.tabs.create({url: urls.shift(), active: true});
tsergeant2db36262017-05-15 02:47:53366 urls.forEach(function(url) {
367 chrome.tabs.create({url: url, active: false});
tsergeant13a466462017-05-15 01:21:03368 });
tsergeant2db36262017-05-15 02:47:53369 }
370 },
tsergeant77365182017-05-05 04:02:33371
tsergeant2db36262017-05-15 02:47:53372 /**
373 * Returns all URLs in the given set of nodes and their immediate children.
374 * Note that these will be ordered by insertion order into the |itemIds|
375 * set, and that it is possible to duplicate a URL by passing in both the
376 * parent ID and child ID.
377 * @param {!Set<string>} itemIds
378 * @return {!Array<string>}
379 * @private
380 */
381 expandUrls_: function(itemIds) {
382 var urls = [];
383 var nodes = this.getState().nodes;
tsergeant13a466462017-05-15 01:21:03384
tsergeant2db36262017-05-15 02:47:53385 itemIds.forEach(function(id) {
386 var node = nodes[id];
387 if (node.url) {
388 urls.push(node.url);
389 } else {
390 node.children.forEach(function(childId) {
391 var childNode = nodes[childId];
392 if (childNode.url)
393 urls.push(childNode.url);
394 });
395 }
396 });
tsergeant13a466462017-05-15 01:21:03397
tsergeant2db36262017-05-15 02:47:53398 return urls;
399 },
400
401 /**
402 * @param {!Set<string>} itemIds
403 * @param {function(BookmarkNode):boolean} predicate
404 * @return {boolean} True if any node in |itemIds| returns true for
405 * |predicate|.
406 */
407 containsMatchingNode_: function(itemIds, predicate) {
408 var nodes = this.getState().nodes;
409
410 return Array.from(itemIds).some(function(id) {
411 return predicate(nodes[id]);
412 });
413 },
414
415 /**
tsergeant2437f992017-06-13 23:54:29416 * @param {!Set<string>} itemIds
417 * @return {boolean} True if |itemIds| is a single bookmark (non-folder)
418 * node.
419 */
420 isSingleBookmark_: function(itemIds) {
421 return itemIds.size == 1 &&
422 this.containsMatchingNode_(itemIds, function(node) {
423 return !!node.url;
424 });
425 },
426
427 /**
tsergeant2db36262017-05-15 02:47:53428 * @param {Event} e
429 * @private
430 */
431 onOpenItemMenu_: function(e) {
432 if (e.detail.targetElement) {
433 this.openCommandMenuAtElement(e.detail.targetElement);
434 } else {
435 this.openCommandMenuAtPosition(e.detail.x, e.detail.y);
436 }
437 },
438
439 /**
440 * @param {Event} e
441 * @private
442 */
443 onCommandClick_: function(e) {
calamity64e2012a2017-06-21 09:57:14444 this.handle(
445 e.currentTarget.getAttribute('command'), assert(this.menuIds_));
tsergeant4707d172017-06-05 05:47:02446 this.closeCommandMenu();
tsergeant2db36262017-05-15 02:47:53447 },
448
449 /**
450 * @param {!Event} e
451 * @private
452 */
453 onKeydown_: function(e) {
454 var selection = this.getState().selection.items;
tsergeant6c3a6df2017-06-06 23:53:02455 if (e.target == document.body)
456 this.handleKeyEvent(e, selection);
tsergeant2db36262017-05-15 02:47:53457 },
458
459 /**
460 * Close the menu on mousedown so clicks can propagate to the underlying UI.
461 * This allows the user to right click the list while a context menu is
462 * showing and get another context menu.
463 * @param {Event} e
464 * @private
465 */
466 onMenuMousedown_: function(e) {
tsergeant9b9aa15f2017-06-22 03:22:27467 if (e.path[0] != this.$.dropdown.getIfExists())
tsergeant2db36262017-05-15 02:47:53468 return;
469
tsergeant4707d172017-06-05 05:47:02470 this.closeCommandMenu();
tsergeant2db36262017-05-15 02:47:53471 },
472
473 /**
474 * @param {Command} command
475 * @return {string}
476 * @private
477 */
478 getCommandLabel_: function(command) {
479 var multipleNodes = this.menuIds_.size > 1 ||
480 this.containsMatchingNode_(this.menuIds_, function(node) {
481 return !node.url;
482 });
483 var label;
484 switch (command) {
485 case Command.EDIT:
tsergeant4707d172017-06-05 05:47:02486 if (this.menuIds_.size != 1)
tsergeant2db36262017-05-15 02:47:53487 return '';
488
489 var id = Array.from(this.menuIds_)[0];
490 var itemUrl = this.getState().nodes[id].url;
491 label = itemUrl ? 'menuEdit' : 'menuRename';
492 break;
493 case Command.COPY:
494 label = 'menuCopyURL';
495 break;
496 case Command.DELETE:
497 label = 'menuDelete';
498 break;
499 case Command.OPEN_NEW_TAB:
500 label = multipleNodes ? 'menuOpenAllNewTab' : 'menuOpenNewTab';
501 break;
502 case Command.OPEN_NEW_WINDOW:
503 label = multipleNodes ? 'menuOpenAllNewWindow' : 'menuOpenNewWindow';
504 break;
505 case Command.OPEN_INCOGNITO:
506 label = multipleNodes ? 'menuOpenAllIncognito' : 'menuOpenIncognito';
507 break;
508 }
509
510 return loadTimeData.getString(assert(label));
511 },
512
513 /**
514 * @param {Command} command
calamity64e2012a2017-06-21 09:57:14515 * @return {string}
516 * @private
517 */
518 getCommandSublabel_: function(command) {
519 var multipleNodes = this.menuIds_.size > 1 ||
520 this.containsMatchingNode_(this.menuIds_, function(node) {
521 return !node.url;
522 });
523 switch (command) {
524 case Command.OPEN_NEW_TAB:
525 var urls = this.expandUrls_(this.menuIds_);
526 return multipleNodes && urls.length > 0 ? String(urls.length) : '';
527 default:
528 return '';
529 }
530 },
531
532 /** @private */
533 onMenuIdsChanged_: function() {
534 if (!this.menuIds_)
535 return;
536
537 this.hasAnySublabel_ = this.menuCommands_.some(function(command) {
538 return this.getCommandSublabel_(command) != '';
539 }.bind(this));
540 },
541
542 /**
543 * @param {Command} command
tsergeant2db36262017-05-15 02:47:53544 * @return {boolean}
545 * @private
546 */
tsergeant2437f992017-06-13 23:54:29547 showDividerAfter_: function(command, itemIds) {
548 return command == Command.DELETE &&
549 (this.globalCanEdit_ || this.isSingleBookmark_(itemIds));
tsergeant2db36262017-05-15 02:47:53550 },
551 });
552
553 /** @private {bookmarks.CommandManager} */
554 CommandManager.instance_ = null;
555
556 /** @return {!bookmarks.CommandManager} */
557 CommandManager.getInstance = function() {
558 return assert(CommandManager.instance_);
559 };
560
561 return {
562 CommandManager: CommandManager,
563 };
tsergeant77365182017-05-05 04:02:33564});