UNPKG

docusaurus-theme-openapi-docs

Version:

OpenAPI theme for Docusaurus.

180 lines (159 loc) 5.15 kB
/* ============================================================================ * Copyright (c) Palo Alto Networks * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * ========================================================================== */ import React from "react"; import Admonition from "@theme/Admonition"; import CodeBlock from "@theme/CodeBlock"; import ReactMarkdown from "react-markdown"; import rehypeRaw from "rehype-raw"; import remarkGfm from "remark-gfm"; function remarkAdmonition() { return (tree) => { const openingTagRegex = /^:::(\w+)(?:\[(.*?)\])?\s*$/; const closingTagRegex = /^:::\s*$/; const textOnlyAdmonition = /^:::(\w+)(?:\[(.*?)\])?\s*([\s\S]*?)\s*:::$/; const nodes = []; let bufferedChildren = []; let insideAdmonition = false; let type = null; let title = null; tree.children.forEach((node) => { if ( node.type === "paragraph" && node.children.length === 1 && node.children[0].type === "text" ) { const text = node.children[0].value.trim(); const openingMatch = text.match(openingTagRegex); const closingMatch = text.match(closingTagRegex); const textOnlyAdmonitionMatch = text.match(textOnlyAdmonition); if (textOnlyAdmonitionMatch) { const type = textOnlyAdmonitionMatch[1]; const title = textOnlyAdmonitionMatch[2] ? textOnlyAdmonitionMatch[2]?.trim() : undefined; const content = textOnlyAdmonitionMatch[3]; const admonitionNode = { type: "admonition", data: { hName: "Admonition", // Tells ReactMarkdown to replace the node with Admonition component hProperties: { type, // Passed as a prop to the Admonition component title, }, }, children: [ { type: "text", value: content?.trim(), // Trim leading/trailing whitespace }, ], }; nodes.push(admonitionNode); return; } if (openingMatch) { type = openingMatch[1]; title = openingMatch[2] || type; insideAdmonition = true; return; } if (closingMatch && insideAdmonition) { nodes.push({ type: "admonition", data: { hName: "Admonition", hProperties: { type: type, title: title }, }, children: bufferedChildren, }); bufferedChildren = []; insideAdmonition = false; type = null; title = null; return; } } if (insideAdmonition) { bufferedChildren.push(node); } else { nodes.push(node); } }); if (bufferedChildren.length > 0 && type) { nodes.push({ type: "admonition", data: { hName: "Admonition", hProperties: { type: type, title: title }, }, children: bufferedChildren, }); } tree.children = nodes; }; } function convertAstToHtmlStr(ast) { if (!ast || !Array.isArray(ast)) { return ""; } const convertNode = (node) => { switch (node.type) { case "text": return node.value; case "element": const { tagName, properties, children } = node; // Convert attributes to a string const attrs = properties ? Object.entries(properties) .map(([key, value]) => `${key}="${value}"`) .join(" ") : ""; // Convert children to HTML const childrenHtml = children ? children.map(convertNode).join("") : ""; return `<${tagName} ${attrs}>${childrenHtml}</${tagName}>`; default: return ""; } }; return ast.map(convertNode).join(""); } function Markdown({ children }) { return ( <ReactMarkdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm, remarkAdmonition]} components={{ pre: (props) => <div {...props} />, code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ""); return match ? ( <CodeBlock className={className} language={match[1]} {...props}> {children} </CodeBlock> ) : ( <code className={className} {...props}> {children} </code> ); }, admonition: ({ node, ...props }) => { const type = node.data?.hProperties?.type || "note"; const title = node.data?.hProperties?.title || type; const content = convertAstToHtmlStr(node.children); return ( <Admonition type={type} title={title} {...props}> <div dangerouslySetInnerHTML={{ __html: content }} /> </Admonition> ); }, }} > {children} </ReactMarkdown> ); } export default Markdown;