Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
31035c0912 | |||
e12fa3c550 |
@ -99,12 +99,12 @@ executors:
|
||||
wpcli_php_max_wporg:
|
||||
<<: *default_job_config
|
||||
docker:
|
||||
- image: mailpoet/wordpress:8.1_20230307.1 # We need to use 8.1 to emulate the WP.org environment
|
||||
- image: mailpoet/wordpress:8.1_20230307.1
|
||||
|
||||
wpcli_php_latest:
|
||||
<<: *default_job_config
|
||||
docker:
|
||||
- image: mailpoet/wordpress:8.2_20241126.1
|
||||
- image: mailpoet/wordpress:8.1_20230307.1
|
||||
|
||||
wpcli_php_mysql_oldest:
|
||||
<<: *default_job_config
|
||||
@ -115,7 +115,7 @@ executors:
|
||||
wpcli_php_mysql_latest:
|
||||
<<: *default_job_config
|
||||
docker:
|
||||
- image: mailpoet/wordpress:8.2_20241126.1
|
||||
- image: mailpoet/wordpress:8.1_20230307.1
|
||||
- image: cimg/mysql:8.0
|
||||
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_520_ci
|
||||
|
||||
@ -141,8 +141,6 @@ jobs:
|
||||
key: composer-{{ checksum "tasks/code_sniffer/composer.json" }}-{{ checksum "tasks/code_sniffer/composer.lock" }}
|
||||
- restore_cache:
|
||||
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
|
||||
- restore_cache:
|
||||
key: composer-{{ checksum "../tests_env/composer.json" }}-{{ checksum "../tests_env/composer.lock" }}
|
||||
- restore_cache:
|
||||
key: composer-prefixed-{{ checksum "prefixer-checksum" }}
|
||||
- restore_cache:
|
||||
@ -159,7 +157,7 @@ jobs:
|
||||
./do install
|
||||
./do compile:all --env production --skip-tests
|
||||
./do doctrine:generate-cache
|
||||
../tests_env/vendor/bin/codecept build
|
||||
vendor/bin/codecept build
|
||||
./do twig:generate-cache
|
||||
- run:
|
||||
name: 'Check Prettier formatting'
|
||||
@ -180,10 +178,6 @@ jobs:
|
||||
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
|
||||
paths:
|
||||
- vendor
|
||||
- save_cache:
|
||||
key: composer-{{ checksum "../tests_env/composer.json" }}-{{ checksum "../tests_env/composer.lock" }}
|
||||
paths:
|
||||
- ../tests_env/vendor
|
||||
- save_cache:
|
||||
key: composer-prefixed-{{ checksum "prefixer-checksum" }}
|
||||
paths:
|
||||
@ -197,10 +191,10 @@ jobs:
|
||||
- run:
|
||||
name: Download additional WP Plugins for tests
|
||||
command: |
|
||||
./do download:woo-commerce-zip 9.6.0
|
||||
./do download:woo-commerce-subscriptions-zip 7.1.0
|
||||
./do download:woo-commerce-zip 9.1.4
|
||||
./do download:woo-commerce-subscriptions-zip 6.5.0
|
||||
./do download:woo-commerce-memberships-zip 1.26.5
|
||||
./do download:automate-woo-zip 6.1.5
|
||||
./do download:automate-woo-zip 6.0.31
|
||||
- run:
|
||||
name: Dump tests ENV variables for acceptance tests
|
||||
command: |
|
||||
@ -451,19 +445,19 @@ jobs:
|
||||
- run:
|
||||
name: 'Pull test docker images'
|
||||
# Pull docker images with 3 retries
|
||||
command: i='0';while ! docker compose -f ../tests_env/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
command: i='0';while ! docker-compose -f ../tests_env/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
- 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_env/docker
|
||||
docker compose create || docker compose create
|
||||
docker-compose create || docker-compose create
|
||||
- run:
|
||||
# Some tools we use may need different version based on PHP version used in docker
|
||||
name: Ensure correct versions of tools
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project -e COMPOSER_DEV_MODE=1 --entrypoint "php tools/install.php" codeception_acceptance
|
||||
docker-compose run --rm -w /project -e COMPOSER_DEV_MODE=1 --entrypoint "php tools/install.php" codeception_acceptance
|
||||
- when:
|
||||
condition: ${WOOCOMMERCE_VERSION}
|
||||
steps:
|
||||
@ -471,7 +465,7 @@ jobs:
|
||||
name: Download WooCommerce Core
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip ${WOOCOMMERCE_VERSION}" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip ${WOOCOMMERCE_VERSION}" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
- when:
|
||||
condition: << parameters.woo_subscriptions_version >>
|
||||
steps:
|
||||
@ -479,7 +473,7 @@ jobs:
|
||||
name: Download WooCommerce Subscriptions
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-subscriptions-zip << parameters.woo_subscriptions_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-subscriptions-zip << parameters.woo_subscriptions_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
- when:
|
||||
condition: << parameters.woo_memberships_version >>
|
||||
steps:
|
||||
@ -487,7 +481,7 @@ jobs:
|
||||
name: Download WooCommerce Memberships
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-memberships-zip << parameters.woo_memberships_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-memberships-zip << parameters.woo_memberships_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
- when:
|
||||
condition: << parameters.automate_woo_version >>
|
||||
steps:
|
||||
@ -495,7 +489,7 @@ jobs:
|
||||
name: Download AutomateWoo
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:automate-woo-zip << parameters.automate_woo_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:automate-woo-zip << parameters.automate_woo_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
|
||||
- run:
|
||||
name: Group acceptance tests
|
||||
command: |
|
||||
@ -524,7 +518,7 @@ jobs:
|
||||
--xml
|
||||
-g circleci_split_group
|
||||
)
|
||||
docker compose run -e SKIP_DEPS=1 \
|
||||
docker-compose run -e SKIP_DEPS=1 \
|
||||
-e CIRCLE_BRANCH=${CIRCLE_BRANCH} \
|
||||
-e CIRCLE_JOB=${CIRCLE_JOB} \
|
||||
-e MULTISITE=<< parameters.multisite >> \
|
||||
@ -583,13 +577,13 @@ jobs:
|
||||
- run:
|
||||
name: 'Pull test docker images'
|
||||
# Pull docker images with 3 retries
|
||||
command: i='0';while ! docker compose -f tests/performance/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
command: i='0';while ! docker-compose -f tests/performance/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
- 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/performance
|
||||
docker compose create || docker compose create
|
||||
docker-compose create || docker-compose create
|
||||
- run:
|
||||
name: Run performance tests
|
||||
command: |
|
||||
@ -747,19 +741,19 @@ jobs:
|
||||
- run:
|
||||
name: 'Pull test docker images'
|
||||
# Pull docker images with 3 retries
|
||||
command: i='0';while ! docker compose -f ../tests_env/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
command: i='0';while ! docker-compose -f ../tests_env/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
- 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_env/docker
|
||||
docker compose create || docker compose create
|
||||
docker-compose create || docker-compose create
|
||||
- run:
|
||||
# Some tools we use may need different version based on PHP version used in docker
|
||||
name: Ensure correct versions of tools
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project -e COMPOSER_DEV_MODE=1 --entrypoint "php tools/install.php" codeception_integration
|
||||
docker-compose run --rm -w /project -e COMPOSER_DEV_MODE=1 --entrypoint "php tools/install.php" codeception_integration
|
||||
- when:
|
||||
condition: ${WOOCOMMERCE_VERSION}
|
||||
steps:
|
||||
@ -767,7 +761,7 @@ jobs:
|
||||
name: Download WooCommerce Core
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip ${WOOCOMMERCE_VERSION}" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip ${WOOCOMMERCE_VERSION}" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
- when:
|
||||
condition: << parameters.woo_subscriptions_version >>
|
||||
steps:
|
||||
@ -775,7 +769,7 @@ jobs:
|
||||
name: Download WooCommerce Subscriptions
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-subscriptions-zip << parameters.woo_subscriptions_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-subscriptions-zip << parameters.woo_subscriptions_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
- when:
|
||||
condition: << parameters.woo_memberships_version >>
|
||||
steps:
|
||||
@ -783,7 +777,7 @@ jobs:
|
||||
name: Download WooCommerce Memberships
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-memberships-zip << parameters.woo_memberships_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-memberships-zip << parameters.woo_memberships_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
- when:
|
||||
condition: << parameters.automate_woo_version >>
|
||||
steps:
|
||||
@ -791,7 +785,7 @@ jobs:
|
||||
name: Download AutomateWoo
|
||||
command: |
|
||||
cd ../tests_env/docker
|
||||
docker compose run --rm -w /project --entrypoint "./do download:automate-woo-zip << parameters.automate_woo_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:automate-woo-zip << parameters.automate_woo_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
|
||||
- run:
|
||||
name: 'PHP Integration tests'
|
||||
command: |
|
||||
@ -810,7 +804,7 @@ jobs:
|
||||
if [[ -n '<< parameters.skip_group >>' ]]; then
|
||||
args+=(--skip-group << parameters.skip_group >>)
|
||||
fi
|
||||
docker compose run -e SKIP_DEPS=1 \
|
||||
docker-compose run -e SKIP_DEPS=1 \
|
||||
-e CIRCLE_BRANCH=${CIRCLE_BRANCH} \
|
||||
-e CIRCLE_JOB=${CIRCLE_JOB} \
|
||||
-e SKIP_PLUGINS=<< parameters.skip_plugins >> \
|
||||
@ -863,30 +857,6 @@ jobs:
|
||||
root: /home/circleci/mailpoet
|
||||
paths:
|
||||
- mailpoet/release_zip_build_number.txt
|
||||
- mailpoet/mailpoet.zip
|
||||
qit_security_scan:
|
||||
executor: wpcli_php_latest
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /home/circleci
|
||||
- run:
|
||||
name: 'Set up environment'
|
||||
command: |
|
||||
# Copy built ZIP to local directory for easier access from QIT
|
||||
cp /home/circleci/mailpoet/mailpoet.zip .
|
||||
# Authenticate in QIT
|
||||
./vendor/bin/qit partner:add --user="${QIT_PARTNER_USER}" --application_password="${QIT_PARTNER_SECRET}"
|
||||
- run:
|
||||
name: 'QIT Security Test'
|
||||
command: ./do qa:qit-security | tee tests/_output/qit-security
|
||||
- run:
|
||||
name: 'Retrieve test results'
|
||||
command: |
|
||||
# Download HTML report from QIT servers
|
||||
grep "Result Url" tests/_output/qit-security | awk '{ print $3 }' | xargs curl -o tests/_output/report.html
|
||||
when: always
|
||||
- store_artifacts:
|
||||
path: tests/_output
|
||||
|
||||
workflows:
|
||||
build_and_test:
|
||||
@ -940,28 +910,40 @@ workflows:
|
||||
name: acceptance_tests_base_and_woo
|
||||
enable_hpos: 1
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_tests_woo_hpos_sync_on
|
||||
group: woo
|
||||
enable_hpos_sync: 1
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_tests_woo_hpos_off
|
||||
group: woo
|
||||
disable_hpos: 1
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_tests_blockbased_theme
|
||||
group: frontend
|
||||
blockbased_theme: 1
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- js_tests:
|
||||
<<: *slack-fail-post-step
|
||||
requires:
|
||||
@ -972,41 +954,55 @@ workflows:
|
||||
enable_hpos_sync: 1
|
||||
name: integration_test_woo_hpos_sync_on
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
group: woo
|
||||
name: integration_test_woo_hpos_on
|
||||
enable_hpos: 1
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
group: woo
|
||||
disable_hpos: 1
|
||||
name: integration_test_woo_hpos_off
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
skip_group: woo
|
||||
skip_plugins: 1
|
||||
name: integration_test_base
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *multisite_acceptance_config
|
||||
name: acceptance_tests_multisite
|
||||
requires:
|
||||
- build
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_trunk_and_release
|
||||
multisite: 1
|
||||
name: integration_tests_multisite
|
||||
requires:
|
||||
- build
|
||||
- unit_tests
|
||||
- static_analysis_php7
|
||||
- static_analysis_php8
|
||||
- qa_js
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_release
|
||||
@ -1035,22 +1031,9 @@ workflows:
|
||||
- integration_test_woo_hpos_on
|
||||
- integration_test_woo_hpos_off
|
||||
- integration_test_woo_hpos_sync_on
|
||||
- integration_with_premium_latest
|
||||
- acceptance_tests_woo_hpos_sync_on
|
||||
- acceptance_tests_woo_hpos_off
|
||||
- acceptance_tests_blockbased_theme
|
||||
- static_analysis_php8
|
||||
- static_analysis_php7
|
||||
- unit_tests
|
||||
- qa_js
|
||||
- qa_php
|
||||
- qa_php_oldest
|
||||
- security_analysis
|
||||
- qa_php_max_wporg
|
||||
- qit_security_scan:
|
||||
<<: *slack-fail-post-step
|
||||
requires:
|
||||
- build_release_zip
|
||||
|
||||
nightly:
|
||||
triggers:
|
||||
@ -1082,15 +1065,15 @@ workflows:
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_oldest
|
||||
woo_core_version: 9.5.2
|
||||
woo_subscriptions_version: 7.0.0
|
||||
woo_core_version: 9.0.2
|
||||
woo_subscriptions_version: 6.4.1
|
||||
woo_memberships_version: 1.25.2
|
||||
automate_woo_version: 6.0.33
|
||||
mysql_command: --max_allowed_packet=100M
|
||||
automate_woo_version: 5.8.5
|
||||
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: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.6.2
|
||||
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 6.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.5.5
|
||||
requires:
|
||||
- build
|
||||
- performance_tests:
|
||||
@ -1123,14 +1106,14 @@ workflows:
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: integration_oldest
|
||||
woo_core_version: 9.5.2
|
||||
woo_subscriptions_version: 7.0.0
|
||||
woo_core_version: 9.0.2
|
||||
woo_subscriptions_version: 6.4.1
|
||||
woo_memberships_version: 1.25.2
|
||||
automate_woo_version: 6.0.33
|
||||
automate_woo_version: 5.8.5
|
||||
codeception_image_version: 7.4-cli_20220605.0
|
||||
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI # We use image with PHP 7.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.6.2
|
||||
mysql_command: --max_allowed_packet=100M
|
||||
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 6.4 and install required WordPress version via CLI # We use image with PHP 6.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.5.5
|
||||
mysql_command: --max_allowed_packet=100M --default-storage-engine=MYISAM
|
||||
mysql_image: mysql:5.5
|
||||
requires:
|
||||
- build
|
||||
@ -1186,26 +1169,26 @@ workflows:
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_with_premium_oldest
|
||||
woo_core_version: 9.5.2
|
||||
woo_subscriptions_version: 7.0.0
|
||||
woo_core_version: 9.0.2
|
||||
woo_subscriptions_version: 6.4.1
|
||||
woo_memberships_version: 1.25.2
|
||||
automate_woo_version: 6.0.33
|
||||
automate_woo_version: 5.8.5
|
||||
codeception_image_version: 7.4-cli_20220605.0
|
||||
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.6.2
|
||||
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 6.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.5.5
|
||||
requires:
|
||||
- build_premium
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: integration_with_premium_oldest
|
||||
woo_core_version: 9.5.2
|
||||
woo_subscriptions_version: 7.0.0
|
||||
woo_core_version: 9.0.2
|
||||
woo_subscriptions_version: 6.4.1
|
||||
woo_memberships_version: 1.25.2
|
||||
automate_woo_version: 6.0.33
|
||||
automate_woo_version: 5.8.5
|
||||
codeception_image_version: 7.4-cli_20220605.0
|
||||
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.6.2
|
||||
mysql_command: --max_allowed_packet=100M
|
||||
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 6.4 and install required WordPress version via CLI
|
||||
wordpress_version: 6.5.5
|
||||
mysql_command: --max_allowed_packet=100M --default-storage-engine=MYISAM
|
||||
mysql_image: mysql:5.5
|
||||
requires:
|
||||
- build_premium
|
||||
|
@ -10,12 +10,6 @@ indent_size = 2
|
||||
ij_smart_tabs = false
|
||||
max_line_length = off
|
||||
|
||||
[packages/php/email-editor/**]
|
||||
indent_style = tab
|
||||
|
||||
[packages/js/email-editor/**.{js,jsx,ts,tsx,scss}]
|
||||
indent_style = tab
|
||||
|
||||
[*.php]
|
||||
ij_php_align_key_value_pairs = false
|
||||
ij_php_align_multiline_chained_methods = false
|
||||
@ -61,5 +55,3 @@ ij_php_space_after_for_semicolon = true
|
||||
ij_php_space_after_colon_in_return_type = true
|
||||
ij_php_space_before_else_keyword = true
|
||||
ij_php_for_statement_new_line_after_left_paren = true
|
||||
ij_php_class_brace_style = end_of_line
|
||||
ij_php_comma_after_last_array_element = true
|
||||
|
@ -39,15 +39,3 @@ e66c76133ec3ef667e382203426d91a4b4aa5174
|
||||
|
||||
# Prettier autoformatting
|
||||
ab27eaee2df740c0add4331a7f8c115a87ecfa2b
|
||||
|
||||
# Move email editor to JS packages folder
|
||||
912282f57ccc839491ff951ec5cf7aa10c14f429
|
||||
|
||||
# Switch email editor js packages to WP coding style
|
||||
b2fb96f8793b63db629d5237010d87332330c51e
|
||||
|
||||
# Email editor Prettier autoformatting
|
||||
8c604453b1d82e3a2c731241e1c96ea8b32ec716
|
||||
|
||||
# Move email editor components out of the engine folder
|
||||
1c3ea9cd0a5fc8848a64d840e2fa16a6c7d8c1fe
|
||||
|
212
.github/workflows/email-editor-package.yml
vendored
212
.github/workflows/email-editor-package.yml
vendored
@ -1,212 +0,0 @@
|
||||
name: Email Editor Package Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.2']
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache Composer vendor dependencies for MailPoet
|
||||
id: composer-mailpoet-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mailpoet/vendor
|
||||
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('mailpoet/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
|
||||
|
||||
- name: Cache Composer vendor-prefixed dependencies for MailPoet
|
||||
id: vendor-prefixed-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mailpoet/vendor-prefixed
|
||||
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/prefixer/composer.json') }}
|
||||
|
||||
- name: Cache Composer vendor for test environment
|
||||
id: composer-tests-env-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: tests_env/vendor
|
||||
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('tests_env/composer.lock') }}-${{ hashFiles('tests_env/composer.json') }}
|
||||
|
||||
- name: Cache Composer dependencies for Email Editor
|
||||
id: composer-email-editor-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: packages/php/email-editor/vendor
|
||||
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('packages/php/email-editor/composer.json') }}
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: gd
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
COMPOSER_DEV_MODE=1 php tools/install.php
|
||||
touch .env
|
||||
working-directory: mailpoet
|
||||
|
||||
# Install Test Environment dependencies only if the cache was not hit
|
||||
- name: Install test environment dependencies
|
||||
if: steps.composer-tests-env-cache.outputs.cache-hit != 'true'
|
||||
run: ../mailpoet/tools/vendor/composer.phar install
|
||||
working-directory: tests_env
|
||||
|
||||
# Install MailPoet dependencies only if the cache was not hit
|
||||
- name: Install mailpoet dependencies
|
||||
if: |
|
||||
steps.composer-mailpoet-cache.outputs.cache-hit != 'true' || steps.vendor-prefixed-cache.outputs.cache-hit != 'true'
|
||||
run: ./tools/vendor/composer.phar install
|
||||
working-directory: mailpoet
|
||||
|
||||
# Install Email Editor dependencies only if the cache was not hit
|
||||
- name: Install email-editor dependencies
|
||||
if: steps.composer-email-editor-cache.outputs.cache-hit != 'true'
|
||||
run: ../../../mailpoet/tools/vendor/composer.phar install
|
||||
working-directory: packages/php/email-editor
|
||||
|
||||
# Dump Email Editor autoload
|
||||
# This is needed to refresh classmap autoload when the composer cache is hit
|
||||
- name: Dump email-editor autoload
|
||||
run: ../../../mailpoet/tools/vendor/composer.phar dump-autoload
|
||||
working-directory: packages/php/email-editor
|
||||
|
||||
# Dump MailPoet autoload
|
||||
# This is needed to refresh classmap autoload when the composer cache is hit
|
||||
- name: Dump MailPoet autoload
|
||||
run: ./tools/vendor/composer.phar dump-autoload
|
||||
working-directory: mailpoet
|
||||
|
||||
# Run Email Editor unit tests
|
||||
- name: Run email-editor package unit tests
|
||||
run: ../../../tests_env/vendor/bin/codecept build && ../../../mailpoet/tools/vendor/composer.phar unit-test
|
||||
working-directory: packages/php/email-editor
|
||||
|
||||
- name: Run email-editor package integration tests
|
||||
run: ../../../mailpoet/tools/vendor/composer.phar integration-test
|
||||
working-directory: packages/php/email-editor
|
||||
|
||||
code-style:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
COMPOSER_DEV_MODE=1 php tools/install.php
|
||||
touch .env
|
||||
working-directory: mailpoet
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: ../../tools/vendor/composer.phar install
|
||||
working-directory: mailpoet/tasks/code_sniffer
|
||||
|
||||
- name: Run code style check
|
||||
run: ../../../mailpoet/tools/vendor/composer.phar code-style
|
||||
working-directory: packages/php/email-editor
|
||||
|
||||
phpstan-static-analysis:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.2']
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache Composer vendor dependencies for MailPoet
|
||||
id: composer-mailpoet-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mailpoet/vendor
|
||||
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('mailpoet/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
|
||||
|
||||
- name: Cache Composer vendor-prefixed dependencies for MailPoet
|
||||
id: vendor-prefixed-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mailpoet/vendor-prefixed
|
||||
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/prefixer/composer.json') }}
|
||||
|
||||
- name: Cache Composer vendor for test environment
|
||||
id: composer-tests-env-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: tests_env/vendor
|
||||
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('tests_env/composer.lock') }}-${{ hashFiles('tests_env/composer.json') }}
|
||||
|
||||
- name: Cache Composer dependencies for Email Editor
|
||||
id: composer-email-editor-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: packages/php/email-editor/vendor
|
||||
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('packages/php/email-editor/composer.json') }}
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: gd
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
COMPOSER_DEV_MODE=1 php tools/install.php
|
||||
touch .env
|
||||
working-directory: mailpoet
|
||||
|
||||
# Install Test Environment dependencies only if the cache was not hit
|
||||
- name: Install test environment dependencies
|
||||
if: steps.composer-tests-env-cache.outputs.cache-hit != 'true'
|
||||
run: ../mailpoet/tools/vendor/composer.phar install
|
||||
working-directory: tests_env
|
||||
|
||||
# Install MailPoet dependencies only if the cache was not hit
|
||||
- name: Install mailpoet dependencies
|
||||
if: |
|
||||
steps.composer-mailpoet-cache.outputs.cache-hit != 'true' || steps.vendor-prefixed-cache.outputs.cache-hit != 'true'
|
||||
run: ./tools/vendor/composer.phar install
|
||||
working-directory: mailpoet
|
||||
|
||||
# Install Email Editor dependencies only if the cache was not hit
|
||||
- name: Install email-editor dependencies
|
||||
if: steps.composer-email-editor-cache.outputs.cache-hit != 'true'
|
||||
run: ../../../mailpoet/tools/vendor/composer.phar install
|
||||
working-directory: packages/php/email-editor
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: ../../tools/vendor/composer.phar install
|
||||
working-directory: mailpoet/tasks/phpstan
|
||||
|
||||
# Dump Email Editor autoload
|
||||
# This is needed to refresh classmap autoload when the composer cache is hit
|
||||
- name: Dump email-editor autoload
|
||||
run: ../../../mailpoet/tools/vendor/composer.phar dump-autoload
|
||||
working-directory: packages/php/email-editor
|
||||
|
||||
# Dump MailPoet autoload
|
||||
# This is needed to refresh classmap autoload when the composer cache is hit
|
||||
- name: Dump MailPoet autoload
|
||||
run: ./tools/vendor/composer.phar dump-autoload
|
||||
working-directory: mailpoet
|
||||
|
||||
- name: Run code phpstan
|
||||
run: ../../../mailpoet/tools/vendor/composer.phar phpstan -- --php-version=${{ matrix.php-version == '7.4' && '70400' || '80200' }}
|
||||
working-directory: packages/php/email-editor
|
@ -1,27 +0,0 @@
|
||||
name: Add link to WordPress Playground preview
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
add-wp-playground-link:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check and append description
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
BRANCH_NAME="${{ github.head_ref }}"
|
||||
DESCRIPTION="$(gh pr view $PR_NUMBER --json body -q .body)"
|
||||
HEADING="## Preview"
|
||||
CONTENT="$(printf "${HEADING}\n\n[Preview in WordPress Playground](https://account.mailpoet.com/playground/new/branch:${BRANCH_NAME})\n\n_The latest successful build from \`${BRANCH_NAME}\` will be used. If none is available, the link won't work._")"
|
||||
|
||||
if [[ "$DESCRIPTION" != *"$HEADING"* ]]; then
|
||||
gh pr edit $PR_NUMBER --body "$(printf "${DESCRIPTION}\n\n${CONTENT}")"
|
||||
fi
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,5 +9,3 @@ npm-debug.log
|
||||
mailpoet-premium
|
||||
tsconfig.tsbuildinfo
|
||||
/wordpress
|
||||
packages/php/*/vendor
|
||||
tests_env/vendor
|
||||
|
@ -7,6 +7,7 @@ export MP_GIT_HOOKS_ESLINT="${MP_GIT_HOOKS_ESLINT:-true}"
|
||||
export MP_GIT_HOOKS_STYLELINT="${MP_GIT_HOOKS_STYLELINT:-true}"
|
||||
export MP_GIT_HOOKS_PHPLINT="${MP_GIT_HOOKS_PHPLINT:-true}"
|
||||
export MP_GIT_HOOKS_CODE_SNIFFER="${MP_GIT_HOOKS_CODE_SNIFFER:-true}"
|
||||
export MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS="${MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS:-true}"
|
||||
export MP_GIT_HOOKS_PHPSTAN="${MP_GIT_HOOKS_PHPSTAN:-true}"
|
||||
export MP_GIT_HOOKS_INSTALL_JS="${MP_GIT_HOOKS_INSTALL_JS:-false}"
|
||||
export MP_GIT_HOOKS_INSTALL_PHP="${MP_GIT_HOOKS_INSTALL_PHP:-false}"
|
||||
|
@ -4,5 +4,3 @@
|
||||
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
|
||||
|
||||
installIfUpdates
|
||||
|
||||
./do cleanup:cached-files
|
||||
|
@ -1,8 +1,7 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
. "$(dirname "$0")/common.sh"
|
||||
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
|
||||
|
||||
npx lint-staged -c mailpoet/package.json --cwd mailpoet
|
||||
npx lint-staged -c package.json
|
||||
npx lint-staged -c packages/js/email-editor/package.json --cwd packages/js/email-editor
|
||||
npx lint-staged -c packages/php/email-editor/.lintstagedrc.json --cwd packages/php/email-editor
|
||||
|
@ -25,5 +25,3 @@ vendor-prefixed
|
||||
/mailpoet/views
|
||||
/mailpoet-premium
|
||||
/wordpress
|
||||
/packages/php/email-editor
|
||||
/packages/js/email-editor
|
||||
|
@ -103,8 +103,8 @@ Then create a Docker Compose override file with NFS settings and restart contain
|
||||
```shell
|
||||
cp docker-compose.override.macos-sample.yml docker-compose.override.yml
|
||||
|
||||
docker compose down -v --remove-orphans
|
||||
docker compose up -d
|
||||
docker-compose down -v --remove-orphans
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
**NOTE:** If you are on MacOS Catalina or newer, make sure to put the repository
|
||||
@ -163,10 +163,10 @@ To switch the environment to a different PHP version:
|
||||
dockerfile: dev/{PHP_VERSION}/Dockerfile
|
||||
```
|
||||
|
||||
3. Run `docker compose build wordpress`.
|
||||
3. Run `docker-compose build wordpress`.
|
||||
4. Start the stack with `./do start`.
|
||||
|
||||
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,
|
||||
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`.
|
||||
|
||||
### Disabling the Tracy panel
|
||||
|
@ -21,7 +21,7 @@ mkdir -p wordpress/wp-content/plugins/mailpoet-premium
|
||||
mkdir -p dev/data/mailhog
|
||||
|
||||
for plugin in "mailpoet" "mailpoet-premium"; do
|
||||
docker compose run --rm wordpress /bin/sh -c "
|
||||
docker-compose run --rm wordpress /bin/sh -c "
|
||||
[ -d /var/www/html/wp-content/plugins/$plugin ] &&
|
||||
cd /var/www/html/wp-content/plugins/$plugin &&
|
||||
./do install &&
|
||||
@ -29,7 +29,7 @@ for plugin in "mailpoet" "mailpoet-premium"; do
|
||||
"
|
||||
done
|
||||
|
||||
docker compose run --rm wordpress /bin/sh -c "
|
||||
docker-compose run --rm wordpress /bin/sh -c "
|
||||
cd /var/www/templates &&
|
||||
mkdir assets classes exported
|
||||
"
|
||||
|
@ -29,6 +29,6 @@ cat <<EOT
|
||||
NFS volume sharing is set up. Recreate your containers and volumes using:
|
||||
cp docker-compose.override.macos-sample.yml docker-compose.override.yml
|
||||
|
||||
docker compose down -v --remove-orphans
|
||||
docker compose up -d
|
||||
docker-compose down -v --remove-orphans
|
||||
docker-compose up -d
|
||||
EOT
|
||||
|
16
do
16
do
@ -3,8 +3,8 @@
|
||||
function syntax {
|
||||
cat << EOF
|
||||
./do setup Setup the dev environment.
|
||||
./do start Start the docker containers (docker compose up -d).
|
||||
./do stop Stop the docker containers (docker compose stop).
|
||||
./do start Start the docker containers (docker-compose up -d).
|
||||
./do stop Stop the docker containers (docker-compose stop).
|
||||
./do ssh [--test] Run an interactive bash shell inside the plugin directory.
|
||||
./do run [--test] <command> Run a custom bash command in the wordpress container.
|
||||
./do acceptance [--premium] Run acceptance tests.
|
||||
@ -21,7 +21,7 @@ EOF
|
||||
function ssh_and_run {
|
||||
params=("$@")
|
||||
params=("${params[@]:1}")
|
||||
docker compose exec $1 bash -c "${params[@]}"
|
||||
docker-compose exec $1 bash -c "${params[@]}"
|
||||
}
|
||||
|
||||
if [ "$1" = "" -o "$1" = "--help" ]; then
|
||||
@ -31,10 +31,10 @@ elif [ "$1" = "setup" ]; then
|
||||
./dev/initial-setup.sh
|
||||
|
||||
elif [ "$1" = "start" ]; then
|
||||
docker compose up -d
|
||||
docker-compose up -d
|
||||
|
||||
elif [ "$1" = "stop" ]; then
|
||||
docker compose stop
|
||||
docker-compose stop
|
||||
|
||||
elif [ "$1" = "run" ]; then
|
||||
params=("$@")
|
||||
@ -54,9 +54,9 @@ elif [ "$1" = "ssh" ]; then
|
||||
fi
|
||||
|
||||
if [ "$2" = "--test" ] || [ "$3" = "--test" ]; then
|
||||
docker compose exec --workdir $dir test_wordpress bash
|
||||
docker-compose exec --workdir $dir test_wordpress bash
|
||||
else
|
||||
docker compose exec --workdir $dir wordpress bash
|
||||
docker-compose exec --workdir $dir wordpress bash
|
||||
fi
|
||||
|
||||
elif [ "$1" = "acceptance" ]; then
|
||||
@ -65,7 +65,7 @@ elif [ "$1" = "acceptance" ]; then
|
||||
else
|
||||
cd mailpoet
|
||||
fi
|
||||
COMPOSE_HTTP_TIMEOUT=200 docker compose run codeception_acceptance -e KEEP_DEPS=1 --steps --debug -vvv
|
||||
COMPOSE_HTTP_TIMEOUT=200 docker-compose run codeception_acceptance -e KEEP_DEPS=1 --steps --debug -vvv
|
||||
cd ..
|
||||
|
||||
elif [ "$1" = "build" ]; then
|
||||
|
@ -1,3 +1,5 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# for M1 Macs
|
||||
db:
|
||||
|
@ -25,7 +25,7 @@ services:
|
||||
container_name: mp-wp
|
||||
build:
|
||||
context: .
|
||||
dockerfile: dev/php82/Dockerfile
|
||||
dockerfile: dev/php81/Dockerfile
|
||||
args:
|
||||
UID: ${UID:-1000}
|
||||
GID: ${GID:-1000}
|
||||
@ -49,12 +49,10 @@ services:
|
||||
volumes:
|
||||
- './wordpress:/var/www/html'
|
||||
- './tsconfig.base.json:/var/www/html/wp-content/plugins/tsconfig.base.json:ro'
|
||||
- './.npmrc:/var/www/html/wp-content/plugins/.npmrc'
|
||||
- './package.json:/var/www/html/wp-content/plugins/package.json'
|
||||
- './pnpm-lock.yaml:/var/www/html/wp-content/plugins/pnpm-lock.yaml'
|
||||
- './pnpm-workspace.yaml:/var/www/html/wp-content/plugins/pnpm-workspace.yaml'
|
||||
- './patches:/var/www/html/wp-content/plugins/patches'
|
||||
- './tests_env:/var/www/html/wp-content/plugins/tests_env'
|
||||
- './mailpoet:/var/www/html/wp-content/plugins/mailpoet'
|
||||
- './mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium'
|
||||
- './packages:/var/www/html/wp-content/plugins/packages'
|
||||
|
@ -51,6 +51,7 @@ MP_GIT_HOOKS_ESLINT=true
|
||||
MP_GIT_HOOKS_STYLELINT=true
|
||||
MP_GIT_HOOKS_PHPLINT=true
|
||||
MP_GIT_HOOKS_CODE_SNIFFER=true
|
||||
MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS=true
|
||||
MP_GIT_HOOKS_PHPSTAN=true
|
||||
MP_GIT_HOOKS_INSTALL_JS=false
|
||||
MP_GIT_HOOKS_INSTALL_PHP=false
|
||||
|
2643
mailpoet/CHANGELOG.md
Normal file
2643
mailpoet/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,6 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('./tools/vendor/composer.phar install')
|
||||
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
|
||||
->exec('cd .. && pnpm install --frozen-lockfile --prefer-offline')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
@ -33,7 +32,6 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('./tools/vendor/composer.phar install')
|
||||
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
}
|
||||
@ -210,7 +208,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
$env = ($opts['env']) ?
|
||||
sprintf('./node_modules/.bin/cross-env NODE_ENV="%s"', $opts['env']) :
|
||||
null;
|
||||
return $this->_exec($env . ' ./node_modules/webpack/bin/webpack.js --env BUILD_TESTS=' . ($opts['skip-tests'] ? 'skip' : 'build') . ' --env BUILD_ONLY_TESTS=' . ($opts['only-tests'] ? 'true' : 'false'));
|
||||
return $this->_exec($env . ' ./node_modules/webpack/bin/webpack.js --env BUILD_TESTS=' . ($opts['skip-tests'] ? 'skip' : 'build') . '--env BUILD_ONLY_TESTS=' . ($opts['only-tests'] ? 'true' : 'false'));
|
||||
}
|
||||
|
||||
public function compileCss($opts = ['env' => null]) {
|
||||
@ -222,7 +220,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
|
||||
$compilationResult = $this->taskExecStack()
|
||||
->exec('pnpm run stylelint-check -- "assets/css/src/**/*.scss"')
|
||||
->exec('pnpm run scss' . ($opts['env'] === 'production' ? ' --no-source-map' : ''))
|
||||
->exec('pnpm run scss')
|
||||
->exec('pnpm run autoprefixer')
|
||||
->run();
|
||||
|
||||
@ -332,7 +330,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
public function testUnit(array $opts = ['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false]) {
|
||||
$command = '../tests_env/vendor/bin/codecept run unit';
|
||||
$command = 'vendor/bin/codecept run unit';
|
||||
|
||||
if ($opts['file']) {
|
||||
$command .= ' -f ' . $opts['file'];
|
||||
@ -406,7 +404,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->testIntegration($opts);
|
||||
}
|
||||
|
||||
public function testAcceptance($opts = ['file' => null, 'skip-deps' => false, 'group' => null, 'timeout' => null, 'disable-hpos' => false, 'enable-hpos-sync' => false, 'enable-hpos' => false, 'wordpress-version' => null, 'skip-plugins' => false]) {
|
||||
public function testAcceptance($opts = ['file' => null, 'skip-deps' => false, 'group' => null, 'timeout' => null, 'disable-hpos' => false, 'enable-hpos-sync' => false, 'enable-hpos' => false, 'wordpress-version' => null]) {
|
||||
return $this->runTestsInContainer($opts);
|
||||
}
|
||||
|
||||
@ -422,7 +420,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
->option('env', 'US=' . $opts['us'])
|
||||
->option('env', 'PW=' . $opts['pw'])
|
||||
->option('env', 'K6_BROWSER_HEADLESS=' . ($opts['head'] ? 'false' : 'true'))
|
||||
->option('env', 'K6_BROWSER_TIMEOUT=' . getenv('K6_BROWSER_TIMEOUT'))
|
||||
->option('env', 'K6_BROWSER_TIMEOUT=120s')
|
||||
->option('env', 'SCENARIO=' . $opts['scenario'])
|
||||
->arg($path ?? "$dir/tests/performance/scenarios.js")
|
||||
->dir($dir)->run();
|
||||
@ -439,7 +437,6 @@ class RoboFile extends \Robo\Tasks {
|
||||
->option('env', 'SCENARIO=' . $opts['scenario'])
|
||||
->option('env', 'K6_CLOUD_TOKEN=' . getenv('K6_CLOUD_TOKEN'))
|
||||
->option('env', 'K6_CLOUD_ID=' . getenv('K6_CLOUD_ID'))
|
||||
->option('env', 'K6_BROWSER_TIMEOUT=' . getenv('K6_BROWSER_TIMEOUT'))
|
||||
->option('env', 'K6_PROJECT_NAME=' . $opts['scenario'])
|
||||
->option('out', 'cloud')
|
||||
->arg($path ?? "$dir/tests/performance/scenarios.js")
|
||||
@ -476,14 +473,14 @@ class RoboFile extends \Robo\Tasks {
|
||||
|
||||
// import data & run WordPress setup
|
||||
$this->say('Importing data and running a WordPress setup...');
|
||||
$this->taskExec('COMPOSE_HTTP_TIMEOUT=200 docker compose run --rm -it setup')
|
||||
$this->taskExec('COMPOSE_HTTP_TIMEOUT=200 docker-compose run --rm -it setup')
|
||||
->dir(__DIR__ . '/tests/performance')
|
||||
->run();
|
||||
$this->say('Data imported, WordPress set up.');
|
||||
}
|
||||
|
||||
public function testPerformanceClean() {
|
||||
$this->taskExec('COMPOSE_HTTP_TIMEOUT=200 docker compose down --remove-orphans -v')
|
||||
$this->taskExec('COMPOSE_HTTP_TIMEOUT=200 docker-compose down --remove-orphans -v')
|
||||
->dir(__DIR__ . '/tests/performance')
|
||||
->run();
|
||||
}
|
||||
@ -497,7 +494,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
*/
|
||||
public function deleteDocker() {
|
||||
return $this->taskExec(
|
||||
'docker compose down -v --remove-orphans --rmi all'
|
||||
'docker-compose down -v --remove-orphans --rmi all'
|
||||
)->dir(__DIR__ . '/../tests_env/docker')->run();
|
||||
}
|
||||
|
||||
@ -507,20 +504,20 @@ class RoboFile extends \Robo\Tasks {
|
||||
public function resetTestDocker() {
|
||||
return $this
|
||||
->taskExec(
|
||||
'docker compose down -v --remove-orphans'
|
||||
'docker-compose down -v --remove-orphans'
|
||||
)->dir(__DIR__ . '/../tests_env/docker')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
}
|
||||
|
||||
public function testFailedUnit() {
|
||||
$this->_exec('../tests_env/vendor/bin/codecept build');
|
||||
return $this->_exec('../tests_env/vendor/bin/codecept run unit -g failed');
|
||||
$this->_exec('vendor/bin/codecept build');
|
||||
return $this->_exec('vendor/bin/codecept run unit -g failed');
|
||||
}
|
||||
|
||||
public function testFailedIntegration() {
|
||||
$this->_exec('../tests_env/vendor/bin/codecept build');
|
||||
return $this->_exec('../tests_env/vendor/bin/codecept run integration -g failed');
|
||||
$this->_exec('vendor/bin/codecept build');
|
||||
return $this->_exec('vendor/bin/codecept run integration -g failed');
|
||||
}
|
||||
|
||||
public function containerDump() {
|
||||
@ -624,6 +621,9 @@ class RoboFile extends \Robo\Tasks {
|
||||
$collection->addCode(function() {
|
||||
return $this->qaCodeSniffer([]);
|
||||
});
|
||||
$collection->addCode(function() {
|
||||
return $this->qaMinimalPluginStandard([]);
|
||||
});
|
||||
return $collection->run();
|
||||
}
|
||||
|
||||
@ -650,7 +650,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
'lib/',
|
||||
'lib-3rd-party/',
|
||||
'vendor/composer',
|
||||
'vendor/dragonmantank',
|
||||
'vendor/mtdowling',
|
||||
'vendor-prefixed/',
|
||||
'vendor-prefixed/soundasleep',
|
||||
'mailpoet.php',
|
||||
@ -666,7 +666,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
'vendor-prefixed/cerdic/css-tidy/COPYING',
|
||||
'vendor-prefixed/cerdic/css-tidy/NEWS',
|
||||
'vendor-prefixed/cerdic/css-tidy/testing',
|
||||
'vendor/dragonmantank/cron-expression/tests',
|
||||
'vendor/mtdowling/cron-expression/tests',
|
||||
'vendor/phpmailer/phpmailer/test',
|
||||
'vendor-prefixed/psr/log/Psr/Log/Test',
|
||||
'vendor-prefixed/sabberworm/php-css-parser/tests',
|
||||
@ -686,21 +686,11 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
public function qaLintJavascript() {
|
||||
$collection = $this->collectionBuilder();
|
||||
return $collection->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('pnpm run check-types && pnpm run lint')
|
||||
->exec('cd .. && cd packages/js/email-editor && pnpm run check-types && pnpm run lint:js')
|
||||
->run();
|
||||
return $this->_exec('pnpm run check-types && pnpm run lint');
|
||||
}
|
||||
|
||||
public function qaLintCss() {
|
||||
$collection = $this->collectionBuilder();
|
||||
return $collection->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('pnpm run stylelint-check -- "assets/css/src/**/*.scss"')
|
||||
->exec('cd .. && cd packages/js/email-editor && pnpm run lint:css')
|
||||
->run();
|
||||
return $this->_exec('pnpm run stylelint-check -- "assets/css/src/**/*.scss"');
|
||||
}
|
||||
|
||||
public function qaCodeSniffer(array $filesToCheck, $opts = ['severity' => 'all']) {
|
||||
@ -729,7 +719,6 @@ class RoboFile extends \Robo\Tasks {
|
||||
'tasks/code_sniffer/vendor',
|
||||
'tasks/phpstan/vendor',
|
||||
'tasks/makepot',
|
||||
'tasks/minimal-plugin-standard/vendor',
|
||||
'tools/vendor',
|
||||
'tools/wpscan-semgrep-rules',
|
||||
'temp',
|
||||
@ -754,6 +743,58 @@ class RoboFile extends \Robo\Tasks {
|
||||
->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',
|
||||
'--parallel=' . $this->getParallelism(),
|
||||
'--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',
|
||||
'tools/wpscan-semgrep-rules',
|
||||
'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))
|
||||
->rawArg($stringFilesToCheck)
|
||||
->run();
|
||||
}
|
||||
|
||||
public function qaFixFile($filePath) {
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
if ($extension === 'php') {
|
||||
@ -761,7 +802,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->collectionBuilder()
|
||||
->taskExec(
|
||||
'./tasks/code_sniffer/vendor/bin/phpcbf ' .
|
||||
'--standard=tasks/code_sniffer/MailPoet/free-ruleset.xml ' .
|
||||
'--standard=./tasks/code_sniffer/MailPoet ' .
|
||||
'--runtime-set testVersion 7.4-8.2 ' .
|
||||
$filePath . ' -n'
|
||||
)
|
||||
@ -787,7 +828,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
// make sure Codeception support files are present to avoid invalid errors when running PHPStan
|
||||
$this->_exec('../tests_env/vendor/bin/codecept build');
|
||||
$this->_exec('vendor/bin/codecept build');
|
||||
|
||||
// PHPStan must be run out of main plugin directory to avoid its autoloading
|
||||
// from vendor/autoload.php where some dev dependencies cause conflicts.
|
||||
@ -811,10 +852,6 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->_exec('./tools/semgrep.sh lib/ lib-3rd-party/');
|
||||
}
|
||||
|
||||
public function qaQitSecurity() {
|
||||
return $this->_exec('./vendor/bin/qit run:security mailpoet --zip=mailpoet.zip --wait');
|
||||
}
|
||||
|
||||
public function svnCheckout() {
|
||||
$svnDir = ".mp_svn";
|
||||
|
||||
@ -1611,7 +1648,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
$annotationReaderProvider = new \MailPoet\Doctrine\Annotations\AnnotationReaderProvider();
|
||||
$configuration = (new \MailPoet\Doctrine\ConfigurationFactory($annotationReaderProvider, true))->createConfiguration();
|
||||
$platformClass = \MailPoetVendor\Doctrine\DBAL\Platforms\MySQLPlatform::class;
|
||||
$platformClass = \MailPoet\Doctrine\ConnectionFactory::PLATFORM_CLASS;
|
||||
return \MailPoetVendor\Doctrine\ORM\EntityManager::create([
|
||||
'driverClass' => \MailPoet\Doctrine\ConnectionFactory::DRIVER_CLASS,
|
||||
'platform' => new $platformClass,
|
||||
@ -1622,7 +1659,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
$testType = $opts['test_type'] ?? 'acceptance';
|
||||
$this->doctrineGenerateCache();
|
||||
return $this->taskExec(
|
||||
'COMPOSE_HTTP_TIMEOUT=200 docker compose run ' .
|
||||
'COMPOSE_HTTP_TIMEOUT=200 docker-compose run ' .
|
||||
(isset($opts['wordpress-version']) && $opts['wordpress-version'] ? '-e WORDPRESS_VERSION=' . $opts['wordpress-version'] . ' ' : '') .
|
||||
(isset($opts['skip-deps']) && $opts['skip-deps'] ? '-e SKIP_DEPS=1 ' : '') .
|
||||
(isset($opts['disable-hpos']) && $opts['disable-hpos'] ? '-e DISABLE_HPOS=1 ' : '') .
|
||||
|
@ -1,16 +1,11 @@
|
||||
.mailpoet_history_wrapper {
|
||||
display: grid;
|
||||
grid-gap: $grid-gap;
|
||||
grid-template-columns: 26px 26px auto;
|
||||
display: flex;
|
||||
padding: 12px 20px;
|
||||
|
||||
.mailpoet_reset_template {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet_history_arrow {
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
|
||||
svg {
|
||||
display: inline-block;
|
||||
|
@ -0,0 +1,61 @@
|
||||
.mailpoet_feature_announcement {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.button.mailpoet_feature_announcement_button {
|
||||
height: 28px;
|
||||
min-height: auto;
|
||||
padding: 0 5px 1px;
|
||||
position: relative;
|
||||
|
||||
@include respond-to(small-screen) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet_feature_announcement_icon {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.mailpoet_feature_announcement_dot:before {
|
||||
background: #d54e21;
|
||||
border-radius: 10px;
|
||||
content: '';
|
||||
display: block;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: -4px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.mailpoet_in_beamer_update_notice {
|
||||
background: #f00;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
padding: 20px 10px;
|
||||
position: fixed;
|
||||
right: -400px;
|
||||
text-align: center;
|
||||
transition: right 0.2s ease-in;
|
||||
width: 400px;
|
||||
z-index: 10000000000; // really has to be this high
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.beamer_show & {
|
||||
right: 0;
|
||||
}
|
||||
}
|
@ -95,6 +95,10 @@ h1.title.mailpoet-newsletter-listing-heading {
|
||||
#mailpoet_editor_steps_heading {
|
||||
.mailpoet-top-bar {
|
||||
left: 0;
|
||||
|
||||
.mailpoet-top-bar-beamer {
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-template-preview-image {
|
||||
max-width: 100%;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.mailpoet-template-info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -90,19 +90,14 @@
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.mailpoet-re-engagement-scheduling {
|
||||
display: grid;
|
||||
grid-template-columns: 5fr 2fr 3fr;
|
||||
|
||||
.mailpoet-form-input {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-re-engagement-scheduling-note {
|
||||
color: $color-input-error;
|
||||
}
|
||||
|
||||
.mailpoet-re-engagement-scheduling .mailpoet-form-input-small {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
[data-type='re_engagement'] .mailpoet-newsletter-type-image {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 178.78 177.29'%3E%3Cpath fill='%23fb8959' d='M147 35.1a.15.15 0 00-.05-.28l-2.28-.5a4.12 4.12 0 00-.68-1 4.34 4.34 0 00-.72-.62c-.1-.08-.2-.14-.29-.2l-.11-.07a4.3 4.3 0 00-.57-.28 3.82 3.82 0 00-2-.16c1.76-6.91-1.65-14.3-1.65-14.3L130 33l-14.63-11.19a21 21 0 00-.57 7v.38a14.93 14.93 0 003.95 8.81s.22.22.59.56a16 16 0 001.58 1.28 13.83 13.83 0 002.56 1.55c-1.16.86-2 1.55-2.51 1.92l-1.56 1.17-3 2.3-4.37 3.3s2.22.4 3.28-.51c0 0-1.36 1.45-1 2.86l4.17-3.37 4.55-3.67a20.58 20.58 0 002.85 1.67q.39.2.78.36h.1c.51.21 1 .39 1.49.54h.09a13.41 13.41 0 001.43.34h.08a9.48 9.48 0 001.36.16h.08a10.9 10.9 0 001.28 0h.08a9.77 9.77 0 001.21-.16h.07c.39-.08.77-.17 1.14-.28h.07a11.07 11.07 0 001.06-.4h.07q.53-.22 1-.48h.07l.47-.27.43-.28h.07l.43-.3.39-.3.06-.05c.14-.1.27-.21.4-.32l.35-.32.37-.34c.12-.1.22-.21.33-.32l.33-.35.29-.33.3-.35.43-.61.27-.34c.57-.74 1-1.4 1.34-1.9a8.91 8.91 0 001.39-3.58 2.47 2.47 0 00.07-.61 1.09 1.09 0 000-.18z'/%3E%3Cpath fill='%23a73e27' d='M76.9 67.52l23.39-41.74s22.9 49.52-21.93 65.53z'/%3E%3Cpath fill='%23fb8959' d='M86.34 74.31l-49-37.48S25.82 76.07 61.74 90.9z'/%3E%3Cpath fill='%23f86937' d='M84 73.17s-21.38 11.94-37.62 7.58c0 0 7.81 7.81 15.36 10.15z'/%3E%3Cpath fill='%23fb8959' d='M51.39 95.92s45.69-36.64 58.83-31c9.41 4 9.37 14.38 3.64 23.23s-26.35 38.28-62.47 7.77z'/%3E%3Cpath fill='%23fb8959' d='M55.52 92.76L28 113.55s6 1.09 8.88-1.4c0 0-3.68 3.95-2.66 7.77l26.83-21.69z'/%3E%3Cpath fill='%23a73e27' d='M115.62 70.48l7.19 1.6a.42.42 0 01.15.76l-5.57 3.78a5.27 5.27 0 00-1.77-6.14z'/%3E%3Cpath fill='%237e9ffc' d='M117.7 78.41s-10.67 10-18.61 5.21c-3.67-2.23-4.47-10.94 2.09-10.81 8.17.17 9-7.87 9-7.87s9.22 3.45 7.52 13.47z'/%3E%3Ccircle fill='%230a0851' cx='111.39' cy='72.88' r='1.46'/%3E%3Cpath fill='%23f86937' d='M57.78 100.81s-6-4.2-6.39-4.89l-3.32 2.47z'/%3E%3Cpath fill='%23f5a278' opacity='.55' d='M114.54 134.69l-2.43-1.4a1.73 1.73 0 000-.68 3 3 0 00-1.61-1.67 13.58 13.58 0 00-4.09-1.21 26.84 26.84 0 00-4.4-.23c4-3.54 7.92-7.83 0-10.86-5.93 2.78-13.18 5.3-16.88 8.58l-3.35 2.26-1.34.91-30.95-9s-8.56 11 17.13 15.62c-2.5.7-4.4 1.26-5.4 1.55l-19 5.43s4.69.32 6.92-.41c0 0-2.87 1.17-2.07 2.29l18.38-5.62c25 6.5 39.47-1.28 43.66-3.73a10.79 10.79 0 001.78-1.28z'/%3E%3Cpath fill='%23a73e27' d='M35.76 55.86s6.46 6 14.46 5.21c0 0-7.75 1.91-14.17-3.09zM37.16 63.77s7.24 5.67 15.37 3.14a17 17 0 01-14.68-1z'/%3E%3Cpath fill='%23f86937' d='M94.88 95.61c-4.4 8.61-22.37 9.68-29.33 9.77 24.07 11.66 39.33-4.57 46-13.85-1.29-4.35-11.76-5.53-16.67 4.08z'/%3E%3Cpath fill='%23a73e27' d='M45.51 110.79l-11.29 9.13c-1-3.82 2.66-7.77 2.66-7.77-2.86 2.49-8.88 1.4-8.88 1.4l11.83-8.94s-1.08 5.94 5.68 6.18z'/%3E%3Cpath fill='%230a0851' d='M117.89 76.75S107 87 99.09 82.14a6.8 6.8 0 01-2.76-5.07c-.26 2.51.86 5.39 2.76 6.55 7.94 4.81 18.61-5.21 18.61-5.21a7.87 7.87 0 00.19-1.66z'/%3E%3Cpath fill='%23fb8959' d='M73 34.31c.25-.08.49-.17.73-.27.24-.11.47-.22.69-.34l.33-.19.3-.19.3-.21.27-.21.28-.22.24-.22.2-.19c5.15 3.35 12.35.38 12.35.38l-9.08-5.59v-.15a2.19 2.19 0 000-.43v-.13l1.32-.9a.11.11 0 000-.19l-1.6-.36A3.14 3.14 0 0078 23.28c-1.3-.56-3.84.5-6.48 2a2.46 2.46 0 00-3.08.88l-1.29 1.94C64.76 29.76 63 31.2 63 31.2h.05l-6.55 1.16a3.29 3.29 0 002.19.73s-1.3.46-1.52 1.45l6.59-1.37-2.65 4s6.66 2.16 11.15-2.66l.68-.17zm6.92-8.44a3.82 3.82 0 00-.12-.66 1.43 1.43 0 01.12.66z'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
@ -161,12 +161,3 @@ ul.sending-method-benefits {
|
||||
.mailpoet_install_premium_message {
|
||||
margin-bottom: $grid-gap-medium;
|
||||
}
|
||||
|
||||
.mailpoet-verify-key-button {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.mailpoet-premium-key-toggle {
|
||||
height: 34px;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
@ -559,3 +559,38 @@ h2.mailpoet-heading {
|
||||
font-size: 85%;
|
||||
}
|
||||
}
|
||||
|
||||
// screen-reader-text CSS class only exists within the WordPress environment
|
||||
// the class does not exist when using iFrame forms due to these being used outside WordPress
|
||||
// prefixing with mailpoet-* to not interfere with the default WordPress screen-reader-text class
|
||||
.mailpoet-screen-reader-text {
|
||||
border: 0;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
-webkit-clip-path: inset(50%);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
word-wrap: normal !important;
|
||||
}
|
||||
|
||||
.mailpoet-screen-reader-text:focus {
|
||||
background-color: #ddd;
|
||||
clip: auto !important;
|
||||
-webkit-clip-path: none;
|
||||
clip-path: none;
|
||||
color: #444;
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
padding: 15px 23px 14px;
|
||||
right: 5px;
|
||||
text-decoration: none;
|
||||
top: 5px;
|
||||
width: auto;
|
||||
z-index: 100000;
|
||||
}
|
||||
|
@ -10,15 +10,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WP registration form
|
||||
form#registerform .g-recaptcha:not([data-size='invisible']) {
|
||||
scale: 0.9;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
// WC registration form
|
||||
form.woocommerce-form-register .g-recaptcha {
|
||||
padding-inline-start: 3px;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ textarea.parsley-error {
|
||||
|
||||
.parsley-errors-list {
|
||||
color: #900;
|
||||
font-size: 0.8rem;
|
||||
font-size: 13px;
|
||||
line-height: 1em;
|
||||
list-style-type: none;
|
||||
margin: 8px 0 3px;
|
||||
|
@ -1,3 +1,5 @@
|
||||
$beamer-dot-size: 8px;
|
||||
|
||||
.mailpoet-top-bar {
|
||||
align-items: center;
|
||||
background-color: $color-white;
|
||||
@ -81,7 +83,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-top-bar-tutorial {
|
||||
.mailpoet-top-bar-beamer {
|
||||
align-items: center;
|
||||
background-color: $color-white;
|
||||
border: none;
|
||||
@ -94,7 +96,6 @@
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
top: 4px;
|
||||
width: 75px;
|
||||
|
||||
svg {
|
||||
@ -102,3 +103,15 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-top-bar-beamer-dot:before {
|
||||
background: $color-editor-warning;
|
||||
border-radius: $beamer-dot-size;
|
||||
content: '';
|
||||
display: block;
|
||||
height: $beamer-dot-size;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
top: 2px;
|
||||
width: $beamer-dot-size;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
&:focus {
|
||||
~ .mailpoet-form-checkbox-control {
|
||||
border-color: $color-input-border-focus;
|
||||
border: 2px solid $color-input-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
&:focus {
|
||||
~ .mailpoet-form-radio-control {
|
||||
border-color: $color-input-border-focus;
|
||||
border: 2px solid $color-input-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,9 @@
|
||||
|
||||
input {
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
width: 1px;
|
||||
|
||||
&:focus {
|
||||
~ .mailpoet-form-toggle-control {
|
||||
border-color: $color-input-border-focus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,16 +22,9 @@
|
||||
|
||||
input {
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
width: 1px;
|
||||
|
||||
&:focus {
|
||||
~ .mailpoet-form-yesno-control {
|
||||
box-shadow: 0 0 0 1px $color-input-border-focus;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,3 +110,10 @@ span.mailpoet-gap-half {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.mailpoet-is-dragging {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
@ -4,36 +4,6 @@
|
||||
line-height: $line-height;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
caption,
|
||||
figcaption {
|
||||
// https://developer.chrome.com/docs/css-ui/css-text-wrap-balance
|
||||
text-wrap: balance;
|
||||
|
||||
// Skip in email editor to preserve WYSIWYG functionality
|
||||
.mailpoet_newsletter_wrapper & {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
// https://developer.chrome.com/blog/css-text-wrap-pretty/
|
||||
text-wrap: pretty;
|
||||
|
||||
// Skip in email editor to preserve WYSIWYG functionality
|
||||
.mailpoet_newsletter_wrapper & {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.admin_page_mailpoet-form-editor #wpbody {
|
||||
color: inherit;
|
||||
}
|
||||
|
@ -87,6 +87,7 @@
|
||||
@import 'components-plugin/newsletter-types';
|
||||
@import 'components-plugin/newsletter-template-styles';
|
||||
@import 'components-plugin/welcome-wizard';
|
||||
@import 'components-plugin/feature-announcement';
|
||||
@import 'components-plugin/newsletter-congratulate';
|
||||
@import 'components-plugin/discounts';
|
||||
@import 'components-plugin/review-request';
|
||||
|
@ -31,7 +31,6 @@ $color-tertiary-hover: darken($color-tertiary, 10%);
|
||||
$color-tertiary-light: #dcdcde;
|
||||
$color-grey-0: #f6f7f7;
|
||||
$color-tertiary-light-hover: darken($color-tertiary-light, 10%);
|
||||
$color-tertiary-light-focus: #777;
|
||||
$color-tertiary-light-background: rgba($color-tertiary-light, 0.3);
|
||||
$color-destructive: #b52727;
|
||||
$color-destructive-hover: #a02222;
|
||||
@ -47,7 +46,6 @@ $color-text-dark: #2c3338;
|
||||
// Form colors
|
||||
$color-input-background: #fdfdff;
|
||||
$color-input-border: $color-tertiary-light-hover;
|
||||
$color-input-border-focus: $color-tertiary-light-focus;
|
||||
$color-input-error: #f00;
|
||||
$color-input-success: #7ed321;
|
||||
|
||||
|
@ -53,7 +53,7 @@ function exportMixpanel() {
|
||||
|
||||
if (
|
||||
window.mailpoet_analytics_enabled &&
|
||||
window.mailpoet_3rd_party_libs_enabled
|
||||
window.MailPoet.libs3rdPartyEnabled
|
||||
) {
|
||||
window.MailPoet.trackEvent = track;
|
||||
} else {
|
||||
@ -104,30 +104,11 @@ function cacheEvent(forced, name, data, options, callback) {
|
||||
}
|
||||
|
||||
export function initializeMixpanelWhenLoaded() {
|
||||
const MAX_RETRY = 5;
|
||||
let intervalId;
|
||||
let retryCount = 0;
|
||||
|
||||
const setupMixpanel = () => {
|
||||
if (typeof window.mixpanel === 'object') {
|
||||
exportMixpanel();
|
||||
trackCachedEvents();
|
||||
};
|
||||
|
||||
if (typeof window.mixpanel === 'object') {
|
||||
setupMixpanel();
|
||||
} else {
|
||||
intervalId = setInterval(() => {
|
||||
if (typeof window.mixpanel === 'object') {
|
||||
clearInterval(intervalId);
|
||||
setupMixpanel();
|
||||
} else {
|
||||
retryCount += 1;
|
||||
}
|
||||
|
||||
if (retryCount > MAX_RETRY) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 100);
|
||||
setTimeout(initializeMixpanelWhenLoaded, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
import classnames from 'classnames';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { withFeatureAnnouncement } from './with-feature-announcement';
|
||||
|
||||
type Props = {
|
||||
hasNews: boolean;
|
||||
onBeamerClick: () => void;
|
||||
};
|
||||
|
||||
function FeatureAnnouncementComponent({ hasNews, onBeamerClick }: Props) {
|
||||
const buttonClasses = classnames(
|
||||
'button mailpoet_feature_announcement_button',
|
||||
hasNews ? 'mailpoet_feature_announcement_dot' : '',
|
||||
);
|
||||
return (
|
||||
<div className="mailpoet_feature_announcement">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBeamerClick}
|
||||
className={buttonClasses}
|
||||
title={MailPoet.I18n.t('whatsNew')}
|
||||
>
|
||||
<span className="mailpoet_feature_announcement_icon dashicons dashicons-carrot" />
|
||||
</button>
|
||||
<span id="beamer-empty-element" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const FeatureAnnouncement = withFeatureAnnouncement(
|
||||
FeatureAnnouncementComponent,
|
||||
);
|
||||
export { FeatureAnnouncement };
|
@ -0,0 +1,111 @@
|
||||
import { ComponentType, FC } from 'react';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import ReactStringReplace from 'react-string-replace';
|
||||
import jQuery from 'jquery';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
interface FeatureAnnouncementWindow extends Window {
|
||||
Beamer: {
|
||||
show: () => void;
|
||||
};
|
||||
mailpoet_feature_announcement_has_news: boolean;
|
||||
mailpoet_update_available: boolean;
|
||||
beamer_config: {
|
||||
product_id: string;
|
||||
selector: string;
|
||||
language: string;
|
||||
callback: () => void;
|
||||
filter?: string;
|
||||
};
|
||||
mailpoet_user_locale: string;
|
||||
}
|
||||
|
||||
declare let window: FeatureAnnouncementWindow;
|
||||
|
||||
export const withFeatureAnnouncement = <P extends Record<string, unknown>>(
|
||||
Component: ComponentType<P>,
|
||||
): FC<Omit<P, 'hasNews' | 'onBeamerClick'>> => {
|
||||
const isBeamerInitialized = () => typeof window.Beamer !== 'undefined';
|
||||
let showDot = window.mailpoet_feature_announcement_has_news;
|
||||
let beamerCallback;
|
||||
|
||||
function showPluginUpdateNotice() {
|
||||
if (
|
||||
!window.mailpoet_update_available ||
|
||||
document.getElementById('mailpoet_update_notice')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const updateMailPoetNotice = ReactStringReplace(
|
||||
MailPoet.I18n.t('updateMailPoetNotice'),
|
||||
/\[link\](.*?)\[\/link\]/,
|
||||
(match) => `<a href="update-core.php">${match}</a>`,
|
||||
).join('');
|
||||
jQuery('#beamerOverlay').append(
|
||||
`<p id="mailpoet_update_notice" class="mailpoet_in_beamer_update_notice">${updateMailPoetNotice}</p>`,
|
||||
);
|
||||
}
|
||||
|
||||
function updateLastAnnouncementSeenValue() {
|
||||
const data = { last_announcement_seen: Math.floor(Date.now() / 1000) };
|
||||
void MailPoet.Ajax.post({
|
||||
api_version: MailPoet.apiVersion,
|
||||
endpoint: 'user_flags',
|
||||
action: 'set',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
function loadBeamer() {
|
||||
window.beamer_config = {
|
||||
product_id: 'VvHbhYWy7118',
|
||||
selector: '#beamer-empty-element',
|
||||
language: window.mailpoet_user_locale,
|
||||
callback: beamerCallback,
|
||||
};
|
||||
if (MailPoet.isWoocommerceActive) {
|
||||
window.beamer_config.filter = 'woocommerce';
|
||||
}
|
||||
MailPoet.Modal.loading(true);
|
||||
window.mailpoet_feature_announcement_has_news = false;
|
||||
const s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
s.src = 'https://app.getbeamer.com/js/beamer-embed.js';
|
||||
document.getElementsByTagName('body')[0].appendChild(s);
|
||||
}
|
||||
|
||||
function showBeamer(event = null) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (!isBeamerInitialized()) {
|
||||
loadBeamer();
|
||||
return;
|
||||
}
|
||||
showDot = false;
|
||||
beamerCallback = noop; // We show Beamer panel only on first callback after initialization
|
||||
MailPoet.Modal.loading(false);
|
||||
window.Beamer.show();
|
||||
updateLastAnnouncementSeenValue();
|
||||
showPluginUpdateNotice();
|
||||
}
|
||||
|
||||
beamerCallback = () => {
|
||||
if (!isBeamerInitialized()) {
|
||||
return;
|
||||
}
|
||||
showBeamer();
|
||||
};
|
||||
|
||||
return function withFeatureAnnouncementRenderer({
|
||||
...props
|
||||
}: Omit<P, 'hasNews' | 'onBeamerClick'>) {
|
||||
return (
|
||||
<Component
|
||||
{...(props as P)}
|
||||
onBeamerClick={(e) => showBeamer(e)}
|
||||
hasNews={showDot}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
@ -16,11 +16,7 @@ export type ApiError = {
|
||||
|
||||
export const initializeApi = () => {
|
||||
apiFetch.use((options, next) => {
|
||||
if (
|
||||
options.path &&
|
||||
(options.path.startsWith('/wc-analytics/') ||
|
||||
options.path.startsWith('/wp/v2/'))
|
||||
) {
|
||||
if (options.path && options.path.startsWith('/wc-analytics/')) {
|
||||
return apiFetch.createRootURLMiddleware(`${api.root}/`)(options, next);
|
||||
}
|
||||
return apiFetch.createRootURLMiddleware(apiUrl)(options, next);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { TopBarWithBoundary } from 'common/top-bar/top-bar';
|
||||
import { TopBarWithBeamer } from 'common/top-bar/top-bar';
|
||||
import { SlotFillProvider } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { registerTranslations } from 'common';
|
||||
@ -72,7 +72,7 @@ function Automations(): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBarWithBoundary />
|
||||
<TopBarWithBeamer />
|
||||
<GlobalNotices />
|
||||
<Notices />
|
||||
<MssAccessNotices />
|
||||
|
@ -3,23 +3,14 @@ import { FlowSeparator } from './flow-separator';
|
||||
import { Step as StepData } from './types';
|
||||
|
||||
type Props = {
|
||||
previousStepData: StepData;
|
||||
stepData: StepData;
|
||||
index: number;
|
||||
nextStepData?: StepData;
|
||||
};
|
||||
|
||||
export function FlowEnding({
|
||||
previousStepData,
|
||||
index,
|
||||
nextStepData,
|
||||
}: Props): JSX.Element {
|
||||
export function FlowEnding({ stepData, index }: Props): JSX.Element {
|
||||
return (
|
||||
<div className="mailpoet-automation-editor-step-wrapper">
|
||||
<FlowSeparator
|
||||
previousStepData={previousStepData}
|
||||
nextStepData={nextStepData}
|
||||
index={index}
|
||||
/>
|
||||
<FlowSeparator stepData={stepData} index={index} />
|
||||
<Icon
|
||||
className="mailpoet-automation-editor-automation-end"
|
||||
icon={check}
|
||||
|
@ -6,9 +6,8 @@ import { Step as StepData } from './types';
|
||||
import { RenderStepSeparatorType } from '../../../types/filters';
|
||||
|
||||
type Props = {
|
||||
previousStepData: StepData;
|
||||
stepData: StepData;
|
||||
index: number;
|
||||
nextStepData?: StepData;
|
||||
};
|
||||
|
||||
export function FlowSeparator(props: Props): JSX.Element {
|
||||
@ -36,9 +35,5 @@ export function FlowSeparator(props: Props): JSX.Element {
|
||||
),
|
||||
[context],
|
||||
);
|
||||
return renderSeparator(
|
||||
props.previousStepData,
|
||||
props.index,
|
||||
props.nextStepData,
|
||||
);
|
||||
return renderSeparator(props.stepData, props.index);
|
||||
}
|
||||
|
@ -40,24 +40,13 @@ export function Flow({ stepData, row }: Props): JSX.Element {
|
||||
|
||||
return nextStepData ? (
|
||||
<div key={id}>
|
||||
{row > 0 && (
|
||||
<FlowSeparator
|
||||
previousStepData={stepData}
|
||||
index={i}
|
||||
nextStepData={nextStepData}
|
||||
/>
|
||||
)}
|
||||
{row > 0 && <FlowSeparator stepData={stepData} index={i} />}
|
||||
<FlowStep stepData={nextStepData} index={i} />
|
||||
<Flow stepData={nextStepData} row={row + 1} />
|
||||
</div>
|
||||
) : (
|
||||
<FlowEnding
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={i}
|
||||
previousStepData={stepData}
|
||||
index={i}
|
||||
nextStepData={nextStepData}
|
||||
/>
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<FlowEnding key={i} stepData={stepData} index={i} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
@ -143,7 +143,6 @@ export function* activate() {
|
||||
return {
|
||||
type: 'ACTIVATE',
|
||||
automation: data?.data ?? automation,
|
||||
saved: !!data?.data,
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
@ -41,15 +41,11 @@ export function reducer(state: State, action): State {
|
||||
savedState: 'saved',
|
||||
};
|
||||
case 'ACTIVATE':
|
||||
return action.saved
|
||||
? {
|
||||
...state,
|
||||
automationData: action.automation,
|
||||
savedState: 'saved',
|
||||
}
|
||||
: {
|
||||
...state,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
automationData: action.automation,
|
||||
savedState: 'saved',
|
||||
};
|
||||
case 'DEACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
|
@ -50,7 +50,6 @@ export function initHooks() {
|
||||
return function StatisticSeparatorWrapper(
|
||||
previousStepData: StepData,
|
||||
index: number,
|
||||
nextStepData: StepData,
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
@ -63,11 +62,7 @@ export function initHooks() {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<StatisticSeparator
|
||||
previousStep={previousStepData}
|
||||
nextStep={nextStepData}
|
||||
index={index}
|
||||
/>
|
||||
<StatisticSeparator previousStep={previousStepData} index={index} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -8,13 +8,11 @@ import { Step } from '../../../../../../editor/components/automation/types';
|
||||
type Props = {
|
||||
previousStep: Step;
|
||||
index: number;
|
||||
nextStep?: Step;
|
||||
};
|
||||
|
||||
export function StatisticSeparator({
|
||||
previousStep,
|
||||
index,
|
||||
nextStep,
|
||||
}: Props): JSX.Element | null {
|
||||
const { section, stepType } = useSelect(
|
||||
(s) => ({
|
||||
@ -54,34 +52,15 @@ export function StatisticSeparator({
|
||||
);
|
||||
}
|
||||
|
||||
const completed = data.step_data?.completed || {};
|
||||
const failed = data.step_data?.failed || {};
|
||||
const waiting = data.step_data?.waiting || {};
|
||||
const calculateTotals = (id) =>
|
||||
(completed[id] ?? 0) + (failed[id] ?? 0) + (waiting[id] ?? 0);
|
||||
let totalEntered = 0;
|
||||
if (nextStep) {
|
||||
totalEntered = calculateTotals(nextStep.id);
|
||||
} else if (previousStep.next_steps.length === 2) {
|
||||
// When there is no next step and the previous step has 2+ next steps we are
|
||||
// in an empty if/else branch. To calculate the total we need to subtract
|
||||
// totalEntered of the sibling step from totalEntered of previousStep
|
||||
const siblingStep = previousStep.next_steps.find((step) => step.id);
|
||||
const totalEnteredSibling = siblingStep
|
||||
? calculateTotals(siblingStep.id)
|
||||
: 0;
|
||||
const totalEnteredPrevious = completed[previousStep.id] ?? 0;
|
||||
totalEntered = totalEnteredPrevious - totalEnteredSibling;
|
||||
} else {
|
||||
totalEntered = completed[previousStep.id] ?? 0;
|
||||
}
|
||||
const flow = data.step_data?.flow;
|
||||
const value = flow !== undefined ? flow[previousStep.id] ?? 0 : 0;
|
||||
const percent =
|
||||
data.step_data.total > 0
|
||||
? Math.round((totalEntered / data.step_data.total) * 100)
|
||||
? Math.round((value / data.step_data.total) * 100)
|
||||
: 0;
|
||||
const formattedValue = Intl.NumberFormat(locale.toString(), {
|
||||
notation: 'compact',
|
||||
}).format(totalEntered);
|
||||
}).format(value);
|
||||
const formattedPercent = Intl.NumberFormat(locale.toString(), {
|
||||
style: 'percent',
|
||||
}).format(percent / 100);
|
||||
|
@ -2,7 +2,7 @@ import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { dispatch, select, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { TopBarWithBoundary } from '../../../../common/top-bar/top-bar';
|
||||
import { TopBarWithBeamer } from '../../../../common/top-bar/top-bar';
|
||||
import { Notices } from '../../../listing/components/notices';
|
||||
import { Header } from './components/header';
|
||||
import { Overview } from './components/overview';
|
||||
@ -49,7 +49,7 @@ function TopBarWithBreadcrumb(): JSX.Element {
|
||||
}));
|
||||
|
||||
return (
|
||||
<TopBarWithBoundary>
|
||||
<TopBarWithBeamer>
|
||||
<p className="mailpoet-automation-analytics-title">
|
||||
<a href={MailPoet.urls.automationListing}>
|
||||
{__('Automations', 'mailpoet')}
|
||||
@ -57,7 +57,7 @@ function TopBarWithBreadcrumb(): JSX.Element {
|
||||
› <strong>{automation.name}</strong>
|
||||
<AutomationStatus status={automation.status} />
|
||||
</p>
|
||||
</TopBarWithBoundary>
|
||||
</TopBarWithBeamer>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ const sections: Record<string, Section> = {
|
||||
},
|
||||
},
|
||||
customQuery: {
|
||||
order: 'desc',
|
||||
order: 'asc',
|
||||
order_by: 'updated_at',
|
||||
limit: 25,
|
||||
page: 1,
|
||||
|
@ -167,7 +167,7 @@ export type StepFlowData = {
|
||||
total: number;
|
||||
waiting: Record<string, number> | undefined;
|
||||
failed: Record<string, number> | undefined;
|
||||
completed: Record<string, number> | undefined;
|
||||
flow: Record<string, number> | undefined;
|
||||
};
|
||||
|
||||
export type AutomationFlowSectionData = SectionData & {
|
||||
|
@ -3,7 +3,6 @@ import { Step } from '../../../../../editor/components/automation/types';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
|
||||
const transactionalTriggers = [
|
||||
'mailpoet:custom-trigger',
|
||||
'woocommerce:order-status-changed',
|
||||
'woocommerce:order-created',
|
||||
'woocommerce:order-completed',
|
||||
@ -16,9 +15,6 @@ const transactionalTriggers = [
|
||||
'woocommerce-subscriptions:subscription-status-changed',
|
||||
'woocommerce-subscriptions:trial-ended',
|
||||
'woocommerce-subscriptions:trial-started',
|
||||
'woocommerce:buys-from-a-tag',
|
||||
'woocommerce:buys-from-a-category',
|
||||
'woocommerce:buys-a-product',
|
||||
];
|
||||
|
||||
export function isTransactional(step: Step): boolean {
|
||||
|
@ -7,7 +7,6 @@ import { step as AbandonedCartTrigger } from './steps/abandoned-cart';
|
||||
import { MailPoet } from '../../../mailpoet';
|
||||
import { step as BuysAProductTrigger } from './steps/buys-a-product';
|
||||
import { step as BuysFromACategory } from './steps/buys-from-a-category';
|
||||
import { step as BuysFromATag } from './steps/buys-from-a-tag';
|
||||
import { step as MadeAReview } from './steps/made-a-review';
|
||||
// Insert new imports here
|
||||
|
||||
@ -22,7 +21,6 @@ export const initialize = (): void => {
|
||||
registerStepType(AbandonedCartTrigger);
|
||||
registerStepType(BuysAProductTrigger);
|
||||
registerStepType(BuysFromACategory);
|
||||
registerStepType(BuysFromATag);
|
||||
registerStepType(MadeAReview);
|
||||
// Insert new steps here
|
||||
};
|
||||
|
@ -1,86 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Search } from '@woocommerce/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { PanelBody } from '@wordpress/components';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { PlainBodyTitle } from '../../../../../editor/components';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
import { OrderStatusPanel } from '../../order-status-changed/edit/order-status-panel';
|
||||
import autocompleter from './tag-autocompleter';
|
||||
|
||||
type Tag = {
|
||||
key: string | number;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
async function fetchTags(include: number[], callback: (tags: Tag[]) => void) {
|
||||
const path = addQueryArgs('/wp/v2/product_tag/', { include });
|
||||
const data: { id: number; name: string }[] = await apiFetch({
|
||||
path,
|
||||
method: 'GET',
|
||||
});
|
||||
callback(data.map((item) => ({ key: item?.id, label: item?.name })));
|
||||
}
|
||||
|
||||
export function Edit(): JSX.Element {
|
||||
const [current, setCurrent] = useState<Tag[]>([]);
|
||||
const { selectedStep } = useSelect((select) => ({
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
}));
|
||||
const tagIds: number[] = useMemo(
|
||||
() => (selectedStep.args?.tag_ids as number[]) ?? [],
|
||||
[selectedStep],
|
||||
);
|
||||
const [isBusy, setIsBusy] = useState(tagIds.length > 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBusy) {
|
||||
return;
|
||||
}
|
||||
void fetchTags(tagIds, (tags: Tag[]) => {
|
||||
setCurrent(tags);
|
||||
setIsBusy(false);
|
||||
});
|
||||
}, [isBusy, tagIds]);
|
||||
return (
|
||||
<>
|
||||
<PanelBody opened>
|
||||
<PlainBodyTitle title={__('Tags', 'mailpoet')} />
|
||||
<Search
|
||||
disabled={isBusy}
|
||||
type="custom"
|
||||
autocompleter={autocompleter}
|
||||
className={`mailpoet-product-search ${isBusy ? 'is-busy' : ''}`}
|
||||
placeholder={__('Search for a tag', 'mailpoet')}
|
||||
selected={current}
|
||||
onChange={(items: Tag[]) => {
|
||||
setCurrent(items);
|
||||
void dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
'tag_ids',
|
||||
items.map((item) => item.key),
|
||||
);
|
||||
}}
|
||||
multiple
|
||||
inlineTags
|
||||
/>
|
||||
</PanelBody>
|
||||
|
||||
<OrderStatusPanel
|
||||
label={__('Order settings', 'mailpoet')}
|
||||
showFrom={false}
|
||||
showTo
|
||||
toLabel={__('Order status', 'mailpoet')}
|
||||
onChange={(status, property) => {
|
||||
void dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
property,
|
||||
status,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { AutoCompleter } from '@woocommerce/components/build-types/search/autocompleters';
|
||||
|
||||
const tagAutoCompleter: AutoCompleter = {
|
||||
name: 'tags',
|
||||
className: 'woocommerce-search__product-result',
|
||||
options(search) {
|
||||
const query = search
|
||||
? {
|
||||
search,
|
||||
per_page: 10,
|
||||
orderby: 'count',
|
||||
}
|
||||
: {};
|
||||
return apiFetch({
|
||||
path: addQueryArgs('/wp/v2/product_tag', query),
|
||||
});
|
||||
},
|
||||
isDebounced: true,
|
||||
getOptionIdentifier(tag) {
|
||||
return tag.id as number;
|
||||
},
|
||||
getOptionKeywords(tag) {
|
||||
return [tag.name] as string[];
|
||||
},
|
||||
getFreeTextOptions(query) {
|
||||
const label = (
|
||||
<span key="name" className="woocommerce-search__result-name">
|
||||
{__('Search results', 'mailpoet')}
|
||||
</span>
|
||||
);
|
||||
const titleOption = {
|
||||
key: 'title',
|
||||
label,
|
||||
value: { id: query, name: query },
|
||||
};
|
||||
|
||||
return [titleOption];
|
||||
},
|
||||
getOptionLabel(tag) {
|
||||
return (
|
||||
<span
|
||||
key="name"
|
||||
className="woocommerce-search__result-name"
|
||||
aria-label={tag.name}
|
||||
>
|
||||
{tag.name}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
// This is slightly different than gutenberg/Autocomplete, we don't support different methods
|
||||
// of replace/insertion, so we can just return the value.
|
||||
getOptionCompletion(tag) {
|
||||
const value = {
|
||||
key: tag.id,
|
||||
label: tag.name,
|
||||
};
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
export default tagAutoCompleter;
|
@ -1,26 +0,0 @@
|
||||
export function Icon(): JSX.Element {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.73173 4.00134C5.8081 3.03517 6.61579 2.27551 7.60166 2.27551C8.58753 2.27551 9.39522 3.03517 9.47159 4.00134H10.1904C10.7479 4.00134 11.2033 4.45681 11.2033 5.01426V10.1917C11.2033 10.7492 10.7479 11.2047 10.1904 11.2047H5.01292C4.45547 11.2047 4 10.7492 4 10.1917V5.01426C4 4.45681 4.45547 4.00134 5.01292 4.00134H5.73173ZM7.60166 3.43843C7.36389 3.43843 7.15161 3.55664 7.02145 3.73861C6.96586 3.81633 6.92553 3.90533 6.90474 4.00134H8.29858C8.27779 3.90533 8.23746 3.81633 8.18187 3.73861C8.05171 3.55664 7.83943 3.43843 7.60166 3.43843ZM5.16291 5.16426V9.65285C5.16291 9.86763 5.33703 10.0417 5.55181 10.0417H9.65151C9.86629 10.0417 10.0404 9.86763 10.0404 9.65285V5.16426H9.47749V5.87717C9.47749 6.19732 9.21618 6.45863 8.89603 6.45863C8.57589 6.45863 8.31458 6.19732 8.31458 5.87717V5.16426H6.88875V5.87717C6.88875 6.19732 6.62743 6.45863 6.30729 6.45863C5.98714 6.45863 5.72583 6.19732 5.72583 5.87717V5.16426H5.16291Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M13.5858 4.58722C13.9609 4.21215 14.4696 4.00143 15 4.00143H18C18.5304 4.00143 19.0391 4.21215 19.4142 4.58722C19.7893 4.96229 20 5.471 20 6.00143V9.00143C20 9.53187 19.7893 10.0406 19.4142 10.4156C19.0391 10.7907 18.5304 11.0014 18 11.0014H15C14.4696 11.0014 13.9609 10.7907 13.5858 10.4156C13.2107 10.0406 13 9.53187 13 9.00143V6.00143C13 5.471 13.2107 4.96229 13.5858 4.58722ZM15 5.50143H18C18.1326 5.50143 18.2598 5.55411 18.3536 5.64788C18.4473 5.74165 18.5 5.86883 18.5 6.00143V9.00143C18.5 9.13404 18.4473 9.26122 18.3536 9.35499C18.2598 9.44876 18.1326 9.50143 18 9.50143H15C14.8674 9.50143 14.7402 9.44876 14.6464 9.35499C14.5527 9.26122 14.5 9.13404 14.5 9.00143V6.00143C14.5 5.86883 14.5527 5.74165 14.6464 5.64788C14.7402 5.55411 14.8674 5.50143 15 5.50143Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M13.5858 13.5844C13.9609 13.2093 14.4696 12.9986 15 12.9986H18C18.5304 12.9986 19.0391 13.2093 19.4142 13.5844C19.7893 13.9594 20 14.4681 20 14.9986V17.9986C20 18.529 19.7893 19.0377 19.4142 19.4128C19.0391 19.7879 18.5304 19.9986 18 19.9986H15C14.4696 19.9986 13.9609 19.7879 13.5858 19.4128C13.2107 19.0377 13 18.529 13 17.9986V14.9986C13 14.4681 13.2107 13.9594 13.5858 13.5844ZM15 14.4986H18C18.1326 14.4986 18.2598 14.5513 18.3536 14.645C18.4473 14.7388 18.5 14.866 18.5 14.9986V17.9986C18.5 18.1312 18.4473 18.2584 18.3536 18.3521C18.2598 18.4459 18.1326 18.4986 18 18.4986H15C14.8674 18.4986 14.7402 18.4459 14.6464 18.3521C14.5527 18.2584 14.5 18.1312 14.5 17.9986V14.9986C14.5 14.866 14.5527 14.7388 14.6464 14.645C14.7402 14.5513 14.8674 14.4986 15 14.4986Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.58579 13.5844C4.21071 13.9594 4 14.4681 4 14.9986V17.9986C4 18.529 4.21071 19.0377 4.58579 19.4128C4.96086 19.7879 5.46957 19.9986 6 19.9986H9C9.53043 19.9986 10.0391 19.7879 10.4142 19.4128C10.7893 19.0377 11 18.529 11 17.9986V14.9986C11 14.4681 10.7893 13.9594 10.4142 13.5844C10.0391 13.2093 9.53043 12.9986 9 12.9986H6C5.46957 12.9986 4.96086 13.2093 4.58579 13.5844ZM9 14.4986H6C5.86739 14.4986 5.74021 14.5513 5.64645 14.645C5.55268 14.7388 5.5 14.866 5.5 14.9986V17.9986C5.5 18.1312 5.55268 18.2584 5.64645 18.3521C5.74021 18.4459 5.86739 18.4986 6 18.4986H9C9.13261 18.4986 9.25979 18.4459 9.35355 18.3521C9.44732 18.2584 9.5 18.1312 9.5 17.9986V14.9986C9.5 14.866 9.44732 14.7388 9.35355 14.645C9.25979 14.5513 9.13261 14.4986 9 14.4986Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { StepType } from '../../../../editor/store';
|
||||
import { Edit } from './edit';
|
||||
import { Icon } from './icon';
|
||||
|
||||
const keywords = [
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('tag', 'mailpoet'),
|
||||
// translators: verb, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('buy', 'mailpoet'),
|
||||
// translators: verb, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('purchase', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('ecommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('woocommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('product', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('order', 'mailpoet'),
|
||||
];
|
||||
export const step: StepType = {
|
||||
key: 'woocommerce:buys-from-a-tag',
|
||||
group: 'triggers',
|
||||
title: () => __('Customer buys from a tag', 'mailpoet'),
|
||||
description: () =>
|
||||
__(
|
||||
'Start the automation when a customer buys a product from a tag.',
|
||||
'mailpoet',
|
||||
),
|
||||
|
||||
subtitle: () => __('Trigger', 'mailpoet'),
|
||||
keywords,
|
||||
foreground: '#2271b1',
|
||||
background: '#f0f6fc',
|
||||
icon: () => <Icon />,
|
||||
edit: () => <Edit />,
|
||||
} as const;
|
@ -4,27 +4,27 @@ import { Icon } from './icon';
|
||||
import { PremiumModalForStepEdit } from '../../../../components/premium-modal-steps-edit';
|
||||
|
||||
const keywords = [
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('review', 'mailpoet'),
|
||||
// translators: verb, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: verb, used as a search keyword for "Customer makes a review" trigger
|
||||
__('buy', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('comment', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('ecommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('woocommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('product', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('order', 'mailpoet'),
|
||||
];
|
||||
export const step: StepType = {
|
||||
key: 'woocommerce:made-a-review',
|
||||
group: 'triggers',
|
||||
title: () => __('Customer posts a review', 'mailpoet'),
|
||||
title: () => __('Customer makes a review', 'mailpoet'),
|
||||
description: () =>
|
||||
__('Start the automation when a customer posts a review.', 'mailpoet'),
|
||||
__('Start the automation when a customer makes a review.', 'mailpoet'),
|
||||
|
||||
subtitle: () => __('Trigger', 'mailpoet'),
|
||||
keywords,
|
||||
|
@ -3,7 +3,7 @@ import { __ } from '@wordpress/i18n';
|
||||
import { registerTranslations } from 'common';
|
||||
import { automationTemplateCategories, automationTemplates } from './config';
|
||||
import { initializeApi } from '../api';
|
||||
import { TopBarWithBoundary } from '../../common/top-bar/top-bar';
|
||||
import { TopBarWithBeamer } from '../../common/top-bar/top-bar';
|
||||
import { FromScratchButton } from './components/from-scratch';
|
||||
import { BackButton, PageHeader } from '../../common/page-header';
|
||||
import { MailPoet } from '../../mailpoet';
|
||||
@ -50,7 +50,7 @@ function Templates(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="mailpoet-main-container">
|
||||
<TopBarWithBoundary />
|
||||
<TopBarWithBeamer />
|
||||
<PageHeader
|
||||
heading={__('Start with a template', 'mailpoet')}
|
||||
headingPrefix={
|
||||
|
@ -37,9 +37,8 @@ export type RenderStepFooterType = JSX.Element | null;
|
||||
|
||||
// mailpoet.automation.render_step_separator
|
||||
export type RenderStepSeparatorType = (
|
||||
previousStep: Step,
|
||||
step: Step,
|
||||
index: number,
|
||||
nextStep?: Step,
|
||||
) => JSX.Element;
|
||||
|
||||
// mailpoet.automation.editor.create_store
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { PlacesType } from 'react-tooltip';
|
||||
import { Badge } from './badge';
|
||||
|
||||
@ -10,59 +10,47 @@ type StatsBadgeProps = {
|
||||
isInverted?: boolean;
|
||||
};
|
||||
|
||||
const getStats = () => ({
|
||||
const stats = {
|
||||
opened: {
|
||||
badgeRanges: [30, 10, 0],
|
||||
badgeTypes: ['excellent', 'good', 'critical'],
|
||||
tooltipText: {
|
||||
// translators: Shows a percentage range, "above 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
excellent: sprintf(__('above %s%%', 'mailpoet'), 30),
|
||||
// translators: Shows a percentage range, "between 10% and 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
good: sprintf(__('between %s%% and %s%%', 'mailpoet'), 10, 30),
|
||||
// translators: Shows a percentage range, "below 10%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
critical: sprintf(__('below %s%%', 'mailpoet'), 10),
|
||||
excellent: _x('above 30%', 'Excellent open rate', 'mailpoet'),
|
||||
good: _x('between 10 and 30%', 'Good open rate', 'mailpoet'),
|
||||
critical: _x('under 10%', 'Critical open rate', 'mailpoet'),
|
||||
},
|
||||
},
|
||||
clicked: {
|
||||
badgeRanges: [3, 1, 0],
|
||||
badgeTypes: ['excellent', 'good', 'critical'],
|
||||
tooltipText: {
|
||||
// translators: Shows a percentage range, "above 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
excellent: sprintf(__('above %s%%', 'mailpoet'), 3),
|
||||
// translators: Shows a percentage range, "between 10% and 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
good: sprintf(__('between %s%% and %s%%', 'mailpoet'), 1, 3),
|
||||
// translators: Shows a percentage range, "below 10%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
critical: sprintf(__('below %s%%', 'mailpoet'), 1),
|
||||
excellent: _x('above 3%', 'Excellent click rate', 'mailpoet'),
|
||||
good: _x('between 1 and 3%', 'Good click rate', 'mailpoet'),
|
||||
critical: _x('under 1%', 'Critical click rate', 'mailpoet'),
|
||||
},
|
||||
},
|
||||
bounced: {
|
||||
badgeRanges: [1.5, 0.5, 0],
|
||||
badgeTypes: ['critical', 'good', 'excellent'],
|
||||
tooltipText: {
|
||||
// translators: Shows a percentage range, "below 10%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
excellent: sprintf(__('below %s%%', 'mailpoet'), 0.5),
|
||||
// translators: Shows a percentage range, "between 10% and 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
good: sprintf(__('between %s%% and %s%%', 'mailpoet'), 0.5, 1.5),
|
||||
// translators: Shows a percentage range, "above 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
critical: sprintf(__('above %s%%', 'mailpoet'), 1.5),
|
||||
excellent: _x('below 0.5%', 'Excellent bounce rate', 'mailpoet'),
|
||||
good: _x('between 0.5% and 1.5%', 'Good bounce rate', 'mailpoet'),
|
||||
critical: _x('above 1.5%', 'Critical bounce rate', 'mailpoet'),
|
||||
},
|
||||
},
|
||||
unsubscribed: {
|
||||
badgeRanges: [0.7, 0.3, 0],
|
||||
badgeTypes: ['critical', 'good', 'excellent'],
|
||||
tooltipText: {
|
||||
// translators: Shows a percentage range, "below 10%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
excellent: sprintf(__('below %s%%', 'mailpoet'), 0.3),
|
||||
// translators: Shows a percentage range, "between 10% and 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
good: sprintf(__('between %s%% and %s%%', 'mailpoet'), 0.3, 0.7),
|
||||
// translators: Shows a percentage range, "above 30%". Used in contexts like open, click, bounce, or unsubscribe rates.
|
||||
critical: sprintf(__('above %s%%', 'mailpoet'), 0.7),
|
||||
excellent: _x('Below 0.3%', 'Excellent unsubscribe rate', 'mailpoet'),
|
||||
good: _x('between 0.3% and 0.7%', 'Good unsubscribe rate', 'mailpoet'),
|
||||
critical: _x('above 0.7%', 'Critical unsubscribe rate', 'mailpoet'),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getBadgeType = (statName, rate) => {
|
||||
const stat = getStats()[statName] || null;
|
||||
const stat = stats[statName] || null;
|
||||
if (!stat) {
|
||||
return null;
|
||||
}
|
||||
@ -103,7 +91,7 @@ function StatsBadge(props: StatsBadgeProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stat = getStats()[props.stat] || null;
|
||||
const stat = stats[props.stat] || null;
|
||||
if (!stat) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,40 +1,21 @@
|
||||
import { _x } from '@wordpress/i18n';
|
||||
import { Button, Input } from 'common/index';
|
||||
import { Input } from 'common/index';
|
||||
import { useAction, useSelector } from 'settings/store/hooks';
|
||||
import { useState } from 'react';
|
||||
|
||||
type KeyInputPropType = {
|
||||
placeholder?: string;
|
||||
isFullWidth?: boolean;
|
||||
forceRevealed?: boolean;
|
||||
};
|
||||
|
||||
export function KeyInput({
|
||||
placeholder,
|
||||
isFullWidth = false,
|
||||
forceRevealed = false,
|
||||
}: KeyInputPropType) {
|
||||
const state = useSelector('getKeyActivationState')();
|
||||
const setState = useAction('updateKeyActivationState');
|
||||
const [isRevealed, setIsRevealed] = useState(false);
|
||||
const inputType = forceRevealed || isRevealed ? 'text' : 'password';
|
||||
const toggleButton = !forceRevealed && (
|
||||
<Button
|
||||
className="mailpoet-premium-key-toggle"
|
||||
variant="tertiary"
|
||||
onClick={() => setIsRevealed(!isRevealed)}
|
||||
>
|
||||
{isRevealed
|
||||
? // translators: Used as a button to show or hide the premium key
|
||||
_x('Hide', 'verb', 'mailpoet')
|
||||
: // translators: Used as a button to show or hide the premium key
|
||||
_x('Show', 'verb', 'mailpoet')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Input
|
||||
type={inputType}
|
||||
type="text"
|
||||
id="mailpoet_premium_key"
|
||||
name="premium[premium_key]"
|
||||
placeholder={placeholder}
|
||||
@ -48,7 +29,6 @@ export function KeyInput({
|
||||
key: event.target.value.trim() || null,
|
||||
})
|
||||
}
|
||||
iconEnd={toggleButton}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { action } from '_storybook/action';
|
||||
import { TopBar } from '../top-bar';
|
||||
|
||||
export default {
|
||||
@ -16,7 +17,7 @@ export function TopBarWithoutChildren() {
|
||||
left: '0px',
|
||||
}}
|
||||
>
|
||||
<TopBar />
|
||||
<TopBar hasNews={false} onBeamerClick={action('beamer click')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -33,7 +34,7 @@ export function TopBarWithoutChildrenWithNews() {
|
||||
left: '0px',
|
||||
}}
|
||||
>
|
||||
<TopBar />
|
||||
<TopBar hasNews onBeamerClick={action('beamer click')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { action } from '_storybook/action';
|
||||
import { TopBar } from '../top-bar';
|
||||
import { Button } from '../../button/button';
|
||||
|
||||
@ -17,7 +18,7 @@ export function TopBarWithChildren() {
|
||||
left: '0px',
|
||||
}}
|
||||
>
|
||||
<TopBar>
|
||||
<TopBar hasNews={false} onBeamerClick={action('beamer click')}>
|
||||
<Button>Button</Button>
|
||||
</TopBar>
|
||||
</div>
|
||||
|
22
mailpoet/assets/js/src/common/top-bar/beamer-icon.tsx
Normal file
22
mailpoet/assets/js/src/common/top-bar/beamer-icon.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
export function BeamerIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6 17V14.5002L6.00101 14.4997C7.10049 13.9995 7.90003 12.8998 8 11.7002V9.50016C8 7.96615 8.78223 6.62769 10.0309 5.95845C10.3288 5.79879 10.6533 5.67721 11 5.60016C10.9 5.50016 10.8 5.30016 10.8 5.10016C10.8 5.01146 10.8109 4.92493 10.8315 4.84189C10.95 4.36325 11.3887 4.00017 11.9 4.00017C11.9334 4.00017 11.9672 3.99696 12 4C12.0332 3.99688 12.0661 4.00004 12.1 4.00004C12.6113 4.00004 13.05 4.36313 13.1685 4.84177C13.1891 4.92481 13.2 5.01133 13.2 5.10004C13.2 5.30004 13.1 5.50004 13 5.60004C13.3467 5.67709 13.6712 5.79867 13.9691 5.95833C15.2178 6.62757 16 7.96602 16 9.50004V11.7C16.1 12.9 16.9 14.0002 18 14.5002V17.0002L6 17ZM17 16H7V15.148C8.2117 14.4742 8.98499 13.2218 9.09655 11.8831L9.1 9.50004C9.1 8.15301 9.99683 6.94434 11.3676 6.56356L11.7403 6.46006L12.0003 6.40228L12.2553 6.45896L12.6324 6.56368C14.0032 6.94446 14.9 8.15313 14.9 9.50016L14.9035 11.8832C15.015 13.2219 15.7883 14.4743 17 15.1481V16ZM12.0001 5.3601C12.221 5.3601 12.4001 5.18102 12.4001 4.9601C12.4001 4.73919 12.221 4.5601 12.0001 4.5601C11.7792 4.5601 11.6001 4.73919 11.6001 4.9601C11.6001 5.18102 11.7792 5.3601 12.0001 5.3601Z"
|
||||
fill="#2C3338"
|
||||
/>
|
||||
<path
|
||||
d="M13.8303 19C14.0017 18.7054 14.1 18.3637 14.1 18L9.9 18.0002C9.9 18.3638 9.99834 18.7055 10.1697 19.0002C10.5169 19.5968 11.1636 20.0002 11.9 20.0002C11.9339 20.0002 11.9677 19.9993 12.0012 19.9976C12.034 19.9992 12.0669 20 12.1 20C12.8364 20 13.4831 19.5967 13.8303 19Z"
|
||||
fill="#2C3338"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -1,25 +1,62 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
import { withFeatureAnnouncement } from 'announcements/with-feature-announcement';
|
||||
import { HideScreenOptions } from 'common/hide-screen-options/hide-screen-options';
|
||||
import { MailPoetLogoResponsive } from './mailpoet-logo-responsive';
|
||||
import { BeamerIcon } from './beamer-icon';
|
||||
import { ScreenOptionsFix } from './screen-options-fix';
|
||||
import { withBoundary } from '../error-boundary';
|
||||
import { MailPoet } from '../../mailpoet';
|
||||
|
||||
type Props = {
|
||||
children?: ReactNode;
|
||||
hasNews?: boolean;
|
||||
onBeamerClick?: () => void;
|
||||
logoWithLink?: boolean;
|
||||
hideScreenOptions?: boolean;
|
||||
};
|
||||
|
||||
export function TopBar({
|
||||
children,
|
||||
hasNews,
|
||||
onBeamerClick,
|
||||
logoWithLink = true,
|
||||
hideScreenOptions = false,
|
||||
}: Props) {
|
||||
const buttonClasses = classnames(
|
||||
'mailpoet-top-bar-beamer',
|
||||
hasNews ? 'mailpoet-top-bar-beamer-dot' : '',
|
||||
);
|
||||
return (
|
||||
<div className="mailpoet-top-bar">
|
||||
<MailPoetLogoResponsive withLink={logoWithLink} />
|
||||
<div className="mailpoet-top-bar-children">{children}</div>
|
||||
<div className="mailpoet-flex-grow" />
|
||||
{onBeamerClick && MailPoet.libs3rdPartyEnabled && (
|
||||
<div>
|
||||
<a
|
||||
role="button"
|
||||
onClick={onBeamerClick}
|
||||
className={buttonClasses}
|
||||
title={__('What’s new', 'mailpoet')}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
['keydown', 'keypress'].includes(event.type) &&
|
||||
['Enter', ' '].includes(event.key)
|
||||
) {
|
||||
event.preventDefault();
|
||||
onBeamerClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<BeamerIcon />
|
||||
<span>{__('Updates', 'mailpoet')}</span>
|
||||
</a>
|
||||
<span id="beamer-empty-element" />
|
||||
</div>
|
||||
)}
|
||||
<ScreenOptionsFix />
|
||||
{hideScreenOptions && <HideScreenOptions />}
|
||||
</div>
|
||||
@ -27,4 +64,4 @@ export function TopBar({
|
||||
}
|
||||
|
||||
TopBar.displayName = 'TopBar';
|
||||
export const TopBarWithBoundary = withBoundary(TopBar);
|
||||
export const TopBarWithBeamer = withFeatureAnnouncement(withBoundary(TopBar));
|
||||
|
@ -69,7 +69,9 @@ export const MailPoetDate: {
|
||||
}
|
||||
return momentDate.format(this.convertFormat(this.options.format));
|
||||
},
|
||||
toDate: function toDate(date: MomentInput): Date {
|
||||
toDate: function toDate(date: MomentInput, opts?: DateOptions): Date {
|
||||
const options = opts || {};
|
||||
this.init(options);
|
||||
return Moment.utc(date).toDate();
|
||||
},
|
||||
short: function short(date: MomentInput): string {
|
||||
|
@ -14,7 +14,7 @@
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
},
|
||||
"apiVersion": 3,
|
||||
"apiVersion": 2,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"attributes": {
|
||||
"logo": {
|
@ -2,14 +2,10 @@ import { registerBlockType } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
||||
import { PanelBody, RadioControl, Icon } from '@wordpress/components';
|
||||
import { useState } from 'react';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import metadata from './block.json';
|
||||
import { storeName } from '../../engine/store/constants';
|
||||
import MailPoetIcon from './mailpoet-icon';
|
||||
import { PremiumModal } from '../../common/premium-modal';
|
||||
import './style.scss';
|
||||
|
||||
const getCdnUrl = () => window.mailpoet_cdn_url;
|
||||
const getPremiumPluginStatus = () => window.mailpoet_premium_active;
|
||||
|
||||
function LogoImage({
|
||||
logoSrc,
|
||||
@ -18,31 +14,8 @@ function LogoImage({
|
||||
logoSrc: string;
|
||||
style?: React.CSSProperties;
|
||||
}): JSX.Element {
|
||||
const [isModalOpened, setIsModalOpened] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="mailpoet-email-footer-credit"
|
||||
onClick={() => setIsModalOpened(true)}
|
||||
>
|
||||
<img
|
||||
src={logoSrc}
|
||||
style={style}
|
||||
alt="Powered by MailPoet"
|
||||
width="100px"
|
||||
/>
|
||||
</button>
|
||||
{!!isModalOpened && (
|
||||
<PremiumModal onRequestClose={() => setIsModalOpened(false)}>
|
||||
{__(
|
||||
'A MailPoet logo will appear in the footer of all emails sent with the free version of MailPoet.',
|
||||
'mailpoet',
|
||||
)}
|
||||
</PremiumModal>
|
||||
)}
|
||||
</>
|
||||
<img src={logoSrc} style={style} alt="Powered by MailPoet" width="100px" />
|
||||
);
|
||||
}
|
||||
|
||||
@ -54,10 +27,13 @@ function Edit({
|
||||
setAttributes: (value: { logo: string }) => void;
|
||||
}): JSX.Element {
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
const cdnUrl = getCdnUrl();
|
||||
const isPremiumPluginActive = getPremiumPluginStatus();
|
||||
|
||||
const { cdnUrl, isPremiumPluginActive } = useSelect(
|
||||
(select) => ({
|
||||
cdnUrl: select(storeName).getCdnUrl(),
|
||||
isPremiumPluginActive: select(storeName).isPremiumPluginActive(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
if (isPremiumPluginActive) {
|
||||
return null;
|
||||
}
|
||||
@ -91,6 +67,15 @@ function Edit({
|
||||
) as unknown as string,
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<LogoImage
|
||||
logoSrc={`${cdnUrl}email-editor/logo-dark.png`}
|
||||
style={{ background: '#000000' }}
|
||||
/>
|
||||
) as unknown as string,
|
||||
value: 'dark',
|
||||
},
|
||||
]}
|
||||
onChange={(value) => {
|
||||
setAttributes({
|
@ -0,0 +1,21 @@
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { Block } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Disables Styles for button
|
||||
* Currently we are not able to read these styles in renderer
|
||||
*/
|
||||
function enhanceButtonBlock() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/change-button',
|
||||
(settings: Block, name) => {
|
||||
if (name === 'core/button') {
|
||||
return { ...settings, styles: [] };
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { enhanceButtonBlock };
|
@ -0,0 +1,28 @@
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { Block } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Switch layout to reduced flex email layout
|
||||
* Email render engine can't handle full flex layout se we need to switch to reduced flex layout
|
||||
*/
|
||||
function enhanceButtonsBlock() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/change-buttons',
|
||||
(settings: Block, name) => {
|
||||
if (name === 'core/buttons') {
|
||||
return {
|
||||
...settings,
|
||||
supports: {
|
||||
...settings.supports,
|
||||
layout: false, // disable block editor's layouts
|
||||
__experimentalEmailFlexLayout: true, // enable MailPoet's reduced flex email layout
|
||||
},
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { enhanceButtonsBlock };
|
@ -0,0 +1,25 @@
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { Block } from '@wordpress/blocks';
|
||||
|
||||
function enhanceColumnBlock() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/change-column',
|
||||
(settings: Block, name) => {
|
||||
if (name === 'core/column') {
|
||||
return {
|
||||
...settings,
|
||||
supports: {
|
||||
...settings.supports,
|
||||
background: {
|
||||
backgroundImage: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { enhanceColumnBlock };
|
@ -0,0 +1,87 @@
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { createHigherOrderComponent } from '@wordpress/compose';
|
||||
import { Block } from '@wordpress/blocks';
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
|
||||
const columnsEditCallback = createHigherOrderComponent(
|
||||
(BlockEdit) =>
|
||||
function alterBlocksEdits(props) {
|
||||
if (props.name !== 'core/columns') {
|
||||
return <BlockEdit {...props} />;
|
||||
}
|
||||
// CSS sets opacity by the class is-disabled by the toggle component from the Gutenberg package
|
||||
// To deactivating the input we use CSS pointer-events because we want to avoid JavaScript hacks
|
||||
const deactivateToggleCss = `
|
||||
.components-panel__body .components-toggle-control .components-form-toggle { opacity: 0.3; }
|
||||
.components-panel__body .components-toggle-control .components-form-toggle__input { pointer-events: none; }
|
||||
.components-panel__body .components-toggle-control label { pointer-events: none; }
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BlockEdit {...props} />
|
||||
<InspectorControls>
|
||||
<style>{deactivateToggleCss}</style>
|
||||
</InspectorControls>
|
||||
</>
|
||||
);
|
||||
},
|
||||
'columnsEditCallback',
|
||||
);
|
||||
|
||||
function deactivateStackOnMobile() {
|
||||
addFilter(
|
||||
'editor.BlockEdit',
|
||||
'mailpoet-email-editor/deactivate-stack-on-mobile',
|
||||
columnsEditCallback,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables layout support for columns and column blocks because
|
||||
* the default layout `flex` add gaps between columns that it is not possible to support in emails.
|
||||
*/
|
||||
function disableColumnsLayout() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/disable-columns-layout',
|
||||
(settings, name) => {
|
||||
if (name === 'core/columns' || name === 'core/column') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return {
|
||||
...settings,
|
||||
supports: {
|
||||
...settings.supports,
|
||||
layout: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function enhanceColumnsBlock() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/change-columns',
|
||||
(settings: Block, name) => {
|
||||
if (name === 'core/columns') {
|
||||
return {
|
||||
...settings,
|
||||
supports: {
|
||||
...settings.supports,
|
||||
background: {
|
||||
backgroundImage: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { deactivateStackOnMobile, disableColumnsLayout, enhanceColumnsBlock };
|
@ -1,11 +1,8 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import {
|
||||
Block as WPBlock,
|
||||
BlockSupports as WPBlockSupports,
|
||||
} from '@wordpress/blocks/index';
|
||||
Block as WPBlock,
|
||||
BlockSupports as WPBlockSupports,
|
||||
} from '@wordpress/blocks';
|
||||
|
||||
// Extend the BlockSupports type to include shadow
|
||||
// The shadow is not included in WP6.4 but it is in WP6.5
|
||||
@ -18,19 +15,19 @@ type Block = WPBlock & { supports?: BlockSupports };
|
||||
* Currently we are not able to read these styles in renderer
|
||||
*/
|
||||
function alterSupportConfiguration() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/block-support',
|
||||
( settings: Block ) => {
|
||||
if ( settings.supports?.shadow ) {
|
||||
return {
|
||||
...settings,
|
||||
supports: { ...settings.supports, shadow: false },
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
);
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/block-support',
|
||||
(settings: Block) => {
|
||||
if (settings.supports?.shadow) {
|
||||
return {
|
||||
...settings,
|
||||
supports: { ...settings.supports, shadow: false },
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { alterSupportConfiguration };
|
@ -0,0 +1,26 @@
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
|
||||
/**
|
||||
* Disables layout support for group blocks because the default layout `flex` add gaps between columns that it is not possible to support in emails.
|
||||
*/
|
||||
function disableGroupVariations() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/disable-group-variations',
|
||||
(settings, name) => {
|
||||
if (name === 'core/group') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return {
|
||||
...settings,
|
||||
variations: settings.variations.filter(
|
||||
(variation) => variation.name === 'group',
|
||||
),
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { disableGroupVariations };
|
@ -0,0 +1,61 @@
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { createHigherOrderComponent } from '@wordpress/compose';
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { Block } from '@wordpress/blocks';
|
||||
|
||||
const imageEditCallback = createHigherOrderComponent(
|
||||
(BlockEdit) =>
|
||||
function alterBlocksEdits(props) {
|
||||
if (props.name !== 'core/image') {
|
||||
return <BlockEdit {...props} />;
|
||||
}
|
||||
// Because we cannot support displaying the modal with image after clicking in the email we have to hide the toggle
|
||||
const deactivateToggleCss = `
|
||||
.components-tools-panel .components-toggle-control { display: none; }
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BlockEdit {...props} />
|
||||
<InspectorControls>
|
||||
<style>{deactivateToggleCss}</style>
|
||||
</InspectorControls>
|
||||
</>
|
||||
);
|
||||
},
|
||||
'imageEditCallback',
|
||||
);
|
||||
|
||||
/**
|
||||
* Because CSS property filter is not supported in almost 50% of email clients we have to disable it
|
||||
*/
|
||||
function disableImageFilter() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/deactivate-image-filter',
|
||||
(settings: Block, name) => {
|
||||
if (name === 'core/image') {
|
||||
return {
|
||||
...settings,
|
||||
supports: {
|
||||
...settings.supports,
|
||||
filter: {
|
||||
duetone: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function hideExpandOnClick() {
|
||||
addFilter(
|
||||
'editor.BlockEdit',
|
||||
'mailpoet-email-editor/hide-expand-on-click',
|
||||
imageEditCallback,
|
||||
);
|
||||
}
|
||||
|
||||
export { hideExpandOnClick, disableImageFilter };
|
@ -0,0 +1,58 @@
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { Block } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
|
||||
function Placeholder({ layoutClassNames }) {
|
||||
const blockProps = useBlockProps({ className: layoutClassNames });
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<p>{__('This is the Content block.', 'mailpoet')}</p>
|
||||
<p>
|
||||
{__(
|
||||
'It will display all the blocks in the email content, which might be only simple text paragraphs. You can enrich your message with images, incorporate data through tables, explore different layout designs with columns, or use any other block type.',
|
||||
'mailpoet',
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Curried function to add a custom placeholder to the post content block, or just use the original Edit component.
|
||||
function PostContentEdit(OriginalEditComponent) {
|
||||
return function Edit({
|
||||
context,
|
||||
__unstableLayoutClassNames: layoutClassNames,
|
||||
}) {
|
||||
const { postId: contextPostId, postType: contextPostType } = context;
|
||||
const hasContent = contextPostId && contextPostType;
|
||||
|
||||
if (hasContent) {
|
||||
return (
|
||||
<OriginalEditComponent
|
||||
{...{ context, __unstableLayoutClassNames: layoutClassNames }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Placeholder layoutClassNames={layoutClassNames} />;
|
||||
};
|
||||
}
|
||||
|
||||
function enhancePostContentBlock() {
|
||||
addFilter(
|
||||
'blocks.registerBlockType',
|
||||
'mailpoet-email-editor/change-post-content',
|
||||
(settings: Block, name) => {
|
||||
if (name === 'core/post-content') {
|
||||
return {
|
||||
...settings,
|
||||
edit: PostContentEdit(settings.edit),
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { enhancePostContentBlock };
|
@ -0,0 +1,18 @@
|
||||
import { unregisterFormatType } from '@wordpress/rich-text';
|
||||
|
||||
/**
|
||||
* Disable Rich text formats we currently cannot support
|
||||
* Note: This will remove its support for all blocks in the email editor e.g., p, h1,h2, etc
|
||||
*/
|
||||
function disableCertainRichTextFormats() {
|
||||
// remove support for inline image - We can't use it
|
||||
unregisterFormatType('core/image');
|
||||
|
||||
// remove support for Inline code - Not well formatted
|
||||
unregisterFormatType('core/code');
|
||||
|
||||
// remove support for Language - Not supported for now
|
||||
unregisterFormatType('core/language');
|
||||
}
|
||||
|
||||
export { disableCertainRichTextFormats };
|
30
mailpoet/assets/js/src/email-editor/engine/blocks/index.ts
Normal file
30
mailpoet/assets/js/src/email-editor/engine/blocks/index.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { registerCoreBlocks } from '@wordpress/block-library';
|
||||
import { enhanceColumnBlock } from './core/column';
|
||||
import {
|
||||
disableColumnsLayout,
|
||||
deactivateStackOnMobile,
|
||||
enhanceColumnsBlock,
|
||||
} from './core/columns';
|
||||
import { enhancePostContentBlock } from './core/post-content';
|
||||
import { disableGroupVariations } from './core/group';
|
||||
import { disableImageFilter, hideExpandOnClick } from './core/image';
|
||||
import { disableCertainRichTextFormats } from './core/rich-text';
|
||||
import { enhanceButtonBlock } from './core/button';
|
||||
import { enhanceButtonsBlock } from './core/buttons';
|
||||
import { alterSupportConfiguration } from './core/general-block-support';
|
||||
|
||||
export function initBlocks() {
|
||||
deactivateStackOnMobile();
|
||||
hideExpandOnClick();
|
||||
disableImageFilter();
|
||||
disableCertainRichTextFormats();
|
||||
disableColumnsLayout();
|
||||
disableGroupVariations();
|
||||
enhanceButtonBlock();
|
||||
enhanceButtonsBlock();
|
||||
enhanceColumnBlock();
|
||||
enhanceColumnsBlock();
|
||||
enhancePostContentBlock();
|
||||
alterSupportConfiguration();
|
||||
registerCoreBlocks();
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
/**
|
||||
* This component is simplified version of the original one from @wordpress/editor package.
|
||||
* The original component can be found here: https://github.com/WordPress/gutenberg/blob/46446b853d740c309c0675c7bf2ca4170a618c42/packages/editor/src/components/autosave-monitor/index.js
|
||||
* The main reason for the own solution is that the original component needs to initialize the @wordpress/editor store.
|
||||
* We could use the action `setEditedPost` from the editor package, but it is only in a newer version of the editor package.
|
||||
*/
|
||||
export function AutosaveMonitor() {
|
||||
const { hasEdits, autosaveInterval } = useSelect(
|
||||
(select) => ({
|
||||
hasEdits: select(storeName).hasEdits(),
|
||||
autosaveInterval: select(storeName).getAutosaveInterval(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const { saveEditedEmail } = useDispatch(storeName);
|
||||
|
||||
useEffect(() => {
|
||||
let autosaveTimer: NodeJS.Timeout | undefined;
|
||||
|
||||
if (hasEdits && autosaveInterval > 0) {
|
||||
autosaveTimer = setTimeout(() => {
|
||||
void saveEditedEmail();
|
||||
}, autosaveInterval * 1000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (autosaveTimer) {
|
||||
clearTimeout(autosaveTimer);
|
||||
}
|
||||
};
|
||||
}, [hasEdits, autosaveInterval, saveEditedEmail]);
|
||||
|
||||
return null;
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { useSelect } from '@wordpress/data';
|
||||
|
||||
import {
|
||||
ErrorBoundary,
|
||||
PostLockedModal,
|
||||
// @ts-expect-error No types for this exist yet.
|
||||
privateApis as editorPrivateApis,
|
||||
} from '@wordpress/editor';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { SlotFillProvider, Spinner } from '@wordpress/components';
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Layout } from './layout';
|
||||
import { unlock } from '../../../lock-unlock';
|
||||
import { useNavigateToEntityRecord } from '../../hooks/use-navigate-to-entity-record';
|
||||
|
||||
const { ExperimentalEditorProvider } = unlock(editorPrivateApis);
|
||||
|
||||
export function InnerEditor({
|
||||
postId: initialPostId,
|
||||
postType: initialPostType,
|
||||
settings,
|
||||
initialEdits,
|
||||
...props
|
||||
}) {
|
||||
const {
|
||||
currentPost,
|
||||
onNavigateToEntityRecord,
|
||||
onNavigateToPreviousEntityRecord,
|
||||
} = useNavigateToEntityRecord(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
initialPostId,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
initialPostType,
|
||||
'post-only',
|
||||
);
|
||||
|
||||
const { post, template } = useSelect(
|
||||
(select) => {
|
||||
const { getEntityRecord } = select(coreStore);
|
||||
const { getEditedPostTemplate } = select(storeName);
|
||||
const postObject = getEntityRecord(
|
||||
'postType',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
currentPost.postType,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
currentPost.postId,
|
||||
);
|
||||
return {
|
||||
template:
|
||||
currentPost.postType !== 'wp_template'
|
||||
? getEditedPostTemplate()
|
||||
: null,
|
||||
post: postObject,
|
||||
};
|
||||
},
|
||||
[currentPost.postType, currentPost.postId],
|
||||
);
|
||||
|
||||
const editorSettings = useMemo(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
() => ({
|
||||
...settings,
|
||||
onNavigateToEntityRecord,
|
||||
onNavigateToPreviousEntityRecord,
|
||||
defaultRenderingMode: 'template-locked',
|
||||
supportsTemplateMode: true,
|
||||
}),
|
||||
[settings, onNavigateToEntityRecord, onNavigateToPreviousEntityRecord],
|
||||
);
|
||||
|
||||
if (!post || (currentPost.postType !== 'wp_template' && !template)) {
|
||||
return (
|
||||
<div className="spinner-container">
|
||||
<Spinner style={{ width: '80px', height: '80px' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SlotFillProvider>
|
||||
<ExperimentalEditorProvider
|
||||
settings={editorSettings}
|
||||
post={post}
|
||||
initialEdits={initialEdits}
|
||||
useSubRegistry={false}
|
||||
__unstableTemplate={template}
|
||||
{...props}
|
||||
>
|
||||
{/* @ts-expect-error ErrorBoundary type is incorrect there is no onError */}
|
||||
<ErrorBoundary>
|
||||
<Layout />
|
||||
<PostLockedModal />
|
||||
</ErrorBoundary>
|
||||
</ExperimentalEditorProvider>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
@import '~@wordpress/base-styles/colors';
|
||||
|
||||
#mailpoet-email-editor {
|
||||
.editor-header__toolbar {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.editor-header__center {
|
||||
flex-grow: 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Specific styles for the component EmailTypeInfo
|
||||
// Styles are based on the Block Card component from Gutenberg block editor
|
||||
.mailpoet-email-sidebar__email-type-info {
|
||||
.components-panel__row {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.mailpoet-email-type-info__icon {
|
||||
flex: 0 0 24px;
|
||||
margin-left: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.mailpoet-email-type-info__content {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 4px;
|
||||
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-email-editor__settings-panel {
|
||||
.mailpoet-settings-panel__subject .components-base-control__label {
|
||||
width: 100%;
|
||||
|
||||
.components-external-link {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-settings-panel__help {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.components-text {
|
||||
color: #757575;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-settings-panel__preview-text .components-base-control__label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mailpoet-settings-panel__preview-text-length {
|
||||
color: $black;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.mailpoet-settings-panel__preview-text-length-warning {
|
||||
color: $alert-yellow;
|
||||
}
|
||||
|
||||
.mailpoet-settings-panel__preview-text-length-error {
|
||||
color: $alert-red;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-post-visual-editor {
|
||||
line-height: 1.4; /* Recommended line-height that is also used in the email editor */
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%; /* From MJMJ - Automatic test adjustment on mobile max to 100% */
|
||||
-ms-text-size-adjust: 100%; /* From MJMJ - Automatic test adjustment on mobile max to 100% */
|
||||
word-spacing: normal;
|
||||
}
|
||||
|
||||
.visual-editor__email_layout_wrapper {
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.visual-editor__email_content_wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Set default padding-left to have consistent default look in editor and in email
|
||||
// This also overrides the default values in browsers for padding-inline-start
|
||||
ul,
|
||||
ol,
|
||||
ul.has-background,
|
||||
ol.has-background {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
// Override default button border radius which is set in core to 9999px
|
||||
.wp-block-button__link {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.is-mobile-preview {
|
||||
.wp-block-columns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.wp-block-column {
|
||||
box-sizing: border-box;
|
||||
// override flex-basis set in style attribute to fix the height of the column in mobile preview. Blocks overriding is as a part of style.css in blocks-library
|
||||
flex-basis: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for mobile preview height
|
||||
&.is-mobile-preview {
|
||||
> div {
|
||||
display: block !important;
|
||||
}
|
||||
.editor-styles-wrapper {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.has-global-padding .wp-block-post-content > .alignfull {
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
}
|
||||
|
||||
// For the WYSIWYG experience we don't want to display any margins between blocks in the editor
|
||||
.wp-block {
|
||||
clear: both; // for ensuring that floated elements (images) are cleared
|
||||
}
|
||||
|
||||
// Resetting the margin for images in the editor to avoid unexpected spacing
|
||||
.editor-styles-wrapper .is-layout-constrained .wp-block-image {
|
||||
figcaption {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.alignleft,
|
||||
&.alignright {
|
||||
margin-inline-end: 0;
|
||||
margin-inline-start: 0;
|
||||
text-align: center;
|
||||
}
|
||||
&.aligncenter {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-styles-wrapper {
|
||||
.wp-block-columns:not(.is-not-stacked-on-mobile)
|
||||
> .wp-block-column[style*='flex-basis'] {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
// Content does not need margin. The rendered template does not add margins to match.
|
||||
.editor-styles-wrapper .wp-block-post-content {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// Hide the advanced settings in the sidebar. This panel is not used in the email editor at this moment.
|
||||
.block-editor-block-inspector__advanced {
|
||||
display: none;
|
||||
}
|
@ -1,2 +1,4 @@
|
||||
import './index.scss';
|
||||
|
||||
export * from './editor';
|
||||
export * from './layout';
|
@ -0,0 +1,151 @@
|
||||
import {
|
||||
// @ts-expect-error No types for this exist yet.
|
||||
__experimentalUseResizeCanvas as useResizeCanvas,
|
||||
BlockSelectionClearer,
|
||||
} from '@wordpress/block-editor';
|
||||
|
||||
import {
|
||||
UnsavedChangesWarning,
|
||||
// @ts-expect-error No types for this exist yet.
|
||||
privateApis as editorPrivateApis,
|
||||
store as editorStore,
|
||||
} from '@wordpress/editor';
|
||||
import { uploadMedia } from '@wordpress/media-utils';
|
||||
import classnames from 'classnames';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import {
|
||||
ComplementaryArea,
|
||||
FullscreenMode,
|
||||
InterfaceSkeleton,
|
||||
} from '@wordpress/interface';
|
||||
|
||||
import './index.scss';
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { storeName } from '../../store';
|
||||
import { useEmailCss } from '../../hooks';
|
||||
import { AutosaveMonitor } from '../autosave';
|
||||
import { BlockCompatibilityWarnings, Sidebar } from '../sidebar';
|
||||
import { Header } from '../header';
|
||||
import { ListviewSidebar } from '../listview-sidebar/listview-sidebar';
|
||||
import { InserterSidebar } from '../inserter-sidebar/inserter-sidebar';
|
||||
import { EditorNotices, SentEmailNotice } from '../notices';
|
||||
import { StylesSidebar } from '../styles-sidebar';
|
||||
import { unlock } from '../../../lock-unlock';
|
||||
import { TemplateSelection } from '../template-select';
|
||||
|
||||
const { EditorCanvas } = unlock(editorPrivateApis);
|
||||
|
||||
export function Layout() {
|
||||
const {
|
||||
isFullscreenActive,
|
||||
isSidebarOpened,
|
||||
initialSettings,
|
||||
previewDeviceType,
|
||||
isInserterSidebarOpened,
|
||||
isListviewSidebarOpened,
|
||||
canUserEditMedia,
|
||||
hasFixedToolbar,
|
||||
focusMode,
|
||||
styles,
|
||||
isEditingTemplate,
|
||||
} = useSelect(
|
||||
(select) => ({
|
||||
isFullscreenActive: select(storeName).isFeatureActive('fullscreenMode'),
|
||||
isSidebarOpened: select(storeName).isSidebarOpened(),
|
||||
isInserterSidebarOpened: select(storeName).isInserterSidebarOpened(),
|
||||
isListviewSidebarOpened: select(storeName).isListviewSidebarOpened(),
|
||||
initialSettings: select(storeName).getInitialEditorSettings(),
|
||||
previewDeviceType: select(storeName).getPreviewState().deviceType,
|
||||
canUserEditMedia: select(coreStore).canUser('create', 'media'),
|
||||
hasFixedToolbar: select(storeName).isFeatureActive('fixedToolbar'),
|
||||
focusMode: select(storeName).isFeatureActive('focusMode'),
|
||||
styles: select(storeName).getStyles(),
|
||||
isEditingTemplate:
|
||||
select(editorStore).getCurrentPostType() === 'wp_template',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const { toggleInserterSidebar } = useDispatch(storeName);
|
||||
|
||||
const [emailCss] = useEmailCss();
|
||||
const className = classnames('edit-post-layout', {
|
||||
'is-sidebar-opened': isSidebarOpened,
|
||||
});
|
||||
|
||||
const contentWrapperStyles = useResizeCanvas(previewDeviceType);
|
||||
|
||||
if (isEditingTemplate) {
|
||||
contentWrapperStyles.height = '100%';
|
||||
}
|
||||
|
||||
// Styles for the canvas. Based on template-canvas.php, this equates to the body element.
|
||||
const canvasStyles = {
|
||||
background:
|
||||
previewDeviceType === 'Desktop' ? styles.color.background : 'transparent',
|
||||
fontFamily: styles.typography.fontFamily,
|
||||
transition: 'all 0.3s ease 0s',
|
||||
};
|
||||
|
||||
const settings = {
|
||||
...initialSettings,
|
||||
mediaUpload: canUserEditMedia ? uploadMedia : null,
|
||||
hasFixedToolbar,
|
||||
focusMode,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FullscreenMode isActive={isFullscreenActive} />
|
||||
<UnsavedChangesWarning />
|
||||
<AutosaveMonitor />
|
||||
<SentEmailNotice />
|
||||
<Sidebar />
|
||||
<StylesSidebar />
|
||||
<TemplateSelection />
|
||||
<InterfaceSkeleton
|
||||
className={className}
|
||||
header={<Header />}
|
||||
editorNotices={<EditorNotices />}
|
||||
content={
|
||||
<>
|
||||
<EditorNotices />
|
||||
<BlockSelectionClearer
|
||||
className="edit-post-visual-editor"
|
||||
style={canvasStyles}
|
||||
onClick={() => {
|
||||
// Clear inserter sidebar when canvas is clicked.
|
||||
if (isInserterSidebarOpened) {
|
||||
void toggleInserterSidebar();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={classnames('visual-editor__email_content_wrapper', {
|
||||
'is-mobile-preview': previewDeviceType === 'Mobile',
|
||||
'is-desktop-preview': previewDeviceType === 'Desktop',
|
||||
})}
|
||||
style={contentWrapperStyles}
|
||||
>
|
||||
<EditorCanvas
|
||||
disableIframe={false}
|
||||
styles={[...settings.styles, ...emailCss]}
|
||||
autoFocus
|
||||
className="has-global-padding"
|
||||
renderAppender={false} // With false the appender is rendered in the template mode
|
||||
/>
|
||||
</div>
|
||||
</BlockSelectionClearer>
|
||||
</>
|
||||
}
|
||||
sidebar={<ComplementaryArea.Slot scope={storeName} />}
|
||||
secondarySidebar={
|
||||
(isInserterSidebarOpened && <InserterSidebar />) ||
|
||||
(isListviewSidebarOpened && <ListviewSidebar />)
|
||||
}
|
||||
/>
|
||||
{/* Rendering Warning component here ensures that the warning is displayed under the border configuration. */}
|
||||
<BlockCompatibilityWarnings />
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import { useRef } from '@wordpress/element';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
VisuallyHidden,
|
||||
__experimentalText as Text,
|
||||
TextControl,
|
||||
} from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chevronDown } from '@wordpress/icons';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
// @see https://github.com/WordPress/gutenberg/blob/5e0ffdbc36cb2e967dfa6a6b812a10a2e56a598f/packages/edit-post/src/components/header/document-actions/index.js
|
||||
|
||||
export function CampaignName() {
|
||||
const { showIconLabels } = useSelect(
|
||||
(select) => ({
|
||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||
postId: select(storeName).getEmailPostId(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const [emailTitle = '', setTitle] = useEntityProp(
|
||||
'postType',
|
||||
'mailpoet_email',
|
||||
'title',
|
||||
);
|
||||
|
||||
const titleRef = useRef(null);
|
||||
return (
|
||||
<div ref={titleRef} className="mailpoet-email-editor-campaign-name">
|
||||
<Dropdown
|
||||
popoverProps={{
|
||||
placement: 'bottom',
|
||||
anchor: titleRef.current,
|
||||
}}
|
||||
contentClassName="mailpoet-email-editor-campaign-name__dropdown"
|
||||
renderToggle={({ isOpen, onToggle }) => (
|
||||
<>
|
||||
<Button
|
||||
onClick={onToggle}
|
||||
className="mailpoet-email-campaign-name__link"
|
||||
>
|
||||
<Text size="body" as="h1">
|
||||
<VisuallyHidden as="span">
|
||||
{__('Editing email:', 'mailpoet')}
|
||||
</VisuallyHidden>
|
||||
{emailTitle}
|
||||
</Text>
|
||||
</Button>
|
||||
<Button
|
||||
className="mailpoet-email-campaign-name__toggle"
|
||||
icon={chevronDown}
|
||||
aria-expanded={isOpen}
|
||||
aria-haspopup="true"
|
||||
onClick={onToggle}
|
||||
label={__('Change campaign name', 'mailpoet')}
|
||||
>
|
||||
{showIconLabels && __('Rename', 'mailpoet')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
renderContent={() => (
|
||||
<div className="mailpoet-email-editor-email-title-edit">
|
||||
<TextControl
|
||||
label={__('Campaign name', 'mailpoet')}
|
||||
value={emailTitle}
|
||||
onChange={(newTitle) => {
|
||||
setTitle(newTitle);
|
||||
}}
|
||||
name="campaign_name"
|
||||
help={__(
|
||||
`Name your email campaign to indicate its purpose. This would only be visible to you and not shown to your subscribers.`,
|
||||
'mailpoet',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
import { useRef, useState } from '@wordpress/element';
|
||||
import { PinnedItems } from '@wordpress/interface';
|
||||
import { Button, ToolbarItem as WpToolbarItem } from '@wordpress/components';
|
||||
import {
|
||||
NavigableToolbar,
|
||||
BlockToolbar as WPBlockToolbar,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { store as coreDataStore } from '@wordpress/core-data';
|
||||
// @ts-expect-error DocumentBar types are not available
|
||||
import { DocumentBar, store as editorStore } from '@wordpress/editor';
|
||||
import { store as preferencesStore } from '@wordpress/preferences';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { plus, listView, undo, redo, next, previous } from '@wordpress/icons';
|
||||
import classnames from 'classnames';
|
||||
import { storeName } from '../../store';
|
||||
import { MoreMenu } from './more-menu';
|
||||
import { PreviewDropdown } from '../preview';
|
||||
import { SaveButton } from './save-button';
|
||||
import { CampaignName } from './campaign-name';
|
||||
import { SendButton } from './send-button';
|
||||
|
||||
import { unlock } from '../../../lock-unlock';
|
||||
|
||||
// Build type for ToolbarItem contains only "as" and "children" properties but it takes all props from
|
||||
// component passed to "as" property (in this case Button). So as fix for TS errors we need to pass all props from Button to ToolbarItem.
|
||||
// We should be able to remove this fix when ToolbarItem will be fixed in Gutenberg.
|
||||
const ToolbarItem = WpToolbarItem as React.ForwardRefExoticComponent<
|
||||
React.ComponentProps<typeof WpToolbarItem> &
|
||||
React.ComponentProps<typeof Button>
|
||||
>;
|
||||
|
||||
// Definition of BlockToolbar in currently installed Gutenberg packages (wp-6.4) is missing hideDragHandle prop
|
||||
// After updating to newer version of Gutenberg we should be able to remove this fix
|
||||
const BlockToolbar = WPBlockToolbar as React.FC<
|
||||
React.ComponentProps<typeof WPBlockToolbar> & {
|
||||
hideDragHandle?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export function Header() {
|
||||
const inserterButton = useRef();
|
||||
const listviewButton = useRef();
|
||||
const undoButton = useRef();
|
||||
const redoButton = useRef();
|
||||
|
||||
const [isBlockToolsCollapsed, setIsBlockToolsCollapsed] = useState(false);
|
||||
|
||||
const { toggleInserterSidebar, toggleListviewSidebar } =
|
||||
useDispatch(storeName);
|
||||
const { undo: undoAction, redo: redoAction } = useDispatch(coreDataStore);
|
||||
const {
|
||||
isInserterSidebarOpened,
|
||||
isListviewSidebarOpened,
|
||||
isFixedToolbarActive,
|
||||
isBlockSelected,
|
||||
hasUndo,
|
||||
hasRedo,
|
||||
hasDocumentNavigationHistory,
|
||||
} = useSelect((select) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { getEditorSettings: _getEditorSettings } = unlock(
|
||||
select(editorStore),
|
||||
);
|
||||
const editorSettings = _getEditorSettings();
|
||||
|
||||
return {
|
||||
isInserterSidebarOpened: select(storeName).isInserterSidebarOpened(),
|
||||
isListviewSidebarOpened: select(storeName).isListviewSidebarOpened(),
|
||||
isFixedToolbarActive: select(preferencesStore).get(
|
||||
'core',
|
||||
'fixedToolbar',
|
||||
),
|
||||
isBlockSelected: !!select(blockEditorStore).getBlockSelectionStart(),
|
||||
hasUndo: select(coreDataStore).hasUndo(),
|
||||
hasRedo: select(coreDataStore).hasRedo(),
|
||||
hasDocumentNavigationHistory:
|
||||
!!editorSettings.onNavigateToPreviousEntityRecord,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const preventDefault = (event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const shortLabelInserter = !isInserterSidebarOpened
|
||||
? __('Add', 'mailpoet')
|
||||
: __('Close', 'mailpoet');
|
||||
|
||||
return (
|
||||
<div className="editor-header edit-post-header">
|
||||
<div className="editor-header__toolbar edit-post-header__toolbar">
|
||||
<NavigableToolbar
|
||||
className="editor-header__toolbar edit-post-header-toolbar is-unstyled editor-document-tools"
|
||||
aria-label={__('Email document tools', 'mailpoet')}
|
||||
>
|
||||
<div className="editor-document-tools__left">
|
||||
<ToolbarItem
|
||||
ref={inserterButton}
|
||||
as={Button}
|
||||
className="editor-header-toolbar__inserter-toggle edit-post-header-toolbar__inserter-toggle"
|
||||
variant="primary"
|
||||
isPressed={isInserterSidebarOpened}
|
||||
onMouseDown={preventDefault}
|
||||
onClick={toggleInserterSidebar}
|
||||
disabled={false}
|
||||
icon={plus}
|
||||
label={shortLabelInserter}
|
||||
showTooltip
|
||||
aria-expanded={isInserterSidebarOpened}
|
||||
/>
|
||||
<ToolbarItem
|
||||
ref={undoButton}
|
||||
as={Button}
|
||||
className="editor-history__undo"
|
||||
isPressed={false}
|
||||
onMouseDown={preventDefault}
|
||||
onClick={undoAction}
|
||||
disabled={!hasUndo}
|
||||
icon={undo}
|
||||
label={__('Undo', 'mailpoet')}
|
||||
showTooltip
|
||||
/>
|
||||
<ToolbarItem
|
||||
ref={redoButton}
|
||||
as={Button}
|
||||
className="editor-history__redo"
|
||||
isPressed={false}
|
||||
onMouseDown={preventDefault}
|
||||
onClick={redoAction}
|
||||
disabled={!hasRedo}
|
||||
icon={redo}
|
||||
label={__('Redo', 'mailpoet')}
|
||||
showTooltip
|
||||
/>
|
||||
<ToolbarItem
|
||||
ref={listviewButton}
|
||||
as={Button}
|
||||
className="editor-header-toolbar__document-overview-toggle edit-post-header-toolbar__document-overview-toggle"
|
||||
isPressed={isListviewSidebarOpened}
|
||||
onMouseDown={preventDefault}
|
||||
onClick={toggleListviewSidebar}
|
||||
disabled={false}
|
||||
icon={listView}
|
||||
label={__('List view', 'mailpoet')}
|
||||
showTooltip
|
||||
aria-expanded={isInserterSidebarOpened}
|
||||
/>
|
||||
</div>
|
||||
</NavigableToolbar>
|
||||
{isFixedToolbarActive && isBlockSelected && (
|
||||
<>
|
||||
<div
|
||||
className={classnames('selected-block-tools-wrapper', {
|
||||
'is-collapsed': isBlockToolsCollapsed,
|
||||
})}
|
||||
>
|
||||
<BlockToolbar hideDragHandle />
|
||||
</div>
|
||||
<Button
|
||||
className="editor-header__block-tools-toggle edit-post-header__block-tools-toggle"
|
||||
icon={isBlockToolsCollapsed ? next : previous}
|
||||
onClick={() => {
|
||||
setIsBlockToolsCollapsed((collapsed) => !collapsed);
|
||||
}}
|
||||
label={
|
||||
isBlockToolsCollapsed
|
||||
? __('Show block tools', 'mailpoet')
|
||||
: __('Hide block tools', 'mailpoet')
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{(!isFixedToolbarActive ||
|
||||
!isBlockSelected ||
|
||||
isBlockToolsCollapsed) && (
|
||||
<div className="editor-header__center edit-post-header__center">
|
||||
{hasDocumentNavigationHistory ? <DocumentBar /> : <CampaignName />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="editor-header__settings edit-post-header__settings">
|
||||
<SaveButton />
|
||||
<PreviewDropdown />
|
||||
<SendButton />
|
||||
<PinnedItems.Slot scope={storeName} />
|
||||
<MoreMenu />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// Document actions - Component in header for displaying email/campaign title edit popup
|
||||
.mailpoet-email-editor-campaign-name {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
|
||||
.components-dropdown {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.components-button {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mailpoet-email-campaign-name__link {
|
||||
display: inline-flex;
|
||||
height: fit-content;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// Document actions - Popup for editing email/campaign title
|
||||
.mailpoet-email-editor-campaign-name__dropdown {
|
||||
.components-popover__content {
|
||||
min-width: 280px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mailpoet-email-editor-email-title-edit {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
.mailpoet-email-editor-save-button__dropdown {
|
||||
.components-popover__content {
|
||||
min-width: 280px;
|
||||
}
|
||||
}
|
@ -1 +1,3 @@
|
||||
import './index.scss';
|
||||
|
||||
export * from './header';
|
@ -0,0 +1,113 @@
|
||||
import { MenuGroup, MenuItem, DropdownMenu } from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { displayShortcut } from '@wordpress/keycodes';
|
||||
import { moreVertical } from '@wordpress/icons';
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { PreferenceToggleMenuItem } from '@wordpress/preferences';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { storeName } from '../../store';
|
||||
import { TrashModal } from './trash-modal';
|
||||
|
||||
// See:
|
||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/header/more-menu/index.js
|
||||
// https://github.com/WordPress/gutenberg/blob/0ee78b1bbe9c6f3e6df99f3b967132fa12bef77d/packages/edit-site/src/components/header/more-menu/index.js
|
||||
|
||||
export function MoreMenu(): JSX.Element {
|
||||
const [showTrashModal, setShowTrashModal] = useState(false);
|
||||
const { urls, postId } = useSelect(
|
||||
(select) => ({
|
||||
urls: select(storeName).getUrls(),
|
||||
postId: select(storeName).getEmailPostId(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const [status, setStatus] = useEntityProp(
|
||||
'postType',
|
||||
'mailpoet_email',
|
||||
'status',
|
||||
);
|
||||
const { saveEditedEmail, updateEmailMailPoetProperty } =
|
||||
useDispatch(storeName);
|
||||
const goToListings = () => {
|
||||
window.location.href = urls.listings;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu
|
||||
className="edit-site-more-menu"
|
||||
popoverProps={{
|
||||
className: 'edit-site-more-menu__content',
|
||||
}}
|
||||
icon={moreVertical}
|
||||
label={__('More', 'mailpoet')}
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
<MenuGroup label={_x('View', 'noun', 'mailpoet')}>
|
||||
<PreferenceToggleMenuItem
|
||||
scope={storeName}
|
||||
name="fixedToolbar"
|
||||
label={__('Top toolbar', 'mailpoet')}
|
||||
info={__(
|
||||
'Access all block and document tools in a single place',
|
||||
'mailpoet',
|
||||
)}
|
||||
messageActivated={__('Top toolbar activated', 'mailpoet')}
|
||||
messageDeactivated={__('Top toolbar deactivated', 'mailpoet')}
|
||||
/>
|
||||
<PreferenceToggleMenuItem
|
||||
scope={storeName}
|
||||
name="focusMode"
|
||||
label={__('Spotlight mode', 'mailpoet')}
|
||||
info={__('Focus at one block at a time', 'mailpoet')}
|
||||
messageActivated={__('Spotlight mode activated', 'mailpoet')}
|
||||
messageDeactivated={__(
|
||||
'Spotlight mode deactivated',
|
||||
'mailpoet',
|
||||
)}
|
||||
/>
|
||||
<PreferenceToggleMenuItem
|
||||
scope={storeName}
|
||||
name="fullscreenMode"
|
||||
label={__('Fullscreen mode', 'mailpoet')}
|
||||
info={__('Work without distraction', 'mailpoet')}
|
||||
messageActivated={__('Fullscreen mode activated', 'mailpoet')}
|
||||
messageDeactivated={__(
|
||||
'Fullscreen mode deactivated',
|
||||
'mailpoet',
|
||||
)}
|
||||
shortcut={displayShortcut.secondary('f')}
|
||||
/>
|
||||
</MenuGroup>
|
||||
<MenuGroup>
|
||||
{status === 'trash' ? (
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
await setStatus('draft');
|
||||
await updateEmailMailPoetProperty('deleted_at', '');
|
||||
await saveEditedEmail();
|
||||
}}
|
||||
>
|
||||
{__('Restore from trash', 'mailpoet')}
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem onClick={() => setShowTrashModal(true)} isDestructive>
|
||||
{__('Move to trash', 'mailpoet')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuGroup>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
{showTrashModal && (
|
||||
<TrashModal
|
||||
onClose={() => setShowTrashModal(false)}
|
||||
onRemove={goToListings}
|
||||
postId={postId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { useRef } from '@wordpress/element';
|
||||
import { Button, Dropdown } from '@wordpress/components';
|
||||
import {
|
||||
// @ts-expect-error No types available for useEntitiesSavedStatesIsDirty
|
||||
useEntitiesSavedStatesIsDirty,
|
||||
// @ts-expect-error Our current version of packages doesn't have EntitiesSavedStates export
|
||||
EntitiesSavedStates,
|
||||
} from '@wordpress/editor';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { check, cloud, Icon } from '@wordpress/icons';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
export function SaveButton() {
|
||||
const { saveEditedEmail } = useDispatch(storeName);
|
||||
|
||||
const { dirtyEntityRecords } = useEntitiesSavedStatesIsDirty();
|
||||
|
||||
const { hasEdits, isEmpty, isSaving } = useSelect(
|
||||
(select) => ({
|
||||
hasEdits: select(storeName).hasEdits(),
|
||||
isEmpty: select(storeName).isEmpty(),
|
||||
isSaving: select(storeName).isSaving(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const buttonRef = useRef(null);
|
||||
|
||||
const hasNonEmailEdits = dirtyEntityRecords.some(
|
||||
(entity) => entity.name !== 'mailpoet_email',
|
||||
);
|
||||
|
||||
const isSaved = !isEmpty && !isSaving && !hasEdits;
|
||||
const isDisabled = isEmpty || isSaving || isSaved;
|
||||
|
||||
let label = __('Save Draft', 'mailpoet');
|
||||
if (isSaved) {
|
||||
label = __('Saved', 'mailpoet');
|
||||
} else if (isSaving) {
|
||||
label = __('Saving', 'mailpoet');
|
||||
}
|
||||
|
||||
return hasNonEmailEdits ? (
|
||||
<div ref={buttonRef}>
|
||||
<Dropdown
|
||||
popoverProps={{
|
||||
placement: 'bottom',
|
||||
anchor: buttonRef.current,
|
||||
}}
|
||||
contentClassName="mailpoet-email-editor-save-button__dropdown"
|
||||
renderToggle={({ onToggle }) => (
|
||||
<Button onClick={onToggle} variant="tertiary">
|
||||
{hasEdits
|
||||
? __('Save email & template', 'mailpoet')
|
||||
: __('Save template', 'mailpoet')}
|
||||
</Button>
|
||||
)}
|
||||
renderContent={({ onToggle }) => (
|
||||
<EntitiesSavedStates close={onToggle} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button variant="tertiary" disabled={isDisabled} onClick={saveEditedEmail}>
|
||||
{isSaving && <Icon icon={cloud} />}
|
||||
{isSaved && <Icon icon={check} />}
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import {
|
||||
store as editorStore,
|
||||
// @ts-expect-error No types available for useEntitiesSavedStatesIsDirty
|
||||
useEntitiesSavedStatesIsDirty,
|
||||
} from '@wordpress/editor';
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import { MailPoetEmailData, storeName } from 'email-editor/engine/store';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useContentValidation } from 'email-editor/engine/hooks';
|
||||
|
||||
export function SendButton() {
|
||||
const [mailpoetEmail] = useEntityProp(
|
||||
'postType',
|
||||
'mailpoet_email',
|
||||
'mailpoet_data',
|
||||
);
|
||||
|
||||
const { isDirty } = useEntitiesSavedStatesIsDirty();
|
||||
|
||||
const { validateContent, isValid } = useContentValidation();
|
||||
const { hasEmptyContent, isEmailSent, isEditingTemplate } = useSelect(
|
||||
(select) => ({
|
||||
hasEmptyContent: select(storeName).hasEmptyContent(),
|
||||
isEmailSent: select(storeName).isEmailSent(),
|
||||
isEditingTemplate:
|
||||
select(editorStore).getCurrentPostType() === 'wp_template',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const isDisabled =
|
||||
isEditingTemplate || hasEmptyContent || isEmailSent || isValid || isDirty;
|
||||
|
||||
const mailpoetEmailData: MailPoetEmailData = mailpoetEmail;
|
||||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
if (validateContent()) {
|
||||
window.location.href = `admin.php?page=mailpoet-newsletters#/send/${mailpoetEmailData.id}`;
|
||||
}
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{__('Send', 'mailpoet')}
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, Modal } from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
|
||||
export function TrashModal({
|
||||
onClose,
|
||||
onRemove,
|
||||
postId,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onRemove: () => void;
|
||||
postId: number;
|
||||
}) {
|
||||
const { getLastEntityDeleteError } = useSelect(coreStore);
|
||||
const { deleteEntityRecord } = useDispatch(coreStore);
|
||||
const { createErrorNotice } = useDispatch(noticesStore);
|
||||
const closeCallback = () => {
|
||||
onClose();
|
||||
};
|
||||
const trashCallback = async () => {
|
||||
const success = await deleteEntityRecord(
|
||||
'postType',
|
||||
'mailpoet_email',
|
||||
postId as unknown as string,
|
||||
{},
|
||||
{ throwOnError: false },
|
||||
);
|
||||
if (success) {
|
||||
onRemove();
|
||||
} else {
|
||||
const lastError = getLastEntityDeleteError(
|
||||
'postType',
|
||||
'mailpoet_email',
|
||||
postId,
|
||||
);
|
||||
// Already deleted.
|
||||
if (lastError?.code === 410) {
|
||||
onRemove();
|
||||
} else {
|
||||
const errorMessage = lastError?.message
|
||||
? (lastError.message as string)
|
||||
: __(
|
||||
'An error occurred while moving the email to the trash.',
|
||||
'mailpoet',
|
||||
);
|
||||
await createErrorNotice(errorMessage, {
|
||||
type: 'snackbar',
|
||||
isDismissible: true,
|
||||
context: 'email-editor',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
className="mailpoet-move-to-trash-modal"
|
||||
title={__('Move to trash', 'mailpoet')}
|
||||
onRequestClose={closeCallback}
|
||||
focusOnMount="firstContentElement"
|
||||
>
|
||||
<p>
|
||||
{__('Are you sure you want to move this email to trash?', 'mailpoet')}
|
||||
</p>
|
||||
<div className="mailpoet-send-preview-modal-footer">
|
||||
<Button variant="tertiary" onClick={closeCallback}>
|
||||
{__('Cancel', 'mailpoet')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={trashCallback}>
|
||||
{__('Move to trash', 'mailpoet')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default TrashModal;
|
@ -0,0 +1,31 @@
|
||||
import {
|
||||
__experimentalLibrary as Library,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { store as editorStore } from '@wordpress/editor';
|
||||
|
||||
export function InserterSidebar() {
|
||||
const { postContentId, isEditingEmailContent } = useSelect((select) => {
|
||||
const blocks = select(blockEditorStore).getBlocks();
|
||||
return {
|
||||
postContentId: blocks.find((block) => block.name === 'core/post-content')
|
||||
?.clientId,
|
||||
isEditingEmailContent:
|
||||
select(editorStore).getCurrentPostType() !== 'wp_template',
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="edit-post-editor__inserter-panel">
|
||||
<div className="edit-post-editor__inserter-panel-content">
|
||||
<Library
|
||||
showMostUsedBlocks
|
||||
showInserterHelpPanel={false}
|
||||
// In the email content mode we insert primarily into the post content block.
|
||||
rootClientId={isEditingEmailContent ? postContentId : null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import {
|
||||
useShortcut,
|
||||
store as keyboardShortcutsStore,
|
||||
} from '@wordpress/keyboard-shortcuts';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
// See:
|
||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/keyboard-shortcuts/index.js
|
||||
// https://github.com/WordPress/gutenberg/blob/0ee78b1bbe9c6f3e6df99f3b967132fa12bef77d/packages/edit-site/src/components/keyboard-shortcuts/index.js
|
||||
|
||||
export function KeyboardShortcuts(): null {
|
||||
const { isSidebarOpened, hasEdits, isSaving } = useSelect((select) => ({
|
||||
isSidebarOpened: select(storeName).isSidebarOpened(),
|
||||
isSaving: select(storeName).isSaving(),
|
||||
hasEdits: select(storeName).hasEdits(),
|
||||
}));
|
||||
|
||||
const { openSidebar, closeSidebar, saveEditedEmail, toggleFeature } =
|
||||
useDispatch(storeName);
|
||||
|
||||
const { registerShortcut } = useDispatch(keyboardShortcutsStore);
|
||||
|
||||
useEffect(() => {
|
||||
void registerShortcut({
|
||||
name: 'mailpoet/email-editor/toggle-fullscreen',
|
||||
category: 'global',
|
||||
description: __('Toggle fullscreen mode.', 'mailpoet'),
|
||||
keyCombination: {
|
||||
modifier: 'secondary',
|
||||
character: 'f',
|
||||
},
|
||||
});
|
||||
|
||||
void registerShortcut({
|
||||
name: 'mailpoet/email-editor/toggle-sidebar',
|
||||
category: 'global',
|
||||
description: __('Show or hide the settings sidebar.', 'mailpoet'),
|
||||
keyCombination: {
|
||||
modifier: 'primaryShift',
|
||||
character: ',',
|
||||
},
|
||||
});
|
||||
|
||||
void registerShortcut({
|
||||
name: 'mailpoet/email-editor/save',
|
||||
category: 'global',
|
||||
description: __('Save your changes.', 'mailpoet'),
|
||||
keyCombination: {
|
||||
modifier: 'primary',
|
||||
character: 's',
|
||||
},
|
||||
});
|
||||
}, [registerShortcut]);
|
||||
|
||||
useShortcut('mailpoet/email-editor/toggle-fullscreen', () => {
|
||||
void toggleFeature('fullscreenMode');
|
||||
});
|
||||
|
||||
useShortcut('mailpoet/email-editor/toggle-sidebar', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (isSidebarOpened) {
|
||||
void closeSidebar();
|
||||
} else {
|
||||
void openSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
useShortcut('mailpoet/email-editor/save', (event) => {
|
||||
event.preventDefault();
|
||||
if (!hasEdits || isSaving) {
|
||||
return;
|
||||
}
|
||||
void saveEditedEmail();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { __experimentalListView as ListView } from '@wordpress/block-editor';
|
||||
|
||||
export function ListviewSidebar() {
|
||||
return (
|
||||
<div className="edit-post-editor__inserter-panel edit-post-editor__document-overview-panel">
|
||||
<div className="edit-post-editor__list-view-panel-content">
|
||||
<div className="edit-post-editor__list-view-container">
|
||||
<ListView />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
.components-notice.mailpoet-email-editor-validation-errors {
|
||||
margin: 0;
|
||||
|
||||
ul {
|
||||
list-style: disc outside;
|
||||
margin: 0 0 0 20px;
|
||||
}
|
||||
li {
|
||||
margin: 8px 0;
|
||||
padding: 0;
|
||||
}
|
||||
li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
button.components-button.is-link {
|
||||
color: #1e1e1e;
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import './index.scss';
|
||||
|
||||
export { EditorNotices } from './notices';
|
||||
export { EditorSnackbars } from './snackbars';
|
||||
export { SentEmailNotice } from './sent-email-notice';
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user