Initial Paprika Next.js implementation

This commit is contained in:
2026-07-06 12:12:36 -04:00
commit 7d585484a6
38 changed files with 14824 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
import fs from "node:fs";
import vm from "node:vm";
const sourcePath = "doc/index.html";
const html = fs.readFileSync(sourcePath, "utf8");
const styleMatch = html.match(/<style>([\s\S]*?)<\/style>/);
const scriptMatch = html.match(/<script>([\s\S]*?)<\/script>/);
if (!styleMatch || !scriptMatch) {
throw new Error("Could not find inline style or script in doc/index.html");
}
const css = styleMatch[1]
.replaceAll("href=\"?page=", "href=\"/")
.replace(/href="\/home"/g, "href=\"/\"");
const legacyScript = scriptMatch[1];
const renderScript = legacyScript.replace(
/const params = new URLSearchParams\(window\.location\.search\);[\s\S]*$/,
"globalThis.__pageData = pageData;"
);
const sandbox = {
console,
URLSearchParams,
window: { location: { search: "" } },
document: {
title: "",
getElementById() {
return { innerHTML: "" };
},
querySelectorAll() {
return [];
},
querySelector() {
return null;
}
}
};
vm.createContext(sandbox);
vm.runInContext(renderScript, sandbox, { filename: sourcePath });
const pageData = sandbox.__pageData;
const routeMap = {
home: "/",
press: "/press",
judging: "/judging",
scholarly: "/scholarly",
services: "/services",
why: "/why",
resources: "/resources",
faq: "/faq",
thanks: "/thanks",
terms: "/terms",
privacy: "/privacy",
disclaimer: "/disclaimer"
};
const pages = Object.fromEntries(
Object.entries(routeMap).map(([key, path]) => {
const page = pageData[key];
if (!page?.body) {
throw new Error(`Missing page body for ${key}`);
}
return [
key,
{
key,
path,
title: key === "home" ? "Paprika" : `Paprika - ${key}`,
html: normalizeLinks(page.body)
}
];
})
);
function normalizeLinks(value) {
return value
.replaceAll("href=\"?page=home\"", "href=\"/\"")
.replace(/href="\?page=([a-z-]+)"/g, (_match, page) => {
if (page === "home") return "href=\"/\"";
return `href="/${page}"`;
});
}
fs.mkdirSync("app", { recursive: true });
fs.mkdirSync("lib", { recursive: true });
fs.writeFileSync(
"app/globals.css",
`${css}\n\n.form-error { color: var(--deep-red); font-size: 14px; font-weight: 750; line-height: 1.4; }\n.form-success { color: var(--green); font-size: 14px; font-weight: 750; line-height: 1.4; }\n.field-control { width: 100%; border: 0; outline: 0; background: transparent; color: var(--ink); font: inherit; font-weight: 650; }\n.field-control::placeholder { color: var(--muted); opacity: .78; }\ntextarea.field-control { min-height: 126px; resize: vertical; line-height: 1.45; }\n.checkbox-field { align-items: flex-start; gap: 12px; text-align: left; color: var(--text); font-size: 14px; line-height: 1.45; }\n.checkbox-field input { margin-top: 3px; accent-color: var(--red); }\n.button[disabled] { cursor: not-allowed; opacity: .62; }\n`
);
fs.writeFileSync(
"lib/legacy-pages.ts",
`export type LegacyPageKey = ${Object.keys(pages).map((key) => JSON.stringify(key)).join(" | ")};\n\nexport type LegacyPage = {\n key: LegacyPageKey;\n path: string;\n title: string;\n html: string;\n};\n\nexport const legacyPages = ${JSON.stringify(pages, null, 2)} as const satisfies Record<LegacyPageKey, LegacyPage>;\n`
);
console.log(`Generated ${Object.keys(pages).length} pages from ${sourcePath}`);