feat: improve mention input loading state and prevent button flash on submit

This commit is contained in:
lyzno1 2025-10-11 14:13:27 +08:00
parent a4add403fb
commit 00dab7ca5f
No known key found for this signature in database
2 changed files with 17 additions and 14 deletions

View File

@ -13,7 +13,7 @@ import {
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiArrowUpLine, RiAtLine } from '@remixicon/react' import { RiArrowUpLine, RiAtLine, RiLoader2Line } from '@remixicon/react'
import Textarea from 'react-textarea-autosize' import Textarea from 'react-textarea-autosize'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Avatar from '@/app/components/base/avatar' import Avatar from '@/app/components/base/avatar'
@ -347,11 +347,9 @@ export const MentionInput: FC<MentionInputProps> = memo(({
const cursorPosition = textarea.selectionStart || 0 const cursorPosition = textarea.selectionStart || 0
const textBeforeCursor = value.slice(0, cursorPosition) const textBeforeCursor = value.slice(0, cursorPosition)
// 🔒 如果已经在 mention 模式,不插入新的 @
if (showMentionDropdown) if (showMentionDropdown)
return return
// 🔒 如果光标前已有未完成的 @,不插入新的 @
if (/@\w*$/.test(textBeforeCursor)) if (/@\w*$/.test(textBeforeCursor))
return return
@ -409,16 +407,21 @@ export const MentionInput: FC<MentionInputProps> = memo(({
}, 0) }, 0)
}, [value, mentionPosition, onChange, mentionedUserIds, evaluateContentLayout, syncHighlightScroll]) }, [value, mentionPosition, onChange, mentionedUserIds, evaluateContentLayout, syncHighlightScroll])
const handleSubmit = useCallback((e?: React.MouseEvent) => { const handleSubmit = useCallback(async (e?: React.MouseEvent) => {
if (e) { if (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
} }
if (value.trim()) { if (value.trim()) {
onSubmit(value.trim(), mentionedUserIds) try {
setMentionedUserIds([]) await onSubmit(value.trim(), mentionedUserIds)
setShowMentionDropdown(false) setMentionedUserIds([])
setShowMentionDropdown(false)
}
catch (error) {
console.error('Failed to submit', error)
}
} }
}, [value, mentionedUserIds, onSubmit]) }, [value, mentionedUserIds, onSubmit])
@ -540,7 +543,9 @@ export const MentionInput: FC<MentionInputProps> = memo(({
disabled={!value.trim() || disabled || loading} disabled={!value.trim() || disabled || loading}
onClick={handleSubmit} onClick={handleSubmit}
> >
<RiArrowUpLine className='h-4 w-4 text-components-button-primary-text' /> {loading
? <RiLoader2Line className='h-4 w-4 animate-spin text-components-button-primary-text' />
: <RiArrowUpLine className='h-4 w-4 text-components-button-primary-text' />}
</Button> </Button>
</div> </div>
)} )}
@ -574,6 +579,7 @@ export const MentionInput: FC<MentionInputProps> = memo(({
disabled={loading || !value.trim()} disabled={loading || !value.trim()}
onClick={() => handleSubmit()} onClick={() => handleSubmit()}
> >
{loading && <RiLoader2Line className='mr-1 h-3.5 w-3.5 animate-spin' />}
{t('common.operation.save')} {t('common.operation.save')}
</Button> </Button>
</div> </div>

View File

@ -161,12 +161,14 @@ export const CommentThread: FC<CommentThreadProps> = memo(({
const handleReplySubmit = useCallback(async (content: string, mentionedUserIds: string[]) => { const handleReplySubmit = useCallback(async (content: string, mentionedUserIds: string[]) => {
if (!onReply || loading) return if (!onReply || loading) return
setReplyContent('')
try { try {
await onReply(content, mentionedUserIds) await onReply(content, mentionedUserIds)
setReplyContent('')
} }
catch (error) { catch (error) {
console.error('Failed to send reply', error) console.error('Failed to send reply', error)
setReplyContent(content)
} }
}, [onReply, loading]) }, [onReply, loading])
@ -418,11 +420,6 @@ export const CommentThread: FC<CommentThreadProps> = memo(({
</div> </div>
)} )}
</div> </div>
{loading && (
<div className='bg-components-panel-bg/70 absolute inset-0 z-30 flex items-center justify-center text-sm text-text-tertiary'>
{t('workflow.comments.loading')}
</div>
)}
{onReply && ( {onReply && (
<div className='border-t border-components-panel-border px-4 py-3'> <div className='border-t border-components-panel-border px-4 py-3'>
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>