Skip to content

Commit

Permalink
Field: handle field types in 'meta' data
Browse files Browse the repository at this point in the history
  • Loading branch information
Gnito committed Feb 7, 2023
1 parent 6cf6aae commit d2ebb98
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 36 deletions.
86 changes: 50 additions & 36 deletions src/containers/PageBuilder/Field/Field.helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ export const exposeLinkProps = data => {
return cleanUrl ? { children: linkText, href: cleanUrl } : {};
};

const getValidSanitizedImage = image => {
const { id, type, attributes } = image || {};
const variantEntries = Object.entries(attributes?.variants || {});
const variants = variantEntries.reduce((validVariants, entry) => {
const [key, value] = entry;
const { url, width, height } = value || {};

const isValid = typeof width === 'number' && typeof height === 'number';
return isValid
? {
...validVariants,
[key]: { url: sanitizeUrl(url), width, height },
}
: validVariants;
}, {});

const isValidImage = Object.keys(variants).length > 0;
const sanitizedImage = { id, type, attributes: { ...attributes, variants } };

return isValidImage ? sanitizedImage : null;
};

/**
* Exposes "alt" and image props.
* The "image" contains imageAsset entity, which has been denormalized at this point:
Expand Down Expand Up @@ -70,30 +92,15 @@ export const exposeImageProps = data => {
// Note: data includes also "aspectRatio" key (and "fieldType"),
// but image refs can rely on actual image variants
const { alt, image } = data;
const { id, type, attributes } = image || {};
const { type } = image || {};

if (type !== 'imageAsset') {
return {};
}

const variantEntries = Object.entries(image?.attributes?.variants || {});
const variants = variantEntries.reduce((validVariants, entry) => {
const [key, value] = entry;
const { url, width, height } = value || {};

const isValid = typeof width === 'number' && typeof height === 'number';
return isValid
? {
...validVariants,
[key]: { url: sanitizeUrl(url), width, height },
}
: validVariants;
}, {});

const alternativeText = typeof alt === 'string' ? alt : '🖼️';
const isValidImage = Object.keys(variants).length > 0;
const sanitizedImage = { id, type, attributes: { ...attributes, variants } };
return isValidImage ? { alt: alternativeText, image: sanitizedImage } : {};
const sanitizedImage = getValidSanitizedImage(image);
return sanitizedImage ? { alt: alternativeText, image: sanitizedImage } : {};
};

/**
Expand All @@ -118,7 +125,7 @@ const exposeColorValue = color => {
*/
export const exposeCustomAppearanceProps = data => {
const { backgroundImage, backgroundColor, textColor, alt } = data;
const { id, type, attributes } = backgroundImage || {};
const { type } = backgroundImage || {};

if (!!type && type !== 'imageAsset') {
return {};
Expand All @@ -132,23 +139,8 @@ export const exposeCustomAppearanceProps = data => {
const isValidTextColor = ['light', 'dark'].includes(textColor);
const textColorMaybe = isValidTextColor ? { textColor } : {};

const variantEntries = Object.entries(backgroundImage?.attributes?.variants || {});
const variants = variantEntries.reduce((validVariants, entry) => {
const [key, value] = entry;
const { url, width, height } = value || {};

const isValid = typeof width === 'number' && typeof height === 'number';
return isValid
? {
...validVariants,
[key]: { url: sanitizeUrl(url), width, height },
}
: validVariants;
}, {});

const isValidImage = Object.keys(variants).length > 0;
const sanitizedImage = { id, type, attributes: { ...attributes, variants } };
const backgroundImageMaybe = isValidImage ? { backgroundImage: sanitizedImage, alt } : {};
const sanitizedImage = getValidSanitizedImage(backgroundImage);
const backgroundImageMaybe = sanitizedImage ? { backgroundImage: sanitizedImage, alt } : {};

return {
...backgroundImageMaybe,
Expand Down Expand Up @@ -184,3 +176,25 @@ export const exposeYoutubeProps = data => {
}
: {};
};

export const exposeOpenGraphData = data => {
const { title, description, image } = data || {};
const { type } = image || {};

if (!!type && type !== 'imageAsset') {
return {};
}

const isString = content => typeof content === 'string' && content.length > 0;
const sanitizedImage = getValidSanitizedImage(image);
const image1200 = sanitizedImage?.attributes?.variants?.social1200;
const image600 = sanitizedImage?.attributes?.variants?.social600;

return {
title: isString(title) ? title : null,
description: isString(description) ? description : null,
// Open Graph can handle multiple images, so we return arrays for the sake of consistency
images1200: image1200 ? [image1200] : null,
images600: image600 ? [image600] : null,
};
};
61 changes: 61 additions & 0 deletions src/containers/PageBuilder/Field/Field.helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
exposeImageProps,
exposeCustomAppearanceProps,
exposeYoutubeProps,
exposeOpenGraphData,
} from './Field.helpers';

describe('Field helpers', () => {
Expand Down Expand Up @@ -317,4 +318,64 @@ describe('Field helpers', () => {
expect(exposeYoutubeProps({ youtubeVideoId: '9RQli>kX4vvw' })).toEqual({});
});
});

describe('exposeOpenGraphData(data)', () => {
it('should return title, description, images1200, and images600 props ', () => {
const title = 'Title';
const description = 'Description';
const imageVariant1200 = {
url: `https://picsum.photos/1200/630`,
width: 1200,
height: 630,
};
const imageVariant600 = {
url: `https://picsum.photos/600/315`,
width: 600,
height: 315,
};

const image = {
id: 'image',
type: 'imageAsset',
attributes: {
variants: {
social1200: imageVariant1200,
social600: imageVariant600,
},
},
};

const data = { title, description, image };
expect(exposeOpenGraphData(data)).toEqual({
title,
description,
images1200: [imageVariant1200],
images600: [imageVariant600],
});
expect(exposeOpenGraphData({ title })).toEqual({
title,
description: null,
images1200: null,
images600: null,
});
expect(exposeOpenGraphData({ description })).toEqual({
title: null,
description,
images1200: null,
images600: null,
});
expect(exposeOpenGraphData()).toEqual({
title: null,
description: null,
images1200: null,
images600: null,
});
expect(exposeOpenGraphData({ unKnownKey: null })).toEqual({
title: null,
description: null,
images1200: null,
images600: null,
});
});
});
});
9 changes: 9 additions & 0 deletions src/containers/PageBuilder/Field/Field.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
exposeCustomAppearanceProps,
exposeImageProps,
exposeYoutubeProps,
exposeOpenGraphData,
} from './Field.helpers';

const TEXT_CONTENT = [
Expand All @@ -35,6 +36,8 @@ const TEXT_CONTENT = [
'heading6',
'paragraph',
'markdown',
'metaTitle',
'metaDescription',
];

////////////////////////
Expand Down Expand Up @@ -97,6 +100,12 @@ const defaultFieldComponents = {
},
},
},

// Page's metadata goes to <head> and it is not currently rendered as a separate component
// Instead, valid data is passed to <Page>, which then renders it using react-helmet-async
metaTitle: { component: null, pickValidProps: exposeContentString },
metaDescription: { component: null, pickValidProps: exposeContentString },
openGraphData: { component: null, pickValidProps: exposeOpenGraphData },
};

//////////////////
Expand Down

0 comments on commit d2ebb98

Please sign in to comment.