video-flow-b/components/common/HighlightEditor.tsx
2025-08-27 15:23:29 +08:00

172 lines
5.4 KiB
TypeScript

import Placeholder from "@tiptap/extension-placeholder";
import { useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import { useEffect } from "react";
import { HighlightTextExtension } from "../ui/main-editor/HighlightText";
/**
* 高亮编辑器组件
* 使用 Tiptap 实现高亮和文本编辑功能
* 让 文本中的<type>xxxx</type>标签高亮
*/
export const HighlightEditor = ({
content,
onContentChange,
type,
placeholder,
cursorPosition,
onCursorPositionChange,
}: {
/** 内容 */
content: string;
/** 内容变化回调 */
onContentChange: (content: string) => void;
/** 标签类型*/
type: string;
/**提示语 */
placeholder: string;
/** 光标位置 */
cursorPosition?: number;
/** 光标位置变化回调 */
onCursorPositionChange?: (position: number) => void;
}) => {
console.log(44444);
const editor = useEditor({
extensions: [
StarterKit,
HighlightTextExtension,
Placeholder.configure({
placeholder,
emptyEditorClass: "is-editor-empty",
}),
],
content: "",
onUpdate: ({ editor }) => {
const textContent = editor.getText();
if (!textContent.trim()) {
onContentChange("");
return;
}
// 获取 HTML 内容并转换 highlight-text 标签
const htmlContent = editor.getHTML();
const convertedContent = htmlContent.replace(
/<highlight-text[^>]*type="([^"]*)"[^>]*text="([^"]*)"[^>]*>([^<]*)<\/highlight-text>/g,
'<$1>$2</$1>'
);
console.log('convertedContent:::', convertedContent);
// 保存当前光标位置
const { from } = editor.state.selection;
onCursorPositionChange?.(from);
// 传递转换后的内容
onContentChange(convertedContent);
},
editorProps: {
handleKeyDown: (view, event) => {
const { from, to } = view.state.selection;
const doc = view.state.doc;
// 检查光标前后是否有标签
const textBefore =
from > 0 ? doc.textBetween(Math.max(0, from - 50), from) : "";
const textAfter =
to < doc.content.size
? doc.textBetween(to, Math.min(doc.content.size, to + 50))
: "";
const beforeMatch = textBefore.match(new RegExp(`<${type}[^>]*>[^<]*$`));
const afterMatch = textAfter.match(new RegExp(`^[^>]*<\\/${type}>`));
// 只允许删除操作)
if (beforeMatch || afterMatch) {
if (event.key !== "Backspace" && event.key !== "Delete") {
event.preventDefault();
return true;
}
}
return false;
},
},
immediatelyRender: false,
});
useEffect(() => {
if (editor) {
if (!content || content.trim() === "") {
editor.commands.clearContent(true);
return;
}
// 将带标签的内容转换为高亮显示(支持新的标签格式)
const htmlContent = content.replace(
new RegExp(`<${type}[^>]*>([^<]+)<\/${type}>`, "g"),
'<highlight-text type="' + type + '" text="$1" color="blue">$1</highlight-text>'
);
console.log('111111', 111111)
editor.commands.setContent(htmlContent, { emitUpdate: false });
// 恢复光标位置
if (cursorPosition && cursorPosition > 0) {
// 确保光标位置不超出文档范围
const docSize = editor.state.doc.content.size;
const safePosition = Math.min(cursorPosition, docSize);
if (safePosition > 0) {
editor.commands.setTextSelection(safePosition);
}
}
}
}, [content, editor, cursorPosition]);
return (
<div className="flex-1 min-w-0 relative pr-20">
<style jsx>{`
.${type}-name-highlight {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white !important;
padding: 2px 6px;
border-radius: 4px;
font-weight: 500;
margin: 0 2px;
user-select: none;
cursor: default;
pointer-events: none;
}
/* 移除 Tiptap 编辑器的默认焦点样式 */
.ProseMirror:focus {
outline: none !important;
border: none !important;
box-shadow: none !important;
}
.ProseMirror:focus-visible {
outline: none !important;
border: none !important;
box-shadow: none !important;
}
/* 移除编辑器容器的焦点样式 */
.ProseMirror-focused {
outline: none !important;
border: none !important;
box-shadow: none !important;
}
/* 确保编辑器内容区域没有边框和轮廓 */
.ProseMirror {
outline: none !important;
border: none !important;
box-shadow: none !important;
}
`}</style>
<EditorContent
editor={editor}
className="w-full bg-transparent border-none outline-none resize-none text-white placeholder:text-white/40 text-sm leading-relaxed min-h-[60px] focus:outline-none focus:ring-0 focus:border-0"
style={{ outline: "none", border: "none", boxShadow: "none" }}
/>
</div>
);
};