export function autoFixMarkdown(markdown: string): string { return autoCloseTrailingLink(markdown); } /** * Normalize math delimiters for editor consumption * Converts display delimiters (\[...\], \\[...\\]) to $$ format * Converts inline delimiters (\(...\), \\(...\\)) to $ format * This ensures consistent format before loading into Tiptap editor */ export function normalizeMathForEditor(markdown: string): string { let normalized = markdown; // Convert display math - handle double backslash first to avoid conflicts normalized = normalized .replace(/\\\\\[([^\]]*)\\\\\]/g, (_match, content) => `$$${content}$$`) // \\[...\\] → $$...$$ .replace(/\\\[([^\]]*)\\\]/g, (_match, content) => `$$${content}$$`); // \[...\] → $$...$$ // Convert inline math - handle double backslash first to avoid conflicts normalized = normalized .replace(/\\\\\(([^)]*)\\\\\)/g, (_match, content) => `$${content}$`) // \\(...\\) → $...$ .replace(/\\\(([^)]*)\\\)/g, (_match, content) => `$${content}$`); // \(...\) → $...$ // Replace double backslashes with single in math contexts // For inline math: $...$ normalized = normalized.replace( /\$([^\$]+?)\$/g, (match, mathContent) => { return `$${mathContent.replace(/\\\\/g, '\\')}$`; } ); // For display math: $$...$$ normalized = normalized.replace( /\$\$([\s\S]+?)\$\$/g, (match, mathContent) => { return `$$${mathContent.replace(/\\\\/g, '\\')}$$`; } ); return normalized; } /** * Normalize math delimiters for display consumption * Ensures all math delimiters are in $$ format for remarkMath/rehypeKatex * This is used by the Markdown display component */ export function normalizeMathForDisplay(markdown: string): string { let normalized = markdown; // Convert all LaTeX-style delimiters to $$ // Both display and inline math use $$ for display component (remarkMath handles both) // Handle double backslash first to avoid conflicts normalized = normalized .replace(/\\\\\[([^\]]*)\\\\\]/g, (_match, content) => `$$${content}$$`) // \\[...\\] → $$...$$ .replace(/\\\[([^\]]*)\\\]/g, (_match, content) => `$$${content}$$`) // \[...\] → $$...$$ .replace(/\\\\\(([^)]*)\\\\\)/g, (_match, content) => `$$${content}$$`) // \\(...\\) → $$...$$ .replace(/\\\(([^)]*)\\\)/g, (_match, content) => `$$${content}$$`); // \(...\) → $$...$$ // Replace double backslashes with single in math contexts // For inline math: $...$ normalized = normalized.replace( /\$([^\$]+?)\$/g, (match, mathContent) => { return `$${mathContent.replace(/\\\\/g, '\\')}$`; } ); // For display math: $$...$$ normalized = normalized.replace( /\$\$([\s\S]+?)\$\$/g, (match, mathContent) => { return `$$${mathContent.replace(/\\\\/g, '\\')}$$`; } ); return normalized; } function autoCloseTrailingLink(markdown: string): string { // Fix unclosed Markdown links or images let fixedMarkdown: string = markdown; // Fix unclosed image syntax ![...](...) fixedMarkdown = fixedMarkdown.replace( /!\[([^\]]*)\]\(([^)]*)$/g, (match: string, altText: string, url: string): string => { return `![${altText}](${url})`; }, ); // Fix unclosed link syntax [...](...) fixedMarkdown = fixedMarkdown.replace( /\[([^\]]*)\]\(([^)]*)$/g, (match: string, linkText: string, url: string): string => { return `[${linkText}](${url})`; }, ); // Fix unclosed image syntax ![...] fixedMarkdown = fixedMarkdown.replace( /!\[([^\]]*)$/g, (match: string, altText: string): string => { return `![${altText}]`; }, ); // Fix unclosed link syntax [...] fixedMarkdown = fixedMarkdown.replace( /\[([^\]]*)$/g, (match: string, linkText: string): string => { return `[${linkText}]`; }, ); // Fix unclosed images or links missing ")" fixedMarkdown = fixedMarkdown.replace( /!\[([^\]]*)\]\(([^)]*)$/g, (match: string, altText: string, url: string): string => { return `![${altText}](${url})`; }, ); fixedMarkdown = fixedMarkdown.replace( /\[([^\]]*)\]\(([^)]*)$/g, (match: string, linkText: string, url: string): string => { return `[${linkText}](${url})`; }, ); return fixedMarkdown; }