From 68d3d63e344c8d7097dd7bf1ee43b70a07df918a Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 29 Jul 2025 11:26:54 +0100 Subject: [PATCH] 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 --- .../components/review-panel-add-comment.tsx | 71 +++++++++++++------ .../review-panel-comment-content.tsx | 56 ++++++++++----- .../components/review-panel-message.tsx | 29 ++++---- .../hooks/use-submittable-text-input.ts | 31 -------- .../review-panel-new/utils/form-events.ts | 7 ++ 5 files changed, 110 insertions(+), 84 deletions(-) delete mode 100644 services/web/frontend/js/features/review-panel-new/hooks/use-submittable-text-input.ts create mode 100644 services/web/frontend/js/features/review-panel-new/utils/form-events.ts diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx index f3e8b88a47..6a301c8b7a 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx @@ -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) => { + 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) => { + setContent(e.target.value) + }, + [] + ) const handleBlur = useCallback(() => { if (content === '') { @@ -73,9 +98,9 @@ export const ReviewPanelAddComment = memo<{ const handleSubmit = useCallback( event => { event.preventDefault() - submitForm(content) + submitForm() }, - [submitForm, content] + [submitForm] ) // We only ever want to focus the element once diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-comment-content.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-comment-content.tsx index 6ae385936b..7b42422e90 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-comment-content.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-comment-content.tsx @@ -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 @@ -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>) => { - 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) => { + 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) => { + 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 diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-message.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-message.tsx index 41c8a8d5a2..14b9b5c050 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-message.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-message.tsx @@ -21,6 +21,7 @@ export const ReviewPanelMessage: FC<{ hasReplies: boolean isReply: boolean onResolve?: () => Promise + hasActiveContent?: boolean onEdit?: (commentId: CommentId, content: string) => Promise 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' }} > - + + + )} diff --git a/services/web/frontend/js/features/review-panel-new/hooks/use-submittable-text-input.ts b/services/web/frontend/js/features/review-panel-new/hooks/use-submittable-text-input.ts deleted file mode 100644 index 919dc39415..0000000000 --- a/services/web/frontend/js/features/review-panel-new/hooks/use-submittable-text-input.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useCallback, useState, Dispatch, SetStateAction } from 'react' - -export default function useSubmittableTextInput( - handleSubmit: ( - content: string, - setContent: Dispatch> - ) => void -) { - const [content, setContent] = useState('') - - const handleKeyPress = useCallback( - (e: React.KeyboardEvent) => { - 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) => { - setContent(e.target.value) - }, - [] - ) - - return { handleChange, handleKeyPress, content } -} diff --git a/services/web/frontend/js/features/review-panel-new/utils/form-events.ts b/services/web/frontend/js/features/review-panel-new/utils/form-events.ts new file mode 100644 index 0000000000..ab8980f7b8 --- /dev/null +++ b/services/web/frontend/js/features/review-panel-new/utils/form-events.ts @@ -0,0 +1,7 @@ +export const isFormSubmitKeypressEvent = ( + event: React.KeyboardEvent +) => { + return ( + event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.metaKey + ) +}