125 lines
4 KiB
JavaScript
125 lines
4 KiB
JavaScript
|
|
#!/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`);
|