diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue
index 9fbab75..d32f597 100644
--- a/src/components/editor/CustomEditor.vue
+++ b/src/components/editor/CustomEditor.vue
@@ -174,6 +174,23 @@ const showMentionList = () => {
selectedMentionIndex.value = 0
}
+// 处理鼠标点击选择mention
+const handleMentionSelectByMouse = (member) => {
+ const selection = window.getSelection();
+ if (selection && selection.rangeCount > 0) {
+ insertMention(member, selection.getRangeAt(0).cloneRange());
+ } else {
+ // 如果没有有效的选区,尝试聚焦编辑器并获取选区
+ editorRef.value?.focus();
+ nextTick(() => {
+ const newSelection = window.getSelection();
+ if (newSelection && newSelection.rangeCount > 0) {
+ insertMention(member, newSelection.getRangeAt(0).cloneRange());
+ }
+ });
+ }
+};
+
// 隐藏mention列表
const hideMentionList = () => {
showMention.value = false
@@ -193,66 +210,74 @@ const updateMentionPosition = (range) => {
}
// 插入mention
-const insertMention = (member) => {
- console.log('插入mention',member)
- const selection = window.getSelection()
- if (!selection.rangeCount) return
-
- const range = selection.getRangeAt(0)
- const textNode = range.startContainer
- const offset = range.startOffset
-
- // 找到@符号的位置
- const textContent = textNode.textContent || ''
- const atIndex = textContent.lastIndexOf('@', offset - 1)
-
- // 创建mention元素
- const mentionSpan = document.createElement('span')
- mentionSpan.className = 'mention'
- mentionSpan.setAttribute('data-user-id',String(member.id))
- mentionSpan.textContent = `@${member.value || member.nickname}`
- mentionSpan.contentEditable = 'false'
-
- if (atIndex !== -1) {
- // 如果找到@符号,替换文本
- const beforeText = textContent.substring(0, atIndex)
- const afterText = textContent.substring(offset)
-
- // 创建新的文本节点
- const beforeNode = document.createTextNode(beforeText)
- const afterNode = document.createTextNode(' ' + afterText)
-
- // 替换内容
- const parent = textNode.parentNode
- parent.insertBefore(beforeNode, textNode)
- parent.insertBefore(mentionSpan, textNode)
- parent.insertBefore(afterNode, textNode)
- parent.removeChild(textNode)
+const insertMention = (member, clonedRange) => {
+ console.log('插入mention', member);
+ const selection = window.getSelection();
+ if (!clonedRange || !selection) return;
+
+ const range = clonedRange; // 使用传入的克隆 range
+
+ const textNode = range.startContainer;
+ const offset = range.startOffset;
+ const textContent = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent || '' : '';
+ // @符号的查找逻辑仅当光标在文本节点内且不在开头时才有意义
+ const atIndex = (textNode.nodeType === Node.TEXT_NODE && offset > 0) ? textContent.lastIndexOf('@', offset - 1) : -1;
+
+ const mentionSpan = document.createElement('span');
+ mentionSpan.className = 'mention';
+ mentionSpan.setAttribute('data-user-id', String(member.id));
+ mentionSpan.textContent = `@${member.value || member.nickname}`;
+ mentionSpan.contentEditable = 'false';
+
+ if (atIndex !== -1 && textNode.nodeType === Node.TEXT_NODE) {
+ const parent = textNode.parentNode;
+ if (!parent) return; // Sanity check
+
+ const textBeforeAt = textContent.substring(0, atIndex);
+ const textAfterCursor = textContent.substring(offset);
+
+ const beforeNode = document.createTextNode(textBeforeAt);
+ const spaceAfterMentionNode = document.createTextNode(' '); // Ensure space after mention
+ const afterNode = document.createTextNode(textAfterCursor);
+
+ parent.insertBefore(beforeNode, textNode);
+ parent.insertBefore(mentionSpan, textNode);
+ parent.insertBefore(spaceAfterMentionNode, textNode);
+ parent.insertBefore(afterNode, textNode);
+ parent.removeChild(textNode);
+
+ range.setStartAfter(spaceAfterMentionNode);
+ range.collapse(true);
} else {
- // 如果没有找到@符号,直接在光标位置插入
- range.deleteContents()
-
- // 插入@提及元素
- range.insertNode(mentionSpan)
-
- // 在@提及元素后添加空格
- const spaceNode = document.createTextNode(' ')
- range.setStartAfter(mentionSpan)
- range.insertNode(spaceNode)
-
- // 将光标移动到空格后
- range.setStartAfter(spaceNode)
- range.collapse(true)
- selection.removeAllRanges()
- selection.addRange(range)
+ // 如果没有找到@符号,或者光标不在合适的文本节点内,直接在当前光标位置插入
+ if (!range.collapsed) {
+ range.deleteContents();
+ }
+
+ range.insertNode(mentionSpan);
+
+ const spaceNode = document.createTextNode(' ');
+ // 正确地将 range 移动到 mentionSpan 之后再插入空格
+ const tempRangeForSpace = range.cloneRange(); // 使用临时 range 避免干扰主 range
+ tempRangeForSpace.setStartAfter(mentionSpan);
+ tempRangeForSpace.collapse(true);
+ tempRangeForSpace.insertNode(spaceNode);
+
+ // 将主 range 移动到新插入的空格之后
+ range.setStartAfter(spaceNode);
+ range.collapse(true);
}
-
- // 触发输入事件以更新编辑器内容
- handleInput({ target: editorRef.value })
-
- // 隐藏mention列表
- hideMentionList()
-}
+
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ editorRef.value?.focus(); // 确保编辑器在操作后仍有焦点
+
+ nextTick(() => {
+ handleInput({ target: editorRef.value });
+ hideMentionList();
+ });
+};
// 处理粘贴事件
const handlePaste = (event) => {
@@ -350,12 +375,15 @@ const handleKeydown = (event) => {
break
case 'Enter':
case 'Tab':
- event.preventDefault()
- const selectedMember = mentionList.value[selectedMentionIndex.value]
+ event.preventDefault();
+ const selectedMember = mentionList.value[selectedMentionIndex.value];
if (selectedMember) {
- insertMention(selectedMember)
+ const selection = window.getSelection();
+ if (selection && selection.rangeCount > 0) {
+ insertMention(selectedMember, selection.getRangeAt(0).cloneRange());
+ }
}
- break
+ break;
case 'Escape':
hideMentionList()
break
@@ -398,6 +426,25 @@ const handleKeydown = (event) => {
}
prevSibling = prevSibling.previousSibling
}
+ } else {
+ // 如果光标在文本节点中间或末尾,且当前文本节点只包含空格
+ // 检查前一个兄弟节点是否是mention
+ if (!container.textContent.trim()) {
+ let prevSibling = container.previousSibling
+ while (prevSibling) {
+ if (prevSibling.nodeType === Node.ELEMENT_NODE &&
+ prevSibling.classList &&
+ prevSibling.classList.contains('mention')) {
+ targetMention = prevSibling
+ break
+ }
+ // 如果是文本节点且不为空,停止查找
+ if (prevSibling.nodeType === Node.TEXT_NODE && prevSibling.textContent.trim()) {
+ break
+ }
+ prevSibling = prevSibling.previousSibling
+ }
+ }
}
} else if (container.nodeType === Node.ELEMENT_NODE) {
// 如果光标在元素节点中,检查前一个子节点
@@ -930,27 +977,44 @@ const insertImageEmoji = (imgSrc, altText) => {
}
// 事件监听
-const onSubscribeMention = (data) => {
- // 确保编辑器获得焦点
- editorRef.value?.focus()
-
- // 如果编辑器为空或者光标不在编辑器内,将光标移动到编辑器末尾
- const selection = window.getSelection()
- if (!selection.rangeCount || !editorRef.value.contains(selection.anchorNode)) {
- const range = document.createRange()
- if (editorRef.value.lastChild) {
- range.setStartAfter(editorRef.value.lastChild)
+const onSubscribeMention = async (data) => {
+ const editorNode = editorRef.value;
+ if (!editorNode) return;
+
+ editorNode.focus();
+ await nextTick(); // 确保焦点已设置
+
+ let selection = window.getSelection();
+ if (!selection || selection.rangeCount === 0) {
+ const range = document.createRange();
+ if (editorNode.lastChild) {
+ range.setStartAfter(editorNode.lastChild);
} else {
- range.setStart(editorRef.value, 0)
+ range.setStart(editorNode, 0);
}
- range.collapse(true)
- selection.removeAllRanges()
- selection.addRange(range)
+ range.collapse(true);
+ if (selection) selection.removeAllRanges();
+ selection?.addRange(range);
+ await nextTick();
+ selection = window.getSelection();
+ } else if (!editorNode.contains(selection.anchorNode)) {
+ const range = document.createRange();
+ if (editorNode.lastChild) {
+ range.setStartAfter(editorNode.lastChild);
+ } else {
+ range.setStart(editorNode, 0);
+ }
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ await nextTick();
+ selection = window.getSelection();
}
-
- // 插入@提及
- insertMention(data)
-}
+
+ if (selection && selection.rangeCount > 0) {
+ insertMention(data, selection.getRangeAt(0).cloneRange());
+ }
+};
const onSubscribeQuote = (data) => {
// 保存引用数据,但不保存到草稿中
@@ -1443,7 +1507,7 @@ const handleEditorClick = (event) => {
{{ nav.title }}
-