diff --git a/packages/js/email-editor/src/blocks/core/rich-text.tsx b/packages/js/email-editor/src/blocks/core/rich-text.tsx index 7b656f3da6..8e11e0745d 100644 --- a/packages/js/email-editor/src/blocks/core/rich-text.tsx +++ b/packages/js/email-editor/src/blocks/core/rich-text.tsx @@ -7,9 +7,14 @@ import { createTextToHtmlMap, getCursorPosition, isMatchingComment, + replacePersonalizationTagsWithHTMLComments, } from '../../components/personalization-tags/rich-text-utils'; import { PersonalizationTagsModal } from '../../components/personalization-tags/personalization-tags-modal'; import { useCallback, useState } from '@wordpress/element'; +import { addFilter } from '@wordpress/hooks'; +import * as React from 'react'; +import { storeName } from '../../store'; +import { createHigherOrderComponent } from '@wordpress/compose'; /** * Disable Rich text formats we currently cannot support @@ -121,4 +126,82 @@ function extendRichTextFormats() { } ); } -export { disableCertainRichTextFormats, extendRichTextFormats }; +const personalizationTagsLiveContentUpdate = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { attributes, setAttributes, name } = props; + const { content } = attributes; + + // Fetch the personalization tags list + const list = useSelect( + ( select ) => select( storeName ).getPersonalizationTagsList(), + [] + ); + + // Memoized function to replace content tags + const updatedContent = useCallback( () => { + if ( ! content ) { + return ''; + } + return replacePersonalizationTagsWithHTMLComments( content, list ); + }, [ content, list ] ); + + // Handle content updates + const handleSetAttributes = useCallback( + ( newAttributes ) => { + if ( newAttributes.content !== undefined ) { + const replacedContent = + replacePersonalizationTagsWithHTMLComments( + newAttributes.content, + list + ); + setAttributes( { + ...newAttributes, + content: replacedContent, + } ); + } else { + setAttributes( newAttributes ); + } + }, + [ list, setAttributes ] + ); + + // Only process supported blocks + if ( + name === 'core/paragraph' || + name === 'core/heading' || + name === 'core/list-item' + ) { + return ( + + ); + } + + // Return default for unsupported blocks + return ; + }, + 'personalizationTagsLiveContentUpdate' +); + +/** + * Replace written personalization tags with HTML comments in real-time. + */ +function replaceWrittenPersonalizationTags() { + addFilter( + 'editor.BlockEdit', + 'mailpoet-email-editor/with-live-content-update', + personalizationTagsLiveContentUpdate + ); +} + +export { + disableCertainRichTextFormats, + extendRichTextFormats, + replaceWrittenPersonalizationTags, +}; diff --git a/packages/js/email-editor/src/blocks/index.ts b/packages/js/email-editor/src/blocks/index.ts index 5093bd243a..4c6d71c20f 100644 --- a/packages/js/email-editor/src/blocks/index.ts +++ b/packages/js/email-editor/src/blocks/index.ts @@ -11,6 +11,7 @@ import { disableImageFilter, hideExpandOnClick } from './core/image'; import { disableCertainRichTextFormats, extendRichTextFormats, + replaceWrittenPersonalizationTags, } from './core/rich-text'; import { enhanceButtonBlock } from './core/button'; import { enhanceButtonsBlock } from './core/buttons'; @@ -29,6 +30,7 @@ export function initBlocks() { enhanceColumnsBlock(); enhancePostContentBlock(); extendRichTextFormats(); + replaceWrittenPersonalizationTags(); alterSupportConfiguration(); registerCoreBlocks(); } diff --git a/packages/js/email-editor/src/components/personalization-tags/rich-text-utils.ts b/packages/js/email-editor/src/components/personalization-tags/rich-text-utils.ts index b3b4d660f3..5b72d26ee9 100644 --- a/packages/js/email-editor/src/components/personalization-tags/rich-text-utils.ts +++ b/packages/js/email-editor/src/components/personalization-tags/rich-text-utils.ts @@ -1,4 +1,5 @@ import * as React from '@wordpress/element'; +import { PersonalizationTag } from '../../store'; /** * Maps indices of characters in HTML representation of the value to corresponding characters of stored value in RichText content. The stored value doesn't contain tags. @@ -184,9 +185,35 @@ const isMatchingComment = ( return htmlCommentRegex.test( substring ); }; +/** + * Replace registered personalization tags with HTML comments in content. + * @param content string The content to replace the tags in. + * @param tags PersonalizationTag[] The tags to replace in the content. + */ +const replacePersonalizationTagsWithHTMLComments = ( + content: string, + tags: PersonalizationTag[] +) => { + tags.forEach( ( tag ) => { + if ( ! content.includes( tag.token ) ) { + // Skip if the token is not in the content + return; + } + + const escapedRegExp = tag.token.replace( + /[.*+?^${}()|[\]\\]/g, + '\\$&' + ); // Escape special characters + const regex = new RegExp( `(?)`, 'g' ); // Match token not inside HTML comments + content = content.replace( regex, `` ); + } ); + return content; +}; + export { isMatchingComment, getCursorPosition, createTextToHtmlMap, mapRichTextToValue, + replacePersonalizationTagsWithHTMLComments, };