diff --git a/lib/graphql/Fragments/Metadata.graphql b/lib/graphql/Fragments/Metadata.graphql index 2b5b891f1..b649e213a 100644 --- a/lib/graphql/Fragments/Metadata.graphql +++ b/lib/graphql/Fragments/Metadata.graphql @@ -1,30 +1,6 @@ -fragment MetadataImageConnection on SeoMetadata { - imageConnection { - edges { - node { - dimension { - height - width - } - url(transform: { width: "1200" }) - } - } - } -} - fragment Metadata on SeoMetadata { noindex description title - imageConnection { - edges { - node { - dimension { - height - width - } - url(transform: { width: "1200" }) - } - } - } + seo_image } diff --git a/lib/graphql/Query/LoyaltyPage/Metadata.graphql b/lib/graphql/Query/LoyaltyPage/Metadata.graphql index c22d46bd2..b084d8738 100644 --- a/lib/graphql/Query/LoyaltyPage/Metadata.graphql +++ b/lib/graphql/Query/LoyaltyPage/Metadata.graphql @@ -15,7 +15,7 @@ query GetLoyaltyPageMetadata($locale: String!, $uid: String!) { preamble hero_image blocks { - ... on ContentPageBlocksContent { + ... on LoyaltyPageBlocksContent { __typename content { content { diff --git a/server/routers/contentstack/metadata/output.ts b/server/routers/contentstack/metadata/output.ts index 95d497357..9474dd3fe 100644 --- a/server/routers/contentstack/metadata/output.ts +++ b/server/routers/contentstack/metadata/output.ts @@ -7,39 +7,59 @@ import type { Metadata } from "next" import { RTETypeEnum } from "@/types/rte/enums" +const metaDataJsonSchema = z.object({ + children: z.array( + z.object({ + type: z.nativeEnum(RTETypeEnum), + children: z.array( + z.object({ + text: z.string().optional(), + }) + ), + }) + ), +}) + +const metaDataBlocksSchema = z + .array( + z.object({ + content: z + .object({ + content: z + .object({ + json: metaDataJsonSchema, + }) + .optional() + .nullable(), + }) + .optional() + .nullable(), + }) + ) + .optional() + .nullable() + export const rawMetadataSchema = z.object({ - web: z.object({ - seo_metadata: z - .object({ - title: z.string().optional().nullable(), - description: z.string().optional().nullable(), - noindex: z.boolean().optional().nullable(), - imageConnection: z - .object({ - edges: z.array( - z.object({ - node: z.object({ - url: z.string(), - dimension: z.object({ - width: z.number(), - height: z.number(), - }), - }), - }) - ), - }) - .optional() - .nullable(), - }) - .optional() - .nullable(), - breadcrumbs: z - .object({ - title: z.string().optional().nullable(), - }) - .optional() - .nullable(), - }), + web: z + .object({ + seo_metadata: z + .object({ + title: z.string().optional().nullable(), + description: z.string().optional().nullable(), + noindex: z.boolean().optional().nullable(), + seo_image: tempImageVaultAssetSchema.nullable(), + }) + .optional() + .nullable(), + breadcrumbs: z + .object({ + title: z.string().optional().nullable(), + }) + .optional() + .nullable(), + }) + .optional() + .nullable(), heading: z.string().optional().nullable(), preamble: z.string().optional().nullable(), header: z @@ -49,49 +69,26 @@ export const rawMetadataSchema = z.object({ }) .optional() .nullable(), - hero_image: tempImageVaultAssetSchema, - blocks: z - .array( - z.object({ - content: z - .object({ - content: z - .object({ - json: z.object({ - children: z.array( - z.object({ - type: z.nativeEnum(RTETypeEnum), - children: z.array( - z.object({ - text: z.string().optional().nullable(), - }) - ), - }) - ), - }), - }) - .optional() - .nullable(), - }) - .optional() - .nullable(), - }) - ) - .optional() - .nullable(), + hero_image: tempImageVaultAssetSchema.nullable(), + blocks: metaDataBlocksSchema, }) export const metadataSchema = rawMetadataSchema.transform((data) => { + const noIndex = !!data.web?.seo_metadata?.noindex + const metadata: Metadata = { - robots: { - index: !data.web.seo_metadata?.noindex, - follow: true, - }, title: getTitle(data), description: getDescription(data), openGraph: { images: getImages(data), }, } + + if (noIndex) { + metadata.robots = { + index: false, + follow: true, + } + } return metadata }) diff --git a/server/routers/contentstack/metadata/utils.ts b/server/routers/contentstack/metadata/utils.ts index eeadf11e1..3cd9abdd0 100644 --- a/server/routers/contentstack/metadata/utils.ts +++ b/server/routers/contentstack/metadata/utils.ts @@ -3,8 +3,63 @@ import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metada export const affix = "metadata" +/** + * Truncates the given text "intelligently" based on the last period found near the max length. + * + * - If a period exists within the extended range (`maxLength` to `maxLength + maxExtension`), + * the function truncates after the closest period to `maxLength`. + * - If no period is found in the range, it truncates the text after the last period found in the max length of the text. + * - If no periods exist at all, it truncates at `maxLength` and appends ellipsis (`...`). + * + * @param {string} text - The input text to be truncated. + * @param {number} [maxLength=150] - The desired maximum length of the truncated text. + * @param {number} [minLength=120] - The minimum allowable length for the truncated text. + * @param {number} [maxExtension=10] - The maximum number of characters to extend beyond `maxLength` to find a period. + * @returns {string} - The truncated text. + */ +function truncateTextAfterLastPeriod( + text: string, + maxLength: number = 150, + minLength: number = 120, + maxExtension: number = 10 +): string { + if (text.length <= maxLength) { + return text + } + + // Define the extended range + const extendedEnd = Math.min(text.length, maxLength + maxExtension) + const extendedText = text.slice(0, extendedEnd) + + // Find all periods within the extended range and filter after minLength to get valid periods + const periodsInRange = [...extendedText.matchAll(/\./g)].map( + ({ index }) => index + ) + const validPeriods = periodsInRange.filter((index) => index + 1 >= minLength) + + if (validPeriods.length > 0) { + // Find the period closest to maxLength + const closestPeriod = validPeriods.reduce((closest, index) => + Math.abs(index + 1 - maxLength) < Math.abs(closest + 1 - maxLength) + ? index + : closest + ) + return extendedText.slice(0, closestPeriod + 1) + } + + // Fallback: If no period is found within the valid range, look for the last period in the truncated text + const maxLengthText = text.slice(0, maxLength) + const lastPeriodIndex = maxLengthText.lastIndexOf(".") + if (lastPeriodIndex !== -1) { + return text.slice(0, lastPeriodIndex + 1) + } + + // Final fallback: Return maxLength text including ellipsis + return `${maxLengthText}...` +} + export function getTitle(data: RawMetadataSchema) { - const metadata = data.web.seo_metadata + const metadata = data.web?.seo_metadata if (metadata?.title) { return metadata.title } @@ -21,15 +76,15 @@ export function getTitle(data: RawMetadataSchema) { } export function getDescription(data: RawMetadataSchema) { - const metadata = data.web.seo_metadata + const metadata = data.web?.seo_metadata if (metadata?.description) { return metadata.description } if (data.preamble) { - return data.preamble + return truncateTextAfterLastPeriod(data.preamble) } if (data.header?.preamble) { - return data.header.preamble + return truncateTextAfterLastPeriod(data.header.preamble) } if (data.blocks?.length) { const jsonData = data.blocks[0].content?.content?.json @@ -40,24 +95,26 @@ export function getDescription(data: RawMetadataSchema) { if (firstParagraph?.children?.length) { return firstParagraph.children[0].text + ? truncateTextAfterLastPeriod(firstParagraph.children[0].text) + : "" } } return "" } export function getImages(data: RawMetadataSchema) { - const metadataImages = data.web.seo_metadata?.imageConnection?.edges + const metadataImage = data.web?.seo_metadata?.seo_image const heroImage = data.hero_image - if (metadataImages?.length) { - return metadataImages.map((edge) => { - const { width, height } = edge.node.dimension - return { - url: edge.node.url, - width: 1200, - height: Math.round((1200 * height) / width), - } - }) + // Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15) + if (metadataImage) { + return [ + { + url: metadataImage.url, + width: metadataImage.dimensions.width, + height: metadataImage.dimensions.height, + }, + ] } if (heroImage) { return [ diff --git a/utils/generateMetadata.ts b/utils/generateMetadata.ts index 6b50d26a0..39d7fae1f 100644 --- a/utils/generateMetadata.ts +++ b/utils/generateMetadata.ts @@ -1,7 +1,5 @@ import { serverClient } from "@/lib/trpc/server" export async function generateMetadata() { - const data = await serverClient().contentstack.metadata.get() - - return data + return await serverClient().contentstack.metadata.get() }