-
Notifications
You must be signed in to change notification settings - Fork 61
fix: align floating comments by page #1813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: align floating comments by page #1813
Conversation
- group floating comments per page using PresentationEditor bounds - normalize per-page positions and avoid overlaps in the sidebar - expand tracked-change and standard comment bodies when active - keep floating comments column full height for consistent layout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88c4d41523
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Update the actual top for rendering | ||
| comments.forEach((comment) => { | ||
| comment.top = comment.relativeTop; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve absolute comment positions across layout updates
This block overwrites comment.top with the page‑relative value. Because processLocations is re‑invoked whenever comment positions refresh (the watchEffect depends on getFloatingComments/renderedSizes, which update on layout events), subsequent runs will treat the already‑relative comment.top as absolute and subtract pageTop again. That compounds the offset and can push comments upward/out of view after any layout update or zoom. Consider keeping an absolute top (e.g., absoluteTop) and deriving a separate render top each run, or recomputing from fresh bounds instead of mutating comment.top.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the floating comments layout to be page-aware, positioning comments relative to their corresponding document pages with independent per-page scrolling. This architectural change aims to prevent comment overlaps across pages and maintain stable layouts in multi-page documents.
Changes:
- Refactored comment positioning from global to per-page coordinate system using PresentationEditor page bounds
- Implemented independent scrollable containers for each page's comments
- Added visual feedback by expanding comment bodies when active
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
| packages/superdoc/src/components/CommentsLayer/FloatingComments.vue | Core refactoring: added page-aware positioning logic, per-page containers with independent scrolling, and updated comment positioning calculations |
| packages/superdoc/src/components/CommentsLayer/CommentDialog.vue | Enhanced UX by adding collapsible comment bodies that expand when active |
| packages/superdoc/src/SuperDoc.vue | Extended floating comments container to full height to support new per-page layout |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <div | ||
| v-for="comment in renderedSizes" | ||
| :key="comment.id" | ||
| :style="getCommentPosition(comment)" | ||
| class="floating-comment" | ||
| v-for="(comments, pageIndex) in commentsByPage" | ||
| :key="`page-${pageIndex}`" | ||
| class="page-comments-container" | ||
| :data-page-index="pageIndex" | ||
| :style="getPageContainerStyle(pageIndex)" | ||
| :ref=" | ||
| (el) => { | ||
| if (el) pageContainerRefs[pageIndex] = el; | ||
| } | ||
| " | ||
| > |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The page-comments-container elements are scrollable but may not be keyboard accessible. Consider adding tabindex="0" to make these containers focusable and keyboard-navigable, allowing users to scroll through comments using keyboard arrow keys.
| const minTop = prev.top + prev.height + 15; | ||
| if (comment.top < minTop) { | ||
| comment.top = minTop; | ||
| const minTop = prev.relativeTop + prev.height + 15; |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic number 15 represents the minimum spacing between comments but lacks explanation. Consider extracting it as a named constant for better code clarity. For example: const MIN_COMMENT_SPACING = 15;
| const gapOffset = pageIndex * 24; | ||
| if (!position) { | ||
| return { | ||
| position: 'absolute', | ||
| top: `${pageIndex * 1056 + gapOffset}px`, |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pageIndex parameter in getPageContainerStyle receives a string from the v-for loop (object keys are strings), but it's used in numeric calculations (line 82: pageIndex * 24). While JavaScript's type coercion will convert it to a number, this implicit conversion reduces code clarity. Consider explicitly converting: const gapOffset = parseInt(pageIndex) * 24; or Number(pageIndex) * 24;
| const gapOffset = pageIndex * 24; | |
| if (!position) { | |
| return { | |
| position: 'absolute', | |
| top: `${pageIndex * 1056 + gapOffset}px`, | |
| const numericPageIndex = Number(pageIndex); | |
| const gapOffset = numericPageIndex * 24; | |
| if (!position) { | |
| return { | |
| position: 'absolute', | |
| top: `${numericPageIndex * 1056 + gapOffset}px`, |
| class="floating-comment" | ||
| :parent="parent" | ||
| :comment="comment.commentRef" | ||
| /> | ||
| > | ||
| <CommentDialog | ||
| :key="comment.id + commentsRenderKey" | ||
| class="floating-comment" |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class "floating-comment" is applied to both the wrapper div (line 247) and the CommentDialog component (line 251), creating nested elements with the same class. This could lead to unintended CSS cascading effects since both elements will have position: absolute. Consider using a more specific class name for one of these elements, such as "floating-comment-wrapper" for the outer div.
| .comments-dialog { | ||
| position: absolute; | ||
| min-width: 300px; | ||
| min-width: 290px; | ||
| } |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The .comments-dialog class is defined here but appears to be unused in this component's template. The class is actually used in CommentDialog.vue, which has its own scoped styles defining the same class. Due to Vue's scoped styling, this definition won't affect the child component and appears to be dead code. Consider removing it to reduce confusion.
| } | ||
| nextTick(processLocations); | ||
| }); |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removal of the watch on activeComment means that floating comments will no longer automatically scroll into view when a comment becomes active. The old implementation called scrollIntoView on the active comment's element. With the new per-page container structure, this functionality should be re-implemented to scroll the appropriate page container to bring the active comment into view, otherwise users may lose the ability to automatically navigate to active comments.
| <div | ||
| v-for="comment in renderedSizes" | ||
| :key="comment.id" | ||
| :style="getCommentPosition(comment)" | ||
| class="floating-comment" | ||
| v-for="(comments, pageIndex) in commentsByPage" | ||
| :key="`page-${pageIndex}`" | ||
| class="page-comments-container" | ||
| :data-page-index="pageIndex" | ||
| :style="getPageContainerStyle(pageIndex)" | ||
| :ref=" | ||
| (el) => { | ||
| if (el) pageContainerRefs[pageIndex] = el; | ||
| } | ||
| " | ||
| > |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The page-comments-container elements have scrollable overflow but lack ARIA attributes for accessibility. Consider adding role="region" and aria-label or aria-labelledby attributes to help screen reader users understand that these are scrollable comment regions for specific pages. For example: role="region" :aria-label="Comments for page ${parseInt(pageIndex) + 1}"
| top: pageBounds.y || index * 1056, // fallback: approximate page height with gap | ||
| height: pageBounds.h || pageBounds.height || 1056, | ||
| pageIndex: index, | ||
| }; | ||
| }); | ||
| return positions; | ||
| }; | ||
| // Get style for each page comment container | ||
| const getPageContainerStyle = computed(() => (pageIndex) => { | ||
| const position = pagePositions.value[pageIndex]; | ||
| const gapOffset = pageIndex * 24; | ||
| if (!position) { | ||
| return { | ||
| position: 'absolute', | ||
| top: `${pageIndex * 1056 + gapOffset}px`, |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic numbers 1056 (page height) and 24 (gap offset) are used in multiple places without explanation. Consider extracting these as named constants at the top of the script section for better maintainability. For example: const DEFAULT_PAGE_HEIGHT = 1056; const PAGE_GAP = 24;
| :style="getPageContainerStyle(pageIndex)" | ||
| :ref=" | ||
| (el) => { | ||
| if (el) pageContainerRefs[pageIndex] = el; |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ref assignment pattern pageContainerRefs[pageIndex] = el is missing .value. In Vue 3 composition API, refs created with ref() require accessing .value to update the underlying object. This should be pageContainerRefs.value[pageIndex] = el to correctly store the element references.
| if (el) pageContainerRefs[pageIndex] = el; | |
| if (el) pageContainerRefs.value[pageIndex] = el; |
| const presentation = PresentationEditor.getInstance(props.currentDocument.id); | ||
| if (!presentation) return {}; | ||
| const pages = presentation.getPages(); |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code assumes presentation.getPages() will always return an array, but if it returns null or undefined, calling .forEach() on line 65 will throw a runtime error. Consider adding a check: const pages = presentation.getPages() || []; or const pages = presentation.getPages(); if (!pages) return {};
| const pages = presentation.getPages(); | |
| const pages = presentation.getPages() || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Refactored the comments layout to be page-aware: comments are aligned to their corresponding document pages, positioned relative to the page container, and rendered with independent per-page scrolling to avoid overlaps and maintain stable layouts.