import { useMemo, useState } from 'react' import PropTypes from 'prop-types' import CodeMirror from '@uiw/react-codemirror' import { EditorView, Decoration, ViewPlugin } from '@codemirror/view' import { StateField } from '@codemirror/state' import { farmControlDark, farmControlLight } from '../../../codemirror/farmControlTheme' import { getCodeLanguageExtension, toEditorCode } from './codeBlockEditorUtils' import { useThemeContext } from '../context/ThemeContext' import ScrollBox from './ScrollBox' import { Button, Modal, Typography, Card } from 'antd' const { Link } = Typography const codeBlockEditorLayoutTheme = EditorView.theme({ '&': { height: 'auto !important' }, '.cm-scroller': { overflow: 'visible !important' }, '.cm-gutters': { position: 'sticky', left: '0', zIndex: 2 } }) function createScrollBoxSyncExtension() { return ViewPlugin.fromClass( class { constructor(view) { this.view = view this.scrollEl = view.dom.closest('.simplebar-content-wrapper') this.syncing = false this.onExternalScroll = this.onExternalScroll.bind(this) this.onEditorScroll = this.onEditorScroll.bind(this) if (this.scrollEl) { this.scrollEl.addEventListener('scroll', this.onExternalScroll, { passive: true }) view.scrollDOM.addEventListener('scroll', this.onEditorScroll, { passive: true }) } } syncFromExternal() { const { view, scrollEl } = this if (this.syncing || !scrollEl) return this.syncing = true view.scrollDOM.scrollTop = scrollEl.scrollTop view.scrollDOM.scrollLeft = scrollEl.scrollLeft view.scrollDOM.dispatchEvent(new Event('scroll')) this.syncing = false } syncFromEditor() { const { view, scrollEl } = this if (this.syncing || !scrollEl) return this.syncing = true scrollEl.scrollTop = view.scrollDOM.scrollTop scrollEl.scrollLeft = view.scrollDOM.scrollLeft this.syncing = false } onExternalScroll() { this.syncFromExternal() } onEditorScroll() { this.syncFromEditor() } destroy() { if (this.scrollEl) { this.scrollEl.removeEventListener('scroll', this.onExternalScroll) this.view.scrollDOM.removeEventListener('scroll', this.onEditorScroll) } } } ) } function buildErrorLineDecorations(state, errorLineSet) { if (!errorLineSet.size) { return Decoration.none } const decorations = [] for (let i = 1; i <= state.doc.lines; i++) { if (errorLineSet.has(i)) { const line = state.doc.line(i) decorations.push( Decoration.line({ class: 'cm-errorLine' }).range(line.from) ) } } return Decoration.set(decorations, true) } function createErrorLineExtension(errorLines) { const errorLineSet = new Set(errorLines) return StateField.define({ create(state) { return buildErrorLineDecorations(state, errorLineSet) }, update(decorations, tr) { if (tr.docChanged) { return buildErrorLineDecorations(tr.state, errorLineSet) } return decorations.map(tr.changes) }, provide: (f) => EditorView.decorations.from(f) }) } export default function CodeBlockEditor({ code = '', value, language = 'javascript', style = {}, className = '', readOnly = false, onChange = null, height = 'auto', showLineNumbers = true, disabled = false, minimal = false, autoCompleteObject = null, errorLines = [] }) { const { isDarkMode } = useThemeContext() const [codeMirrorOpen, setCodeMirrorOpen] = useState(false) const sourceCode = value !== undefined ? value : code const editorCode = toEditorCode(sourceCode, language) const languageExtension = useMemo( () => getCodeLanguageExtension(language, autoCompleteObject), [language, autoCompleteObject] ) const errorLineExtension = useMemo(() => { if (!errorLines?.length) { return [] } return [createErrorLineExtension(errorLines)] }, [errorLines]) const editorExtensions = useMemo( () => [ languageExtension, ...errorLineExtension, codeBlockEditorLayoutTheme, createScrollBoxSyncExtension() ], [languageExtension, errorLineExtension] ) const handleOnChange = (value) => { if (typeof code == 'object' && language == 'json') { onChange(JSON.parse(value)) } else { onChange(value) } } const codeMirror = (
) return ( <> {minimal ? (
{ setCodeMirrorOpen(true) }} >
{editorCode.slice(-64).trim()}...
{ setCodeMirrorOpen(false) }} > Close } > {codeMirror}
) : ( codeMirror )} ) } CodeBlockEditor.propTypes = { code: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), language: PropTypes.oneOfType([ PropTypes.string, PropTypes.arrayOf(PropTypes.string) ]), style: PropTypes.object, className: PropTypes.string, readOnly: PropTypes.bool, onChange: PropTypes.func, height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), showLineNumbers: PropTypes.bool, disabled: PropTypes.bool, minimal: PropTypes.bool, autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), errorLines: PropTypes.arrayOf(PropTypes.number) }