diff --git a/mailpoet/tests/acceptance/EmailEditor/CreateAndSendEmailUsingGutenbergCest.php b/mailpoet/tests/acceptance/EmailEditor/CreateAndSendEmailUsingGutenbergCest.php
index f121f36101..d1e658c393 100644
--- a/mailpoet/tests/acceptance/EmailEditor/CreateAndSendEmailUsingGutenbergCest.php
+++ b/mailpoet/tests/acceptance/EmailEditor/CreateAndSendEmailUsingGutenbergCest.php
@@ -52,7 +52,7 @@ class CreateAndSendEmailUsingGutenbergCest {
$i->click('[aria-label="Change campaign name"]');
$i->click('Email', '.editor-sidebar__panel-tabs');
$i->fillField('[data-automation-id="email_subject"]', 'My New Subject');
- $i->fillField('[data-automation-id="email_preview_text"]', 'My New Preview Text');
+ $i->fillField('[data-automation-id="email_preheader"]', 'My New Preview Text');
$i->wantTo('Send an email and verify it was delivered');
$i->click('Save Draft');
diff --git a/packages/js/email-editor/src/components/personalization-tags/personalization-tags-modal.tsx b/packages/js/email-editor/src/components/personalization-tags/personalization-tags-modal.tsx
index fdb226ce8b..614861e73b 100644
--- a/packages/js/email-editor/src/components/personalization-tags/personalization-tags-modal.tsx
+++ b/packages/js/email-editor/src/components/personalization-tags/personalization-tags-modal.tsx
@@ -1,7 +1,7 @@
import { Modal, SearchControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { PersonalizationTag, storeName } from '../../store';
-import { select } from '@wordpress/data';
+import { useSelect } from '@wordpress/data';
import { external, Icon } from '@wordpress/icons';
import './index.scss';
import { useState } from '@wordpress/element';
@@ -12,12 +12,15 @@ const PersonalizationTagsModal = ( { onInsert, isOpened, closeCallback } ) => {
const [ activeCategory, setActiveCategory ] = useState( null );
const [ searchQuery, setSearchQuery ] = useState( '' );
+ const list = useSelect(
+ ( select ) => select( storeName ).getPersonalizationTagsList(),
+ []
+ );
+
if ( ! isOpened ) {
return null;
}
- const list = select( storeName ).getPersonalizationTagsList();
-
const groupedTags: Record< string, PersonalizationTag[] > = list.reduce(
( groups, item ) => {
const { category, name, token } = item;
diff --git a/packages/js/email-editor/src/components/personalization-tags/rich-text-with-button.tsx b/packages/js/email-editor/src/components/personalization-tags/rich-text-with-button.tsx
new file mode 100644
index 0000000000..44e82aa9d6
--- /dev/null
+++ b/packages/js/email-editor/src/components/personalization-tags/rich-text-with-button.tsx
@@ -0,0 +1,130 @@
+import { BaseControl, Button } from '@wordpress/components';
+import { PersonalizationTagsModal } from './personalization-tags-modal';
+import { useRef, useState } from '@wordpress/element';
+import {
+ createTextToHtmlMap,
+ getCursorPosition,
+ isMatchingComment,
+} from './rich-text-utils';
+import { useDispatch } from '@wordpress/data';
+import { useEntityProp } from '@wordpress/core-data';
+import { storeName } from '../../store';
+import { RichText } from '@wordpress/block-editor';
+import { __ } from '@wordpress/i18n';
+
+export function RichTextWithButton( {
+ label,
+ labelSuffix,
+ help,
+ placeholder,
+ attributeName,
+} ) {
+ const [ mailpoetEmailData ] = useEntityProp(
+ 'postType',
+ 'mailpoet_email',
+ 'mailpoet_data'
+ );
+
+ const { updateEmailMailPoetProperty } = useDispatch( storeName );
+
+ const [ selectionRange, setSelectionRange ] = useState( null );
+ const [ isModalOpened, setIsModalOpened ] = useState( false );
+
+ const richTextRef = useRef( null );
+
+ const handleInsertPersonalizationTag = async ( value ) => {
+ // Retrieve the current value of the active RichText
+ const currentValue = mailpoetEmailData[ attributeName ] ?? '';
+
+ // Generate text-to-HTML mapping
+ const { mapping } = createTextToHtmlMap( currentValue );
+ // Ensure selection range is within bounds
+ const start = selectionRange?.start ?? currentValue.length;
+ const end = selectionRange?.end ?? currentValue.length;
+
+ // Default values for starting and ending indexes.
+ let htmlStart = start;
+ let htmlEnd = end;
+ // If indexes are not matching a comment, update them
+ if ( ! isMatchingComment( currentValue, start, end ) ) {
+ htmlStart = mapping[ start ] ?? currentValue.length;
+ htmlEnd = mapping[ end ] ?? currentValue.length;
+ }
+
+ // Insert the new tag
+ const updatedValue =
+ currentValue.slice( 0, htmlStart ) +
+ `` +
+ currentValue.slice( htmlEnd );
+
+ // Update the corresponding property
+ updateEmailMailPoetProperty( attributeName, updatedValue );
+
+ setSelectionRange( null );
+ };
+
+ const finalLabel = (
+ <>
+ { label }
+