({
- focusedEntryIndex: 0,
- overflowTop: 0,
- height: 0,
- positions: [],
- })
+ const previousLayoutInfoRef = useRef(initialLayoutInfo)
const layout = () => {
const container = containerRef.current
@@ -158,8 +147,10 @@ function PositionedEntries({
const layoutElement = reviewPanelOpen ? box : indicator
if (box && callout && layoutElement) {
- const previousEntryTopData = box.dataset.previousEntryTop
- const previousCalloutTopData = callout.dataset.previousEntryTop
+ const previousPositions = previousLayoutInfoRef.current?.positions.find(
+ pos => pos.entryId === entryId
+ )?.positions
+ const visible = Boolean(entry.screenPos)
entryViews.push({
entryId,
wrapper,
@@ -167,17 +158,10 @@ function PositionedEntries({
box,
callout,
layout: layoutElement,
- visible: !!entry.screenPos,
+ visible,
height: 0,
- previousEntryTop:
- previousEntryTopData !== undefined
- ? parseInt(previousEntryTopData)
- : null,
- previousCalloutTop:
- previousCalloutTopData !== undefined
- ? parseInt(previousCalloutTopData)
- : null,
entry,
+ previousPositions,
})
} else {
debugConsole.log(
@@ -305,10 +289,9 @@ function PositionedEntries({
// Position the main wrapper in its original position, if it had
// one, or its new position otherwise
- const entryTopInitial =
- entryView.previousEntryTop === null
- ? entryTop
- : entryView.previousEntryTop
+ const entryTopInitial = entryView.previousPositions
+ ? entryView.previousPositions.entryTop
+ : entryTop
css(entryView.box, {
top: entryTopInitial + overflowTop + 'px',
@@ -322,25 +305,20 @@ function PositionedEntries({
entryView.indicator.style.top = entryTopInitial + overflowTop + 'px'
}
- entryView.box.dataset.previousEntryTop = entryTopInitial + ''
-
// Position the callout element in its original position, if it had
// one, or its new position otherwise
calloutEl.classList.toggle(
'rp-entry-callout-inverted',
callout.inverted
)
- const calloutTopInitial =
- entryView.previousCalloutTop === null
- ? callout.top
- : entryView.previousCalloutTop
+ const calloutTopInitial = entryView.previousPositions
+ ? entryView.previousPositions.callout.top
+ : callout.top
css(calloutEl, {
top: calloutTopInitial + overflowTop + 'px',
height: callout.height + 'px',
})
-
- entryView.box.dataset.previousCalloutTop = calloutTopInitial + ''
}
}
}
@@ -355,19 +333,17 @@ function PositionedEntries({
const { entryTop, callout } = positions
// Position the main wrapper, if it's moved
- if (entryView.previousEntryTop !== entryTop) {
+ if (entryView.previousPositions?.entryTop !== entryTop) {
entryView.box.style.top = entryTop + overflowTop + 'px'
}
- entryView.box.dataset.previousEntryTop = entryTop + ''
if (entryView.indicator) {
entryView.indicator.style.top = entryTop + overflowTop + 'px'
}
// Position the callout element
- if (entryView.previousCalloutTop !== callout.top) {
+ if (entryView.previousPositions?.callout.top !== callout.top) {
calloutEl.style.top = callout.top + overflowTop + 'px'
- entryView.callout.dataset.previousCalloutTop = callout.top + ''
}
}
}
@@ -430,8 +406,14 @@ function PositionedEntries({
}
}
- useEventListener('review-panel:layout', () => {
- layout()
+ useEventListener('review-panel:layout', (event: CustomEvent) => {
+ if (!layoutSuspended) {
+ // Clear previous positions if forcing a layout
+ if (event.detail.force) {
+ previousLayoutInfoRef.current = initialLayoutInfo
+ }
+ layout()
+ }
})
// Layout on first render. This is necessary to ensure layout happens when
@@ -440,6 +422,11 @@ function PositionedEntries({
dispatchReviewPanelLayout()
}, [])
+ // Ensure layout is performed after opening or closing the review panel
+ useEffect(() => {
+ previousLayoutInfoRef.current = initialLayoutInfo
+ }, [reviewPanelOpen])
+
return (
void
- onMouseLeave?: () => void
- onIndicatorClick?: () => void
}
diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts
index d1a96ac390..bdcba2f30d 100644
--- a/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts
+++ b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts
@@ -137,6 +137,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
const [isAddingComment, setIsAddingComment] = useState(false)
const [navHeight, setNavHeight] = useState(0)
const [toolbarHeight, setToolbarHeight] = useState(0)
+ const [layoutSuspended, setLayoutSuspended] = useState(false)
const values = useMemo(
() => ({
@@ -164,6 +165,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
trackChangesOnForGuests,
trackChangesForGuestsAvailable,
formattedProjectMembers,
+ layoutSuspended,
}),
[
collapsed,
@@ -190,6 +192,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
trackChangesOnForGuests,
trackChangesForGuestsAvailable,
formattedProjectMembers,
+ layoutSuspended,
]
)
@@ -220,6 +223,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
setIsAddingComment,
setNavHeight,
setToolbarHeight,
+ setLayoutSuspended,
}),
[
handleSetSubview,
@@ -246,6 +250,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
setIsAddingComment,
setNavHeight,
setToolbarHeight,
+ setLayoutSuspended,
]
)
diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts b/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts
index ca60e019bf..9917fd9e79 100644
--- a/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts
+++ b/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts
@@ -45,10 +45,11 @@ export interface ReviewPanelState {
name: string
}
>
+ layoutSuspended: boolean
}
updaterFns: {
handleSetSubview: (subView: SubView) => void
- handleLayoutChange: () => void
+ handleLayoutChange: (force?: boolean) => void
gotoEntry: (docId: DocId, entryOffset: number) => void
resolveComment: (docId: DocId, entryId: ThreadId) => void
deleteComment: (threadId: ThreadId, commentId: CommentId) => void
@@ -82,6 +83,9 @@ export interface ReviewPanelState {
setToolbarHeight: React.Dispatch<
React.SetStateAction>
>
+ setLayoutSuspended: React.Dispatch<
+ React.SetStateAction>
+ >
}
}
/* eslint-enable no-use-before-define */
diff --git a/services/web/frontend/js/features/source-editor/extensions/changes/change-manager.ts b/services/web/frontend/js/features/source-editor/extensions/changes/change-manager.ts
index 173b14cdd7..f261060312 100644
--- a/services/web/frontend/js/features/source-editor/extensions/changes/change-manager.ts
+++ b/services/web/frontend/js/features/source-editor/extensions/changes/change-manager.ts
@@ -31,8 +31,10 @@ export const dispatchEditorEvent = (type: string, payload?: unknown) => {
}, 0)
}
-export const dispatchReviewPanelLayout = () => {
- window.dispatchEvent(new CustomEvent('review-panel:layout'))
+export const dispatchReviewPanelLayout = (force = false) => {
+ window.dispatchEvent(
+ new CustomEvent('review-panel:layout', { detail: { force } })
+ )
}
const scheduleDispatchReviewPanelLayout = debounce(
diff --git a/services/web/frontend/stylesheets/app/editor/review-panel.less b/services/web/frontend/stylesheets/app/editor/review-panel.less
index 67087429ac..7f51c08f71 100644
--- a/services/web/frontend/stylesheets/app/editor/review-panel.less
+++ b/services/web/frontend/stylesheets/app/editor/review-panel.less
@@ -232,10 +232,17 @@
.rp-entry-indicator {
display: none;
z-index: 2; // above .review-panel-toggler
+
.rp-size-mini & {
display: block;
}
+ .rp-floating-entry & {
+ display: block;
+ position: static;
+ width: @review-off-width - 4px;
+ }
+
.rp-size-mini &-add-comment {
display: none;
}
@@ -280,8 +287,8 @@
width: @review-panel-width;
}
- .rp-state-current-file-mini & {
- display: none;
+ .rp-state-current-file-mini &,
+ .rp-floating-entry & {
left: @review-off-width + @rp-entry-arrow-width;
box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.2);
z-index: 1;
@@ -309,6 +316,17 @@
}
}
+ .rp-state-current-file-mini & {
+ display: none;
+ }
+
+ .rp-floating-entry & {
+ position: absolute;
+ width: @review-panel-width;
+ left: @review-off-width + @rp-entry-arrow-width;
+ top: 0;
+ }
+
.rp-state-current-file-mini.rp-layout-left & {
left: auto;
right: @review-off-width + @rp-entry-arrow-width;
@@ -1258,7 +1276,6 @@ button when (@is-overleaf-light = true) {
.rp-entry-list-react {
position: relative;
- overflow-x: hidden;
}
.rp-state-current-file & {
@@ -1299,7 +1316,16 @@ button when (@is-overleaf-light = true) {
height: 0;
transition: height 150ms;
}
+}
+.rp-floating-entry {
+ position: absolute;
+ font-size: @rp-base-font-size;
+ color: @rp-type-blue;
+}
+
+.ol-cm-review-panel,
+.rp-floating-entry {
.rp-entry-metadata {
button {
padding: 0;