import ReactMarkdown, { type Components } from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import rehypeSanitize from 'rehype-sanitize'
import remarkGfm from 'remark-gfm'
export interface MarkdownContentProps {
/** Markdown source. */
content: string
/**
* Element overrides. Consumers (`MarkdownUI`, `Note`, …) supply their own
* typography / link styling here; the engine itself stays opinion-free.
*/
components?: Components
/**
* When `true`, raw HTML in the source is rendered. Defaults to `false` so
* caption slots like `Note` stay safe-by-default; the document-style
* Markdown widget opts in.
*/
allowHtml?: boolean
/**
* When `true`, `
` / `` markdown produces an `
` tag.
* Defaults to `false` so caption slots stay text-only by default.
*/
allowImages?: boolean
/**
* When `true`, enables GitHub Flavored Markdown (tables, strikethrough,
* task lists, autolinks) via `remark-gfm`. Defaults to `false` so existing
* consumers keep CommonMark-only parsing.
*/
gfm?: boolean
}
// Allowlist of URL schemes considered safe for `` and `
`.
// Anything outside this set (e.g. `data:`, `javascript:`, `vbscript:`) is
// stripped by `safeUrlTransform`. Belt-and-braces with `rehype-sanitize`'s
// `defaultSchema` (which already restricts protocols when raw HTML is parsed)
// — this layer also covers the `allowHtml=false` path, where the sanitizer
// doesn't run but `react-markdown`'s default `urlTransform` only blocks
// `javascript:`.
const SAFE_URL_PREFIX = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i
function safeUrlTransform(url: string): string {
const trimmed = (url ?? '').trim()
if (!trimmed) return ''
if (SAFE_URL_PREFIX.test(trimmed)) return url
return ''
}
/**
* Low-level Markdown rendering primitive. Wraps `react-markdown` with two
* opt-in capabilities (`allowHtml`, `allowImages`) and a consumer-supplied
* component map for typography. Both default OFF — Note-style consumers
* are safe-by-default; MarkdownUI opts both ON to render rich content.
*
* `allowHtml` plugs in `rehype-raw` so raw HTML nodes in the source are
* actually parsed, then `rehype-sanitize` with the default schema strips
* `