Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(207)

Side by Side Diff: chrome/browser/resources/md_bookmarks/command_manager.js

Issue 2868383004: MD Bookmarks: Make CommandManager available as a singleton (Closed)
Patch Set: Rebase? Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 Polymer({ 5 /**
6 is: 'bookmarks-command-manager', 6 * @fileoverview Element which shows context menus and handles keyboard
7 7 * shortcuts.
8 behaviors: [ 8 */
9 bookmarks.StoreClient, 9 cr.define('bookmarks', function() {
10 ], 10
11 11 var CommandManager = Polymer({
12 properties: { 12 is: 'bookmarks-command-manager',
13 /** @private {!Array<Command>} */ 13
14 menuCommands_: { 14 behaviors: [
15 type: Array, 15 bookmarks.StoreClient,
16 value: function() { 16 ],
17 return [ 17
18 Command.EDIT, 18 properties: {
19 Command.COPY, 19 /** @private {!Array<Command>} */
20 Command.DELETE, 20 menuCommands_: {
21 // <hr> 21 type: Array,
22 Command.OPEN_NEW_TAB, 22 value: function() {
23 Command.OPEN_NEW_WINDOW, 23 return [
24 Command.OPEN_INCOGNITO, 24 Command.EDIT,
25 ]; 25 Command.COPY,
26 Command.DELETE,
27 // <hr>
28 Command.OPEN_NEW_TAB,
29 Command.OPEN_NEW_WINDOW,
30 Command.OPEN_INCOGNITO,
31 ];
32 },
26 }, 33 },
27 }, 34
28 35 /** @type {Set<string>} */
29 /** @type {Set<string>} */ 36 menuIds_: Object,
30 menuIds_: Object, 37 },
31 }, 38
32 39 attached: function() {
33 attached: function() { 40 assert(CommandManager.instance_ == null);
34 /** @private {function(!Event)} */ 41 CommandManager.instance_ = this;
35 this.boundOnOpenItemMenu_ = this.onOpenItemMenu_.bind(this); 42
36 document.addEventListener('open-item-menu', this.boundOnOpenItemMenu_); 43 /** @private {function(!Event)} */
37 44 this.boundOnOpenItemMenu_ = this.onOpenItemMenu_.bind(this);
38 /** @private {function(!Event)} */ 45 document.addEventListener('open-item-menu', this.boundOnOpenItemMenu_);
39 this.boundOnKeydown_ = this.onKeydown_.bind(this); 46
40 document.addEventListener('keydown', this.boundOnKeydown_); 47 /** @private {function(!Event)} */
41 48 this.boundOnKeydown_ = this.onKeydown_.bind(this);
42 /** @private {Object<Command, string>} */ 49 document.addEventListener('keydown', this.boundOnKeydown_);
43 this.shortcuts_ = {}; 50
44 this.shortcuts_[Command.EDIT] = cr.isMac ? 'enter' : 'f2'; 51 /** @private {Object<Command, string>} */
45 this.shortcuts_[Command.COPY] = cr.isMac ? 'meta+c' : 'ctrl+c'; 52 this.shortcuts_ = {};
46 this.shortcuts_[Command.DELETE] = cr.isMac ? 'delete backspace' : 'delete'; 53 this.shortcuts_[Command.EDIT] = cr.isMac ? 'enter' : 'f2';
47 this.shortcuts_[Command.OPEN_NEW_TAB] = 54 this.shortcuts_[Command.COPY] = cr.isMac ? 'meta+c' : 'ctrl+c';
48 cr.isMac ? 'meta+enter' : 'ctrl+enter'; 55 this.shortcuts_[Command.DELETE] =
49 this.shortcuts_[Command.OPEN_NEW_WINDOW] = 'shift+enter'; 56 cr.isMac ? 'delete backspace' : 'delete';
50 }, 57 this.shortcuts_[Command.OPEN_NEW_TAB] =
51 58 cr.isMac ? 'meta+enter' : 'ctrl+enter';
52 detached: function() { 59 this.shortcuts_[Command.OPEN_NEW_WINDOW] = 'shift+enter';
53 document.removeEventListener('open-item-menu', this.boundOnOpenItemMenu_); 60 },
54 document.removeEventListener('keydown', this.boundOnKeydown_); 61
55 }, 62 detached: function() {
56 63 CommandManager.instance_ = null;
57 /** 64 document.removeEventListener('open-item-menu', this.boundOnOpenItemMenu_);
58 * Display the command context menu at (|x|, |y|) in window co-ordinates. 65 document.removeEventListener('keydown', this.boundOnKeydown_);
59 * Commands will execute on the currently selected items. 66 },
60 * @param {number} x 67
61 * @param {number} y 68 /**
62 */ 69 * Display the command context menu at (|x|, |y|) in window co-ordinates.
63 openCommandMenuAtPosition: function(x, y) { 70 * Commands will execute on the currently selected items.
64 this.menuIds_ = this.getState().selection.items; 71 * @param {number} x
65 /** @type {!CrActionMenuElement} */ (this.$.dropdown) 72 * @param {number} y
66 .showAtPosition({top: y, left: x}); 73 */
67 }, 74 openCommandMenuAtPosition: function(x, y) {
68 75 this.menuIds_ = this.getState().selection.items;
69 /** 76 /** @type {!CrActionMenuElement} */ (this.$.dropdown)
70 * Display the command context menu positioned to cover the |target| 77 .showAtPosition({top: y, left: x});
71 * element. Commands will execute on the currently selected items. 78 },
72 * @param {!Element} target 79
73 */ 80 /**
74 openCommandMenuAtElement: function(target) { 81 * Display the command context menu positioned to cover the |target|
75 this.menuIds_ = this.getState().selection.items; 82 * element. Commands will execute on the currently selected items.
76 /** @type {!CrActionMenuElement} */ (this.$.dropdown).showAt(target); 83 * @param {!Element} target
77 }, 84 */
78 85 openCommandMenuAtElement: function(target) {
79 closeCommandMenu: function() { 86 this.menuIds_ = this.getState().selection.items;
80 /** @type {!CrActionMenuElement} */ (this.$.dropdown).close(); 87 /** @type {!CrActionMenuElement} */ (this.$.dropdown).showAt(target);
81 }, 88 },
82 89
83 //////////////////////////////////////////////////////////////////////////// 90 closeCommandMenu: function() {
84 // Command handlers: 91 /** @type {!CrActionMenuElement} */ (this.$.dropdown).close();
85 92 },
86 /** 93
87 * Determine if the |command| can be executed with the given |itemIds|. 94 ////////////////////////////////////////////////////////////////////////////
88 * Commands which appear in the context menu should be implemented separately 95 // Command handlers:
89 * using `isCommandVisible_` and `isCommandEnabled_`. 96
90 * @param {Command} command 97 /**
91 * @param {!Set<string>} itemIds 98 * Determine if the |command| can be executed with the given |itemIds|.
92 * @return {boolean} 99 * Commands which appear in the context menu should be implemented
93 */ 100 * separately using `isCommandVisible_` and `isCommandEnabled_`.
94 canExecute: function(command, itemIds) { 101 * @param {Command} command
95 return this.isCommandVisible_(command, itemIds) && 102 * @param {!Set<string>} itemIds
96 this.isCommandEnabled_(command, itemIds); 103 * @return {boolean}
97 }, 104 */
98 105 canExecute: function(command, itemIds) {
99 /** 106 return this.isCommandVisible_(command, itemIds) &&
100 * @param {Command} command 107 this.isCommandEnabled_(command, itemIds);
101 * @param {!Set<string>} itemIds 108 },
102 * @return {boolean} True if the command should be visible in the context 109
103 * menu. 110 /**
104 */ 111 * @param {Command} command
105 isCommandVisible_: function(command, itemIds) { 112 * @param {!Set<string>} itemIds
106 switch (command) { 113 * @return {boolean} True if the command should be visible in the context
107 case Command.EDIT: 114 * menu.
108 return itemIds.size == 1; 115 */
109 case Command.COPY: 116 isCommandVisible_: function(command, itemIds) {
110 return itemIds.size == 1 && 117 switch (command) {
111 this.containsMatchingNode_(itemIds, function(node) { 118 case Command.EDIT:
112 return !!node.url; 119 return itemIds.size == 1;
113 }); 120 case Command.COPY:
114 case Command.DELETE: 121 return itemIds.size == 1 &&
115 case Command.OPEN_NEW_TAB: 122 this.containsMatchingNode_(itemIds, function(node) {
116 case Command.OPEN_NEW_WINDOW: 123 return !!node.url;
117 case Command.OPEN_INCOGNITO: 124 });
118 return itemIds.size > 0; 125 case Command.DELETE:
119 default: 126 case Command.OPEN_NEW_TAB:
120 return false; 127 case Command.OPEN_NEW_WINDOW:
121 } 128 case Command.OPEN_INCOGNITO:
122 }, 129 return itemIds.size > 0;
123 130 default:
124 /** 131 return false;
125 * @param {Command} command 132 }
126 * @param {!Set<string>} itemIds 133 },
127 * @return {boolean} True if the command should be clickable in the context 134
128 * menu. 135 /**
129 */ 136 * @param {Command} command
130 isCommandEnabled_: function(command, itemIds) { 137 * @param {!Set<string>} itemIds
131 switch (command) { 138 * @return {boolean} True if the command should be clickable in the context
132 case Command.OPEN_NEW_TAB: 139 * menu.
133 case Command.OPEN_NEW_WINDOW: 140 */
134 case Command.OPEN_INCOGNITO: 141 isCommandEnabled_: function(command, itemIds) {
135 return this.expandUrls_(itemIds).length > 0; 142 switch (command) {
136 default: 143 case Command.OPEN_NEW_TAB:
137 return true; 144 case Command.OPEN_NEW_WINDOW:
138 } 145 case Command.OPEN_INCOGNITO:
139 }, 146 return this.expandUrls_(itemIds).length > 0;
140 147 default:
141 /** 148 return true;
142 * @param {Command} command 149 }
143 * @param {!Set<string>} itemIds 150 },
144 */ 151
145 handle: function(command, itemIds) { 152 /**
146 switch (command) { 153 * @param {Command} command
147 case Command.EDIT: 154 * @param {!Set<string>} itemIds
148 var id = Array.from(itemIds)[0]; 155 */
149 /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get()) 156 handle: function(command, itemIds) {
150 .showEditDialog(this.getState().nodes[id]); 157 switch (command) {
151 break; 158 case Command.EDIT:
152 case Command.COPY: 159 var id = Array.from(itemIds)[0];
153 var idList = Array.from(itemIds); 160 /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
154 chrome.bookmarkManagerPrivate.copy(idList, function() { 161 .showEditDialog(this.getState().nodes[id]);
155 // TODO(jiaxi): Add toast later. 162 break;
163 case Command.COPY:
164 var idList = Array.from(itemIds);
165 chrome.bookmarkManagerPrivate.copy(idList, function() {
166 // TODO(jiaxi): Add toast later.
167 });
168 break;
169 case Command.DELETE:
170 chrome.bookmarkManagerPrivate.removeTrees(
171 Array.from(this.minimizeDeletionSet_(itemIds)), function() {
172 // TODO(jiaxi): Add toast later.
173 });
174 break;
175 case Command.OPEN_NEW_TAB:
176 case Command.OPEN_NEW_WINDOW:
177 case Command.OPEN_INCOGNITO:
178 this.openUrls_(this.expandUrls_(itemIds), command);
179 break;
180 }
181 },
182
183 ////////////////////////////////////////////////////////////////////////////
184 // Private functions:
185
186 /**
187 * Minimize the set of |itemIds| by removing any node which has an ancestor
188 * node already in the set. This ensures that instead of trying to delete
189 * both a node and its descendant, we will only try to delete the topmost
190 * node, preventing an error in the bookmarkManagerPrivate.removeTrees API
191 * call.
192 * @param {!Set<string>} itemIds
193 * @return {!Set<string>}
194 */
195 minimizeDeletionSet_: function(itemIds) {
196 var minimizedSet = new Set();
197 var nodes = this.getState().nodes;
198 itemIds.forEach(function(itemId) {
199 var currentId = itemId;
200 while (currentId != ROOT_NODE_ID) {
201 currentId = assert(nodes[currentId].parentId);
202 if (itemIds.has(currentId))
203 return;
204 }
205 minimizedSet.add(itemId);
206 });
207 return minimizedSet;
208 },
209
210 /**
211 * @param {!Array<string>} urls
212 * @param {Command} command
213 * @private
214 */
215 openUrls_: function(urls, command) {
216 assert(
217 command == Command.OPEN_NEW_TAB ||
218 command == Command.OPEN_NEW_WINDOW ||
219 command == Command.OPEN_INCOGNITO);
220
221 if (urls.length == 0)
222 return;
223
224 var incognito = command == Command.OPEN_INCOGNITO;
225 if (command == Command.OPEN_NEW_WINDOW || incognito) {
226 chrome.windows.create({url: urls, incognito: incognito});
227 } else {
228 urls.forEach(function(url) {
229 chrome.tabs.create({url: url, active: false});
156 }); 230 });
157 break; 231 }
158 case Command.DELETE: 232 },
159 chrome.bookmarkManagerPrivate.removeTrees( 233
160 Array.from(this.minimizeDeletionSet_(itemIds)), function() { 234 /**
161 // TODO(jiaxi): Add toast later. 235 * Returns all URLs in the given set of nodes and their immediate children.
162 }); 236 * Note that these will be ordered by insertion order into the |itemIds|
163 break; 237 * set, and that it is possible to duplicate a URL by passing in both the
164 case Command.OPEN_NEW_TAB: 238 * parent ID and child ID.
165 case Command.OPEN_NEW_WINDOW: 239 * @param {!Set<string>} itemIds
166 case Command.OPEN_INCOGNITO: 240 * @return {!Array<string>}
167 this.openUrls_(this.expandUrls_(itemIds), command); 241 * @private
168 break; 242 */
169 } 243 expandUrls_: function(itemIds) {
170 }, 244 var urls = [];
171 245 var nodes = this.getState().nodes;
172 //////////////////////////////////////////////////////////////////////////// 246
173 // Private functions: 247 itemIds.forEach(function(id) {
174 248 var node = nodes[id];
175 /** 249 if (node.url) {
176 * Minimize the set of |itemIds| by removing any node which has an ancestor 250 urls.push(node.url);
177 * node already in the set. This ensures that instead of trying to delete both 251 } else {
178 * a node and its descendant, we will only try to delete the topmost node, 252 node.children.forEach(function(childId) {
179 * preventing an error in the bookmarkManagerPrivate.removeTrees API call. 253 var childNode = nodes[childId];
180 * @param {!Set<string>} itemIds 254 if (childNode.url)
181 * @return {!Set<string>} 255 urls.push(childNode.url);
182 */ 256 });
183 minimizeDeletionSet_: function(itemIds) { 257 }
184 var minimizedSet = new Set(); 258 });
185 var nodes = this.getState().nodes; 259
186 itemIds.forEach(function(itemId) { 260 return urls;
187 var currentId = itemId; 261 },
188 while (currentId != ROOT_NODE_ID) { 262
189 currentId = assert(nodes[currentId].parentId); 263 /**
190 if (itemIds.has(currentId)) 264 * @param {!Set<string>} itemIds
265 * @param {function(BookmarkNode):boolean} predicate
266 * @return {boolean} True if any node in |itemIds| returns true for
267 * |predicate|.
268 */
269 containsMatchingNode_: function(itemIds, predicate) {
270 var nodes = this.getState().nodes;
271
272 return Array.from(itemIds).some(function(id) {
273 return predicate(nodes[id]);
274 });
275 },
276
277 /**
278 * @param {Event} e
279 * @private
280 */
281 onOpenItemMenu_: function(e) {
282 if (e.detail.targetElement) {
283 this.openCommandMenuAtElement(e.detail.targetElement);
284 } else {
285 this.openCommandMenuAtPosition(e.detail.x, e.detail.y);
286 }
287 },
288
289 /**
290 * @param {Event} e
291 * @private
292 */
293 onCommandClick_: function(e) {
294 this.closeCommandMenu();
295 this.handle(e.target.getAttribute('command'), assert(this.menuIds_));
296 },
297
298 /**
299 * @param {!Event} e
300 * @private
301 */
302 onKeydown_: function(e) {
303 var selection = this.getState().selection.items;
304 // TODO(tsergeant): Prevent keyboard shortcuts when a dialog is open or
305 // text field is focused.
306 for (var commandName in this.shortcuts_) {
307 var shortcut = this.shortcuts_[commandName];
308 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(
309 e, shortcut) &&
310 this.canExecute(commandName, selection)) {
311 this.handle(commandName, selection);
312
313 e.stopPropagation();
314 e.preventDefault();
191 return; 315 return;
192 } 316 }
193 minimizedSet.add(itemId); 317 }
194 }); 318 },
195 return minimizedSet; 319
196 }, 320 /**
197 321 * Close the menu on mousedown so clicks can propagate to the underlying UI.
198 /** 322 * This allows the user to right click the list while a context menu is
199 * @param {!Array<string>} urls 323 * showing and get another context menu.
200 * @param {Command} command 324 * @param {Event} e
201 * @private 325 * @private
202 */ 326 */
203 openUrls_: function(urls, command) { 327 onMenuMousedown_: function(e) {
204 assert( 328 if (e.path[0] != this.$.dropdown)
205 command == Command.OPEN_NEW_TAB || command == Command.OPEN_NEW_WINDOW ||
206 command == Command.OPEN_INCOGNITO);
207
208 if (urls.length == 0)
209 return;
210
211 var incognito = command == Command.OPEN_INCOGNITO;
212 if (command == Command.OPEN_NEW_WINDOW || incognito) {
213 chrome.windows.create({url: urls, incognito: incognito});
214 } else {
215 urls.forEach(function(url) {
216 chrome.tabs.create({url: url, active: false});
217 });
218 }
219 },
220
221 /**
222 * Returns all URLs in the given set of nodes and their immediate children.
223 * Note that these will be ordered by insertion order into the |itemIds| set,
224 * and that it is possible to duplicate a URL by passing in both the parent ID
225 * and child ID.
226 * @param {!Set<string>} itemIds
227 * @return {!Array<string>}
228 * @private
229 */
230 expandUrls_: function(itemIds) {
231 var urls = [];
232 var nodes = this.getState().nodes;
233
234 itemIds.forEach(function(id) {
235 var node = nodes[id];
236 if (node.url) {
237 urls.push(node.url);
238 } else {
239 node.children.forEach(function(childId) {
240 var childNode = nodes[childId];
241 if (childNode.url)
242 urls.push(childNode.url);
243 });
244 }
245 });
246
247 return urls;
248 },
249
250 /**
251 * @param {!Set<string>} itemIds
252 * @param {function(BookmarkNode):boolean} predicate
253 * @return {boolean} True if any node in |itemIds| returns true for
254 * |predicate|.
255 */
256 containsMatchingNode_: function(itemIds, predicate) {
257 var nodes = this.getState().nodes;
258
259 return Array.from(itemIds).some(function(id) {
260 return predicate(nodes[id]);
261 });
262 },
263
264 /**
265 * @param {Event} e
266 * @private
267 */
268 onOpenItemMenu_: function(e) {
269 if (e.detail.targetElement) {
270 this.openCommandMenuAtElement(e.detail.targetElement);
271 } else {
272 this.openCommandMenuAtPosition(e.detail.x, e.detail.y);
273 }
274 },
275
276 /**
277 * @param {Event} e
278 * @private
279 */
280 onCommandClick_: function(e) {
281 this.closeCommandMenu();
282 this.handle(e.target.getAttribute('command'), assert(this.menuIds_));
283 },
284
285 /**
286 * @param {!Event} e
287 * @private
288 */
289 onKeydown_: function(e) {
290 var selection = this.getState().selection.items;
291 // TODO(tsergeant): Prevent keyboard shortcuts when a dialog is open or text
292 // field is focused.
293 for (var commandName in this.shortcuts_) {
294 var shortcut = this.shortcuts_[commandName];
295 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(e, shortcut) &&
296 this.canExecute(commandName, selection)) {
297 this.handle(commandName, selection);
298
299 e.stopPropagation();
300 e.preventDefault();
301 return; 329 return;
302 } 330
303 } 331 this.$.dropdown.close();
304 }, 332 },
305 333
306 /** 334 /**
307 * Close the menu on mousedown so clicks can propagate to the underlying UI. 335 * @param {Command} command
308 * This allows the user to right click the list while a context menu is 336 * @return {string}
309 * showing and get another context menu. 337 * @private
310 * @param {Event} e 338 */
311 * @private 339 getCommandLabel_: function(command) {
312 */ 340 var multipleNodes = this.menuIds_.size > 1 ||
313 onMenuMousedown_: function(e) { 341 this.containsMatchingNode_(this.menuIds_, function(node) {
314 if (e.path[0] != this.$.dropdown) 342 return !node.url;
315 return; 343 });
316 344 var label;
317 this.$.dropdown.close(); 345 switch (command) {
318 }, 346 case Command.EDIT:
319 347 if (this.menuIds_.size > 1)
320 /** 348 return '';
321 * @param {Command} command 349
322 * @return {string} 350 var id = Array.from(this.menuIds_)[0];
323 * @private 351 var itemUrl = this.getState().nodes[id].url;
324 */ 352 label = itemUrl ? 'menuEdit' : 'menuRename';
325 getCommandLabel_: function(command) { 353 break;
326 var multipleNodes = this.menuIds_.size > 1 || 354 case Command.COPY:
327 this.containsMatchingNode_(this.menuIds_, function(node) { 355 label = 'menuCopyURL';
328 return !node.url; 356 break;
329 }); 357 case Command.DELETE:
330 var label; 358 label = 'menuDelete';
331 switch (command) { 359 break;
332 case Command.EDIT: 360 case Command.OPEN_NEW_TAB:
333 if (this.menuIds_.size > 1) 361 label = multipleNodes ? 'menuOpenAllNewTab' : 'menuOpenNewTab';
334 return ''; 362 break;
335 363 case Command.OPEN_NEW_WINDOW:
336 var id = Array.from(this.menuIds_)[0]; 364 label = multipleNodes ? 'menuOpenAllNewWindow' : 'menuOpenNewWindow';
337 var itemUrl = this.getState().nodes[id].url; 365 break;
338 label = itemUrl ? 'menuEdit' : 'menuRename'; 366 case Command.OPEN_INCOGNITO:
339 break; 367 label = multipleNodes ? 'menuOpenAllIncognito' : 'menuOpenIncognito';
340 case Command.COPY: 368 break;
341 label = 'menuCopyURL'; 369 }
342 break; 370
343 case Command.DELETE: 371 return loadTimeData.getString(assert(label));
344 label = 'menuDelete'; 372 },
345 break; 373
346 case Command.OPEN_NEW_TAB: 374 /**
347 label = multipleNodes ? 'menuOpenAllNewTab' : 'menuOpenNewTab'; 375 * @param {Command} command
348 break; 376 * @return {boolean}
349 case Command.OPEN_NEW_WINDOW: 377 * @private
350 label = multipleNodes ? 'menuOpenAllNewWindow' : 'menuOpenNewWindow'; 378 */
351 break; 379 showDividerAfter_: function(command) {
352 case Command.OPEN_INCOGNITO: 380 return command == Command.DELETE;
353 label = multipleNodes ? 'menuOpenAllIncognito' : 'menuOpenIncognito'; 381 },
354 break; 382 });
355 } 383
356 384 /** @private {bookmarks.CommandManager} */
357 return loadTimeData.getString(assert(label)); 385 CommandManager.instance_ = null;
358 }, 386
359 387 /** @return {!bookmarks.CommandManager} */
360 /** 388 CommandManager.getInstance = function() {
361 * @param {Command} command 389 return assert(CommandManager.instance_);
362 * @return {boolean} 390 };
363 * @private 391
364 */ 392 return {
365 showDividerAfter_: function(command) { 393 CommandManager: CommandManager,
366 return command == Command.DELETE; 394 };
367 },
368 }); 395 });
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698