HEX
Server: nginx
System: Linux pool195-106-36.bur.atomicsites.net 6.12.57+deb12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1~bpo12+1 (2025-11-17) x86_64
User: (0)
PHP: 8.3.32
Disabled: pcntl_fork
Upload Files
File: //wordpress/plugins/jetpack/16.0/_inc/content-guidelines-ai/components/suggestion-actions.jsx
import { Button } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback, useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { STORE_NAME } from '../constants';
import { recordGuidelinesEvent } from '../lib/tracks';
import { AI_STORE_NAME } from '../store';
import DiffView from './diff-view';

export default function SuggestionActions( { slug } ) {
	const suggestion = useSelect( select => select( AI_STORE_NAME ).getSuggestion( slug ), [ slug ] );
	const sectionLoading = useSelect(
		select => select( AI_STORE_NAME ).isSectionLoading( slug ),
		[ slug ]
	);
	const { clearSuggestion } = useDispatch( AI_STORE_NAME );
	const { setGuideline } = useDispatch( STORE_NAME );

	const [ original, setOriginal ] = useState( '' );
	const [ textareaHeight, setTextareaHeight ] = useState( null );

	// Direct DOM class manipulation is necessary because this component is rendered in
	// a separate React root injected into Gutenberg's page — we can't control classes
	// on Gutenberg-owned elements through React props.
	useEffect( () => {
		const item = document.querySelector( `.guidelines__list-item[data-slug="${ slug }"]` );
		const form = item?.querySelector( 'form' );
		if ( ! form ) {
			return;
		}

		// Capture textarea draft and height before hiding it.
		if ( suggestion && ! form.classList.contains( 'has-jetpack-suggestion' ) ) {
			const textarea = form.querySelector( 'textarea' );
			if ( textarea ) {
				setOriginal( textarea.value || '' );
				if ( textarea.offsetHeight > 0 ) {
					setTextareaHeight( textarea.offsetHeight );
				} else {
					// Fallback when textarea is hidden (e.g. collapsed accordion).
					// Compute height from rows attribute to match the textarea.
					const rows = parseInt( textarea.getAttribute( 'rows' ), 10 ) || 4;
					// line-height: 20px, padding: 9px top + 9px bottom, border: 1px + 1px.
					setTextareaHeight( rows * 20 + 20 );
				}
			}
		}

		form.classList.toggle( 'has-jetpack-suggestion', !! suggestion );
		form.classList.toggle( 'is-jetpack-loading', sectionLoading && ! suggestion );
		return () => {
			form.classList.remove( 'has-jetpack-suggestion', 'is-jetpack-loading' );
		};
	}, [ slug, suggestion, sectionLoading ] );

	const handleAccept = useCallback( () => {
		recordGuidelinesEvent( 'accept', { type: 'section', slug } );
		setGuideline( slug, suggestion );
		clearSuggestion( slug );
	}, [ slug, suggestion, setGuideline, clearSuggestion ] );

	const handleDismiss = useCallback( () => {
		recordGuidelinesEvent( 'dismiss', { type: 'section', slug } );
		clearSuggestion( slug );
	}, [ slug, clearSuggestion ] );

	if ( ! suggestion ) {
		return null;
	}

	return (
		<div className="jetpack-content-guidelines-ai__suggestion">
			<DiffView
				original={ original }
				suggestion={ suggestion }
				onAccept={ handleAccept }
				height={ textareaHeight }
			/>
			<div className="jetpack-content-guidelines-ai__suggestion-actions">
				<Button variant="primary" onClick={ handleAccept }>
					{ __( 'Accept suggestion', 'jetpack' ) }
				</Button>
				<Button variant="tertiary" onClick={ handleDismiss }>
					{ __( 'Dismiss', 'jetpack' ) }
				</Button>
			</div>
		</div>
	);
}