166 lines
5.7 KiB
TypeScript
166 lines
5.7 KiB
TypeScript
import { useCallback } from 'react'
|
|
|
|
import {
|
|
mdiCodeBracesBox,
|
|
mdiFormatBold,
|
|
mdiFormatClear,
|
|
mdiFormatItalic,
|
|
mdiFormatListBulleted,
|
|
mdiFormatListNumbered,
|
|
mdiFormatQuoteClose,
|
|
mdiFormatStrikethrough,
|
|
mdiFormatUnderline,
|
|
mdiLinkVariant,
|
|
mdiLinkVariantOff,
|
|
mdiRedoVariant,
|
|
mdiUndoVariant,
|
|
mdiXml,
|
|
} from '@mdi/js'
|
|
import Icon from '@mdi/react'
|
|
import { EditorContent } from '@tiptap/react'
|
|
import type { Editor } from '@tiptap/react'
|
|
|
|
interface RichTextEditorProps {
|
|
editor: Editor
|
|
}
|
|
|
|
const RichTextEditor = ({ editor }: RichTextEditorProps) => {
|
|
const setLink = useCallback(() => {
|
|
const previousUrl = editor.getAttributes('link').href
|
|
const url = window.prompt('URL', previousUrl)
|
|
|
|
if (url === null) return
|
|
|
|
if (url === '') {
|
|
editor.chain().focus().extendMarkRange('link').unsetLink().run()
|
|
return
|
|
}
|
|
|
|
editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
|
|
}, [editor])
|
|
|
|
if (!editor) return null
|
|
|
|
return (
|
|
<div className="flex flex-col gap-2">
|
|
<div className="flex flex-wrap gap-2 justify-center">
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('link') ? 'btn-primary' : ''}`}
|
|
onClick={setLink}
|
|
>
|
|
<Icon path={mdiLinkVariant} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn btn-sm btn-square"
|
|
onClick={() => editor.chain().focus().unsetLink().run()}
|
|
disabled={!editor.isActive('link')}
|
|
>
|
|
<Icon path={mdiLinkVariantOff} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('bulletList') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
|
>
|
|
<Icon path={mdiFormatListBulleted} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('orderedList') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
|
>
|
|
<Icon path={mdiFormatListNumbered} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn btn-sm btn-square"
|
|
onClick={() => editor.chain().focus().unsetAllMarks().run()}
|
|
>
|
|
<Icon path={mdiFormatClear} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn btn-sm btn-square"
|
|
onClick={() => editor.chain().focus().undo().run()}
|
|
disabled={!editor.can().chain().focus().undo().run()}
|
|
>
|
|
<Icon path={mdiUndoVariant} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn btn-sm btn-square"
|
|
onClick={() => editor.chain().focus().redo().run()}
|
|
disabled={!editor.can().chain().focus().redo().run()}
|
|
>
|
|
<Icon path={mdiRedoVariant} className="size-5" />
|
|
</button>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2 justify-center">
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('bold') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
disabled={!editor.can().chain().focus().toggleBold().run()}
|
|
>
|
|
<Icon path={mdiFormatBold} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('italic') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
|
>
|
|
<Icon path={mdiFormatItalic} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('underline') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
|
disabled={!editor.can().chain().focus().toggleUnderline().run()}
|
|
>
|
|
<Icon path={mdiFormatUnderline} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('static') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
|
disabled={!editor.can().chain().focus().toggleStrike().run()}
|
|
>
|
|
<Icon path={mdiFormatStrikethrough} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('code') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
|
disabled={!editor.can().chain().focus().toggleCode().run()}
|
|
>
|
|
<Icon path={mdiXml} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('codeBlock') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
|
disabled={!editor.can().chain().focus().toggleCodeBlock().run()}
|
|
>
|
|
<Icon path={mdiCodeBracesBox} className="size-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-square ${editor.isActive('blockquote') ? 'btn-primary' : ''}`}
|
|
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
|
>
|
|
<Icon path={mdiFormatQuoteClose} className="size-5" />
|
|
</button>
|
|
</div>
|
|
<EditorContent
|
|
editor={editor}
|
|
className="textarea textarea-bordered font-normal prose"
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default RichTextEditor
|