lume-plugin-kroki/mod.ts

112 lines
3.2 KiB
TypeScript

/*
* Copyright 2024 Foster Hangdaan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as v from "@valibot/valibot";
import Site from "lume/core/site.ts";
import diagrams from "./resources/diagrams.json" with { type: "json" };
import { deflateSync } from "node:zlib";
const textEncoder = new TextEncoder();
const diagramNames: string[] = diagrams.map((d) => d.name);
const OptionsSchema = v.optional(
v.object({
// The names of diagrams that will be converted.
enabledDiagrams: v.optional(
v.pipe(
v.array(v.string()),
v.everyItem(
(item) => diagramNames.includes(item),
`Invalid names provided to \`enabledDiagrams\`. Valid names are: ${
diagramNames.join(", ")
}`,
),
),
diagramNames,
),
// Preferred output format.
// Falls back to `svg` if the diagram does not support it.
// For a complete list, refer to https://kroki.io/#support
format: v.optional(
v.union([
v.literal("jpeg"),
v.literal("png"),
v.literal("svg"),
]),
"svg",
),
server: v.optional(
v.pipe(
v.string(),
v.nonEmpty(),
v.url(),
),
"https://kroki.io",
),
}),
{
enabledDiagrams: diagramNames,
format: "svg",
server: "https://kroki.io",
},
);
export type Options = v.InferInput<typeof OptionsSchema>;
export default function (options?: Options): (site: Site) => void {
const opts = v.parse(OptionsSchema, options);
return (site: Site) => {
site.process([".html"], (pages) => {
pages.forEach((page) => {
diagrams.forEach((diagram) => {
if (page.document && opts.enabledDiagrams.includes(diagram.name)) {
const format = diagram.formats.includes(opts.format)
? opts.format
: "svg";
const codeblocks = page.document.querySelectorAll(
`pre > code.${diagram.matcher}`,
);
for (const codeblock of codeblocks) {
if (
codeblock.textContent && codeblock.parentElement
) {
const encoded = textEncoder.encode(codeblock.textContent);
const compressed = deflateSync(encoded);
const result = compressed.toString("base64url");
const img = page.document.createElement("img");
const url = new URL(
`${opts.server}/${diagram.slug}/${format}/${result}`,
);
img.setAttribute("src", url.toString());
codeblock.parentElement.replaceWith(img);
}
}
}
});
});
});
};
}