From 82a704bfedd23c731a35af65340b41562b9ab587 Mon Sep 17 00:00:00 2001 From: Martial Maillot Date: Thu, 6 Feb 2025 11:23:54 +0100 Subject: [PATCH] feat(contributions): ajout du support des infographies dans les contributions --- .../code-du-travail-frontend/app/layout.tsx | 1 + .../code-du-travail-frontend/src/lib/xss.ts | 9 +++- .../src/modules/common/ImageWrapper.tsx | 44 +++++++++++++++ .../DisplayContentContribution.tsx | 33 +++++++++++- .../src/modules/utils/useScrollBlock.ts | 54 +++++++++++++++++++ 5 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 packages/code-du-travail-frontend/src/modules/common/ImageWrapper.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/utils/useScrollBlock.ts diff --git a/packages/code-du-travail-frontend/app/layout.tsx b/packages/code-du-travail-frontend/app/layout.tsx index c5e1acfcde..45c5d3fcfe 100644 --- a/packages/code-du-travail-frontend/app/layout.tsx +++ b/packages/code-du-travail-frontend/app/layout.tsx @@ -1,4 +1,5 @@ import "./globals.css"; +import 'react-image-lightbox/style.css'; import { Metadata } from "next/types"; import { SITE_URL } from "../src/config"; import { headers } from "next/headers"; diff --git a/packages/code-du-travail-frontend/src/lib/xss.ts b/packages/code-du-travail-frontend/src/lib/xss.ts index ac5a401269..f444086130 100644 --- a/packages/code-du-travail-frontend/src/lib/xss.ts +++ b/packages/code-du-travail-frontend/src/lib/xss.ts @@ -12,7 +12,14 @@ const whiteListTags = ["webcomponent-tooltip", "webcomponent-tooltip-cc"]; * @type {string[]} * class is used for modeles-de-courrier */ -const whiteListAttr = ["class", "rel", "href", "target"]; +const whiteListAttr = [ + "class", + "rel", + "href", + "target", + "data-pdf", + "data-pdf-size", +]; export const xssWrapper = (text: string): string => { return xss(text, { diff --git a/packages/code-du-travail-frontend/src/modules/common/ImageWrapper.tsx b/packages/code-du-travail-frontend/src/modules/common/ImageWrapper.tsx new file mode 100644 index 0000000000..f7f067c30a --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/common/ImageWrapper.tsx @@ -0,0 +1,44 @@ +import React, { useState } from "react"; +import Lightbox from "react-image-lightbox"; + +import { css } from "@styled-system/css"; +import useScrollBlock from "../utils/useScrollBlock"; + +type Props = { + altText: string; + src: string; +}; + +const InfographicWrapper = ({ altText, src }: Props): JSX.Element => { + const [isOpen, setIsOpen] = useState(false); + const [blockScroll, allowScroll] = useScrollBlock(); + + const onOpen = () => { + setIsOpen(true); + blockScroll(); + }; + + const onClose = () => { + setIsOpen(false); + allowScroll(); + }; + + return ( + <> + + {isOpen && } + + ); +}; + +const ImageZoom = css({ + cursor: "zoom-in", +}); + +export default InfographicWrapper; diff --git a/packages/code-du-travail-frontend/src/modules/contributions/DisplayContentContribution.tsx b/packages/code-du-travail-frontend/src/modules/contributions/DisplayContentContribution.tsx index a1245bd2d0..beab1e47ae 100644 --- a/packages/code-du-travail-frontend/src/modules/contributions/DisplayContentContribution.tsx +++ b/packages/code-du-travail-frontend/src/modules/contributions/DisplayContentContribution.tsx @@ -5,13 +5,15 @@ import parse, { HTMLReactParserOptions, Text, } from "html-react-parser"; -import { xssWrapper } from "../../lib"; +import { toUrl, xssWrapper } from "../../lib"; import Alert from "@codegouvfr/react-dsfr/Alert"; import { ElementType } from "react"; import { AccordionWithAnchor } from "../common/AccordionWithAnchor"; import { v4 as generateUUID } from "uuid"; import { fr } from "@codegouvfr/react-dsfr"; import { FicheServicePublic } from "../fiche-service-public/builder"; +import ImageWrapper from "../common/ImageWrapper"; +import { Tile } from "@codegouvfr/react-dsfr/Tile"; const DEFAULT_HEADING_LEVEL = 3; export type numberLevel = 2 | 3 | 4 | 5 | 6; @@ -257,6 +259,35 @@ const options = (titleLevel: numberLevel): HTMLReactParserOptions => { > ); } + if (domNode.name === "div" && domNode.attribs.class === "infographic") { + const pdfUrl = domNode.attribs["data-pdf"]; + const pdfSize = domNode.attribs["data-pdf-size"]; + const firstNode = domNode.children.shift(); + const pictoUrl = + firstNode?.type === "tag" ? firstNode.attribs.src : ""; + return ( +
+ + {domToReact( + domNode.children as DOMNode[], + options(titleLevel as numberLevel) + )} + +
+ ); + } if (domNode.name === "strong") { // Disable trim on strong return {renderChildrenWithNoTrim(domNode)}; diff --git a/packages/code-du-travail-frontend/src/modules/utils/useScrollBlock.ts b/packages/code-du-travail-frontend/src/modules/utils/useScrollBlock.ts new file mode 100644 index 0000000000..02dc8dfd34 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/utils/useScrollBlock.ts @@ -0,0 +1,54 @@ +import { useRef } from "react"; + +const safeDocument: any = typeof document !== "undefined" ? document : {}; + +/** + * Usage: + * const [blockScroll, allowScroll] = useScrollBlock(); + */ +const useScrollBlock = (): [() => void, () => void] => { + const scrollBlocked = useRef(false); + const html = safeDocument.documentElement; + const { body } = safeDocument; + + const blockScroll = (): void => { + if (!body || !body.style || scrollBlocked.current) return; + if (document == undefined) return; + + const scrollBarWidth = window.innerWidth - html.clientWidth; + const bodyPaddingRight = + parseInt( + window.getComputedStyle(body).getPropertyValue("padding-right") + ) || 0; + + /** + * 1. Fixes a bug in iOS and desktop Safari whereby setting + * `overflow: hidden` on the html/body does not prevent scrolling. + * 2. Fixes a bug in desktop Safari where `overflowY` does not prevent + * scroll if an `overflow-x` style is also applied to the body. + */ + html.style.position = "relative"; /* [1] */ + html.style.overflow = "hidden"; /* [2] */ + body.style.position = "relative"; /* [1] */ + body.style.overflow = "hidden"; /* [2] */ + body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`; + + scrollBlocked.current = true; + }; + + const allowScroll = (): void => { + if (!body || !body.style || !scrollBlocked.current) return; + + html.style.position = ""; + html.style.overflow = ""; + body.style.position = ""; + body.style.overflow = ""; + body.style.paddingRight = ""; + + scrollBlocked.current = false; + }; + + return [blockScroll, allowScroll]; +}; + +export default useScrollBlock;