Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 101 lines 4.1 kB view raw
1/** 2 * Markdown Import Parser 3 * 4 * Uses markdown-it to convert markdown strings to HTML for importing 5 * .md files into the TipTap editor with full formatting preserved. 6 * 7 * Supports: headings, bold, italic, strikethrough, code (inline + blocks), 8 * links, images, blockquotes, lists (bullet, ordered, task via GFM), 9 * horizontal rules, tables (GFM), hard line breaks. 10 */ 11 12import MarkdownIt from 'markdown-it'; 13import type { Token, Renderer, MarkdownItOptions, MarkdownItPlugin } from 'markdown-it'; 14 15// Initialize markdown-it with GFM-like defaults 16const md = new MarkdownIt({ 17 html: false, // Disable raw HTML passthrough for security 18 linkify: true, // Auto-convert URLs to links 19 typographer: false, // Keep raw characters (don't smart-quote) 20 breaks: false, // Standard CommonMark line break behavior 21}); 22 23// Enable strikethrough (~~text~~) via built-in plugin 24md.enable('strikethrough'); 25 26// Enable tables via built-in plugin 27md.enable('table'); 28 29/** 30 * Custom plugin: task list checkboxes (GFM style) 31 * Converts `- [ ] text` and `- [x] text` into checkbox list items. 32 */ 33/** 34 * Scan a bullet_list's children and return true if ANY list_item 35 * starts with a GFM checkbox pattern `[ ]` / `[x]`. 36 */ 37function listContainsTaskItems(tokens: Token[], listOpenIdx: number): boolean { 38 let depth = 0; 39 for (let i = listOpenIdx; i < tokens.length; i++) { 40 if (tokens[i].type === 'bullet_list_open') depth++; 41 if (tokens[i].type === 'bullet_list_close') { depth--; if (depth === 0) break; } 42 if (depth === 1 && tokens[i].type === 'list_item_open') { 43 const content = tokens[i + 2]; // list_item_open -> paragraph_open -> inline 44 if (content?.type === 'inline' && /^\[([ xX])\]\s*/.test(content.content)) { 45 return true; 46 } 47 } 48 } 49 return false; 50} 51 52const taskListPlugin: MarkdownItPlugin = function taskListPlugin(md: MarkdownIt): void { 53 // --- Override bullet_list_open: emit <ul data-type="taskList"> when items have checkboxes --- 54 const defaultListOpen = md.renderer.rules.bullet_list_open || 55 function (tokens: Token[], idx: number, options: MarkdownItOptions, _env: unknown, self: Renderer): string { 56 return self.renderToken(tokens, idx, options); 57 }; 58 59 md.renderer.rules.bullet_list_open = function (tokens: Token[], idx: number, options: MarkdownItOptions, env: unknown, self: Renderer): string { 60 if (listContainsTaskItems(tokens, idx)) { 61 return '<ul data-type="taskList">'; 62 } 63 return defaultListOpen(tokens, idx, options, env, self); 64 }; 65 66 // --- Override list_item_open: emit TipTap-compatible task item attributes --- 67 const defaultItemOpen = md.renderer.rules.list_item_open || 68 function (tokens: Token[], idx: number, options: MarkdownItOptions, _env: unknown, self: Renderer): string { 69 return self.renderToken(tokens, idx, options); 70 }; 71 72 md.renderer.rules.list_item_open = function (tokens: Token[], idx: number, options: MarkdownItOptions, env: unknown, self: Renderer): string { 73 const contentToken = tokens[idx + 2]; // list_item_open -> paragraph_open -> inline 74 if (contentToken && contentToken.type === 'inline' && contentToken.content) { 75 const match = contentToken.content.match(/^\[([ xX])\]\s*/); 76 if (match) { 77 const checked = match[1].toLowerCase() === 'x'; 78 // Remove the checkbox syntax from the content 79 contentToken.content = contentToken.content.replace(/^\[([ xX])\]\s*/, ''); 80 if (contentToken.children && contentToken.children.length > 0) { 81 const firstChild = contentToken.children[0]; 82 if (firstChild.type === 'text') { 83 firstChild.content = firstChild.content.replace(/^\[([ xX])\]\s*/, ''); 84 } 85 } 86 return `<li data-type="taskItem" data-checked="${checked}">`; 87 } 88 } 89 return defaultItemOpen(tokens, idx, options, env, self); 90 }; 91}; 92 93md.use(taskListPlugin); 94 95/** 96 * Convert a markdown string to HTML. 97 */ 98export function markdownToHtml(mdString: string): string { 99 if (!mdString) return ''; 100 return md.render(mdString); 101}