import { useState, useEffect } from '@wordpress/element'; import { Button, CheckboxControl, SelectControl, TextControl, Spinner, } from '@wordpress/components'; import apiFetch from '@wordpress/api-fetch'; const TYPE_OPTIONS = [ { label: 'Text', value: 'CHAR' }, { label: 'Number (integer)', value: 'NUMERIC' }, { label: 'Number (decimal)', value: 'DECIMAL' }, { label: 'Date', value: 'DATE' }, { label: 'Date & Time', value: 'DATETIME' }, { label: 'Time', value: 'TIME' }, { label: 'Binary', value: 'BINARY' }, { label: 'Unsigned integer', value: 'UNSIGNED' }, ]; const COMPARE_OPTIONS = [ { label: '= (equals)', value: '=' }, { label: '!= (not equals)', value: '!=' }, { label: '< (less than)', value: '<' }, { label: '> (greater than)', value: '>' }, { label: '<= (less or equal)', value: '<=' }, { label: '>= (greater or equal)', value: '>=' }, { label: 'LIKE (contains)', value: 'LIKE' }, { label: 'NOT LIKE', value: 'NOT LIKE' }, { label: 'IN (one of)', value: 'IN' }, { label: 'NOT IN', value: 'NOT IN' }, { label: 'BETWEEN', value: 'BETWEEN' }, { label: 'NOT BETWEEN', value: 'NOT BETWEEN' }, { label: 'EXISTS', value: 'EXISTS' }, { label: 'NOT EXISTS', value: 'NOT EXISTS' }, ]; export default function MetaKeySelector( { blockConfig, objectType, endpoint, onChange } ) { const [ availableKeys, setAvailableKeys ] = useState( [] ); const [ loading, setLoading ] = useState( false ); const [ patternInput, setPatternInput ] = useState( '' ); const [ patternError, setPatternError ] = useState( '' ); const [ search, setSearch ] = useState( '' ); const metaKeys = blockConfig.meta_keys || {}; const metaObjectType = objectType || 'post'; const postType = blockConfig.post_type || ''; useEffect( () => { setLoading( true ); let path; if ( endpoint ) { path = `wlconduit/v1/ext/${ endpoint }`; } else { path = `wlconduit/v1/meta-keys?object_type=${ metaObjectType }`; if ( metaObjectType === 'post' && postType ) { path += `&post_type=${ postType }`; } } apiFetch( { path } ) .then( ( keys ) => setAvailableKeys( keys || [] ) ) .catch( () => setAvailableKeys( [] ) ) .finally( () => setLoading( false ) ); }, [ endpoint, metaObjectType, postType ] ); function toggleKey( key ) { const updated = { ...metaKeys }; if ( updated[ key ] ) { delete updated[ key ]; } else { updated[ key ] = { as_field: true, as_filter: false, type: 'CHAR', compare: [ '=' ], }; } onChange( updated ); } function updateKeyConfig( key, partial ) { const updated = { ...metaKeys, [ key ]: { ...metaKeys[ key ], ...partial }, }; onChange( updated ); } function toggleCompare( key, op ) { const current = metaKeys[ key ]?.compare || [ '=' ]; const updated = current.includes( op ) ? current.filter( ( c ) => c !== op ) : [ ...current, op ]; updateKeyConfig( key, { compare: updated } ); } function addPatternKey() { const trimmed = patternInput.trim(); if ( ! trimmed ) { return; } if ( ! trimmed.includes( '%' ) ) { setPatternError( 'Pattern key must contain a % wildcard.' ); return; } if ( metaKeys[ trimmed ] ) { setPatternError( 'This pattern key already exists.' ); return; } setPatternError( '' ); onChange( { ...metaKeys, [ trimmed ]: { as_field: true }, } ); setPatternInput( '' ); } function removePatternKey( key ) { const updated = { ...metaKeys }; delete updated[ key ]; onChange( updated ); } const patternKeys = Object.keys( metaKeys ).filter( ( k ) => k.includes( '%' ) ); function handleRefresh() { setLoading( true ); let path; if ( endpoint ) { path = `wlconduit/v1/ext/${ endpoint }?refresh=1`; } else { path = `wlconduit/v1/meta-keys?object_type=${ metaObjectType }&refresh=1`; if ( metaObjectType === 'post' && postType ) { path += `&post_type=${ postType }`; } } apiFetch( { path } ) .then( ( keys ) => setAvailableKeys( keys || [] ) ) .catch( () => {} ) .finally( () => setLoading( false ) ); } if ( loading ) { return ; } return (

Select custom fields to include as data or expose as filters.

{ availableKeys.length > 0 && ( ) } { availableKeys.length === 0 ? (

No meta keys found.

) : ( availableKeys.filter( ( item ) => { if ( ! search ) { return true; } const key = item.key || item; return key.toLowerCase().includes( search.toLowerCase() ); } ).map( ( item ) => { const key = item.key || item; const isInternal = item.internal || false; const isSerialized = item.serialized || false; const isSelected = !! metaKeys[ key ]; const config = metaKeys[ key ] || {}; return (
toggleKey( key ) } /> { isSelected && (
updateKeyConfig( key, { as_field: val } ) } /> { isSerialized ? (

Serialized value — filtering not available.

) : ( updateKeyConfig( key, { as_filter: val } ) } /> ) } { ! isSerialized && config.as_filter && ( <> updateKeyConfig( key, { type: val } ) } />
{ COMPARE_OPTIONS.map( ( op ) => ( { if ( e.key === 'Enter' ) { e.preventDefault(); addPatternKey(); } } } />
{ patternError && (

{ patternError }

) } { patternKeys.map( ( key ) => (
{ key }
) ) }
); }