blog/lib/tasks/articles.ts

149 lines
5.5 KiB
TypeScript
Raw Normal View History

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";
2024-06-24 17:31:11 +00:00
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",
`<abbr title="${attributes["$positional"]}">${target}</abbr>`,
);
});
});
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("<!doctype html>"), Buffer.from(renderToStaticMarkup(root))]);
}
function renderArticle() {
const allArticles: [string, DateTime, Document][] = [];
2024-06-24 17:31:11 +00:00
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`,
},
2024-06-24 17:31:11 +00:00
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/"));