fix(editor): 修复编辑器空内容判断和换行处理问题

改进编辑器空内容检测逻辑,确保更准确地判断是否为空内容
重构换行处理逻辑,使用辅助函数插入换行符并保持光标位置
优化消息发送前的空内容检查,防止发送无效消息
This commit is contained in:
Phoenix 2025-06-11 11:20:15 +08:00
parent d46ced7614
commit 88bbf16699
2 changed files with 116 additions and 66 deletions

View File

@ -123,11 +123,16 @@ const handleInput = (event) => {
const isEmpty = textContent === '' &&
!target.querySelector('img, .editor-file, .mention')
if (isEmpty && target.innerHTML !== '') {
target.innerHTML = ''
const editorNode = target;
const currentNormalizedHtml = editorNode.innerHTML.trim().toLowerCase().replace(/\s+/g, '');
const hasTextContent = editorNode.textContent.trim() !== '';
const hasSpecialElements = editorNode.querySelector('img, .editor-file, .mention');
if (!hasTextContent && !hasSpecialElements) {
if (currentNormalizedHtml !== '' && currentNormalizedHtml !== '<br>') {
editorNode.innerHTML = '';
}
}
@ -344,6 +349,36 @@ const handlePaste = (event) => {
}
// Helper function to insert line break
const insertLineBreak = (range) => {
const editor = editorRef.value;
if (!editor) return;
const br = document.createElement('br');
range.deleteContents(); // Clear selected content or collapsed cursor position
range.insertNode(br);
// Create a zero-width space or a text node to ensure the cursor can be placed after the <br>
// and that the <br> is not immediately removed by cleanup logic if it's the only content.
const nbsp = document.createTextNode('\u200B'); // Zero-width space
range.setStartAfter(br);
range.insertNode(nbsp);
range.setStartAfter(nbsp);
range.collapse(true);
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
// Ensure editor focus and trigger input handling
editor.focus();
nextTick(() => {
handleInput({ target: editor });
});
};
const handleKeydown = (event) => {
if (showMention.value) {
@ -531,45 +566,61 @@ const handleKeydown = (event) => {
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey || event.shiftKey)) {
const selection = window.getSelection()
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0)
const br = document.createElement('br')
range.deleteContents()
range.insertNode(br)
const textNode = document.createTextNode('')
range.setStartAfter(br)
range.insertNode(textNode)
range.setStartAfter(textNode)
range.collapse(true)
selection.removeAllRanges()
selection.addRange(range)
handleInput({ target: editorRef.value })
event.preventDefault();
const editor = editorRef.value;
if (!editor) return;
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) {
//
editor.focus();
// DOM
nextTick(() => {
const newSelection = window.getSelection();
if (newSelection && newSelection.rangeCount > 0) {
insertLineBreak(newSelection.getRangeAt(0));
}
});
return;
}
event.preventDefault()
return
insertLineBreak(selection.getRangeAt(0));
return;
}
if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
event.preventDefault()
const editor = editorRef.value
const quoteElement = editor?.querySelector('.editor-quote')
if (!quoteElement && quoteData.value) {
quoteData.value = null
event.preventDefault();
const editor = editorRef.value;
if (!editor) return;
const messageData = parseEditorContent();
const isEmptyMessage = messageData.items.length === 0 ||
(messageData.items.length === 1 &&
messageData.items[0].type === 1 &&
!messageData.items[0].content.trimEnd());
if (isEmptyMessage) {
// If the message is considered empty, prevent sending.
// Ensure editor is truly empty if it wasn't already.
if (editor.innerHTML !== '') {
clearEditor();
}
return;
}
sendMessage()
// If message is not empty, proceed to send.
const quoteElement = editor.querySelector('.editor-quote');
if (!quoteElement && quoteData.value) {
quoteData.value = null;
}
sendMessage();
}
}
@ -592,10 +643,15 @@ const sendMessage = () => {
}
messageData.items.forEach(item => {
if (item.type === 1 && cleanInvisibleChars(item.content.trimEnd())) {
if (item.type === 1 && cleanInvisibleChars(item.content).trimEnd()) { // Apply trimEnd after cleaning
const finalContent = cleanInvisibleChars(item.content).replace(/<br\s*\/?>/gi, '\n').trimEnd();
if (!finalContent && !messageData.mentionUids.length && !messageData.quoteId) {
// If after processing, the content is empty and no mentions/quote, skip
return;
}
const data = {
items: [{
content: cleanInvisibleChars(item.content),
content: finalContent, // Use the processed content
type: 1
}],
mentionUids: messageData.mentionUids,
@ -655,36 +711,30 @@ const parseEditorContent = () => {
const processNode = (node) => {
if (node.nodeType === Node.TEXT_NODE) {
textContent += node.textContent
return
textContent += node.textContent;
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) return
if (node.classList.contains('mention')) {
const userId = node.getAttribute('data-user-id')
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.tagName === 'BR') {
textContent += '\n';
} else if (node.classList.contains('mention')) {
const userId = node.getAttribute('data-user-id');
if (userId) {
mentionUids.push(Number(userId))
mentionUids.push(Number(userId));
}
textContent += node.textContent
textContent += node.textContent;
} else if (node.tagName === 'IMG') {
processImage(node)
processImage(node);
} else if (node.classList.contains('emoji')) {
textContent += node.getAttribute('alt') || node.textContent
textContent += node.getAttribute('alt') || node.textContent;
} else if (node.classList.contains('editor-file')) {
processFile(node)
processFile(node);
} else if (node.childNodes.length) {
Array.from(node.childNodes).forEach(processNode)
Array.from(node.childNodes).forEach(processNode);
} else {
textContent += node.textContent
textContent += node.textContent;
}
}
@ -758,11 +808,11 @@ const parseEditorContent = () => {
Array.from(tempDiv.childNodes).forEach(processNode)
if (textContent.trim()) {
if (textContent) {
items.push({
type: 1,
content: textContent.trimEnd()
})
content: textContent
});
}

View File

@ -18,7 +18,7 @@ export function isLoggedIn() {
*/
export function getAccessToken() {
// return storage.get(AccessToken) || ''
return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b891a491a664540c3af42964b31bedf8b1c93e8a754bb71e4b95d53ad8e6b16ac1575f536a9e7a062e44f3bb48a367623d38bd875a10afa3a53e79374ffda424138ed9ad4cab0d972432567ae7149b2bf3c'
return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b8993ebccf0349a53e3197efc45b9dbe3f2bf1dc0dddce6787811964e76efefec3b3fd39fce15d43989c156413f12de3f0c74c1ff1d3c5da214d3bcefef7546498e37fa73453c749a56ea66777488bd3550'
}
/**