utopia-ui/src/Components/Input/RichTextEditor.tsx
Ulf Gebhardt 2c50d66edc
feat(source): tip tap version 2 (#231)
* tip tap version 2

* youtube

* menu-bar

* refactorng layout

* fixed flex layout

* fixed flex layout

* a lot of ui fixes

* optimizing flex layout & styling inputs

* markdown styling

* fix linting

* updated snapshots

* layout optimization

* flex layout optimizations, text editor fine tuning and markdown rendering

* updated snapshots

---------

Co-authored-by: Anton Tranelis <mail@antontranelis.de>
Co-authored-by: Anton Tranelis <31516529+antontranelis@users.noreply.github.com>
2025-06-10 08:46:33 +00:00

111 lines
3.0 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { Color } from '@tiptap/extension-color'
import { Image } from '@tiptap/extension-image'
import { Link } from '@tiptap/extension-link'
import { Placeholder } from '@tiptap/extension-placeholder'
import { EditorContent, useEditor } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
import { useEffect } from 'react'
import { Markdown } from 'tiptap-markdown'
import { TextEditorMenu } from './TextEditorMenu'
interface RichTextEditorProps {
labelTitle?: string
labelStyle?: string
containerStyle?: string
defaultValue: string
placeholder?: string
showMenu?: boolean
updateFormValue?: (value: string) => void
}
/**
* @category Input
*/
export function RichTextEditor({
labelTitle,
labelStyle,
containerStyle,
defaultValue,
placeholder,
showMenu = true,
updateFormValue,
}: RichTextEditorProps) {
const handleChange = () => {
let newValue: string | undefined = editor?.storage.markdown.getMarkdown()
const regex = /!\[.*?\]\(.*?\)/g
newValue = newValue?.replace(regex, (match: string) => match + '\n\n')
if (updateFormValue && newValue) {
updateFormValue(newValue)
}
}
const editor = useEditor({
extensions: [
Color.configure({ types: ['textStyle', 'listItem'] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false,
},
orderedList: {
keepMarks: true,
keepAttributes: false,
},
}),
Markdown.configure({
linkify: true,
transformCopiedText: true,
transformPastedText: true,
}),
Image,
Link,
Placeholder.configure({
placeholder,
emptyEditorClass: 'is-editor-empty',
}),
],
content: defaultValue,
onUpdate: handleChange,
editorProps: {
attributes: {
class: `tw:h-full markdown tw:max-h-full tw:p-2 tw:overflow-y-auto`,
},
},
})
useEffect(() => {
if (editor?.storage.markdown.getMarkdown() === '' || !editor?.storage.markdown.getMarkdown()) {
editor?.commands.setContent(defaultValue)
}
}, [defaultValue, editor])
return (
<div
className={`tw:form-control tw:w-full tw:flex tw:flex-col tw:min-h-0 ${containerStyle ?? ''}`}
>
{labelTitle ? (
<label className='tw:label tw:pb-1'>
<span className={`tw:label-text tw:text-base-content ${labelStyle ?? ''}`}>
{labelTitle}
</span>
</label>
) : null}
<div
className={`editor-wrapper tw:border-base-content/20 tw:rounded-box tw:border tw:flex tw:flex-col tw:flex-1 tw:min-h-0`}
>
{editor ? (
<>
{showMenu ? <TextEditorMenu editor={editor} /> : null}
<EditorContent editor={editor} />
</>
) : null}
</div>
</div>
)
}