From 8c4f9d664bac09644ec31c1ceea10daa7c796ddf Mon Sep 17 00:00:00 2001 From: littlefrontender Date: Mon, 27 Apr 2026 12:58:09 +0400 Subject: [PATCH 1/2] fix: update customMarkdownConverter to prevent escaping of '<' in text and table cells, and adjust regex for special characters --- src/editor/customMarkdownConverter.test.ts | 35 ++++++++++++++++++++-- src/editor/customMarkdownConverter.ts | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/editor/customMarkdownConverter.test.ts b/src/editor/customMarkdownConverter.test.ts index d4f1f54..d94d862 100644 --- a/src/editor/customMarkdownConverter.test.ts +++ b/src/editor/customMarkdownConverter.test.ts @@ -62,7 +62,7 @@ describe("blocksToMarkdown", () => { expect(blocksToMarkdown(blocks)).toBe(""); }); - it("preserves HTML comments inline among text and still escapes stray angle brackets", () => { + it("preserves HTML comments inline among text without escaping stray angle brackets", () => { const blocks: CustomEditorBlock[] = [ { id: "c2", @@ -75,7 +75,38 @@ describe("blocksToMarkdown", () => { }, ]; - expect(blocksToMarkdown(blocks)).toBe("before after \\
"); + expect(blocksToMarkdown(blocks)).toBe("before after
"); + }); + + it("does not escape `<` in plain text (e.g. comparison operators)", () => { + const blocks: CustomEditorBlock[] = [ + { + id: "p_lt", + type: "paragraph", + props: baseProps, + content: [ + { type: "text", text: "< 768px is mobile", styles: {} }, + ], + children: [], + }, + ]; + + expect(blocksToMarkdown(blocks)).toBe("< 768px is mobile"); + }); + + it("does not escape `<` in table cells (viewport breakpoints case)", () => { + const markdown = [ + "| Viewport Width | Layout Expected | Nav Behavior |", + "| --- | --- | --- |", + "| < 768px | Single column | Hamburger menu |", + "| 768px – 1024px | Two column | Collapsed sidebar |", + "| > 1024px | Full desktop layout | Full nav visible |", + ].join("\n"); + + const blocks = markdownToBlocks(markdown); + const out = blocksToMarkdown(blocks as CustomEditorBlock[]); + expect(out).toBe(markdown); + expect(out).not.toContain("\\<"); }); it("places bold markers outside leading/trailing spaces", () => { diff --git a/src/editor/customMarkdownConverter.ts b/src/editor/customMarkdownConverter.ts index 50bb2d5..a9fa921 100644 --- a/src/editor/customMarkdownConverter.ts +++ b/src/editor/customMarkdownConverter.ts @@ -60,7 +60,7 @@ const headingPrefixes: Record = { 6: "######", }; -const SPECIAL_CHAR_REGEX = /([*_`~()<\\])/g; +const SPECIAL_CHAR_REGEX = /([*_`~()\\])/g; const HTML_COMMENT_REGEX = //g; const HTML_SPAN_REGEX = /<\/?span[^>]*>/g; const HTML_UNDERLINE_REGEX = /<\/?u>/g; From e3744d79c677a9edee816ec6cb09d8898380661b Mon Sep 17 00:00:00 2001 From: littlefrontender Date: Mon, 27 Apr 2026 13:13:10 +0400 Subject: [PATCH 2/2] feat: enhance customMarkdownConverter to prevent escaping of markdown characters in inline code and fenced code blocks, and improve related tests --- src/editor/customMarkdownConverter.test.ts | 60 ++++++++++++++++++++++ src/editor/customMarkdownConverter.ts | 6 ++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/editor/customMarkdownConverter.test.ts b/src/editor/customMarkdownConverter.test.ts index d94d862..f5a4e0e 100644 --- a/src/editor/customMarkdownConverter.test.ts +++ b/src/editor/customMarkdownConverter.test.ts @@ -109,6 +109,66 @@ describe("blocksToMarkdown", () => { expect(out).not.toContain("\\<"); }); + it("does not escape markdown chars inside inline code", () => { + const blocks: CustomEditorBlock[] = [ + { + id: "p_code", + type: "paragraph", + props: baseProps, + content: [ + { type: "text", text: "**bold**", styles: { code: true } }, + ], + children: [], + }, + ]; + + expect(blocksToMarkdown(blocks)).toBe("`**bold**`"); + }); + + it("does not escape markdown chars inside inline code in a table (syntax/rendered case)", () => { + const markdown = [ + "| Syntax | Rendered As |", + "| --- | --- |", + "| `**bold**` | **bold** |", + "| `*italic*` | _italic_ |", + "| `~~strike~~` | ~~strike~~ |", + ].join("\n"); + + const blocks = markdownToBlocks(markdown); + const out = blocksToMarkdown(blocks as CustomEditorBlock[]); + expect(out).toBe(markdown); + expect(out).not.toMatch(/\\[*_~]/); + }); + + it("does not escape markdown chars inside fenced code blocks", () => { + const markdown = [ + "```", + "**bold** _italic_ ~~strike~~
", + "```", + ].join("\n"); + + const blocks = markdownToBlocks(markdown); + const out = blocksToMarkdown(blocks as CustomEditorBlock[]); + expect(out).toBe(markdown); + expect(out).not.toMatch(/\\[*_~<]/); + }); + + it("still escapes literal backticks inside inline code", () => { + const blocks: CustomEditorBlock[] = [ + { + id: "p_tick", + type: "paragraph", + props: baseProps, + content: [ + { type: "text", text: "a`b", styles: { code: true } }, + ], + children: [], + }, + ]; + + expect(blocksToMarkdown(blocks)).toBe("`a\\`b`"); + }); + it("places bold markers outside leading/trailing spaces", () => { const blocks: CustomEditorBlock[] = [ { diff --git a/src/editor/customMarkdownConverter.ts b/src/editor/customMarkdownConverter.ts index a9fa921..80e3a1d 100644 --- a/src/editor/customMarkdownConverter.ts +++ b/src/editor/customMarkdownConverter.ts @@ -234,7 +234,9 @@ function inlineToMarkdown(content: CustomEditorBlock["content"]): string { i += 2; continue; } - result.push(applyTextStyles(escapeMarkdown(item.text), item.styles)); + const isCode = (item.styles as any)?.code === true; + const rendered = isCode ? item.text : escapeMarkdown(item.text); + result.push(applyTextStyles(rendered, item.styles)); i += 1; continue; } @@ -334,7 +336,7 @@ function serializeBlock( case "codeBlock": { const language = (block.props as any).language || ""; const fence = "```" + language; - const body = inlineToMarkdown(block.content); + const body = inlineContentToPlainText(block.content); lines.push(fence); if (body.length > 0) { lines.push(body);