2025-09-05 14:59:21 +08:00

52 lines
1.9 KiB
JavaScript

import { visit } from "unist-util-visit";
import { toString } from "hast-util-to-string";
export default rehypeHighlight;
export function rehypeHighlight(opts) {
const options = opts;
return async (tree) => {
const tasks = [];
const styles = [];
visit(
tree,
(node) => ["pre", "code"].includes(node.tagName) && !!(node.properties?.language || node.properties?.highlights),
(node) => {
const _node = node;
const highlights = typeof _node.properties.highlights === "string" ? _node.properties.highlights.split(/[,\s]+/).map(Number) : Array.isArray(_node.properties.highlights) ? _node.properties.highlights.map(Number) : [];
const task = options.highlighter(
toString(node),
_node.properties.language,
options.theme,
{
highlights: highlights.filter(Boolean),
meta: _node.properties.meta
}
).then(({ tree: tree2, className, style, inlineStyle }) => {
_node.properties.className = ((_node.properties.className || "") + " " + className).trim();
_node.properties.style = ((_node.properties.style || "") + " " + inlineStyle).trim();
if (_node.children[0]?.tagName === "code") {
_node.children[0].children = tree2;
} else {
_node.children = tree2[0].children || tree2;
}
if (style)
styles.push(style);
});
tasks.push(task);
}
);
if (tasks.length) {
await Promise.all(tasks);
tree.children.push({
type: "element",
tagName: "style",
children: [{ type: "text", value: cleanCSS(styles.join("")) }],
properties: {}
});
}
};
}
const cleanCSS = (css) => {
const styles = css.split("}").filter((s) => Boolean(s.trim())).map((s) => s.trim() + "}");
return Array.from(new Set(styles)).join("");
};