97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
import { useEditor, EditorContent } from '@tiptap/react'
|
||
import StarterKit from '@tiptap/starter-kit'
|
||
import { useEffect } from 'react'
|
||
|
||
interface NoteCardProps {
|
||
anchorLabel: string
|
||
html: string
|
||
autoSaveMsg?: string
|
||
onChange: (html: string) => void
|
||
onClose: () => void
|
||
}
|
||
|
||
export function NoteCard({ anchorLabel, html, autoSaveMsg, onChange, onClose }: NoteCardProps) {
|
||
const editor = useEditor({
|
||
extensions: [StarterKit],
|
||
content: html || '<p></p>',
|
||
onUpdate({ editor }) {
|
||
const next = editor.getHTML()
|
||
onChange(next === '<p></p>' ? '' : next)
|
||
},
|
||
})
|
||
|
||
useEffect(() => {
|
||
if (!editor || editor.isDestroyed) return
|
||
if (editor.getHTML() !== html) {
|
||
editor.commands.setContent(html || '<p></p>', { emitUpdate: false })
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [html])
|
||
|
||
useEffect(() => () => { editor?.destroy() }, [editor])
|
||
|
||
if (!editor) return null
|
||
|
||
return (
|
||
<div className="note-card">
|
||
<div className="note-card-header">
|
||
<span className="note-card-label">✎ {anchorLabel}</span>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||
{autoSaveMsg && <span className="note-card-autosave">{autoSaveMsg}</span>}
|
||
<button type="button" className="note-card-close" onClick={onClose} aria-label="Close note">×</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="note-card-toolbar" role="toolbar" aria-label="Text formatting">
|
||
<button
|
||
type="button"
|
||
className={`note-tool${editor.isActive('bold') ? ' note-tool--active' : ''}`}
|
||
onMouseDown={e => { e.preventDefault(); editor.chain().focus().toggleBold().run() }}
|
||
aria-label="Bold"
|
||
title="Bold (Ctrl+B)"
|
||
><strong>B</strong></button>
|
||
<button
|
||
type="button"
|
||
className={`note-tool${editor.isActive('italic') ? ' note-tool--active' : ''}`}
|
||
onMouseDown={e => { e.preventDefault(); editor.chain().focus().toggleItalic().run() }}
|
||
aria-label="Italic"
|
||
title="Italic (Ctrl+I)"
|
||
><em>I</em></button>
|
||
<button
|
||
type="button"
|
||
className={`note-tool${editor.isActive('bulletList') ? ' note-tool--active' : ''}`}
|
||
onMouseDown={e => { e.preventDefault(); editor.chain().focus().toggleBulletList().run() }}
|
||
aria-label="Bullet list"
|
||
title="Bullet list"
|
||
>•—</button>
|
||
<button
|
||
type="button"
|
||
className={`note-tool${editor.isActive('orderedList') ? ' note-tool--active' : ''}`}
|
||
onMouseDown={e => { e.preventDefault(); editor.chain().focus().toggleOrderedList().run() }}
|
||
aria-label="Numbered list"
|
||
title="Numbered list"
|
||
>1.</button>
|
||
<div className="note-tool-divider" />
|
||
<button
|
||
type="button"
|
||
className="note-tool"
|
||
onMouseDown={e => { e.preventDefault(); editor.chain().focus().undo().run() }}
|
||
disabled={!editor.can().undo()}
|
||
aria-label="Undo"
|
||
title="Undo (Ctrl+Z)"
|
||
>↩</button>
|
||
<button
|
||
type="button"
|
||
className="note-tool"
|
||
onMouseDown={e => { e.preventDefault(); editor.chain().focus().redo().run() }}
|
||
disabled={!editor.can().redo()}
|
||
aria-label="Redo"
|
||
title="Redo (Ctrl+Y)"
|
||
>↪</button>
|
||
</div>
|
||
|
||
<EditorContent editor={editor} className="note-card-editor" />
|
||
</div>
|
||
)
|
||
}
|