Compare commits
296 Commits
Author | SHA1 | Date | |
---|---|---|---|
0180a1245b | |||
593071efff | |||
3c46c87da9 | |||
9d1639c100 | |||
8ff4f7720b | |||
13d35c4385 | |||
c778b84932 | |||
1ee06d7196 | |||
5134c60291 | |||
57486a522a | |||
8ae34e83ec | |||
19d13c5165 | |||
f64b42d5cd | |||
78539d0e6f | |||
2e6dd9b20f | |||
d1b91cd661 | |||
fb3fdbf1ed | |||
a0e8e52183 | |||
9364a422b3 | |||
5e16bc4184 | |||
75295e55c2 | |||
13329d568d | |||
b9993da62c | |||
5605c0c9dd | |||
c60a7a233c | |||
6c8705f3c2 | |||
99a889fab6 | |||
a1457678b0 | |||
b6e0b7ceb0 | |||
8d3e751845 | |||
410aa76d37 | |||
c9b350adab | |||
9f46300d7d | |||
55e7c97af0 | |||
c4536f3591 | |||
e4d4b575bd | |||
2771c8c33e | |||
79dbaa119d | |||
05d054dfad | |||
4bbde3435d | |||
74d711fa73 | |||
464c27849b | |||
2f202f6b3d | |||
186b4c9a36 | |||
4b94392362 | |||
cca2a1923d | |||
76fe4dba59 | |||
fef557bb94 | |||
5c5069643e | |||
6e391559fe | |||
62e47dcf12 | |||
85e1467936 | |||
8d816337f7 | |||
8209facb5c | |||
19d5c44588 | |||
afc04e44af | |||
80d6cd0187 | |||
b1f9a8a84f | |||
5635014285 | |||
91e66476d3 | |||
33b4ed324e | |||
a90672652e | |||
cc49c36466 | |||
0a82c82387 | |||
d987341094 | |||
25f70df2ad | |||
42859d94a3 | |||
4abaebf96d | |||
cada06360b | |||
fe6519d503 | |||
5a4cebf0d7 | |||
cb8d532d64 | |||
7f31f0888e | |||
b54d31c797 | |||
f3a597ef90 | |||
d1fbe3dae1 | |||
f30e25964c | |||
908f2f9f60 | |||
4a781849db | |||
c4094efd91 | |||
c13974ad82 | |||
787cccb33d | |||
7e13bbcc57 | |||
741cfb1775 | |||
797491ddec | |||
cfcba021b1 | |||
35f59cdcc0 | |||
9f60cf1554 | |||
21f03bbcb7 | |||
ee37f39980 | |||
883023e581 | |||
ca3e309104 | |||
d46f9203ee | |||
7c19aecaa0 | |||
050417ec83 | |||
5b077b3911 | |||
70743b98b6 | |||
103714a035 | |||
0ae162b334 | |||
4f97bb45e1 | |||
d4fa041ba8 | |||
eb1acc8145 | |||
6479a6cc10 | |||
9eb586b8e6 | |||
f39c0c58c2 | |||
0c155ffe7c | |||
ad77fa547a | |||
7747c30028 | |||
ea9be3e078 | |||
fff8176a49 | |||
71ad9f50cb | |||
8eff3dc3c6 | |||
49ff4880f8 | |||
4d93aff2cb | |||
7b859660b7 | |||
3e22223d0c | |||
384c9fb17b | |||
79acc0e4df | |||
52a29ff449 | |||
c4db1df528 | |||
1fe7bedf20 | |||
f5be4e47e8 | |||
2ae39c9255 | |||
6028027d47 | |||
283452f47e | |||
7be91476f0 | |||
b528587b1f | |||
056b971f7b | |||
fcfd6f1f09 | |||
90186f2af5 | |||
c9ef3cfb95 | |||
b6f40a9b52 | |||
1936524cec | |||
8b2c5116f4 | |||
9734759ba5 | |||
c76b4c3c99 | |||
9092aa3029 | |||
87e515b89d | |||
05e5ca3f43 | |||
5fed328826 | |||
7538a08678 | |||
82d6a4b096 | |||
b21ef30202 | |||
653f31e997 | |||
149c794905 | |||
cbf48327d9 | |||
c83ed66160 | |||
042fe48669 | |||
9aba09d42e | |||
25a8519583 | |||
9fc442ae8e | |||
1e7542848a | |||
78821a69ab | |||
4ae459838a | |||
aa83883cfa | |||
6d9f0767af | |||
c03feeae3f | |||
8effcf2c59 | |||
f81c5617ed | |||
e6c4c02484 | |||
ff332e78e9 | |||
f211ffce3b | |||
ba127990e3 | |||
03ca022596 | |||
ee00464cc5 | |||
aaa1c659b8 | |||
b9ffc0e280 | |||
a943589de9 | |||
c0e252f42a | |||
980574eb6d | |||
72ea5fb84e | |||
cc303c0cd3 | |||
d11c9c5296 | |||
fd893cec0e | |||
debe9ea1ba | |||
3029e6102f | |||
9c8c89149b | |||
edb3f25838 | |||
2f730c280c | |||
1aa0a86d66 | |||
6b6f488f1e | |||
c1e8815134 | |||
64e5742700 | |||
ce6d45e557 | |||
91a1ca69ed | |||
549e2e7e86 | |||
ce6711d69e | |||
0639b1ad1f | |||
ad37c6b9c5 | |||
605f8f1ce1 | |||
1ffd5741be | |||
a1d606e533 | |||
a6ab757d22 | |||
654dd1e8d0 | |||
40c19cd5d8 | |||
3cccce3d86 | |||
57203a6917 | |||
d01805a911 | |||
4774ab09f3 | |||
5dad1fa545 | |||
c3481dd4b7 | |||
b5338979b1 | |||
436406f800 | |||
112f7b21d5 | |||
b45c47a9e7 | |||
cb3952c9fb | |||
b597cb39f1 | |||
3102f114bd | |||
bdf244536d | |||
ca3b6412ee | |||
469dc15e4d | |||
253be30153 | |||
d7fb5450f4 | |||
280bc06c03 | |||
c1a505c425 | |||
614d9f7e56 | |||
7c9b2be760 | |||
0f2bc75248 | |||
f0df936dbc | |||
4b675a41d6 | |||
0e7cc668e2 | |||
70debcc828 | |||
4249c7a2cb | |||
fbe2f72706 | |||
6b9c252649 | |||
be75e0d3ee | |||
8e12bbf97f | |||
1936a3bcdd | |||
7eb37119ed | |||
aadca62a32 | |||
11401e2dab | |||
4cae2a7d2d | |||
9866f4c707 | |||
b632d74e5f | |||
c3a97b7139 | |||
a772aceb39 | |||
d59ace50f5 | |||
ff6f686333 | |||
2ef8512fa7 | |||
cabea5dadb | |||
b80505a17c | |||
e0b676fac4 | |||
6566f515f5 | |||
2836f72435 | |||
0b644e7c8f | |||
df4d3bacab | |||
6dc0acb63a | |||
a346492d46 | |||
b6513262b0 | |||
868e07ab86 | |||
9ec6f52098 | |||
222d6b4eac | |||
0683dc9817 | |||
45e6ddd9cb | |||
c556ec64fc | |||
5bc9573ac6 | |||
a3d7afd1ca | |||
8c44ef561c | |||
c51a3f208c | |||
5524805ec3 | |||
816ec7e1d7 | |||
d74283ecad | |||
e810840445 | |||
b2e2087cfc | |||
69d920376f | |||
22efacd2d7 | |||
25c72c6ce7 | |||
e94d85fd47 | |||
c6b379c840 | |||
71bf3bbe19 | |||
6e60056061 | |||
96edbc159f | |||
c3c0765b78 | |||
b614997150 | |||
f81323ad52 | |||
cfc0d79bed | |||
c6adfc6a5c | |||
53149c291c | |||
e8f0a0067d | |||
cc4ff09aea | |||
77dfd28479 | |||
cd283c11fe | |||
57baff7b1d | |||
fd72756622 | |||
9b3abea2e5 | |||
42c557a981 | |||
e4db455a47 | |||
5d1f3153cd | |||
fcd8509cef | |||
ad731fc5ed | |||
4257ff6313 | |||
f9a3875de1 | |||
b17d217669 | |||
556e4a0ce0 | |||
a72d84b940 | |||
049074e793 |
@ -3,7 +3,7 @@ jobs:
|
|||||||
build_and_code_qa:
|
build_and_code_qa:
|
||||||
working_directory: /home/circleci/mailpoet
|
working_directory: /home/circleci/mailpoet
|
||||||
docker:
|
docker:
|
||||||
- image: mailpoet/wordpress:7.1_20180417.1
|
- image: mailpoet/wordpress:7.1_20181009.1
|
||||||
environment:
|
environment:
|
||||||
TZ: /usr/share/zoneinfo/Etc/UTC
|
TZ: /usr/share/zoneinfo/Etc/UTC
|
||||||
steps:
|
steps:
|
||||||
@ -42,7 +42,35 @@ jobs:
|
|||||||
root: /home/circleci/mailpoet
|
root: /home/circleci/mailpoet
|
||||||
paths:
|
paths:
|
||||||
- .
|
- .
|
||||||
php5_and_js:
|
php5_unit:
|
||||||
|
working_directory: /home/circleci/mailpoet
|
||||||
|
docker:
|
||||||
|
- image: mailpoet/wordpress:5.6.30_20180417.1
|
||||||
|
- image: circleci/mysql:5.7
|
||||||
|
environment:
|
||||||
|
TZ: /usr/share/zoneinfo/Etc/UTC
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: /home/circleci/mailpoet
|
||||||
|
- run:
|
||||||
|
name: "Set up virtual host"
|
||||||
|
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
|
||||||
|
- run:
|
||||||
|
name: "Set up test environment"
|
||||||
|
command: source ./.circleci/setup.bash && setup php5
|
||||||
|
- run:
|
||||||
|
name: "PHP Unit tests"
|
||||||
|
command: |
|
||||||
|
WP_ROOT="/home/circleci/mailpoet/wordpress" ./do t:u --xml
|
||||||
|
- store_test_results:
|
||||||
|
path: tests/_output
|
||||||
|
- store_artifacts:
|
||||||
|
path: tests/_output
|
||||||
|
destination: codeception
|
||||||
|
- store_artifacts:
|
||||||
|
path: /tmp/fake-mailer/
|
||||||
|
destination: fake-mailer
|
||||||
|
php5_integration_and_js:
|
||||||
working_directory: /home/circleci/mailpoet
|
working_directory: /home/circleci/mailpoet
|
||||||
docker:
|
docker:
|
||||||
- image: mailpoet/wordpress:5.6.30_20180417.1
|
- image: mailpoet/wordpress:5.6.30_20180417.1
|
||||||
@ -67,9 +95,9 @@ jobs:
|
|||||||
mkdir test-results/mocha
|
mkdir test-results/mocha
|
||||||
./do t:j test-results/mocha/junit.xml
|
./do t:j test-results/mocha/junit.xml
|
||||||
- run:
|
- run:
|
||||||
name: "PHP Unit tests"
|
name: "PHP Integration tests"
|
||||||
command: |
|
command: |
|
||||||
WP_TEST_PATH="/home/circleci/mailpoet/wordpress" ./do t:u --xml
|
WP_ROOT="/home/circleci/mailpoet/wordpress" ./do t:i --xml
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: test-results/mocha
|
path: test-results/mocha
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
@ -131,10 +159,10 @@ jobs:
|
|||||||
path: tests/_output
|
path: tests/_output
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: tests/_output
|
path: tests/_output
|
||||||
php7:
|
php7_unit:
|
||||||
working_directory: /home/circleci/mailpoet
|
working_directory: /home/circleci/mailpoet
|
||||||
docker:
|
docker:
|
||||||
- image: mailpoet/wordpress:7.1_20180417.1
|
- image: mailpoet/wordpress:7.1_20181009.1
|
||||||
- image: circleci/mysql:5.7
|
- image: circleci/mysql:5.7
|
||||||
environment:
|
environment:
|
||||||
TZ: /usr/share/zoneinfo/Etc/UTC
|
TZ: /usr/share/zoneinfo/Etc/UTC
|
||||||
@ -162,10 +190,41 @@ jobs:
|
|||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/fake-mailer/
|
path: /tmp/fake-mailer/
|
||||||
destination: fake-mailer
|
destination: fake-mailer
|
||||||
php7_multisite:
|
php7_integration:
|
||||||
working_directory: /home/circleci/mailpoet
|
working_directory: /home/circleci/mailpoet
|
||||||
docker:
|
docker:
|
||||||
- image: mailpoet/wordpress:7.1_20180417.1
|
- image: mailpoet/wordpress:7.1_20181009.1
|
||||||
|
- image: circleci/mysql:5.7
|
||||||
|
environment:
|
||||||
|
TZ: /usr/share/zoneinfo/Etc/UTC
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: /home/circleci/mailpoet
|
||||||
|
- run:
|
||||||
|
name: "Set up virtual host"
|
||||||
|
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
|
||||||
|
- run:
|
||||||
|
name: "Prepare example.com for testing"
|
||||||
|
command: echo 127.0.0.1 example.com | sudo tee -a /etc/hosts
|
||||||
|
- run:
|
||||||
|
name: "Set up test environment"
|
||||||
|
command: source ./.circleci/setup.bash && setup php7
|
||||||
|
- run:
|
||||||
|
name: "PHP Integration tests"
|
||||||
|
command: |
|
||||||
|
./do t:i --xml
|
||||||
|
- store_test_results:
|
||||||
|
path: tests/_output
|
||||||
|
- store_artifacts:
|
||||||
|
path: tests/_output
|
||||||
|
destination: codeception
|
||||||
|
- store_artifacts:
|
||||||
|
path: /tmp/fake-mailer/
|
||||||
|
destination: fake-mailer
|
||||||
|
php7_integration_multisite:
|
||||||
|
working_directory: /home/circleci/mailpoet
|
||||||
|
docker:
|
||||||
|
- image: mailpoet/wordpress:7.1_20181009.1
|
||||||
- image: circleci/mysql:5.7
|
- image: circleci/mysql:5.7
|
||||||
environment:
|
environment:
|
||||||
TZ: /usr/share/zoneinfo/Etc/UTC
|
TZ: /usr/share/zoneinfo/Etc/UTC
|
||||||
@ -182,9 +241,9 @@ jobs:
|
|||||||
name: "Set up test environment"
|
name: "Set up test environment"
|
||||||
command: source ./.circleci/setup.bash && setup php7_multisite
|
command: source ./.circleci/setup.bash && setup php7_multisite
|
||||||
- run:
|
- run:
|
||||||
name: "PHP Unit tests"
|
name: "PHP Integration tests"
|
||||||
command: |
|
command: |
|
||||||
./do t:multisite-unit --xml
|
./do t:multisite-integration --xml
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: tests/_output
|
path: tests/_output
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
@ -198,20 +257,24 @@ workflows:
|
|||||||
build_and_test:
|
build_and_test:
|
||||||
jobs:
|
jobs:
|
||||||
- build_and_code_qa
|
- build_and_code_qa
|
||||||
- php7:
|
- php5_unit:
|
||||||
requires:
|
requires:
|
||||||
- build_and_code_qa
|
- build_and_code_qa
|
||||||
- php5_and_js:
|
- php7_unit:
|
||||||
requires:
|
requires:
|
||||||
- build_and_code_qa
|
- build_and_code_qa
|
||||||
|
- php5_integration_and_js:
|
||||||
|
requires:
|
||||||
|
- php5_unit
|
||||||
|
- php7_integration:
|
||||||
|
requires:
|
||||||
|
- php7_unit
|
||||||
|
- php7_integration_multisite:
|
||||||
|
requires:
|
||||||
|
- php7_unit
|
||||||
- acceptance_tests:
|
- acceptance_tests:
|
||||||
requires:
|
requires:
|
||||||
- build_and_code_qa
|
- php5_unit
|
||||||
- php7_multisite:
|
|
||||||
requires:
|
|
||||||
- build_and_code_qa
|
|
||||||
- php7
|
|
||||||
- acceptance_tests_multisite:
|
- acceptance_tests_multisite:
|
||||||
requires:
|
requires:
|
||||||
- build_and_code_qa
|
- php5_unit
|
||||||
- acceptance_tests
|
|
||||||
|
@ -40,14 +40,14 @@ function setup {
|
|||||||
# Add a second blog
|
# Add a second blog
|
||||||
wp site create --slug=php7_multisite $wp_cli_wordpress_path $wp_cli_allow_root
|
wp site create --slug=php7_multisite $wp_cli_wordpress_path $wp_cli_allow_root
|
||||||
echo "WP_TEST_MULTISITE_SLUG=php7_multisite" >> .env
|
echo "WP_TEST_MULTISITE_SLUG=php7_multisite" >> .env
|
||||||
echo "WP_TEST_PATH_MULTISITE=/home/circleci/mailpoet/wordpress" >> .env
|
echo "WP_ROOT_MULTISITE=/home/circleci/mailpoet/wordpress" >> .env
|
||||||
echo "HTTP_HOST=mailpoet.loc" >> .env
|
echo "HTTP_HOST=mailpoet.loc" >> .env
|
||||||
|
|
||||||
# Add a third dummy blog
|
# Add a third dummy blog
|
||||||
wp site create --slug=dummy_multisite $wp_cli_wordpress_path $wp_cli_allow_root
|
wp site create --slug=dummy_multisite $wp_cli_wordpress_path $wp_cli_allow_root
|
||||||
else
|
else
|
||||||
wp core install --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc --title="WordPress Single" $wp_cli_wordpress_path $wp_cli_allow_root
|
wp core install --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc --title="WordPress Single" $wp_cli_wordpress_path $wp_cli_allow_root
|
||||||
echo "WP_TEST_PATH=/home/circleci/mailpoet/wordpress" >> .env
|
echo "WP_ROOT=/home/circleci/mailpoet/wordpress" >> .env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Softlink plugin to plugin path
|
# Softlink plugin to plugin path
|
||||||
|
18
.env.sample
18
.env.sample
@ -1,10 +1,17 @@
|
|||||||
WP_TEST_PATH="/var/www/wordpress"
|
# Required
|
||||||
WP_TEST_PATH_MULTISITE="/var/www/wordpress"
|
WP_ROOT="/var/www/wordpress"
|
||||||
|
WP_TEST_ENABLE_NETWORK_TESTS="false"
|
||||||
|
WP_TEST_MAILER_ENABLE_SENDING="false"
|
||||||
|
|
||||||
|
# Optional: for multisite acceptance tests
|
||||||
|
WP_ROOT_MULTISITE="/var/www/wordpress"
|
||||||
WP_TEST_MULTISITE_SLUG=""
|
WP_TEST_MULTISITE_SLUG=""
|
||||||
WP_TEST_ENABLE_NETWORK_TESTS="true"
|
HTTP_HOST="" // URL of your site (used for multisite env and equals to DOMAIN_CURRENT_SITE from wp-config.php)
|
||||||
|
|
||||||
|
# Optional: for sending tests
|
||||||
|
# These are required if WP_TEST_MAILER_ENABLE_SENDING is "true"
|
||||||
WP_TEST_IMPORT_MAILCHIMP_API=""
|
WP_TEST_IMPORT_MAILCHIMP_API=""
|
||||||
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
|
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
|
||||||
WP_TEST_MAILER_ENABLE_SENDING="true"
|
|
||||||
WP_TEST_MAILER_AMAZON_ACCESS=""
|
WP_TEST_MAILER_AMAZON_ACCESS=""
|
||||||
WP_TEST_MAILER_AMAZON_SECRET=""
|
WP_TEST_MAILER_AMAZON_SECRET=""
|
||||||
WP_TEST_MAILER_AMAZON_REGION=""
|
WP_TEST_MAILER_AMAZON_REGION=""
|
||||||
@ -13,7 +20,8 @@ WP_TEST_MAILER_SENDGRID_API=""
|
|||||||
WP_TEST_MAILER_SMTP_HOST=""
|
WP_TEST_MAILER_SMTP_HOST=""
|
||||||
WP_TEST_MAILER_SMTP_LOGIN=""
|
WP_TEST_MAILER_SMTP_LOGIN=""
|
||||||
WP_TEST_MAILER_SMTP_PASSWORD=""
|
WP_TEST_MAILER_SMTP_PASSWORD=""
|
||||||
|
|
||||||
|
# Optional: for plugin deployment
|
||||||
WP_SVN_USERNAME=""
|
WP_SVN_USERNAME=""
|
||||||
WP_SVN_PASSWORD=""
|
WP_SVN_PASSWORD=""
|
||||||
WP_TRANSIFEX_API_TOKEN=""
|
WP_TRANSIFEX_API_TOKEN=""
|
||||||
HTTP_HOST="" // URL of your site (used for multisite env and equals to DOMAIN_CURRENT_SITE from wp-config.php)
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"amd": true,
|
"amd": true,
|
||||||
"browser": true
|
"browser": true
|
||||||
},
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 6,
|
"ecmaVersion": 6,
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -23,4 +23,8 @@ lang
|
|||||||
.mp_svn
|
.mp_svn
|
||||||
/nbproject/
|
/nbproject/
|
||||||
tests/_data/acceptanceGenerated.sql
|
tests/_data/acceptanceGenerated.sql
|
||||||
lib/Dependencies
|
lib/Dependencies
|
||||||
|
lib/DI/CachedContainer.php
|
||||||
|
mozart/Dependencies
|
||||||
|
mozart/Classes
|
||||||
|
mozart/vendor
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
## Code.
|
## PHP Code
|
||||||
- Two spaces indentation.
|
- Two spaces indentation.
|
||||||
- CamelCase for classes.
|
- CamelCase for classes.
|
||||||
- camelCase for methods.
|
- camelCase for methods.
|
||||||
@ -10,27 +10,23 @@
|
|||||||
- Require other classes with 'use' at the beginning of the class file.
|
- Require other classes with 'use' at the beginning of the class file.
|
||||||
- Do not specify 'public' if method is public, it's implicit.
|
- Do not specify 'public' if method is public, it's implicit.
|
||||||
- Always use guard clauses.
|
- Always use guard clauses.
|
||||||
- Ensure compatibility with PHP 5.3 and newer versions.
|
- Ensure compatibility with PHP 5.5 and newer versions.
|
||||||
- Cover your code in tests.
|
- Cover your code in tests.
|
||||||
|
|
||||||
Recommendations:
|
## JS Code
|
||||||
- Max line length at 80 chars.
|
- Javascript code should follow the [Airbnb style guide](https://github.com/airbnb/javascript).
|
||||||
- Keep classes under 100 LOC.
|
|
||||||
- Keep methods under 10 LOC.
|
|
||||||
- Pass no more than 4 parameters/hash keys into a method.
|
|
||||||
- Keep Pull Requests small, under 100 LOC changed.
|
|
||||||
|
|
||||||
## Git flow.
|
## Git flow
|
||||||
- Do not commit to master.
|
- Do not commit to master.
|
||||||
- Open a short-living feature branch.
|
- Open a short-living feature branch.
|
||||||
- Open a pull request.
|
- Open a pull request.
|
||||||
- Add Jira issue reference in the title of the Pull Request.
|
- Add Jira issue reference in the title of the Pull Request.
|
||||||
- Work on the pull request.
|
- Work on the pull request.
|
||||||
- Wait for review and confirmation from another developer before merging to master.
|
- Use the `./do qa` command to check your code style before pushing.
|
||||||
- Commit title no more than 80 chars, empty line after.
|
- Use good commit messages as explained here https://chris.beams.io/posts/git-commit
|
||||||
- Commit description as long as you want, 80 chars wrap.
|
- Wait for review from another developer.
|
||||||
|
|
||||||
## Issues creation.
|
## Issues creation
|
||||||
- Issues are managed on Jira.
|
- Issues are managed on Jira.
|
||||||
- Discuss issues on public Slack chats, discuss code in pull requests.
|
- Discuss issues on public Slack chats, discuss code in pull requests.
|
||||||
- Open a small Jira issue only when it has been discussed.
|
- Open a small Jira issue only when it has been discussed.
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
FROM mailpoet/wordpress:5.6-cli_20180417.1
|
FROM mailpoet/wordpress:5.6-cli_20181009.1
|
||||||
|
|
||||||
ENV COMPOSER_ALLOW_SUPERUSER=1
|
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||||
|
|
||||||
RUN composer global require --optimize-autoloader "hirak/prestissimo"
|
RUN composer global require --optimize-autoloader "hirak/prestissimo"
|
||||||
|
|
||||||
WORKDIR /wp-core/wp-content/plugins/mailpoet
|
WORKDIR /wp-core/wp-content/plugins/mailpoet
|
||||||
ENV WP_TEST_PATH=/wp-core
|
ENV WP_ROOT=/wp-core
|
||||||
|
|
||||||
ADD docker-entrypoint.sh /
|
ADD docker-entrypoint.sh /
|
||||||
|
|
||||||
|
302
README.md
302
README.md
@ -1,187 +1,156 @@
|
|||||||
# MailPoet.
|
# MailPoet
|
||||||
|
|
||||||
MailPoet done the right way.
|
MailPoet done the right way.
|
||||||
|
|
||||||
# Install.
|
# Contents
|
||||||
|
|
||||||
- Install system dependencies:
|
- [Setup](#setup)
|
||||||
```
|
- [Frameworks and libraries](#frameworks-and-libraries)
|
||||||
php
|
- [Workflow Commands](#workflow-commands)
|
||||||
nodejs
|
- [Coding and Testing](#coding-and-testing)
|
||||||
wordpress
|
|
||||||
```
|
|
||||||
|
|
||||||
- Clone the repo in `wp-content/plugins`.
|
# Setup
|
||||||
|
|
||||||
- Install composer.
|
## Requirements
|
||||||
```sh
|
- PHP 5.6+
|
||||||
$ curl -sS https://getcomposer.org/installer | php
|
- NodeJS
|
||||||
$ ./composer.phar install
|
- WordPress
|
||||||
```
|
- Docker & Docker Compose
|
||||||
|
|
||||||
- Install dependencies.
|
## Installation
|
||||||
```sh
|
```bash
|
||||||
$ ./do install
|
# go to WP plugins directory
|
||||||
```
|
$ cd path_to_wp_directory/wp-content/plugins
|
||||||
|
# clone this repository
|
||||||
- Update dependencies when needed.
|
$ git clone https://github.com/mailpoet/mailpoet.git
|
||||||
```sh
|
$ cd mailpoet
|
||||||
$ ./do update
|
# create the .env file
|
||||||
```
|
|
||||||
|
|
||||||
- Copy .env.sample to .env.
|
|
||||||
```sh
|
|
||||||
$ cp .env.sample .env
|
$ cp .env.sample .env
|
||||||
```
|
# change the values on .env file
|
||||||
|
# download composer
|
||||||
- Compile assets.
|
$ curl -sS https://getcomposer.org/installer | php
|
||||||
```sh
|
$ chmod +x ./composer.phar
|
||||||
|
# install PHP dependencies
|
||||||
|
$ ./composer.phar install
|
||||||
|
# install all dependencies (PHP and JS)
|
||||||
|
$ ./do install
|
||||||
|
# compile JS and CSS files
|
||||||
$ ./do compile:all
|
$ ./do compile:all
|
||||||
```
|
```
|
||||||
|
|
||||||
# Tests.
|
# Frameworks and libraries
|
||||||
|
|
||||||
- Unit tests (using [verify](https://github.com/Codeception/Verify)):
|
- [Paris ORM](https://github.com/j4mie/paris).
|
||||||
```sh
|
- [Symfony/dependency-injection](https://github.com/symfony/dependency-injection) ([docs for 3.4](https://symfony.com/doc/3.4/components/dependency_injection.html)).
|
||||||
$ ./do test:unit
|
- [Mozart](https://github.com/coenjacobs/mozart) for moving dependencies into MP namespace
|
||||||
|
- [Twig](https://twig.symfony.com/) and [Handlebars](https://handlebarsjs.com/) are used for templates rendering.
|
||||||
|
- [Monolog](https://seldaek.github.io/monolog/) is used for logging.
|
||||||
|
- [Robo](https://robo.li/) is used to write and run workflow commands.
|
||||||
|
- [Codeception](https://codeception.com/) is used to write unit and acceptance tests.
|
||||||
|
- [Docker](https://www.docker.com/), [Docker Compose](https://docs.docker.com/compose/) and [Selenium](https://www.seleniumhq.org/) to run acceptance tests.
|
||||||
|
- [React](https://reactjs.org/) is used to create most of UIs.
|
||||||
|
- [Marionette](https://marionettejs.com/) is used to build the newsletters editor.
|
||||||
|
- [Stylus](http://stylus-lang.com/) is used to write styles.
|
||||||
|
- [Mocha](https://mochajs.org/), [Chai](https://www.chaijs.com/) and [Sinon](https://sinonjs.org/) are used to write Javascript tests.
|
||||||
|
- [ESLint](https://eslint.org/) is used to lint JS files.
|
||||||
|
- [Webpack](https://webpack.js.org/) is used to bundle assets.
|
||||||
|
|
||||||
|
# Workflow Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./do install # install PHP and JS dependencies
|
||||||
|
$ ./do update # update PHP and JS dependencies
|
||||||
|
|
||||||
|
$ ./do compile:css # compiles Stylus files into CSS.
|
||||||
|
$ ./do compile:js # bundles JS files for the browser.
|
||||||
|
$ ./do compile:all # compiles CSS and JS files.
|
||||||
|
|
||||||
|
$ ./do watch:css # watch CSS files for changes and compile them.
|
||||||
|
$ ./do watch:js # watch JS files for changes and compile them.
|
||||||
|
$ ./do watch # watch CSS and JS files for changes and compile them.
|
||||||
|
|
||||||
|
$ ./do test:unit [--file=...] [--debug]
|
||||||
|
# runs the PHP unit tests.
|
||||||
|
# if --file specified then only tests on that file are executed.
|
||||||
|
# if --debug then tests are executed in debugging mode.
|
||||||
|
$ ./do test:integration [--file=...] [--multisite] [--debug]
|
||||||
|
# runs the PHP integration tests.
|
||||||
|
# if --file specified then only tests on that file are executed.
|
||||||
|
# if --multisite then tests are executed in a multisite wordpress setup.
|
||||||
|
# if --debug then tests are executed in debugging mode.
|
||||||
|
$ ./do test:multisite:integration # alias for ./do test:integration --multisite
|
||||||
|
$ ./do test:debug:unit # alias for ./do test:unit --debug
|
||||||
|
$ ./do test:debug:integration # alias for ./do test:integration --debug
|
||||||
|
$ ./do test:failed:unit # run the last failing unit test.
|
||||||
|
$ ./do test:failed:integration # run the last failing integration test.
|
||||||
|
$ ./do test:coverage # run tests and output coverage information.
|
||||||
|
$ ./do test:javascript # run the JS tests.
|
||||||
|
$ ./do test:acceptance [--file=...] [--skip-deps]
|
||||||
|
# run acceptances tests into a docker environment.
|
||||||
|
# if --file given then only tests on that file are executed.
|
||||||
|
# if --skip-deps then it skips installation of composer dependencies.
|
||||||
|
$ ./do test:acceptance:multisite [--file=...] [--skip-deps]
|
||||||
|
# same as test:acceptance but runs into a multisite wordpress setup.
|
||||||
|
$ ./do delete:docker # stop and remove all running docker containers.
|
||||||
|
|
||||||
|
$ ./do qa:lint # PHP code linter.
|
||||||
|
$ ./do qa:lint:javascript # JS code linter.
|
||||||
|
$ ./do qa # PHP and JS linters.
|
||||||
|
|
||||||
|
$ ./do container:dump # Generates DI container cache.
|
||||||
```
|
```
|
||||||
|
|
||||||
- JS tests (using Mocha):
|
# Coding and Testing
|
||||||
```sh
|
|
||||||
$ ./do test:javascript
|
## DI
|
||||||
|
|
||||||
|
We use Symfony/dependency-injection container. Container configuration can be found in `libs/DI/ContainerFactory.php`
|
||||||
|
The container is configured and used with minimum sub-dependencies to keep final package size small.
|
||||||
|
You can check [the docs](https://symfony.com/doc/3.4/components/dependency_injection.html) to learn more about Symfony Container.
|
||||||
|
|
||||||
|
## Mozart
|
||||||
|
|
||||||
|
We use Mozart plugin for composer to prevent plugin libraries conflicts in PHP. Two plugins may be using different versions of a library. Mozart prefix dependencies namespaces and moves them into `libs\Dependencies` directory.
|
||||||
|
Dependencies handled by Mozart are configured in extra configuration file `mozart/composer.json`. Installation and processing is triggered in post scripts of the main `composer.json` file.
|
||||||
|
|
||||||
|
## i18n
|
||||||
|
|
||||||
|
We use functions `__()`, `_n()` and `_x()` with domain `mailpoet` to translate strings.
|
||||||
|
|
||||||
|
**in PHP code**
|
||||||
|
|
||||||
|
```php
|
||||||
|
__('text to translate', 'mailpoet');
|
||||||
|
_n('single text', 'plural text', $number, 'mailpoet');
|
||||||
|
_x('text to translate', 'context for translators', 'mailpoet');
|
||||||
```
|
```
|
||||||
|
|
||||||
- Debug tests:
|
**in Twig views**
|
||||||
```sh
|
|
||||||
$ ./do test:debug
|
|
||||||
```
|
|
||||||
|
|
||||||
- Code linters and quality checkers:
|
|
||||||
```sh
|
|
||||||
$ ./do qa
|
|
||||||
```
|
|
||||||
|
|
||||||
- Javascript linter:
|
|
||||||
```sh
|
|
||||||
$ ./do lint:javascript
|
|
||||||
```
|
|
||||||
|
|
||||||
# CSS
|
|
||||||
- [Stylus](https://learnboost.github.io/stylus/)
|
|
||||||
- [Nib extension](http://tj.github.io/nib/)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
assets/css/src -> place your *.styl files here
|
|
||||||
```
|
|
||||||
|
|
||||||
### Watch for changes and recompile
|
|
||||||
```sh
|
|
||||||
$ ./do watch
|
|
||||||
```
|
|
||||||
|
|
||||||
## Module loading and organization
|
|
||||||
|
|
||||||
Our JS modules are stored in `assets/js/` folder. Modules should follow AMD module definition style:
|
|
||||||
|
|
||||||
```js
|
|
||||||
define('moduleName', ['dependency1', 'dependency2'], function(dependency1, dependency2){
|
|
||||||
// Module code here
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Module exports here
|
|
||||||
};
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Module loader will look for `dependency1` in `node_modules/` dependencies, as well as in `assets/js`. So you can use dependencies, defined in `package.json`, without the need of providing an absolute path to it.
|
|
||||||
Once found, dependencies will be injected into your module via function arguments.
|
|
||||||
|
|
||||||
When it comes to loading modules on a real page, WebPack uses "entry points" to create different bundles. In order for the module to be included in a specific bundle, it must be reachable from that bundle's entry point. [A good example on WebPack's website](http://webpack.github.io/docs/code-splitting.html#split-app-and-vendor-code).
|
|
||||||
|
|
||||||
Once javascript is compiled with `./do compile:javascript`, your module will be placed into a bundle. Including that bundle in a webpage will give provide you access to your module.
|
|
||||||
|
|
||||||
## Handlebars (`views/*.hbs`)
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!-- use the `templates` block -->
|
<%= __('text to translate') %>
|
||||||
<% block templates %>
|
<%= _n('single text', 'plural text', $number) %>
|
||||||
<!-- include a .hbs template -->
|
<%= _x('text to translate', 'context for translators') %>
|
||||||
<%= partial('my_template_1', 'form/templates/toolbar/fields.hbs') %>
|
```
|
||||||
|
|
||||||
<!-- include a .hbs template and register it as a partial -->
|
The domain `mailpoet` will be added automatically by the Twig functions.
|
||||||
<%= partial('my_template_2', 'form/templates/blocks.hbs', '_my_partial') %>
|
|
||||||
|
|
||||||
<!-- custom partial using partial defined above -->
|
**in Javascript code**
|
||||||
<script id="my_template_3" type="text/x-handlebars-template">
|
|
||||||
{{> _my_partial }}
|
First add the string to the translations block in the Twig view:
|
||||||
</script>
|
|
||||||
|
```html
|
||||||
|
<% block translations %>
|
||||||
|
<%= localize({
|
||||||
|
'key': __('string to translate'),
|
||||||
|
...
|
||||||
|
}) %>
|
||||||
<% endblock %>
|
<% endblock %>
|
||||||
```
|
```
|
||||||
|
|
||||||
# i18n
|
Then use `MailPoet.I18n.t('key')` to get the translated string on your Javascript code.
|
||||||
- Use the regular WordPress functions in PHP and Twig:
|
|
||||||
|
|
||||||
```php
|
## Acceptance testing
|
||||||
__()
|
|
||||||
_n()
|
|
||||||
_x()
|
|
||||||
```
|
|
||||||
|
|
||||||
```html
|
|
||||||
<p>
|
|
||||||
<%= __('Click %shere%s!') | format('<a href="#">', '</a>') | raw %>
|
|
||||||
</p>
|
|
||||||
```
|
|
||||||
|
|
||||||
```html
|
|
||||||
<p>
|
|
||||||
<%= _n('deleted %d message', 'deleted %d messages', count) | format(count) %>
|
|
||||||
<!-- count === 1 -> "deleted 1 message" -->
|
|
||||||
<!-- count > 1 -> "deleted $count messages" -->
|
|
||||||
</p>
|
|
||||||
```
|
|
||||||
|
|
||||||
- Handlebars.
|
|
||||||
|
|
||||||
You can use Twig i18n functions in Handlebars, just load your template from a Twig view.
|
|
||||||
|
|
||||||
# Build
|
|
||||||
|
|
||||||
To build a plugin , run `./build.sh`.
|
|
||||||
|
|
||||||
Some build process steps are described below (their dependencies etc.).
|
|
||||||
|
|
||||||
## packtranslations step
|
|
||||||
|
|
||||||
This step imports translations from Transifex and generates MO files. It requires:
|
|
||||||
* `tx` client: https://docs.transifex.com/client/installing-the-client
|
|
||||||
* `msgfmt` command (from Gettext package)
|
|
||||||
Finally , a `WP_TRANSIFEX_API_TOKEN` environment variable should be initialized with a valid key.
|
|
||||||
|
|
||||||
# Publish
|
|
||||||
|
|
||||||
The `publish` command currently does the following:
|
|
||||||
* Pushes translations POT file to Transifex;
|
|
||||||
* Publishes the release in SVN.
|
|
||||||
|
|
||||||
Before you run it, you need to:
|
|
||||||
1. Ensure there is an up-to-date local copy of MailPoet SVN repository in `.mp_svn` directory by running `./do svn:checkout`.
|
|
||||||
2. Have all your features merged in Git `master`, your `mailpoet.php` and `readme.txt` tagged with a new version.
|
|
||||||
3. Run `./build.sh` to produce a `mailpoet.zip` distributable archive.
|
|
||||||
|
|
||||||
Everything's ready? Then run `./do publish`.
|
|
||||||
If the job goes fine, you'll get a message like this:
|
|
||||||
```
|
|
||||||
Go to '.mp_svn' and run 'svn ci -m "Release 3.0.0-beta.9"' to publish the
|
|
||||||
release
|
|
||||||
|
|
||||||
Run 'svn copy ...' to tag the release
|
|
||||||
```
|
|
||||||
It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process.
|
|
||||||
|
|
||||||
If you're confident, execute `./do publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables.
|
|
||||||
|
|
||||||
# Acceptance testing
|
|
||||||
|
|
||||||
We are using Gravity Flow plugin's setup as an example for our acceptance test suite: https://www.stevenhenty.com/learn-acceptance-testing-deeply/
|
We are using Gravity Flow plugin's setup as an example for our acceptance test suite: https://www.stevenhenty.com/learn-acceptance-testing-deeply/
|
||||||
|
|
||||||
@ -189,17 +158,6 @@ From the article above:
|
|||||||
|
|
||||||
_Windows users only: enable hard drive sharing in the Docker settings._
|
_Windows users only: enable hard drive sharing in the Docker settings._
|
||||||
|
|
||||||
The browser runs in a docker container. You can use a VNC client to watch the test run, follow instructions in official
|
The browser runs in a docker container. You can use a VNC client to watch the test run, follow instructions in official
|
||||||
repo: https://github.com/SeleniumHQ/docker-selenium
|
repo: https://github.com/SeleniumHQ/docker-selenium
|
||||||
If you’re on a Mac, you can open vnc://localhost:5900 in Safari to watch the tests running in Chrome. If you’re on Windows, you’ll need a VNC client. Password: secret.
|
If you’re on a Mac, you can open vnc://localhost:5900 in Safari to watch the tests running in Chrome. If you’re on Windows, you’ll need a VNC client. Password: secret.
|
||||||
|
|
||||||
|
|
||||||
To run tests:
|
|
||||||
```sh
|
|
||||||
$ ./do test:acceptance
|
|
||||||
```
|
|
||||||
|
|
||||||
You can skip installation of composer dependencies using --skip-deps parameter.
|
|
||||||
```sh
|
|
||||||
$ ./do test:acceptance --skip-deps
|
|
||||||
```
|
|
||||||
|
77
RoboFile.php
77
RoboFile.php
@ -156,7 +156,27 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
function testUnit(array $opts=['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false]) {
|
function testUnit(array $opts=['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false]) {
|
||||||
$this->loadEnv();
|
$this->loadEnv();
|
||||||
|
|
||||||
$command = 'vendor/bin/codecept run unit -c codeception.unit.yml';
|
$command = 'vendor/bin/codecept run unit';
|
||||||
|
|
||||||
|
if($opts['file']) {
|
||||||
|
$command .= ' -f ' . $opts['file'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if($opts['xml']) {
|
||||||
|
$command .= ' --xml';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($opts['debug']) {
|
||||||
|
$command .= ' --debug';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_exec($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testIntegration(array $opts=['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false]) {
|
||||||
|
$this->loadEnv();
|
||||||
|
|
||||||
|
$command = 'vendor/bin/codecept run integration';
|
||||||
|
|
||||||
if($opts['multisite']) {
|
if($opts['multisite']) {
|
||||||
$command = 'MULTISITE=true ' . $command;
|
$command = 'MULTISITE=true ' . $command;
|
||||||
@ -177,14 +197,14 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
return $this->_exec($command);
|
return $this->_exec($command);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testMultisiteUnit($opts=['file' => null, 'xml' => false, 'multisite' => true]) {
|
function testMultisiteIntegration($opts=['file' => null, 'xml' => false, 'multisite' => true]) {
|
||||||
return $this->testUnit($opts);
|
return $this->testIntegration($opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testCoverage($opts=['file' => null, 'xml' => false]) {
|
function testCoverage($opts=['file' => null, 'xml' => false]) {
|
||||||
$this->loadEnv();
|
$this->loadEnv();
|
||||||
$command = join(' ', array(
|
$command = join(' ', array(
|
||||||
'vendor/bin/codecept run unit -c codeception.unit.yml ',
|
'vendor/bin/codecept run -s acceptance',
|
||||||
(($opts['file']) ? $opts['file'] : ''),
|
(($opts['file']) ? $opts['file'] : ''),
|
||||||
'--coverage',
|
'--coverage',
|
||||||
($opts['xml']) ? '--coverage-xml' : '--coverage-html'
|
($opts['xml']) ? '--coverage-xml' : '--coverage-html'
|
||||||
@ -219,16 +239,12 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
return $this->_exec('vendor/bin/security-checker security:check --format=simple');
|
return $this->_exec('vendor/bin/security-checker security:check --format=simple');
|
||||||
}
|
}
|
||||||
|
|
||||||
function testDebug($opts=['file' => null, 'xml' => false]) {
|
function testDebugUnit($opts=['file' => null, 'xml' => false, 'debug' => true]) {
|
||||||
$this->loadEnv();
|
return $this->testUnit($opts);
|
||||||
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
|
}
|
||||||
|
|
||||||
$command = 'vendor/bin/codecept run unit -c codeception.unit.yml --debug -f '.(($opts['file']) ? $opts['file'] : '');
|
function testDebugIntegration($opts=['file' => null, 'xml' => false, 'debug' => true]) {
|
||||||
|
return $this->testIntegration($opts);
|
||||||
if($opts['xml']) {
|
|
||||||
$command .= ' --xml';
|
|
||||||
}
|
|
||||||
return $this->_exec($command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testAcceptance($opts=['file' => null, 'skip-deps' => false]) {
|
function testAcceptance($opts=['file' => null, 'skip-deps' => false]) {
|
||||||
@ -254,10 +270,31 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
return $this->_exec('docker-compose down -v --remove-orphans --rmi all');
|
return $this->_exec('docker-compose down -v --remove-orphans --rmi all');
|
||||||
}
|
}
|
||||||
|
|
||||||
function testFailed() {
|
function testFailedUnit() {
|
||||||
$this->loadEnv();
|
$this->loadEnv();
|
||||||
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
|
$this->_exec('vendor/bin/codecept build');
|
||||||
return $this->_exec('vendor/bin/codecept run -c codeception.unit.yml -g failed');
|
return $this->_exec('vendor/bin/codecept run unit -g failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFailedIntegration() {
|
||||||
|
$this->loadEnv();
|
||||||
|
$this->_exec('vendor/bin/codecept build');
|
||||||
|
return $this->_exec('vendor/bin/codecept run integration -g failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function containerDump() {
|
||||||
|
$this->say('Deleting DI Container');
|
||||||
|
$this->_exec('rm -f ./lib/DI/CachedContainer.php');
|
||||||
|
$this->say('Generating DI container cache');
|
||||||
|
$this->loadEnv();
|
||||||
|
define('ABSPATH', getenv('WP_ROOT') . '/');
|
||||||
|
if (!file_exists(ABSPATH . 'wp-config.php')) {
|
||||||
|
$this->yell('WP_ROOT env variable does not contain valid path to wordpress root.', 40, 'red');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
$container_factory = new \MailPoet\DI\ContainerFactory();
|
||||||
|
$container_factory->dumpContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
function qa() {
|
function qa() {
|
||||||
@ -288,7 +325,7 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
->taskExec(
|
->taskExec(
|
||||||
'./vendor/bin/phpcs '.
|
'./vendor/bin/phpcs '.
|
||||||
'--standard=./tasks/code_sniffer/MailPoet '.
|
'--standard=./tasks/code_sniffer/MailPoet '.
|
||||||
'--runtime-set testVersion 5.5-7.2 '.
|
'--runtime-set testVersion 5.6-7.2 '.
|
||||||
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,./lib/Dependencies/*,'.
|
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,./lib/Dependencies/*,'.
|
||||||
'./lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '.
|
'./lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '.
|
||||||
'lib/ '.
|
'lib/ '.
|
||||||
@ -297,9 +334,9 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
->taskExec(
|
->taskExec(
|
||||||
'./vendor/bin/phpcs '.
|
'./vendor/bin/phpcs '.
|
||||||
'--standard=./tasks/code_sniffer/MailPoet '.
|
'--standard=./tasks/code_sniffer/MailPoet '.
|
||||||
'--runtime-set testVersion 5.5-7.2 '.
|
'--runtime-set testVersion 5.6-7.2 '.
|
||||||
'--ignore=./tests/unit/_bootstrap.php '.
|
'--ignore=./tests/unit/_bootstrap.php,./tests/unit/_fixtures.php,./tests/integration/_bootstrap.php,./tests/integration/_fixtures.php '.
|
||||||
'tests/unit/ '.
|
'tests/unit tests/integration tests/acceptance tests/DataFactories '.
|
||||||
$severityFlag
|
$severityFlag
|
||||||
)
|
)
|
||||||
->run();
|
->run();
|
||||||
|
@ -16,3 +16,18 @@ Style for Members plugin
|
|||||||
|
|
||||||
#wpbody
|
#wpbody
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
|
||||||
|
/* menu icon */
|
||||||
|
#adminmenu #toplevel_page_mailpoet-newsletters .wp-menu-image
|
||||||
|
background-size: 18px 18px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
#adminmenu #toplevel_page_mailpoet-newsletters.wp-not-current-submenu .wp-menu-image
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABcFJREFUeNrsWHtsU1UYP7e37/W9MDYYInSosI6XEBhjgxEEDBI0GNTEGFFgEkR5iEGFsChBghJgSwBh4RElGhN1iQy2MF5jjMfYg40NeQxhjiEra2nXrrfry98pt1hqW1hj+YuTfOm95/R8/Z1zvu/3/U4ZEqV9uX7jZH3akAKXy5UR6NPpEjdtWJe/vurUSVO0uROyssmq1fnTTabOPXhN4bs5oVC476/Wmx98tnJZ2HkCEr2tgRmCO8xm09JPVq2emT0pN+pEj8czjmGYrXhMDuqWwmbBZkea9yhAOhgT3OHz+VisMlcgEPSNNCkrexIBaAN257nQ+WhimDZWQGEbVp7ldDoHRxoH4CSpVJoJ8EwkF/8rIBybfsmyFZkTcyaHHec4BwWbFYvvmAB5vV5WpVKPk0pl/f8TzBNzyOKPlmeYzeYhTwwQ316y220ZoZ1yeUKiVqcb6/V62CcK6J7ZrH1/4SIDDeDgZrPZRuPj1Vj9xgzI7XaT5JR+L2o02pR/uSeHvDc/b7jl3r0+fFcPzBpPQF0UC59pxOf1vtzVZR3/gCMSE5P7pw4YDSINdN2GVccTUCXsz8CL1WpRz33r7VE0kP3vFssoxM5UP1ifj0gkkssqtXo/kiBugDpgJ2Gd/vPo6SGD9PpMpVKZlp4xnMx5480RAJlEx2QyGamtOd+4vXBrk1qtjg8grFSGdC8B6V2lO0B3wuN2jzcajQaFQiF6/oWhIylInhxvddz5+/CF+lqhWCyJDyCVSiXbt6foZE31uYt0B/xZ1dWlmDlr9rPTZswcZ+rszAo6ritqtaZSLBYr6XtcAEkkUvbc2aru1tabx0Ui8V3ahxJChqVnTIMtxXOqv4JKZc7a2vOnyw4dsCtVKnHcghor9QGUBjFRhh24GDg2p5ObBHuFPvurp1jceuP69WOX/7hEpPxOxo2HECuSyooTd+sQsYilQPrI6QbyoOlxNau1Wn+6+3qRYTEBksvlpLmpkVxqvngEP9waOg6Q9gv1dVUnjh6xxIOpQ+sRi0zzz9FoNKeRPfXBAUufcZw3rrdcrairqY4kNZgwfh8bkDso5UlCgqJbKBI56Hv12TOWlmtXypFJrgAoxBdpbKivaaivawxmi4dWxLJSHLs9opaKejwJCfuVShXrcvUkyWRyY8WJo7+2t7X5s6v63BnCObniBXmLdYPT0qY7OQ5lTXfl4IHinRizB4m1FnDXUfgYLWBZH+rcscNlpRHLCRMN0OzXXhf0SUqaAH08EGzcCg6qgkNP8HcoQ+dOmTrMbrdDJcpq62qrrWeqTj0YH/DMQPiZMxDj07E75HZ7+5GS34tbyNP2tMWpMajQ9HMQLPV+WgobQHyWJwVgaLpB6/V40vkEa2eKD5bT28EO2JT7qS5f9/nK5b+ZOu8aeQka6e5luXWrjbPbbOHogqSmDpBD5ioiZDLtk4HD+mzYtGWuw8GtCAhAZu/+n9fiIT+EbTmBgK3kBZkvDJl61FrN2aId205VVVbcMBo7zJAhJEGhIH2TkhPHjs9MW/Thx9m4CNBbiSiEHH18n97r844BjZBAUfYj/f6nXz4Fz3wdg1jz7wR4quCHfbsLSksOtE2dNkM/b37eEmPHnTzwDoPrdu9j6JvNhfq+ySk/AuVQAFP01gHdURTU02KJZBfU4rucw5ETvOLHLqosS9n9mn/mxs2FVMOsBvcvwKrUvJSIVAAZgBA+jgqkwGDuMMceXOc4+OoymTp343ntQ0vZ8O0WOerODBzHFP5vFCaMAwlEl0EkEvWjei0KGKfb7Wp3dDua6I+GCQkKsgPfO9btsB9auXSJ7ZG1LFJ7Z958FXT0FxzHLQQoTRgwVmilbeVlpWuKdm5z98Z3TPdv3CScKKTlGcNHeHEpzAlRDU7c79eXHirJ371ru7e3vtlYCa3hQh2VqjdHjhpjgLh/8E8HVMFxVPOv9hZ9Z43F7z8CDAAMFFaTyVAO5gAAAABJRU5ErkJggg==');
|
||||||
|
|
||||||
|
#adminmenu #toplevel_page_mailpoet-newsletters.wp-has-current-submenu .wp-menu-image
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAa9JREFUeNrsWO2NgkAUnGeuAErgOuAq0A7YDs4OtIPLVWCsQK8CtQJKkA6kA+lg7s+a4Mp+HLtgLnESfoDsY3gz77FPwAKSGckD71GRzBAAkorkxVi/wVCQXLIfu4C1GcmrZX3hWjtz/JZbrquALCkAtnuyoYTgCKg895RDlZkNXFe65AognJyQS7bBZGIIuR5cPotQmVqu6AyRzD1Za8YmVHsImFnbjk3oxzj/dMjV9LxAckJ747zoyKY85NMTEpEWwNEi28pDfjRTm2++0lnqfqNqEWkmISQiRwCt8c37SiFXTNmbcixTyBVDyJWBo/badIREpHaU9OlZnbovS62I7MciZFZJ2+OT1uOdNiDuHd5cXgAw7+wct2ZPIvnRMXRjZkdEapLfOg4AnIa2gxde+L8gmesJtSK5nvjZiuRZHwsAEJIXYyisdbetLX3kVuKNZxQq9GGbTuYAFsa1d3A4ziSVQWTR83/AX7CEDhyDg57lN5FxriRzJAwWg+phgtHm3iTIWCguJHc3M8dURhHgkwd/TVGya5unntnDqh5CWUzMWSQnc5O2j9m+AsDvAIQNSWYrVntNAAAAAElFTkSuQmCC');
|
||||||
|
|
||||||
|
#adminmenu #toplevel_page_mailpoet-newsletters a:hover .wp-menu-image
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAa9JREFUeNrsWO2NgkAUnGeuAErgOuAq0A7YDs4OtIPLVWCsQK8CtQJKkA6kA+lg7s+a4Mp+HLtgLnESfoDsY3gz77FPwAKSGckD71GRzBAAkorkxVi/wVCQXLIfu4C1GcmrZX3hWjtz/JZbrquALCkAtnuyoYTgCKg895RDlZkNXFe65AognJyQS7bBZGIIuR5cPotQmVqu6AyRzD1Za8YmVHsImFnbjk3oxzj/dMjV9LxAckJ747zoyKY85NMTEpEWwNEi28pDfjRTm2++0lnqfqNqEWkmISQiRwCt8c37SiFXTNmbcixTyBVDyJWBo/badIREpHaU9OlZnbovS62I7MciZFZJ2+OT1uOdNiDuHd5cXgAw7+wct2ZPIvnRMXRjZkdEapLfOg4AnIa2gxde+L8gmesJtSK5nvjZiuRZHwsAEJIXYyisdbetLX3kVuKNZxQq9GGbTuYAFsa1d3A4ziSVQWTR83/AX7CEDhyDg57lN5FxriRzJAwWg+phgtHm3iTIWCguJHc3M8dURhHgkwd/TVGya5unntnDqh5CWUzMWSQnc5O2j9m+AsDvAIQNSWYrVntNAAAAAElFTkSuQmCC');
|
||||||
|
@ -36,3 +36,5 @@
|
|||||||
@require 'welcome_wizard'
|
@require 'welcome_wizard'
|
||||||
@require 'intro'
|
@require 'intro'
|
||||||
@require 'in_app_announcements'
|
@require 'in_app_announcements'
|
||||||
|
@require 'newsletter_congratulate.styl'
|
||||||
|
@require 'discounts'
|
||||||
|
17
assets/css/src/discounts.styl
Normal file
17
assets/css/src/discounts.styl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.mailpoet-discount-container
|
||||||
|
margin: 15px
|
||||||
|
padding: 20px
|
||||||
|
background: white
|
||||||
|
border: 1px solid #FF5301
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
.mailpoet-discount-container h1
|
||||||
|
margin: 0
|
||||||
|
line-height: 1.2em
|
||||||
|
font-size: 2.8em
|
||||||
|
font-weight: 400
|
||||||
|
|
||||||
|
.mailpoet-discount-container p
|
||||||
|
line-height: 1.2em
|
||||||
|
font-size: 1.2em
|
||||||
|
|
@ -40,9 +40,14 @@
|
|||||||
top: -3px
|
top: -3px
|
||||||
left: 8px
|
left: 8px
|
||||||
|
|
||||||
.mailpoet_in_app_announcement_background_videos
|
.mailpoet_in_app_announcement_background_videos, .mailpoet_drag_and_drop_tutorial
|
||||||
text-align: center
|
text-align: center
|
||||||
h2
|
h2
|
||||||
font-size: 28px
|
font-size: 28px
|
||||||
video
|
video
|
||||||
margin-top: 20px
|
margin-top: 20px
|
||||||
|
|
||||||
|
.new_subscriber_notification_announcement
|
||||||
|
h2
|
||||||
|
font-size: 28px
|
||||||
|
text-align: center
|
||||||
|
@ -168,14 +168,18 @@ body.mailpoet_modal_opened
|
|||||||
padding-bottom: 52px
|
padding-bottom: 52px
|
||||||
|
|
||||||
#mailpoet_loading
|
#mailpoet_loading
|
||||||
width: 150px
|
|
||||||
height: 32px
|
|
||||||
position: relative
|
position: relative
|
||||||
left: 50%
|
left: 50%
|
||||||
top: 50%
|
top: 50%
|
||||||
margin-left: -75px
|
margin-left: -75px
|
||||||
margin-top: -16px
|
margin-top: -16px
|
||||||
|
|
||||||
|
.mailpoet_loading
|
||||||
|
height: 32px
|
||||||
|
width: 150px
|
||||||
|
display flex
|
||||||
|
flex-direction: row
|
||||||
|
|
||||||
.mailpoet_modal_loading
|
.mailpoet_modal_loading
|
||||||
animation-direction(linear)
|
animation-direction(linear)
|
||||||
animation-duration(1.9500000000000002s)
|
animation-duration(1.9500000000000002s)
|
||||||
@ -183,18 +187,17 @@ body.mailpoet_modal_opened
|
|||||||
animation-name(bounce_mailpoet_modal_loading)
|
animation-name(bounce_mailpoet_modal_loading)
|
||||||
border-radius(21px)
|
border-radius(21px)
|
||||||
background-color: #E01D4E
|
background-color: #E01D4E
|
||||||
float: left
|
|
||||||
height: 32px
|
height: 32px
|
||||||
margin-left: 17px
|
margin-left: 17px
|
||||||
width: 32px
|
width: 32px
|
||||||
|
|
||||||
#mailpoet_modal_loading_1
|
#mailpoet_modal_loading_1, .mailpoet_modal_loading_1
|
||||||
animation-delay(0.39s)
|
animation-delay(0.39s)
|
||||||
|
|
||||||
#mailpoet_modal_loading_2
|
#mailpoet_modal_loading_2, .mailpoet_modal_loading_2
|
||||||
animation-delay(0.9099999999999999s)
|
animation-delay(0.9099999999999999s)
|
||||||
|
|
||||||
#mailpoet_modal_loading_3
|
#mailpoet_modal_loading_3, .mailpoet_modal_loading_3
|
||||||
animation-delay(1.1700000000000002s)
|
animation-delay(1.1700000000000002s)
|
||||||
|
|
||||||
@keyframes bounce_mailpoet_modal_loading
|
@keyframes bounce_mailpoet_modal_loading
|
||||||
|
23
assets/css/src/newsletter_congratulate.styl
Normal file
23
assets/css/src/newsletter_congratulate.styl
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.newsletter_congratulate_page
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
|
.mailpoet_newsletter_loading
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.mailpoet_loading
|
||||||
|
margin: 100px auto 0 auto;
|
||||||
|
|
||||||
|
.mailpoet_newsletter_loading_header
|
||||||
|
margin: 30px;
|
||||||
|
|
||||||
|
.mailpoet_congratulate_success
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
h1
|
||||||
|
text-align center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
img, .button
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
display: block;
|
@ -236,3 +236,28 @@ select.mailpoet_font-size
|
|||||||
margin-left: 10px
|
margin-left: 10px
|
||||||
input.mailpoet_option_offset_left_small
|
input.mailpoet_option_offset_left_small
|
||||||
margin-left: 10px !important
|
margin-left: 10px !important
|
||||||
|
|
||||||
|
.mailpoet_form_field span.select2-container
|
||||||
|
width: 103px !important
|
||||||
|
|
||||||
|
span.select2-container--open > span.select2-dropdown
|
||||||
|
width: 150px !important
|
||||||
|
|
||||||
|
span.select2-container--open > span.select2-dropdown li.select2-results__option
|
||||||
|
font-size: 13px
|
||||||
|
margin: 0px !important
|
||||||
|
|
||||||
|
& .select2-results__group
|
||||||
|
font-weight: normal
|
||||||
|
color: #bfbfbf
|
||||||
|
|
||||||
|
& .select2-results__option
|
||||||
|
padding-left: 15px
|
||||||
|
font-size: 13px
|
||||||
|
|
||||||
|
&[aria-selected=true]
|
||||||
|
background-color: #eee
|
||||||
|
color: #444
|
||||||
|
|
||||||
|
.mailpoet-fonts-notice
|
||||||
|
color: #999
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#mailpoet_editor_bottom
|
#mailpoet_editor_bottom
|
||||||
margin: 10px 0 70px
|
margin: 10px 0 120px
|
||||||
|
|
||||||
.mailpoet_save_wrapper
|
.mailpoet_save_wrapper
|
||||||
float: right
|
float: right
|
||||||
@ -7,6 +7,9 @@
|
|||||||
margin-right: 20px
|
margin-right: 20px
|
||||||
margin-bottom: 10px
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
.mailpoet_save_next
|
||||||
|
margin-left: 5px
|
||||||
|
|
||||||
.mailpoet_save_options
|
.mailpoet_save_options
|
||||||
border-radius(3px)
|
border-radius(3px)
|
||||||
|
|
||||||
@ -43,6 +46,8 @@
|
|||||||
|
|
||||||
.mailpoet_save_show_options_icon
|
.mailpoet_save_show_options_icon
|
||||||
vertical-align: middle
|
vertical-align: middle
|
||||||
|
height: 14px;
|
||||||
|
margin-top: -6px;
|
||||||
|
|
||||||
.mailpoet_save_as_template_container,
|
.mailpoet_save_as_template_container,
|
||||||
.mailpoet_export_template_container
|
.mailpoet_export_template_container
|
||||||
@ -61,16 +66,25 @@
|
|||||||
.mailpoet_save_as_template_title,
|
.mailpoet_save_as_template_title,
|
||||||
.mailpoet_export_template_title
|
.mailpoet_export_template_title
|
||||||
font-size: 1.1em
|
font-size: 1.1em
|
||||||
|
|
||||||
|
.mailpoet_save_next, .mailpoet_save_button_group
|
||||||
|
float: right
|
||||||
|
|
||||||
|
.mailpoet_editor_messages
|
||||||
|
position: absolute
|
||||||
|
right: 0
|
||||||
|
|
||||||
.mailpoet_editor_last_saved
|
.mailpoet_editor_last_saved
|
||||||
color: $primary-inactive-color
|
color: $primary-inactive-color
|
||||||
font-size: 0.9em
|
font-size: 0.9em
|
||||||
position: absolute
|
|
||||||
right: 0
|
|
||||||
margin-top: 10px
|
margin-top: 10px
|
||||||
|
text-align: right
|
||||||
|
|
||||||
.mailpoet_save_error
|
.mailpoet_save_error
|
||||||
|
margin-top: 10px
|
||||||
|
width: $sidebar-width - 20px
|
||||||
color: $error-text-color
|
color: $error-text-color
|
||||||
|
text-align: right
|
||||||
|
|
||||||
.mailpoet_save_dropdown_down
|
.mailpoet_save_dropdown_down
|
||||||
.mailpoet_save_options,
|
.mailpoet_save_options,
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
& > .mailpoet_block
|
& > .mailpoet_block
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|
||||||
|
.mailpoet_container_block
|
||||||
|
margin-bottom: 0
|
||||||
|
|
||||||
|
|
||||||
.mailpoet_automated_latest_content_display_options
|
.mailpoet_automated_latest_content_display_options
|
||||||
animation-slide-open-downwards()
|
animation-slide-open-downwards()
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ $column-margin = 20px
|
|||||||
$one-column-width = $newsletter-width - (2 * $column-margin)
|
$one-column-width = $newsletter-width - (2 * $column-margin)
|
||||||
$two-column-width = ($newsletter-width / 2) - (2 * $column-margin)
|
$two-column-width = ($newsletter-width / 2) - (2 * $column-margin)
|
||||||
$three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
|
$three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
|
||||||
|
$two-column-wider-column-width = (($newsletter-width / 3) - $column-margin) * 2
|
||||||
|
|
||||||
.mailpoet_container
|
.mailpoet_container
|
||||||
width: 100%
|
width: 100%
|
||||||
@ -27,12 +28,6 @@ $three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
|
|||||||
.mailpoet_container_horizontal > *
|
.mailpoet_container_horizontal > *
|
||||||
vertical-align: top
|
vertical-align: top
|
||||||
|
|
||||||
/**
|
|
||||||
* Enforce column widths:
|
|
||||||
* 1 column: 20px + 560px + 20px
|
|
||||||
* 2 columns: 20px + 260px + 20px + 260px + 20px
|
|
||||||
* 3 columns: 20px + 160px + 20px + 20px + 160px + 20px + 20px + 160px + 20px
|
|
||||||
*/
|
|
||||||
|
|
||||||
#mailpoet_editor_content
|
#mailpoet_editor_content
|
||||||
.mailpoet_container
|
.mailpoet_container
|
||||||
@ -73,6 +68,14 @@ $three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
|
|||||||
//padding-right: 20px
|
//padding-right: 20px
|
||||||
width: $column-margin + $three-column-width + $column-margin
|
width: $column-margin + $three-column-width + $column-margin
|
||||||
|
|
||||||
|
& > .mailpoet_container_block > .mailpoet_container > .mailpoet_container_block > .mailpoet_container_horizontal.mailpoet_irregular_width_contents_container.column_layout_1_2 > .mailpoet_container_block:first-child,
|
||||||
|
& > .mailpoet_container_block > .mailpoet_container > .mailpoet_container_block > .mailpoet_container_horizontal.mailpoet_irregular_width_contents_container.column_layout_2_1 > .mailpoet_container_block:nth-child(2)
|
||||||
|
width: $column-margin + $three-column-width + $column-margin
|
||||||
|
|
||||||
|
& > .mailpoet_container_block > .mailpoet_container > .mailpoet_container_block > .mailpoet_container_horizontal.mailpoet_irregular_width_contents_container.column_layout_2_1 > .mailpoet_container_block:first-child,
|
||||||
|
& > .mailpoet_container_block > .mailpoet_container > .mailpoet_container_block > .mailpoet_container_horizontal.mailpoet_irregular_width_contents_container.column_layout_1_2 > .mailpoet_container_block:nth-child(2)
|
||||||
|
width: $column-margin + $two-column-wider-column-width + $column-margin
|
||||||
|
|
||||||
.mailpoet_container_empty
|
.mailpoet_container_empty
|
||||||
text-align: center
|
text-align: center
|
||||||
background-color: #f2f2f2
|
background-color: #f2f2f2
|
||||||
|
BIN
assets/img/in_app_announcements/new-subscriber-notification.png
Normal file
BIN
assets/img/in_app_announcements/new-subscriber-notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 751 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
BIN
assets/img/newsletter/congrat-illu-success.png
Normal file
BIN
assets/img/newsletter/congrat-illu-success.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 229 KiB |
Binary file not shown.
After Width: | Height: | Size: 436 KiB |
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const KeyValueTable = props => (
|
const KeyValueTable = props => (
|
||||||
@ -13,13 +14,13 @@ const KeyValueTable = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
KeyValueTable.propTypes = {
|
KeyValueTable.propTypes = {
|
||||||
max_width: React.PropTypes.string,
|
max_width: PropTypes.string,
|
||||||
rows: React.PropTypes.arrayOf(React.PropTypes.shape({
|
rows: PropTypes.arrayOf(PropTypes.shape({
|
||||||
key: React.PropTypes.string.isRequired,
|
key: PropTypes.string.isRequired,
|
||||||
value: React.PropTypes.oneOfType([
|
value: PropTypes.oneOfType([
|
||||||
React.PropTypes.string,
|
PropTypes.string,
|
||||||
React.PropTypes.number,
|
PropTypes.number,
|
||||||
React.PropTypes.element,
|
PropTypes.element,
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
})).isRequired,
|
})).isRequired,
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
|
||||||
@ -10,10 +11,10 @@ const PrintBoolean = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
PrintBoolean.propTypes = {
|
PrintBoolean.propTypes = {
|
||||||
truthy: React.PropTypes.string,
|
truthy: PropTypes.string,
|
||||||
falsy: React.PropTypes.string,
|
falsy: PropTypes.string,
|
||||||
unknown: React.PropTypes.string,
|
unknown: PropTypes.string,
|
||||||
children: React.PropTypes.bool,
|
children: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintBoolean.defaultProps = {
|
PrintBoolean.defaultProps = {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const SteppedProgressBar = (props) => {
|
const SteppedProgressBar = (props) => {
|
||||||
@ -20,8 +21,8 @@ const SteppedProgressBar = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SteppedProgressBar.propTypes = {
|
SteppedProgressBar.propTypes = {
|
||||||
steps_count: React.PropTypes.number.isRequired,
|
steps_count: PropTypes.number.isRequired,
|
||||||
step: React.PropTypes.number.isRequired,
|
step: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = SteppedProgressBar;
|
module.exports = SteppedProgressBar;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const FormFieldCheckbox = React.createClass({
|
class FormFieldCheckbox extends React.Component {
|
||||||
onValueChange: function onValueChange(e) {
|
onValueChange = (e) => {
|
||||||
e.target.value = this.checkbox.checked ? '1' : '0';
|
e.target.value = this.checkbox.checked ? '1' : '0';
|
||||||
return this.props.onValueChange(e);
|
return this.props.onValueChange(e);
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
if (this.props.field.values === undefined) {
|
if (this.props.field.values === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -37,7 +39,16 @@ const FormFieldCheckbox = React.createClass({
|
|||||||
{ options }
|
{ options }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
FormFieldCheckbox.propTypes = {
|
||||||
|
onValueChange: PropTypes.func.isRequired,
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
values: PropTypes.object.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
export default FormFieldCheckbox;
|
export default FormFieldCheckbox;
|
||||||
|
@ -36,7 +36,10 @@ FormFieldDateYear.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
placeholder: PropTypes.string.isRequired,
|
placeholder: PropTypes.string.isRequired,
|
||||||
onValueChange: PropTypes.func.isRequired,
|
onValueChange: PropTypes.func.isRequired,
|
||||||
year: PropTypes.string.isRequired,
|
year: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FormFieldDateMonth(props) {
|
function FormFieldDateMonth(props) {
|
||||||
@ -71,7 +74,10 @@ FormFieldDateMonth.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
placeholder: PropTypes.string.isRequired,
|
placeholder: PropTypes.string.isRequired,
|
||||||
onValueChange: PropTypes.func.isRequired,
|
onValueChange: PropTypes.func.isRequired,
|
||||||
month: PropTypes.string.isRequired,
|
month: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]).isRequired,
|
||||||
monthNames: PropTypes.arrayOf(PropTypes.string).isRequired,
|
monthNames: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,7 +114,10 @@ FormFieldDateDay.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
placeholder: PropTypes.string.isRequired,
|
placeholder: PropTypes.string.isRequired,
|
||||||
onValueChange: PropTypes.func.isRequired,
|
onValueChange: PropTypes.func.isRequired,
|
||||||
day: PropTypes.string.isRequired,
|
day: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FormFieldDate extends React.Component {
|
class FormFieldDate extends React.Component {
|
||||||
|
@ -7,9 +7,10 @@ import FormFieldCheckbox from 'form/fields/checkbox.jsx';
|
|||||||
import FormFieldSelection from 'form/fields/selection.jsx';
|
import FormFieldSelection from 'form/fields/selection.jsx';
|
||||||
import FormFieldDate from 'form/fields/date.jsx';
|
import FormFieldDate from 'form/fields/date.jsx';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const FormField = React.createClass({
|
class FormField extends React.Component {
|
||||||
renderField: function renderField(data, inline = false) {
|
renderField = (data, inline = false) => {
|
||||||
let description = false;
|
let description = false;
|
||||||
if (data.field.description) {
|
if (data.field.description) {
|
||||||
description = (
|
description = (
|
||||||
@ -76,8 +77,9 @@ const FormField = React.createClass({
|
|||||||
{ description }
|
{ description }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
let field = false;
|
let field = false;
|
||||||
|
|
||||||
if (this.props.field.fields !== undefined) {
|
if (this.props.field.fields !== undefined) {
|
||||||
@ -113,7 +115,29 @@ const FormField = React.createClass({
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormField.propTypes = {
|
||||||
|
onValueChange: PropTypes.func,
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
values: PropTypes.object,
|
||||||
|
tip: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.string,
|
||||||
|
]),
|
||||||
|
label: PropTypes.string,
|
||||||
|
fields: PropTypes.array,
|
||||||
|
description: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
|
FormField.defaultProps = {
|
||||||
|
onValueChange: function onValueChange() {
|
||||||
|
// no-op
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
export default FormField;
|
export default FormField;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const FormFieldRadio = React.createClass({
|
class FormFieldRadio extends React.Component { // eslint-disable-line react/prefer-stateless-function, max-len
|
||||||
render: function render() {
|
render() {
|
||||||
if (this.props.field.values === undefined) {
|
if (this.props.field.values === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -30,7 +31,23 @@ const FormFieldRadio = React.createClass({
|
|||||||
{ options }
|
{ options }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormFieldRadio.propTypes = {
|
||||||
|
onValueChange: PropTypes.func,
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
values: PropTypes.object,
|
||||||
|
}).isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
|
FormFieldRadio.defaultProps = {
|
||||||
|
onValueChange: function onValueChange() {
|
||||||
|
// no-op
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
|
||||||
export default FormFieldRadio;
|
export default FormFieldRadio;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const FormFieldSelect = React.createClass({
|
class FormFieldSelect extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
if (this.props.field.values === undefined) {
|
if (this.props.field.values === undefined) {
|
||||||
return false;
|
return false;
|
||||||
@ -70,7 +71,29 @@ const FormFieldSelect = React.createClass({
|
|||||||
{options}
|
{options}
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormFieldSelect.propTypes = {
|
||||||
|
onValueChange: PropTypes.func,
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
values: PropTypes.object,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
filter: PropTypes.func,
|
||||||
|
sortBy: PropTypes.func,
|
||||||
|
validation: PropTypes.object,
|
||||||
|
}).isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
automationId: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
FormFieldSelect.defaultProps = {
|
||||||
|
automationId: '',
|
||||||
|
onValueChange: function onValueChange() {
|
||||||
|
// no-op
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = FormFieldSelect;
|
module.exports = FormFieldSelect;
|
||||||
|
@ -3,23 +3,16 @@ import jQuery from 'jquery';
|
|||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import 'react-dom';
|
import 'react-dom';
|
||||||
import 'select2';
|
import 'select2';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const Selection = React.createClass({
|
class Selection extends React.Component {
|
||||||
allowMultipleValues: function allowMultipleValues() {
|
componentDidMount() {
|
||||||
return (this.props.field.multiple === true);
|
|
||||||
},
|
|
||||||
isSelect2Initialized: function isSelect2Initialized() {
|
|
||||||
return (jQuery(`#${this.select.id}`).hasClass('select2-hidden-accessible') === true);
|
|
||||||
},
|
|
||||||
isSelect2Component: function isSelect2Component() {
|
|
||||||
return this.allowMultipleValues() || this.props.field.forceSelect2;
|
|
||||||
},
|
|
||||||
componentDidMount: function componentDidMount() {
|
|
||||||
if (this.isSelect2Component()) {
|
if (this.isSelect2Component()) {
|
||||||
this.setupSelect2();
|
this.setupSelect2();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
componentDidUpdate: function componentDidUpdate(prevProps) {
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
if ((this.props.item !== undefined && prevProps.item !== undefined)
|
if ((this.props.item !== undefined && prevProps.item !== undefined)
|
||||||
&& (this.props.item.id !== prevProps.item.id)
|
&& (this.props.item.id !== prevProps.item.id)
|
||||||
) {
|
) {
|
||||||
@ -34,38 +27,73 @@ const Selection = React.createClass({
|
|||||||
) {
|
) {
|
||||||
this.resetSelect2();
|
this.resetSelect2();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
componentWillUnmount: function componentWillUnmount() {
|
|
||||||
|
componentWillUnmount() {
|
||||||
if (this.isSelect2Component()) {
|
if (this.isSelect2Component()) {
|
||||||
this.destroySelect2();
|
this.destroySelect2();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getFieldId: function getFieldId(data) {
|
|
||||||
|
getFieldId = (data) => {
|
||||||
const props = data || this.props;
|
const props = data || this.props;
|
||||||
return props.field.id || props.field.name;
|
return props.field.id || props.field.name;
|
||||||
},
|
};
|
||||||
resetSelect2: function resetSelect2() {
|
|
||||||
this.destroySelect2();
|
|
||||||
this.setupSelect2();
|
|
||||||
},
|
|
||||||
destroySelect2: function destroySelect2() {
|
|
||||||
if (this.isSelect2Initialized()) {
|
|
||||||
jQuery(`#${this.select.id}`).select2('destroy');
|
|
||||||
this.cleanupAfterSelect2();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cleanupAfterSelect2: function cleanupAfterSelect2() {
|
|
||||||
// remove DOM elements created by Select2 that are not tracked by React
|
|
||||||
jQuery(`#${this.select.id}`)
|
|
||||||
.find('option:not(.default)')
|
|
||||||
.remove();
|
|
||||||
|
|
||||||
// unbind events (https://select2.org/programmatic-control/methods#event-unbinding)
|
getSelectedValues = () => {
|
||||||
jQuery(`#${this.select.id}`)
|
if (this.props.field.selected !== undefined) {
|
||||||
.off('select2:unselecting')
|
return this.props.field.selected(this.props.item);
|
||||||
.off('select2:opening');
|
} else if (this.props.item !== undefined && this.props.field.name !== undefined) {
|
||||||
},
|
if (this.allowMultipleValues()) {
|
||||||
setupSelect2: function setupSelect2() {
|
if (_.isArray(this.props.item[this.props.field.name])) {
|
||||||
|
return this.props.item[this.props.field.name].map(item => item.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.props.item[this.props.field.name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
getItems = () => {
|
||||||
|
let items;
|
||||||
|
if (typeof (window[`mailpoet_${this.props.field.endpoint}`]) !== 'undefined') {
|
||||||
|
items = window[`mailpoet_${this.props.field.endpoint}`];
|
||||||
|
} else if (this.props.field.values !== undefined) {
|
||||||
|
items = this.props.field.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isArray(items)) {
|
||||||
|
if (this.props.field.filter !== undefined) {
|
||||||
|
items = items.filter(this.props.field.filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
getLabel = (item) => {
|
||||||
|
if (this.props.field.getLabel !== undefined) {
|
||||||
|
return this.props.field.getLabel(item, this.props.item);
|
||||||
|
}
|
||||||
|
return item.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
getSearchLabel = (item) => {
|
||||||
|
if (this.props.field.getSearchLabel !== undefined) {
|
||||||
|
return this.props.field.getSearchLabel(item, this.props.item);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
getValue = (item) => {
|
||||||
|
if (this.props.field.getValue !== undefined) {
|
||||||
|
return this.props.field.getValue(item, this.props.item);
|
||||||
|
}
|
||||||
|
return item.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
setupSelect2 = () => {
|
||||||
if (this.isSelect2Initialized()) {
|
if (this.isSelect2Initialized()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -138,38 +166,39 @@ const Selection = React.createClass({
|
|||||||
});
|
});
|
||||||
|
|
||||||
select2.on('change', this.handleChange);
|
select2.on('change', this.handleChange);
|
||||||
},
|
};
|
||||||
getSelectedValues: function getSelectedValues() {
|
|
||||||
if (this.props.field.selected !== undefined) {
|
|
||||||
return this.props.field.selected(this.props.item);
|
|
||||||
} else if (this.props.item !== undefined && this.props.field.name !== undefined) {
|
|
||||||
if (this.allowMultipleValues()) {
|
|
||||||
if (_.isArray(this.props.item[this.props.field.name])) {
|
|
||||||
return this.props.item[this.props.field.name].map(item => item.id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.props.item[this.props.field.name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
getItems: function getItems() {
|
|
||||||
let items;
|
|
||||||
if (typeof (window[`mailpoet_${this.props.field.endpoint}`]) !== 'undefined') {
|
|
||||||
items = window[`mailpoet_${this.props.field.endpoint}`];
|
|
||||||
} else if (this.props.field.values !== undefined) {
|
|
||||||
items = this.props.field.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isArray(items)) {
|
resetSelect2 = () => {
|
||||||
if (this.props.field.filter !== undefined) {
|
this.destroySelect2();
|
||||||
items = items.filter(this.props.field.filter);
|
this.setupSelect2();
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
destroySelect2 = () => {
|
||||||
},
|
if (this.isSelect2Initialized()) {
|
||||||
handleChange: function handleChange(e) {
|
jQuery(`#${this.select.id}`).select2('destroy');
|
||||||
|
this.cleanupAfterSelect2();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanupAfterSelect2 = () => {
|
||||||
|
// remove DOM elements created by Select2 that are not tracked by React
|
||||||
|
jQuery(`#${this.select.id}`)
|
||||||
|
.find('option:not(.default)')
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
// unbind events (https://select2.org/programmatic-control/methods#event-unbinding)
|
||||||
|
jQuery(`#${this.select.id}`)
|
||||||
|
.off('select2:unselecting')
|
||||||
|
.off('select2:opening');
|
||||||
|
};
|
||||||
|
|
||||||
|
allowMultipleValues = () => (this.props.field.multiple === true);
|
||||||
|
|
||||||
|
isSelect2Initialized = () => (jQuery(`#${this.select.id}`).hasClass('select2-hidden-accessible') === true);
|
||||||
|
|
||||||
|
isSelect2Component = () => this.allowMultipleValues() || this.props.field.forceSelect2;
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
if (this.props.onValueChange === undefined) return;
|
if (this.props.onValueChange === undefined) return;
|
||||||
|
|
||||||
const valueTextPair = jQuery(`#${this.select.id}`).children(':selected').map(function element() {
|
const valueTextPair = jQuery(`#${this.select.id}`).children(':selected').map(function element() {
|
||||||
@ -185,43 +214,28 @@ const Selection = React.createClass({
|
|||||||
id: e.target.id,
|
id: e.target.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
getLabel: function getLabel(item) {
|
|
||||||
if (this.props.field.getLabel !== undefined) {
|
|
||||||
return this.props.field.getLabel(item, this.props.item);
|
|
||||||
}
|
|
||||||
return item.name;
|
|
||||||
},
|
|
||||||
getSearchLabel: function getSearchLabel(item) {
|
|
||||||
if (this.props.field.getSearchLabel !== undefined) {
|
|
||||||
return this.props.field.getSearchLabel(item, this.props.item);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
getValue: function getValue(item) {
|
|
||||||
if (this.props.field.getValue !== undefined) {
|
|
||||||
return this.props.field.getValue(item, this.props.item);
|
|
||||||
}
|
|
||||||
return item.id;
|
|
||||||
},
|
|
||||||
// When it's impossible to represent the desired value in DOM,
|
// When it's impossible to represent the desired value in DOM,
|
||||||
// this function may be used to transform the placeholder value into
|
// this function may be used to transform the placeholder value into
|
||||||
// desired value.
|
// desired value.
|
||||||
transformChangedValue: function transformChangedValue(value, textValuePair) {
|
transformChangedValue = (value, textValuePair) => {
|
||||||
if (typeof this.props.field.transformChangedValue === 'function') {
|
if (typeof this.props.field.transformChangedValue === 'function') {
|
||||||
return this.props.field.transformChangedValue.call(this, value, textValuePair);
|
return this.props.field.transformChangedValue.call(this, value, textValuePair);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
},
|
};
|
||||||
insertEmptyOption: function insertEmptyOption() {
|
|
||||||
|
insertEmptyOption = () => {
|
||||||
// https://select2.org/placeholders
|
// https://select2.org/placeholders
|
||||||
// For single selects only, in order for the placeholder value to appear,
|
// For single selects only, in order for the placeholder value to appear,
|
||||||
// we must have a blank <option> as the first option in the <select> control.
|
// we must have a blank <option> as the first option in the <select> control.
|
||||||
if (this.allowMultipleValues()) return undefined;
|
if (this.allowMultipleValues()) return undefined;
|
||||||
if (this.props.field.placeholder) return (<option className="default" />);
|
if (this.props.field.placeholder) return (<option className="default" />);
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
const items = this.getItems(this.props.field);
|
const items = this.getItems(this.props.field);
|
||||||
const selectedValues = this.getSelectedValues();
|
const selectedValues = this.getSelectedValues();
|
||||||
const options = items.map((item) => {
|
const options = items.map((item) => {
|
||||||
@ -255,7 +269,42 @@ const Selection = React.createClass({
|
|||||||
{ options }
|
{ options }
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Selection.propTypes = {
|
||||||
|
onValueChange: PropTypes.func,
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
values: PropTypes.object,
|
||||||
|
getLabel: PropTypes.func,
|
||||||
|
resetSelect2OnUpdate: PropTypes.bool,
|
||||||
|
selected: PropTypes.func,
|
||||||
|
endpoint: PropTypes.string,
|
||||||
|
filter: PropTypes.func,
|
||||||
|
getSearchLabel: PropTypes.func,
|
||||||
|
getValue: PropTypes.func,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
remoteQuery: PropTypes.object,
|
||||||
|
extendSelect2Options: PropTypes.object,
|
||||||
|
multiple: PropTypes.bool,
|
||||||
|
forceSelect2: PropTypes.bool,
|
||||||
|
transformChangedValue: PropTypes.func,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
validation: PropTypes.object,
|
||||||
|
}).isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
width: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
Selection.defaultProps = {
|
||||||
|
onValueChange: function onValueChange() {
|
||||||
|
// no-op
|
||||||
},
|
},
|
||||||
});
|
disabled: false,
|
||||||
|
width: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Selection;
|
export default Selection;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const FormFieldText = React.createClass({
|
class FormFieldText extends React.Component { // eslint-disable-line react/prefer-stateless-function, max-len
|
||||||
render() {
|
render() {
|
||||||
const name = this.props.field.name || null;
|
const name = this.props.field.name || null;
|
||||||
const item = this.props.item || {};
|
const item = this.props.item || {};
|
||||||
@ -51,7 +52,31 @@ const FormFieldText = React.createClass({
|
|||||||
{...this.props.field.validation}
|
{...this.props.field.validation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormFieldText.propTypes = {
|
||||||
|
onValueChange: PropTypes.func,
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
defaultValue: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
|
class: PropTypes.string,
|
||||||
|
size: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
|
disabled: PropTypes.func,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
validation: PropTypes.object,
|
||||||
|
}).isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
|
FormFieldText.defaultProps = {
|
||||||
|
onValueChange: function onValueChange() {
|
||||||
|
// no-op
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
module.exports = FormFieldText;
|
module.exports = FormFieldText;
|
||||||
|
@ -3,30 +3,43 @@ import MailPoet from 'mailpoet';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import FormField from 'form/fields/field.jsx';
|
import FormField from 'form/fields/field.jsx';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const Form = React.createClass({
|
class Form extends React.Component {
|
||||||
contextTypes: {
|
static contextTypes = {
|
||||||
router: React.PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
},
|
};
|
||||||
getDefaultProps: function getDefaultProps() {
|
|
||||||
return {
|
static defaultProps = {
|
||||||
params: {},
|
params: {},
|
||||||
};
|
errors: undefined,
|
||||||
},
|
fields: undefined,
|
||||||
getInitialState: function getInitialState() {
|
item: undefined,
|
||||||
return {
|
onItemLoad: undefined,
|
||||||
loading: false,
|
isValid: undefined,
|
||||||
errors: [],
|
onSuccess: undefined,
|
||||||
item: {},
|
onChange: undefined,
|
||||||
};
|
loading: false,
|
||||||
},
|
beforeFormContent: undefined,
|
||||||
getValues: function getValues() {
|
afterFormContent: undefined,
|
||||||
return this.props.item ? this.props.item : this.state.item;
|
children: undefined,
|
||||||
},
|
id: '',
|
||||||
getErrors: function getErrors() {
|
onSubmit: undefined,
|
||||||
return this.props.errors ? this.props.errors : this.state.errors;
|
automationId: '',
|
||||||
},
|
messages: {
|
||||||
componentDidMount: function componentDidMount() {
|
onUpdate: () => { /* no-op */ },
|
||||||
|
onCreate: () => { /* no-op */ },
|
||||||
|
},
|
||||||
|
endpoint: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
errors: [],
|
||||||
|
item: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
if (this.props.params.id !== undefined) {
|
if (this.props.params.id !== undefined) {
|
||||||
this.loadItem(this.props.params.id);
|
this.loadItem(this.props.params.id);
|
||||||
} else {
|
} else {
|
||||||
@ -36,8 +49,9 @@ const Form = React.createClass({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
componentWillReceiveProps: function componentWillReceiveProps(props) {
|
|
||||||
|
componentWillReceiveProps(props) {
|
||||||
if (props.params.id === undefined) {
|
if (props.params.id === undefined) {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -49,10 +63,16 @@ const Form = React.createClass({
|
|||||||
this.form.reset();
|
this.form.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
loadItem: function loadItem(id) {
|
|
||||||
|
getValues = () => this.props.item || this.state.item;
|
||||||
|
|
||||||
|
getErrors = () => this.props.errors || this.state.errors;
|
||||||
|
|
||||||
|
loadItem = (id) => {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
|
if (!this.props.endpoint) return;
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: this.props.endpoint,
|
endpoint: this.props.endpoint,
|
||||||
@ -76,8 +96,9 @@ const Form = React.createClass({
|
|||||||
this.context.router.push('/new');
|
this.context.router.push('/new');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
handleSubmit: function handleSubmit(e) {
|
|
||||||
|
handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// handle validation
|
// handle validation
|
||||||
@ -105,6 +126,8 @@ const Form = React.createClass({
|
|||||||
item.id = this.props.params.id;
|
item.id = this.props.params.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.props.endpoint) return;
|
||||||
|
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: this.props.endpoint,
|
endpoint: this.props.endpoint,
|
||||||
@ -129,8 +152,9 @@ const Form = React.createClass({
|
|||||||
this.setState({ errors: response.errors });
|
this.setState({ errors: response.errors });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
handleValueChange: function handleValueChange(e) {
|
|
||||||
|
handleValueChange = (e) => {
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
return this.props.onChange(e);
|
return this.props.onChange(e);
|
||||||
}
|
}
|
||||||
@ -143,8 +167,9 @@ const Form = React.createClass({
|
|||||||
item,
|
item,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
let errors;
|
let errors;
|
||||||
if (this.getErrors() !== undefined) {
|
if (this.getErrors() !== undefined) {
|
||||||
errors = this.getErrors().map(error => (
|
errors = this.getErrors().map(error => (
|
||||||
@ -231,7 +256,32 @@ const Form = React.createClass({
|
|||||||
{ afterFormContent }
|
{ afterFormContent }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
Form.propTypes = {
|
||||||
|
params: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
item: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
|
errors: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
endpoint: PropTypes.string,
|
||||||
|
fields: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
messages: PropTypes.shape({
|
||||||
|
onUpdate: PropTypes.func,
|
||||||
|
onCreate: PropTypes.func,
|
||||||
|
}).isRequired,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
children: PropTypes.array, // eslint-disable-line react/forbid-prop-types
|
||||||
|
id: PropTypes.string,
|
||||||
|
automationId: PropTypes.string,
|
||||||
|
beforeFormContent: PropTypes.func,
|
||||||
|
afterFormContent: PropTypes.func,
|
||||||
|
onItemLoad: PropTypes.func,
|
||||||
|
isValid: PropTypes.func,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
onSuccess: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
export default Form;
|
export default Form;
|
||||||
|
@ -672,7 +672,7 @@ WysijaForm = {
|
|||||||
if (type === undefined) type = 'block';
|
if (type === undefined) type = 'block';
|
||||||
// identify element
|
// identify element
|
||||||
id = element.identify();
|
id = element.identify();
|
||||||
instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
|
instance = WysijaForm.instances[id] || new (WysijaForm[type.capitalize().camelize()])(id);
|
||||||
|
|
||||||
WysijaForm.instances[id] = instance;
|
WysijaForm.instances[id] = instance;
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
|
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { createHashHistory } from 'history';
|
import { createHashHistory } from 'history';
|
||||||
import FormList from './list.jsx';
|
import FormList from './list.jsx';
|
||||||
|
|
||||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||||
|
|
||||||
const App = React.createClass({
|
class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
const container = document.getElementById('forms_container');
|
const container = document.getElementById('forms_container');
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Listing from '../listing/listing.jsx';
|
import Listing from '../listing/listing.jsx';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -122,8 +123,8 @@ const itemActions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const FormList = React.createClass({
|
class FormList extends React.Component {
|
||||||
createForm() {
|
createForm = () => {
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: 'forms',
|
endpoint: 'forms',
|
||||||
@ -138,8 +139,9 @@ const FormList = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
renderItem(form, actions) {
|
|
||||||
|
renderItem = (form, actions) => {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
'column-primary',
|
'column-primary',
|
||||||
@ -177,7 +179,8 @@ const FormList = React.createClass({
|
|||||||
</td>
|
</td>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -204,7 +207,12 @@ const FormList = React.createClass({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
FormList.propTypes = {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = FormList;
|
module.exports = FormList;
|
||||||
|
@ -151,6 +151,16 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
|
|||||||
case 'Times New Roman': return new Handlebars.SafeString("'Times New Roman', Times, Baskerville, Georgia, serif");
|
case 'Times New Roman': return new Handlebars.SafeString("'Times New Roman', Times, Baskerville, Georgia, serif");
|
||||||
case 'Trebuchet MS': return new Handlebars.SafeString("'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif");
|
case 'Trebuchet MS': return new Handlebars.SafeString("'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif");
|
||||||
case 'Verdana': return new Handlebars.SafeString('Verdana, Geneva, sans-serif');
|
case 'Verdana': return new Handlebars.SafeString('Verdana, Geneva, sans-serif');
|
||||||
|
case 'Arvo': return new Handlebars.SafeString('arvo, courier, georgia, serif');
|
||||||
|
case 'Lato': return new Handlebars.SafeString("lato, 'helvetica neue', helvetica, arial, sans-serif");
|
||||||
|
case 'Lora': return new Handlebars.SafeString("lora, georgia, 'times new roman', serif");
|
||||||
|
case 'Merriweather': return new Handlebars.SafeString("merriweather, georgia, 'times new roman', serif");
|
||||||
|
case 'Merriweather Sans': return new Handlebars.SafeString("'merriweather sans', 'helvetica neue', helvetica, arial, sans-serif");
|
||||||
|
case 'Noticia Text': return new Handlebars.SafeString("'noticia text', georgia, 'times new roman', serif");
|
||||||
|
case 'Open Sans': return new Handlebars.SafeString("'open sans', 'helvetica neue', helvetica, arial, sans-serif");
|
||||||
|
case 'Playfair Display': return new Handlebars.SafeString("playfair display, georgia, 'times new roman', serif");
|
||||||
|
case 'Roboto': return new Handlebars.SafeString("roboto, 'helvetica neue', helvetica, arial, sans-serif");
|
||||||
|
case 'Source Sans Pro': return new Handlebars.SafeString("'source sans pro', 'helvetica neue', helvetica, arial, sans-serif");
|
||||||
default: return font;
|
default: return font;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactTooltip from 'react-tooltip';
|
import ReactTooltip from 'react-tooltip';
|
||||||
import ReactHtmlParser from 'react-html-parser';
|
import ReactHtmlParser from 'react-html-parser';
|
||||||
@ -47,10 +48,10 @@ function Tooltip(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.propTypes = {
|
Tooltip.propTypes = {
|
||||||
tooltipId: React.PropTypes.string,
|
tooltipId: PropTypes.string,
|
||||||
tooltip: React.PropTypes.node.isRequired,
|
tooltip: PropTypes.node.isRequired,
|
||||||
place: React.PropTypes.string,
|
place: PropTypes.string,
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
Tooltip.defaultProps = {
|
Tooltip.defaultProps = {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import KeyValueTable from 'common/key_value_table.jsx';
|
import KeyValueTable from 'common/key_value_table.jsx';
|
||||||
import PrintBoolean from 'common/print_boolean.jsx';
|
import PrintBoolean from 'common/print_boolean.jsx';
|
||||||
@ -46,12 +47,12 @@ const CronStatus = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CronStatus.propTypes = {
|
CronStatus.propTypes = {
|
||||||
status_data: React.PropTypes.shape({
|
status_data: PropTypes.shape({
|
||||||
accessible: React.PropTypes.bool,
|
accessible: PropTypes.bool,
|
||||||
status: React.PropTypes.string,
|
status: PropTypes.string,
|
||||||
updated_at: React.PropTypes.number,
|
updated_at: PropTypes.number,
|
||||||
run_accessed_at: React.PropTypes.number,
|
run_accessed_at: PropTypes.number,
|
||||||
run_completed_at: React.PropTypes.number,
|
run_completed_at: PropTypes.number,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,18 +2,24 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
|
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
|
||||||
import { createHashHistory } from 'history';
|
import { createHashHistory } from 'history';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import SystemStatus from 'help/system_status.jsx';
|
|
||||||
import SystemInfo from 'help/system_info.jsx';
|
|
||||||
import KnowledgeBase from 'help/knowledge_base.jsx';
|
import KnowledgeBase from 'help/knowledge_base.jsx';
|
||||||
|
import SystemInfo from 'help/system_info.jsx';
|
||||||
|
import SystemStatus from 'help/system_status.jsx';
|
||||||
|
import YourPrivacy from 'help/your_privacy.jsx';
|
||||||
|
|
||||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||||
|
|
||||||
const App = React.createClass({
|
class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
const container = document.getElementById('help_container');
|
const container = document.getElementById('help_container');
|
||||||
|
|
||||||
@ -26,6 +32,7 @@ if (container) {
|
|||||||
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={KnowledgeBase} />
|
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={KnowledgeBase} />
|
||||||
<Route path="systemStatus(/)**" params={{ tab: 'systemStatus' }} component={SystemStatus} />
|
<Route path="systemStatus(/)**" params={{ tab: 'systemStatus' }} component={SystemStatus} />
|
||||||
<Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={SystemInfo} />
|
<Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={SystemInfo} />
|
||||||
|
<Route path="yourPrivacy(/)**" params={{ tab: 'yourPrivacy' }} component={YourPrivacy} />
|
||||||
</Route>
|
</Route>
|
||||||
</Router>
|
</Router>
|
||||||
), container);
|
), container);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import KeyValueTable from 'common/key_value_table.jsx';
|
import KeyValueTable from 'common/key_value_table.jsx';
|
||||||
import TasksList from './tasks_list/tasks_list.jsx';
|
import TasksList from './tasks_list/tasks_list.jsx';
|
||||||
@ -58,23 +59,23 @@ const QueueStatus = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QueueStatus.propTypes = {
|
QueueStatus.propTypes = {
|
||||||
status_data: React.PropTypes.shape({
|
status_data: PropTypes.shape({
|
||||||
status: React.PropTypes.string,
|
status: PropTypes.string,
|
||||||
started: React.PropTypes.number,
|
started: PropTypes.number,
|
||||||
sent: React.PropTypes.number,
|
sent: PropTypes.number,
|
||||||
retry_attempt: React.PropTypes.number,
|
retry_attempt: PropTypes.number,
|
||||||
retry_at: React.PropTypes.number,
|
retry_at: PropTypes.number,
|
||||||
error: React.PropTypes.shape({
|
error: PropTypes.shape({
|
||||||
operation: React.PropTypes.string,
|
operation: PropTypes.string,
|
||||||
error_message: React.PropTypes.string,
|
error_message: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
tasksStatusCounts: React.PropTypes.shape({
|
tasksStatusCounts: PropTypes.shape({
|
||||||
completed: React.PropTypes.number.isRequired,
|
completed: PropTypes.number.isRequired,
|
||||||
running: React.PropTypes.number.isRequired,
|
running: PropTypes.number.isRequired,
|
||||||
paused: React.PropTypes.number.isRequired,
|
paused: PropTypes.number.isRequired,
|
||||||
scheduled: React.PropTypes.number.isRequired,
|
scheduled: PropTypes.number.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
latestTasks: React.PropTypes.arrayOf(TasksListDataRow.propTypes.task).isRequired,
|
latestTasks: PropTypes.arrayOf(TasksListDataRow.propTypes.task).isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -19,6 +20,11 @@ const tabs = [
|
|||||||
label: MailPoet.I18n.t('tabSystemInfoTitle'),
|
label: MailPoet.I18n.t('tabSystemInfoTitle'),
|
||||||
link: '/systemInfo',
|
link: '/systemInfo',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'yourPrivacy',
|
||||||
|
label: MailPoet.I18n.t('tabYourPrivacyTitle'),
|
||||||
|
link: '/yourPrivacy',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function Tabs(props) {
|
function Tabs(props) {
|
||||||
@ -44,7 +50,7 @@ function Tabs(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Tabs.propTypes = { tab: React.PropTypes.string };
|
Tabs.propTypes = { tab: PropTypes.string };
|
||||||
Tabs.defaultProps = { tab: 'knowledgeBase' };
|
Tabs.defaultProps = { tab: 'knowledgeBase' };
|
||||||
|
|
||||||
module.exports = Tabs;
|
module.exports = Tabs;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import TaskListDataRow from './tasks_list_data_row.jsx';
|
import TaskListDataRow from './tasks_list_data_row.jsx';
|
||||||
@ -30,8 +31,8 @@ const TasksList = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TasksList.propTypes = {
|
TasksList.propTypes = {
|
||||||
show_scheduled_at: React.PropTypes.bool,
|
show_scheduled_at: PropTypes.bool,
|
||||||
tasks: React.PropTypes.arrayOf(TaskListDataRow.propTypes.task).isRequired,
|
tasks: PropTypes.arrayOf(TaskListDataRow.propTypes.task).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
TasksList.defaultProps = {
|
TasksList.defaultProps = {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
|
||||||
@ -36,19 +37,19 @@ const TasksListDataRow = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
TasksListDataRow.propTypes = {
|
TasksListDataRow.propTypes = {
|
||||||
show_scheduled_at: React.PropTypes.bool,
|
show_scheduled_at: PropTypes.bool,
|
||||||
task: React.PropTypes.shape({
|
task: PropTypes.shape({
|
||||||
id: React.PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
type: React.PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
priority: React.PropTypes.number.isRequired,
|
priority: PropTypes.number.isRequired,
|
||||||
updated_at: React.PropTypes.number.isRequired,
|
updated_at: PropTypes.number.isRequired,
|
||||||
scheduled_at: React.PropTypes.number,
|
scheduled_at: PropTypes.number,
|
||||||
status: React.PropTypes.string,
|
status: PropTypes.string,
|
||||||
newsletter: React.PropTypes.shape({
|
newsletter: PropTypes.shape({
|
||||||
newsletter_id: React.PropTypes.number.isRequired,
|
newsletter_id: PropTypes.number.isRequired,
|
||||||
queue_id: React.PropTypes.number.isRequired,
|
queue_id: PropTypes.number.isRequired,
|
||||||
preview_url: React.PropTypes.string.isRequired,
|
preview_url: PropTypes.string.isRequired,
|
||||||
subject: React.PropTypes.string,
|
subject: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ const TasksListLabelsRow = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
TasksListLabelsRow.propTypes = {
|
TasksListLabelsRow.propTypes = {
|
||||||
show_scheduled_at: React.PropTypes.bool,
|
show_scheduled_at: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
TasksListLabelsRow.defaultProps = {
|
TasksListLabelsRow.defaultProps = {
|
||||||
|
20
assets/js/src/help/your_privacy.jsx
Normal file
20
assets/js/src/help/your_privacy.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
|
||||||
|
import Tabs from './tabs.jsx';
|
||||||
|
|
||||||
|
function YourPrivacy() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tabs tab="yourPrivacy" />
|
||||||
|
|
||||||
|
<p>{MailPoet.I18n.t('yourPrivacyContent1')}</p>
|
||||||
|
<p>{MailPoet.I18n.t('yourPrivacyContent2')}</p>
|
||||||
|
<p>{MailPoet.I18n.t('yourPrivacyContent3')}</p>
|
||||||
|
|
||||||
|
<a target="_blank" rel="noreferrer noopener" href="https://www.mailpoet.com/privacy-notice/" className="button button-primary">{MailPoet.I18n.t('yourPrivacyButton')}</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = YourPrivacy;
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import InAppAnnouncementDot from './in_app_announcement_dot.jsx';
|
import InAppAnnouncementDot from './in_app_announcement_dot.jsx';
|
||||||
@ -78,11 +79,11 @@ const validateBooleanWithWindowDependency = (props, propName, componentName, win
|
|||||||
};
|
};
|
||||||
|
|
||||||
InAppAnnouncement.propTypes = {
|
InAppAnnouncement.propTypes = {
|
||||||
width: React.PropTypes.string,
|
width: PropTypes.string,
|
||||||
height: React.PropTypes.string,
|
height: PropTypes.string,
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: React.PropTypes.element.isRequired,
|
children: PropTypes.element.isRequired,
|
||||||
validUntil: React.PropTypes.instanceOf(Date),
|
validUntil: PropTypes.instanceOf(Date),
|
||||||
showToNewUser: (props, propName, componentName) => (
|
showToNewUser: (props, propName, componentName) => (
|
||||||
validateBooleanWithWindowDependency(props, propName, componentName, 'mailpoet_is_new_user')
|
validateBooleanWithWindowDependency(props, propName, componentName, 'mailpoet_is_new_user')
|
||||||
),
|
),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import ReactDOMServer from 'react-dom/server';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -20,11 +21,11 @@ const InAppAnnouncementDot = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
InAppAnnouncementDot.propTypes = {
|
InAppAnnouncementDot.propTypes = {
|
||||||
children: React.PropTypes.element.isRequired,
|
children: PropTypes.element.isRequired,
|
||||||
width: React.PropTypes.string,
|
width: PropTypes.string,
|
||||||
height: React.PropTypes.string,
|
height: PropTypes.string,
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
onUserTrigger: React.PropTypes.func,
|
onUserTrigger: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
InAppAnnouncementDot.defaultProps = {
|
InAppAnnouncementDot.defaultProps = {
|
||||||
|
@ -1,29 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const ListingBulkActions = React.createClass({
|
class ListingBulkActions extends React.Component {
|
||||||
getInitialState: function getInitialState() {
|
state = {
|
||||||
return {
|
action: false,
|
||||||
action: false,
|
extra: false,
|
||||||
extra: false,
|
};
|
||||||
};
|
|
||||||
},
|
|
||||||
handleChangeAction: function handleChangeAction(e) {
|
|
||||||
this.setState({
|
|
||||||
action: e.target.value,
|
|
||||||
extra: false,
|
|
||||||
}, () => {
|
|
||||||
const action = this.getSelectedAction();
|
|
||||||
|
|
||||||
// action on select callback
|
getSelectedAction = () => {
|
||||||
if (action !== null && action.onSelect !== undefined) {
|
const selectedAction = this.action.value;
|
||||||
this.setState({
|
if (selectedAction.length > 0) {
|
||||||
extra: action.onSelect(e),
|
const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
|
||||||
});
|
|
||||||
|
if (action.length > 0) {
|
||||||
|
return action[0];
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
},
|
return null;
|
||||||
handleApplyAction: function handleApplyAction(e) {
|
};
|
||||||
|
|
||||||
|
handleApplyAction = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const action = this.getSelectedAction();
|
const action = this.getSelectedAction();
|
||||||
@ -58,19 +55,25 @@ const ListingBulkActions = React.createClass({
|
|||||||
action: false,
|
action: false,
|
||||||
extra: false,
|
extra: false,
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
getSelectedAction: function getSelectedAction() {
|
|
||||||
const selectedAction = this.action.value;
|
|
||||||
if (selectedAction.length > 0) {
|
|
||||||
const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
|
|
||||||
|
|
||||||
if (action.length > 0) {
|
handleChangeAction = (e) => {
|
||||||
return action[0];
|
this.setState({
|
||||||
|
action: e.target.value,
|
||||||
|
extra: false,
|
||||||
|
}, () => {
|
||||||
|
const action = this.getSelectedAction();
|
||||||
|
|
||||||
|
// action on select callback
|
||||||
|
if (action !== null && action.onSelect !== undefined) {
|
||||||
|
this.setState({
|
||||||
|
extra: action.onSelect(e),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return null;
|
};
|
||||||
},
|
|
||||||
render: function render() {
|
render() {
|
||||||
if (this.props.bulk_actions.length === 0) {
|
if (this.props.bulk_actions.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -108,7 +111,17 @@ const ListingBulkActions = React.createClass({
|
|||||||
{ this.state.extra }
|
{ this.state.extra }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
ListingBulkActions.propTypes = {
|
||||||
|
bulk_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
selection: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.bool,
|
||||||
|
]).isRequired,
|
||||||
|
selected_ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
onBulkAction: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default ListingBulkActions;
|
export default ListingBulkActions;
|
||||||
|
@ -1,32 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const ListingFilters = React.createClass({
|
class ListingFilters extends React.Component {
|
||||||
handleFilterAction: function handleFilterAction() {
|
componentDidUpdate() {
|
||||||
const filters = {};
|
|
||||||
this.getAvailableFilters().forEach((filter, i) => {
|
|
||||||
filters[this[`filter-${i}`].name] = this[`filter-${i}`].value;
|
|
||||||
});
|
|
||||||
if (this.props.onBeforeSelectFilter) {
|
|
||||||
this.props.onBeforeSelectFilter(filters);
|
|
||||||
}
|
|
||||||
return this.props.onSelectFilter(filters);
|
|
||||||
},
|
|
||||||
handleEmptyTrash: function handleEmptyTrash() {
|
|
||||||
return this.props.onEmptyTrash();
|
|
||||||
},
|
|
||||||
getAvailableFilters: function getAvailableFilters() {
|
|
||||||
const filters = this.props.filters;
|
|
||||||
return Object.keys(filters).filter(filter => !(
|
|
||||||
filters[filter].length === 0
|
|
||||||
|| (
|
|
||||||
filters[filter].length === 1
|
|
||||||
&& !filters[filter][0].value
|
|
||||||
)
|
|
||||||
));
|
|
||||||
},
|
|
||||||
componentDidUpdate: function componentDidUpdate() {
|
|
||||||
const selectedFilters = this.props.filter;
|
const selectedFilters = this.props.filter;
|
||||||
this.getAvailableFilters().forEach(
|
this.getAvailableFilters().forEach(
|
||||||
(filter, i) => {
|
(filter, i) => {
|
||||||
@ -37,8 +15,33 @@ const ListingFilters = React.createClass({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
render: function render() {
|
|
||||||
|
getAvailableFilters = () => {
|
||||||
|
const filters = this.props.filters;
|
||||||
|
return Object.keys(filters).filter(filter => !(
|
||||||
|
filters[filter].length === 0
|
||||||
|
|| (
|
||||||
|
filters[filter].length === 1
|
||||||
|
&& !filters[filter][0].value
|
||||||
|
)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleEmptyTrash = () => this.props.onEmptyTrash();
|
||||||
|
|
||||||
|
handleFilterAction = () => {
|
||||||
|
const filters = {};
|
||||||
|
this.getAvailableFilters().forEach((filter, i) => {
|
||||||
|
filters[this[`filter-${i}`].name] = this[`filter-${i}`].value;
|
||||||
|
});
|
||||||
|
if (this.props.onBeforeSelectFilter) {
|
||||||
|
this.props.onBeforeSelectFilter(filters);
|
||||||
|
}
|
||||||
|
return this.props.onSelectFilter(filters);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
const filters = this.props.filters;
|
const filters = this.props.filters;
|
||||||
const availableFilters = this.getAvailableFilters()
|
const availableFilters = this.getAvailableFilters()
|
||||||
.map((filter, i) => (
|
.map((filter, i) => (
|
||||||
@ -89,7 +92,23 @@ const ListingFilters = React.createClass({
|
|||||||
{ emptyTrash }
|
{ emptyTrash }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
ListingFilters.propTypes = {
|
||||||
|
filters: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.array,
|
||||||
|
]).isRequired,
|
||||||
|
onEmptyTrash: PropTypes.func.isRequired,
|
||||||
|
onBeforeSelectFilter: PropTypes.func,
|
||||||
|
onSelectFilter: PropTypes.func.isRequired,
|
||||||
|
filter: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||||
|
group: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ListingFilters.defaultProps = {
|
||||||
|
onBeforeSelectFilter: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default ListingFilters;
|
export default ListingFilters;
|
||||||
|
@ -32,6 +32,7 @@ class ListingGroups extends React.Component {
|
|||||||
data-automation-id={`filters_${group.label.replace(' ', '_').toLowerCase()}`}
|
data-automation-id={`filters_${group.label.replace(' ', '_').toLowerCase()}`}
|
||||||
>
|
>
|
||||||
{group.label}
|
{group.label}
|
||||||
|
|
||||||
<span className="count">({ parseInt(group.count, 10).toLocaleString() })</span>
|
<span className="count">({ parseInt(group.count, 10).toLocaleString() })</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import PropTypes from 'prop-types';
|
||||||
|
import ListingColumn from './listing_column.jsx';
|
||||||
|
|
||||||
const ListingHeader = React.createClass({
|
class ListingHeader extends React.Component {
|
||||||
handleSelectItems: function handleSelectItems() {
|
handleSelectItems = () => this.props.onSelectItems(this.toggle.checked);
|
||||||
return this.props.onSelectItems(
|
|
||||||
this.toggle.checked
|
render() {
|
||||||
);
|
|
||||||
},
|
|
||||||
render: function render() {
|
|
||||||
const columns = this.props.columns.map((column, index) => {
|
const columns = this.props.columns.map((column, index) => {
|
||||||
const renderColumn = column;
|
const renderColumn = column;
|
||||||
renderColumn.is_primary = (index === 0);
|
renderColumn.is_primary = (index === 0);
|
||||||
@ -53,49 +51,26 @@ const ListingHeader = React.createClass({
|
|||||||
{columns}
|
{columns}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
const ListingColumn = React.createClass({
|
ListingHeader.propTypes = {
|
||||||
handleSort: function handleSort() {
|
onSelectItems: PropTypes.func.isRequired,
|
||||||
const sortBy = this.props.column.name;
|
onSort: PropTypes.func.isRequired,
|
||||||
const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
|
columns: PropTypes.arrayOf(PropTypes.object),
|
||||||
this.props.onSort(sortBy, sortOrder);
|
sort_by: PropTypes.string,
|
||||||
},
|
sort_order: PropTypes.string,
|
||||||
render: function render() {
|
is_selectable: PropTypes.bool.isRequired,
|
||||||
const classes = classNames(
|
selection: PropTypes.oneOfType([
|
||||||
'manage-column',
|
PropTypes.string,
|
||||||
{ 'column-primary': this.props.column.is_primary },
|
PropTypes.bool,
|
||||||
{ sortable: this.props.column.sortable },
|
]).isRequired,
|
||||||
this.props.column.sorted,
|
};
|
||||||
{ sorted: (this.props.sort_by === this.props.column.name) }
|
|
||||||
);
|
|
||||||
let label;
|
|
||||||
|
|
||||||
if (this.props.column.sortable === true) {
|
ListingHeader.defaultProps = {
|
||||||
label = (
|
columns: [],
|
||||||
<a
|
sort_by: undefined,
|
||||||
onClick={this.handleSort}
|
sort_order: 'desc',
|
||||||
role="button"
|
};
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<span>{ this.props.column.label }</span>
|
|
||||||
<span className="sorting-indicator" />
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
label = this.props.column.label;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<th
|
|
||||||
role="columnheader"
|
|
||||||
className={classes}
|
|
||||||
id={this.props.column.name}
|
|
||||||
scope="col"
|
|
||||||
width={this.props.column.width || null}
|
|
||||||
>{label}</th>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = ListingHeader;
|
module.exports = ListingHeader;
|
||||||
|
@ -1,305 +1,76 @@
|
|||||||
import MailPoet from 'mailpoet';
|
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import createReactClass from 'create-react-class';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { Link } from 'react-router';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
import ListingBulkActions from 'listing/bulk_actions.jsx';
|
import ListingBulkActions from 'listing/bulk_actions.jsx';
|
||||||
import ListingHeader from 'listing/header.jsx';
|
import ListingHeader from 'listing/header.jsx';
|
||||||
import ListingPages from 'listing/pages.jsx';
|
import ListingPages from 'listing/pages.jsx';
|
||||||
import ListingSearch from 'listing/search.jsx';
|
import ListingSearch from 'listing/search.jsx';
|
||||||
import ListingGroups from 'listing/groups.jsx';
|
import ListingGroups from 'listing/groups.jsx';
|
||||||
import ListingFilters from 'listing/filters.jsx';
|
import ListingFilters from 'listing/filters.jsx';
|
||||||
|
import ListingItems from 'listing/listing_items.jsx';
|
||||||
|
|
||||||
const ListingItem = React.createClass({
|
const Listing = createReactClass({ // eslint-disable-line react/prefer-es6-class
|
||||||
getInitialState: function getInitialState() {
|
displayName: 'Listing',
|
||||||
return {
|
|
||||||
expanded: false,
|
/* eslint-disable react/require-default-props */
|
||||||
};
|
propTypes: {
|
||||||
|
limit: PropTypes.number,
|
||||||
|
sort_by: PropTypes.string,
|
||||||
|
sort_order: PropTypes.string,
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
auto_refresh: PropTypes.bool,
|
||||||
|
location: PropTypes.shape({
|
||||||
|
pathname: PropTypes.string,
|
||||||
|
}),
|
||||||
|
base_url: PropTypes.string,
|
||||||
|
type: PropTypes.string,
|
||||||
|
endpoint: PropTypes.string.isRequired,
|
||||||
|
afterGetItems: PropTypes.func,
|
||||||
|
messages: PropTypes.shape({
|
||||||
|
onRestore: PropTypes.func,
|
||||||
|
onTrash: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
|
}),
|
||||||
|
onRenderItem: PropTypes.func.isRequired,
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
bulk_actions: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
item_actions: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
search: PropTypes.bool,
|
||||||
|
groups: PropTypes.bool,
|
||||||
|
renderExtraActions: PropTypes.func,
|
||||||
|
onBeforeSelectFilter: PropTypes.func,
|
||||||
|
getListingItemKey: PropTypes.func,
|
||||||
},
|
},
|
||||||
handleSelectItem: function handleSelectItem(e) {
|
/* eslint-enable react/require-default-props */
|
||||||
this.props.onSelectItem(
|
|
||||||
parseInt(e.target.value, 10),
|
|
||||||
e.target.checked
|
|
||||||
);
|
|
||||||
|
|
||||||
return !e.target.checked;
|
|
||||||
},
|
|
||||||
handleRestoreItem: function handleRestoreItem(id) {
|
|
||||||
this.props.onRestoreItem(id);
|
|
||||||
},
|
|
||||||
handleTrashItem: function handleTrashItem(id) {
|
|
||||||
this.props.onTrashItem(id);
|
|
||||||
},
|
|
||||||
handleDeleteItem: function handleDeleteItem(id) {
|
|
||||||
this.props.onDeleteItem(id);
|
|
||||||
},
|
|
||||||
handleToggleItem: function handleToggleItem() {
|
|
||||||
this.setState({ expanded: !this.state.expanded });
|
|
||||||
},
|
|
||||||
render: function render() {
|
|
||||||
let checkbox = false;
|
|
||||||
|
|
||||||
if (this.props.is_selectable === true) {
|
|
||||||
checkbox = (
|
|
||||||
<th className="check-column" scope="row">
|
|
||||||
<label className="screen-reader-text" htmlFor={`listing-row-checkbox-${this.props.item.id}`}>{
|
|
||||||
`Select ${this.props.item[this.props.columns[0].name]}`
|
|
||||||
}</label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
value={this.props.item.id}
|
|
||||||
checked={
|
|
||||||
this.props.item.selected || this.props.selection === 'all'
|
|
||||||
}
|
|
||||||
onChange={this.handleSelectItem}
|
|
||||||
disabled={this.props.selection === 'all'}
|
|
||||||
id={`listing-row-checkbox-${this.props.item.id}`}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const customActions = this.props.item_actions;
|
|
||||||
let itemActions = false;
|
|
||||||
|
|
||||||
if (customActions.length > 0) {
|
|
||||||
let isFirst = true;
|
|
||||||
itemActions = customActions
|
|
||||||
.filter(action => action.display === undefined || action.display(this.props.item))
|
|
||||||
.map((action, index) => {
|
|
||||||
let customAction = null;
|
|
||||||
|
|
||||||
if (action.name === 'trash') {
|
|
||||||
customAction = (
|
|
||||||
<span key={`action-${action.name}`} className="trash">
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={() => this.handleTrashItem(this.props.item.id)}
|
|
||||||
>
|
|
||||||
{MailPoet.I18n.t('moveToTrash')}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else if (action.refresh) {
|
|
||||||
customAction = (
|
|
||||||
<span
|
|
||||||
onClick={this.props.onRefreshItems}
|
|
||||||
key={`action-${action.name}`}
|
|
||||||
className={action.name}
|
|
||||||
role="button"
|
|
||||||
tabIndex={index}
|
|
||||||
>
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
{ action.link(this.props.item) }
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else if (action.link) {
|
|
||||||
customAction = (
|
|
||||||
<span
|
|
||||||
key={`action-${action.name}`}
|
|
||||||
className={action.name}
|
|
||||||
>
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
{ action.link(this.props.item) }
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
customAction = (
|
|
||||||
<span
|
|
||||||
key={`action-${action.name}`}
|
|
||||||
className={action.name}
|
|
||||||
>
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={
|
|
||||||
(action.onClick !== undefined)
|
|
||||||
? () => action.onClick(this.props.item, this.props.onRefreshItems)
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
>{ action.label }</a>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customAction !== null && isFirst === true) {
|
|
||||||
isFirst = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return customAction;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
itemActions = (
|
|
||||||
<span className="edit">
|
|
||||||
<Link to={`/edit/${this.props.item.id}`}>{MailPoet.I18n.t('edit')}</Link>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actions;
|
|
||||||
|
|
||||||
if (this.props.group === 'trash') {
|
|
||||||
actions = (
|
|
||||||
<div>
|
|
||||||
<div className="row-actions">
|
|
||||||
<span>
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={() => this.handleRestoreItem(this.props.item.id)}
|
|
||||||
>{MailPoet.I18n.t('restore')}</a>
|
|
||||||
</span>
|
|
||||||
{ ' | ' }
|
|
||||||
<span className="delete">
|
|
||||||
<a
|
|
||||||
className="submitdelete"
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={() => this.handleDeleteItem(this.props.item.id)}
|
|
||||||
>{MailPoet.I18n.t('deletePermanently')}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => this.handleToggleItem(this.props.item.id)}
|
|
||||||
className="toggle-row"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
actions = (
|
|
||||||
<div>
|
|
||||||
<div className="row-actions">
|
|
||||||
{ itemActions }
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => this.handleToggleItem(this.props.item.id)}
|
|
||||||
className="toggle-row"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowClasses = classNames({ 'is-expanded': this.state.expanded });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr className={rowClasses} data-automation-id={`listing_item_${this.props.item.id}`}>
|
|
||||||
{ checkbox }
|
|
||||||
{ this.props.onRenderItem(this.props.item, actions) }
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const ListingItems = React.createClass({
|
|
||||||
render: function render() {
|
|
||||||
if (this.props.items.length === 0) {
|
|
||||||
let message;
|
|
||||||
if (this.props.loading === true) {
|
|
||||||
message = (this.props.messages.onLoadingItems
|
|
||||||
&& this.props.messages.onLoadingItems(this.props.group))
|
|
||||||
|| MailPoet.I18n.t('loadingItems');
|
|
||||||
} else {
|
|
||||||
message = (this.props.messages.onNoItemsFound
|
|
||||||
&& this.props.messages.onNoItemsFound(this.props.group))
|
|
||||||
|| MailPoet.I18n.t('noItemsFound');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tbody>
|
|
||||||
<tr className="no-items">
|
|
||||||
<td
|
|
||||||
colSpan={
|
|
||||||
this.props.columns.length
|
|
||||||
+ (this.props.is_selectable ? 1 : 0)
|
|
||||||
}
|
|
||||||
className="colspanchange"
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const selectAllClasses = classNames(
|
|
||||||
'mailpoet_select_all',
|
|
||||||
{ mailpoet_hidden: (
|
|
||||||
this.props.selection === false
|
|
||||||
|| (this.props.count <= this.props.limit)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tbody>
|
|
||||||
<tr className={selectAllClasses}>
|
|
||||||
<td colSpan={
|
|
||||||
this.props.columns.length
|
|
||||||
+ (this.props.is_selectable ? 1 : 0)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
(this.props.selection !== 'all')
|
|
||||||
? MailPoet.I18n.t('selectAllLabel')
|
|
||||||
: MailPoet.I18n.t('selectedAllLabel').replace(
|
|
||||||
'%d',
|
|
||||||
this.props.count.toLocaleString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
<a
|
|
||||||
onClick={this.props.onSelectAll}
|
|
||||||
href="javascript:;"
|
|
||||||
>{
|
|
||||||
(this.props.selection !== 'all')
|
|
||||||
? MailPoet.I18n.t('selectAllLink')
|
|
||||||
: MailPoet.I18n.t('clearSelection')
|
|
||||||
}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{this.props.items.map((item) => {
|
|
||||||
const renderItem = item;
|
|
||||||
renderItem.id = parseInt(item.id, 10);
|
|
||||||
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
|
|
||||||
let key = `item-${renderItem.id}-${item.id}`;
|
|
||||||
if (typeof this.props.getListingItemKey === 'function') {
|
|
||||||
key = this.props.getListingItemKey(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListingItem
|
|
||||||
columns={this.props.columns}
|
|
||||||
onSelectItem={this.props.onSelectItem}
|
|
||||||
onRenderItem={this.props.onRenderItem}
|
|
||||||
onDeleteItem={this.props.onDeleteItem}
|
|
||||||
onRestoreItem={this.props.onRestoreItem}
|
|
||||||
onTrashItem={this.props.onTrashItem}
|
|
||||||
onRefreshItems={this.props.onRefreshItems}
|
|
||||||
selection={this.props.selection}
|
|
||||||
is_selectable={this.props.is_selectable}
|
|
||||||
item_actions={this.props.item_actions}
|
|
||||||
group={this.props.group}
|
|
||||||
key={key}
|
|
||||||
item={renderItem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const Listing = React.createClass({
|
|
||||||
contextTypes: {
|
contextTypes: {
|
||||||
router: React.PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDefaultProps: () => ({
|
||||||
|
limit: 10,
|
||||||
|
sort_by: null,
|
||||||
|
sort_order: undefined,
|
||||||
|
auto_refresh: true,
|
||||||
|
location: undefined,
|
||||||
|
base_url: '',
|
||||||
|
type: undefined,
|
||||||
|
afterGetItems: undefined,
|
||||||
|
messages: undefined,
|
||||||
|
columns: [],
|
||||||
|
bulk_actions: [],
|
||||||
|
item_actions: [],
|
||||||
|
search: true,
|
||||||
|
groups: true,
|
||||||
|
renderExtraActions: undefined,
|
||||||
|
onBeforeSelectFilter: undefined,
|
||||||
|
getListingItemKey: undefined,
|
||||||
|
}),
|
||||||
|
|
||||||
getInitialState: function getInitialState() {
|
getInitialState: function getInitialState() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -319,63 +90,28 @@ const Listing = React.createClass({
|
|||||||
meta: {},
|
meta: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getParam: function getParam(param) {
|
|
||||||
const regex = /(.*)\[(.*)\]/;
|
|
||||||
const matches = regex.exec(param);
|
|
||||||
return [matches[1], matches[2]];
|
|
||||||
},
|
|
||||||
initWithParams: function initWithParams(params) {
|
|
||||||
const state = this.getInitialState();
|
|
||||||
// check for url params
|
|
||||||
if (params.splat) {
|
|
||||||
params.splat.split('/').forEach((param) => {
|
|
||||||
const [key, value] = this.getParam(param);
|
|
||||||
const filters = {};
|
|
||||||
switch (key) {
|
|
||||||
case 'filter':
|
|
||||||
value.split('&').forEach((pair) => {
|
|
||||||
const [k, v] = pair.split('=');
|
|
||||||
filters[k] = v;
|
|
||||||
});
|
|
||||||
|
|
||||||
state.filter = filters;
|
componentDidMount: function componentDidMount() {
|
||||||
break;
|
this.isComponentMounted = true;
|
||||||
default:
|
const params = this.props.params || {};
|
||||||
state[key] = value;
|
this.initWithParams(params);
|
||||||
}
|
|
||||||
|
if (this.props.auto_refresh) {
|
||||||
|
jQuery(document).on('heartbeat-tick.mailpoet', () => {
|
||||||
|
this.getItems();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// limit per page
|
|
||||||
if (this.props.limit !== undefined) {
|
|
||||||
state.limit = Math.abs(Number(this.props.limit));
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by
|
|
||||||
if (state.sort_by === null && this.props.sort_by !== undefined) {
|
|
||||||
state.sort_by = this.props.sort_by;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort order
|
|
||||||
if (state.sort_order === null && this.props.sort_order !== undefined) {
|
|
||||||
state.sort_order = this.props.sort_order;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(state, () => {
|
|
||||||
this.getItems();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
getParams: function getParams() {
|
|
||||||
// get all route parameters (without the "splat")
|
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
|
||||||
const params = _.omit(this.props.params, 'splat');
|
const params = nextProps.params || {};
|
||||||
// TODO:
|
this.initWithParams(params);
|
||||||
// find a way to set the "type" in the routes definition
|
|
||||||
// so that it appears in `this.props.params`
|
|
||||||
if (this.props.type) {
|
|
||||||
params.type = this.props.type;
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function componentWillUnmount() {
|
||||||
|
this.isComponentMounted = false;
|
||||||
|
},
|
||||||
|
|
||||||
setParams: function setParams() {
|
setParams: function setParams() {
|
||||||
if (this.props.location) {
|
if (this.props.location) {
|
||||||
const params = Object.keys(this.state)
|
const params = Object.keys(this.state)
|
||||||
@ -413,17 +149,19 @@ const Listing = React.createClass({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getUrlWithParams: function getUrlWithParams(params) {
|
getUrlWithParams: function getUrlWithParams(params) {
|
||||||
let baseUrl = (this.props.base_url !== undefined)
|
let baseUrl = (this.props.base_url !== undefined)
|
||||||
? this.props.base_url
|
? this.props.base_url
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (baseUrl !== null) {
|
if (baseUrl) {
|
||||||
baseUrl = this.setBaseUrlParams(baseUrl);
|
baseUrl = this.setBaseUrlParams(baseUrl);
|
||||||
return `/${baseUrl}/${params}`;
|
return `/${baseUrl}/${params}`;
|
||||||
}
|
}
|
||||||
return `/${params}`;
|
return `/${params}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
setBaseUrlParams: function setBaseUrlParams(baseUrl) {
|
setBaseUrlParams: function setBaseUrlParams(baseUrl) {
|
||||||
let ret = baseUrl;
|
let ret = baseUrl;
|
||||||
if (ret.indexOf(':') !== -1) {
|
if (ret.indexOf(':') !== -1) {
|
||||||
@ -437,24 +175,25 @@ const Listing = React.createClass({
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
componentDidMount: function componentDidMount() {
|
|
||||||
this.isComponentMounted = true;
|
|
||||||
const params = this.props.params || {};
|
|
||||||
this.initWithParams(params);
|
|
||||||
|
|
||||||
if (this.props.auto_refresh) {
|
getParams: function getParams() {
|
||||||
jQuery(document).on('heartbeat-tick.mailpoet', () => {
|
// get all route parameters (without the "splat")
|
||||||
this.getItems();
|
const params = _.omit(this.props.params, 'splat');
|
||||||
});
|
// TODO:
|
||||||
|
// find a way to set the "type" in the routes definition
|
||||||
|
// so that it appears in `this.props.params`
|
||||||
|
if (this.props.type) {
|
||||||
|
params.type = this.props.type;
|
||||||
}
|
}
|
||||||
|
return params;
|
||||||
},
|
},
|
||||||
componentWillUnmount: function componentWillUnmount() {
|
|
||||||
this.isComponentMounted = false;
|
getParam: function getParam(param) {
|
||||||
},
|
const regex = /(.*)\[(.*)\]/;
|
||||||
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
|
const matches = regex.exec(param);
|
||||||
const params = nextProps.params || {};
|
return [matches[1], matches[2]];
|
||||||
this.initWithParams(params);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getItems: function getItems() {
|
getItems: function getItems() {
|
||||||
if (!this.isComponentMounted) return;
|
if (!this.isComponentMounted) return;
|
||||||
|
|
||||||
@ -507,6 +246,49 @@ const Listing = React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initWithParams: function initWithParams(params) {
|
||||||
|
const state = this.getInitialState();
|
||||||
|
// check for url params
|
||||||
|
if (params.splat) {
|
||||||
|
params.splat.split('/').forEach((param) => {
|
||||||
|
const [key, value] = this.getParam(param);
|
||||||
|
const filters = {};
|
||||||
|
switch (key) {
|
||||||
|
case 'filter':
|
||||||
|
value.split('&').forEach((pair) => {
|
||||||
|
const [k, v] = pair.split('=');
|
||||||
|
filters[k] = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.filter = filters;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit per page
|
||||||
|
if (this.props.limit !== undefined) {
|
||||||
|
state.limit = Math.abs(Number(this.props.limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by
|
||||||
|
if (state.sort_by === null && this.props.sort_by !== undefined) {
|
||||||
|
state.sort_by = this.props.sort_by;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort order
|
||||||
|
if (state.sort_order === null && this.props.sort_order !== undefined) {
|
||||||
|
state.sort_order = this.props.sort_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(state, () => {
|
||||||
|
this.getItems();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
handleRestoreItem: function handleRestoreItem(id) {
|
handleRestoreItem: function handleRestoreItem(id) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
@ -535,6 +317,7 @@ const Listing = React.createClass({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTrashItem: function handleTrashItem(id) {
|
handleTrashItem: function handleTrashItem(id) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
@ -563,6 +346,7 @@ const Listing = React.createClass({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleDeleteItem: function handleDeleteItem(id) {
|
handleDeleteItem: function handleDeleteItem(id) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
@ -591,6 +375,7 @@ const Listing = React.createClass({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleEmptyTrash: function handleEmptyTrash() {
|
handleEmptyTrash: function handleEmptyTrash() {
|
||||||
return this.handleBulkAction('all', {
|
return this.handleBulkAction('all', {
|
||||||
action: 'delete',
|
action: 'delete',
|
||||||
@ -611,6 +396,7 @@ const Listing = React.createClass({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleBulkAction: function handleBulkAction(selectedIds, params) {
|
handleBulkAction: function handleBulkAction(selectedIds, params) {
|
||||||
if (
|
if (
|
||||||
this.state.selection === false
|
this.state.selection === false
|
||||||
@ -651,6 +437,7 @@ const Listing = React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSearch: function handleSearch(search) {
|
handleSearch: function handleSearch(search) {
|
||||||
this.setState({
|
this.setState({
|
||||||
search,
|
search,
|
||||||
@ -661,6 +448,7 @@ const Listing = React.createClass({
|
|||||||
this.setParams();
|
this.setParams();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSort: function handleSort(sortBy, sortOrder = 'asc') {
|
handleSort: function handleSort(sortBy, sortOrder = 'asc') {
|
||||||
this.setState({
|
this.setState({
|
||||||
sort_by: sortBy,
|
sort_by: sortBy,
|
||||||
@ -669,6 +457,7 @@ const Listing = React.createClass({
|
|||||||
this.setParams();
|
this.setParams();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSelectItem: function handleSelectItem(id, isChecked) {
|
handleSelectItem: function handleSelectItem(id, isChecked) {
|
||||||
let selectedIds = this.state.selected_ids;
|
let selectedIds = this.state.selected_ids;
|
||||||
let selection = false;
|
let selection = false;
|
||||||
@ -690,6 +479,7 @@ const Listing = React.createClass({
|
|||||||
selected_ids: selectedIds,
|
selected_ids: selectedIds,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSelectItems: function handleSelectItems(isChecked) {
|
handleSelectItems: function handleSelectItems(isChecked) {
|
||||||
if (isChecked === false) {
|
if (isChecked === false) {
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
@ -702,6 +492,7 @@ const Listing = React.createClass({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSelectAll: function handleSelectAll() {
|
handleSelectAll: function handleSelectAll() {
|
||||||
if (this.state.selection === 'all') {
|
if (this.state.selection === 'all') {
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
@ -712,12 +503,14 @@ const Listing = React.createClass({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clearSelection: function clearSelection() {
|
clearSelection: function clearSelection() {
|
||||||
this.setState({
|
this.setState({
|
||||||
selection: false,
|
selection: false,
|
||||||
selected_ids: [],
|
selected_ids: [],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleFilter: function handleFilter(filters) {
|
handleFilter: function handleFilter(filters) {
|
||||||
this.setState({
|
this.setState({
|
||||||
filter: filters,
|
filter: filters,
|
||||||
@ -726,6 +519,7 @@ const Listing = React.createClass({
|
|||||||
this.setParams();
|
this.setParams();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleGroup: function handleGroup(group) {
|
handleGroup: function handleGroup(group) {
|
||||||
// reset search
|
// reset search
|
||||||
jQuery('#search_input').val('');
|
jQuery('#search_input').val('');
|
||||||
@ -739,6 +533,7 @@ const Listing = React.createClass({
|
|||||||
this.setParams();
|
this.setParams();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSetPage: function handleSetPage(page) {
|
handleSetPage: function handleSetPage(page) {
|
||||||
this.setState({
|
this.setState({
|
||||||
page,
|
page,
|
||||||
@ -748,13 +543,16 @@ const Listing = React.createClass({
|
|||||||
this.setParams();
|
this.setParams();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleRenderItem: function handleRenderItem(item, actions) {
|
handleRenderItem: function handleRenderItem(item, actions) {
|
||||||
const render = this.props.onRenderItem(item, actions, this.state.meta);
|
const render = this.props.onRenderItem(item, actions, this.state.meta);
|
||||||
return render.props.children;
|
return render.props.children;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleRefreshItems: function handleRefreshItems() {
|
handleRefreshItems: function handleRefreshItems() {
|
||||||
this.getItems();
|
this.getItems();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function render() {
|
render: function render() {
|
||||||
const items = this.state.items;
|
const items = this.state.items;
|
||||||
const sortBy = this.state.sort_by;
|
const sortBy = this.state.sort_by;
|
||||||
|
68
assets/js/src/listing/listing_column.jsx
Normal file
68
assets/js/src/listing/listing_column.jsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class ListingColumn extends React.Component {
|
||||||
|
handleSort = () => {
|
||||||
|
const sortBy = this.props.column.name;
|
||||||
|
const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
|
||||||
|
this.props.onSort(sortBy, sortOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const classes = classNames(
|
||||||
|
'manage-column',
|
||||||
|
{ 'column-primary': this.props.column.is_primary },
|
||||||
|
{ sortable: this.props.column.sortable },
|
||||||
|
this.props.column.sorted,
|
||||||
|
{ sorted: (this.props.sort_by === this.props.column.name) }
|
||||||
|
);
|
||||||
|
let label;
|
||||||
|
|
||||||
|
if (this.props.column.sortable === true) {
|
||||||
|
label = (
|
||||||
|
<a
|
||||||
|
onClick={this.handleSort}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<span>{ this.props.column.label }</span>
|
||||||
|
<span className="sorting-indicator" />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
label = this.props.column.label;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
role="columnheader"
|
||||||
|
className={classes}
|
||||||
|
id={this.props.column.name}
|
||||||
|
scope="col"
|
||||||
|
width={this.props.column.width || null}
|
||||||
|
>{label}</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListingColumn.propTypes = {
|
||||||
|
column: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
sorted: PropTypes.string,
|
||||||
|
is_primary: PropTypes.bool,
|
||||||
|
sortable: PropTypes.bool,
|
||||||
|
label: PropTypes.string,
|
||||||
|
width: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
|
}).isRequired,
|
||||||
|
sort_by: PropTypes.string,
|
||||||
|
onSort: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ListingColumn.defaultProps = {
|
||||||
|
sort_by: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ListingColumn;
|
215
assets/js/src/listing/listing_item.jsx
Normal file
215
assets/js/src/listing/listing_item.jsx
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
class ListingItem extends React.Component {
|
||||||
|
state = {
|
||||||
|
expanded: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelectItem = (e) => {
|
||||||
|
this.props.onSelectItem(
|
||||||
|
parseInt(e.target.value, 10),
|
||||||
|
e.target.checked
|
||||||
|
);
|
||||||
|
|
||||||
|
return !e.target.checked;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRestoreItem = (id) => {
|
||||||
|
this.props.onRestoreItem(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTrashItem = (id) => {
|
||||||
|
this.props.onTrashItem(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDeleteItem = (id) => {
|
||||||
|
this.props.onDeleteItem(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleToggleItem = () => {
|
||||||
|
this.setState({ expanded: !this.state.expanded });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let checkbox = false;
|
||||||
|
|
||||||
|
if (this.props.is_selectable === true) {
|
||||||
|
checkbox = (
|
||||||
|
<th className="check-column" scope="row">
|
||||||
|
<label className="screen-reader-text" htmlFor={`listing-row-checkbox-${this.props.item.id}`}>{
|
||||||
|
`Select ${this.props.item[this.props.columns[0].name]}`
|
||||||
|
}</label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value={this.props.item.id}
|
||||||
|
checked={
|
||||||
|
this.props.item.selected || this.props.selection === 'all'
|
||||||
|
}
|
||||||
|
onChange={this.handleSelectItem}
|
||||||
|
disabled={this.props.selection === 'all'}
|
||||||
|
id={`listing-row-checkbox-${this.props.item.id}`}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customActions = this.props.item_actions;
|
||||||
|
let itemActions = false;
|
||||||
|
|
||||||
|
if (customActions.length > 0) {
|
||||||
|
let isFirst = true;
|
||||||
|
itemActions = customActions
|
||||||
|
.filter(action => action.display === undefined || action.display(this.props.item))
|
||||||
|
.map((action, index) => {
|
||||||
|
let customAction = null;
|
||||||
|
|
||||||
|
if (action.name === 'trash') {
|
||||||
|
customAction = (
|
||||||
|
<span key={`action-${action.name}`} className="trash">
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
<a
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={() => this.handleTrashItem(this.props.item.id)}
|
||||||
|
>
|
||||||
|
{MailPoet.I18n.t('moveToTrash')}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (action.refresh) {
|
||||||
|
customAction = (
|
||||||
|
<span
|
||||||
|
onClick={this.props.onRefreshItems}
|
||||||
|
key={`action-${action.name}`}
|
||||||
|
className={action.name}
|
||||||
|
role="button"
|
||||||
|
tabIndex={index}
|
||||||
|
>
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
{ action.link(this.props.item) }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (action.link) {
|
||||||
|
customAction = (
|
||||||
|
<span
|
||||||
|
key={`action-${action.name}`}
|
||||||
|
className={action.name}
|
||||||
|
>
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
{ action.link(this.props.item) }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
customAction = (
|
||||||
|
<span
|
||||||
|
key={`action-${action.name}`}
|
||||||
|
className={action.name}
|
||||||
|
>
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
<a
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={
|
||||||
|
(action.onClick !== undefined)
|
||||||
|
? () => action.onClick(this.props.item, this.props.onRefreshItems)
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
>{ action.label }</a>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customAction !== null && isFirst === true) {
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return customAction;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
itemActions = (
|
||||||
|
<span className="edit">
|
||||||
|
<Link to={`/edit/${this.props.item.id}`}>{MailPoet.I18n.t('edit')}</Link>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actions;
|
||||||
|
|
||||||
|
if (this.props.group === 'trash') {
|
||||||
|
actions = (
|
||||||
|
<div>
|
||||||
|
<div className="row-actions">
|
||||||
|
<span>
|
||||||
|
<a
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={() => this.handleRestoreItem(this.props.item.id)}
|
||||||
|
>{MailPoet.I18n.t('restore')}</a>
|
||||||
|
</span>
|
||||||
|
{ ' | ' }
|
||||||
|
<span className="delete">
|
||||||
|
<a
|
||||||
|
className="submitdelete"
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={() => this.handleDeleteItem(this.props.item.id)}
|
||||||
|
>{MailPoet.I18n.t('deletePermanently')}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => this.handleToggleItem(this.props.item.id)}
|
||||||
|
className="toggle-row"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
actions = (
|
||||||
|
<div>
|
||||||
|
<div className="row-actions">
|
||||||
|
{ itemActions }
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => this.handleToggleItem(this.props.item.id)}
|
||||||
|
className="toggle-row"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowClasses = classNames({ 'is-expanded': this.state.expanded });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={rowClasses} data-automation-id={`listing_item_${this.props.item.id}`}>
|
||||||
|
{ checkbox }
|
||||||
|
{ this.props.onRenderItem(this.props.item, actions) }
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListingItem.propTypes = {
|
||||||
|
onSelectItem: PropTypes.func.isRequired,
|
||||||
|
onRestoreItem: PropTypes.func.isRequired,
|
||||||
|
onTrashItem: PropTypes.func.isRequired,
|
||||||
|
onDeleteItem: PropTypes.func.isRequired,
|
||||||
|
is_selectable: PropTypes.bool.isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
selection: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.bool,
|
||||||
|
]).isRequired,
|
||||||
|
item_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onRefreshItems: PropTypes.func.isRequired,
|
||||||
|
onRenderItem: PropTypes.func.isRequired,
|
||||||
|
group: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ListingItem;
|
140
assets/js/src/listing/listing_items.jsx
Normal file
140
assets/js/src/listing/listing_items.jsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import ListingItem from 'listing/listing_item.jsx';
|
||||||
|
|
||||||
|
class ListingItems extends React.Component { // eslint-disable-line react/prefer-stateless-function, max-len
|
||||||
|
render() {
|
||||||
|
if (this.props.items.length === 0) {
|
||||||
|
let message;
|
||||||
|
if (this.props.loading === true) {
|
||||||
|
message = (this.props.messages.onLoadingItems
|
||||||
|
&& this.props.messages.onLoadingItems(this.props.group))
|
||||||
|
|| MailPoet.I18n.t('loadingItems');
|
||||||
|
} else {
|
||||||
|
message = (this.props.messages.onNoItemsFound
|
||||||
|
&& this.props.messages.onNoItemsFound(this.props.group))
|
||||||
|
|| MailPoet.I18n.t('noItemsFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
<tr className="no-items">
|
||||||
|
<td
|
||||||
|
colSpan={
|
||||||
|
this.props.columns.length
|
||||||
|
+ (this.props.is_selectable ? 1 : 0)
|
||||||
|
}
|
||||||
|
className="colspanchange"
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const selectAllClasses = classNames(
|
||||||
|
'mailpoet_select_all',
|
||||||
|
{ mailpoet_hidden: (
|
||||||
|
this.props.selection === false
|
||||||
|
|| (this.props.count <= this.props.limit)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
<tr className={selectAllClasses}>
|
||||||
|
<td colSpan={
|
||||||
|
this.props.columns.length
|
||||||
|
+ (this.props.is_selectable ? 1 : 0)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
(this.props.selection !== 'all')
|
||||||
|
? MailPoet.I18n.t('selectAllLabel')
|
||||||
|
: MailPoet.I18n.t('selectedAllLabel').replace(
|
||||||
|
'%d',
|
||||||
|
this.props.count.toLocaleString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<a
|
||||||
|
onClick={this.props.onSelectAll}
|
||||||
|
href="javascript:;"
|
||||||
|
>{
|
||||||
|
(this.props.selection !== 'all')
|
||||||
|
? MailPoet.I18n.t('selectAllLink')
|
||||||
|
: MailPoet.I18n.t('clearSelection')
|
||||||
|
}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{this.props.items.map((item) => {
|
||||||
|
const renderItem = item;
|
||||||
|
renderItem.id = parseInt(item.id, 10);
|
||||||
|
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
|
||||||
|
let key = `item-${renderItem.id}-${item.id}`;
|
||||||
|
if (typeof this.props.getListingItemKey === 'function') {
|
||||||
|
key = this.props.getListingItemKey(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListingItem
|
||||||
|
columns={this.props.columns}
|
||||||
|
onSelectItem={this.props.onSelectItem}
|
||||||
|
onRenderItem={this.props.onRenderItem}
|
||||||
|
onDeleteItem={this.props.onDeleteItem}
|
||||||
|
onRestoreItem={this.props.onRestoreItem}
|
||||||
|
onTrashItem={this.props.onTrashItem}
|
||||||
|
onRefreshItems={this.props.onRefreshItems}
|
||||||
|
selection={this.props.selection}
|
||||||
|
is_selectable={this.props.is_selectable}
|
||||||
|
item_actions={this.props.item_actions}
|
||||||
|
group={this.props.group}
|
||||||
|
key={key}
|
||||||
|
item={renderItem}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListingItems.propTypes = {
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
loading: PropTypes.bool.isRequired,
|
||||||
|
messages: PropTypes.shape({
|
||||||
|
onLoadingItems: PropTypes.func,
|
||||||
|
onNoItemsFound: PropTypes.func,
|
||||||
|
}).isRequired,
|
||||||
|
group: PropTypes.string.isRequired,
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
is_selectable: PropTypes.bool.isRequired,
|
||||||
|
selection: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.bool,
|
||||||
|
]).isRequired,
|
||||||
|
count: PropTypes.number.isRequired,
|
||||||
|
limit: PropTypes.number.isRequired,
|
||||||
|
onSelectAll: PropTypes.func.isRequired,
|
||||||
|
selected_ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
getListingItemKey: PropTypes.func,
|
||||||
|
onSelectItem: PropTypes.func.isRequired,
|
||||||
|
onRenderItem: PropTypes.func.isRequired,
|
||||||
|
onDeleteItem: PropTypes.func.isRequired,
|
||||||
|
onRestoreItem: PropTypes.func.isRequired,
|
||||||
|
onTrashItem: PropTypes.func.isRequired,
|
||||||
|
onRefreshItems: PropTypes.func.isRequired,
|
||||||
|
item_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ListingItems.defaultProps = {
|
||||||
|
getListingItemKey: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ListingItems;
|
@ -1,56 +1,62 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const ListingPages = React.createClass({
|
class ListingPages extends React.Component {
|
||||||
getInitialState: function getInitialState() {
|
state = {
|
||||||
return {
|
page: null,
|
||||||
page: null,
|
};
|
||||||
};
|
|
||||||
},
|
setPage = (page) => {
|
||||||
setPage: function setPage(page) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
page: null,
|
page: null,
|
||||||
}, () => {
|
}, () => {
|
||||||
this.props.onSetPage(this.constrainPage(page));
|
this.props.onSetPage(this.constrainPage(page));
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
setFirstPage: function setFirstPage() {
|
|
||||||
|
setFirstPage = () => {
|
||||||
this.setPage(1);
|
this.setPage(1);
|
||||||
},
|
};
|
||||||
setLastPage: function setLastPage() {
|
|
||||||
|
setLastPage = () => {
|
||||||
this.setPage(this.getLastPage());
|
this.setPage(this.getLastPage());
|
||||||
},
|
};
|
||||||
setPreviousPage: function setPreviousPage() {
|
|
||||||
|
setPreviousPage = () => {
|
||||||
this.setPage(this.constrainPage(
|
this.setPage(this.constrainPage(
|
||||||
parseInt(this.props.page, 10) - 1)
|
parseInt(this.props.page, 10) - 1)
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
setNextPage: function setNextPage() {
|
|
||||||
|
setNextPage = () => {
|
||||||
this.setPage(this.constrainPage(
|
this.setPage(this.constrainPage(
|
||||||
parseInt(this.props.page, 10) + 1)
|
parseInt(this.props.page, 10) + 1)
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
constrainPage: function constrainPage(page) {
|
|
||||||
return Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
|
getLastPage = () => Math.ceil(this.props.count / this.props.limit);
|
||||||
},
|
|
||||||
handleSetManualPage: function handleSetManualPage(e) {
|
handleSetManualPage = (e) => {
|
||||||
if (e.which === 13) {
|
if (e.which === 13) {
|
||||||
this.setPage(this.state.page);
|
this.setPage(this.state.page);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
handleChangeManualPage: function handleChangeManualPage(e) {
|
|
||||||
|
handleChangeManualPage = (e) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
page: e.target.value,
|
page: e.target.value,
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
handleBlurManualPage: function handleBlurManualPage(e) {
|
|
||||||
|
handleBlurManualPage = (e) => {
|
||||||
this.setPage(e.target.value);
|
this.setPage(e.target.value);
|
||||||
},
|
};
|
||||||
getLastPage: function getLastPage() {
|
|
||||||
return Math.ceil(this.props.count / this.props.limit);
|
constrainPage = page => Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
|
||||||
},
|
|
||||||
render: function render() {
|
render() {
|
||||||
if (this.props.count === 0) {
|
if (this.props.count === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -181,7 +187,17 @@ const ListingPages = React.createClass({
|
|||||||
{ pagination }
|
{ pagination }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
ListingPages.propTypes = {
|
||||||
|
onSetPage: PropTypes.func.isRequired,
|
||||||
|
page: PropTypes.oneOfType([
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.string,
|
||||||
|
]).isRequired,
|
||||||
|
count: PropTypes.number.isRequired,
|
||||||
|
limit: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = ListingPages;
|
module.exports = ListingPages;
|
||||||
|
13
assets/js/src/loading.jsx
Normal file
13
assets/js/src/loading.jsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Loading() {
|
||||||
|
return (
|
||||||
|
<div className="mailpoet_loading">
|
||||||
|
<div className="mailpoet_modal_loading mailpoet_modal_loading_1" />
|
||||||
|
<div className="mailpoet_modal_loading mailpoet_modal_loading_2" />
|
||||||
|
<div className="mailpoet_modal_loading mailpoet_modal_loading_3" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Loading;
|
@ -87,7 +87,7 @@ define('modal', ['mailpoet', 'jquery'],
|
|||||||
'<div class="mailpoet_popup_body clearfix"></div>' +
|
'<div class="mailpoet_popup_body clearfix"></div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>',
|
'</div>',
|
||||||
loading: '<div id="mailpoet_loading" style="display:none;">' +
|
loading: '<div id="mailpoet_loading" class="mailpoet_loading" style="display:none;">' +
|
||||||
'<div id="mailpoet_modal_loading_1" class="mailpoet_modal_loading"></div>' +
|
'<div id="mailpoet_modal_loading_1" class="mailpoet_modal_loading"></div>' +
|
||||||
'<div id="mailpoet_modal_loading_2" class="mailpoet_modal_loading"></div>' +
|
'<div id="mailpoet_modal_loading_2" class="mailpoet_modal_loading"></div>' +
|
||||||
'<div id="mailpoet_modal_loading_3" class="mailpoet_modal_loading"></div>' +
|
'<div id="mailpoet_modal_loading_3" class="mailpoet_modal_loading"></div>' +
|
||||||
|
@ -33,9 +33,9 @@ define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
|
|||||||
jQuery('#logger').html('');
|
jQuery('#logger').html('');
|
||||||
result.split('\n').forEach(function (resultRow) {
|
result.split('\n').forEach(function (resultRow) {
|
||||||
var row = resultRow;
|
var row = resultRow;
|
||||||
if (row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
|
if (row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row.toLowerCase() === MailPoet.I18n.t('import_stopped_by_user').toLowerCase()) {
|
||||||
row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red
|
row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red
|
||||||
} else if (row === MailPoet.I18n.t('import_complete')) { // Test if the import is complete
|
} else if (row.toLowerCase() === MailPoet.I18n.t('import_complete').toLowerCase()) { // Test if the import is complete
|
||||||
jQuery('#import-actions').hide();
|
jQuery('#import-actions').hide();
|
||||||
jQuery('#upgrade-completed').show();
|
jQuery('#upgrade-completed').show();
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import InAppAnnouncement from 'in_app_announcements/in_app_announcement.jsx';
|
import InAppAnnouncement from 'in_app_announcements/in_app_announcement.jsx';
|
||||||
@ -19,8 +20,8 @@ const BackgroundImageAnnouncement = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
BackgroundImageAnnouncement.propTypes = {
|
BackgroundImageAnnouncement.propTypes = {
|
||||||
username: React.PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
videoUrl: React.PropTypes.string.isRequired,
|
videoUrl: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = BackgroundImageAnnouncement;
|
module.exports = BackgroundImageAnnouncement;
|
||||||
|
@ -39,6 +39,7 @@ define([
|
|||||||
defaults: function () {
|
defaults: function () {
|
||||||
return this._getDefaults({
|
return this._getDefaults({
|
||||||
type: 'container',
|
type: 'container',
|
||||||
|
columnLayout: false,
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
image: {
|
image: {
|
||||||
src: null,
|
src: null,
|
||||||
@ -163,6 +164,8 @@ define([
|
|||||||
this.renderOptions = _.defaults(options.renderOptions || {}, {});
|
this.renderOptions = _.defaults(options.renderOptions || {}, {});
|
||||||
},
|
},
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
|
var classIrregular = '';
|
||||||
|
var columnLayout;
|
||||||
this.toolsView = new Module.ContainerBlockToolsView({
|
this.toolsView = new Module.ContainerBlockToolsView({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
tools: {
|
tools: {
|
||||||
@ -183,7 +186,13 @@ define([
|
|||||||
// Sets child container orientation HTML class here,
|
// Sets child container orientation HTML class here,
|
||||||
// as child CollectionView won't have access to model
|
// as child CollectionView won't have access to model
|
||||||
// and will overwrite existing region element instead
|
// and will overwrite existing region element instead
|
||||||
this.$('> .mailpoet_container').attr('class', 'mailpoet_container mailpoet_container_' + this.model.get('orientation'));
|
columnLayout = this.model.get('columnLayout');
|
||||||
|
if (typeof columnLayout === 'string') {
|
||||||
|
classIrregular = 'mailpoet_irregular_width_contents_container column_layout_' + columnLayout;
|
||||||
|
}
|
||||||
|
this.$('> .mailpoet_container').attr('class',
|
||||||
|
'mailpoet_container mailpoet_container_' + this.model.get('orientation') + ' ' + classIrregular
|
||||||
|
);
|
||||||
},
|
},
|
||||||
showTools: function () {
|
showTools: function () {
|
||||||
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
|
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
|
||||||
@ -353,6 +362,48 @@ define([
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Module.TwoColumn12ContainerWidgetView = base.WidgetView.extend({
|
||||||
|
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
|
||||||
|
getTemplate: function () { return window.templates.twoColumn12LayoutInsertion; },
|
||||||
|
behaviors: {
|
||||||
|
DraggableBehavior: {
|
||||||
|
cloneOriginal: true,
|
||||||
|
drop: function () {
|
||||||
|
var block = new Module.ContainerBlockModel({
|
||||||
|
orientation: 'horizontal',
|
||||||
|
blocks: [
|
||||||
|
new Module.ContainerBlockModel(),
|
||||||
|
new Module.ContainerBlockModel()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
block.set('columnLayout', '1_2');
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Module.TwoColumn21ContainerWidgetView = base.WidgetView.extend({
|
||||||
|
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
|
||||||
|
getTemplate: function () { return window.templates.twoColumn21LayoutInsertion; },
|
||||||
|
behaviors: {
|
||||||
|
DraggableBehavior: {
|
||||||
|
cloneOriginal: true,
|
||||||
|
drop: function () {
|
||||||
|
var block = new Module.ContainerBlockModel({
|
||||||
|
orientation: 'horizontal',
|
||||||
|
blocks: [
|
||||||
|
new Module.ContainerBlockModel(),
|
||||||
|
new Module.ContainerBlockModel()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
block.set('columnLayout', '2_1');
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
App.on('before:start', function (BeforeStartApp) {
|
App.on('before:start', function (BeforeStartApp) {
|
||||||
BeforeStartApp.registerBlockType('container', {
|
BeforeStartApp.registerBlockType('container', {
|
||||||
blockModel: Module.ContainerBlockModel,
|
blockModel: Module.ContainerBlockModel,
|
||||||
@ -376,6 +427,18 @@ define([
|
|||||||
priority: 100,
|
priority: 100,
|
||||||
widgetView: Module.ThreeColumnContainerWidgetView
|
widgetView: Module.ThreeColumnContainerWidgetView
|
||||||
});
|
});
|
||||||
|
|
||||||
|
BeforeStartApp.registerLayoutWidget({
|
||||||
|
name: 'twoColumn12Layout',
|
||||||
|
priority: 100,
|
||||||
|
widgetView: Module.TwoColumn12ContainerWidgetView
|
||||||
|
});
|
||||||
|
|
||||||
|
BeforeStartApp.registerLayoutWidget({
|
||||||
|
name: 'twoColumn21Layout',
|
||||||
|
priority: 100,
|
||||||
|
widgetView: Module.TwoColumn21ContainerWidgetView
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return Module;
|
return Module;
|
||||||
|
@ -266,7 +266,6 @@ define([
|
|||||||
},
|
},
|
||||||
validateNewsletter: function (jsonObject) {
|
validateNewsletter: function (jsonObject) {
|
||||||
var body = '';
|
var body = '';
|
||||||
var contents;
|
|
||||||
if (!App._contentContainer.isValid()) {
|
if (!App._contentContainer.isValid()) {
|
||||||
this.showValidationError(App._contentContainer.validationError);
|
this.showValidationError(App._contentContainer.validationError);
|
||||||
return;
|
return;
|
||||||
@ -282,10 +281,9 @@ define([
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
contents = JSON.stringify(jsonObject);
|
|
||||||
if ((App.getNewsletter().get('type') === 'notification') &&
|
if ((App.getNewsletter().get('type') === 'notification') &&
|
||||||
contents.indexOf('"type":"automatedLatestContent"') < 0 &&
|
body.indexOf('"type":"automatedLatestContent"') < 0 &&
|
||||||
contents.indexOf('"type":"automatedLatestContentLayout"') < 0
|
body.indexOf('"type":"automatedLatestContentLayout"') < 0
|
||||||
) {
|
) {
|
||||||
this.showValidationError(MailPoet.I18n.t('automatedLatestContentMissing'));
|
this.showValidationError(MailPoet.I18n.t('automatedLatestContentMissing'));
|
||||||
return;
|
return;
|
||||||
|
@ -10,8 +10,7 @@ const displayTutorial = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MailPoet.Modal.popup({
|
MailPoet.Modal.popup({
|
||||||
title: MailPoet.I18n.t('tutorialVideoTitle'),
|
template: `<div class="mailpoet_drag_and_drop_tutorial"><h2>${MailPoet.I18n.t('tutorialVideoTitle')}</h2><video style="height:640px;" src="${window.config.dragDemoUrl}" controls autoplay></video></div>`,
|
||||||
template: `<video style="height:640px;" src="${window.config.dragDemoUrl}" controls autoplay></video>`,
|
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
|
||||||
const Breadcrumb = React.createClass({
|
class Breadcrumb extends React.Component {
|
||||||
getInitialState: function getInitialState() {
|
constructor(props) {
|
||||||
const steps = this.props.steps || [
|
super(props);
|
||||||
|
const steps = props.steps || [
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
label: MailPoet.I18n.t('selectType'),
|
label: MailPoet.I18n.t('selectType'),
|
||||||
@ -24,12 +26,14 @@ const Breadcrumb = React.createClass({
|
|||||||
label: MailPoet.I18n.t('send'),
|
label: MailPoet.I18n.t('send'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return {
|
|
||||||
|
this.state = {
|
||||||
step: null,
|
step: null,
|
||||||
steps,
|
steps,
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
const steps = this.state.steps.map((step, index) => {
|
const steps = this.state.steps.map((step, index) => {
|
||||||
const stepClasses = classNames(
|
const stepClasses = classNames(
|
||||||
{ mailpoet_current: (this.props.step === step.name) }
|
{ mailpoet_current: (this.props.step === step.name) }
|
||||||
@ -58,8 +62,17 @@ const Breadcrumb = React.createClass({
|
|||||||
{ steps }
|
{ steps }
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
Breadcrumb.propTypes = {
|
||||||
|
steps: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
step: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
Breadcrumb.defaultProps = {
|
||||||
|
steps: undefined,
|
||||||
|
step: null,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Breadcrumb;
|
module.exports = Breadcrumb;
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import createReactClass from 'create-react-class';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Listing from 'listing/listing.jsx';
|
import Listing from 'listing/listing.jsx';
|
||||||
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
||||||
@ -157,8 +159,17 @@ const newsletterActions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const NewsletterListNotification = React.createClass({
|
const NewsletterListNotification = createReactClass({ // eslint-disable-line react/prefer-es6-class
|
||||||
|
|
||||||
|
displayName: 'NewsletterListNotification',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
},
|
||||||
|
|
||||||
mixins: [MailerMixin, CronMixin],
|
mixins: [MailerMixin, CronMixin],
|
||||||
|
|
||||||
updateStatus: function updateStatus(e) {
|
updateStatus: function updateStatus(e) {
|
||||||
// make the event persist so that we can still override the selected value
|
// make the event persist so that we can still override the selected value
|
||||||
// in the ajax callback
|
// in the ajax callback
|
||||||
@ -185,6 +196,7 @@ const NewsletterListNotification = React.createClass({
|
|||||||
e.target.value = response.status;
|
e.target.value = response.status;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderStatus: function renderStatus(newsletter) {
|
renderStatus: function renderStatus(newsletter) {
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
@ -197,6 +209,7 @@ const NewsletterListNotification = React.createClass({
|
|||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSettings: function renderSettings(newsletter) {
|
renderSettings: function renderSettings(newsletter) {
|
||||||
let sendingFrequency;
|
let sendingFrequency;
|
||||||
|
|
||||||
@ -265,6 +278,7 @@ const NewsletterListNotification = React.createClass({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHistoryLink: function renderHistoryLink(newsletter) {
|
renderHistoryLink: function renderHistoryLink(newsletter) {
|
||||||
const childrenCount = Number((newsletter.children_count));
|
const childrenCount = Number((newsletter.children_count));
|
||||||
if (childrenCount === 0) {
|
if (childrenCount === 0) {
|
||||||
@ -274,10 +288,12 @@ const NewsletterListNotification = React.createClass({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
data-automation-id={`history-${newsletter.id}`}
|
||||||
to={`/notification/history/${newsletter.id}`}
|
to={`/notification/history/${newsletter.id}`}
|
||||||
>{ MailPoet.I18n.t('viewHistory') }</Link>
|
>{ MailPoet.I18n.t('viewHistory') }</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderItem: function renderItem(newsletter, actions) {
|
renderItem: function renderItem(newsletter, actions) {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -311,6 +327,7 @@ const NewsletterListNotification = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function render() {
|
render: function render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import createReactClass from 'create-react-class';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Hooks from 'wp-js-hooks';
|
import Hooks from 'wp-js-hooks';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Listing from 'listing/listing.jsx';
|
import Listing from 'listing/listing.jsx';
|
||||||
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
||||||
@ -57,8 +59,16 @@ let newsletterActions = [
|
|||||||
Hooks.addFilter('mailpoet_newsletters_listings_notification_history_actions', StatisticsMixin.addStatsCTAAction);
|
Hooks.addFilter('mailpoet_newsletters_listings_notification_history_actions', StatisticsMixin.addStatsCTAAction);
|
||||||
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_notification_history_actions', newsletterActions);
|
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_notification_history_actions', newsletterActions);
|
||||||
|
|
||||||
const NewsletterListNotificationHistory = React.createClass({
|
const NewsletterListNotificationHistory = createReactClass({ // eslint-disable-line react/prefer-es6-class, max-len
|
||||||
|
displayName: 'NewsletterListNotificationHistory',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
},
|
||||||
|
|
||||||
mixins: [QueueMixin, StatisticsMixin, MailerMixin, CronMixin],
|
mixins: [QueueMixin, StatisticsMixin, MailerMixin, CronMixin],
|
||||||
|
|
||||||
renderItem: function renderItem(newsletter, actions, meta) {
|
renderItem: function renderItem(newsletter, actions, meta) {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -96,6 +106,7 @@ const NewsletterListNotificationHistory = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function render() {
|
render: function render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import createReactClass from 'create-react-class';
|
||||||
import { confirmAlert } from 'react-confirm-alert';
|
import { confirmAlert } from 'react-confirm-alert';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Hooks from 'wp-js-hooks';
|
import Hooks from 'wp-js-hooks';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Listing from 'listing/listing.jsx';
|
import Listing from 'listing/listing.jsx';
|
||||||
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
||||||
@ -172,8 +174,16 @@ let newsletterActions = [
|
|||||||
Hooks.addFilter('mailpoet_newsletters_listings_standard_actions', StatisticsMixin.addStatsCTAAction);
|
Hooks.addFilter('mailpoet_newsletters_listings_standard_actions', StatisticsMixin.addStatsCTAAction);
|
||||||
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_standard_actions', newsletterActions);
|
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_standard_actions', newsletterActions);
|
||||||
|
|
||||||
const NewsletterListStandard = React.createClass({
|
const NewsletterListStandard = createReactClass({ // eslint-disable-line react/prefer-es6-class
|
||||||
|
displayName: 'NewsletterListStandard',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
},
|
||||||
|
|
||||||
mixins: [QueueMixin, StatisticsMixin, MailerMixin, CronMixin],
|
mixins: [QueueMixin, StatisticsMixin, MailerMixin, CronMixin],
|
||||||
|
|
||||||
renderItem: function renderItem(newsletter, actions, meta) {
|
renderItem: function renderItem(newsletter, actions, meta) {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -212,6 +222,7 @@ const NewsletterListStandard = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function render() {
|
render: function render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -3,30 +3,30 @@ import { Link } from 'react-router';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Hooks from 'wp-js-hooks';
|
import Hooks from 'wp-js-hooks';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class ListingTabs extends React.Component {
|
||||||
|
state = {
|
||||||
|
tab: null,
|
||||||
|
tabs: Hooks.applyFilters('mailpoet_newsletters_listings_tabs', [
|
||||||
|
{
|
||||||
|
name: 'standard',
|
||||||
|
label: MailPoet.I18n.t('tabStandardTitle'),
|
||||||
|
link: '/standard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'welcome',
|
||||||
|
label: MailPoet.I18n.t('tabWelcomeTitle'),
|
||||||
|
link: '/welcome',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notification',
|
||||||
|
label: MailPoet.I18n.t('tabNotificationTitle'),
|
||||||
|
link: '/notification',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
const ListingTabs = React.createClass({
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
tab: null,
|
|
||||||
tabs: Hooks.applyFilters('mailpoet_newsletters_listings_tabs', [
|
|
||||||
{
|
|
||||||
name: 'standard',
|
|
||||||
label: MailPoet.I18n.t('tabStandardTitle'),
|
|
||||||
link: '/standard',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'welcome',
|
|
||||||
label: MailPoet.I18n.t('tabWelcomeTitle'),
|
|
||||||
link: '/welcome',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'notification',
|
|
||||||
label: MailPoet.I18n.t('tabNotificationTitle'),
|
|
||||||
link: '/notification',
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
render() {
|
render() {
|
||||||
const tabs = this.state.tabs.map((tab) => {
|
const tabs = this.state.tabs.map((tab) => {
|
||||||
const tabClasses = classNames(
|
const tabClasses = classNames(
|
||||||
@ -38,6 +38,7 @@ const ListingTabs = React.createClass({
|
|||||||
<Link
|
<Link
|
||||||
key={`tab-${tab.label}`}
|
key={`tab-${tab.label}`}
|
||||||
className={tabClasses}
|
className={tabClasses}
|
||||||
|
data-automation-id={`tab-${tab.label}`}
|
||||||
to={tab.link}
|
to={tab.link}
|
||||||
onClick={() => MailPoet.trackEvent(`Tab Emails > ${tab.name} clicked`,
|
onClick={() => MailPoet.trackEvent(`Tab Emails > ${tab.name} clicked`,
|
||||||
{ 'MailPoet Free version': window.mailpoet_version }
|
{ 'MailPoet Free version': window.mailpoet_version }
|
||||||
@ -51,7 +52,11 @@ const ListingTabs = React.createClass({
|
|||||||
{ tabs }
|
{ tabs }
|
||||||
</h2>
|
</h2>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
ListingTabs.propTypes = {
|
||||||
|
tab: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = ListingTabs;
|
module.exports = ListingTabs;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import createReactClass from 'create-react-class';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Listing from 'listing/listing.jsx';
|
import Listing from 'listing/listing.jsx';
|
||||||
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
import ListingTabs from 'newsletters/listings/tabs.jsx';
|
||||||
@ -153,8 +155,16 @@ let newsletterActions = [
|
|||||||
Hooks.addFilter('mailpoet_newsletters_listings_welcome_notification_actions', StatisticsMixin.addStatsCTAAction);
|
Hooks.addFilter('mailpoet_newsletters_listings_welcome_notification_actions', StatisticsMixin.addStatsCTAAction);
|
||||||
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_welcome_notification_actions', newsletterActions);
|
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_welcome_notification_actions', newsletterActions);
|
||||||
|
|
||||||
const NewsletterListWelcome = React.createClass({
|
const NewsletterListWelcome = createReactClass({ // eslint-disable-line react/prefer-es6-class
|
||||||
|
displayName: 'NewsletterListWelcome',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
},
|
||||||
|
|
||||||
mixins: [StatisticsMixin, MailerMixin, CronMixin],
|
mixins: [StatisticsMixin, MailerMixin, CronMixin],
|
||||||
|
|
||||||
updateStatus: function updateStatus(e) {
|
updateStatus: function updateStatus(e) {
|
||||||
// make the event persist so that we can still override the selected value
|
// make the event persist so that we can still override the selected value
|
||||||
// in the ajax callback
|
// in the ajax callback
|
||||||
@ -181,6 +191,7 @@ const NewsletterListWelcome = React.createClass({
|
|||||||
e.target.value = response.status;
|
e.target.value = response.status;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderStatus: function renderStatus(newsletter) {
|
renderStatus: function renderStatus(newsletter) {
|
||||||
const totalSent = (parseInt(newsletter.total_sent, 10)) ?
|
const totalSent = (parseInt(newsletter.total_sent, 10)) ?
|
||||||
MailPoet.I18n.t('sentToXSubscribers')
|
MailPoet.I18n.t('sentToXSubscribers')
|
||||||
@ -203,6 +214,7 @@ const NewsletterListWelcome = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSettings: function renderSettings(newsletter) {
|
renderSettings: function renderSettings(newsletter) {
|
||||||
let sendingEvent;
|
let sendingEvent;
|
||||||
let sendingDelay;
|
let sendingDelay;
|
||||||
@ -278,6 +290,7 @@ const NewsletterListWelcome = React.createClass({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderItem: function renderItem(newsletter, actions) {
|
renderItem: function renderItem(newsletter, actions) {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -316,6 +329,7 @@ const NewsletterListWelcome = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function render() {
|
render: function render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
|
import { IndexRedirect, Route, Router, useRouterHistory } from 'react-router';
|
||||||
import { createHashHistory } from 'history';
|
import { createHashHistory } from 'history';
|
||||||
import Hooks from 'wp-js-hooks';
|
import Hooks from 'wp-js-hooks';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import NewsletterTypes from 'newsletters/types.jsx';
|
import NewsletterTypes from 'newsletters/types.jsx';
|
||||||
import NewsletterTemplates from 'newsletters/templates.jsx';
|
import NewsletterTemplates from 'newsletters/templates.jsx';
|
||||||
import NewsletterSend from 'newsletters/send.jsx';
|
import NewsletterSend from 'newsletters/send.jsx';
|
||||||
|
import NewsletterCongratulate from 'newsletters/send/congratulate/congratulate.jsx';
|
||||||
import NewsletterTypeStandard from 'newsletters/types/standard.jsx';
|
import NewsletterTypeStandard from 'newsletters/types/standard.jsx';
|
||||||
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx';
|
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx';
|
||||||
import NewsletterTypeWelcome from 'newsletters/types/welcome/welcome.jsx';
|
import NewsletterTypeWelcome from 'newsletters/types/welcome/welcome.jsx';
|
||||||
@ -19,11 +21,15 @@ import NewsletterListNotificationHistory from 'newsletters/listings/notification
|
|||||||
|
|
||||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||||
|
|
||||||
const App = React.createClass({
|
class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
const container = document.getElementById('newsletters_container');
|
const container = document.getElementById('newsletters_container');
|
||||||
|
|
||||||
@ -84,6 +90,11 @@ if (container) {
|
|||||||
path: 'template/:id',
|
path: 'template/:id',
|
||||||
component: NewsletterTemplates,
|
component: NewsletterTemplates,
|
||||||
},
|
},
|
||||||
|
/* congratulate */
|
||||||
|
{
|
||||||
|
path: 'send/congratulate/:id',
|
||||||
|
component: NewsletterCongratulate,
|
||||||
|
},
|
||||||
/* Sending options */
|
/* Sending options */
|
||||||
{
|
{
|
||||||
path: 'send/:id',
|
path: 'send/:id',
|
||||||
@ -93,7 +104,7 @@ if (container) {
|
|||||||
|
|
||||||
routes = Hooks.applyFilters('mailpoet_newsletters_before_router', [...routes, ...getAutomaticEmailsRoutes()]);
|
routes = Hooks.applyFilters('mailpoet_newsletters_before_router', [...routes, ...getAutomaticEmailsRoutes()]);
|
||||||
|
|
||||||
const mailpoetListing = ReactDOM.render(( // eslint-disable-line react/no-render-return-value
|
window.mailpoet_listing = ReactDOM.render(( // eslint-disable-line react/no-render-return-value
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Route path="/" component={App}>
|
<Route path="/" component={App}>
|
||||||
<IndexRedirect to="standard" />
|
<IndexRedirect to="standard" />
|
||||||
@ -110,6 +121,4 @@ if (container) {
|
|||||||
</Route>
|
</Route>
|
||||||
</Router>
|
</Router>
|
||||||
), container);
|
), container);
|
||||||
|
|
||||||
window.mailpoet_listing = mailpoetListing;
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import createReactClass from 'create-react-class';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
||||||
@ -10,11 +11,21 @@ import HelpTooltip from 'help-tooltip.jsx';
|
|||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
import { fromUrl } from 'common/thumbnail.jsx';
|
import { fromUrl } from 'common/thumbnail.jsx';
|
||||||
import Hooks from 'wp-js-hooks';
|
import Hooks from 'wp-js-hooks';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const NewsletterSend = React.createClass({
|
const NewsletterSend = createReactClass({ // eslint-disable-line react/prefer-es6-class
|
||||||
contextTypes: {
|
displayName: 'NewsletterSend',
|
||||||
router: React.PropTypes.object.isRequired,
|
|
||||||
|
propTypes: {
|
||||||
|
params: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
contextTypes: {
|
||||||
|
router: PropTypes.object.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function getInitialState() {
|
getInitialState: function getInitialState() {
|
||||||
return {
|
return {
|
||||||
fields: [],
|
fields: [],
|
||||||
@ -22,14 +33,26 @@ const NewsletterSend = React.createClass({
|
|||||||
loading: true,
|
loading: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function componentDidMount() {
|
||||||
|
this.loadItem(this.props.params.id);
|
||||||
|
jQuery('#mailpoet_newsletter').parsley();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function componentWillReceiveProps(props) {
|
||||||
|
this.loadItem(props.params.id);
|
||||||
|
},
|
||||||
|
|
||||||
getFieldsByNewsletter: function getFieldsByNewsletter(newsletter) {
|
getFieldsByNewsletter: function getFieldsByNewsletter(newsletter) {
|
||||||
const type = this.getSubtype(newsletter);
|
const type = this.getSubtype(newsletter);
|
||||||
return type.getFields(newsletter);
|
return type.getFields(newsletter);
|
||||||
},
|
},
|
||||||
|
|
||||||
getSendButtonOptions: function getSendButtonOptions() {
|
getSendButtonOptions: function getSendButtonOptions() {
|
||||||
const type = this.getSubtype(this.state.item);
|
const type = this.getSubtype(this.state.item);
|
||||||
return type.getSendButtonOptions(this.state.item);
|
return type.getSendButtonOptions(this.state.item);
|
||||||
},
|
},
|
||||||
|
|
||||||
getSubtype: function getSubtype(newsletter) {
|
getSubtype: function getSubtype(newsletter) {
|
||||||
switch (newsletter.type) {
|
switch (newsletter.type) {
|
||||||
case 'notification': return NotificationNewsletterFields;
|
case 'notification': return NotificationNewsletterFields;
|
||||||
@ -37,16 +60,11 @@ const NewsletterSend = React.createClass({
|
|||||||
default: return Hooks.applyFilters('mailpoet_newsletters_send_newsletter_fields', StandardNewsletterFields, newsletter);
|
default: return Hooks.applyFilters('mailpoet_newsletters_send_newsletter_fields', StandardNewsletterFields, newsletter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
isValid: function isValid() {
|
isValid: function isValid() {
|
||||||
return jQuery('#mailpoet_newsletter').parsley().isValid();
|
return jQuery('#mailpoet_newsletter').parsley().isValid();
|
||||||
},
|
},
|
||||||
componentDidMount: function componentDidMount() {
|
|
||||||
this.loadItem(this.props.params.id);
|
|
||||||
jQuery('#mailpoet_newsletter').parsley();
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function componentWillReceiveProps(props) {
|
|
||||||
this.loadItem(props.params.id);
|
|
||||||
},
|
|
||||||
loadItem: function loadItem(id) {
|
loadItem: function loadItem(id) {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
@ -72,6 +90,7 @@ const NewsletterSend = React.createClass({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
saveTemplate: function saveTemplate(response, done) {
|
saveTemplate: function saveTemplate(response, done) {
|
||||||
fromUrl(response.meta.preview_url)
|
fromUrl(response.meta.preview_url)
|
||||||
.then((thumbnail) => {
|
.then((thumbnail) => {
|
||||||
@ -97,6 +116,7 @@ const NewsletterSend = React.createClass({
|
|||||||
this.showError({ errors: [err] });
|
this.showError({ errors: [err] });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSend: function handleSend(e) {
|
handleSend: function handleSend(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -124,6 +144,7 @@ const NewsletterSend = React.createClass({
|
|||||||
MailPoet.Modal.loading(false);
|
MailPoet.Modal.loading(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendNewsletter: function sendNewsletter(newsletter) {
|
sendNewsletter: function sendNewsletter(newsletter) {
|
||||||
return MailPoet.Ajax.post(
|
return MailPoet.Ajax.post(
|
||||||
Hooks.applyFilters(
|
Hooks.applyFilters(
|
||||||
@ -141,6 +162,11 @@ const NewsletterSend = React.createClass({
|
|||||||
).done((response) => {
|
).done((response) => {
|
||||||
// save template in recently sent category
|
// save template in recently sent category
|
||||||
this.saveTemplate(newsletter, () => {
|
this.saveTemplate(newsletter, () => {
|
||||||
|
if (window.mailpoet_show_congratulate_after_first_newsletter) {
|
||||||
|
MailPoet.Modal.loading(false);
|
||||||
|
this.context.router.push(`/send/congratulate/${this.state.item.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// redirect to listing based on newsletter type
|
// redirect to listing based on newsletter type
|
||||||
this.context.router.push(Hooks.applyFilters('mailpoet_newsletters_send_server_request_response_redirect', `/${this.state.item.type || ''}`, this.state.item));
|
this.context.router.push(Hooks.applyFilters('mailpoet_newsletters_send_server_request_response_redirect', `/${this.state.item.type || ''}`, this.state.item));
|
||||||
const customResponse = Hooks.applyFilters('mailpoet_newsletters_send_server_request_response', this.state.item, response);
|
const customResponse = Hooks.applyFilters('mailpoet_newsletters_send_server_request_response', this.state.item, response);
|
||||||
@ -171,6 +197,7 @@ const NewsletterSend = React.createClass({
|
|||||||
MailPoet.Modal.loading(false);
|
MailPoet.Modal.loading(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
activateNewsletter: function activateEmail(newsletter) {
|
activateNewsletter: function activateEmail(newsletter) {
|
||||||
return MailPoet.Ajax.post({
|
return MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
@ -183,6 +210,11 @@ const NewsletterSend = React.createClass({
|
|||||||
}).done((response) => {
|
}).done((response) => {
|
||||||
// save template in recently sent category
|
// save template in recently sent category
|
||||||
this.saveTemplate(newsletter, () => {
|
this.saveTemplate(newsletter, () => {
|
||||||
|
if (window.mailpoet_show_congratulate_after_first_newsletter) {
|
||||||
|
MailPoet.Modal.loading(false);
|
||||||
|
this.context.router.push(`/send/congratulate/${this.state.item.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// redirect to listing based on newsletter type
|
// redirect to listing based on newsletter type
|
||||||
this.context.router.push(`/${this.state.item.type || ''}`);
|
this.context.router.push(`/${this.state.item.type || ''}`);
|
||||||
const opts = this.state.item.options;
|
const opts = this.state.item.options;
|
||||||
@ -213,6 +245,7 @@ const NewsletterSend = React.createClass({
|
|||||||
MailPoet.Modal.loading(false);
|
MailPoet.Modal.loading(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleResume: function handleResume(e) {
|
handleResume: function handleResume(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
@ -251,6 +284,7 @@ const NewsletterSend = React.createClass({
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSave: function handleSave(e) {
|
handleSave: function handleSave(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -264,6 +298,7 @@ const NewsletterSend = React.createClass({
|
|||||||
this.showError(err);
|
this.showError(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleRedirectToDesign: function handleRedirectToDesign(e) {
|
handleRedirectToDesign: function handleRedirectToDesign(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const redirectTo = e.target.href;
|
const redirectTo = e.target.href;
|
||||||
@ -278,6 +313,7 @@ const NewsletterSend = React.createClass({
|
|||||||
this.showError(err);
|
this.showError(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
saveNewsletter: function saveNewsletter() {
|
saveNewsletter: function saveNewsletter() {
|
||||||
const data = this.state.item;
|
const data = this.state.item;
|
||||||
data.queue = undefined;
|
data.queue = undefined;
|
||||||
@ -298,10 +334,9 @@ const NewsletterSend = React.createClass({
|
|||||||
endpoint: 'newsletters',
|
endpoint: 'newsletters',
|
||||||
action: 'save',
|
action: 'save',
|
||||||
data: newsletterData,
|
data: newsletterData,
|
||||||
}).always(() => {
|
|
||||||
this.setState({ loading: false });
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
showError: (response) => {
|
showError: (response) => {
|
||||||
if (response.errors.length > 0) {
|
if (response.errors.length > 0) {
|
||||||
MailPoet.Notice.error(
|
MailPoet.Notice.error(
|
||||||
@ -310,6 +345,7 @@ const NewsletterSend = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleFormChange: function handleFormChange(e) {
|
handleFormChange: function handleFormChange(e) {
|
||||||
const item = this.state.item;
|
const item = this.state.item;
|
||||||
const field = e.target.name;
|
const field = e.target.name;
|
||||||
@ -321,6 +357,7 @@ const NewsletterSend = React.createClass({
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function render() {
|
render: function render() {
|
||||||
const isPaused = this.state.item.status === 'sending'
|
const isPaused = this.state.item.status === 'sending'
|
||||||
&& this.state.item.queue
|
&& this.state.item.queue
|
||||||
|
136
assets/js/src/newsletters/send/congratulate/congratulate.jsx
Normal file
136
assets/js/src/newsletters/send/congratulate/congratulate.jsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import Success from './success.jsx';
|
||||||
|
import Fail from './fail.jsx';
|
||||||
|
import Loading from './loading.jsx';
|
||||||
|
|
||||||
|
const SECONDS_WAITING_FOR_SUCCESS = 20;
|
||||||
|
const SECONDS_MINIMUIM_LOADING_SCREEN_DISPLAYED = 6;
|
||||||
|
|
||||||
|
function successPageClosed() {
|
||||||
|
return MailPoet.Ajax.post({
|
||||||
|
api_version: window.mailpoet_api_version,
|
||||||
|
endpoint: 'settings',
|
||||||
|
action: 'set',
|
||||||
|
data: { show_congratulate_after_first_newsletter: false },
|
||||||
|
}).always(() => {
|
||||||
|
window.location = window.mailpoet_main_page;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSuccess(newsletter, testingPassed) {
|
||||||
|
if (testingPassed) {
|
||||||
|
MailPoet.trackEvent('Cron testing done', {
|
||||||
|
'Cron is working': 'true',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (<Success
|
||||||
|
illustrationImageUrl={window.mailpoet_congratulations_success_image}
|
||||||
|
successClicked={successPageClosed}
|
||||||
|
newsletter={newsletter}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFail() {
|
||||||
|
MailPoet.trackEvent('Cron testing done', {
|
||||||
|
'Cron is working': 'false',
|
||||||
|
});
|
||||||
|
return (<Fail
|
||||||
|
failClicked={() => {
|
||||||
|
window.location = window.mailpoet_main_page;
|
||||||
|
}}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLoading(showRichLoadingScreen) {
|
||||||
|
return (<Loading
|
||||||
|
illustrationImageUrl={window.mailpoet_congratulations_loading_image}
|
||||||
|
successClicked={successPageClosed}
|
||||||
|
showRichLoadingScreen={showRichLoadingScreen}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Congratulate extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
fail: false,
|
||||||
|
newsletter: null,
|
||||||
|
testingPassed: false,
|
||||||
|
timeStart: moment(),
|
||||||
|
minimumLoadingTimePassed: false,
|
||||||
|
};
|
||||||
|
this.tick = this.tick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.loadNewsletter(this.props.params.id);
|
||||||
|
this.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(props) {
|
||||||
|
this.loadNewsletter(props.params.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
if (moment().subtract(SECONDS_WAITING_FOR_SUCCESS, 'second').isAfter(this.state.timeStart)) {
|
||||||
|
this.setState({ error: true, loading: false });
|
||||||
|
}
|
||||||
|
if (this.state.loading) {
|
||||||
|
this.loadNewsletter(this.props.params.id);
|
||||||
|
}
|
||||||
|
if (moment().subtract(SECONDS_MINIMUIM_LOADING_SCREEN_DISPLAYED, 'seconds').isAfter(this.state.timeStart)) {
|
||||||
|
this.setState({ minimumLoadingTimePassed: true });
|
||||||
|
}
|
||||||
|
if (this.state.loading || !this.state.minimumLoadingTimePassed) {
|
||||||
|
setTimeout(this.tick, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNewsletter(id) {
|
||||||
|
MailPoet.Ajax.post({
|
||||||
|
api_version: window.mailpoet_api_version,
|
||||||
|
endpoint: 'newsletters',
|
||||||
|
action: 'get',
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.done(response => this.newsletterLoaded(response.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
newsletterLoaded(newsletter) {
|
||||||
|
if ((newsletter.type !== 'standard') || (newsletter.status === 'scheduled')) {
|
||||||
|
this.setState({ newsletter, loading: false, minimumLoadingTimePassed: true });
|
||||||
|
} else if ((newsletter.status === 'sent') || (newsletter.status === 'sending')) {
|
||||||
|
this.setState({ newsletter, loading: false, testingPassed: true });
|
||||||
|
} else {
|
||||||
|
this.setState({ newsletter });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
|
if (this.state.loading || !this.state.minimumLoadingTimePassed) {
|
||||||
|
return renderLoading(!!this.state.newsletter);
|
||||||
|
} else if (this.state.error) {
|
||||||
|
return renderFail();
|
||||||
|
}
|
||||||
|
return renderSuccess(this.state.newsletter, this.state.testingPassed);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<div className="newsletter_congratulate_page">{this.renderContent()}</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Congratulate.propTypes = {
|
||||||
|
params: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Congratulate;
|
34
assets/js/src/newsletters/send/congratulate/fail.jsx
Normal file
34
assets/js/src/newsletters/send/congratulate/fail.jsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ReactStringReplace from 'react-string-replace';
|
||||||
|
import MailPoet from '../../../mailpoet';
|
||||||
|
|
||||||
|
function Fail(props) {
|
||||||
|
return (
|
||||||
|
<div className="mailpoet_centered">
|
||||||
|
<h1>{MailPoet.I18n.t('congratulationsSendFailHeader')}</h1>
|
||||||
|
<p>
|
||||||
|
{ ReactStringReplace(
|
||||||
|
MailPoet.I18n.t('congratulationsSendFailExplain'),
|
||||||
|
/\[link\](.*?)\[\/link\]/g,
|
||||||
|
(match, i) => (
|
||||||
|
<a
|
||||||
|
key={i}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://kb.mailpoet.com/article/231-sending-does-not-work"
|
||||||
|
>{ match }</a>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<button className="button" onClick={props.failClicked}>{MailPoet.I18n.t('close')}</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Fail.propTypes = {
|
||||||
|
failClicked: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Fail;
|
32
assets/js/src/newsletters/send/congratulate/loading.jsx
Normal file
32
assets/js/src/newsletters/send/congratulate/loading.jsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from '../../../mailpoet';
|
||||||
|
import LoadingDots from '../../../loading.jsx';
|
||||||
|
|
||||||
|
function renderRichData(showRichData, illustrationImageUrl) {
|
||||||
|
if (showRichData) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="mailpoet_newsletter_loading_header">{MailPoet.I18n.t('congratulationsLoadingHeader')}</h1>
|
||||||
|
<img src={illustrationImageUrl} alt="" width="800px" height="266px" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (<div />);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Loading(props) {
|
||||||
|
return (
|
||||||
|
<div className="mailpoet_newsletter_loading">
|
||||||
|
<LoadingDots />
|
||||||
|
{renderRichData(props.showRichLoadingScreen, props.illustrationImageUrl)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading.propTypes = {
|
||||||
|
illustrationImageUrl: PropTypes.string.isRequired,
|
||||||
|
showRichLoadingScreen: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Loading;
|
38
assets/js/src/newsletters/send/congratulate/success.jsx
Normal file
38
assets/js/src/newsletters/send/congratulate/success.jsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from '../../../mailpoet';
|
||||||
|
|
||||||
|
function renderHeader(newsletter) {
|
||||||
|
if (newsletter.type === 'welcome') {
|
||||||
|
return MailPoet.I18n.t('congratulationsWelcomeEmailSuccessHeader');
|
||||||
|
} else if (newsletter.type === 'notification') {
|
||||||
|
return MailPoet.I18n.t('congratulationsPostNotificationSuccessHeader');
|
||||||
|
} else if (newsletter.type === 'automatic') {
|
||||||
|
return MailPoet.I18n.t('congratulationsWooSuccessHeader');
|
||||||
|
} else if (newsletter.status === 'scheduled') {
|
||||||
|
return MailPoet.I18n.t('congratulationsScheduleSuccessHeader');
|
||||||
|
}
|
||||||
|
return MailPoet.I18n.t('congratulationsSendSuccessHeader');
|
||||||
|
}
|
||||||
|
|
||||||
|
function Success(props) {
|
||||||
|
return (
|
||||||
|
<div className="mailpoet_congratulate_success">
|
||||||
|
<h1>{renderHeader(props.newsletter)}</h1>
|
||||||
|
<img src={props.illustrationImageUrl} alt="" width="750" height="250" />
|
||||||
|
<button className="button" onClick={props.successClicked}>{MailPoet.I18n.t('close')}</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Success.propTypes = {
|
||||||
|
successClicked: PropTypes.func.isRequired,
|
||||||
|
illustrationImageUrl: PropTypes.string.isRequired,
|
||||||
|
newsletter: PropTypes.shape({
|
||||||
|
status: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Success;
|
172
assets/js/src/newsletters/send/date_text.jsx
Normal file
172
assets/js/src/newsletters/send/date_text.jsx
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import jQuery from 'jquery';
|
||||||
|
import _ from 'underscore';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const datepickerTranslations = {
|
||||||
|
closeText: MailPoet.I18n.t('close'),
|
||||||
|
currentText: MailPoet.I18n.t('today'),
|
||||||
|
nextText: MailPoet.I18n.t('next'),
|
||||||
|
prevText: MailPoet.I18n.t('previous'),
|
||||||
|
monthNames: [
|
||||||
|
MailPoet.I18n.t('january'),
|
||||||
|
MailPoet.I18n.t('february'),
|
||||||
|
MailPoet.I18n.t('march'),
|
||||||
|
MailPoet.I18n.t('april'),
|
||||||
|
MailPoet.I18n.t('may'),
|
||||||
|
MailPoet.I18n.t('june'),
|
||||||
|
MailPoet.I18n.t('july'),
|
||||||
|
MailPoet.I18n.t('august'),
|
||||||
|
MailPoet.I18n.t('september'),
|
||||||
|
MailPoet.I18n.t('october'),
|
||||||
|
MailPoet.I18n.t('november'),
|
||||||
|
MailPoet.I18n.t('december'),
|
||||||
|
],
|
||||||
|
monthNamesShort: [
|
||||||
|
MailPoet.I18n.t('januaryShort'),
|
||||||
|
MailPoet.I18n.t('februaryShort'),
|
||||||
|
MailPoet.I18n.t('marchShort'),
|
||||||
|
MailPoet.I18n.t('aprilShort'),
|
||||||
|
MailPoet.I18n.t('mayShort'),
|
||||||
|
MailPoet.I18n.t('juneShort'),
|
||||||
|
MailPoet.I18n.t('julyShort'),
|
||||||
|
MailPoet.I18n.t('augustShort'),
|
||||||
|
MailPoet.I18n.t('septemberShort'),
|
||||||
|
MailPoet.I18n.t('octoberShort'),
|
||||||
|
MailPoet.I18n.t('novemberShort'),
|
||||||
|
MailPoet.I18n.t('decemberShort'),
|
||||||
|
],
|
||||||
|
dayNames: [
|
||||||
|
MailPoet.I18n.t('sunday'),
|
||||||
|
MailPoet.I18n.t('monday'),
|
||||||
|
MailPoet.I18n.t('tuesday'),
|
||||||
|
MailPoet.I18n.t('wednesday'),
|
||||||
|
MailPoet.I18n.t('thursday'),
|
||||||
|
MailPoet.I18n.t('friday'),
|
||||||
|
MailPoet.I18n.t('saturday'),
|
||||||
|
],
|
||||||
|
dayNamesShort: [
|
||||||
|
MailPoet.I18n.t('sundayShort'),
|
||||||
|
MailPoet.I18n.t('mondayShort'),
|
||||||
|
MailPoet.I18n.t('tuesdayShort'),
|
||||||
|
MailPoet.I18n.t('wednesdayShort'),
|
||||||
|
MailPoet.I18n.t('thursdayShort'),
|
||||||
|
MailPoet.I18n.t('fridayShort'),
|
||||||
|
MailPoet.I18n.t('saturdayShort'),
|
||||||
|
],
|
||||||
|
dayNamesMin: [
|
||||||
|
MailPoet.I18n.t('sundayMin'),
|
||||||
|
MailPoet.I18n.t('mondayMin'),
|
||||||
|
MailPoet.I18n.t('tuesdayMin'),
|
||||||
|
MailPoet.I18n.t('wednesdayMin'),
|
||||||
|
MailPoet.I18n.t('thursdayMin'),
|
||||||
|
MailPoet.I18n.t('fridayMin'),
|
||||||
|
MailPoet.I18n.t('saturdayMin'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
class DateText extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
const $element = jQuery(this.dateInput);
|
||||||
|
const that = this;
|
||||||
|
if ($element.datepicker) {
|
||||||
|
// Override jQuery UI datepicker Date parsing and formatting
|
||||||
|
jQuery.datepicker.parseDate = function parseDate(format, value) {
|
||||||
|
// Transform string format to Date object
|
||||||
|
return MailPoet.Date.toDate(value, {
|
||||||
|
parseFormat: this.props.displayFormat,
|
||||||
|
format,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
jQuery.datepicker.formatDate = function formatDate(format, value) {
|
||||||
|
// Transform Date object to string format
|
||||||
|
const newValue = MailPoet.Date.format(value, {
|
||||||
|
format,
|
||||||
|
});
|
||||||
|
return newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
$element.datepicker(_.extend({
|
||||||
|
dateFormat: this.props.displayFormat,
|
||||||
|
isRTL: false,
|
||||||
|
onSelect: function onSelect(value) {
|
||||||
|
that.onChange({
|
||||||
|
target: {
|
||||||
|
name: that.getFieldName(),
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, datepickerTranslations));
|
||||||
|
|
||||||
|
this.datepickerInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.datepickerInitialized) {
|
||||||
|
jQuery(this.dateInput).datepicker('destroy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (event) => {
|
||||||
|
const changeEvent = event;
|
||||||
|
// Swap display format to storage format
|
||||||
|
const displayDate = changeEvent.target.value;
|
||||||
|
const storageDate = this.getStorageDate(displayDate);
|
||||||
|
|
||||||
|
changeEvent.target.value = storageDate;
|
||||||
|
this.props.onChange(changeEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
getFieldName = () => this.props.name || 'date';
|
||||||
|
|
||||||
|
getDisplayDate = (date) => {
|
||||||
|
const formatting = {
|
||||||
|
parseFormat: this.props.storageFormat,
|
||||||
|
format: this.props.displayFormat,
|
||||||
|
};
|
||||||
|
return MailPoet.Date.format(date, formatting);
|
||||||
|
};
|
||||||
|
|
||||||
|
getStorageDate = (date) => {
|
||||||
|
const formatting = {
|
||||||
|
parseFormat: this.props.displayFormat,
|
||||||
|
format: this.props.storageFormat,
|
||||||
|
};
|
||||||
|
return MailPoet.Date.format(date, formatting);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
size="30"
|
||||||
|
name={this.getFieldName()}
|
||||||
|
value={this.getDisplayDate(this.props.value)}
|
||||||
|
readOnly
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
onChange={this.onChange}
|
||||||
|
ref={(c) => { this.dateInput = c; }}
|
||||||
|
{...this.props.validation}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateText.propTypes = {
|
||||||
|
displayFormat: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
name: PropTypes.string,
|
||||||
|
storageFormat: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
disabled: PropTypes.bool.isRequired,
|
||||||
|
validation: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
|
DateText.defaultProps = {
|
||||||
|
name: 'date',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = DateText;
|
93
assets/js/src/newsletters/send/date_time.jsx
Normal file
93
assets/js/src/newsletters/send/date_time.jsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import DateText from 'newsletters/send/date_text.jsx';
|
||||||
|
import TimeSelect from 'newsletters/send/time_select.jsx';
|
||||||
|
|
||||||
|
class DateTime extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = this.buildStateFromProps(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.setState(this.buildStateFromProps(nextProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
getDateTime = () => [this.state.date, this.state.time].join(this.DATE_TIME_SEPARATOR);
|
||||||
|
|
||||||
|
DATE_TIME_SEPARATOR = ' ';
|
||||||
|
|
||||||
|
buildStateFromProps = (props) => {
|
||||||
|
const value = props.value || this.props.defaultDateTime;
|
||||||
|
const [date, time] = value.split(this.DATE_TIME_SEPARATOR);
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
time,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = (event) => {
|
||||||
|
const newState = {};
|
||||||
|
newState[event.target.name] = event.target.value;
|
||||||
|
|
||||||
|
this.setState(newState, this.propagateChange);
|
||||||
|
};
|
||||||
|
|
||||||
|
propagateChange = () => {
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange({
|
||||||
|
target: {
|
||||||
|
name: this.props.name || '',
|
||||||
|
value: this.getDateTime(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<DateText
|
||||||
|
name="date"
|
||||||
|
value={this.state.date}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
displayFormat={this.props.dateDisplayFormat}
|
||||||
|
storageFormat={this.props.dateStorageFormat}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
validation={this.props.dateValidation}
|
||||||
|
/>
|
||||||
|
<TimeSelect
|
||||||
|
name="time"
|
||||||
|
value={this.state.time}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
validation={this.props.timeValidation}
|
||||||
|
timeOfDayItems={this.props.timeOfDayItems}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime.propTypes = {
|
||||||
|
defaultDateTime: PropTypes.string.isRequired,
|
||||||
|
dateDisplayFormat: PropTypes.string.isRequired,
|
||||||
|
dateStorageFormat: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
name: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
dateValidation: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
timeValidation: PropTypes.any, // eslint-disable-line react/forbid-prop-types
|
||||||
|
timeOfDayItems: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
DateTime.defaultProps = {
|
||||||
|
onChange: undefined,
|
||||||
|
name: '',
|
||||||
|
disabled: false,
|
||||||
|
timeValidation: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = DateTime;
|
@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import jQuery from 'jquery';
|
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Hooks from 'wp-js-hooks';
|
import Hooks from 'wp-js-hooks';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import DateTime from 'newsletters/send/date_time.jsx';
|
||||||
|
|
||||||
const currentTime = window.mailpoet_current_time || '00:00';
|
const currentTime = window.mailpoet_current_time || '00:00';
|
||||||
const defaultDateTime = `${window.mailpoet_current_date} 00:00:00`;
|
const defaultDateTime = `${window.mailpoet_current_date} 00:00:00`;
|
||||||
@ -10,248 +12,33 @@ const timeOfDayItems = window.mailpoet_schedule_time_of_day;
|
|||||||
const dateDisplayFormat = window.mailpoet_date_display_format;
|
const dateDisplayFormat = window.mailpoet_date_display_format;
|
||||||
const dateStorageFormat = window.mailpoet_date_storage_format;
|
const dateStorageFormat = window.mailpoet_date_storage_format;
|
||||||
|
|
||||||
const datepickerTranslations = {
|
class StandardScheduling extends React.Component {
|
||||||
closeText: MailPoet.I18n.t('close'),
|
getCurrentValue = () => {
|
||||||
currentText: MailPoet.I18n.t('today'),
|
const schedulingOptions = {
|
||||||
nextText: MailPoet.I18n.t('next'),
|
isScheduled: '0',
|
||||||
prevText: MailPoet.I18n.t('previous'),
|
scheduledAt: defaultDateTime,
|
||||||
monthNames: [
|
|
||||||
MailPoet.I18n.t('january'),
|
|
||||||
MailPoet.I18n.t('february'),
|
|
||||||
MailPoet.I18n.t('march'),
|
|
||||||
MailPoet.I18n.t('april'),
|
|
||||||
MailPoet.I18n.t('may'),
|
|
||||||
MailPoet.I18n.t('june'),
|
|
||||||
MailPoet.I18n.t('july'),
|
|
||||||
MailPoet.I18n.t('august'),
|
|
||||||
MailPoet.I18n.t('september'),
|
|
||||||
MailPoet.I18n.t('october'),
|
|
||||||
MailPoet.I18n.t('november'),
|
|
||||||
MailPoet.I18n.t('december'),
|
|
||||||
],
|
|
||||||
monthNamesShort: [
|
|
||||||
MailPoet.I18n.t('januaryShort'),
|
|
||||||
MailPoet.I18n.t('februaryShort'),
|
|
||||||
MailPoet.I18n.t('marchShort'),
|
|
||||||
MailPoet.I18n.t('aprilShort'),
|
|
||||||
MailPoet.I18n.t('mayShort'),
|
|
||||||
MailPoet.I18n.t('juneShort'),
|
|
||||||
MailPoet.I18n.t('julyShort'),
|
|
||||||
MailPoet.I18n.t('augustShort'),
|
|
||||||
MailPoet.I18n.t('septemberShort'),
|
|
||||||
MailPoet.I18n.t('octoberShort'),
|
|
||||||
MailPoet.I18n.t('novemberShort'),
|
|
||||||
MailPoet.I18n.t('decemberShort'),
|
|
||||||
],
|
|
||||||
dayNames: [
|
|
||||||
MailPoet.I18n.t('sunday'),
|
|
||||||
MailPoet.I18n.t('monday'),
|
|
||||||
MailPoet.I18n.t('tuesday'),
|
|
||||||
MailPoet.I18n.t('wednesday'),
|
|
||||||
MailPoet.I18n.t('thursday'),
|
|
||||||
MailPoet.I18n.t('friday'),
|
|
||||||
MailPoet.I18n.t('saturday'),
|
|
||||||
],
|
|
||||||
dayNamesShort: [
|
|
||||||
MailPoet.I18n.t('sundayShort'),
|
|
||||||
MailPoet.I18n.t('mondayShort'),
|
|
||||||
MailPoet.I18n.t('tuesdayShort'),
|
|
||||||
MailPoet.I18n.t('wednesdayShort'),
|
|
||||||
MailPoet.I18n.t('thursdayShort'),
|
|
||||||
MailPoet.I18n.t('fridayShort'),
|
|
||||||
MailPoet.I18n.t('saturdayShort'),
|
|
||||||
],
|
|
||||||
dayNamesMin: [
|
|
||||||
MailPoet.I18n.t('sundayMin'),
|
|
||||||
MailPoet.I18n.t('mondayMin'),
|
|
||||||
MailPoet.I18n.t('tuesdayMin'),
|
|
||||||
MailPoet.I18n.t('wednesdayMin'),
|
|
||||||
MailPoet.I18n.t('thursdayMin'),
|
|
||||||
MailPoet.I18n.t('fridayMin'),
|
|
||||||
MailPoet.I18n.t('saturdayMin'),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const DateText = React.createClass({
|
|
||||||
onChange: function onChange(event) {
|
|
||||||
const changeEvent = event;
|
|
||||||
// Swap display format to storage format
|
|
||||||
const displayDate = changeEvent.target.value;
|
|
||||||
const storageDate = this.getStorageDate(displayDate);
|
|
||||||
|
|
||||||
changeEvent.target.value = storageDate;
|
|
||||||
this.props.onChange(changeEvent);
|
|
||||||
},
|
|
||||||
componentDidMount: function componentDidMount() {
|
|
||||||
const $element = jQuery(this.dateInput);
|
|
||||||
const that = this;
|
|
||||||
if ($element.datepicker) {
|
|
||||||
// Override jQuery UI datepicker Date parsing and formatting
|
|
||||||
jQuery.datepicker.parseDate = function parseDate(format, value) {
|
|
||||||
// Transform string format to Date object
|
|
||||||
return MailPoet.Date.toDate(value, {
|
|
||||||
parseFormat: dateDisplayFormat,
|
|
||||||
format,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
jQuery.datepicker.formatDate = function formatDate(format, value) {
|
|
||||||
// Transform Date object to string format
|
|
||||||
const newValue = MailPoet.Date.format(value, {
|
|
||||||
format,
|
|
||||||
});
|
|
||||||
return newValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
$element.datepicker(_.extend({
|
|
||||||
dateFormat: this.props.displayFormat,
|
|
||||||
isRTL: false,
|
|
||||||
onSelect: function onSelect(value) {
|
|
||||||
that.onChange({
|
|
||||||
target: {
|
|
||||||
name: that.getFieldName(),
|
|
||||||
value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}, datepickerTranslations));
|
|
||||||
|
|
||||||
this.datepickerInitialized = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillUnmount: function componentWillUnmount() {
|
|
||||||
if (this.datepickerInitialized) {
|
|
||||||
jQuery(this.dateInput).datepicker('destroy');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getFieldName: function getFieldName() {
|
|
||||||
return this.props.name || 'date';
|
|
||||||
},
|
|
||||||
getDisplayDate: function getDisplayDate(date) {
|
|
||||||
return MailPoet.Date.format(date, {
|
|
||||||
parseFormat: this.props.storageFormat,
|
|
||||||
format: this.props.displayFormat,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getStorageDate: function getStorageDate(date) {
|
|
||||||
return MailPoet.Date.format(date, {
|
|
||||||
parseFormat: this.props.displayFormat,
|
|
||||||
format: this.props.storageFormat,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render: function render() {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
size="10"
|
|
||||||
name={this.getFieldName()}
|
|
||||||
value={this.getDisplayDate(this.props.value)}
|
|
||||||
readOnly
|
|
||||||
disabled={this.props.disabled}
|
|
||||||
onChange={this.onChange}
|
|
||||||
ref={(c) => { this.dateInput = c; }}
|
|
||||||
{...this.props.validation}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const TimeSelect = React.createClass({
|
|
||||||
render: function render() {
|
|
||||||
const options = Object.keys(timeOfDayItems).map(
|
|
||||||
value => (
|
|
||||||
<option
|
|
||||||
key={`option-${timeOfDayItems[value]}`}
|
|
||||||
value={value}
|
|
||||||
>
|
|
||||||
{ timeOfDayItems[value] }
|
|
||||||
</option>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<select
|
|
||||||
name={this.props.name || 'time'}
|
|
||||||
value={this.props.value}
|
|
||||||
disabled={this.props.disabled}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
{...this.props.validation}
|
|
||||||
>
|
|
||||||
{options}
|
|
||||||
</select>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const DateTime = React.createClass({
|
|
||||||
DATE_TIME_SEPARATOR: ' ',
|
|
||||||
getInitialState: function getInitialState() {
|
|
||||||
return this.buildStateFromProps(this.props);
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
|
|
||||||
this.setState(this.buildStateFromProps(nextProps));
|
|
||||||
},
|
|
||||||
buildStateFromProps: function buildStateFromProps(props) {
|
|
||||||
const value = props.value || defaultDateTime;
|
|
||||||
const [date, time] = value.split(this.DATE_TIME_SEPARATOR);
|
|
||||||
return {
|
|
||||||
date,
|
|
||||||
time,
|
|
||||||
};
|
};
|
||||||
},
|
|
||||||
handleChange: function handleChange(event) {
|
|
||||||
const newState = {};
|
|
||||||
newState[event.target.name] = event.target.value;
|
|
||||||
|
|
||||||
this.setState(newState, this.propagateChange);
|
|
||||||
},
|
|
||||||
propagateChange: function propagateChange() {
|
|
||||||
if (this.props.onChange) {
|
|
||||||
this.props.onChange({
|
|
||||||
target: {
|
|
||||||
name: this.props.name || '',
|
|
||||||
value: this.getDateTime(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getDateTime: function getDateTime() {
|
|
||||||
return [this.state.date, this.state.time].join(this.DATE_TIME_SEPARATOR);
|
|
||||||
},
|
|
||||||
render: function render() {
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<DateText
|
|
||||||
name="date"
|
|
||||||
value={this.state.date}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
displayFormat={dateDisplayFormat}
|
|
||||||
storageFormat={dateStorageFormat}
|
|
||||||
disabled={this.props.disabled}
|
|
||||||
validation={this.props.dateValidation}
|
|
||||||
/>
|
|
||||||
<TimeSelect
|
|
||||||
name="time"
|
|
||||||
value={this.state.time}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={this.props.disabled}
|
|
||||||
validation={this.props.timeValidation}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const StandardScheduling = React.createClass({
|
|
||||||
getCurrentValue: function getCurrentValue() {
|
|
||||||
return _.defaults(
|
return _.defaults(
|
||||||
this.props.item[this.props.field.name] || {},
|
this.props.item[this.props.field.name] || {},
|
||||||
{
|
schedulingOptions
|
||||||
isScheduled: '0',
|
|
||||||
scheduledAt: defaultDateTime,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
handleValueChange: function handleValueChange(event) {
|
|
||||||
|
getDateValidation = () => ({
|
||||||
|
'data-parsley-required': true,
|
||||||
|
'data-parsley-required-message': MailPoet.I18n.t('noScheduledDateError'),
|
||||||
|
'data-parsley-errors-container': '#mailpoet_scheduling',
|
||||||
|
});
|
||||||
|
|
||||||
|
isScheduled = () => this.getCurrentValue().isScheduled === '1';
|
||||||
|
|
||||||
|
handleCheckboxChange = (event) => {
|
||||||
|
const changeEvent = event;
|
||||||
|
changeEvent.target.value = this.isScheduledInput.checked ? '1' : '0';
|
||||||
|
return this.handleValueChange(changeEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleValueChange = (event) => {
|
||||||
const oldValue = this.getCurrentValue();
|
const oldValue = this.getCurrentValue();
|
||||||
const newValue = {};
|
const newValue = {};
|
||||||
newValue[event.target.name] = event.target.value;
|
newValue[event.target.name] = event.target.value;
|
||||||
@ -262,23 +49,9 @@ const StandardScheduling = React.createClass({
|
|||||||
value: _.extend({}, oldValue, newValue),
|
value: _.extend({}, oldValue, newValue),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
handleCheckboxChange: function handleCheckboxChange(event) {
|
|
||||||
const changeEvent = event;
|
render() {
|
||||||
changeEvent.target.value = this.isScheduledInput.checked ? '1' : '0';
|
|
||||||
return this.handleValueChange(changeEvent);
|
|
||||||
},
|
|
||||||
isScheduled: function isScheduled() {
|
|
||||||
return this.getCurrentValue().isScheduled === '1';
|
|
||||||
},
|
|
||||||
getDateValidation: function getDateValidation() {
|
|
||||||
return {
|
|
||||||
'data-parsley-required': true,
|
|
||||||
'data-parsley-required-message': MailPoet.I18n.t('noScheduledDateError'),
|
|
||||||
'data-parsley-errors-container': '#mailpoet_scheduling',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
render: function render() {
|
|
||||||
let schedulingOptions;
|
let schedulingOptions;
|
||||||
|
|
||||||
if (this.isScheduled()) {
|
if (this.isScheduled()) {
|
||||||
@ -290,6 +63,10 @@ const StandardScheduling = React.createClass({
|
|||||||
onChange={this.handleValueChange}
|
onChange={this.handleValueChange}
|
||||||
disabled={this.props.field.disabled}
|
disabled={this.props.field.disabled}
|
||||||
dateValidation={this.getDateValidation()}
|
dateValidation={this.getDateValidation()}
|
||||||
|
defaultDateTime={defaultDateTime}
|
||||||
|
timeOfDayItems={timeOfDayItems}
|
||||||
|
dateDisplayFormat={dateDisplayFormat}
|
||||||
|
dateStorageFormat={dateStorageFormat}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
@ -313,8 +90,21 @@ const StandardScheduling = React.createClass({
|
|||||||
{schedulingOptions}
|
{schedulingOptions}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
StandardScheduling.propTypes = {
|
||||||
|
item: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
}).isRequired,
|
||||||
|
onValueChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
StandardScheduling.defaultProps = {
|
||||||
|
item: {},
|
||||||
|
};
|
||||||
|
|
||||||
let fields = [
|
let fields = [
|
||||||
{
|
{
|
||||||
|
46
assets/js/src/newsletters/send/time_select.jsx
Normal file
46
assets/js/src/newsletters/send/time_select.jsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class TimeSelect extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||||
|
render() {
|
||||||
|
const options = Object.keys(this.props.timeOfDayItems).map(
|
||||||
|
value => (
|
||||||
|
<option
|
||||||
|
key={`option-${this.props.timeOfDayItems[value]}`}
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
{ this.props.timeOfDayItems[value] }
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
name={this.props.name || 'time'}
|
||||||
|
value={this.props.value}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
onChange={this.props.onChange}
|
||||||
|
{...this.props.validation}
|
||||||
|
>
|
||||||
|
{options}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSelect.propTypes = {
|
||||||
|
timeOfDayItems: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||||
|
name: PropTypes.string,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
validation: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeSelect.defaultProps = {
|
||||||
|
name: 'time',
|
||||||
|
disabled: false,
|
||||||
|
validation: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = TimeSelect;
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
||||||
@ -5,11 +6,12 @@ import Hooks from 'wp-js-hooks';
|
|||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import 'react-router';
|
import 'react-router';
|
||||||
|
|
||||||
const NewsletterTypes = React.createClass({
|
class NewsletterTypes extends React.Component {
|
||||||
contextTypes: {
|
static contextTypes = {
|
||||||
router: React.PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
},
|
};
|
||||||
setupNewsletter: function setupNewsletter(type) {
|
|
||||||
|
setupNewsletter = (type) => {
|
||||||
if (type !== undefined) {
|
if (type !== undefined) {
|
||||||
this.context.router.push(`/new/${type}`);
|
this.context.router.push(`/new/${type}`);
|
||||||
MailPoet.trackEvent('Emails > Type selected', {
|
MailPoet.trackEvent('Emails > Type selected', {
|
||||||
@ -17,8 +19,32 @@ const NewsletterTypes = React.createClass({
|
|||||||
'Email type': type,
|
'Email type': type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
createNewsletter: function createNewsletter(type) {
|
|
||||||
|
getAutomaticEmails = () => {
|
||||||
|
if (!window.mailpoet_automatic_emails) return [];
|
||||||
|
|
||||||
|
return _.map(window.mailpoet_automatic_emails, (automaticEmail) => {
|
||||||
|
const email = automaticEmail;
|
||||||
|
const onClick = _.partial(this.setupNewsletter, automaticEmail.slug);
|
||||||
|
email.action = (() => (
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className="button button-primary"
|
||||||
|
onClick={onClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
{ MailPoet.I18n.t('setUp') }
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))();
|
||||||
|
|
||||||
|
return email;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
createNewsletter = (type) => {
|
||||||
MailPoet.trackEvent('Emails > Type selected', {
|
MailPoet.trackEvent('Emails > Type selected', {
|
||||||
'MailPoet Free version': window.mailpoet_version,
|
'MailPoet Free version': window.mailpoet_version,
|
||||||
'Email type': type,
|
'Email type': type,
|
||||||
@ -41,30 +67,9 @@ const NewsletterTypes = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
getAutomaticEmails: function getAutomaticEmails() {
|
|
||||||
if (!window.mailpoet_automatic_emails) return [];
|
|
||||||
|
|
||||||
return _.map(window.mailpoet_automatic_emails, (automaticEmail) => {
|
render() {
|
||||||
const email = automaticEmail;
|
|
||||||
const onClick = _.partial(this.setupNewsletter, automaticEmail.slug);
|
|
||||||
email.action = (() => (
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
className="button button-primary"
|
|
||||||
onClick={onClick}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
{ MailPoet.I18n.t('setUp') }
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
))();
|
|
||||||
|
|
||||||
return email;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render: function render() {
|
|
||||||
const createStandardNewsletter = _.partial(this.createNewsletter, 'standard');
|
const createStandardNewsletter = _.partial(this.createNewsletter, 'standard');
|
||||||
const createNotificationNewsletter = _.partial(this.setupNewsletter, 'notification');
|
const createNotificationNewsletter = _.partial(this.setupNewsletter, 'notification');
|
||||||
const createWelcomeNewsletter = _.partial(this.setupNewsletter, 'welcome');
|
const createWelcomeNewsletter = _.partial(this.setupNewsletter, 'welcome');
|
||||||
@ -169,7 +174,7 @@ const NewsletterTypes = React.createClass({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
module.exports = NewsletterTypes;
|
module.exports = NewsletterTypes;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
||||||
@ -10,27 +11,28 @@ const field = {
|
|||||||
component: Scheduling,
|
component: Scheduling,
|
||||||
};
|
};
|
||||||
|
|
||||||
const NewsletterNotification = React.createClass({
|
class NewsletterNotification extends React.Component {
|
||||||
contextTypes: {
|
static contextTypes = {
|
||||||
router: React.PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
},
|
};
|
||||||
getInitialState: function getInitialState() {
|
|
||||||
return {
|
state = {
|
||||||
options: {
|
options: {
|
||||||
intervalType: 'daily',
|
intervalType: 'daily',
|
||||||
timeOfDay: 0,
|
timeOfDay: 0,
|
||||||
weekDay: 1,
|
weekDay: 1,
|
||||||
monthDay: 0,
|
monthDay: 0,
|
||||||
nthWeekDay: 1,
|
nthWeekDay: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
|
||||||
handleValueChange: function handleValueChange(event) {
|
handleValueChange = (event) => {
|
||||||
const state = this.state;
|
const state = this.state;
|
||||||
state[event.target.name] = event.target.value;
|
state[event.target.name] = event.target.value;
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
},
|
};
|
||||||
handleNext: function handleNext() {
|
|
||||||
|
handleNext = () => {
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: 'newsletters',
|
endpoint: 'newsletters',
|
||||||
@ -49,11 +51,13 @@ const NewsletterNotification = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
showTemplateSelection: function showTemplateSelection(newsletterId) {
|
|
||||||
|
showTemplateSelection = (newsletterId) => {
|
||||||
this.context.router.push(`/template/${newsletterId}`);
|
this.context.router.push(`/template/${newsletterId}`);
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
|
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
|
||||||
@ -77,8 +81,8 @@ const NewsletterNotification = React.createClass({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
module.exports = NewsletterNotification;
|
module.exports = NewsletterNotification;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Select from 'form/fields/select.jsx';
|
import Select from 'form/fields/select.jsx';
|
||||||
import {
|
import {
|
||||||
intervalValues,
|
intervalValues,
|
||||||
@ -34,11 +35,10 @@ const nthWeekDayField = {
|
|||||||
values: nthWeekDayValues,
|
values: nthWeekDayValues,
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotificationScheduling = React.createClass({
|
class NotificationScheduling extends React.Component {
|
||||||
getCurrentValue: function getCurrentValue() {
|
getCurrentValue = () => this.props.item[this.props.field.name] || {};
|
||||||
return (this.props.item[this.props.field.name] || {});
|
|
||||||
},
|
handleValueChange = (name, value) => {
|
||||||
handleValueChange: function handleValueChange(name, value) {
|
|
||||||
const oldValue = this.getCurrentValue();
|
const oldValue = this.getCurrentValue();
|
||||||
const newValue = {};
|
const newValue = {};
|
||||||
|
|
||||||
@ -50,38 +50,15 @@ const NotificationScheduling = React.createClass({
|
|||||||
value: _.extend({}, oldValue, newValue),
|
value: _.extend({}, oldValue, newValue),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
handleIntervalChange: function handleIntervalChange(event) {
|
|
||||||
return this.handleValueChange(
|
handleIntervalChange = event => this.handleValueChange('intervalType', event.target.value);
|
||||||
'intervalType',
|
handleTimeOfDayChange = event => this.handleValueChange('timeOfDay', event.target.value);
|
||||||
event.target.value
|
handleWeekDayChange = event => this.handleValueChange('weekDay', event.target.value);
|
||||||
);
|
handleMonthDayChange = event => this.handleValueChange('monthDay', event.target.value);
|
||||||
},
|
handleNthWeekDayChange = event => this.handleValueChange('nthWeekDay', event.target.value);
|
||||||
handleTimeOfDayChange: function handleTimeOfDayChange(event) {
|
|
||||||
return this.handleValueChange(
|
render() {
|
||||||
'timeOfDay',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
handleWeekDayChange: function handleWeekDayChange(event) {
|
|
||||||
return this.handleValueChange(
|
|
||||||
'weekDay',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
handleMonthDayChange: function handleMonthDayChange(event) {
|
|
||||||
return this.handleValueChange(
|
|
||||||
'monthDay',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
handleNthWeekDayChange: function handleNthWeekDayChange(event) {
|
|
||||||
return this.handleValueChange(
|
|
||||||
'nthWeekDay',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
render: function render() {
|
|
||||||
const value = this.getCurrentValue();
|
const value = this.getCurrentValue();
|
||||||
let timeOfDaySelection;
|
let timeOfDaySelection;
|
||||||
let weekDaySelection;
|
let weekDaySelection;
|
||||||
@ -143,7 +120,15 @@ const NotificationScheduling = React.createClass({
|
|||||||
{timeOfDaySelection}
|
{timeOfDaySelection}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
NotificationScheduling.propTypes = {
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
onValueChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = NotificationScheduling;
|
module.exports = NotificationScheduling;
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
import Breadcrumb from 'newsletters/breadcrumb.jsx';
|
||||||
|
|
||||||
const NewsletterStandard = React.createClass({
|
class NewsletterStandard extends React.Component {
|
||||||
contextTypes: {
|
static contextTypes = {
|
||||||
router: React.PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
},
|
};
|
||||||
showTemplateSelection: function showTemplateSelection(newsletterId) {
|
|
||||||
this.context.router.push(`/template/${newsletterId}`);
|
componentDidMount() {
|
||||||
},
|
|
||||||
componentDidMount: function componentDidMount() {
|
|
||||||
// No options for this type, create a newsletter upon mounting
|
// No options for this type, create a newsletter upon mounting
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
@ -28,16 +27,21 @@ const NewsletterStandard = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
render: function render() {
|
|
||||||
|
showTemplateSelection = (newsletterId) => {
|
||||||
|
this.context.router.push(`/template/${newsletterId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h1>
|
<h1>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h1>
|
||||||
<Breadcrumb step="type" />
|
<Breadcrumb step="type" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
module.exports = NewsletterStandard;
|
module.exports = NewsletterStandard;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import MailPoet from 'mailpoet';
|
|||||||
import Select from 'form/fields/select.jsx';
|
import Select from 'form/fields/select.jsx';
|
||||||
import Text from 'form/fields/text.jsx';
|
import Text from 'form/fields/text.jsx';
|
||||||
import { timeDelayValues } from 'newsletters/scheduling/common.jsx';
|
import { timeDelayValues } from 'newsletters/scheduling/common.jsx';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const availableRoles = window.mailpoet_roles || {};
|
const availableRoles = window.mailpoet_roles || {};
|
||||||
const availableSegments = _.filter(
|
const availableSegments = _.filter(
|
||||||
@ -47,14 +48,14 @@ const afterTimeTypeField = {
|
|||||||
values: timeDelayValues,
|
values: timeDelayValues,
|
||||||
};
|
};
|
||||||
|
|
||||||
const WelcomeScheduling = React.createClass({
|
class WelcomeScheduling extends React.Component {
|
||||||
contextTypes: {
|
static contextTypes = {
|
||||||
router: React.PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
},
|
};
|
||||||
getCurrentValue: function getCurrentValue() {
|
|
||||||
return (this.props.item[this.props.field.name] || {});
|
getCurrentValue = () => this.props.item[this.props.field.name] || {};
|
||||||
},
|
|
||||||
handleValueChange: function handleValueChange(name, value) {
|
handleValueChange = (name, value) => {
|
||||||
const oldValue = this.getCurrentValue();
|
const oldValue = this.getCurrentValue();
|
||||||
const newValue = {};
|
const newValue = {};
|
||||||
|
|
||||||
@ -66,38 +67,15 @@ const WelcomeScheduling = React.createClass({
|
|||||||
value: _.extend({}, oldValue, newValue),
|
value: _.extend({}, oldValue, newValue),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
handleEventChange: function handleEventChange(event) {
|
|
||||||
return this.handleValueChange(
|
handleEventChange = event => this.handleValueChange('event', event.target.value);
|
||||||
'event',
|
handleSegmentChange = event => this.handleValueChange('segment', event.target.value);
|
||||||
event.target.value
|
handleRoleChange = event => this.handleValueChange('role', event.target.value);
|
||||||
);
|
handleAfterTimeNumberChange = event => this.handleValueChange('afterTimeNumber', event.target.value);
|
||||||
},
|
handleAfterTimeTypeChange = event => this.handleValueChange('afterTimeType', event.target.value);
|
||||||
handleSegmentChange: function handleSegmentChange(event) {
|
|
||||||
return this.handleValueChange(
|
handleNext = () => {
|
||||||
'segment',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
handleRoleChange: function handleRoleChange(event) {
|
|
||||||
return this.handleValueChange(
|
|
||||||
'role',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
handleAfterTimeNumberChange: function handleAfterTimeNumberChange(event) {
|
|
||||||
return this.handleValueChange(
|
|
||||||
'afterTimeNumber',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
handleAfterTimeTypeChange: function handleAfterTimeTypeChange(event) {
|
|
||||||
return this.handleValueChange(
|
|
||||||
'afterTimeType',
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
},
|
|
||||||
handleNext: function handleNext() {
|
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: 'newsletters',
|
endpoint: 'newsletters',
|
||||||
@ -116,11 +94,13 @@ const WelcomeScheduling = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
showTemplateSelection: function showTemplateSelection(newsletterId) {
|
|
||||||
|
showTemplateSelection = (newsletterId) => {
|
||||||
this.context.router.push(`/template/${newsletterId}`);
|
this.context.router.push(`/template/${newsletterId}`);
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
const value = this.getCurrentValue();
|
const value = this.getCurrentValue();
|
||||||
let roleSegmentSelection;
|
let roleSegmentSelection;
|
||||||
let timeNumber;
|
let timeNumber;
|
||||||
@ -171,7 +151,15 @@ const WelcomeScheduling = React.createClass({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
WelcomeScheduling.propTypes = {
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
field: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
onValueChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = WelcomeScheduling;
|
module.exports = WelcomeScheduling;
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
function displayPoll() {
|
function displayPoll() {
|
||||||
if (window.mailpoet_display_nps_poll && window.satismeter) {
|
if (
|
||||||
|
window.mailpoet_display_nps_poll
|
||||||
|
&& window.satismeter
|
||||||
|
&& window.mailpoet_installed_at_isoFormat
|
||||||
|
) {
|
||||||
|
// New users poll
|
||||||
window.satismeter({
|
window.satismeter({
|
||||||
writeKey: '6L479eVPXk7pBn6S',
|
writeKey: '6L479eVPXk7pBn6S',
|
||||||
userId: window.mailpoet_current_wp_user.ID + window.mailpoet_site_url,
|
userId: window.mailpoet_current_wp_user.ID + window.mailpoet_site_url,
|
||||||
traits: {
|
traits: {
|
||||||
name: window.mailpoet_current_wp_user.user_nicename,
|
name: window.mailpoet_current_wp_user.user_nicename,
|
||||||
email: window.mailpoet_current_wp_user.user_email,
|
email: window.mailpoet_current_wp_user.user_email,
|
||||||
createdAt: window.mailpoet_settings.installed_at,
|
createdAt: window.mailpoet_installed_at_isoFormat,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Old users poll
|
||||||
|
window.satismeter({
|
||||||
|
writeKey: 'k0aJAsQAWI2ERyGv',
|
||||||
|
userId: window.mailpoet_current_wp_user.ID + window.mailpoet_site_url,
|
||||||
|
traits: {
|
||||||
|
name: window.mailpoet_current_wp_user.user_nicename,
|
||||||
|
email: window.mailpoet_current_wp_user.user_email,
|
||||||
|
createdAt: window.mailpoet_installed_at_isoFormat,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Listing from 'listing/listing.jsx';
|
import Listing from 'listing/listing.jsx';
|
||||||
|
|
||||||
@ -191,8 +192,8 @@ const itemActions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const SegmentList = React.createClass({
|
class SegmentList extends React.Component {
|
||||||
renderItem: function renderItem(segment, actions) {
|
renderItem = (segment, actions) => {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
'column-primary',
|
'column-primary',
|
||||||
@ -248,8 +249,9 @@ const SegmentList = React.createClass({
|
|||||||
</td>
|
</td>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="title">
|
<h1 className="title">
|
||||||
@ -272,7 +274,12 @@ const SegmentList = React.createClass({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
SegmentList.propTypes = {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = SegmentList;
|
module.exports = SegmentList;
|
||||||
|
@ -2,17 +2,22 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
|
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
|
||||||
import { createHashHistory } from 'history';
|
import { createHashHistory } from 'history';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import SegmentList from 'segments/list.jsx';
|
import SegmentList from 'segments/list.jsx';
|
||||||
import SegmentForm from 'segments/form.jsx';
|
import SegmentForm from 'segments/form.jsx';
|
||||||
|
|
||||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||||
|
|
||||||
const App = React.createClass({
|
class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
const container = document.getElementById('segments_container');
|
const container = document.getElementById('segments_container');
|
||||||
|
|
||||||
|
14
assets/js/src/settings/announcement.jsx
Normal file
14
assets/js/src/settings/announcement.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import React from 'react';
|
||||||
|
import Announcement from './new_subscriber_announcement.jsx';
|
||||||
|
|
||||||
|
const container = document.getElementById('new_subscriber_announcement');
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<Announcement
|
||||||
|
installedAt={window.mailpoet_installed_at}
|
||||||
|
imageUrl={window.mailpoet_new_subscriber_announcement_image}
|
||||||
|
/>, container
|
||||||
|
);
|
||||||
|
}
|
30
assets/js/src/settings/new_subscriber_announcement.jsx
Normal file
30
assets/js/src/settings/new_subscriber_announcement.jsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import moment from 'moment';
|
||||||
|
import InAppAnnouncement from 'in_app_announcements/in_app_announcement.jsx';
|
||||||
|
|
||||||
|
const NewSubscriberNotificationAnnouncement = props => (
|
||||||
|
<InAppAnnouncement
|
||||||
|
validUntil={moment(props.installedAt).add(3, 'months').toDate()}
|
||||||
|
height="700px"
|
||||||
|
showOnlyOnceSlug="new_subscriber_notification"
|
||||||
|
showToNewUser={false}
|
||||||
|
>
|
||||||
|
<div className="new_subscriber_notification_announcement">
|
||||||
|
<h1>{MailPoet.I18n.t('announcementHeader')}</h1>
|
||||||
|
<img src={props.imageUrl} width="600px" height="460px" alt="" />
|
||||||
|
<p>
|
||||||
|
{MailPoet.I18n.t('announcementParagraph1')}<br />
|
||||||
|
{MailPoet.I18n.t('announcementParagraph2')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</InAppAnnouncement>
|
||||||
|
);
|
||||||
|
|
||||||
|
NewSubscriberNotificationAnnouncement.propTypes = {
|
||||||
|
installedAt: PropTypes.string.isRequired,
|
||||||
|
imageUrl: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = NewSubscriberNotificationAnnouncement;
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Form from 'form/form.jsx';
|
import Form from 'form/form.jsx';
|
||||||
import ReactStringReplace from 'react-string-replace';
|
import ReactStringReplace from 'react-string-replace';
|
||||||
|
|
||||||
@ -175,8 +176,8 @@ function afterFormContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubscriberForm = React.createClass({
|
class SubscriberForm extends React.Component { // eslint-disable-line react/prefer-stateless-function, max-len
|
||||||
render: function render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="title">
|
<h1 className="title">
|
||||||
@ -194,7 +195,11 @@ const SubscriberForm = React.createClass({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
SubscriberForm.propTypes = {
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = SubscriberForm;
|
module.exports = SubscriberForm;
|
||||||
|
@ -4,6 +4,7 @@ import { Link } from 'react-router';
|
|||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Listing from 'listing/listing.jsx';
|
import Listing from 'listing/listing.jsx';
|
||||||
import Selection from 'form/fields/selection.jsx';
|
import Selection from 'form/fields/selection.jsx';
|
||||||
@ -242,8 +243,8 @@ const itemActions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const SubscriberList = React.createClass({
|
class SubscriberList extends React.Component {
|
||||||
getSegmentFromId: function getSegmentFromId(segmentId) {
|
getSegmentFromId = (segmentId) => {
|
||||||
let result = false;
|
let result = false;
|
||||||
window.mailpoet_segments.forEach((segment) => {
|
window.mailpoet_segments.forEach((segment) => {
|
||||||
if (segment.id === segmentId) {
|
if (segment.id === segmentId) {
|
||||||
@ -251,8 +252,9 @@ const SubscriberList = React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
},
|
};
|
||||||
renderItem: function renderItem(subscriber, actions) {
|
|
||||||
|
renderItem = (subscriber, actions) => {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
'column-primary',
|
'column-primary',
|
||||||
@ -333,8 +335,9 @@ const SubscriberList = React.createClass({
|
|||||||
</td>
|
</td>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
render: function render() {
|
|
||||||
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="title">
|
<h1 className="title">
|
||||||
@ -368,7 +371,12 @@ const SubscriberList = React.createClass({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
SubscriberList.propTypes = {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = SubscriberList;
|
module.exports = SubscriberList;
|
||||||
|
@ -2,16 +2,21 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
|
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
|
||||||
import { createHashHistory } from 'history';
|
import { createHashHistory } from 'history';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import SubscriberList from 'subscribers/list.jsx';
|
import SubscriberList from 'subscribers/list.jsx';
|
||||||
import SubscriberForm from 'subscribers/form.jsx';
|
import SubscriberForm from 'subscribers/form.jsx';
|
||||||
|
|
||||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||||
|
|
||||||
const App = React.createClass({
|
class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
const container = document.getElementById('subscribers_container');
|
const container = document.getElementById('subscribers_container');
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SteppedProgressBar from '../common/stepped_progess_bar.jsx';
|
import SteppedProgressBar from '../common/stepped_progess_bar.jsx';
|
||||||
|
|
||||||
@ -13,9 +14,9 @@ const WelcomeWizardHeader = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
WelcomeWizardHeader.propTypes = {
|
WelcomeWizardHeader.propTypes = {
|
||||||
current_step: React.PropTypes.number.isRequired,
|
current_step: PropTypes.number.isRequired,
|
||||||
steps_count: React.PropTypes.number.isRequired,
|
steps_count: PropTypes.number.isRequired,
|
||||||
logo_src: React.PropTypes.string.isRequired,
|
logo_src: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = WelcomeWizardHeader;
|
module.exports = WelcomeWizardHeader;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import ReactStringReplace from 'react-string-replace';
|
import ReactStringReplace from 'react-string-replace';
|
||||||
@ -62,7 +63,7 @@ const WelcomeWizardHelpInfoStep = props => (
|
|||||||
module.exports = WelcomeWizardHelpInfoStep;
|
module.exports = WelcomeWizardHelpInfoStep;
|
||||||
|
|
||||||
WelcomeWizardHelpInfoStep.propTypes = {
|
WelcomeWizardHelpInfoStep.propTypes = {
|
||||||
next: React.PropTypes.func.isRequired,
|
next: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = WelcomeWizardHelpInfoStep;
|
module.exports = WelcomeWizardHelpInfoStep;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ const WelcomeWizardMigratedUserStep = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
WelcomeWizardMigratedUserStep.propTypes = {
|
WelcomeWizardMigratedUserStep.propTypes = {
|
||||||
next: React.PropTypes.func.isRequired,
|
next: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = WelcomeWizardMigratedUserStep;
|
module.exports = WelcomeWizardMigratedUserStep;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
@ -47,13 +48,13 @@ const WelcomeWizardSenderStep = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
WelcomeWizardSenderStep.propTypes = {
|
WelcomeWizardSenderStep.propTypes = {
|
||||||
finish: React.PropTypes.func.isRequired,
|
finish: PropTypes.func.isRequired,
|
||||||
loading: React.PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
update_sender: React.PropTypes.func.isRequired,
|
update_sender: PropTypes.func.isRequired,
|
||||||
submit_sender: React.PropTypes.func.isRequired,
|
submit_sender: PropTypes.func.isRequired,
|
||||||
sender: React.PropTypes.shape({
|
sender: PropTypes.shape({
|
||||||
name: React.PropTypes.string,
|
name: PropTypes.string,
|
||||||
address: React.PropTypes.string,
|
address: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import ReactStringReplace from 'react-string-replace';
|
import ReactStringReplace from 'react-string-replace';
|
||||||
@ -48,8 +49,8 @@ const WelcomeWizardUsageTrackingStep = props => (
|
|||||||
module.exports = WelcomeWizardUsageTrackingStep;
|
module.exports = WelcomeWizardUsageTrackingStep;
|
||||||
|
|
||||||
WelcomeWizardUsageTrackingStep.propTypes = {
|
WelcomeWizardUsageTrackingStep.propTypes = {
|
||||||
allow_action: React.PropTypes.func.isRequired,
|
allow_action: PropTypes.func.isRequired,
|
||||||
allow_text: React.PropTypes.string.isRequired,
|
allow_text: PropTypes.string.isRequired,
|
||||||
skip_action: React.PropTypes.func.isRequired,
|
skip_action: PropTypes.func.isRequired,
|
||||||
loading: React.PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user