Merge pull request #21208 from overleaf/dp-accept-reject-all-changes

Add options to accept/reject all changes to review tooltip

GitOrigin-RevId: 1cea76926d59d0354c8abaa6ba69b7e99c02fbe0
This commit is contained in:
David 2024-10-23 11:14:16 +01:00 committed by Copybot
parent c4b23b4d39
commit 1c5f5950fa
7 changed files with 84 additions and 20 deletions

View file

@ -6,7 +6,7 @@ import {
import { EditorSelection } from '@codemirror/state' import { EditorSelection } from '@codemirror/state'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useThreadsActionsContext } from '../context/threads-context' import { useThreadsActionsContext } from '../context/threads-context'
import { removeNewCommentRangeEffect } from '@/features/source-editor/extensions/add-comment' import { removeNewCommentRangeEffect } from '@/features/source-editor/extensions/review-tooltip'
import useSubmittableTextInput from '../hooks/use-submittable-text-input' import useSubmittableTextInput from '../hooks/use-submittable-text-input'
import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area' import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area'
import { ReviewPanelEntry } from './review-panel-entry' import { ReviewPanelEntry } from './review-panel-entry'

View file

@ -28,7 +28,7 @@ import { canAggregate } from '../utils/can-aggregate'
import ReviewPanelEmptyState from './review-panel-empty-state' import ReviewPanelEmptyState from './review-panel-empty-state'
import useEventListener from '@/shared/hooks/use-event-listener' import useEventListener from '@/shared/hooks/use-event-listener'
import { hasActiveRange } from '@/features/review-panel-new/utils/has-active-range' import { hasActiveRange } from '@/features/review-panel-new/utils/has-active-range'
import { addCommentStateField } from '@/features/source-editor/extensions/add-comment' import { reviewTooltipStateField } from '@/features/source-editor/extensions/review-tooltip'
import ReviewPanelMoreCommentsButton from './review-panel-more-comments-button' import ReviewPanelMoreCommentsButton from './review-panel-more-comments-button'
import useMoreCommments from '../hooks/use-more-comments' import useMoreCommments from '../hooks/use-more-comments'
import { Decoration } from '@codemirror/view' import { Decoration } from '@codemirror/view'
@ -111,7 +111,10 @@ const ReviewPanelCurrentFile: FC = () => {
const positionsRef = useRef<Map<string, number>>(new Map()) const positionsRef = useRef<Map<string, number>>(new Map())
const addCommentRanges = state.field(addCommentStateField, false)?.ranges const addCommentRanges = state.field(
reviewTooltipStateField,
false
)?.addCommentRanges
useEffect(() => { useEffect(() => {
if (aggregatedRanges) { if (aggregatedRanges) {

View file

@ -4,6 +4,7 @@ import {
SetStateAction, SetStateAction,
useCallback, useCallback,
useEffect, useEffect,
useMemo,
useState, useState,
} from 'react' } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
@ -14,14 +15,19 @@ import {
useCodeMirrorViewContext, useCodeMirrorViewContext,
} from '@/features/source-editor/components/codemirror-context' } from '@/features/source-editor/components/codemirror-context'
import { import {
addCommentStateField,
buildAddNewCommentRangeEffect, buildAddNewCommentRangeEffect,
} from '@/features/source-editor/extensions/add-comment' reviewTooltipStateField,
} from '@/features/source-editor/extensions/review-tooltip'
import { getTooltip } from '@codemirror/view' import { getTooltip } from '@codemirror/view'
import useViewerPermissions from '@/shared/hooks/use-viewer-permissions' import useViewerPermissions from '@/shared/hooks/use-viewer-permissions'
import usePreviousValue from '@/shared/hooks/use-previous-value' import usePreviousValue from '@/shared/hooks/use-previous-value'
import { useLayoutContext } from '@/shared/context/layout-context' import { useLayoutContext } from '@/shared/context/layout-context'
import { useReviewPanelViewActionsContext } from '../context/review-panel-view-context' import { useReviewPanelViewActionsContext } from '../context/review-panel-view-context'
import {
useRangesActionsContext,
useRangesContext,
} from '../context/ranges-context'
import { isInsertOperation } from '@/utils/operations'
const ReviewTooltipMenu: FC = () => { const ReviewTooltipMenu: FC = () => {
const state = useCodeMirrorStateContext() const state = useCodeMirrorStateContext()
@ -29,7 +35,7 @@ const ReviewTooltipMenu: FC = () => {
const isViewer = useViewerPermissions() const isViewer = useViewerPermissions()
const [show, setShow] = useState(true) const [show, setShow] = useState(true)
const tooltipState = state.field(addCommentStateField, false)?.tooltip const tooltipState = state.field(reviewTooltipStateField, false)?.tooltip
const previousTooltipState = usePreviousValue(tooltipState) const previousTooltipState = usePreviousValue(tooltipState)
useEffect(() => { useEffect(() => {
@ -62,6 +68,8 @@ const ReviewTooltipMenuContent: FC<{
const state = useCodeMirrorStateContext() const state = useCodeMirrorStateContext()
const { setReviewPanelOpen } = useLayoutContext() const { setReviewPanelOpen } = useLayoutContext()
const { setView } = useReviewPanelViewActionsContext() const { setView } = useReviewPanelViewActionsContext()
const ranges = useRangesContext()
const { acceptChanges, rejectChanges } = useRangesActionsContext()
const addComment = useCallback(() => { const addComment = useCallback(() => {
setReviewPanelOpen(true) setReviewPanelOpen(true)
@ -80,6 +88,28 @@ const ReviewTooltipMenuContent: FC<{
} }
}, [addComment]) }, [addComment])
const changeIdsInSelection = useMemo(() => {
return (ranges?.changes ?? [])
.filter(({ op }) => {
const opFrom = op.p
const opLength = isInsertOperation(op) ? op.i.length : 0
const opTo = opFrom + opLength
const selection = state.selection.main
return opFrom >= selection.from && opTo <= selection.to
})
.map(({ id }) => id)
}, [ranges, state.selection.main])
const acceptChangesHandler = useCallback(() => {
acceptChanges(...changeIdsInSelection)
}, [acceptChanges, changeIdsInSelection])
const rejectChangesHandler = useCallback(() => {
rejectChanges(...changeIdsInSelection)
}, [rejectChanges, changeIdsInSelection])
const showChangesButtons = changeIdsInSelection.length > 0
return ( return (
<div className="review-tooltip-menu"> <div className="review-tooltip-menu">
<button <button
@ -89,6 +119,23 @@ const ReviewTooltipMenuContent: FC<{
<MaterialIcon type="chat" /> <MaterialIcon type="chat" />
{t('add_comment')} {t('add_comment')}
</button> </button>
{showChangesButtons && (
<>
<div className="review-tooltip-menu-divider" />
<button
className="review-tooltip-menu-button"
onClick={acceptChangesHandler}
>
<MaterialIcon type="check" />
</button>
<button
className="review-tooltip-menu-button"
onClick={rejectChangesHandler}
>
<MaterialIcon type="clear" />
</button>
</>
)}
</div> </div>
) )
} }

View file

@ -52,7 +52,7 @@ import { mathPreview } from './math-preview'
import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { ranges } from './ranges' import { ranges } from './ranges'
import { trackDetachedComments } from './track-detached-comments' import { trackDetachedComments } from './track-detached-comments'
import { addComment } from './add-comment' import { reviewTooltip } from './review-tooltip'
const moduleExtensions: Array<(options: Record<string, any>) => Extension> = const moduleExtensions: Array<(options: Record<string, any>) => Extension> =
importOverleafModules('sourceEditorExtensions').map( importOverleafModules('sourceEditorExtensions').map(
@ -138,7 +138,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
trackDetachedComments(options.currentDoc), trackDetachedComments(options.currentDoc),
visual(options.visual), visual(options.visual),
mathPreview(options.settings.mathPreview), mathPreview(options.settings.mathPreview),
addComment(), reviewTooltip(),
toolbarPanel(), toolbarPanel(),
verticalOverflow(), verticalOverflow(),
highlightActiveLine(options.visual.visual), highlightActiveLine(options.visual.visual),

View file

@ -31,31 +31,31 @@ export const buildAddNewCommentRangeEffect = (range: SelectionRange) => {
) )
} }
export const addComment = (): Extension => { export const reviewTooltip = (): Extension => {
if (!isSplitTestEnabled('review-panel-redesign')) { if (!isSplitTestEnabled('review-panel-redesign')) {
return [] return []
} }
return [addCommentTheme, addCommentStateField, textSelected] return [reviewTooltipTheme, reviewTooltipStateField, textSelected]
} }
export const addCommentStateField = StateField.define<{ export const reviewTooltipStateField = StateField.define<{
tooltip: Tooltip | null tooltip: Tooltip | null
ranges: DecorationSet addCommentRanges: DecorationSet
}>({ }>({
create() { create() {
return { tooltip: null, ranges: Decoration.none } return { tooltip: null, addCommentRanges: Decoration.none }
}, },
update(field, tr) { update(field, tr) {
let { tooltip, ranges } = field let { tooltip, addCommentRanges } = field
ranges = ranges.map(tr.changes) addCommentRanges = addCommentRanges.map(tr.changes)
for (const effect of tr.effects) { for (const effect of tr.effects) {
if (effect.is(removeNewCommentRangeEffect)) { if (effect.is(removeNewCommentRangeEffect)) {
const rangeToRemove = effect.value const rangeToRemove = effect.value
ranges = ranges.update({ addCommentRanges = addCommentRanges.update({
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
filter: (from, to, value) => { filter: (from, to, value) => {
return value.spec.id !== rangeToRemove.spec.id return value.spec.id !== rangeToRemove.spec.id
@ -65,7 +65,7 @@ export const addCommentStateField = StateField.define<{
if (effect.is(addNewCommentRangeEffect)) { if (effect.is(addNewCommentRangeEffect)) {
const rangeToAdd = effect.value const rangeToAdd = effect.value
ranges = ranges.update({ addCommentRanges = addCommentRanges.update({
add: [rangeToAdd], add: [rangeToAdd],
}) })
} }
@ -79,11 +79,11 @@ export const addCommentStateField = StateField.define<{
} }
} }
return { tooltip, ranges } return { tooltip, addCommentRanges }
}, },
provide: field => [ provide: field => [
EditorView.decorations.from(field, field => field.ranges), EditorView.decorations.from(field, field => field.addCommentRanges),
showTooltip.compute([field], state => state.field(field).tooltip), showTooltip.compute([field], state => state.field(field).tooltip),
], ],
}) })
@ -109,7 +109,7 @@ function buildTooltip(range: SelectionRange): Tooltip | null {
/** /**
* Styles for the tooltip * Styles for the tooltip
*/ */
const addCommentTheme = EditorView.baseTheme({ const reviewTooltipTheme = EditorView.baseTheme({
'.review-tooltip-menu-container.cm-tooltip': { '.review-tooltip-menu-container.cm-tooltip': {
backgroundColor: 'transparent', backgroundColor: 'transparent',
border: 'none', border: 'none',

View file

@ -619,10 +619,12 @@
} }
.review-tooltip-menu { .review-tooltip-menu {
display: flex;
box-shadow: 0px 2px 4px 0px #1e253029; box-shadow: 0px 2px 4px 0px #1e253029;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
padding: 4px; padding: 4px;
gap: 4px;
} }
.review-tooltip-menu-button { .review-tooltip-menu-button {
@ -635,6 +637,11 @@
border-radius: @border-radius-base; border-radius: @border-radius-base;
} }
.review-tooltip-menu-divider {
width: 1px;
background-color: #e7e9ee;
}
.review-tooltip-add-comment-button { .review-tooltip-add-comment-button {
padding: 2px 8px; padding: 2px 8px;
} }

View file

@ -623,10 +623,12 @@
} }
.review-tooltip-menu { .review-tooltip-menu {
display: flex;
box-shadow: 0 2px 4px 0 #1e253029; box-shadow: 0 2px 4px 0 #1e253029;
border: none; border: none;
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
padding: var(--spacing-02); padding: var(--spacing-02);
gap: var(--spacing-02);
} }
.review-tooltip-menu-button { .review-tooltip-menu-button {
@ -644,6 +646,11 @@
padding: var(--spacing-01) var(--spacing-04); padding: var(--spacing-01) var(--spacing-04);
} }
.review-tooltip-menu-divider {
width: 1px;
background-color: #e7e9ee;
}
.review-panel-tooltip { .review-panel-tooltip {
pointer-events: none; // this is to prevent mouseLeave event from firing when hovering over the tooltip pointer-events: none; // this is to prevent mouseLeave event from firing when hovering over the tooltip
} }