From 8feb41fe742d323c24b037086b29003c007f1c79 Mon Sep 17 00:00:00 2001 From: Soya <3100034521@qq.com> Date: Fri, 2 Feb 2024 21:47:13 +0800 Subject: [PATCH] feat: editor add menubar (#231) * feat: add menubar editor * feat: Rich menubar functions * chore: remove file * fix: mobile not wrap Signed-off-by: Innei --- .../modules/dashboard/writing/Writing.tsx | 129 ++++++++++++++++-- .../ui/editor/Milkdown/MilkdownEditor.tsx | 16 ++- .../ui/editor/Milkdown/plugins/CodeBlock.tsx | 9 +- src/components/ui/excalidraw/Excalidraw.tsx | 5 + 4 files changed, 142 insertions(+), 17 deletions(-) diff --git a/src/components/modules/dashboard/writing/Writing.tsx b/src/components/modules/dashboard/writing/Writing.tsx index ca335a9ad5..8d4431e41f 100644 --- a/src/components/modules/dashboard/writing/Writing.tsx +++ b/src/components/modules/dashboard/writing/Writing.tsx @@ -4,6 +4,17 @@ import { atom, useAtomValue, useSetAtom, useStore } from 'jotai' import type { FC } from 'react' import type { MilkdownRef } from '../../../ui/editor' +import { editorViewCtx, schemaCtx } from '@milkdown/core' +import { redoCommand, undoCommand } from '@milkdown/plugin-history' +import { + toggleEmphasisCommand, + toggleStrongCommand, + wrapInBulletListCommand, + wrapInHeadingCommand, + wrapInOrderedListCommand, +} from '@milkdown/preset-commonmark' +import { callCommand } from '@milkdown/utils' + import { useEventCallback } from '~/hooks/common/use-event-callback' import { clsxm } from '~/lib/helper' import { jotaiStore } from '~/lib/store' @@ -36,6 +47,97 @@ export const Writing: FC<{ ) } +const MenuBar = () => { + const editorRef = useEditorRef() + + const menuList = [ + { + icon: 'icon-[material-symbols--undo]', + action: () => editorRef?.getAction(callCommand(undoCommand.key)), + }, + { + icon: 'icon-[material-symbols--redo]', + action: () => editorRef?.getAction(callCommand(redoCommand.key)), + }, + { + icon: 'icon-[mingcute--bold-fill]', + action: () => editorRef?.getAction(callCommand(toggleStrongCommand.key)), + }, + { + icon: 'icon-[mingcute--italic-fill]', + action: () => + editorRef?.getAction(callCommand(toggleEmphasisCommand.key)), + }, + { + icon: 'icon-[mingcute--list-check-fill]', + action: () => + editorRef?.getAction(callCommand(wrapInBulletListCommand.key)), + }, + { + icon: 'icon-[material-symbols--format-list-numbered-rounded]', + action: () => + editorRef?.getAction(callCommand(wrapInOrderedListCommand.key)), + }, + { + icon: 'icon-[material-symbols--format-h1]', + action: () => + editorRef?.getAction(callCommand(wrapInHeadingCommand.key, 1)), + }, + { + icon: 'icon-[material-symbols--format-h2]', + action: () => + editorRef?.getAction(callCommand(wrapInHeadingCommand.key, 2)), + }, + { + icon: 'icon-[material-symbols--format-h3]', + action: () => + editorRef?.getAction(callCommand(wrapInHeadingCommand.key, 3)), + }, + { + icon: 'icon-[material-symbols--format-h4]', + action: () => + editorRef?.getAction(callCommand(wrapInHeadingCommand.key, 4)), + }, + { + icon: 'icon-[mingcute--drawing-board-line]', + action: () => { + const ctx = editorRef?.editor.ctx + if (!ctx) return + const view = ctx.get(editorViewCtx) + if (!view) return + const state = view.state + + const currentCursorPosition = state.selection.from + const nextNode = ctx.get(schemaCtx).node('code_block', { + language: 'excalidraw', + }) + + view.dispatch(state.tr.insert(currentCursorPosition, nextNode)) + }, + }, + ] + + return ( +
+ {menuList.map((menu, key) => ( + + ))} +
+ ) +} + const Editor = () => { const ctxAtom = useBaseWritingContext() const setAtom = useSetAtom(ctxAtom) @@ -58,18 +160,21 @@ const Editor = () => { }, []) return ( -
- -
+ <> + +
+ +
+ ) } diff --git a/src/components/ui/editor/Milkdown/MilkdownEditor.tsx b/src/components/ui/editor/Milkdown/MilkdownEditor.tsx index 87b2ed5985..24ed688f7e 100644 --- a/src/components/ui/editor/Milkdown/MilkdownEditor.tsx +++ b/src/components/ui/editor/Milkdown/MilkdownEditor.tsx @@ -11,6 +11,7 @@ import { useRef, } from 'react' import type { Config } from '@milkdown/core' +import type { Ctx } from '@milkdown/ctx' import { defaultValueCtx, @@ -47,6 +48,9 @@ export interface MilkdownProps { export interface MilkdownRef { getMarkdown(): string | undefined setMarkdown(markdown: string): void + getAction(cb: (ctx: Ctx) => void): void + + editor: Editor } export const MilkdownEditor = forwardRef( @@ -102,7 +106,6 @@ const MilkdownEditorImpl = forwardRef( .markdownUpdated((ctx, markdown) => { if (isUnMounted.current) return - console.log('markdown', markdown) props.onMarkdownChange?.(markdown) props.onChange?.({ target: { value: markdown } }) }) @@ -132,9 +135,20 @@ const MilkdownEditorImpl = forwardRef( [get], ) + const getAction = useCallback( + (cb: (ctx: Ctx) => void) => { + get()?.action(cb) + }, + [get], + ) + useImperativeHandle(ref, () => ({ getMarkdown, setMarkdown, + getAction, + get editor() { + return editorRef.current! + }, })) const isUnMounted = useIsUnMounted() diff --git a/src/components/ui/editor/Milkdown/plugins/CodeBlock.tsx b/src/components/ui/editor/Milkdown/plugins/CodeBlock.tsx index 908f89bb09..e02a1051ed 100644 --- a/src/components/ui/editor/Milkdown/plugins/CodeBlock.tsx +++ b/src/components/ui/editor/Milkdown/plugins/CodeBlock.tsx @@ -24,17 +24,17 @@ const CodeBlock = () => { const { node } = useNodeViewContext() const language = node.attrs.language - const content = node.content.firstChild?.text || '' + const content = node.content.firstChild?.text switch (language) { case 'excalidraw': { - return + return } } return (
- +
) } @@ -65,7 +65,7 @@ const NormalCodeBlock: FC<{
-
+
{ if (!el) { @@ -205,6 +205,7 @@ const SharedModalAction: FC<{ const { getPos, view, node } = nodeCtx const { dismiss } = useCurrentModal() const ctx = useEditorCtx() + console.log(node) const deleteNode = () => { const pos = getPos() diff --git a/src/components/ui/excalidraw/Excalidraw.tsx b/src/components/ui/excalidraw/Excalidraw.tsx index 89f7fe00f1..377e61a97e 100644 --- a/src/components/ui/excalidraw/Excalidraw.tsx +++ b/src/components/ui/excalidraw/Excalidraw.tsx @@ -34,6 +34,8 @@ export const Excalidraw: FC<{ files: BinaryFiles, ) => void className?: string + + onReady?: (api: ExcalidrawImperativeAPI) => void }> = ({ data, viewModeEnabled = true, @@ -41,6 +43,7 @@ export const Excalidraw: FC<{ onChange, className, showExtendButton = true, + onReady, }) => { const excalidrawAPIRef = React.useRef() const modal = useModalStack() @@ -64,6 +67,8 @@ export const Excalidraw: FC<{ fitToContent: true, }) }, 1000) + + onReady?.(api) }} />