Skip to content

Commit

Permalink
feat: add editorial content administration
Browse files Browse the repository at this point in the history
  • Loading branch information
UnbearableBear committed Dec 15, 2020
1 parent 7e804ca commit 3f8bc89
Show file tree
Hide file tree
Showing 17 changed files with 1,211 additions and 32 deletions.
2 changes: 2 additions & 0 deletions targets/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"http-proxy-middleware": "^1.0.6",
"isomorphic-unfetch": "^3.1.0",
"jsonwebtoken": "^8.5.1",
"lodash.get": "^4.4.2",
"memoizee": "^0.4.14",
"micromark": "^2.11.2",
"next": "^10.0.3",
"next-transpile-modules": "^6.0.0",
"next-urql": "^2.1.1",
Expand Down
1 change: 1 addition & 0 deletions targets/frontend/src/components/button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const defaultButtonStyles = {
borderWidth: 2,
cursor: "pointer",
display: "inline-flex",
fontFamily: "muli",
fontSize: "inherit",
fontWeight: "bold",
lineHeight: "inherit",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/** @jsx jsx */

import micromark from "micromark";
import PropTypes from "prop-types";
import { useState } from "react";
import { IoIosEye } from "react-icons/io";
import { Button } from "src/components/button";
import { Dialog } from "src/components/dialog";
import { Flex, jsx } from "theme-ui";

export const MarkdownPreviewModal = ({ markdown }) => {
const [showMarkdownPreview, setShowMarkdownPreview] = useState(false);
return (
<>
<Flex sx={{ justifyContent: "flex-end", mt: "xsmall" }}>
<Button
variant="secondary"
type="button"
size="small"
onClick={() => setShowMarkdownPreview(true)}
>
<IoIosEye
sx={{ height: "iconSmall", mr: "xsmall", width: "iconSmall" }}
/>
Prévisualiser le rendu du markdown
</Button>
</Flex>
<Dialog
isOpen={showMarkdownPreview}
onDismiss={() => setShowMarkdownPreview(false)}
aria-label="Prévisualiser le rendu"
sx={{
left: "50%",
margin: 0,
maxHeight: "90vh",
maxWidth: "50rem",
top: "50%",
transform: "translate(-50%, -50%)",
width: "90vw",
}}
>
<div
dangerouslySetInnerHTML={{
__html: micromark(markdown, "utf8", { allowDangerousHtml: true }),
}}
/>
</Dialog>
</>
);
};

MarkdownPreviewModal.propTypes = {
markdown: PropTypes.string.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
/** @jsx jsx */

import PropTypes from "prop-types";
import React, { useEffect, useState } from "react";
import { useWatch } from "react-hook-form";
import {
IoIosArrowDropdown,
IoIosArrowDropup,
IoIosReorder,
IoMdTrash,
} from "react-icons/io";
import { SortableElement, sortableHandle } from "react-sortable-hoc";
import { Button, IconButton } from "src/components/button";
import { ReferenceBlocks } from "src/components/editorialContent/ReferenceBlocks";
import { FormErrorMessage } from "src/components/forms/ErrorMessage";
import { Stack } from "src/components/layout/Stack";
import { MarkdownLink } from "src/components/MarkdownLink";
import { Container, Field, Flex, jsx, Label, Radio, Textarea } from "theme-ui";

import { MarkdownPreviewModal } from "./MarkdownPreviewModal";

export const TYPES = {
GRAPHIC: "graphic",
MARKDOWN: "markdown",
};

const DragHandle = sortableHandle(() => (
<IconButton
type="button"
variant="secondary"
sx={{ cursor: "grab", mt: "large" }}
>
<IoIosReorder
aria-label="Réordonner les sections"
style={{ height: "3rem", width: "3rem" }}
/>
</IconButton>
));

const RootSection = ({
block,
control,
errors,
blockIndex: index,
name,
numberOfBlocks,
register,
remove,
}) => {
const [isOpen, setOpen] = useState(!block.title || numberOfBlocks === 1);
const type = useWatch({
control,
defaultValue: block.type,
name: `${name}.type`,
});
const markdown = useWatch({
control,
defaultValue: "",
name: `${name}.markdown`,
});

useEffect(() => {
if (errors) {
setOpen(true);
}
}, [errors, setOpen]);

return (
<li sx={{ listStyleType: "none" }}>
<Container
key={block.key}
bg="highlight"
sx={{ flex: "1 0 auto", mb: "medium", p: "small" }}
>
<Stack>
<Flex
sx={{
alignItem: "flex-start",
justifyContent: "space-between",
}}
>
{numberOfBlocks > 1 && <DragHandle />}
<div sx={{ flex: "1 0 auto", mx: "small", my: "0" }}>
<Field
name={`${name}.title`}
label="Titre de la section"
defaultValue={block.title}
ref={register({
required: {
message: "Le titre de la section est requis",
value: true,
},
})}
/>
<FormErrorMessage errors={errors} fieldName="title" />
</div>
<div sx={{ mt: "medium" }}>
<Button
type="button"
variant="secondary"
size="small"
onClick={() => setOpen(!isOpen)}
>
{isOpen ? (
<IoIosArrowDropup
sx={{ height: "1.8rem", m: "0.25rem", width: "1.8rem" }}
/>
) : (
<IoIosArrowDropdown
sx={{ height: "1.8rem", m: "0.25rem", width: "1.8rem" }}
/>
)}
</Button>
</div>
</Flex>
<div sx={{ display: isOpen ? "block" : "none" }}>
<Stack>
<div>
<Flex sx={{ justifyContent: "flex-start" }}>
<Label
sx={{
alignItems: "center",
cursor: "pointer",
flex: "0 1 auto",
justifyContent: "flex-start",
mr: "large",
width: "auto",
}}
>
Mardown{" "}
<Radio
sx={{ ml: "xxsmall" }}
name={`${name}.type`}
value={TYPES.MARKDOWN}
defaultChecked={block.type === TYPES.MARKDOWN}
ref={register({
required: {
message: "Il faut choisir le type de section",
value: true,
},
})}
/>
</Label>
<Label
sx={{
alignItems: "center",
cursor: "pointer",
flex: "0 1 auto",
justifyContent: "flex-center",
width: "auto",
}}
>
Graphique{" "}
<Radio
sx={{ ml: "xxsmall" }}
name={`${name}.type`}
value={TYPES.GRAPHIC}
defaultChecked={block.type === TYPES.GRAPHIC}
ref={register({
required: {
message: "Il faut choisir le type de section",
value: true,
},
})}
/>
</Label>
{numberOfBlocks > 1 && (
<Flex sx={{ flex: "1 0 auto", justifyContent: "flex-end" }}>
<Button
type="button"
size="small"
onClick={() => remove(index)}
>
<IoMdTrash
sx={{
height: "iconSmall",
mr: "xsmall",
width: "iconSmall",
}}
/>
Supprimer cette section
</Button>
</Flex>
)}
</Flex>
<FormErrorMessage errors={errors} fieldName="type" />
</div>
{type === TYPES.GRAPHIC && (
<>
<div>
<Field
name={`${name}.imgUrl`}
label="Lien de l’image"
defaultValue={block.imgUrl}
ref={register({
required: {
message: "L’url de l’image est requise",
value: true,
},
})}
/>
<FormErrorMessage errors={errors} fieldName="imgUrl" />
</div>
<div>
<Field
name={`${name}.altText`}
label="Brève description de l’image"
defaultValue={block.altText}
ref={register({
required: {
message:
"La brève description de l’image est requise",
value: true,
},
})}
/>
<FormErrorMessage errors={errors} fieldName="altText" />
</div>
<div>
<Field
name={`${name}.fileUrl`}
label="Lien du pdf"
defaultValue={block.fileUrl}
ref={register({
required: {
message: "L’url du pdf est requise",
value: true,
},
})}
/>
<FormErrorMessage errors={errors} fieldName="fileUrl" />
</div>
<div>
<Field
name={`${name}.size`}
label="Taille du pdf"
defaultValue={block.size}
ref={register({
required: {
message: "La taille du pdf est requise",
value: true,
},
})}
/>
<FormErrorMessage errors={errors} fieldName="size" />
</div>
</>
)}
<div>
<Label htmlFor={"markdown"}>
Texte&nbsp;
<MarkdownLink />
</Label>
<Textarea
name={`${name}.markdown`}
id={`${name}.markdown`}
rows={10}
defaultValue={block.markdown}
ref={register({
required: {
message: "Ce champ est requis",
value: true,
},
})}
/>
<FormErrorMessage errors={errors} fieldName="markdown" />
{markdown && <MarkdownPreviewModal markdown={markdown} />}
</div>
<div>
<ReferenceBlocks
control={control}
register={register}
name={`${name}.references`}
errors={errors}
/>
</div>
</Stack>
</div>
</Stack>
</Container>
</li>
);
};

export const SortableSection = SortableElement(React.memo(RootSection));

RootSection.propTypes = {
block: PropTypes.object.isRequired,
blockIndex: PropTypes.number.isRequired,
control: PropTypes.object.isRequired,
errors: PropTypes.object,
name: PropTypes.string.isRequired,
numberOfBlocks: PropTypes.number.isRequired,
register: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
};
Loading

0 comments on commit 3f8bc89

Please sign in to comment.