/** * Content Template * * Generates {Module}Content.tsx — the prop-driven body rendered inside the * container's "Details" tab. Always emitted (returns a string): * - Content-extending modules render the BlockNote content. * - Other modules render an AttributeElement layout of their fields and * relationships. */ import { pluralize, toCamelCase } from "../../transformers/name-transformer"; import { FrontendTemplateData } from "../../types/template-data.interface"; /** * Generate the content display component file content * * @param data - Frontend template data * @returns Generated file content (always a string) */ export function generateContentTemplate(data: FrontendTemplateData): string { const { names, extendsContent } = data; const camel = names.camelCase; // Content-extending modules render their BlockNote body, prop-driven. if (extendsContent) { return `"use client"; import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface"; import { BlockNoteEditorContainer, Card } from "@carlonicora/nextjs-jsonapi/components"; type ${names.pascalCase}ContentProps = { ${camel}: ${names.pascalCase}Interface }; export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}ContentProps) { return ( ); } `; } // Rich-text (BlockNote) fields each render as their own labelled section. const richFields = data.fields.filter((f) => f.isContentField); const attributeElements = generateAttributeElements(data); const hasAttributes = attributeElements.trim().length > 0; // When the module has one or more BlockNote fields, render a stacked layout of // labelled editors (read-only display), followed by an attribute grid if any // plain fields remain. if (richFields.length > 0) { const richSections = richFields .map( (field) => `

{t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`)}

`, ) .join("\n"); const attributesBlock = hasAttributes ? `\n

{t(\`features.${names.camelCase.toLowerCase()}.sections.details\`)}

${attributeElements}
` : ""; const importLine = hasAttributes ? `import { AttributeElement, BlockNoteEditorContainer, Card } from "@carlonicora/nextjs-jsonapi/components";` : `import { BlockNoteEditorContainer, Card } from "@carlonicora/nextjs-jsonapi/components";`; return `"use client"; import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface"; ${importLine} import { useTranslations } from "next-intl"; type ${names.pascalCase}ContentProps = { ${camel}: ${names.pascalCase}Interface }; export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}ContentProps) { const t = useTranslations(); return (
${richSections}${attributesBlock}
); } `; } // Non-content modules render an AttributeElement layout of their fields. return `"use client"; import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface"; import { AttributeElement } from "@carlonicora/nextjs-jsonapi/components"; import { useTranslations } from "next-intl"; type ${names.pascalCase}ContentProps = { ${camel}: ${names.pascalCase}Interface }; export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}ContentProps) { const t = useTranslations(); return (

{t(\`features.${names.camelCase.toLowerCase()}.sections.details\`)}

${attributeElements}
); } `; } /** * Generate attribute elements for display fields and relationships. * * Ported from the (removed) details template, but accessing the entity via the * `camelCase` prop instead of context. */ function generateAttributeElements(data: FrontendTemplateData): string { const { names, fields, relationships } = data; const camel = names.camelCase; const elements: string[] = []; // Add custom fields (excluding name, tldr, abstract, content and rich-text // BlockNote fields, which are rendered as their own sections elsewhere) const displayableFields = fields.filter( (f) => !f.isContentField && !["name", "tldr", "abstract", "content", "id"].includes(f.name), ); displayableFields.forEach((field) => { if (field.type === "string") { elements.push(` `); } else if (field.type === "boolean") { elements.push(` `); } else if (field.type === "number") { elements.push(` `); } }); // Add relationship displays (basic - can be enhanced manually) relationships .filter((rel) => rel.variant !== "Author") // Skip author, usually shown in title .forEach((rel) => { const displayProp = rel.targetHasName ? "name" : "id"; if (rel.single) { const propName = toCamelCase(rel.alias || rel.variant || rel.name); elements.push(` {${camel}.${propName} && ( )}`); } else { const effectiveMany = rel.alias || rel.name; const propName = pluralize(toCamelCase(effectiveMany)); elements.push(` {${camel}.${propName} && ${camel}.${propName}.length > 0 && ( item.${displayProp}).join(", ")} /> )}`); } }); return elements.join("\n"); }