import { isRemoteAllowed } from "@astrojs/internal-helpers/remote"; import { defaultExcludeLanguages, markdownConfigDefaults, syntaxHighlightDefaults } from "@astrojs/internal-helpers/markdown"; import Slugger from "github-slugger"; import { createShikiHighlighter } from "@astrojs/internal-helpers/shiki"; let satteri; async function loadSatteri() { satteri ??= await import("satteri"); return satteri; } function createCollectImagesPlugin(image = {}) { const domains = image?.domains ?? []; const remotePatterns = image?.remotePatterns ?? []; return { name: "collect-images", image(node, ctx) { const url = node.url ? decodeURI(node.url) : void 0; if (!url) return; const astro = ctx.data.astro; if (URL.canParse(url)) { if (isRemoteAllowed(url, { domains, remotePatterns })) { astro?.remoteImagePaths.add(url); } } else if (!url.startsWith("/")) { astro?.localImagePaths.add(url); } } }; } function resolveFrontmatterExpression(expr, frontmatter) { if (!expr.startsWith("frontmatter.") && !expr.startsWith("frontmatter[")) return void 0; const pathStr = expr.slice("frontmatter".length); const parts = []; const pathRegex = /\.(\w+)|\[(\d+)\]|\["([^"]+)"\]|\['([^']+)'\]/g; let match; while ((match = pathRegex.exec(pathStr)) !== null) { parts.push(match[1] ?? match[2] ?? match[3] ?? match[4]); } if (parts.length === 0) return void 0; let value = frontmatter; for (const key of parts) { if (value == null || typeof value !== "object") return void 0; value = value[key]; } return typeof value === "string" ? value : void 0; } function collectHastText(node, frontmatter) { let text = ""; if (node.type === "mdxFlowExpression" || node.type === "mdxTextExpression") { if (node.value != null && frontmatter) { const resolved = resolveFrontmatterExpression(node.value.trim(), frontmatter); text += resolved ?? node.value; } } else if ("value" in node && node.value != null) { text += node.value; } if ("children" in node && node.children) { for (const child of node.children) { text += collectHastText(child, frontmatter); } } return text; } function makeFragmentNode(html) { return { type: "mdxJsxFlowElement", name: "Fragment", attributes: [{ type: "mdxJsxAttribute", name: "set:html", value: html }], children: [] }; } function createHeadingIdsPlugin() { const slugger = new Slugger(); const headings = []; return { name: "heading-ids", element: { filter: ["h1", "h2", "h3", "h4", "h5", "h6"], visit(node, ctx) { const astro = ctx.data.astro; const rawText = ctx.textContent(node); const text = rawText.includes("frontmatter") ? collectHastText(node, astro?.frontmatter ?? {}) : rawText; const existingId = node.properties?.id; const slug = typeof existingId === "string" ? existingId : slugger.slug(text); const depth = Number.parseInt(node.tagName[1], 10); headings.push({ depth, slug, text }); if (astro) astro.headings = headings; if (typeof existingId !== "string") { ctx.setProperty(node, "id", slug); } } } }; } function makeRawNode(html) { return { type: "raw", value: html }; } const HAST_PRESERVED_PROPERTIES = /* @__PURE__ */ new Set(["className", "htmlFor"]); function createImageMarkerPlugin() { const indexBySrc = /* @__PURE__ */ new Map(); return { name: "image-marker", element: { filter: ["img"], visit(node, ctx) { const props = node.properties ?? {}; const rawSrc = typeof props.src === "string" ? props.src : void 0; if (!rawSrc) return; const src = decodeURI(rawSrc); const astro = ctx.data.astro; const isLocal = astro?.localImagePaths.has(src) ?? false; const isRemote = !isLocal && (astro?.remoteImagePaths.has(src) ?? false); if (!isLocal && !isRemote) return; const { src: _src, ...rest } = props; const index = indexBySrc.get(rawSrc) ?? 0; indexBySrc.set(rawSrc, index + 1); const imageProperties = { ...rest, src, index }; if (isRemote && !("width" in props) && !("height" in props)) { imageProperties.inferSize = true; } ctx.setProperty(node, "__ASTRO_IMAGE_", JSON.stringify(imageProperties)); for (const key of Object.keys(rest)) { if (!HAST_PRESERVED_PROPERTIES.has(key)) { ctx.setProperty(node, key, null); } } ctx.setProperty(node, "src", null); } } }; } function createHighlightPlugin(highlight, excludeLangs, options) { const wrapResult = options?.mdx ? makeFragmentNode : makeRawNode; return { name: "highlight", element: { filter: ["pre"], async visit(node, ctx) { const codeChild = node.children?.find( (c) => c.type === "element" && c.tagName === "code" ); if (!codeChild || codeChild.type !== "element") return; const lang = codeChild.data?.lang ?? "plaintext"; const meta = codeChild.data?.meta ?? void 0; if (excludeLangs && excludeLangs.includes(lang) || defaultExcludeLanguages.includes(lang)) { return; } const code = ctx.textContent(codeChild).replace(/\n$/, ""); const html = await highlight(code, lang, meta); return wrapResult(html); } } }; } async function createHighlightFn(syntaxHighlight, shikiConfig) { const syntaxHighlightType = typeof syntaxHighlight === "string" ? syntaxHighlight : syntaxHighlight ? syntaxHighlight.type : void 0; if (syntaxHighlightType === "shiki") { const hl = await createShikiHighlighter({ langs: shikiConfig?.langs, theme: shikiConfig?.theme, themes: shikiConfig?.themes, langAlias: shikiConfig?.langAlias }); return (code, lang, meta) => hl.codeToHtml(code, lang, { meta, wrap: shikiConfig?.wrap, defaultColor: shikiConfig?.defaultColor, transformers: shikiConfig?.transformers }); } if (syntaxHighlightType === "prism") { const { runHighlighterWithAstro } = await import("@astrojs/prism/dist/highlighter"); return async (code, lang) => { const { html, classLanguage } = await runHighlighterWithAstro(lang, code); return `
${html}
`; }; } return void 0; } async function createSatteriMarkdownProcessor(opts) { const s = await loadSatteri(); const { syntaxHighlight = syntaxHighlightDefaults, shikiConfig = markdownConfigDefaults.shikiConfig, gfm = markdownConfigDefaults.gfm, smartypants = markdownConfigDefaults.smartypants, mdastPlugins: userMdastPlugins = [], hastPlugins: userHastPlugins = [], features: userFeatures } = opts ?? {}; const highlightFn = await createHighlightFn(syntaxHighlight, shikiConfig); const syntaxHighlightExcludeLangs = typeof syntaxHighlight === "object" ? syntaxHighlight.excludeLangs : void 0; return { async render(content, renderOpts) { const astro = { frontmatter: renderOpts?.frontmatter ?? {}, headings: [], localImagePaths: /* @__PURE__ */ new Set(), remoteImagePaths: /* @__PURE__ */ new Set() }; const allMdastPlugins = [ ...userMdastPlugins, createCollectImagesPlugin(opts?.image) ]; const hastPlugins = []; if (highlightFn) { hastPlugins.push(createHighlightPlugin(highlightFn, syntaxHighlightExcludeLangs)); } hastPlugins.push(...userHastPlugins); hastPlugins.push(createImageMarkerPlugin()); hastPlugins.push(createHeadingIdsPlugin()); const { html, data } = await s.markdownToHtml(content, { mdastPlugins: allMdastPlugins, hastPlugins, features: { gfm: gfm !== false, smartPunctuation: smartypants !== false, ...userFeatures }, fileURL: renderOpts?.fileURL, data: { astro } }); const result = data.astro; return { code: html, metadata: { headings: result?.headings ?? [], localImagePaths: result ? Array.from(result.localImagePaths) : [], remoteImagePaths: result ? Array.from(result.remoteImagePaths) : [], frontmatter: result?.frontmatter ?? {} } }; } }; } export { collectHastText, createCollectImagesPlugin, createHeadingIdsPlugin, createHighlightFn, createHighlightPlugin, createImageMarkerPlugin, createSatteriMarkdownProcessor, makeFragmentNode };