Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
lsagetlethias committed Jan 3, 2025
1 parent db3ed41 commit 7dca9c1
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,113 +2,22 @@

import { fr } from "@codegouvfr/react-dsfr";
import Button from "@codegouvfr/react-dsfr/Button";
import ButtonsGroup from "@codegouvfr/react-dsfr/ButtonsGroup";
import Input from "@codegouvfr/react-dsfr/Input";
import { useIsDark } from "@codegouvfr/react-dsfr/useIsDark";
import { Editor, loader } from "@monaco-editor/react";
import * as monaco from "monaco-editor";
import { conf as mdxConf, language as mdxLangage } from "monaco-editor/esm/vs/basic-languages/mdx/mdx";
import { zodResolver } from "@hookform/resolvers/zod";
import { Editor } from "@monaco-editor/react";
import { Fragment, type ReactElement, useEffect, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";

import { Grid, GridCol } from "@/dsfr";
import { mdxRenderer } from "@/lib/mdx/renderer";
import { frHexColor } from "@/utils/dsfrHexColors";
import { frHexColorDark } from "@/utils/dsfrHexDarkColors";
import { RecapCard } from "@/dsfr/base/RecapCard";
import { CUSTOM_LANGUAGE_ID, getCustomThemeID, preloadMonacoReact } from "@/lib/client/monaco";

import style from "./MdxEditor.module.scss";

loader.config({
monaco,
});

void loader.init().then(monacoInstance => {
monacoInstance.languages.register({ id: "mdx-mustache" });
monacoInstance.languages.setMonarchTokensProvider("mdx-mustache", {
...mdxLangage,
tokenizer: {
...mdxLangage.tokenizer,
// redefine the content rule to add mustache-variable
content: [
[
/\{\{/,
{
token: "mustache-bracket",
bracket: "@open",
next: "@mustache-variable",
},
],
[/(\[)(.+)(]\()(.+)(\s+".*")(\))/, ["", "string.link", "", "type.identifier", "string.link", ""]],
[/(\[)(.+)(]\()(.+)(\))/, ["", "type.identifier", "", "string.link", ""]],
[/(\[)(.+)(]\[)(.+)(])/, ["", "type.identifier", "", "type.identifier", ""]],
[/(\[)(.+)(]:\s+)(\S*)/, ["", "type.identifier", "", "string.link"]],
[/(\[)(.+)(])/, ["", "type.identifier", ""]],
[/`.*`/, "variable.source"],
[/_/, { token: "emphasis", next: "@emphasis_underscore" }],
[/\*(?!\*)/, { token: "emphasis", next: "@emphasis_asterisk" }],
[/\*\*/, { token: "strong", next: "@strong" }],
],
"mustache-variable": [
[/\s*([\w.]+)\s*/, "mustache-variable"],
[
/\}\}/,
{
token: "mustache-bracket",
bracket: "@close",
next: "@pop",
},
],
],
},
});
monacoInstance.languages.setLanguageConfiguration("mdx-mustache", {
...mdxConf,
brackets: [
["[", "]"],
["(", ")"],
],
});

const textDefaultInfoDark = frHexColorDark.decisions.artwork.major.pinkTuile.default;
const backgroundContrastInfoDark = frHexColorDark.decisions.background.contrast.pinkTuile.default;
const textDefaultInfoLight = frHexColor.decisions.artwork.major.pinkTuile.default;
const backgroundContrastInfoLight = frHexColor.decisions.background.contrast.pinkTuile.default;
const bracketColor = frHexColor.decisions.border.default.brownCafeCreme.default;

monacoInstance.editor.defineTheme("mdx-mustache-theme-dark", {
base: "vs-dark", // Hérite du thème sombre par défaut
inherit: true,
colors: {
"editor.background": frHexColorDark.decisions.background.raised.grey.default,
},
rules: [
{ token: "mustache-variable", foreground: textDefaultInfoDark, background: backgroundContrastInfoDark },
{ token: "mustache-bracket", foreground: bracketColor, background: backgroundContrastInfoDark },
],
});

monacoInstance.editor.defineTheme("mdx-mustache-theme-light", {
base: "vs", // Hérite du thème clair par défaut
inherit: true,
colors: {
"editor.background": frHexColor.decisions.background.raised.grey.default,
},
rules: [
{
token: "mustache-variable",
foreground: textDefaultInfoLight,
background: backgroundContrastInfoLight,
fontStyle: "bold underline",
},
{
token: "mustache-bracket",
foreground: bracketColor,
background: backgroundContrastInfoLight,
fontStyle: "bold underline",
},
],
});
});
void preloadMonacoReact();

const mdxEditorFormSchema = z.object({
variables: z
Expand All @@ -134,7 +43,6 @@ type MdxEditorFormType = z.infer<typeof mdxEditorFormSchema>;
export const MdxEditor = ({ defaultValue }: { defaultValue: string }) => {
const { isDark } = useIsDark();
const [mdxSource, setMdxSource] = useState<string>(defaultValue);
const [frontmatter, setFrontmatter] = useState<Record<string, unknown>>({});
const [content, setContent] = useState<ReactElement | null>(null);

const {
Expand All @@ -143,7 +51,12 @@ export const MdxEditor = ({ defaultValue }: { defaultValue: string }) => {
watch,
handleSubmit,
control,
} = useForm<MdxEditorFormType>();
trigger,
setValue,
} = useForm<MdxEditorFormType>({
mode: "onChange",
resolver: zodResolver(mdxEditorFormSchema),
});

const {
fields: varFields,
Expand All @@ -155,79 +68,115 @@ export const MdxEditor = ({ defaultValue }: { defaultValue: string }) => {
});

function handleEditorChange(value?: string) {
value && setMdxSource(value);
if (value === undefined) return;
setMdxSource(value);
setValue("mdxContent", value, {
shouldTouch: true,
shouldValidate: true,
});
}

useEffect(() => {
void (async () => {
const { content, frontmatter } = await mdxRenderer(mdxSource, {});
setFrontmatter(frontmatter);
setContent(content);
// const { content, frontmatter } = await mdxRenderer(mdxSource, {});
// setFrontmatter(frontmatter);
// setContent(content);
})();
}, [mdxSource]);

return (
<form onSubmit={void handleSubmit(data => console.log(data))}>
<form noValidate onSubmit={e => void handleSubmit(data => console.log(data))(e)}>
<ButtonsGroup
inlineLayoutWhen="sm and up"
buttonsEquisized
buttons={[
{
children: "Retour",
linkProps: {
href: "/template",
},
iconId: "fr-icon-arrow-left-line",
iconPosition: "left",
priority: "secondary",
},
{
children: "Sauvegarder",
nativeButtonProps: {
type: "submit",
disabled: !isValid,
},
iconId: "fr-icon-save-line",
iconPosition: "right",
priority: "primary",
},
]}
/>
<Grid haveGutters className={style.editor}>
<GridCol base={2} px="3w">
<Button
size="small"
priority="secondary"
iconId="fr-icon-add-line"
type="button"
onClick={() =>
appendVar(
{ name: "", description: "" },
{ shouldFocus: true, focusName: `variables.${varFields.length}.name` },
)
<GridCol base={2} className="overflow-auto h-full">
<RecapCard
title="Variables"
sideButtonProps={{
title: "Ajouter une variable",
size: "small",
priority: "secondary",
iconId: "fr-icon-add-line",
type: "button",
onClick: () =>
appendVar(
{ name: "", description: "" },
{ shouldFocus: true, focusName: `variables.${varFields.length}.name` },
),
}}
content={
<>
Définissez les variables utilisées dans le contenu
{varFields.map((field, index) => {
const name = `variables.${index}.name` as const;
const description = `variables.${index}.description` as const;
return (
<Fragment key={field.id}>
<hr className={fr.cx("fr-mt-2w", "fr-pb-1w")} />
<Input
nativeInputProps={{
...register(name),
placeholder: `Nom ${index + 1}`,
}}
hideLabel
label={`Nom variable ${index + 1}`}
action={
<Button
iconId="fr-icon-delete-line"
type="button"
priority="secondary"
title="Supprimer la variable"
onClick={() => removeVar(index)}
/>
}
/>
<Input
textArea
classes={{
nativeInputOrTextArea: "resize-y",
}}
nativeTextAreaProps={{
...register(description),
placeholder: `Description ${index + 1}`,
}}
hideLabel
label={`Description variable ${index + 1}`}
/>
</Fragment>
);
})}
</>
}
>
Ajouter une variable
</Button>
{varFields.map((field, index) => {
const name = `variables.${index}.name` as const;
const description = `variables.${index}.description` as const;
return (
<Fragment key={field.id}>
<hr className={fr.cx("fr-mt-2w", "fr-pb-1w")} />
<Input
nativeInputProps={{
...register(name),
placeholder: `Nom ${index + 1}`,
}}
hideLabel
label={`Nom variable ${index + 1}`}
action={
<Button
iconId="fr-icon-delete-line"
type="button"
priority="secondary"
title="Supprimer la variable"
onClick={() => removeVar(index)}
/>
}
/>
<Input
textArea
classes={{
nativeInputOrTextArea: "resize-y",
}}
nativeTextAreaProps={{
...register(description),
placeholder: `Description ${index + 1}`,
}}
hideLabel
label={`Description variable ${index + 1}`}
/>
</Fragment>
);
})}
/>
</GridCol>
<GridCol base={5}>
<Editor
defaultValue={defaultValue}
language="mdx-mustache"
theme={`mdx-mustache-theme-${isDark ? "dark" : "light"}`}
language={CUSTOM_LANGUAGE_ID}
theme={getCustomThemeID(isDark)}
onChange={handleEditorChange}
options={{
automaticLayout: true,
Expand All @@ -238,8 +187,30 @@ export const MdxEditor = ({ defaultValue }: { defaultValue: string }) => {
/>
</GridCol>
<GridCol base={5} className="overflow-auto h-full">
<code>{JSON.stringify({ frontmatter })}</code>
{content}
<RecapCard
title="Aperçu"
content={
<>
{/* {content} */}
<h1>superlongtitleazlkelzalalkkleazlzalkakleekllkeazklelka</h1>
<br />
Fake
<br />
Fake
<br />
Fake
<br />
Fake
<br />
Fake
<br />
Fake
<br />
Fake
<br />
</>
}
/>
</GridCol>
</Grid>
</form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { ClientOnly } from "@/components/utils/ClientOnly";
import { Container } from "@/dsfr";
import { gitRepo } from "@/lib/repo";
import { type GitSha7, type TemplateType } from "@/lib/repo/IGitRepo";
import { mdxService } from "@/lib/services";
import { GetTemplateWithRawContent } from "@/useCases/GetTemplateWithRawContent";

import { MdxEditor } from "./MdxEditor";

interface Params {
groupId: string;
sha: string;
type: string;
sha: GitSha7;
type: TemplateType;
}

interface Props {
Expand All @@ -15,15 +19,13 @@ interface Props {

const TemplateEdit = async ({ params }: Props) => {
const { groupId, sha, type } = await params;
const rawTemplate = await fetch(
`https://raw.githubusercontent.com/incubateur-ademe/legal-site-templates-test/${sha}/templates/${groupId}/${type}.md`,
);
const template = await rawTemplate.text();
const useCase = new GetTemplateWithRawContent(mdxService, gitRepo);
const { raw, template } = await useCase.execute({ groupId, templateId: sha, type });

return (
<Container ptmd="14v" mbmd="14v" className="min-h-64 max-h-full" fluid>
<Container className="min-h-64 max-h-full" ptmd="14v" mbmd="14v" size="md" fluid mx="3w">
<ClientOnly>
<MdxEditor defaultValue={template} />
<MdxEditor defaultValue={raw} />
</ClientOnly>
</Container>
);
Expand Down
Loading

0 comments on commit 7dca9c1

Please sign in to comment.