diff --git a/third_party/muya/src/state/__tests__/tableEscapedPipe.spec.ts b/third_party/muya/src/state/__tests__/tableEscapedPipe.spec.ts new file mode 100644 index 0000000..a2f2249 --- /dev/null +++ b/third_party/muya/src/state/__tests__/tableEscapedPipe.spec.ts @@ -0,0 +1,69 @@ +// @vitest-environment happy-dom + +import { describe, expect, it, vi } from 'vitest'; +import { Muya } from '../../muya'; +import { MarkdownToState } from '../markdownToState'; +import ExportMarkdown from '../stateToMarkdown'; + +vi.mock('../../utils/prism/index', () => ({ + default: {}, + walkTokens: () => null, + loadedLanguages: new Set(), + transformAliasToOrigin: (language: string) => language, + loadLanguage: () => Promise.resolve([]), + search: () => [], +})); + +function boot(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + return muya; +} + +function codeTexts(muya: Muya): string[] { + return [...muya.domNode!.querySelectorAll('td code')].map(code => code.textContent ?? ''); +} + +function roundTrip(markdown: string): string { + const states = new MarkdownToState().generate(markdown); + return new ExportMarkdown({ listIndentation: 1 }).generate(states); +} + +const TABLE = [ + '| a | b |', + '| --- | --- |', + '| `\\|` | x |', + '| `\\|\\|` | y |', + '', +].join('\n'); + +describe('#4849: escaped pipe in a table cell', () => { + it('renders `\\|` inside code as | without the backslash in the editor', () => { + const muya = boot(TABLE); + + expect(codeTexts(muya)).toEqual(['|', '||']); + }); + + it('keeps the table structure', () => { + const muya = boot(TABLE); + const rows = muya.domNode!.querySelectorAll('tr'); + + expect(rows.length).toBe(3); + expect(rows[2].querySelectorAll('td').length).toBe(2); + }); + + it('round-trips the escaped pipes back to `\\|` / `\\|\\|`', () => { + const markdown = roundTrip(TABLE); + + expect(markdown).toContain('`\\|`'); + expect(markdown).toContain('`\\|\\|`'); + }); + + it('round-trips an escaped pipe in plain cell text', () => { + const markdown = roundTrip('| a | b |\n| --- | --- |\n| x \\| y | z |\n'); + + expect(markdown).toContain('x \\| y'); + }); +}); diff --git a/third_party/muya/src/state/markdownToState.ts b/third_party/muya/src/state/markdownToState.ts index a049bc5..e42878e 100644 --- a/third_party/muya/src/state/markdownToState.ts +++ b/third_party/muya/src/state/markdownToState.ts @@ -14,11 +14,6 @@ import logger from '../utils/logger'; import { lexBlock } from '../utils/marked'; const debug = logger('import markdown: '); -function restoreTableEscapeCharacters(text: string) { - // NOTE: markedjs replaces all escaped "|" ("\|") characters inside a cell with "|". - // We have to re-escape the character to not break the table. - return text.replace(/\|/g, '\\|'); -} function getOrderedListItemMarker(token: TBlockToken) { if (token.type !== 'list_item') @@ -313,7 +308,7 @@ export class MarkdownToState { children: header.map((h, i) => ({ name: 'table.cell' as const, meta: { align: align[i] || 'none' }, - text: restoreTableEscapeCharacters(h.text), + text: h.text, })), }); @@ -323,7 +318,7 @@ export class MarkdownToState { children: row.map((c, i) => ({ name: 'table.cell' as const, meta: { align: align[i] || 'none' }, - text: restoreTableEscapeCharacters(c.text), + text: c.text, })), })), );