UNPKG

@paroicms/site-generator-plugin

Version:

ParoiCMS Site Generator Plugin

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