import { readFileSync } from "node:fs"; import { join, basename, dirname } from "node:path"; import { Transform } from "node:stream"; import asciidoctor, { Document } from "asciidoctor"; import File from "vinyl"; import { DateTime } from "luxon"; import { unified } from "unified"; import rehypeParse from "rehype-parse"; import rehypeStringify from "rehype-stringify"; import rehypePresetMinify from "rehype-preset-minify"; import rehypePrism from "@mapbox/rehype-prism"; import { PRODUCTION, DEFAULT_DATE } from "../environment.js"; import rehypeExternalLinks from "rehype-external-links"; import visit from "unist-util-visit"; import { src, dest } from "gulp"; import renderLayout from "../../src/layout.js"; import renderArticleLayout from "../../src/article.js"; import renderIndex from "../../src/index.js"; import { renderToStaticMarkup } from "preact-render-to-string"; import { SITE_TITLE, SITE_DESCRIPTION } from "../../src/constants.js"; import { JSX } from "preact/jsx-runtime"; import { reloadAssets } from "../assets.js"; const Asciidoctor = asciidoctor(); const EXTENSION_REGISTRY = Asciidoctor.Extensions.create(); EXTENSION_REGISTRY.inlineMacro("abbr", function () { this.process(function (parent, target, attributes) { return this.createInline( parent, "quoted", `${target}`, ); }); }); function extractImages(gulp: Transform, file: File) { return function () { return function (tree) { visit(tree, "element", function (node: any) { if (node.tagName !== "img") { return; } const path = join(dirname(file.path)); const image = new File({ path: join(basename(file.path, ".asciidoc"), node.properties.src), contents: readFileSync(path), }); gulp.push(image); }); }; }; } function renderDocument(root: JSX.Element): Buffer { return Buffer.concat([Buffer.from(""), Buffer.from(renderToStaticMarkup(root))]); } function renderArticle() { const allArticles: [string, DateTime, Document][] = []; reloadAssets(); return new Transform({ readableObjectMode: true, writableObjectMode: true, async transform(file: File, _, callback) { try { const slug = basename(file.path, ".asciidoc"); const article = Asciidoctor.load(file.contents.toString(), { extension_registry: EXTENSION_REGISTRY, }); const date = DateTime.fromISO(article.getAttribute("docdate"), { zone: "UTC" }); if (PRODUCTION && date.equals(DEFAULT_DATE)) { callback(null); return; } allArticles.push([slug, date, article]); const vfile = await unified() .use(rehypeParse) .use(rehypeExternalLinks, { rel: "noopener", target: "_blank" }) .use(extractImages(this, file)) .use(rehypePrism) .use(rehypePresetMinify) .use(rehypeStringify) .process(article.convert()); const content = renderLayout({ title: article.getDoctitle({}) as string, meta: { description: article.getAttribute("description"), keywords: article.getAttribute("keywords"), "og:title": article.getDoctitle({}) as string, "og:type": "article", "og:article:published_time": article.getAttribute("docdate"), "og:url": `https://adaedra.eu/${slug}/`, "og:description": article.getAttribute("description"), "og:site_name": SITE_TITLE, }, Content: () => renderArticleLayout({ article, date, body: vfile.toString(), }), }); file.contents = renderDocument(content); file.path = join(slug, "index.html"); file.base = null; callback(null, file); } catch (e) { console.error(e); callback(e); } }, final(callback) { try { allArticles.sort(([, a], [, b]) => b.diff(a).toMillis()); const contents = renderLayout({ meta: { description: SITE_DESCRIPTION, "og:title": SITE_TITLE, "og:type": "website", "og:url": `https://adaedra.eu`, }, Content: () => renderIndex({ articles: allArticles }), }); this.push( new File({ path: "index.html", contents: renderDocument(contents), }), ); callback(null); } catch (e) { console.error(e); callback(e); } }, }); } export const articles = () => src("articles/**/*.asciidoc").pipe(renderArticle()).pipe(dest("dist/"));