Create new richText component with button

[MAILPOET-6354]
This commit is contained in:
Jan Lysý
2024-12-12 16:27:33 +01:00
committed by Aschepikov
parent b264c74ce9
commit 2aae43b91e
4 changed files with 174 additions and 223 deletions

View File

@ -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;

View File

@ -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>
);
}

View File

@ -1,37 +1,13 @@
import {
BaseControl,
Button,
ExternalLink,
PanelBody,
} from '@wordpress/components';
import { useDispatch } from '@wordpress/data';
import { ExternalLink, PanelBody } from '@wordpress/components';
import { useEntityProp } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
import { createInterpolateElement, useState, useRef } from '@wordpress/element';
import { createInterpolateElement } from '@wordpress/element';
import classnames from 'classnames';
import { storeName } from '../../store';
import { RichText } from '@wordpress/block-editor';
import {
createTextToHtmlMap,
getCursorPosition,
isMatchingComment,
} from '../personalization-tags/rich-text-utils';
import { PersonalizationTagsModal } from '../personalization-tags/personalization-tags-modal';
import { RichTextWithButton } from '../personalization-tags/rich-text-with-button';
const previewTextMaxLength = 150;
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() {
const [ mailpoetEmailData ] = useEntityProp(
'postType',
@ -39,59 +15,6 @@ export function DetailsPanel() {
'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(
__(
'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 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 (
<PanelBody
title={ __( 'Details', 'mailpoet' ) }
className="mailpoet-email-editor__settings-panel"
>
<BaseControl
id="mailpoet-settings-panel__subject"
label={ subjectLabel }
className="mailpoet-settings-panel__subject"
<RichTextWithButton
attributeName="subject"
label={ __( 'Subject', 'mailpoet' ) }
labelSuffix={
<ExternalLink href="https://kb.mailpoet.com/article/215-personalize-newsletter-with-shortcodes#list">
{ __( 'Shortcode guide', 'mailpoet' ) }
</ExternalLink>
}
help={ subjectHelp }
>
<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>
placeholder={ __( 'Eg. The summer sale is here!', 'mailpoet' ) }
/>
<BaseControl
id="mailpoet-settings-panel__richtext"
label={ preheaderLabel }
className="mailpoet-settings-panel__preview-text"
<RichTextWithButton
attributeName="preheader"
label={ __( 'Preview text', 'mailpoet' ) }
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 }
>
<PersonalizationTagsModal
isOpened={ preheaderModalIsOpened }
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>
placeholder={ __(
"Add a preview text to capture subscribers' attention and increase open rates.",
'mailpoet'
) }
/>
</PanelBody>
);
}