UNPKG

@paroicms/site-generator-plugin

Version:

ParoiCMS Site Generator Plugin

348 lines (347 loc) 13.4 kB
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>`; }