[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/
4.4 kB
151 lines
1import { type Node, select, selectAll } from "unist-util-select";
2import { visit } from "unist-util-visit";
3import type { EmbedsOption } from ".";
4
5export default function createPlugin({
6 embeds = [],
7 importNamespace,
8}: {
9 embeds: EmbedsOption[];
10 importNamespace: string;
11}) {
12 /**
13 * Get the name of the embed component for this URL
14 * @param {string} url URL to test
15 * @returns Component node for this URL or undefined if none matched
16 */
17 function getComponentForUrl(url: string) {
18 for (const embed of embeds) {
19 const { componentName, urlArgument = "href", urlMatcher } = embed;
20
21 if (!urlMatcher) continue;
22
23 let match = urlMatcher(url);
24
25 if (!match) continue;
26
27 // MDX custom component node.
28 return {
29 type: "mdxJsxFlowElement",
30 name: `${importNamespace}_${componentName}`,
31 attributes: [
32 { type: "mdxJsxAttribute", name: urlArgument, value: match },
33 ],
34 children: [],
35 };
36 }
37 return undefined;
38 }
39
40 function getComponentForDirective(node: Node) {
41 for (const embed of embeds) {
42 const { componentName, urlArgument = "href", directiveName } = embed;
43
44 // @ts-expect-error
45 if (!directiveName || !node.name) continue;
46
47 const name = (node as any).name;
48 const attributes = (node as any).attributes ?? {};
49 const children = (node as any).children ?? [];
50
51 if (
52 directiveName === name ||
53 (Array.isArray(directiveName) && directiveName.includes(name))
54 ) {
55 // turn into array
56 const attributeArray = Object.keys(attributes).map((key) => {
57 return { type: "mdxJsxAttribute", name: key, value: attributes[key] };
58 });
59
60 // if there's one child and it's either a link node with a text node child or a text node
61 // add that as a attribute using the urlArgument
62 if (children && children.length === 1) {
63 const child = children[0];
64 if (child.type === "link") {
65 if (
66 child.children.length === 1 &&
67 child.children[0].type === "text"
68 ) {
69 attributeArray.push({
70 type: "mdxJsxAttribute",
71 name: urlArgument,
72 value: child.url,
73 });
74 }
75 } else if (child.type === "text") {
76 attributeArray.push({
77 type: "mdxJsxAttribute",
78 name: urlArgument,
79 value: child.value,
80 });
81 }
82 }
83
84 return {
85 type: "mdxJsxFlowElement",
86 name: `${importNamespace}_${componentName}`,
87 attributes: attributeArray,
88 children: children ?? [],
89 };
90 }
91 }
92 return undefined;
93 }
94
95 type Link = Node & {
96 url?: string;
97 value?: string;
98 children?: Node[];
99 };
100
101 function transformer(tree: Node) {
102 visit(tree, function (node) {
103 if (
104 node.type === "containerDirective" ||
105 node.type === "leafDirective" ||
106 node.type === "textDirective"
107 ) {
108 let component = getComponentForDirective(node);
109 if (component) {
110 // @ts-expect-error We’re overriding the initial node type with arbitrary data.
111 for (const key in component) node[key] = component[key];
112 }
113 }
114 });
115
116 const paragraphs = selectAll("paragraph", tree);
117 paragraphs.forEach((paragraph) => {
118 const link = select(":scope > link:only-child", paragraph) as Link | undefined;
119 if (!link) return;
120
121 const { url, children } = link;
122 // We’re only interested in HTTP links
123 if (!url || !url.startsWith("http")) return;
124 // URLs should have a single child
125 if (!children || children.length !== 1) return;
126
127 // The child should be a text node with a value matching the URL
128 const child = children[0];
129 if (
130 !child ||
131 child.type !== "text" ||
132 !("value" in child) ||
133 child.value !== url
134 ) {
135 return;
136 }
137
138 const component = getComponentForUrl(url);
139 if (component) {
140 // @ts-expect-error We’re overriding the initial node type with arbitrary data.
141 for (const key in component) paragraph[key] = component[key];
142 }
143 });
144
145 return tree;
146 }
147
148 return function attacher() {
149 return transformer;
150 };
151}