diff --git a/package-lock.json b/package-lock.json
index 057ccb17acc0b..efd5ed04bed52 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4473,6 +4473,7 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
"dependencies": {
"@emotion/memoize": "^0.8.1"
}
@@ -4480,7 +4481,8 @@
"node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
- "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "license": "MIT"
},
"node_modules/@emotion/jest": {
"version": "11.7.1",
@@ -51177,6 +51179,7 @@
"license": "GPL-2.0-or-later",
"dependencies": {
"@babel/runtime": "7.25.7",
+ "@floating-ui/react-dom": "2.0.8",
"@wordpress/a11y": "*",
"@wordpress/api-fetch": "*",
"@wordpress/blob": "*",
@@ -51233,6 +51236,19 @@
"react-dom": "^18.0.0"
}
},
+ "packages/editor/node_modules/@floating-ui/react-dom": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz",
+ "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.6.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"packages/element": {
"name": "@wordpress/element",
"version": "6.14.0",
@@ -51856,6 +51872,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"browserslist": "^4.23.3",
"caniuse-lite": "^1.0.30001646",
@@ -51878,6 +51895,7 @@
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "license": "MIT",
"engines": {
"node": "*"
},
@@ -51889,7 +51907,8 @@
"packages/postcss-plugins-preset/node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
},
"packages/postcss-themes": {
"name": "@wordpress/postcss-themes",
diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js
index 21b9f4bca7fc3..2288acc1e0355 100644
--- a/packages/block-editor/src/private-apis.js
+++ b/packages/block-editor/src/private-apis.js
@@ -50,6 +50,8 @@ import useBlockDisplayTitle from './components/block-title/use-block-display-tit
import TabbedSidebar from './components/tabbed-sidebar';
import CommentIconSlotFill from './components/collab/block-comment-icon-slot';
import CommentIconToolbarSlotFill from './components/collab/block-comment-icon-toolbar-slot';
+import { useBlockElementRef } from './components/block-list/use-block-props/use-block-refs';
+
/**
* Private @wordpress/block-editor APIs.
*/
@@ -97,4 +99,5 @@ lock( privateApis, {
sectionRootClientIdKey,
CommentIconSlotFill,
CommentIconToolbarSlotFill,
+ useBlockElementRef,
} );
diff --git a/packages/editor/package.json b/packages/editor/package.json
index b19e2fb3dab71..442d32b08e7d7 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -34,6 +34,7 @@
],
"dependencies": {
"@babel/runtime": "7.25.7",
+ "@floating-ui/react-dom": "2.0.8",
"@wordpress/a11y": "*",
"@wordpress/api-fetch": "*",
"@wordpress/blob": "*",
diff --git a/packages/editor/src/components/collab-sidebar/comments.js b/packages/editor/src/components/collab-sidebar/comments.js
index d492d233956cb..b166eb0fae2fe 100644
--- a/packages/editor/src/components/collab-sidebar/comments.js
+++ b/packages/editor/src/components/collab-sidebar/comments.js
@@ -2,11 +2,16 @@
* External dependencies
*/
import clsx from 'clsx';
+import {
+ useFloating,
+ autoUpdate,
+ offset as offsetMiddleware,
+} from '@floating-ui/react-dom';
/**
* WordPress dependencies
*/
-import { useState, RawHTML } from '@wordpress/element';
+import { useState, RawHTML, useRef, useEffect } from '@wordpress/element';
import {
__experimentalHStack as HStack,
__experimentalVStack as VStack,
@@ -18,13 +23,19 @@ import {
import { Icon, check, published, moreVertical } from '@wordpress/icons';
import { __, _x, sprintf } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
-import { store as blockEditorStore } from '@wordpress/block-editor';
+import {
+ store as blockEditorStore,
+ privateApis as blockEditorPrivateApis,
+} from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import CommentAuthorInfo from './comment-author-info';
import CommentForm from './comment-form';
+import { unlock } from '../../lock-unlock';
+
+const { useBlockElementRef } = unlock( blockEditorPrivateApis );
/**
* Renders the Comments component.
@@ -37,6 +48,7 @@ import CommentForm from './comment-form';
* @param {Function} props.onCommentResolve - The function to mark a comment as resolved.
* @param {boolean} props.showCommentBoard - Whether to show the comment board.
* @param {Function} props.setShowCommentBoard - The function to set the comment board visibility.
+ * @param {boolean} props.canvasSidebar - Whether is this canvas sidebar or not.
* @return {React.ReactNode} The rendered Comments component.
*/
export function Comments( {
@@ -47,7 +59,19 @@ export function Comments( {
onCommentResolve,
showCommentBoard,
setShowCommentBoard,
+ canvasSidebar,
} ) {
+ const [ heights, setHeights ] = useState( {} );
+
+ const updateHeight = ( id, newHeight ) => {
+ setHeights( ( prev ) => {
+ if ( prev[ id ] !== newHeight ) {
+ return { ...prev, [ id ]: newHeight };
+ }
+ return prev;
+ } );
+ };
+
const { blockCommentId } = useSelect( ( select ) => {
const { getBlockAttributes, getSelectedBlockClientId } =
select( blockEditorStore );
@@ -64,11 +88,20 @@ export function Comments( {
showCommentBoard && blockCommentId ? blockCommentId : null
);
+ // Object to store offsets for each board.
+ const offsetsRef = useRef( {} );
+
+ const updateOffsets = ( id, offset ) => {
+ offsetsRef.current[ id ] = offset;
+ };
+
const clearThreadFocus = () => {
setFocusThread( null );
setShowCommentBoard( false );
};
+ const ParentWrapper = canvasSidebar ? ThreadWrapper : VStack;
+
return (
<>
{
@@ -89,9 +122,12 @@ export function Comments( {
}
{ Array.isArray( threads ) &&
threads.length > 0 &&
- threads.map( ( thread ) => (
- (
+ setFocusThread( thread.id ) }
+ offsetsRef={ offsetsRef }
+ updateOffsets={ updateOffsets }
+ previousThreadId={ threads[ index - 1 ]?.id }
+ updateHeight={ updateHeight }
+ heights={ heights }
>
-
+
) ) }
>
);
@@ -357,3 +396,81 @@ const CommentBoard = ( { thread, onResolve, onEdit, onDelete, status } ) => {
>
);
};
+
+const ThreadWrapper = ( {
+ children,
+ thread,
+ className,
+ onClick,
+ offsetsRef,
+ updateOffsets,
+ previousThreadId,
+ updateHeight,
+ heights,
+} ) => {
+ const blockRef = useRef();
+ useBlockElementRef( thread.clientId, blockRef );
+
+ const selectedBlockElementRect = blockRef.current?.getBoundingClientRect();
+
+ const initialOffsetTop = selectedBlockElementRect?.top;
+
+ const previousOffset = previousThreadId
+ ? offsetsRef.current[ previousThreadId ]
+ : 0;
+
+ const previousBoardHeight = heights[ previousThreadId ];
+
+ const calculateOffset = () => {
+ if (
+ previousOffset &&
+ initialOffsetTop < previousOffset + previousBoardHeight
+ ) {
+ return previousOffset - initialOffsetTop + previousBoardHeight + 20;
+ }
+ return 0;
+ };
+
+ const { y, refs } = useFloating( {
+ placement: 'right-start',
+ middleware: [
+ offsetMiddleware( {
+ crossAxis: calculateOffset(),
+ } ),
+ ],
+ whileElementsMounted: autoUpdate,
+ } );
+
+ useEffect( () => {
+ if ( blockRef.current ) {
+ refs.setReference( blockRef.current ); // Bind reference element
+ }
+ }, [ blockRef, refs ] );
+
+ useEffect( () => {
+ if ( y !== null && y !== 0 ) {
+ updateOffsets( thread.id, y, refs.floating?.current?.clientHeight ); // Pass the offset to the parent
+ }
+ }, [ y, updateOffsets ] );
+
+ useEffect( () => {
+ if ( refs.floating?.current ) {
+ const newHeight = refs.floating?.current.scrollHeight;
+ updateHeight( thread.id, newHeight );
+ }
+ }, [ thread.id, updateHeight ] );
+
+ return (
+
+ { children }
+
+ );
+};
diff --git a/packages/editor/src/components/collab-sidebar/index.js b/packages/editor/src/components/collab-sidebar/index.js
index c53ee9e7e919c..d58a3f766d9df 100644
--- a/packages/editor/src/components/collab-sidebar/index.js
+++ b/packages/editor/src/components/collab-sidebar/index.js
@@ -56,6 +56,7 @@ function CollabSidebarContent( {
setShowCommentBoard,
styles,
comments,
+ canvasSidebar = false,
} ) {
const { createNotice } = useDispatch( noticesStore );
const { saveEntityRecord, deleteEntityRecord } = useDispatch( coreStore );
@@ -203,7 +204,6 @@ function CollabSidebarContent( {
setShowCommentBoard={ setShowCommentBoard }
/>
);
@@ -296,19 +297,23 @@ export default function CollabSidebar() {
return { resultComments: [], sortedThreads: [] };
}
+ const blockCommentIds = getCommentIdsFromBlocks( blocks );
+ const blockCommentIdMap = new Map(
+ blockCommentIds.map( ( item ) => [ item.commentID, item ] )
+ );
+
const updatedResult = result.map( ( item ) => ( {
...item,
reply: [ ...item.reply ].reverse(),
+ clientId: blockCommentIdMap.get( item.id )?.clientID,
} ) );
- const blockCommentIds = getCommentIdsFromBlocks( blocks );
-
const threadIdMap = new Map(
updatedResult.map( ( thread ) => [ thread.id, thread ] )
);
const sortedComments = blockCommentIds
- .map( ( id ) => threadIdMap.get( id ) )
+ .map( ( item ) => threadIdMap.get( item.commentID ) )
.filter( ( thread ) => thread !== undefined );
return { resultComments: updatedResult, sortedThreads: sortedComments };
@@ -367,6 +372,7 @@ export default function CollabSidebar() {
styles={ {
backgroundColor,
} }
+ canvasSidebar
/>
>
diff --git a/packages/editor/src/components/collab-sidebar/style.scss b/packages/editor/src/components/collab-sidebar/style.scss
index 29fcc80779f68..585b5e4580746 100644
--- a/packages/editor/src/components/collab-sidebar/style.scss
+++ b/packages/editor/src/components/collab-sidebar/style.scss
@@ -8,11 +8,19 @@
.editor-collab-sidebar {
height: 100%;
+ overflow: hidden;
+
+ .editor-collab-sidebar-panel {
+ &__thread {
+ position: absolute;
+ width: calc(100% - 32px);
+ }
+ }
}
.editor-collab-sidebar-panel {
padding: $grid-unit-20;
- height: 100%;
+ height: 100vw;
&__thread {
position: relative;
diff --git a/packages/editor/src/components/collab-sidebar/utils.js b/packages/editor/src/components/collab-sidebar/utils.js
index 51345392098ff..020ab14790eef 100644
--- a/packages/editor/src/components/collab-sidebar/utils.js
+++ b/packages/editor/src/components/collab-sidebar/utils.js
@@ -27,7 +27,10 @@ export function getCommentIdsFromBlocks( blocks ) {
block.attributes.blockCommentId &&
! commentIds.includes( block.attributes.blockCommentId )
) {
- commentIds.push( block.attributes.blockCommentId );
+ commentIds.push( {
+ clientID: block.clientId,
+ commentID: block.attributes.blockCommentId,
+ } );
}
// Recursively check inner blocks