forked from 77media/video-flow
73 lines
1.8 KiB
TypeScript
73 lines
1.8 KiB
TypeScript
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 HighlightTextAttributes {
|
|
type: string;
|
|
text: string;
|
|
color: string;
|
|
}
|
|
|
|
interface HighlightTextOptions {
|
|
type?: string;
|
|
text: string;
|
|
color: string;
|
|
}
|
|
|
|
export function HighlightText(props: ReactNodeViewProps) {
|
|
const { text: initialText, color } = props.node.attrs as HighlightTextAttributes
|
|
const [text, setText] = useState(initialText)
|
|
|
|
const handleInput = (e: React.FormEvent<HTMLSpanElement>) => {
|
|
const newText = e.currentTarget.textContent || ''
|
|
setText(newText)
|
|
// 通知Tiptap更新内容
|
|
props.updateAttributes({
|
|
text: newText
|
|
})
|
|
}
|
|
|
|
return (
|
|
<NodeViewWrapper
|
|
as="span"
|
|
data-alt="highlight-text"
|
|
contentEditable={true}
|
|
suppressContentEditableWarning={true}
|
|
onInput={handleInput}
|
|
className={`relative inline text-${color}-400 hover:text-${color}-300 transition-colors duration-200`}
|
|
>
|
|
{text}
|
|
|
|
{/* 暂时空着 为后续可视化文本预留 */}
|
|
</NodeViewWrapper>
|
|
)
|
|
}
|
|
|
|
export const HighlightTextExtension = Node.create<HighlightTextOptions>({
|
|
name: 'highlightText',
|
|
group: 'inline',
|
|
inline: true,
|
|
atom: false,
|
|
|
|
addAttributes() {
|
|
return {
|
|
type: { default: null },
|
|
text: { default: '' },
|
|
color: { default: 'blue' },
|
|
};
|
|
},
|
|
|
|
parseHTML() {
|
|
return [{ tag: 'highlight-text' }];
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes }) {
|
|
return ['highlight-text', mergeAttributes(HTMLAttributes)];
|
|
},
|
|
|
|
addNodeView() {
|
|
return ReactNodeViewRenderer(HighlightText);
|
|
},
|
|
}); |