@paroicms/site-generator-plugin
Version:
ParoiCMS Site Generator Plugin
348 lines (347 loc) • 13.4 kB
JavaScript
import { isDef } from "@paroicms/public-anywhere-lib";
import { camelToKebabCase } from "../lib/utils.js";
import { templateOfDocumentBreadcrumb } from "./common-template-creator.js";
import { templateOfDocumentCard } from "./document-card-template-creator.js";
import { createIdKeyProvider } from "./id-key-provider.js";
import { getJtPartType, getJtRoutingDocumentType, hasTemporalChildren, } from "./jt-site-schema-helpers.js";
import { templatesOfLabeledList } from "./labeled-list-template-creator.js";
import { getPredefinedDataType, indent, localizedLabelTemplate } from "./template-helpers.js";
export function templateOfDocumentType(ctx, documentType) {
const childrenTemplate = templateOfDocumentChildren(ctx, documentType, createIdKeyProvider());
const featuredImageTemplate = documentType.withFeaturedImage
? templateOfPicture({
imageKey: "doc.featuredImage",
})
: undefined;
const titleFieldsTemplate = templateOfTitleAndFields(ctx, documentType);
const listTemplates = documentType.lists
?.map((list) => templateOfList(ctx, list, { listKey: `doc.list.${list.listName}`, nested: false }))
.filter(isDef) ?? [];
const specialTemplate = templateOfSpecialDocument(ctx, documentType);
const labeledListTemplates = templatesOfLabeledList(ctx, documentType);
ctx.addLiquidFile("partials", "breadcrumb.liquid", templateOfDocumentBreadcrumb(), {
skipIfExists: true,
});
const blocks = [
`{% render "partials/breadcrumb" doc: doc %}`,
featuredImageTemplate,
titleFieldsTemplate,
specialTemplate,
...listTemplates,
...labeledListTemplates,
childrenTemplate,
].filter(isDef);
return `{% layout "layouts/main-layout.liquid" %}
{% block %}
${blocks.join("\n\n")}
{% endblock %}`;
}
function templateOfTitleAndFields(ctx, documentType) {
const blocks1 = ["<h1>{{ doc.title }}</h1>"];
const fieldsTemplate = templateOfFields(ctx, documentType.fields, { parentKey: "doc.field" });
if (fieldsTemplate) {
blocks1.push(fieldsTemplate);
}
const textBlock = `<div class="TextWidth">
${indent(blocks1.join("\n"), 1, { skipFirst: true })}
</div>`;
const siblingsTemplate = documentType.documentKind === "regular" ? templateOfSiblingLinks(ctx) : undefined;
const blocks2 = [textBlock, siblingsTemplate].filter(isDef);
return `<div class="_bg2">
<div class="Container">
<div class="Page">
${indent(blocks2.join("\n"), 3, { skipFirst: true })}
</div>
</div>
</div>`;
}
function templateOfSpecialDocument(ctx, documentType) {
const { addLiquidFile } = ctx;
if (documentType.typeName === "search" || documentType.typeName === "searchPage") {
addLiquidFile("partials", "result-item.public.liquid", templateOfDocumentCard(ctx, "doc"));
return `<div class="Container">
<div class="Page">
{% out searchApp(class: "Container", template: "partials/result-item.public", by: 10) %}
</div>
</div>`;
}
if (documentType.typeName === "contact" || documentType.typeName === "contactPage") {
return `<div class="Container">
<div class="Page">
<div class="TextWidth Pt">
{% out contactForm %}
</div>
</div>
</div>`;
}
}
function templateOfDocumentChildren(ctx, parentType, parentIdKeyProvider) {
const routingBlocks = templateOfRoutingChildren(ctx, parentType, parentIdKeyProvider);
const regularBlocks = parentType.documentKind === "routing" && parentType.regularChildren
? templateOfRegularDocumentCards(ctx, parentType, parentIdKeyProvider, {
mode: "all",
})
: undefined;
return [routingBlocks, regularBlocks].filter(isDef).join("\n\n") || undefined;
}
function templateOfRoutingChildren(ctx, parentType, parentIdKeyProvider) {
const { siteSchema } = ctx;
const routingBlocks = (parentType.routingChildren ?? [])
.map((childName) => {
const child = getJtRoutingDocumentType(siteSchema, childName);
if (child.redirectTo) {
return templateOfDocumentChildren(ctx, child, parentIdKeyProvider.createForRoutingChild(childName));
}
return templateOfRoutingChild(ctx, child, parentIdKeyProvider);
})
.filter(isDef);
if (routingBlocks.length === 0)
return;
return `<div class="Container">
${indent(routingBlocks.join("\n\n"), 1, { skipFirst: true })}
</div>`;
}
function templateOfRoutingChild(ctx, child, parentIdKeyProvider) {
const { siteSchema } = ctx;
const { typeName, regularChildrenSorting } = child;
const variableName = `${typeName}Doc`;
const idKeyProvider = parentIdKeyProvider.createForRoutingChild(typeName);
const key = idKeyProvider.key;
if (!hasTemporalChildren(siteSchema, child) || regularChildrenSorting !== "publishDate desc") {
const buttonTemplate = `{% set ${variableName} = doc(${key}) %}
{% if ${variableName} %}
<a class="Button" href="{{ ${variableName}.url }}">{{ ${variableName}.title }}</a>
{% endif %}`;
return buttonTemplate;
}
const cardsTemplate = templateOfRegularDocumentCards(ctx, child, idKeyProvider, {
mode: "sampleOnly",
});
return `{% set ${variableName} = doc(${key}) %}
{% if ${variableName} %}
<div class="Pt">
<h2>
<a class="TextLink" href="{{ ${variableName}.url }}">{{ ${variableName}.title }}</a>
</h2>
${indent(cardsTemplate, 2, { skipFirst: true })}
</div>
{% endif %}`;
}
function templateOfRegularDocumentCards(ctx, parentType, parentIdKeyProvider, { mode }) {
const { siteSchema, addLiquidFile } = ctx;
const { typeName: parentTypeName } = parentType;
const childrenVariableName = `${parentTypeName}Children`;
const childVariableName = `${parentTypeName}Child`;
const key = parentIdKeyProvider.key;
if (mode === "sampleOnly") {
return `{% set ${childrenVariableName} = docs(${key}.children, limit: 4) %}
<div class="List">
{% for ${childVariableName} in ${childrenVariableName} %}
${indent(templateOfDocumentCard(ctx, childVariableName, { parentType }), 2, { skipFirst: true })}
{% endfor %}
</div>`;
}
if (!hasTemporalChildren(siteSchema, parentType)) {
return `{% set ${childrenVariableName} = docs(${key}.children) %}
<div class="Container List Pt Pb">
{% for ${childVariableName} in ${childrenVariableName} %}
${indent(templateOfDocumentCard(ctx, childVariableName, { parentType }), 2, { skipFirst: true })}
{% endfor %}
</div>`;
}
const cardTemplateName = `${camelToKebabCase(parentTypeName)}-card.public`;
addLiquidFile("partials", `${cardTemplateName}.liquid`, templateOfDocumentCard(ctx, "doc", { parentType }), { skipIfExists: true });
return `{% set ${childrenVariableName} = paginatedDocs(${key}.children, by: 10) %}
<div class="Container">
{% out infiniteLoading(class: "Page List", paginatedDocs: ${childrenVariableName}, template: "partials/${cardTemplateName}") %}
</div>`;
}
function templateOfFields(ctx, fields, { parentKey }) {
if (!fields || fields.length === 0)
return;
const fieldTemplates = fields.map((fieldOrQualifiedName) => templateOfField(ctx, fieldOrQualifiedName, parentKey));
return fieldTemplates.join("\n");
}
function templateOfField(ctx, fieldOrQualifiedName, parentKey) {
if (typeof fieldOrQualifiedName !== "string" && fieldOrQualifiedName.storedAs === "partField") {
return templateOfPartField(ctx, fieldOrQualifiedName, parentKey);
}
const { dataType, renderAs, name } = typeof fieldOrQualifiedName === "string"
? getPredefinedDataType(ctx, fieldOrQualifiedName)
: fieldOrQualifiedName.storedAs === "labeling"
? { dataType: "labeling", renderAs: undefined, name: fieldOrQualifiedName.name }
: fieldOrQualifiedName;
if (dataType === "labeling") {
return `{% if ${parentKey}.${name} %}
<div class="Field">
{% for tag in ${parentKey}.${name} %}
{% if tag.inRightLanguage %}
<a class="Label" href="{{ tag.url }}">{{ tag.title }}</a>
{% else %}
<span class="Label">{{ tag.title }}</span>
{% endif %}
{% endfor %}
</div>
{% endif %}`;
}
if (renderAs === "html") {
return `<div class="Field Text">{{ ${parentKey}.${name} | raw }}</div>`;
}
if (dataType === "date") {
return `<div class="Field">{{ ${parentKey}.${name} | formatDate: "short" }}</div>`;
}
if (dataType === "dateTime") {
return `<div class="Field">{{ ${parentKey}.${name} | formatDate: "long" }}</div>`;
}
if (dataType === "json") {
return `<div class="Field">{{ ${parentKey}.${name} | json }}</div>`;
}
if (dataType === "gallery") {
return `<div class="Field">
{% for media in ${parentKey}.${name} %}
{% set im = image(media, resize: "150x150") %}
<img
class="Field-img"
src="{{ im.url }}"
width="{{ im.width }}"
height="{{ im.height }}"
loading="lazy"
alt=""
{{ media | zoomable }}
>
{% endfor %}
</div>`;
}
if (dataType === "media") {
const mediaKey = `${parentKey}.${name}`;
return `{% set im = image(${mediaKey}, resize: "x250x") %}
<div class="Field">
<img
class="Field-img"
src="{{ im.url }}"
width="{{ im.width }}"
height="{{ im.height }}"
loading="lazy"
alt=""
{{ ${mediaKey} | zoomable }}
>
</div>`;
}
if (name === "title") {
return `<h2>{{ ${parentKey}.${name} }}</h2>`;
}
return `<div class="Field">{{ ${parentKey}.${name} }}</div>`;
}
function templateOfPartField(ctx, field, parentKey) {
const partKey = `${parentKey}.${field.name}`;
const partType = ctx.siteSchema.nodeTypes?.find((nt) => nt.typeName === field.partType && nt.kind === "part");
if (!partType?.fields || partType.fields.length === 0) {
return `{%- comment -%} Part field: ${field.name} (no fields) {%- endcomment -%}`;
}
const innerFields = templateOfFields(ctx, partType.fields, { parentKey: `${partKey}.field` });
if (!innerFields)
return "";
return `{% if ${partKey} %}
<div class="${camelToKebabCase(field.partType)}">
${innerFields}
</div>
{% endif %}`;
}
function templateOfPicture({ imageKey }) {
return `{% if ${imageKey} %}
{% set smallIm = image(${imageKey}, resize: "360x48") %}
{% set largeIm = image(${imageKey}, resize: "1200x160") %}
<div class="Container">
<picture class="Hero">
<source
srcset="{{ largeIm.url }}"
width="{{ largeIm.width }}"
height="{{ largeIm.height }}"
media="(min-width: 361px)"
>
<img
src="{{ smallIm.url }}"
width="{{ smallIm.width }}"
height="{{ smallIm.height }}"
loading="lazy"
alt=""
>
</picture>
</div>
{% endif %}`;
}
function templateOfList(ctx, list, { listKey, nested }) {
const { siteSchema, addLiquidFile, hasLiquidFile } = ctx;
if (list.parts.length === 0)
return;
const partTemplates = list.parts
.map((partName, index) => {
const partType = getJtPartType(siteSchema, partName);
const partTemplateName = `${camelToKebabCase(partType.typeName)}-part`;
if (!hasLiquidFile("partials", `${partTemplateName}.liquid`)) {
const partTemplate = templateOfPart(ctx, partType, "part");
addLiquidFile("partials", `${partTemplateName}.liquid`, partTemplate, {
skipIfExists: true,
});
}
const ifOrElsif = index === 0 ? "if" : "elsif";
return `{% ${ifOrElsif} part.type == "${partType.typeName}" %}
{% render "partials/${partTemplateName}.liquid" part: part %}`;
})
.filter(isDef);
partTemplates.push("{% endif %}");
if (nested) {
return `{% if ${listKey} %}
<div class="Indent">
{% for part in ${listKey} %}
${indent(partTemplates.join("\n"), 3, { skipFirst: true })}
{% endfor %}
</div>
{% endif %}`;
}
return `{% if ${listKey} %}
<div class="_bg2">
<div class="Container Pb">
{% for part in ${listKey} %}
${indent(partTemplates.join("\n"), 4, { skipFirst: true })}
{% endfor %}
</div>
</div>
{% endif %}`;
}
function templateOfPart(ctx, part, partKey) {
const templates = [
templateOfFields(ctx, part.fields, { parentKey: `${partKey}.field` }),
part.list
? templateOfList(ctx, part.list, {
listKey: `${partKey}.parts`,
nested: true,
})
: undefined,
].filter(isDef);
return `<section class="TextWidth">
${indent(templates.join("\n\n"), 1, { skipFirst: true })}
</section>`;
}
function templateOfSiblingLinks(ctx) {
const previousLabelTemplate = localizedLabelTemplate(ctx, {
en: "Previous",
fr: "Précédent",
});
const nextLabelTemplate = localizedLabelTemplate(ctx, {
en: "Next",
fr: "Suivant",
});
return `<div class="Row spaceBetween">
{% set previous = doc(doc.siblings.previous) %}
{% if previous %}
<a href="{{ previous.url }}" title="{{ previous.title }}">← ${previousLabelTemplate}</a>
{% else %}
<span></span>
{% endif %}
{% set next = doc(doc.siblings.next) %}
{% if next %}
<a href="{{ next.url }}" title="{{ next.title }}">${nextLabelTemplate} →</a>
{% endif %}
</div>`;
}