IOweb/node_modules/@astrojs/markdown-satteri/dist/satteri-processor.js
2026-07-03 15:07:38 -05:00

248 lines
8.5 KiB
JavaScript

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 `<pre class="${classLanguage}" data-language="${lang}"><code class="${classLanguage}">${html}</code></pre>`;
};
}
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
};