Skip to content

Commit

Permalink
Comment Template: Optimize comment pagination with nested replies (Wo…
Browse files Browse the repository at this point in the history
…rdPress#38187)

* REST: Set children attr of comments as embeddable

* REST: Expose some discussion settings

* Add hook to generate comment query args

* Fix how the default comments page index is computed

* Move REST API code to experimental

* Use experimental settings in Comment Template

* Add `defaultPage` attribute to Comments Query Loop

* Keep other attributes when `inherit` changes

* Fix `comment_order` option usage

* Replace `asc` with `ASC` inside `$comment_args`

* Fix `array_reverse` and order in comment template

* Refactor `build_comment_query_vars` to use `inherit`

* Simplify `build_comment_query_vars` function

* Adapt Next and Numbers blocks to use `inherit`

* Run `format-php` and fix lint errors

* Regenerate comment query loop fixtures

* Update comment template PHP tests

Added a new test case for when `comments/inherit` is `true`.

* Write JSDocs and refactor Comment Template hooks

* Use spread syntax to clone `topLevelComments`

* Simplify comments.

* Adapt some code after rebase

* Fix comment page index for oldest comments

* Pass missing context to Comment Pagination blocks

* Fix order when `inherit` is true

* Add `paginationArrow` back to Pagination Next
  • Loading branch information
DAreRodz authored Feb 16, 2022
1 parent 9e4580d commit 805cc5a
Show file tree
Hide file tree
Showing 18 changed files with 349 additions and 106 deletions.
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ An advanced block that allows displaying post comments based on different query
- **Name:** core/comments-query-loop
- **Category:** theme
- **Supports:** align (full, wide), color (background, gradients, link, text), ~~html~~
- **Attributes:** inherit, order, perPage, tagName
- **Attributes:** defaultPage, inherit, order, perPage, tagName

## Cover

Expand Down
62 changes: 47 additions & 15 deletions lib/compat/experimental/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function build_comment_query_vars_from_block( $block ) {

$comment_args = array(
'orderby' => 'comment_date_gmt',
'order' => 'ASC',
'status' => 'approve',
'no_found_rows' => false,
'update_comment_meta_cache' => false, // We lazy-load comment meta for performance.
Expand All @@ -34,29 +35,36 @@ function build_comment_query_vars_from_block( $block ) {
$comment_args['hierarchical'] = false;
}

$per_page = ! empty( $block->context['comments/perPage'] ) ? (int) $block->context['comments/perPage'] : 0;
if ( 0 === $per_page && get_option( 'page_comments' ) ) {
$per_page = (int) get_query_var( 'comments_per_page' );
if ( 0 === $per_page ) {
$per_page = (int) get_option( 'comments_per_page' );
}
$inherit = ! empty( $block->context['comments/inherit'] );

$per_page = (int) get_option( 'comments_per_page' );
$query_per_page = (int) get_query_var( 'comments_per_page' );
if ( 0 !== $query_per_page ) {
$per_page = $query_per_page;
}
$block_per_page = ! empty( $block->context['comments/perPage'] ) ? (int) $block->context['comments/perPage'] : 0;
if ( ! $inherit && 0 !== $block_per_page ) {
$per_page = $block_per_page;
}

$default_page = get_option( 'default_comments_page' );
if ( ! $inherit && ! empty( $block->context['comments/defaultPage'] ) ) {
$default_page = $block->context['comments/defaultPage'];
}

if ( $per_page > 0 ) {
$comment_args['number'] = $per_page;
$page = (int) get_query_var( 'cpage' );

$page = (int) get_query_var( 'cpage' );
if ( $page ) {
$comment_args['offset'] = ( $page - 1 ) * $per_page;
} elseif ( 'oldest' === get_option( 'default_comments_page' ) ) {
$comment_args['offset'] = 0;
$comment_args['paged'] = $page;
} elseif ( 'oldest' === $default_page ) {
$comment_args['paged'] = 1;
} elseif ( 'newest' === $default_page ) {
$comment_args['paged'] = ( new WP_Comment_Query( $comment_args ) )->max_num_pages;
}
}

$comment_args['order'] = ! empty( $block->context['comments/order'] ) ? $block->context['comments/order'] : null;
if ( empty( $comment_args['order'] ) && get_option( 'comment_order' ) ) {
$comment_args['order'] = get_option( 'comment_order' );
}

return $comment_args;
}
}
Expand Down Expand Up @@ -128,3 +136,27 @@ function extend_block_editor_settings_with_discussion_settings( $settings ) {
}
}
add_filter( 'block_editor_settings_all', 'extend_block_editor_settings_with_discussion_settings' );

if ( ! function_exists( 'gutenberg_rest_comment_set_children_as_embeddable' ) ) {
/**
* Mark the `children` attr of comments as embeddable so they can be included in
* REST API responses without additional requests.
*
* @return void
*/
function gutenberg_rest_comment_set_children_as_embeddable() {
add_filter(
'rest_prepare_comment',
function ( $response ) {
$links = $response->get_links();
if ( isset( $links['children'] ) ) {
$href = $links['children'][0]['href'];
$response->remove_link( 'children', $href );
$response->add_link( 'children', $href, array( 'embeddable' => true ) );
}
return $response;
}
);
}
}
add_action( 'rest_api_init', 'gutenberg_rest_comment_set_children_as_embeddable' );
8 changes: 7 additions & 1 deletion packages/block-library/src/comment-template/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
"parent": [ "core/comments-query-loop" ],
"description": "Contains the block elements used to render a comment, like the title, date, author, avatar and more.",
"textdomain": "default",
"usesContext": [ "comments/perPage", "postId", "comments/order" ],
"usesContext": [
"comments/defaultPage",
"comments/inherit",
"comments/order",
"comments/perPage",
"postId"
],
"supports": {
"reusable": false,
"html": false,
Expand Down
86 changes: 42 additions & 44 deletions packages/block-library/src/comment-template/edit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { useState, useMemo, memo } from '@wordpress/element';
import { useState, memo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import {
Expand All @@ -17,7 +17,7 @@ import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { convertToTree } from './util';
import { useCommentQueryArgs, useCommentTree } from './hooks';

const TEMPLATE = [
[ 'core/comment-author-avatar' ],
Expand Down Expand Up @@ -106,10 +106,10 @@ function CommentTemplateInnerBlocks( {
{ comment === ( activeComment || firstComment ) ? children : null }

{ /* To avoid flicker when switching active block contexts, a preview
is ALWAYS rendered and the preview for the active block is hidden.
This ensures that when switching the active block, the component is not
is ALWAYS rendered and the preview for the active block is hidden.
This ensures that when switching the active block, the component is not
mounted again but rather it only toggles the `isHidden` prop.
The same strategy is used for preventing the flicker in the Post Template
block. */ }
<MemoizedCommentTemplatePreview
Expand Down Expand Up @@ -209,60 +209,58 @@ const CommentsList = ( {

export default function CommentTemplateEdit( {
clientId,
context: { postId, 'comments/perPage': perPage, 'comments/order': order },
context: {
postId,
'comments/perPage': perPage,
'comments/order': order,
'comments/defaultPage': defaultPage,
'comments/inherit': inherit,
},
} ) {
const blockProps = useBlockProps();

const [ activeComment, setActiveComment ] = useState();
const {
commentOrder,
commentsPerPage,
threadCommentsDepth,
threadComments,
} = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return getSettings().__experimentalDiscussionSettings;
const { commentOrder, threadCommentsDepth, threadComments } = useSelect(
( select ) => {
const { getSettings } = select( blockEditorStore );
return getSettings().__experimentalDiscussionSettings;
}
);

const commentQuery = useCommentQueryArgs( {
postId,
perPage,
defaultPage,
inherit,
} );

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

const commentQuery = {
post: postId,
status: 'approve',
context: 'embed',
order: order || commentOrder,
};

if ( order ) {
commentQuery.order = order;
}
return {
rawComments: getEntityRecords(
'root',
'comment',
commentQuery
),
// Request only top-level comments. Replies are embedded.
topLevelComments: commentQuery
? getEntityRecords( 'root', 'comment', commentQuery )
: null,
blocks: getBlocks( clientId ),
};
},
[ postId, clientId, order ]
[ clientId, commentQuery ]
);
// TODO: Replicate the logic used on the server.
perPage = perPage || commentsPerPage;
// We convert the flat list of comments to tree.
// Then, we show only a maximum of `perPage` number of comments.
// This is because passing `per_page` to `getEntityRecords()` does not
// take into account nested comments.

let comments = useMemo(
() => convertToTree( rawComments ).slice( 0, perPage ),
[ rawComments, perPage ]
order = inherit || ! order ? commentOrder : order;

// Generate a tree structure of comment IDs.
let commentTree = useCommentTree(
// Reverse the order of top comments if needed.
order === 'desc' && topLevelComments
? [ ...topLevelComments ].reverse()
: topLevelComments
);

if ( ! rawComments ) {
if ( ! topLevelComments ) {
return (
<p { ...blockProps }>
<Spinner />
Expand All @@ -271,20 +269,20 @@ export default function CommentTemplateEdit( {
}

if ( ! postId ) {
comments = getCommentsPlaceholder( {
commentTree = getCommentsPlaceholder( {
perPage,
threadComments,
threadCommentsDepth,
} );
}

if ( ! comments.length ) {
if ( ! commentTree.length ) {
return <p { ...blockProps }> { __( 'No results found.' ) }</p>;
}

return (
<CommentsList
comments={ comments }
comments={ commentTree }
blockProps={ blockProps }
blocks={ blocks }
activeComment={ activeComment }
Expand Down
Loading

0 comments on commit 805cc5a

Please sign in to comment.