Merge pull request #20583 from overleaf/dk-review-gotoposition

Goto position when clicking on review entry in Overview tab

GitOrigin-RevId: 17c9cefed339537afa994778ffbc01cfb294bf46
This commit is contained in:
David 2024-09-26 15:13:54 +01:00 committed by Copybot
parent e253b48bc0
commit 2801b3baad
6 changed files with 52 additions and 20 deletions

View file

@ -15,11 +15,12 @@ import { ThreadId } from '../../../../../types/review-panel/review-panel'
import { Decoration } from '@codemirror/view' import { Decoration } from '@codemirror/view'
export const ReviewPanelAddComment: FC<{ export const ReviewPanelAddComment: FC<{
docId: string
from: number from: number
to: number to: number
value: Decoration value: Decoration
top: number | undefined top: number | undefined
}> = ({ from, to, value, top }) => { }> = ({ from, to, value, top, docId }) => {
const { t } = useTranslation() const { t } = useTranslation()
const view = useCodeMirrorViewContext() const view = useCodeMirrorViewContext()
const state = useCodeMirrorStateContext() const state = useCodeMirrorStateContext()
@ -113,6 +114,7 @@ export const ReviewPanelAddComment: FC<{
return ( return (
<ReviewPanelEntry <ReviewPanelEntry
docId={docId}
top={top} top={top}
position={from} position={from}
op={{ op={{

View file

@ -21,7 +21,9 @@ export const ReviewPanelChange = memo<{
aggregate?: Change<DeleteOperation> aggregate?: Change<DeleteOperation>
top?: number top?: number
editable?: boolean editable?: boolean
}>(({ change, aggregate, top, editable = true }) => { docId: string
hoverRanges?: boolean
}>(({ change, aggregate, top, docId, hoverRanges, editable = true }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { acceptChanges, rejectChanges } = useRangesActionsContext() const { acceptChanges, rejectChanges } = useRangesActionsContext()
const permissions = usePermissionsContext() const permissions = usePermissionsContext()
@ -42,6 +44,8 @@ export const ReviewPanelChange = memo<{
top={top} top={top}
op={change.op} op={change.op}
position={change.op.p} position={change.op.p}
docId={docId}
hoverRanges={hoverRanges}
> >
<div className="review-panel-entry-indicator"> <div className="review-panel-entry-indicator">
<MaterialIcon type="edit" className="review-panel-entry-icon" /> <MaterialIcon type="edit" className="review-panel-entry-icon" />

View file

@ -8,8 +8,10 @@ import { ReviewPanelCommentContent } from './review-panel-comment-content'
export const ReviewPanelComment = memo<{ export const ReviewPanelComment = memo<{
comment: Change<CommentOperation> comment: Change<CommentOperation>
docId: string
top?: number top?: number
}>(({ comment, top }) => { hoverRanges?: boolean
}>(({ comment, top, docId, hoverRanges }) => {
const threads = useThreadsContext() const threads = useThreadsContext()
const thread = threads?.[comment.op.t] const thread = threads?.[comment.op.t]
@ -22,9 +24,11 @@ export const ReviewPanelComment = memo<{
className={classnames('review-panel-entry-comment', { className={classnames('review-panel-entry-comment', {
'review-panel-entry-loaded': !!threads?.[comment.op.t], 'review-panel-entry-loaded': !!threads?.[comment.op.t],
})} })}
docId={docId}
top={top} top={top}
op={comment.op} op={comment.op}
position={comment.op.p} position={comment.op.p}
hoverRanges={hoverRanges}
> >
<div className="review-panel-entry-indicator"> <div className="review-panel-entry-indicator">
<MaterialIcon type="comment" className="review-panel-entry-icon" /> <MaterialIcon type="comment" className="review-panel-entry-icon" />

View file

@ -251,6 +251,7 @@ const ReviewPanelCurrentFile: FC = () => {
const { id, from, to, value, top } = entry const { id, from, to, value, top } = entry
return ( return (
<ReviewPanelAddComment <ReviewPanelAddComment
docId={ranges!.docId}
key={id} key={id}
from={from} from={from}
to={to} to={to}
@ -264,6 +265,7 @@ const ReviewPanelCurrentFile: FC = () => {
change => change =>
positions.has(change.id) && ( positions.has(change.id) && (
<ReviewPanelChange <ReviewPanelChange
docId={ranges!.docId}
key={change.id} key={change.id}
change={change} change={change}
top={positions.get(change.id)} top={positions.get(change.id)}
@ -276,6 +278,7 @@ const ReviewPanelCurrentFile: FC = () => {
comment => comment =>
positions.has(comment.id) && ( positions.has(comment.id) && (
<ReviewPanelComment <ReviewPanelComment
docId={ranges!.docId}
key={comment.id} key={comment.id}
comment={comment} comment={comment}
top={positions.get(comment.id)} top={positions.get(comment.id)}

View file

@ -5,44 +5,56 @@ import {
useCodeMirrorViewContext, useCodeMirrorViewContext,
} from '@/features/source-editor/components/codemirror-editor' } from '@/features/source-editor/components/codemirror-editor'
import { isSelectionWithinOp } from '../utils/is-selection-within-op' import { isSelectionWithinOp } from '../utils/is-selection-within-op'
import { EditorSelection } from '@codemirror/state'
import { EditorView } from '@codemirror/view'
import classNames from 'classnames' import classNames from 'classnames'
import { highlightRanges } from '@/features/source-editor/extensions/ranges' import { highlightRanges } from '@/features/source-editor/extensions/ranges'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
export const ReviewPanelEntry: FC<{ export const ReviewPanelEntry: FC<{
position: number position: number
op: AnyOperation op: AnyOperation
docId: string
top?: number top?: number
className?: string className?: string
selectLineOnFocus?: boolean selectLineOnFocus?: boolean
}> = ({ children, position, top, op, className, selectLineOnFocus = true }) => { hoverRanges?: boolean
}> = ({
children,
position,
top,
op,
className,
selectLineOnFocus = true,
docId,
hoverRanges = true,
}) => {
const state = useCodeMirrorStateContext() const state = useCodeMirrorStateContext()
const view = useCodeMirrorViewContext() const view = useCodeMirrorViewContext()
const { openDocId } = useEditorManagerContext()
const [focused, setFocused] = useState(false) const [focused, setFocused] = useState(false)
const highlighted = isSelectionWithinOp(op, state.selection.main) const highlighted = isSelectionWithinOp(op, state.selection.main)
const focusHandler = useCallback(() => { const focusHandler = useCallback(() => {
setTimeout(() => { if (selectLineOnFocus) {
// without setTimeout, error "EditorView.update are not allowed while an update is in progress" can occur openDocId(docId, { gotoOffset: position, keepCurrentView: true })
// this can be avoided by using onClick rather than onFocus but it will then not pick up <Tab> or <Shift+Tab> events for focusing entries }
if (selectLineOnFocus) {
view.dispatch({
selection: EditorSelection.cursor(position),
effects: EditorView.scrollIntoView(position, { y: 'center' }),
})
}
}, 0)
setFocused(true) setFocused(true)
}, [view, position, selectLineOnFocus]) }, [selectLineOnFocus, docId, openDocId, position])
return ( return (
<div <div
onFocus={focusHandler} onFocus={focusHandler}
onBlur={() => setFocused(false)} onBlur={() => setFocused(false)}
onMouseEnter={() => view.dispatch(highlightRanges(op))} onMouseEnter={() => {
onMouseLeave={() => view.dispatch(highlightRanges())} if (hoverRanges) {
view.dispatch(highlightRanges(op))
}
}}
onMouseLeave={() => {
if (hoverRanges) {
view.dispatch(highlightRanges())
}
}}
role="button" role="button"
tabIndex={position + 1} tabIndex={position + 1}
className={classNames( className={classNames(

View file

@ -82,11 +82,18 @@ export const ReviewPanelOverviewFile: FC<{
change={change} change={change}
aggregate={aggregates.get(change.id)} aggregate={aggregates.get(change.id)}
editable={false} editable={false}
docId={doc.doc.id}
hoverRanges={false}
/> />
))} ))}
{unresolvedComments.map(comment => ( {unresolvedComments.map(comment => (
<ReviewPanelComment key={comment.id} comment={comment} /> <ReviewPanelComment
key={comment.id}
comment={comment}
docId={doc.doc.id}
hoverRanges={false}
/>
))} ))}
</div> </div>
)} )}