PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.1
WebberZone Top 10 — Popular Posts v4.5.1
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
top-10 / includes / frontend / blocks / src / post-count / post-count-block.js

post-count-block.js in WebberZone Top 10 — Popular Posts 4.5.1, at includes/frontend/blocks/src/post-count/post-count-block.js

239 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useEffect, useRef, useState } from '@wordpress/element';
2 import { __ } from '@wordpress/i18n';
3 import apiFetch from '@wordpress/api-fetch';
4 import { addQueryArgs } from '@wordpress/url';
5
6 const countRequests = new Map();
7 let defaultPostIdRequest;
8
9 const fetchCount = ( postId, counterType, blogId, fromDate, toDate ) => {
10 const query = { counter: counterType };
11
12 if ( blogId ) {
13 query.blog_id = blogId;
14 }
15
16 if ( fromDate ) {
17 query.from_date = fromDate;
18 }
19
20 if ( toDate ) {
21 query.to_date = toDate;
22 }
23
24 const path = addQueryArgs( `/top-10/v1/counter/${ postId }`, query );
25 if ( countRequests.has( path ) ) {
26 return countRequests.get( path );
27 }
28
29 const request = apiFetch( { path, method: 'GET' } ).catch( () => {
30 // Do not retain failed requests in the shared cache. A transient REST
31 // error must not poison the result for the editor session.
32 countRequests.delete( path );
33 return null;
34 } );
35 countRequests.set( path, request );
36
37 return request;
38 };
39
40 const fetchDefaultPostId = () => {
41 if ( ! defaultPostIdRequest ) {
42 defaultPostIdRequest = apiFetch( {
43 path: '/wp/v2/posts?per_page=1&_fields=id',
44 } )
45 .then( ( response ) =>
46 response.length > 0 ? response[ 0 ].id : null
47 )
48 .catch( () => {
49 // Allow a later block render to retry after a transient REST failure.
50 defaultPostIdRequest = undefined;
51 return null;
52 } );
53 }
54
55 return defaultPostIdRequest;
56 };
57
58 const PostCountBlock = ( { attributes, context, blockProps } ) => {
59 const [ counts, setCounts ] = useState( {
60 total: null,
61 daily: null,
62 overall: null,
63 } );
64 const [ postId, setPostId ] = useState( context?.postId || null );
65 const [ isResolvingPostId, setIsResolvingPostId ] = useState(
66 ! context?.postId
67 );
68 const requestId = useRef( 0 );
69 const {
70 counter: counterType = 'total',
71 blogId = 0,
72 fromDate,
73 toDate,
74 textBefore = '',
75 textAfter = '',
76 textAdvanced = '',
77 advancedMode = false,
78 svgCode = '',
79 svgIconSize = '1',
80 svgIconSizeUnit = 'em',
81 svgPaddingValues = [ 0, 0, 0, 0 ],
82 svgPaddingUnits = [ 'px', 'px', 'px', 'px' ],
83 svgIconLocation = 'before',
84 numberFormat = false,
85 } = attributes;
86
87 useEffect( () => {
88 if ( context?.postId ) {
89 setPostId( context.postId );
90 setIsResolvingPostId( false );
91 return;
92 }
93
94 let isCurrent = true;
95 setIsResolvingPostId( true );
96
97 fetchDefaultPostId().then( ( defaultPostId ) => {
98 if ( ! isCurrent ) {
99 return;
100 }
101
102 setPostId( defaultPostId );
103 setIsResolvingPostId( false );
104 } );
105
106 return () => {
107 isCurrent = false;
108 };
109 }, [ context?.postId ] );
110
111 useEffect( () => {
112 if ( ! postId ) {
113 return undefined;
114 }
115
116 const currentRequestId = ++requestId.current;
117 const countTypes =
118 advancedMode && textAdvanced
119 ? [
120 ...new Set( [
121 counterType,
122 ...[ 'total', 'daily', 'overall' ].filter(
123 ( type ) =>
124 textAdvanced.includes( `%${ type }count%` )
125 ),
126 ] ),
127 ]
128 : [ counterType ];
129
130 setCounts( { total: null, daily: null, overall: null } );
131
132 Promise.all(
133 countTypes.map( ( type ) =>
134 fetchCount( postId, type, blogId, fromDate, toDate ).then(
135 ( count ) => [ type, count ]
136 )
137 )
138 ).then( ( results ) => {
139 if ( currentRequestId !== requestId.current ) {
140 return;
141 }
142
143 setCounts( {
144 total: null,
145 daily: null,
146 overall: null,
147 ...Object.fromEntries( results ),
148 } );
149 } );
150
151 return () => {
152 requestId.current += 1;
153 };
154 }, [
155 postId,
156 counterType,
157 blogId,
158 fromDate,
159 toDate,
160 textAdvanced,
161 advancedMode,
162 ] );
163
164 if ( isResolvingPostId ) {
165 return <div { ...blockProps }>{ __( 'Loading…', 'top-10' ) }</div>;
166 }
167
168 if ( ! postId ) {
169 return (
170 <div { ...blockProps }>
171 { __( 'No valid post ID found.', 'top-10' ) }
172 </div>
173 );
174 }
175
176 if ( Object.values( counts ).every( ( count ) => count === null ) ) {
177 return <div { ...blockProps }>{ __( 'Loading…', 'top-10' ) }</div>;
178 }
179
180 const formatNumber = ( num ) => {
181 return numberFormat && num !== null && num !== undefined
182 ? num.toLocaleString()
183 : num;
184 };
185
186 const renderContent = () => {
187 if ( ! advancedMode || ! textAdvanced ) {
188 return (
189 <span className="tptn-post-count-text">
190 { textBefore }
191 { formatNumber( counts[ counterType ] ) }
192 { textAfter }
193 </span>
194 );
195 }
196
197 const replacedText = textAdvanced.replace(
198 /%(\w+)count%/g,
199 ( match, type ) => formatNumber( counts[ type ] ?? 'N/A' )
200 );
201 return <span className="tptn-post-count-text">{ replacedText }</span>;
202 };
203 const renderIcon = () => {
204 if ( ! svgCode ) {
205 return null;
206 }
207
208 const paddingStyle = `padding:${ svgPaddingValues
209 .map( ( val, index ) => `${ val }${ svgPaddingUnits[ index ] }` )
210 .join( ' ' ) };`;
211 const svgStyle = `width: ${ svgIconSize }${ svgIconSizeUnit }; height: ${ svgIconSize }${ svgIconSizeUnit }; ${ paddingStyle }`;
212
213 const svgWithStyle = svgCode.replace(
214 '<svg',
215 ` <svg style="${ svgStyle }"`
216 );
217
218 return (
219 <span
220 className="tptn-post-count-icon"
221 dangerouslySetInnerHTML={ { __html: svgWithStyle } }
222 />
223 );
224 };
225
226 const content = renderContent();
227 const icon = renderIcon();
228
229 return (
230 <div { ...blockProps }>
231 { svgIconLocation === 'before' && icon }
232 { content }
233 { svgIconLocation === 'after' && icon }
234 </div>
235 );
236 };
237
238 export default PostCountBlock;
239