mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-09-30 14:45:56 +00:00
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:
parent
9354efceb1
commit
512b3fad5b
4 changed files with 54 additions and 4 deletions
|
@ -201,8 +201,8 @@ more = More
|
||||||
|
|
||||||
[editor]
|
[editor]
|
||||||
buttons.heading.tooltip = Add heading
|
buttons.heading.tooltip = Add heading
|
||||||
buttons.bold.tooltip = Add bold text
|
buttons.bold.tooltip = Add bold text (Ctrl+B / ⌘B)
|
||||||
buttons.italic.tooltip = Add italic text
|
buttons.italic.tooltip = Add italic text (Ctrl+I / ⌘I)
|
||||||
buttons.quote.tooltip = Quote text
|
buttons.quote.tooltip = Quote text
|
||||||
buttons.code.tooltip = Add code
|
buttons.code.tooltip = Add code
|
||||||
buttons.link.tooltip = Add a link
|
buttons.link.tooltip = Add a link
|
||||||
|
|
|
@ -25,8 +25,8 @@ Template Attributes:
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="markdown-toolbar-group">
|
<div class="markdown-toolbar-group">
|
||||||
<md-header class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header>
|
<md-header class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header>
|
||||||
<md-bold class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.bold.tooltip"}}">{{svg "octicon-bold"}}</md-bold>
|
<md-bold class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.bold.tooltip"}}" data-md-ctrl-shortcut="b">{{svg "octicon-bold"}}</md-bold>
|
||||||
<md-italic class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.italic.tooltip"}}">{{svg "octicon-italic"}}</md-italic>
|
<md-italic class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.italic.tooltip"}}" data-md-ctrl-shortcut="i">{{svg "octicon-italic"}}</md-italic>
|
||||||
</div>
|
</div>
|
||||||
<div class="markdown-toolbar-group">
|
<div class="markdown-toolbar-group">
|
||||||
<md-quote class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.quote.tooltip"}}">{{svg "octicon-quote"}}</md-quote>
|
<md-quote class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.quote.tooltip"}}">{{svg "octicon-quote"}}</md-quote>
|
||||||
|
|
|
@ -517,3 +517,36 @@ test('Multiple combo markdown: insert table', async ({page}) => {
|
||||||
await expect(comboboxTwo).toHaveValue('| Header | Header | Header |\n|---------|---------|---------|\n| Content | Content | Content |\n| Content | Content | Content |\n');
|
await expect(comboboxTwo).toHaveValue('| Header | Header | Header |\n|---------|---------|---------|\n| Content | Content | Content |\n| Content | Content | Content |\n');
|
||||||
await save_visual(page);
|
await save_visual(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Markdown bold/italic toolbar and shortcut', async ({page}) => {
|
||||||
|
const initText = `line 1\nline 2\nline 3\nline 4`;
|
||||||
|
|
||||||
|
const response = await page.goto('/user2/repo1/issues/new');
|
||||||
|
expect(response?.status()).toBe(200);
|
||||||
|
|
||||||
|
const textarea = page.locator('textarea[name=content]');
|
||||||
|
await textarea.fill(initText);
|
||||||
|
await textarea.focus();
|
||||||
|
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('line 1'), it.value.indexOf('line 2')));
|
||||||
|
|
||||||
|
// Cases: bold via toolbar, bold via shortcut, repeat w/ italics
|
||||||
|
page.locator('md-bold').click();
|
||||||
|
await expect(textarea).toHaveValue(`**line 1**\nline 2\nline 3\nline 4`);
|
||||||
|
page.locator('md-bold').click();
|
||||||
|
await expect(textarea).toHaveValue(`line 1\nline 2\nline 3\nline 4`);
|
||||||
|
|
||||||
|
await textarea.press('ControlOrMeta+KeyB');
|
||||||
|
await expect(textarea).toHaveValue(`**line 1**\nline 2\nline 3\nline 4`);
|
||||||
|
await textarea.press('ControlOrMeta+KeyB');
|
||||||
|
await expect(textarea).toHaveValue(`line 1\nline 2\nline 3\nline 4`);
|
||||||
|
|
||||||
|
page.locator('md-italic').click();
|
||||||
|
await expect(textarea).toHaveValue(`_line 1_\nline 2\nline 3\nline 4`);
|
||||||
|
page.locator('md-italic').click();
|
||||||
|
await expect(textarea).toHaveValue(`line 1\nline 2\nline 3\nline 4`);
|
||||||
|
|
||||||
|
await textarea.press('ControlOrMeta+KeyI');
|
||||||
|
await expect(textarea).toHaveValue(`_line 1_\nline 2\nline 3\nline 4`);
|
||||||
|
await textarea.press('ControlOrMeta+KeyI');
|
||||||
|
await expect(textarea).toHaveValue(`line 1\nline 2\nline 3\nline 4`);
|
||||||
|
});
|
||||||
|
|
|
@ -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-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}"]`);
|
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.
|
// Track whether any actual input or pointer action was made after focusing, and only intercept Tab presses after that.
|
||||||
this.tabEnabled = false;
|
this.tabEnabled = false;
|
||||||
// This tracks whether last Tab action was ignored, and if it immediately happens *again*, lose focus.
|
// 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.
|
if (!this.breakLine()) return; // Nothing changed, let the default handler work.
|
||||||
this.options?.onContentChanged?.(this, e);
|
this.options?.onContentChanged?.(this, e);
|
||||||
e.preventDefault();
|
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) {
|
} else if (noModifiers) {
|
||||||
this.activateTabHandling();
|
this.activateTabHandling();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue