@astrojs/markdoc
Version:
Add support for Markdoc in your Astro site
149 lines (148 loc) • 4.52 kB
JavaScript
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
};