feat: support Markdown editor bold & italic keyboard shortcuts (#9110)

Ctrl/Cmd-B & Ctrl/Cmd-I are implemented in the markdown editor to allow bolding & italicizing of text. The shortcut defers to the toolbar for implementation, and so should have the exact same behavior as clicking B or I.

Fixes #7549
(Partially addresses #3604)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9110
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Lucas <sclu1034@noreply.codeberg.org>
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
This commit is contained in:
Mathieu Fenniak 2025-09-08 11:44:21 +02:00 committed by Beowulf
commit 512b3fad5b
4 changed files with 54 additions and 4 deletions

View file

@ -99,6 +99,12 @@ class ComboMarkdownEditor {
this.textareaMarkdownToolbar.querySelector('button[data-md-action="new-table"]')?.setAttribute('data-modal', `div[data-markdown-table-modal-id="${this.elementIdSuffix}"]`);
this.textareaMarkdownToolbar.querySelector('button[data-md-action="new-link"]')?.setAttribute('data-modal', `div[data-markdown-link-modal-id="${this.elementIdSuffix}"]`);
// Find all data-md-ctrl-shortcut elements in the markdown toolbar.
const shortcutKeys = new Map();
for (const el of this.textareaMarkdownToolbar.querySelectorAll('[data-md-ctrl-shortcut]')) {
shortcutKeys.set(el.getAttribute('data-md-ctrl-shortcut'), el);
}
// Track whether any actual input or pointer action was made after focusing, and only intercept Tab presses after that.
this.tabEnabled = false;
// This tracks whether last Tab action was ignored, and if it immediately happens *again*, lose focus.
@ -145,6 +151,17 @@ class ComboMarkdownEditor {
if (!this.breakLine()) return; // Nothing changed, let the default handler work.
this.options?.onContentChanged?.(this, e);
e.preventDefault();
} else if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
const normalizedShortcutKey = e.key.charCodeAt(0) <= 127 ?
// if ascii, e.key is preferred as it is agnostic to keyboard layouts (QWERTY/Dvorak)...
e.key.toLowerCase() :
// if not ascii, e.code is used to support keyboards w/ other writing systems (eg. и or ბ); "KeyB" transformed to "b" to compare against the shortcut character.
e.code.replace('Key', '').toLowerCase();
const shortcutElement = shortcutKeys.get(normalizedShortcutKey);
if (shortcutElement) {
shortcutElement.click();
e.preventDefault();
}
} else if (noModifiers) {
this.activateTabHandling();
}