Add keyboard shortcuts to all toolbar buttons

This commit is contained in:
gsb
2026-04-29 07:23:10 +00:00
parent 1f523cbc0f
commit 8bef75e59f
3 changed files with 115 additions and 6 deletions
+4 -4
View File
@@ -187,7 +187,7 @@ export const defaultBlockTags: Record<string, Tag> = {
* ```
*/
name: 'fencedCode',
button: { show: true, label: 'Code Block' },
button: { show: true, label: 'Code Block', shortcut: 'Ctrl+Shift+E' },
template: '```\ncode\n```',
replaceSelection: true,
match: (context) => {
@@ -225,7 +225,7 @@ export const defaultBlockTags: Record<string, Tag> = {
* ___
*/
name: 'hr',
button: { show: true, label: 'Divider' },
button: { show: true, label: 'Divider', shortcut: 'Ctrl+Shift+-' },
template: '---',
replaceSelection: false,
match: (context) => {
@@ -276,7 +276,7 @@ export const defaultBlockTags: Record<string, Tag> = {
* > more quoted text
*/
name: 'blockquote',
button: { show: true, label: 'Quote' },
button: { show: true, label: 'Quote', shortcut: 'Ctrl+Shift+.' },
template: '> Quote\n> continues here',
replaceSelection: true,
match: (context) => {
@@ -330,7 +330,7 @@ export const defaultBlockTags: Record<string, Tag> = {
* | cell 1 | cell 2 |
*/
name: 'table',
button: { show: true, label: 'Table' },
button: { show: true, label: 'Table', shortcut: 'Ctrl+Shift+T' },
template: '| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |',
replaceSelection: false,
match: (context) => {
+63 -2
View File
@@ -83,6 +83,31 @@ export class ToolbarManager {
});
}
// Heading and list variants (derived from their parent tags)
for (let i = 1; i <= 6; i++) {
this.register(`h${i}`, {
label: `H${i}`,
shortcut: `Ctrl+${i}`,
action: 'prefix',
delimiter: '#'.repeat(i) + ' ',
replaceSelection: true,
});
}
this.register('ul', {
label: 'Bullet List',
shortcut: 'Ctrl+Shift+8',
action: 'insert',
template: '- Item 1\n- Item 2\n- Item 3',
replaceSelection: false,
});
this.register('ol', {
label: 'Numbered List',
shortcut: 'Ctrl+Shift+7',
action: 'insert',
template: '1. Item 1\n2. Item 2\n3. Item 3',
replaceSelection: false,
});
for (const macro of macros) {
if (macro.button === false) {
continue;
@@ -102,7 +127,7 @@ export class ToolbarManager {
handler: () => this.editor.save(),
});
this.register('toggle', {
label: 'Edit', action: 'custom',
label: 'Edit', shortcut: 'Ctrl+Shift+V', action: 'custom',
handler: () => {
this.editor.getState() === 'view'
? this.editor.wysiwyg()
@@ -110,7 +135,7 @@ export class ToolbarManager {
},
});
this.register('markdown', {
label: 'Source', action: 'custom',
label: 'Source', shortcut: 'Ctrl+/', action: 'custom',
handler: () => {
this.editor.getState() === 'edit'
? this.editor.wysiwyg()
@@ -119,6 +144,42 @@ export class ToolbarManager {
});
this.layout = layout || this.defaultLayout();
this.bindShortcuts();
}
/**
* Listen for keyboard shortcuts on the document and dispatch
* to the matching toolbar button.
*/
private bindShortcuts(): void {
const shortcutMap = new Map<string, Button>();
for (const button of this.buttons.values()) {
if (button.shortcut) {
shortcutMap.set(button.shortcut.toLowerCase(), button);
}
}
document.addEventListener('keydown', (event: KeyboardEvent) => {
const parts: string[] = [];
if (event.ctrlKey || event.metaKey) parts.push('ctrl');
if (event.shiftKey) parts.push('shift');
if (event.altKey) parts.push('alt');
let key = event.key;
if (key === '/') key = '/';
else if (key === '.') key = '.';
else if (key === '-') key = '-';
else key = key.toLowerCase();
parts.push(key);
const combo = parts.join('+');
const button = shortcutMap.get(combo);
if (button) {
event.preventDefault();
this.executeAction(button);
}
});
}
private register(id: string, def: Partial<Button>): void {