Disable Resolve Comment button while comment text is being entered (#27465)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
GitOrigin-RevId: 05a2c522b6cc49237eec859f50bad8759050f9f9
This commit is contained in:
Alf Eaton
2025-07-29 11:26:54 +01:00
committed by Copybot
parent 2cfe2b1e15
commit 68d3d63e34
5 changed files with 110 additions and 84 deletions

View File

@@ -7,13 +7,13 @@ import { EditorSelection } from '@codemirror/state'
import { useTranslation } from 'react-i18next'
import { useThreadsActionsContext } from '../context/threads-context'
import { removeNewCommentRangeEffect } from '@/features/source-editor/extensions/review-tooltip'
import useSubmittableTextInput from '../hooks/use-submittable-text-input'
import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area'
import { ReviewPanelEntry } from './review-panel-entry'
import { ThreadId } from '../../../../../types/review-panel/review-panel'
import { useModalsContext } from '@/features/ide-react/context/modals-context'
import { debugConsole } from '@/utils/debugging'
import OLButton from '@/features/ui/components/ol/ol-button'
import { isFormSubmitKeypressEvent } from '@/features/review-panel-new/utils/form-events'
export const ReviewPanelAddComment = memo<{
docId: string
@@ -28,6 +28,7 @@ export const ReviewPanelAddComment = memo<{
const { addComment } = useThreadsActionsContext()
const [submitting, setSubmitting] = useState(false)
const { showGenericMessageModal } = useModalsContext()
const [content, setContent] = useState('')
const handleClose = useCallback(() => {
view.dispatch({
@@ -35,32 +36,56 @@ export const ReviewPanelAddComment = memo<{
})
}, [view, threadId])
const submitForm = useCallback(
async (message: string) => {
setSubmitting(true)
const submitForm = useCallback(async () => {
if (content.trim().length === 0) {
return
}
const content = view.state.sliceDoc(from, to)
setSubmitting(true)
try {
await addComment(from, content, message)
handleClose()
view.dispatch({
selection: EditorSelection.cursor(view.state.selection.main.anchor),
})
} catch (err) {
debugConsole.error(err)
showGenericMessageModal(
t('add_comment_error_title'),
t('add_comment_error_message')
)
const text = view.state.sliceDoc(from, to)
try {
await addComment(from, text, content)
handleClose()
view.dispatch({
selection: EditorSelection.cursor(view.state.selection.main.anchor),
})
} catch (err) {
debugConsole.error(err)
showGenericMessageModal(
t('add_comment_error_title'),
t('add_comment_error_message')
)
}
setSubmitting(false)
}, [
content,
view,
from,
to,
addComment,
handleClose,
showGenericMessageModal,
t,
])
const handleKeyPress = useCallback(
(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (isFormSubmitKeypressEvent(event)) {
event.preventDefault()
submitForm()
}
setSubmitting(false)
},
[addComment, view, handleClose, from, to, showGenericMessageModal, t]
[submitForm]
)
const { handleChange, handleKeyPress, content } =
useSubmittableTextInput(submitForm)
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value)
},
[]
)
const handleBlur = useCallback(() => {
if (content === '') {
@@ -73,9 +98,9 @@ export const ReviewPanelAddComment = memo<{
const handleSubmit = useCallback<FormEventHandler>(
event => {
event.preventDefault()
submitForm(content)
submitForm()
},
[submitForm, content]
[submitForm]
)
// We only ever want to focus the element once

View File

@@ -1,4 +1,4 @@
import { Dispatch, memo, SetStateAction, useCallback, useState } from 'react'
import { memo, useCallback, useState } from 'react'
import { Change, CommentOperation } from '../../../../../types/change'
import { ReviewPanelMessage } from './review-panel-message'
import { useTranslation } from 'react-i18next'
@@ -6,12 +6,12 @@ import { useThreadsContext } from '../context/threads-context'
import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area'
import ReviewPanelResolvedMessage from './review-panel-resolved-message'
import { ReviewPanelResolvedCommentThread } from '../../../../../types/review-panel/comment-thread'
import useSubmittableTextInput from '../hooks/use-submittable-text-input'
import {
CommentId,
ThreadId,
} from '../../../../../types/review-panel/review-panel'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
import { isFormSubmitKeypressEvent } from '@/features/review-panel-new/utils/form-events'
export const ReviewPanelCommentContent = memo<{
comment: Change<CommentOperation>
@@ -39,27 +39,46 @@ export const ReviewPanelCommentContent = memo<{
const threads = useThreadsContext()
const permissions = usePermissionsContext()
const [submitting, setSubmitting] = useState(false)
const [content, setContent] = useState('')
const handleSubmit = useCallback(
(content: string, setContent: Dispatch<SetStateAction<string>>) => {
if (!onReply || submitting) {
return
const hasActiveContent = content.trim().length > 0
const handleSubmit = useCallback(() => {
if (!onReply || submitting) {
return
}
if (!hasActiveContent) {
return
}
setSubmitting(true)
return onReply(content)
.then(() => {
setContent('')
})
.finally(() => {
setSubmitting(false)
})
}, [onReply, submitting, content, hasActiveContent])
const handleKeyPress = useCallback(
(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (isFormSubmitKeypressEvent(event)) {
event.preventDefault()
handleSubmit()
}
setSubmitting(true)
onReply(content)
.then(() => {
setContent('')
})
.finally(() => {
setSubmitting(false)
})
},
[onReply, submitting]
[handleSubmit]
)
const { handleChange, handleKeyPress, content } =
useSubmittableTextInput(handleSubmit)
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value)
},
[]
)
const thread = threads?.[comment.op.t]
if (!thread) {
@@ -85,6 +104,7 @@ export const ReviewPanelCommentContent = memo<{
isReply={isReply}
hasReplies={!isReply && thread.messages.length > 1}
onResolve={onResolve}
hasActiveContent={hasActiveContent}
onEdit={onEdit}
onDelete={() =>
isReply

View File

@@ -21,6 +21,7 @@ export const ReviewPanelMessage: FC<{
hasReplies: boolean
isReply: boolean
onResolve?: () => Promise<void>
hasActiveContent?: boolean
onEdit?: (commentId: CommentId, content: string) => Promise<void>
onDelete?: () => void
isThreadResolved: boolean
@@ -32,6 +33,7 @@ export const ReviewPanelMessage: FC<{
onEdit,
onDelete,
isThreadResolved,
hasActiveContent = false,
}) => {
const { t } = useTranslation()
const [editing, setEditing] = useState(false)
@@ -80,18 +82,21 @@ export const ReviewPanelMessage: FC<{
description={t('resolve_comment')}
tooltipProps={{ className: 'review-panel-tooltip' }}
>
<button
type="button"
tabIndex={0}
className="btn"
onClick={onResolve}
>
<MaterialIcon
type="check"
className="review-panel-entry-actions-icon"
accessibilityLabel={t('resolve_comment')}
/>
</button>
<span>
<button
type="button"
tabIndex={0}
className="btn"
onClick={onResolve}
disabled={hasActiveContent}
>
<MaterialIcon
type="check"
className="review-panel-entry-actions-icon"
accessibilityLabel={t('resolve_comment')}
/>
</button>
</span>
</OLTooltip>
</PreventSelectingEntry>
)}

View File

@@ -1,31 +0,0 @@
import { useCallback, useState, Dispatch, SetStateAction } from 'react'
export default function useSubmittableTextInput(
handleSubmit: (
content: string,
setContent: Dispatch<SetStateAction<string>>
) => void
) {
const [content, setContent] = useState('')
const handleKeyPress = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
e.preventDefault()
if (content.trim().length > 0) {
handleSubmit(content, setContent)
}
}
},
[content, handleSubmit]
)
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value)
},
[]
)
return { handleChange, handleKeyPress, content }
}

View File

@@ -0,0 +1,7 @@
export const isFormSubmitKeypressEvent = (
event: React.KeyboardEvent<HTMLTextAreaElement>
) => {
return (
event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.metaKey
)
}