@@ -11,8 +11,11 @@ import {
11
11
import {
12
12
store as blockEditorStore ,
13
13
__experimentalBlockVariationPicker as BlockVariationPicker ,
14
+ __experimentalUseBlockPreview as useBlockPreview ,
14
15
useBlockProps ,
15
16
InnerBlocks ,
17
+ BlockContextProvider ,
18
+ useInnerBlocksProps ,
16
19
} from '@wordpress/block-editor' ;
17
20
18
21
import {
@@ -22,7 +25,7 @@ import {
22
25
23
26
import { useDispatch , useSelect } from '@wordpress/data' ;
24
27
25
- import { useState } from '@wordpress/element' ;
28
+ import { useState , useMemo , memo } from '@wordpress/element' ;
26
29
27
30
import ServerSideRender from '@wordpress/server-side-render' ;
28
31
@@ -41,11 +44,132 @@ const LoadingResponsePlaceholder = () => (
41
44
</ BlockEditorPlaceholder >
42
45
) ;
43
46
47
+ // Component for rendering actual editable inner blocks for active feed item
48
+ function FeedItemInnerBlocks ( { feedItem, classList } ) {
49
+ const innerBlocksProps = useInnerBlocksProps (
50
+ {
51
+ className : `wp-block-feedzy-feed-item ${ classList } ` ,
52
+ 'data-feed-item-id' : feedItem . id ,
53
+ } ,
54
+ {
55
+ templateLock : false ,
56
+ __unstableDisableLayoutClassNames : true ,
57
+ }
58
+ ) ;
59
+ return < div { ...innerBlocksProps } /> ;
60
+ }
61
+
62
+ // Component for rendering interactive preview of feed items
63
+ function FeedItemBlockPreview ( {
64
+ blocks,
65
+ feedItem,
66
+ classList,
67
+ isHidden,
68
+ setActiveFeedItemId,
69
+ } ) {
70
+ const blockPreviewProps = useBlockPreview ( {
71
+ blocks,
72
+ props : {
73
+ className : `wp-block-feedzy-feed-item ${ classList } ` ,
74
+ 'data-feed-item-id' : feedItem . id ,
75
+ } ,
76
+ } ) ;
77
+
78
+ const handleOnClick = ( ) => {
79
+ setActiveFeedItemId ( feedItem . id ) ;
80
+ } ;
81
+
82
+ const handleOnKeyPress = ( event ) => {
83
+ if ( event . key === 'Enter' || event . key === ' ' ) {
84
+ event . preventDefault ( ) ;
85
+ setActiveFeedItemId ( feedItem . id ) ;
86
+ }
87
+ } ;
88
+
89
+ const style = {
90
+ display : isHidden ? 'none' : undefined ,
91
+ cursor : 'pointer' ,
92
+ outline : 'none' ,
93
+ } ;
94
+
95
+ return (
96
+ < div
97
+ { ...blockPreviewProps }
98
+ tabIndex = { 0 }
99
+ role = "button"
100
+ onClick = { handleOnClick }
101
+ onKeyPress = { handleOnKeyPress }
102
+ style = { style }
103
+ aria-label = { `Edit feed item: ${ feedItem . title } ` }
104
+ />
105
+ ) ;
106
+ }
107
+
108
+ const MemoizedFeedItemBlockPreview = memo ( FeedItemBlockPreview ) ;
109
+
110
+ // Hook to fetch and parse RSS feed data
111
+ function useFeedData ( feedUrl ) {
112
+ const [ feedData , setFeedData ] = useState ( null ) ;
113
+ const [ isLoading , setIsLoading ] = useState ( false ) ;
114
+ const [ error , setError ] = useState ( null ) ;
115
+
116
+ // In a real implementation, you'd use useSelect with REST API or apiFetch
117
+ // This is a simplified example
118
+ useMemo ( ( ) => {
119
+ if ( ! feedUrl ) {
120
+ return ;
121
+ }
122
+
123
+ setIsLoading ( true ) ;
124
+ setError ( null ) ;
125
+
126
+ // Simulate API call - replace with actual RSS parsing
127
+ const mockFeedItems = [
128
+ {
129
+ id : 1 ,
130
+ title : 'Sample Feed Item 1' ,
131
+ excerpt : 'This is the first feed item excerpt...' ,
132
+ url : 'https://example.com/post-1' ,
133
+ date : '2025-01-15' ,
134
+ author : 'John Doe' ,
135
+ image : 'https://example.com/image1.jpg' ,
136
+ } ,
137
+ {
138
+ id : 2 ,
139
+ title : 'Sample Feed Item 2' ,
140
+ excerpt : 'This is the second feed item excerpt...' ,
141
+ url : 'https://example.com/post-2' ,
142
+ date : '2025-01-14' ,
143
+ author : 'Jane Smith' ,
144
+ image : 'https://example.com/image2.jpg' ,
145
+ } ,
146
+ {
147
+ id : 3 ,
148
+ title : 'Sample Feed Item 3' ,
149
+ excerpt : 'This is the third feed item excerpt...' ,
150
+ url : 'https://example.com/post-3' ,
151
+ date : '2025-01-13' ,
152
+ author : 'Bob Johnson' ,
153
+ image : 'https://example.com/image3.jpg' ,
154
+ } ,
155
+ ] ;
156
+
157
+ // Simulate async operation
158
+ setTimeout ( ( ) => {
159
+ setFeedData ( mockFeedItems ) ;
160
+ setIsLoading ( false ) ;
161
+ } , 1000 ) ;
162
+ } , [ feedUrl ] ) ;
163
+
164
+ return { feedData, isLoading, error } ;
165
+ }
166
+
44
167
const Edit = ( { attributes, setAttributes, clientId } ) => {
45
168
const blockProps = useBlockProps ( ) ;
46
169
47
170
const [ isEditing , setIsEditing ] = useState ( ! attributes ?. feed ?. source ) ;
48
171
const [ isPreviewing , setIsPreviewing ] = useState ( false ) ;
172
+ const [ activeFeedItemId , setActiveFeedItemId ] = useState ( ) ;
49
173
50
174
const { clearSelectedBlock, replaceInnerBlocks } =
51
175
useDispatch ( blockEditorStore ) ;
@@ -66,12 +190,19 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
66
190
( select ) => {
67
191
const { getBlock } = select ( blockEditorStore ) ;
68
192
const block = getBlock ( clientId ) ;
69
-
70
193
return serialize ( block ?. innerBlocks ) ?? '' ;
71
194
} ,
72
195
[ clientId ]
73
196
) ;
74
197
198
+ const blocks = useSelect (
199
+ ( select ) => {
200
+ const { getBlocks } = select ( blockEditorStore ) ;
201
+ return getBlocks ( clientId ) ;
202
+ } ,
203
+ [ clientId ]
204
+ ) ;
205
+
75
206
const hasInnerBlocks = useSelect (
76
207
( select ) => 0 < select ( blockEditorStore ) . getBlocks ( clientId ) . length ,
77
208
[ clientId ]
@@ -87,6 +218,29 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
87
218
return getDefaultBlockVariation ( name , 'block' ) ;
88
219
} , [ ] ) ;
89
220
221
+ // Fetch RSS feed data for interactive preview
222
+ const {
223
+ feedData,
224
+ isLoading : isFeedLoading ,
225
+ error : feedError ,
226
+ } = useFeedData ( attributes ?. feed ?. source ) ;
227
+
228
+ // Create block contexts for each feed item
229
+ const feedItemContexts = useMemo (
230
+ ( ) =>
231
+ feedData ?. map ( ( feedItem ) => ( {
232
+ item_id : feedItem . id ,
233
+ item_title : feedItem . title ,
234
+ item_excerpt : feedItem . excerpt ,
235
+ item_url : feedItem . url ,
236
+ item_date : feedItem . date ,
237
+ item_author : feedItem . author ,
238
+ item_image : feedItem . image ,
239
+ classList : `feed-item-${ feedItem . id } ` ,
240
+ } ) ) ,
241
+ [ feedData ]
242
+ ) ;
243
+
90
244
const onSaveFeed = ( ) => {
91
245
setIsEditing ( false ) ;
92
246
} ;
@@ -109,6 +263,7 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
109
263
} ) ;
110
264
} ;
111
265
266
+ // Editing state - show feed configuration
112
267
if ( isEditing ) {
113
268
return (
114
269
< div { ...blockProps } >
@@ -121,6 +276,7 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
121
276
) ;
122
277
}
123
278
279
+ // Server-side preview state
124
280
if ( isPreviewing && innerBlocksContent ) {
125
281
return (
126
282
< >
@@ -149,6 +305,109 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
149
305
) ;
150
306
}
151
307
308
+ // Interactive preview state - show feed items with interactive editing
309
+ if ( hasInnerBlocks && feedItemContexts && ! isEditing && ! isPreviewing ) {
310
+ if ( isFeedLoading ) {
311
+ return (
312
+ < >
313
+ < Controls
314
+ attributes = { attributes }
315
+ isEditing = { isEditing }
316
+ isPreviewing = { isPreviewing }
317
+ setAttributes = { setAttributes }
318
+ onChangeLayout = { onChangeLayout }
319
+ onChangeQuery = { onChangeQuery }
320
+ setIsEditing = { setIsEditing }
321
+ setIsPreviewing = { setIsPreviewing }
322
+ />
323
+ < div { ...blockProps } >
324
+ < LoadingResponsePlaceholder />
325
+ </ div >
326
+ </ >
327
+ ) ;
328
+ }
329
+
330
+ if ( feedError ) {
331
+ return (
332
+ < >
333
+ < Controls
334
+ attributes = { attributes }
335
+ isEditing = { isEditing }
336
+ isPreviewing = { isPreviewing }
337
+ setAttributes = { setAttributes }
338
+ onChangeLayout = { onChangeLayout }
339
+ onChangeQuery = { onChangeQuery }
340
+ setIsEditing = { setIsEditing }
341
+ setIsPreviewing = { setIsPreviewing }
342
+ />
343
+ < div { ...blockProps } >
344
+ < p > Error loading feed: { feedError } </ p >
345
+ </ div >
346
+ </ >
347
+ ) ;
348
+ }
349
+
350
+ // Interactive preview with real feed data
351
+ return (
352
+ < >
353
+ < Controls
354
+ attributes = { attributes }
355
+ isEditing = { isEditing }
356
+ isPreviewing = { isPreviewing }
357
+ setAttributes = { setAttributes }
358
+ onChangeLayout = { onChangeLayout }
359
+ onChangeQuery = { onChangeQuery }
360
+ setIsEditing = { setIsEditing }
361
+ setIsPreviewing = { setIsPreviewing }
362
+ />
363
+
364
+ < div { ...blockProps } >
365
+ < div className = "wp-block-feedzy-feed-items" >
366
+ { feedItemContexts . map ( ( feedItemContext ) => (
367
+ < BlockContextProvider
368
+ key = { feedItemContext . feedItemId }
369
+ value = { {
370
+ 'feedzy-rss-feeds/feedItem' :
371
+ feedItemContext ,
372
+ } }
373
+ >
374
+ { /* Render editable inner blocks for active feed item */ }
375
+ { feedItemContext . item_id ===
376
+ ( activeFeedItemId ||
377
+ feedItemContexts [ 0 ] ?. item_id ) ? (
378
+ < FeedItemInnerBlocks
379
+ feedItem = { {
380
+ id : feedItemContext . item_id ,
381
+ title : feedItemContext . item_title ,
382
+ } }
383
+ classList = { feedItemContext . classList }
384
+ />
385
+ ) : null }
386
+
387
+ { /* Render interactive preview for inactive feed items */ }
388
+ < MemoizedFeedItemBlockPreview
389
+ blocks = { blocks }
390
+ feedItem = { {
391
+ id : feedItemContext . item_id ,
392
+ title : feedItemContext . item_title ,
393
+ } }
394
+ classList = { feedItemContext . classList }
395
+ setActiveFeedItemId = { setActiveFeedItemId }
396
+ isHidden = {
397
+ feedItemContext . item_id ===
398
+ ( activeFeedItemId ||
399
+ feedItemContexts [ 0 ] ?. item_id )
400
+ }
401
+ />
402
+ </ BlockContextProvider >
403
+ ) ) }
404
+ </ div >
405
+ </ div >
406
+ </ >
407
+ ) ;
408
+ }
409
+
410
+ // Default state - show block variation picker or inner blocks
152
411
return (
153
412
< >
154
413
< Controls
0 commit comments