Add keyboard shortcuts to all toolbar buttons
This commit is contained in:
parent
1f523cbc0f
commit
8bef75e59f
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -239,6 +239,54 @@ describe('ToolbarManager', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('heading and list buttons', () => {
|
||||
it('registers h1-h6', () => {
|
||||
const editor = new r.Editor({ autoToolbar: false });
|
||||
editor.run();
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const btn = editor.toolbar.buttons.get(`h${i}`);
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.label).toBe(`H${i}`);
|
||||
expect(btn!.shortcut).toBe(`Ctrl+${i}`);
|
||||
expect(btn!.action).toBe('prefix');
|
||||
}
|
||||
});
|
||||
|
||||
it('registers ul and ol', () => {
|
||||
const editor = new r.Editor({ autoToolbar: false });
|
||||
editor.run();
|
||||
expect(editor.toolbar.buttons.get('ul')!.shortcut).toBe('Ctrl+Shift+8');
|
||||
expect(editor.toolbar.buttons.get('ol')!.shortcut).toBe('Ctrl+Shift+7');
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyboard shortcuts', () => {
|
||||
it('all formatting buttons have shortcuts', () => {
|
||||
const editor = new r.Editor({ autoToolbar: false });
|
||||
editor.run();
|
||||
const expected = ['bold', 'italic', 'code', 'link', 'save'];
|
||||
for (const id of expected) {
|
||||
expect(editor.toolbar.buttons.get(id)!.shortcut).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('block buttons have shortcuts', () => {
|
||||
const editor = new r.Editor({ autoToolbar: false });
|
||||
editor.run();
|
||||
expect(editor.toolbar.buttons.get('fencedCode')!.shortcut).toBe('Ctrl+Shift+E');
|
||||
expect(editor.toolbar.buttons.get('blockquote')!.shortcut).toBe('Ctrl+Shift+.');
|
||||
expect(editor.toolbar.buttons.get('table')!.shortcut).toBe('Ctrl+Shift+T');
|
||||
expect(editor.toolbar.buttons.get('hr')!.shortcut).toBe('Ctrl+Shift+-');
|
||||
});
|
||||
|
||||
it('editor actions have shortcuts', () => {
|
||||
const editor = new r.Editor({ autoToolbar: false });
|
||||
editor.run();
|
||||
expect(editor.toolbar.buttons.get('toggle')!.shortcut).toBe('Ctrl+Shift+V');
|
||||
expect(editor.toolbar.buttons.get('markdown')!.shortcut).toBe('Ctrl+/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('save button', () => {
|
||||
it('triggers editor.save()', () => {
|
||||
resetDOM();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user