277 lines
8.0 KiB
TypeScript
277 lines
8.0 KiB
TypeScript
import { Box, TextField, Typography, Paper } from "@mui/material";
|
||
import { Create } from "@refinedev/mui";
|
||
import { useForm } from "@refinedev/react-hook-form";
|
||
import { Controller } from "react-hook-form";
|
||
import React, { useState, useEffect } from "react";
|
||
import ReactMarkdown from "react-markdown";
|
||
import Cookies from "js-cookie";
|
||
import { MarkdownEditor } from "../../components/MarkdownEditor";
|
||
import "easymde/dist/easymde.min.css";
|
||
|
||
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
||
|
||
export const ArticleCreate = () => {
|
||
const [language, setLanguage] = useState(Cookies.get("lang")!);
|
||
const [articleData, setArticleData] = useState<{
|
||
ru: { heading: string; body: string };
|
||
en: { heading: string; body: string };
|
||
zh: { heading: string; body: string };
|
||
}>({
|
||
ru: { heading: "", body: "" },
|
||
en: { heading: "", body: "" },
|
||
zh: { heading: "", body: "" },
|
||
});
|
||
|
||
const {
|
||
saveButtonProps,
|
||
refineCore: { formLoading },
|
||
register,
|
||
control,
|
||
watch,
|
||
formState: { errors },
|
||
setValue,
|
||
} = useForm({
|
||
refineCoreProps: {
|
||
resource: "article/",
|
||
meta: {
|
||
headers: {
|
||
"Accept-Language": language,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
useEffect(() => {
|
||
const lang = Cookies.get("lang")!;
|
||
Cookies.set("lang", language);
|
||
return () => {
|
||
Cookies.set("lang", lang);
|
||
};
|
||
}, [language]);
|
||
|
||
useEffect(() => {
|
||
setValue(
|
||
"heading",
|
||
articleData[language as keyof typeof articleData]?.heading || ""
|
||
);
|
||
setValue(
|
||
"body",
|
||
articleData[language as keyof typeof articleData]?.body || ""
|
||
);
|
||
setPreview(articleData[language as keyof typeof articleData]?.body || "");
|
||
setHeadingPreview(
|
||
articleData[language as keyof typeof articleData]?.heading || ""
|
||
);
|
||
}, [language, articleData, setValue]);
|
||
|
||
const handleLanguageChange = (lang: string) => {
|
||
setArticleData((prevData) => ({
|
||
...prevData,
|
||
[language]: {
|
||
heading: watch("heading") || "",
|
||
body: watch("body") || "",
|
||
},
|
||
}));
|
||
setLanguage(lang);
|
||
Cookies.set("lang", lang);
|
||
};
|
||
|
||
const [preview, setPreview] = useState("");
|
||
const [headingPreview, setHeadingPreview] = useState("");
|
||
|
||
// Следим за изменениями в полях body и heading
|
||
const bodyContent = watch("body");
|
||
const headingContent = watch("heading");
|
||
|
||
useEffect(() => {
|
||
setPreview(bodyContent || "");
|
||
}, [bodyContent]);
|
||
|
||
useEffect(() => {
|
||
setHeadingPreview(headingContent || "");
|
||
}, [headingContent]);
|
||
|
||
const simpleMDEOptions = React.useMemo(
|
||
() => ({
|
||
placeholder: "Введите контент в формате Markdown...",
|
||
spellChecker: false,
|
||
}),
|
||
[]
|
||
);
|
||
|
||
return (
|
||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||
<Box sx={{ display: "flex", flex: 1, gap: 2 }}>
|
||
{/* Форма создания */}
|
||
<Box sx={{ display: "flex", flex: 1, flexDirection: "column", gap: 2 }}>
|
||
<Box
|
||
sx={{
|
||
flex: 1,
|
||
display: "flex",
|
||
gap: 2,
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
cursor: "pointer",
|
||
flex: 1,
|
||
display: "flex",
|
||
justifyContent: "center",
|
||
bgcolor: language === "ru" ? "primary.main" : "transparent",
|
||
color: language === "ru" ? "white" : "inherit",
|
||
p: 1,
|
||
borderRadius: 1,
|
||
}}
|
||
onClick={() => handleLanguageChange("ru")}
|
||
>
|
||
RU
|
||
</Box>
|
||
<Box
|
||
sx={{
|
||
cursor: "pointer",
|
||
flex: 1,
|
||
display: "flex",
|
||
justifyContent: "center",
|
||
bgcolor: language === "en" ? "primary.main" : "transparent",
|
||
color: language === "en" ? "white" : "inherit",
|
||
p: 1,
|
||
borderRadius: 1,
|
||
}}
|
||
onClick={() => handleLanguageChange("en")}
|
||
>
|
||
EN
|
||
</Box>
|
||
<Box
|
||
sx={{
|
||
cursor: "pointer",
|
||
flex: 1,
|
||
display: "flex",
|
||
justifyContent: "center",
|
||
bgcolor: language === "zh" ? "primary.main" : "transparent",
|
||
color: language === "zh" ? "white" : "inherit",
|
||
p: 1,
|
||
borderRadius: 1,
|
||
}}
|
||
onClick={() => handleLanguageChange("zh")}
|
||
>
|
||
ZH
|
||
</Box>
|
||
</Box>
|
||
<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
|
||
InputLabelProps={{ 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>
|
||
);
|
||
};
|