Skip to content

Commit 9c71cfc

Browse files
committed
tp
Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>
1 parent f4f12e2 commit 9c71cfc

File tree

2 files changed

+85
-1
lines changed

2 files changed

+85
-1
lines changed

packages/tiptap/src/shared/utils.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,69 @@ console.log("hello");
362362
});
363363
});
364364

365+
describe("md2json mark sanitization", () => {
366+
const schema = getSchema(getExtensions());
367+
368+
function validateJsonContent(json: JSONContent): {
369+
valid: boolean;
370+
error?: string;
371+
} {
372+
try {
373+
schema.nodeFromJSON(json);
374+
return { valid: true };
375+
} catch (error) {
376+
return {
377+
valid: false,
378+
error: error instanceof Error ? error.message : String(error),
379+
};
380+
}
381+
}
382+
383+
test("bold wrapping code produces valid schema (no bold+code)", () => {
384+
const json = md2json("**`code`**");
385+
const validation = validateJsonContent(json);
386+
expect(validation.valid).toBe(true);
387+
if (!validation.valid) {
388+
throw new Error(`Schema validation failed: ${validation.error}`);
389+
}
390+
});
391+
392+
test("italic wrapping code produces valid schema", () => {
393+
const json = md2json("*`code`*");
394+
const validation = validateJsonContent(json);
395+
expect(validation.valid).toBe(true);
396+
});
397+
398+
test("strikethrough wrapping code produces valid schema", () => {
399+
const json = md2json("~~`code`~~");
400+
const validation = validateJsonContent(json);
401+
expect(validation.valid).toBe(true);
402+
});
403+
404+
test("bold+code keeps only code mark", () => {
405+
const json = md2json("**`code`**");
406+
const findTextNode = (node: JSONContent): JSONContent | null => {
407+
if (node.type === "text") return node;
408+
for (const child of node.content || []) {
409+
const found = findTextNode(child);
410+
if (found) return found;
411+
}
412+
return null;
413+
};
414+
const textNode = findTextNode(json);
415+
expect(textNode).toBeDefined();
416+
expect(textNode!.marks).toBeDefined();
417+
expect(textNode!.marks!.length).toBe(1);
418+
expect(textNode!.marks![0].type).toBe("code");
419+
});
420+
421+
test("mixed bold and code in same paragraph produces valid schema", () => {
422+
const json = md2json("**bold** and **`code`** and more");
423+
const validation = validateJsonContent(json);
424+
expect(validation.valid).toBe(true);
425+
});
426+
});
427+
365428
describe("schema validation", () => {
366429
const schema = getSchema(getExtensions());
367430

packages/tiptap/src/shared/utils.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export function json2md(jsonContent: JSONContent): string {
4545

4646
export function md2json(markdown: string): JSONContent {
4747
try {
48-
return getMarkdownManager().parse(markdown);
48+
const json = getMarkdownManager().parse(markdown);
49+
return sanitizeMarks(json);
4950
} catch (error) {
5051
console.error(error);
5152

@@ -60,3 +61,23 @@ export function md2json(markdown: string): JSONContent {
6061
};
6162
}
6263
}
64+
65+
/**
66+
* The `code` mark has `excludes: "_"` in TipTap, meaning it excludes all other marks.
67+
* When `code` is present on a text node, strip all other marks to match ProseMirror's schema.
68+
*/
69+
function sanitizeMarks(node: JSONContent): JSONContent {
70+
if (node.type === "text" && node.marks) {
71+
const hasCode = node.marks.some((m) => m.type === "code");
72+
if (hasCode && node.marks.length > 1) {
73+
return { ...node, marks: [{ type: "code" }] };
74+
}
75+
return node;
76+
}
77+
78+
if (node.content) {
79+
return { ...node, content: node.content.map(sanitizeMarks) };
80+
}
81+
82+
return node;
83+
}

0 commit comments

Comments
 (0)