diff --git a/src/views/home/components/Input.vue b/src/views/home/components/Input.vue
index 36d958c..1827812 100644
--- a/src/views/home/components/Input.vue
+++ b/src/views/home/components/Input.vue
@@ -53,7 +53,7 @@
Upload files
-
+
Trending report
@@ -215,7 +215,7 @@
v-if="!isAgentMode"
class="report-btn flex space-between align-center outer"
:class="{ 'is-cn': isCn }"
- @click="toogltReportTag"
+ @click="toogltReportTag()"
>
{{ $t('Input.trendingReport') }}
@@ -337,8 +337,61 @@
const editorRef = ref
(null)
const inputValue = ref('')
- const reportTags = ref([])
- const customPlaceholder = ref(null)
+ const reportTags = ref([])
+ const reportPromptText = ref(null)
+ let reportTypewriterTimeout: ReturnType | null = null
+
+ const stopReportTypewriter = () => {
+ if (reportTypewriterTimeout) {
+ clearTimeout(reportTypewriterTimeout)
+ reportTypewriterTimeout = null
+ }
+ }
+
+ const moveCaretToTextEnd = (textNode: Text) => {
+ const selection = window.getSelection()
+ if (!selection || selection.anchorNode !== textNode) return
+
+ const range = document.createRange()
+ range.setStart(textNode, textNode.data.length)
+ range.collapse(true)
+ selection.removeAllRanges()
+ selection.addRange(range)
+ }
+
+ const typeReportPromptText = (textNode: Text, text: string, index = 0) => {
+ if (reportPromptText.value !== textNode) return
+
+ if (index >= text.length) {
+ reportTypewriterTimeout = null
+ return
+ }
+
+ textNode.textContent = `${textNode.textContent || ''}${text.charAt(index)}`
+ handleEditorInput()
+ moveCaretToTextEnd(textNode)
+
+ reportTypewriterTimeout = setTimeout(() => {
+ typeReportPromptText(textNode, text, index + 1)
+ }, 30)
+ }
+
+ const removeReportPromptText = () => {
+ stopReportTypewriter()
+ const promptText = reportPromptText.value
+ if (promptText?.parentNode) {
+ const nextNode = promptText.nextSibling
+ if (
+ nextNode &&
+ nextNode.nodeType === Node.TEXT_NODE &&
+ nextNode.textContent === '\u200B'
+ ) {
+ nextNode.remove()
+ }
+ promptText.parentNode.removeChild(promptText)
+ }
+ reportPromptText.value = null
+ }
// 控制占位符显示
const showPlaceholder = computed(() => {
@@ -356,25 +409,6 @@
return textContent === '' && !hasMeaningfulChildren && isEmptyHTML
})
- // 打字机效果显示placeholder
- let typewriterTimeout: ReturnType | null = null
- const typeWriterEffect = (element: HTMLElement, text: string, index: number = 0) => {
- if (index < text.length) {
- element.innerText += text.charAt(index)
- typewriterTimeout = setTimeout(() => {
- typeWriterEffect(element, text, index + 1)
- }, 30) // 每个字符间隔30ms
- }
- }
-
- // 停止打字机效果
- const stopTypewriter = () => {
- if (typewriterTimeout) {
- clearTimeout(typewriterTimeout)
- typewriterTimeout = null
- }
- }
-
// 导出给父组件调用的方法
const addReportTag = (text?: string) => {
// 使用传入的文本,如果没有传入则使用默认的翻译文本
@@ -404,15 +438,12 @@
imgClose.addEventListener('click', (ev) => {
ev.stopPropagation()
- stopTypewriter() // 关闭标签时停止打字机效果
// remove tag when close clicked
+ removeReportPromptText()
tag.remove()
const idx = reportTags.value.indexOf(tag)
if (idx > -1) reportTags.value.splice(idx, 1)
- if (customPlaceholder.value) {
- customPlaceholder.value.remove()
- customPlaceholder.value = null
- }
+ handleEditorInput()
})
// assemble
@@ -431,75 +462,48 @@
const range = selection.getRangeAt(0)
range.insertNode(tag)
- // Insert a zero-width space text node after the tag so the caret can be placed there
- const zwsp = document.createTextNode('\u200B')
- if (tag.parentNode) tag.parentNode.insertBefore(zwsp, tag.nextSibling)
-
- // Create a new collapsed range positioned inside the zwsp (after the tag)
- const newRange = document.createRange()
- newRange.setStart(zwsp, 1)
- newRange.collapse(true)
-
- selection.removeAllRanges()
- selection.addRange(newRange)
-
// ensure editor has focus
editorRef.value && (editorRef.value as HTMLElement).focus()
} else if (editorRef.value) {
// If no selection in editor, append directly to editor and place caret after
editorRef.value.appendChild(tag)
- const zwsp = document.createTextNode('\u200B')
- editorRef.value.appendChild(zwsp)
- const sel = window.getSelection()
- if (sel) {
- const r = document.createRange()
- r.setStart(zwsp, 1)
- r.collapse(true)
- sel.removeAllRanges()
- sel.addRange(r)
- }
editorRef.value && (editorRef.value as HTMLElement).focus()
}
reportTags.value.push(tag)
- const placeholderSpan = document.createElement('span')
- placeholderSpan.className = 'custom-placeholder'
- // 初始为空字符串,稍后通过打字机效果填充
- placeholderSpan.innerText = ''
+ const promptText = document.createTextNode('')
if (tag.parentNode) {
- tag.parentNode.insertBefore(placeholderSpan, tag.nextSibling)
+ tag.parentNode.insertBefore(promptText, tag.nextSibling)
const zwsp = document.createTextNode('\u200B')
- tag.parentNode.insertBefore(zwsp, placeholderSpan.nextSibling)
+ tag.parentNode.insertBefore(zwsp, promptText.nextSibling)
const newRange = document.createRange()
- newRange.setStart(placeholderSpan, 0)
+ newRange.setStart(promptText, promptText.data.length)
newRange.collapse(true)
- selection.removeAllRanges()
- selection.addRange(newRange)
- selection.addRange(newRange)
+ const currentSelection = window.getSelection()
+ currentSelection?.removeAllRanges()
+ currentSelection?.addRange(newRange)
}
- customPlaceholder.value = placeholderSpan
-
- // 打字机效果显示placeholder文本
- const placeholderText = t('Input.reportPlaceholder')
- typeWriterEffect(placeholderSpan, placeholderText)
-
- const removePlaceholderOnInput = () => {
- stopTypewriter() // 用户输入时停止打字机效果
- if (placeholderSpan.parentNode) {
- placeholderSpan.remove()
- customPlaceholder.value = null
- }
- editorRef.value?.removeEventListener('input', removePlaceholderOnInput)
- }
- editorRef.value?.addEventListener('input', removePlaceholderOnInput)
+ reportPromptText.value = promptText
+ typeReportPromptText(promptText, t('Input.reportPlaceholder'))
}
const toogltReportTag = (clear = false) => {
- stopTypewriter() // 移除标签时停止打字机效果
+ const shouldClear = clear === true
+
// 清理掉已被删除的标签引用(从 DOM 中移除的元素)
reportTags.value = reportTags.value.filter((tag) => tag.parentNode !== null)
+ if (shouldClear) {
+ removeReportPromptText()
+ reportTags.value.forEach((tag) => {
+ tag.remove()
+ })
+ reportTags.value = []
+ return
+ }
+
if (reportTags.value.length > 0) {
+ removeReportPromptText()
// 移除所有标签及其关联的零宽空格
reportTags.value.forEach((tag) => {
if (
@@ -512,10 +516,7 @@
tag.remove()
})
reportTags.value = []
- if (customPlaceholder.value) {
- customPlaceholder.value.remove()
- customPlaceholder.value = null
- }
+ handleEditorInput()
} else {
// 添加标签
addReportTag()
@@ -538,8 +539,8 @@
editor.innerHTML = ''
editor.textContent = ''
reportTags.value = []
- customPlaceholder.value = null
- stopTypewriter()
+ stopReportTypewriter()
+ reportPromptText.value = null
}
}
}
@@ -554,7 +555,6 @@
let node: Node | null
while ((node = walker.nextNode())) {
// 使用 closest() 检查当前节点的祖先元素是否包含需要排除的 class
- if (node.parentElement?.closest('.custom-placeholder')) continue
if (node.parentElement?.closest('.editor-tag')) continue
text += node.textContent
}
@@ -569,12 +569,12 @@
const hasTextContent = editor.textContent?.replace(/[\s\u200B]/g, '').trim().length > 0
// 如果编辑器完全为空,清空它以显示占位符
- // 同时也要清空 reportTags 和 customPlaceholder
+ // 同时也要清空 reportTags 和 reportPromptText
if (!hasChildElements && !hasTextContent) {
editor.innerHTML = ''
reportTags.value = []
- customPlaceholder.value = null
- stopTypewriter()
+ stopReportTypewriter()
+ reportPromptText.value = null
}
// 自动调整高度
@@ -642,20 +642,14 @@
const element = nodeToDelete as Element
const isEditorTag = element.classList.contains('editor-tag')
const isReportTag = element.classList.contains('report-tag')
- const isCustomPlaceholder = element.classList.contains('custom-placeholder')
- if (isEditorTag || isReportTag || isCustomPlaceholder) {
+ if (isEditorTag || isReportTag) {
e.preventDefault()
+ removeReportPromptText()
element.remove()
- // 如果删除的是 customPlaceholder,停止打字机效果
- if (isCustomPlaceholder) {
- stopTypewriter()
- customPlaceholder.value = null
- }
-
// 从 reportTags 中移除
- const index = reportTags.value.indexOf(nodeToDelete as Element)
+ const index = reportTags.value.indexOf(nodeToDelete as HTMLElement)
if (index > -1) {
reportTags.value.splice(index, 1)
}
@@ -663,6 +657,7 @@
// 删除标签后清理零宽字符并检查是否为空
nextTick(() => {
cleanupEditor()
+ handleEditorInput()
})
return
}
@@ -677,10 +672,10 @@
// 检查是否完全为空,需要显示占位符
if (editorRef.value.children.length === 0) {
reportTags.value = []
- customPlaceholder.value = null
- stopTypewriter()
+ reportPromptText.value = null
}
}
+ handleEditorInput()
}
})
}
@@ -837,6 +832,7 @@
})
})
onUnmounted(() => {
+ stopReportTypewriter()
MyEvent.remove('quote', handleQuote)
MyEvent.remove('projectChange', handleInitInput)
})
@@ -1161,11 +1157,6 @@