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,
};