forked from 77media/video-flow
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import { Node, mergeAttributes } from '@tiptap/core'
|
|
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { useState } from 'react'
|
|
|
|
export const CharacterToken = Node.create({
|
|
name: 'characterToken',
|
|
group: 'inline',
|
|
inline: true,
|
|
atom: true,
|
|
selectable: true,
|
|
|
|
addAttributes() {
|
|
return {
|
|
id: { default: null },
|
|
name: { default: '角色名' },
|
|
avatar: { default: '' },
|
|
gender: { default: '未知' },
|
|
age: { default: '-' },
|
|
}
|
|
},
|
|
|
|
parseHTML() {
|
|
return [{ tag: 'span[data-character]' }]
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes }) {
|
|
return ['span', mergeAttributes({ 'data-character': '' }, HTMLAttributes), HTMLAttributes.name]
|
|
},
|
|
|
|
addNodeView() {
|
|
return ReactNodeViewRenderer(CharacterView)
|
|
},
|
|
})
|
|
|
|
function CharacterView({ node }) {
|
|
const [showCard, setShowCard] = useState(false)
|
|
const { name, avatar, gender, age } = node.attrs
|
|
|
|
const handleClick = () => {
|
|
console.log('点击角色:', name)
|
|
alert(`点击角色:${name}`)
|
|
}
|
|
|
|
return (
|
|
<NodeViewWrapper
|
|
as="span"
|
|
contentEditable={false}
|
|
className="relative inline-block px-2 py-0.5 rounded bg-yellow-100 text-yellow-900 font-semibold cursor-pointer border border-yellow-300 shadow-sm hover:bg-yellow-200"
|
|
onMouseEnter={() => setShowCard(true)}
|
|
onMouseLeave={() => setShowCard(false)}
|
|
onClick={handleClick}
|
|
>
|
|
{name}
|
|
|
|
<AnimatePresence>
|
|
{showCard && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 4 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: 4 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="absolute top-full left-0 mt-2 w-60 rounded-lg bg-white text-black shadow-xl p-3 z-50"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<img
|
|
src={avatar || 'https://placekitten.com/64/64'}
|
|
alt={name}
|
|
className="w-12 h-12 rounded-full border"
|
|
/>
|
|
<div>
|
|
<div className="font-semibold text-base">{name}</div>
|
|
<div className="text-sm text-gray-600">{gender} / {age}</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</NodeViewWrapper>
|
|
)
|
|
}
|