mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-07-19 01:29:40 +02:00
fix(ui): multiple ComboMarkdownEditors on one page interfere (#8417)
When there are multiple combo-markdown-editors, then only the first will get changes from the toolbar buttons. Fixes: #6742 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8417 Reviewed-by: Beowulf <beowulf@beocode.eu> Co-authored-by: zokki <zokki.softwareschmiede@gmail.com> Co-committed-by: zokki <zokki.softwareschmiede@gmail.com>
This commit is contained in:
parent
81e59014da
commit
1937fcf476
3 changed files with 91 additions and 17 deletions
|
@ -79,6 +79,22 @@ func DeclareGitRepos(t *testing.T) func() {
|
|||
Filename: "a-file",
|
||||
Versions: []string{"{a}{а}"},
|
||||
}}, nil),
|
||||
newRepo(t, 2, "multiple-combo-boxes", nil, []FileChanges{{
|
||||
Filename: ".forgejo/issue_template/multi-combo-boxes.yaml",
|
||||
Versions: []string{`
|
||||
name: "Multiple combo-boxes"
|
||||
description: "To show something"
|
||||
body:
|
||||
- type: textarea
|
||||
id: textarea-one
|
||||
attributes:
|
||||
label: one
|
||||
- type: textarea
|
||||
id: textarea-two
|
||||
attributes:
|
||||
label: two
|
||||
`},
|
||||
}}, nil),
|
||||
newRepo(t, 11, "dependency-test", &tests.DeclarativeRepoOptions{
|
||||
UnitConfig: optional.Some(map[unit_model.Type]convert.Conversion{
|
||||
unit_model.TypeIssues: &repo_model.IssuesConfig{
|
||||
|
|
|
@ -456,3 +456,62 @@ test('Combo Markdown: preview mode switch', async ({page}) => {
|
|||
await expect(previewPanel).toBeHidden();
|
||||
await save_visual(page);
|
||||
});
|
||||
|
||||
test('Multiple combo markdown: insert table', async ({page}) => {
|
||||
const response = await page.goto('/user2/multiple-combo-boxes/issues/new?template=.forgejo%2fissue_template%2fmulti-combo-boxes.yaml');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
// check that there are two textareas
|
||||
const textareaOne = page.locator('textarea[name=form-field-textarea-one]');
|
||||
const comboboxOne = page.locator('textarea#_combo_markdown_editor_0');
|
||||
await expect(textareaOne).toBeVisible();
|
||||
await expect(comboboxOne).toBeHidden();
|
||||
const textareaTwo = page.locator('textarea[name=form-field-textarea-two]');
|
||||
const comboboxTwo = page.locator('textarea#_combo_markdown_editor_1');
|
||||
await expect(textareaTwo).toBeVisible();
|
||||
await expect(comboboxTwo).toBeHidden();
|
||||
|
||||
// focus first one and add table to it
|
||||
await textareaOne.click();
|
||||
await expect(comboboxOne).toBeVisible();
|
||||
await expect(comboboxTwo).toBeHidden();
|
||||
|
||||
const newTableButtonOne = page.locator('[for="_combo_markdown_editor_0"] button[data-md-action="new-table"]');
|
||||
await newTableButtonOne.click();
|
||||
|
||||
const newTableModalOne = page.locator('div[data-markdown-table-modal-id="0"]');
|
||||
await expect(newTableModalOne).toBeVisible();
|
||||
|
||||
await newTableModalOne.locator('input[name="table-rows"]').fill('3');
|
||||
await newTableModalOne.locator('input[name="table-columns"]').fill('2');
|
||||
|
||||
await newTableModalOne.locator('button[data-selector-name="ok-button"]').click();
|
||||
|
||||
await expect(newTableModalOne).toBeHidden();
|
||||
|
||||
await expect(comboboxOne).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n');
|
||||
await expect(comboboxTwo).toBeEmpty();
|
||||
await save_visual(page);
|
||||
|
||||
// focus second one and add table to it
|
||||
await textareaTwo.click();
|
||||
await expect(comboboxOne).toBeHidden();
|
||||
await expect(comboboxTwo).toBeVisible();
|
||||
|
||||
const newTableButtonTwo = page.locator('[for="_combo_markdown_editor_1"] button[data-md-action="new-table"]');
|
||||
await newTableButtonTwo.click();
|
||||
|
||||
const newTableModalTwo = page.locator('div[data-markdown-table-modal-id="1"]');
|
||||
await expect(newTableModalTwo).toBeVisible();
|
||||
|
||||
await newTableModalTwo.locator('input[name="table-rows"]').fill('2');
|
||||
await newTableModalTwo.locator('input[name="table-columns"]').fill('3');
|
||||
|
||||
await newTableModalTwo.locator('button[data-selector-name="ok-button"]').click();
|
||||
|
||||
await expect(newTableModalTwo).toBeHidden();
|
||||
|
||||
await expect(comboboxOne).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n');
|
||||
await expect(comboboxTwo).toHaveValue('| Header | Header | Header |\n|---------|---------|---------|\n| Content | Content | Content |\n| Content | Content | Content |\n');
|
||||
await save_visual(page);
|
||||
});
|
||||
|
|
|
@ -11,8 +11,6 @@ import {initTextExpander} from './TextExpander.js';
|
|||
import {showErrorToast, showHintToast} from '../../modules/toast.js';
|
||||
import {POST} from '../../modules/fetch.js';
|
||||
|
||||
let elementIdCounter = 0;
|
||||
|
||||
/**
|
||||
* validate if the given textarea is non-empty.
|
||||
* @param {HTMLElement} textarea - The textarea element to be validated.
|
||||
|
@ -39,10 +37,13 @@ export function validateTextareaNonEmpty(textarea) {
|
|||
const listPrefixRegex = /^\s*((\d+)[.)]\s|[-*+]\s{1,4}\[[ x]\]\s?|[-*+]\s|(>\s?)+)?/;
|
||||
|
||||
class ComboMarkdownEditor {
|
||||
static idSuffixCounter = 0;
|
||||
|
||||
constructor(container, options = {}) {
|
||||
container._giteaComboMarkdownEditor = this;
|
||||
this.options = options;
|
||||
this.container = container;
|
||||
this.elementIdSuffix = ComboMarkdownEditor.idSuffixCounter++;
|
||||
}
|
||||
|
||||
async init() {
|
||||
|
@ -55,8 +56,6 @@ class ComboMarkdownEditor {
|
|||
this.setupLinkInserter();
|
||||
|
||||
await this.switchToUserPreference();
|
||||
|
||||
elementIdCounter++;
|
||||
}
|
||||
|
||||
applyEditorHeights(el, heights) {
|
||||
|
@ -74,7 +73,7 @@ class ComboMarkdownEditor {
|
|||
setupTextarea() {
|
||||
this.textarea = this.container.querySelector('.markdown-text-editor');
|
||||
this.textarea._giteaComboMarkdownEditor = this;
|
||||
this.textarea.id = `_combo_markdown_editor_${elementIdCounter}`;
|
||||
this.textarea.id = `_combo_markdown_editor_${this.elementIdSuffix}`;
|
||||
this.textarea.addEventListener('input', (e) => this.options?.onContentChanged?.(this, e));
|
||||
this.applyEditorHeights(this.textarea, this.options.editorHeights);
|
||||
|
||||
|
@ -96,8 +95,8 @@ class ComboMarkdownEditor {
|
|||
this.textareaMarkdownToolbar.querySelector('button[data-md-action="unindent"]')?.addEventListener('click', () => {
|
||||
this.indentSelection(true, false);
|
||||
});
|
||||
this.textareaMarkdownToolbar.querySelector('button[data-md-action="new-table"]')?.setAttribute('data-modal', `div[data-markdown-table-modal-id="${elementIdCounter}"]`);
|
||||
this.textareaMarkdownToolbar.querySelector('button[data-md-action="new-link"]')?.setAttribute('data-modal', `div[data-markdown-link-modal-id="${elementIdCounter}"]`);
|
||||
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}"]`);
|
||||
|
||||
// Track whether any actual input or pointer action was made after focusing, and only intercept Tab presses after that.
|
||||
this.tabEnabled = false;
|
||||
|
@ -195,7 +194,7 @@ class ComboMarkdownEditor {
|
|||
setupDropzone() {
|
||||
const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
|
||||
if (dropzoneParentContainer) {
|
||||
this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone');
|
||||
this.dropzone = this.container.closest(dropzoneParentContainer)?.querySelector('.dropzone');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,13 +206,13 @@ class ComboMarkdownEditor {
|
|||
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
|
||||
const tabEditor = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-writer');
|
||||
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}`);
|
||||
tabEditor.setAttribute('data-tab', `markdown-writer-${this.elementIdSuffix}`);
|
||||
tabPreviewer.setAttribute('data-tab', `markdown-previewer-${this.elementIdSuffix}`);
|
||||
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}`);
|
||||
panelEditor.setAttribute('data-tab', `markdown-writer-${this.elementIdSuffix}`);
|
||||
panelPreviewer.setAttribute('data-tab', `markdown-previewer-${this.elementIdSuffix}`);
|
||||
|
||||
tabEditor.addEventListener('click', () => {
|
||||
toolbar.classList.remove('markdown-toolbar-hidden');
|
||||
|
@ -276,10 +275,10 @@ class ComboMarkdownEditor {
|
|||
|
||||
setupTableInserter() {
|
||||
const newTableModal = this.container.querySelector('div[data-modal-name="new-markdown-table"]');
|
||||
newTableModal.setAttribute('data-markdown-table-modal-id', elementIdCounter);
|
||||
newTableModal.setAttribute('data-markdown-table-modal-id', this.elementIdSuffix);
|
||||
|
||||
const button = newTableModal.querySelector('button[data-selector-name="ok-button"]');
|
||||
button.setAttribute('data-element-id', elementIdCounter);
|
||||
button.setAttribute('data-element-id', this.elementIdSuffix);
|
||||
button.addEventListener('click', this.addNewTable);
|
||||
}
|
||||
|
||||
|
@ -311,8 +310,8 @@ class ComboMarkdownEditor {
|
|||
|
||||
setupLinkInserter() {
|
||||
const newLinkModal = this.container.querySelector('div[data-modal-name="new-markdown-link"]');
|
||||
newLinkModal.setAttribute('data-markdown-link-modal-id', elementIdCounter);
|
||||
const textarea = document.getElementById(`_combo_markdown_editor_${elementIdCounter}`);
|
||||
newLinkModal.setAttribute('data-markdown-link-modal-id', this.elementIdSuffix);
|
||||
const textarea = document.getElementById(`_combo_markdown_editor_${this.elementIdSuffix}`);
|
||||
|
||||
$(newLinkModal).modal({
|
||||
// Pre-fill the description field from the selection to create behavior similar
|
||||
|
@ -331,7 +330,7 @@ class ComboMarkdownEditor {
|
|||
});
|
||||
|
||||
const button = newLinkModal.querySelector('button[data-selector-name="ok-button"]');
|
||||
button.setAttribute('data-element-id', elementIdCounter);
|
||||
button.setAttribute('data-element-id', this.elementIdSuffix);
|
||||
button.addEventListener('click', this.addNewLink);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue