Skip to content

Commit

Permalink
feat(contributions): ajout du support des infographies dans les contr…
Browse files Browse the repository at this point in the history
…ibutions
  • Loading branch information
m-maillot committed Feb 12, 2025
1 parent 397232c commit 24237f6
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .kontinuous/env/dev/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}}"
Expand Down
4 changes: 2 additions & 2 deletions .kontinuous/env/preprod/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}}"
Expand Down
4 changes: 2 additions & 2 deletions .kontinuous/env/prod/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}}"
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/code-du-travail-frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
3 changes: 2 additions & 1 deletion packages/code-du-travail-frontend/middleware.ts
Original file line number Diff line number Diff line change
@@ -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'" : ""
};
Expand Down
5 changes: 2 additions & 3 deletions packages/code-du-travail-frontend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? "";
Expand Down
4 changes: 2 additions & 2 deletions packages/code-du-travail-frontend/src/lib/url.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down
10 changes: 9 additions & 1 deletion packages/code-du-travail-frontend/src/lib/xss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<img
src={src}
alt={altText}
onClick={onOpen}
aria-hidden="true"
className={`${ImageZoom}`}
/>
{isOpen && <Lightbox mainSrc={src} onCloseRequest={onClose} />}
</>
);
};

const ImageZoom = css({
cursor: "zoom-in",
});

export default InfographicWrapper;
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -257,6 +259,44 @@ const options = (titleLevel: numberLevel): HTMLReactParserOptions => {
></Alert>
);
}
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 (
<div>
<ImageWrapper altText={""} src={toUrl(pictoName)} />
{domToReact(
domNode.children as DOMNode[],
options(titleLevel as numberLevel)
)}
<Tile
downloadButton
enlargeLinkOrButton
imageSvg={false}
imageUrl={`/static/assets/img/modeles-de-courriers-download.svg`}
title={`Télécharger l'infographie`}
titleAs={`h${titleLevel}`}
detail={`Format PDF - ${pdfSize}Ko`}
imageAlt={""}
linkProps={{
href: toUrl(pdfName),
}}
/>
</div>
);
}
if (domNode.name === "strong") {
// Disable trim on strong
return <strong>{renderChildrenWithNoTrim(domNode)}</strong>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<DisplayContentContribution
content={`
<div class="infographic" data-pdf="file.pdf" data-pdf-size="3200" data-infographic="file.svg"><img src="https://cdtn-dev-public.s3.gra.io.cloud.ovh.net/draft/default/infographie_test.svg" height="auto" width="500"><div><div class="details" data-type="details"><button type="button"></button><div><summary>Afficher le contenu de l'infographie</summary><div data-type="detailsContent" hidden="hidden"><p>Décrire ici le contenu de l'infographie</p></div></div></div></div></div>`}
titleLevel={3}
></DisplayContentContribution>
);

expect(asFragment().firstChild).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,80 @@ exports[`DisplayContentContribution Headings should replace span with class "tit
</div>
`;

exports[`DisplayContentContribution Infographic should replace div with infographic class to Infographic component 1`] = `
<div>
<div>
<img
alt=""
aria-hidden="true"
class="cursor_zoom-in"
src="bucket.url/undefined/default/file.svg"
/>
<div>
<div
class="details"
>
&lt;button type="button"&gt;&lt;/button&gt;
<div>
<summary>
Afficher le contenu de l'infographie
</summary>
<div>
<p
class="fr-mt-2w"
>
Décrire ici le contenu de l'infographie
</p>
</div>
</div>
</div>
</div>
<div
class="fr-tile fr-enlarge-link fr-tile--vertical fr-tile--download"
id="fr-tile-:r0:"
>
<div
class="fr-tile__body"
>
<div
class="fr-tile__content"
>
<h3
class="fr-tile__title"
>
<a
aria-disabled="false"
class=""
href="bucket.url/undefined/default/file.pdf"
>
Télécharger l'infographie
</a>
</h3>
<p
class="fr-tile__detail"
>
Format PDF - 3200Ko
</p>
</div>
</div>
<div
class="fr-tile__header"
>
<div
class="fr-tile__img"
>
<img
alt=""
class="fr-responsive-img"
src="/static/assets/img/modeles-de-courriers-download.svg"
/>
</div>
</div>
</div>
</div>
</div>
`;

exports[`DisplayContentContribution Tables should add thead to table if not present and move table into a Table element 1`] = `
<div>
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 24237f6

Please sign in to comment.