diff --git a/src/renderer.js b/src/renderer.js index 7dd1caf..50985f7 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -33,6 +33,39 @@ marked.use({ gfm: true }); +// Footnotes support +const markedFootnote = require('marked-footnote'); +marked.use(markedFootnote()); + +// Admonitions support (:::note, :::warning, :::tip, :::danger, :::info) +marked.use({ + extensions: [{ + name: 'admonition', + level: 'block', + start(src) { return src.match(/^:::(note|warning|tip|danger|info)/m)?.index; }, + tokenizer(src) { + const match = src.match(/^:::(note|warning|tip|danger|info)\s*\n([\s\S]*?)^:::\s*$/m); + if (match) { + return { + type: 'admonition', + raw: match[0], + admonitionType: match[1], + text: match[2].trim() + }; + } + }, + renderer(token) { + const icons = { note: '\u2139', warning: '\u26A0', tip: '\uD83D\uDCA1', danger: '\uD83D\uDD34', info: '\u2139' }; + const icon = icons[token.admonitionType] || '\u2139'; + const inner = this.parser.parse(token.text); + return `
+
${icon} ${token.admonitionType.charAt(0).toUpperCase() + token.admonitionType.slice(1)}
+
${inner}
+
`; + } + }] +}); + // Tab Management class TabManager { constructor() { @@ -337,7 +370,37 @@ class TabManager { return; } const html = marked.parse(tab.content); - const sanitizedHtml = DOMPurify.sanitize(html); + let sanitizedHtml = DOMPurify.sanitize(html); + + // TOC generation + if (sanitizedHtml.includes('[[toc]]') || sanitizedHtml.includes('[TOC]')) { + const headingRegex = /]*>(.*?)<\/h[1-6]>/gi; + let tocMatch; + const toc = []; + const tempHtml = sanitizedHtml; + while ((tocMatch = headingRegex.exec(tempHtml)) !== null) { + const level = parseInt(tocMatch[1]); + const text = tocMatch[2].replace(/<[^>]+>/g, ''); + const id = text.toLowerCase().replace(/[^\w]+/g, '-'); + toc.push({ level, text, id }); + } + + if (toc.length > 0) { + const tocHtml = ''; + sanitizedHtml = sanitizedHtml.replace(/\[\[toc\]\]|\[TOC\]/gi, tocHtml); + + // Add IDs to headings for anchor links + toc.forEach(h => { + sanitizedHtml = sanitizedHtml.replace( + new RegExp(`(]*)>`, 'i'), + `$1 id="${h.id}">` + ); + }); + } + } + preview.innerHTML = sanitizedHtml; // Render math expressions if KaTeX is available diff --git a/src/styles.css b/src/styles.css index bf107a0..a901a8a 100644 --- a/src/styles.css +++ b/src/styles.css @@ -4009,5 +4009,43 @@ body.theme-github .mermaid { border-color: #e1e4e8; } +/* Task List Styles */ +.preview-content input[type="checkbox"] { + margin-right: 6px; + pointer-events: none; +} +.preview-content li.task-list-item { + list-style: none; + margin-left: -20px; +} + +/* Admonition Styles */ +.admonition { + border-left: 4px solid; + border-radius: 4px; + padding: 12px 16px; + margin: 16px 0; +} +.admonition-title { + font-weight: 600; + margin-bottom: 8px; +} +.admonition-note, .admonition-info { border-color: #3b82f6; background: #eff6ff; } +.admonition-warning { border-color: #f59e0b; background: #fffbeb; } +.admonition-tip { border-color: #10b981; background: #ecfdf5; } +.admonition-danger { border-color: #ef4444; background: #fef2f2; } + +/* TOC Styles */ +.toc { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; margin: 16px 0; } +.toc h2 { font-size: 14px; margin-bottom: 8px; } +.toc ul { list-style: none; padding: 0; } +.toc li { padding: 2px 0; } +.toc .toc-h1 { padding-left: 0; } +.toc .toc-h2 { padding-left: 16px; } +.toc .toc-h3 { padding-left: 32px; } +.toc .toc-h4 { padding-left: 48px; } +.toc a { color: #5661b3; text-decoration: none; } +.toc a:hover { text-decoration: underline; } + /* Command Palette Styles */ /* Command Palette styles moved to styles-modern.css */