/** * WordPress dependencies */ import { Button, Notice, PanelRow } from '@wordpress/components'; import { dispatch, useDispatch, useSelect } from '@wordpress/data'; import { createInterpolateElement, useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { external, Icon } from '@wordpress/icons'; /** * Internal dependencies */ import { dispatchCoreEditor, GutenbergFunction } from '../../../@types/gutenberg/types'; import { Telemetry } from '../../../js/telemetry/telemetry'; import { getPersonaLabel, PersonaProp } from '../../common/components/persona-selector'; import { getToneLabel, ToneProp } from '../../common/components/tone-selector'; import { ContentHelperError } from '../../common/content-helper-error'; import { SidebarSettings, useSettings } from '../../common/settings'; import { PinnedTitleSuggestions } from './component-pinned'; import { TitleSuggestionsSettings } from './component-settings'; import { TitleSuggestions } from './component-suggestions'; import { TitleSuggestion } from './component-title-suggestion'; import { TitleSuggestionsProvider } from './provider'; import { TitleStore, TitleType } from './store'; import './title-suggestions.scss'; /** * Title Suggestions Panel. * * @since 3.12.0 * * @return {import('react').JSX.Element} The Title Suggestions Panel. */ export const TitleSuggestionsPanel = (): React.JSX.Element => { const { settings, setSettings } = useSettings(); const [ error, setError ] = useState(); const [ tone, setTone ] = useState( settings.TitleSuggestions.Tone ); const [ persona, setPersona ] = useState( settings.TitleSuggestions.Persona ); const { loading, titles, pinnedTitles, allTitles, acceptedTitle, originalTitle, } = useSelect( ( select ) => { const { isLoading, getTitles, getAcceptedTitle, getOriginalTitle, } = select( TitleStore ); // eslint-disable-next-line @typescript-eslint/no-shadow const allTitles = getTitles( TitleType.PostTitle ); return { acceptedTitle: getAcceptedTitle( TitleType.PostTitle ), loading: isLoading(), titles: allTitles.filter( ( title ) => ! title.isPinned ), pinnedTitles: allTitles.filter( ( title ) => title.isPinned ), allTitles, originalTitle: getOriginalTitle( TitleType.PostTitle ), }; }, [] ); const { setTitles, setLoading, setAcceptedTitle, setOriginalTitle, } = useDispatch( TitleStore ); const { createNotice } = useDispatch( 'core/notices' ); const onSettingChange = ( key: keyof SidebarSettings[ 'TitleSuggestions' ], value: string | boolean ) => { setSettings( { TitleSuggestions: { ...settings.TitleSuggestions, [ key ]: value, }, } ); }; const currentPostContent = useSelect( ( select ) => { const { getEditedPostContent } = select( 'core/editor' ) as GutenbergFunction; return getEditedPostContent(); }, [] ); const currentPostTitle = useSelect( ( select ) => { const { getEditedPostAttribute } = select( 'core/editor' ) as GutenbergFunction; return getEditedPostAttribute( 'title' ); }, [] ); const generateTitles = async ( titleType: TitleType, content: string, selectedTone: ToneProp, selectedPersona: PersonaProp, ): Promise => { await setLoading( true ); const provider = TitleSuggestionsProvider.getInstance(); try { const genTitles = await provider.generateTitles( content, 3, selectedTone, selectedPersona ); await setTitles( titleType, genTitles ); } catch ( err: any ) { // eslint-disable-line @typescript-eslint/no-explicit-any setError( err ); setTitles( titleType, [] ); } await setLoading( false ); }; const generateOnClickHandler = async () => { setError( undefined ); if ( false === loading ) { Telemetry.trackEvent( 'title_suggestions_generate_pressed', { request_more: titles.length > 0, total_titles: titles.length, total_pinned: titles.filter( ( title ) => title.isPinned ).length, tone, persona, } ); // Generate titles based on the current post content. await generateTitles( TitleType.PostTitle, currentPostContent, tone, persona ); } }; /** * Handles the accepted title changing, and applies the accepted title to * the post. * * @since 3.14.0 */ useEffect( () => { if ( ! acceptedTitle ) { return; } // Save the original title. setOriginalTitle( TitleType.PostTitle, currentPostTitle ); // Set the post title to the accepted title. dispatchCoreEditor.editPost( { title: acceptedTitle?.title } ); // Pin the accepted title on the list of generated titles. if ( acceptedTitle ) { dispatch( TitleStore ).pinTitle( TitleType.PostTitle, acceptedTitle ); Telemetry.trackEvent( 'title_suggestions_accept_pressed', { old_title: currentPostTitle, new_title: acceptedTitle.title, } ); } // Remove the accepted title. setAcceptedTitle( TitleType.PostTitle, undefined ); // Show snackbar notification. createNotice( 'success', __( 'Title suggestion applied.', 'wp-parsely' ), { type: 'snackbar', className: 'parsely-title-suggestion-applied', explicitDismiss: true, actions: [ { label: __( 'Undo', 'wp-parsely' ), onClick: () => { // Restore the original title. dispatchCoreEditor.editPost( { title: currentPostTitle } ); setOriginalTitle( TitleType.PostTitle, undefined ); }, }, ], } ); }, [ acceptedTitle ] ); // eslint-disable-line react-hooks/exhaustive-deps /** * Displays a snackbar notification when an error occurs. * * @since 3.14.0 */ useEffect( () => { if ( undefined === error ) { return; } error.createErrorSnackbar(); }, [ error ] ); // eslint-disable-line react-hooks/exhaustive-deps return (
{ allTitles.length > 0 ? ( { createInterpolateElement( // translators: %1$s is the tone, %2$s is the persona. __( "We've generated a few titles based on the content of your post, written as a .", 'wp-parsely' ), { tone: { getToneLabel( tone ) }, persona: { getPersonaLabel( persona ) }, } ) } ) : ( __( 'Use Parse.ly AI to generate a title for your post.', 'wp-parsely' ) ) }
{ error && ( setError( undefined ) } status="info" > { error.Message() } ) } { ( originalTitle !== undefined ) && ( ) } { 0 < allTitles.length && ( <> { pinnedTitles.length > 0 && ( ) } { titles.length > 0 && ( ) } ) } { onSettingChange( 'Persona', selectedPersona ); setPersona( selectedPersona ); } } onSettingChange={ onSettingChange } onToneChange={ ( selectedTone ) => { onSettingChange( 'Tone', selectedTone ); setTone( selectedTone ); } } persona={ settings.TitleSuggestions.Persona } tone={ settings.TitleSuggestions.Tone } />
); };