Skip to content

Commit

Permalink
refactor: milkdown code editor node view
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Feb 2, 2024
1 parent 8feb41f commit 619f0ea
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const EditorModal: FC<{
<div className="relative flex w-full flex-grow flex-col lg:w-[600px]">
<div className="relative max-h-[450px] w-full overflow-auto">
<CodeEditor
minHeight="350px"
content={value}
language="json"
onChange={(value) => {
Expand Down
43 changes: 32 additions & 11 deletions src/components/ui/code-editor/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
import { useEffect, useState } from 'react'
import type { FC } from 'react'
import { forwardRef, useEffect, useState } from 'react'

import { clsxm } from '~/lib/helper'

import { BaseCodeHighlighter } from '../code-highlighter'

export const CodeEditor: FC<{
content: string
language: string
export const CodeEditor = forwardRef<
HTMLTextAreaElement,
{
content: string
language: string

onChange?: (value: string) => void
}> = ({ content, language, onChange }) => {
onChange?: (value: string) => void
minHeight?: string
className?: string
}
>(({ content, language, onChange, minHeight, className }, ref) => {
const [highlighterValue, setHighlighterValue] = useState(content)

useEffect(() => {
setHighlighterValue(content)
}, [content])

const sharedStyles = {
minHeight,
}
return (
<div className="relative">
<div
className={clsxm(
'relative [&_*]:!font-mono [&_*]:!text-base [&_*]:!leading-[1.5]',
className,
)}
contentEditable={false}
>
<textarea
className="absolute h-full w-full resize-none overflow-hidden bg-transparent p-0 !font-mono text-transparent caret-accent *:leading-4"
contentEditable={false}
ref={ref}
className="absolute h-full w-full resize-none overflow-hidden bg-transparent p-0 text-transparent caret-accent"
style={sharedStyles}
value={highlighterValue}
onChange={(e) => {
setHighlighterValue(e.target.value)
onChange?.(e.target.value)
}}
/>
<BaseCodeHighlighter
className="code-wrap pointer-events-none relative z-[1] !m-0 !p-0 *:!font-mono *:!leading-4"
className="code-wrap pointer-events-none relative z-[1] !m-0 !p-0"
style={sharedStyles}
lang={language}
content={highlighterValue}
/>
</div>
)
}
})

CodeEditor.displayName = 'CodeEditor'
18 changes: 11 additions & 7 deletions src/components/ui/code-highlighter/CodeHighlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,23 @@ export const HighLighter: FC<Props> = (props) => {
)
}

export const BaseCodeHighlighter: Component<Props> = ({
content,
lang,
className,
}) => {
export const BaseCodeHighlighter: Component<
Props & {
style: React.CSSProperties
}
> = ({ content, lang, className, style }) => {
const ref = useRef<HTMLElement>(null)
useLoadHighlighter(ref)

useEffect(() => {
window.Prism?.highlightElement(ref.current)
}, [content])
}, [content, lang])
return (
<pre className={clsxm('!bg-transparent', className)} data-start="1">
<pre
className={clsxm('!bg-transparent', className)}
style={style}
data-start="1"
>
<code
className={`language-${lang ?? 'markup'} !bg-transparent`}
ref={ref}
Expand Down
2 changes: 2 additions & 0 deletions src/components/ui/editor/Milkdown/MilkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { gfm } from '@milkdown/preset-gfm'
import { replaceAll } from '@milkdown/utils'

import { useIsUnMounted } from '~/hooks/common/use-is-unmounted'
import { isDev } from '~/lib/env'

import { setEditorCtx } from './ctx'
import styles from './index.module.css'
Expand Down Expand Up @@ -106,6 +107,7 @@ const MilkdownEditorImpl = forwardRef<MilkdownRef, MilkdownProps>(
.markdownUpdated((ctx, markdown) => {
if (isUnMounted.current) return

if (isDev) console.log('markdownUpdated', markdown)
props.onMarkdownChange?.(markdown)
props.onChange?.({ target: { value: markdown } })
})
Expand Down
100 changes: 35 additions & 65 deletions src/components/ui/editor/Milkdown/plugins/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { $view } from '@milkdown/utils'
import { BlockLoading } from '~/components/modules/shared/BlockLoading'
import { StyledButton } from '~/components/ui/button'
import { CodeEditor } from '~/components/ui/code-editor'
import { HighLighter } from '~/components/ui/code-highlighter'
import { Input } from '~/components/ui/input'
import { useCurrentModal, useModalStack } from '~/components/ui/modal'

Expand Down Expand Up @@ -44,74 +43,46 @@ const NormalCodeBlock: FC<{
language: string
}> = ({ content, language }) => {
const nodeCtx = useNodeViewContext()
const ctx = useEditorCtx()

const modalStack = useModalStack()
return (
<div className="group relative">
<CodeEditor
ref={(el) => {
if (!content && el)
requestAnimationFrame(() => requestAnimationFrame(() => el.focus()))
}}
content={content}
minHeight="20px"
className="rounded-md border bg-gray-100 p-2 dark:bg-zinc-900"
language={language}
onChange={(code) => {
const view = nodeCtx.view

const handleEdit = () => {
const Content: FC<ModalContentPropsInternal> = () => {
const valueRef = useRef<string | undefined>(content)
const nextLangRef = useRef(language)
const node = nodeCtx.node

return (
<div className="flex w-[60ch] max-w-full flex-col overflow-auto">
<div className="max-h-[400px] overflow-auto">
<CodeEditor
content={content}
language={language}
onChange={(code) => {
valueRef.current = code
}}
/>
</div>

<div className="relative">
<div className="absolute left-0 top-4">
<Input
ref={(el) => {
if (!el) {
nodeCtx.setAttrs({
language: nextLangRef.current,
})
}
}}
defaultValue={language}
onBlur={(e) => {
const v = e.target.value
nodeCtx.setAttrs({
language: v,
})
nextLangRef.current = v
}}
/>
</div>
<SharedModalAction
nodeCtx={nodeCtx}
getValue={() => valueRef.current}
/>
</div>
</div>
)
}
modalStack.present({
title: 'Edit Code Block',
content: Content,
})
}
const pos = nodeCtx.getPos()
if (!pos) return

if (!content) {
return (
<div
className="my-4 flex h-12 w-full max-w-full cursor-pointer rounded bg-slate-100 text-sm center dark:bg-neutral-800"
onClick={handleEdit}
contentEditable={false}
>
Empty Code Block, Click to edit
const tr = view.state.tr

const nextNode = ctx!.get(schemaCtx).text(code)

tr.replaceWith(pos + 1, pos + node.nodeSize, nextNode)
view.dispatch(tr)
}}
/>
<div className="absolute bottom-1 right-1 opacity-0 duration-200 group-hover:opacity-100">
<Input
defaultValue={language}
onBlur={(e) => {
const v = e.target.value
nodeCtx.setAttrs({
language: v,
})
}}
/>
</div>
)
}
return (
<div contentEditable={false} onClick={handleEdit}>
<HighLighter content={content} lang={language} key={content} />
</div>
)
}
Expand Down Expand Up @@ -205,7 +176,6 @@ const SharedModalAction: FC<{
const { getPos, view, node } = nodeCtx
const { dismiss } = useCurrentModal()
const ctx = useEditorCtx()
console.log(node)

const deleteNode = () => {
const pos = getPos()
Expand Down

0 comments on commit 619f0ea

Please sign in to comment.