feat(SW-200): Replaced Contentstack image with Imagevault image for metadata
This commit is contained in:
@@ -1,30 +1,6 @@
|
|||||||
fragment MetadataImageConnection on SeoMetadata {
|
|
||||||
imageConnection {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
dimension {
|
|
||||||
height
|
|
||||||
width
|
|
||||||
}
|
|
||||||
url(transform: { width: "1200" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment Metadata on SeoMetadata {
|
fragment Metadata on SeoMetadata {
|
||||||
noindex
|
noindex
|
||||||
description
|
description
|
||||||
title
|
title
|
||||||
imageConnection {
|
seo_image
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
dimension {
|
|
||||||
height
|
|
||||||
width
|
|
||||||
}
|
|
||||||
url(transform: { width: "1200" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ query GetLoyaltyPageMetadata($locale: String!, $uid: String!) {
|
|||||||
preamble
|
preamble
|
||||||
hero_image
|
hero_image
|
||||||
blocks {
|
blocks {
|
||||||
... on ContentPageBlocksContent {
|
... on LoyaltyPageBlocksContent {
|
||||||
__typename
|
__typename
|
||||||
content {
|
content {
|
||||||
content {
|
content {
|
||||||
|
|||||||
@@ -7,39 +7,59 @@ import type { Metadata } from "next"
|
|||||||
|
|
||||||
import { RTETypeEnum } from "@/types/rte/enums"
|
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({
|
export const rawMetadataSchema = z.object({
|
||||||
web: z.object({
|
web: z
|
||||||
seo_metadata: z
|
.object({
|
||||||
.object({
|
seo_metadata: z
|
||||||
title: z.string().optional().nullable(),
|
.object({
|
||||||
description: z.string().optional().nullable(),
|
title: z.string().optional().nullable(),
|
||||||
noindex: z.boolean().optional().nullable(),
|
description: z.string().optional().nullable(),
|
||||||
imageConnection: z
|
noindex: z.boolean().optional().nullable(),
|
||||||
.object({
|
seo_image: tempImageVaultAssetSchema.nullable(),
|
||||||
edges: z.array(
|
})
|
||||||
z.object({
|
.optional()
|
||||||
node: z.object({
|
.nullable(),
|
||||||
url: z.string(),
|
breadcrumbs: z
|
||||||
dimension: z.object({
|
.object({
|
||||||
width: z.number(),
|
title: z.string().optional().nullable(),
|
||||||
height: z.number(),
|
})
|
||||||
}),
|
.optional()
|
||||||
}),
|
.nullable(),
|
||||||
})
|
})
|
||||||
),
|
.optional()
|
||||||
})
|
.nullable(),
|
||||||
.optional()
|
|
||||||
.nullable(),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.nullable(),
|
|
||||||
breadcrumbs: z
|
|
||||||
.object({
|
|
||||||
title: z.string().optional().nullable(),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.nullable(),
|
|
||||||
}),
|
|
||||||
heading: z.string().optional().nullable(),
|
heading: z.string().optional().nullable(),
|
||||||
preamble: z.string().optional().nullable(),
|
preamble: z.string().optional().nullable(),
|
||||||
header: z
|
header: z
|
||||||
@@ -49,49 +69,26 @@ export const rawMetadataSchema = z.object({
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
hero_image: tempImageVaultAssetSchema,
|
hero_image: tempImageVaultAssetSchema.nullable(),
|
||||||
blocks: z
|
blocks: metaDataBlocksSchema,
|
||||||
.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(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const metadataSchema = rawMetadataSchema.transform((data) => {
|
export const metadataSchema = rawMetadataSchema.transform((data) => {
|
||||||
|
const noIndex = !!data.web?.seo_metadata?.noindex
|
||||||
|
|
||||||
const metadata: Metadata = {
|
const metadata: Metadata = {
|
||||||
robots: {
|
|
||||||
index: !data.web.seo_metadata?.noindex,
|
|
||||||
follow: true,
|
|
||||||
},
|
|
||||||
title: getTitle(data),
|
title: getTitle(data),
|
||||||
description: getDescription(data),
|
description: getDescription(data),
|
||||||
openGraph: {
|
openGraph: {
|
||||||
images: getImages(data),
|
images: getImages(data),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (noIndex) {
|
||||||
|
metadata.robots = {
|
||||||
|
index: false,
|
||||||
|
follow: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
return metadata
|
return metadata
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,8 +3,63 @@ import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metada
|
|||||||
|
|
||||||
export const affix = "metadata"
|
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) {
|
export function getTitle(data: RawMetadataSchema) {
|
||||||
const metadata = data.web.seo_metadata
|
const metadata = data.web?.seo_metadata
|
||||||
if (metadata?.title) {
|
if (metadata?.title) {
|
||||||
return metadata.title
|
return metadata.title
|
||||||
}
|
}
|
||||||
@@ -21,15 +76,15 @@ export function getTitle(data: RawMetadataSchema) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getDescription(data: RawMetadataSchema) {
|
export function getDescription(data: RawMetadataSchema) {
|
||||||
const metadata = data.web.seo_metadata
|
const metadata = data.web?.seo_metadata
|
||||||
if (metadata?.description) {
|
if (metadata?.description) {
|
||||||
return metadata.description
|
return metadata.description
|
||||||
}
|
}
|
||||||
if (data.preamble) {
|
if (data.preamble) {
|
||||||
return data.preamble
|
return truncateTextAfterLastPeriod(data.preamble)
|
||||||
}
|
}
|
||||||
if (data.header?.preamble) {
|
if (data.header?.preamble) {
|
||||||
return data.header.preamble
|
return truncateTextAfterLastPeriod(data.header.preamble)
|
||||||
}
|
}
|
||||||
if (data.blocks?.length) {
|
if (data.blocks?.length) {
|
||||||
const jsonData = data.blocks[0].content?.content?.json
|
const jsonData = data.blocks[0].content?.content?.json
|
||||||
@@ -40,24 +95,26 @@ export function getDescription(data: RawMetadataSchema) {
|
|||||||
|
|
||||||
if (firstParagraph?.children?.length) {
|
if (firstParagraph?.children?.length) {
|
||||||
return firstParagraph.children[0].text
|
return firstParagraph.children[0].text
|
||||||
|
? truncateTextAfterLastPeriod(firstParagraph.children[0].text)
|
||||||
|
: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getImages(data: RawMetadataSchema) {
|
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
|
const heroImage = data.hero_image
|
||||||
|
|
||||||
if (metadataImages?.length) {
|
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
|
||||||
return metadataImages.map((edge) => {
|
if (metadataImage) {
|
||||||
const { width, height } = edge.node.dimension
|
return [
|
||||||
return {
|
{
|
||||||
url: edge.node.url,
|
url: metadataImage.url,
|
||||||
width: 1200,
|
width: metadataImage.dimensions.width,
|
||||||
height: Math.round((1200 * height) / width),
|
height: metadataImage.dimensions.height,
|
||||||
}
|
},
|
||||||
})
|
]
|
||||||
}
|
}
|
||||||
if (heroImage) {
|
if (heroImage) {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
export async function generateMetadata() {
|
export async function generateMetadata() {
|
||||||
const data = await serverClient().contentstack.metadata.get()
|
return await serverClient().contentstack.metadata.get()
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user