Create new richText component with button
[MAILPOET-6354]
This commit is contained in:
@ -52,7 +52,7 @@ class CreateAndSendEmailUsingGutenbergCest {
|
|||||||
$i->click('[aria-label="Change campaign name"]');
|
$i->click('[aria-label="Change campaign name"]');
|
||||||
$i->click('Email', '.editor-sidebar__panel-tabs');
|
$i->click('Email', '.editor-sidebar__panel-tabs');
|
||||||
$i->fillField('[data-automation-id="email_subject"]', 'My New Subject');
|
$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->wantTo('Send an email and verify it was delivered');
|
||||||
$i->click('Save Draft');
|
$i->click('Save Draft');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Modal, SearchControl } from '@wordpress/components';
|
import { Modal, SearchControl } from '@wordpress/components';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { PersonalizationTag, storeName } from '../../store';
|
import { PersonalizationTag, storeName } from '../../store';
|
||||||
import { select } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { external, Icon } from '@wordpress/icons';
|
import { external, Icon } from '@wordpress/icons';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
import { useState } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
@ -12,12 +12,15 @@ const PersonalizationTagsModal = ( { onInsert, isOpened, closeCallback } ) => {
|
|||||||
const [ activeCategory, setActiveCategory ] = useState( null );
|
const [ activeCategory, setActiveCategory ] = useState( null );
|
||||||
const [ searchQuery, setSearchQuery ] = useState( '' );
|
const [ searchQuery, setSearchQuery ] = useState( '' );
|
||||||
|
|
||||||
|
const list = useSelect(
|
||||||
|
( select ) => select( storeName ).getPersonalizationTagsList(),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
if ( ! isOpened ) {
|
if ( ! isOpened ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = select( storeName ).getPersonalizationTagsList();
|
|
||||||
|
|
||||||
const groupedTags: Record< string, PersonalizationTag[] > = list.reduce(
|
const groupedTags: Record< string, PersonalizationTag[] > = list.reduce(
|
||||||
( groups, item ) => {
|
( groups, item ) => {
|
||||||
const { category, name, token } = item;
|
const { category, name, token } = item;
|
||||||
|
@ -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 ) +
|
||||||
|
`<!--${ value }-->` +
|
||||||
|
currentValue.slice( htmlEnd );
|
||||||
|
|
||||||
|
// Update the corresponding property
|
||||||
|
updateEmailMailPoetProperty( attributeName, updatedValue );
|
||||||
|
|
||||||
|
setSelectionRange( null );
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalLabel = (
|
||||||
|
<>
|
||||||
|
<span>{ label }</span>
|
||||||
|
<Button
|
||||||
|
className="mailpoet-settings-panel__personalization-tags-button"
|
||||||
|
icon="shortcode"
|
||||||
|
title={ __( 'Personalization Tags', 'mailpoet' ) }
|
||||||
|
onClick={ () => setIsModalOpened( true ) }
|
||||||
|
/>
|
||||||
|
{ labelSuffix }
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseControl
|
||||||
|
id={ `mailpoet-settings-panel__${ attributeName }` }
|
||||||
|
label={ finalLabel }
|
||||||
|
className={ `mailpoet-settings-panel__${ attributeName }-text` }
|
||||||
|
help={ help }
|
||||||
|
>
|
||||||
|
<PersonalizationTagsModal
|
||||||
|
isOpened={ isModalOpened }
|
||||||
|
onInsert={ ( value ) => {
|
||||||
|
handleInsertPersonalizationTag( value );
|
||||||
|
setIsModalOpened( false );
|
||||||
|
} }
|
||||||
|
closeCallback={ () => setIsModalOpened( false ) }
|
||||||
|
/>
|
||||||
|
<RichText
|
||||||
|
ref={ richTextRef }
|
||||||
|
className="mailpoet-settings-panel__richtext"
|
||||||
|
placeholder={ placeholder }
|
||||||
|
onFocus={ () => {
|
||||||
|
setSelectionRange(
|
||||||
|
getCursorPosition(
|
||||||
|
richTextRef,
|
||||||
|
mailpoetEmailData[ attributeName ] ?? ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} }
|
||||||
|
onKeyUp={ () => {
|
||||||
|
setSelectionRange(
|
||||||
|
getCursorPosition(
|
||||||
|
richTextRef,
|
||||||
|
mailpoetEmailData[ attributeName ] ?? ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} }
|
||||||
|
onClick={ () => {
|
||||||
|
setSelectionRange(
|
||||||
|
getCursorPosition(
|
||||||
|
richTextRef,
|
||||||
|
mailpoetEmailData[ attributeName ] ?? ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} }
|
||||||
|
onChange={ ( value ) =>
|
||||||
|
updateEmailMailPoetProperty( attributeName, value )
|
||||||
|
}
|
||||||
|
value={ mailpoetEmailData[ attributeName ] ?? '' }
|
||||||
|
data-automation-id="email_preview_text"
|
||||||
|
/>
|
||||||
|
</BaseControl>
|
||||||
|
);
|
||||||
|
}
|
@ -1,37 +1,13 @@
|
|||||||
import {
|
import { ExternalLink, PanelBody } from '@wordpress/components';
|
||||||
BaseControl,
|
|
||||||
Button,
|
|
||||||
ExternalLink,
|
|
||||||
PanelBody,
|
|
||||||
} from '@wordpress/components';
|
|
||||||
import { useDispatch } from '@wordpress/data';
|
|
||||||
import { useEntityProp } from '@wordpress/core-data';
|
import { useEntityProp } from '@wordpress/core-data';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { createInterpolateElement, useState, useRef } from '@wordpress/element';
|
import { createInterpolateElement } from '@wordpress/element';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { storeName } from '../../store';
|
import { RichTextWithButton } from '../personalization-tags/rich-text-with-button';
|
||||||
import { RichText } from '@wordpress/block-editor';
|
|
||||||
import {
|
|
||||||
createTextToHtmlMap,
|
|
||||||
getCursorPosition,
|
|
||||||
isMatchingComment,
|
|
||||||
} from '../personalization-tags/rich-text-utils';
|
|
||||||
import { PersonalizationTagsModal } from '../personalization-tags/personalization-tags-modal';
|
|
||||||
|
|
||||||
const previewTextMaxLength = 150;
|
const previewTextMaxLength = 150;
|
||||||
const previewTextRecommendedLength = 80;
|
const previewTextRecommendedLength = 80;
|
||||||
|
|
||||||
function PersonalizationTagsButton( { onClick } ) {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className="mailpoet-settings-panel__personalization-tags-button"
|
|
||||||
icon="shortcode"
|
|
||||||
title={ __( 'Personalization Tags', 'mailpoet' ) }
|
|
||||||
onClick={ () => onClick() }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DetailsPanel() {
|
export function DetailsPanel() {
|
||||||
const [ mailpoetEmailData ] = useEntityProp(
|
const [ mailpoetEmailData ] = useEntityProp(
|
||||||
'postType',
|
'postType',
|
||||||
@ -39,59 +15,6 @@ export function DetailsPanel() {
|
|||||||
'mailpoet_data'
|
'mailpoet_data'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { updateEmailMailPoetProperty } = useDispatch( storeName );
|
|
||||||
|
|
||||||
const [ selectionRange, setSelectionRange ] = useState( null );
|
|
||||||
const [ subjectModalIsOpened, setSubjectModalIsOpened ] = useState( false );
|
|
||||||
const [ preheaderModalIsOpened, setPreheaderModalIsOpened ] =
|
|
||||||
useState( false );
|
|
||||||
|
|
||||||
const subjectRef = useRef( null );
|
|
||||||
const preheaderRef = useRef( null );
|
|
||||||
|
|
||||||
const handleInsertPersonalizationTag = async ( value, activeRichText ) => {
|
|
||||||
// Retrieve the current value of the active RichText
|
|
||||||
const currentValue =
|
|
||||||
activeRichText === 'subject'
|
|
||||||
? mailpoetEmailData?.subject ?? ''
|
|
||||||
: mailpoetEmailData?.preheader ?? '';
|
|
||||||
|
|
||||||
const ref = activeRichText === 'subject' ? subjectRef : preheaderRef;
|
|
||||||
if ( ! ref ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 ) +
|
|
||||||
`<!--${ value }-->` +
|
|
||||||
currentValue.slice( htmlEnd );
|
|
||||||
|
|
||||||
// Update the corresponding property
|
|
||||||
if ( activeRichText === 'subject' ) {
|
|
||||||
updateEmailMailPoetProperty( 'subject', updatedValue );
|
|
||||||
} else if ( activeRichText === 'preheader' ) {
|
|
||||||
updateEmailMailPoetProperty( 'preheader', updatedValue );
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectionRange( null );
|
|
||||||
};
|
|
||||||
|
|
||||||
const subjectHelp = createInterpolateElement(
|
const subjectHelp = createInterpolateElement(
|
||||||
__(
|
__(
|
||||||
'Use shortcodes to personalize your email, or learn more about <bestPracticeLink>best practices</bestPracticeLink> and using <emojiLink>emoji in subject lines</emojiLink>.',
|
'Use shortcodes to personalize your email, or learn more about <bestPracticeLink>best practices</bestPracticeLink> and using <emojiLink>emoji in subject lines</emojiLink>.',
|
||||||
@ -117,20 +40,6 @@ export function DetailsPanel() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const subjectLabel = (
|
|
||||||
<>
|
|
||||||
<span>{ __( 'Subject', 'mailpoet' ) }</span>
|
|
||||||
<PersonalizationTagsButton
|
|
||||||
onClick={ () => {
|
|
||||||
setSubjectModalIsOpened( true );
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
<ExternalLink href="https://kb.mailpoet.com/article/215-personalize-newsletter-with-shortcodes#list">
|
|
||||||
{ __( 'Shortcode guide', 'mailpoet' ) }
|
|
||||||
</ExternalLink>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const previewTextLength = mailpoetEmailData?.preheader?.length ?? 0;
|
const previewTextLength = mailpoetEmailData?.preheader?.length ?? 0;
|
||||||
|
|
||||||
const preheaderHelp = createInterpolateElement(
|
const preheaderHelp = createInterpolateElement(
|
||||||
@ -153,140 +62,49 @@ export function DetailsPanel() {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const preheaderLabel = (
|
|
||||||
<>
|
|
||||||
<span>{ __( 'Preview text', 'mailpoet' ) }</span>
|
|
||||||
<PersonalizationTagsButton
|
|
||||||
onClick={ () => {
|
|
||||||
setPreheaderModalIsOpened( true );
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={ classnames(
|
|
||||||
'mailpoet-settings-panel__preview-text-length',
|
|
||||||
{
|
|
||||||
'mailpoet-settings-panel__preview-text-length-warning':
|
|
||||||
previewTextLength > previewTextRecommendedLength,
|
|
||||||
'mailpoet-settings-panel__preview-text-length-error':
|
|
||||||
previewTextLength > previewTextMaxLength,
|
|
||||||
}
|
|
||||||
) }
|
|
||||||
>
|
|
||||||
{ previewTextLength }/{ previewTextMaxLength }
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelBody
|
<PanelBody
|
||||||
title={ __( 'Details', 'mailpoet' ) }
|
title={ __( 'Details', 'mailpoet' ) }
|
||||||
className="mailpoet-email-editor__settings-panel"
|
className="mailpoet-email-editor__settings-panel"
|
||||||
>
|
>
|
||||||
<BaseControl
|
<RichTextWithButton
|
||||||
id="mailpoet-settings-panel__subject"
|
attributeName="subject"
|
||||||
label={ subjectLabel }
|
label={ __( 'Subject', 'mailpoet' ) }
|
||||||
className="mailpoet-settings-panel__subject"
|
labelSuffix={
|
||||||
|
<ExternalLink href="https://kb.mailpoet.com/article/215-personalize-newsletter-with-shortcodes#list">
|
||||||
|
{ __( 'Shortcode guide', 'mailpoet' ) }
|
||||||
|
</ExternalLink>
|
||||||
|
}
|
||||||
help={ subjectHelp }
|
help={ subjectHelp }
|
||||||
>
|
placeholder={ __( 'Eg. The summer sale is here!', 'mailpoet' ) }
|
||||||
<PersonalizationTagsModal
|
/>
|
||||||
isOpened={ subjectModalIsOpened }
|
|
||||||
onInsert={ ( value ) => {
|
|
||||||
handleInsertPersonalizationTag( value, 'subject' );
|
|
||||||
setSubjectModalIsOpened( false );
|
|
||||||
} }
|
|
||||||
closeCallback={ () => setSubjectModalIsOpened( false ) }
|
|
||||||
/>
|
|
||||||
<RichText
|
|
||||||
ref={ subjectRef }
|
|
||||||
className="mailpoet-settings-panel__richtext"
|
|
||||||
placeholder={ __(
|
|
||||||
'Eg. The summer sale is here!',
|
|
||||||
'mailpoet'
|
|
||||||
) }
|
|
||||||
onFocus={ () => {
|
|
||||||
setSelectionRange(
|
|
||||||
getCursorPosition(
|
|
||||||
subjectRef,
|
|
||||||
mailpoetEmailData?.subject ?? ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} }
|
|
||||||
onKeyUp={ () => {
|
|
||||||
setSelectionRange(
|
|
||||||
getCursorPosition(
|
|
||||||
subjectRef,
|
|
||||||
mailpoetEmailData?.subject ?? ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} }
|
|
||||||
onClick={ () => {
|
|
||||||
setSelectionRange(
|
|
||||||
getCursorPosition(
|
|
||||||
subjectRef,
|
|
||||||
mailpoetEmailData?.subject ?? ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} }
|
|
||||||
onChange={ ( value ) =>
|
|
||||||
updateEmailMailPoetProperty( 'subject', value )
|
|
||||||
}
|
|
||||||
value={ mailpoetEmailData?.subject ?? '' }
|
|
||||||
data-automation-id="email_subject"
|
|
||||||
/>
|
|
||||||
</BaseControl>
|
|
||||||
|
|
||||||
<BaseControl
|
<RichTextWithButton
|
||||||
id="mailpoet-settings-panel__richtext"
|
attributeName="preheader"
|
||||||
label={ preheaderLabel }
|
label={ __( 'Preview text', 'mailpoet' ) }
|
||||||
className="mailpoet-settings-panel__preview-text"
|
labelSuffix={
|
||||||
|
<span
|
||||||
|
className={ classnames(
|
||||||
|
'mailpoet-settings-panel__preview-text-length',
|
||||||
|
{
|
||||||
|
'mailpoet-settings-panel__preview-text-length-warning':
|
||||||
|
previewTextLength >
|
||||||
|
previewTextRecommendedLength,
|
||||||
|
'mailpoet-settings-panel__preview-text-length-error':
|
||||||
|
previewTextLength > previewTextMaxLength,
|
||||||
|
}
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
{ previewTextLength }/{ previewTextMaxLength }
|
||||||
|
</span>
|
||||||
|
}
|
||||||
help={ preheaderHelp }
|
help={ preheaderHelp }
|
||||||
>
|
placeholder={ __(
|
||||||
<PersonalizationTagsModal
|
"Add a preview text to capture subscribers' attention and increase open rates.",
|
||||||
isOpened={ preheaderModalIsOpened }
|
'mailpoet'
|
||||||
onInsert={ ( value ) => {
|
) }
|
||||||
handleInsertPersonalizationTag( value, 'preheader' );
|
/>
|
||||||
setPreheaderModalIsOpened( false );
|
|
||||||
} }
|
|
||||||
closeCallback={ () => setPreheaderModalIsOpened( false ) }
|
|
||||||
/>
|
|
||||||
<RichText
|
|
||||||
ref={ preheaderRef }
|
|
||||||
className="mailpoet-settings-panel__richtext"
|
|
||||||
placeholder={ __(
|
|
||||||
"Add a preview text to capture subscribers' attention and increase open rates.",
|
|
||||||
'mailpoet'
|
|
||||||
) }
|
|
||||||
onFocus={ () => {
|
|
||||||
setSelectionRange(
|
|
||||||
getCursorPosition(
|
|
||||||
preheaderRef,
|
|
||||||
mailpoetEmailData?.preheader ?? ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} }
|
|
||||||
onKeyUp={ () => {
|
|
||||||
setSelectionRange(
|
|
||||||
getCursorPosition(
|
|
||||||
preheaderRef,
|
|
||||||
mailpoetEmailData?.preheader ?? ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} }
|
|
||||||
onClick={ () => {
|
|
||||||
setSelectionRange(
|
|
||||||
getCursorPosition(
|
|
||||||
preheaderRef,
|
|
||||||
mailpoetEmailData?.preheader ?? ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} }
|
|
||||||
onChange={ ( value ) =>
|
|
||||||
updateEmailMailPoetProperty( 'preheader', value )
|
|
||||||
}
|
|
||||||
value={ mailpoetEmailData?.preheader ?? '' }
|
|
||||||
data-automation-id="email_preview_text"
|
|
||||||
/>
|
|
||||||
</BaseControl>
|
|
||||||
</PanelBody>
|
</PanelBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user