| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- 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'
- import useReviewPanelLayout from './use-review-panel-layout'
- const useMoreCommments = (
- changes: Change<EditOperation>[],
- comments: Change<CommentOperation>[],
- newComments: DecorationSet
- ): {
- onEntriesPositioned: () => void
- onMoreCommentsAboveClick: null | (() => void)
- onMoreCommentsBelowClick: null | (() => void)
- } => {
- const view = useCodeMirrorViewContext()
- const { showPanel, mini } = useReviewPanelLayout()
- const reviewPanelOpen = showPanel && !mini
- 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 || !reviewPanelOpen) {
- 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, reviewPanelOpen]
- )
- 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
|