Compare commits

..

18 Commits

Author SHA1 Message Date
d095480c90 Release 5.7.1
Some checks failed
Email Editor Package Tests / code-style (push) Has been cancelled
Email Editor Package Tests / phpstan-static-analysis (7.4) (push) Has been cancelled
Email Editor Package Tests / phpstan-static-analysis (8.2) (push) Has been cancelled
Email Editor Package Tests / build (7.4) (push) Has been cancelled
Email Editor Package Tests / build (8.2) (push) Has been cancelled
2025-02-17 19:30:19 +03:00
cd44c42a1d Upgrade Action Scheduler to 3.9.2
[MAILPOET-6467]
2025-02-17 15:47:23 +01:00
a4d67a319a Properly set block opt-in checkbox label to render with correct classes
This fixes verical misalignment between the checkbox and the label

[MAILPOET-6436]
2025-02-17 13:08:29 +01:00
1372de1bba Update CheckboxControl types
6b8a403a03/plugins/woocommerce-blocks/packages/components/checkbox-control/index.tsx (L12-L21)

[MAILPOET-6436]
2025-02-17 13:08:29 +01:00
4de580b7df Update Readme and add missing text. 2025-02-14 09:06:09 +01:00
642733ee0c Update Readme with information about the email editor js filters 2025-02-14 09:06:09 +01:00
80f354b860 Simplify the WordPress Playground link and add networking support 2025-02-14 09:06:09 +01:00
f96e74cf96 Remove unnecessary early return in the RichTextWithButton component
MAILPOET-6431
2025-02-13 10:27:43 +01:00
ee513fd871 Add clarifying comments to sections of the codebase
MAILPOET-6431
2025-02-13 10:27:43 +01:00
2adc6d23ca Update acceptance test case to fix failing test
MAILPOET-6431
2025-02-13 10:27:43 +01:00
5023bbb6f2 Fix Incorrect use of <label for=FORM_ELEMENT notice
https://github.com/user-attachments/assets/9a996747-9ab1-453f-bd11-deffea36a468

MAILPOET-6431
2025-02-13 10:27:43 +01:00
b16071ee0e Remove unnecessary use of updateEmailMailPoetProperty
MAILPOET-6431
2025-02-13 10:27:43 +01:00
e5498b6baf Remove unnecessary use of mailpoet_data
MAILPOET-6431
2025-02-13 10:27:43 +01:00
9ad79e75b5 Update email editor mailpoet sidebar implementation
MAILPOET-6431
2025-02-13 10:27:43 +01:00
aa17e90365 Refactor RichTextWithButton component and ensure it is reusable
We also removed the usage of mailpoet_data

MAILPOET-6431
2025-02-13 10:27:43 +01:00
4b917dd1b3 Move current MailPoet specific information on the sidebar to an extension point
MAILPOET-6431
2025-02-13 10:27:43 +01:00
7c425cb04d Refactor send email preview component to remove hardcoded text and add point for extensibility
MAILPOET-6431
2025-02-13 10:27:43 +01:00
43990d9e8b Remove mailpoet_data use from send email preview feature
MAILPOET-6431
2025-02-13 10:27:43 +01:00
31 changed files with 359 additions and 328 deletions

View File

@ -197,7 +197,7 @@ jobs:
- run:
name: Download additional WP Plugins for tests
command: |
./do download:woo-commerce-zip 9.6.1
./do download:woo-commerce-zip 9.6.0
./do download:woo-commerce-subscriptions-zip 7.1.0
./do download:woo-commerce-memberships-zip 1.26.5
./do download:automate-woo-zip 6.1.5

View File

@ -1,30 +0,0 @@
clone:
git:
image: woodpeckerci/plugin-git
settings:
depth: 1
steps:
build:
image: node:current-bookworm-slim
commands:
- apt update
- apt install php php-symfony bash -y
- npm install pnpm
- cd mailpoet
- bash build.sh
- mkdir ../output
- mv mailpoet.zip ../output
- cd ..
release:
image: woodpeckerci/plugin-gitea-release:latest
settings:
base_url: https://git.cavemanon.xyz
api_key:
from_secret: releasesmithapikey
files: "output/"
prerelease: false
title: "${CI_COMMIT_TAG}"
when:
- event: tag

View File

@ -39,13 +39,13 @@ interface JQuery {
declare module '@woocommerce/blocks-checkout' {
type CheckboxControlProps = {
className?: string;
label?: string;
label?: string | React.ReactNode;
id?: string;
instanceId?: string;
onChange?: (value: boolean) => void;
children?: React.ReactChildren | React.ReactElement;
onChange: (value: boolean) => void;
children?: React.ReactChildren;
hasError?: boolean;
checked?: boolean;
disabled?: string | boolean | undefined;
};
export const CheckboxControl: (props: CheckboxControlProps) => JSX.Element;
}

View File

@ -0,0 +1,142 @@
import { ExternalLink } from '@wordpress/components';
import { select, dispatch } from '@wordpress/data';
import { store as coreDataStore, useEntityProp } from '@wordpress/core-data';
import { store as editorStore } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';
import { createInterpolateElement } from '@wordpress/element';
import classnames from 'classnames';
const previewTextMaxLength = 150;
const previewTextRecommendedLength = 80;
export function EmailSidebarExtensionBody({ RichTextWithButton }) {
const [mailpoetEmailData] = useEntityProp(
'postType',
'mailpoet_email',
'mailpoet_data',
);
const updateEmailMailPoetProperty = (name: string, value: string) => {
const postId = select(editorStore).getCurrentPostId();
const currentPostType = 'mailpoet_email'; // only for mailpoet_email post-type
const editedPost = select(coreDataStore).getEditedEntityRecord(
'postType',
currentPostType,
postId,
);
// @ts-expect-error Property 'mailpoet_data' does not exist on type 'Updatable<Attachment<any>>'.
const mailpoetData = editedPost?.mailpoet_data || {};
void dispatch(coreDataStore).editEntityRecord(
'postType',
currentPostType,
postId,
{
mailpoet_data: {
...mailpoetData,
[name]: value,
},
},
);
};
const subjectHelp = createInterpolateElement(
__(
'Use personalization tags to personalize your email, or learn more about <bestPracticeLink>best practices</bestPracticeLink> and using <emojiLink>emoji in subject lines</emojiLink>.',
'mailpoet',
),
{
bestPracticeLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/17-email-subject-line-best-practices-to-boost-engagement/"
target="_blank"
rel="noopener noreferrer"
/>
),
emojiLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/tips-using-emojis-in-subject-lines/"
target="_blank"
rel="noopener noreferrer"
/>
),
},
);
const previewTextLength = mailpoetEmailData?.preheader?.length ?? 0;
const preheaderHelp = createInterpolateElement(
__(
'<link>This text</link> will appear in the inbox, underneath the subject line.',
'mailpoet',
),
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={new URL(
'article/418-preview-text',
'https://kb.mailpoet.com/',
).toString()}
key="preview-text-kb"
target="_blank"
rel="noopener noreferrer"
/>
),
},
);
return (
<>
<RichTextWithButton
attributeName="subject"
attributeValue={mailpoetEmailData?.subject}
updateProperty={updateEmailMailPoetProperty}
label={__('Subject', 'mailpoet')}
labelSuffix={
<ExternalLink href="https://kb.mailpoet.com/article/435-a-guide-to-personalisation-tags-for-tailored-newsletters#list">
{__('Guide', 'mailpoet')}
</ExternalLink>
}
help={subjectHelp}
placeholder={__('Eg. The summer sale is here!', 'mailpoet')}
/>
<br />
<RichTextWithButton
attributeName="preheader"
attributeValue={mailpoetEmailData?.preheader}
updateProperty={updateEmailMailPoetProperty}
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}
placeholder={__(
"Add a preview text to capture subscribers' attention and increase open rates.",
'mailpoet',
)}
/>
</>
);
}
export function EmailSidebarExtension(RichTextWithButton: JSX.Element) {
return <EmailSidebarExtensionBody RichTextWithButton={RichTextWithButton} />;
}

View File

@ -5,6 +5,7 @@
import { addFilter, addAction } from '@wordpress/hooks';
import { MailPoet } from 'mailpoet';
import { withSatismeterSurvey } from './satismeter-survey';
import { EmailSidebarExtension } from './email-sidebar-extension';
import './index.scss';
import { useValidationRules } from './validate-email-content';
@ -12,10 +13,12 @@ addFilter('mailpoet_email_editor_wrap_editor_component', 'mailpoet', (editor) =>
withSatismeterSurvey(editor),
);
// validate email editor content using the defined validation rules
// content is first validated when the "Send" button is clicked and revalidated on "Save Draft"
addFilter(
'mailpoet_email_editor_content_validation_rules',
'mailpoet',
(validationRules: []) => [...validationRules, ...useValidationRules()],
() => useValidationRules(), // returns a memorized set of rules (array of rules)
);
const EVENTS_TO_TRACK = [
@ -53,3 +56,22 @@ addFilter(
'mailpoet',
() => !!window.mailpoet_analytics_enabled,
);
// integration point for settings sidebar
addFilter(
'mailpoet_email_editor_setting_sidebar_extension_component',
'mailpoet',
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
(RichTextWithButton) => EmailSidebarExtension.bind(null, RichTextWithButton),
);
// use mailpoet data subject if available
addFilter(
'mailpoet_email_editor_preferred_template_title',
'mailpoet',
(...args) => {
const [, post] = args;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return post?.mailpoet_data?.subject || ''; // use MailPoet subject as title
},
);

View File

@ -64,12 +64,14 @@ export function useValidationRules() {
label: __('Insert link', 'mailpoet'),
onClick: () => {
if (!hasFooter) {
// update the email content
void dispatch(blockEditorStore).insertBlock(
linksParagraphBlock,
undefined,
contentBlockId,
);
} else {
// update the template
void dispatch(coreDataStore).editEntityRecord(
'postType',
'wp_template',

View File

@ -30,8 +30,10 @@ export function FrontendBlock({
}
return (
<CheckboxControl checked={checked} onChange={setChecked}>
<RawHTML>{text || defaultText}</RawHTML>
</CheckboxControl>
<CheckboxControl
checked={checked}
onChange={setChecked}
label={<RawHTML>{text || defaultText}</RawHTML>}
/>
);
}

View File

@ -56,7 +56,11 @@ export function Edit({
<div {...blockProps}>
{optinEnabled ? (
<div className="wc-block-checkout__marketing">
<CheckboxControl id="mailpoet-marketing-optin" checked={false} />
<CheckboxControl
id="mailpoet-marketing-optin"
checked={false}
onChange={() => {}}
/>
<RichText
value={currentText}
onChange={(value) => setAttributes({ text: value })}

View File

@ -1,5 +1,9 @@
== Changelog ==
= 5.7.1 - 2025-02-17 =
* Improved: Apply get_the_excerpt filter to MailPoets post excerpts;
* Fixed: conflict with Rank Math plugin breaking "Scheduled Actions" page.
= 5.7.0 - 2025-02-11 =
* Improved: Rename Review Trigger in Automations;
* Changed: minimum required WooCommerce is 9.5;

View File

@ -5,7 +5,7 @@
"dragonmantank/cron-expression": "^3.3",
"mailpoet/email-editor": "*",
"mixpanel/mixpanel-php": "2.*",
"woocommerce/action-scheduler": "^3.8.0"
"woocommerce/action-scheduler": "3.9.2"
},
"require-dev": {
"ext-gd": "*",

18
mailpoet/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "51893b0f5ed38d130932b86b48e55b94",
"content-hash": "a95263a51332277e75d7147e3dc4551e",
"packages": [
{
"name": "dragonmantank/cron-expression",
@ -221,20 +221,20 @@
},
{
"name": "woocommerce/action-scheduler",
"version": "3.8.0",
"version": "3.9.2",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/action-scheduler.git",
"reference": "99cd7981f51c98883082534d4852491858d72834"
"reference": "efbb7953f72a433086335b249292f280dd43ddfe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/99cd7981f51c98883082534d4852491858d72834",
"reference": "99cd7981f51c98883082534d4852491858d72834",
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/efbb7953f72a433086335b249292f280dd43ddfe",
"reference": "efbb7953f72a433086335b249292f280dd43ddfe",
"shasum": ""
},
"require": {
"php": ">=5.6"
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^7.5",
@ -258,9 +258,9 @@
"homepage": "https://actionscheduler.org/",
"support": {
"issues": "https://github.com/woocommerce/action-scheduler/issues",
"source": "https://github.com/woocommerce/action-scheduler/tree/3.8.0"
"source": "https://github.com/woocommerce/action-scheduler/tree/3.9.2"
},
"time": "2024-05-22T13:50:29+00:00"
"time": "2025-02-03T09:09:30+00:00"
}
],
"packages-dev": [
@ -8343,7 +8343,7 @@
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@ -35,7 +35,7 @@ class EmailEditorPreviewEmail {
}
private function validateData($data) {
if (empty($data['email']) || empty($data['postId']) || empty($data['newsletterId'])) {
if (empty($data['email']) || empty($data['postId'])) {
throw new \InvalidArgumentException(esc_html__('Missing required data', 'mailpoet'));
}
@ -50,9 +50,9 @@ class EmailEditorPreviewEmail {
* @throws \Exception
*/
private function fetchNewsletter($postData): NewsletterEntity {
$newsletter = $this->newslettersRepository->findOneById((int)$postData['newsletterId']);
$newsletter = $this->newslettersRepository->findOneBy(['wpPost' => (int)$postData['postId']]);
if (!$newsletter) {
if (!$newsletter instanceof NewsletterEntity) {
throw new \Exception(esc_html__('This email does not exist.', 'mailpoet'));
}

View File

@ -167,6 +167,8 @@ class Subscribers {
}
private function getFreeSubscribersLimit() {
return 999999999;
$installationTime = strtotime((string)$this->settings->get('installed_at'));
$oldUser = $installationTime < strtotime(self::NEW_LIMIT_DATE);
return $oldUser ? self::SUBSCRIBERS_OLD_LIMIT : self::SUBSCRIBERS_NEW_LIMIT;
}
}

View File

@ -2,7 +2,7 @@
/*
* Plugin Name: MailPoet
* Version: 5.7.0
* Version: 5.7.1
* Plugin URI: https://www.mailpoet.com
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
* Author: MailPoet
@ -20,7 +20,7 @@
*/
$mailpoetPlugin = [
'version' => '5.7.0',
'version' => '5.7.1',
'filename' => __FILE__,
'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',

View File

@ -3,7 +3,7 @@ Contributors: mailpoet, woocommerce, automattic
Tags: email marketing, post notification, woocommerce emails, email automation, newsletter
Requires at least: 6.6
Tested up to: 6.7
Stable tag: 5.7.0
Stable tag: 5.7.1
Requires PHP: 7.4
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -222,11 +222,8 @@ Check our [Knowledge Base](https://kb.mailpoet.com) or contact us through our [s
== Changelog ==
= 5.7.0 - 2025-02-11 =
* Improved: Rename Review Trigger in Automations;
* Changed: minimum required WooCommerce is 9.5;
* Fixed: Automation UI shows wrong saved status after failed activation;
* Fixed: Email preview does not work with sent emails;
* Fixed: email content patterns are mixed with page starter patterns.
= 5.7.1 - 2025-02-17 =
* Improved: Apply get_the_excerpt filter to MailPoets post excerpts;
* Fixed: conflict with Rank Math plugin breaking "Scheduled Actions" page.
[See the changelog for all versions.](https://github.com/mailpoet/mailpoet/blob/trunk/mailpoet/changelog.txt)

View File

@ -55,7 +55,8 @@ class CreateAndSendEmailUsingGutenbergCest {
$i->click('Save Draft');
$i->waitForText('Saved');
$i->waitForText('Email saved!');
$i->click('Send');
$i->click('[aria-label="Close sidebar"]'); // close the sidebar. It sometimes interferes with the send button click
$i->click('[data-automation-id="email_editor_send_button"]');
$i->waitForElement('[name="subject"]');
$subject = $i->grabValueFrom('[name="subject"]');
verify($subject)->equals('My New Subject');

View File

@ -4,7 +4,7 @@ This folder contains the code for the MailPoet Email Editor JS Package.
We aim to extract the package as an independent library, so it can be used in other projects.
As we are still in an exploration phase, we keep it together with the MailPoet codebase.
You can try the email editor in [the WordPress Playground](https://playground.wordpress.net/?mode=seamless#%7B%22preferredVersions%22:%7B%22php%22:%228.2%22,%22wp%22:%22latest%22%7D,%22phpExtensionBundles%22:%5B%22kitchen-sink%22%5D,%22features%22:%7B%7D,%22landingPage%22:%22/wp-admin/admin.php?page=mailpoet-newsletters%22,%22steps%22:%5B%7B%22step%22:%22login%22,%22username%22:%22admin%22,%22password%22:%22password%22%7D,%7B%22step%22:%22installPlugin%22,%22pluginData%22:%7B%22resource%22:%22url%22,%22url%22:%22https://account.mailpoet.com/playground/plugin-proxy/branch:trunk%22%7D%7D,%7B%22step%22:%22mkdir%22,%22path%22:%22wordpress/wp-content/mu-plugins%22%7D,%7B%22step%22:%22writeFile%22,%22path%22:%22wordpress/wp-content/mu-plugins/addFilter-2.php%22,%22data%22:%22%3C?php%20%5Cnuse%20MailPoet%5C%5CDI%5C%5CContainerWrapper;%5Cnuse%20MailPoet%5C%5CFeatures%5C%5CFeatureFlagsRepository;%5Cnuse%20MailPoet%5C%5CFeatures%5C%5CFeaturesController;%5Cnadd_filter('mailpoet_skip_welcome_wizard',%20'__return_true');%22%7D%5D%7D).
You can try the email editor in [the WordPress Playground](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/mailpoet/mailpoet/refs/heads/trunk/packages/js/email-editor/blueprint.json).
You can locate the PHP package here `packages/php/email-editor`
@ -98,11 +98,13 @@ We may add, update and delete any of them.
### Filters
| Name | Argument | Return | Description |
|--------------------------------------------------|---------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| `mailpoet_email_editor_events_tracking_enabled` | `boolean` (false-default) | `boolean` | Used to enable the email editor events tracking and collection |
| `mailpoet_email_editor_wrap_editor_component` | `JSX.Element` Editor | `JSX.Element` Editor | The main editor component. Custom component can wrap the editor and provide additional functionality |
| `mailpoet_email_editor_send_button_label` | `string` 'Send' | `string` 'Send' (default) | Email editor send button label. The `Send` text can be updated using this filter |
| `mailpoet_email_editor_send_action_callback` | `function` sendAction | `function` sendAction | Action to perform when the Send button is clicked |
| `mailpoet_email_editor_content_validation_rules` | `array` rules | `EmailContentValidationRule[]` rules | Email editor content validation rules. The validation is done on `send btton` click and revalidated on `save draft` |
| Name | Argument | Return | Description |
|-----------------------------------------------------------------|----------------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| `mailpoet_email_editor_events_tracking_enabled` | `boolean` (false-default) | `boolean` | Used to enable the email editor events tracking and collection |
| `mailpoet_email_editor_wrap_editor_component` | `JSX.Element` Editor | `JSX.Element` Editor | The main editor component. Custom component can wrap the editor and provide additional functionality |
| `mailpoet_email_editor_send_button_label` | `string` 'Send' | `string` 'Send' (default) | Email editor send button label. The `Send` text can be updated using this filter |
| `mailpoet_email_editor_send_action_callback` | `function` sendAction | `function` sendAction | Action to perform when the Send button is clicked |
| `mailpoet_email_editor_content_validation_rules` | `array` rules | `EmailContentValidationRule[]` rules | Email editor content validation rules. The validation is done on `send btton` click and revalidated on `save draft` |
| `mailpoet_email_editor_check_sending_method_configuration_link` | `string` link | `string` link | Edit or remove the sending configuration link message |
| `mailpoet_email_editor_setting_sidebar_extension_component` | `JSX.Element` RichTextWithButton | `JSX.Element` Sidebar extension component | Add components to the Email settings sidebar |
| `mailpoet_email_editor_preferred_template_title` | `string` '', `Post` post | `string` custom (preferred) template title | Custom title for Email preset template selector |

View File

@ -0,0 +1,32 @@
{
"landingPage": "/wp-admin/admin.php?page=mailpoet-newsletters",
"preferredVersions": {
"php": "8.2",
"wp": "latest"
},
"features": {
"networking": true
},
"steps": [
{
"step": "login",
"username": "admin"
},
{
"step": "installPlugin",
"pluginData": {
"resource": "url",
"url": "https://account.mailpoet.com/playground/plugin-proxy/branch:trunk"
}
},
{
"step": "mkdir",
"path": "wordpress/wp-content/mu-plugins"
},
{
"step": "writeFile",
"path": "wordpress/wp-content/mu-plugins/addFilter-2.php",
"data": "<?php \nadd_filter('mailpoet_skip_welcome_wizard', '__return_true');"
}
]
}

View File

@ -35,8 +35,7 @@ export function MoreMenu(): JSX.Element {
editorCurrentPostType,
'status'
);
const { saveEditedEmail, updateEmailMailPoetProperty } =
useDispatch( storeName );
const { saveEditedEmail } = useDispatch( storeName );
const goToListings = () => {
window.location.href = urls.listings;
};
@ -132,10 +131,6 @@ export function MoreMenu(): JSX.Element {
<MenuItem
onClick={ async () => {
await setStatus( 'draft' );
await updateEmailMailPoetProperty(
'deleted_at',
''
);
await saveEditedEmail();
recordEvent(
'header_more_menu_restore_from_trash_button_clicked'

View File

@ -63,6 +63,7 @@ export function SendButton( { validateContent, isContentInvalid } ) {
}
} }
disabled={ isDisabled }
data-automation-id="email_editor_send_button"
>
{ label }
</Button>

View File

@ -18,7 +18,7 @@ type Replacement = {
function getChildElement( rootElement: HTMLElement ): HTMLElement | null {
let currentElement: HTMLElement | null = rootElement;
while ( currentElement && currentElement.children.length > 0 ) {
while ( currentElement && currentElement?.children?.length > 0 ) {
// Traverse into the first child element
currentElement = currentElement.children[ 0 ] as HTMLElement;
}

View File

@ -1,10 +1,10 @@
/**
* External dependencies
*/
import type { ReactNode } from 'react';
import { BaseControl, Button } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { useSelect } from '@wordpress/data';
import { useCallback, useRef, useState } from '@wordpress/element';
import { useEntityProp } from '@wordpress/core-data';
import { create, insert, toHTMLString } from '@wordpress/rich-text';
import { RichText } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
@ -17,25 +17,32 @@ import {
getCursorPosition,
replacePersonalizationTagsWithHTMLComments,
} from './rich-text-utils';
import { storeName, editorCurrentPostType } from '../../store';
import { storeName } from '../../store';
import { PersonalizationTagsPopover } from './personalization-tags-popover';
import { recordEvent, recordEventOnce } from '../../events';
type Props = {
label: string;
labelSuffix?: ReactNode;
help?: ReactNode;
placeholder: string;
attributeName: string;
attributeValue?: string;
updateProperty: (
theAttributeName: string,
theUpdatedValue: string
) => void;
};
export function RichTextWithButton( {
label,
labelSuffix,
help,
placeholder,
attributeName,
} ) {
const [ mailpoetEmailData ] = useEntityProp(
'postType',
editorCurrentPostType,
'mailpoet_data'
);
const { updateEmailMailPoetProperty } = useDispatch( storeName );
attributeValue,
updateProperty = () => {},
}: Props ) {
const [ selectionRange, setSelectionRange ] = useState( null );
const [ isModalOpened, setIsModalOpened ] = useState( false );
const list = useSelect(
@ -61,11 +68,11 @@ export function RichTextWithButton( {
const updatedValue = toHTMLString( { value: richTextValue } );
// Update the corresponding property
updateEmailMailPoetProperty( attributeName, updatedValue );
updateProperty( attributeName, updatedValue );
setSelectionRange( null );
},
[ attributeName, updateEmailMailPoetProperty ]
[ attributeName, updateProperty ]
);
const finalLabel = (
@ -90,13 +97,13 @@ export function RichTextWithButton( {
</>
);
if ( ! mailpoetEmailData ) {
if ( ! attributeName ) {
return null;
}
return (
<BaseControl
id={ `mailpoet-settings-panel__${ attributeName }` }
id="" // See https://github.com/mailpoet/mailpoet/pull/6089#discussion_r1952126850 to understand why the ID is empty
label={ finalLabel }
className={ `mailpoet-settings-panel__${ attributeName }-text` }
help={ help }
@ -107,7 +114,7 @@ export function RichTextWithButton( {
onInsert={ ( value ) => {
handleInsertPersonalizationTag(
value,
mailpoetEmailData[ attributeName ] ?? '',
attributeValue ?? '',
selectionRange
);
setIsModalOpened( false );
@ -125,17 +132,13 @@ export function RichTextWithButton( {
<PersonalizationTagsPopover
contentRef={ richTextRef }
onUpdate={ ( originalTag, updatedTag ) => {
const currentValue =
mailpoetEmailData[ attributeName ] ?? '';
const currentValue = attributeValue ?? '';
// When we update the tag, we need to add brackets to the tag, because the popover removes them
const updatedContent = currentValue.replace(
`<!--[${ originalTag }]-->`,
`<!--[${ updatedTag }]-->`
);
updateEmailMailPoetProperty(
attributeName,
updatedContent
);
updateProperty( attributeName, updatedContent );
} }
/>
<RichText
@ -144,26 +147,17 @@ export function RichTextWithButton( {
placeholder={ placeholder }
onFocus={ () => {
setSelectionRange(
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
getCursorPosition( richTextRef, attributeValue ?? '' )
);
} }
onKeyUp={ () => {
setSelectionRange(
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
getCursorPosition( richTextRef, attributeValue ?? '' )
);
} }
onClick={ () => {
setSelectionRange(
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
getCursorPosition( richTextRef, attributeValue ?? '' )
);
} }
onChange={ ( value ) => {
@ -171,7 +165,7 @@ export function RichTextWithButton( {
value ?? '',
list
);
updateEmailMailPoetProperty( attributeName, value );
updateProperty( attributeName, value );
recordEventOnce(
'rich_text_with_button_input_field_updated',
{
@ -179,7 +173,7 @@ export function RichTextWithButton( {
}
);
} }
value={ mailpoetEmailData[ attributeName ] ?? '' }
value={ attributeValue ?? '' }
data-automation-id={ `email_${ attributeName }` }
/>
</BaseControl>

View File

@ -4,7 +4,7 @@
import { Button, Modal, TextControl } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { check, Icon } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import {
useEffect,
useRef,
@ -13,19 +13,23 @@ import {
} from '@wordpress/element';
import { ENTER } from '@wordpress/keycodes';
import { isEmail } from '@wordpress/url';
import { useEntityProp } from '@wordpress/core-data';
import { applyFilters } from '@wordpress/hooks';
/**
* Internal dependencies
*/
import {
MailPoetEmailData,
SendingPreviewStatus,
storeName,
editorCurrentPostType,
} from '../../store';
import { recordEvent, recordEventOnce } from '../../events';
const sendingMethodConfigurationLink = applyFilters(
'mailpoet_email_editor_check_sending_method_configuration_link',
'admin.php?page=mailpoet-settings#mta'
) as string;
function RawSendPreviewEmail() {
const sendToRef = useRef( null );
@ -40,19 +44,11 @@ function RawSendPreviewEmail() {
isSendingPreviewEmail,
sendingPreviewStatus,
isModalOpened,
errorMessage,
} = useSelect( ( select ) => select( storeName ).getPreviewState(), [] );
const [ mailpoetEmailData ] = useEntityProp(
'postType',
editorCurrentPostType,
'mailpoet_data'
) as [ MailPoetEmailData, unknown, unknown ];
const handleSendPreviewEmail = () => {
void requestSendingNewsletterPreview(
mailpoetEmailData.id,
previewToEmail
);
void requestSendingNewsletterPreview( previewToEmail );
};
const closeCallback = () => {
@ -81,33 +77,48 @@ function RawSendPreviewEmail() {
>
{ sendingPreviewStatus === SendingPreviewStatus.ERROR ? (
<div className="mailpoet-send-preview-modal-notice-error">
{ __(
'Sorry, we were unable to send this email.',
'mailpoet'
) }
<p>
{ __(
'Sorry, we were unable to send this email.',
'mailpoet'
) }
</p>
<strong>
{ errorMessage &&
sprintf(
// translators: %s is an error message.
__( 'Error: %s', 'mailpoet' ),
errorMessage
) }
</strong>
<ul>
<li>
{ createInterpolateElement(
__(
'Please check your <link>sending method configuration</link> with your hosting provider.',
'mailpoet'
),
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="admin.php?page=mailpoet-settings#mta"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'send_preview_email_modal_check_sending_method_configuration_link_clicked'
)
}
/>
{ sendingMethodConfigurationLink &&
createInterpolateElement(
__(
'Please check your <link>sending method configuration</link> with your hosting provider.',
'mailpoet'
),
}
) }
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={
sendingMethodConfigurationLink
}
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'send_preview_email_modal_check_sending_method_configuration_link_clicked'
)
}
/>
),
}
) }
</li>
<li>
{ createInterpolateElement(
@ -119,10 +130,7 @@ function RawSendPreviewEmail() {
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={ new URL(
'free-plan',
'https://www.mailpoet.com/'
).toString() }
href={ `https://account.mailpoet.com/?s=1&g=1&utm_source=mailpoet_email_editor&utm_medium=plugin&utm_source_platform=${ editorCurrentPostType }` }
key="sign-up-for-free"
target="_blank"
rel="noopener noreferrer"

View File

@ -1,92 +1,22 @@
/**
* External dependencies
*/
import { ExternalLink, PanelBody } from '@wordpress/components';
import { useEntityProp } from '@wordpress/core-data';
import { PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { createInterpolateElement } from '@wordpress/element';
import classnames from 'classnames';
import { applyFilters } from '@wordpress/hooks';
/**
* Internal dependencies
*/
import { editorCurrentPostType } from '../../store';
import { recordEvent } from '../../events';
import { RichTextWithButton } from '../personalization-tags/rich-text-with-button';
const previewTextMaxLength = 150;
const previewTextRecommendedLength = 80;
const SidebarExtensionComponent = applyFilters(
'mailpoet_email_editor_setting_sidebar_extension_component',
RichTextWithButton
) as () => JSX.Element;
export function DetailsPanel() {
const [ mailpoetEmailData ] = useEntityProp(
'postType',
editorCurrentPostType,
'mailpoet_data'
);
const subjectHelp = createInterpolateElement(
__(
'Use personalization tags to personalize your email, or learn more about <bestPracticeLink>best practices</bestPracticeLink> and using <emojiLink>emoji in subject lines</emojiLink>.',
'mailpoet'
),
{
bestPracticeLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/17-email-subject-line-best-practices-to-boost-engagement/"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'details_panel_subject_help_best_practice_link_clicked'
)
}
/>
),
emojiLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/tips-using-emojis-in-subject-lines/"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'details_panel_subject_help_emoji_in_subject_lines_link_clicked'
)
}
/>
),
}
);
const previewTextLength = mailpoetEmailData?.preheader?.length ?? 0;
const preheaderHelp = createInterpolateElement(
__(
'<link>This text</link> will appear in the inbox, underneath the subject line.',
'mailpoet'
),
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={ new URL(
'article/418-preview-text',
'https://kb.mailpoet.com/'
).toString() }
key="preview-text-kb"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'details_panel_preheader_help_text_link_clicked'
)
}
/>
),
}
);
return (
<PanelBody
title={ __( 'Details', 'mailpoet' ) }
@ -95,50 +25,7 @@ export function DetailsPanel() {
recordEvent( 'details_panel_body_toggle', { opened: data } )
}
>
<RichTextWithButton
attributeName="subject"
label={ __( 'Subject', 'mailpoet' ) }
labelSuffix={
<ExternalLink
href="https://kb.mailpoet.com/article/435-a-guide-to-personalisation-tags-for-tailored-newsletters#list"
onClick={ () =>
recordEvent(
'details_panel_personalisation_tags_guide_link_clicked'
)
}
>
{ __( 'Guide', 'mailpoet' ) }
</ExternalLink>
}
help={ subjectHelp }
placeholder={ __( 'Eg. The summer sale is here!', 'mailpoet' ) }
/>
<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 }
placeholder={ __(
"Add a preview text to capture subscribers' attention and increase open rates.",
'mailpoet'
) }
/>
<>{ <SidebarExtensionComponent /> }</>
</PanelBody>
);
}

View File

@ -5,6 +5,7 @@ import { useMemo } from '@wordpress/element';
import { parse } from '@wordpress/blocks';
import { BlockInstance } from '@wordpress/blocks/index';
import { useSelect } from '@wordpress/data';
import { applyFilters } from '@wordpress/hooks';
/**
* Internal dependencies
@ -163,6 +164,11 @@ export function usePreviewTemplates(
const allEmailPosts = useMemo( () => {
return emailPosts?.map( ( post: EmailEditorPostType ) => {
const preferredTitle = applyFilters(
'mailpoet_email_editor_preferred_template_title',
'',
post
);
const { postTemplateContent } = generateTemplateContent(
post,
allTemplates
@ -180,9 +186,8 @@ export function usePreviewTemplates(
const template = {
...post,
title: {
raw: post?.mailpoet_data?.subject || post.title.raw,
rendered:
post?.mailpoet_data?.subject || post.title.rendered, // use MailPoet subject as title
raw: post.title.raw,
rendered: preferredTitle || post.title.rendered,
},
};
return {

View File

@ -134,29 +134,6 @@ export function* saveEditedEmail() {
} );
}
export function* updateEmailMailPoetProperty( name: string, value: string ) {
const postId = select( storeName ).getEmailPostId();
// There can be a better way how to get the edited post data
const editedPost = select( coreDataStore ).getEditedEntityRecord(
'postType',
editorCurrentPostType,
postId
);
// @ts-expect-error Property 'mailpoet_data' does not exist on type 'Updatable<Attachment<any>>'.
const mailpoetData = editedPost?.mailpoet_data || {};
yield dispatch( coreDataStore ).editEntityRecord(
'postType',
editorCurrentPostType,
postId,
{
mailpoet_data: {
...mailpoetData,
[ name ]: value,
},
}
);
}
export const setTemplateToPost =
( templateSlug ) =>
async ( { registry } ) => {
@ -168,10 +145,7 @@ export const setTemplateToPost =
} );
};
export function* requestSendingNewsletterPreview(
newsletterId: number,
email: string
) {
export function* requestSendingNewsletterPreview( email: string ) {
// If preview is already sending do nothing
const previewState = select( storeName ).getPreviewState();
if ( previewState.isSendingPreviewEmail ) {
@ -192,7 +166,6 @@ export function* requestSendingNewsletterPreview(
path: '/mailpoet-email-editor/v1/send_preview_email',
method: 'POST',
data: {
newsletterId,
email,
postId,
},
@ -213,6 +186,9 @@ export function* requestSendingNewsletterPreview(
state: {
sendingPreviewStatus: SendingPreviewStatus.ERROR,
isSendingPreviewEmail: false,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
errorMessage: JSON.stringify( errorResponse?.error ),
},
};
}

View File

@ -96,13 +96,8 @@ export const isEmpty = createRegistrySelector( ( select ) => (): boolean => {
return true;
}
const { content, mailpoet_data: mailpoetData, title } = post;
return (
! content.raw &&
! mailpoetData.subject &&
! mailpoetData.preheader &&
! title.raw
);
const { content, title } = post;
return ! content.raw && ! title.raw;
} );
export const hasEmptyContent = createRegistrySelector(

View File

@ -202,6 +202,7 @@ export type State = {
isModalOpened: boolean;
isSendingPreviewEmail: boolean;
sendingPreviewStatus: SendingPreviewStatus | null;
errorMessage?: string;
};
personalizationTags: {
list: PersonalizationTag[];
@ -209,13 +210,6 @@ export type State = {
};
};
export type MailPoetEmailData = {
id: number;
subject: string;
preheader: string;
preview_url: string;
};
export type EmailTemplate = {
id: string;
slug: string;
@ -267,7 +261,6 @@ export type MailPoetEmailPostContentExtended = {
export type EmailEditorPostType = Omit< Post, 'type' > & {
type: string;
mailpoet_data?: MailPoetEmailPostContentExtended;
};
export type EmailContentValidationAction = {

View File

@ -75,5 +75,3 @@ We may add, update and delete any of them.
| `mailpoet_email_editor_send_preview_email` | `Array` $postData | `boolean` Result of processing. Was email sent successfully? | Allows override of the send preview mail function. Folks may choose to use custom implementation |
| `mailpoet_email_editor_post_sent_status_args` | `Array` `sent` post status args | `Array` register_post_status args | Allows update of the argument for the sent post status |
## TODO
- We use `mailpoet_data` in some section of the codebase. This will be updated.

View File

@ -66,7 +66,6 @@ class Email_Api_Controller {
* $data - Post Data
* format
* [_locale] => user
* [newsletterId] => NEWSLETTER_ID
* [email] => Provided email address
* [postId] => POST_ID
*/

View File

@ -75,7 +75,7 @@ services:
- mailhog-data:/mailhog-data
wordpress:
image: wordpress:${WORDPRESS_IMAGE_VERSION:-6.7.2-php8.3}
image: wordpress:${WORDPRESS_IMAGE_VERSION:-6.7.1-php8.3}
container_name: wordpress_${CIRCLE_NODE_INDEX:-default}
depends_on:
smtp: