-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
hooks.js
148 lines (137 loc) · 4.64 KB
/
hooks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* WordPress dependencies
*/
import { useState, useEffect, useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
// This is limited by WP REST API
const MAX_COMMENTS_PER_PAGE = 100;
/**
* Return an object with the query args needed to fetch the default page of
* comments.
*
* @param {Object} props Hook props.
* @param {number} props.postId ID of the post that contains the comments.
* discussion settings.
*
* @return {Object} Query args to retrieve the comments.
*/
export const useCommentQueryArgs = ( { postId } ) => {
// Initialize the query args that are not going to change.
const queryArgs = {
status: 'approve',
order: 'asc',
context: 'embed',
parent: 0,
_embed: 'children',
};
// Get the Discussion settings that may be needed to query the comments.
const {
pageComments,
commentsPerPage,
defaultCommentsPage: defaultPage,
} = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
const { __experimentalDiscussionSettings } = getSettings();
return __experimentalDiscussionSettings;
} );
// WP REST API doesn't allow fetching more than max items limit set per single page of data.
// As for the editor performance is more important than completeness of data and fetching only the
// max allowed for single page should be enough for the purpose of design and laying out the page.
// Fetching over the limit would return an error here but would work with backend query.
const perPage = pageComments
? Math.min( commentsPerPage, MAX_COMMENTS_PER_PAGE )
: MAX_COMMENTS_PER_PAGE;
// Get the number of the default page.
const page = useDefaultPageIndex( {
defaultPage,
postId,
perPage,
queryArgs,
} );
// Merge, memoize and return all query arguments, unless the default page's
// number is not known yet.
return useMemo( () => {
return page
? {
...queryArgs,
post: postId,
per_page: perPage,
page,
}
: null;
}, [ postId, perPage, page ] );
};
/**
* Return the index of the default page, depending on whether `defaultPage` is
* `newest` or `oldest`. In the first case, the only way to know the page's
* index is by using the `X-WP-TotalPages` header, which forces to make an
* additional request.
*
* @param {Object} props Hook props.
* @param {string} props.defaultPage Page shown by default (newest/oldest).
* @param {number} props.postId ID of the post that contains the comments.
* @param {number} props.perPage The number of comments included per page.
* @param {Object} props.queryArgs Other query args.
*
* @return {number} Index of the default comments page.
*/
const useDefaultPageIndex = ( { defaultPage, postId, perPage, queryArgs } ) => {
// Store the default page indices.
const [ defaultPages, setDefaultPages ] = useState( {} );
const key = `${ postId }_${ perPage }`;
const page = defaultPages[ key ] || 0;
useEffect( () => {
// Do nothing if the page is already known or not the newest page.
if ( page || defaultPage !== 'newest' ) {
return;
}
// We need to fetch comments to know the index. Use HEAD and limit
// fields just to ID, to make this call as light as possible.
apiFetch( {
path: addQueryArgs( '/wp/v2/comments', {
...queryArgs,
post: postId,
per_page: perPage,
_fields: 'id',
} ),
method: 'HEAD',
parse: false,
} ).then( ( res ) => {
const pages = parseInt( res.headers.get( 'X-WP-TotalPages' ) );
setDefaultPages( {
...defaultPages,
[ key ]: pages <= 1 ? 1 : pages, // If there are 0 pages, it means that there are no comments, but there is no 0th page.
} );
} );
}, [ defaultPage, postId, perPage, setDefaultPages ] );
// The oldest one is always the first one.
return defaultPage === 'newest' ? page : 1;
};
/**
* Generate a tree structure of comment IDs from a list of comment entities. The
* children of each comment are obtained from `_embedded`.
*
* @typedef {{ commentId: number, children: CommentNode }} CommentNode
*
* @param {Object[]} topLevelComments List of comment entities.
* @return {{ commentTree: CommentNode[]}} Tree of comment IDs.
*/
export const useCommentTree = ( topLevelComments ) => {
const commentTree = useMemo(
() =>
topLevelComments?.map( ( { id, _embedded } ) => {
const [ children ] = _embedded?.children || [ [] ];
return {
commentId: id,
children: children.map( ( child ) => ( {
commentId: child.id,
} ) ),
};
} ),
[ topLevelComments ]
);
return commentTree;
};