admonitions
mdxGitHub-style blockquote callouts. Converts > [!NOTE], > [!WARNING] etc. into <Callout> JSX elements.
document0/admonitions•v0.1.0•react, next, astro, vue, solid, svelte
Installation
$npx @document0/cli add document0/admonitions
This will also install: unist-util-visit@^5.0.0
Usage
import { processMdx } from "@document0/mdx";
import { admonitions } from "./plugins/document0/admonitions";
const result = await processMdx(source, {
plugins: [admonitions()],
});Source
After installation, this lives at plugins/document0/admonitions/index.ts and you can modify it however you like.
import { visit } from "unist-util-visit";
type AdmonitionType = "note" | "tip" | "important" | "warning" | "caution";
const PATTERN = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/i;
interface MdastNode {
type: string;
children?: MdastNode[];
value?: string;
[key: string]: unknown;
}
export interface AdmonitionsOptions {
/**
* JSX component name emitted in the MDX output.
* You must provide this component in your MDX component map.
* @default "Callout"
*/
component?: string;
}
/**
* Remark plugin: converts GitHub-style blockquote admonitions
* into MDX JSX elements.
*
* ```md
* > [!NOTE]
* > Plain text only in the blockquote body. The plugin emits the Callout wrapper.
* ```
*/
export function remarkAdmonitions(options?: AdmonitionsOptions) {
const componentName = options?.component ?? "Callout";
return (tree: Parameters<typeof visit>[0]) => {
visit(
tree,
"blockquote",
(node: MdastNode, index, parent: MdastNode | undefined) => {
if (parent === undefined || index === undefined) return;
const firstChild = node.children?.[0];
if (!firstChild || firstChild.type !== "paragraph") return;
const firstInline = firstChild.children?.[0];
if (!firstInline || firstInline.type !== "text" || !firstInline.value)
return;
const match = firstInline.value.match(PATTERN);
if (!match) return;
const type = match[1]!.toLowerCase() as AdmonitionType;
firstInline.value = firstInline.value.slice(match[0].length);
if (!firstInline.value) firstChild.children!.shift();
if (firstChild.children!.length === 0) node.children!.shift();
parent.children![index as number] = {
type: "mdxJsxFlowElement",
name: componentName,
attributes: [
{ type: "mdxJsxAttribute", name: "type", value: type },
],
children: node.children ?? [],
data: { _mdxExplicitJsx: true },
};
},
);
};
}
/**
* Document0 plugin wrapper.
*
* ```ts
* processMdx(source, { plugins: [admonitions()] });
* ```
*/
export function admonitions(options?: AdmonitionsOptions) {
return {
name: "admonitions",
remarkPlugins: [() => remarkAdmonitions(options)],
};
}
Tags
remarkmdxcalloutsmarkdown