All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
218 lines
5.9 KiB
JavaScript
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
|