218 lines
5.9 KiB
JavaScript

import { EditorContent, useEditor } from '@tiptap/react'
import Link from '@tiptap/extension-link'
import Placeholder from '@tiptap/extension-placeholder'
import Underline from '@tiptap/extension-underline'
import { Markdown } from '@tiptap/markdown'
import StarterKit from '@tiptap/starter-kit'
import { Card, Splitter, theme } from 'antd'
import PropTypes from 'prop-types'
import { useEffect, useRef, useState } from 'react'
import { useThemeContext } from '../context/ThemeContext'
import MarkdownToolbar from './MarkdownToolbar'
import CodeBlockEditor from './CodeBlockEditor'
const MarkdownInput = ({
value,
onChange,
minHeight = '120px',
size = 'small',
showCard = true
}) => {
const markdownValue = typeof value === 'string' ? value : ''
const lastMarkdownRef = useRef(markdownValue)
const { isDarkMode } = useThemeContext()
const { token } = theme.useToken()
const [viewState, setViewState] = useState({ editor: true, code: false })
const [focusedPanel, setFocusedPanel] = useState(null)
const editorPanelRef = useRef(null)
const codePanelRef = useRef(null)
const handlePanelBlur = () => {
requestAnimationFrame(() => {
const active = document.activeElement
if (editorPanelRef.current?.contains(active)) {
setFocusedPanel('editor')
} else if (codePanelRef.current?.contains(active)) {
setFocusedPanel('code')
} else {
setFocusedPanel(null)
}
})
}
const editor = useEditor({
extensions: [
StarterKit,
Link.configure({
autolink: true,
defaultProtocol: 'https',
openOnClick: false
}),
Placeholder.configure({
placeholder: 'Enter text here...'
}),
Underline,
Markdown
],
content: markdownValue,
contentType: 'markdown',
editorProps: {
attributes: {
class: 'md-editor__content'
}
},
immediatelyRender: false,
onUpdate: ({ editor }) => {
const nextMarkdown = editor.getMarkdown()
lastMarkdownRef.current = nextMarkdown
onChange?.(nextMarkdown)
}
})
useEffect(() => {
if (!editor) {
return
}
if (markdownValue !== lastMarkdownRef.current) {
editor.commands.setContent(markdownValue, {
contentType: 'markdown',
emitUpdate: false
})
lastMarkdownRef.current = markdownValue
}
}, [editor, markdownValue])
const editorPanel = (
<div
ref={editorPanelRef}
className='markdown md-editor'
onFocusCapture={() => setFocusedPanel('editor')}
onBlurCapture={handlePanelBlur}
>
<EditorContent editor={editor} />
</div>
)
const codePanel = (
<div
ref={codePanelRef}
className='markdown-code-editor'
onFocusCapture={() => setFocusedPanel('code')}
onBlurCapture={handlePanelBlur}
>
<CodeBlockEditor
code={markdownValue}
language='markdown'
showLineNumbers={false}
style={{ border: 'none' }}
onChange={(val) => {
onChange?.(val)
}}
/>
</div>
)
const renderContent = () => {
if (viewState.editor && viewState.code) {
return (
<Splitter
className='farmcontrol-splitter markdown-splitter'
vertical={true}
>
{viewState.editor && (
<Splitter.Panel style={{ minHeight: 80 }}>
{editorPanel}
</Splitter.Panel>
)}
{viewState.code && (
<Splitter.Panel style={{ minHeight: 80 }}>
{codePanel}
</Splitter.Panel>
)}
</Splitter>
)
}
if (viewState.code) {
return codePanel
}
return editorPanel
}
const cardContent = (
<div
className={isDarkMode ? 'dark-theme' : 'light-theme'}
style={{
'--basePageBg': token.colorBgContainer,
'--baseBase': token.colorBgContainer,
'--baseBgSubtle': token.colorBgElevated,
'--baseBg': token.colorFillTertiary,
'--baseBgHover': token.colorFillSecondary,
'--baseBgActive': token.colorFill,
'--baseLine': token.colorBorderSecondary,
'--baseBorder': token.colorBorder,
'--baseBorderHover': token.colorTextTertiary,
'--baseSolid': token.colorTextTertiary,
'--baseSolidHover': token.colorTextSecondary,
'--baseText': token.colorText,
'--baseTextContrast': token.colorText,
'--accentBase': token.colorPrimaryBg,
'--accentBgSubtle': token.colorPrimaryBg,
'--accentBg': token.colorPrimaryBgHover,
'--accentBgHover': token.colorPrimaryHover,
'--accentBgActive': token.colorPrimaryActive,
'--accentLine': token.colorPrimaryBorder,
'--accentBorder': token.colorPrimaryBorderHover,
'--accentBorderHover': token.colorPrimaryHover,
'--accentSolid': token.colorPrimary,
'--accentSolidHover': token.colorPrimaryHover,
'--accentText': token.colorPrimary,
'--accentTextContrast': token.colorWhite,
'--md-editor-min-height': minHeight
}}
>
<MarkdownToolbar
editor={editor}
size={size}
showCard={showCard}
viewState={viewState}
onViewStateChange={setViewState}
editingToolsDisabled={
focusedPanel === 'code' || (viewState.code && !viewState.editor)
}
/>
{renderContent()}
</div>
)
return (
<>
{showCard ? (
<Card
style={{ height: '100%' }}
styles={{
body: {
height: '100%',
padding: 0,
overflow: 'auto'
}
}}
>
{cardContent}
</Card>
) : (
cardContent
)}
</>
)
}
MarkdownInput.propTypes = {
value: PropTypes.string,
onChange: PropTypes.func,
minHeight: PropTypes.string,
size: PropTypes.string,
showCard: PropTypes.bool
}
export default MarkdownInput