From efd31ba00d949e53a22289fd4e151238eba22ead Mon Sep 17 00:00:00 2001 From: Martial Maillot Date: Thu, 30 Jan 2025 09:50:06 +0100 Subject: [PATCH 1/2] feat(contributions): ajouter des infographies # Conflicts: # targets/frontend/src/components/forms/EditionField/MenuSpecial.tsx --- .../components/forms/EditionField/Editor.tsx | 187 +++++++++++++++++- .../forms/EditionField/MenuInfographic.tsx | 60 ++++++ .../forms/EditionField/MenuSpecial.tsx | 18 +- .../forms/EditionField/extensions/Alert.ts | 2 +- .../EditionField/extensions/Infographic.ts | 182 +++++++++++++++++ .../forms/EditionField/extensions/index.ts | 1 + 6 files changed, 441 insertions(+), 9 deletions(-) create mode 100644 targets/frontend/src/components/forms/EditionField/MenuInfographic.tsx create mode 100644 targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts diff --git a/targets/frontend/src/components/forms/EditionField/Editor.tsx b/targets/frontend/src/components/forms/EditionField/Editor.tsx index 5a26884c7..2b638642a 100644 --- a/targets/frontend/src/components/forms/EditionField/Editor.tsx +++ b/targets/frontend/src/components/forms/EditionField/Editor.tsx @@ -17,7 +17,18 @@ import { DetailsSummary } from "@tiptap-pro/extension-details-summary"; import { DetailsContent } from "@tiptap-pro/extension-details-content"; import { Placeholder } from "@tiptap/extension-placeholder"; import { Link } from "@tiptap/extension-link"; -import { Alert, Title } from "./extensions"; +import { Alert, Infographic, Title } from "./extensions"; +import { MenuInfographic } from "./MenuInfographic"; +import { + Button, + DialogActions, + DialogContentText, + TextField, +} from "@mui/material"; +import DialogTitle from "@mui/material/DialogTitle"; +import Dialog from "@mui/material/Dialog"; +import DialogContent from "@mui/material/DialogContent"; +import { NodeSelection } from "@tiptap/pm/state"; export type EditorProps = { label: string; @@ -29,6 +40,35 @@ export type EditorProps = { const emptyHtml = "

"; +type ModeCreation = { + mode: 0; +}; +const Creation: ModeCreation = { mode: 0 }; + +type ModeEdition = { + mode: 1; + infoUrl: string; + pdfUrl: string; + pdfSize: string; +}; +const Edition = ( + infoUrl: string, + pdfUrl: string, + pdfSize: string +): ModeEdition => ({ + mode: 1, + infoUrl, + pdfUrl, + pdfSize, +}); + +type ModeHide = { + mode: -1; +}; +const Hide: ModeHide = { mode: -1 }; + +type Mode = ModeEdition | ModeCreation | ModeHide; + export const Editor = ({ label, content, @@ -39,6 +79,8 @@ export const Editor = ({ const [currentContent, setCurrentContent] = useState(content); const [focus, setFocus] = useState(false); const [isClient, setIsClient] = useState(false); + const [infographicModal, setInfographicModal] = useState(Hide); + const editor = useEditor({ content, editable: !disabled, @@ -75,6 +117,7 @@ export const Editor = ({ }), Alert, Title, + Infographic, ], onUpdate: ({ editor }) => { const html = editor.getHTML(); @@ -98,6 +141,36 @@ export const Editor = ({ editor?.setOptions({ editable: !disabled }); }, [disabled]); + useEffect(() => { + // We need to focus on the infographic to edit it + const handleClick = (event: MouseEvent) => { + const target = event.target as HTMLElement; + + if ( + target.tagName === "IMG" && + target.closest(".infographic") && + editor + ) { + const pos = editor.view.posAtDOM( + target.closest(".infographic") as HTMLElement, + 0 + ); + + editor.view.dispatch( + editor.state.tr.setSelection( + NodeSelection.create(editor.state.doc, pos) + ) + ); + editor.commands.focus(); + } + }; + + document.addEventListener("click", handleClick); + return () => { + document.removeEventListener("click", handleClick); + }; + }, [editor]); + return ( <> {isClient && ( @@ -109,8 +182,28 @@ export const Editor = ({ htmlFor={label} > - + { + setInfographicModal(Creation); + }} + /> + { + const node = editor?.state.selection.$from.node(); + if (node?.type.name === "infographic") { + const src = node.attrs.src; + const dataPdf = node.attrs.urlPdf; + const dataPdfSize = node.attrs.pdfSize; + setInfographicModal(Edition(src, dataPdf, dataPdfSize)); + } + }} + onDelete={() => { + editor?.commands.removeInfographic(); + }} + /> )} + { + setInfographicModal(Hide); + }} + PaperProps={{ + component: "form", + onSubmit: (event: React.FormEvent) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const { infoUrl, pdfUrl, pdfSize } = Object.fromEntries( + (formData as any).entries() + ); + if (infographicModal.mode === Creation.mode) { + editor + ?.chain() + .focus() + .setInfographic(infoUrl, pdfUrl, pdfSize) + .run(); + } else { + editor?.commands.updateInfographicSrc(infoUrl, pdfUrl, pdfSize); + } + setInfographicModal(Hide); + }, + }} + > + Infographie + + + Veuillez renseigner les informations suivantes pour ajouter une + infographie au document + + + + + + + + + + ); }; @@ -170,6 +349,10 @@ const StyledEditorContent = styled(EditorContent)(() => { backgroundColor: fr.colors.decisions.background.contrast.info.active, borderRadius: "0.6rem", }, + ".infographic": { + marginBottom: "1.6rem", + color: fr.colors.decisions.text.default, + }, li: { p: { margin: "0", diff --git a/targets/frontend/src/components/forms/EditionField/MenuInfographic.tsx b/targets/frontend/src/components/forms/EditionField/MenuInfographic.tsx new file mode 100644 index 000000000..378093433 --- /dev/null +++ b/targets/frontend/src/components/forms/EditionField/MenuInfographic.tsx @@ -0,0 +1,60 @@ +import { Editor, FloatingMenu } from "@tiptap/react"; +import Delete from "@mui/icons-material/Delete"; +import EditIcon from "@mui/icons-material/Edit"; +import { styled } from "@mui/system"; + +export const MenuInfographic = ({ + editor, + onEdit, + onDelete, +}: { + editor: Editor | null; + onEdit: () => void; + onDelete: () => void; +}) => { + return editor ? ( + { + return ( + editor?.state.selection.$from.node()?.type.name === "infographic" && + state.selection.content().content.size > 0 && + editor.isActive("infographic") + ); + }} + > + + + + ) : ( + <> + ); +}; + +const InfographicFloatingMenu = styled(FloatingMenu)` + display: flex; + background-color: #0d0d0d; + padding: 0.2rem; + border-radius: 0.5rem; + + button { + border: none; + background: none; + font-size: 0.85rem; + font-weight: 500; + padding: 0 0.2rem; + opacity: 0.6; + color: #fff; + + &:hover, + &.is-active { + opacity: 1; + } + } +`; diff --git a/targets/frontend/src/components/forms/EditionField/MenuSpecial.tsx b/targets/frontend/src/components/forms/EditionField/MenuSpecial.tsx index c6b4f3ffe..3ae405b4d 100644 --- a/targets/frontend/src/components/forms/EditionField/MenuSpecial.tsx +++ b/targets/frontend/src/components/forms/EditionField/MenuSpecial.tsx @@ -6,10 +6,10 @@ import { } from "@tiptap/react"; import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; import FormatListNumberedIcon from "@mui/icons-material/FormatListNumbered"; +import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate"; import GridOnIcon from "@mui/icons-material/GridOn"; import StorageIcon from "@mui/icons-material/Storage"; import { styled } from "@mui/system"; -import InfoIcon from "@mui/icons-material/Info"; import { Node as ProseMirrorNode } from "@tiptap/pm/model"; const tableHTML = ` @@ -25,7 +25,13 @@ const tableHTML = ` `; -export const MenuSpecial = ({ editor }: { editor: Editor | null }) => { +export const MenuSpecial = ({ + editor, + onNewInfographic, +}: { + editor: Editor | null; + onNewInfographic: (editor: Editor) => void; +}) => { const getTextContent = (node: ProseMirrorNode) => { if (editor) { return getText(node, { @@ -110,13 +116,13 @@ export const MenuSpecial = ({ editor }: { editor: Editor | null }) => { ) : ( diff --git a/targets/frontend/src/components/forms/EditionField/extensions/Alert.ts b/targets/frontend/src/components/forms/EditionField/extensions/Alert.ts index 4e75bb67c..62d36314e 100644 --- a/targets/frontend/src/components/forms/EditionField/extensions/Alert.ts +++ b/targets/frontend/src/components/forms/EditionField/extensions/Alert.ts @@ -26,7 +26,7 @@ export const Alert = Node.create({ group: "block", parseHTML() { - return [{ tag: "div" }]; + return [{ tag: "div.alert" }]; }, renderHTML({ HTMLAttributes }) { diff --git a/targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts b/targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts new file mode 100644 index 000000000..1121fcb81 --- /dev/null +++ b/targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts @@ -0,0 +1,182 @@ +import { Node } from "@tiptap/core"; + +export interface InfographicOptions {} + +declare module "@tiptap/core" { + interface Commands { + infographic: { + setInfographic: ( + src: string, + urlPdf: string, + sizePdf: string + ) => ReturnType; + updateInfographicSrc: ( + newSrc: string, + newUrlPdf: string, + newSizePdf: string + ) => ReturnType; + removeInfographic: () => ReturnType; + }; + } +} + +export const Infographic = Node.create({ + name: "infographic", + draggable: true, + + addOptions() { + return { + HTMLAttributes: {}, + }; + }, + + addAttributes() { + return { + src: { + parseHTML: (element) => + element.querySelector("img")?.getAttribute("src"), + renderHTML: (attributes) => { + return { src: attributes.src }; + }, + }, + urlPdf: { + parseHTML: (element) => + element.querySelector("div.infographic")?.getAttribute("data-pdf"), + renderHTML: (attributes) => { + return { "data-pdf": attributes.urlPdf }; + }, + }, + pdfSize: { + parseHTML: (element) => + element + .querySelector("div.infographic") + ?.getAttribute("data-pdf-size"), + renderHTML: (attributes) => { + return { "data-pdf-size": attributes.pdfSize }; + }, + }, + }; + }, + + content: "block+", + + group: "block", + + parseHTML() { + return [ + { + tag: "div.infographic", + getAttrs: (element) => { + const el = element as HTMLElement; + return { + src: el.querySelector("img")?.getAttribute("src") || "", + urlPdf: el.getAttribute("data-pdf") || "", + pdfSize: el.getAttribute("data-pdf-size") || "", + }; + }, + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return [ + "div", + { + class: "infographic", + "data-pdf": HTMLAttributes["data-pdf"], + "data-pdf-size": HTMLAttributes["data-pdf-size"], + }, + [ + "img", + { + src: HTMLAttributes.src, + height: "auto", + width: "500", + }, + ], + ["div", {}, 0], + ]; + }, + + addCommands() { + return { + setInfographic: + (src: string, urlPdf: string, pdfSize: string) => + ({ commands }) => { + return commands.insertContent({ + type: this.name, + attrs: { src, urlPdf, pdfSize }, + content: [ + { + type: "details", + content: [ + { + type: "detailsSummary", + content: [ + { + type: "text", + text: "Afficher le contenu de l'infographie", + }, + ], + }, + { + type: "detailsContent", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Décrire ici le contenu de l'infographie", + }, + ], + }, + ], + }, + ], + }, + ], + }); + }, + + updateInfographicSrc: + (newSrc: string, newUrlPdf: string, newSizePdf: string) => + ({ state, chain }) => { + const { selection } = state; + const node = selection.$anchor.node(); + + if (node.type.name !== "infographic") { + return false; + } + return chain() + .updateAttributes("infographic", { + src: newSrc, + urlPdf: newUrlPdf, + pdfSize: newSizePdf, + }) + .run(); + }, + + removeInfographic: + () => + ({ state, dispatch }) => { + const { selection } = state; + const node = selection.$anchor.node(); + + if (node.type.name !== "infographic") { + return false; + } + + if (dispatch) { + const tr = state.tr.delete( + selection.$anchor.before(), + selection.$anchor.after() + ); + dispatch(tr); + } + + return true; + }, + }; + }, +}); diff --git a/targets/frontend/src/components/forms/EditionField/extensions/index.ts b/targets/frontend/src/components/forms/EditionField/extensions/index.ts index 7f9a73ca3..c0df52c3d 100644 --- a/targets/frontend/src/components/forms/EditionField/extensions/index.ts +++ b/targets/frontend/src/components/forms/EditionField/extensions/index.ts @@ -1,2 +1,3 @@ export * from "./Alert"; export * from "./Titles"; +export * from "./Infographic"; From b42b05cb495402030157b04a65af2022c7573eb8 Mon Sep 17 00:00:00 2001 From: Martial Maillot Date: Thu, 30 Jan 2025 09:50:06 +0100 Subject: [PATCH 2/2] feat(contributions): ajouter des infographies # Conflicts: # targets/frontend/src/components/forms/EditionField/MenuSpecial.tsx # Conflicts: # targets/frontend/src/components/forms/EditionField/Editor.tsx # targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts --- .../env/dev/templates/www.configmap.yaml | 3 - .kontinuous/env/dev/values.yaml | 3 + .../env/preprod/templates/www.configmap.yaml | 3 - .kontinuous/env/preprod/values.yaml | 3 + .../env/prod/templates/www.configmap.yaml | 3 - .kontinuous/env/prod/values.yaml | 3 + targets/export-elasticsearch/src/server.ts | 4 +- targets/frontend/Dockerfile | 6 ++ .../components/forms/EditionField/Editor.tsx | 57 +++++++++++-------- .../EditionField/extensions/Infographic.ts | 44 +++++++------- .../components/forms/EditionField/index.tsx | 2 + .../src/components/utils/fileBaseUrl.ts | 6 ++ .../frontend/src/components/utils/index.ts | 1 + targets/frontend/src/lib/upload.ts | 8 ++- 14 files changed, 88 insertions(+), 58 deletions(-) create mode 100644 targets/frontend/src/components/utils/fileBaseUrl.ts diff --git a/.kontinuous/env/dev/templates/www.configmap.yaml b/.kontinuous/env/dev/templates/www.configmap.yaml index f944770de..df8dcceff 100644 --- a/.kontinuous/env/dev/templates/www.configmap.yaml +++ b/.kontinuous/env/dev/templates/www.configmap.yaml @@ -7,6 +7,3 @@ data: MATOMO_URL: "https://matomo.fabrique.social.gouv.fr/" NODE_ENV: "production" URL_EXPORT: "http://export" - BUCKET_PUBLIC_ENDPOINT: "https://cdtn-dev-public.s3.gra.io.cloud.ovh.net" - BUCKET_DEFAULT_FOLDER: "default" - BUCKET_DRAFT_FOLDER: "draft" diff --git a/.kontinuous/env/dev/values.yaml b/.kontinuous/env/dev/values.yaml index 4ec6da5b2..5d6bbb78e 100644 --- a/.kontinuous/env/dev/values.yaml +++ b/.kontinuous/env/dev/values.yaml @@ -4,6 +4,9 @@ jobs: with: buildArgs: NEXT_PUBLIC_BASE_PATH: https://www-{{.Values.global.host}} + NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT: "https://cdtn-dev-public.s3.gra.io.cloud.ovh.net" + NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER: "default" + NEXT_PUBLIC_BUCKET_DRAFT_FOLDER: "draft" post-restore: ~needs: [ pg, hasura ] use: psql diff --git a/.kontinuous/env/preprod/templates/www.configmap.yaml b/.kontinuous/env/preprod/templates/www.configmap.yaml index f944770de..df8dcceff 100644 --- a/.kontinuous/env/preprod/templates/www.configmap.yaml +++ b/.kontinuous/env/preprod/templates/www.configmap.yaml @@ -7,6 +7,3 @@ data: MATOMO_URL: "https://matomo.fabrique.social.gouv.fr/" NODE_ENV: "production" URL_EXPORT: "http://export" - BUCKET_PUBLIC_ENDPOINT: "https://cdtn-dev-public.s3.gra.io.cloud.ovh.net" - BUCKET_DEFAULT_FOLDER: "default" - BUCKET_DRAFT_FOLDER: "draft" diff --git a/.kontinuous/env/preprod/values.yaml b/.kontinuous/env/preprod/values.yaml index f9b64d9e4..26a229461 100644 --- a/.kontinuous/env/preprod/values.yaml +++ b/.kontinuous/env/preprod/values.yaml @@ -4,6 +4,9 @@ jobs: with: buildArgs: NEXT_PUBLIC_BASE_PATH: https://cdtn-admin-preprod.ovh.fabrique.social.gouv.fr + NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT: "https://cdtn-dev-public.s3.gra.io.cloud.ovh.net" + NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER: "default" + NEXT_PUBLIC_BUCKET_DRAFT_FOLDER: "draft" post-restore: ~needs: [ pg, hasura ] use: psql diff --git a/.kontinuous/env/prod/templates/www.configmap.yaml b/.kontinuous/env/prod/templates/www.configmap.yaml index e13dd7931..afcbf08c2 100644 --- a/.kontinuous/env/prod/templates/www.configmap.yaml +++ b/.kontinuous/env/prod/templates/www.configmap.yaml @@ -9,6 +9,3 @@ data: NODE_ENV: "production" PRODUCTION: "true" URL_EXPORT: "http://export" - BUCKET_PUBLIC_ENDPOINT: "https://cdtn-prod-public.s3.gra.io.cloud.ovh.net" - BUCKET_DEFAULT_FOLDER: "default" - BUCKET_DRAFT_FOLDER: "draft" diff --git a/.kontinuous/env/prod/values.yaml b/.kontinuous/env/prod/values.yaml index ed6a32e7e..d01bc2d81 100644 --- a/.kontinuous/env/prod/values.yaml +++ b/.kontinuous/env/prod/values.yaml @@ -4,6 +4,9 @@ jobs: with: buildArgs: NEXT_PUBLIC_BASE_PATH: https://cdtn-admin.fabrique.social.gouv.fr + NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT: "https://cdtn-prod-public.s3.gra.io.cloud.ovh.net" + NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER: "default" + NEXT_PUBLIC_BUCKET_DRAFT_FOLDER: "draft" www: autoscale: diff --git a/targets/export-elasticsearch/src/server.ts b/targets/export-elasticsearch/src/server.ts index efd5c0292..283555c3d 100644 --- a/targets/export-elasticsearch/src/server.ts +++ b/targets/export-elasticsearch/src/server.ts @@ -44,7 +44,7 @@ rootContainer .toConstantValue(process.env.BUCKET_SECRET_KEY ?? ""); rootContainer .bind(S3Parameters.BUCKET_DRAFT_FOLDER) - .toConstantValue(process.env.BUCKET_DRAFT_FOLDER ?? `draft`); + .toConstantValue(process.env.NEXT_PUBLIC_BUCKET_DRAFT_FOLDER ?? `draft`); rootContainer .bind(S3Parameters.BUCKET_PUBLISHED_FOLDER) .toConstantValue(process.env.BUCKET_PUBLISHED_FOLDER ?? `published`); @@ -53,7 +53,7 @@ rootContainer .toConstantValue(process.env.BUCKET_PREVIEW_FOLDER ?? `preview`); rootContainer .bind(S3Parameters.BUCKET_DEFAULT_FOLDER) - .toConstantValue(process.env.BUCKET_DEFAULT_FOLDER ?? `default`); + .toConstantValue(process.env.NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER ?? `default`); /* REPOSITORIES */ rootContainer.bind(getName(S3Repository)).to(S3Repository); rootContainer diff --git a/targets/frontend/Dockerfile b/targets/frontend/Dockerfile index 0cd95fd16..a26f7384e 100644 --- a/targets/frontend/Dockerfile +++ b/targets/frontend/Dockerfile @@ -25,6 +25,12 @@ FROM deps as dist ARG NEXT_PUBLIC_BASE_PATH ENV NEXT_PUBLIC_BASE_PATH=$NEXT_PUBLIC_BASE_PATH +ARG NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT +ENV NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT=$NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT +ARG NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER +ENV NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER=$NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER +ARG NEXT_PUBLIC_BUCKET_DRAFT_FOLDER +ENV NEXT_PUBLIC_BUCKET_DRAFT_FOLDER=$NEXT_PUBLIC_BUCKET_DRAFT_FOLDER COPY --from=build-types /app/shared/types /app/shared/types COPY --from=build-utils /app/shared/utils /app/shared/utils diff --git a/targets/frontend/src/components/forms/EditionField/Editor.tsx b/targets/frontend/src/components/forms/EditionField/Editor.tsx index 2b638642a..4853688a8 100644 --- a/targets/frontend/src/components/forms/EditionField/Editor.tsx +++ b/targets/frontend/src/components/forms/EditionField/Editor.tsx @@ -36,6 +36,7 @@ export type EditorProps = { onUpdate: (content: string) => void; disabled?: boolean; isError?: boolean; + infographicBaseUrl: string; }; const emptyHtml = "

"; @@ -47,18 +48,18 @@ const Creation: ModeCreation = { mode: 0 }; type ModeEdition = { mode: 1; - infoUrl: string; - pdfUrl: string; + infoName: string; + pdfName: string; pdfSize: string; }; const Edition = ( - infoUrl: string, - pdfUrl: string, + infoName: string, + pdfName: string, pdfSize: string ): ModeEdition => ({ mode: 1, - infoUrl, - pdfUrl, + infoName, + pdfName, pdfSize, }); @@ -74,6 +75,7 @@ export const Editor = ({ content, onUpdate, disabled, + infographicBaseUrl, isError = false, }: EditorProps) => { const [currentContent, setCurrentContent] = useState(content); @@ -117,7 +119,9 @@ export const Editor = ({ }), Alert, Title, - Infographic, + Infographic.configure({ + baseUrl: infographicBaseUrl, + }), ], onUpdate: ({ editor }) => { const html = editor.getHTML(); @@ -194,10 +198,10 @@ export const Editor = ({ onEdit={() => { const node = editor?.state.selection.$from.node(); if (node?.type.name === "infographic") { - const src = node.attrs.src; - const dataPdf = node.attrs.urlPdf; + const dataInfo = node.attrs.infoName; + const dataPdf = node.attrs.pdfName; const dataPdfSize = node.attrs.pdfSize; - setInfographicModal(Edition(src, dataPdf, dataPdfSize)); + setInfographicModal(Edition(dataInfo, dataPdf, dataPdfSize)); } }} onDelete={() => { @@ -229,17 +233,17 @@ export const Editor = ({ onSubmit: (event: React.FormEvent) => { event.preventDefault(); const formData = new FormData(event.currentTarget); - const { infoUrl, pdfUrl, pdfSize } = Object.fromEntries( + const { infoName, pdfName, pdfSize } = Object.fromEntries( (formData as any).entries() ); if (infographicModal.mode === Creation.mode) { editor ?.chain() .focus() - .setInfographic(infoUrl, pdfUrl, pdfSize) + .setInfographic(infoName, pdfName, pdfSize) .run(); } else { - editor?.commands.updateInfographicSrc(infoUrl, pdfUrl, pdfSize); + editor?.commands.updateInfographicSrc(infoName, pdfName, pdfSize); } setInfographicModal(Hide); }, @@ -255,41 +259,46 @@ export const Editor = ({ autoFocus required margin="dense" - id="infoUrl" - name="infoUrl" - label="URL vers l'infographie" + id="infoName" + name="infoName" + label="Nom du fichier contenant l'image de l'infographie" defaultValue={ - infographicModal.mode === 1 ? infographicModal.infoUrl : undefined + infographicModal.mode === 1 + ? infographicModal.infoName + : undefined } type="text" fullWidth variant="standard" + helperText={"Exemple : MIN_Travail_Emploi_RVB.svg"} /> @@ -300,7 +309,7 @@ export const Editor = ({ > Annuler - diff --git a/targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts b/targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts index 1121fcb81..6f4fbcc1f 100644 --- a/targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts +++ b/targets/frontend/src/components/forms/EditionField/extensions/Infographic.ts @@ -1,19 +1,21 @@ import { Node } from "@tiptap/core"; -export interface InfographicOptions {} +export interface InfographicOptions { + baseUrl: string; +} declare module "@tiptap/core" { interface Commands { infographic: { setInfographic: ( - src: string, - urlPdf: string, + infoName: string, + pdfName: string, sizePdf: string ) => ReturnType; updateInfographicSrc: ( - newSrc: string, - newUrlPdf: string, - newSizePdf: string + newInfoName: string, + newPdfName: string, + newPdfSize: string ) => ReturnType; removeInfographic: () => ReturnType; }; @@ -27,23 +29,24 @@ export const Infographic = Node.create({ addOptions() { return { HTMLAttributes: {}, + baseUrl: "", }; }, addAttributes() { return { - src: { + infoName: { parseHTML: (element) => - element.querySelector("img")?.getAttribute("src"), + element.querySelector("img")?.getAttribute("data-infographic"), renderHTML: (attributes) => { - return { src: attributes.src }; + return { "data-infographic": attributes.infoName }; }, }, - urlPdf: { + pdfName: { parseHTML: (element) => element.querySelector("div.infographic")?.getAttribute("data-pdf"), renderHTML: (attributes) => { - return { "data-pdf": attributes.urlPdf }; + return { "data-pdf": attributes.pdfName }; }, }, pdfSize: { @@ -69,8 +72,8 @@ export const Infographic = Node.create({ getAttrs: (element) => { const el = element as HTMLElement; return { - src: el.querySelector("img")?.getAttribute("src") || "", - urlPdf: el.getAttribute("data-pdf") || "", + infoName: el.getAttribute("data-infographic") || "", + pdfName: el.getAttribute("data-pdf") || "", pdfSize: el.getAttribute("data-pdf-size") || "", }; }, @@ -85,11 +88,12 @@ export const Infographic = Node.create({ class: "infographic", "data-pdf": HTMLAttributes["data-pdf"], "data-pdf-size": HTMLAttributes["data-pdf-size"], + "data-infographic": HTMLAttributes["data-infographic"], }, [ "img", { - src: HTMLAttributes.src, + src: `${this.options.baseUrl}/${HTMLAttributes["data-infographic"]}`, height: "auto", width: "500", }, @@ -101,11 +105,11 @@ export const Infographic = Node.create({ addCommands() { return { setInfographic: - (src: string, urlPdf: string, pdfSize: string) => + (infoName: string, pdfName: string, pdfSize: string) => ({ commands }) => { return commands.insertContent({ type: this.name, - attrs: { src, urlPdf, pdfSize }, + attrs: { infoName, pdfName, pdfSize }, content: [ { type: "details", @@ -140,7 +144,7 @@ export const Infographic = Node.create({ }, updateInfographicSrc: - (newSrc: string, newUrlPdf: string, newSizePdf: string) => + (newInfoName: string, newPdfName: string, newPdfSize: string) => ({ state, chain }) => { const { selection } = state; const node = selection.$anchor.node(); @@ -150,9 +154,9 @@ export const Infographic = Node.create({ } return chain() .updateAttributes("infographic", { - src: newSrc, - urlPdf: newUrlPdf, - pdfSize: newSizePdf, + infoName: newInfoName, + pdfName: newPdfName, + pdfSize: newPdfSize, }) .run(); }, diff --git a/targets/frontend/src/components/forms/EditionField/index.tsx b/targets/frontend/src/components/forms/EditionField/index.tsx index a4166be53..bc3de90a6 100644 --- a/targets/frontend/src/components/forms/EditionField/index.tsx +++ b/targets/frontend/src/components/forms/EditionField/index.tsx @@ -5,6 +5,7 @@ import { Controller } from "react-hook-form"; import { CommonFormProps } from "../type"; import { Editor } from "./Editor"; import { styled } from "@mui/system"; +import { buildFilePathUrl } from "../../utils"; type FormEditionProps = CommonFormProps; @@ -21,6 +22,7 @@ export const FormEditionField = (props: FormEditionProps) => { onUpdate={onChange} content={value} disabled={props.disabled} + infographicBaseUrl={buildFilePathUrl()} isError={!!error} /> {error && ( diff --git a/targets/frontend/src/components/utils/fileBaseUrl.ts b/targets/frontend/src/components/utils/fileBaseUrl.ts new file mode 100644 index 000000000..5c75dfc99 --- /dev/null +++ b/targets/frontend/src/components/utils/fileBaseUrl.ts @@ -0,0 +1,6 @@ +export const buildFilePathUrl = () => + `${ + process.env.NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT ?? "http://localhost:9000" + }/${process.env.NEXT_PUBLIC_BUCKET_DRAFT_FOLDER ?? "draft"}/${ + process.env.NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER ?? "default" + }`; diff --git a/targets/frontend/src/components/utils/index.ts b/targets/frontend/src/components/utils/index.ts index 096d75dce..1e703cfc2 100644 --- a/targets/frontend/src/components/utils/index.ts +++ b/targets/frontend/src/components/utils/index.ts @@ -1,2 +1,3 @@ export * from "./Pagination"; export * from "./BreadcrumbLink"; +export * from "./fileBaseUrl"; diff --git a/targets/frontend/src/lib/upload.ts b/targets/frontend/src/lib/upload.ts index a51c08b5a..f4c134cba 100644 --- a/targets/frontend/src/lib/upload.ts +++ b/targets/frontend/src/lib/upload.ts @@ -10,12 +10,14 @@ const mime = require("mime-types"); const region = process.env.BUCKET_REGION ?? "us-east-1"; const endpoint = process.env.BUCKET_ENDPOINT ?? "http://localhost:9000"; const publicEndpoint = - process.env.BUCKET_PUBLIC_ENDPOINT ?? "http://localhost:9000"; + process.env.NEXT_PUBLIC_BUCKET_PUBLIC_ENDPOINT ?? "http://localhost:9000"; const accessKeyId = process.env.BUCKET_ACCESS_KEY ?? "MINIO_ACCESS_KEY"; const secretAccessKey = process.env.BUCKET_SECRET_KEY ?? "MINIO_SECRET_KEY"; const bucketName = process.env.BUCKET_NAME ?? "cdtn"; -const bucketDraftFolder = process.env.BUCKET_DRAFT_FOLDER ?? "draft"; -const bucketDefaultFolder = process.env.BUCKET_DEFAULT_FOLDER ?? "default"; +const bucketDraftFolder = + process.env.NEXT_PUBLIC_BUCKET_DRAFT_FOLDER ?? "draft"; +const bucketDefaultFolder = + process.env.NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER ?? "default"; const client = new S3Client({ region,