| | import { useState, useLayoutEffect, useEffect, useRef, memo } from "react"; |
| | import { useTranslation } from "react-i18next"; |
| | import { MagicdownEditor } from "@xiangfa/mdeditor"; |
| | import View from "./View"; |
| | import { |
| | ResizableHandle, |
| | ResizablePanel, |
| | ResizablePanelGroup, |
| | } from "@/components/ui/resizable"; |
| | import { useMobile } from "@/hooks/useMobile"; |
| | import { cn } from "@/utils/style"; |
| |
|
| | import "./style.css"; |
| |
|
| | type EditorProps = { |
| | className?: string; |
| | defaultValue: string; |
| | hideView?: boolean; |
| | onChange: (value: string) => void; |
| | }; |
| |
|
| | function Editor({ |
| | className, |
| | defaultValue, |
| | onChange, |
| | hideView = false, |
| | }: EditorProps) { |
| | const { t } = useTranslation(); |
| | const isMobile = useMobile(750); |
| | const markdownEditorRef = useRef<HTMLDivElement>(null); |
| | const [markdownEditor, setMarkdownEditor] = useState<MagicdownEditor>(); |
| | const [content, setContent] = useState<string>(defaultValue); |
| |
|
| | |
| | useEffect(() => { |
| | if (markdownEditor?.status === "created") { |
| | markdownEditor.update(defaultValue); |
| | } |
| | }, [markdownEditor, defaultValue]); |
| |
|
| | |
| | useLayoutEffect(() => { |
| | const editor = new MagicdownEditor({ |
| | root: markdownEditorRef.current, |
| | defaultValue: "", |
| | placeholder: t("editor.placeholder"), |
| | onChange: (value) => { |
| | setContent(value); |
| | }, |
| | i18n: { |
| | tooltip: { |
| | bold: t("editor.tooltip.bold"), |
| | italic: t("editor.tooltip.italic"), |
| | strikethrough: t("editor.tooltip.strikethrough"), |
| | link: t("editor.tooltip.link"), |
| | code: t("editor.tooltip.code"), |
| | math: t("editor.tooltip.math"), |
| | quote: t("editor.tooltip.quote"), |
| | }, |
| | slash: { |
| | heading: { |
| | name: t("editor.slash.heading.name"), |
| | description: t("editor.slash.heading.description"), |
| | }, |
| | h1: { |
| | name: t("editor.slash.h1.name"), |
| | description: t("editor.slash.h1.description"), |
| | }, |
| | h2: { |
| | name: t("editor.slash.h2.name"), |
| | description: t("editor.slash.h2.description"), |
| | }, |
| | h3: { |
| | name: t("editor.slash.h3.name"), |
| | description: t("editor.slash.h3.description"), |
| | }, |
| | h4: { |
| | name: t("editor.slash.h4.name"), |
| | description: t("editor.slash.h4.description"), |
| | }, |
| | h5: { |
| | name: t("editor.slash.h5.name"), |
| | description: t("editor.slash.h5.description"), |
| | }, |
| | h6: { |
| | name: t("editor.slash.h6.name"), |
| | description: t("editor.slash.h6.description"), |
| | }, |
| | list: { |
| | name: t("editor.slash.list.name"), |
| | description: t("editor.slash.list.description"), |
| | }, |
| | ul: { |
| | name: t("editor.slash.ul.name"), |
| | description: t("editor.slash.ul.description"), |
| | }, |
| | ol: { |
| | name: t("editor.slash.ol.name"), |
| | description: t("editor.slash.ol.description"), |
| | }, |
| | todo: { |
| | name: t("editor.slash.todo.name"), |
| | description: t("editor.slash.todo.description"), |
| | }, |
| | advanced: { |
| | name: t("editor.slash.advanced.name"), |
| | description: t("editor.slash.advanced.description"), |
| | }, |
| | link: { |
| | name: t("editor.slash.link.name"), |
| | description: t("editor.slash.link.description"), |
| | }, |
| | image: { |
| | name: t("editor.slash.image.name"), |
| | description: t("editor.slash.image.description"), |
| | }, |
| | code: { |
| | name: t("editor.slash.code.name"), |
| | description: t("editor.slash.code.description"), |
| | }, |
| | math: { |
| | name: t("editor.slash.math.name"), |
| | description: t("editor.slash.math.description"), |
| | }, |
| | table: { |
| | name: t("editor.slash.table.name"), |
| | description: t("editor.slash.table.description"), |
| | }, |
| | quote: { |
| | name: t("editor.slash.quote.name"), |
| | description: t("editor.slash.quote.description"), |
| | }, |
| | horizontal: { |
| | name: t("editor.slash.horizontal.name"), |
| | description: t("editor.slash.horizontal.description"), |
| | }, |
| | }, |
| | placeholder: t("editor.placeholder"), |
| | }, |
| | }); |
| |
|
| | editor.create().then(() => { |
| | setMarkdownEditor(editor); |
| | }); |
| |
|
| | return () => { |
| | editor.destroy(); |
| | }; |
| | }, [t]); |
| |
|
| | return ( |
| | <ResizablePanelGroup |
| | className="flex rounded-md border" |
| | direction="horizontal" |
| | > |
| | <ResizablePanel> |
| | <div |
| | className={cn( |
| | "relative flex-1 text-base whitespace-break-spaces p-1.5", |
| | className |
| | )} |
| | ref={markdownEditorRef} |
| | onBlur={() => onChange(content)} |
| | ></div> |
| | </ResizablePanel> |
| | {isMobile || hideView ? null : ( |
| | <> |
| | <ResizableHandle /> |
| | <ResizablePanel> |
| | <div className="magicdown-view flex-1 prose prose-slate dark:prose-invert overflow-auto max-w-full p-2"> |
| | <View>{content}</View> |
| | </div> |
| | </ResizablePanel> |
| | </> |
| | )} |
| | </ResizablePanelGroup> |
| | ); |
| | } |
| |
|
| | export default memo(Editor); |
| |
|