mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
a331ac6116
Add More Comments buttons GitOrigin-RevId: 3cd6539683cbc53ceb63488ab9e9b0ffe53e079c
148 lines
5 KiB
TypeScript
148 lines
5 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-editor'
|
|
import {
|
|
Change,
|
|
CommentOperation,
|
|
EditOperation,
|
|
} from '../../../../../types/change'
|
|
import { DecorationSet, EditorView } from '@codemirror/view'
|
|
import { EditorSelection } from '@codemirror/state'
|
|
import _ from 'lodash'
|
|
|
|
const useMoreCommments = (
|
|
changes: Change<EditOperation>[],
|
|
comments: Change<CommentOperation>[],
|
|
newComments: DecorationSet
|
|
): {
|
|
onEntriesPositioned: () => void
|
|
onMoreCommentsAboveClick: null | (() => void)
|
|
onMoreCommentsBelowClick: null | (() => void)
|
|
} => {
|
|
const view = useCodeMirrorViewContext()
|
|
|
|
const [positionAbove, setPositionAbove] = useState<number | null>(null)
|
|
const [positionBelow, setPositionBelow] = useState<number | null>(null)
|
|
|
|
const updateEntryPositions = useMemo(
|
|
() =>
|
|
_.debounce(
|
|
() =>
|
|
view.requestMeasure({
|
|
key: 'review-panel-more-comments',
|
|
read(view) {
|
|
const container = view.scrollDOM
|
|
|
|
if (!container) {
|
|
return { positionAbove: null, positionBelow: null }
|
|
}
|
|
|
|
const containerTop = container.scrollTop
|
|
const containerBottom = containerTop + container.clientHeight
|
|
|
|
// First check for any entries in view by looking for the actual rendered entries
|
|
for (const entryElt of container.querySelectorAll<HTMLElement>(
|
|
'.review-panel-entry'
|
|
)) {
|
|
const entryTop = entryElt?.offsetTop ?? 0
|
|
const entryBottom = entryTop + (entryElt?.offsetHeight ?? 0)
|
|
|
|
if (entryBottom > containerTop && entryTop < containerBottom) {
|
|
// Some part of the entry is in view
|
|
return { positionAbove: null, positionBelow: null }
|
|
}
|
|
}
|
|
|
|
// Find the max and min positions in the visible part of the viewport
|
|
const visibleFrom = view.lineBlockAtHeight(containerTop).from
|
|
const visibleTo = view.lineBlockAtHeight(containerBottom).to
|
|
|
|
// Then go through the positions to find the first entry above and below the visible viewport.
|
|
// We can't use the rendered entries for this because only the entries that are in the viewport (or
|
|
// have been in the viewport during the current page view session) are actually rendered.
|
|
let firstEntryAbove: number | null = null
|
|
let firstEntryBelow: number | null = null
|
|
|
|
const updateFirstEntryAboveBelowPositions = (
|
|
position: number
|
|
) => {
|
|
if (visibleFrom === null || position < visibleFrom) {
|
|
firstEntryAbove = Math.max(firstEntryAbove ?? 0, position)
|
|
}
|
|
|
|
if (visibleTo === null || position > visibleTo) {
|
|
firstEntryBelow = Math.min(
|
|
firstEntryBelow ?? Number.MAX_VALUE,
|
|
position
|
|
)
|
|
}
|
|
}
|
|
|
|
for (const entry of [...changes, ...comments]) {
|
|
updateFirstEntryAboveBelowPositions(entry.op.p)
|
|
}
|
|
|
|
const cursor = newComments.iter()
|
|
while (cursor.value) {
|
|
updateFirstEntryAboveBelowPositions(cursor.from)
|
|
cursor.next()
|
|
}
|
|
|
|
return {
|
|
positionAbove: firstEntryAbove,
|
|
positionBelow: firstEntryBelow,
|
|
}
|
|
},
|
|
write({ positionAbove, positionBelow }) {
|
|
setPositionAbove(positionAbove)
|
|
setPositionBelow(positionBelow)
|
|
},
|
|
}),
|
|
200
|
|
),
|
|
[changes, comments, newComments, view]
|
|
)
|
|
|
|
useEffect(() => {
|
|
const scrollerElt = document.getElementsByClassName('cm-scroller')[0]
|
|
if (scrollerElt) {
|
|
scrollerElt.addEventListener('scroll', updateEntryPositions)
|
|
return () => {
|
|
scrollerElt.removeEventListener('scroll', updateEntryPositions)
|
|
}
|
|
}
|
|
}, [updateEntryPositions])
|
|
|
|
const onMoreCommentsClick = useCallback(
|
|
(position: number) => {
|
|
view.dispatch({
|
|
effects: EditorView.scrollIntoView(position, {
|
|
y: 'center',
|
|
}),
|
|
selection: EditorSelection.cursor(position),
|
|
})
|
|
},
|
|
[view]
|
|
)
|
|
|
|
const onMoreCommentsAboveClick = useCallback(() => {
|
|
if (positionAbove !== null) {
|
|
onMoreCommentsClick(positionAbove)
|
|
}
|
|
}, [positionAbove, onMoreCommentsClick])
|
|
|
|
const onMoreCommentsBelowClick = useCallback(() => {
|
|
if (positionBelow !== null) {
|
|
onMoreCommentsClick(positionBelow)
|
|
}
|
|
}, [positionBelow, onMoreCommentsClick])
|
|
|
|
return {
|
|
onEntriesPositioned: updateEntryPositions,
|
|
onMoreCommentsAboveClick:
|
|
positionAbove !== null ? onMoreCommentsAboveClick : null,
|
|
onMoreCommentsBelowClick:
|
|
positionBelow !== null ? onMoreCommentsBelowClick : null,
|
|
}
|
|
}
|
|
|
|
export default useMoreCommments
|