PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 2.2.0
GenerateBlocks v2.2.0
trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.3.0
generateblocks / src / blocks / looper / components / LoopInnerBlocksRenderer.jsx
generateblocks / src / blocks / looper / components Last commit date
BlockSettings.jsx 1 year ago LoopInnerBlocksRenderer.jsx 1 year ago
LoopInnerBlocksRenderer.jsx
279 lines
1 import {
2 BlockContextProvider,
3 useInnerBlocksProps,
4 __experimentalUseBlockPreview as useBlockPreview, // eslint-disable-line @wordpress/no-unsafe-wp-apis
5 store as blockEditorStore,
6 } from '@wordpress/block-editor';
7 import { useSelect } from '@wordpress/data';
8 import { Spinner } from '@wordpress/components';
9 import { memo, useEffect, useMemo, useState } from '@wordpress/element';
10 import { applyFilters } from '@wordpress/hooks';
11 import apiFetch from '@wordpress/api-fetch';
12
13 import { BlockAppender } from '@components/index';
14
15 const DISALLOWED_KEYS = [ 'post_password', 'password' ];
16
17 function BlockPreview( { blocks, isHidden } ) {
18 const style = {
19 display: isHidden ? 'none' : undefined,
20 };
21
22 const blockPreviewProps = useBlockPreview( {
23 blocks,
24 props: {
25 style,
26 className: 'gb-loop-preview',
27 },
28 } );
29
30 return isHidden ? <div { ...blockPreviewProps } /> : blockPreviewProps.children;
31 }
32
33 const MemoizedBlockPreview = memo( BlockPreview );
34
35 function useWpQuery( shouldRequest = true, { query, attributes, selectedBlock, context, queryType } ) {
36 const { currentPostId, currentPostAuthor } = useSelect( ( select ) => {
37 const { getCurrentPost } = select( 'core/editor' );
38 const currentPost = getCurrentPost ? getCurrentPost() : null;
39 return {
40 currentPostId: currentPost?.id,
41 currentPostAuthor: currentPost?.author,
42 };
43 } );
44
45 const {
46 isAdminUser = false,
47 } = gbPermissions ?? {};
48
49 const [ data, setData ] = useState( [] );
50 const [ isLoading, setIsLoading ] = useState( true );
51
52 useEffect( () => {
53 if ( ! shouldRequest ) {
54 return;
55 }
56
57 const args = {
58 ...query,
59 post_type: query.post_type || 'post',
60 };
61
62 /**
63 * Filter post_status based on user role.
64 *
65 * TODO - Expand this in the future to handle custom user roles and other advanced use cases.
66 */
67 if ( ! isAdminUser ) {
68 if ( Array.isArray( args?.post_status ) ) {
69 const disallowedStatuses = [ 'private', 'draft', 'trash' ];
70 args.post_status = args.post_status.filter( ( status ) => ! disallowedStatuses.includes( status ) );
71 } else {
72 args.post_status = 'publish';
73 }
74 }
75
76 const { queryLoopEditorPostsCap = 50 } = generateBlocksEditor;
77
78 if ( args.posts_per_page > queryLoopEditorPostsCap ) {
79 args.posts_per_page = queryLoopEditorPostsCap;
80 }
81
82 async function fetchPosts() {
83 setIsLoading( true );
84
85 try {
86 const response = await apiFetch( {
87 path: '/generateblocks/v1/get-wp-query',
88 method: 'POST',
89 data: {
90 args,
91 attributes,
92 context,
93 queryType,
94 block: selectedBlock,
95 postId: currentPostId,
96 authorId: currentPostAuthor,
97 },
98 } );
99
100 const { posts = [] } = response;
101 setData( posts );
102 } catch ( error ) {
103 console.error( 'Error fetching post record:', error ); // eslint-disable-line no-console
104 } finally {
105 setIsLoading( false );
106 }
107 }
108
109 fetchPosts();
110 }, [ query, currentPostId, currentPostAuthor ] );
111
112 const result = { data, isResolvingData: isLoading, hasResolvedData: data?.length > 0 };
113
114 return shouldRequest ? result : null;
115 }
116
117 export function LoopInnerBlocksRenderer( props ) {
118 const {
119 clientId,
120 attributes,
121 isSelected,
122 } = props;
123
124 const context = applyFilters( 'generateblocks.editor.preview.context', props.context, { props } );
125
126 const {
127 'generateblocks/query': query = {},
128 'generateblocks/queryType': queryType = 'WP_Query',
129 'generateblocks/queryId': queryId = null,
130 } = context;
131 let dataState = {
132 data: null,
133 isResolvingData: true,
134 hasResolvedData: false,
135 queryParams: [],
136 };
137 const { getSelectedBlock } = useSelect( blockEditorStore );
138 const selectedBlock = getSelectedBlock();
139 const wpQuery = useWpQuery( 'WP_Query' === queryType, { query, context, queryType, attributes, selectedBlock } );
140
141 const otherQuery = applyFilters( 'generateblocks.editor.looper.query', null, {
142 query,
143 queryType,
144 context,
145 props,
146 useWpQuery,
147 selectedBlock,
148 } );
149
150 if ( null !== wpQuery ) {
151 dataState = ( { ...wpQuery } );
152 } else if ( null !== otherQuery && null === wpQuery ) {
153 dataState = ( { ...otherQuery } );
154 } else {
155 dataState = {
156 data: [],
157 isResolvingData: false,
158 hasResolvedData: false,
159 };
160 }
161
162 const {
163 data,
164 isResolvingData,
165 hasResolvedData,
166 } = dataState;
167
168 const innerBlocks = useSelect( ( select ) => {
169 return select( 'core/block-editor' )?.getBlocks( clientId );
170 }, [] );
171 const [ previewId, setPreviewId ] = useState( {
172 [ queryId ]: null,
173 } );
174
175 const innerBlocksProps = useInnerBlocksProps(
176 {},
177 {
178 renderAppender: () => ! innerBlocks.length ? (
179 <BlockAppender
180 clientId={ clientId }
181 isSelected={ isSelected }
182 attributes={ attributes }
183 />
184 ) : false,
185 blockContext: {
186 ...context,
187 'generateblocks/loopPreviewId': previewId,
188 },
189 }
190 );
191
192 const loopItemsContext = useMemo( () => {
193 if ( hasResolvedData && data?.length ) {
194 let { posts_per_page: perPage = 10, offset = 0 } = query;
195
196 // Ensure the params are a valid integer for comparison.
197 perPage = parseInt( perPage, 10 );
198 offset = parseInt( offset, 10 );
199
200 if ( perPage < 0 ) {
201 perPage = data.length;
202 }
203
204 const items = 'WP_Query' !== queryType
205 ? data.slice(
206 offset > -1 ? offset : 0,
207 offset > -1 ? offset + perPage : perPage
208 ) : data;
209
210 const result = items.map( ( item, index ) => {
211 const { ID = null, id = null, post_type: postType = 'post' } = item;
212
213 // Remove any disallowed or hidden keys
214 for ( const itemKey in item ) {
215 if ( DISALLOWED_KEYS.includes( itemKey ) || itemKey.startsWith( '_' ) ) {
216 delete item[ itemKey ];
217 }
218 }
219
220 return {
221 postType,
222 postId: id ? id : ID,
223 'generateblocks/loopItem': item,
224 'generateblocks/loopIndex': index + 1, // Preview doesn't support pagination so this index is correct.
225 'generateblocks/loopPreviewId': previewId,
226 'generateblocks/hasLoopItems': true,
227 'generateblocks/setLoopPreviewId': setPreviewId,
228 };
229 } );
230
231 return result;
232 }
233
234 // If no data found, return limited context for the preview loop item.
235 return [ {
236 postId: applyFilters( 'generateblocks.editor.looper.fallback.postId', 0, props ),
237 postType: applyFilters( 'generateblocks.editor.looper.fallback.postType', 'post', props ),
238 'generateblocks/loopItem': {
239 ID: 0,
240 },
241 'generateblocks/loopIndex': 1,
242 'generateblocks/loopPreviewId': previewId,
243 'generateblocks/setLoopPreviewId': setPreviewId,
244 'generateblocks/hasLoopItems': false,
245 } ];
246 }, [ data, hasResolvedData, query?.posts_per_page, query?.offset, previewId, queryId ] );
247
248 if ( isResolvingData ) {
249 return ( <Spinner /> );
250 }
251
252 return loopItemsContext.map( ( loopItemContext, index ) => {
253 // Include index in case the postId is the same for all loop items.
254 const contextId = loopItemContext?.postId ?? loopItemContext?.[ 'generateblocks/loopIndex' ] ?? index;
255 const key = `${ contextId }-${ index }`;
256 let isActive = 0 === index;
257
258 if ( previewId[ queryId ] ) {
259 const previewIdInt = parseInt( previewId[ queryId ], 10 );
260
261 if ( previewIdInt ) {
262 isActive = contextId ? contextId === previewIdInt : false;
263 }
264 }
265 return (
266 <BlockContextProvider
267 key={ key }
268 value={ loopItemContext }
269 >
270 { isActive && innerBlocksProps.children }
271 <MemoizedBlockPreview
272 blocks={ innerBlocks }
273 isHidden={ isActive }
274 />
275 </BlockContextProvider>
276 );
277 } );
278 }
279