/**
|
* Feed Block - Edit Component
|
* Fetches available feed types from /jvb/v1/feed/types
|
* Allows configuration of content types and inherit query setting
|
*/
|
|
import { useEffect, useState } from '@wordpress/element';
|
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
import {
|
PanelBody,
|
CheckboxControl,
|
ToggleControl,
|
Spinner,
|
Notice
|
} from '@wordpress/components';
|
import apiFetch from '@wordpress/api-fetch';
|
import { __ } from '@wordpress/i18n';
|
|
export default function Edit({ attributes, setAttributes }) {
|
const [feedTypes, setFeedTypes] = useState(null);
|
const [loading, setLoading] = useState(true);
|
const [error, setError] = useState(null);
|
|
const blockProps = useBlockProps({
|
className: 'feed-block-editor'
|
});
|
|
/**
|
* Fetch available feed types on component mount
|
*/
|
useEffect(() => {
|
apiFetch({
|
path: '/jvb/v1/feed/types',
|
headers: {
|
'If-Modified-Since': localStorage.getItem('feed_types_modified'),
|
}
|
})
|
.then(types => {
|
setFeedTypes(types);
|
setLoading(false);
|
|
// Store Last-Modified for future requests
|
// (apiFetch doesn't expose response headers easily,
|
// but the server will handle 304s)
|
|
// Initialize contentTypes if not set and not inheriting
|
if (!attributes.contentTypes && !attributes.inheritQuery) {
|
const firstType = Object.keys(types)[0];
|
if (firstType) {
|
setAttributes({ contentTypes: [firstType] });
|
}
|
}
|
})
|
.catch(err => {
|
console.error('Error loading feed types:', err);
|
setError(err.message);
|
setLoading(false);
|
});
|
}, [attributes.inheritQuery]);
|
|
/**
|
* Toggle a content type in the selection
|
*/
|
const toggleContentType = (slug, checked) => {
|
const currentTypes = attributes.contentTypes || [];
|
|
const newTypes = checked
|
? [...currentTypes, slug]
|
: currentTypes.filter(t => t !== slug);
|
|
setAttributes({ contentTypes: newTypes });
|
};
|
|
/**
|
* Get friendly label for content type
|
*/
|
const getTypeLabel = (slug, config) => {
|
return `${config.plural} (${config.type})`;
|
};
|
|
/**
|
* Group types by category for better UX
|
*/
|
const groupedTypes = feedTypes ? {
|
content: Object.entries(feedTypes)
|
.filter(([_, config]) => config.type === 'content'),
|
taxonomy: Object.entries(feedTypes)
|
.filter(([_, config]) => config.type === 'taxonomy')
|
} : { content: [], taxonomy: [] };
|
|
return (
|
<div {...blockProps}>
|
<InspectorControls>
|
<PanelBody
|
title={__('Feed Settings', 'jvb')}
|
initialOpen={true}
|
>
|
<ToggleControl
|
label={__('Inherit from Page Context', 'jvb')}
|
help={
|
attributes.inheritQuery
|
? __('Feed will adapt to the current page (profile, taxonomy, etc.)', 'jvb')
|
: __('Manually select content types to display', 'jvb')
|
}
|
checked={attributes.inheritQuery}
|
onChange={(value) => setAttributes({ inheritQuery: value })}
|
/>
|
|
{!attributes.inheritQuery && (
|
<>
|
{loading && (
|
<div style={{ textAlign: 'center', padding: '20px' }}>
|
<Spinner />
|
<p>{__('Loading feed types...', 'jvb')}</p>
|
</div>
|
)}
|
|
{error && (
|
<Notice status="error" isDismissible={false}>
|
{__('Error loading feed types: ', 'jvb')} {error}
|
</Notice>
|
)}
|
|
{!loading && !error && feedTypes && (
|
<>
|
{groupedTypes.content.length > 0 && (
|
<>
|
<h4>{__('Content Types', 'jvb')}</h4>
|
{groupedTypes.content.map(([slug, config]) => (
|
<CheckboxControl
|
key={slug}
|
label={getTypeLabel(slug, config)}
|
checked={
|
attributes.contentTypes?.includes(slug) || false
|
}
|
onChange={(checked) =>
|
toggleContentType(slug, checked)
|
}
|
help={
|
config.taxonomies?.length > 0
|
? `Filters: ${config.taxonomies.join(', ')}`
|
: null
|
}
|
/>
|
))}
|
</>
|
)}
|
|
{groupedTypes.taxonomy.length > 0 && (
|
<>
|
<h4 style={{ marginTop: '20px' }}>
|
{__('Content Taxonomies', 'jvb')}
|
</h4>
|
<p style={{ fontSize: '12px', color: '#757575' }}>
|
{__('These are collections that group other content', 'jvb')}
|
</p>
|
{groupedTypes.taxonomy.map(([slug, config]) => (
|
<CheckboxControl
|
key={slug}
|
label={getTypeLabel(slug, config)}
|
checked={
|
attributes.contentTypes?.includes(slug) || false
|
}
|
onChange={(checked) =>
|
toggleContentType(slug, checked)
|
}
|
help={
|
config.for_content?.length > 0
|
? `Contains: ${config.for_content.join(', ')}`
|
: null
|
}
|
/>
|
))}
|
</>
|
)}
|
|
{!attributes.contentTypes?.length && (
|
<Notice status="warning" isDismissible={false}>
|
{__('Please select at least one content type', 'jvb')}
|
</Notice>
|
)}
|
</>
|
)}
|
</>
|
)}
|
</PanelBody>
|
|
<PanelBody
|
title={__('Display Settings', 'jvb')}
|
initialOpen={false}
|
>
|
<ToggleControl
|
label={__('Show Gallery View', 'jvb')}
|
help={__('Enable lightbox for images', 'jvb')}
|
checked={attributes.enableGallery || false}
|
onChange={(value) =>
|
setAttributes({ enableGallery: value })
|
}
|
/>
|
</PanelBody>
|
</InspectorControls>
|
|
<div className="feed-block-placeholder">
|
<div className="feed-block-icon">
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
|
<rect x="3" y="3" width="7" height="7" fill="currentColor" opacity="0.3" />
|
<rect x="13" y="3" width="7" height="7" fill="currentColor" opacity="0.3" />
|
<rect x="3" y="13" width="7" height="7" fill="currentColor" opacity="0.3" />
|
<rect x="13" y="13" width="7" height="7" fill="currentColor" opacity="0.3" />
|
</svg>
|
</div>
|
|
<h3>{__('Feed Block', 'jvb')}</h3>
|
|
{attributes.inheritQuery ? (
|
<p className="feed-block-description">
|
{__('📍 Inheriting from page context', 'jvb')}
|
</p>
|
) : (
|
<div className="feed-block-description">
|
{attributes.contentTypes?.length > 0 ? (
|
<>
|
<p><strong>{__('Showing:', 'jvb')}</strong></p>
|
<ul style={{
|
listStyle: 'none',
|
padding: '0',
|
margin: '8px 0'
|
}}>
|
{attributes.contentTypes.map(type => {
|
const config = feedTypes?.[type];
|
return (
|
<li key={type} style={{
|
padding: '4px 0',
|
color: '#2271b1'
|
}}>
|
✓ {config?.plural || type}
|
</li>
|
);
|
})}
|
</ul>
|
</>
|
) : (
|
<p style={{ color: '#d63638' }}>
|
{__('⚠️ No content types selected', 'jvb')}
|
</p>
|
)}
|
</div>
|
)}
|
|
<p className="feed-block-note">
|
{__('Feed will be displayed on the frontend', 'jvb')}
|
</p>
|
</div>
|
</div>
|
);
|
}
|