Skip to content
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

Comment Template block: Handle nested comments #36065

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fb025fb
Comments Query: Use discussion settings as a defult for queryPerPage
michalczaplinski Oct 28, 2021
bd78b9c
Comments Query: Make <ul> into <ol>
michalczaplinski Oct 28, 2021
9d86e2b
Comment Template: Add styles
michalczaplinski Oct 29, 2021
b5ee661
Comment Template: Handle the discussion settings
michalczaplinski Oct 29, 2021
42ceb0b
Fix whitespace
michalczaplinski Oct 29, 2021
f8f4436
Comments template: Stop using comments_per_page
michalczaplinski Nov 9, 2021
7b552de
Comments Template: Update the fixtures
michalczaplinski Nov 24, 2021
4ae2cbf
Comments Template: Update JS-side implementation to fix bugs with fo…
michalczaplinski Nov 24, 2021
d1b1723
Comments Template: Update styles and tests
michalczaplinski Nov 24, 2021
25951a4
Comment Template: Add comments to imports because eslint
michalczaplinski Nov 24, 2021
6d461e1
Comments Template: Add better comments
michalczaplinski Nov 24, 2021
4574b50
Coment Template: Refactor JS + add better comments
michalczaplinski Nov 25, 2021
2dd4dea
Comment Template: Fix JSDoc
michalczaplinski Nov 25, 2021
2aa2f71
Comment Template: Add nested comments on PHP side
michalczaplinski Nov 26, 2021
54ad6c4
Comment Template: Add css to block-library CSS file.
michalczaplinski Nov 26, 2021
7c83c30
Comment Template: Generate correct attributes
michalczaplinski Nov 26, 2021
08a3f22
Just reformat
michalczaplinski Nov 26, 2021
8ca6657
Comment Template: Revert useless rename
michalczaplinski Nov 26, 2021
a707338
Remove unnecesary isset() call
michalczaplinski Nov 29, 2021
2db7cac
Move RenderComments to inside CommentTemplateInnerBlocks
michalczaplinski Nov 29, 2021
9226e01
Fix typo
michalczaplinski Nov 29, 2021
3b57775
Comment Template: Simplify mapping over comments
michalczaplinski Nov 29, 2021
e4e18fd
Comment Template: Prefix functions with `block_core_comment_template`
michalczaplinski Nov 29, 2021
1c34cab
Comment Template: Refactor
michalczaplinski Dec 1, 2021
8a8e9b5
Comment Template: Change how we destructure children in CommentTempla…
michalczaplinski Dec 1, 2021
13216ef
Comment Template: Add PHP test cases
michalczaplinski Dec 1, 2021
737a123
Comment Template: Rename RenderComments to CommentList
michalczaplinski Dec 1, 2021
a352e05
Comment Template: Refactor to make ConmmentTemplateInnerBlock more re…
michalczaplinski Dec 1, 2021
712a9c1
Comment Template: Handle queryPerPage correctly on the JS side
michalczaplinski Dec 1, 2021
802e379
Comment Template: Remove the `id` from the comment object
michalczaplinski Dec 3, 2021
4913a8c
Comment Template: Remove tearDown() from PHP tests
michalczaplinski Dec 3, 2021
6c4fd11
Comment Template: Add comment to clarify that build step adds 'gutenb…
michalczaplinski Dec 3, 2021
49cbc42
Comment Template: Rename the blockContexts to comments
michalczaplinski Dec 3, 2021
71c7d00
Comment Template: Add context `embed`
michalczaplinski Dec 3, 2021
ebdd15f
Fix code styling issues
gziolo Dec 6, 2021
2e1ce22
Fix code styling issues
gziolo Dec 6, 2021
d56656b
Improve JSDoc for `convertToTree` function
gziolo Dec 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/block-library/src/comment-template/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"reusable": false,
"html": false,
"align": true
}
},
"style": "wp-block-comment-template"
}
143 changes: 107 additions & 36 deletions packages/block-library/src/comment-template/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import {
import { Spinner } from '@wordpress/components';
import { store as coreStore } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import { convertToTree } from './util';

const TEMPLATE = [
[ 'core/comment-author-avatar' ],
[ 'core/comment-author-name' ],
Expand All @@ -23,43 +28,126 @@ const TEMPLATE = [
[ 'core/comment-edit-link' ],
];

function CommentTemplateInnerBlocks() {
const innerBlocksProps = useInnerBlocksProps( {}, { template: TEMPLATE } );
return <li { ...innerBlocksProps } />;
/**
* Component which renders the inner blocks of the Comment Template.
*
* @param {Object} props Component props.
* @param {Array} [props.comment] - A comment object.
* @param {Array} [props.activeComment] - The block that is currently active.
* @param {Array} [props.setActiveComment] - The setter for activeComment.
* @param {Array} [props.firstBlock] - First comment in the array.
* @param {Array} [props.blocks] - Array of blocks returned from
* getBlocks() in parent .
* @return {WPElement} Inner blocks of the Comment Template
*/
function CommentTemplateInnerBlocks( {
comment,
activeComment,
setActiveComment,
firstBlock,
blocks,
} ) {
const { children, ...innerBlocksProps } = useInnerBlocksProps(
{},
{ template: TEMPLATE }
);
return (
<li { ...innerBlocksProps }>
{ comment === ( activeComment || firstBlock ) ? (
children
) : (
<BlockPreview
blocks={ blocks }
__experimentalLive
__experimentalOnClick={ () => setActiveComment( comment ) }
/>
) }
{ comment?.children?.length > 0 ? (
<CommentsList
comments={ comment.children }
activeComment={ activeComment }
setActiveComment={ setActiveComment }
blocks={ blocks }
/>
) : null }
</li>
);
}

/**
* Component that renders a list of (nested) comments. It is called recursively.
*
* @param {Object} props Component props.
* @param {Array} [props.comments] - Array of comment objects.
* @param {Array} [props.blockProps] - Props from parent's `useBlockProps()`.
* @param {Array} [props.activeComment] - The block that is currently active.
* @param {Array} [props.setActiveComment] - The setter for activeComment.
* @param {Array} [props.blocks] - Array of blocks returned from
* getBlocks() in parent .
* @return {WPElement} List of comments.
*/
const CommentsList = ( {
comments,
blockProps,
activeComment,
setActiveComment,
blocks,
} ) => (
<ol { ...blockProps }>
{ comments &&
comments.map( ( comment ) => (
<BlockContextProvider
key={ comment.commentId }
value={ comment }
>
<CommentTemplateInnerBlocks
comment={ comment }
activeComment={ activeComment }
setActiveComment={ setActiveComment }
blocks={ blocks }
firstBlock={ comments[ 0 ] }
/>
</BlockContextProvider>
) ) }
</ol>
);

export default function CommentTemplateEdit( {
clientId,
context: { postId, queryPerPage },
} ) {
const blockProps = useBlockProps();

const [ activeBlockContext, setActiveBlockContext ] = useState();
const [ activeComment, setActiveComment ] = useState();

const { comments, blocks } = useSelect(
const { rawComments, blocks } = useSelect(
( select ) => {
const { getEntityRecords } = select( coreStore );
const { getBlocks } = select( blockEditorStore );

return {
comments: getEntityRecords( 'root', 'comment', {
rawComments: getEntityRecords( 'root', 'comment', {
post: postId,
status: 'approve',
per_page: queryPerPage,
order: 'asc',
context: 'embed',
} ),
blocks: getBlocks( clientId ),
};
},
[ queryPerPage, postId, clientId ]
[ postId, clientId ]
);

const blockContexts = useMemo(
() => comments?.map( ( comment ) => ( { commentId: comment.id } ) ),
[ comments ]
// We convert the flat list of comments to tree.
// Then, we show only a maximum of `queryPerPage` number of comments.
// This is because passing `per_page` to `getEntityRecords()` does not
// take into account nested comments.
const comments = useMemo(
() => convertToTree( rawComments ).slice( 0, queryPerPage ),
[ rawComments, queryPerPage ]
);

if ( ! comments ) {
if ( ! rawComments ) {
return (
<p { ...blockProps }>
<Spinner />
Expand All @@ -72,29 +160,12 @@ export default function CommentTemplateEdit( {
}

return (
<ul { ...blockProps }>
{ blockContexts &&
blockContexts.map( ( blockContext ) => (
<BlockContextProvider
key={ blockContext.commentId }
value={ blockContext }
>
{ blockContext ===
( activeBlockContext || blockContexts[ 0 ] ) ? (
<CommentTemplateInnerBlocks />
) : (
<li>
<BlockPreview
blocks={ blocks }
__experimentalLive
__experimentalOnClick={ () =>
setActiveBlockContext( blockContext )
}
/>
</li>
) }
</BlockContextProvider>
) ) }
</ul>
<CommentsList
comments={ comments }
blockProps={ blockProps }
blocks={ blocks }
activeComment={ activeComment }
setActiveComment={ setActiveComment }
/>
);
}
61 changes: 48 additions & 13 deletions packages/block-library/src/comment-template/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@
* @package WordPress
*/

/**
* Function that recursively renders a list of nested comments.
*
* @param WP_Comment[] $comments The array of comments.
* @param WP_Block $block Block instance.
* @return string
*/
function block_core_comment_template_render_comments( $comments, $block ) {
$content = '';
foreach ( $comments as $comment ) {

$block_content = ( new WP_Block(
$block->parsed_block,
array(
'commentId' => $comment->comment_ID,
)
) )->render( array( 'dynamic' => false ) );

$children = $comment->get_children();

// If the comment has children, recurse to create the HTML for the nested
// comments.
if ( ! empty( $children ) ) {
$inner_content = block_core_comment_template_render_comments(
$children,
$block
);
$block_content .= sprintf( '<ol>%1$s</ol>', $inner_content );
}

$content .= '<li>' . $block_content . '</li>';
}

return $content;

}

/**
* Renders the `core/comment-template` block on the server.
*
Expand All @@ -27,26 +64,24 @@ function render_block_core_comment_template( $attributes, $content, $block ) {
$number = $block->context['queryPerPage'];

// Get an array of comments for the current post.
$comments = get_approved_comments( $post_id, array( 'number' => $number ) );
$comments = get_approved_comments(
$post_id,
array(
'number' => $number,
'hierarchical' => 'threaded',
)
);

if ( count( $comments ) === 0 ) {
return '';
}

$content = '';
foreach ( $comments as $comment ) {
$block_content = ( new WP_Block(
$block->parsed_block,
array(
'commentId' => $comment->comment_ID,
)
) )->render( array( 'dynamic' => false ) );
$content .= '<li>' . $block_content . '</li>';
}
$wrapper_attributes = get_block_wrapper_attributes();

return sprintf(
'<ul>%1$s</ul>',
$content
'<ol %1$s>%2$s</ol>',
$wrapper_attributes,
block_core_comment_template_render_comments( $comments, $block )
);
}

Expand Down
17 changes: 17 additions & 0 deletions packages/block-library/src/comment-template/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.wp-block-comment-template {
margin-bottom: 0;
max-width: 100%;
list-style: none;
padding: 0;

li {
clear: both;
}

ol {
margin-bottom: 0;
max-width: 100%;
list-style: none;
padding-left: 2rem;
}
}
michalczaplinski marked this conversation as resolved.
Show resolved Hide resolved
45 changes: 45 additions & 0 deletions packages/block-library/src/comment-template/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Internal dependencies
*/
import { convertToTree } from '../util';

describe( 'Convert to tree', () => {
it( 'Empty comments', () => {
const comments = convertToTree( [] );

expect( comments ).toEqual( [] );
} );

it( 'Handle comments', () => {
const comments = convertToTree( [
{ id: 1, parent: 0 },
{ id: 2, parent: 0 },
{ id: 3, parent: 2 },
{ id: 4, parent: 2 },
{ id: 5, parent: 4 },
{ id: 6, parent: 1 },
] );

expect( comments ).toEqual( [
{
commentId: 1,
children: [
{
commentId: 6,
children: [],
},
],
},
{
commentId: 2,
children: [
{ commentId: 3, children: [] },
{
commentId: 4,
children: [ { commentId: 5, children: [] } ],
},
],
},
] );
} );
} );
56 changes: 56 additions & 0 deletions packages/block-library/src/comment-template/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
*
* This function converts a flat list of comment objects with a `parent` property
* to a nested list of comment objects with a `children` property. The `children`
* property is itself a list of comment objects.
*
* @example
* ```
* const comments = [
* { id: 1, parent: 0 },
* { id: 2, parent: 1 },
* { id: 3, parent: 2 },
* { id: 4, parent: 1 },
* ];
* expect( convertToTree( comments ) ).toEqual( [
* {
* commentId: 1,
* children: [
* { commentId: 2, children: [ { commentId: 3, children: [] } ] },
* { commentId: 4, children: [] },
* ],
* },
* ] );
* ```
* @typedef {{id: number, parent: number}} Comment
* @param {Comment[]} data - List of comment objects.
*
* @return {Object[]} Nested list of comment objects with a `children` property.
*/
export const convertToTree = ( data ) => {
const table = {};
if ( ! data ) return [];

// First create a hash table of { [id]: { ...comment, children: [] }}
data.forEach( ( item ) => {
table[ item.id ] = { commentId: item.id, children: [] };
} );

const tree = [];

// Iterate over the original comments again
data.forEach( ( item ) => {
if ( item.parent ) {
// If the comment has a "parent", then find that parent in the table that
// we have created above and push the current comment to the array of its
// children.
table[ item.parent ].children.push( table[ item.id ] );
} else {
// Otherwise, if the comment has no parent (also works if parent is 0)
// that means that it's a top-level comment so we can find it in the table
// and push it to the final tree.
tree.push( table[ item.id ] );
}
} );
return tree;
};
Loading