Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion packages/blockly/core/contextmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js';
import {config} from './config.js';
import type {
ActionContextMenuOption,
ContextMenuOption,
LegacyContextMenuOption,
} from './contextmenu_registry.js';
Expand All @@ -25,6 +26,7 @@ import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import {Rect} from './utils/rect.js';
import {getShortcutKeysShort} from './utils/shortcut_formatting.js';
import * as svgMath from './utils/svg_math.js';
import * as WidgetDiv from './widgetdiv.js';
import type {WorkspaceSvg} from './workspace_svg.js';
Expand Down Expand Up @@ -134,7 +136,7 @@ function populate_(
continue;
}

const menuItem = new MenuItem(option.text);
const menuItem = new MenuItem(makeMenuitem(option));
menuItem.setRightToLeft(rtl);
menuItem.setRole(aria.Role.MENUITEM);
menu.addChild(menuItem);
Expand Down Expand Up @@ -302,3 +304,46 @@ export function callbackFactory(
export function getMenu(): Menu | null {
return menu_;
}

/**
* Creates a menu item to represent the given context menu option.
* For text-based menu options, this wraps the text in a container with its
* corresponding keyboard shortcut, if any. HTML-based menu options are displayed
* as-is.
*
* @param option The context menu option to generate a menu item for.
* @returns A `MenuItem` representing the given context menu option.
*/
function makeMenuitem(
option: ActionContextMenuOption | LegacyContextMenuOption,
) {
const text = option.text;
if (text && !(text instanceof HTMLElement)) {
const container = document.createElement('div');
container.className = 'blocklyShortcutContainer';
const label = document.createElement('span');
label.textContent = text;
const shortcut = document.createElement('span');
shortcut.className = 'blocklyShortcut';
shortcut.textContent = ` ${getKeyboardShortcut(option)}`;
container.appendChild(label);
container.appendChild(shortcut);
return container;
}

return option.text;
}

/**
* Returns a keyboard shortcut for the given context menu item, if any.
*
* @param option The context menu item to retrieve a keyboard shortcut for.
* @returns The keyboard shortcut registered under the same ID as the context
Comment thread
gonfunko marked this conversation as resolved.
Outdated
* menu item, if any.
*/
function getKeyboardShortcut(
option: ContextMenuOption | LegacyContextMenuOption,
): string {
if (!('id' in option)) return '';
return getShortcutKeysShort(option.associatedKeyboardShortcut ?? option.id);
}
7 changes: 7 additions & 0 deletions packages/blockly/core/contextmenu_items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function registerUndo() {
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
id: 'undoWorkspace',
weight: 1,
associatedKeyboardShortcut: 'undo',
};
ContextMenuRegistry.registry.register(undoOption);
}
Expand All @@ -76,6 +77,7 @@ export function registerRedo() {
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
id: 'redoWorkspace',
weight: 2,
associatedKeyboardShortcut: 'redo',
};
ContextMenuRegistry.registry.register(redoOption);
}
Expand Down Expand Up @@ -103,6 +105,7 @@ export function registerCleanup() {
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
id: 'cleanWorkspace',
weight: 3,
associatedKeyboardShortcut: 'cleanup',
};
ContextMenuRegistry.registry.register(cleanOption);
}
Expand Down Expand Up @@ -349,6 +352,7 @@ export function registerDuplicate() {
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
id: 'blockDuplicate',
weight: 1,
associatedKeyboardShortcut: 'duplicate',
};
ContextMenuRegistry.registry.register(duplicateOption);
}
Expand Down Expand Up @@ -547,6 +551,7 @@ export function registerDelete() {
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
id: 'blockDelete',
weight: 6,
associatedKeyboardShortcut: 'delete',
};
ContextMenuRegistry.registry.register(deleteOption);
}
Expand Down Expand Up @@ -596,6 +601,7 @@ export function registerCommentDelete() {
scopeType: ContextMenuRegistry.ScopeType.COMMENT,
id: 'commentDelete',
weight: 6,
associatedKeyboardShortcut: 'delete',
};
ContextMenuRegistry.registry.register(deleteOption);
}
Expand All @@ -616,6 +622,7 @@ export function registerCommentDuplicate() {
scopeType: ContextMenuRegistry.ScopeType.COMMENT,
id: 'commentDuplicate',
weight: 1,
associatedKeyboardShortcut: 'duplicate',
};
ContextMenuRegistry.registry.register(duplicateOption);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/blockly/core/contextmenu_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export class ContextMenuRegistry {
| ContextMenuRegistry.SeparatorContextMenuOption
| ContextMenuRegistry.ActionContextMenuOption;
menuOption = {
id: item.id,
scope,
weight: item.weight,
};
Expand All @@ -122,6 +123,7 @@ export class ContextMenuRegistry {
text: displayText,
callback: item.callback,
enabled: precondition === 'enabled',
associatedKeyboardShortcut: item.associatedKeyboardShortcut,
};
}

Expand Down Expand Up @@ -188,6 +190,7 @@ export namespace ContextMenuRegistry {
displayText: ((p1: Scope) => string | HTMLElement) | string | HTMLElement;
separator?: never;
preconditionFn: (p1: Scope, menuOpenEvent: Event) => string;
associatedKeyboardShortcut?: string;
Comment thread
gonfunko marked this conversation as resolved.
}

/**
Expand All @@ -208,8 +211,10 @@ export namespace ContextMenuRegistry {
* Fields common to all context menu items as used by contextmenu.ts.
*/
export interface CoreContextMenuOption {
id: string;
scope: Scope;
weight: number;
associatedKeyboardShortcut?: string;
}

/**
Expand Down Expand Up @@ -276,3 +281,5 @@ export type RegistryItem = ContextMenuRegistry.RegistryItem;
export type ContextMenuOption = ContextMenuRegistry.ContextMenuOption;
export type LegacyContextMenuOption =
ContextMenuRegistry.LegacyContextMenuOption;
export type ActionContextMenuOption =
ContextMenuRegistry.ActionContextMenuOption;
13 changes: 13 additions & 0 deletions packages/blockly/core/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,19 @@ input[type=number] {
margin-right: 4px;
}

.blocklyRTL .blocklyMenuItemContent .blocklyShortcutContainer {
flex-direction: row-reverse;
}
.blocklyMenuItemContent .blocklyShortcutContainer {
width: 100%;
display: flex;
justify-content: space-between;
gap: 16px;
}
.blocklyMenuItemContent .blocklyShortcutContainer .blocklyShortcut {
color: #ccc;
}

.blocklyBlockDragSurface, .blocklyAnimationLayer {
position: absolute;
top: 0;
Expand Down
183 changes: 92 additions & 91 deletions packages/blockly/core/utils/shortcut_formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,97 +48,6 @@ const shortModifierNames: Record<string, string> = {
'Alt': userAgent.APPLE ? '⌥' : Msg['ALT_KEY'],
};

/**
* Key names for common characters. These should be used with keyup/keydown
* events, since the .keyCode property on those is meant to indicate the
* _physical key_ the user held down on the keyboard. Hence the mapping uses
* only the unshifted version of each key (e.g. no '#', since that's shift+3).
* Keypress events on the other hand generate (mostly) ASCII codes since they
* correspond to *characters* the user typed.
*
* For further reference: http://unixpapa.com/js/key.html
*
* This list is not localized and therefore some of the key codes are not
* correct for non-US keyboard layouts.
*
* Partially copied from goog.events.keynames and modified to use translatable
* strings or symbols for keys.
*/
const keyNames: Record<number, string> = {
8: Msg['BACKSPACE_KEY'],
9: Msg['TAB_KEY'],
13: Msg['ENTER_KEY'],
16: Msg['SHIFT_KEY'],
17: Msg['CTRL_KEY'],
18: Msg['ALT_KEY'],
19: Msg['PAUSE_KEY'],
20: Msg['CAPS_LOCK_KEY'],
27: Msg['ESCAPE_KEY'],
32: Msg['SPACE_KEY'],
33: Msg['PAGE_UP_KEY'],
34: Msg['PAGE_DOWN_KEY'],
35: Msg['END_KEY'],
36: Msg['HOME_KEY'],
37: '←',
38: '↑',
39: '→',
40: '↓',
45: Msg['INSERT_KEY'],
46: Msg['DELETE_KEY'],
48: '0',
49: '1',
50: '2',
51: '3',
52: '4',
53: '5',
54: '6',
55: '7',
56: '8',
57: '9',
59: ';',
61: '=',
93: Msg['CONTEXT_MENU_KEY'],
96: '0',
97: '1',
98: '2',
99: '3',
100: '4',
101: '5',
102: '6',
103: '7',
104: '8',
105: '9',
106: '×',
107: '+',
109: '−',
110: '.',
111: '÷',
112: 'F1',
113: 'F2',
114: 'F3',
115: 'F4',
116: 'F5',
117: 'F6',
118: 'F7',
119: 'F8',
120: 'F9',
121: 'F10',
122: 'F11',
123: 'F12',
186: ';',
187: '=',
189: '-',
188: ',',
190: '.',
191: '/',
192: '`',
219: '[',
220: '\\',
221: ']',
222: "'",
224: '⌘',
};

/**
* Gets a user-facing name for a keycode.
*
Expand All @@ -150,6 +59,98 @@ function getKeyName(keyCode: number): string {
// letters a-z
return String.fromCharCode(keyCode);
}

/**
* Key names for common characters. These should be used with keyup/keydown
* events, since the .keyCode property on those is meant to indicate the
* _physical key_ the user held down on the keyboard. Hence the mapping uses
* only the unshifted version of each key (e.g. no '#', since that's shift+3).
* Keypress events on the other hand generate (mostly) ASCII codes since they
* correspond to *characters* the user typed.
*
* For further reference: http://unixpapa.com/js/key.html
*
* This list is not localized and therefore some of the key codes are not
* correct for non-US keyboard layouts.
*
* Partially copied from goog.events.keynames and modified to use translatable
* strings or symbols for keys.
*/
const keyNames: Record<number, string> = {
8: Msg['BACKSPACE_KEY'],
9: Msg['TAB_KEY'],
13: Msg['ENTER_KEY'],
16: Msg['SHIFT_KEY'],
17: Msg['CTRL_KEY'],
18: Msg['ALT_KEY'],
19: Msg['PAUSE_KEY'],
20: Msg['CAPS_LOCK_KEY'],
27: Msg['ESCAPE_KEY'],
32: Msg['SPACE_KEY'],
33: Msg['PAGE_UP_KEY'],
34: Msg['PAGE_DOWN_KEY'],
35: Msg['END_KEY'],
36: Msg['HOME_KEY'],
37: '←',
38: '↑',
39: '→',
40: '↓',
45: Msg['INSERT_KEY'],
46: Msg['DELETE_KEY'],
48: '0',
49: '1',
50: '2',
51: '3',
52: '4',
53: '5',
54: '6',
55: '7',
56: '8',
57: '9',
59: ';',
61: '=',
93: Msg['CONTEXT_MENU_KEY'],
96: '0',
97: '1',
98: '2',
99: '3',
100: '4',
101: '5',
102: '6',
103: '7',
104: '8',
105: '9',
106: '×',
107: '+',
109: '−',
110: '.',
111: '÷',
112: 'F1',
113: 'F2',
114: 'F3',
115: 'F4',
116: 'F5',
117: 'F6',
118: 'F7',
119: 'F8',
120: 'F9',
121: 'F10',
122: 'F11',
123: 'F12',
186: ';',
187: '=',
189: '-',
188: ',',
190: '.',
191: '/',
192: '`',
219: '[',
220: '\\',
221: ']',
222: "'",
224: '⌘',
};

const keyName = keyNames[keyCode];
if (keyName) return keyName;
console.warn('Unknown key code: ' + keyCode);
Expand Down