218 lines
6.5 KiB
TypeScript
218 lines
6.5 KiB
TypeScript
import { Box, TextField, Typography, Paper } from "@mui/material";
|
||
import { Create } from "@refinedev/mui";
|
||
import { useForm } from "@refinedev/react-hook-form";
|
||
import { Controller, FieldValues } from "react-hook-form";
|
||
import React, { useState, useEffect } from "react";
|
||
import ReactMarkdown from "react-markdown";
|
||
import { MarkdownEditor } from "../../components/MarkdownEditor";
|
||
import "easymde/dist/easymde.min.css";
|
||
import { LanguageSelector } from "@ui";
|
||
import { observer } from "mobx-react-lite";
|
||
import { EVERY_LANGUAGE, Languages, languageStore, META_LANGUAGE } from "@stores";
|
||
|
||
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
||
|
||
export const ArticleCreate = observer(() => {
|
||
const { language, setLanguageAction } = languageStore;
|
||
const [articleData, setArticleData] = useState({
|
||
heading: EVERY_LANGUAGE(""),
|
||
body: EVERY_LANGUAGE("")
|
||
});
|
||
|
||
const {
|
||
saveButtonProps,
|
||
refineCore: { formLoading, onFinish },
|
||
register,
|
||
control,
|
||
watch,
|
||
formState: { errors },
|
||
setValue,
|
||
handleSubmit,
|
||
} = useForm({
|
||
refineCoreProps: {
|
||
resource: "article",
|
||
...META_LANGUAGE(language)
|
||
}
|
||
});
|
||
|
||
// Следим за изменениями в полях body и heading
|
||
const bodyContent = watch("body");
|
||
const headingContent = watch("heading");
|
||
|
||
function updateTranslations(update: boolean = true) {
|
||
const newArticleData = {
|
||
...articleData,
|
||
heading: {
|
||
...articleData.heading,
|
||
[language]: watch("heading") ?? "",
|
||
},
|
||
body: {
|
||
...articleData.body,
|
||
[language]: watch("body") ?? "",
|
||
}
|
||
}
|
||
if(update) setArticleData(newArticleData);
|
||
return newArticleData;
|
||
}
|
||
|
||
const handleFormSubmit = handleSubmit((values) => {
|
||
const newTranslations = updateTranslations(false);
|
||
return onFinish({
|
||
translations: newTranslations
|
||
});
|
||
});
|
||
|
||
useEffect(() => {
|
||
setValue("heading", articleData.heading[language] ?? "");
|
||
setValue("body", articleData.body[language] ?? "");
|
||
setPreview(articleData.body[language] ?? "");
|
||
setHeadingPreview(articleData.heading[language] ?? "");
|
||
}, [language, articleData, setValue]);
|
||
|
||
const handleLanguageChange = (lang: Languages) => {
|
||
updateTranslations();
|
||
setLanguageAction(lang);
|
||
};
|
||
|
||
const [preview, setPreview] = useState("");
|
||
const [headingPreview, setHeadingPreview] = useState("");
|
||
|
||
useEffect(() => {
|
||
setPreview(bodyContent ?? "");
|
||
}, [bodyContent]);
|
||
|
||
useEffect(() => {
|
||
setHeadingPreview(headingContent ?? "");
|
||
}, [headingContent]);
|
||
|
||
const simpleMDEOptions = React.useMemo(() => ({
|
||
placeholder: "Введите контент в формате Markdown...",
|
||
spellChecker: false,
|
||
}), []);
|
||
|
||
return (
|
||
<Create isLoading={formLoading} saveButtonProps={{
|
||
onClick: handleFormSubmit
|
||
}}>
|
||
<Box sx={{ display: "flex", flex: 1, gap: 2 }}>
|
||
{/* Форма создания */}
|
||
<Box sx={{ display: "flex", flex: 1, flexDirection: "column", gap: 2 }}>
|
||
<LanguageSelector action={handleLanguageChange} />
|
||
<Box
|
||
component="form"
|
||
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||
autoComplete="off"
|
||
>
|
||
<TextField
|
||
{...register("heading", {
|
||
required: "Это поле является обязательным",
|
||
})}
|
||
error={!!(errors as any)?.heading}
|
||
helperText={(errors as any)?.heading?.message}
|
||
margin="normal"
|
||
fullWidth
|
||
slotProps={{inputLabel: {shrink: true}}}
|
||
type="text"
|
||
label="Заголовок *"
|
||
name="heading"
|
||
/>
|
||
|
||
<Controller
|
||
control={control}
|
||
name="body"
|
||
//rules={{ required: "Это поле является обязательным" }}
|
||
defaultValue=""
|
||
render={({ field: { onChange, value } }) => (
|
||
<MemoizedSimpleMDE
|
||
value={value}
|
||
onChange={onChange}
|
||
options={simpleMDEOptions}
|
||
className="my-markdown-editor"
|
||
/>
|
||
)}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* Блок предпросмотра */}
|
||
<Paper
|
||
sx={{
|
||
flex: 1,
|
||
p: 2,
|
||
maxHeight: "calc(100vh - 200px)",
|
||
overflowY: "auto",
|
||
position: "sticky",
|
||
top: 16,
|
||
borderRadius: 2,
|
||
border: "1px solid",
|
||
borderColor: "primary.main",
|
||
bgcolor: (theme) =>
|
||
theme.palette.mode === "dark" ? "background.paper" : "#fff",
|
||
}}
|
||
>
|
||
<Typography variant="h6" gutterBottom color="primary">
|
||
Предпросмотр
|
||
</Typography>
|
||
|
||
{/* Заголовок статьи */}
|
||
<Typography
|
||
variant="h4"
|
||
gutterBottom
|
||
sx={{
|
||
color: (theme) =>
|
||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||
mb: 3,
|
||
}}
|
||
>
|
||
{headingPreview}
|
||
</Typography>
|
||
|
||
{/* Markdown контент */}
|
||
<Box
|
||
sx={{
|
||
"& img": {
|
||
maxWidth: "100%",
|
||
height: "auto",
|
||
borderRadius: 1,
|
||
},
|
||
"& h1, & h2, & h3, & h4, & h5, & h6": {
|
||
color: "primary.main",
|
||
mt: 2,
|
||
mb: 1,
|
||
},
|
||
"& p": {
|
||
mb: 2,
|
||
color: (theme) =>
|
||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||
},
|
||
"& a": {
|
||
color: "primary.main",
|
||
textDecoration: "none",
|
||
"&:hover": {
|
||
textDecoration: "underline",
|
||
},
|
||
},
|
||
"& blockquote": {
|
||
borderLeft: "4px solid",
|
||
borderColor: "primary.main",
|
||
pl: 2,
|
||
my: 2,
|
||
color: "text.secondary",
|
||
},
|
||
"& code": {
|
||
bgcolor: (theme) =>
|
||
theme.palette.mode === "dark" ? "grey.900" : "grey.100",
|
||
p: 0.5,
|
||
borderRadius: 0.5,
|
||
color: "primary.main",
|
||
},
|
||
}}
|
||
>
|
||
<ReactMarkdown>{preview}</ReactMarkdown>
|
||
</Box>
|
||
</Paper>
|
||
</Box>
|
||
</Create>
|
||
);
|
||
});
|