diff --git a/portal-ui/src/components/RichTextEditor.vue b/portal-ui/src/components/RichTextEditor.vue index 7eeb6d9..5e6e125 100644 --- a/portal-ui/src/components/RichTextEditor.vue +++ b/portal-ui/src/components/RichTextEditor.vue @@ -28,6 +28,7 @@ @keyup="handleKeyup" @click="handleEditorClick" @keydown="handleKeydown" + @paste="handlePaste" > @@ -187,6 +188,64 @@ export default { } } + const insertPlainTextAtCursor = (text) => { + if (!editorRef.value || text == null) return + const normalized = String(text).replace(/\r\n/g, '\n').replace(/\r/g, '\n') + editorRef.value.focus() + const selection = window.getSelection() + if (!selection || selection.rangeCount === 0) return + let range = selection.getRangeAt(0) + if (!editorRef.value.contains(range.commonAncestorContainer)) { + const r = document.createRange() + r.selectNodeContents(editorRef.value) + r.collapse(false) + selection.removeAllRanges() + selection.addRange(r) + range = selection.getRangeAt(0) + } + range.deleteContents() + const lines = normalized.split('\n') + const fragment = document.createDocumentFragment() + lines.forEach((line, index) => { + if (line) fragment.appendChild(document.createTextNode(line)) + if (index < lines.length - 1) fragment.appendChild(document.createElement('br')) + }) + range.insertNode(fragment) + range.collapse(false) + selection.removeAllRanges() + selection.addRange(range) + } + + const handlePaste = (e) => { + if (!editorRef.value) return + const cd = e.clipboardData + if (!cd) return + const items = cd.items + if (items) { + for (let i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + const blob = items[i].getAsFile() + const reader = new FileReader() + reader.onload = (event) => { + insertImage(event.target.result, 'pasted-image') + } + reader.readAsDataURL(blob) + e.preventDefault() + return + } + } + } + let text = cd.getData('text/plain') || '' + if (!text && cd.getData('text/html')) { + const tmp = document.createElement('div') + tmp.innerHTML = cd.getData('text/html') + text = tmp.innerText || '' + } + e.preventDefault() + insertPlainTextAtCursor(text) + handleInput() + } + // 显示 @ 面板 const showMentionPanel = () => { if (props.uploadedImages && props.uploadedImages.length > 0) { @@ -261,23 +320,6 @@ export default { onMounted(() => { if (editorRef.value) { editorRef.value.innerHTML = props.modelValue || '' - - // 添加粘贴事件处理 - editorRef.value.addEventListener('paste', (e) => { - const items = e.clipboardData.items - for (let i = 0; i < items.length; i++) { - if (items[i].type.indexOf('image') !== -1) { - const blob = items[i].getAsFile() - const reader = new FileReader() - reader.onload = (event) => { - insertImage(event.target.result, 'pasted-image') - } - reader.readAsDataURL(blob) - e.preventDefault() - break - } - } - }) } }) @@ -292,6 +334,7 @@ export default { handleInput, handleKeyup, handleKeydown, + handlePaste, handleEditorClick, insertMentionImage, clear @@ -341,7 +384,12 @@ export default { font-size: 14px; outline: none; overflow-y: auto; - + + /* 覆盖粘贴残留的内联颜色,保证深底上始终可读 */ + :deep(*) { + color: #fff !important; + } + &:empty:before { content: attr(data-placeholder); color: #666; diff --git a/portal-ui/src/components/VideoComposeCard.vue b/portal-ui/src/components/VideoComposeCard.vue index a54a4a4..e66aed3 100644 --- a/portal-ui/src/components/VideoComposeCard.vue +++ b/portal-ui/src/components/VideoComposeCard.vue @@ -193,6 +193,7 @@ contenteditable="true" :data-placeholder="placeholder" @input="onEditorInput" + @paste="onEditorPaste" @keydown="onEditorKeydown" @keyup="onEditorKeyup" @click="onEditorClick"> @@ -916,6 +917,64 @@ const restoreSelection = () => { selection.addRange(range) } +const insertPlainTextInEditor = (text) => { + if (!editorRef.value) return + const normalizedText = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n') + editorRef.value.focus() + restoreSelection() + const selection = window.getSelection() + if (!selection || selection.rangeCount === 0) return + let range = selection.getRangeAt(0) + if (!editorRef.value.contains(range.commonAncestorContainer)) { + const r = document.createRange() + r.selectNodeContents(editorRef.value) + r.collapse(false) + selection.removeAllRanges() + selection.addRange(r) + range = selection.getRangeAt(0) + } + range.deleteContents() + const lines = normalizedText.split('\n') + const fragment = document.createDocumentFragment() + lines.forEach((line, index) => { + if (line) fragment.appendChild(document.createTextNode(line)) + if (index < lines.length - 1) fragment.appendChild(document.createElement('br')) + }) + range.insertNode(fragment) + range.collapse(false) + selection.removeAllRanges() + selection.addRange(range) + saveSelection() +} + +const onEditorPaste = (e) => { + if (!editorRef.value) return + const cd = e.clipboardData + if (!cd) return + const items = cd.items + if (items) { + for (const item of items) { + if (item.kind === 'file' && item.type && item.type.startsWith('image/')) { + return + } + } + } + let text = cd.getData('text/plain') || '' + if (!text) { + const html = cd.getData('text/html') + if (html) { + const tmp = document.createElement('div') + tmp.innerHTML = html + text = tmp.innerText || '' + } + } + if (!text) return + e.preventDefault() + insertPlainTextInEditor(text) + setPrompt(getEditorPlainText()) + saveSelection() +} + const hasActiveMentionTrigger = () => { const selection = window.getSelection() if (!selection || selection.rangeCount === 0) return false @@ -2255,6 +2314,16 @@ defineExpose({ word-break: break-word; overflow-y: auto; scrollbar-color: rgba(255, 255, 255, 0.22) rgba(255, 255, 255, 0.06); + color: rgba(255, 255, 255, 0.92); + + :deep(*) { + color: rgba(255, 255, 255, 0.92) !important; + } + + :deep(.vg-inline-ref-audio) { + color: rgba(255, 255, 255, 0.88) !important; + background: rgba(0, 202, 224, 0.12); + } } .vg-rich-editor:empty:before { diff --git a/portal-ui/src/components/VideoRichEditor.vue b/portal-ui/src/components/VideoRichEditor.vue index 6cd63bf..1107b5f 100644 --- a/portal-ui/src/components/VideoRichEditor.vue +++ b/portal-ui/src/components/VideoRichEditor.vue @@ -496,6 +496,15 @@ onMounted(() => { overflow-y: auto; color: rgba(255, 255, 255, 0.9); caret-color: #00cae0; + + :deep(*) { + color: rgba(255, 255, 255, 0.9) !important; + } + + :deep(.inline-rich-reference) { + color: #5eebf5 !important; + background: rgba(0, 202, 224, 0.15); + } border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 14px; padding: 16px 18px;