UNPKG

@astrojs/markdoc

Version:
149 lines (148 loc) 4.52 kB
import Markdoc, { } from "@markdoc/markdoc"; import { createComponent, renderComponent } from "astro/runtime/server/index.js"; import { setupHeadingConfig } from "./heading-ids.js"; import { htmlTag } from "./html/tagdefs/html.tag.js"; async function setupConfig(userConfig = {}, options, experimentalHeadingIdCompat) { let defaultConfig = setupHeadingConfig(experimentalHeadingIdCompat); if (userConfig.extends) { for (let extension of userConfig.extends) { if (extension instanceof Promise) { extension = await extension; } defaultConfig = mergeConfig(defaultConfig, extension); } } let merged = mergeConfig(defaultConfig, userConfig); if (options?.allowHTML) { merged = mergeConfig(merged, HTML_CONFIG); } return merged; } function setupConfigSync(userConfig = {}, options, experimentalHeadingIdCompat) { const defaultConfig = setupHeadingConfig(experimentalHeadingIdCompat); let merged = mergeConfig(defaultConfig, userConfig); if (options?.allowHTML) { merged = mergeConfig(merged, HTML_CONFIG); } return merged; } function mergeConfig(configA, configB) { return { ...configA, ...configB, ctx: { ...configA.ctx, ...configB.ctx }, tags: { ...configA.tags, ...configB.tags }, nodes: { ...configA.nodes, ...configB.nodes }, functions: { ...configA.functions, ...configB.functions }, variables: { ...configA.variables, ...configB.variables }, partials: { ...configA.partials, ...configB.partials }, validation: { ...configA.validation, ...configB.validation } }; } function resolveComponentImports(markdocConfig, tagComponentMap, nodeComponentMap) { for (const [tag, render] of Object.entries(tagComponentMap)) { const config = markdocConfig.tags[tag]; if (config) config.render = render; } for (const [node, render] of Object.entries(nodeComponentMap)) { const config = markdocConfig.nodes[node]; if (config) config.render = render; } return markdocConfig; } function getTextContent(childNodes) { let text = ""; for (const node of childNodes) { if (typeof node === "string" || typeof node === "number") { text += node; } else if (typeof node === "object" && Markdoc.Tag.isTag(node)) { text += getTextContent(node.children); } } return text; } const headingLevels = [1, 2, 3, 4, 5, 6]; function collectHeadings(children, collectedHeadings) { for (const node of children) { if (typeof node !== "object" || !Markdoc.Tag.isTag(node)) continue; if (node.attributes.__collectHeading === true && typeof node.attributes.level === "number") { collectedHeadings.push({ slug: node.attributes.id, depth: node.attributes.level, text: getTextContent(node.children) }); continue; } for (const level of headingLevels) { if (node.name === "h" + level) { collectedHeadings.push({ slug: node.attributes.id, depth: level, text: getTextContent(node.children) }); } } collectHeadings(node.children, collectedHeadings); } } function createGetHeadings(stringifiedAst, userConfig, options, experimentalHeadingIdCompat) { return function getHeadings() { const config = setupConfigSync(userConfig, options, experimentalHeadingIdCompat); const ast = Markdoc.Ast.fromJSON(stringifiedAst); const content = Markdoc.transform(ast, config); let collectedHeadings = []; collectHeadings(Array.isArray(content) ? content : [content], collectedHeadings); return collectedHeadings; }; } function createContentComponent(Renderer, stringifiedAst, userConfig, options, tagComponentMap, nodeComponentMap, experimentalHeadingIdCompat) { return createComponent({ async factory(result, props) { const withVariables = mergeConfig(userConfig, { variables: props }); const config = resolveComponentImports( await setupConfig(withVariables, options, experimentalHeadingIdCompat), tagComponentMap, nodeComponentMap ); return renderComponent(result, Renderer.name, Renderer, { stringifiedAst, config }, {}); }, propagation: "self" }); } const HTML_CONFIG = { tags: { "html-tag": htmlTag } }; export { collectHeadings, createContentComponent, createGetHeadings, getTextContent, mergeConfig, resolveComponentImports, setupConfig, setupConfigSync };