From 6aa06c351f89f336afb6b67b7acafa131898a834 Mon Sep 17 00:00:00 2001 From: Ali Amori Kadhim Date: Tue, 26 Sep 2023 14:13:24 +0200 Subject: [PATCH 1/2] feat(pdc-frontend): improve the image component & enable figcaption - [x] fix the broken image - [x] improve the image component implementation - [x] enable figcaption --- .../src/app/[locale]/[...not-found]/page.tsx | 2 +- .../src/app/[locale]/not-found.tsx | 2 +- .../src/app/[locale]/products/[slug]/page.tsx | 47 ++++-------- .../app/[locale]/search/tips/[query]/page.tsx | 2 +- .../src/components/FAQSection/index.tsx | 11 +-- .../src/components/Img/index.module.scss | 22 ++++++ .../pdc-frontend/src/components/Img/index.tsx | 39 ++++++++++ .../src/components/Markdown/index.tsx | 76 +++++-------------- apps/pdc-frontend/src/util/buildImgURL.ts | 12 +++ 9 files changed, 114 insertions(+), 99 deletions(-) create mode 100644 apps/pdc-frontend/src/components/Img/index.module.scss create mode 100644 apps/pdc-frontend/src/components/Img/index.tsx create mode 100644 apps/pdc-frontend/src/util/buildImgURL.ts diff --git a/apps/pdc-frontend/src/app/[locale]/[...not-found]/page.tsx b/apps/pdc-frontend/src/app/[locale]/[...not-found]/page.tsx index a4d1fe01..1a75ae10 100644 --- a/apps/pdc-frontend/src/app/[locale]/[...not-found]/page.tsx +++ b/apps/pdc-frontend/src/app/[locale]/[...not-found]/page.tsx @@ -26,7 +26,7 @@ const NotFoundPage = async ({ params: { locale } }: { params: { locale: string } ]} /> {data?.notFoundPage?.data?.attributes?.title} - {data?.notFoundPage?.data?.attributes?.body} + {data?.notFoundPage?.data?.attributes?.body} ); }; diff --git a/apps/pdc-frontend/src/app/[locale]/not-found.tsx b/apps/pdc-frontend/src/app/[locale]/not-found.tsx index bc45c380..5d623c4f 100644 --- a/apps/pdc-frontend/src/app/[locale]/not-found.tsx +++ b/apps/pdc-frontend/src/app/[locale]/not-found.tsx @@ -29,7 +29,7 @@ const NotFoundPage = async () => { ]} /> {data?.notFoundPage?.data?.attributes?.title} - {data?.notFoundPage?.data?.attributes?.body} + {data?.notFoundPage?.data?.attributes?.body} ); }; diff --git a/apps/pdc-frontend/src/app/[locale]/products/[slug]/page.tsx b/apps/pdc-frontend/src/app/[locale]/products/[slug]/page.tsx index e52a825f..72e13b91 100644 --- a/apps/pdc-frontend/src/app/[locale]/products/[slug]/page.tsx +++ b/apps/pdc-frontend/src/app/[locale]/products/[slug]/page.tsx @@ -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'; @@ -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'; @@ -135,7 +136,7 @@ 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 @@ -143,7 +144,7 @@ const Product = async ({ params: { locale, slug }, searchParams }: ProductProps) switch (component.__typename) { case 'ComponentComponentsBlockContent': return component.content ? ( - + {component.content} ) : null; @@ -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} /> ); } @@ -183,7 +183,7 @@ const Product = async ({ params: { locale, slug }, searchParams }: ProductProps) label: title, headingLevel: 3, // TODO add this property from CMS body: ( - + {body} ), @@ -191,35 +191,20 @@ const Product = async ({ params: { locale, slug }, searchParams }: ProductProps) /> ); case 'ComponentComponentsImage': - if ( - component.imageData && - component.imageData.data.attributes && - component.imageData.data.attributes.url - ) { - return ( - {component.imageData.data.attributes.alternativeText - ); - } else { - return null; - } + return ( + {component?.imageData?.data?.attributes?.alternativeText} + ); + case 'ComponentComponentsSpotlight': return component.content ? ( - - {component.content} - + {component.content} ) : null; case 'ComponentComponentsButtonLink': diff --git a/apps/pdc-frontend/src/app/[locale]/search/tips/[query]/page.tsx b/apps/pdc-frontend/src/app/[locale]/search/tips/[query]/page.tsx index 787a70d7..1375e5cc 100644 --- a/apps/pdc-frontend/src/app/[locale]/search/tips/[query]/page.tsx +++ b/apps/pdc-frontend/src/app/[locale]/search/tips/[query]/page.tsx @@ -60,7 +60,7 @@ const SearchTips = async ({ params: { locale, query } }: any) => { ]} /> {`${title} "${query}"`} - {body} + {body} ); }; diff --git a/apps/pdc-frontend/src/components/FAQSection/index.tsx b/apps/pdc-frontend/src/components/FAQSection/index.tsx index cb20f528..1d0d7cc5 100644 --- a/apps/pdc-frontend/src/components/FAQSection/index.tsx +++ b/apps/pdc-frontend/src/components/FAQSection/index.tsx @@ -13,16 +13,9 @@ export interface FAQSectionProps { accordion?: AccordionType[]; locale?: string; priceData?: any; - strapiBackendURL?: any; } -export const FAQSection: React.FC = ({ - sectionTitle, - accordion, - locale, - priceData, - strapiBackendURL, -}) => ( +export const FAQSection: React.FC = ({ sectionTitle, accordion, locale, priceData }) => (
{sectionTitle} {accordion && accordion.length > 0 && ( @@ -32,7 +25,7 @@ export const FAQSection: React.FC = ({ label: title, headingLevel: 3, // TODO add this property from CMS body: ( - + {body} ), diff --git a/apps/pdc-frontend/src/components/Img/index.module.scss b/apps/pdc-frontend/src/components/Img/index.module.scss new file mode 100644 index 00000000..0ce265bd --- /dev/null +++ b/apps/pdc-frontend/src/components/Img/index.module.scss @@ -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); +} diff --git a/apps/pdc-frontend/src/components/Img/index.tsx b/apps/pdc-frontend/src/components/Img/index.tsx new file mode 100644 index 00000000..ed1cf132 --- /dev/null +++ b/apps/pdc-frontend/src/components/Img/index.tsx @@ -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, HTMLImageElement>, 'ref'> {} +export const Img = (props: ImgProps & { 'data-figcaption'?: null }) => { + const { height, width, src, alt, title } = props; + + if (width && height && src) { + const imgElement = ( + {alt + ); + + if (props['data-figcaption']) { + return ( +
+ {imgElement} +
{props['data-figcaption']}
+
+ ); + } + + return imgElement; + } + + return null; +}; diff --git a/apps/pdc-frontend/src/components/Markdown/index.tsx b/apps/pdc-frontend/src/components/Markdown/index.tsx index 84b1c07e..af1b5b62 100644 --- a/apps/pdc-frontend/src/components/Markdown/index.tsx +++ b/apps/pdc-frontend/src/components/Markdown/index.tsx @@ -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'; @@ -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; @@ -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; @@ -137,33 +126,15 @@ const components = ({ delete node.properties?.style; return {children}; }, - img: ({ width, height, src, alt }) => { - if (width && height && src && alt) { - return ( - {alt - ); - } else { - return null; - } - }, - 'react-widget': ({ node }: any) => { + img: (props) => 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, @@ -180,23 +151,16 @@ interface MarkdownProps { children: any; priceData?: any; locale?: string; - strapiBackendURL?: string; } -export const Markdown: React.FC = ({ children, priceData, locale, strapiBackendURL }) => { - const url = strapiBackendURL ? new URL(strapiBackendURL) : null; - const { t } = useTranslation(locale ?? fallbackLng, 'common'); - return ( - - {children} - - ); -}; +export const Markdown: React.FC = ({ children, priceData, locale }) => ( + + {children} + +); diff --git a/apps/pdc-frontend/src/util/buildImgURL.ts b/apps/pdc-frontend/src/util/buildImgURL.ts new file mode 100644 index 00000000..a6258bbe --- /dev/null +++ b/apps/pdc-frontend/src/util/buildImgURL.ts @@ -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}`; +}; From 1781da874d038558f6d87d423cb7efb5234eac3f Mon Sep 17 00:00:00 2001 From: Ali Amori Kadhim Date: Wed, 27 Sep 2023 15:08:45 +0200 Subject: [PATCH 2/2] feat(strapi-tiptap-editor): enable `figcaption` as property --- .../admin/src/components/Editor/index.tsx | 17 ++++++++++------- .../admin/src/components/MediaLib/index.tsx | 3 ++- .../admin/src/components/Wysiwyg/index.tsx | 2 ++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/strapi-tiptap-editor/admin/src/components/Editor/index.tsx b/packages/strapi-tiptap-editor/admin/src/components/Editor/index.tsx index a3d1f176..fdd4360b 100644 --- a/packages/strapi-tiptap-editor/admin/src/components/Editor/index.tsx +++ b/packages/strapi-tiptap-editor/admin/src/components/Editor/index.tsx @@ -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')) { diff --git a/packages/strapi-tiptap-editor/admin/src/components/MediaLib/index.tsx b/packages/strapi-tiptap-editor/admin/src/components/MediaLib/index.tsx index 44ffc80f..bc2a2412 100644 --- a/packages/strapi-tiptap-editor/admin/src/components/MediaLib/index.tsx +++ b/packages/strapi-tiptap-editor/admin/src/components/MediaLib/index.tsx @@ -51,9 +51,10 @@ const MediaLib: React.FC = ({ 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); diff --git a/packages/strapi-tiptap-editor/admin/src/components/Wysiwyg/index.tsx b/packages/strapi-tiptap-editor/admin/src/components/Wysiwyg/index.tsx index 9559db16..06f8f851 100644 --- a/packages/strapi-tiptap-editor/admin/src/components/Wysiwyg/index.tsx +++ b/packages/strapi-tiptap-editor/admin/src/components/Wysiwyg/index.tsx @@ -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, }; },