Skip to content

Commit e1ad8b4

Browse files
feat: editable preview
1 parent d9bdd16 commit e1ad8b4

File tree

3 files changed

+438
-9
lines changed

3 files changed

+438
-9
lines changed

js/FeedzyLoop/edit.js

Lines changed: 261 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ import {
1111
import {
1212
store as blockEditorStore,
1313
__experimentalBlockVariationPicker as BlockVariationPicker,
14+
__experimentalUseBlockPreview as useBlockPreview,
1415
useBlockProps,
1516
InnerBlocks,
17+
BlockContextProvider,
18+
useInnerBlocksProps,
1619
} from '@wordpress/block-editor';
1720

1821
import {
@@ -22,7 +25,7 @@ import {
2225

2326
import { useDispatch, useSelect } from '@wordpress/data';
2427

25-
import { useState } from '@wordpress/element';
28+
import { useState, useMemo, memo } from '@wordpress/element';
2629

2730
import ServerSideRender from '@wordpress/server-side-render';
2831

@@ -41,11 +44,132 @@ const LoadingResponsePlaceholder = () => (
4144
</BlockEditorPlaceholder>
4245
);
4346

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+
44167
const Edit = ({ attributes, setAttributes, clientId }) => {
45168
const blockProps = useBlockProps();
46169

47170
const [isEditing, setIsEditing] = useState(!attributes?.feed?.source);
48171
const [isPreviewing, setIsPreviewing] = useState(false);
172+
const [activeFeedItemId, setActiveFeedItemId] = useState();
49173

50174
const { clearSelectedBlock, replaceInnerBlocks } =
51175
useDispatch(blockEditorStore);
@@ -66,12 +190,19 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
66190
(select) => {
67191
const { getBlock } = select(blockEditorStore);
68192
const block = getBlock(clientId);
69-
70193
return serialize(block?.innerBlocks) ?? '';
71194
},
72195
[clientId]
73196
);
74197

198+
const blocks = useSelect(
199+
(select) => {
200+
const { getBlocks } = select(blockEditorStore);
201+
return getBlocks(clientId);
202+
},
203+
[clientId]
204+
);
205+
75206
const hasInnerBlocks = useSelect(
76207
(select) => 0 < select(blockEditorStore).getBlocks(clientId).length,
77208
[clientId]
@@ -87,6 +218,29 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
87218
return getDefaultBlockVariation(name, 'block');
88219
}, []);
89220

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+
90244
const onSaveFeed = () => {
91245
setIsEditing(false);
92246
};
@@ -109,6 +263,7 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
109263
});
110264
};
111265

266+
// Editing state - show feed configuration
112267
if (isEditing) {
113268
return (
114269
<div {...blockProps}>
@@ -121,6 +276,7 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
121276
);
122277
}
123278

279+
// Server-side preview state
124280
if (isPreviewing && innerBlocksContent) {
125281
return (
126282
<>
@@ -149,6 +305,109 @@ const Edit = ({ attributes, setAttributes, clientId }) => {
149305
);
150306
}
151307

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
152411
return (
153412
<>
154413
<Controls

js/FeedzyTag/block.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,11 @@
182182
"__experimentalSettings": true
183183
},
184184
"usesContext": [
185-
"postId",
186-
"postType",
187-
"queryId"
185+
"feedzy-rss-feeds/feedItem",
186+
"postId"
188187
],
189188
"providesContext": {
190-
"feedzy/tag": "tag"
189+
"feedzy-rss-feeds/tag": "tag"
191190
},
192191
"editorScript": "file:./index.js",
193192
"editorStyle": "file:./index.css",

0 commit comments

Comments
 (0)