import { __, sprintf } from '@wordpress/i18n'; import { Notice, SelectControl, Button, Modal, Spinner } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { useMemo, useState } from '@wordpress/element'; import { store as editorStore } from '@wordpress/editor'; import apiFetch from '@wordpress/api-fetch'; import { getLoadingItemLabel } from '../../shared/i18nPhrases'; import { getEditorConfig } from '../config'; import usePostDataField from '../hooks/usePostDataField'; const INHERIT_VALUE = '__airygen_schema_inherit__'; const DEFAULT_OPTIONS = [ { value: 'Article', label: __( 'Article', 'airygen-seo' ) }, { value: 'NewsArticle', label: __( 'News Article', 'airygen-seo' ) }, { value: 'BlogPosting', label: __( 'Blog Posting', 'airygen-seo' ) }, { value: 'TechArticle', label: __( 'Tech Article', 'airygen-seo' ) }, ]; type EditorSelectors = { getCurrentPostType?: () => string | undefined; }; const SchemaPanel = () => { const config = getEditorConfig(); const schema = config.schemaMarkup; const postType = useSelect( ( select ): string | undefined => { const selectors = select( editorStore ) as EditorSelectors; return selectors.getCurrentPostType?.(); }, [], ); const [ metaValue, setMetaValue ] = usePostDataField( 'schemaArticleType' ); const [ mode, setMode ] = useState< 'preview' | 'custom' >( 'preview' ); const [ isPreviewOpen, setIsPreviewOpen ] = useState( false ); const [ previewLoading, setPreviewLoading ] = useState( false ); const [ previewError, setPreviewError ] = useState( '' ); const [ previewJson, setPreviewJson ] = useState( '' ); const [ copyMessage, setCopyMessage ] = useState( '' ); const [ copyStatus, setCopyStatus ] = useState< '' | 'success' | 'error' | 'warn' >( '' ); const [ previewWarning, setPreviewWarning ] = useState( '' ); const postId = useSelect( ( select ) => { const selectors = select( editorStore ) as EditorSelectors & { getCurrentPostId?: () => number | undefined; }; return selectors.getCurrentPostId?.() || 0; }, [], ); const typeOptions = useMemo( () => { const combined = new Set(); DEFAULT_OPTIONS.forEach( ( option ) => combined.add( option.value ) ); if ( schema?.post_type_defaults ) { Object.values( schema.post_type_defaults ).forEach( ( value ) => { if ( typeof value === 'string' && value !== '' ) { combined.add( value ); } } ); } if ( schema?.article_type ) { combined.add( schema.article_type ); } return Array.from( combined ).map( ( value ) => ( { value, label: value, } ) ); }, [ schema ] ); let copyStatusClass = 'airygen-json-preview__status'; if ( copyStatus === 'success' ) { copyStatusClass += ' airygen-json-preview__status--success'; } else if ( copyStatus === 'error' ) { copyStatusClass += ' airygen-json-preview__status--error'; } if ( ! schema ) { return ( { __( 'Schema Markup defaults are not set yet. Configure them in the Airygen dashboard.', 'airygen-seo', ) } ); } const articleDefault = ( postType === 'product' ? 'Product' : ( postType && schema.post_type_defaults?.[ postType ] ) ) || schema.article_type || 'Article'; const isProductPostType = postType === 'product'; const currentValue = metaValue && metaValue !== '' ? metaValue : INHERIT_VALUE; const selectValue = isProductPostType ? INHERIT_VALUE : currentValue; let previewType = currentValue; if ( isProductPostType ) { previewType = 'Product'; } else if ( currentValue === INHERIT_VALUE ) { previewType = articleDefault; } const fetchPreview = async () => { if ( ! postId ) { setPreviewError( __( 'Save the post first to preview JSON-LD.', 'airygen-seo' ) ); return; } setPreviewLoading( true ); setPreviewError( '' ); setPreviewWarning( '' ); try { const response = await apiFetch< { jsonld?: unknown } >( { path: `/airygen/v1/schema/preview?post=${ postId }`, } ); const json = JSON.stringify( response?.jsonld ?? response, null, 2 ); setPreviewJson( json ); const payload = ( response as { jsonld?: unknown } ).jsonld ?? response; const isEmpty = payload === null || ( Array.isArray( payload ) && payload.length === 0 ) || ( typeof payload === 'object' && payload !== null && Object.keys( payload ).length === 0 ); if ( isEmpty ) { setCopyStatus( 'warn' ); setPreviewWarning( __( 'JSON-LD is empty. Check Schema settings and enable the graphs you need.', 'airygen-seo' ), ); } } catch ( error ) { const message = ( error as { message?: string } )?.message ?? __( 'Unable to load JSON-LD preview.', 'airygen-seo' ); setPreviewError( message ); } finally { setPreviewLoading( false ); } }; const copyToClipboard = async () => { if ( ! previewJson ) { setCopyStatus( 'error' ); setCopyMessage( __( 'Nothing to copy.', 'airygen-seo' ) ); return; } try { if ( navigator?.clipboard?.writeText ) { await navigator.clipboard.writeText( previewJson ); } else { const textarea = document.createElement( 'textarea' ); textarea.value = previewJson; document.body.appendChild( textarea ); textarea.select(); document.execCommand( 'copy' ); document.body.removeChild( textarea ); } setCopyStatus( 'success' ); setCopyMessage( __( 'Copied to clipboard.', 'airygen-seo' ) ); } catch ( error ) { const message = ( error as { message?: string } )?.message ?? __( 'Failed to copy.', 'airygen-seo' ); setCopyStatus( 'error' ); setCopyMessage( message ); } }; return (
{ mode === 'preview' ? (

{ sprintf( /* translators: %s is the resolved schema type. */ __( 'Article schema: %s', 'airygen-seo' ), previewType, ) }

{ isPreviewOpen && ( { setIsPreviewOpen( false ); setCopyMessage( '' ); setCopyStatus( '' ); setPreviewWarning( '' ); } } className="airygen-json-preview-modal" > { previewLoading && (
{ getLoadingItemLabel( __( 'JSON-LD', 'airygen-seo' ) ) }
) } { ! previewLoading && previewError && ( { previewError } ) } { ! previewLoading && ! previewError && ( <> { previewWarning && ( { previewWarning } ) }
											{ previewJson || __( 'No JSON-LD available for this post.', 'airygen-seo' ) }
										
{ copyMessage }
) }
) }
) : ( { if ( next === INHERIT_VALUE ) { setMetaValue( '' ); return; } setMetaValue( next ); } } help={ __( 'Choose a different Article schema for this post or leave it on the inherited default.', 'airygen-seo', ) } /> ) }
); }; export default SchemaPanel;