import { Node, mergeAttributes } from '@tiptap/core' import { ReactNodeViewRenderer, NodeViewWrapper, ReactNodeViewProps } from '@tiptap/react' import { motion, AnimatePresence } from 'framer-motion' import { useState } from 'react' import { Check } from 'lucide-react' interface CharacterAttributes { id: string | null; name: string; avatar: string; gender: string; age: string; } interface Role { name: string; url: string; } interface CharacterTokenOptions { roles?: Role[]; } export function CharacterToken(props: ReactNodeViewProps) { const [showRoleList, setShowRoleList] = useState(false) const { name, avatar } = props.node.attrs as CharacterAttributes const extension = props.extension as Node const roles = extension.options.roles || [] const handleRoleSelect = (role: Role) => { const { editor } = props; const pos = props.getPos(); if (typeof pos === 'number') { const { tr } = editor.state; tr.setNodeMarkup(pos, undefined, { ...props.node.attrs, name: role.name, avatar: role.url, }); editor.view.dispatch(tr); } setShowRoleList(false); } return ( setShowRoleList(false)} onMouseEnter={() => setShowRoleList(true)} > {name} {showRoleList && (
{roles.map((role) => { const isSelected = role.name === name; return (
handleRoleSelect(role)} >
{role.name} {isSelected && (
)}
{role.name}
); })}
)}
) } export const CharacterTokenExtension = Node.create({ name: 'characterToken', group: 'inline', inline: true, atom: true, addOptions() { return { roles: [], } }, addAttributes() { return { id: { default: null }, name: { default: '' }, gender: { default: '' }, age: { default: '' }, avatar: { default: '' }, }; }, parseHTML() { return [{ tag: 'character-token' }]; }, renderHTML({ HTMLAttributes }) { return ['character-token', mergeAttributes(HTMLAttributes)]; }, addNodeView() { return ReactNodeViewRenderer(CharacterToken); }, });