// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2025 Fideus Labs LLC import type { Root, Parent, Node, Heading, Paragraph, Myst, DefinitionList, DefinitionTerm, DefinitionDescription, Underline, Delete, Smallcaps, Break, Abbreviation, Math as MystMath, InlineMath, Keyboard, Superscript, Subscript, } from "@awesome-myst/myst-zod"; import { basicTransformations } from "myst-transforms"; import { mystParse } from "myst-parser"; import { highlightCode, highlightInlineCode } from "./shiki-highlighter.js"; import { renderInlineMath, renderDisplayMath } from "./katex-renderer.js"; /** Function to render MyST content as HTML (simplified) */ export async function renderMystAst(root: Root): Promise { if (!root || !root.children) { return "

No content available

"; } // Counter for generating unique IDs let abbreviationCounter = 0; const renderNode = async (node: Node): Promise => { switch (node.type) { case "paragraph": { const children = await Promise.all( (node as Paragraph).children?.map(renderNode) || [] ); return `

${children.join("")}

`; } case "heading": { const headingNode = node as Heading; const level = Math.min(6, Math.max(1, headingNode.depth || 2)); const children = await Promise.all( headingNode.children?.map(renderNode) || [] ); const text = children.join(""); const id = text .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); return `${text}`; } case "text": return (node as any).value || ""; case "break": return "
"; case "emphasis": { const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return `${children.join("")}`; } case "strong": { const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return `${children.join("")}`; } case "underline": { const children = await Promise.all( (node as Underline).children?.map(renderNode) || [] ); return `${children.join("")}`; } case "delete": { const children = await Promise.all( (node as Delete).children?.map(renderNode) || [] ); return `${children.join("")}`; } case "smallcaps": { const children = await Promise.all( (node as Smallcaps).children?.map(renderNode) || [] ); return `${children.join( "" )}`; } case "superscript": { const children = await Promise.all( (node as Superscript).children?.map(renderNode) || [] ); return `${children.join("")}`; } case "subscript": { const children = await Promise.all( (node as Subscript).children?.map(renderNode) || [] ); return `${children.join("")}`; } case "abbreviation": { const abbrevNode = node as Abbreviation; const children = await Promise.all( abbrevNode.children?.map(renderNode) || [] ); const content = children.join(""); if (abbrevNode.title) { const uniqueId = `abbr-${++abbreviationCounter}`; return `${content}${abbrevNode.title}`; } else { // Fallback to standard abbr without tooltip if no title return `${content}`; } } case "keyboard": { const keyboardNode = node as Keyboard; const children = await Promise.all( keyboardNode.children?.map(renderNode) || [] ); const content = children.join(""); // Style keyboard input using Web Awesome design tokens, matching SearchDialog.astro styling return `${content}`; } case "inlineCode": { const codeNode = node as any; const code = codeNode.value || ""; const lang = codeNode.lang; // MyST might provide language for inline code return await highlightInlineCode(code, lang); } case "code": { const codeNode = node as any; const code = codeNode.value || ""; const lang = codeNode.lang || codeNode.language || "text"; const highlighted = await highlightCode(code, lang); return highlighted; } case "list": { const listNode = node as any; const tag = listNode.ordered ? "ol" : "ul"; const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return `<${tag}>${children.join("")}`; } case "listItem": { const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return `
  • ${children.join("")}
  • `; } case "definitionList": { const children = await Promise.all( (node as DefinitionList).children?.map(renderNode) || [] ); return `
    ${children.join("")}
    `; } case "definitionTerm": { const children = await Promise.all( (node as DefinitionTerm).children?.map(renderNode) || [] ); return `
    ${children.join("")}
    `; } case "definitionDescription": { const children = await Promise.all( (node as DefinitionDescription).children?.map(renderNode) || [] ); return `
    ${children.join("")}
    `; } case "blockquote": { const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return `
    ${children.join("")}
    `; } case "link": { const linkNode = node as any; const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return `${children.join("")}`; } case "block": { // Block nodes are container nodes that just pass through their children const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return children.join(""); } case "inlineMath": { const mathNode = node as InlineMath; const math = mathNode.value || ""; return renderInlineMath(math); } case "math": { const mathNode = node as MystMath; const math = mathNode.value || ""; return renderDisplayMath(math); } case "myst": return `${(node as Myst).value}`; case "html": { const htmlNode = node as any; const htmlContent = htmlNode.value || ""; return htmlContent } default: console.warn(`Unknown node type: ${(node as Parent).type}`); console.log("Unknown node structure:", JSON.stringify(node, null, 2)); const children = await Promise.all( (node as Parent).children?.map(renderNode) || [] ); return children.join(""); } }; const renderedChildren = await Promise.all( root.children?.map(renderNode) || [] ); return renderedChildren.join("\n"); } /** * Parse MyST markdown and render it to HTML in one function * @param mystContent - Raw MyST markdown string * @returns Promise - Rendered HTML string */ export async function mystParseAndRender(mystContent: string): Promise { try { // Parse the MyST content const tree = mystParse(mystContent); // Create a minimal VFile-like object for the transformations const file = { path: "rendered.md", messages: [], data: {}, history: [], cwd: "/tmp", value: "", map: undefined, fail: () => {}, info: () => {}, message: () => {}, warn: () => {}, }; // Apply basic MyST transformations to the root AST try { basicTransformations(tree as Root, file as any); } catch (error) { console.warn("Failed to apply basic transformations:", error); } // Render the parsed tree to HTML return await renderMystAst(tree as Root); } catch (error) { console.error("MyST parse and render error:", error); const errorMessage = error instanceof Error ? error.message : "Unknown error"; return `

    Error parsing MyST content: ${errorMessage}

    `; } }