Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 93 additions & 2 deletions src/editor/customMarkdownConverter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe("blocksToMarkdown", () => {
expect(blocksToMarkdown(blocks)).toBe("<!-- ai/agent generated description -->");
});

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",
Expand All @@ -75,7 +75,98 @@ describe("blocksToMarkdown", () => {
},
];

expect(blocksToMarkdown(blocks)).toBe("before <!-- note --> after \\<div>");
expect(blocksToMarkdown(blocks)).toBe("before <!-- note --> after <div>");
});

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("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~~ <div>",
"```",
].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", () => {
Expand Down
8 changes: 5 additions & 3 deletions src/editor/customMarkdownConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const headingPrefixes: Record<number, string> = {
6: "######",
};

const SPECIAL_CHAR_REGEX = /([*_`~()<\\])/g;
const SPECIAL_CHAR_REGEX = /([*_`~()\\])/g;
const HTML_COMMENT_REGEX = /<!--[\s\S]*?-->/g;
const HTML_SPAN_REGEX = /<\/?span[^>]*>/g;
const HTML_UNDERLINE_REGEX = /<\/?u>/g;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
Loading