feat: trending report不再作为Placeholder
This commit is contained in:
@@ -53,7 +53,7 @@
|
|||||||
<span>Upload files</span>
|
<span>Upload files</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="gap"></div>
|
<div class="gap"></div>
|
||||||
<div class="report flex align-center" @click="toogltReportTag">
|
<div class="report flex align-center" @click="toogltReportTag()">
|
||||||
<SvgIcon color="#5A5A5A" name="light" size="11" />
|
<SvgIcon color="#5A5A5A" name="light" size="11" />
|
||||||
<span>Trending report</span>
|
<span>Trending report</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
v-if="!isAgentMode"
|
v-if="!isAgentMode"
|
||||||
class="report-btn flex space-between align-center outer"
|
class="report-btn flex space-between align-center outer"
|
||||||
:class="{ 'is-cn': isCn }"
|
:class="{ 'is-cn': isCn }"
|
||||||
@click="toogltReportTag"
|
@click="toogltReportTag()"
|
||||||
>
|
>
|
||||||
<SvgIcon class="light-icon" color="#FFDB56" name="light" size="16" />
|
<SvgIcon class="light-icon" color="#FFDB56" name="light" size="16" />
|
||||||
<span>{{ $t('Input.trendingReport') }}</span>
|
<span>{{ $t('Input.trendingReport') }}</span>
|
||||||
@@ -337,8 +337,61 @@
|
|||||||
|
|
||||||
const editorRef = ref<HTMLDivElement | null>(null)
|
const editorRef = ref<HTMLDivElement | null>(null)
|
||||||
const inputValue = ref<string>('')
|
const inputValue = ref<string>('')
|
||||||
const reportTags = ref([])
|
const reportTags = ref<HTMLElement[]>([])
|
||||||
const customPlaceholder = ref<HTMLElement | null>(null)
|
const reportPromptText = ref<Text | null>(null)
|
||||||
|
let reportTypewriterTimeout: ReturnType<typeof setTimeout> | 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(() => {
|
const showPlaceholder = computed(() => {
|
||||||
@@ -356,25 +409,6 @@
|
|||||||
return textContent === '' && !hasMeaningfulChildren && isEmptyHTML
|
return textContent === '' && !hasMeaningfulChildren && isEmptyHTML
|
||||||
})
|
})
|
||||||
|
|
||||||
// 打字机效果显示placeholder
|
|
||||||
let typewriterTimeout: ReturnType<typeof setTimeout> | 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) => {
|
const addReportTag = (text?: string) => {
|
||||||
// 使用传入的文本,如果没有传入则使用默认的翻译文本
|
// 使用传入的文本,如果没有传入则使用默认的翻译文本
|
||||||
@@ -404,15 +438,12 @@
|
|||||||
|
|
||||||
imgClose.addEventListener('click', (ev) => {
|
imgClose.addEventListener('click', (ev) => {
|
||||||
ev.stopPropagation()
|
ev.stopPropagation()
|
||||||
stopTypewriter() // 关闭标签时停止打字机效果
|
|
||||||
// remove tag when close clicked
|
// remove tag when close clicked
|
||||||
|
removeReportPromptText()
|
||||||
tag.remove()
|
tag.remove()
|
||||||
const idx = reportTags.value.indexOf(tag)
|
const idx = reportTags.value.indexOf(tag)
|
||||||
if (idx > -1) reportTags.value.splice(idx, 1)
|
if (idx > -1) reportTags.value.splice(idx, 1)
|
||||||
if (customPlaceholder.value) {
|
handleEditorInput()
|
||||||
customPlaceholder.value.remove()
|
|
||||||
customPlaceholder.value = null
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// assemble
|
// assemble
|
||||||
@@ -431,75 +462,48 @@
|
|||||||
const range = selection.getRangeAt(0)
|
const range = selection.getRangeAt(0)
|
||||||
range.insertNode(tag)
|
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
|
// ensure editor has focus
|
||||||
editorRef.value && (editorRef.value as HTMLElement).focus()
|
editorRef.value && (editorRef.value as HTMLElement).focus()
|
||||||
} else if (editorRef.value) {
|
} else if (editorRef.value) {
|
||||||
// If no selection in editor, append directly to editor and place caret after
|
// If no selection in editor, append directly to editor and place caret after
|
||||||
editorRef.value.appendChild(tag)
|
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()
|
editorRef.value && (editorRef.value as HTMLElement).focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
reportTags.value.push(tag)
|
reportTags.value.push(tag)
|
||||||
const placeholderSpan = document.createElement('span')
|
const promptText = document.createTextNode('')
|
||||||
placeholderSpan.className = 'custom-placeholder'
|
|
||||||
// 初始为空字符串,稍后通过打字机效果填充
|
|
||||||
placeholderSpan.innerText = ''
|
|
||||||
if (tag.parentNode) {
|
if (tag.parentNode) {
|
||||||
tag.parentNode.insertBefore(placeholderSpan, tag.nextSibling)
|
tag.parentNode.insertBefore(promptText, tag.nextSibling)
|
||||||
const zwsp = document.createTextNode('\u200B')
|
const zwsp = document.createTextNode('\u200B')
|
||||||
tag.parentNode.insertBefore(zwsp, placeholderSpan.nextSibling)
|
tag.parentNode.insertBefore(zwsp, promptText.nextSibling)
|
||||||
const newRange = document.createRange()
|
const newRange = document.createRange()
|
||||||
newRange.setStart(placeholderSpan, 0)
|
newRange.setStart(promptText, promptText.data.length)
|
||||||
newRange.collapse(true)
|
newRange.collapse(true)
|
||||||
selection.removeAllRanges()
|
const currentSelection = window.getSelection()
|
||||||
selection.addRange(newRange)
|
currentSelection?.removeAllRanges()
|
||||||
selection.addRange(newRange)
|
currentSelection?.addRange(newRange)
|
||||||
}
|
}
|
||||||
customPlaceholder.value = placeholderSpan
|
reportPromptText.value = promptText
|
||||||
|
typeReportPromptText(promptText, t('Input.reportPlaceholder'))
|
||||||
// 打字机效果显示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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toogltReportTag = (clear = false) => {
|
const toogltReportTag = (clear = false) => {
|
||||||
stopTypewriter() // 移除标签时停止打字机效果
|
const shouldClear = clear === true
|
||||||
|
|
||||||
// 清理掉已被删除的标签引用(从 DOM 中移除的元素)
|
// 清理掉已被删除的标签引用(从 DOM 中移除的元素)
|
||||||
reportTags.value = reportTags.value.filter((tag) => tag.parentNode !== null)
|
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) {
|
if (reportTags.value.length > 0) {
|
||||||
|
removeReportPromptText()
|
||||||
// 移除所有标签及其关联的零宽空格
|
// 移除所有标签及其关联的零宽空格
|
||||||
reportTags.value.forEach((tag) => {
|
reportTags.value.forEach((tag) => {
|
||||||
if (
|
if (
|
||||||
@@ -512,10 +516,7 @@
|
|||||||
tag.remove()
|
tag.remove()
|
||||||
})
|
})
|
||||||
reportTags.value = []
|
reportTags.value = []
|
||||||
if (customPlaceholder.value) {
|
handleEditorInput()
|
||||||
customPlaceholder.value.remove()
|
|
||||||
customPlaceholder.value = null
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 添加标签
|
// 添加标签
|
||||||
addReportTag()
|
addReportTag()
|
||||||
@@ -538,8 +539,8 @@
|
|||||||
editor.innerHTML = ''
|
editor.innerHTML = ''
|
||||||
editor.textContent = ''
|
editor.textContent = ''
|
||||||
reportTags.value = []
|
reportTags.value = []
|
||||||
customPlaceholder.value = null
|
stopReportTypewriter()
|
||||||
stopTypewriter()
|
reportPromptText.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -554,7 +555,6 @@
|
|||||||
let node: Node | null
|
let node: Node | null
|
||||||
while ((node = walker.nextNode())) {
|
while ((node = walker.nextNode())) {
|
||||||
// 使用 closest() 检查当前节点的祖先元素是否包含需要排除的 class
|
// 使用 closest() 检查当前节点的祖先元素是否包含需要排除的 class
|
||||||
if (node.parentElement?.closest('.custom-placeholder')) continue
|
|
||||||
if (node.parentElement?.closest('.editor-tag')) continue
|
if (node.parentElement?.closest('.editor-tag')) continue
|
||||||
text += node.textContent
|
text += node.textContent
|
||||||
}
|
}
|
||||||
@@ -569,12 +569,12 @@
|
|||||||
const hasTextContent = editor.textContent?.replace(/[\s\u200B]/g, '').trim().length > 0
|
const hasTextContent = editor.textContent?.replace(/[\s\u200B]/g, '').trim().length > 0
|
||||||
|
|
||||||
// 如果编辑器完全为空,清空它以显示占位符
|
// 如果编辑器完全为空,清空它以显示占位符
|
||||||
// 同时也要清空 reportTags 和 customPlaceholder
|
// 同时也要清空 reportTags 和 reportPromptText
|
||||||
if (!hasChildElements && !hasTextContent) {
|
if (!hasChildElements && !hasTextContent) {
|
||||||
editor.innerHTML = ''
|
editor.innerHTML = ''
|
||||||
reportTags.value = []
|
reportTags.value = []
|
||||||
customPlaceholder.value = null
|
stopReportTypewriter()
|
||||||
stopTypewriter()
|
reportPromptText.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动调整高度
|
// 自动调整高度
|
||||||
@@ -642,20 +642,14 @@
|
|||||||
const element = nodeToDelete as Element
|
const element = nodeToDelete as Element
|
||||||
const isEditorTag = element.classList.contains('editor-tag')
|
const isEditorTag = element.classList.contains('editor-tag')
|
||||||
const isReportTag = element.classList.contains('report-tag')
|
const isReportTag = element.classList.contains('report-tag')
|
||||||
const isCustomPlaceholder = element.classList.contains('custom-placeholder')
|
|
||||||
|
|
||||||
if (isEditorTag || isReportTag || isCustomPlaceholder) {
|
if (isEditorTag || isReportTag) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
removeReportPromptText()
|
||||||
element.remove()
|
element.remove()
|
||||||
|
|
||||||
// 如果删除的是 customPlaceholder,停止打字机效果
|
|
||||||
if (isCustomPlaceholder) {
|
|
||||||
stopTypewriter()
|
|
||||||
customPlaceholder.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 reportTags 中移除
|
// 从 reportTags 中移除
|
||||||
const index = reportTags.value.indexOf(nodeToDelete as Element)
|
const index = reportTags.value.indexOf(nodeToDelete as HTMLElement)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
reportTags.value.splice(index, 1)
|
reportTags.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
@@ -663,6 +657,7 @@
|
|||||||
// 删除标签后清理零宽字符并检查是否为空
|
// 删除标签后清理零宽字符并检查是否为空
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
cleanupEditor()
|
cleanupEditor()
|
||||||
|
handleEditorInput()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -677,10 +672,10 @@
|
|||||||
// 检查是否完全为空,需要显示占位符
|
// 检查是否完全为空,需要显示占位符
|
||||||
if (editorRef.value.children.length === 0) {
|
if (editorRef.value.children.length === 0) {
|
||||||
reportTags.value = []
|
reportTags.value = []
|
||||||
customPlaceholder.value = null
|
reportPromptText.value = null
|
||||||
stopTypewriter()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
handleEditorInput()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -837,6 +832,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
stopReportTypewriter()
|
||||||
MyEvent.remove('quote', handleQuote)
|
MyEvent.remove('quote', handleQuote)
|
||||||
MyEvent.remove('projectChange', handleInitInput)
|
MyEvent.remove('projectChange', handleInitInput)
|
||||||
})
|
})
|
||||||
@@ -1161,11 +1157,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.custom-placeholder {
|
|
||||||
color: #999;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.fida-style-select-popover {
|
.fida-style-select-popover {
|
||||||
width: 34.2rem !important;
|
width: 34.2rem !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main-input-container flex-1">
|
<div class="main-input-container flex-1">
|
||||||
<div class="slogan">
|
<div class="slogan">
|
||||||
<p>Creating Things with <span class="fiDA">FiDA</span> that</p>
|
<p>Creating Works with <span class="fiDA">FiDA</span> that</p>
|
||||||
<p>Bloom Your Creativity</p>
|
<p>Bloom Your Creativity</p>
|
||||||
</div>
|
</div>
|
||||||
<Input />
|
<Input />
|
||||||
|
|||||||
Reference in New Issue
Block a user