From 675d05fd7a88a98647d827e1234595580fe3d5a1 Mon Sep 17 00:00:00 2001 From: littlefrontender Date: Tue, 5 May 2026 15:32:26 +0400 Subject: [PATCH] feat: enhance markdownToBlocks and customMarkdownConverter to support various code block language identifiers and improve serialization tests --- src/editor/customMarkdownConverter.test.ts | 84 ++++++++++++++++++++++ src/editor/customMarkdownConverter.ts | 13 +++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/editor/customMarkdownConverter.test.ts b/src/editor/customMarkdownConverter.test.ts index fa4f713..86eb1d9 100644 --- a/src/editor/customMarkdownConverter.test.ts +++ b/src/editor/customMarkdownConverter.test.ts @@ -2685,6 +2685,90 @@ describe("markdownToBlocks", () => { }, ]); }); + + it("preserves opening-fence content when it is not a clean language identifier", () => { + const markdown = [ + "```curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\", + "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two", + "```", + ].join("\n"); + const blocks = markdownToBlocks(markdown); + expect(blocks).toEqual([ + { + type: "codeBlock", + props: { language: "" }, + content: [ + { + type: "text", + text: [ + "curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\", + "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two", + ].join("\n"), + styles: {}, + }, + ], + children: [], + }, + ]); + }); + + it("round-trips an opening-fence-with-content code block to stable markdown", () => { + const markdown = [ + "```curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\", + "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two", + "```", + ].join("\n"); + const blocks = markdownToBlocks(markdown); + const serialized = blocksToMarkdown(blocks as CustomEditorBlock[]); + expect(serialized).toBe( + [ + "```", + "curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\", + "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two", + "```", + ].join("\n"), + ); + expect(markdownToBlocks(serialized)).toEqual(blocks); + }); + + it("preserves hyphenated language identifiers like shell-session", () => { + const markdown = ["```shell-session", "$ ls", "```"].join("\n"); + const blocks = markdownToBlocks(markdown); + expect(blocks).toEqual([ + { + type: "codeBlock", + props: { language: "shell-session" }, + content: [{ type: "text", text: "$ ls", styles: {} }], + children: [], + }, + ]); + }); + + it("preserves digit-prefixed language identifiers like 1c-enterprise", () => { + const markdown = ["```1c-enterprise", "code", "```"].join("\n"); + const blocks = markdownToBlocks(markdown); + expect(blocks).toEqual([ + { + type: "codeBlock", + props: { language: "1c-enterprise" }, + content: [{ type: "text", text: "code", styles: {} }], + children: [], + }, + ]); + }); + + it("sanitizes a malformed in-memory language prop on serialize", () => { + const blocks: CustomEditorBlock[] = [ + { + id: "1", + type: "codeBlock", + props: { ...baseProps, language: "curl http://x" } as any, + content: [{ type: "text", text: "body", styles: {} }] as any, + children: [], + }, + ]; + expect(blocksToMarkdown(blocks)).toBe(["```", "body", "```"].join("\n")); + }); }); describe("file block serialization", () => { diff --git a/src/editor/customMarkdownConverter.ts b/src/editor/customMarkdownConverter.ts index 7870787..725bdc0 100644 --- a/src/editor/customMarkdownConverter.ts +++ b/src/editor/customMarkdownConverter.ts @@ -334,7 +334,8 @@ function serializeBlock( return lines; } case "codeBlock": { - const language = (block.props as any).language || ""; + const rawLanguage = (block.props as any).language || ""; + const language = /[\s`]/.test(rawLanguage) ? "" : rawLanguage; const fence = "```" + language; const body = inlineContentToPlainText(block.content); lines.push(fence); @@ -1290,8 +1291,16 @@ function parseCodeBlock(lines: string[], index: number): { block: CustomPartialB }; } - const language = afterOpening.trim(); + const info = afterOpening.trim(); + let language = ""; const body: string[] = []; + if (info.length > 0) { + if (/[\s`]/.test(info)) { + body.push(afterOpening); + } else { + language = info; + } + } let next = index + 1; while (next < lines.length && !lines[next].startsWith("```") ) { body.push(lines[next]);