Compare commits
256 Commits
Author | SHA1 | Date | |
---|---|---|---|
edceda63e9 | |||
ad5ef6ebbc | |||
3c5bc5b384 | |||
f229df0383 | |||
9092f892d0 | |||
9c3fb0856f | |||
2a8c665e12 | |||
9b1942ce48 | |||
932b4532f0 | |||
6533a1444d | |||
05a109dcf4 | |||
5b5b7856c6 | |||
4ceb508218 | |||
3afc7dc140 | |||
1c1058667b | |||
129aed2ae9 | |||
f8c7adf5a6 | |||
6346d39507 | |||
1b76f3b862 | |||
40c140a631 | |||
7fd11d4fb5 | |||
7fa694314b | |||
080e385b02 | |||
7c0b625793 | |||
f0b858b1f8 | |||
b218663e7e | |||
d8ea29423d | |||
ff038a1c56 | |||
588e8fca6b | |||
8d9f23d5f8 | |||
ea4637c740 | |||
c0e56a4f92 | |||
53a4722e91 | |||
d175870adf | |||
a64229f688 | |||
cd69e1e7b9 | |||
321db0b004 | |||
aaf01475b0 | |||
1cd38a396e | |||
1f1c9dd077 | |||
0da6d84309 | |||
3a05598166 | |||
5035d64298 | |||
56f6d20244 | |||
650d730c3f | |||
decaa0f636 | |||
c2b9e33b79 | |||
34347f241d | |||
5802f2132d | |||
8e502e5f50 | |||
0187f0cede | |||
8457aa7e2b | |||
67156b7c6f | |||
f9250b66a9 | |||
1fcdccaa33 | |||
5b93b88f46 | |||
cbd41cd1be | |||
95a8943b7e | |||
dd363370f6 | |||
1027a6a676 | |||
0fd93b0ff9 | |||
bfade87b62 | |||
26d9030544 | |||
5688a0daea | |||
92996ac781 | |||
1f471782ab | |||
320459eaab | |||
e65b2cce02 | |||
520ee981d3 | |||
f100ef72da | |||
146e1c871f | |||
669367c9c6 | |||
77d62c431a | |||
4a5fc507ca | |||
06f59dd320 | |||
4367f44449 | |||
856e0f69d3 | |||
c4bd2acd84 | |||
210c240aab | |||
d19f5dc732 | |||
6541c20466 | |||
3952484848 | |||
7d918604c3 | |||
ffaf5b88b6 | |||
eca4a9e923 | |||
1cee4e67e8 | |||
b1c35b60d8 | |||
1e5cfb4eaf | |||
5e5956420f | |||
78446d174a | |||
917d64a3e3 | |||
a3d51b2b08 | |||
3ffbd6e236 | |||
9753331d52 | |||
b266edb8e7 | |||
f1899055b8 | |||
c3f0a36665 | |||
88efb1f3d1 | |||
146f5881f7 | |||
3279fb1154 | |||
5f5efbe876 | |||
12afcfb656 | |||
0aac9e9d60 | |||
4e3b1527cd | |||
b27dcdd40a | |||
7b584dd482 | |||
5e71d94b62 | |||
8bb4338627 | |||
4bcd4a2cf2 | |||
ea7971cb3b | |||
9036b210a0 | |||
f337ac388b | |||
b0ae21ec60 | |||
7d11eeeddd | |||
4577ca5476 | |||
846794b60d | |||
5757af95aa | |||
b8c3b38652 | |||
3d21e4f35e | |||
73ce89d051 | |||
49d9dd666b | |||
53dda33c02 | |||
9b9f61bf13 | |||
651e2d3c56 | |||
0bd627d3b1 | |||
99d0eede80 | |||
d09b4ca409 | |||
caa76983e4 | |||
7b6bbb0bc3 | |||
cdf7c81a94 | |||
86812c5259 | |||
657302b720 | |||
172b5215d2 | |||
52a9d9f76c | |||
8eb7a48d3e | |||
a8bceffc9c | |||
2c728c793a | |||
8d974d8147 | |||
8fbc5c270a | |||
92dc5921df | |||
3c3ce37720 | |||
f369e399ed | |||
01aa1e1e52 | |||
2bbc1b7063 | |||
04b8a0ed73 | |||
761f7c6537 | |||
723dc2c9d3 | |||
d66fbb1c20 | |||
c2107b8d59 | |||
926620e8f8 | |||
82aeb89854 | |||
c67c58709b | |||
75b5958a53 | |||
bba5101669 | |||
b9c5dddbaf | |||
0566b3f5fa | |||
ac4adac1ab | |||
33a9097719 | |||
08c74cdf8b | |||
8c5222d850 | |||
08c76a46af | |||
3d24cb1deb | |||
a151f93be1 | |||
bce6a06c15 | |||
10a6e387f3 | |||
b6a37f274c | |||
2cdbc68643 | |||
7db40b27b5 | |||
b1760ff676 | |||
293eef2c78 | |||
299c6b779e | |||
842f435976 | |||
11faf925cc | |||
1295aa21cc | |||
84cd137c76 | |||
37067dff67 | |||
c7f850e1ba | |||
b25f2cd5f4 | |||
af95080b67 | |||
6b8c35d5fe | |||
a35e0dced4 | |||
c6259eb185 | |||
670017b342 | |||
ce7b210da9 | |||
1a2d8b8d40 | |||
fe2df00a8c | |||
20577d974f | |||
7369465a3a | |||
5769fa45f5 | |||
193b4cbf98 | |||
461df1a560 | |||
d5f78680eb | |||
96dde7107b | |||
3e756bfadd | |||
82c755200e | |||
735d33d05b | |||
90c8e5b2c9 | |||
cd1f06b116 | |||
173f347431 | |||
353d3389cd | |||
fe364978a1 | |||
cd512bbb01 | |||
f5193df721 | |||
25b5d24867 | |||
1d99ff0943 | |||
99d6d96b05 | |||
6ddfe2c605 | |||
d2ec7bba42 | |||
2557171953 | |||
0d7e45ea03 | |||
e677169a51 | |||
e268eac378 | |||
375f2b13c5 | |||
34406a2f72 | |||
cc658bcc3d | |||
973aefe7ae | |||
34e91251e8 | |||
882402645d | |||
d9d4dccc09 | |||
9d2fcccbf5 | |||
c4fdd881e6 | |||
83f7eee8ae | |||
dc1f63d8bb | |||
3e2425abd8 | |||
65e78c7990 | |||
2bdc5d3683 | |||
1de276378f | |||
cc76fe77f0 | |||
c8636aac6f | |||
a09f41143e | |||
8b29694446 | |||
5e0e89408c | |||
86076547c3 | |||
b1bbabe14d | |||
f5c678ec2d | |||
223625bd9b | |||
fbd7cf8cc7 | |||
26c7e4d1cc | |||
4832771185 | |||
7fb8d64628 | |||
36fed3bbf8 | |||
8ab738b315 | |||
b84b9606e9 | |||
aa68b7e148 | |||
ca44339512 | |||
91f5376dfc | |||
d51ef70db6 | |||
c164268c20 | |||
4c7c94c75d | |||
ccf141b279 | |||
e42808f22d | |||
75a4b2c538 | |||
f1d64c0572 | |||
25c57bf9fe | |||
079c90dbba | |||
0fc342ee4c |
@ -312,6 +312,7 @@ jobs:
|
||||
working_directory: /home/circleci/mailpoet/mailpoet
|
||||
machine:
|
||||
image: ubuntu-2204:2022.10.2
|
||||
docker_layer_caching: false
|
||||
parameters:
|
||||
multisite:
|
||||
type: integer
|
||||
@ -322,7 +323,7 @@ jobs:
|
||||
mysql_command:
|
||||
type: string
|
||||
default: ''
|
||||
mysql_image_version:
|
||||
mysql_image:
|
||||
type: string
|
||||
default: ''
|
||||
codeception_image_version:
|
||||
@ -351,7 +352,7 @@ jobs:
|
||||
default: 0
|
||||
environment:
|
||||
MYSQL_COMMAND: << parameters.mysql_command >>
|
||||
MYSQL_IMAGE_VERSION: << parameters.mysql_image_version >>
|
||||
MYSQL_IMAGE: << parameters.mysql_image >>
|
||||
CODECEPTION_IMAGE_VERSION: << parameters.codeception_image_version >>
|
||||
WORDPRESS_IMAGE_VERSION: << parameters.wordpress_image_version >>
|
||||
steps:
|
||||
@ -411,6 +412,12 @@ jobs:
|
||||
circleci tests glob "tests/acceptance/**/*Cest.php" | circleci tests split --split-by=timings > tests/acceptance/_groups/circleci_split_group
|
||||
fi
|
||||
cat tests/acceptance/_groups/circleci_split_group
|
||||
- run:
|
||||
name: Create docker containers for test
|
||||
# We experienced some failures when creating containers so we do it explicitly with one retry
|
||||
command: |
|
||||
cd tests/docker
|
||||
docker-compose create || docker-compose create
|
||||
- run:
|
||||
name: Run acceptance tests
|
||||
command: |
|
||||
@ -477,8 +484,11 @@ jobs:
|
||||
working_directory: /home/circleci/mailpoet/mailpoet
|
||||
machine:
|
||||
image: ubuntu-2204:2022.10.2
|
||||
docker_layer_caching: false
|
||||
environment:
|
||||
CODECEPTION_IMAGE_VERSION: << parameters.codeception_image_version >>
|
||||
MYSQL_COMMAND: << parameters.mysql_command >>
|
||||
MYSQL_IMAGE: << parameters.mysql_image >>
|
||||
parameters:
|
||||
codeception_image_version:
|
||||
type: string
|
||||
@ -501,6 +511,12 @@ jobs:
|
||||
multisite:
|
||||
type: integer
|
||||
default: 0
|
||||
mysql_command:
|
||||
type: string
|
||||
default: ''
|
||||
mysql_image:
|
||||
type: string
|
||||
default: ''
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /home/circleci
|
||||
@ -618,7 +634,7 @@ workflows:
|
||||
- build
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_tests
|
||||
name: acceptance_tests_base_and_woo_cot_off
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
@ -646,28 +662,10 @@ workflows:
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_tests_woo_cot_off
|
||||
group: woo
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- js_tests:
|
||||
<<: *slack-fail-post-step
|
||||
requires:
|
||||
- build
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
group: woo
|
||||
name: integration_test_woocommerce
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
group: woo
|
||||
@ -728,15 +726,13 @@ workflows:
|
||||
<<: *slack-fail-post-step
|
||||
requires:
|
||||
- build
|
||||
- acceptance_tests
|
||||
- acceptance_tests_base_and_woo_cot_off
|
||||
- js_tests
|
||||
- integration_test_woocommerce
|
||||
- integration_test_base
|
||||
- integration_test_woo_cot_no_sync
|
||||
- integration_test_woo_cot_off
|
||||
- integration_test_woo_cot_sync
|
||||
- acceptance_tests_woo_cot_sync
|
||||
- acceptance_tests_woo_cot_off
|
||||
- acceptance_tests_woo_cot_no_sync
|
||||
|
||||
nightly:
|
||||
@ -766,8 +762,8 @@ workflows:
|
||||
woo_subscriptions_version: 4.3.0
|
||||
woo_memberships_version: 1.21.0
|
||||
woo_blocks_version: 6.8.0
|
||||
mysql_command: --max_allowed_packet=100M
|
||||
mysql_image_version: 5.7.36
|
||||
mysql_command: --max_allowed_packet=100M --default-storage-engine=MYISAM
|
||||
mysql_image: mysql:5.5
|
||||
codeception_image_version: 7.4-cli_20220605.0
|
||||
wordpress_image_version: wp-5.8_php7.3_20221104.1
|
||||
requires:
|
||||
@ -792,6 +788,8 @@ workflows:
|
||||
<<: *slack-fail-post-step
|
||||
name: integration_oldest
|
||||
codeception_image_version: 7.2-cli_20220605.0
|
||||
mysql_command: --max_allowed_packet=100M --default-storage-engine=MYISAM
|
||||
mysql_image: mysql:5.5
|
||||
requires:
|
||||
- build
|
||||
- build_premium:
|
||||
|
15
README.md
15
README.md
@ -126,23 +126,24 @@ You can access this help in your command line running `./do` without parameters.
|
||||
|
||||
[Read the article.](https://mailpoet.atlassian.net/wiki/spaces/MAILPOET/pages/629374977/Adding+new+templates+to+the+plugin)
|
||||
|
||||
## 🚥 Testing with PHP 7.4 or PHP 8.0
|
||||
## 🚥 Testing with different PHP versions
|
||||
|
||||
To switch the environment to PHP 7.4/8.0:
|
||||
To switch the environment to a different PHP version:
|
||||
|
||||
1. Configure the `wordpress` service in `docker-compose.override.yml` to build from the php74 Dockerfile:
|
||||
1. Check https://github.com/mailpoet/mailpoet/tree/trunk/dev for a list of available PHP versions. Each directory starting with `php` corresponds to a available version.
|
||||
2. Configure the `wordpress` service in `docker-compose.override.yml` to build from the desired PHP version Dockerfile (replace {PHP_VERSION} with the name of the directory that corresponds to the version that you want to use):
|
||||
|
||||
```yaml
|
||||
wordpress:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: dev/php74/Dockerfile # OR dev/php80/Dockerfile
|
||||
dockerfile: dev/{PHP_VERSION}/Dockerfile
|
||||
```
|
||||
|
||||
2. Run `docker-compose build wordpress`.
|
||||
3. Start the stack with `./do start`.
|
||||
3. Run `docker-compose build wordpress`.
|
||||
4. Start the stack with `./do start`.
|
||||
|
||||
To switch back to PHP 8.1 remove what was added in 1) and, run `docker-compose build wordpress` for application container and `docker-compose build test_wordpress` for tests container,
|
||||
To switch back to the default PHP version remove what was added in 2) and, run `docker-compose build wordpress` for application container and `docker-compose build test_wordpress` for tests container,
|
||||
and start the stack using `./do start`.
|
||||
|
||||
## ✅ TODO
|
||||
|
46
dev/php82/Dockerfile
Normal file
46
dev/php82/Dockerfile
Normal file
@ -0,0 +1,46 @@
|
||||
FROM php:8.2.0RC6-apache
|
||||
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
|
||||
# additinal extensions
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev gettext subversion \
|
||||
&& \
|
||||
# Install NodeJS, enable Corepack
|
||||
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
|
||||
apt-get install -y nodejs build-essential && \
|
||||
corepack enable && \
|
||||
\
|
||||
# Install WP-CLI
|
||||
curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
|
||||
chmod +x /usr/local/bin/wp && \
|
||||
\
|
||||
# Clean up
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY dev/php.ini /usr/local/etc/php/conf.d/php_user.ini
|
||||
|
||||
# msmtp config
|
||||
RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc
|
||||
|
||||
# xdebug build an config
|
||||
ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini
|
||||
RUN git clone -b "3.2.0RC2" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \
|
||||
&& docker-php-ext-configure xdebug --enable-xdebug-dev \
|
||||
&& docker-php-ext-install xdebug \
|
||||
&& mkdir /tmp/debug
|
||||
COPY dev/xdebug.ini /tmp/xdebug.ini
|
||||
RUN cat /tmp/xdebug.ini >> $XDEBUGINI_PATH
|
||||
|
||||
# php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install mysqli
|
||||
|
||||
# allow .htaccess files (between <Directory /var/www/> and </Directory>, which is WordPress installation)
|
||||
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
|
||||
|
||||
# ensure existing content in /var/www/html respects UID and GID, give Node permissions for Corepack
|
||||
RUN chown -R ${UID}:${GID} /var/www/html && \
|
||||
mkdir -p /.node && chown -R ${UID}:${GID} /.node
|
@ -15,6 +15,7 @@ services:
|
||||
volumes:
|
||||
- my-datavolume:/var/lib/mysql
|
||||
- ./dev/database/create_test_db.sh:/docker-entrypoint-initdb.d/10-create_test_db.sh
|
||||
command: --sql_mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,ANSI,ONLY_FULL_GROUP_BY
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: somewordpress
|
||||
MYSQL_DATABASE: wordpress
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
// phpcs:disable PSR1.Classes.ClassDeclaration
|
||||
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
||||
@ -425,6 +425,9 @@ class RoboFile extends \Robo\Tasks {
|
||||
$collection->addCode(function() {
|
||||
return $this->qaCodeSniffer([]);
|
||||
});
|
||||
$collection->addCode(function() {
|
||||
return $this->qaMinimalPluginStandard([]);
|
||||
});
|
||||
return $collection->run();
|
||||
}
|
||||
|
||||
@ -536,6 +539,55 @@ class RoboFile extends \Robo\Tasks {
|
||||
|
||||
$stringFilesToCheck = !empty($filesToCheck) ? implode(' ', $filesToCheck) : '.';
|
||||
|
||||
return $this->taskExec($task)
|
||||
->arg('--ignore=' . implode(',', $ignorePatterns))
|
||||
->rawArg($stringFilesToCheck)
|
||||
->run();
|
||||
}
|
||||
|
||||
public function qaMinimalPluginStandard(array $filesToCheck, $opts = ['severity' => 'all']) {
|
||||
$severityFlag = $opts['severity'] === 'all' ? '-w' : '-n';
|
||||
|
||||
$task = implode(' ', [
|
||||
'php -d memory_limit=-1',
|
||||
'./tasks/code_sniffer/vendor/bin/phpcs',
|
||||
'--extensions=php',
|
||||
$severityFlag,
|
||||
'--standard=tasks/code_sniffer/vendor/wporg/plugin-directory/MinimalPluginStandard',
|
||||
'-s',
|
||||
]);
|
||||
|
||||
$ignorePaths = [
|
||||
'.mp_svn',
|
||||
'assets',
|
||||
'doc',
|
||||
'generated',
|
||||
'lib/Config/PopulatorData/Templates',
|
||||
'lib-3rd-party',
|
||||
'node_modules',
|
||||
'plugin_repository',
|
||||
'prefixer/build',
|
||||
'prefixer/vendor',
|
||||
'tasks/code_sniffer/vendor',
|
||||
'tasks/phpstan/vendor',
|
||||
'tasks/makepot',
|
||||
'tools/vendor',
|
||||
'temp',
|
||||
'tests/_data',
|
||||
'tests/_output',
|
||||
'tests/_support/_generated',
|
||||
'vendor',
|
||||
'vendor-prefixed',
|
||||
'views',
|
||||
];
|
||||
|
||||
// the "--ignore" arg takes a list of regexes, we need to anchor and escape them
|
||||
$ignorePatterns = array_map(function (string $path): string {
|
||||
return '^' . preg_quote(__DIR__ . DIRECTORY_SEPARATOR . $path);
|
||||
}, $ignorePaths);
|
||||
|
||||
$stringFilesToCheck = !empty($filesToCheck) ? implode(' ', $filesToCheck) : '.';
|
||||
|
||||
return $this
|
||||
->taskExec($task)
|
||||
->arg('--ignore=' . implode(',', $ignorePatterns))
|
||||
|
BIN
mailpoet/assets/audio/0.mp3
Normal file
BIN
mailpoet/assets/audio/0.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/1.mp3
Normal file
BIN
mailpoet/assets/audio/1.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/2.mp3
Normal file
BIN
mailpoet/assets/audio/2.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/3.mp3
Normal file
BIN
mailpoet/assets/audio/3.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/4.mp3
Normal file
BIN
mailpoet/assets/audio/4.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/5.mp3
Normal file
BIN
mailpoet/assets/audio/5.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/6.mp3
Normal file
BIN
mailpoet/assets/audio/6.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/7.mp3
Normal file
BIN
mailpoet/assets/audio/7.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/8.mp3
Normal file
BIN
mailpoet/assets/audio/8.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/9.mp3
Normal file
BIN
mailpoet/assets/audio/9.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/a.mp3
Normal file
BIN
mailpoet/assets/audio/a.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/b.mp3
Normal file
BIN
mailpoet/assets/audio/b.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/c.mp3
Normal file
BIN
mailpoet/assets/audio/c.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/d.mp3
Normal file
BIN
mailpoet/assets/audio/d.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/e.mp3
Normal file
BIN
mailpoet/assets/audio/e.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/f.mp3
Normal file
BIN
mailpoet/assets/audio/f.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/g.mp3
Normal file
BIN
mailpoet/assets/audio/g.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/h.mp3
Normal file
BIN
mailpoet/assets/audio/h.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/i.mp3
Normal file
BIN
mailpoet/assets/audio/i.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/j.mp3
Normal file
BIN
mailpoet/assets/audio/j.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/k.mp3
Normal file
BIN
mailpoet/assets/audio/k.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/l.mp3
Normal file
BIN
mailpoet/assets/audio/l.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/m.mp3
Normal file
BIN
mailpoet/assets/audio/m.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/n.mp3
Normal file
BIN
mailpoet/assets/audio/n.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/o.mp3
Normal file
BIN
mailpoet/assets/audio/o.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/p.mp3
Normal file
BIN
mailpoet/assets/audio/p.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/q.mp3
Normal file
BIN
mailpoet/assets/audio/q.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/r.mp3
Normal file
BIN
mailpoet/assets/audio/r.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/s.mp3
Normal file
BIN
mailpoet/assets/audio/s.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/t.mp3
Normal file
BIN
mailpoet/assets/audio/t.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/u.mp3
Normal file
BIN
mailpoet/assets/audio/u.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/v.mp3
Normal file
BIN
mailpoet/assets/audio/v.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/w.mp3
Normal file
BIN
mailpoet/assets/audio/w.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/x.mp3
Normal file
BIN
mailpoet/assets/audio/x.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/y.mp3
Normal file
BIN
mailpoet/assets/audio/y.mp3
Normal file
Binary file not shown.
BIN
mailpoet/assets/audio/z.mp3
Normal file
BIN
mailpoet/assets/audio/z.mp3
Normal file
Binary file not shown.
@ -36,7 +36,8 @@
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
textarea,
|
||||
input[type='text'].components-form-token-field__input {
|
||||
background: right top/26px no-repeat url('../../img/icons/alert.svg');
|
||||
padding-right: 26px;
|
||||
}
|
||||
|
@ -51,4 +51,8 @@
|
||||
.mailpoet_form_field_block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mailpoet_form_field_input_nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
@ -122,3 +122,21 @@ body .components-modal__screen-overlay {
|
||||
justify-content: flex-end;
|
||||
margin-top: $grid-gap-half;
|
||||
}
|
||||
|
||||
.mailpoet-locked-badge {
|
||||
align-items: center;
|
||||
background: #fcf9e8;
|
||||
border: .5px solid #f5e6ab;
|
||||
border-radius: 4px;
|
||||
color: #bd8600;
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
gap: 4px;
|
||||
height: 20px;
|
||||
letter-spacing: .2px;
|
||||
line-height: 16px;
|
||||
padding: 2px 8px 2px 4px;
|
||||
text-transform: uppercase;
|
||||
width: 82px;
|
||||
}
|
||||
|
@ -22,6 +22,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
#mailpoet-wizard-container {
|
||||
.mailpoet-top-bar {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-wizard-logo {
|
||||
margin-bottom: 100px;
|
||||
text-align: center;
|
||||
@ -35,6 +41,7 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
|
||||
@include respond-to(medium-screen) {
|
||||
flex-direction: column;
|
||||
@ -42,7 +49,7 @@
|
||||
}
|
||||
|
||||
.mailpoet-wizard-step-illustration {
|
||||
margin-right: $grid-gap;
|
||||
margin-right: $grid-gap-xl;
|
||||
max-width: $grid-column;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
@ -63,12 +70,16 @@
|
||||
}
|
||||
|
||||
.mailpoet-wizard-step-content {
|
||||
max-width: $grid-column-small + $grid-gap + $grid-column;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
|
||||
@include respond-to(medium-screen) {
|
||||
max-width: $grid-column;
|
||||
}
|
||||
|
||||
.mailpoet-button {
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-wizard-label {
|
||||
@ -111,12 +122,16 @@
|
||||
|
||||
.mailpoet-wizard-woocommerce-option {
|
||||
align-items: center;
|
||||
box-shadow: 0 -1px 0 0 $color-tertiary-light;
|
||||
box-shadow: 0 1px 0 0 $color-tertiary-light;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 25px;
|
||||
padding-top: 1px;
|
||||
|
||||
&:last-child {
|
||||
box-shadow: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-wizard-note {
|
||||
|
12
mailpoet/assets/css/src/components-public/captcha.scss
Normal file
12
mailpoet/assets/css/src/components-public/captcha.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.mailpoet_captcha_form {
|
||||
.mailpoet_icon_button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
}
|
@ -112,3 +112,11 @@
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.authorize-sender-email-and-domain-modal {
|
||||
z-index: 30; // overlay other modals
|
||||
}
|
||||
|
||||
.authorize-sender-email-and-domain-modal-overlay {
|
||||
z-index: $modal-screen-overlay-z-index + 4; // overlay other modals
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ $beamer-dot-size: 8px;
|
||||
}
|
||||
|
||||
.mailpoet-top-bar-logo {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
z-index: 1;
|
||||
@ -39,6 +38,10 @@ $beamer-dot-size: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
a.mailpoet-top-bar-logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-top-bar-logo-desktop {
|
||||
|
@ -19,3 +19,4 @@
|
||||
@import 'components-public/public';
|
||||
@import 'components-public/animation';
|
||||
@import 'components-public/form_colors';
|
||||
@import 'components-public/captcha';
|
||||
|
1
mailpoet/assets/img/icons/controls-volumeon.svg
Normal file
1
mailpoet/assets/img/icons/controls-volumeon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g><path d="M2 7h4l5-4v14l-5-4H2V7zm12.69-2.46C14.82 4.59 18 5.92 18 10s-3.18 5.41-3.31 5.46c-.06.03-.13.04-.19.04-.2 0-.39-.12-.46-.31-.11-.26.02-.55.27-.65.11-.05 2.69-1.15 2.69-4.54 0-3.41-2.66-4.53-2.69-4.54-.25-.1-.38-.39-.27-.65.1-.25.39-.38.65-.27zM16 10c0 2.57-2.23 3.43-2.32 3.47-.06.02-.12.03-.18.03-.2 0-.39-.12-.47-.32-.1-.26.04-.55.29-.65.07-.02 1.68-.67 1.68-2.53s-1.61-2.51-1.68-2.53c-.25-.1-.38-.39-.29-.65.1-.25.39-.39.65-.29.09.04 2.32.9 2.32 3.47z"/></g></svg>
|
After Width: | Height: | Size: 587 B |
1
mailpoet/assets/img/icons/image-rotate.svg
Normal file
1
mailpoet/assets/img/icons/image-rotate.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g><path d="M10.25 1.02c5.1 0 8.75 4.04 8.75 9s-3.65 9-8.75 9c-3.2 0-6.02-1.59-7.68-3.99l2.59-1.52c1.1 1.5 2.86 2.51 4.84 2.51 3.3 0 6-2.79 6-6s-2.7-6-6-6c-1.97 0-3.72 1-4.82 2.49L7 8.02l-6 2v-7L2.89 4.6c1.69-2.17 4.36-3.58 7.36-3.58z"/></g></svg>
|
After Width: | Height: | Size: 355 B |
@ -1,12 +1,10 @@
|
||||
import { useState, Fragment } from 'react';
|
||||
import { DropdownMenu } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { moreVertical, trash } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { PremiumModal } from 'common/premium_modal';
|
||||
import { Step as StepData } from './types';
|
||||
import { storeName } from '../../store';
|
||||
import { StepMoreControlsType } from '../../../types/filters';
|
||||
|
||||
type Props = {
|
||||
@ -14,12 +12,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export function StepMoreMenu({ step }: Props): JSX.Element {
|
||||
const { stepType } = useSelect(
|
||||
(select) => ({
|
||||
stepType: select(storeName).getStepType(step.key),
|
||||
}),
|
||||
[step],
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const moreControls: StepMoreControlsType = Hooks.applyFilters(
|
||||
@ -53,7 +45,6 @@ export function StepMoreMenu({ step }: Props): JSX.Element {
|
||||
},
|
||||
},
|
||||
step,
|
||||
stepType,
|
||||
);
|
||||
|
||||
const slots = Object.values(moreControls).filter(
|
||||
|
@ -21,7 +21,7 @@ const Dropdown: ComponentType<
|
||||
}
|
||||
> = WpDropdown;
|
||||
|
||||
export function DocumentActions({ children }): JSX.Element {
|
||||
function DocumentActions({ children }): JSX.Element {
|
||||
const { automationName, automationStatus, showIconLabels } = useSelect(
|
||||
(select) => ({
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
@ -101,3 +101,6 @@ export function DocumentActions({ children }): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DocumentActions.displayName = 'DocumentActions';
|
||||
export { DocumentActions };
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { createContext } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { ErrorBoundary } from 'common';
|
||||
import { Chip } from '../chip';
|
||||
import { ColoredIcon } from '../icons';
|
||||
import {
|
||||
@ -70,6 +71,8 @@ function StepError({ stepId }: StepErrorProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
StepError.displayName = 'StepError';
|
||||
|
||||
export function Errors(): JSX.Element | null {
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
|
||||
@ -160,9 +163,11 @@ export function Errors(): JSX.Element | null {
|
||||
__('The following steps are not fully set:', 'mailpoet')
|
||||
}
|
||||
</div>
|
||||
{stepErrors.map((error) => (
|
||||
<StepError key={error.step_id} stepId={error.step_id} />
|
||||
))}
|
||||
<ErrorBoundary>
|
||||
{stepErrors.map((error) => (
|
||||
<StepError key={error.step_id} stepId={error.step_id} />
|
||||
))}
|
||||
</ErrorBoundary>
|
||||
</Composite>
|
||||
</ErrorsCompositeContext.Provider>
|
||||
</Popover>
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
import { dispatch, useDispatch, useSelect } from '@wordpress/data';
|
||||
import { PinnedItems } from '@wordpress/interface';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { ErrorBoundary } from 'common';
|
||||
import { DocumentActions } from './document_actions';
|
||||
import { Errors } from './errors';
|
||||
import { InserterToggle } from './inserter_toggle';
|
||||
@ -227,28 +228,32 @@ export function Header({ showInserterToggle }: Props): JSX.Element {
|
||||
</div>
|
||||
|
||||
<div className="edit-site-header_center">
|
||||
<DocumentActions>
|
||||
{() => (
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit">
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit-title">
|
||||
{__('Automation name', 'mailpoet')}
|
||||
<ErrorBoundary>
|
||||
<DocumentActions>
|
||||
{() => (
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit">
|
||||
<div className="mailpoet-automation-editor-dropdown-name-edit-title">
|
||||
{__('Automation name', 'mailpoet')}
|
||||
</div>
|
||||
<TextControl
|
||||
value={automationName}
|
||||
onChange={(newName) => setAutomationName(newName)}
|
||||
help={__(
|
||||
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
||||
'mailpoet',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<TextControl
|
||||
value={automationName}
|
||||
onChange={(newName) => setAutomationName(newName)}
|
||||
help={__(
|
||||
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
||||
'mailpoet',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DocumentActions>
|
||||
)}
|
||||
</DocumentActions>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<div className="edit-site-header_end">
|
||||
<div className="edit-site-header__actions">
|
||||
<Errors />
|
||||
<ErrorBoundary>
|
||||
<Errors />
|
||||
</ErrorBoundary>
|
||||
{automationStatus === AutomationStatus.DRAFT && (
|
||||
<>
|
||||
<SaveDraftButton />
|
||||
|
@ -0,0 +1,2 @@
|
||||
export * from './panel';
|
||||
export * from './form-token-field';
|
@ -1,7 +1,10 @@
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { storeName } from './constants';
|
||||
import { StepType } from './types';
|
||||
|
||||
export const registerStepType = (stepType: StepType): void => {
|
||||
dispatch(storeName).registerStepType(stepType);
|
||||
dispatch(storeName).registerStepType(
|
||||
Hooks.applyFilters('mailpoet.automation.register_step_type', stepType),
|
||||
);
|
||||
};
|
||||
|
@ -2,11 +2,19 @@ import { registerStepType } from '../../editor/store';
|
||||
import { step as SendEmailStep } from './steps/send_email';
|
||||
import { step as SomeoneSubscribesTrigger } from './steps/someone-subscribes';
|
||||
import { step as WpUserRegisteredTrigger } from './steps/wp-user-registered';
|
||||
import { step as AddTagsAction } from './steps/add_tags';
|
||||
import { step as RemoveTagsAction } from './steps/remove_tags';
|
||||
import { step as AddToListStep } from './steps/add_to_list';
|
||||
import { step as RemoveFromListStep } from './steps/remove_from_list';
|
||||
import { registerStepControls } from './step-controls';
|
||||
|
||||
export const initialize = (): void => {
|
||||
registerStepType(SendEmailStep);
|
||||
registerStepType(WpUserRegisteredTrigger);
|
||||
registerStepType(SomeoneSubscribesTrigger);
|
||||
registerStepType(AddTagsAction);
|
||||
registerStepType(RemoveTagsAction);
|
||||
registerStepType(AddToListStep);
|
||||
registerStepType(RemoveFromListStep);
|
||||
registerStepControls();
|
||||
};
|
||||
|
@ -2,7 +2,6 @@ import { __ } from '@wordpress/i18n';
|
||||
import { chartBar } from '@wordpress/icons';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { MoreControlType, StepMoreControlsType } from '../../../types/filters';
|
||||
import { StepType } from '../../../editor/store';
|
||||
import { Step } from '../../../editor/components/automation/types';
|
||||
|
||||
const emailStatisticsControl = (step: Step): MoreControlType => {
|
||||
@ -30,12 +29,8 @@ export function registerStepControls() {
|
||||
Hooks.addFilter(
|
||||
'mailpoet.automation.step.more-controls',
|
||||
'mailpoet',
|
||||
(
|
||||
controls: StepMoreControlsType,
|
||||
step: Step,
|
||||
stepType: StepType,
|
||||
): StepMoreControlsType => {
|
||||
if (stepType.key === 'mailpoet:send-email') {
|
||||
(controls: StepMoreControlsType, step: Step): StepMoreControlsType => {
|
||||
if (step.key === 'mailpoet:send-email') {
|
||||
return {
|
||||
statistics: emailStatisticsControl(step),
|
||||
...controls,
|
||||
|
@ -0,0 +1,28 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { tag } from '@wordpress/icons';
|
||||
import { StepType } from '../../../../editor/store';
|
||||
import { LockedBadge } from '../../../../../common/premium_modal/locked_badge';
|
||||
import { PremiumModalForStepEdit } from '../../../../../common/premium_modal';
|
||||
|
||||
export const step: StepType = {
|
||||
key: 'mailpoet:add-tag',
|
||||
group: 'actions',
|
||||
title: __('Add tag', 'mailpoet'),
|
||||
description: __('Add a tag or multiple tags to a subscriber.', 'mailpoet'),
|
||||
subtitle: () => <LockedBadge text={__('Premium', 'mailpoet')} />,
|
||||
foreground: '#00A32A',
|
||||
background: '#EDFAEF',
|
||||
icon: () => (
|
||||
<div style={{ width: '100%', height: '100%', scale: '1.4' }}>{tag}</div>
|
||||
),
|
||||
edit: () => (
|
||||
<PremiumModalForStepEdit
|
||||
tracking={{
|
||||
utm_medium: 'upsell_modal',
|
||||
utm_campaign: 'create_automation_editor_add_tag',
|
||||
}}
|
||||
>
|
||||
{__('Adding tags is a premium feature.', 'mailpoet')}
|
||||
</PremiumModalForStepEdit>
|
||||
),
|
||||
} as const;
|
@ -0,0 +1,28 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { list } from '@wordpress/icons';
|
||||
import { StepType } from '../../../../editor/store/types';
|
||||
import { PremiumModalForStepEdit } from '../../../../../common/premium_modal';
|
||||
import { LockedBadge } from '../../../../../common/premium_modal/locked_badge';
|
||||
|
||||
export const step: StepType = {
|
||||
key: 'mailpoet:add-to-list',
|
||||
group: 'actions',
|
||||
title: __('Add to list', 'mailpoet'),
|
||||
description: __('Add a subscriber to a list.', 'mailpoet'),
|
||||
subtitle: () => <LockedBadge text={__('Premium', 'mailpoet')} />,
|
||||
foreground: '#00A32A',
|
||||
background: '#EDFAEF',
|
||||
icon: () => (
|
||||
<div style={{ width: '100%', height: '100%', scale: '1.12' }}>{list}</div>
|
||||
),
|
||||
edit: () => (
|
||||
<PremiumModalForStepEdit
|
||||
tracking={{
|
||||
utm_medium: 'upsell_modal',
|
||||
utm_campaign: 'create_automation_editor_add_to_list',
|
||||
}}
|
||||
>
|
||||
{__('Adding subscribers to lists is a premium feature.', 'mailpoet')}
|
||||
</PremiumModalForStepEdit>
|
||||
),
|
||||
} as const;
|
@ -0,0 +1,28 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { list } from '@wordpress/icons';
|
||||
import { StepType } from '../../../../editor/store/types';
|
||||
import { PremiumModalForStepEdit } from '../../../../../common/premium_modal';
|
||||
import { LockedBadge } from '../../../../../common/premium_modal/locked_badge';
|
||||
|
||||
export const step: StepType = {
|
||||
key: 'mailpoet:remove-from-list',
|
||||
group: 'actions',
|
||||
title: __('Remove from list', 'mailpoet'),
|
||||
description: __('Remove a subscriber from a list.', 'mailpoet'),
|
||||
subtitle: () => <LockedBadge text={__('Premium', 'mailpoet')} />,
|
||||
foreground: '#00A32A',
|
||||
background: '#EDFAEF',
|
||||
icon: () => (
|
||||
<div style={{ width: '100%', height: '100%', scale: '1.12' }}>{list}</div>
|
||||
),
|
||||
edit: () => (
|
||||
<PremiumModalForStepEdit
|
||||
tracking={{
|
||||
utm_medium: 'upsell_modal',
|
||||
utm_campaign: 'create_automation_editor_remove_from_list',
|
||||
}}
|
||||
>
|
||||
{__('Removing subscribers from lists is a premium feature.', 'mailpoet')}
|
||||
</PremiumModalForStepEdit>
|
||||
),
|
||||
} as const;
|
@ -0,0 +1,31 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { tag } from '@wordpress/icons';
|
||||
import { StepType } from '../../../../editor/store';
|
||||
import { LockedBadge } from '../../../../../common/premium_modal/locked_badge';
|
||||
import { PremiumModalForStepEdit } from '../../../../../common/premium_modal';
|
||||
|
||||
export const step: StepType = {
|
||||
key: 'mailpoet:remove-tag',
|
||||
group: 'actions',
|
||||
title: __('Remove tag', 'mailpoet'),
|
||||
description: __(
|
||||
'Remove a tag or multiple tags from a subscriber',
|
||||
'mailpoet',
|
||||
),
|
||||
subtitle: () => <LockedBadge text={__('Premium', 'mailpoet')} />,
|
||||
foreground: '#00A32A',
|
||||
background: '#EDFAEF',
|
||||
icon: () => (
|
||||
<div style={{ width: '100%', height: '100%', scale: '1.4' }}>{tag}</div>
|
||||
),
|
||||
edit: () => (
|
||||
<PremiumModalForStepEdit
|
||||
tracking={{
|
||||
utm_medium: 'upsell_modal',
|
||||
utm_campaign: 'create_automation_editor_remove_tag',
|
||||
}}
|
||||
>
|
||||
{__('Removing tags is a premium feature.', 'mailpoet')}
|
||||
</PremiumModalForStepEdit>
|
||||
),
|
||||
} as const;
|
@ -3,8 +3,10 @@ import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
import { segments } from './segment';
|
||||
import { PlainBodyTitle } from '../../../../../editor/components/panel';
|
||||
import { FormTokenField } from '../../../components/form-token-field';
|
||||
import {
|
||||
PlainBodyTitle,
|
||||
FormTokenField,
|
||||
} from '../../../../../editor/components';
|
||||
|
||||
export function ListPanel(): JSX.Element {
|
||||
const { selectedStep } = useSelect(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FormTokenItem } from '../../../components/form-token-field';
|
||||
import { FormTokenItem } from '../../../../../editor/components';
|
||||
|
||||
type Segment = FormTokenItem & {
|
||||
type: string;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FormTokenItem } from '../../../components/form-token-field';
|
||||
import { FormTokenItem } from '../../../../../editor/components';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -3,9 +3,11 @@ import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import ReactStringReplace from 'react-string-replace';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
import { PlainBodyTitle } from '../../../../../editor/components/panel';
|
||||
import {
|
||||
PlainBodyTitle,
|
||||
FormTokenField,
|
||||
} from '../../../../../editor/components';
|
||||
import { userRoles } from './role';
|
||||
import { FormTokenField } from '../../../components/form-token-field';
|
||||
|
||||
function SettingsInfoText(): JSX.Element {
|
||||
return (
|
||||
|
@ -92,6 +92,7 @@ function AuthorizeSenderEmailAndDomainModal({
|
||||
<Modal
|
||||
onRequestClose={onRequestClose}
|
||||
contentClassName="authorize-sender-email-and-domain-modal"
|
||||
overlayClassName="authorize-sender-email-and-domain-modal-overlay"
|
||||
>
|
||||
<Tabs activeKey={initialTab}>
|
||||
<Tab
|
||||
|
@ -6,7 +6,7 @@ type Props = {
|
||||
active: string;
|
||||
};
|
||||
|
||||
export function Categories({ onSelect, categories, active }: Props) {
|
||||
function Categories({ onSelect, categories, active }: Props) {
|
||||
const cats = categories.map((category) => (
|
||||
<CategoriesItem
|
||||
{...category}
|
||||
@ -18,3 +18,6 @@ export function Categories({ onSelect, categories, active }: Props) {
|
||||
|
||||
return <div className="mailpoet-categories">{cats}</div>;
|
||||
}
|
||||
|
||||
Categories.displayName = 'Categories';
|
||||
export { Categories };
|
||||
|
@ -1,6 +1,7 @@
|
||||
import classnames from 'classnames';
|
||||
import ReactDatePicker, { ReactDatePickerProps } from 'react-datepicker';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { withBoundary } from '../error_boundary';
|
||||
|
||||
type Props = ReactDatePickerProps & {
|
||||
dimension?: 'small';
|
||||
@ -9,7 +10,7 @@ type Props = ReactDatePickerProps & {
|
||||
iconEnd?: JSX.Element;
|
||||
};
|
||||
|
||||
export function Datepicker({
|
||||
function Datepicker({
|
||||
dimension,
|
||||
isFullWidth,
|
||||
iconStart,
|
||||
@ -34,3 +35,8 @@ export function Datepicker({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Datepicker.displayName = 'Datepicker';
|
||||
const DatepickerWithBoundary = withBoundary(Datepicker);
|
||||
|
||||
export { DatepickerWithBoundary as Datepicker };
|
||||
|
@ -0,0 +1,68 @@
|
||||
import { Children, Component } from '@wordpress/element';
|
||||
import { ComponentType } from 'react';
|
||||
import { getComponentDisplayName } from './utils';
|
||||
|
||||
type ErrorBoundaryState = {
|
||||
hasError: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export type ErrorBoundaryProps = {
|
||||
onError?: (Error_Boundary_State) => ComponentType;
|
||||
};
|
||||
|
||||
export class ErrorBoundary extends Component<
|
||||
ErrorBoundaryProps,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hasError: false,
|
||||
error: '',
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error & { fileName?: string }) {
|
||||
return {
|
||||
hasError: true,
|
||||
error: `${error?.toString()} \nFile name: ${
|
||||
error?.fileName ?? 'No fileName reported'
|
||||
} \nStack trace: ${error?.stack ?? 'No stack trace reported'}`,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('logging', { error, errorInfo });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onError } = this.props;
|
||||
const { hasError, error } = this.state;
|
||||
if (hasError) {
|
||||
if (onError) {
|
||||
return onError(this.state);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<h1>
|
||||
The application{' '}
|
||||
<strong>
|
||||
{Children.map(this.props.children, (child) =>
|
||||
getComponentDisplayName(child as ComponentType),
|
||||
).join(', ')}{' '}
|
||||
</strong>
|
||||
encountered an error
|
||||
</h1>
|
||||
<p>
|
||||
Please report the following error to{' '}
|
||||
<a href="https://www.mailpoet.com/support/">MailPoet support</a>
|
||||
</p>
|
||||
<pre>{error}</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
3
mailpoet/assets/js/src/common/error_boundary/index.ts
Normal file
3
mailpoet/assets/js/src/common/error_boundary/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './utils';
|
||||
export * from './error_boundary';
|
||||
export * from './with_boundary';
|
4
mailpoet/assets/js/src/common/error_boundary/utils.ts
Normal file
4
mailpoet/assets/js/src/common/error_boundary/utils.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
export const getComponentDisplayName = (component: ComponentType): string =>
|
||||
component.displayName || component.name || 'Unknown application/component';
|
@ -0,0 +1,14 @@
|
||||
import { ComponentType, JSXElementConstructor } from 'react';
|
||||
import { ErrorBoundary, ErrorBoundaryProps } from './error_boundary';
|
||||
|
||||
export const withBoundary = <P extends Record<string, unknown> | unknown>(
|
||||
Bound: JSXElementConstructor<P>,
|
||||
props?: ErrorBoundaryProps,
|
||||
): ComponentType<P> =>
|
||||
function bounder(boundProps: P) {
|
||||
return (
|
||||
<ErrorBoundary {...props}>
|
||||
<Bound {...boundProps} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
@ -7,7 +7,7 @@ type Props = InputHTMLAttributes<HTMLInputElement> & {
|
||||
automationId?: string;
|
||||
};
|
||||
|
||||
export function Toggle({
|
||||
function Toggle({
|
||||
dimension,
|
||||
onCheck,
|
||||
automationId,
|
||||
@ -33,3 +33,7 @@ export function Toggle({
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
Toggle.displayName = 'FormToggle';
|
||||
|
||||
export { Toggle };
|
||||
|
@ -7,3 +7,4 @@ export * from './loading';
|
||||
export * from './form';
|
||||
export * from './tag';
|
||||
export * from './listings';
|
||||
export * from './error_boundary';
|
||||
|
@ -63,7 +63,7 @@ type NewsletterStatusProps = {
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export function NewsletterStatus({
|
||||
function NewsletterStatus({
|
||||
scheduledFor,
|
||||
processed,
|
||||
total,
|
||||
@ -150,3 +150,6 @@ export function NewsletterStatus({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NewsletterStatus.displayName = 'NewsletterStatus';
|
||||
export { NewsletterStatus };
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { withBoundary } from '../error_boundary';
|
||||
|
||||
export function UnsavedChangesNotice({ storeName }) {
|
||||
function UnsavedChangesNotice({ storeName }) {
|
||||
const hasUnsavedChanges = useSelect(
|
||||
(sel) => sel(storeName).hasUnsavedChanges(),
|
||||
[],
|
||||
@ -24,3 +25,7 @@ export function UnsavedChangesNotice({ storeName }) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
UnsavedChangesNotice.displayName = 'UnsavedChangesNotice';
|
||||
const UnsavedChangesNoticeWithBoundary = withBoundary(UnsavedChangesNotice);
|
||||
export { UnsavedChangesNoticeWithBoundary as UnsavedChangesNotice };
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
Button,
|
||||
Modal,
|
||||
} from '@wordpress/components';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import {
|
||||
@ -21,6 +22,8 @@ import {
|
||||
useUpgradeInfo,
|
||||
UtmParams,
|
||||
} from './upgrade_info';
|
||||
import { storeName } from '../../automation/editor/store';
|
||||
import { withBoundary } from '../error_boundary';
|
||||
|
||||
export const premiumValidAndActive =
|
||||
premiumFeaturesEnabled && MailPoet.premiumActive;
|
||||
@ -49,11 +52,7 @@ const getCta = (state: State, upgradeInfo: UpgradeInfo): string => {
|
||||
return cta;
|
||||
};
|
||||
|
||||
export function PremiumModal({
|
||||
children,
|
||||
tracking,
|
||||
...props
|
||||
}: Props): JSX.Element {
|
||||
function PremiumModal({ children, tracking, ...props }: Props): JSX.Element {
|
||||
const [state, setState] = useState<State>();
|
||||
const upgradeInfo = useUpgradeInfo(tracking);
|
||||
|
||||
@ -126,3 +125,44 @@ export function PremiumModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
PremiumModal.displayName = 'PremiumModal';
|
||||
|
||||
type EditProps = Omit<Props, 'onRequestClose'>;
|
||||
|
||||
function PremiumModalForStepEdit({
|
||||
children,
|
||||
...props
|
||||
}: EditProps): JSX.Element {
|
||||
const [showModal, setShowModal] = useState(true);
|
||||
useEffect(() => {
|
||||
if (showModal) {
|
||||
return;
|
||||
}
|
||||
const { selectStep } = dispatch(storeName);
|
||||
selectStep(undefined);
|
||||
setShowModal(true);
|
||||
}, [showModal]);
|
||||
if (!showModal) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<PremiumModal
|
||||
onRequestClose={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</PremiumModal>
|
||||
);
|
||||
}
|
||||
|
||||
PremiumModalForStepEdit.displayName = 'PremiumModalForStepEdit';
|
||||
const PremiumModalForStepEditWithBoundary = withBoundary(
|
||||
PremiumModalForStepEdit,
|
||||
);
|
||||
export {
|
||||
PremiumModal,
|
||||
PremiumModalForStepEditWithBoundary as PremiumModalForStepEdit,
|
||||
};
|
||||
|
31
mailpoet/assets/js/src/common/premium_modal/locked_badge.tsx
Normal file
31
mailpoet/assets/js/src/common/premium_modal/locked_badge.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
const lockIcon = (
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_1896_34966)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6 1.625C4.96447 1.625 4.125 2.46447 4.125 3.5V5H3.5C3.22386 5 3 5.22386 3 5.5V9.5C3 9.77614 3.22386 10 3.5 10H8.5C8.77614 10 9 9.77614 9 9.5V5.5C9 5.22386 8.77614 5 8.5 5H7.875V3.5C7.875 2.46447 7.03553 1.625 6 1.625ZM7.125 5V3.5C7.125 2.87868 6.62132 2.375 6 2.375C5.37868 2.375 4.875 2.87868 4.875 3.5V5H7.125Z"
|
||||
fill="#BD8600"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1896_34966">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export function LockedBadge({ text }): JSX.Element {
|
||||
return (
|
||||
<span className="mailpoet-locked-badge">
|
||||
{lockIcon} {text}
|
||||
</span>
|
||||
);
|
||||
}
|
@ -77,5 +77,5 @@ Preview.defaultProps = {
|
||||
onDisplayTypeChange: () => {},
|
||||
selectedDisplayType: 'desktop',
|
||||
};
|
||||
|
||||
Preview.displayName = 'FormEditorPreview';
|
||||
export { Preview };
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withBoundary } from './error_boundary';
|
||||
|
||||
function ScrollToTopComponent({ children, location: { pathname } }) {
|
||||
useEffect(() => {
|
||||
@ -9,4 +10,5 @@ function ScrollToTopComponent({ children, location: { pathname } }) {
|
||||
return children || null;
|
||||
}
|
||||
|
||||
export const ScrollToTop = withRouter(ScrollToTopComponent);
|
||||
ScrollToTopComponent.displayName = 'ScrollToTopComponent';
|
||||
export const ScrollToTop = withRouter(withBoundary(ScrollToTopComponent));
|
||||
|
@ -163,5 +163,5 @@ SenderEmailAddressWarning.defaultProps = {
|
||||
showSenderDomainWarning: false,
|
||||
onSuccessfulEmailOrDomainAuthorization: noop,
|
||||
};
|
||||
|
||||
SenderEmailAddressWarning.displayName = 'SenderEmailAddressWarning';
|
||||
export { SenderEmailAddressWarning };
|
||||
|
@ -1,5 +1,6 @@
|
||||
import range from 'lodash/range';
|
||||
import classnames from 'classnames';
|
||||
import { withBoundary } from 'common';
|
||||
import { ContentWrapperFix } from './content_wrapper_fix';
|
||||
|
||||
type Props = {
|
||||
@ -8,7 +9,7 @@ type Props = {
|
||||
titles?: string[];
|
||||
};
|
||||
|
||||
function Steps({ count, current, titles }: Props) {
|
||||
function StepsComponent({ count, current, titles }: Props) {
|
||||
return (
|
||||
<div className="mailpoet-steps">
|
||||
<ContentWrapperFix />
|
||||
@ -35,8 +36,9 @@ function Steps({ count, current, titles }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
Steps.defaultProps = {
|
||||
StepsComponent.defaultProps = {
|
||||
titles: [],
|
||||
};
|
||||
|
||||
StepsComponent.displayName = 'StepsComponent';
|
||||
const Steps = withBoundary(StepsComponent);
|
||||
export { Steps };
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from 'react-router-dom';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import { Tabs, Props as TabProps } from './tabs';
|
||||
import { Props as TabProps, Tabs } from './tabs';
|
||||
|
||||
function RouterAwareTabs(
|
||||
props: TabProps & {
|
||||
@ -105,4 +105,6 @@ function RoutedTabs({
|
||||
);
|
||||
}
|
||||
|
||||
RoutedTabs.displayName = 'RoutedTabs';
|
||||
|
||||
export { RoutedTabs };
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Heading } from 'common/typography/heading/heading';
|
||||
import { Tags } from '../tags';
|
||||
import { SegmentTags, StringTags } from '../tags';
|
||||
|
||||
export default {
|
||||
title: 'Tags',
|
||||
@ -18,15 +18,20 @@ export function Segments() {
|
||||
<>
|
||||
<Heading level={1}>Tags</Heading>
|
||||
<Heading level={2}>Segments</Heading>
|
||||
<Tags segments={segments} dimension="large" />
|
||||
<SegmentTags segments={segments} dimension="large" />
|
||||
<div className="mailpoet-gap" />
|
||||
<Tags segments={segments}>
|
||||
<SegmentTags segments={segments}>
|
||||
<span>Prefix: </span>
|
||||
</Tags>
|
||||
</SegmentTags>
|
||||
<Heading level={2}>Strings</Heading>
|
||||
<Tags strings={strings} dimension="large" variant="good" />
|
||||
<StringTags strings={strings} dimension="large" variant="good" />
|
||||
<div className="mailpoet-gap" />
|
||||
<Tags strings={strings} dimension="large" variant="good" isInverted />
|
||||
<StringTags
|
||||
strings={strings}
|
||||
dimension="large"
|
||||
variant="good"
|
||||
isInverted
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -3,11 +3,36 @@ import { Tag, TagVariant } from './tag';
|
||||
import { Tooltip } from '../tooltip/tooltip';
|
||||
import { MailPoet } from '../../mailpoet';
|
||||
|
||||
type SharedTagProps = {
|
||||
children?: ReactNode;
|
||||
dimension?: 'large';
|
||||
variant?: TagVariant;
|
||||
isInverted?: boolean;
|
||||
};
|
||||
|
||||
type TagData = {
|
||||
name: string;
|
||||
target?: string;
|
||||
tooltip?: string;
|
||||
};
|
||||
|
||||
type TagProps = SharedTagProps & {
|
||||
tags: TagData[];
|
||||
};
|
||||
|
||||
type StringTagsProps = SharedTagProps & {
|
||||
strings: string[];
|
||||
};
|
||||
|
||||
type Segment = {
|
||||
name: string;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
type SegmentTagsProps = SharedTagProps & {
|
||||
segments: Segment[];
|
||||
};
|
||||
|
||||
type SubscriberTag = {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -15,99 +40,96 @@ type SubscriberTag = {
|
||||
tag_id: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children?: ReactNode;
|
||||
dimension?: 'large';
|
||||
segments?: Segment[];
|
||||
subscriberTags?: SubscriberTag[];
|
||||
strings?: string[];
|
||||
variant?: TagVariant;
|
||||
isInverted?: boolean;
|
||||
type SubscriberTagsProps = SharedTagProps & {
|
||||
subscribers: SubscriberTag[];
|
||||
};
|
||||
|
||||
function Tags({
|
||||
children,
|
||||
dimension,
|
||||
segments,
|
||||
subscriberTags,
|
||||
strings,
|
||||
variant,
|
||||
isInverted,
|
||||
}: Props) {
|
||||
function Tags({ children, tags, dimension, variant, isInverted }: TagProps) {
|
||||
return (
|
||||
<div className="mailpoet-tags">
|
||||
{children}
|
||||
{segments &&
|
||||
segments.map((segment) => {
|
||||
const tag = (
|
||||
<Tag key={segment.name} dimension={dimension} variant="list">
|
||||
{segment.name}
|
||||
</Tag>
|
||||
);
|
||||
if (!segment.id) {
|
||||
return tag;
|
||||
}
|
||||
const randomId = Math.random().toString(36).substring(2, 15);
|
||||
const tooltipId = `segment-tooltip-${randomId}`;
|
||||
|
||||
return (
|
||||
<div key={randomId}>
|
||||
<Tooltip id={tooltipId} place="top">
|
||||
{MailPoet.I18n.t('viewFilteredSubscribersMessage')}
|
||||
</Tooltip>
|
||||
<a
|
||||
data-tip=""
|
||||
data-for={tooltipId}
|
||||
href={`admin.php?page=mailpoet-subscribers#/filter[segment=${segment.id}]`}
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{subscriberTags &&
|
||||
subscriberTags.map((subscriberTag) => {
|
||||
const randomId = Math.random().toString(36).substring(2, 15);
|
||||
const tooltipId = `tag-tooltip-${randomId}`;
|
||||
const tag = (
|
||||
<Tag
|
||||
key={subscriberTag.name}
|
||||
dimension={dimension}
|
||||
variant={variant || 'list'}
|
||||
isInverted={isInverted}
|
||||
>
|
||||
{subscriberTag.name}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={randomId}>
|
||||
<Tooltip id={tooltipId} place="top">
|
||||
{MailPoet.I18n.t('viewFilteredSubscribersMessage')}
|
||||
</Tooltip>
|
||||
<a
|
||||
data-tip=""
|
||||
data-for={tooltipId}
|
||||
href={`admin.php?page=mailpoet-subscribers#/filter[tag=${subscriberTag.tag_id}]`}
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{strings &&
|
||||
strings.map((string) => (
|
||||
{tags.map((item) => {
|
||||
const tag = (
|
||||
<Tag
|
||||
key={string}
|
||||
key={item.name}
|
||||
dimension={dimension}
|
||||
variant={variant || 'list'} // due to backward compatibility we use `list` as the default value
|
||||
variant={variant || 'list'}
|
||||
isInverted={isInverted}
|
||||
>
|
||||
{string}
|
||||
{item.name}
|
||||
</Tag>
|
||||
))}
|
||||
);
|
||||
if (!item.target) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
const randomId = Math.random().toString(36).substring(2, 15);
|
||||
const tooltipId = `segment-tooltip-${randomId}`;
|
||||
return (
|
||||
<div key={randomId}>
|
||||
{item.tooltip && (
|
||||
<Tooltip id={tooltipId} place="top">
|
||||
{MailPoet.I18n.t('viewFilteredSubscribersMessage')}
|
||||
</Tooltip>
|
||||
)}
|
||||
<a data-tip="" data-for={tooltipId} href={item.target}>
|
||||
{tag}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { Tags };
|
||||
Tags.displayName = 'Tags';
|
||||
|
||||
function StringTags({ children, strings, ...props }: StringTagsProps) {
|
||||
const tags: TagData[] = strings.map((item) => ({
|
||||
name: item,
|
||||
}));
|
||||
return (
|
||||
<Tags tags={tags} {...props}>
|
||||
{children}
|
||||
</Tags>
|
||||
);
|
||||
}
|
||||
|
||||
StringTags.displayName = 'StringTags';
|
||||
|
||||
function SegmentTags({ children, segments, ...props }: SegmentTagsProps) {
|
||||
const tags: TagData[] = segments.map((segment) => ({
|
||||
name: segment.name,
|
||||
target: segment.id
|
||||
? `admin.php?page=mailpoet-subscribers#/filter[segment=${segment.id}]`
|
||||
: undefined,
|
||||
tooltip: MailPoet.I18n.t('viewFilteredSubscribersMessage'),
|
||||
}));
|
||||
return (
|
||||
<Tags tags={tags} {...props}>
|
||||
{children}
|
||||
</Tags>
|
||||
);
|
||||
}
|
||||
|
||||
SegmentTags.displayName = 'SegmentTags';
|
||||
|
||||
function SubscriberTags({
|
||||
children,
|
||||
subscribers,
|
||||
...props
|
||||
}: SubscriberTagsProps) {
|
||||
const tags: TagData[] = subscribers.map((item) => ({
|
||||
name: item.name,
|
||||
target: `admin.php?page=mailpoet-subscribers#/filter[tag=${item.tag_id}]`,
|
||||
tooltip: MailPoet.I18n.t('viewFilteredSubscribersMessage'),
|
||||
}));
|
||||
return (
|
||||
<Tags tags={tags} {...props}>
|
||||
{children}
|
||||
</Tags>
|
||||
);
|
||||
}
|
||||
|
||||
SubscriberTags.displayName = 'SubscriberTags';
|
||||
export { SegmentTags, StringTags, SubscriberTags };
|
||||
|
@ -5,36 +5,54 @@ import { MailPoetLogoMobile } from './mailpoet_logo_mobile';
|
||||
|
||||
type Props = {
|
||||
onClick?: () => void;
|
||||
withLink?: boolean;
|
||||
};
|
||||
export function MailPoetLogoResponsive({ onClick }: Props) {
|
||||
export function MailPoetLogoResponsive({ onClick, withLink = true }: Props) {
|
||||
const history = useHistory();
|
||||
let logo;
|
||||
let onLogoClick = onClick;
|
||||
if (!onClick) {
|
||||
onLogoClick = () => history.push('/');
|
||||
}
|
||||
return (
|
||||
<a
|
||||
role="button"
|
||||
className="mailpoet-top-bar-logo"
|
||||
title={t('topBarLogoTitle')}
|
||||
onClick={onLogoClick}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
['keydown', 'keypress'].includes(event.type) &&
|
||||
['Enter', ' '].includes(event.key)
|
||||
) {
|
||||
event.preventDefault();
|
||||
onLogoClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="mailpoet-top-bar-logo-desktop">
|
||||
<MailPoetLogo />
|
||||
|
||||
if (withLink) {
|
||||
logo = (
|
||||
<a
|
||||
role="button"
|
||||
className="mailpoet-top-bar-logo"
|
||||
title={t('topBarLogoTitle')}
|
||||
onClick={onLogoClick}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
['keydown', 'keypress'].includes(event.type) &&
|
||||
['Enter', ' '].includes(event.key)
|
||||
) {
|
||||
event.preventDefault();
|
||||
onLogoClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="mailpoet-top-bar-logo-desktop">
|
||||
<MailPoetLogo />
|
||||
</div>
|
||||
<div className="mailpoet-top-bar-logo-mobile">
|
||||
<MailPoetLogoMobile />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
logo = (
|
||||
<div className="mailpoet-top-bar-logo">
|
||||
<div className="mailpoet-top-bar-logo-desktop">
|
||||
<MailPoetLogo />
|
||||
</div>
|
||||
<div className="mailpoet-top-bar-logo-mobile">
|
||||
<MailPoetLogoMobile />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mailpoet-top-bar-logo-mobile">
|
||||
<MailPoetLogoMobile />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return logo;
|
||||
}
|
||||
|
@ -5,21 +5,28 @@ import { withFeatureAnnouncement } from 'announcements/with_feature_announcement
|
||||
import { MailPoetLogoResponsive } from './mailpoet_logo_responsive';
|
||||
import { BeamerIcon } from './beamer_icon';
|
||||
import { ScreenOptionsFix } from './screen_options_fix';
|
||||
import { withBoundary } from '../error_boundary';
|
||||
|
||||
type Props = {
|
||||
children?: ReactNode;
|
||||
hasNews?: boolean;
|
||||
onBeamerClick?: () => void;
|
||||
logoWithLink?: boolean;
|
||||
};
|
||||
|
||||
export function TopBar({ children, hasNews, onBeamerClick }: Props) {
|
||||
export function TopBar({
|
||||
children,
|
||||
hasNews,
|
||||
onBeamerClick,
|
||||
logoWithLink = true,
|
||||
}: Props) {
|
||||
const buttonClasses = classnames(
|
||||
'mailpoet-top-bar-beamer',
|
||||
hasNews ? 'mailpoet-top-bar-beamer-dot' : '',
|
||||
);
|
||||
return (
|
||||
<div className="mailpoet-top-bar">
|
||||
<MailPoetLogoResponsive />
|
||||
<MailPoetLogoResponsive withLink={logoWithLink} />
|
||||
<div className="mailpoet-top-bar-children">{children}</div>
|
||||
<div className="mailpoet-flex-grow" />
|
||||
{onBeamerClick && (
|
||||
@ -51,4 +58,5 @@ export function TopBar({ children, hasNews, onBeamerClick }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
export const TopBarWithBeamer = withFeatureAnnouncement(TopBar);
|
||||
TopBar.displayName = 'TopBar';
|
||||
export const TopBarWithBeamer = withFeatureAnnouncement(withBoundary(TopBar));
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { GlobalContext, useGlobalContextValue } from 'context/index.jsx';
|
||||
import { Notices } from 'notices/notices.jsx';
|
||||
import { ErrorBoundary } from 'common';
|
||||
|
||||
function ExperimentalFeatures() {
|
||||
const [flags, setFlags] = useState(null);
|
||||
@ -106,6 +107,14 @@ function ExperimentalFeatures() {
|
||||
const experimentalFeaturesContainer = document.getElementById(
|
||||
'experimental_features_container',
|
||||
);
|
||||
|
||||
ExperimentalFeatures.displayName = 'ExperimentalFeatures';
|
||||
|
||||
if (experimentalFeaturesContainer) {
|
||||
ReactDOM.render(<ExperimentalFeatures />, experimentalFeaturesContainer);
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<ExperimentalFeatures />
|
||||
</ErrorBoundary>,
|
||||
experimentalFeaturesContainer,
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TokenFieldProps, TokenField } from 'common/form/tokenField/tokenField';
|
||||
import { FormTokenItem } from '../../automation/integrations/mailpoet/components/form-token-field';
|
||||
import { FormTokenItem } from '../../automation/editor/components';
|
||||
|
||||
interface TokenFormFieldProps {
|
||||
onValueChange: TokenFieldProps['onChange'];
|
||||
|
@ -1,5 +1,6 @@
|
||||
import PanelColorGradientSettings from '@wordpress/block-editor/build-module/components/colors-gradients/panel-color-gradient-settings';
|
||||
import { useSetting } from '@wordpress/block-editor';
|
||||
import { withBoundary } from 'common';
|
||||
|
||||
type Setting = {
|
||||
label: string;
|
||||
@ -14,7 +15,7 @@ type Props = {
|
||||
settings: Setting[];
|
||||
};
|
||||
|
||||
export function ColorGradientSettings({ title, settings }: Props): JSX.Element {
|
||||
function ColorGradientSettings({ title, settings }: Props): JSX.Element {
|
||||
const settingsColors = useSetting('color.palette');
|
||||
const settingsGradients = useSetting('color.gradients');
|
||||
return (
|
||||
@ -28,3 +29,7 @@ export function ColorGradientSettings({ title, settings }: Props): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ColorGradientSettings.displayName = 'ColorGradientSettings';
|
||||
const ColorGradientSettingsWithBoundary = withBoundary(ColorGradientSettings);
|
||||
export { ColorGradientSettingsWithBoundary as ColorGradientSettings };
|
||||
|
@ -1,6 +1,6 @@
|
||||
import classnames from 'classnames';
|
||||
import '@wordpress/core-data';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { Popover, SlotFillProvider } from '@wordpress/components';
|
||||
import { uploadMedia } from '@wordpress/media-utils';
|
||||
import {
|
||||
@ -9,13 +9,14 @@ import {
|
||||
BlockList,
|
||||
BlockSelectionClearer,
|
||||
BlockTools,
|
||||
WritingFlow,
|
||||
ObserveTyping,
|
||||
SETTINGS_DEFAULTS,
|
||||
WritingFlow,
|
||||
} from '@wordpress/block-editor';
|
||||
import { ShortcutProvider } from '@wordpress/keyboard-shortcuts';
|
||||
|
||||
import { UnsavedChangesNotice } from 'common/notices/unsaved_changes_notice.jsx';
|
||||
import { ErrorBoundary } from 'common';
|
||||
import { fetchLinkSuggestions } from '../utils/link_suggestions';
|
||||
import { Header } from './header.jsx';
|
||||
import { Tutorial } from './tutorial';
|
||||
@ -113,10 +114,12 @@ export function Editor() {
|
||||
<div className={layoutClass}>
|
||||
<div className="interface-interface-skeleton__editor">
|
||||
<div className="interface-interface-skeleton__header">
|
||||
<Header
|
||||
isInserterOpened={isInserterOpened}
|
||||
setIsInserterOpened={toggleInserter}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<Header
|
||||
isInserterOpened={isInserterOpened}
|
||||
setIsInserterOpened={toggleInserter}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<div className="interface-interface-skeleton__body">
|
||||
<BlockEditorProvider
|
||||
@ -132,7 +135,9 @@ export function Editor() {
|
||||
</div>
|
||||
)}
|
||||
<div className="interface-interface-skeleton__content">
|
||||
<Notices />
|
||||
<ErrorBoundary>
|
||||
<Notices />
|
||||
</ErrorBoundary>
|
||||
<UnsavedChangesNotice storeName="mailpoet-form-editor" />
|
||||
<BlockSelectionClearer className="edit-post-visual-editor editor-styles-wrapper">
|
||||
<BlockTools>
|
||||
@ -141,9 +146,11 @@ export function Editor() {
|
||||
<div className="mailpoet_form">
|
||||
<WritingFlow>
|
||||
<ObserveTyping>
|
||||
<FormStylingBackground>
|
||||
<BlockList />
|
||||
</FormStylingBackground>
|
||||
<ErrorBoundary>
|
||||
<FormStylingBackground>
|
||||
<BlockList />
|
||||
</FormStylingBackground>
|
||||
</ErrorBoundary>
|
||||
</ObserveTyping>
|
||||
</WritingFlow>
|
||||
</div>
|
||||
@ -152,19 +159,27 @@ export function Editor() {
|
||||
</div>
|
||||
{sidebarOpened && (
|
||||
<div className="interface-interface-skeleton__sidebar">
|
||||
<Sidebar />
|
||||
<ErrorBoundary>
|
||||
<Sidebar />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
)}
|
||||
</BlockEditorProvider>
|
||||
</div>
|
||||
<FormStyles />
|
||||
<Fullscreen />
|
||||
<ErrorBoundary>
|
||||
<FormStyles />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<Fullscreen />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<Popover.Slot />
|
||||
</div>
|
||||
</SlotFillProvider>
|
||||
</ShortcutProvider>
|
||||
<FormPreview />
|
||||
<ErrorBoundary>
|
||||
<FormPreview />
|
||||
</ErrorBoundary>
|
||||
<Tutorial />
|
||||
</>
|
||||
);
|
||||
|
@ -3,9 +3,9 @@ import {
|
||||
Panel,
|
||||
PanelBody,
|
||||
RadioControl,
|
||||
SelectControl,
|
||||
TextareaControl,
|
||||
ToggleControl,
|
||||
SelectControl,
|
||||
} from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
@ -168,5 +168,5 @@ BasicSettingsPanel.propTypes = {
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
isOpened: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
BasicSettingsPanel.displayName = 'FormEditorBasicSettingsPanel';
|
||||
export { BasicSettingsPanel };
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ErrorBoundary } from 'common';
|
||||
import { BelowPostsSettings } from './settings_panels/below_posts_settings';
|
||||
import { PopUpSettings } from './settings_panels/popup_settings';
|
||||
import { OtherSettings } from './settings_panels/other_settings';
|
||||
@ -11,11 +12,31 @@ type Props = {
|
||||
export function SettingsPanel({ activePanel }: Props): JSX.Element {
|
||||
return (
|
||||
<div className="mailpoet-styles-settings">
|
||||
{activePanel === 'others' && <OtherSettings />}
|
||||
{activePanel === 'below_posts' && <BelowPostsSettings />}
|
||||
{activePanel === 'fixed_bar' && <FixedBarSettings />}
|
||||
{activePanel === 'popup' && <PopUpSettings />}
|
||||
{activePanel === 'slide_in' && <SlideInSettings />}
|
||||
{activePanel === 'others' && (
|
||||
<ErrorBoundary>
|
||||
<OtherSettings />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
{activePanel === 'below_posts' && (
|
||||
<ErrorBoundary>
|
||||
<BelowPostsSettings />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
{activePanel === 'fixed_bar' && (
|
||||
<ErrorBoundary>
|
||||
<FixedBarSettings />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
{activePanel === 'popup' && (
|
||||
<ErrorBoundary>
|
||||
<PopUpSettings />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
{activePanel === 'slide_in' && (
|
||||
<ErrorBoundary>
|
||||
<SlideInSettings />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,14 +2,13 @@ import { MailPoet } from 'mailpoet';
|
||||
import { __, assocPath, compose } from 'lodash/fp';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
import { withBoundary } from 'common';
|
||||
|
||||
type Props = {
|
||||
settingsPlacementKey: string;
|
||||
};
|
||||
|
||||
export function AnimationSettings({
|
||||
settingsPlacementKey,
|
||||
}: Props): JSX.Element {
|
||||
function AnimationSettings({ settingsPlacementKey }: Props): JSX.Element {
|
||||
const formSettings = useSelect(
|
||||
(select) => select('mailpoet-form-editor').getFormSettings(),
|
||||
[],
|
||||
@ -42,3 +41,7 @@ export function AnimationSettings({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
AnimationSettings.displayName = 'FormEditorAnimationSettings';
|
||||
const AnimationSettingsWithBoundary = withBoundary(AnimationSettings);
|
||||
export { AnimationSettingsWithBoundary as AnimationSettings };
|
||||
|
@ -2,12 +2,13 @@ import { MailPoet } from 'mailpoet';
|
||||
import { __, assocPath, compose } from 'lodash/fp';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
import { withBoundary } from 'common';
|
||||
|
||||
type Props = {
|
||||
settingsPlacementKey: string;
|
||||
};
|
||||
|
||||
export function CookieSettings({ settingsPlacementKey }: Props): JSX.Element {
|
||||
function CookieSettings({ settingsPlacementKey }: Props): JSX.Element {
|
||||
const cookieExpirationValues = [3, 7, 14, 30, 60, 90];
|
||||
const formSettings = useSelect(
|
||||
(select) => select('mailpoet-form-editor').getFormSettings(),
|
||||
@ -47,3 +48,7 @@ export function CookieSettings({ settingsPlacementKey }: Props): JSX.Element {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
CookieSettings.displayName = 'FormEditorCookieSettings';
|
||||
const CookieSettingsWithBoundary = withBoundary(CookieSettings);
|
||||
export { CookieSettingsWithBoundary as CookieSettings };
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import {
|
||||
SelectControl,
|
||||
RadioControl,
|
||||
SelectControl,
|
||||
ToggleControl,
|
||||
} from '@wordpress/components';
|
||||
import { assocPath, compose, __ } from 'lodash/fp';
|
||||
import { __, assocPath, compose } from 'lodash/fp';
|
||||
import { SizeSettings } from 'form_editor/components/size_settings';
|
||||
import { AnimationSettings } from './animation_settings';
|
||||
import { PlacementSettings } from './placement_settings';
|
||||
@ -13,7 +13,7 @@ import { CookieSettings } from './cookie_settings';
|
||||
|
||||
const delayValues = [0, 2, 5, 10, 15, 30, 45, 60, 120, 180, 240];
|
||||
|
||||
export function FixedBarSettings(): JSX.Element {
|
||||
function FixedBarSettings(): JSX.Element {
|
||||
const formSettings = useSelect(
|
||||
(select) => select('mailpoet-form-editor').getFormSettings(),
|
||||
[],
|
||||
@ -96,3 +96,6 @@ export function FixedBarSettings(): JSX.Element {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
FixedBarSettings.displayName = 'FormEditorFixedBarSettings';
|
||||
export { FixedBarSettings };
|
||||
|
@ -7,7 +7,7 @@ import { assocPath } from 'lodash/fp';
|
||||
import { TextareaControl } from '@wordpress/components';
|
||||
import { SizeSettings } from 'form_editor/components/size_settings';
|
||||
|
||||
export function OtherSettings(): JSX.Element {
|
||||
function OtherSettings(): JSX.Element {
|
||||
const [copyAreaContent, setCopyAreaContent] = useState(null);
|
||||
|
||||
const formExports = useSelect(
|
||||
@ -117,3 +117,6 @@ export function OtherSettings(): JSX.Element {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
OtherSettings.displayName = 'FormEditorOtherSettings';
|
||||
export { OtherSettings };
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user