@paroicms/site-generator-plugin
Version:
ParoiCMS Site Generator Plugin
194 lines (191 loc) • 7.02 kB
JavaScript
import { mkdir, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { getPredefinedFields } from "../lib/create-prompt.js";
import { camelToKebabCase } from "../lib/utils.js";
import { templateOfSiteFooter, templateOfSiteHeader } from "./common-template-creator.js";
import { templateOfDocumentType } from "./document-template-creator.js";
import { isMultilingual } from "./jt-site-schema-helpers.js";
import { getThemeCssContent } from "./theme-css.js";
export async function createTheme(ctx, siteDir, siteSchema) {
const themeContext = createThemeCreatorContext(siteSchema);
for (const nodeType of siteSchema.nodeTypes ?? []) {
if (nodeType.kind === "site")
continue;
if (nodeType.kind === "part")
continue;
themeContext.addLiquidFile("root", `${camelToKebabCase(nodeType.typeName)}.liquid`, templateOfDocumentType(themeContext, nodeType));
}
themeContext.addFile("theme.json", getThemeJsonContent());
themeContext.addFile(".theme-check.yml", getLiquidLinterCheckContent());
themeContext.addFile("assets/scss/theme.scss", getThemeCssContent());
themeContext.addFile("assets/css/theme.css", getThemeCssContent());
themeContext.addLiquidFile("layouts", "main-layout.liquid", templateOfLayout(themeContext));
themeContext.addLiquidFile("root", "404.liquid", templateOf404(themeContext));
if (isMultilingual(siteSchema)) {
themeContext.addLiquidFile("root", "index.liquid", templateOfIndex());
}
const themeDir = join(siteDir, "theme");
await mkdir(themeDir);
const { files, issues } = themeContext.toFiles();
for (const file of files) {
const filePath = join(themeDir, file.path);
const dirPath = dirname(filePath);
await ensureDirectory(dirPath, { recursive: true });
await writeFile(filePath, file.content, "utf-8");
}
if (issues) {
ctx.logger.warn(`Issues in "${siteDir}":`, issues);
}
}
function createThemeCreatorContext(siteSchema) {
const languages = siteSchema.languages ?? [];
const liquidFiles = new Map();
const localeFiles = new Map();
const otherFiles = new Map();
const issues = [];
return {
siteSchema,
predefinedFields: new Map(getPredefinedFields().map((f) => [f.fieldName, f])),
setLocalizedLabel(label) {
for (const language of languages) {
const value = label[language];
if (!value)
continue;
let f = localeFiles.get(language);
if (!f) {
f = {};
localeFiles.set(language, f);
}
f[language] = value;
}
},
hasLiquidFile(directory, filename) {
const path = directory === "root" ? `templates/${filename}` : `templates/${directory}/${filename}`;
return liquidFiles.has(path);
},
addLiquidFile(directory, filename, content, { skipIfExists = false } = {}) {
const path = directory === "root" ? `templates/${filename}` : `templates/${directory}/${filename}`;
if (liquidFiles.has(path)) {
if (skipIfExists)
return;
issues.push(`Liquid file already exists, overwrite: "${path}"`);
}
liquidFiles.set(path, content);
},
addFile(path, content) {
if (otherFiles.has(path))
throw new Error(`File already exists: "${path}"`);
otherFiles.set(path, content);
},
toFiles() {
const files = [
...Array.from(liquidFiles.entries()).map(([path, content]) => ({ path, content })),
...Array.from(localeFiles.entries()).map(([language, content]) => ({
path: `locales/${language}.json`,
content: JSON.stringify(content, null, 2),
})),
...Array.from(otherFiles.entries()).map(([path, content]) => ({ path, content })),
];
files.sort((a, b) => a.path.localeCompare(b.path));
return { files, issues: issues.length > 0 ? issues : undefined };
},
};
}
// function getDefaultLiquidContent() {
// return `{% layout "layouts/main-layout.liquid" doc: doc site: site %}
// {% block %}
// <div class="Container">
// <div class="Page">
// {{ doc | info }}
// </div>
// </div>
// {% endblock %}`;
// }
function templateOf404(ctx) {
const { siteSchema } = ctx;
const rawParam = isMultilingual(siteSchema) ? " raw: true" : "";
return `{% layout "layouts/main-layout.liquid" doc: doc site: site${rawParam} %}
{% block %}
<div class="Container">
<div class="Page">
<h1>404</h1>
<p>This page doesn't exist. Please <a href='/'>return to home page</a>.</p>
</div>
</div>
{% endblock %}`;
}
function templateOfIndex() {
return `{% layout "layouts/main-layout.liquid" doc: doc site: site raw: true %}
{% block %}
<div class="Container">
<div class="Page">
{% for translation in doc.translations %}
<a href="{{ translation.url }}">{{ translation.languageLabel }}</a>
{% endfor %}
</div>
</div>
{% endblock %}`;
}
function templateOfLayout(ctx) {
const { siteSchema } = ctx;
const multilingual = isMultilingual(siteSchema);
ctx.addLiquidFile("partials", "site-header.liquid", templateOfSiteHeader(ctx));
ctx.addLiquidFile("partials", "site-footer.liquid", templateOfSiteFooter(ctx));
const renderHeader = `{% render "partials/site-header" doc: doc site: site %}`;
const headerTemplate = multilingual
? `{% unless raw %}
${renderHeader}
{% endunless %}`
: renderHeader;
const renderFooter = `{% render "partials/site-footer" doc: doc site: site %}`;
const footerTemplate = multilingual
? `{% unless raw %}
${renderFooter}
{% endunless %}`
: renderFooter;
return `<!doctype html>
<html lang="{{ doc.language }}">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
{{ doc | headTags }}
<title>
{% if doc.type != "home" and doc.title %}
{{ doc.title }} -{% endif %}
{{ site.field.title }}</title>
<link rel="stylesheet" href="{{ site.assetsUrl }}/css/theme.css">
</head>
<body>
${headerTemplate}
{% block -%}
{%- endblock %}
${footerTemplate}
</body>
</html>`;
}
function getThemeJsonContent() {
return JSON.stringify({
fTextImages: ["700x", "x400x", "700x350"],
pixelRatio: 1.5,
}, null, 2);
}
function getLiquidLinterCheckContent() {
return `UndefinedObject:
enabled: false
TranslationKeyExists:
enabled: false
MissingTemplate:
enabled: false
RemoteAsset:
enabled: false
UnknownFilter:
enabled: false`;
}
async function ensureDirectory(dirPath, { recursive = false } = {}) {
try {
await mkdir(dirPath, { recursive });
}
catch (e) {
if (e.code !== "EEXIST")
throw e;
}
}