163 lines
5.9 KiB
TypeScript
163 lines
5.9 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { env } from "node:process";
|
|
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 { readAssetManifest } from "../hash.js";
|
|
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";
|
|
|
|
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][] = [];
|
|
const assetManifest = readAssetManifest();
|
|
|
|
const asset = (path: string) => {
|
|
if (assetManifest.hasOwnProperty(path)) {
|
|
path = assetManifest[path];
|
|
}
|
|
|
|
return env.ASSETS_HOSTS
|
|
? new URL(path, env.ASSETS_HOSTS).toString()
|
|
: join("/_assets/", path);
|
|
};
|
|
|
|
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({
|
|
asset,
|
|
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({
|
|
asset,
|
|
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({
|
|
asset,
|
|
meta: {
|
|
description: SITE_DESCRIPTION,
|
|
"og:title": SITE_TITLE,
|
|
"og:type": "website",
|
|
"og:url": `https://adaedra.eu`,
|
|
},
|
|
Content: () => renderIndex({ articles: allArticles, asset }),
|
|
});
|
|
|
|
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/"));
|