diff --git a/lib/tasks/articles.ts b/lib/tasks/articles.ts
index e09386e..6ec0bf2 100644
--- a/lib/tasks/articles.ts
+++ b/lib/tasks/articles.ts
@@ -1,4 +1,3 @@
-import { compileFile } from "pug";
import { readFileSync } from "node:fs";
import { env } from "node:process";
import { join, basename, dirname } from "node:path";
@@ -16,6 +15,12 @@ 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();
@@ -29,18 +34,6 @@ EXTENSION_REGISTRY.inlineMacro("abbr", function () {
});
});
-const SITE_TITLE = "Ad\xE6dra's blog";
-const SITE_DESCRIPTION = [
- "Ad\xE6dra",
- "Software Developper in Paris, France",
- "Rust, Ruby, Typescript, Linux",
-].join(" \u2022 ");
-const SITE_DEFAULT_META = {
- "og:image": "https://adaedra.blob.core.windows.net/blog-assets/og_preview.png",
- "og:image:alt": "Ad\xE6dra's mascot",
- "og:locale": "en_GB",
-};
-
function extractImages(gulp: Transform, file: File) {
return function () {
return function (tree) {
@@ -61,10 +54,12 @@ function extractImages(gulp: Transform, file: File) {
};
}
+function renderDocument(root: JSX.Element): Buffer {
+ return Buffer.concat([Buffer.from(""), Buffer.from(renderToStaticMarkup(root))]);
+}
+
function renderArticle() {
const allArticles: [string, DateTime, Document][] = [];
- const renderLayout = compileFile("src/layout.pug");
- const renderArticleLayout = compileFile("src/article.pug");
const assetManifest = readAssetManifest();
const asset = (path: string) => {
@@ -103,31 +98,28 @@ function renderArticle() {
.use(rehypeStringify)
.process(article.convert());
const content = renderLayout({
- SITE_TITLE,
asset,
- title: article.getDoctitle({}),
- meta: Object.assign({}, SITE_DEFAULT_META, {
+ title: article.getDoctitle({}) as string,
+ meta: {
description: article.getAttribute("description"),
keywords: article.getAttribute("keywords"),
- "og:title": article.getDoctitle({}),
+ "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,
- }),
- render() {
- return renderArticleLayout({
+ },
+ Content: () =>
+ renderArticleLayout({
asset,
article,
date,
- DateTime,
body: vfile.toString(),
- });
- },
+ }),
});
- file.contents = Buffer.from(content);
+ file.contents = renderDocument(content);
file.path = join(slug, "index.html");
file.base = null;
@@ -140,22 +132,23 @@ function renderArticle() {
final(callback) {
try {
allArticles.sort(([, a], [, b]) => b.diff(a).toMillis());
- const renderIndex = compileFile("src/index.pug");
const contents = renderLayout({
- SITE_TITLE,
asset,
- meta: Object.assign({}, SITE_DEFAULT_META, {
+ meta: {
description: SITE_DESCRIPTION,
"og:title": SITE_TITLE,
"og:type": "website",
"og:url": `https://adaedra.eu`,
- }),
- render() {
- return renderIndex({ articles: allArticles, asset });
},
+ Content: () => renderIndex({ articles: allArticles, asset }),
});
- this.push(new File({ path: "index.html", contents: Buffer.from(contents) }));
+ this.push(
+ new File({
+ path: "index.html",
+ contents: renderDocument(contents),
+ }),
+ );
callback(null);
} catch (e) {
console.error(e);
diff --git a/package-lock.json b/package-lock.json
index ba632bd..f228f36 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,8 @@
"postcss": "^8.4.38",
"postcss-import": "^16.1.0",
"postcss-nesting": "^12.1.5",
- "pug": "^3.0.3",
+ "preact": "^10.22.0",
+ "preact-render-to-string": "^6.5.5",
"rehype": "^13.0.1",
"rehype-external-links": "^3.0.0",
"rehype-preset-minify": "^7.0.0",
@@ -28,12 +29,10 @@
"unified": "^11.0.4"
},
"devDependencies": {
- "@types/express": "^4.17.21",
"@types/glob": "^8.1.0",
"@types/gulp": "^4.0.17",
"@types/gulp-postcss": "^8.0.6",
"@types/luxon": "^3.4.2",
- "@types/pug": "^2.0.10",
"@types/tmp": "^0.2.6",
"prettier": "^3.3.1"
}
@@ -374,55 +373,12 @@
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="
},
- "node_modules/@types/body-parser": {
- "version": "1.19.5",
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
- "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
- "dev": true,
- "dependencies": {
- "@types/connect": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/connect": {
- "version": "3.4.38",
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
- "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
- "dev": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/expect": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz",
"integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==",
"dev": true
},
- "node_modules/@types/express": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
- "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
- "dev": true,
- "dependencies": {
- "@types/body-parser": "*",
- "@types/express-serve-static-core": "^4.17.33",
- "@types/qs": "*",
- "@types/serve-static": "*"
- }
- },
- "node_modules/@types/express-serve-static-core": {
- "version": "4.19.3",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz",
- "integrity": "sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==",
- "dev": true,
- "dependencies": {
- "@types/node": "*",
- "@types/qs": "*",
- "@types/range-parser": "*",
- "@types/send": "*"
- }
- },
"node_modules/@types/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz",
@@ -474,12 +430,6 @@
"@types/unist": "^2"
}
},
- "node_modules/@types/http-errors": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
- "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
- "dev": true
- },
"node_modules/@types/luxon": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
@@ -494,12 +444,6 @@
"@types/unist": "*"
}
},
- "node_modules/@types/mime": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
- "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
- "dev": true
- },
"node_modules/@types/minimatch": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
@@ -520,45 +464,6 @@
"integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==",
"dev": true
},
- "node_modules/@types/pug": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
- "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
- "dev": true
- },
- "node_modules/@types/qs": {
- "version": "6.9.15",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
- "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
- "dev": true
- },
- "node_modules/@types/range-parser": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
- "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
- "dev": true
- },
- "node_modules/@types/send": {
- "version": "0.17.4",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
- "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
- "dev": true,
- "dependencies": {
- "@types/mime": "^1",
- "@types/node": "*"
- }
- },
- "node_modules/@types/serve-static": {
- "version": "1.15.7",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
- "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
- "dev": true,
- "dependencies": {
- "@types/http-errors": "*",
- "@types/node": "*",
- "@types/send": "*"
- }
- },
"node_modules/@types/streamx": {
"version": "2.9.5",
"resolved": "https://registry.npmjs.org/@types/streamx/-/streamx-2.9.5.tgz",
@@ -4341,6 +4246,23 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "node_modules/preact": {
+ "version": "10.22.0",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz",
+ "integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/preact-render-to-string": {
+ "version": "6.5.5",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.5.tgz",
+ "integrity": "sha512-KiMFTKNTmT/ccE79BURR/r6XRc2I2TCTZ0MpeWqHW2XnllbeghXvwGsdAfF/MzMilUcTfODtSmMxgoRFL9TM5g==",
+ "peerDependencies": {
+ "preact": ">=10"
+ }
+ },
"node_modules/prettier": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
diff --git a/package.json b/package.json
index ea3d124..71a46ae 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,8 @@
"postcss": "^8.4.38",
"postcss-import": "^16.1.0",
"postcss-nesting": "^12.1.5",
- "pug": "^3.0.3",
+ "preact": "^10.22.0",
+ "preact-render-to-string": "^6.5.5",
"rehype": "^13.0.1",
"rehype-external-links": "^3.0.0",
"rehype-preset-minify": "^7.0.0",
@@ -24,12 +25,10 @@
"unified": "^11.0.4"
},
"devDependencies": {
- "@types/express": "^4.17.21",
"@types/glob": "^8.1.0",
"@types/gulp": "^4.0.17",
"@types/gulp-postcss": "^8.0.6",
"@types/luxon": "^3.4.2",
- "@types/pug": "^2.0.10",
"@types/tmp": "^0.2.6",
"prettier": "^3.3.1"
},
diff --git a/src/article.pug b/src/article.pug
deleted file mode 100644
index 6e5edbb..0000000
--- a/src/article.pug
+++ /dev/null
@@ -1,13 +0,0 @@
-nav
- a(href="/" title="Back to home page")
- img(src=asset("avatar.avif") alt="Adædra's avatar")
- | Adædra
-
-article
- header.article-header
- = date.toLocaleString(DateTime.DATE_FULL)
- h1!= article.getDocumentTitle()
-
- main
- a(name="content")
- != body
diff --git a/src/article.tsx b/src/article.tsx
new file mode 100644
index 0000000..2d2ca77
--- /dev/null
+++ b/src/article.tsx
@@ -0,0 +1,28 @@
+import { Document } from "asciidoctor";
+import { DateTime } from "luxon";
+
+type Props = {
+ article: Document;
+ date: DateTime;
+ body: string;
+ asset(path: string): string;
+};
+
+export default ({ asset, article, date, body }: Props) => (
+ <>
+
+
- | Rust, Ruby, TypeScript, Linux
- | Paris, France
- img(src=asset("cariboudev.avif") alt="Adædra's mascot")/
-
-h2 Latest articles
-ul.index-list
- - for ([slug, date, article] of articles)
- li
- = date.toISODate()
- |
- a(href=`/${slug}/`)!= article.getDocumentTitle()
-
-h2 Find me elsewhere
-ul.index-list
- li: a(href="https://gitea.adaedra.eu/adaedra" target="_blank")
- svg(xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-label="Git Logo"): use(href="/logos.svg#git")/
- | Gitea
- li: a(href="https://nerdculture.de/@adaedra" target="_blank" rel="noopener")
- svg(xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-label="Mastodon Logo"): use(href="/logos.svg#mastodon")/
- | Mastodon
- li: a(href="https://www.linkedin.com/in/thibault-hamel/" target="_blank" rel="noopener")
- svg(xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-label="LinkedIn Logo"): use(href="/logos.svg#linkedin")/
- | LinkedIn
-
-footer.index-footer
- | Mascot drawn by
- a(href="https://linktr.ee/cutepencilcase" target="_blank" rel="noopener") CutePencilCase
- |
Font
- a(href="https://github.com/be5invis/Iosevka" target="_blank" rel="noopener") Iosevka
- | • Color theme
- a(href="https://github.com/morhetz/gruvbox-contrib" target="_blank" rel="noopener") Gruvbox
- | • Social icons
- a(href="https://icons8.com/" target="_blank" rel="noopener") Icons8
- br/
- a(href="https://gitea.adaedra.eu/adaedra/blog" target="_blank") Source code
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..34e9358
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,84 @@
+import { Document } from "asciidoctor";
+import { DateTime } from "luxon";
+
+type HeaderProps = {
+ asset(path: string): string;
+};
+
+const Header = ({ asset }: HeaderProps) => (
+ Adædra
+ Software developer
+
+ Rust, Ruby, TypeScript, Linux
+
+ Paris, France
+