From 07282da9671de8fb2601e9e641decb2655439ad8 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 01 Jan 2026 23:20:54 +0000
Subject: [PATCH] =FeedRoutes.php: fixed the extractTaxonomies method
---
src/feed/edit.js | 346 ++++++++++++++++++++++++++++++++++++++++-----------------
1 files changed, 244 insertions(+), 102 deletions(-)
diff --git a/src/feed/edit.js b/src/feed/edit.js
index d6bc6ff..2a5228d 100644
--- a/src/feed/edit.js
+++ b/src/feed/edit.js
@@ -1,114 +1,256 @@
-import { __ } from '@wordpress/i18n';
-import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
-import { PanelBody, TextControl, ToggleControl, SelectControl, CheckboxControl } from '@wordpress/components';
+/**
+ * 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 './editor.scss';
+import { __ } from '@wordpress/i18n';
export default function Edit({ attributes, setAttributes }) {
- const blockProps = useBlockProps();
- const [availableTypes, setAvailableTypes] = useState({});
+ const [feedTypes, setFeedTypes] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
- useEffect(() => {
- apiFetch({ path: '/jvb/v1/types' }).then(types => {
- setAvailableTypes(JSON.parse(types));
- });
- }, []);
+ const blockProps = useBlockProps({
+ className: 'feed-block-editor'
+ });
- return (
- <>
- <InspectorControls>
- <PanelBody title={__('Feed Settings', 'jvb')}>
- <TextControl
- label={__('Title', 'jvb')}
- value={attributes.title}
- onChange={(title) => setAttributes({ title })}
- />
+ /**
+ * 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);
- <ToggleControl
- label={__('Inherit Current Query', 'jvb')}
- help={__('Inherit filters from the current archive or taxonomy query', 'jvb')}
- checked={attributes.inheritQuery}
- onChange={(inheritQuery) => setAttributes({ inheritQuery })}
- />
+ // Store Last-Modified for future requests
+ // (apiFetch doesn't expose response headers easily,
+ // but the server will handle 304s)
- {!attributes.inheritQuery && (
- <div className="feed-content-types">
- <p className="components-base-control__label">
- {__('Content Types', 'jvb')}
- </p>
- <div className="checkbox-list">
- {Object.entries(availableTypes).map(([id, label]) => (
- <CheckboxControl
- key={id}
- label={label}
- checked={attributes.contentTypes.includes(id)}
- onChange={(isChecked) => {
- const newTypes = isChecked
- ? [...attributes.contentTypes, id]
- : attributes.contentTypes.filter(t => t !== id);
- setAttributes({contentTypes: newTypes});
- }}
- />
- ))}
- </div>
- <div className="select-all-wrapper">
- <CheckboxControl
- label={__('Select All', 'jvb')}
- checked={attributes.contentTypes.length === Object.keys(availableTypes).length}
- onChange={(isChecked) => {
- setAttributes({
- contentTypes: isChecked ? Object.keys(availableTypes) : []
- });
- }}
- />
- </div>
- </div>
- )}
+ // 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]);
- <SelectControl
- label={__('Items Per Page', 'jvb')}
- value={attributes.itemsPerPage}
- options={[
- {label: '12', value: 12},
- {label: '24', value: 24},
- {label: '36', value: 36}
- ]}
- onChange={(itemsPerPage) => setAttributes({itemsPerPage: parseInt(itemsPerPage)})}
- />
+ /**
+ * Toggle a content type in the selection
+ */
+ const toggleContentType = (slug, checked) => {
+ const currentTypes = attributes.contentTypes || [];
- <SelectControl
- label={__('Default Order', 'jvb')}
- value={attributes.defaultOrder}
- options={[
- {label: __('Newest First', 'jvb'), value: 'date_desc' },
- { label: __('Oldest First', 'jvb'), value: 'date_asc' },
- { label: __('Random', 'jvb'), value: 'random' }
- ]}
- onChange={(defaultOrder) => setAttributes({ defaultOrder })}
- />
- </PanelBody>
- </InspectorControls>
+ const newTypes = checked
+ ? [...currentTypes, slug]
+ : currentTypes.filter(t => t !== slug);
- <div {...blockProps}>
- <div className="feed-block-preview">
- <h2>{attributes.title}</h2>
- <div className="feed-filters">
- <div className="filter-preview">
- {attributes.contentTypes.map(type => (
- <span key={type} className="content-type-badge">
- {availableTypes[type]}
- </span>
- ))}
- </div>
- </div>
- <div className="feed-grid-placeholder">
- {[...Array(6)].map((_, i) => (
- <div key={i} className="grid-item-placeholder" />
- ))}
- </div>
- </div>
- </div>
- </>
- );
+ 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>
+ );
}
--
Gitblit v1.10.0