diff --git a/.kontinuous/env/dev/values.yaml b/.kontinuous/env/dev/values.yaml index 4592decae4..4af3653797 100644 --- a/.kontinuous/env/dev/values.yaml +++ b/.kontinuous/env/dev/values.yaml @@ -10,9 +10,9 @@ jobs: NEXT_PUBLIC_SENTRY_BASE_URL: https://sentry.fabrique.social.gouv.fr NEXT_PUBLIC_SENTRY_ENV: dev NEXT_PUBLIC_SENTRY_RELEASE: "{{.Values.global.branchSlug32}}" - NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER: "default" + NEXT_PUBLIC_BUCKET_FOLDER: "preview" NEXT_PUBLIC_BUCKET_SITEMAP_FOLDER: "sitemap" - NEXT_PUBLIC_BUCKET_URL: https://cdtn-prod-public.s3.gra.io.cloud.ovh.net/preview + NEXT_PUBLIC_BUCKET_URL: https://cdtn-dev-public.s3.gra.io.cloud.ovh.net NEXT_PUBLIC_PIWIK_SITE_ID: "3" NEXT_PUBLIC_PIWIK_URL: https://matomo.fabrique.social.gouv.fr NEXT_PUBLIC_COMMIT: "{{.Values.global.sha}}" diff --git a/.kontinuous/env/preprod/values.yaml b/.kontinuous/env/preprod/values.yaml index 6631fbaeb9..102a2c6e3d 100644 --- a/.kontinuous/env/preprod/values.yaml +++ b/.kontinuous/env/preprod/values.yaml @@ -9,9 +9,9 @@ jobs: NEXT_PUBLIC_SENTRY_BASE_URL: https://sentry.fabrique.social.gouv.fr NEXT_PUBLIC_SENTRY_ENV: preproduction NEXT_PUBLIC_SENTRY_RELEASE: "{{.Values.global.sha}}" - NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER: "default" + NEXT_PUBLIC_BUCKET_FOLDER: "preview" NEXT_PUBLIC_BUCKET_SITEMAP_FOLDER: "sitemap" - NEXT_PUBLIC_BUCKET_URL: https://cdtn-prod-public.s3.gra.io.cloud.ovh.net/preview + NEXT_PUBLIC_BUCKET_URL: https://cdtn-prod-public.s3.gra.io.cloud.ovh.net NEXT_PUBLIC_PIWIK_SITE_ID: "3" NEXT_PUBLIC_PIWIK_URL: https://matomo.fabrique.social.gouv.fr NEXT_PUBLIC_COMMIT: "{{.Values.global.sha}}" diff --git a/.kontinuous/env/prod/values.yaml b/.kontinuous/env/prod/values.yaml index e2cc3dbe6c..a073535fd3 100644 --- a/.kontinuous/env/prod/values.yaml +++ b/.kontinuous/env/prod/values.yaml @@ -10,9 +10,9 @@ jobs: NEXT_PUBLIC_SENTRY_ENV: production NEXT_PUBLIC_SENTRY_RELEASE: "{{.Values.global.imageTag}}" NEXT_PUBLIC_IS_PRODUCTION_DEPLOYMENT: "true" - NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER: "default" + NEXT_PUBLIC_BUCKET_FOLDER: "published" NEXT_PUBLIC_BUCKET_SITEMAP_FOLDER: "sitemap" - NEXT_PUBLIC_BUCKET_URL: https://cdtn-prod-public.s3.gra.io.cloud.ovh.net/published + NEXT_PUBLIC_BUCKET_URL: https://cdtn-prod-public.s3.gra.io.cloud.ovh.net NEXT_PUBLIC_PIWIK_SITE_ID: "4" NEXT_PUBLIC_PIWIK_URL: https://matomo.fabrique.social.gouv.fr NEXT_PUBLIC_COMMIT: "{{.Values.global.sha}}" diff --git a/Dockerfile b/Dockerfile index 90177f3f71..a423c83d12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,8 @@ ARG NEXT_PUBLIC_IS_PRODUCTION_DEPLOYMENT ENV NEXT_PUBLIC_IS_PRODUCTION_DEPLOYMENT=$NEXT_PUBLIC_IS_PRODUCTION_DEPLOYMENT ARG NEXT_PUBLIC_IS_PREPRODUCTION_DEPLOYMENT ENV NEXT_PUBLIC_IS_PREPRODUCTION_DEPLOYMENT=$NEXT_PUBLIC_IS_PREPRODUCTION_DEPLOYMENT -ARG NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER -ENV NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER=$NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER +ARG NEXT_PUBLIC_BUCKET_FOLDER +ENV NEXT_PUBLIC_BUCKET_FOLDER=$NEXT_PUBLIC_BUCKET_FOLDER ARG NEXT_PUBLIC_BUCKET_SITEMAP_FOLDER ENV NEXT_PUBLIC_BUCKET_SITEMAP_FOLDER=$NEXT_PUBLIC_BUCKET_SITEMAP_FOLDER ARG NEXT_PUBLIC_BUCKET_URL diff --git a/packages/code-du-travail-frontend/app/layout.tsx b/packages/code-du-travail-frontend/app/layout.tsx index c5e1acfcde..1fc41b5000 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/middleware.ts b/packages/code-du-travail-frontend/middleware.ts index 091cdad54f..90f5653c7f 100644 --- a/packages/code-du-travail-frontend/middleware.ts +++ b/packages/code-du-travail-frontend/middleware.ts @@ -1,9 +1,10 @@ import { NextResponse } from "next/server"; +import { BUCKET_URL } from "./src/config"; export function middleware(request) { const nonce = Buffer.from(crypto.randomUUID()).toString("base64"); const ContentSecurityPolicy = ` - img-src 'self' https://travail-emploi.gouv.fr https://www.service-public.fr https://cdtn-prod-public.s3.gra.io.cloud.ovh.net https://matomo.fabrique.social.gouv.fr data:; + img-src 'self' https://travail-emploi.gouv.fr https://www.service-public.fr ${BUCKET_URL} https://matomo.fabrique.social.gouv.fr data:; script-src 'self' 'nonce-${nonce}' https://mon-entreprise.urssaf.fr https://matomo.fabrique.social.gouv.fr ${ process.env.NEXT_PUBLIC_APP_ENV !== "production" ? "'unsafe-eval'" : "" }; diff --git a/packages/code-du-travail-frontend/src/config.ts b/packages/code-du-travail-frontend/src/config.ts index 13e3b680f2..91252c5c60 100644 --- a/packages/code-du-travail-frontend/src/config.ts +++ b/packages/code-du-travail-frontend/src/config.ts @@ -2,9 +2,8 @@ const { version } = require("../package.json"); export const BUCKET_URL = process.env.NEXT_PUBLIC_BUCKET_URL ?? - "https://cdtn-prod-public.s3.gra.io.cloud.ovh.net/preview"; -export const BUCKET_DEFAULT_FOLDER = - process.env.NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER ?? "default"; + "https://cdtn-dev-public.s3.gra.io.cloud.ovh.net"; +export const BUCKET_FOLDER = process.env.NEXT_PUBLIC_BUCKET_FOLDER ?? "preview"; export const BUCKET_SITEMAP_FOLDER = process.env.NEXT_PUBLIC_BUCKET_SITEMAP_FOLDER ?? "sitemap"; export const COMMIT = process.env.NEXT_PUBLIC_COMMIT ?? ""; diff --git a/packages/code-du-travail-frontend/src/lib/url.ts b/packages/code-du-travail-frontend/src/lib/url.ts index a99bc25595..3685a2f521 100644 --- a/packages/code-du-travail-frontend/src/lib/url.ts +++ b/packages/code-du-travail-frontend/src/lib/url.ts @@ -1,10 +1,10 @@ -import { BUCKET_URL, BUCKET_DEFAULT_FOLDER } from "../config"; +import { BUCKET_URL, BUCKET_FOLDER } from "../config"; export const toUrl = (file: string): string => { if (!file) return ""; const index = file.lastIndexOf("/"); const filename = index !== -1 ? file.substring(index + 1) : file; - return `${BUCKET_URL}/${BUCKET_DEFAULT_FOLDER}/${filename}`; + return `${BUCKET_URL}/${BUCKET_FOLDER}/default/${filename}`; }; export const removeQueryParameters = (url: string): string => { diff --git a/packages/code-du-travail-frontend/src/lib/xss.ts b/packages/code-du-travail-frontend/src/lib/xss.ts index ac5a401269..6176b3ad88 100644 --- a/packages/code-du-travail-frontend/src/lib/xss.ts +++ b/packages/code-du-travail-frontend/src/lib/xss.ts @@ -12,7 +12,15 @@ 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", + "data-infographic", +]; 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..a7adc845e5 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,44 @@ const options = (titleLevel: numberLevel): HTMLReactParserOptions => { > ); } + if (domNode.name === "div" && domNode.attribs.class === "infographic") { + const pdfName = domNode.attribs["data-pdf"]; + const pdfSize = domNode.attribs["data-pdf-size"]; + const pictoName = domNode.attribs["data-infographic"]; + // Remove the img tag. It contains an absolute path. We need to build our own path. + const firstChild = + domNode.children.length > 0 ? domNode.children[0] : undefined; + if ( + firstChild && + firstChild.type === "tag" && + firstChild.name === "img" + ) { + domNode.children.shift(); + } + + 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/contributions/__tests__/DisplayContentContribution.test.tsx b/packages/code-du-travail-frontend/src/modules/contributions/__tests__/DisplayContentContribution.test.tsx index 3825b60f84..c081aaa1ba 100644 --- a/packages/code-du-travail-frontend/src/modules/contributions/__tests__/DisplayContentContribution.test.tsx +++ b/packages/code-du-travail-frontend/src/modules/contributions/__tests__/DisplayContentContribution.test.tsx @@ -496,4 +496,18 @@ describe("DisplayContentContribution", () => { expect(asFragment().firstChild).toMatchSnapshot(); }); }); + + describe("Infographic", () => { + it(`should replace div with infographic class to Infographic component`, () => { + const { asFragment } = render( +
Afficher le contenu de l'infographie
`} + titleLevel={3} + >
+ ); + + expect(asFragment().firstChild).toMatchSnapshot(); + }); + }); }); diff --git a/packages/code-du-travail-frontend/src/modules/contributions/__tests__/__snapshots__/DisplayContentContribution.test.tsx.snap b/packages/code-du-travail-frontend/src/modules/contributions/__tests__/__snapshots__/DisplayContentContribution.test.tsx.snap index 8e8bc17582..d50b44f2ee 100644 --- a/packages/code-du-travail-frontend/src/modules/contributions/__tests__/__snapshots__/DisplayContentContribution.test.tsx.snap +++ b/packages/code-du-travail-frontend/src/modules/contributions/__tests__/__snapshots__/DisplayContentContribution.test.tsx.snap @@ -587,6 +587,80 @@ exports[`DisplayContentContribution Headings should replace span with class "tit `; +exports[`DisplayContentContribution Infographic should replace div with infographic class to Infographic component 1`] = ` +
+
+ +
+
+ <button type="button"></button> +
+ + Afficher le contenu de l'infographie + +
+

+ Décrire ici le contenu de l'infographie +

+
+
+
+
+ +
+
+`; + exports[`DisplayContentContribution Tables should add thead to table if not present and move table into a Table element 1`] = `
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;