/** * Markdown Import Parser * * Uses markdown-it to convert markdown strings to HTML for importing * .md files into the TipTap editor with full formatting preserved. * * Supports: headings, bold, italic, strikethrough, code (inline + blocks), * links, images, blockquotes, lists (bullet, ordered, task via GFM), * horizontal rules, tables (GFM), hard line breaks. */ import MarkdownIt from 'markdown-it'; import type { Token, Renderer, MarkdownItOptions, MarkdownItPlugin } from 'markdown-it'; // Initialize markdown-it with GFM-like defaults const md = new MarkdownIt({ html: false, // Disable raw HTML passthrough for security linkify: true, // Auto-convert URLs to links typographer: false, // Keep raw characters (don't smart-quote) breaks: false, // Standard CommonMark line break behavior }); // Enable strikethrough (~~text~~) via built-in plugin md.enable('strikethrough'); // Enable tables via built-in plugin md.enable('table'); /** * Custom plugin: task list checkboxes (GFM style) * Converts `- [ ] text` and `- [x] text` into checkbox list items. */ /** * Scan a bullet_list's children and return true if ANY list_item * starts with a GFM checkbox pattern `[ ]` / `[x]`. */ function listContainsTaskItems(tokens: Token[], listOpenIdx: number): boolean { let depth = 0; for (let i = listOpenIdx; i < tokens.length; i++) { if (tokens[i].type === 'bullet_list_open') depth++; if (tokens[i].type === 'bullet_list_close') { depth--; if (depth === 0) break; } if (depth === 1 && tokens[i].type === 'list_item_open') { const content = tokens[i + 2]; // list_item_open -> paragraph_open -> inline if (content?.type === 'inline' && /^\[([ xX])\]\s*/.test(content.content)) { return true; } } } return false; } const taskListPlugin: MarkdownItPlugin = function taskListPlugin(md: MarkdownIt): void { // --- Override bullet_list_open: emit