import { Box, TextField, Typography, Paper } from "@mui/material"; import { Edit } from "@refinedev/mui"; import { useForm } from "@refinedev/react-hook-form"; import { Controller, FieldValues } from "react-hook-form"; import { useParams } from "react-router"; import React, { useState, useEffect, useMemo } from "react"; import ReactMarkdown from "react-markdown"; import { useList } from "@refinedev/core"; import { MarkdownEditor, LinkedItems } from "@components"; import { MediaItem, mediaFields } from "./types"; import "easymde/dist/easymde.min.css"; import { EVERY_LANGUAGE, Languages, languageStore, META_LANGUAGE } from "@stores"; import { observer } from "mobx-react-lite"; import { LanguageSelector, MediaView } from "@ui"; const MemoizedSimpleMDE = React.memo(MarkdownEditor); export const ArticleEdit = observer(() => { const { language, setLanguageAction } = languageStore; const [articleData, setArticleData] = useState({ heading: EVERY_LANGUAGE(""), body: EVERY_LANGUAGE("") }); const { id: articleId } = useParams<{ id: string }>(); const [preview, setPreview] = useState(""); const [headingPreview, setHeadingPreview] = useState(""); const simpleMDEOptions = useMemo( () => ({ placeholder: "Введите контент в формате Markdown...", spellChecker: false, }), [] ); const { saveButtonProps, refineCore: { onFinish }, register, control, handleSubmit, watch, formState: { errors }, setValue, } = useForm<{ heading: string; body: string }>({ refineCoreProps: META_LANGUAGE(language) }); const bodyContent = watch("body"); const headingContent = watch("heading"); useEffect(() => { setValue("heading", articleData.heading[language] ?? ""); setHeadingPreview(articleData.heading[language] ?? ""); setValue("body", articleData.body[language] ?? ""); setPreview(articleData.body[language] ?? ""); }, [language, articleData, setValue]); 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 handleLanguageChange = (lang: Languages) => { updateTranslations(); setLanguageAction(lang); }; const handleFormSubmit = handleSubmit((values: FieldValues) => { const newTranslations = updateTranslations(false); console.log(newTranslations); return onFinish({ translations: newTranslations }); }); useEffect(() => { setPreview(bodyContent ?? ""); }, [bodyContent]); useEffect(() => { setHeadingPreview(headingContent ?? ""); }, [headingContent]); const { data: mediaData } = useList<MediaItem>({ resource: `article/${articleId}/media`, }); useEffect(() => { return () => { setLanguageAction("ru"); }; }, [setLanguageAction]); return ( <Edit saveButtonProps={{ ...saveButtonProps, onClick: handleFormSubmit }} > <Box sx={{ display: "flex", 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?.heading} helperText={errors?.heading?.message as string} 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} // markdown onChange={onChange} options={simpleMDEOptions} className="my-markdown-editor" /> )} /> {articleId && ( <LinkedItems<MediaItem> type="edit" parentId={articleId} parentResource="article" childResource="media" fields={mediaFields} title="медиа" /> )} </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> {/* Привязанные медиа */} {mediaData?.data && mediaData.data.length > 0 && ( <Box sx={{ mb: 3 }}> <Typography variant="subtitle1" gutterBottom color="primary"> Привязанные медиа: </Typography> <Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", mb: 2, }} > {mediaData.data.map((media) => ( <Box key={media.id} sx={{ width: 120, height: 120, borderRadius: 1, overflow: "hidden", border: "1px solid", borderColor: "primary.main", }} > <MediaView media={media} /> {/* <img src={`${import.meta.env.VITE_KRBL_MEDIA}${ media.id }/download?token=${localStorage.getItem(TOKEN_KEY)}`} alt={media.media_name} style={{ width: "100%", height: "100%", objectFit: "cover", }} /> */} </Box> ))} </Box> </Box> )} </Paper> </Box> </Edit> ); });