");
+ });
+
+ 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~~
",
+ "```",
+ ].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", () => {
diff --git a/src/editor/customMarkdownConverter.ts b/src/editor/customMarkdownConverter.ts
index 50bb2d5..80e3a1d 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;
@@ -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);