Migrate email editor content validation rules to the MP plugin
MAILPOET-6432
This commit is contained in:
committed by
Rostislav Wolný
parent
e2286167d8
commit
5190ac1ff2
@@ -6,11 +6,18 @@ import { addFilter, addAction } from '@wordpress/hooks';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { withSatismeterSurvey } from './satismeter-survey';
|
||||
import './index.scss';
|
||||
import { ValidateEmailContent } from './validate-email-content';
|
||||
|
||||
addFilter('mailpoet_email_editor_wrap_editor_component', 'mailpoet', (editor) =>
|
||||
withSatismeterSurvey(editor),
|
||||
);
|
||||
|
||||
addFilter(
|
||||
'mailpoet_email_editor_content_validation_rules',
|
||||
'mailpoet',
|
||||
(validationRules: []) => [...validationRules, ...ValidateEmailContent()],
|
||||
);
|
||||
|
||||
const EVENTS_TO_TRACK = [
|
||||
'email_editor_events_editor_layout_loaded', // email editor was opened
|
||||
'email_editor_events_template_select_modal_template_selected', // a template was selected from the template-select modal
|
||||
|
@@ -0,0 +1,93 @@
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { store as blockEditorStore } from '@wordpress/block-editor';
|
||||
import { store as coreDataStore } from '@wordpress/core-data';
|
||||
|
||||
const emailEditorStore = 'email-editor/editor';
|
||||
|
||||
const contentLink = `<a data-link-href='[mailpoet/subscription-unsubscribe-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${__(
|
||||
'Unsubscribe',
|
||||
'mailpoet',
|
||||
)}</a> | <a data-link-href='[mailpoet/subscription-manage-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${__(
|
||||
'Manage subscription',
|
||||
'mailpoet',
|
||||
)}</a>`;
|
||||
|
||||
export function ValidateEmailContent() {
|
||||
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,
|
||||
};
|
||||
});
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
const { editedTemplateContent, postTemplateId } = useSelect((mapSelect) => ({
|
||||
editedTemplateContent:
|
||||
// @ts-ignore
|
||||
mapSelect(emailEditorStore).getCurrentTemplateContent() as string,
|
||||
postTemplateId:
|
||||
// @ts-ignore
|
||||
mapSelect(emailEditorStore).getCurrentTemplate()?.id as string,
|
||||
}));
|
||||
|
||||
return useMemo(() => {
|
||||
const linksParagraphBlock = createBlock('core/paragraph', {
|
||||
align: 'center',
|
||||
fontSize: 'small',
|
||||
content: contentLink,
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'missing-unsubscribe-link',
|
||||
test: (emailContent: string) =>
|
||||
!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}
|
||||
<!-- wp:paragraph {"align":"center","fontSize":"small"} -->
|
||||
${contentLink}
|
||||
<!-- /wp:paragraph -->
|
||||
`,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}, [contentBlockId, postTemplateId, hasFooter, editedTemplateContent]);
|
||||
}
|
@@ -1,123 +1,50 @@
|
||||
/**
|
||||
* 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';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
import { useSelect, subscribe } from '@wordpress/data';
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { storeName as emailEditorStore } from '../store';
|
||||
import {
|
||||
EmailContentValidationRule,
|
||||
storeName as emailEditorStore,
|
||||
} from '../store';
|
||||
import { useShallowEqual } from './use-shallow-equal';
|
||||
import { useValidationNotices } from './use-validation-notices';
|
||||
|
||||
// Shared reference to an empty array for cases where it is important to avoid
|
||||
// returning a new array reference on every invocation
|
||||
const EMPTY_ARRAY = [];
|
||||
|
||||
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(
|
||||
|
||||
const { editedContent, editedTemplateContent } = useSelect(
|
||||
( mapSelect ) => ( {
|
||||
editedContent:
|
||||
mapSelect( emailEditorStore ).getEditedEmailContent(),
|
||||
editedTemplateContent:
|
||||
mapSelect( emailEditorStore ).getCurrentTemplateContent(),
|
||||
postTemplateId:
|
||||
mapSelect( emailEditorStore ).getCurrentTemplate()?.id,
|
||||
} )
|
||||
);
|
||||
|
||||
const rules: EmailContentValidationRule[] = applyFilters(
|
||||
'mailpoet_email_editor_content_validation_rules',
|
||||
EMPTY_ARRAY
|
||||
) as EmailContentValidationRule[];
|
||||
|
||||
const content = useShallowEqual( editedContent );
|
||||
const templateContent = useShallowEqual( editedTemplateContent );
|
||||
|
||||
const contentLink = `<a data-link-href='[mailpoet/subscription-unsubscribe-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${ __(
|
||||
'Unsubscribe',
|
||||
'mailpoet'
|
||||
) }</a> | <a data-link-href='[mailpoet/subscription-manage-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${ __(
|
||||
'Manage subscription',
|
||||
'mailpoet'
|
||||
) }</a>`;
|
||||
|
||||
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 }
|
||||
<!-- wp:paragraph {"align":"center","fontSize":"small"} -->
|
||||
${ contentLink }
|
||||
<!-- /wp:paragraph -->
|
||||
`,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}, [
|
||||
contentBlockId,
|
||||
hasFooter,
|
||||
contentLink,
|
||||
postTemplateId,
|
||||
editedTemplateContent,
|
||||
] );
|
||||
|
||||
const validateContent = useCallback( (): boolean => {
|
||||
let isValid = true;
|
||||
rules.forEach( ( { id, test, message, actions } ) => {
|
||||
|
@@ -269,3 +269,15 @@ export type EmailEditorPostType = Omit< Post, 'type' > & {
|
||||
type: string;
|
||||
mailpoet_data?: MailPoetEmailPostContentExtended;
|
||||
};
|
||||
|
||||
export type EmailContentValidationAction = {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export type EmailContentValidationRule = {
|
||||
id: string;
|
||||
test: ( emailContent: string ) => boolean;
|
||||
message: string;
|
||||
actions: EmailContentValidationAction[];
|
||||
};
|
||||
|
Reference in New Issue
Block a user