mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-14 20:40:17 -05:00
Fix accept changes request for new review panel (#20956)
* Fix accept changes request for new review panel * Implement pessimistic UI updates for new review panel (#20986) * Implement pessimistic UI updates for new review panel * deleteThread, reopenThread handlers * use finally * handleSubmit in useCallback GitOrigin-RevId: 358181a6b5601ad1b3579e001564dfa4da67a81d
This commit is contained in:
parent
42a199043a
commit
4a3eed35d6
13 changed files with 313 additions and 137 deletions
|
@ -29,6 +29,8 @@
|
|||
"accept_all": "",
|
||||
"accept_and_continue": "",
|
||||
"accept_change": "",
|
||||
"accept_change_error_description": "",
|
||||
"accept_change_error_title": "",
|
||||
"accept_invitation": "",
|
||||
"accept_or_reject_each_changes_individually": "",
|
||||
"accept_terms_and_conditions": "",
|
||||
|
@ -53,6 +55,8 @@
|
|||
"add_another_token": "",
|
||||
"add_comma_separated_emails_help": "",
|
||||
"add_comment": "",
|
||||
"add_comment_error_message": "",
|
||||
"add_comment_error_title": "",
|
||||
"add_company_details": "",
|
||||
"add_email_address": "",
|
||||
"add_email_to_claim_features": "",
|
||||
|
@ -303,6 +307,8 @@
|
|||
"delete_authentication_token_info": "",
|
||||
"delete_certificate": "",
|
||||
"delete_comment": "",
|
||||
"delete_comment_error_message": "",
|
||||
"delete_comment_error_title": "",
|
||||
"delete_comment_message": "",
|
||||
"delete_comment_thread": "",
|
||||
"delete_comment_thread_message": "",
|
||||
|
@ -384,6 +390,8 @@
|
|||
"easily_import_and_sync_your_references": "",
|
||||
"easily_manage_your_project_files_everywhere": "",
|
||||
"edit": "",
|
||||
"edit_comment_error_message": "",
|
||||
"edit_comment_error_title": "",
|
||||
"edit_dictionary": "",
|
||||
"edit_dictionary_empty": "",
|
||||
"edit_dictionary_remove": "",
|
||||
|
@ -1181,6 +1189,8 @@
|
|||
"rename": "",
|
||||
"rename_project": "",
|
||||
"reopen": "",
|
||||
"reopen_comment_error_message": "",
|
||||
"reopen_comment_error_title": "",
|
||||
"replace_figure": "",
|
||||
"replace_from_another_project": "",
|
||||
"replace_from_computer": "",
|
||||
|
@ -1201,6 +1211,8 @@
|
|||
"resize": "",
|
||||
"resolve": "",
|
||||
"resolve_comment": "",
|
||||
"resolve_comment_error_message": "",
|
||||
"resolve_comment_error_title": "",
|
||||
"resolved_comments": "",
|
||||
"restore": "",
|
||||
"restore_file": "",
|
||||
|
|
|
@ -13,6 +13,8 @@ import { Button } from 'react-bootstrap'
|
|||
import { ReviewPanelEntry } from './review-panel-entry'
|
||||
import { ThreadId } from '../../../../../types/review-panel/review-panel'
|
||||
import { Decoration } from '@codemirror/view'
|
||||
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
export const ReviewPanelAddComment: FC<{
|
||||
docId: string
|
||||
|
@ -25,8 +27,8 @@ export const ReviewPanelAddComment: FC<{
|
|||
const view = useCodeMirrorViewContext()
|
||||
const state = useCodeMirrorStateContext()
|
||||
const { addComment } = useThreadsActionsContext()
|
||||
const [error, setError] = useState<Error>()
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const { showGenericMessageModal } = useModalsContext()
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
view.dispatch({
|
||||
|
@ -35,22 +37,27 @@ export const ReviewPanelAddComment: FC<{
|
|||
}, [view, value])
|
||||
|
||||
const submitForm = useCallback(
|
||||
message => {
|
||||
async message => {
|
||||
setSubmitting(true)
|
||||
|
||||
const content = view.state.sliceDoc(from, to)
|
||||
|
||||
addComment(from, content, message)
|
||||
.catch(setError)
|
||||
.finally(() => setSubmitting(false))
|
||||
|
||||
view.dispatch({
|
||||
selection: EditorSelection.cursor(view.state.selection.main.anchor),
|
||||
})
|
||||
|
||||
handleClose()
|
||||
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')
|
||||
)
|
||||
}
|
||||
setSubmitting(false)
|
||||
},
|
||||
[addComment, view, handleClose, from, to]
|
||||
[addComment, view, handleClose, from, to, showGenericMessageModal, t]
|
||||
)
|
||||
|
||||
const { handleChange, handleKeyPress, content } =
|
||||
|
@ -123,6 +130,7 @@ export const ReviewPanelAddComment: FC<{
|
|||
t: value.spec.id as ThreadId,
|
||||
}}
|
||||
selectLineOnFocus={false}
|
||||
disabled={submitting}
|
||||
>
|
||||
<form
|
||||
className="review-panel-entry-content"
|
||||
|
@ -158,7 +166,6 @@ export const ReviewPanelAddComment: FC<{
|
|||
{t('comment')}
|
||||
</Button>
|
||||
</div>
|
||||
{error && <div>{error.message}</div>}
|
||||
</form>
|
||||
</ReviewPanelEntry>
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { memo } from 'react'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { useRangesActionsContext } from '../context/ranges-context'
|
||||
import {
|
||||
Change,
|
||||
|
@ -15,6 +15,7 @@ import { formatTimeBasedOnYear } from '@/features/utils/format-date'
|
|||
import { useChangesUsersContext } from '../context/changes-users-context'
|
||||
import { ReviewPanelChangeUser } from './review-panel-change-user'
|
||||
import { ReviewPanelEntry } from './review-panel-entry'
|
||||
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||
|
||||
export const ReviewPanelChange = memo<{
|
||||
change: Change<EditOperation>
|
||||
|
@ -42,6 +43,27 @@ export const ReviewPanelChange = memo<{
|
|||
const { acceptChanges, rejectChanges } = useRangesActionsContext()
|
||||
const permissions = usePermissionsContext()
|
||||
const changesUsers = useChangesUsersContext()
|
||||
const { showGenericMessageModal } = useModalsContext()
|
||||
|
||||
const [accepting, setAccepting] = useState(false)
|
||||
|
||||
const acceptHandler = useCallback(async () => {
|
||||
setAccepting(true)
|
||||
try {
|
||||
if (aggregate) {
|
||||
await acceptChanges(change.id, aggregate.id)
|
||||
} else {
|
||||
await acceptChanges(change.id)
|
||||
}
|
||||
} catch (err) {
|
||||
showGenericMessageModal(
|
||||
t('accept_change_error_title'),
|
||||
t('accept_change_error_description')
|
||||
)
|
||||
} finally {
|
||||
setAccepting(false)
|
||||
}
|
||||
}, [acceptChanges, aggregate, change.id, showGenericMessageModal, t])
|
||||
|
||||
if (!changesUsers) {
|
||||
// if users are not loaded yet, do not show "Unknown" user
|
||||
|
@ -61,6 +83,7 @@ export const ReviewPanelChange = memo<{
|
|||
position={change.op.p}
|
||||
docId={docId}
|
||||
hoverRanges={hoverRanges}
|
||||
disabled={accepting}
|
||||
>
|
||||
<div
|
||||
className="review-panel-entry-indicator"
|
||||
|
@ -92,14 +115,7 @@ export const ReviewPanelChange = memo<{
|
|||
description={t('accept_change')}
|
||||
tooltipProps={{ className: 'review-panel-tooltip' }}
|
||||
>
|
||||
<Button
|
||||
onClick={() =>
|
||||
aggregate
|
||||
? acceptChanges(change.id, aggregate.id)
|
||||
: acceptChanges(change.id)
|
||||
}
|
||||
bsStyle={null}
|
||||
>
|
||||
<Button onClick={acceptHandler} bsStyle={null}>
|
||||
<MaterialIcon
|
||||
type="check"
|
||||
className="review-panel-entry-actions-icon"
|
||||
|
|
|
@ -1,100 +1,98 @@
|
|||
import { Dispatch, memo, SetStateAction, useCallback, useState } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { Change, CommentOperation } from '../../../../../types/change'
|
||||
import { ReviewPanelMessage } from './review-panel-message'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
useThreadsActionsContext,
|
||||
useThreadsContext,
|
||||
} from '../context/threads-context'
|
||||
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 } from '../../../../../types/review-panel/review-panel'
|
||||
|
||||
export const ReviewPanelCommentContent = memo<{
|
||||
comment: Change<CommentOperation>
|
||||
isResolved: boolean
|
||||
onEdit?: (commentId: CommentId, content: string) => Promise<void>
|
||||
onReply?: (content: string) => Promise<void>
|
||||
onDelete?: (commentId: CommentId) => Promise<void>
|
||||
onResolve?: () => Promise<void>
|
||||
onLeave?: () => void
|
||||
onEnter?: () => void
|
||||
}>(({ comment, isResolved, onLeave, onEnter }) => {
|
||||
const { t } = useTranslation()
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [error, setError] = useState<Error>()
|
||||
const threads = useThreadsContext()
|
||||
const { resolveThread, addMessage } = useThreadsActionsContext()
|
||||
}>(
|
||||
({
|
||||
comment,
|
||||
isResolved,
|
||||
onResolve,
|
||||
onDelete,
|
||||
onEdit,
|
||||
onReply,
|
||||
onLeave,
|
||||
onEnter,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const threads = useThreadsContext()
|
||||
|
||||
const handleSubmitReply = useCallback(
|
||||
(content: string, setContent: Dispatch<SetStateAction<string>>) => {
|
||||
setSubmitting(true)
|
||||
addMessage(comment.op.t, content)
|
||||
.then(() => {
|
||||
setContent('')
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitting(false)
|
||||
})
|
||||
},
|
||||
[addMessage, comment.op.t]
|
||||
)
|
||||
const handleSubmit = useCallback(
|
||||
(content, setContent) => onReply?.(content).then(() => setContent('')),
|
||||
[onReply]
|
||||
)
|
||||
|
||||
const { handleChange, handleKeyPress, content } =
|
||||
useSubmittableTextInput(handleSubmitReply)
|
||||
const { handleChange, handleKeyPress, content } =
|
||||
useSubmittableTextInput(handleSubmit)
|
||||
|
||||
const thread = threads?.[comment.op.t]
|
||||
if (!thread) {
|
||||
return null
|
||||
}
|
||||
const thread = threads?.[comment.op.t]
|
||||
if (!thread) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="review-panel-entry-content"
|
||||
onMouseEnter={onEnter}
|
||||
onMouseLeave={onLeave}
|
||||
>
|
||||
{thread.messages.map((message, i) => {
|
||||
const isReply = i !== 0
|
||||
return (
|
||||
<div
|
||||
className="review-panel-entry-content"
|
||||
onMouseEnter={onEnter}
|
||||
onMouseLeave={onLeave}
|
||||
>
|
||||
{thread.messages.map((message, i) => {
|
||||
const isReply = i !== 0
|
||||
|
||||
return (
|
||||
<div key={message.id} className="review-panel-comment-wrapper">
|
||||
{isReply && <div className="review-panel-comment-reply-divider" />}
|
||||
<ReviewPanelMessage
|
||||
message={message}
|
||||
threadId={comment.op.t}
|
||||
isReply={isReply}
|
||||
hasReplies={!isReply && thread.messages.length > 1}
|
||||
onResolve={() => resolveThread(comment.op.t)}
|
||||
isThreadResolved={isResolved}
|
||||
return (
|
||||
<div key={message.id} className="review-panel-comment-wrapper">
|
||||
{isReply && (
|
||||
<div className="review-panel-comment-reply-divider" />
|
||||
)}
|
||||
<ReviewPanelMessage
|
||||
message={message}
|
||||
isReply={isReply}
|
||||
hasReplies={!isReply && thread.messages.length > 1}
|
||||
onResolve={onResolve}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
isThreadResolved={isResolved}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{isResolved && (
|
||||
<div className="review-panel-comment-wrapper">
|
||||
<div className="review-panel-comment-reply-divider" />
|
||||
<ReviewPanelResolvedMessage
|
||||
thread={thread as ReviewPanelResolvedCommentThread}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
)}
|
||||
|
||||
{isResolved && (
|
||||
<div className="review-panel-comment-wrapper">
|
||||
<div className="review-panel-comment-reply-divider" />
|
||||
<ReviewPanelResolvedMessage
|
||||
thread={thread as ReviewPanelResolvedCommentThread}
|
||||
{!isResolved && (
|
||||
<AutoExpandingTextArea
|
||||
name="content"
|
||||
className="review-panel-comment-input"
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder={t('reply')}
|
||||
value={content}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isResolved && (
|
||||
<AutoExpandingTextArea
|
||||
name="content"
|
||||
className="review-panel-comment-input"
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder={t('reply')}
|
||||
value={content}
|
||||
disabled={submitting}
|
||||
/>
|
||||
)}
|
||||
|
||||
{error && <div>{error.message}</div>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
ReviewPanelCommentContent.displayName = 'ReviewPanelCommentContent'
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import { memo } from 'react'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Change, CommentOperation } from '../../../../../types/change'
|
||||
import { useThreadsContext } from '../context/threads-context'
|
||||
import {
|
||||
useThreadsActionsContext,
|
||||
useThreadsContext,
|
||||
} from '../context/threads-context'
|
||||
import classnames from 'classnames'
|
||||
import { ReviewPanelEntry } from './review-panel-entry'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { ReviewPanelCommentContent } from './review-panel-comment-content'
|
||||
import { CommentId } from '../../../../../types/review-panel/review-panel'
|
||||
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
export const ReviewPanelComment = memo<{
|
||||
comment: Change<CommentOperation>
|
||||
|
@ -16,6 +23,82 @@ export const ReviewPanelComment = memo<{
|
|||
hovered?: boolean
|
||||
}>(({ comment, top, hovered, onEnter, onLeave, docId, hoverRanges }) => {
|
||||
const threads = useThreadsContext()
|
||||
const { resolveThread, editMessage, deleteMessage, addMessage } =
|
||||
useThreadsActionsContext()
|
||||
const { showGenericMessageModal } = useModalsContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [processing, setProcessing] = useState(false)
|
||||
|
||||
const handleResolveComment = useCallback(async () => {
|
||||
setProcessing(true)
|
||||
try {
|
||||
await resolveThread(comment.op.t)
|
||||
} catch (err) {
|
||||
debugConsole.error(err)
|
||||
showGenericMessageModal(
|
||||
t('resolve_comment_error_title'),
|
||||
t('resolve_comment_error_message')
|
||||
)
|
||||
} finally {
|
||||
setProcessing(false)
|
||||
}
|
||||
}, [comment.op.t, resolveThread, showGenericMessageModal, t])
|
||||
|
||||
const handleEditMessage = useCallback(
|
||||
async (commentId: CommentId, content: string) => {
|
||||
setProcessing(true)
|
||||
try {
|
||||
await editMessage(comment.op.t, commentId, content)
|
||||
} catch (err) {
|
||||
debugConsole.error(err)
|
||||
showGenericMessageModal(
|
||||
t('edit_comment_error_title'),
|
||||
t('edit_comment_error_message')
|
||||
)
|
||||
} finally {
|
||||
setProcessing(false)
|
||||
}
|
||||
},
|
||||
[comment.op.t, editMessage, showGenericMessageModal, t]
|
||||
)
|
||||
|
||||
const handleDeleteMessage = useCallback(
|
||||
async (commentId: CommentId) => {
|
||||
setProcessing(true)
|
||||
try {
|
||||
await deleteMessage(comment.op.t, commentId)
|
||||
} catch (err) {
|
||||
debugConsole.error(err)
|
||||
showGenericMessageModal(
|
||||
t('delete_comment_error_title'),
|
||||
t('delete_comment_error_message')
|
||||
)
|
||||
} finally {
|
||||
setProcessing(false)
|
||||
}
|
||||
},
|
||||
[comment.op.t, deleteMessage, showGenericMessageModal, t]
|
||||
)
|
||||
|
||||
const handleSubmitReply = useCallback(
|
||||
async (content: string) => {
|
||||
setProcessing(true)
|
||||
try {
|
||||
await addMessage(comment.op.t, content)
|
||||
} catch (err) {
|
||||
debugConsole.error(err)
|
||||
showGenericMessageModal(
|
||||
t('add_comment_error_title'),
|
||||
t('add_comment_error_message')
|
||||
)
|
||||
throw err
|
||||
} finally {
|
||||
setProcessing(false)
|
||||
}
|
||||
},
|
||||
[addMessage, comment.op.t, showGenericMessageModal, t]
|
||||
)
|
||||
|
||||
const thread = threads?.[comment.op.t]
|
||||
if (!thread || thread.resolved || thread.messages.length === 0) {
|
||||
|
@ -33,6 +116,7 @@ export const ReviewPanelComment = memo<{
|
|||
op={comment.op}
|
||||
position={comment.op.p}
|
||||
hoverRanges={hoverRanges}
|
||||
disabled={processing}
|
||||
>
|
||||
<div
|
||||
className="review-panel-entry-indicator"
|
||||
|
@ -46,6 +130,10 @@ export const ReviewPanelComment = memo<{
|
|||
isResolved={false}
|
||||
onLeave={onLeave}
|
||||
onEnter={onEnter}
|
||||
onResolve={handleResolveComment}
|
||||
onEdit={handleEditMessage}
|
||||
onDelete={handleDeleteMessage}
|
||||
onReply={handleSubmitReply}
|
||||
/>
|
||||
</ReviewPanelEntry>
|
||||
)
|
||||
|
|
|
@ -21,6 +21,7 @@ export const ReviewPanelEntry: FC<{
|
|||
className?: string
|
||||
selectLineOnFocus?: boolean
|
||||
hoverRanges?: boolean
|
||||
disabled?: boolean
|
||||
}> = ({
|
||||
children,
|
||||
position,
|
||||
|
@ -30,6 +31,7 @@ export const ReviewPanelEntry: FC<{
|
|||
selectLineOnFocus = true,
|
||||
docId,
|
||||
hoverRanges = true,
|
||||
disabled,
|
||||
}) => {
|
||||
const state = useCodeMirrorStateContext()
|
||||
const view = useCodeMirrorViewContext()
|
||||
|
@ -95,6 +97,7 @@ export const ReviewPanelEntry: FC<{
|
|||
{
|
||||
'review-panel-entry-focused': focused,
|
||||
'review-panel-entry-highlighted': highlighted,
|
||||
'review-panel-entry-disabled': disabled,
|
||||
},
|
||||
className
|
||||
)}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { FC, useCallback, useState } from 'react'
|
||||
import {
|
||||
CommentId,
|
||||
ReviewPanelCommentThreadMessage,
|
||||
ThreadId,
|
||||
} from '../../../../../types/review-panel/review-panel'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useThreadsActionsContext } from '../context/threads-context'
|
||||
import { formatTimeBasedOnYear } from '@/features/utils/format-date'
|
||||
import Tooltip from '@/shared/components/tooltip'
|
||||
import { Button } from 'react-bootstrap'
|
||||
|
@ -17,49 +16,39 @@ import ReviewPanelDeleteCommentModal from './review-panel-delete-comment-modal'
|
|||
|
||||
export const ReviewPanelMessage: FC<{
|
||||
message: ReviewPanelCommentThreadMessage
|
||||
threadId: ThreadId
|
||||
hasReplies: boolean
|
||||
isReply: boolean
|
||||
onResolve: () => void
|
||||
onResolve?: () => Promise<void>
|
||||
onEdit?: (commentId: CommentId, content: string) => Promise<void>
|
||||
onDelete?: (CommentId: CommentId) => Promise<void>
|
||||
isThreadResolved: boolean
|
||||
}> = ({
|
||||
message,
|
||||
threadId,
|
||||
isReply,
|
||||
hasReplies,
|
||||
onResolve,
|
||||
onEdit,
|
||||
onDelete,
|
||||
isThreadResolved,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const [error, setError] = useState<Error>()
|
||||
const [content, setContent] = useState(message.content)
|
||||
const { editMessage, deleteMessage } = useThreadsActionsContext()
|
||||
|
||||
const handleEditOption = useCallback(() => setEditing(true), [])
|
||||
const showDeleteModal = useCallback(() => setDeleting(true), [])
|
||||
const hideDeleteModal = useCallback(() => setDeleting(false), [])
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
await editMessage(threadId, message.id, content)
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setEditing(false)
|
||||
})
|
||||
}, [content, editMessage, message.id, threadId])
|
||||
const handleSubmit = useCallback(() => {
|
||||
onEdit?.(message.id, content)
|
||||
setEditing(false)
|
||||
}, [content, message.id, onEdit])
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
await deleteMessage(threadId, message.id)
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setDeleting(false)
|
||||
})
|
||||
}, [deleteMessage, message.id, threadId])
|
||||
const handleDelete = useCallback(() => {
|
||||
onDelete?.(message.id)
|
||||
setDeleting(false)
|
||||
}, [message.id, onDelete])
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
|
@ -83,7 +72,6 @@ export const ReviewPanelMessage: FC<{
|
|||
value={content}
|
||||
autoFocus // eslint-disable-line jsx-a11y/no-autofocus
|
||||
/>
|
||||
{error && <div>{error.message}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC } from 'react'
|
||||
import React, { FC, useCallback, useState } from 'react'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { ThreadId } from '../../../../../types/review-panel/review-panel'
|
||||
import { useThreadsActionsContext } from '../context/threads-context'
|
||||
|
@ -8,17 +8,57 @@ import { ExpandableContent } from './review-panel-expandable-content'
|
|||
import { ReviewPanelCommentContent } from './review-panel-comment-content'
|
||||
import { Change, CommentOperation } from '../../../../../types/change'
|
||||
import Tooltip from '@/shared/components/tooltip'
|
||||
import classNames from 'classnames'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||
|
||||
export const ReviewPanelResolvedThread: FC<{
|
||||
id: string
|
||||
id: ThreadId
|
||||
comment: Change<CommentOperation>
|
||||
docName: string
|
||||
}> = ({ id, comment, docName }) => {
|
||||
const { t } = useTranslation()
|
||||
const { reopenThread, deleteThread } = useThreadsActionsContext()
|
||||
const [processing, setProcessing] = useState(false)
|
||||
const { showGenericMessageModal } = useModalsContext()
|
||||
|
||||
const handleReopenThread = useCallback(async () => {
|
||||
setProcessing(true)
|
||||
try {
|
||||
await reopenThread(id)
|
||||
} catch (err) {
|
||||
debugConsole.error(err)
|
||||
showGenericMessageModal(
|
||||
t('reopen_comment_error_title'),
|
||||
t('reopen_comment_error_message')
|
||||
)
|
||||
} finally {
|
||||
setProcessing(false)
|
||||
}
|
||||
}, [id, reopenThread, showGenericMessageModal, t])
|
||||
|
||||
const handleDeleteThread = useCallback(async () => {
|
||||
setProcessing(true)
|
||||
try {
|
||||
await deleteThread(id)
|
||||
} catch (err) {
|
||||
debugConsole.error(err)
|
||||
showGenericMessageModal(
|
||||
t('delete_comment_error_title'),
|
||||
t('delete_comment_error_message')
|
||||
)
|
||||
} finally {
|
||||
setProcessing(false)
|
||||
}
|
||||
}, [id, deleteThread, showGenericMessageModal, t])
|
||||
|
||||
return (
|
||||
<div className="review-panel-resolved-comment" key={id}>
|
||||
<div
|
||||
className={classNames('review-panel-resolved-comment', {
|
||||
'review-panel-resolved-disabled': processing,
|
||||
})}
|
||||
key={id}
|
||||
>
|
||||
<div className="review-panel-resolved-comment-header">
|
||||
<div>
|
||||
<Trans
|
||||
|
@ -38,7 +78,7 @@ export const ReviewPanelResolvedThread: FC<{
|
|||
overlayProps={{ placement: 'bottom' }}
|
||||
description={t('reopen')}
|
||||
>
|
||||
<Button onClick={() => reopenThread(id as ThreadId)}>
|
||||
<Button onClick={handleReopenThread}>
|
||||
<MaterialIcon type="refresh" accessibilityLabel={t('reopen')} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
@ -48,7 +88,7 @@ export const ReviewPanelResolvedThread: FC<{
|
|||
overlayProps={{ placement: 'bottom' }}
|
||||
description={t('delete')}
|
||||
>
|
||||
<Button onClick={() => deleteThread(id as ThreadId)}>
|
||||
<Button onClick={handleDeleteThread}>
|
||||
<MaterialIcon type="delete" accessibilityLabel={t('delete')} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
|
|
@ -6,6 +6,7 @@ import useProjectRanges from '../hooks/use-project-ranges'
|
|||
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import { Change, CommentOperation } from '../../../../../types/change'
|
||||
import { ThreadId } from '../../../../../types/review-panel/review-panel'
|
||||
|
||||
export const ReviewPanelResolvedThreadsMenu: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
@ -92,7 +93,7 @@ export const ReviewPanelResolvedThreadsMenu: FC = () => {
|
|||
return (
|
||||
<ReviewPanelResolvedThread
|
||||
key={thread.id}
|
||||
id={thread.id}
|
||||
id={thread.id as ThreadId}
|
||||
comment={comment}
|
||||
docName={docNameForThread.get(thread.id) ?? t('unknown')}
|
||||
/>
|
||||
|
|
|
@ -16,6 +16,8 @@ import {
|
|||
import RangesTracker from '@overleaf/ranges-tracker'
|
||||
import { rejectChanges } from '@/features/source-editor/extensions/changes/reject-changes'
|
||||
import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-context'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||
|
||||
export type Ranges = {
|
||||
docId: string
|
||||
|
@ -57,7 +59,7 @@ const RangesActionsContext = createContext<RangesActions | undefined>(undefined)
|
|||
|
||||
export const RangesProvider: FC = ({ children }) => {
|
||||
const view = useCodeMirrorViewContext()
|
||||
|
||||
const { projectId } = useIdeReactContext()
|
||||
const [currentDoc] = useScopeValue<DocumentContainer | null>(
|
||||
'editor.sharejs_doc'
|
||||
)
|
||||
|
@ -115,8 +117,10 @@ export const RangesProvider: FC = ({ children }) => {
|
|||
|
||||
const actions = useMemo(
|
||||
() => ({
|
||||
acceptChanges(...ids: string[]) {
|
||||
async acceptChanges(...ids: string[]) {
|
||||
if (currentDoc?.ranges) {
|
||||
const url = `/project/${projectId}/doc/${currentDoc.doc_id}/changes/accept`
|
||||
await postJSON(url, { body: { change_ids: ids } })
|
||||
currentDoc.ranges.removeChangeIds(ids)
|
||||
setRanges(buildRanges(currentDoc))
|
||||
}
|
||||
|
@ -127,7 +131,7 @@ export const RangesProvider: FC = ({ children }) => {
|
|||
}
|
||||
},
|
||||
}),
|
||||
[currentDoc, view]
|
||||
[currentDoc, projectId, view]
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -248,7 +248,6 @@ export const ThreadsProvider: FC = ({ children }) => {
|
|||
await postJSON(`/project/${projectId}/thread/${threadId}/messages`, {
|
||||
body: { content },
|
||||
})
|
||||
// TODO: error_submitting_comment
|
||||
},
|
||||
async editMessage(
|
||||
threadId: ThreadId,
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
.review-panel-entry.review-panel-entry-disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.review-panel-entry-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
@ -235,6 +240,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.review-panel-resolved-disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.review-panel-resolved-comments-loading {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
"accept_all": "Accept all",
|
||||
"accept_and_continue": "Accept and continue",
|
||||
"accept_change": "Accept change",
|
||||
"accept_change_error_description": "There was an error accepting a track change. Please try again in a few moments.",
|
||||
"accept_change_error_title": "Accept Change Error",
|
||||
"accept_invitation": "Accept invitation",
|
||||
"accept_or_reject_each_changes_individually": "Accept or reject each change individually",
|
||||
"accept_terms_and_conditions": "Accept terms and conditions",
|
||||
|
@ -64,6 +66,8 @@
|
|||
"add_another_token": "Add another token",
|
||||
"add_comma_separated_emails_help": "Separate multiple email addresses using the comma (,) character.",
|
||||
"add_comment": "Add comment",
|
||||
"add_comment_error_message": "There was an error adding your comment. Please try again in a few moments.",
|
||||
"add_comment_error_title": "Add Comment Error",
|
||||
"add_company_details": "Add Company Details",
|
||||
"add_email": "Add Email",
|
||||
"add_email_address": "Add email address",
|
||||
|
@ -427,6 +431,8 @@
|
|||
"delete_authentication_token_info": "You’re about to delete a Git authentication token. If you do, it can no longer be used to authenticate your identity when performing Git operations.",
|
||||
"delete_certificate": "Delete certificate",
|
||||
"delete_comment": "Delete comment",
|
||||
"delete_comment_error_message": "There was an error deleting your comment. Please try again in a few moments.",
|
||||
"delete_comment_error_title": "Delete Comment Error",
|
||||
"delete_comment_message": "You cannot undo this action.",
|
||||
"delete_comment_thread": "Delete comment thread",
|
||||
"delete_comment_thread_message": "This will delete the whole comment thread. You cannot undo this action.",
|
||||
|
@ -526,6 +532,8 @@
|
|||
"easily_manage_your_project_files_everywhere": "Easily manage your project files, everywhere",
|
||||
"easy_collaboration_for_students": "Easy collaboration for students. Supports longer or more complex projects.",
|
||||
"edit": "Edit",
|
||||
"edit_comment_error_message": "There was an error editing your comment. Please try again in a few moments.",
|
||||
"edit_comment_error_title": "Edit Comment Error",
|
||||
"edit_dictionary": "Edit Dictionary",
|
||||
"edit_dictionary_empty": "Your custom dictionary is empty.",
|
||||
"edit_dictionary_remove": "Remove from dictionary",
|
||||
|
@ -1680,6 +1688,8 @@
|
|||
"rename_project": "Rename Project",
|
||||
"renaming": "Renaming",
|
||||
"reopen": "Re-open",
|
||||
"reopen_comment_error_message": "There was an error reopening your comment. Please try again in a few moments.",
|
||||
"reopen_comment_error_title": "Reopen Comment Error",
|
||||
"replace_figure": "Replace figure",
|
||||
"replace_from_another_project": "Replace from another project",
|
||||
"replace_from_computer": "Replace from computer",
|
||||
|
|
Loading…
Reference in a new issue