diff --git a/web/src/components/MemoEditor/ActionButton/MarkdownMenu.tsx b/web/src/components/MemoEditor/ActionButton/MarkdownMenu.tsx new file mode 100644 index 00000000..97017909 --- /dev/null +++ b/web/src/components/MemoEditor/ActionButton/MarkdownMenu.tsx @@ -0,0 +1,96 @@ +import { Dropdown, IconButton, Menu, MenuButton, MenuItem } from "@mui/joy"; +import Icon from "@/components/Icon"; +import showPreviewMarkdownDialog from "@/components/PreviewMarkdownDialog"; +import { EditorRefActions } from "../Editor"; + +interface Props { + editorRef: React.RefObject; +} + +const MarkdownMenu = (props: Props) => { + const { editorRef } = props; + + const handleCodeBlockClick = () => { + if (!editorRef.current) { + return; + } + + const cursorPosition = editorRef.current.getCursorPosition(); + const prevValue = editorRef.current.getContent().slice(0, cursorPosition); + if (prevValue === "" || prevValue.endsWith("\n")) { + editorRef.current.insertText("", "```\n", "\n```"); + } else { + editorRef.current.insertText("", "\n```\n", "\n```"); + } + setTimeout(() => { + editorRef.current?.scrollToCursor(); + editorRef.current?.focus(); + }); + }; + + const handleCheckboxClick = () => { + if (!editorRef.current) { + return; + } + + const currentPosition = editorRef.current.getCursorPosition(); + const currentLineNumber = editorRef.current.getCursorLineNumber(); + const currentLine = editorRef.current.getLine(currentLineNumber); + let newLine = ""; + let cursorChange = 0; + if (/^- \[( |x|X)\] /.test(currentLine)) { + newLine = currentLine.replace(/^- \[( |x|X)\] /, ""); + cursorChange = -6; + } else if (/^\d+\. |- /.test(currentLine)) { + const match = currentLine.match(/^\d+\. |- /) ?? [""]; + newLine = currentLine.replace(/^\d+\. |- /, "- [ ] "); + cursorChange = -match[0].length + 6; + } else { + newLine = "- [ ] " + currentLine; + cursorChange = 6; + } + editorRef.current.setLine(currentLineNumber, newLine); + editorRef.current.setCursorPosition(currentPosition + cursorChange); + setTimeout(() => { + editorRef.current?.scrollToCursor(); + editorRef.current?.focus(); + }); + }; + + const handlePreviewClick = () => { + showPreviewMarkdownDialog(editorRef.current?.getContent() ?? ""); + }; + + return ( + + + + + + + + Code block + + + + Checkbox + + + + Preview + + + + ); +}; + +export default MarkdownMenu; diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 2db720cb..9a72c991 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -18,6 +18,7 @@ import showCreateMemoRelationDialog from "../CreateMemoRelationDialog"; import showCreateResourceDialog from "../CreateResourceDialog"; import Icon from "../Icon"; import VisibilityIcon from "../VisibilityIcon"; +import MarkdownMenu from "./ActionButton/MarkdownMenu"; import TagSelector from "./ActionButton/TagSelector"; import Editor, { EditorRefActions } from "./Editor"; import RelationListView from "./RelationListView"; @@ -366,7 +367,7 @@ const MemoEditor = (props: Props) => { onFocus={handleEditorFocus} > -
+
e.stopPropagation()}>
handleTagSelectorClick(tag)} /> { > +
diff --git a/web/src/components/PreviewMarkdownDialog.tsx b/web/src/components/PreviewMarkdownDialog.tsx new file mode 100644 index 00000000..82b14fcb --- /dev/null +++ b/web/src/components/PreviewMarkdownDialog.tsx @@ -0,0 +1,57 @@ +import { useEffect, useState } from "react"; +import { markdownServiceClient } from "@/grpcweb"; +import { Node } from "@/types/proto/api/v2/markdown_service"; +import { generateDialog } from "./Dialog"; +import Icon from "./Icon"; +import MemoContent from "./MemoContent"; + +interface Props extends DialogProps { + content: string; +} + +const PreviewMarkdownDialog: React.FC = ({ content, destroy }: Props) => { + const [nodes, setNodes] = useState([]); + + useEffect(() => { + (async () => { + const { nodes } = await markdownServiceClient.parseMarkdown({ + markdown: content, + }); + setNodes(nodes); + })(); + }, []); + + const handleCloseBtnClick = () => { + destroy(); + }; + + return ( + <> +
+
+

Preview

+
+ +
+
+ +
+ + ); +}; + +export default function showPreviewMarkdownDialog(content: string): void { + generateDialog( + { + className: "preview-markdown-dialog", + dialogName: "preview-markdown-dialog", + containerClassName: "dark:!bg-zinc-800", + }, + PreviewMarkdownDialog, + { + content, + } + ); +}