Implement rich text for project description
This commit is contained in:
165
web/src/components/RichTextEditor/RichTextEditor.tsx
Normal file
165
web/src/components/RichTextEditor/RichTextEditor.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
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
|
Reference in New Issue
Block a user