eureka-web/build.js

124 lines
4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* build.js — собрать весь сайт Эврики из данных.
*
* Один генератор на весь сайт: корневая главная (lib/home.js) и галерея
* (gallery/works.json + gallery/works/). Оболочка — общая (lib/template.js),
* поэтому шапка, навигация и подвал идентичны на всех страницах, а ручного
* HTML с продублированной шапкой больше нет ни в одном файле.
*
* Никаких зависимостей: Node 24, стандартная библиотека.
*
* Запуск (из корня eureka-web):
* node build.js
*
* Что пишет:
* index.html — главная
* gallery/index.html — лента работ
* gallery/work/<slug>.html — страница работы
*
* Так добавление работы — это правка gallery/works.json + файл в gallery/works/,
* затем build.
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { page, esc } from "./lib/template.js";
import { homeTitle, homeDescription, homeBody } from "./lib/home.js";
const ROOT = path.dirname(fileURLToPath(import.meta.url));
const GALLERY = path.join(ROOT, "gallery");
const data = JSON.parse(fs.readFileSync(path.join(GALLERY, "works.json"), "utf8"));
// --- главная --------------------------------------------------------------
function buildHome() {
fs.writeFileSync(
path.join(ROOT, "index.html"),
page({
title: homeTitle,
description: homeDescription,
body: homeBody,
active: "home",
})
);
}
// --- галерея --------------------------------------------------------------
function workCard(w) {
return ` <a class="card" href="/gallery/work/${w.slug}.html">
<img src="/gallery/works/${w.file}" alt="${esc(w.title)}" loading="lazy">
<span class="card-title">${esc(w.title)}</span>
</a>`;
}
function buildGalleryIndex() {
const cards = data.works.map(workCard).join("\n");
const body = `
<h1 class="sr-only">Галерея Эврики</h1>
<p class="lede">${esc(data.site.intro)}</p>
<section class="grid">
${cards}
</section>`;
fs.writeFileSync(
path.join(GALLERY, "index.html"),
page({
title: "Галерея Эврики",
description: data.site.intro,
body,
active: "gallery",
mainClass: "wide",
})
);
}
function buildWorks() {
const dir = path.join(GALLERY, "work");
fs.mkdirSync(dir, { recursive: true });
const all = data.works;
for (let i = 0; i < all.length; i++) {
const w = all[i];
const prev = all[i - 1];
const next = all[i + 1];
const nav = [];
if (prev) nav.push(`<a class="pager" href="${prev.slug}.html">← ${esc(prev.title)}</a>`);
else nav.push(`<span></span>`);
if (next) nav.push(`<a class="pager" href="${next.slug}.html">${esc(next.title)} →</a>`);
else nav.push(`<span></span>`);
const body = `
<article class="work">
<figure>
<a class="zoom" href="/gallery/works/${w.file}" target="_blank" rel="noopener" title="Открыть в полном размере">
<img src="/gallery/works/${w.file}" alt="${esc(w.title)}">
</a>
</figure>
<div class="meta">
<h1>${esc(w.title)}</h1>
<p class="when">${esc(w.date)} · ${esc(w.method)}</p>
<p class="note">${esc(w.note)}</p>
<p class="full"><a href="/gallery/works/${w.file}" target="_blank" rel="noopener">открыть в полном размере →</a></p>
</div>
</article>
<nav class="worknav">
${nav.join("\n")}
</nav>`;
fs.writeFileSync(
path.join(dir, `${w.slug}.html`),
page({
title: `${w.title} — Эврика`,
description: w.note,
body,
active: "gallery",
mainClass: "wide",
})
);
}
}
buildHome();
buildGalleryIndex();
buildWorks();
console.log(`built: index.html, gallery/index.html, ${data.works.length} works`);