Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(contributions): ajouter des infographies #1544

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .kontinuous/env/dev/templates/www.configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
3 changes: 3 additions & 0 deletions .kontinuous/env/dev/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions .kontinuous/env/preprod/templates/www.configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
3 changes: 3 additions & 0 deletions .kontinuous/env/preprod/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions .kontinuous/env/prod/templates/www.configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
3 changes: 3 additions & 0 deletions .kontinuous/env/prod/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions targets/export-elasticsearch/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ rootContainer
.toConstantValue(process.env.BUCKET_SECRET_KEY ?? "");
rootContainer
.bind<string>(S3Parameters.BUCKET_DRAFT_FOLDER)
.toConstantValue(process.env.BUCKET_DRAFT_FOLDER ?? `draft`);
.toConstantValue(process.env.NEXT_PUBLIC_BUCKET_DRAFT_FOLDER ?? `draft`);
rootContainer
.bind<string>(S3Parameters.BUCKET_PUBLISHED_FOLDER)
.toConstantValue(process.env.BUCKET_PUBLISHED_FOLDER ?? `published`);
Expand All @@ -53,7 +53,7 @@ rootContainer
.toConstantValue(process.env.BUCKET_PREVIEW_FOLDER ?? `preview`);
rootContainer
.bind<string>(S3Parameters.BUCKET_DEFAULT_FOLDER)
.toConstantValue(process.env.BUCKET_DEFAULT_FOLDER ?? `default`);
.toConstantValue(process.env.NEXT_PUBLIC_BUCKET_DEFAULT_FOLDER ?? `default`);
/* REPOSITORIES */
rootContainer.bind<S3Repository>(getName(S3Repository)).to(S3Repository);
rootContainer
Expand Down
6 changes: 6 additions & 0 deletions targets/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARG NODE_VERSION=20.3.1-alpine

FROM node:$NODE_VERSION AS deps

Check failure on line 3 in targets/frontend/Dockerfile

View workflow job for this annotation

GitHub Actions / Lint Dockerfile

DL3026 error: Use only an allowed registry in the FROM image

WORKDIR /app

Expand All @@ -25,6 +25,12 @@

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
Expand All @@ -36,7 +42,7 @@
RUN yarn workspaces focus --production frontend && yarn cache clean
RUN mkdir -p targets/frontend/node_modules

FROM node:$NODE_VERSION

Check failure on line 45 in targets/frontend/Dockerfile

View workflow job for this annotation

GitHub Actions / Lint Dockerfile

DL3026 error: Use only an allowed registry in the FROM image

WORKDIR /app

Expand Down
193 changes: 191 additions & 2 deletions targets/frontend/src/components/forms/EditionField/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,72 @@ 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;
content?: string;
onUpdate: (content: string) => void;
disabled?: boolean;
isError?: boolean;
infographicBaseUrl: string;
};

const emptyHtml = "<p></p>";

type ModeCreation = {
mode: 0;
};
const Creation: ModeCreation = { mode: 0 };

type ModeEdition = {
mode: 1;
infoName: string;
pdfName: string;
pdfSize: string;
};
const Edition = (
infoName: string,
pdfName: string,
pdfSize: string
): ModeEdition => ({
mode: 1,
infoName,
pdfName,
pdfSize,
});

type ModeHide = {
mode: -1;
};
const Hide: ModeHide = { mode: -1 };

type Mode = ModeEdition | ModeCreation | ModeHide;

export const Editor = ({
label,
content,
onUpdate,
disabled,
infographicBaseUrl,
isError = false,
}: EditorProps) => {
const [currentContent, setCurrentContent] = useState(content);
const [focus, setFocus] = useState(false);
const [isClient, setIsClient] = useState(false);
const [infographicModal, setInfographicModal] = useState<Mode>(Hide);

const editor = useEditor({
content,
editable: !disabled,
Expand Down Expand Up @@ -75,6 +119,9 @@ export const Editor = ({
}),
Alert,
Title,
Infographic.configure({
baseUrl: infographicBaseUrl,
}),
],
onUpdate: ({ editor }) => {
const html = editor.getHTML();
Expand All @@ -98,6 +145,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 && (
Expand All @@ -109,8 +186,28 @@ export const Editor = ({
htmlFor={label}
>
<MenuStyle editor={editor} />
<MenuSpecial editor={editor} />
<MenuSpecial
editor={editor}
onNewInfographic={() => {
setInfographicModal(Creation);
}}
/>
<MenuTable editor={editor} />
<MenuInfographic
editor={editor}
onEdit={() => {
const node = editor?.state.selection.$from.node();
if (node?.type.name === "infographic") {
const dataInfo = node.attrs.infoName;
const dataPdf = node.attrs.pdfName;
const dataPdfSize = node.attrs.pdfSize;
setInfographicModal(Edition(dataInfo, dataPdf, dataPdfSize));
}
}}
onDelete={() => {
editor?.commands.removeInfographic();
}}
/>

<StyledEditorContent
editor={editor}
Expand All @@ -126,6 +223,94 @@ export const Editor = ({
/>
</TitleBox>
)}
<Dialog
open={infographicModal.mode !== Hide.mode}
onClose={() => {
setInfographicModal(Hide);
}}
PaperProps={{
component: "form",
onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const { infoName, pdfName, pdfSize } = Object.fromEntries(
(formData as any).entries()
);
if (infographicModal.mode === Creation.mode) {
editor
?.chain()
.focus()
.setInfographic(infoName, pdfName, pdfSize)
.run();
} else {
editor?.commands.updateInfographicSrc(infoName, pdfName, pdfSize);
}
setInfographicModal(Hide);
},
}}
>
<DialogTitle>Infographie</DialogTitle>
<DialogContent>
<DialogContentText>
Veuillez renseigner les informations suivantes pour ajouter une
infographie au document
</DialogContentText>
<TextField
autoFocus
required
margin="dense"
id="infoName"
name="infoName"
label="Nom de l'infographie"
defaultValue={
infographicModal.mode === 1
? infographicModal.infoName
: undefined
}
type="text"
fullWidth
variant="standard"
/>
<TextField
required
margin="dense"
id="pdfName"
name="pdfName"
label="Nom du PDF"
defaultValue={
infographicModal.mode === 1 ? infographicModal.pdfName : undefined
}
type="text"
fullWidth
variant="standard"
/>
<TextField
required
margin="dense"
id="pdfSize"
name="pdfSize"
label="Taille du PDF"
defaultValue={
infographicModal.mode === 1 ? infographicModal.pdfSize : undefined
}
type="text"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button
onClick={() => {
setInfographicModal(Hide);
}}
>
Annuler
</Button>
<Button type="submit">
{infographicModal.mode === Creation.mode ? "Ajouter" : "Modifier"}
</Button>
</DialogActions>
</Dialog>
</>
);
};
Expand Down Expand Up @@ -170,6 +355,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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 ? (
<InfographicFloatingMenu
className="floating-menu"
tippyOptions={{ duration: 100, maxWidth: 500 }}
editor={editor}
shouldShow={({ editor, state }) => {
return (
editor?.state.selection.$from.node()?.type.name === "infographic" &&
state.selection.content().content.size > 0 &&
editor.isActive("infographic")
);
}}
>
<button onClick={onEdit} type="button" title="Editer l'infographie">
<EditIcon width={24} fill="white" />
</button>
<button onClick={onDelete} type="button" title="Supprimer l'infographie">
<Delete />
</button>
</InfographicFloatingMenu>
) : (
<></>
);
};

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;
}
}
`;
Loading
Loading