From afffbe29827c6278184b15ef172b8c0eefb3810f Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Mon, 7 Apr 2025 13:47:13 +0000 Subject: [PATCH] ui: use switch for markdown editor modes (#7481) Replaces https://codeberg.org/forgejo/forgejo/pulls/5478 Closes https://codeberg.org/forgejo/forgejo/issues/244 Use switch for preview mode switching instead of tabs. It is placed in line with the toolbar buttons. Preview: * https://codeberg.org/attachments/38910747-c14c-41d1-9935-c35f3e17033b * https://codeberg.org/attachments/ff8ea47a-f157-424f-8b7f-af1008d5e8b5 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7481 Reviewed-by: Gusted Reviewed-by: Beowulf --- templates/shared/combomarkdowneditor.tmpl | 73 ++++++++++--------- tests/e2e/markdown-editor.test.e2e.ts | 48 +++++++++++- web_src/css/editor/combomarkdowneditor.css | 8 ++ .../js/features/comp/ComboMarkdownEditor.js | 5 +- web_src/js/features/repo-legacy.js | 2 +- 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/templates/shared/combomarkdowneditor.tmpl b/templates/shared/combomarkdowneditor.tmpl index 1ea22660f7..6d82c37c58 100644 --- a/templates/shared/combomarkdowneditor.tmpl +++ b/templates/shared/combomarkdowneditor.tmpl @@ -13,43 +13,44 @@ Template Attributes: * EasyMDE: whether to display button for switching to legacy editor */}}
- {{if .MarkdownPreviewUrl}} - - {{end}} + + + {{if .MarkdownPreviewUrl}} + + {{end}} +
+ {{svg "octicon-heading"}} + {{svg "octicon-bold"}} + {{svg "octicon-italic"}} +
+
+ {{svg "octicon-quote"}} + {{svg "octicon-code"}} + +
+
+ {{svg "octicon-list-unordered"}} + {{svg "octicon-list-ordered"}} + {{svg "octicon-tasklist"}} + + +
+
+ + {{svg "octicon-mention"}} + {{svg "octicon-cross-reference"}} +
+
+ + {{if .EasyMDE}} + + {{end}} +
+
- -
- {{svg "octicon-heading"}} - {{svg "octicon-bold"}} - {{svg "octicon-italic"}} -
-
- {{svg "octicon-quote"}} - {{svg "octicon-code"}} - -
-
- {{svg "octicon-list-unordered"}} - {{svg "octicon-list-ordered"}} - {{svg "octicon-tasklist"}} - - -
-
- - {{svg "octicon-mention"}} - {{svg "octicon-cross-reference"}} -
-
- - {{if .EasyMDE}} - - {{end}} -
-
diff --git a/tests/e2e/markdown-editor.test.e2e.ts b/tests/e2e/markdown-editor.test.e2e.ts index 39ced25bc3..c69c9a7f0c 100644 --- a/tests/e2e/markdown-editor.test.e2e.ts +++ b/tests/e2e/markdown-editor.test.e2e.ts @@ -39,7 +39,7 @@ test('Markdown image preview behaviour', async ({page}, workerInfo) => { await save_visual(page); }); -test('markdown indentation', async ({page}) => { +test('Markdown indentation', async ({page}) => { const initText = `* first\n* second\n* third\n* last`; const response = await page.goto('/user2/repo1/issues/new'); @@ -109,7 +109,7 @@ test('markdown indentation', async ({page}) => { await expect(textarea).toHaveValue(initText); }); -test('markdown list continuation', async ({page}) => { +test('Markdown list continuation', async ({page}) => { const initText = `* first\n* second`; const response = await page.goto('/user2/repo1/issues/new'); @@ -202,7 +202,7 @@ test('markdown list continuation', async ({page}) => { } }); -test('markdown insert table', async ({page}) => { +test('Markdown insert table', async ({page}) => { const response = await page.goto('/user2/repo1/issues/new'); expect(response?.status()).toBe(200); @@ -225,7 +225,7 @@ test('markdown insert table', async ({page}) => { await save_visual(page); }); -test('markdown insert link', async ({page}) => { +test('Markdown insert link', async ({page}) => { const response = await page.goto('/user2/repo1/issues/new'); expect(response?.status()).toBe(200); @@ -277,3 +277,43 @@ test('text expander has higher prio then prefix continuation', async ({page}) => await textarea.press('Enter'); await expect(textarea).toHaveValue(`* first\n* 😸\n* @user2 \n* `); }); + +test('Combo Markdown: preview mode switch', async ({page}) => { + // Load page with editor + const response = await page.goto('/user2/repo1/issues/new'); + expect(response?.status()).toBe(200); + + const toolbarItem = page.locator('md-header'); + const editorPanel = page.locator('[data-tab-panel="markdown-writer"]'); + const previewPanel = page.locator('[data-tab-panel="markdown-previewer"]'); + + // Verify correct visibility of related UI elements + await expect(toolbarItem).toBeVisible(); + await expect(editorPanel).toBeVisible(); + await expect(previewPanel).toBeHidden(); + + // Fill some content + const textarea = page.locator('textarea.markdown-text-editor'); + await textarea.fill('**Content** :100: _100_'); + + // Switch to preview mode + await page.locator('a[data-tab-for="markdown-previewer"]').click(); + + // Verify that the related UI elements were switched correctly + await expect(toolbarItem).toBeHidden(); + await expect(editorPanel).toBeHidden(); + await expect(previewPanel).toBeVisible(); + await save_visual(page); + + // Verify that some content rendered + await expect(page.locator('[data-tab-panel="markdown-previewer"] .emoji[data-alias="100"]')).toBeVisible(); + + // Switch back to edit mode + await page.locator('a[data-tab-for="markdown-writer"]').click(); + + // Verify that the related UI elements were switched back correctly + await expect(toolbarItem).toBeVisible(); + await expect(editorPanel).toBeVisible(); + await expect(previewPanel).toBeHidden(); + await save_visual(page); +}); diff --git a/web_src/css/editor/combomarkdowneditor.css b/web_src/css/editor/combomarkdowneditor.css index f190c7eb1f..a40f0c175c 100644 --- a/web_src/css/editor/combomarkdowneditor.css +++ b/web_src/css/editor/combomarkdowneditor.css @@ -11,6 +11,14 @@ flex-wrap: wrap; } +markdown-toolbar .switch .item { + padding: 0.25em 1em; +} + +.markdown-toolbar-hidden .markdown-toolbar-button { + display: none; +} + .combo-markdown-editor .markdown-toolbar-group { display: flex; } diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index efd068f354..53c6b85728 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -151,7 +151,7 @@ class ComboMarkdownEditor { setupTab() { const $container = $(this.container); - const tabs = $container[0].querySelectorAll('.tabular.menu > .item'); + const tabs = $container[0].querySelectorAll('.switch > .item'); // Fomantic Tab requires the "data-tab" to be globally unique. // So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic. @@ -159,12 +159,14 @@ class ComboMarkdownEditor { const tabPreviewer = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-previewer'); tabEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`); tabPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`); + const toolbar = $container[0].querySelector('markdown-toolbar'); const panelEditor = $container[0].querySelector('.ui.tab[data-tab-panel="markdown-writer"]'); const panelPreviewer = $container[0].querySelector('.ui.tab[data-tab-panel="markdown-previewer"]'); panelEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`); panelPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`); tabEditor.addEventListener('click', () => { + toolbar.classList.remove('markdown-toolbar-hidden'); requestAnimationFrame(() => { this.focus(); }); @@ -177,6 +179,7 @@ class ComboMarkdownEditor { this.previewMode = this.options.previewMode ?? 'comment'; this.previewWiki = this.options.previewWiki ?? false; tabPreviewer.addEventListener('click', async () => { + toolbar.classList.add('markdown-toolbar-hidden'); const formData = new FormData(); formData.append('mode', this.previewMode); formData.append('context', this.previewContext); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index c74ba1efbe..2f7db50e64 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -469,7 +469,7 @@ async function onEditContent(event) { editContentZone.querySelector('button[data-button-name="cancel-edit"]').addEventListener('click', cancelAndReset); editContentZone.querySelector('button[data-button-name="save-edit"]').addEventListener('click', saveAndRefresh); } else { - const tabEditor = editContentZone.querySelector('.combo-markdown-editor').querySelector('.tabular.menu > a[data-tab-for=markdown-writer]'); + const tabEditor = editContentZone.querySelector('.combo-markdown-editor').querySelector('.switch > a[data-tab-for=markdown-writer]'); tabEditor?.click(); }