loading up the forgejo repo on tangled to test page performance

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 <gusted@noreply.codeberg.org>
Reviewed-by: Beowulf <beowulf@beocode.eu>

0ko afffbe29 8296a23d

Changed files
+94 -42
templates
tests
web_src
+37 -36
templates/shared/combomarkdowneditor.tmpl
··· 13 13 * EasyMDE: whether to display button for switching to legacy editor 14 14 */}} 15 15 <div {{if .ContainerId}}id="{{.ContainerId}}"{{end}} class="combo-markdown-editor {{.ContainerClasses}}" data-dropzone-parent-container="{{.DropzoneParentContainer}}"> 16 - {{if .MarkdownPreviewUrl}} 17 - <div class="ui top tabular menu"> 18 - <a href="#" class="active item" data-tab-for="markdown-writer">{{ctx.Locale.Tr "write"}}</a> 19 - <a href="#" class="item" data-tab-for="markdown-previewer" data-preview-url="{{.MarkdownPreviewUrl}}" data-preview-context="{{.MarkdownPreviewContext}}">{{ctx.Locale.Tr "preview"}}</a> 20 - </div> 21 - {{end}} 16 + 17 + <markdown-toolbar> 18 + {{if .MarkdownPreviewUrl}} 19 + <div class="switch"> 20 + <a href="#" class="active item" data-tab-for="markdown-writer">{{ctx.Locale.Tr "write"}}</a> 21 + <a href="#" class="item" data-tab-for="markdown-previewer" data-preview-url="{{.MarkdownPreviewUrl}}" data-preview-context="{{.MarkdownPreviewContext}}">{{ctx.Locale.Tr "preview"}}</a> 22 + </div> 23 + {{end}} 24 + <div class="markdown-toolbar-group"> 25 + <md-header class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header> 26 + <md-bold class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.bold.tooltip"}}">{{svg "octicon-bold"}}</md-bold> 27 + <md-italic class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.italic.tooltip"}}">{{svg "octicon-italic"}}</md-italic> 28 + </div> 29 + <div class="markdown-toolbar-group"> 30 + <md-quote class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.quote.tooltip"}}">{{svg "octicon-quote"}}</md-quote> 31 + <md-code class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.code.tooltip"}}">{{svg "octicon-code"}}</md-code> 32 + <button class="markdown-toolbar-button show-modal button" data-md-button data-md-action="new-link" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.link.tooltip"}}">{{svg "octicon-link"}}</button> 33 + </div> 34 + <div class="markdown-toolbar-group"> 35 + <md-unordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.unordered.tooltip"}}">{{svg "octicon-list-unordered"}}</md-unordered-list> 36 + <md-ordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.ordered.tooltip"}}">{{svg "octicon-list-ordered"}}</md-ordered-list> 37 + <md-task-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.task.tooltip"}}">{{svg "octicon-tasklist"}}</md-task-list> 38 + <button type="button" class="markdown-toolbar-button" data-md-button data-md-action="unindent" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.unindent.tooltip"}}">{{svg "octicon-arrow-left"}}</button> 39 + <button type="button" class="markdown-toolbar-button" data-md-button data-md-action="indent" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.indent.tooltip"}}">{{svg "octicon-arrow-right"}}</button> 40 + </div> 41 + <div class="markdown-toolbar-group"> 42 + <button type="button" class="markdown-toolbar-button show-modal button" data-md-button data-md-action="new-table" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.new_table.tooltip"}}">{{svg "octicon-table"}}</button> 43 + <md-mention class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.mention.tooltip"}}">{{svg "octicon-mention"}}</md-mention> 44 + <md-ref class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.ref.tooltip"}}">{{svg "octicon-cross-reference"}}</md-ref> 45 + </div> 46 + <div class="markdown-toolbar-group"> 47 + <button class="markdown-toolbar-button markdown-switch-monospace" data-md-button role="switch" data-enable-text="{{ctx.Locale.Tr "editor.buttons.enable_monospace_font"}}" data-disable-text="{{ctx.Locale.Tr "editor.buttons.disable_monospace_font"}}">{{svg "octicon-typography"}}</button> 48 + {{if .EasyMDE}} 49 + <button class="markdown-toolbar-button markdown-switch-easymde" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.switch_to_legacy.tooltip"}}">{{svg "octicon-arrow-switch"}}</button> 50 + {{end}} 51 + </div> 52 + </markdown-toolbar> 22 53 <div class="ui tab active" data-tab-panel="markdown-writer"> 23 - <markdown-toolbar> 24 - <div class="markdown-toolbar-group"> 25 - <md-header class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header> 26 - <md-bold class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.bold.tooltip"}}">{{svg "octicon-bold"}}</md-bold> 27 - <md-italic class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.italic.tooltip"}}">{{svg "octicon-italic"}}</md-italic> 28 - </div> 29 - <div class="markdown-toolbar-group"> 30 - <md-quote class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.quote.tooltip"}}">{{svg "octicon-quote"}}</md-quote> 31 - <md-code class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.code.tooltip"}}">{{svg "octicon-code"}}</md-code> 32 - <button class="markdown-toolbar-button show-modal button" data-md-button data-md-action="new-link" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.link.tooltip"}}">{{svg "octicon-link"}}</button> 33 - </div> 34 - <div class="markdown-toolbar-group"> 35 - <md-unordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.unordered.tooltip"}}">{{svg "octicon-list-unordered"}}</md-unordered-list> 36 - <md-ordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.ordered.tooltip"}}">{{svg "octicon-list-ordered"}}</md-ordered-list> 37 - <md-task-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.task.tooltip"}}">{{svg "octicon-tasklist"}}</md-task-list> 38 - <button type="button" class="markdown-toolbar-button" data-md-button data-md-action="unindent" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.unindent.tooltip"}}">{{svg "octicon-arrow-left"}}</button> 39 - <button type="button" class="markdown-toolbar-button" data-md-button data-md-action="indent" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.indent.tooltip"}}">{{svg "octicon-arrow-right"}}</button> 40 - </div> 41 - <div class="markdown-toolbar-group"> 42 - <button type="button" class="markdown-toolbar-button show-modal button" data-md-button data-md-action="new-table" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.new_table.tooltip"}}">{{svg "octicon-table"}}</button> 43 - <md-mention class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.mention.tooltip"}}">{{svg "octicon-mention"}}</md-mention> 44 - <md-ref class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.ref.tooltip"}}">{{svg "octicon-cross-reference"}}</md-ref> 45 - </div> 46 - <div class="markdown-toolbar-group"> 47 - <button class="markdown-toolbar-button markdown-switch-monospace" data-md-button role="switch" data-enable-text="{{ctx.Locale.Tr "editor.buttons.enable_monospace_font"}}" data-disable-text="{{ctx.Locale.Tr "editor.buttons.disable_monospace_font"}}">{{svg "octicon-typography"}}</button> 48 - {{if .EasyMDE}} 49 - <button class="markdown-toolbar-button markdown-switch-easymde" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.switch_to_legacy.tooltip"}}">{{svg "octicon-arrow-switch"}}</button> 50 - {{end}} 51 - </div> 52 - </markdown-toolbar> 53 54 <text-expander keys=": @" suffix=""> 54 55 <textarea class="markdown-text-editor js-quick-submit"{{if .TextareaName}} name="{{.TextareaName}}"{{end}}{{if .TextareaPlaceholder}} placeholder="{{.TextareaPlaceholder}}"{{end}}{{if .TextareaAriaLabel}} aria-label="{{.TextareaAriaLabel}}"{{end}}{{if .DisableAutosize}} data-disable-autosize="{{.DisableAutosize}}"{{end}}>{{.TextareaContent}}</textarea> 55 56 </text-expander>
+44 -4
tests/e2e/markdown-editor.test.e2e.ts
··· 39 39 await save_visual(page); 40 40 }); 41 41 42 - test('markdown indentation', async ({page}) => { 42 + test('Markdown indentation', async ({page}) => { 43 43 const initText = `* first\n* second\n* third\n* last`; 44 44 45 45 const response = await page.goto('/user2/repo1/issues/new'); ··· 109 109 await expect(textarea).toHaveValue(initText); 110 110 }); 111 111 112 - test('markdown list continuation', async ({page}) => { 112 + test('Markdown list continuation', async ({page}) => { 113 113 const initText = `* first\n* second`; 114 114 115 115 const response = await page.goto('/user2/repo1/issues/new'); ··· 202 202 } 203 203 }); 204 204 205 - test('markdown insert table', async ({page}) => { 205 + test('Markdown insert table', async ({page}) => { 206 206 const response = await page.goto('/user2/repo1/issues/new'); 207 207 expect(response?.status()).toBe(200); 208 208 ··· 225 225 await save_visual(page); 226 226 }); 227 227 228 - test('markdown insert link', async ({page}) => { 228 + test('Markdown insert link', async ({page}) => { 229 229 const response = await page.goto('/user2/repo1/issues/new'); 230 230 expect(response?.status()).toBe(200); 231 231 ··· 277 277 await textarea.press('Enter'); 278 278 await expect(textarea).toHaveValue(`* first\n* 😸\n* @user2 \n* `); 279 279 }); 280 + 281 + test('Combo Markdown: preview mode switch', async ({page}) => { 282 + // Load page with editor 283 + const response = await page.goto('/user2/repo1/issues/new'); 284 + expect(response?.status()).toBe(200); 285 + 286 + const toolbarItem = page.locator('md-header'); 287 + const editorPanel = page.locator('[data-tab-panel="markdown-writer"]'); 288 + const previewPanel = page.locator('[data-tab-panel="markdown-previewer"]'); 289 + 290 + // Verify correct visibility of related UI elements 291 + await expect(toolbarItem).toBeVisible(); 292 + await expect(editorPanel).toBeVisible(); 293 + await expect(previewPanel).toBeHidden(); 294 + 295 + // Fill some content 296 + const textarea = page.locator('textarea.markdown-text-editor'); 297 + await textarea.fill('**Content** :100: _100_'); 298 + 299 + // Switch to preview mode 300 + await page.locator('a[data-tab-for="markdown-previewer"]').click(); 301 + 302 + // Verify that the related UI elements were switched correctly 303 + await expect(toolbarItem).toBeHidden(); 304 + await expect(editorPanel).toBeHidden(); 305 + await expect(previewPanel).toBeVisible(); 306 + await save_visual(page); 307 + 308 + // Verify that some content rendered 309 + await expect(page.locator('[data-tab-panel="markdown-previewer"] .emoji[data-alias="100"]')).toBeVisible(); 310 + 311 + // Switch back to edit mode 312 + await page.locator('a[data-tab-for="markdown-writer"]').click(); 313 + 314 + // Verify that the related UI elements were switched back correctly 315 + await expect(toolbarItem).toBeVisible(); 316 + await expect(editorPanel).toBeVisible(); 317 + await expect(previewPanel).toBeHidden(); 318 + await save_visual(page); 319 + });
+8
web_src/css/editor/combomarkdowneditor.css
··· 11 11 flex-wrap: wrap; 12 12 } 13 13 14 + markdown-toolbar .switch .item { 15 + padding: 0.25em 1em; 16 + } 17 + 18 + .markdown-toolbar-hidden .markdown-toolbar-button { 19 + display: none; 20 + } 21 + 14 22 .combo-markdown-editor .markdown-toolbar-group { 15 23 display: flex; 16 24 }
+4 -1
web_src/js/features/comp/ComboMarkdownEditor.js
··· 151 151 152 152 setupTab() { 153 153 const $container = $(this.container); 154 - const tabs = $container[0].querySelectorAll('.tabular.menu > .item'); 154 + const tabs = $container[0].querySelectorAll('.switch > .item'); 155 155 156 156 // Fomantic Tab requires the "data-tab" to be globally unique. 157 157 // So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic. ··· 159 159 const tabPreviewer = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-previewer'); 160 160 tabEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`); 161 161 tabPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`); 162 + const toolbar = $container[0].querySelector('markdown-toolbar'); 162 163 const panelEditor = $container[0].querySelector('.ui.tab[data-tab-panel="markdown-writer"]'); 163 164 const panelPreviewer = $container[0].querySelector('.ui.tab[data-tab-panel="markdown-previewer"]'); 164 165 panelEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`); 165 166 panelPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`); 166 167 167 168 tabEditor.addEventListener('click', () => { 169 + toolbar.classList.remove('markdown-toolbar-hidden'); 168 170 requestAnimationFrame(() => { 169 171 this.focus(); 170 172 }); ··· 177 179 this.previewMode = this.options.previewMode ?? 'comment'; 178 180 this.previewWiki = this.options.previewWiki ?? false; 179 181 tabPreviewer.addEventListener('click', async () => { 182 + toolbar.classList.add('markdown-toolbar-hidden'); 180 183 const formData = new FormData(); 181 184 formData.append('mode', this.previewMode); 182 185 formData.append('context', this.previewContext);
+1 -1
web_src/js/features/repo-legacy.js
··· 469 469 editContentZone.querySelector('button[data-button-name="cancel-edit"]').addEventListener('click', cancelAndReset); 470 470 editContentZone.querySelector('button[data-button-name="save-edit"]').addEventListener('click', saveAndRefresh); 471 471 } else { 472 - const tabEditor = editContentZone.querySelector('.combo-markdown-editor').querySelector('.tabular.menu > a[data-tab-for=markdown-writer]'); 472 + const tabEditor = editContentZone.querySelector('.combo-markdown-editor').querySelector('.switch > a[data-tab-for=markdown-writer]'); 473 473 tabEditor?.click(); 474 474 } 475 475