import { __, sprintf } from '@wordpress/i18n'; import { getNoGlobalRobotsDefaultsConfiguredYetLabel } from '../../shared/i18nPhrases'; import { Button, CheckboxControl, SelectControl, TextControl, RadioControl, } from '@wordpress/components'; import { useEffect, useState } from '@wordpress/element'; import { getEditorConfig } from '../config'; import usePostDataField from '../hooks/usePostDataField'; const ROBOTS_EXTRA_OPTIONS = [ { key: 'noarchive', label: __( 'Prevent caching', 'airygen-seo' ) }, { key: 'nosnippet', label: __( 'Hide snippets', 'airygen-seo' ) }, { key: 'noimageindex', label: __( 'Block image indexing', 'airygen-seo' ), }, { key: 'notranslate', label: __( 'Disable translation prompts', 'airygen-seo' ), }, ] as const; const KNOWN_ROBOTS_TOKENS = new Set( [ 'index', 'noindex', 'follow', 'nofollow', ...ROBOTS_EXTRA_OPTIONS.map( ( opt ) => opt.key ) ].map( ( token ) => token.toLowerCase(), ), ); const parseRobotsTokens = ( value: string ): string[] => value .split( ',' ) .map( ( token ) => token.trim() ) .filter( Boolean ); type IndexChoice = '' | 'index' | 'noindex'; type FollowChoice = '' | 'follow' | 'nofollow'; type MaxImagePreviewChoice = '' | 'none' | 'standard' | 'large'; type MaxVideoPreviewChoice = '' | '-1' | '0' | '30' | '60'; const formatRobotsValue = ( index: IndexChoice, follow: FollowChoice, extra: Set< string >, custom: string, maxImagePreview: MaxImagePreviewChoice, maxVideoPreview: MaxVideoPreviewChoice, ): string => { const tokens: string[] = []; if ( index ) { tokens.push( index ); } if ( follow ) { tokens.push( follow ); } tokens.push( ...Array.from( extra ) ); if ( maxImagePreview ) { tokens.push( `max-image-preview:${ maxImagePreview }` ); } if ( maxVideoPreview ) { tokens.push( `max-video-preview:${ maxVideoPreview }` ); } const customTokens = parseRobotsTokens( custom ).filter( ( token ) => ! KNOWN_ROBOTS_TOKENS.has( token.toLowerCase() ), ); return [ ...tokens, ...customTokens ].join( ', ' ); }; const RobotsPanel = () => { const defaultDirective = getEditorConfig().robots?.default_directive?.trim() ?? ''; const [ robotsValue, setRobotsValue, hasLoaded ] = usePostDataField( 'robots' ); const [ indexChoice, setIndexChoice ] = useState< IndexChoice >( '' ); const [ followChoice, setFollowChoice ] = useState< FollowChoice >( '' ); const [ extras, setExtras ] = useState< Set< string > >( new Set() ); const [ custom, setCustom ] = useState( '' ); const [ mode, setMode ] = useState< 'global' | 'custom' >( 'global' ); const [ sourceChoice, setSourceChoice ] = useState< 'default' | 'custom' >( 'default' ); const [ maxImagePreview, setMaxImagePreview ] = useState< MaxImagePreviewChoice >( '' ); const [ maxVideoPreview, setMaxVideoPreview ] = useState< MaxVideoPreviewChoice >( '' ); const previewValue = formatRobotsValue( indexChoice, followChoice, extras, custom, maxImagePreview, maxVideoPreview, ); useEffect( () => { setSourceChoice( robotsValue.trim() ? 'custom' : 'default' ); if ( ! hasLoaded ) { return; } const source = robotsValue.trim() || defaultDirective; const tokens = parseRobotsTokens( source ); let nextIndex: IndexChoice = ''; let nextFollow: FollowChoice = ''; const nextExtras = new Set< string >(); let nextImagePreview: MaxImagePreviewChoice = ''; let nextVideoPreview: MaxVideoPreviewChoice = ''; const customTokens: string[] = []; tokens.forEach( ( rawToken ) => { const token = rawToken.toLowerCase(); if ( token === 'index' || token === 'noindex' ) { nextIndex = token; return; } if ( token === 'follow' || token === 'nofollow' ) { nextFollow = token; return; } if ( token.startsWith( 'max-image-preview:' ) ) { const value = token.split( ':' )[ 1 ] ?? ''; if ( value === 'none' || value === 'standard' || value === 'large' ) { nextImagePreview = value as MaxImagePreviewChoice; return; } } if ( token.startsWith( 'max-video-preview:' ) ) { const value = token.split( ':' )[ 1 ] ?? ''; if ( value === '-1' || value === '0' || value === '30' || value === '60' ) { nextVideoPreview = value as MaxVideoPreviewChoice; return; } } if ( KNOWN_ROBOTS_TOKENS.has( token ) ) { nextExtras.add( token ); return; } customTokens.push( rawToken ); } ); setIndexChoice( nextIndex ); setFollowChoice( nextFollow ); setExtras( nextExtras ); setCustom( customTokens.join( ', ' ) ); setMaxImagePreview( nextImagePreview ); setMaxVideoPreview( nextVideoPreview ); }, [ robotsValue, defaultDirective, hasLoaded ] ); const updateMetaFromState = ( nextIndex: IndexChoice = indexChoice, nextFollow: FollowChoice = followChoice, nextExtras: Set< string > = extras, nextCustom: string = custom, nextImagePreview: MaxImagePreviewChoice = maxImagePreview, nextVideoPreview: MaxVideoPreviewChoice = maxVideoPreview, ) => { const value = formatRobotsValue( nextIndex, nextFollow, nextExtras, nextCustom, nextImagePreview, nextVideoPreview, ); setRobotsValue( value.trim() ); }; const toggleExtra = ( key: string, checked: boolean ) => { const next = new Set( extras ); if ( checked ) { next.add( key ); } else { next.delete( key ); } setExtras( next ); updateMetaFromState( indexChoice, followChoice, next, custom, maxImagePreview, maxVideoPreview, ); }; const clearOverride = () => { setIndexChoice( '' ); setFollowChoice( '' ); setExtras( new Set() ); setCustom( '' ); setMaxImagePreview( '' ); setMaxVideoPreview( '' ); setRobotsValue( '' ); setSourceChoice( 'default' ); }; const PreviewTabIcon = () => ( ); const CustomTabIcon = () => ( ); return (
{ 'global' === mode && ( <>
{ defaultDirective ? sprintf( /* translators: %s: Global robots directive applied by default. */ __( 'Current default: %s', 'airygen-seo' ), defaultDirective, ) : getNoGlobalRobotsDefaultsConfiguredYetLabel() }
{ const next = ( value as 'default' | 'custom' ) ?? 'default'; setSourceChoice( next ); if ( next === 'default' ) { clearOverride(); } } } /> ) } { 'custom' === mode && ( <> { const coercion = ( next as IndexChoice ) || ''; setIndexChoice( coercion ); updateMetaFromState( coercion, followChoice, extras, custom, maxImagePreview, maxVideoPreview, ); } } /> { const coercion = ( next as FollowChoice ) || ''; setFollowChoice( coercion ); updateMetaFromState( indexChoice, coercion, extras, custom, maxImagePreview, maxVideoPreview, ); } } /> { const value = ( next as MaxImagePreviewChoice ) || ''; setMaxImagePreview( value ); updateMetaFromState( indexChoice, followChoice, extras, custom, value, maxVideoPreview, ); } } /> { const value = ( next as MaxVideoPreviewChoice ) || ''; setMaxVideoPreview( value ); updateMetaFromState( indexChoice, followChoice, extras, custom, maxImagePreview, value, ); } } />
{ ROBOTS_EXTRA_OPTIONS.map( ( option ) => ( toggleExtra( option.key, checked ) } /> ) ) }
{ setCustom( next ); updateMetaFromState( indexChoice, followChoice, extras, next, maxImagePreview, maxVideoPreview, ); } } />
{ __( 'Preview', 'airygen-seo' ) }
{ previewValue || __( 'No directives yet', 'airygen-seo' ) }
) }
); }; export default RobotsPanel;