@paroicms/site-generator-plugin
Version:
ParoiCMS Site Generator Plugin
348 lines (347 loc) • 13 kB
JavaScript
import { camelToKebabCase } from "../lib/utils.js";
import { templateOfDocumentBreadcrumb } from "./common-template-creator.js";
import { createIdKeyProvider } from "./id-key-provider.js";
import { getJtPartType, getJtRoutingDocumentType, hasTemporalChildren, } from "./jt-site-schema-helpers.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 })) ?? [];
const specialTemplate = templateOfSpecialDocument(ctx, documentType);
ctx.addLiquidFile("partials", "breadcrumb.liquid", templateOfDocumentBreadcrumb(), {
skipIfExists: true,
});
const blocks = [
`{% render "partials/breadcrumb" doc: doc site: site %}`,
featuredImageTemplate,
titleFieldsTemplate,
childrenTemplate,
specialTemplate,
...listTemplates,
].filter(Boolean);
return `{% layout "layouts/main-layout.liquid" doc: doc site: site %}
{% 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);
const blocks2 = [textBlock, siblingsTemplate].filter(Boolean);
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", templateOfDocumentTile("doc"));
return `<div
class="Container"
data-effect="paSearchApp"
data-template="result-item"
data-limit="10"></div>`;
}
if (documentType.typeName === "contact" || documentType.typeName === "contactPage") {
return `<div class="Container">
<div class="TextWidth Pt">
<div
data-effect="paContactForm"
data-home-url="{{ site.home.url }}"></div>
</div>
</div>`;
}
}
function templateOfDocumentChildren(ctx, parentType, parentIdKeyProvider) {
const routingBlocks = templateOfRoutingChildren(ctx, parentType, parentIdKeyProvider);
const regularBlocks = parentType.documentKind === "routing" &&
parentType.regularChildren &&
templateOfRegularDocumentTiles(ctx, parentType, parentIdKeyProvider, {
mode: "all",
});
return [routingBlocks, regularBlocks].filter(Boolean).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(Boolean);
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 idKey = idKeyProvider.idKey;
if (!hasTemporalChildren(siteSchema, child) || regularChildrenSorting !== "publishDate desc") {
const buttonTemplate = `{% getDoc ${variableName} id: ${idKey} %}
<a class="Button" href="{{ ${variableName}.url }}">{{ ${variableName}.title }}</a>`;
return buttonTemplate;
}
const tilesTemplate = templateOfRegularDocumentTiles(ctx, child, idKeyProvider, {
mode: "sampleOnly",
});
return `{% getDoc ${variableName} id: ${idKey} %}
<div class="Pt">
<h2>
<a class="TextLink" href="{{ ${variableName}.url }}">{{ ${variableName}.title }}</a>
</h2>
${indent(tilesTemplate, 1, { skipFirst: true })}
</div>`;
}
function templateOfRegularDocumentTiles(ctx, parentType, parentIdKeyProvider, { mode }) {
const { siteSchema, addLiquidFile } = ctx;
const { typeName: parentTypeName } = parentType;
const childrenVariableName = `${parentTypeName}Children`;
const childVariableName = `${parentTypeName}Child`;
const idKey = parentIdKeyProvider.idKey;
if (mode === "sampleOnly") {
return `{% getDocs ${childrenVariableName} parentId: ${idKey} limit: 4 %}
<div class="List">
{% for ${childVariableName} in ${childrenVariableName} %}
${indent(templateOfDocumentTile(childVariableName), 2, { skipFirst: true })}
{% endfor %}
</div>`;
}
if (!hasTemporalChildren(siteSchema, parentType)) {
return `{% getDocs ${childrenVariableName} parentId: ${idKey} %}
<div class="Container List Pt Pb">
{% for ${childVariableName} in ${childrenVariableName} %}
${indent(templateOfDocumentTile(childVariableName), 2, { skipFirst: true })}
{% endfor %}
</div>`;
}
const tileTemplateName = `${camelToKebabCase(parentTypeName)}-tile`;
addLiquidFile("partials", `${tileTemplateName}.public.liquid`, templateOfDocumentTile("doc"));
return `{% getPaginatedDocs ${childrenVariableName} parentId: ${idKey} pageSize: 10 %}
<div class="Container">
<div
class="Page List"
data-effect="paInfiniteLoading"
data-parent-id="{{ ${idKey} }}"
data-start="{{ ${childrenVariableName}.pageSize }}"
data-limit="{{ ${childrenVariableName}.pageSize }}"
data-total="{{ ${childrenVariableName}.total }}"
data-template="${tileTemplateName}">
{% for ${childrenVariableName} in ${childrenVariableName}.items %}
{% render "partials/${tileTemplateName}.public.liquid" doc: ${childrenVariableName} %}
{% endfor %}
</div>
</div>`;
}
function templateOfFields(ctx, fields, { parentKey }) {
if (!fields || fields.length === 0)
return;
const fieldTemplates = fields.map((fieldOrName) => templateOfField(ctx, fieldOrName, parentKey));
return fieldTemplates.join("\n");
}
function templateOfField(ctx, fieldOrName, parentKey) {
const fieldName = typeof fieldOrName === "string" ? fieldOrName : fieldOrName.name;
const { dataType, renderAs } = typeof fieldOrName === "string"
? getPredefinedDataType(ctx, fieldName)
: fieldOrName.storedAs === "labeling"
? { dataType: "labeling" }
: fieldOrName;
if (dataType === "labeling") {
return `{% if ${parentKey}.${fieldName} %}
<div class="Field">
{% for tag in ${parentKey}.${fieldName} %}
{% if tag.inRightLanguage %}
<a href="{{ tag.url }}">{{ tag.title }}</a>
{% else %}
<span>{{ tag.title }}</span>
{% endif %}
{% endfor %}
</div>
{% endif %}`;
}
if (renderAs === "html") {
return `<div class="Field Text">{{ ${parentKey}.${fieldName} | raw }}</div>`;
}
if (dataType === "date") {
return `<div class="Field">{{ ${parentKey}.${fieldName} | formatDate: "short" }}</div>`;
}
if (dataType === "dateTime") {
return `<div class="Field">{{ ${parentKey}.${fieldName} | formatDate: "long" }}</div>`;
}
if (dataType === "json") {
return `<div class="Field">{{ ${parentKey}.${fieldName} | json }}</div>`;
}
if (dataType === "gallery") {
return `<div class="Field">
{% for media in ${parentKey}.${fieldName} %}
{% useImage 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}.${fieldName}`;
return `{% useImage 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 (fieldName === "title") {
return `<h2>{{ ${parentKey}.${fieldName} }}</h2>`;
}
return `<div class="Field">{{ ${parentKey}.${fieldName} }}</div>`;
}
function templateOfPicture({ imageKey }) {
return `{% if ${imageKey} %}
{% useImage smallIm image: ${imageKey} resize: "360x48" %}
{% useImage 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(Boolean);
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(Boolean);
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">
{% if doc.siblings.previous %}
<a href="{{ doc.siblings.previous.url }}" title="{{ doc.siblings.previous.title }}">← ${previousLabelTemplate}</a>
{% else %}
<span></span>
{% endif %}
{% if doc.siblings.next %}
<a href="{{ doc.siblings.next.url }}" title="{{ doc.siblings.next.title }}">${nextLabelTemplate} →</a>
{% endif %}
</div>`;
}
function templateOfDocumentTile(docVariableName) {
return `<a href="{{ ${docVariableName}.url }}">
<article>
{% if ${docVariableName}.defaultImage %}
<div>
{% useImage im image: ${docVariableName}.defaultImage resize: "120x120" %}
<img
src="{{ im.url }}"
width="{{ im.width }}"
height="{{ im.height }}"
loading="lazy"
alt="">
</div>
{% endif %}
<div>
<h3>{{ ${docVariableName}.title }}</h3>
<p>{{ ${docVariableName}.excerpt | makeExcerpt: 40 }}</p>
</div>
</article>
</a>`;
}