Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve the image component & enable figcaption #223

Merged
merged 2 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/pdc-frontend/src/app/[locale]/[...not-found]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const NotFoundPage = async ({ params: { locale } }: { params: { locale: string }
]}
/>
<PageTitle>{data?.notFoundPage?.data?.attributes?.title}</PageTitle>
<Markdown strapiBackendURL={process.env.STRAPI_PUBLIC_URL}>{data?.notFoundPage?.data?.attributes?.body}</Markdown>
<Markdown>{data?.notFoundPage?.data?.attributes?.body}</Markdown>
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion apps/pdc-frontend/src/app/[locale]/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const NotFoundPage = async () => {
]}
/>
<PageTitle>{data?.notFoundPage?.data?.attributes?.title}</PageTitle>
<Markdown strapiBackendURL={process.env.STRAPI_PUBLIC_URL}>{data?.notFoundPage?.data?.attributes?.body}</Markdown>
<Markdown>{data?.notFoundPage?.data?.attributes?.body}</Markdown>
</div>
);
};
Expand Down
47 changes: 16 additions & 31 deletions apps/pdc-frontend/src/app/[locale]/products/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Metadata } from 'next';
import { cookies, draftMode } from 'next/headers';
import Image from 'next/image';
import { notFound } from 'next/navigation';
import React from 'react';
import { useTranslation } from '@/app/i18n';
Expand All @@ -22,11 +21,13 @@ import {
import { BottomBar, BottomBarItem } from '@/components/BottomBar';
import { Breadcrumbs } from '@/components/Breadcrumb';
import { FAQSection } from '@/components/FAQSection';
import { Img } from '@/components/Img';
import { Markdown } from '@/components/Markdown';
import { PreviewAlert } from '@/components/PreviewAlert';
import { ReactionLink } from '@/components/ReactionLink';
import { ScrollToTopButton } from '@/components/ScrollToTopButton';
import { GET_PRODUCT_BY_SLUG_FETCH } from '@/query';
import { buildImgURL } from '@/util/buildImgURL';
import { createStrapiURL } from '@/util/createStrapiURL';
import { fetchData } from '@/util/fetchData';

Expand Down Expand Up @@ -135,15 +136,15 @@ const Product = async ({ params: { locale, slug }, searchParams }: ProductProps)
const { product } = await getAllProducts(locale, slug);

const priceData = product?.attributes.price && product?.attributes.price?.data?.attributes.price;
const strapiImageURL = process.env.STRAPI_PUBLIC_URL;

const { t } = await useTranslation(locale, 'common');
const Sections = () =>
product?.attributes && product?.attributes.sections.length > 0
? product?.attributes.sections.map((component: any, index: number) => {
switch (component.__typename) {
case 'ComponentComponentsBlockContent':
return component.content ? (
<Markdown strapiBackendURL={strapiImageURL} locale={locale} key={index} priceData={priceData}>
<Markdown locale={locale} key={index} priceData={priceData}>
{component.content}
</Markdown>
) : null;
Expand All @@ -170,7 +171,6 @@ const Product = async ({ params: { locale, slug }, searchParams }: ProductProps)
accordion={component.faq.data.attributes.faq.accordion}
sectionTitle={component.faq.data.attributes.title}
priceData={priceData}
strapiBackendURL={strapiImageURL}
/>
);
}
Expand All @@ -183,43 +183,28 @@ const Product = async ({ params: { locale, slug }, searchParams }: ProductProps)
label: title,
headingLevel: 3, // TODO add this property from CMS
body: (
<Markdown priceData={priceData} locale={locale} strapiBackendURL={strapiImageURL}>
<Markdown priceData={priceData} locale={locale}>
{body}
</Markdown>
),
}))}
/>
);
case 'ComponentComponentsImage':
if (
component.imageData &&
component.imageData.data.attributes &&
component.imageData.data.attributes.url
) {
return (
<Image
src={`${strapiImageURL}${component.imageData.data.attributes.url}`}
alt={component.imageData.data.attributes.alternativeText || ''}
sizes="(max-width: 768px) 100vw,
(max-width: 1200px) 50vw,
33vw"
width={component.imageData.data.attributes.width}
height={component.imageData.data.attributes.height}
style={{
maxWidth: '100%',
height: 'auto',
}}
/>
);
} else {
return null;
}
return (
<Img
src={buildImgURL(component?.imageData?.data?.attributes?.url)}
width={component?.imageData?.data?.attributes?.width}
height={component?.imageData?.data?.attributes?.height}
alt={component?.imageData?.data?.attributes?.alternativeText}
data-figcaption={component?.imageData?.data?.attributes?.caption}
/>
);

case 'ComponentComponentsSpotlight':
return component.content ? (
<SpotlightSection type={component.type} aside={component?.aside}>
<Markdown strapiBackendURL={strapiImageURL} priceData={priceData}>
{component.content}
</Markdown>
<Markdown priceData={priceData}>{component.content}</Markdown>
</SpotlightSection>
) : null;
case 'ComponentComponentsButtonLink':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const SearchTips = async ({ params: { locale, query } }: any) => {
]}
/>
<PageTitle>{`${title} "${query}"`}</PageTitle>
<Markdown strapiBackendURL={process.env.STRAPI_PUBLIC_URL}>{body}</Markdown>
<Markdown>{body}</Markdown>
</>
);
};
Expand Down
11 changes: 2 additions & 9 deletions apps/pdc-frontend/src/components/FAQSection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,9 @@ export interface FAQSectionProps {
accordion?: AccordionType[];
locale?: string;
priceData?: any;
strapiBackendURL?: any;
}

export const FAQSection: React.FC<FAQSectionProps> = ({
sectionTitle,
accordion,
locale,
priceData,
strapiBackendURL,
}) => (
export const FAQSection: React.FC<FAQSectionProps> = ({ sectionTitle, accordion, locale, priceData }) => (
<section>
<Heading2>{sectionTitle}</Heading2>
{accordion && accordion.length > 0 && (
Expand All @@ -32,7 +25,7 @@ export const FAQSection: React.FC<FAQSectionProps> = ({
label: title,
headingLevel: 3, // TODO add this property from CMS
body: (
<Markdown priceData={priceData} locale={locale} strapiBackendURL={strapiBackendURL}>
<Markdown priceData={priceData} locale={locale}>
{body}
</Markdown>
),
Expand Down
22 changes: 22 additions & 0 deletions apps/pdc-frontend/src/components/Img/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.utrecht-img {
block-size: auto;
max-inline-size: 100%;
}

.utrecht-figure {
--utrecht-figure-margin-inline-start: 0;
--utrecht-figure-margin-inline-end: 0;
--utrecht-figure-margin-block-start: var(--utrecht-space-block-md);
--utrecht-figure-margin-block-end: var(--utrecht-space-block-md);

margin-block-end: var(--utrecht-figure-margin-block-end);
margin-block-start: var(--utrecht-figure-margin-block-start);
margin-inline-end: var(--utrecht-figure-margin-inline-end);
margin-inline-start: var(--utrecht-figure-margin-inline-start);
}

.utrecht-figure__figcaption {
--utrecht-figure--figcaption-color: var(--utrecht-color-grey-40);

color: var(--utrecht-figure--figcaption-color);
}
39 changes: 39 additions & 0 deletions apps/pdc-frontend/src/components/Img/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import classNames from 'classnames/bind';
import Image from 'next/image';
import React from 'react';
import styles from './index.module.scss';

const css = classNames.bind(styles);

export interface ImgProps
extends Omit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, 'ref'> {}
export const Img = (props: ImgProps & { 'data-figcaption'?: null }) => {
const { height, width, src, alt, title } = props;

if (width && height && src) {
const imgElement = (
<Image
className={css('utrecht-img')}
src={src}
alt={alt || ''}
title={title}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
width={width as number}
height={height as number}
/>
);

if (props['data-figcaption']) {
return (
<figure className={css('utrecht-figure')}>
{imgElement}
<figcaption className={css('utrecht-figure__figcaption')}>{props['data-figcaption']}</figcaption>
</figure>
);
}

return imgElement;
}

return null;
};
76 changes: 20 additions & 56 deletions apps/pdc-frontend/src/components/Markdown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
'use client';

import isAbsoluteUrl from 'is-absolute-url';
import Image from 'next/image';
import NextLink from 'next/link';
import React from 'react';
import ReactMarkdown, { Components } from 'react-markdown';
Expand All @@ -26,8 +23,10 @@ import {
UnorderedList,
UnorderedListItem,
} from '@/components';
import { useTranslation } from '../../app/i18n/client';
import { buildImgURL } from '@/util/buildImgURL';
import { useTranslation } from '../../app/i18n';
import { fallbackLng } from '../../app/i18n/settings';
import { Img } from '../Img';

type PriceTypes = {
value: string;
Expand All @@ -36,17 +35,7 @@ type PriceTypes = {
id: string;
};

const components = ({
strapiBackendURL,
priceData,
locale,
freeProductLabel,
}: {
strapiBackendURL: string;
priceData: PriceTypes[];
locale: string;
freeProductLabel: string;
}) =>
const components = ({ priceData, locale }: { priceData: PriceTypes[]; locale: string }) =>
({
h1: ({ children, node }) => {
delete node.properties?.style;
Expand Down Expand Up @@ -137,33 +126,15 @@ const components = ({
delete node.properties?.style;
return <TableCaption {...node.properties}>{children}</TableCaption>;
},
img: ({ width, height, src, alt }) => {
if (width && height && src && alt) {
return (
<Image
src={`${strapiBackendURL}${src}`}
alt={alt || ''}
sizes="(max-width: 768px) 100vw,
(max-width: 1200px) 50vw,
33vw"
width={width as number}
height={height as number}
style={{
maxWidth: '100%',
height: 'auto',
}}
/>
);
} else {
return null;
}
},
'react-widget': ({ node }: any) => {
img: (props) => props.src && <Img {...props} src={buildImgURL(props.src)} />,
'react-widget': async ({ node }: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { t } = await useTranslation(locale ?? fallbackLng, 'common');
if (node.properties?.id && priceData && priceData.length > 0) {
const product = priceData.find(({ id }) => id === node.properties?.id);
const price =
Number(product?.value) === 0
? freeProductLabel
? t('text.free-product')
: new Intl.NumberFormat(locale, {
style: 'currency',
currency: product?.currency,
Expand All @@ -180,23 +151,16 @@ interface MarkdownProps {
children: any;
priceData?: any;
locale?: string;
strapiBackendURL?: string;
}

export const Markdown: React.FC<MarkdownProps> = ({ children, priceData, locale, strapiBackendURL }) => {
const url = strapiBackendURL ? new URL(strapiBackendURL) : null;
const { t } = useTranslation(locale ?? fallbackLng, 'common');
return (
<ReactMarkdown
components={components({
priceData,
locale: locale ?? fallbackLng,
strapiBackendURL: url?.origin ?? '',
freeProductLabel: t('text.free-product'),
})}
rehypePlugins={[rehypeRaw]}
>
{children}
</ReactMarkdown>
);
};
export const Markdown: React.FC<MarkdownProps> = ({ children, priceData, locale }) => (
<ReactMarkdown
components={components({
priceData,
locale: locale ?? fallbackLng,
})}
rehypePlugins={[rehypeRaw]}
>
{children}
</ReactMarkdown>
);
12 changes: 12 additions & 0 deletions apps/pdc-frontend/src/util/buildImgURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const buildImgURL = (src: string) => {
if (!process.env.STRAPI_PUBLIC_URL) {
throw new Error('`STRAPI_PUBLIC_URL` is required to construct the image URL in the Markdown component.');
}
const url = new URL(process.env.STRAPI_PUBLIC_URL);
// if we need to support a different image-provider-upload, we can use the following approach
// just rename env variable
// if (process.env.DEPLOY_TO_VERCEL && Boolean(process.env.DEPLOY_TO_VERCEL)) {
// return src;
// }
return `${url?.origin}${src}`;
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ const Editor = ({ editor, settings, productPrice }: EditorProps) => {
const [mediaLibVisible, setMediaLibVisible] = useState(false);
const [forceInsert, setForceInsert] = useState(false);
const handleToggleMediaLib = () => setMediaLibVisible((prev) => !prev);
const getUpdatedImage = (asset: any) => ({
src: asset.url,
alt: asset.alt,
...(asset.width && { width: asset.width }),
...(asset.height && { height: asset.height }),
...(asset.url?.includes('lazy') || (asset.caption === 'lazy' && { loading: 'lazy' })),
});
const getUpdatedImage = (asset: any) => {
return {
src: asset.url,
alt: asset.alt,
'data-figcaption': asset?.caption,
...(asset.width && { width: asset.width }),
...(asset.height && { height: asset.height }),
...(asset.url?.includes('lazy') || (asset.caption === 'lazy' && { loading: 'lazy' })),
};
};

const handleChangeAssets = (assets: any[]) => {
if (!forceInsert && editor.isActive('image')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ const MediaLib: React.FC<MediaLibProps> = ({ isOpen, onChange, onToggle }) => {
const handleSelectAssets = (images: Image[]) => {
const formattedImages = images.map((image) => ({
...image,
alt: image.alternativeText || image.name,
alt: image.alternativeText,
url: prefixFileUrlWithBackendUrl(image.url),
mime: image.mime,
'data-figcaption': image?.caption,
}));

onChange(formattedImages);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,12 @@ const WysiwygContent = ({
width: { default: null },
height: { default: null },
loading: { default: null },
'data-figcaption': { default: null },
renderHTML: (attributes: any) => {
return {
width: attributes.width,
height: attributes.height,
'data-figcaption': attributes?.caption,
loading: attributes.loading,
};
},
Expand Down