/** * External dependencies */ import { __ } from '@wordpress/i18n'; import { useCallback, useMemo } from '@wordpress/element'; import { dispatch, useSelect, subscribe } from '@wordpress/data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { store as coreDataStore } from '@wordpress/core-data'; /** * Internal dependencies */ import { storeName as emailEditorStore } from '../store'; import { useShallowEqual } from './use-shallow-equal'; import { useValidationNotices } from './use-validation-notices'; export type ContentValidationData = { isInvalid: boolean; validateContent: () => boolean; }; export const useContentValidation = (): ContentValidationData => { const { contentBlockId, hasFooter } = useSelect( ( select ) => { const allBlocks = select( blockEditorStore ).getBlocks(); const noBodyBlocks = allBlocks.filter( ( block ) => block.name !== 'mailpoet/powered-by-mailpoet' && block.name !== 'core/post-content' ); // @ts-expect-error getBlocksByName is not defined in types const blocks = select( blockEditorStore ).getBlocksByName( 'core/post-content' ) as string[] | undefined; return { contentBlockId: blocks?.[ 0 ], hasFooter: noBodyBlocks.length > 0, }; } ); const { addValidationNotice, hasValidationNotice, removeValidationNotice } = useValidationNotices(); const { editedContent, editedTemplateContent, postTemplateId } = useSelect( ( mapSelect ) => ( { editedContent: mapSelect( emailEditorStore ).getEditedEmailContent(), editedTemplateContent: mapSelect( emailEditorStore ).getCurrentTemplateContent(), postTemplateId: mapSelect( emailEditorStore ).getCurrentTemplate()?.id, } ) ); const content = useShallowEqual( editedContent ); const templateContent = useShallowEqual( editedTemplateContent ); const contentLink = `${ __( 'Unsubscribe', 'mailpoet' ) } | ${ __( 'Manage subscription', 'mailpoet' ) }`; const rules = useMemo( () => { const linksParagraphBlock = createBlock( 'core/paragraph', { align: 'center', fontSize: 'small', content: contentLink, } ); return [ { id: 'missing-unsubscribe-link', test: ( emailContent ) => ! emailContent.includes( '[mailpoet/subscription-unsubscribe-url]' ), message: __( 'All emails must include an "Unsubscribe" link.', 'mailpoet' ), actions: [ { label: __( 'Insert link', 'mailpoet' ), onClick: () => { if ( ! hasFooter ) { void dispatch( blockEditorStore ).insertBlock( linksParagraphBlock, undefined, contentBlockId ); } else { void dispatch( coreDataStore ).editEntityRecord( 'postType', 'wp_template', postTemplateId, { content: ` ${ editedTemplateContent } ${ contentLink } `, } ); } }, }, ], }, ]; }, [ contentBlockId, hasFooter, contentLink, postTemplateId, editedTemplateContent, ] ); const validateContent = useCallback( (): boolean => { let isValid = true; rules.forEach( ( { id, test, message, actions } ) => { // Check both content and template content for the rule. if ( test( content + templateContent ) ) { addValidationNotice( id, message, actions ); isValid = false; } else if ( hasValidationNotice( id ) ) { removeValidationNotice( id ); } } ); return isValid; }, [ content, templateContent, addValidationNotice, removeValidationNotice, hasValidationNotice, rules, ] ); // Subscribe to updates so notices can be dismissed once resolved. subscribe( () => { if ( ! hasValidationNotice() ) { return; } validateContent(); }, emailEditorStore ); return { isInvalid: hasValidationNotice(), validateContent, }; };