diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 856515c7b4..0614bcf167 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -201,8 +201,8 @@ more = More [editor] buttons.heading.tooltip = Add heading -buttons.bold.tooltip = Add bold text -buttons.italic.tooltip = Add italic text +buttons.bold.tooltip = Add bold text (Ctrl+B / ⌘B) +buttons.italic.tooltip = Add italic text (Ctrl+I / ⌘I) buttons.quote.tooltip = Quote text buttons.code.tooltip = Add code buttons.link.tooltip = Add a link diff --git a/templates/shared/combomarkdowneditor.tmpl b/templates/shared/combomarkdowneditor.tmpl index febca5ec7f..ce1be8b48e 100644 --- a/templates/shared/combomarkdowneditor.tmpl +++ b/templates/shared/combomarkdowneditor.tmpl @@ -25,8 +25,8 @@ Template Attributes: {{end}}
{{svg "octicon-heading"}} - {{svg "octicon-bold"}} - {{svg "octicon-italic"}} + {{svg "octicon-bold"}} + {{svg "octicon-italic"}}
{{svg "octicon-quote"}} diff --git a/tests/e2e/markdown-editor.test.e2e.ts b/tests/e2e/markdown-editor.test.e2e.ts index d0ab88fe26..c2d4057bc9 100644 --- a/tests/e2e/markdown-editor.test.e2e.ts +++ b/tests/e2e/markdown-editor.test.e2e.ts @@ -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 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`); +}); diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index d96628c644..37544bf10f 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -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(); }