Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/flo-bit/tiny-docs. quick setup, simple, minimalistic docs for your github project flo-bit.dev/tiny-docs
Something went wrong. Try again.
4.4 kB · 151 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152import { type Node, select, selectAll } from "unist-util-select";import { visit } from "unist-util-visit";import type { EmbedsOption } from ".";
export default function createPlugin({ embeds = [], importNamespace,}: { embeds: EmbedsOption[]; importNamespace: string;}) { /** * Get the name of the embed component for this URL * @param {string} url URL to test * @returns Component node for this URL or undefined if none matched */ function getComponentForUrl(url: string) { for (const embed of embeds) { const { componentName, urlArgument = "href", urlMatcher } = embed;
if (!urlMatcher) continue;
let match = urlMatcher(url);
if (!match) continue;
// MDX custom component node. return { type: "mdxJsxFlowElement", name: `${importNamespace}_${componentName}`, attributes: [ { type: "mdxJsxAttribute", name: urlArgument, value: match }, ], children: [], }; } return undefined; }
function getComponentForDirective(node: Node) { for (const embed of embeds) { const { componentName, urlArgument = "href", directiveName } = embed;
// @ts-expect-error if (!directiveName || !node.name) continue;
const name = (node as any).name; const attributes = (node as any).attributes ?? {}; const children = (node as any).children ?? [];
if ( directiveName === name || (Array.isArray(directiveName) && directiveName.includes(name)) ) { // turn into array const attributeArray = Object.keys(attributes).map((key) => { return { type: "mdxJsxAttribute", name: key, value: attributes[key] }; });
// if there's one child and it's either a link node with a text node child or a text node // add that as a attribute using the urlArgument if (children && children.length === 1) { const child = children[0]; if (child.type === "link") { if ( child.children.length === 1 && child.children[0].type === "text" ) { attributeArray.push({ type: "mdxJsxAttribute", name: urlArgument, value: child.url, }); } } else if (child.type === "text") { attributeArray.push({ type: "mdxJsxAttribute", name: urlArgument, value: child.value, }); } }
return { type: "mdxJsxFlowElement", name: `${importNamespace}_${componentName}`, attributes: attributeArray, children: children ?? [], }; } } return undefined; }
type Link = Node & { url?: string; value?: string; children?: Node[]; };
function transformer(tree: Node) { visit(tree, function (node) { if ( node.type === "containerDirective" || node.type === "leafDirective" || node.type === "textDirective" ) { let component = getComponentForDirective(node); if (component) { // @ts-expect-error We’re overriding the initial node type with arbitrary data. for (const key in component) node[key] = component[key]; } } });
const paragraphs = selectAll("paragraph", tree); paragraphs.forEach((paragraph) => { const link = select(":scope > link:only-child", paragraph) as Link | undefined; if (!link) return;
const { url, children } = link; // We’re only interested in HTTP links if (!url || !url.startsWith("http")) return; // URLs should have a single child if (!children || children.length !== 1) return;
// The child should be a text node with a value matching the URL const child = children[0]; if ( !child || child.type !== "text" || !("value" in child) || child.value !== url ) { return; }
const component = getComponentForUrl(url); if (component) { // @ts-expect-error We’re overriding the initial node type with arbitrary data. for (const key in component) paragraph[key] = component[key]; } });
return tree; }
return function attacher() { return transformer; };}