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