import { Marked } from "marked"; import { preprocessLinks } from "@/components/markdown/linkify"; /** * Dedicated Marked instance for converting markdown → Tiptap-compatible HTML. * * Uses a separate instance (not the global `marked`) to avoid interfering with * @tiptap/markdown's internal marked instance. Custom renderer ensures output * matches Tiptap's ProseMirror schema requirements (e.g. block content in cells). */ const tiptapMarked = new Marked(); tiptapMarked.use({ renderer: { // Tiptap's TableCell/TableHeader nodes require `content: "block+"`. // Default marked outputs bare inline content in
so it's valid block content. tablecell({ tokens, header }) { const tag = header ? "th" : "td"; const content = this.parser.parseInline(tokens); return `<${tag}>
${content}
${tag}>\n`; }, }, }); // --------------------------------------------------------------------------- // Mention preprocessing // --------------------------------------------------------------------------- /** * Convert mention link syntax to HTML spans matching Tiptap's Mention * extension parseHTML expectations (data-type, data-id, data-label, data-mention-type). */ function mentionsToHtml(text: string): string { return text.replace( /\[@?([^\]]+)\]\(mention:\/\/(\w+)\/([^)]+)\)/g, (_match, label: string, type: string, id: string) => { const prefix = type === "issue" ? "" : "@"; return ( `${prefix}${label}` ); }, ); } /** * Convert legacy mention shortcodes [@ id="UUID" label="LABEL"] to the * standard markdown link format before further processing. */ function preprocessMentionShortcodes(text: string): string { if (!text.includes("[@ ")) return text; return text.replace( /\[@\s+([^\]]*)\]/g, (match: string, attrString: string) => { const attrs: Record