UNPKG

markdown-to-jsx

Version:

A very fast and versatile markdown toolchain. AST, React, React Native, SolidJS, Vue, Markdown, and HTML output available with full customization.

599 lines (598 loc) 18.8 kB
import { Component, JSX as JSX2, Accessor, Context } from "solid-js"; import * as React from "react"; /** * Analogous to `node.type`. Please note that the values here may change at any time, * so do not hard code against the value directly. */ declare const RuleTypeConst: { readonly blockQuote: 0; readonly breakLine: 1; readonly breakThematic: 2; readonly codeBlock: 3; readonly codeInline: 4; readonly footnote: 5; readonly footnoteReference: 6; readonly frontmatter: 7; readonly gfmTask: 8; readonly heading: 9; readonly htmlBlock: 10; readonly htmlComment: 11; readonly htmlSelfClosing: 12; readonly image: 13; readonly link: 14; readonly orderedList: 15; readonly paragraph: 16; readonly ref: 17; readonly refCollection: 18; readonly table: 19; readonly text: 20; readonly textFormatted: 21; readonly unorderedList: 22; }; type RuleTypeValue = (typeof RuleTypeConst)[keyof typeof RuleTypeConst]; /** * markdown-to-jsx types and interfaces */ declare namespace MarkdownToJSX { /** * RequireAtLeastOne<{ ... }> <- only requires at least one key */ type RequireAtLeastOne< T, Keys extends keyof T = keyof T > = Pick<T, Exclude<keyof T, Keys>> & { [K in Keys]-? : Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys]; /** * React.createElement function type */ export type CreateElement = typeof React.createElement; /** * HTML tag names that can be used in JSX */ export type HTMLTags = keyof React.JSX.IntrinsicElements & (string & {}); /** * Parser and renderer state */ export type State = { /** true if the current content is inside anchor link grammar */ inAnchor?: boolean; /** true if inside a blockquote */ inBlockQuote?: boolean; /** true if parsing in an HTML context */ inHTML?: boolean; /** true if in a list */ inList?: boolean; /** true if parsing in an inline context (subset of rules around formatting and links) */ inline?: boolean; /** use this for the `key` prop */ key?: string | number; /** reference definitions (footnotes are stored with '^' prefix) */ refs?: { [key: string]: { target: string; title: string; }; }; /** current recursion depth during rendering */ renderDepth?: number; /** internal: block parse recursion depth */ _depth?: number; /** internal: disable setext heading detection (lazy blockquote continuation) */ _noSetext?: boolean; /** internal: HTML nesting depth for stack overflow protection */ _htmlDepth?: number; /** internal: set by collectReferenceDefinitions when input ends inside an unclosed fence */ _endsInsideFence?: boolean; }; /** * Blockquote node in the AST */ export interface BlockQuoteNode { /** Optional alert type (Note, Tip, Warning, etc.) */ alert?: string; /** Child nodes within the blockquote */ children: MarkdownToJSX.ASTNode[]; type: typeof RuleType2.blockQuote; } /** * Hard line break node */ export interface BreakLineNode { type: typeof RuleType2.breakLine; } /** * Thematic break (horizontal rule) node */ export interface BreakThematicNode { type: typeof RuleType2.breakThematic; } /** * Code block node (fenced code blocks) */ export interface CodeBlockNode { type: typeof RuleType2.codeBlock; /** HTML attributes for the code block */ attrs?: React.JSX.IntrinsicAttributes; /** Programming language identifier */ lang?: string; /** Code content */ text: string; } /** * Inline code node */ export interface CodeInlineNode { type: typeof RuleType2.codeInline; /** Code text */ text: string; } /** * Footnote definition node (not rendered, stored in refCollection) */ export interface FootnoteNode { type: typeof RuleType2.footnote; } /** * Footnote reference node */ export interface FootnoteReferenceNode { type: typeof RuleType2.footnoteReference; /** Link target (anchor) */ target: string; /** Display text */ text: string; } /** * YAML frontmatter node */ export interface FrontmatterNode { type: typeof RuleType2.frontmatter; /** Frontmatter content */ text: string; } /** * GFM task list item node */ export interface GFMTaskNode { type: typeof RuleType2.gfmTask; /** Whether the task is completed */ completed: boolean; } /** * Heading node */ export interface HeadingNode { type: typeof RuleType2.heading; /** Child nodes (text content) */ children: MarkdownToJSX.ASTNode[]; /** Generated HTML ID for anchor linking */ id: string; /** Heading level (1-6) */ level: 1 | 2 | 3 | 4 | 5 | 6; } /** * HTML comment node */ export interface HTMLCommentNode { type: typeof RuleType2.htmlComment; /** Comment text */ text: string; } /** * Image node */ export interface ImageNode { type: typeof RuleType2.image; /** Alt text */ alt?: string; /** Image URL */ target: string; /** Title attribute */ title?: string; } /** * Link node */ export interface LinkNode { type: typeof RuleType2.link; /** Child nodes (link text) */ children: MarkdownToJSX.ASTNode[]; /** Link URL (null for reference links without definition) */ target: string | null; /** Title attribute */ title?: string; } /** * Ordered list node */ export interface OrderedListNode { type: typeof RuleType2.orderedList; /** Array of list items, each item is an array of nodes */ items: MarkdownToJSX.ASTNode[][]; /** Starting number for the list */ start?: number; } /** * Unordered list node */ export interface UnorderedListNode { type: typeof RuleType2.unorderedList; /** Array of list items, each item is an array of nodes */ items: MarkdownToJSX.ASTNode[][]; } /** * Paragraph node */ export interface ParagraphNode { type: typeof RuleType2.paragraph; /** Child nodes */ children: MarkdownToJSX.ASTNode[]; } /** * Reference definition node (not rendered, stored in refCollection) */ export interface ReferenceNode { type: typeof RuleType2.ref; } /** * Reference collection node (appears at AST root, includes footnotes with '^' prefix) */ export interface ReferenceCollectionNode { type: typeof RuleType2.refCollection; /** Map of reference labels to their definitions */ refs: { [key: string]: { target: string; title: string; }; }; } /** * Table node */ export interface TableNode { type: typeof RuleType2.table; /** * alignment for each table column */ align: ("left" | "right" | "center")[]; /** Table cells (3D array: rows -> cells -> nodes) */ cells: MarkdownToJSX.ASTNode[][][]; /** Table header row */ header: MarkdownToJSX.ASTNode[][]; } /** * Plain text node */ export interface TextNode { type: typeof RuleType2.text; /** Text content */ text: string; } /** * Formatted text node (bold, italic, etc.) */ export interface FormattedTextNode { type: typeof RuleType2.textFormatted; /** * the corresponding html tag */ tag: string; /** Child nodes */ children: MarkdownToJSX.ASTNode[]; } /** @deprecated Use `FormattedTextNode` instead. */ export type TextFormattedNode = FormattedTextNode; /** * HTML block node (includes JSX components) */ export interface HTMLNode { type: typeof RuleType2.htmlBlock; /** Parsed HTML attributes */ attrs?: Record<string, any>; /** Parsed child nodes (always parsed, even for verbatim blocks) */ children?: ASTNode[] | undefined; /** @internal Whether this is a closing tag */ _isClosingTag?: boolean; /** @internal Whether this is a verbatim block (script, style, pre, etc.) */ _verbatim?: boolean; /** @internal Original raw attribute string */ _rawAttrs?: string; /** @internal Original raw HTML content (for verbatim blocks) */ _rawText?: string | undefined; /** @deprecated Use `_rawText` instead. This property will be removed in a future major version. */ text?: string | undefined; /** HTML tag name */ tag: string; } /** * Self-closing HTML tag node */ export interface HTMLSelfClosingNode { type: typeof RuleType2.htmlSelfClosing; /** Parsed HTML attributes */ attrs?: Record<string, any>; /** @internal Whether this is a closing tag */ _isClosingTag?: boolean; /** HTML tag name */ tag: string; /** @internal Original raw HTML content */ _rawText?: string; } /** * Union type of all possible AST node types */ export type ASTNode = BlockQuoteNode | BreakLineNode | BreakThematicNode | CodeBlockNode | CodeInlineNode | FootnoteNode | FootnoteReferenceNode | FrontmatterNode | GFMTaskNode | HeadingNode | HTMLCommentNode | ImageNode | LinkNode | OrderedListNode | UnorderedListNode | ParagraphNode | ReferenceNode | ReferenceCollectionNode | TableNode | TextNode | FormattedTextNode | HTMLNode | HTMLSelfClosingNode; /** * Function type for rendering AST nodes */ export type ASTRender = (ast: MarkdownToJSX.ASTNode | MarkdownToJSX.ASTNode[], state: MarkdownToJSX.State) => React.ReactNode; /** * Override configuration for HTML tags or custom components */ export type Override = RequireAtLeastOne<{ component: React.ElementType; props: Object; }> | React.ElementType; /** * Map of HTML tags and custom components to their override configurations */ export type Overrides = { [tag in HTMLTags]? : Override } & { [customComponent: string]: Override; }; /** * Compiler options */ export type Options = Partial<{ /** * Ultimate control over the output of all rendered JSX. */ createElement: (tag: Parameters<CreateElement>[0], props: React.JSX.IntrinsicAttributes, ...children: React.ReactNode[]) => React.ReactNode; /** * The library automatically generates an anchor tag for bare URLs included in the markdown * document, but this behavior can be disabled if desired. */ disableAutoLink: boolean; /** * Disable the compiler's best-effort transcription of provided raw HTML * into JSX-equivalent. This is the functionality that prevents the need to * use `dangerouslySetInnerHTML` in React. */ disableParsingRawHTML: boolean; /** * Disable the compiler's parsing of HTML blocks. */ ignoreHTMLBlocks?: boolean; /** * Enable GFM tagfilter extension to filter potentially dangerous HTML tags. * When enabled, the following tags are escaped: title, textarea, style, xmp, * iframe, noembed, noframes, script, plaintext. * https://github.github.com/gfm/#disallowed-raw-html-extension- * @default true */ tagfilter?: boolean; /** * Forces the compiler to have space between hash sign and the header text which * is explicitly stated in the most of the markdown specs. * https://github.github.com/gfm/#atx-heading * `The opening sequence of # characters must be followed by a space or by the end of line.` */ enforceAtxHeadings: boolean; /** * **⚠️ SECURITY WARNING: STRONGLY DISCOURAGED FOR USER INPUTS** * * When enabled, attempts to eval expressions in JSX props that cannot be serialized * as JSON (functions, variables, complex expressions). This uses `eval()` which can * execute arbitrary code. * * **ONLY use this option when:** * - The markdown source is completely trusted (e.g., your own documentation) * - You control all JSX components and their props * - The content is NOT user-generated or user-editable * * **DO NOT use this option when:** * - Processing user-submitted markdown * - Rendering untrusted content * - Building public-facing applications with user content * * Example unsafe input: `<Component onClick={() => fetch('/admin/delete-all')} />` * * When disabled (default), unserializable expressions remain as strings that can be * safely inspected or handled on a case-by-case basis via custom renderRule logic. * * @default false */ evalUnserializableExpressions?: boolean; /** * Forces the compiler to always output content with a block-level wrapper * (`<p>` or any block-level syntax your markdown already contains.) */ forceBlock: boolean; /** * Forces the compiler to always output content with an inline wrapper (`<span>`) */ forceInline: boolean; /** * Forces the compiler to wrap results, even if there is only a single * child or no children. */ forceWrapper: boolean; /** * Selectively control the output of particular HTML tags as they would be * emitted by the compiler. */ overrides: Overrides; /** * Allows for full control over rendering of particular rules. * For example, to implement a LaTeX renderer such as `react-katex`: * * ``` * renderRule(next, node, renderChildren, state) { * if (node.type === RuleType.codeBlock && node.lang === 'latex') { * return ( * <TeX as="div" key={state.key}> * {String.raw`${node.text}`} * </TeX> * ) * } * * return next(); * } * ``` * * Thar be dragons obviously, but you can do a lot with this * (have fun!) To see how things work internally, check the `render` * method in source for a particular rule. */ renderRule: (next: () => React.ReactNode, node: ASTNode, renderChildren: ASTRender, state: State) => React.ReactNode; /** * Override the built-in sanitizer function for URLs, etc if desired. The built-in version is available as a library export called `sanitizer`. */ sanitizer: (value: string, tag: string, attribute: string) => string | null; /** * Override normalization of non-URI-safe characters for use in generating * HTML IDs for anchor linking purposes. */ slugify: (input: string, defaultFn: (input: string) => string) => string; /** * Declare the type of the wrapper to be used when there are multiple * children to render. Set to `null` to get an array of children back * without any wrapper, or use `React.Fragment` to get a React element * that won't show up in the DOM. */ wrapper: React.ElementType | null; /** * Props to apply to the wrapper element. */ wrapperProps?: React.JSX.IntrinsicAttributes; /** * Preserve frontmatter in the output by rendering it as a <pre> element. * By default, frontmatter is parsed but not rendered. * @default false */ preserveFrontmatter?: boolean; /** * Optimize rendering for streaming scenarios where markdown content arrives * incrementally (e.g., from LLM APIs). When enabled, incomplete inline syntax * is suppressed to avoid displaying raw markdown characters while waiting * for the closing delimiter to arrive. * * Fenced code blocks render normally with content visible as it streams. * * @default false * * @example * ```tsx * // Streaming markdown example * function StreamingMarkdown({ content }) { * return ( * <Markdown options={{ optimizeForStreaming: true }}> * {content} * </Markdown> * ) * } * ``` */ optimizeForStreaming?: boolean; }>; } declare const RuleType2: typeof RuleTypeConst; type RuleType2 = RuleTypeValue; type RequireAtLeastOne< T, Keys extends keyof T = keyof T > = MarkdownToJSX.RequireAtLeastOne<T, Keys>; /** * Main parser entry point - matches original parser interface */ declare function parser(source: string, options?: MarkdownToJSX.Options): MarkdownToJSX.ASTNode[]; /** * Sanitize URLs and other input values to prevent XSS attacks. * Filters out javascript:, vbscript:, and data: URLs (except data:image). * * * @param input - The URL or value to sanitize * @returns Sanitized value, or null if unsafe */ declare function sanitizer(input: string): string | null; /** * Convert a string to a URL-safe slug by normalizing characters and replacing spaces with hyphens. * Based on https://stackoverflow.com/a/18123682/1141611 * Not complete, but probably good enough. * * * @param str - String to slugify * @returns URL-safe slug */ declare function slugify(str: string): string; type HTag = string | Component<Record<string, unknown>>; type HProps = Record<string, unknown>; type HChildren = JSX2.Element | JSX2.Element[] | string | null; /** * Override configuration for HTML tags or custom components in SolidJS output */ type SolidOverride = RequireAtLeastOne<{ component: string | Component<Record<string, unknown>>; props: Record<string, unknown>; }> | string | Component<Record<string, unknown>>; /** * Map of HTML tags and custom components to their override configurations */ type SolidOverrides = { [tag in MarkdownToJSX.HTMLTags]? : SolidOverride } & { [customComponent: string]: SolidOverride; }; /** * SolidJS compiler options */ type SolidOptions = Omit<MarkdownToJSX.Options, "createElement" | "wrapperProps" | "renderRule" | "overrides"> & { /** Custom createElement function for SolidJS */ createElement?: (tag: HTag, props: HProps, ...children: HChildren[]) => JSX2.Element; /** Props for wrapper element */ wrapperProps?: JSX2.HTMLAttributes<HTMLElement>; /** Custom rendering function for AST rules */ renderRule?: (next: () => JSX2.Element | string | null, node: MarkdownToJSX.ASTNode, renderChildren: (children: MarkdownToJSX.ASTNode[]) => JSX2.Element | JSX2.Element[], state: Omit<MarkdownToJSX.State, "key">) => JSX2.Element | string | null; /** Override configurations for HTML tags */ overrides?: SolidOverrides; }; /** * Convert AST nodes to SolidJS JSX elements * * @param ast - Array of AST nodes to render * @param options - SolidJS compiler options * @returns SolidJS JSX element(s) */ declare function astToJSX(ast: MarkdownToJSX.ASTNode[], options?: SolidOptions): JSX2.Element | JSX2.Element[] | null; /** * Compile markdown string to SolidJS JSX elements * * @param markdown - Markdown string to compile * @param options - SolidJS compiler options * @returns SolidJS JSX element(s) */ declare function compiler(markdown?: string, options?: SolidOptions): JSX2.Element | JSX2.Element[] | null; /** * SolidJS context for sharing compiler options across Markdown components */ declare const MarkdownContext: Context<SolidOptions | undefined>; /** * SolidJS context provider for sharing compiler options across Markdown components * * @param options - Default compiler options to share * @param children - SolidJS children */ declare const MarkdownProvider: Component<{ options?: SolidOptions; children: JSX2.Element; }>; /** * A SolidJS component for easy markdown rendering. Feed the markdown content as a direct child * and the rest is taken care of automatically. Supports reactive content via signals/accessors. * * @param children - Markdown string content or signal/accessor * @param options - Compiler options */ declare const Markdown: Component<Omit<JSX2.HTMLAttributes<HTMLElement>, "children"> & { children?: string | Accessor<string> | null; options?: SolidOptions; }>; export { slugify, sanitizer, parser, Markdown as default, compiler, astToJSX, SolidOverrides, SolidOverride, SolidOptions, RuleType2 as RuleType, MarkdownToJSX, MarkdownProvider, MarkdownContext, Markdown };