Skip to content

Commit 7d36354

Browse files
authored
feat: Render keyboard shortcuts nicely in menus. (#594)
1 parent 7c8dff9 commit 7d36354

File tree

6 files changed

+76
-28
lines changed

6 files changed

+76
-28
lines changed

src/actions/clipboard.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from 'blockly';
1515
import * as Constants from '../constants';
1616
import {Navigation} from '../navigation';
17-
import {getShortActionShortcut} from '../shortcut_formatting';
17+
import {getMenuItem} from '../shortcut_formatting';
1818
import {clearPasteHints, showCopiedHint, showCutHint} from '../hints';
1919

2020
/**
@@ -104,10 +104,7 @@ export class Clipboard {
104104
private registerCutContextMenuAction() {
105105
const cutAction: ContextMenuRegistry.RegistryItem = {
106106
displayText: (scope) =>
107-
Msg['CUT_SHORTCUT'].replace(
108-
'%1',
109-
getShortActionShortcut(Constants.SHORTCUT_NAMES.CUT),
110-
),
107+
getMenuItem(Msg['CUT_SHORTCUT'], Constants.SHORTCUT_NAMES.CUT),
111108
preconditionFn: (scope) => this.cutPrecondition(scope),
112109
callback: (scope, menuOpenEvent) => {
113110
if (!isCopyable(scope.focusedNode)) return false;
@@ -250,10 +247,7 @@ export class Clipboard {
250247
private registerCopyContextMenuAction() {
251248
const copyAction: ContextMenuRegistry.RegistryItem = {
252249
displayText: (scope) =>
253-
Msg['COPY_SHORTCUT'].replace(
254-
'%1',
255-
getShortActionShortcut(Constants.SHORTCUT_NAMES.COPY),
256-
),
250+
getMenuItem(Msg['COPY_SHORTCUT'], Constants.SHORTCUT_NAMES.COPY),
257251
preconditionFn: (scope) => this.copyPrecondition(scope),
258252
callback: (scope, menuOpenEvent) => {
259253
if (!isCopyable(scope.focusedNode)) return false;
@@ -331,10 +325,7 @@ export class Clipboard {
331325
private registerPasteContextMenuAction() {
332326
const pasteAction: ContextMenuRegistry.RegistryItem = {
333327
displayText: (scope) =>
334-
Msg['PASTE_SHORTCUT'].replace(
335-
'%1',
336-
getShortActionShortcut(Constants.SHORTCUT_NAMES.PASTE),
337-
),
328+
getMenuItem(Msg['PASTE_SHORTCUT'], Constants.SHORTCUT_NAMES.PASTE),
338329
preconditionFn: (scope) => this.pastePrecondition(scope),
339330
callback: (scope: ContextMenuRegistry.Scope, menuOpenEvent: Event) => {
340331
const workspace = this.copyWorkspace;

src/actions/delete.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import {ContextMenuRegistry, Msg, ShortcutItems} from 'blockly';
8-
import {getShortActionShortcut} from '../shortcut_formatting';
8+
import {getMenuItem} from '../shortcut_formatting';
99

1010
/**
1111
* Action to delete the block the cursor is currently on.
@@ -58,17 +58,23 @@ export class DeleteAction {
5858
this.oldDisplayText = this.oldContextMenuItem.displayText;
5959

6060
const displayText = (scope: ContextMenuRegistry.Scope) => {
61-
const shortcut = getShortActionShortcut(ShortcutItems.names.DELETE);
62-
61+
let label: string;
6362
// Use the original item's text, which is dynamic based on the number
6463
// of blocks that will be deleted.
6564
if (typeof this.oldDisplayText === 'function') {
66-
return this.oldDisplayText(scope) + ` (${shortcut})`;
65+
const result = this.oldDisplayText(scope);
66+
if (result instanceof HTMLElement) {
67+
label = result.innerText;
68+
} else {
69+
label = result;
70+
}
6771
} else if (typeof this.oldDisplayText === 'string') {
68-
return this.oldDisplayText + ` (${shortcut})`;
72+
label = this.oldDisplayText;
73+
} else {
74+
label = Msg['DELETE_BLOCK'];
6975
}
7076

71-
return Msg['DELETE_BLOCK'].replace('%1', shortcut);
77+
return getMenuItem(label, ShortcutItems.names.DELETE);
7278
};
7379

7480
this.oldContextMenuItem.displayText = displayText;

src/actions/edit.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
keyboardNavigationController,
1212
} from 'blockly';
1313
import {Navigation} from 'src/navigation';
14-
import {getShortActionShortcut} from '../shortcut_formatting';
14+
import {getMenuItem} from '../shortcut_formatting';
1515
import * as Constants from '../constants';
1616

1717
/**
@@ -57,9 +57,9 @@ export class EditAction {
5757
*/
5858
private registerContextMenuAction() {
5959
const editAboveItem: ContextMenuRegistry.RegistryItem = {
60-
displayText: Msg['EDIT_BLOCK_CONTENTS'].replace(
61-
'%1',
62-
getShortActionShortcut(Constants.SHORTCUT_NAMES.RIGHT),
60+
displayText: getMenuItem(
61+
Msg['EDIT_BLOCK_CONTENTS'],
62+
Constants.SHORTCUT_NAMES.RIGHT,
6363
),
6464
preconditionFn: (scope: ContextMenuRegistry.Scope, menuOpenEvent) => {
6565
if (menuOpenEvent instanceof PointerEvent) return 'hidden';

src/actions/move.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from 'blockly';
1717
import {Direction} from '../drag_direction';
1818
import {Mover} from './mover';
19-
import {getShortActionShortcut} from '../shortcut_formatting';
19+
import {getMenuItem} from '../shortcut_formatting';
2020

2121
const KeyCodes = utils.KeyCodes;
2222
const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind(
@@ -156,10 +156,7 @@ export class MoveActions {
156156
private registerMenuItems() {
157157
const menuItems: ContextMenuRegistry.RegistryItem[] = [
158158
{
159-
displayText: Msg['MOVE_BLOCK'].replace(
160-
'%1',
161-
getShortActionShortcut('start_move'),
162-
),
159+
displayText: getMenuItem(Msg['MOVE_BLOCK'], 'start_move'),
163160
preconditionFn: (scope, menuOpenEvent) => {
164161
const workspace = scope.block?.workspace as WorkspaceSvg | null;
165162
if (!workspace || menuOpenEvent instanceof PointerEvent)

src/shortcut_formatting.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@ import {keyNames} from './keynames';
33

44
const isMacPlatform = navigator.platform.startsWith('Mac');
55

6+
/**
7+
* Returns an HTML menu item with a label and grey keyboard shortcut.
8+
*
9+
* @param labelText The text of the mneu item.
10+
* @param action The identifier of an action to use the keyboard shortcut of.
11+
* @returns A nicely formatted menu item.
12+
*/
13+
export function getMenuItem(labelText: string, action: string): HTMLElement {
14+
// TODO: Once core is updated to remove the shortcut placeholders from the
15+
// keyboard shortcut messages, remove this.
16+
if (labelText.indexOf(')') === labelText.length - 1) {
17+
labelText = labelText.split(' (')[0];
18+
}
19+
const container = document.createElement('div');
20+
container.className = 'blocklyShortcutContainer';
21+
const label = document.createElement('span');
22+
label.textContent = labelText;
23+
const shortcut = document.createElement('span');
24+
shortcut.className = 'blocklyShortcut';
25+
shortcut.textContent = getShortActionShortcut(action);
26+
container.appendChild(label);
27+
container.appendChild(shortcut);
28+
return container;
29+
}
30+
631
/**
732
* Find the primary shortcut for this platform and return it as single string
833
* in a short user facing format.

test/index.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,35 @@
161161
.blocklyToolboxCategoryContainer:focus-visible {
162162
outline: none;
163163
}
164+
165+
.blocklyRTL
166+
.blocklyKeyboardNavigation
167+
.blocklyMenuItemContent
168+
.blocklyShortcutContainer {
169+
flex-direction: row-reverse;
170+
}
171+
.blocklyKeyboardNavigation
172+
.blocklyMenuItemContent
173+
.blocklyShortcutContainer {
174+
width: 100%;
175+
display: flex;
176+
justify-content: space-between;
177+
}
178+
.blocklyKeyboardNavigation
179+
.blocklyMenuItemContent
180+
.blocklyShortcutContainer
181+
.blocklyShortcut {
182+
color: #ccc;
183+
margin-left: 16px;
184+
}
185+
.blocklyRTL
186+
.blocklyKeyboardNavigation
187+
.blocklyMenuItemContent
188+
.blocklyShortcutContainer
189+
.blocklyShortcut {
190+
margin-left: 0;
191+
margin-right: 16px;
192+
}
164193
</style>
165194
</head>
166195

0 commit comments

Comments
 (0)