[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/
0

Configure Feed

Select the types of activity you want to include in your feed.

tiny-docs / custom-embeds / remark-plugin.ts
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}