UNPKG

@humanspeak/svelte-markdown

Version:

Markdown and HTML renderer for Svelte 5 — built for rendering streaming AI agent output from Claude Code, ChatGPT, and agentic workflows. XSS-safe defaults, streaming-aware sanitization, token caching, TypeScript types, and Svelte 5 runes.

81 lines (80 loc) 2.88 kB
/** * Creates a marked extension that tokenizes footnote references (`[^id]`) and * footnote definitions (`[^id]: content`) into custom tokens. * * The extension produces: * - **Inline** `footnoteRef` tokens: `{ type: 'footnoteRef', raw, id }` * - **Block** `footnoteSection` tokens: `{ type: 'footnoteSection', raw, footnotes }` where * `footnotes` is an array of `{ id, text }` objects * * Pair with `FootnoteRef` and `FootnoteSection` components (or your own) for rendering. * * @example * ```svelte * <script lang="ts"> * import SvelteMarkdown from '@humanspeak/svelte-markdown' * import { markedFootnote, FootnoteRef, FootnoteSection } from '@humanspeak/svelte-markdown/extensions' * * const renderers = { footnoteRef: FootnoteRef, footnoteSection: FootnoteSection } * </script> * * <SvelteMarkdown * source={markdown} * extensions={[markedFootnote()]} * {renderers} * /> * ``` * * @returns A `MarkedExtension` with inline `footnoteRef` and block `footnoteSection` tokenizers */ export function markedFootnote() { return { extensions: [ { name: 'footnoteRef', level: 'inline', start(src) { return src.match(/\[\^/)?.index; }, tokenizer(src) { const match = src.match(/^\[\^([^\]\s]+)\](?!:)/); if (match) { return { type: 'footnoteRef', raw: match[0], id: match[1] }; } } }, { name: 'footnoteSection', level: 'block', start(src) { return src.match(/\[\^[^\]\s]+\]:/)?.index; }, tokenizer(src) { const match = src.match(/^(?:\[\^([^\]\s]+)\]:\s*([^\n]*(?:\n(?!\[\^)[^\n]*)*)(?:\n|$))+/); if (match) { const footnotes = []; const defRegex = /\[\^([^\]\s]+)\]:\s*([^\n]*(?:\n(?!\[\^|\n)[^\n]*)*)/g; let defMatch; while ((defMatch = defRegex.exec(match[0])) !== null) { footnotes.push({ id: defMatch[1], text: defMatch[2].trim() }); } if (footnotes.length > 0) { return { type: 'footnoteSection', raw: match[0], footnotes }; } } } } ] }; }