shinkan-jinkendo/frontend/src/components/RichTextEditor.jsx

94 lines
3.1 KiB
React
Raw Normal View History

import React, { useRef, useEffect, useState, useCallback } from 'react'
function exec(cmd, value = null) {
try {
document.execCommand(cmd, false, value)
} catch (_) {
/* ignore */
}
}
/**
* Leichter WYSIWYG-Editor (contenteditable) — ohne zusätzliche npm-Pakete.
* Speichert HTML; Anzeige über sanitizeTrainerHtml + dangerouslySetInnerHTML.
*/
export default function RichTextEditor({ value, onChange, placeholder, minHeight = '140px' }) {
const ref = useRef(null)
const [focused, setFocused] = useState(false)
const lastExternal = useRef(value)
useEffect(() => {
if (!ref.current) return
if (focused) return
if (value !== lastExternal.current) {
lastExternal.current = value
if (ref.current.innerHTML !== (value || '')) {
ref.current.innerHTML = value || ''
}
}
}, [value, focused])
const sync = useCallback(() => {
if (!ref.current) return
const html = ref.current.innerHTML
lastExternal.current = html
onChange(html)
}, [onChange])
const onLink = () => {
const url = window.prompt('Link-URL (https://…)')
if (url) exec('createLink', url)
sync()
}
return (
<div className="rich-text-editor-wrap">
<div className="rich-text-toolbar" role="toolbar" aria-label="Formatierung">
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('bold')}>
<strong>B</strong>
</button>
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('italic')}>
<em>I</em>
</button>
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('underline')}>
U
</button>
<span className="rte-sep" />
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('formatBlock', 'h3')}>
H3
</button>
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('insertUnorderedList')}>
•
</button>
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('insertOrderedList')}>
1.
</button>
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('formatBlock', 'p')}>
</button>
<span className="rte-sep" />
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={onLink}>
Link
</button>
<button type="button" className="rte-btn" onMouseDown={(e) => e.preventDefault()} onClick={() => exec('removeFormat')}>
⌫
</button>
</div>
<div
ref={ref}
className="rich-text-editor"
contentEditable
suppressContentEditableWarning
data-placeholder={placeholder || ''}
style={{ minHeight }}
onFocus={() => setFocused(true)}
onBlur={() => {
setFocused(false)
sync()
}}
onInput={sync}
/>
</div>
)
}