Skip to content

Commit

Permalink
feat: editor add menubar (#231)
Browse files Browse the repository at this point in the history
* feat: add menubar editor

* feat: Rich menubar functions

* chore: remove file

* fix: mobile not wrap

Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Soya-xy authored and Innei committed Feb 2, 2024
1 parent 244e96e commit 8feb41f
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 17 deletions.
129 changes: 117 additions & 12 deletions src/components/modules/dashboard/writing/Writing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 (
<div className="my-2 flex w-full flex-wrap space-x-2">
{menuList.map((menu, key) => (
<button
key={key}
className="flex items-center justify-center rounded p-2 text-xl text-gray-500 hover:bg-gray-300 hover:text-black dark:hover:bg-zinc-700 dark:hover:text-zinc-300"
onClick={() => {
menu.action()

editorRef?.getAction((ctx) => {
ctx.get(editorViewCtx).focus()
})
}}
>
<i className={menu.icon} />
</button>
))}
</div>
)
}

const Editor = () => {
const ctxAtom = useBaseWritingContext()
const setAtom = useSetAtom(ctxAtom)
Expand All @@ -58,18 +160,21 @@ const Editor = () => {
}, [])

return (
<div
className={clsxm(
'relative h-0 flex-grow overflow-auto rounded-xl border p-3 duration-200 focus-within:border-accent',
'border-zinc-200 bg-white placeholder:text-slate-500 focus-visible:border-accent dark:border-neutral-800 dark:bg-zinc-900',
)}
>
<MilkdownEditor
ref={milkdownRef}
onMarkdownChange={handleMarkdownChange}
initialMarkdown={store.get(ctxAtom).text}
/>
</div>
<>
<MenuBar />
<div
className={clsxm(
'relative h-0 flex-grow overflow-auto rounded-xl border p-3 duration-200 focus-within:border-accent',
'border-zinc-200 bg-white placeholder:text-slate-500 focus-visible:border-accent dark:border-neutral-800 dark:bg-zinc-900',
)}
>
<MilkdownEditor
ref={milkdownRef}
onMarkdownChange={handleMarkdownChange}
initialMarkdown={store.get(ctxAtom).text}
/>
</div>
</>
)
}

Expand Down
16 changes: 15 additions & 1 deletion src/components/ui/editor/Milkdown/MilkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useRef,
} from 'react'
import type { Config } from '@milkdown/core'
import type { Ctx } from '@milkdown/ctx'

import {
defaultValueCtx,
Expand Down Expand Up @@ -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<MilkdownRef, MilkdownProps>(
Expand Down Expand Up @@ -102,7 +106,6 @@ const MilkdownEditorImpl = forwardRef<MilkdownRef, MilkdownProps>(
.markdownUpdated((ctx, markdown) => {
if (isUnMounted.current) return

console.log('markdown', markdown)
props.onMarkdownChange?.(markdown)
props.onChange?.({ target: { value: markdown } })
})
Expand Down Expand Up @@ -132,9 +135,20 @@ const MilkdownEditorImpl = forwardRef<MilkdownRef, MilkdownProps>(
[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()
Expand Down
9 changes: 5 additions & 4 deletions src/components/ui/editor/Milkdown/plugins/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ExcalidrawBoard content={content} />
return <ExcalidrawBoard content={content || '{}'} />
}
}

return (
<div className="my-4">
<NormalCodeBlock content={content} language={language} />
<NormalCodeBlock content={content || ''} language={language} />
</div>
)
}
Expand Down Expand Up @@ -65,7 +65,7 @@ const NormalCodeBlock: FC<{
</div>

<div className="relative">
<div className="absolute left-0 top-0">
<div className="absolute left-0 top-4">
<Input
ref={(el) => {
if (!el) {
Expand Down Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions src/components/ui/excalidraw/Excalidraw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ export const Excalidraw: FC<{
files: BinaryFiles,
) => void
className?: string

onReady?: (api: ExcalidrawImperativeAPI) => void
}> = ({
data,
viewModeEnabled = true,
zenModeEnabled = true,
onChange,
className,
showExtendButton = true,
onReady,
}) => {
const excalidrawAPIRef = React.useRef<ExcalidrawImperativeAPI>()
const modal = useModalStack()
Expand All @@ -64,6 +67,8 @@ export const Excalidraw: FC<{
fitToContent: true,
})
}, 1000)

onReady?.(api)
}}
/>

Expand Down

0 comments on commit 8feb41f

Please sign in to comment.