RichEditor now supports readOnly prop — hides toolbar and disables editing via ReactQuill's built-in readOnly. Content renders with proper Quill CSS (list margins, indentation, fonts) instead of broken browser defaults from dangerouslySetInnerHTML. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
2.9 KiB
TypeScript
109 lines
2.9 KiB
TypeScript
import { useMemo, useRef, useCallback } from 'react'
|
|
import ReactQuill from 'react-quill-new'
|
|
import 'react-quill-new/dist/quill.snow.css'
|
|
|
|
const Quill = ReactQuill.Quill
|
|
|
|
if (!(Quill as any).__bohaRegistered) {
|
|
const Font = Quill.import('attributors/class/font') as any
|
|
Font.whitelist = [
|
|
'arial', 'tahoma', 'verdana', 'georgia', 'times-new-roman',
|
|
'courier-new', 'trebuchet-ms', 'impact', 'comic-sans-ms',
|
|
'lucida-console', 'palatino-linotype', 'garamond'
|
|
]
|
|
Quill.register(Font, true)
|
|
|
|
const SizeStyle = Quill.import('attributors/style/size') as any
|
|
SizeStyle.whitelist = [
|
|
'8px', '9px', '10px', '11px', '12px', '14px', '16px',
|
|
'18px', '20px', '24px', '28px', '32px', '36px', '48px'
|
|
]
|
|
Quill.register(SizeStyle, true)
|
|
;(Quill as any).__bohaRegistered = true
|
|
}
|
|
|
|
const Font = Quill.import('attributors/class/font') as any
|
|
const SIZE_WHITELIST = [
|
|
'8px', '9px', '10px', '11px', '12px', '14px', '16px',
|
|
'18px', '20px', '24px', '28px', '32px', '36px', '48px'
|
|
]
|
|
|
|
const COLORS = [
|
|
'#000000', '#1a1a1a', '#333333', '#555555', '#777777', '#999999', '#bbbbbb', '#dddddd', '#ffffff',
|
|
'#de3a3a', '#e57373', '#c62828',
|
|
'#1565c0', '#42a5f5', '#0d47a1',
|
|
'#2e7d32', '#66bb6a', '#1b5e20',
|
|
'#f57f17', '#ffca28', '#e65100',
|
|
'#6a1b9a', '#ab47bc', '#4a148c',
|
|
'#00695c', '#26a69a', '#004d40',
|
|
'#37474f', '#78909c', '#263238',
|
|
]
|
|
|
|
const TOOLBAR = [
|
|
[{ font: Font.whitelist }],
|
|
[{ size: SIZE_WHITELIST }],
|
|
['bold', 'italic', 'underline', 'strike'],
|
|
[{ color: COLORS }, { background: COLORS }],
|
|
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
[{ indent: '-1' }, { indent: '+1' }],
|
|
[{ align: [] }],
|
|
['link'],
|
|
['clean']
|
|
]
|
|
|
|
const FORMATS = [
|
|
'font', 'size',
|
|
'bold', 'italic', 'underline', 'strike',
|
|
'color', 'background',
|
|
'list', 'indent', 'align',
|
|
'link'
|
|
]
|
|
|
|
interface RichEditorProps {
|
|
value: string
|
|
onChange: (value: string) => void
|
|
placeholder?: string
|
|
minHeight?: string
|
|
readOnly?: boolean
|
|
}
|
|
|
|
export default function RichEditor({
|
|
value,
|
|
onChange,
|
|
placeholder = 'Obsah...',
|
|
minHeight = '120px',
|
|
readOnly = false,
|
|
}: RichEditorProps) {
|
|
const quillRef = useRef<ReactQuill>(null)
|
|
const lastValueRef = useRef(value)
|
|
|
|
const modules = useMemo(() => ({
|
|
toolbar: readOnly ? false : TOOLBAR,
|
|
clipboard: {
|
|
matchVisual: false,
|
|
},
|
|
}), [readOnly])
|
|
|
|
const handleChange = useCallback((content: string, _delta: any, source: string) => {
|
|
if (source !== 'user') return
|
|
if (content === lastValueRef.current) return
|
|
lastValueRef.current = content
|
|
onChange(content)
|
|
}, [onChange])
|
|
|
|
return (
|
|
<div className="rich-editor" style={{ '--re-min-height': minHeight } as React.CSSProperties}>
|
|
<ReactQuill
|
|
ref={quillRef}
|
|
theme="snow"
|
|
value={value || ''}
|
|
onChange={handleChange}
|
|
modules={modules}
|
|
formats={FORMATS}
|
|
placeholder={placeholder}
|
|
readOnly={readOnly}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|