Compare commits
769 Commits
Author | SHA1 | Date | |
---|---|---|---|
2f25dc6a20 | |||
fc38ee2f08 | |||
33bebc6629 | |||
14a8e02f99 | |||
0bf6c87ec7 | |||
422fba2835 | |||
f36dbb78e6 | |||
3213dd0d08 | |||
3f2199fd63 | |||
a4477a9bd6 | |||
52790d7bd6 | |||
0b9812210f | |||
756dbb4641 | |||
b38742ddc0 | |||
49e38549ec | |||
afcb0a0d7f | |||
d18f0e50b5 | |||
6868a07ead | |||
cca76d0d97 | |||
70fa77d333 | |||
412201d965 | |||
045a92c7d6 | |||
2ba2e3eca5 | |||
90c294f60e | |||
57b953dd14 | |||
0f81a8db60 | |||
2d6971f8df | |||
0abe8b5371 | |||
5bad682879 | |||
fa9d32c230 | |||
1b3ceca7b2 | |||
9be326b45d | |||
2d2e1298c4 | |||
5dc3a4386e | |||
b617dde266 | |||
962e91f9dc | |||
4047b41a7f | |||
59199140bf | |||
1079c0beae | |||
1694baab7d | |||
335ac9c778 | |||
65efd234f9 | |||
f726d943db | |||
2d1e950097 | |||
ca2f16970b | |||
4a123f8fe9 | |||
7b224328e1 | |||
699dfa19f0 | |||
06d56fe19d | |||
1b7ac62b5c | |||
ae358ce13e | |||
18f35b5e91 | |||
67ca305b7f | |||
cb0908fc70 | |||
42df59076d | |||
ff8be3bdc6 | |||
f7b6dcf409 | |||
940f3848dd | |||
e847ad2df2 | |||
2459a103fd | |||
0dd3f2178e | |||
de873eca71 | |||
ef461da77f | |||
caf6dcddfa | |||
9684c88651 | |||
256bca8ed9 | |||
8d56e8582f | |||
56959f2f49 | |||
bbc7de6898 | |||
0df246da15 | |||
757e18355d | |||
4b29b04bd1 | |||
6cb94bc413 | |||
f624c891ab | |||
b83abf0ac5 | |||
ef1b0036e5 | |||
5efa9f65c6 | |||
a8e3dd424e | |||
26628ba156 | |||
78cabde9e1 | |||
1ec0372c2d | |||
31e2d5e771 | |||
d2dbf86a9c | |||
1c39d39078 | |||
6d0795abba | |||
5f45c6cc74 | |||
d4d806e247 | |||
758a545eb6 | |||
578088d2e5 | |||
e17dba2b07 | |||
7e5cf533f0 | |||
f7c656aed5 | |||
2e42305710 | |||
b3e310652e | |||
6737158130 | |||
906558a772 | |||
d559483c7b | |||
bec3e02285 | |||
9d6d72dd8c | |||
3b76f838d1 | |||
909ad86e33 | |||
dd8f58e35e | |||
4b2061fcfa | |||
8e6ca502b3 | |||
ef85834db5 | |||
2bdcd0eb42 | |||
3b90be4122 | |||
375e70d84b | |||
42d586610e | |||
e4a5438512 | |||
11b22fa63a | |||
45b933d635 | |||
15cf087d40 | |||
ed09c3e5d4 | |||
285a556f21 | |||
dc1ef2af47 | |||
6f17f0d2d1 | |||
91076580ef | |||
d4abaa7150 | |||
d2bc0fd24a | |||
1f26079b5f | |||
87a1211a55 | |||
0ca0a7d029 | |||
cb3d49f200 | |||
c0da428c27 | |||
435cd9b777 | |||
bfde34eb8d | |||
a4c1b24c35 | |||
2cbd2d54f3 | |||
378f6d803a | |||
af4d29ebe6 | |||
599661e028 | |||
84294b7ee6 | |||
a3e6eb5bba | |||
67359980e9 | |||
f1b955d74a | |||
809be415c5 | |||
457a4d1bba | |||
1e16912763 | |||
e2c9971c99 | |||
c1d31ca400 | |||
a011c3aade | |||
e35e97cdbf | |||
49a59d35a1 | |||
9a46640c15 | |||
678a0b3835 | |||
0c008325c4 | |||
ad5441487b | |||
104620a40a | |||
c1a3ba67f5 | |||
c42bbf3dc4 | |||
7d34274fbf | |||
f930b3303b | |||
a25ea3ddf6 | |||
021349caee | |||
89f2958d23 | |||
8c8435766e | |||
40dd1bbb3b | |||
ba40437eb9 | |||
813db1ae33 | |||
9588397e4e | |||
ecf83ca419 | |||
9cc494f0fa | |||
dd4b7e4d1c | |||
738b2f6c17 | |||
33289342d3 | |||
a00f1efcfe | |||
b89897e6d4 | |||
c539837896 | |||
27edf5f71d | |||
32cc5644f9 | |||
8a664aa7f1 | |||
7e5e8a4282 | |||
70d5d609e2 | |||
19160c99e1 | |||
99b2a7457e | |||
945d7edc70 | |||
6a97badfed | |||
5ec8e4ed52 | |||
1569b5f80a | |||
e62ecc5036 | |||
b0150e184b | |||
8b96854f39 | |||
28d8600078 | |||
4f30158722 | |||
4486f9c5b0 | |||
8515dcf29b | |||
d16eb87782 | |||
3c77e5d25e | |||
aa1a2a0da9 | |||
7c9029b227 | |||
1823bf606a | |||
525b7fdd65 | |||
11031d2b56 | |||
4d45635d03 | |||
b81764402b | |||
947e1150d8 | |||
97f0e512af | |||
f15374de43 | |||
f082c065d1 | |||
72a9951125 | |||
a82d9a63d4 | |||
a7e9979781 | |||
cebd1ee7ae | |||
5faa467306 | |||
53eb9cd2ae | |||
7f6eed6d66 | |||
74f3fa65cd | |||
8723aa4e4e | |||
ccab8b4cf3 | |||
4c4a4ab31d | |||
45df02b0ec | |||
dd7067e590 | |||
1219b5ba49 | |||
01496ac813 | |||
3b3ccc18ce | |||
3dce951e66 | |||
db1dc172aa | |||
26c5cc1e43 | |||
f91bfbf473 | |||
3dae0ef13f | |||
3281ac390e | |||
d731a6b432 | |||
bb869e8ae8 | |||
7331e5cabd | |||
ba05ca35af | |||
91e7bf6336 | |||
ff58067d55 | |||
2ba6bb339e | |||
a47afdd313 | |||
608b559ee1 | |||
181ed45d0b | |||
29fac8d052 | |||
a3d7d53eea | |||
3f6caf5fa4 | |||
42c4139ba5 | |||
ad31b143d2 | |||
6ec15bec22 | |||
71d8fb0d93 | |||
2f293da7a3 | |||
17b56f0160 | |||
bb9fce7f82 | |||
ad4f1f8326 | |||
8ece62c9a6 | |||
a9b9e9c631 | |||
6e289b6a8f | |||
20ced8b099 | |||
f38b632707 | |||
8ce0595342 | |||
f11de2f1ad | |||
e28451d410 | |||
72882aaf2b | |||
fdf9dd0fa3 | |||
97dd0abea2 | |||
a099174226 | |||
a054acc6e6 | |||
74254d7e2a | |||
1795964c69 | |||
0118b2472a | |||
5fe03f0dee | |||
9aef6850c2 | |||
f688a69f8b | |||
b2682fa0b7 | |||
18b15c5440 | |||
c3416977bb | |||
88a00bc38c | |||
a1441dfde6 | |||
8e7336d352 | |||
e0e2933cdf | |||
c93ec629ea | |||
d613df6558 | |||
01c9096543 | |||
f120e839dd | |||
f0ab592c04 | |||
1a269d28b3 | |||
8d4a666bf0 | |||
4a96e483a6 | |||
263a66407f | |||
c4b728f4e1 | |||
9970ad7fb6 | |||
eb380499d9 | |||
4b528549f5 | |||
a903017bc2 | |||
afd25e9174 | |||
b1bbf1b3bc | |||
9e84e8df93 | |||
df775b5a07 | |||
c9c22b9b52 | |||
93d20688ea | |||
9ebcddfa2a | |||
85995bc8a6 | |||
a8f2959bc6 | |||
36242bd580 | |||
1e7dbc8449 | |||
12c159c627 | |||
82ed7e51c5 | |||
0b3aa0d12a | |||
4d788f69aa | |||
c721843c12 | |||
d2be407ccb | |||
e6337216cf | |||
f1c396f0b0 | |||
3622bc9fcb | |||
14fe333678 | |||
cf6466197a | |||
f56bee76f2 | |||
2ec6bc8c99 | |||
4aeccb1961 | |||
bd593b1ad4 | |||
bb7812bd5d | |||
73ed070a34 | |||
9e81c48bf8 | |||
f9028d28c0 | |||
da32b243ea | |||
5092c3d328 | |||
06ad4488bf | |||
a856800e6d | |||
dfc680f3a1 | |||
a9baecc504 | |||
11e15659ac | |||
ebe440a272 | |||
853794d459 | |||
cfea13bf81 | |||
7f291d80b9 | |||
9840b55de6 | |||
ca7322933f | |||
81569e5b81 | |||
1942972282 | |||
a23aac370c | |||
99d6f74d1b | |||
a883e1176c | |||
24b98a1154 | |||
8dbb6ab79f | |||
3e7d1690bd | |||
07d533a810 | |||
499936e3ab | |||
580ac989aa | |||
acf300160d | |||
983df4ee13 | |||
03c782d4ab | |||
0daf7e12c1 | |||
6a2e18a0e1 | |||
316d5ab183 | |||
eb6bba5961 | |||
b5864adf06 | |||
9bce50a633 | |||
365a53cf27 | |||
31a4575d43 | |||
1ae584c4e7 | |||
aac2cd6eb8 | |||
9874e1c371 | |||
16b1c0dc41 | |||
9223fbf478 | |||
eb27de36f4 | |||
636fa38ab6 | |||
9b1503dc7a | |||
2ac3b00af6 | |||
5fcdbfe826 | |||
67036ddb61 | |||
6c0f6a07cd | |||
8139a7dd0a | |||
97adfc14c0 | |||
4ed703a351 | |||
2aee853406 | |||
854736fac7 | |||
841c69af59 | |||
56b4688f93 | |||
e60bc7c387 | |||
6094a83f4b | |||
91ddb98f56 | |||
27d5972306 | |||
6088497433 | |||
0d894a6fef | |||
57f0b88299 | |||
5121dbe0c8 | |||
f874ffc19c | |||
e928a5c2bc | |||
d11badf3ce | |||
3006c982cb | |||
b29e31fdd6 | |||
bc42c8e280 | |||
409697ee64 | |||
cfb4265971 | |||
13d78aac05 | |||
6f176f4e6c | |||
9b584296a5 | |||
5ea87c5eed | |||
7522084ccb | |||
214aa60d0e | |||
7a049ce1b7 | |||
94d293deb7 | |||
bd2d38d757 | |||
abec524daa | |||
cac6beb4ac | |||
cac995e15b | |||
b26380fd10 | |||
6c6a4070be | |||
8b001d820b | |||
2ae3d8ebdf | |||
459ec21f9d | |||
9c7790d07e | |||
f9c5b99e46 | |||
95b0b39366 | |||
4b6fa0e760 | |||
67a3440ced | |||
0de372344a | |||
7a04eeb650 | |||
8dbfe82922 | |||
7322f2151c | |||
c43d2f240d | |||
bbcd267b6f | |||
bbc4acb2a4 | |||
c89cc5a919 | |||
33075940de | |||
51c09b8360 | |||
2fbf85f371 | |||
24cb614adb | |||
55f851208b | |||
990dac7727 | |||
233020ca20 | |||
0c419cde16 | |||
35f9530d8e | |||
992fe2a6e9 | |||
c2cb88f995 | |||
ba69d659ab | |||
397d988eb1 | |||
d85f2341ec | |||
2b3c288b5f | |||
8ec28a23a7 | |||
12c9623e2f | |||
8ba9fdccbc | |||
a2ef62302f | |||
24ecc879d3 | |||
a1104a7f90 | |||
aa959810e9 | |||
45b7a79277 | |||
41c8c0dae5 | |||
2aeab7aaff | |||
4fd0c4b484 | |||
d4623cf763 | |||
181c4fed08 | |||
7884dd8389 | |||
b577d33414 | |||
70de0a01bf | |||
3b7f77d9af | |||
21847ca875 | |||
6153316047 | |||
32f8f07602 | |||
70a04d9bf6 | |||
bb1cc997cc | |||
24f96d9d7d | |||
46c7332da2 | |||
2f42f643ab | |||
63c87f3746 | |||
d4d575cda4 | |||
2cf03ec0a3 | |||
72ad98a77f | |||
b5094f568c | |||
7d224274fc | |||
f3b9f7be92 | |||
6e74f82ace | |||
831eb6af44 | |||
1e8e5aecee | |||
c0f98c9ba6 | |||
746c19d6ed | |||
894a9e8c90 | |||
8fea917337 | |||
c60425afb2 | |||
0776e9ad73 | |||
91981cc324 | |||
1906fafacb | |||
c11d95b402 | |||
b4c8fe6f45 | |||
d0e770e0fc | |||
3d6d1a4282 | |||
dc3b47db00 | |||
900d6694e2 | |||
e8074a61a5 | |||
64501a914a | |||
de70e855ad | |||
03e3b5a94b | |||
3331bed31c | |||
8c5a33a0fe | |||
01eb6c7a98 | |||
f502e0b677 | |||
a6b64a1c5d | |||
5019131b21 | |||
da483fb88f | |||
788bed4622 | |||
3fbe5423d0 | |||
8357295be2 | |||
8072b162d4 | |||
3f2f0ec1a9 | |||
5a5a777b7d | |||
6cac7f3652 | |||
6a9313107c | |||
72c9d301b7 | |||
ad925de801 | |||
1da28b7299 | |||
e837ad7014 | |||
daec56191f | |||
7bd25660df | |||
3b9821fbe1 | |||
cabe2d61b7 | |||
a6d802e2fa | |||
1732c4f634 | |||
bb77134224 | |||
f1cb64b240 | |||
3689545589 | |||
9b67c56281 | |||
dc38b19667 | |||
a574733217 | |||
b90aaa629e | |||
8de186c0e6 | |||
e3719967f9 | |||
138a631ed7 | |||
07b7636a72 | |||
a63ce3cdac | |||
f5c7bb87af | |||
2c8d925971 | |||
0c5beb2511 | |||
9c0316a87d | |||
46c1b682fa | |||
7954346a3f | |||
d87ff67f50 | |||
6642bb3bfa | |||
2cb32e7a78 | |||
fcea9adbd9 | |||
bbdd0dbb6e | |||
1b2cf7bd16 | |||
b7cfa549d5 | |||
ffc1d0a61c | |||
d1b160def7 | |||
493fd01754 | |||
9ced4b1757 | |||
17010e5ba9 | |||
42ad7584d4 | |||
dbc0f9b238 | |||
e62e9a5892 | |||
bc25fa61b4 | |||
2590967183 | |||
86eafd3c17 | |||
90a6f160c2 | |||
c774aec6a2 | |||
8f2fd1d76e | |||
4df11163a1 | |||
82a736ffbb | |||
87052986e8 | |||
0c73c0fadc | |||
5c7e11076d | |||
d1df94c759 | |||
53cc39c6f5 | |||
4955c72ee1 | |||
16661af8c3 | |||
bc80f69e41 | |||
0192934e65 | |||
2793e74858 | |||
5996696cc9 | |||
7f6cf5bbf3 | |||
c0ef2254cd | |||
0dbe04c3f8 | |||
ef1805d9b5 | |||
514f539e83 | |||
50f072705e | |||
f8f7bc3d3d | |||
f1bf2bb097 | |||
bbe2f69a7f | |||
c844488b0b | |||
112fe0cd6e | |||
c9e6dce785 | |||
d1c09c015a | |||
8d61866b77 | |||
0831c748b1 | |||
b2a0bc3860 | |||
1f99345e7b | |||
89782bc94b | |||
155ff09280 | |||
132e6d3342 | |||
f02699158f | |||
be3462925d | |||
3d82230d10 | |||
c20c46fd86 | |||
6e63c72aa5 | |||
e059eec5ea | |||
a2d38c9076 | |||
8d507b2ee0 | |||
5e2979c283 | |||
84ec0de3cd | |||
ee07780833 | |||
ed104156a9 | |||
90f2e9ff9d | |||
98fb7aa65e | |||
a51eb59cf8 | |||
270023b89c | |||
7d77e075e9 | |||
9bbe36b3cb | |||
7a904ed093 | |||
e3c065b353 | |||
4ca2872e0e | |||
ce338f7fe2 | |||
fa28b0a955 | |||
a298650187 | |||
95772ef68a | |||
e1c94db516 | |||
7be1a11d1e | |||
c04b95c09a | |||
cdfeb8d512 | |||
3ef8067968 | |||
9fb04bc3c0 | |||
25727ea0ba | |||
4c8ac369b7 | |||
268dabdc9f | |||
c0ba218949 | |||
ee85139089 | |||
b4f83fe1bd | |||
bd83001ef5 | |||
fb3a9f485f | |||
fd44776ae9 | |||
6dbe338b01 | |||
1f06a7dd0b | |||
37c218f782 | |||
25016f2a8d | |||
792744a270 | |||
c5fbfca132 | |||
c2fde308cb | |||
533d9b0d38 | |||
2035b802e3 | |||
a5e66ec6a0 | |||
beb939df9e | |||
44e342c692 | |||
3a417d460f | |||
1950d6661f | |||
da6e154642 | |||
acebf669a7 | |||
4a2bbe3f88 | |||
9b011c0281 | |||
bf58d8a22d | |||
72d1eb79a6 | |||
bb4893c0a0 | |||
9929cf0aee | |||
83967e84ba | |||
9621cb3ca9 | |||
a413f666fe | |||
d1e2c6c074 | |||
3b6a9f7a6e | |||
d2e5fb89c2 | |||
97d1e95037 | |||
48fbce22e7 | |||
916fe76795 | |||
e10310fb5c | |||
367afcf814 | |||
67fa9e0993 | |||
d7553a5f27 | |||
8c847825fa | |||
d21d9b99b0 | |||
8461c13532 | |||
3b7ffe9ba7 | |||
1724fa22c1 | |||
01089d7a72 | |||
717ebfd20c | |||
daff3d5016 | |||
98fb838169 | |||
f13a4dd4f3 | |||
62a164e4c6 | |||
04238f3339 | |||
bc62474f3c | |||
98005a2a6f | |||
b7fe8dc6d6 | |||
436faea591 | |||
4208b148b4 | |||
5ce5f0bf8a | |||
18518de397 | |||
68f747211a | |||
ebd26ddd98 | |||
924aa0439f | |||
3124d6a61b | |||
149d031b52 | |||
fa96c4697d | |||
d46c9d5412 | |||
9ab8b1f0c5 | |||
9425ac1593 | |||
4e32479609 | |||
7d95b38dc4 | |||
17c83c5bd4 | |||
23bb2f111f | |||
4655b2c64c | |||
84fede11b8 | |||
f37488fc0b | |||
20e2e03982 | |||
a5d96f1534 | |||
a516eb1a95 | |||
6dd8270bec | |||
981cbd579f | |||
52a0aae10f | |||
ebca4257a6 | |||
c3944095bc | |||
a1445d1b6a | |||
e62c24879b | |||
00f06ea202 | |||
32ca24ce38 | |||
44e3adb422 | |||
a1346ebecc | |||
25b51d0446 | |||
556a170903 | |||
7ac386a8e2 | |||
d91b55ec52 | |||
786fbc36a2 | |||
160e8b7a12 | |||
0b1f85da09 | |||
fbc6f54ddc | |||
a603e97b8c | |||
0875b627b6 | |||
035784ece0 | |||
aa93c7349f | |||
82cf4a28fd | |||
832e5ef342 | |||
e3de3a123a | |||
db91590159 | |||
28c61fca0b | |||
e62a8c2ec5 | |||
fdbd1245e3 | |||
0eef46db57 | |||
080ae88a04 | |||
225be9f3cd | |||
c9a42ebb76 | |||
ae9b3df92d | |||
63a08ebb55 | |||
2ef5096fa9 | |||
95cfe2d8ec | |||
49676b3fc5 | |||
c96ac06423 | |||
6a3166c311 | |||
0017df1c2d | |||
f2a0d4ce96 | |||
cb50517cbc | |||
0fedd1779f | |||
1625e1771b | |||
bde78b607b | |||
f3c58c27be | |||
9fec460295 | |||
162859529e | |||
3bdaaf8368 | |||
f6ab0050b2 | |||
10a20935c3 | |||
8135b677f7 | |||
90382bc86d | |||
47af8939cc | |||
4a0deb2182 | |||
3a206b2c88 | |||
70cfcbe7cc | |||
6342cb17bd | |||
64c35b12c8 | |||
6a14f97419 | |||
dfec34eda9 | |||
893231e8e5 | |||
e9110680ee | |||
7b54285ca6 | |||
33ea16eb0f | |||
b1ae07d38e | |||
3f168d052f | |||
158d26ef86 | |||
ae03ee2c46 | |||
ad0adb48e1 | |||
ff5353c894 | |||
abb2389378 | |||
3cf50810f9 | |||
20a6e6d6de | |||
0b1fc8f6c3 | |||
0a771acb02 |
16
.env.sample
@ -1,2 +1,16 @@
|
||||
WP_TEST_PATH="/var/www/wordpress"
|
||||
|
||||
WP_TEST_ENABLE_NETWORK_TESTS="true"
|
||||
WP_TEST_IMPORT_MAILCHIMP_API=""
|
||||
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
|
||||
WP_TEST_MAILER_ENABLE_SENDING="true"
|
||||
WP_TEST_MAILER_AMAZON_ACCESS=""
|
||||
WP_TEST_MAILER_AMAZON_SECRET=""
|
||||
WP_TEST_MAILER_AMAZON_REGION=""
|
||||
WP_TEST_MAILER_ELASTICEMAIL_API=""
|
||||
WP_TEST_MAILER_MAILGUN_API=""
|
||||
WP_TEST_MAILER_MAILGUN_DOMAIN=""
|
||||
WP_TEST_MAILER_MAILPOET_API=""
|
||||
WP_TEST_MAILER_SENDGRID_API=""
|
||||
WP_TEST_MAILER_SMTP_HOST=""
|
||||
WP_TEST_MAILER_SMTP_LOGIN=""
|
||||
WP_TEST_MAILER_SMTP_PASSWORD=""
|
5
.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
.DS_Store
|
||||
TODO
|
||||
composer.phar
|
||||
vendor
|
||||
/vendor
|
||||
tests/_output/*
|
||||
tests/acceptance.suite.yml
|
||||
tests/_support/_generated/*
|
||||
@ -15,4 +15,5 @@ temp
|
||||
wysija-newsletters.zip
|
||||
tests/javascript/testBundles
|
||||
assets/css/*.css
|
||||
assets/js/*.js
|
||||
assets/js/*.js
|
||||
.vagrant
|
||||
|
11
README.md
@ -8,7 +8,6 @@ MailPoet done the right way.
|
||||
```
|
||||
php
|
||||
nodejs
|
||||
phantomjs
|
||||
wordpress
|
||||
```
|
||||
|
||||
@ -47,16 +46,6 @@ $ ./do compile:all
|
||||
$ ./do test:unit
|
||||
```
|
||||
|
||||
- Acceptance tests:
|
||||
```sh
|
||||
$ ./do test:acceptance
|
||||
```
|
||||
|
||||
- Run all tests:
|
||||
```sh
|
||||
$ ./do test:all
|
||||
```
|
||||
|
||||
- Debug tests:
|
||||
```sh
|
||||
$ ./do test:debug
|
||||
|
35
RoboFile.php
@ -47,6 +47,19 @@ class RoboFile extends \Robo\Tasks {
|
||||
->run();
|
||||
}
|
||||
|
||||
function watchCss() {
|
||||
$css_files = $this->rsearch('assets/css/src/', array('styl'));
|
||||
$this->taskWatch()
|
||||
->monitor($css_files, function() {
|
||||
$this->compileCss();
|
||||
})
|
||||
->run();
|
||||
}
|
||||
|
||||
function watchJs() {
|
||||
$this->_exec('./node_modules/webpack/bin/webpack.js --watch');
|
||||
}
|
||||
|
||||
function compileAll() {
|
||||
$this->compileJs();
|
||||
$this->compileCss();
|
||||
@ -61,7 +74,8 @@ class RoboFile extends \Robo\Tasks {
|
||||
'assets/css/src/admin.styl',
|
||||
'assets/css/src/newsletter_editor/newsletter_editor.styl',
|
||||
'assets/css/src/public.styl',
|
||||
'assets/css/src/rtl.styl'
|
||||
'assets/css/src/rtl.styl',
|
||||
'assets/css/src/importExport.styl'
|
||||
);
|
||||
|
||||
$this->_exec(join(' ', array(
|
||||
@ -75,14 +89,14 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
function makepot() {
|
||||
$this->_exec('grunt makepot'.
|
||||
$this->_exec('./node_modules/.bin/grunt makepot'.
|
||||
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
|
||||
' --base_path '.__DIR__
|
||||
);
|
||||
}
|
||||
|
||||
function pushpot() {
|
||||
$this->_exec('grunt pushpot'.
|
||||
$this->_exec('./node_modules/.bin/grunt pushpot'.
|
||||
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
|
||||
' --base_path '.__DIR__
|
||||
);
|
||||
@ -94,11 +108,22 @@ class RoboFile extends \Robo\Tasks {
|
||||
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
|
||||
}
|
||||
|
||||
function testCoverage($file = null) {
|
||||
$this->loadEnv();
|
||||
$this->_exec('vendor/bin/codecept build');
|
||||
$this->_exec(join(' ', array(
|
||||
'vendor/bin/codecept run',
|
||||
(($file) ? $file : ''),
|
||||
'--coverage',
|
||||
'--coverage-html'
|
||||
)));
|
||||
}
|
||||
|
||||
function testJavascript() {
|
||||
$this->compileJs();
|
||||
|
||||
$this->_exec(join(' ', array(
|
||||
'./node_modules/mocha/bin/mocha',
|
||||
'./node_modules/.bin/mocha',
|
||||
'-r tests/javascript/mochaTestHelper.js',
|
||||
'tests/javascript/testBundles/**/*.js'
|
||||
)));
|
||||
@ -120,4 +145,4 @@ class RoboFile extends \Robo\Tasks {
|
||||
$dotenv = new Dotenv\Dotenv(__DIR__);
|
||||
$dotenv->load();
|
||||
}
|
||||
}
|
||||
}
|
@ -5,12 +5,17 @@
|
||||
@require 'common'
|
||||
@require 'modal'
|
||||
@require 'notice'
|
||||
@require 'parsley'
|
||||
|
||||
@require 'form_editor'
|
||||
@require 'listing'
|
||||
@require 'box'
|
||||
@require 'breadcrumb'
|
||||
@require 'form'
|
||||
|
||||
@require 'settings'
|
||||
@require 'form'
|
||||
@require 'parsley'
|
||||
@require 'form_validation'
|
||||
|
||||
@require 'settings'
|
||||
@require 'progress_bar'
|
||||
|
||||
@require 'subscribers'
|
@ -19,6 +19,8 @@ a:focus
|
||||
|
||||
// select 2
|
||||
.select2-container
|
||||
width: 25em !important
|
||||
|
||||
// textareas
|
||||
textarea.regular-text
|
||||
width: 25em !important
|
||||
@ -26,3 +28,25 @@ textarea.regular-text
|
||||
@media screen and (max-width: 782px)
|
||||
.select2-container
|
||||
width: 100% !important
|
||||
|
||||
// progress bars
|
||||
progress-border-radius = 5px
|
||||
progress-background = #efefef
|
||||
progress-foreground = #69b1e9
|
||||
|
||||
progress
|
||||
background-color: progress-background;
|
||||
height: 2em
|
||||
border: 0
|
||||
width: 100%
|
||||
|
||||
progress::-webkit-progress-bar
|
||||
background-color: progress-background;
|
||||
|
||||
progress::-webkit-progress-value
|
||||
background-color: progress-foreground
|
||||
border-radius: progress-border-radius
|
||||
|
||||
progress::-moz-progress-bar
|
||||
background-color: progress-foreground
|
||||
border-radius: progress-border-radius
|
||||
|
@ -4,6 +4,9 @@
|
||||
icons = '../img/form_editor_icons.png'
|
||||
handle_icon = '../img/handle.png'
|
||||
|
||||
#mailpoet_form_name
|
||||
font-size: 23px
|
||||
|
||||
#mailpoet_form_history
|
||||
display: none
|
||||
|
||||
@ -99,6 +102,7 @@ handle_icon = '../img/handle.png'
|
||||
/* MailPoet Form wrapper */
|
||||
#mailpoet_form_wrapper
|
||||
position: relative
|
||||
margin: 20px 0 0 0
|
||||
|
||||
/* MailPoet Form container */
|
||||
#mailpoet_form_container
|
||||
@ -121,6 +125,7 @@ handle_icon = '../img/handle.png'
|
||||
float: none
|
||||
|
||||
#mailpoet_form_toolbar
|
||||
z-index: 999
|
||||
position: absolute
|
||||
width: 400px
|
||||
|
||||
@ -558,3 +563,6 @@ handle_icon = '../img/handle.png'
|
||||
.CodeMirror
|
||||
border: 1px solid #eee
|
||||
|
||||
/* Settings */
|
||||
#mailpoet_form_segments.parsley-error + span .select2-selection
|
||||
border: 1px solid #b94a48
|
6
assets/css/src/form_validation.styl
Normal file
@ -0,0 +1,6 @@
|
||||
.parsley-errors-list
|
||||
margin-top: 8px
|
||||
|
||||
.parsley-required
|
||||
.parsley-custom-error-message
|
||||
color: #b94a48
|
78
assets/css/src/importExport.styl
Normal file
@ -0,0 +1,78 @@
|
||||
.mailpoet_hidden, .mailpoet_validation_error
|
||||
display none
|
||||
|
||||
.form-table
|
||||
th
|
||||
width 300px
|
||||
|
||||
#paste_input
|
||||
width 100%
|
||||
|
||||
input[type="radio"]
|
||||
margin-right 0.5em !important
|
||||
& + span
|
||||
margin-right 2.5em
|
||||
|
||||
span
|
||||
&.mailpoet_mailchimp-key-status
|
||||
&.mailpoet_mailchimp-ok
|
||||
&:before
|
||||
content "\2713"
|
||||
color #0e90d2
|
||||
margin-left 15px
|
||||
&.mailpoet_mailchimp-error
|
||||
&:before
|
||||
content "\2717"
|
||||
color #900
|
||||
margin-left 15px
|
||||
|
||||
#subscribers_data
|
||||
overflow auto
|
||||
table
|
||||
width auto
|
||||
td
|
||||
padding 0.5em
|
||||
& > table
|
||||
& > tbody
|
||||
& > td
|
||||
padding 0.5em
|
||||
& > tr
|
||||
&:nth-child(odd)
|
||||
background #f9f9f9
|
||||
.mailpoet_header
|
||||
text-transform uppercase
|
||||
font-weight 600
|
||||
text-decoration underline
|
||||
|
||||
#subscribers_data th:first-child, #subscribers_data td:first-child
|
||||
width 10em !important
|
||||
text-align center !important
|
||||
padding 0 1em 0 1em !important
|
||||
vertical-align inherit !important
|
||||
|
||||
#subscribers_data
|
||||
& > table
|
||||
& > thead
|
||||
& > tr
|
||||
& > th
|
||||
& > span
|
||||
width 15em !important
|
||||
|
||||
.mailpoet_data_match
|
||||
color #0e90d2
|
||||
margin-left 0.25em
|
||||
|
||||
.mailpoet_import_error, .mailpoet_validation_error
|
||||
color #900
|
||||
|
||||
tr
|
||||
&.mailpoet_segments
|
||||
& > td
|
||||
& > a
|
||||
margin-left 15px
|
||||
|
||||
span
|
||||
&.select2-search
|
||||
&.select2-search--dropdown
|
||||
display none !important
|
||||
|
@ -90,7 +90,7 @@ body.mailpoet_modal_opened
|
||||
padding: 0
|
||||
margin: 0
|
||||
width: 100%
|
||||
transition: margin 0.3s ease-out
|
||||
transition: margin 350ms ease-out
|
||||
|
||||
.mailpoet_panel_wrapper
|
||||
background-color: #f1f1f1
|
||||
@ -159,23 +159,6 @@ body.mailpoet_modal_opened
|
||||
margin: 0
|
||||
text-align: right
|
||||
|
||||
.mailpoet_button
|
||||
padding: 3px 15px
|
||||
border: 1px solid #444
|
||||
font-weight: normal
|
||||
cursor: pointer
|
||||
background-color: #222
|
||||
color: #cfcfcf
|
||||
font-size: 1em
|
||||
|
||||
.mailpoet_button:hover
|
||||
background-color: #00aacc
|
||||
color: #fff
|
||||
|
||||
.mailpoet_button:active
|
||||
background-color: #00ccff
|
||||
color: #fff
|
||||
|
||||
@media screen and (max-width: 782px)
|
||||
#mailpoet_modal_overlay.mailpoet_panel_overlay
|
||||
top: 46px
|
||||
@ -217,4 +200,4 @@ body.mailpoet_modal_opened
|
||||
0%
|
||||
50%
|
||||
background-color: #064E6D
|
||||
100%
|
||||
100%
|
||||
|
@ -7,7 +7,6 @@ $tool-active-secondary-color = #ffffff
|
||||
|
||||
$tool-width = 20px
|
||||
$master-column-tool-width = 24px
|
||||
$layer-selector-width = 30px
|
||||
|
||||
.mailpoet_tools
|
||||
position: absolute
|
||||
@ -33,10 +32,35 @@ $layer-selector-width = 30px
|
||||
width: $master-column-tool-width
|
||||
height: $master-column-tool-width
|
||||
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
max-width: 100%
|
||||
max-height: $master-column-tool-width
|
||||
opacity: 1
|
||||
display: block
|
||||
|
||||
.mailpoet_delete_block_confirm,
|
||||
.mailpoet_delete_block_cancel
|
||||
max-width: 100%
|
||||
max-height: 0
|
||||
opacity: 0
|
||||
overflow: hidden
|
||||
display: block
|
||||
|
||||
.mailpoet_delete_block_activated
|
||||
width: auto
|
||||
height: auto
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
overflow: hidden
|
||||
max-height: 0
|
||||
opacity: 0
|
||||
|
||||
.mailpoet_delete_block_confirm,
|
||||
.mailpoet_delete_block_cancel
|
||||
max-height: $master-column-tool-width*2
|
||||
opacity: 1
|
||||
|
||||
.mailpoet_tool
|
||||
display: inline-block
|
||||
width: $tool-width
|
||||
@ -76,28 +100,45 @@ $layer-selector-width = 30px
|
||||
display: inline-block
|
||||
padding: 2px
|
||||
vertical-align: top
|
||||
animation-background-color()
|
||||
|
||||
.mailpoet_tool
|
||||
padding: 0
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
max-width: $tool-width
|
||||
display: inline-block
|
||||
opacity: 1
|
||||
animation-fade-in-and-scale-horizontally()
|
||||
|
||||
.mailpoet_delete_block_confirm,
|
||||
.mailpoet_delete_block_cancel
|
||||
display: none
|
||||
max-width: 0
|
||||
opacity: 0
|
||||
overflow: hidden
|
||||
display: inline-block
|
||||
animation-fade-in-and-scale-horizontally()
|
||||
|
||||
.mailpoet_delete_block_activated
|
||||
height: auto
|
||||
width: auto
|
||||
border-radius(3px)
|
||||
background-color: $warning-background-color
|
||||
padding: 3px 5px
|
||||
line-height: 1.2em
|
||||
height: auto
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
display: none
|
||||
overflow: hidden
|
||||
max-width: 0
|
||||
opacity: 0
|
||||
|
||||
.mailpoet_delete_block_confirm,
|
||||
.mailpoet_delete_block_cancel
|
||||
display: inline-block
|
||||
max-width: 100%
|
||||
opacity: 1
|
||||
|
||||
.mailpoet_delete_block_cancel
|
||||
margin-left: 3px
|
||||
|
||||
.mailpoet_delete_block_confirm
|
||||
color: $warning-text-color
|
||||
|
@ -52,6 +52,7 @@ $draggable-widget-z-index = 2
|
||||
padding: 0
|
||||
margin: 0
|
||||
z-index: $draggable-widget-z-index
|
||||
animation-fade-in()
|
||||
|
||||
.mailpoet_widget_icon
|
||||
padding: 0
|
||||
|
@ -37,3 +37,6 @@
|
||||
|
||||
input[type=text]
|
||||
vertical-align: middle
|
||||
|
||||
.mailpoet_form_field_block
|
||||
display: block
|
||||
|
@ -38,9 +38,7 @@
|
||||
content: '\f142'
|
||||
|
||||
.mailpoet_save_show_options_icon
|
||||
width: auto
|
||||
height: auto
|
||||
line-height: auto
|
||||
vertical-align: middle
|
||||
|
||||
&::before
|
||||
content: '\f140'
|
||||
@ -48,7 +46,7 @@
|
||||
.mailpoet_save_as_template_container,
|
||||
.mailpoet_export_template_container
|
||||
border-radius(3px)
|
||||
float: left
|
||||
display: inline-block
|
||||
clear: both
|
||||
|
||||
margin-top: 5px
|
||||
|
@ -26,13 +26,9 @@ $widget-icon-width = 30px
|
||||
border-right: 0
|
||||
|
||||
&.closed .mailpoet_region_content
|
||||
max-height: 0px
|
||||
overflow: hidden
|
||||
margin-top: 0
|
||||
display: none
|
||||
|
||||
.mailpoet_region_content
|
||||
max-height: 2000px
|
||||
transition: max-height 0.2s ease
|
||||
padding: 0 20px
|
||||
margin-top: 12px
|
||||
|
||||
|
@ -18,3 +18,12 @@
|
||||
|
||||
& > .mailpoet_block
|
||||
width: 100%
|
||||
|
||||
.mailpoet_automated_latest_content_display_options
|
||||
animation-slide-open-downwards()
|
||||
|
||||
.mailpoet_automated_latest_content_show_amount
|
||||
width: 25px
|
||||
|
||||
.mailpoet_automated_latest_content_content_type
|
||||
width: 180px
|
||||
|
@ -1,4 +1,5 @@
|
||||
$block-hover-highlight-color = $primary-active-color
|
||||
$block-text-line-height = $text-line-height
|
||||
|
||||
.mailpoet_block
|
||||
box-sizing: border-box
|
||||
@ -30,3 +31,34 @@ $block-hover-highlight-color = $primary-active-color
|
||||
|
||||
.mailpoet_content
|
||||
position: relative
|
||||
line-height: $block-text-line-height
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6
|
||||
line-height: $block-text-line-height
|
||||
padding: 0
|
||||
margin: 0
|
||||
font-style: normal
|
||||
font-weight: normal
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
margin-bottom: 0.3em
|
||||
|
||||
p
|
||||
margin-top: 0
|
||||
margin-bottom: 0
|
||||
font-weight: normal
|
||||
|
||||
ul
|
||||
padding: 0
|
||||
margin-top: 10px
|
||||
margin-bottom: 10px
|
||||
|
||||
li
|
||||
margin-top: 0
|
||||
font-weight: normal
|
||||
margin-bottom: 10px
|
||||
|
||||
blockquote
|
||||
margin: 0 0 $block-text-line-height
|
||||
padding-left: 10px
|
||||
border-left: 2px #565656 solid
|
||||
|
@ -1,9 +1,4 @@
|
||||
$button-vertical-padding = 13px
|
||||
|
||||
.mailpoet_button_block
|
||||
padding-top: $button-vertical-padding
|
||||
padding-bottom: $button-vertical-padding
|
||||
|
||||
overflow: hidden
|
||||
|
||||
.mailpoet_editor_button
|
||||
|
@ -79,3 +79,4 @@ $three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
|
||||
box-shadow(inset 1px 2px 1px $primary-inactive-color)
|
||||
color: #656565
|
||||
border-radius(3px)
|
||||
animation-background-color()
|
||||
|
@ -19,6 +19,10 @@ $divider-hover-border-color = $primary-active-color
|
||||
width: 100%
|
||||
border: 1px solid transparent
|
||||
|
||||
.mailpoet_active_divider_style
|
||||
border: 1px solid $active-divider-border-color
|
||||
background: $active-divider-background-color
|
||||
|
||||
.mailpoet_field_divider_style:hover
|
||||
border: 1px solid $divider-hover-border-color
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
.mailpoet_footer_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
margin-bottom: 0
|
||||
|
||||
.mailpoet_content
|
||||
padding: 5px 20px
|
||||
padding: 10px 20px
|
||||
|
||||
& > *:last-child
|
||||
margin-bottom: 0
|
||||
|
@ -1,6 +1,10 @@
|
||||
.mailpoet_header_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
margin-bottom: 0
|
||||
|
||||
.mailpoet_content
|
||||
padding: 5px 20px
|
||||
padding: 10px 20px
|
||||
|
||||
& > *:last-child
|
||||
margin-bottom: 0
|
||||
|
@ -1,5 +1,11 @@
|
||||
.mailpoet_image_block
|
||||
|
||||
img
|
||||
vertical-align: bottom
|
||||
max-width: 100%
|
||||
width: auto
|
||||
height: auto
|
||||
|
||||
&.mailpoet_full_image
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
@ -8,8 +14,3 @@
|
||||
.mailpoet_content a:hover
|
||||
cursor: all-scroll
|
||||
|
||||
img
|
||||
vertical-align: bottom
|
||||
max-width: $newsletter-width
|
||||
width: 100%
|
||||
height: auto
|
||||
|
@ -1,15 +1,12 @@
|
||||
.mailpoet_posts_block
|
||||
box-shadow(none)
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
|
||||
& > .mailpoet_content
|
||||
font-size: 1em
|
||||
text-align: center
|
||||
background-color: $primary-active-color
|
||||
margin: 20px 0
|
||||
padding: 15px
|
||||
box-shadow(inset 1px 2px 1px $primary-inset-shadow-color)
|
||||
color: $white-color
|
||||
border-radius(3px)
|
||||
.mailpoet_posts_block_posts
|
||||
overflow: auto
|
||||
|
||||
& > .mailpoet_block
|
||||
width: 100%
|
||||
|
||||
.mailpoet_post_selection_filter_row
|
||||
margin-top: 5px
|
||||
@ -18,7 +15,11 @@
|
||||
.mailpoet_posts_categories_and_tags
|
||||
width: 100%
|
||||
|
||||
.mailpoet_settings_posts_show_display_options
|
||||
.mailpoet_settings_posts_display_options
|
||||
.mailpoet_settings_posts_selection
|
||||
animation-slide-open-downwards()
|
||||
|
||||
.mailpoet_settings_posts_show_display_options,
|
||||
.mailpoet_settings_posts_show_post_selection
|
||||
display: block
|
||||
margin-top: 10px
|
||||
|
@ -1,4 +1,4 @@
|
||||
$social-block-vertical-padding = 11px
|
||||
$social-block-vertical-padding = 0px
|
||||
|
||||
$social-icon-width = 32px
|
||||
$active-social-icon-set-border-color = #adadad
|
||||
|
@ -1,17 +1,13 @@
|
||||
$text-vertical-padding = 3px
|
||||
|
||||
.mailpoet_text_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
|
||||
& > .mailpoet_content
|
||||
overflow: hidden
|
||||
padding-top: 13px
|
||||
padding-bottom: 13px
|
||||
padding-top: 0
|
||||
padding-bottom: 0px
|
||||
padding-left: 20px
|
||||
padding-right: 20px
|
||||
|
||||
blockquote
|
||||
margin: 1em
|
||||
padding-left: 1em
|
||||
border-left: 2px #565656 solid
|
||||
& > *:last-child
|
||||
margin-bottom: 0
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Fix select2 z-index to work with MailPoet.Modal */
|
||||
.select2-dropdown
|
||||
z-index: 101000
|
||||
z-index: 101000 !important
|
||||
|
||||
/* Remove input field styles from select2 type input */
|
||||
.select2-container
|
||||
@ -129,3 +129,21 @@ body
|
||||
|
||||
#mailpoet_modal_close
|
||||
display: none
|
||||
|
||||
.wrap > .mailpoet_notice,
|
||||
.update-nag
|
||||
margin-left: 2px + 15px !important
|
||||
|
||||
/* Make a button group */
|
||||
.mailpoet_button_group
|
||||
|
||||
.button:first-child
|
||||
border-right: 0
|
||||
border-top-right-radius: 0
|
||||
border-bottom-right-radius: 0
|
||||
|
||||
.button:last-child
|
||||
border-left: 0
|
||||
border-top-left-radius: 0
|
||||
border-bottom-left-radius: 0
|
||||
|
||||
|
31
assets/css/src/newsletter_editor/mixins/transitions.styl
Normal file
@ -0,0 +1,31 @@
|
||||
animation-slide-open-downwards()
|
||||
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
max-height: 2000px
|
||||
opacity: 1
|
||||
|
||||
&.mailpoet_closed
|
||||
max-height: 0
|
||||
opacity: 0
|
||||
overflow-y: hidden
|
||||
|
||||
animation-background-color()
|
||||
transition: background 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
|
||||
animation-fade-in()
|
||||
animation-name: fadeIn
|
||||
animation-duration: 300ms
|
||||
animation-fill-mode: forwards
|
||||
animation-timing-function: ease-in
|
||||
|
||||
animation-fade-in-and-scale-horizontally()
|
||||
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
@require 'mixins/border-radius'
|
||||
@require 'mixins/box-shadow'
|
||||
@require 'mixins/filter-shadow'
|
||||
@require 'mixins/transitions'
|
||||
|
||||
@require 'variables'
|
||||
@require 'common'
|
||||
|
@ -24,3 +24,5 @@ $error-text-color = #d54e21
|
||||
|
||||
// Dimensions
|
||||
$newsletter-width = 660px
|
||||
|
||||
$text-line-height = 1.6em
|
||||
|
29
assets/css/src/progress_bar.styl
Normal file
@ -0,0 +1,29 @@
|
||||
.mailpoet_progress
|
||||
background-color: #efefef
|
||||
height: 25px
|
||||
padding: 0
|
||||
width: 100%
|
||||
margin: 0
|
||||
border-radius: 5px
|
||||
position: relative
|
||||
|
||||
.mailpoet_progress_label
|
||||
position: absolute
|
||||
width: 100%
|
||||
text-align: center
|
||||
display: inline-block
|
||||
margin: 2px 0 0 0
|
||||
|
||||
.mailpoet_progress_bar
|
||||
position: absolute
|
||||
display: inline-block
|
||||
height: 100%
|
||||
border-radius: 3px
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset
|
||||
background-color: #34c2e3
|
||||
background-image: linear-gradient(top, #34c2e3, darken(#34c2e3, 20%))
|
||||
|
||||
.mailpoet_progress_complete
|
||||
.mailpoet_progress_bar
|
||||
background-color: #fecf23
|
||||
background-image: linear-gradient(top, #fecf23, #fd9215)
|
@ -1,3 +1,14 @@
|
||||
@import 'nib'
|
||||
|
||||
@require 'parsley'
|
||||
@require 'form_validation'
|
||||
|
||||
/* labels */
|
||||
.mailpoet_text_label
|
||||
.mailpoet_textarea_label
|
||||
.mailpoet_select_label
|
||||
.mailpoet_radio_label
|
||||
.mailpoet_checkbox_label
|
||||
.mailpoet_list_label
|
||||
.mailpoet_date_label
|
||||
display:block
|
||||
|
3
assets/css/src/subscribers.styl
Normal file
@ -0,0 +1,3 @@
|
||||
#subscribers_container
|
||||
.mailpoet_segments_unsubscribed
|
||||
color: lighten(#555, 33)
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
assets/img/post_notifications_template/ALC-widget-icon.png
Executable file
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/img/post_notifications_template/settings-icon-highlight.png
Executable file
After Width: | Height: | Size: 7.2 KiB |
12
assets/img/preview_magnifying_glass.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="47.002px" height="38.003px" viewBox="0 0 47.002 38.003" enable-background="new 0 0 47.002 38.003" xml:space="preserve">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#565656" d="M46.328,36.365c-1.188,1.725-3.553,2.158-5.273,0.962L25.479,26.52
|
||||
c-1.104-0.763-1.674-2.007-1.631-3.257c-2.006,2.157-4.642,3.606-7.594,4.145c-3.626,0.663-7.288-0.13-10.311-2.227
|
||||
c-3.024-2.098-5.054-5.253-5.714-8.887c-0.661-3.636,0.127-7.31,2.221-10.344c4.325-6.264,12.927-7.834,19.177-3.5
|
||||
c5.672,3.938,7.486,11.412,4.537,17.443c1.152-0.486,2.519-0.392,3.627,0.377l15.58,10.808C47.09,32.274,47.52,34.641,46.328,36.365
|
||||
z M23.235,12.09c-0.459-2.534-1.874-4.734-3.982-6.196C14.897,2.87,8.896,3.963,5.878,8.331c-3.014,4.373-1.922,10.388,2.435,13.408
|
||||
c4.356,3.025,10.356,1.932,13.374-2.438C23.146,17.187,23.696,14.625,23.235,12.09z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/img/sample_template/coffee-grain.jpg
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
assets/img/sample_template/header-v2.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
assets/img/sample_template/map-v2.jpg
Normal file
After Width: | Height: | Size: 130 KiB |
BIN
assets/img/sample_template/sandwich.jpg
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
assets/img/welcome_template/logo-header.gif
Executable file
After Width: | Height: | Size: 4.8 KiB |
8
assets/js/lib/analytics.js
Normal file
@ -0,0 +1,8 @@
|
||||
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
|
||||
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
|
||||
|
||||
mixpanel.init("f683d388fb25fcf331f1b2b5c4449798");
|
||||
|
||||
if (typeof mailpoet_analytics_data === 'object') {
|
||||
mixpanel.track('Wysija Usage', mailpoet_analytics_data || {});
|
||||
}
|
100
assets/js/src/cron.jsx
Normal file
@ -0,0 +1,100 @@
|
||||
define(
|
||||
[
|
||||
'react',
|
||||
'react-dom',
|
||||
'mailpoet'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
ReactDOM,
|
||||
MailPoet
|
||||
) {
|
||||
var CronControl = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
status: 'loading'
|
||||
};
|
||||
},
|
||||
getCronData: function() {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'cron',
|
||||
action: 'getStatus'
|
||||
})
|
||||
.done(function(response) {
|
||||
jQuery('.button-primary')
|
||||
.removeClass('disabled');
|
||||
if(response.status !== undefined) {
|
||||
this.setState(response);
|
||||
} else {
|
||||
this.replaceState();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
componentDidMount: function() {
|
||||
if(this.isMounted()) {
|
||||
this.getCronData();
|
||||
setInterval(this.getCronData, 5000);
|
||||
}
|
||||
},
|
||||
controlCron: function(action) {
|
||||
if(jQuery('.button-primary').hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
jQuery('.button-primary')
|
||||
.addClass('disabled');
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'cron',
|
||||
action: action,
|
||||
})
|
||||
.done(function(response) {
|
||||
if(!response.result) {
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('daemonControlError'));
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
render: function() {
|
||||
if(this.state.status === 'loading') {
|
||||
return(<div>{MailPoet.I18n.t('loadingDaemonStatus')}</div>);
|
||||
}
|
||||
switch(this.state.status) {
|
||||
case 'started':
|
||||
return(
|
||||
<div>
|
||||
{MailPoet.I18n.t('cronDaemonIsRunning')}
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>{MailPoet.I18n.t('stop')}</a>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'starting':
|
||||
case 'stopping':
|
||||
return(
|
||||
<div>
|
||||
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'stopped':
|
||||
return(
|
||||
<div>
|
||||
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
|
||||
<br />
|
||||
<br />
|
||||
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>{MailPoet.I18n.t('start')}</a>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const container = document.getElementById('cron_container');
|
||||
|
||||
if(container) {
|
||||
ReactDOM.render(
|
||||
<CronControl />,
|
||||
container
|
||||
);
|
||||
}
|
||||
});
|
134
assets/js/src/date.js
Normal file
@ -0,0 +1,134 @@
|
||||
define('date',
|
||||
[
|
||||
'mailpoet',
|
||||
'jquery',
|
||||
'moment'
|
||||
], function(
|
||||
MailPoet,
|
||||
jQuery,
|
||||
Moment
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
MailPoet.Date = {
|
||||
version: 0.1,
|
||||
options: {},
|
||||
defaults: {
|
||||
offset: 0,
|
||||
format: 'F, d Y H:i:s'
|
||||
},
|
||||
init: function(options) {
|
||||
options = options || {};
|
||||
|
||||
// set UTC offset
|
||||
if (
|
||||
options.offset === undefined
|
||||
&& window.mailpoet_date_offset !== undefined
|
||||
) {
|
||||
options.offset = window.mailpoet_date_offset;
|
||||
}
|
||||
// set date format
|
||||
if (
|
||||
options.format === undefined
|
||||
&& window.mailpoet_date_format !== undefined
|
||||
) {
|
||||
options.format = window.mailpoet_date_format;
|
||||
}
|
||||
// merge options
|
||||
this.options = jQuery.extend({}, this.defaults, options);
|
||||
|
||||
return this;
|
||||
},
|
||||
format: function(date, options) {
|
||||
this.init(options);
|
||||
return Moment(date).format(this.convertFormat(this.options.format));
|
||||
},
|
||||
short: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'F, j Y'
|
||||
});
|
||||
},
|
||||
full: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'F, j Y H:i:s'
|
||||
});
|
||||
},
|
||||
time: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'H:i:s'
|
||||
});
|
||||
},
|
||||
convertFormat: function(format) {
|
||||
const format_mappings = {
|
||||
date: {
|
||||
D: 'ddd',
|
||||
l: 'dddd',
|
||||
d: 'DD',
|
||||
j: 'D',
|
||||
z: 'DDDD',
|
||||
N: 'E',
|
||||
S: '',
|
||||
M: 'MMM',
|
||||
F: 'MMMM',
|
||||
m: 'MM',
|
||||
n: '',
|
||||
t: '',
|
||||
y: 'YY',
|
||||
Y: 'YYYY',
|
||||
H: 'HH',
|
||||
h: 'hh',
|
||||
g: 'h',
|
||||
A: 'A',
|
||||
i: 'mm',
|
||||
s: 'ss',
|
||||
T: 'z',
|
||||
O: 'ZZ',
|
||||
w: 'd',
|
||||
W: 'WW'
|
||||
},
|
||||
strftime: {
|
||||
a: 'ddd',
|
||||
A: 'dddd',
|
||||
b: 'MMM',
|
||||
B: 'MMMM',
|
||||
d: 'DD',
|
||||
e: 'D',
|
||||
F: 'YYYY-MM-DD',
|
||||
H: 'HH',
|
||||
I: 'hh',
|
||||
j: 'DDDD',
|
||||
k: 'H',
|
||||
l: 'h',
|
||||
m: 'MM',
|
||||
M: 'mm',
|
||||
p: 'A',
|
||||
S: 'ss',
|
||||
u: 'E',
|
||||
w: 'd',
|
||||
W: 'WW',
|
||||
y: 'YY',
|
||||
Y: 'YYYY',
|
||||
z: 'ZZ',
|
||||
Z: 'z'
|
||||
}
|
||||
};
|
||||
|
||||
const replacements = format_mappings['date'];
|
||||
|
||||
let outputFormat = '';
|
||||
|
||||
Object.keys(replacements).forEach(function(key) {
|
||||
if (format.indexOf(key) !== -1) {
|
||||
format = format.replace(key, '%'+key);
|
||||
}
|
||||
});
|
||||
outputFormat = format;
|
||||
Object.keys(replacements).forEach(function(key) {
|
||||
if (outputFormat.indexOf('%'+key) !== -1) {
|
||||
outputFormat = outputFormat.replace('%'+key, replacements[key]);
|
||||
}
|
||||
});
|
||||
return outputFormat;
|
||||
}
|
||||
};
|
||||
});
|
@ -1,62 +1,45 @@
|
||||
define([
|
||||
'react',
|
||||
'react-checkbox-group'
|
||||
'react'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
CheckboxGroup
|
||||
React
|
||||
) {
|
||||
var FormFieldCheckbox = React.createClass({
|
||||
const FormFieldCheckbox = React.createClass({
|
||||
onValueChange: function(e) {
|
||||
e.target.value = this.refs.checkbox.checked ? '1' : '';
|
||||
return this.props.onValueChange(e);
|
||||
},
|
||||
render: function() {
|
||||
var selected_values = this.props.item[this.props.field.name] || '';
|
||||
if(
|
||||
selected_values !== undefined
|
||||
&& selected_values.constructor !== Array
|
||||
) {
|
||||
selected_values = selected_values.split(';').map(function(value) {
|
||||
return value.trim();
|
||||
});
|
||||
if (this.props.field.values === undefined) {
|
||||
return false;
|
||||
}
|
||||
var count = Object.keys(this.props.field.values).length;
|
||||
|
||||
var options = Object.keys(this.props.field.values).map(
|
||||
function(value, index) {
|
||||
const isChecked = !!(this.props.item[this.props.field.name]);
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
(value, index) => {
|
||||
return (
|
||||
<p key={ 'checkbox-' + index }>
|
||||
<label>
|
||||
<input type="checkbox" value={ value } />
|
||||
{ this.props.field.values[value] }
|
||||
<input
|
||||
ref="checkbox"
|
||||
type="checkbox"
|
||||
value="1"
|
||||
checked={ isChecked }
|
||||
onChange={ this.onValueChange }
|
||||
name={ this.props.field.name }
|
||||
/>
|
||||
{ this.props.field.values[value] }
|
||||
</label>
|
||||
</p>
|
||||
);
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<CheckboxGroup
|
||||
name={ this.props.field.name }
|
||||
value={ selected_values }
|
||||
ref={ this.props.field.name }
|
||||
onChange={ this.handleValueChange }>
|
||||
<div>
|
||||
{ options }
|
||||
</CheckboxGroup>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleValueChange: function() {
|
||||
var field = this.props.field.name;
|
||||
var group = this.refs[field];
|
||||
var selected_values = [];
|
||||
|
||||
if(group !== undefined) {
|
||||
selected_values = group.getCheckedValues();
|
||||
}
|
||||
|
||||
return this.props.onValueChange({
|
||||
target: {
|
||||
name: field,
|
||||
value: selected_values.join(';')
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
196
assets/js/src/form/fields/date.jsx
Normal file
@ -0,0 +1,196 @@
|
||||
define([
|
||||
'react',
|
||||
'moment',
|
||||
], function(
|
||||
React,
|
||||
Moment
|
||||
) {
|
||||
class FormFieldDateYear extends React.Component {
|
||||
render() {
|
||||
const yearsRange = 100;
|
||||
const years = [];
|
||||
const currentYear = Moment().year();
|
||||
for (let i = currentYear; i >= currentYear - yearsRange; i--) {
|
||||
years.push((
|
||||
<option
|
||||
key={ i }
|
||||
value={ i }
|
||||
>{ i }</option>
|
||||
));
|
||||
}
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[year]' }
|
||||
value={ this.props.year }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
{ years }
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FormFieldDateMonth extends React.Component {
|
||||
render() {
|
||||
const months = [];
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
months.push((
|
||||
<option
|
||||
key={ i }
|
||||
value={ i }
|
||||
>{ this.props.monthNames[i - 1] }</option>
|
||||
));
|
||||
}
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[month]' }
|
||||
value={ this.props.month }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
{ months }
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FormFieldDateDay extends React.Component {
|
||||
render() {
|
||||
const days = [];
|
||||
for (let i = 1; i <= 31; i++) {
|
||||
days.push((
|
||||
<option
|
||||
key={ i }
|
||||
value={ i }
|
||||
>{ i }</option>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[day]' }
|
||||
value={ this.props.day }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
{ days }
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FormFieldDate extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
year: Moment().year(),
|
||||
month: 1,
|
||||
day: 1
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (
|
||||
(this.props.item !== undefined && prevProps.item !== undefined)
|
||||
&& (this.props.item.id !== prevProps.item.id)
|
||||
) {
|
||||
this.extractTimeStamp();
|
||||
}
|
||||
}
|
||||
extractTimeStamp() {
|
||||
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
|
||||
|
||||
this.setState({
|
||||
year: Moment.unix(timeStamp).year(),
|
||||
// Moment returns the month as [0..11]
|
||||
// We increment it to match PHP's mktime() which expects [1..12]
|
||||
month: Moment.unix(timeStamp).month() + 1,
|
||||
day: Moment.unix(timeStamp).date()
|
||||
});
|
||||
}
|
||||
updateTimeStamp(field) {
|
||||
let newTimeStamp = Moment(
|
||||
`${this.state.month}/${this.state.day}/${this.state.year}`,
|
||||
'M/D/YYYY'
|
||||
).valueOf();
|
||||
if (!isNaN(newTimeStamp) && parseInt(newTimeStamp, 10) > 0) {
|
||||
// convert milliseconds to seconds
|
||||
newTimeStamp /= 1000;
|
||||
return this.props.onValueChange({
|
||||
target: {
|
||||
name: field,
|
||||
value: newTimeStamp
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
onValueChange(e) {
|
||||
// extract property from name
|
||||
const matches = e.target.name.match(/(.*?)\[(.*?)\]/);
|
||||
let field = null;
|
||||
let property = null;
|
||||
|
||||
if (matches !== null && matches.length === 3) {
|
||||
field = matches[1];
|
||||
property = matches[2];
|
||||
|
||||
let value = parseInt(e.target.value, 10);
|
||||
|
||||
this.setState({
|
||||
[`${property}`]: value
|
||||
}, () => {
|
||||
this.updateTimeStamp(field);
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const monthNames = window.mailpoet_month_names || [];
|
||||
|
||||
const dateType = this.props.field.params.date_type;
|
||||
|
||||
const dateSelects = dateType.split('_');
|
||||
|
||||
const fields = dateSelects.map(type => {
|
||||
switch(type) {
|
||||
case 'year':
|
||||
return (<FormFieldDateYear
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'year' }
|
||||
key={ 'year' }
|
||||
name={ this.props.field.name }
|
||||
year={ this.state.year }
|
||||
/>);
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
return (<FormFieldDateMonth
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'month' }
|
||||
key={ 'month' }
|
||||
name={ this.props.field.name }
|
||||
month={ this.state.month }
|
||||
monthNames={ monthNames }
|
||||
/>);
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
return (<FormFieldDateDay
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'day' }
|
||||
key={ 'day' }
|
||||
name={ this.props.field.name }
|
||||
day={ this.state.day }
|
||||
/>);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{fields}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return FormFieldDate;
|
||||
});
|
@ -5,7 +5,8 @@ define([
|
||||
'form/fields/select.jsx',
|
||||
'form/fields/radio.jsx',
|
||||
'form/fields/checkbox.jsx',
|
||||
'form/fields/selection.jsx'
|
||||
'form/fields/selection.jsx',
|
||||
'form/fields/date.jsx',
|
||||
],
|
||||
function(
|
||||
React,
|
||||
@ -14,7 +15,8 @@ function(
|
||||
FormFieldSelect,
|
||||
FormFieldRadio,
|
||||
FormFieldCheckbox,
|
||||
FormFieldSelection
|
||||
FormFieldSelection,
|
||||
FormFieldDate
|
||||
) {
|
||||
var FormField = React.createClass({
|
||||
renderField: function(data, inline = false) {
|
||||
@ -55,6 +57,14 @@ function(
|
||||
case 'selection':
|
||||
field = (<FormFieldSelection {...data} />);
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
field = (<FormFieldDate {...data} />);
|
||||
break;
|
||||
|
||||
case 'reactComponent':
|
||||
field = (<data.field.component {...data} />);
|
||||
break;
|
||||
}
|
||||
|
||||
if(inline === true) {
|
||||
@ -66,10 +76,10 @@ function(
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<p key={ 'field-' + (data.index || 0) }>
|
||||
<div key={ 'field-' + (data.index || 0) }>
|
||||
{ field }
|
||||
{ description }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -115,4 +125,4 @@ function(
|
||||
});
|
||||
|
||||
return FormField;
|
||||
});
|
||||
});
|
||||
|
@ -4,13 +4,15 @@ define([
|
||||
function(
|
||||
React
|
||||
) {
|
||||
var FormFieldRadio = React.createClass({
|
||||
const FormFieldRadio = React.createClass({
|
||||
render: function() {
|
||||
var selected_value = this.props.item[this.props.field.name];
|
||||
var count = Object.keys(this.props.field.values).length;
|
||||
if (this.props.field.values === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var options = Object.keys(this.props.field.values).map(
|
||||
function(value, index) {
|
||||
const selected_value = this.props.item[this.props.field.name];
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
(value, index) => {
|
||||
return (
|
||||
<p key={ 'radio-' + index }>
|
||||
<label>
|
||||
@ -20,11 +22,11 @@ function(
|
||||
value={ value }
|
||||
onChange={ this.props.onValueChange }
|
||||
name={ this.props.field.name } />
|
||||
{ this.props.field.values[value] }
|
||||
{ this.props.field.values[value] }
|
||||
</label>
|
||||
</p>
|
||||
);
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -4,10 +4,14 @@ define([
|
||||
function(
|
||||
React
|
||||
) {
|
||||
var FormFieldSelect = React.createClass({
|
||||
const FormFieldSelect = React.createClass({
|
||||
render: function() {
|
||||
var options =
|
||||
Object.keys(this.props.field.values).map(function(value, index) {
|
||||
if (this.props.field.values === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
(value, index) => {
|
||||
return (
|
||||
<option
|
||||
key={ 'option-' + index }
|
||||
@ -15,7 +19,7 @@ function(
|
||||
{ this.props.field.values[value] }
|
||||
</option>
|
||||
);
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
@ -23,7 +27,9 @@ function(
|
||||
name={ this.props.field.name }
|
||||
id={ 'field_'+this.props.field.name }
|
||||
value={ this.props.item[this.props.field.name] }
|
||||
onChange={ this.props.onValueChange }>
|
||||
onChange={ this.props.onValueChange }
|
||||
{...this.props.field.validation}
|
||||
>
|
||||
{options}
|
||||
</select>
|
||||
);
|
||||
|
@ -13,22 +13,45 @@ function(
|
||||
getInitialState: function() {
|
||||
return {
|
||||
items: [],
|
||||
initialized: false
|
||||
}
|
||||
select2: false
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
this.loadCachedItems();
|
||||
},
|
||||
allowMultipleValues: function() {
|
||||
return (this.props.field.multiple === true);
|
||||
},
|
||||
isSelect2Initialized: function() {
|
||||
return (this.state.select2 === true);
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.loadCachedItems();
|
||||
this.setupSelect2();
|
||||
if(this.allowMultipleValues()) {
|
||||
this.setupSelect2();
|
||||
}
|
||||
},
|
||||
componentDidUpdate: function() {
|
||||
this.setupSelect2();
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
if(
|
||||
(this.props.item !== undefined && prevProps.item !== undefined)
|
||||
&& (this.props.item.id !== prevProps.item.id)
|
||||
) {
|
||||
jQuery('#'+this.refs.select.id)
|
||||
.val(this.getSelectedValues())
|
||||
.trigger('change');
|
||||
}
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
if(this.allowMultipleValues()) {
|
||||
this.destroySelect2();
|
||||
}
|
||||
},
|
||||
destroySelect2: function() {
|
||||
if(this.isSelect2Initialized()) {
|
||||
jQuery('#'+this.refs.select.id).select2('destroy');
|
||||
}
|
||||
},
|
||||
setupSelect2: function() {
|
||||
if(
|
||||
!this.props.field.multiple
|
||||
|| this.state.initialized === true
|
||||
|| this.refs.select === undefined
|
||||
) {
|
||||
if(this.isSelect2Initialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -38,22 +61,48 @@ function(
|
||||
if(item.element && item.element.selected) {
|
||||
return null;
|
||||
} else {
|
||||
return item.text;
|
||||
if(item.title) {
|
||||
return item.title;
|
||||
} else {
|
||||
return item.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
select2.on('change', this.handleChange);
|
||||
select2.select2(
|
||||
'val',
|
||||
this.props.item[this.props.field.name]
|
||||
);
|
||||
var hasRemoved = false;
|
||||
select2.on('select2:unselecting', function(e) {
|
||||
hasRemoved = true;
|
||||
});
|
||||
select2.on('select2:opening', function(e) {
|
||||
if(hasRemoved === true) {
|
||||
hasRemoved = false;
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ initialized: true });
|
||||
select2.on('change', this.handleChange);
|
||||
|
||||
this.setState({ select2: true });
|
||||
},
|
||||
getSelectedValues: function() {
|
||||
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) {
|
||||
return this.props.item[this.props.field.name];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
loadCachedItems: function() {
|
||||
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
|
||||
var items = window['mailpoet_'+this.props.field.endpoint];
|
||||
|
||||
|
||||
if(this.props.field['filter'] !== undefined) {
|
||||
items = items.filter(this.props.field.filter);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
items: items
|
||||
});
|
||||
@ -62,7 +111,7 @@ function(
|
||||
handleChange: function(e) {
|
||||
if(this.props.onValueChange !== undefined) {
|
||||
if(this.props.field.multiple) {
|
||||
value = jQuery('#'+this.refs.select.id).select2('val');
|
||||
value = jQuery('#'+this.refs.select.id).val();
|
||||
} else {
|
||||
value = e.target.value;
|
||||
}
|
||||
@ -73,42 +122,54 @@ function(
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getLabel: function(item) {
|
||||
if(this.props.field['getLabel'] !== undefined) {
|
||||
return this.props.field.getLabel(item, this.props.item);
|
||||
}
|
||||
return item.name;
|
||||
},
|
||||
getSearchLabel: function(item) {
|
||||
if(this.props.field['getSearchLabel'] !== undefined) {
|
||||
return this.props.field.getSearchLabel(item, this.props.item);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getValue: function(item) {
|
||||
if(this.props.field['getValue'] !== undefined) {
|
||||
return this.props.field.getValue(item, this.props.item);
|
||||
}
|
||||
return item.id;
|
||||
},
|
||||
render: function() {
|
||||
if(this.state.items.length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
var options = this.state.items.map(function(item, index) {
|
||||
return (
|
||||
<option
|
||||
key={ item.id }
|
||||
value={ item.id }
|
||||
>
|
||||
{ item.name }
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
var default_value = (
|
||||
(this.props.item !== undefined && this.props.field.name !== undefined)
|
||||
? this.props.item[this.props.field.name]
|
||||
: null
|
||||
);
|
||||
const options = this.state.items.map((item, index) => {
|
||||
let label = this.getLabel(item);
|
||||
let searchLabel = this.getSearchLabel(item);
|
||||
let value = this.getValue(item);
|
||||
|
||||
return (
|
||||
<select
|
||||
id={ this.props.field.id || this.props.field.name }
|
||||
ref="select"
|
||||
placeholder={ this.props.field.placeholder }
|
||||
multiple={ this.props.field.multiple }
|
||||
onChange={ this.handleChange }
|
||||
defaultValue={ default_value }
|
||||
>{ options }</select>
|
||||
<option
|
||||
key={ 'option-'+index }
|
||||
value={ value }
|
||||
title={ searchLabel }
|
||||
>
|
||||
{ label }
|
||||
</option>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<select
|
||||
id={ this.props.field.id || this.props.field.name }
|
||||
ref="select"
|
||||
data-placeholder={ this.props.field.placeholder }
|
||||
multiple={ this.props.field.multiple }
|
||||
defaultValue={ this.getSelectedValues() }
|
||||
{...this.props.field.validation}
|
||||
>{ options }</select>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return Selection;
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,11 @@ function(
|
||||
) {
|
||||
var FormFieldText = React.createClass({
|
||||
render: function() {
|
||||
var value = this.props.item[this.props.field.name];
|
||||
if(value === undefined) {
|
||||
value = this.props.field.defaultValue || '';
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
@ -17,13 +22,14 @@ function(
|
||||
}
|
||||
name={ this.props.field.name }
|
||||
id={ 'field_'+this.props.field.name }
|
||||
value={ this.props.item[this.props.field.name] }
|
||||
value={ value }
|
||||
placeholder={ this.props.field.placeholder }
|
||||
defaultValue={ this.props.field.defaultValue }
|
||||
onChange={ this.props.onValueChange } />
|
||||
onChange={ this.props.onValueChange }
|
||||
{...this.props.field.validation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return FormFieldText;
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,9 @@ function(
|
||||
value={ this.props.item[this.props.field.name] }
|
||||
placeholder={ this.props.field.placeholder }
|
||||
defaultValue={ this.props.field.defaultValue }
|
||||
onChange={ this.props.onValueChange } />
|
||||
onChange={ this.props.onValueChange }
|
||||
{...this.props.field.validation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -13,10 +13,16 @@ define(
|
||||
Router,
|
||||
FormField
|
||||
) {
|
||||
|
||||
var Form = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
params: {},
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
loading: false,
|
||||
@ -24,6 +30,12 @@ define(
|
||||
item: {}
|
||||
};
|
||||
},
|
||||
getValues: function() {
|
||||
return this.props.item ? this.props.item : this.state.item;
|
||||
},
|
||||
getErrors: function() {
|
||||
return this.props.errors ? this.props.errors : this.state.errors;
|
||||
},
|
||||
componentDidMount: function() {
|
||||
if(this.props.params.id !== undefined) {
|
||||
if(this.isMounted()) {
|
||||
@ -37,7 +49,9 @@ define(
|
||||
loading: false,
|
||||
item: {}
|
||||
});
|
||||
this.refs.form.reset();
|
||||
if (props.item === undefined) {
|
||||
this.refs.form.reset();
|
||||
}
|
||||
} else {
|
||||
this.loadItem(props.params.id);
|
||||
}
|
||||
@ -48,14 +62,14 @@ define(
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'get',
|
||||
data: { id: id }
|
||||
data: id
|
||||
}).done(function(response) {
|
||||
if(response === false) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
item: {}
|
||||
}, function() {
|
||||
this.history.pushState(null, '/new');
|
||||
this.context.router.push('/new');
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.setState({
|
||||
@ -68,12 +82,25 @@ define(
|
||||
handleSubmit: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// handle validation
|
||||
if(this.props.isValid !== undefined) {
|
||||
if(this.props.isValid() === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ loading: true });
|
||||
|
||||
// only get values from displayed fields
|
||||
item = {};
|
||||
var item = {};
|
||||
this.props.fields.map(function(field) {
|
||||
item[field.name] = this.state.item[field.name];
|
||||
if(field['fields'] !== undefined) {
|
||||
field.fields.map(function(subfield) {
|
||||
item[subfield.name] = this.state.item[subfield.name];
|
||||
}.bind(this));
|
||||
} else {
|
||||
item[field.name] = this.state.item[field.name];
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// set id if specified
|
||||
@ -88,57 +115,63 @@ define(
|
||||
}).done(function(response) {
|
||||
this.setState({ loading: false });
|
||||
|
||||
if(response === true) {
|
||||
if(response.result === true) {
|
||||
if(this.props.onSuccess !== undefined) {
|
||||
this.props.onSuccess()
|
||||
this.props.onSuccess();
|
||||
} else {
|
||||
this.history.pushState(null, '/')
|
||||
this.context.router.push('/');
|
||||
}
|
||||
|
||||
if(this.props.params.id !== undefined) {
|
||||
this.props.messages['updated']();
|
||||
this.props.messages.onUpdate();
|
||||
} else {
|
||||
this.props.messages['created']();
|
||||
this.props.messages.onCreate();
|
||||
}
|
||||
} else {
|
||||
if(response === false) {
|
||||
// unknown error occurred
|
||||
} else {
|
||||
this.setState({ errors: response });
|
||||
if(response.result === false) {
|
||||
if(response.errors.length > 0) {
|
||||
this.setState({ errors: response.errors });
|
||||
}
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleValueChange: function(e) {
|
||||
var item = this.state.item,
|
||||
field = e.target.name;
|
||||
if (this.props.onChange) {
|
||||
return this.props.onChange(e);
|
||||
} else {
|
||||
var item = this.state.item,
|
||||
field = e.target.name;
|
||||
|
||||
item[field] = e.target.value;
|
||||
item[field] = e.target.value;
|
||||
|
||||
this.setState({
|
||||
item: item
|
||||
});
|
||||
return true;
|
||||
this.setState({
|
||||
item: item
|
||||
});
|
||||
return true;
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var errors = this.state.errors.map(function(error, index) {
|
||||
return (
|
||||
<p key={ 'error-'+index } className="mailpoet_error">
|
||||
{ error }
|
||||
</p>
|
||||
);
|
||||
});
|
||||
if(this.getErrors() !== undefined) {
|
||||
var errors = this.getErrors().map(function(error, index) {
|
||||
return (
|
||||
<p key={ 'error-'+index } className="mailpoet_error">
|
||||
{ error }
|
||||
</p>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
var formClasses = classNames(
|
||||
'mailpoet_form',
|
||||
{ 'mailpoet_form_loading': this.state.loading }
|
||||
{ 'mailpoet_form_loading': this.state.loading || this.props.loading }
|
||||
);
|
||||
|
||||
var fields = this.props.fields.map(function(field, i) {
|
||||
return (
|
||||
<FormField
|
||||
field={ field }
|
||||
item={ this.state.item }
|
||||
item={ this.getValues() }
|
||||
onValueChange={ this.handleValueChange }
|
||||
key={ 'field-'+i } />
|
||||
);
|
||||
@ -152,7 +185,7 @@ define(
|
||||
<input
|
||||
className="button button-primary"
|
||||
type="submit"
|
||||
value="Save"
|
||||
value={MailPoet.I18n.t('save')}
|
||||
disabled={this.state.loading} />
|
||||
);
|
||||
}
|
||||
@ -184,4 +217,4 @@ define(
|
||||
|
||||
return Form;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -231,7 +231,7 @@ var WysijaHistory = {
|
||||
|
||||
/* MailPoet Form */
|
||||
var WysijaForm = {
|
||||
version: '0.6',
|
||||
version: '0.7',
|
||||
options: {
|
||||
container: 'mailpoet_form_container',
|
||||
editor: 'mailpoet_form_editor',
|
||||
@ -317,6 +317,7 @@ var WysijaForm = {
|
||||
save: function() {
|
||||
var position = 1,
|
||||
data = {
|
||||
'name': $F('mailpoet_form_name'),
|
||||
'settings': $('mailpoet_form_settings').serialize(true),
|
||||
'body': [],
|
||||
'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null
|
||||
@ -401,11 +402,30 @@ var WysijaForm = {
|
||||
}
|
||||
});
|
||||
|
||||
// hide list selection if a list widget has been dragged into the editor
|
||||
$('mailpoet_settings_segment_selection')[
|
||||
(($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0) === true)
|
||||
? 'hide' : 'show'
|
||||
]();
|
||||
var hasSegmentSelection = WysijaForm.hasSegmentSelection();
|
||||
|
||||
if(hasSegmentSelection) {
|
||||
$('mailpoet_form_segments').writeAttribute('required', false).disable();
|
||||
$('mailpoet_settings_segment_selection').hide();
|
||||
} else {
|
||||
$('mailpoet_form_segments').writeAttribute('required', true).enable();
|
||||
$('mailpoet_settings_segment_selection').show();
|
||||
}
|
||||
},
|
||||
hasSegmentSelection: function() {
|
||||
return ($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0);
|
||||
},
|
||||
isSegmentSelectionValid: function() {
|
||||
var segment_selection = $$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
|
||||
if(segment_selection !== undefined) {
|
||||
var block = WysijaForm.get(segment_selection).block.getData();
|
||||
return (
|
||||
(block.params.values !== undefined)
|
||||
&&
|
||||
(block.params.values.length > 0)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
setBlockPositions: function(event, target) {
|
||||
// release dragging lock
|
||||
@ -616,6 +636,28 @@ var WysijaForm = {
|
||||
// this is a url, so do not encode the protocol
|
||||
return encodeURI(str).replace(/[!'()*]/g, escape);
|
||||
}
|
||||
},
|
||||
updateBlock: function(field) {
|
||||
var hasUpdated = false;
|
||||
WysijaForm.getBlocks().each(function(b) {
|
||||
if(b.block.getData().id === field.id) {
|
||||
hasUpdated = true;
|
||||
b.block.redraw(field);
|
||||
}
|
||||
});
|
||||
|
||||
return hasUpdated;
|
||||
},
|
||||
removeBlock: function(field, callback) {
|
||||
var hasRemoved = false;
|
||||
WysijaForm.getBlocks().each(function(b) {
|
||||
if(b.block.getData().id === field.id) {
|
||||
hasRemoved = true;
|
||||
b.block.removeBlock(callback);
|
||||
}
|
||||
});
|
||||
|
||||
return hasRemoved;
|
||||
}
|
||||
};
|
||||
|
||||
@ -824,10 +866,6 @@ WysijaForm.Block = Class.create({
|
||||
Effect.Fade(this.element.identify(), {
|
||||
duration: 0.2,
|
||||
afterFinish: function(effect) {
|
||||
if(effect.element.next('.mailpoet_form_block') !== undefined && callback !== false) {
|
||||
// show controls of next block to allow mass delete
|
||||
WysijaForm.get(effect.element.next('.mailpoet_form_block')).block.showControls();
|
||||
}
|
||||
// remove placeholder
|
||||
if(effect.element.previous('.block_placeholder') !== undefined) {
|
||||
effect.element.previous('.block_placeholder').remove();
|
||||
|
@ -1,57 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Router, History } from 'react-router'
|
||||
import MailPoet from 'mailpoet'
|
||||
import Form from 'form/form.jsx'
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: 'Lists',
|
||||
type: 'selection',
|
||||
endpoint: 'segments',
|
||||
multiple: true
|
||||
}
|
||||
]
|
||||
|
||||
const messages = {
|
||||
updated: function() {
|
||||
MailPoet.Notice.success('Form successfully updated!');
|
||||
},
|
||||
created: function() {
|
||||
MailPoet.Notice.success('Form successfully added!');
|
||||
}
|
||||
}
|
||||
|
||||
const FormForm = React.createClass({
|
||||
mixins: [
|
||||
History
|
||||
],
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="title">
|
||||
Form <a
|
||||
href="javascript:;"
|
||||
className="add-new-h2"
|
||||
onClick={ this.history.goBack }
|
||||
>Back to list</a>
|
||||
</h2>
|
||||
|
||||
<Form
|
||||
endpoint="forms"
|
||||
fields={ fields }
|
||||
params={ this.props.params }
|
||||
messages={ messages }
|
||||
onSuccess={ this.history.goBack } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FormForm
|
@ -1,11 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Router, Route, IndexRoute } from 'react-router'
|
||||
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
|
||||
import { createHashHistory } from 'history'
|
||||
import FormList from 'forms/list.jsx'
|
||||
import FormForm from 'forms/form.jsx'
|
||||
import createHashHistory from 'history/lib/createHashHistory'
|
||||
|
||||
let history = createHashHistory({ queryKey: false })
|
||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||
|
||||
const App = React.createClass({
|
||||
render() {
|
||||
@ -13,15 +12,13 @@ const App = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
let container = document.getElementById('forms_container');
|
||||
const container = document.getElementById('forms_container');
|
||||
|
||||
if(container) {
|
||||
ReactDOM.render((
|
||||
<Router history={ history }>
|
||||
<Route path="/" component={ App }>
|
||||
<IndexRoute component={ FormList } />
|
||||
<Route path="new" component={ FormForm } />
|
||||
<Route path="edit/:id" component={ FormForm } />
|
||||
<Route path="*" component={ FormList } />
|
||||
</Route>
|
||||
</Router>
|
||||
|
@ -8,91 +8,82 @@ import MailPoet from 'mailpoet'
|
||||
const columns = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
label: MailPoet.I18n.t('formName'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: 'Lists',
|
||||
label: MailPoet.I18n.t('segments'),
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
label: 'Created on',
|
||||
label: MailPoet.I18n.t('createdOn'),
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
|
||||
const messages = {
|
||||
onTrash: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 form was moved to the trash.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d forms were moved to the trash.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneFormTrashed')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleFormsTrashed')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 form was permanently deleted.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d forms were permanently deleted.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneFormDeleted')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleFormsDeleted')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 form has been restored from the trash.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d forms have been restored from the trash.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneFormRestored')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleFormsRestored')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
const item_actions = [
|
||||
{
|
||||
name: 'edit',
|
||||
label: 'Edit',
|
||||
label: MailPoet.I18n.t('edit'),
|
||||
link: function(item) {
|
||||
return (
|
||||
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a>
|
||||
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate_form',
|
||||
label: 'Duplicate',
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(item, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'forms',
|
||||
@ -100,19 +91,14 @@ const item_actions = [
|
||||
data: item.id
|
||||
}).done(function(response) {
|
||||
MailPoet.Notice.success(
|
||||
('Form "%$1s" has been duplicated.').replace('%$1s', response.name)
|
||||
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
|
||||
);
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const bulk_actions = [
|
||||
},
|
||||
{
|
||||
name: 'trash',
|
||||
label: 'Trash',
|
||||
onSuccess: messages.onTrash
|
||||
name: 'trash'
|
||||
}
|
||||
];
|
||||
|
||||
@ -122,8 +108,8 @@ const FormList = React.createClass({
|
||||
endpoint: 'forms',
|
||||
action: 'create'
|
||||
}).done(function(response) {
|
||||
if(response !== false) {
|
||||
window.location = response;
|
||||
if(response.result && response.form_id) {
|
||||
window.location = mailpoet_form_edit_url + response.form_id;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -144,15 +130,18 @@ const FormList = React.createClass({
|
||||
<div>
|
||||
<td className={ row_classes }>
|
||||
<strong>
|
||||
<a>{ form.name }</a>
|
||||
<a
|
||||
className="row-title"
|
||||
href={ `admin.php?page=mailpoet-form-editor&id=${form.id}` }
|
||||
>{ form.name }</a>
|
||||
</strong>
|
||||
{ actions }
|
||||
</td>
|
||||
<td className="column-format" data-colname="Lists">
|
||||
<td className="column-format" data-colname={MailPoet.I18n.t('segments')}>
|
||||
{ segments }
|
||||
</td>
|
||||
<td className="column-date" data-colname="Created on">
|
||||
<abbr>{ form.created_at }</abbr>
|
||||
<td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
|
||||
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
@ -161,11 +150,11 @@ const FormList = React.createClass({
|
||||
return (
|
||||
<div>
|
||||
<h2 className="title">
|
||||
Forms <a
|
||||
{MailPoet.I18n.t('pageTitle')} <a
|
||||
className="add-new-h2"
|
||||
href="javascript:;"
|
||||
onClick={ this.createForm }
|
||||
>New</a>
|
||||
>{MailPoet.I18n.t('new')}</a>
|
||||
</h2>
|
||||
|
||||
<Listing
|
||||
@ -177,7 +166,6 @@ const FormList = React.createClass({
|
||||
endpoint="forms"
|
||||
onRenderItem={ this.renderItem }
|
||||
columns={ columns }
|
||||
bulk_actions={ bulk_actions }
|
||||
item_actions={ item_actions }
|
||||
/>
|
||||
</div>
|
||||
@ -185,4 +173,4 @@ const FormList = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FormList;
|
||||
module.exports = FormList;
|
||||
|
@ -140,5 +140,20 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
|
||||
return parseInt(string, 10);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('fontWithFallback', function(font) {
|
||||
switch(font) {
|
||||
case 'Arial': return new Handlebars.SafeString("Arial, 'Helvetica Neue', Helvetica, sans-serif");
|
||||
case 'Comic Sans MS': return new Handlebars.SafeString("'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif");
|
||||
case 'Courier New': return new Handlebars.SafeString("'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace");
|
||||
case 'Georgia': return new Handlebars.SafeString("Georgia, Times, 'Times New Roman', serif");
|
||||
case 'Lucida': return new Handlebars.SafeString("'Lucida Sans Unicode', 'Lucida Grande', sans-serif");
|
||||
case 'Tahoma': return new Handlebars.SafeString("Tahoma, Verdana, Segoe, sans-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 'Verdana': return new Handlebars.SafeString("Verdana, Geneva, sans-serif");
|
||||
default: return font;
|
||||
}
|
||||
});
|
||||
|
||||
window.Handlebars = Handlebars;
|
||||
});
|
||||
|
3
assets/js/src/helpscout.js
Normal file
@ -0,0 +1,3 @@
|
||||
define([], function() {
|
||||
!function(e,o,n){window.HSCW=o,window.HS=n,n.beacon=n.beacon||{};var t=n.beacon;t.userConfig={},t.readyQueue=[],t.config=function(e){this.userConfig=e},t.ready=function(e){this.readyQueue.push(e)},o.config={docs:{enabled:!1,baseUrl:""},contact:{enabled:!0,formId:"e5c408c7-895e-11e5-9e75-0a7d6919297d"}};var r=e.getElementsByTagName("script")[0],c=e.createElement("script");c.type="text/javascript",c.async=!0,c.src="https://djtflbt20bdde.cloudfront.net/",r.parentNode.insertBefore(c,r)}(document,window.HSCW||{},window.HS||{});
|
||||
});
|
25
assets/js/src/i18n.js
Normal file
@ -0,0 +1,25 @@
|
||||
define('i18n',
|
||||
[
|
||||
'mailpoet',
|
||||
'underscore',
|
||||
], function(
|
||||
MailPoet,
|
||||
_
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
var translations = {};
|
||||
|
||||
MailPoet.I18n = {
|
||||
add: function(key, value) {
|
||||
translations[key] = value;
|
||||
},
|
||||
t: function(key) {
|
||||
return translations[key] || 'TRANSLATION "%$1s" NOT FOUND'.replace("%$1s", key);
|
||||
},
|
||||
all: function() {
|
||||
return translations;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
23
assets/js/src/iframe.js
Normal file
@ -0,0 +1,23 @@
|
||||
define('iframe', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
'use strict';
|
||||
MailPoet.Iframe = {
|
||||
marginY: 20,
|
||||
autoSize: function(iframe) {
|
||||
if(!iframe) return;
|
||||
|
||||
this.setSize(
|
||||
iframe,
|
||||
iframe.contentWindow.document.body.scrollHeight
|
||||
);
|
||||
},
|
||||
setSize: function(iframe, i) {
|
||||
if(!iframe) return;
|
||||
|
||||
iframe.style.height = (
|
||||
parseInt(i) + this.marginY
|
||||
) + "px";
|
||||
}
|
||||
};
|
||||
|
||||
return MailPoet;
|
||||
});
|
@ -1,8 +1,10 @@
|
||||
define([
|
||||
'react'
|
||||
'react',
|
||||
'mailpoet'
|
||||
],
|
||||
function(
|
||||
React
|
||||
React,
|
||||
MailPoet
|
||||
) {
|
||||
var ListingBulkActions = React.createClass({
|
||||
getInitialState: function() {
|
||||
@ -15,16 +17,16 @@ function(
|
||||
this.setState({
|
||||
action: e.target.value,
|
||||
extra: false
|
||||
});
|
||||
}, function() {
|
||||
var action = this.getSelectedAction();
|
||||
|
||||
var action = this.getSelectedAction();
|
||||
|
||||
// action on select callback
|
||||
if(action !== null && action['onSelect'] !== undefined) {
|
||||
this.setState({
|
||||
extra: action.onSelect(e)
|
||||
});
|
||||
}
|
||||
// action on select callback
|
||||
if(action !== null && action['onSelect'] !== undefined) {
|
||||
this.setState({
|
||||
extra: action.onSelect(e)
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleApplyAction: function(e) {
|
||||
e.preventDefault();
|
||||
@ -82,11 +84,16 @@ function(
|
||||
<label
|
||||
className="screen-reader-text"
|
||||
htmlFor="bulk-action-selector-top">
|
||||
Select bulk action
|
||||
{MailPoet.I18n.t('selectBulkAction')}
|
||||
</label>
|
||||
|
||||
<select ref="action" value={ this.state.action } onChange={this.handleChangeAction}>
|
||||
<option value="">Bulk Actions</option>
|
||||
<select
|
||||
name="bulk_actions"
|
||||
ref="action"
|
||||
value={ this.state.action }
|
||||
onChange={this.handleChangeAction}
|
||||
>
|
||||
<option value="">{MailPoet.I18n.t('bulkActions')}</option>
|
||||
{ this.props.bulk_actions.map(function(action, index) {
|
||||
return (
|
||||
<option
|
||||
@ -99,7 +106,7 @@ function(
|
||||
<input
|
||||
onClick={ this.handleApplyAction }
|
||||
type="submit"
|
||||
defaultValue="Apply"
|
||||
defaultValue={MailPoet.I18n.t('apply')}
|
||||
className="button action" />
|
||||
|
||||
{ this.state.extra }
|
||||
@ -109,4 +116,4 @@ function(
|
||||
});
|
||||
|
||||
return ListingBulkActions;
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,12 @@
|
||||
define([
|
||||
'react',
|
||||
'jquery'
|
||||
'jquery',
|
||||
'mailpoet'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
jQuery
|
||||
jQuery,
|
||||
MailPoet
|
||||
) {
|
||||
var ListingFilters = React.createClass({
|
||||
handleFilterAction: function() {
|
||||
@ -14,6 +16,9 @@ function(
|
||||
})
|
||||
return this.props.onSelectFilter(filters);
|
||||
},
|
||||
handleEmptyTrash: function() {
|
||||
return this.props.onEmptyTrash();
|
||||
},
|
||||
getAvailableFilters: function() {
|
||||
let filters = this.props.filters;
|
||||
|
||||
@ -34,7 +39,7 @@ function(
|
||||
const available_filters = this.getAvailableFilters()
|
||||
.map(function(filter, i) {
|
||||
let default_value = false;
|
||||
if(selected_filters[filter] !== undefined && selected_filters[filter]) {
|
||||
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
|
||||
default_value = selected_filters[filter]
|
||||
} else {
|
||||
jQuery(`select[name="${filter}"]`).val('');
|
||||
@ -60,20 +65,34 @@ function(
|
||||
|
||||
let button = false;
|
||||
|
||||
if(available_filters.length > 0) {
|
||||
if (available_filters.length > 0) {
|
||||
button = (
|
||||
<input
|
||||
id="post-query-submit"
|
||||
onClick={ this.handleFilterAction }
|
||||
type="submit"
|
||||
defaultValue="Filter"
|
||||
defaultValue={MailPoet.I18n.t('filter')}
|
||||
className="button" />
|
||||
);
|
||||
}
|
||||
|
||||
let empty_trash = false;
|
||||
if (this.props.group === 'trash') {
|
||||
empty_trash = (
|
||||
<input
|
||||
onClick={ this.handleEmptyTrash }
|
||||
type="submit"
|
||||
value={MailPoet.I18n.t('emptyTrash')}
|
||||
className="button"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="alignleft actions actions">
|
||||
{ available_filters }
|
||||
{ button }
|
||||
{ empty_trash }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,12 @@
|
||||
define(['react', 'classnames'], function(React, classNames) {
|
||||
define([
|
||||
'react',
|
||||
'classnames',
|
||||
'mailpoet'
|
||||
], function(
|
||||
React,
|
||||
classNames,
|
||||
MailPoet
|
||||
) {
|
||||
|
||||
var ListingHeader = React.createClass({
|
||||
handleSelectItems: function() {
|
||||
@ -11,7 +19,7 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
column.is_primary = (index === 0);
|
||||
column.sorted = (this.props.sort_by === column.name)
|
||||
? this.props.sort_order
|
||||
: 'asc';
|
||||
: 'desc';
|
||||
return (
|
||||
<ListingColumn
|
||||
onSort={this.props.onSort}
|
||||
@ -28,10 +36,11 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
<th
|
||||
className="manage-column column-cb check-column">
|
||||
<label className="screen-reader-text">
|
||||
{ 'Select All' }
|
||||
{MailPoet.I18n.t('selectAll')}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="select_all"
|
||||
ref="toggle"
|
||||
checked={ this.props.selection }
|
||||
onChange={ this.handleSelectItems } />
|
||||
@ -86,4 +95,4 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
});
|
||||
|
||||
return ListingHeader;
|
||||
});
|
||||
});
|
||||
|
@ -74,32 +74,56 @@ define(
|
||||
);
|
||||
}
|
||||
|
||||
var custom_actions = this.props.item_actions;
|
||||
var item_actions = false;
|
||||
const custom_actions = this.props.item_actions;
|
||||
let item_actions = false;
|
||||
|
||||
if(custom_actions.length > 0) {
|
||||
let is_first = true;
|
||||
item_actions = custom_actions.map(function(action, index) {
|
||||
if(action.refresh) {
|
||||
return (
|
||||
if(action.display !== undefined) {
|
||||
if(action.display(this.props.item) === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let custom_action = null;
|
||||
|
||||
if(action.name === 'trash') {
|
||||
custom_action = (
|
||||
<span key={ 'action-'+index } className="trash">
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
<a
|
||||
href="javascript:;"
|
||||
onClick={ this.handleTrashItem.bind(
|
||||
null,
|
||||
this.props.item.id
|
||||
) }>
|
||||
{MailPoet.I18n.t('trash')}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
} else if(action.refresh) {
|
||||
custom_action = (
|
||||
<span
|
||||
onClick={ this.props.onRefreshItems }
|
||||
key={ 'action-'+index } className={ action.name }>
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
{ action.link(this.props.item) }
|
||||
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
|
||||
</span>
|
||||
);
|
||||
} else if(action.link) {
|
||||
return (
|
||||
custom_action = (
|
||||
<span
|
||||
key={ 'action-'+index } className={ action.name }>
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
{ action.link(this.props.item) }
|
||||
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
custom_action = (
|
||||
<span
|
||||
key={ 'action-'+index } className={ action.name }>
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
<a href="javascript:;" onClick={
|
||||
(action.onClick !== undefined)
|
||||
? action.onClick.bind(null,
|
||||
@ -108,15 +132,20 @@ define(
|
||||
)
|
||||
: false
|
||||
}>{ action.label }</a>
|
||||
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if(custom_action !== null && is_first === true) {
|
||||
is_first = false;
|
||||
}
|
||||
|
||||
return custom_action;
|
||||
}.bind(this));
|
||||
} else {
|
||||
item_actions = (
|
||||
<span className="edit">
|
||||
<Link to={ `/edit/${ this.props.item.id }` }>Edit</Link>
|
||||
<Link to={ `/edit/${ this.props.item.id }` }>{MailPoet.I18n.t('edit')}</Link>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -132,7 +161,7 @@ define(
|
||||
null,
|
||||
this.props.item.id
|
||||
)}
|
||||
>Restore</a>
|
||||
>{MailPoet.I18n.t('restore')}</a>
|
||||
</span>
|
||||
{ ' | ' }
|
||||
<span className="delete">
|
||||
@ -143,13 +172,13 @@ define(
|
||||
null,
|
||||
this.props.item.id
|
||||
)}
|
||||
>Delete permanently</a>
|
||||
>{MailPoet.I18n.t('deletePermanently')}</a>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
|
||||
className="toggle-row" type="button">
|
||||
<span className="screen-reader-text">Show more details</span>
|
||||
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@ -158,22 +187,11 @@ define(
|
||||
<div>
|
||||
<div className="row-actions">
|
||||
{ item_actions }
|
||||
{ ' | ' }
|
||||
<span className="trash">
|
||||
<a
|
||||
href="javascript:;"
|
||||
onClick={ this.handleTrashItem.bind(
|
||||
null,
|
||||
this.props.item.id
|
||||
) }>
|
||||
Trash
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
|
||||
className="toggle-row" type="button">
|
||||
<span className="screen-reader-text">Show more details</span>
|
||||
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@ -205,8 +223,8 @@ define(
|
||||
className="colspanchange">
|
||||
{
|
||||
(this.props.loading === true)
|
||||
? MailPoetI18n.loadingItems
|
||||
: MailPoetI18n.noItemsFound
|
||||
? MailPoet.I18n.t('loadingItems')
|
||||
: MailPoet.I18n.t('noItemsFound')
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
@ -232,8 +250,8 @@ define(
|
||||
}>
|
||||
{
|
||||
(this.props.selection !== 'all')
|
||||
? MailPoetI18n.selectAllLabel
|
||||
: MailPoetI18n.selectedAllLabel.replace(
|
||||
? MailPoet.I18n.t('selectAllLabel')
|
||||
: MailPoet.I18n.t('selectedAllLabel').replace(
|
||||
'%d',
|
||||
this.props.count
|
||||
)
|
||||
@ -243,8 +261,8 @@ define(
|
||||
onClick={ this.props.onSelectAll }
|
||||
href="javascript:;">{
|
||||
(this.props.selection !== 'all')
|
||||
? MailPoetI18n.selectAllLink
|
||||
: MailPoetI18n.clearSelection
|
||||
? MailPoet.I18n.t('selectAllLink')
|
||||
: MailPoet.I18n.t('clearSelection')
|
||||
}</a>
|
||||
</td>
|
||||
</tr>
|
||||
@ -277,9 +295,9 @@ define(
|
||||
});
|
||||
|
||||
var Listing = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
loading: false,
|
||||
@ -341,9 +359,12 @@ define(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// default overrides
|
||||
if(this.props.limit !== undefined) {
|
||||
state.limit = Math.abs(~~this.props.limit);
|
||||
}
|
||||
|
||||
this.setState(state, function() {
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
@ -380,7 +401,7 @@ define(
|
||||
|
||||
if(this.props.location) {
|
||||
if(this.props.location.pathname !== params) {
|
||||
this.history.pushState(null, `${params}`)
|
||||
this.context.router.push(`${params}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -388,6 +409,12 @@ define(
|
||||
if(this.isMounted()) {
|
||||
const params = this.props.params || {}
|
||||
this.initWithParams(params)
|
||||
|
||||
if(this.props.auto_refresh) {
|
||||
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) {
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
@ -419,7 +446,13 @@ define(
|
||||
groups: response.groups || [],
|
||||
count: response.count || 0,
|
||||
loading: false
|
||||
});
|
||||
}, function() {
|
||||
if(this.props['onGetItems'] !== undefined) {
|
||||
this.props.onGetItems(
|
||||
~~(this.state.groups[0]['count'])
|
||||
);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
@ -483,10 +516,21 @@ define(
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
handleEmptyTrash: function() {
|
||||
this.handleBulkAction('all', {
|
||||
action: 'delete',
|
||||
group: 'trash'
|
||||
}, function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response)
|
||||
);
|
||||
});
|
||||
},
|
||||
handleBulkAction: function(selected_ids, params, callback) {
|
||||
if(
|
||||
this.state.selection === false
|
||||
&& this.state.selected_ids.length === 0
|
||||
&& selected_ids !== 'all'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -499,8 +543,10 @@ define(
|
||||
limit: 0,
|
||||
filter: this.state.filter,
|
||||
group: this.state.group,
|
||||
search: this.state.search,
|
||||
selection: selected_ids
|
||||
search: this.state.search
|
||||
}
|
||||
if(selected_ids !== 'all') {
|
||||
data.listing.selection = selected_ids;
|
||||
}
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
@ -631,16 +677,16 @@ define(
|
||||
// bulk actions
|
||||
var bulk_actions = this.props.bulk_actions || [];
|
||||
|
||||
if(this.state.group === 'trash') {
|
||||
if(this.state.group === 'trash' && bulk_actions.length > 0) {
|
||||
bulk_actions = [
|
||||
{
|
||||
name: 'restore',
|
||||
label: 'Restore',
|
||||
label: MailPoet.I18n.t('restore'),
|
||||
onSuccess: this.props.messages.onRestore
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
label: 'Delete permanently',
|
||||
label: MailPoet.I18n.t('deletePermanently'),
|
||||
onSuccess: this.props.messages.onDelete
|
||||
}
|
||||
];
|
||||
@ -694,7 +740,10 @@ define(
|
||||
<ListingFilters
|
||||
filters={ this.state.filters }
|
||||
filter={ this.state.filter }
|
||||
onSelectFilter={ this.handleFilter } />
|
||||
group={ this.state.group }
|
||||
onSelectFilter={ this.handleFilter }
|
||||
onEmptyTrash={ this.handleEmptyTrash }
|
||||
/>
|
||||
<ListingPages
|
||||
count={ this.state.count }
|
||||
page={ this.state.page }
|
||||
@ -707,8 +756,8 @@ define(
|
||||
onSort={ this.handleSort }
|
||||
onSelectItems={ this.handleSelectItems }
|
||||
selection={ this.state.selection }
|
||||
sort_by={ this.state.sort_by }
|
||||
sort_order={ this.state.sort_order }
|
||||
sort_by={ sort_by }
|
||||
sort_order={ sort_order }
|
||||
columns={ this.props.columns }
|
||||
is_selectable={ bulk_actions.length > 0 } />
|
||||
</thead>
|
||||
@ -737,8 +786,8 @@ define(
|
||||
onSort={ this.handleSort }
|
||||
onSelectItems={ this.handleSelectItems }
|
||||
selection={ this.state.selection }
|
||||
sort_by={ this.state.sort_by }
|
||||
sort_order={ this.state.sort_order }
|
||||
sort_by={ sort_by }
|
||||
sort_order={ sort_order }
|
||||
columns={ this.props.columns }
|
||||
is_selectable={ bulk_actions.length > 0 } />
|
||||
</tfoot>
|
||||
|
@ -1,4 +1,12 @@
|
||||
define(['react', 'classnames'], function(React, classNames) {
|
||||
define([
|
||||
'react',
|
||||
'classnames',
|
||||
'mailpoet'
|
||||
], function(
|
||||
React,
|
||||
classNames,
|
||||
MailPoet
|
||||
) {
|
||||
|
||||
var ListingPages = React.createClass({
|
||||
getInitialState: function() {
|
||||
@ -7,7 +15,11 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
}
|
||||
},
|
||||
setPage: function(page) {
|
||||
this.props.onSetPage(page);
|
||||
this.setState({
|
||||
page: null
|
||||
}, function () {
|
||||
this.props.onSetPage(this.constrainPage(page));
|
||||
}.bind(this));
|
||||
},
|
||||
setFirstPage: function() {
|
||||
this.setPage(1);
|
||||
@ -16,10 +28,14 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
this.setPage(this.getLastPage());
|
||||
},
|
||||
setPreviousPage: function() {
|
||||
this.setPage(this.constrainPage(this.props.page - 1));
|
||||
this.setPage(this.constrainPage(
|
||||
parseInt(this.props.page, 10) - 1)
|
||||
);
|
||||
},
|
||||
setNextPage: function() {
|
||||
this.setPage(this.constrainPage(this.props.page + 1));
|
||||
this.setPage(this.constrainPage(
|
||||
parseInt(this.props.page, 10) + 1)
|
||||
);
|
||||
},
|
||||
constrainPage: function(page) {
|
||||
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage());
|
||||
@ -27,14 +43,16 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
handleSetManualPage: function(e) {
|
||||
if(e.which === 13) {
|
||||
this.setPage(this.state.page);
|
||||
this.setState({ page: null });
|
||||
}
|
||||
},
|
||||
handleChangeManualPage: function(e) {
|
||||
this.setState({
|
||||
page: this.constrainPage(e.target.value)
|
||||
page: e.target.value
|
||||
});
|
||||
},
|
||||
handleBlurManualPage: function(e) {
|
||||
this.setPage(e.target.value);
|
||||
},
|
||||
getLastPage: function() {
|
||||
return Math.ceil(this.props.count / this.props.limit);
|
||||
},
|
||||
@ -62,7 +80,7 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
<a href="javascript:;"
|
||||
onClick={ this.setPreviousPage }
|
||||
className="prev-page">
|
||||
<span className="screen-reader-text">Previous page</span>
|
||||
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
|
||||
<span aria-hidden="true">‹</span>
|
||||
</a>
|
||||
);
|
||||
@ -73,7 +91,7 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
<a href="javascript:;"
|
||||
onClick={ this.setFirstPage }
|
||||
className="first-page">
|
||||
<span className="screen-reader-text">First page</span>
|
||||
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
);
|
||||
@ -84,7 +102,7 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
<a href="javascript:;"
|
||||
onClick={ this.setNextPage }
|
||||
className="next-page">
|
||||
<span className="screen-reader-text">Next page</span>
|
||||
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
);
|
||||
@ -95,12 +113,17 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
<a href="javascript:;"
|
||||
onClick={ this.setLastPage }
|
||||
className="last-page">
|
||||
<span className="screen-reader-text">Last page</span>
|
||||
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
let pageValue = this.props.page;
|
||||
if(this.state.page !== null) {
|
||||
pageValue = this.state.page;
|
||||
}
|
||||
|
||||
pagination = (
|
||||
<span className="pagination-links">
|
||||
{firstPage}
|
||||
@ -110,19 +133,20 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
<span className="paging-input">
|
||||
<label
|
||||
className="screen-reader-text"
|
||||
htmlFor="current-page-selector">Current Page</label>
|
||||
htmlFor="current-page-selector">{MailPoet.I18n.t('currentPage')}</label>
|
||||
<input
|
||||
type="text"
|
||||
onChange={ this.handleChangeManualPage }
|
||||
onKeyUp={ this.handleSetManualPage }
|
||||
onBlur={ this.handleBlurManualPage }
|
||||
aria-describedby="table-paging"
|
||||
size="1"
|
||||
size="2"
|
||||
ref="page"
|
||||
value={ this.state.page || this.props.page }
|
||||
value={ pageValue }
|
||||
name="paged"
|
||||
id="current-page-selector"
|
||||
className="current-page" />
|
||||
of
|
||||
{MailPoet.I18n.t('pageOutOf')}
|
||||
<span className="total-pages">
|
||||
{Math.ceil(this.props.count / this.props.limit)}
|
||||
</span>
|
||||
@ -142,7 +166,9 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
<span className="displaying-num">{ this.props.count } items</span>
|
||||
<span className="displaying-num">{
|
||||
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count)
|
||||
}</span>
|
||||
{ pagination }
|
||||
</div>
|
||||
);
|
||||
@ -151,4 +177,4 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
});
|
||||
|
||||
return ListingPages;
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,10 @@
|
||||
define(['react'], function(React) {
|
||||
define([
|
||||
'mailpoet',
|
||||
'react'
|
||||
], function(
|
||||
MailPoet,
|
||||
React
|
||||
) {
|
||||
|
||||
var ListingSearch = React.createClass({
|
||||
handleSearch: function(e) {
|
||||
@ -18,7 +24,7 @@ define(['react'], function(React) {
|
||||
<form name="search" onSubmit={this.handleSearch}>
|
||||
<p className="search-box">
|
||||
<label htmlFor="search_input" className="screen-reader-text">
|
||||
Search
|
||||
{MailPoet.I18n.t('searchLabel')}
|
||||
</label>
|
||||
<input
|
||||
type="search"
|
||||
@ -28,7 +34,7 @@ define(['react'], function(React) {
|
||||
defaultValue={this.props.search} />
|
||||
<input
|
||||
type="submit"
|
||||
defaultValue={MailPoetI18n.searchLabel}
|
||||
defaultValue={MailPoet.I18n.t('searchLabel')}
|
||||
className="button" />
|
||||
</p>
|
||||
</form>
|
||||
|
@ -47,7 +47,6 @@ define([
|
||||
autoScroll: true,
|
||||
|
||||
onstart: function(event) {
|
||||
console.log('Drag start', event, this);
|
||||
|
||||
if (that.options.cloneOriginal === true) {
|
||||
// Use substitution instead of a clone
|
||||
|
@ -11,10 +11,20 @@ define([
|
||||
'newsletter_editor/blocks/base',
|
||||
'newsletter_editor/blocks/button',
|
||||
'newsletter_editor/blocks/divider',
|
||||
'newsletter_editor/components/wordpress',
|
||||
'newsletter_editor/components/communication',
|
||||
'mailpoet',
|
||||
'underscore',
|
||||
'jquery'
|
||||
], function(App, BaseBlock, ButtonBlock, DividerBlock, WordpressComponent, _, jQuery) {
|
||||
], function(
|
||||
App,
|
||||
BaseBlock,
|
||||
ButtonBlock,
|
||||
DividerBlock,
|
||||
CommunicationComponent,
|
||||
MailPoet,
|
||||
_,
|
||||
jQuery
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -32,10 +42,10 @@ define([
|
||||
inclusionType: 'include', // 'include'|'exclude'
|
||||
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
|
||||
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
|
||||
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
|
||||
titleAlignment: 'left', // 'left'|'center'|'right'
|
||||
titleIsLink: false, // false|true
|
||||
imagePadded: true, // true|false
|
||||
imageFullWidth: false, // true|false
|
||||
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
|
||||
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
|
||||
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
|
||||
authorPrecededBy: 'Author:',
|
||||
@ -63,17 +73,17 @@ define([
|
||||
initialize: function() {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
this.fetchPosts();
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
|
||||
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
|
||||
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
|
||||
},
|
||||
fetchPosts: function() {
|
||||
var that = this;
|
||||
WordpressComponent.getTransformedPosts(this.toJSON()).done(function(content) {
|
||||
console.log('ALC fetched', arguments);
|
||||
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
|
||||
that.get('_container').get('blocks').reset(content, {parse: true});
|
||||
that.trigger('postsChanged');
|
||||
}).fail(function(error) {
|
||||
console.log('ALC fetchPosts error', arguments);
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
|
||||
});
|
||||
},
|
||||
/**
|
||||
@ -81,7 +91,7 @@ define([
|
||||
* ALC posts on each model change
|
||||
*/
|
||||
_scheduleFetchPosts: function() {
|
||||
var timeout = 2000,
|
||||
var timeout = 500,
|
||||
that = this;
|
||||
if (this._fetchPostsTimer !== undefined) {
|
||||
clearTimeout(this._fetchPostsTimer);
|
||||
@ -100,6 +110,11 @@ define([
|
||||
toolsRegion: '.mailpoet_tools',
|
||||
postsRegion: '.mailpoet_automated_latest_content_block_posts',
|
||||
},
|
||||
modelEvents: _.extend(
|
||||
_.omit(base.BlockView.prototype.modelEvents, 'change'),
|
||||
{
|
||||
'postsChanged': 'render',
|
||||
}),
|
||||
events: _.extend(base.BlockView.prototype.events, {
|
||||
'click .mailpoet_automated_latest_content_block_overlay': 'showSettings',
|
||||
}),
|
||||
@ -137,9 +152,9 @@ define([
|
||||
"keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
|
||||
"change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"),
|
||||
"change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"),
|
||||
"change .mailpoet_automated_latest_content_title_position": _.partial(this.changeField, "titlePosition"),
|
||||
"change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"),
|
||||
"change .mailpoet_automated_latest_content_image_padded": _.partial(this.changeBoolField, "imagePadded"),
|
||||
"change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
|
||||
"change .mailpoet_automated_latest_content_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
|
||||
"change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"),
|
||||
"keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
|
||||
"change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"),
|
||||
@ -161,7 +176,7 @@ define([
|
||||
var that = this;
|
||||
|
||||
// Dynamically update available post types
|
||||
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
|
||||
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
|
||||
multiple: true,
|
||||
@ -174,10 +189,10 @@ define([
|
||||
},
|
||||
transport: function(options, success, failure) {
|
||||
var taxonomies,
|
||||
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
taxonomies = tax;
|
||||
// Fetch available terms based on the list of taxonomies already fetched
|
||||
var promise = WordpressComponent.getTerms({
|
||||
var promise = CommunicationComponent.getTerms({
|
||||
search: options.data.term,
|
||||
taxonomies: _.keys(taxonomies)
|
||||
}).then(function(terms) {
|
||||
@ -226,11 +241,11 @@ define([
|
||||
toggleDisplayOptions: function(event) {
|
||||
var el = this.$('.mailpoet_automated_latest_content_display_options'),
|
||||
showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
|
||||
if (el.hasClass('mailpoet_hidden')) {
|
||||
el.removeClass('mailpoet_hidden');
|
||||
if (el.hasClass('mailpoet_closed')) {
|
||||
el.removeClass('mailpoet_closed');
|
||||
showControl.addClass('mailpoet_hidden');
|
||||
} else {
|
||||
el.addClass('mailpoet_hidden');
|
||||
el.addClass('mailpoet_closed');
|
||||
showControl.removeClass('mailpoet_hidden');
|
||||
}
|
||||
},
|
||||
@ -268,12 +283,13 @@ define([
|
||||
},
|
||||
changeDisplayType: function(event) {
|
||||
var value = jQuery(event.target).val();
|
||||
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_automated_latest_content_title_position_container').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_automated_latest_content_title_position_container').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
@ -282,6 +298,12 @@ define([
|
||||
this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'excerpt') {
|
||||
this.$('.mailpoet_automated_latest_content_featured_image_position_container').removeClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_automated_latest_content_featured_image_position_container').addClass('mailpoet_hidden');
|
||||
}
|
||||
this.changeField('displayType', event);
|
||||
},
|
||||
changeTitleFormat: function(event) {
|
||||
@ -306,7 +328,7 @@ define([
|
||||
_.each(postTypes, function(type) {
|
||||
select.append(jQuery('<option>', {
|
||||
value: type.name,
|
||||
text: type.labels.singular_name,
|
||||
text: type.label,
|
||||
}));
|
||||
});
|
||||
select.val(selectedValue);
|
||||
|
@ -48,6 +48,7 @@ define([
|
||||
},
|
||||
modelEvents: {
|
||||
'change': 'render',
|
||||
'delete': 'deleteBlock',
|
||||
},
|
||||
events: {
|
||||
"mouseenter": "showTools",
|
||||
@ -88,7 +89,9 @@ define([
|
||||
this.$el.addClass('mailpoet_editor_view_' + this.cid);
|
||||
},
|
||||
initialize: function() {
|
||||
this.on('showSettings', this.showSettings);
|
||||
this.on('showSettings', this.showSettings, this);
|
||||
this.on('dom:refresh', this.showBlock, this);
|
||||
this._isFirstRender = true;
|
||||
},
|
||||
showTools: function(_event) {
|
||||
if (!this.showingToolsDisabled) {
|
||||
@ -114,12 +117,49 @@ define([
|
||||
* Defines drop behavior of BlockView instance
|
||||
*/
|
||||
getDropFunc: function() {
|
||||
var that = this;
|
||||
return function() {
|
||||
var newModel = that.model.clone();
|
||||
//that.model.destroy();
|
||||
return newModel;
|
||||
};
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
this.transitionIn();
|
||||
this._isFirstRender = false;
|
||||
}
|
||||
},
|
||||
deleteBlock: function() {
|
||||
this.transitionOut().then(function() {
|
||||
this.model.destroy();
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('slideDown', 'fadeIn', 'easeOut');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('slideUp', 'fadeOut', 'easeIn');
|
||||
},
|
||||
_transition: function(slideDirection, fadeDirection, easing) {
|
||||
var promise = jQuery.Deferred();
|
||||
|
||||
this.$el.velocity(
|
||||
slideDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
complete: function() {
|
||||
promise.resolve();
|
||||
}.bind(this),
|
||||
}
|
||||
).velocity(
|
||||
fadeDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
queue: false, // Do not enqueue, trigger animation in parallel
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
|
||||
@ -168,24 +208,22 @@ define([
|
||||
},
|
||||
deleteBlock: function(event) {
|
||||
event.preventDefault();
|
||||
this.model.destroy();
|
||||
this.model.trigger('delete');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Module.BlockSettingsView = Marionette.LayoutView.extend({
|
||||
className: 'mailpoet_editor_settings',
|
||||
initialize: function() {
|
||||
var that = this;
|
||||
|
||||
MailPoet.Modal.panel({
|
||||
element: this.$el,
|
||||
template: '',
|
||||
position: 'right',
|
||||
width: App.getConfig().get('sidepanelWidth'),
|
||||
onCancel: function() {
|
||||
that.destroy();
|
||||
},
|
||||
this.destroy();
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
close: function(event) {
|
||||
@ -203,6 +241,9 @@ define([
|
||||
changeBoolField: function(field, event) {
|
||||
this.model.set(field, (jQuery(event.target).val() === 'true') ? true : false);
|
||||
},
|
||||
changeBoolCheckboxField: function(field, event) {
|
||||
this.model.set(field, (!!jQuery(event.target).prop('checked')));
|
||||
},
|
||||
changeColorField: function(field, event) {
|
||||
var value = jQuery(event.target).val();
|
||||
if (value === '') {
|
||||
|
@ -32,6 +32,7 @@ define([
|
||||
fontColor: '#000000',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'normal', // 'normal'|'bold'
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
@ -42,16 +43,12 @@ define([
|
||||
Module.ButtonBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_button_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.buttonBlock; },
|
||||
modelEvents: {
|
||||
'change': 'render',
|
||||
},
|
||||
onDragSubstituteBy: function() { return Module.ButtonWidgetView; },
|
||||
initialize: function() {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
var that = this;
|
||||
|
||||
// Listen for attempts to change all dividers in one go
|
||||
this._replaceButtonStylesHandler = function(data) { that.model.set(data); };
|
||||
this._replaceButtonStylesHandler = function(data) { this.model.set(data); }.bind(this);
|
||||
App.getChannel().on('replaceAllButtonStyles', this._replaceButtonStylesHandler);
|
||||
},
|
||||
onRender: function() {
|
||||
@ -76,6 +73,7 @@ define([
|
||||
"change .mailpoet_field_button_font_size": _.partial(this.changeField, "styles.block.fontSize"),
|
||||
"change .mailpoet_field_button_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
|
||||
"change .mailpoet_field_button_border_color": _.partial(this.changeColorField, "styles.block.borderColor"),
|
||||
"change .mailpoet_field_button_font_weight": "changeFontWeight",
|
||||
|
||||
"input .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
|
||||
"change .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
|
||||
@ -132,6 +130,13 @@ define([
|
||||
this.$(fieldToUpdate).val(jQuery(event.target).val());
|
||||
callable(event);
|
||||
},
|
||||
changeFontWeight: function(event) {
|
||||
var checked = !!jQuery(event.target).prop('checked');
|
||||
this.model.set(
|
||||
'styles.block.fontWeight',
|
||||
(checked) ? jQuery(event.target).val() : 'normal'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Module.ButtonWidgetView = base.WidgetView.extend({
|
||||
|
@ -75,7 +75,8 @@ define([
|
||||
getEmptyView: function() { return Module.ContainerBlockEmptyView; },
|
||||
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; },
|
||||
modelEvents: {
|
||||
'change': 'render'
|
||||
'change': 'render',
|
||||
'delete': 'deleteBlock',
|
||||
},
|
||||
events: {
|
||||
"mouseenter": "showTools",
|
||||
@ -136,6 +137,8 @@ define([
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.renderOptions = _.defaults(options.renderOptions || {}, {});
|
||||
this.on('dom:refresh', this.showBlock, this);
|
||||
this._isFirstRender = true;
|
||||
},
|
||||
// Determines which view type should be used for a child
|
||||
getChildView: function(model) {
|
||||
@ -162,10 +165,10 @@ define([
|
||||
this.toolsView = new Module.ContainerBlockToolsView({
|
||||
model: this.model,
|
||||
tools: {
|
||||
settings: this.renderOptions.depth > 1,
|
||||
settings: this.renderOptions.depth === 1,
|
||||
delete: this.renderOptions.depth === 1,
|
||||
move: this.renderOptions.depth === 1,
|
||||
layerSelector: this.renderOptions.depth === 1,
|
||||
layerSelector: false,
|
||||
},
|
||||
});
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
@ -229,12 +232,49 @@ define([
|
||||
_.extend(this, this._buildRegions(this.regions));
|
||||
},
|
||||
getDropFunc: function() {
|
||||
var that = this;
|
||||
return function() {
|
||||
var newModel = that.model.clone();
|
||||
that.model.destroy();
|
||||
return newModel;
|
||||
};
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
this.transitionIn();
|
||||
this._isFirstRender = false;
|
||||
}
|
||||
},
|
||||
deleteBlock: function() {
|
||||
this.transitionOut().done(function() {
|
||||
this.model.destroy();
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('slideDown', 'fadeIn', 'easeIn');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('slideUp', 'fadeOut', 'easeOut');
|
||||
},
|
||||
_transition: function(slideDirection, fadeDirection, easing) {
|
||||
var promise = jQuery.Deferred();
|
||||
|
||||
this.$el.velocity(
|
||||
slideDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
complete: function() {
|
||||
promise.resolve();
|
||||
}.bind(this),
|
||||
}
|
||||
).velocity(
|
||||
fadeDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
queue: false, // Do not enqueue, trigger animation in parallel
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
|
||||
@ -265,6 +305,41 @@ define([
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
regions: {
|
||||
columnsSettingsRegion: '.mailpoet_container_columns_settings',
|
||||
},
|
||||
initialize: function() {
|
||||
base.BlockSettingsView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({
|
||||
collection: this.model.get('blocks'),
|
||||
});
|
||||
},
|
||||
onRender: function() {
|
||||
this.columnsSettingsRegion.show(this._columnsSettingsView);
|
||||
},
|
||||
});
|
||||
|
||||
Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
|
||||
getChildView: function() { return Module.ContainerBlockColumnSettingsView; },
|
||||
childViewOptions: function(model, index) {
|
||||
return {
|
||||
columnIndex: index,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Module.ContainerBlockColumnSettingsView = Marionette.ItemView.extend({
|
||||
getTemplate: function() { return templates.containerBlockColumnSettings; },
|
||||
initialize: function(options) {
|
||||
this.columnNumber = (options.columnIndex || 0) + 1;
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
columnNumber: this.columnNumber,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Module.OneColumnContainerWidgetView = base.WidgetView.extend({
|
||||
|
@ -57,11 +57,9 @@ define([
|
||||
this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
return _.extend({
|
||||
totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px',
|
||||
};
|
||||
}, base.BlockView.prototype.templateHelpers.apply(this));
|
||||
},
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.DividerBlockToolsView({ model: this.model });
|
||||
|
@ -16,7 +16,7 @@ define([
|
||||
defaults: function() {
|
||||
return this._getDefaults({
|
||||
type: 'footer',
|
||||
text: '<a href="[unsubscribeUrl]">Unsubscribe</a> | <a href="[manageSubscriptionUrl]">Manage subscription</a><br /><b>Add your postal address here!</b>',
|
||||
text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
|
||||
styles: {
|
||||
block: {
|
||||
backgroundColor: 'transparent',
|
||||
@ -39,9 +39,9 @@ define([
|
||||
Module.FooterBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_footer_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.footerBlock; },
|
||||
modelEvents: {
|
||||
modelEvents: _.extend({
|
||||
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
|
||||
},
|
||||
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
|
||||
onDragSubstituteBy: function() { return Module.FooterWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
|
||||
@ -60,11 +60,9 @@ define([
|
||||
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
|
||||
invalid_elements: "script",
|
||||
style_formats: [
|
||||
{title: 'Paragraph', block: 'p'},
|
||||
],
|
||||
block_formats: 'Paragraph=p',
|
||||
|
||||
plugins: "link textcolor mailpoet_custom_fields",
|
||||
plugins: "link textcolor colorpicker mailpoet_custom_fields",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
@ -81,7 +79,7 @@ define([
|
||||
},
|
||||
|
||||
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
|
||||
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
|
||||
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -16,7 +16,7 @@ define([
|
||||
defaults: function() {
|
||||
return this._getDefaults({
|
||||
type: 'header',
|
||||
text: 'Display problems? <a href="[viewInBrowserUrl]">View it in your browser</a>',
|
||||
text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
|
||||
styles: {
|
||||
block: {
|
||||
backgroundColor: 'transparent',
|
||||
@ -39,9 +39,9 @@ define([
|
||||
Module.HeaderBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_header_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.headerBlock; },
|
||||
modelEvents: {
|
||||
modelEvents: _.extend({
|
||||
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
|
||||
},
|
||||
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
|
||||
onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
|
||||
@ -60,11 +60,9 @@ define([
|
||||
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
|
||||
invalid_elements: "script",
|
||||
style_formats: [
|
||||
{title: 'Paragraph', block: 'p'},
|
||||
],
|
||||
block_formats: 'Paragraph=p',
|
||||
|
||||
plugins: "link textcolor mailpoet_custom_fields",
|
||||
plugins: "link textcolor colorpicker mailpoet_custom_fields",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
@ -81,7 +79,7 @@ define([
|
||||
},
|
||||
|
||||
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
|
||||
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
|
||||
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ define([
|
||||
link: 'http://example.org',
|
||||
src: 'no-image.png',
|
||||
alt: 'An image of...',
|
||||
padded: true, // true | false - Padded or full width
|
||||
fullWidth: true, // true | false
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
styles: {
|
||||
@ -37,20 +37,18 @@ define([
|
||||
getTemplate: function() { return templates.imageBlock; },
|
||||
onDragSubstituteBy: function() { return Module.ImageWidgetView; },
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
return _.extend({
|
||||
imageMissingSrc: App.getConfig().get('urls.imageMissing'),
|
||||
};
|
||||
}, base.BlockView.prototype.templateHelpers.apply(this));
|
||||
},
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
|
||||
if (this.model.get('padded')) {
|
||||
this.$el.removeClass('mailpoet_full_image');
|
||||
} else {
|
||||
if (this.model.get('fullWidth')) {
|
||||
this.$el.addClass('mailpoet_full_image');
|
||||
} else {
|
||||
this.$el.removeClass('mailpoet_full_image');
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -66,7 +64,7 @@ define([
|
||||
"keyup .mailpoet_field_image_link": _.partial(this.changeField, "link"),
|
||||
"keyup .mailpoet_field_image_address": _.partial(this.changeField, "src"),
|
||||
"keyup .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
|
||||
"change .mailpoet_field_image_padded": _.partial(this.changeBoolField, "padded"),
|
||||
"change .mailpoet_field_image_full_width": _.partial(this.changeBoolCheckboxField, "fullWidth"),
|
||||
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
|
||||
"click .mailpoet_field_image_select_another_image": "showMediaManager",
|
||||
"click .mailpoet_done_editing": "close",
|
||||
|
@ -18,12 +18,12 @@ define([
|
||||
'jquery',
|
||||
'mailpoet',
|
||||
'newsletter_editor/App',
|
||||
'newsletter_editor/components/wordpress',
|
||||
'newsletter_editor/components/communication',
|
||||
'newsletter_editor/blocks/base',
|
||||
'newsletter_editor/blocks/button',
|
||||
'newsletter_editor/blocks/divider',
|
||||
'select2'
|
||||
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) {
|
||||
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, CommunicationComponent, BaseBlock, ButtonBlock, DividerBlock) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -31,7 +31,7 @@ define([
|
||||
base = BaseBlock;
|
||||
|
||||
Module.PostsBlockModel = base.BlockModel.extend({
|
||||
stale: ['_selectedPosts', '_availablePosts'],
|
||||
stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
|
||||
defaults: function() {
|
||||
return this._getDefaults({
|
||||
type: 'posts',
|
||||
@ -43,10 +43,10 @@ define([
|
||||
inclusionType: 'include', // 'include'|'exclude'
|
||||
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
|
||||
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
|
||||
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
|
||||
titleAlignment: 'left', // 'left'|'center'|'right'
|
||||
titleIsLink: false, // false|true
|
||||
imagePadded: true, // true|false
|
||||
imageFullWidth: false, // true|false
|
||||
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
|
||||
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
|
||||
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
|
||||
authorPrecededBy: 'Author:',
|
||||
@ -63,6 +63,7 @@ define([
|
||||
divider: {},
|
||||
_selectedPosts: [],
|
||||
_availablePosts: [],
|
||||
_transformedPosts: new (App.getBlockTypeModel('container'))(),
|
||||
}, App.getConfig().get('blockDefaults.posts'));
|
||||
},
|
||||
relations: function() {
|
||||
@ -71,42 +72,54 @@ define([
|
||||
divider: App.getBlockTypeModel('divider'),
|
||||
_selectedPosts: Backbone.Collection,
|
||||
_availablePosts: Backbone.Collection,
|
||||
_transformedPosts: App.getBlockTypeModel('container'),
|
||||
};
|
||||
},
|
||||
initialize: function() {
|
||||
var that = this;
|
||||
var that = this,
|
||||
POST_REFRESH_DELAY_MS = 500,
|
||||
refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS),
|
||||
refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
|
||||
|
||||
// Attach Radio.Requests API primarily for highlighting
|
||||
_.extend(this, Radio.Requests);
|
||||
|
||||
this.fetchAvailablePosts();
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', this._scheduleFetchAvailablePosts, this);
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
|
||||
|
||||
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
|
||||
this.on('change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
|
||||
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
|
||||
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
|
||||
|
||||
this.on('insertSelectedPosts', this._insertSelectedPosts, this);
|
||||
},
|
||||
fetchAvailablePosts: function() {
|
||||
var that = this;
|
||||
WordpressComponent.getPosts(this.toJSON()).done(function(posts) {
|
||||
console.log('Posts fetched', arguments);
|
||||
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) {
|
||||
that.get('_availablePosts').reset(posts);
|
||||
that.get('_selectedPosts').reset(); // Empty out the collection
|
||||
that.trigger('change:_availablePosts');
|
||||
}).fail(function() {
|
||||
console.log('Posts fetchPosts error', arguments);
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Batch more changes during a specific time, instead of fetching
|
||||
* ALC posts on each model change
|
||||
*/
|
||||
_scheduleFetchAvailablePosts: function() {
|
||||
var timeout = 500,
|
||||
that = this;
|
||||
if (this._fetchPostsTimer !== undefined) {
|
||||
clearTimeout(this._fetchPostsTimer);
|
||||
_refreshTransformedPosts: function() {
|
||||
var that = this,
|
||||
data = this.toJSON();
|
||||
|
||||
data.posts = this.get('_selectedPosts').pluck('ID');
|
||||
|
||||
if (data.posts.length === 0) {
|
||||
this.get('_transformedPosts').get('blocks').reset();
|
||||
return;
|
||||
}
|
||||
this._fetchPostsTimer = setTimeout(function() {
|
||||
that.fetchAvailablePosts();
|
||||
that._fetchPostsTimer = undefined;
|
||||
}, timeout);
|
||||
|
||||
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
|
||||
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
|
||||
}).fail(function() {
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
|
||||
});
|
||||
},
|
||||
_insertSelectedPosts: function() {
|
||||
var that = this,
|
||||
@ -118,11 +131,10 @@ define([
|
||||
|
||||
if (data.posts.length === 0) return;
|
||||
|
||||
WordpressComponent.getTransformedPosts(data).done(function(posts) {
|
||||
console.log('Available posts fetched', arguments);
|
||||
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
|
||||
collection.add(posts, { at: index });
|
||||
}).fail(function() {
|
||||
console.log('Posts fetchPosts error', arguments);
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -130,10 +142,14 @@ define([
|
||||
Module.PostsBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_posts_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.postsBlock; },
|
||||
modelEvents: {},
|
||||
modelEvents: {}, // Forcefully disable all events
|
||||
regions: _.extend({
|
||||
postsRegion: '.mailpoet_posts_block_posts',
|
||||
}, base.BlockView.prototype.regions),
|
||||
onDragSubstituteBy: function() { return Module.PostsWidgetView; },
|
||||
initialize: function() {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.toolsView = new Module.PostsBlockToolsView({ model: this.model });
|
||||
this.model.reply('blockView', this.notifyAboutSelf, this);
|
||||
},
|
||||
@ -142,6 +158,13 @@ define([
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
}
|
||||
this.trigger('showSettings');
|
||||
|
||||
var ContainerView = App.getBlockTypeView('container'),
|
||||
renderOptions = {
|
||||
disableTextEditor: true,
|
||||
disableDragAndDrop: true,
|
||||
};
|
||||
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
|
||||
},
|
||||
notifyAboutSelf: function() {
|
||||
return this;
|
||||
@ -197,8 +220,8 @@ define([
|
||||
},
|
||||
switchToDisplayOptions: function() {
|
||||
// Switch content view
|
||||
this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_closed');
|
||||
this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_closed');
|
||||
|
||||
// Switch controls
|
||||
this.$('.mailpoet_settings_posts_show_display_options').addClass('mailpoet_hidden');
|
||||
@ -206,8 +229,8 @@ define([
|
||||
},
|
||||
switchToPostSelection: function() {
|
||||
// Switch content view
|
||||
this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_closed');
|
||||
this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_closed');
|
||||
|
||||
// Switch controls
|
||||
this.$('.mailpoet_settings_posts_show_post_selection').addClass('mailpoet_hidden');
|
||||
@ -216,6 +239,7 @@ define([
|
||||
insertPosts: function() {
|
||||
this.model.trigger('insertSelectedPosts');
|
||||
this.model.destroy();
|
||||
this.close();
|
||||
},
|
||||
});
|
||||
|
||||
@ -245,7 +269,7 @@ define([
|
||||
var that = this;
|
||||
|
||||
// Dynamically update available post types
|
||||
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
|
||||
this.$('.mailpoet_posts_categories_and_tags').select2({
|
||||
multiple: true,
|
||||
@ -258,10 +282,10 @@ define([
|
||||
},
|
||||
transport: function(options, success, failure) {
|
||||
var taxonomies,
|
||||
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
taxonomies = tax;
|
||||
// Fetch available terms based on the list of taxonomies already fetched
|
||||
var promise = WordpressComponent.getTerms({
|
||||
var promise = CommunicationComponent.getTerms({
|
||||
search: options.data.term,
|
||||
taxonomies: _.keys(taxonomies)
|
||||
}).then(function(terms) {
|
||||
@ -307,11 +331,6 @@ define([
|
||||
},
|
||||
}).trigger( 'change' );
|
||||
},
|
||||
onBeforeDestroy: function() {
|
||||
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
|
||||
// Force close select2 if it hasn't closed yet
|
||||
this.$('.mailpoet_posts_categories_and_tags').select2('close');
|
||||
},
|
||||
changeField: function(field, event) {
|
||||
this.model.set(field, jQuery(event.target).val());
|
||||
},
|
||||
@ -323,7 +342,7 @@ define([
|
||||
_.each(postTypes, function(type) {
|
||||
select.append(jQuery('<option>', {
|
||||
value: type.name,
|
||||
text: type.labels.singular_name,
|
||||
text: type.label,
|
||||
}));
|
||||
});
|
||||
select.val(selectedValue);
|
||||
@ -375,9 +394,9 @@ define([
|
||||
"keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
|
||||
"change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"),
|
||||
"change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"),
|
||||
"change .mailpoet_posts_title_position": _.partial(this.changeField, "titlePosition"),
|
||||
"change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"),
|
||||
"change .mailpoet_posts_image_padded": _.partial(this.changeBoolField, "imagePadded"),
|
||||
"change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
|
||||
"change .mailpoet_posts_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
|
||||
"change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"),
|
||||
"keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
|
||||
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
|
||||
@ -429,11 +448,11 @@ define([
|
||||
changeDisplayType: function(event) {
|
||||
var value = jQuery(event.target).val();
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_posts_title_position_container').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_posts_title_position_container').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
@ -442,6 +461,13 @@ define([
|
||||
this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'excerpt') {
|
||||
this.$('.mailpoet_posts_featured_image_position_container').removeClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_posts_featured_image_position_container').addClass('mailpoet_hidden');
|
||||
}
|
||||
|
||||
this.changeField('displayType', event);
|
||||
},
|
||||
changeTitleFormat: function(event) {
|
||||
|
@ -103,7 +103,8 @@ define([
|
||||
getTemplate: function() { return templates.socialBlock; },
|
||||
childViewContainer: '.mailpoet_social',
|
||||
modelEvents: {
|
||||
'change': 'render'
|
||||
'change': 'render',
|
||||
'delete': 'deleteBlock',
|
||||
},
|
||||
events: {
|
||||
"mouseover": "showTools",
|
||||
@ -145,6 +146,10 @@ define([
|
||||
arguments[0].collection = arguments[0].model.get('icons');
|
||||
Marionette.CompositeView.apply(this, arguments);
|
||||
},
|
||||
initialize: function() {
|
||||
this.on('dom:refresh', this.showBlock, this);
|
||||
this._isFirstRender = true;
|
||||
},
|
||||
// Determines which view type should be used for a child
|
||||
childView: SocialIconView,
|
||||
templateHelpers: function() {
|
||||
@ -170,12 +175,9 @@ define([
|
||||
_event.stopPropagation();
|
||||
},
|
||||
getDropFunc: function() {
|
||||
var that = this;
|
||||
return function() {
|
||||
var newModel = that.model.clone();
|
||||
//that.model.destroy();
|
||||
return newModel;
|
||||
};
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
_buildRegions: function(regions) {
|
||||
var that = this;
|
||||
@ -194,6 +196,46 @@ define([
|
||||
this.regionManager.destroy();
|
||||
_.extend(this, this._buildRegions(this.regions));
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
this.transitionIn();
|
||||
this._isFirstRender = false;
|
||||
}
|
||||
},
|
||||
deleteBlock: function() {
|
||||
this.transitionOut().done(function() {
|
||||
this.model.destroy();
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('slideDown', 'fadeIn', 'easeIn');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('slideUp', 'fadeOut', 'easeOut');
|
||||
},
|
||||
_transition: function(slideDirection, fadeDirection, easing) {
|
||||
var promise = jQuery.Deferred();
|
||||
|
||||
this.$el.velocity(
|
||||
slideDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
complete: function() {
|
||||
promise.resolve();
|
||||
}.bind(this),
|
||||
}
|
||||
).velocity(
|
||||
fadeDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
queue: false, // Do not enqueue, trigger animation in parallel
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
|
||||
Module.SocialBlockToolsView = base.BlockToolsView.extend({
|
||||
|
@ -26,6 +26,8 @@ define([
|
||||
getTemplate: function() { return templates.textBlock; },
|
||||
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing
|
||||
initialize: function(options) {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.renderOptions = _.defaults(options.renderOptions || {}, {
|
||||
disableTextEditor: false,
|
||||
});
|
||||
@ -50,21 +52,15 @@ define([
|
||||
inline: true,
|
||||
|
||||
menubar: false,
|
||||
toolbar1: "styleselect bold italic forecolor | link unlink",
|
||||
toolbar1: "formatselect bold italic forecolor | link unlink",
|
||||
toolbar2: "alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_custom_fields",
|
||||
|
||||
//forced_root_block: 'p',
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],h1[class|style],h2[class|style],h3[class|style],ol[class|style],ul[class|style],li[class|style],strong[class|style],em[class|style],strike,br,blockquote[class|style],table[class|style],tr[class|style],th[class|style],td[class|style]",
|
||||
invalid_elements: "script",
|
||||
style_formats: [
|
||||
{title: 'Heading 1', block: 'h1'},
|
||||
{title: 'Heading 2', block: 'h2'},
|
||||
{title: 'Heading 3', block: 'h3'},
|
||||
block_formats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
|
||||
|
||||
{title: 'Paragraph', block: 'p'},
|
||||
],
|
||||
|
||||
plugins: "link code textcolor mailpoet_custom_fields",
|
||||
plugins: "link code textcolor colorpicker mailpoet_custom_fields",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
@ -81,7 +77,7 @@ define([
|
||||
},
|
||||
|
||||
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
|
||||
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
|
||||
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ define([
|
||||
|
||||
Module._query = function(args) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'wordpress',
|
||||
endpoint: 'automatedLatestContent',
|
||||
action: args.action,
|
||||
data: args.options || {},
|
||||
});
|
||||
@ -63,16 +63,18 @@ define([
|
||||
};
|
||||
|
||||
Module.saveNewsletter = function(options) {
|
||||
return Module._query({
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
options: options,
|
||||
data: options || {},
|
||||
});
|
||||
};
|
||||
|
||||
Module.previewNewsletter = function(options) {
|
||||
return Module._query({
|
||||
action: 'preview',
|
||||
options: options,
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'sendPreview',
|
||||
data: options || {},
|
||||
});
|
||||
};
|
||||
|
@ -10,7 +10,6 @@ define([
|
||||
availableStyles: {},
|
||||
socialIcons: {},
|
||||
blockDefaults: {},
|
||||
translations: {},
|
||||
sidepanelWidth: '331px',
|
||||
validation: {},
|
||||
urls: {},
|
||||
|
@ -11,7 +11,7 @@ define([
|
||||
// Does not hold newsletter content nor newsletter styles, those are
|
||||
// handled by other components.
|
||||
Module.NewsletterModel = SuperModel.extend({
|
||||
stale: ['body'],
|
||||
stale: ['body', 'created_at', 'deleted_at', 'updated_at'],
|
||||
initialize: function(options) {
|
||||
this.on('change', function() {
|
||||
App.getChannel().trigger('autoSave');
|
||||
@ -44,10 +44,10 @@ define([
|
||||
};
|
||||
|
||||
Module.getBody = function() {
|
||||
return JSON.stringify({
|
||||
return {
|
||||
content: App._contentContainer.toJSON(),
|
||||
globalStyles: App.getGlobalStyles().toJSON(),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
Module.toJSON = function() {
|
||||
@ -73,8 +73,7 @@ define([
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
// TODO: Other newsletter information will be needed as well.
|
||||
var body = JSON.parse(options.newsletter.body);
|
||||
var body = options.newsletter.body;
|
||||
App._contentContainer = new (App.getBlockTypeModel('container'))(body.content, {parse: true});
|
||||
App._contentContainerView = new (App.getBlockTypeView('container'))({
|
||||
model: App._contentContainer,
|
||||
|
@ -1,13 +1,26 @@
|
||||
define([
|
||||
'newsletter_editor/App',
|
||||
'newsletter_editor/components/communication',
|
||||
'mailpoet',
|
||||
'notice',
|
||||
'backbone',
|
||||
'backbone.marionette',
|
||||
'jquery',
|
||||
'blob',
|
||||
'filesaver'
|
||||
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver) {
|
||||
'filesaver',
|
||||
'html2canvas'
|
||||
], function(
|
||||
App,
|
||||
CommunicationComponent,
|
||||
MailPoet,
|
||||
Notice,
|
||||
Backbone,
|
||||
Marionette,
|
||||
jQuery,
|
||||
Blob,
|
||||
FileSaver,
|
||||
html2canvas
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -16,26 +29,33 @@ define([
|
||||
|
||||
// Save editor contents to server
|
||||
Module.save = function() {
|
||||
App.getChannel().trigger('beforeEditorSave');
|
||||
|
||||
var json = App.toJSON();
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(json.body)) {
|
||||
json.body = JSON.stringify(json.body);
|
||||
}
|
||||
|
||||
App.getChannel().trigger('beforeEditorSave', json);
|
||||
|
||||
// save newsletter
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: json,
|
||||
}).done(function(response) {
|
||||
CommunicationComponent.saveNewsletter(json).done(function(response) {
|
||||
if(response.success !== undefined && response.success === true) {
|
||||
// TODO: Handle translations
|
||||
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
|
||||
} else if(response.error !== undefined) {
|
||||
if(response.error.length === 0) {
|
||||
// TODO: Handle translations
|
||||
MailPoet.Notice.error("<?php _e('An unknown error occurred, please check your settings.'); ?>");
|
||||
MailPoet.Notice.error(
|
||||
"An unknown error occurred, please check your settings.",
|
||||
{
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$(response.error).each(function(i, error) {
|
||||
MailPoet.Notice.error(error);
|
||||
MailPoet.Notice.error(error, { scroll: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -46,26 +66,73 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
Module.saveTemplate = function(options) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'save',
|
||||
data: _.extend(options || {}, {
|
||||
body: App.getBody(),
|
||||
}),
|
||||
Module.getThumbnail = function(element, options) {
|
||||
var promise = html2canvas(element, options || {});
|
||||
|
||||
return promise.then(function(oldCanvas) {
|
||||
// Temporary workaround for html2canvas-alpha2.
|
||||
// Removes 1px left transparent border from resulting canvas.
|
||||
|
||||
var oldContext = oldCanvas.getContext('2d'),
|
||||
newCanvas = document.createElement("canvas"),
|
||||
newContext = newCanvas.getContext("2d"),
|
||||
leftBorderWidth = 1;
|
||||
|
||||
newCanvas.width = oldCanvas.width;
|
||||
newCanvas.height = oldCanvas.height;
|
||||
|
||||
newContext.drawImage(
|
||||
oldCanvas,
|
||||
leftBorderWidth, 0, oldCanvas.width - leftBorderWidth, oldCanvas.height,
|
||||
0, 0, oldCanvas.width, oldCanvas.height
|
||||
);
|
||||
|
||||
return newCanvas;
|
||||
});
|
||||
};
|
||||
|
||||
Module.exportTemplate = function(options) {
|
||||
var data = _.extend(options || {}, {
|
||||
body: App.getBody(),
|
||||
});
|
||||
var blob = new Blob(
|
||||
[JSON.stringify(data)],
|
||||
{ type: 'application/json;charset=utf-8' }
|
||||
);
|
||||
Module.saveTemplate = function(options) {
|
||||
var that = this,
|
||||
promise = jQuery.Deferred();
|
||||
|
||||
FileSaver.saveAs(blob, 'template.json');
|
||||
promise.then(function(thumbnail) {
|
||||
var data = _.extend(options || {}, {
|
||||
thumbnail: thumbnail.toDataURL('image/jpeg'),
|
||||
body: JSON.stringify(App.getBody()),
|
||||
});
|
||||
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'save',
|
||||
data: data,
|
||||
});
|
||||
});
|
||||
|
||||
Module.getThumbnail(
|
||||
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
|
||||
).then(function(thumbnail) {
|
||||
promise.resolve(thumbnail);
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
Module.exportTemplate = function(options) {
|
||||
var that = this;
|
||||
return Module.getThumbnail(
|
||||
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
|
||||
).then(function(thumbnail) {
|
||||
var data = _.extend(options || {}, {
|
||||
thumbnail: thumbnail.toDataURL('image/jpeg'),
|
||||
body: App.getBody(),
|
||||
});
|
||||
var blob = new Blob(
|
||||
[JSON.stringify(data)],
|
||||
{ type: 'application/json;charset=utf-8' }
|
||||
);
|
||||
|
||||
FileSaver.saveAs(blob, 'template.json');
|
||||
});
|
||||
};
|
||||
|
||||
Module.SaveView = Marionette.LayoutView.extend({
|
||||
@ -119,20 +186,49 @@ define([
|
||||
},
|
||||
saveAsTemplate: function() {
|
||||
var templateName = this.$('.mailpoet_save_as_template_name').val(),
|
||||
templateDescription = this.$('.mailpoet_save_as_template_description').val();
|
||||
templateDescription = this.$('.mailpoet_save_as_template_description').val(),
|
||||
that = this;
|
||||
|
||||
console.log('Saving template with ', templateName, templateDescription);
|
||||
Module.saveTemplate({
|
||||
name: templateName,
|
||||
description: templateDescription,
|
||||
}).done(function() {
|
||||
console.log('Template saved', arguments);
|
||||
}).fail(function() {
|
||||
// TODO: Handle error messages
|
||||
console.log('Template save failed', arguments);
|
||||
});
|
||||
if (templateName === '') {
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('templateNameMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else if (templateDescription === '') {
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('templateDescriptionMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Module.saveTemplate({
|
||||
name: templateName,
|
||||
description: templateDescription,
|
||||
}).done(function() {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('templateSaved'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
}).fail(function() {
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('templateSaveFailed'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
this.hideOptionContents();
|
||||
}
|
||||
|
||||
this.hideOptionContents();
|
||||
},
|
||||
toggleExportTemplate: function() {
|
||||
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
|
||||
@ -143,14 +239,26 @@ define([
|
||||
},
|
||||
exportTemplate: function() {
|
||||
var templateName = this.$('.mailpoet_export_template_name').val(),
|
||||
templateDescription = this.$('.mailpoet_export_template_description').val();
|
||||
templateDescription = this.$('.mailpoet_export_template_description').val(),
|
||||
that = this;
|
||||
|
||||
if (templateName === '') {
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.templateNameMissing'));
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('templateNameMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else if (templateDescription === '') {
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.templateDescriptionMissing'));
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('templateDescriptionMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Exporting template with ', templateName, templateDescription);
|
||||
Module.exportTemplate({
|
||||
name: templateName,
|
||||
description: templateDescription,
|
||||
@ -166,7 +274,6 @@ define([
|
||||
next: function() {
|
||||
this.hideOptionContents();
|
||||
if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
|
||||
console.log('Next');
|
||||
window.location.href = App.getConfig().get('urls.send');
|
||||
}
|
||||
},
|
||||
@ -177,8 +284,8 @@ define([
|
||||
}
|
||||
|
||||
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
|
||||
JSON.stringify(jsonObject).indexOf("[unsubscribeUrl]") < 0) {
|
||||
this.showValidationError(App.getConfig().get('translations.unsubscribeLinkMissing'));
|
||||
JSON.stringify(jsonObject).indexOf("[link:subscription_unsubscribe_url]") < 0) {
|
||||
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,24 @@
|
||||
define([
|
||||
'newsletter_editor/App',
|
||||
'newsletter_editor/components/communication',
|
||||
'mailpoet',
|
||||
'backbone',
|
||||
'backbone.marionette',
|
||||
'backbone.supermodel',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'sticky-kit'
|
||||
], function(App, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
|
||||
], function(
|
||||
App,
|
||||
CommunicationComponent,
|
||||
MailPoet,
|
||||
Backbone,
|
||||
Marionette,
|
||||
SuperModel,
|
||||
_,
|
||||
jQuery,
|
||||
StickyKit
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -50,8 +62,33 @@ define([
|
||||
},
|
||||
events: {
|
||||
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function(event) {
|
||||
this.$el.find('.mailpoet_sidebar_region').addClass('closed');
|
||||
this.$el.find(event.target).parent().parent().removeClass('closed');
|
||||
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'),
|
||||
$targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
|
||||
|
||||
if ($openRegion.get(0) === $targetRegion.get(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$openRegion.find('.mailpoet_region_content').velocity(
|
||||
'slideUp',
|
||||
{
|
||||
duration: 250,
|
||||
easing: "easeOut",
|
||||
complete: function() {
|
||||
$openRegion.addClass('closed');
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
$targetRegion.find('.mailpoet_region_content').velocity(
|
||||
'slideDown',
|
||||
{
|
||||
duration: 250,
|
||||
easing: "easeIn",
|
||||
complete: function() {
|
||||
$targetRegion.removeClass('closed');
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
initialize: function(options) {
|
||||
@ -90,7 +127,6 @@ define([
|
||||
});
|
||||
},
|
||||
onDomRefresh: function() {
|
||||
var that = this;
|
||||
this.$el.parent().stick_in_parent({
|
||||
offset_top: 32,
|
||||
});
|
||||
@ -169,10 +205,8 @@ define([
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.availableStyles = options.availableStyles;
|
||||
var that = this;
|
||||
},
|
||||
onRender: function() {
|
||||
var that = this;
|
||||
this.$('.mailpoet_color').spectrum({
|
||||
clickoutFiresChange: true,
|
||||
showInput: true,
|
||||
@ -202,43 +236,69 @@ define([
|
||||
showPreview: function() {
|
||||
var json = App.toJSON();
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(json.body)) {
|
||||
json.body = JSON.stringify(json.body);
|
||||
}
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'render',
|
||||
action: 'showPreview',
|
||||
data: json,
|
||||
}).done(function(response){
|
||||
console.log('Should open a new window');
|
||||
window.open('data:text/html,' + encodeURIComponent(response.rendered_body), '_blank');
|
||||
MailPoet.Modal.loading(false);
|
||||
|
||||
if (response.result === true) {
|
||||
window.open(response.data.url, '_blank')
|
||||
}
|
||||
MailPoet.Notice.error(response.errors);
|
||||
}).fail(function(error) {
|
||||
console.log('Preview error', json);
|
||||
alert('Something went wrong, check console');
|
||||
MailPoet.Modal.loading(false);
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('newsletterPreviewFailed')
|
||||
);
|
||||
});
|
||||
},
|
||||
sendPreview: function() {
|
||||
// testing sending method
|
||||
console.log('trying to send a preview');
|
||||
// get form data
|
||||
var $emailField = this.$('#mailpoet_preview_to_email');
|
||||
var data = {
|
||||
from_name: this.$('#mailpoet_preview_from_name').val(),
|
||||
from_email: this.$('#mailpoet_preview_from_email').val(),
|
||||
to_email: this.$('#mailpoet_preview_to_email').val(),
|
||||
newsletter: App.newsletterId,
|
||||
subscriber: $emailField.val(),
|
||||
id: App.getNewsletter().get('id'),
|
||||
};
|
||||
|
||||
if (data.subscriber.length <= 0) {
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('newsletterPreviewEmailMissing'),
|
||||
{
|
||||
positionAfter: $emailField,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// send test email
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
// TODO: Migrate logic to new AJAX format
|
||||
Wordpress.previewNewsletter(data).done(function(response) {
|
||||
if(response.success !== undefined && response.success === true) {
|
||||
MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent'));
|
||||
} else if(response.error !== undefined) {
|
||||
if(response.error.length === 0) {
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.unknownErrorOccurred'));
|
||||
} else {
|
||||
$(response.error).each(function(i, error) {
|
||||
MailPoet.Notice.error(error);
|
||||
CommunicationComponent.previewNewsletter(data).done(function(response) {
|
||||
if(response.result !== undefined && response.result === true) {
|
||||
MailPoet.Notice.success(MailPoet.I18n.t('newsletterPreviewSent'), { scroll: true });
|
||||
} else {
|
||||
if (_.isArray(response.errors)) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error, { scroll: true });
|
||||
});
|
||||
} else {
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('newsletterPreviewFailedToSend'),
|
||||
{
|
||||
scroll: true,
|
||||
static: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
MailPoet.Modal.loading(false);
|
||||
|
@ -17,7 +17,7 @@ define([
|
||||
},
|
||||
h1: {
|
||||
fontColor: '#111111',
|
||||
fontFamily: 'Arial Black',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '40px'
|
||||
},
|
||||
h2: {
|
||||
@ -72,7 +72,7 @@ define([
|
||||
|
||||
App.getAvailableStyles = Module.getAvailableStyles;
|
||||
|
||||
var body = JSON.parse(options.newsletter.body);
|
||||
var body = options.newsletter.body;
|
||||
this.setGlobalStyles(body.globalStyles);
|
||||
});
|
||||
|
||||
|
@ -2,39 +2,38 @@ define(
|
||||
[
|
||||
'react',
|
||||
'react-router',
|
||||
'classnames'
|
||||
'classnames',
|
||||
'mailpoet'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
Router,
|
||||
classNames
|
||||
classNames,
|
||||
MailPoet
|
||||
) {
|
||||
var Link = Router.Link;
|
||||
|
||||
var Breadcrumb = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
getInitialState: function() {
|
||||
return {
|
||||
step: null,
|
||||
steps: [
|
||||
{
|
||||
name: 'type',
|
||||
label: 'Select type',
|
||||
label: MailPoet.I18n.t('selectType'),
|
||||
link: '/new'
|
||||
},
|
||||
{
|
||||
name: 'template',
|
||||
label: 'Template'
|
||||
label: MailPoet.I18n.t('template')
|
||||
},
|
||||
{
|
||||
name: 'editor',
|
||||
label: 'Designer'
|
||||
label: MailPoet.I18n.t('designer')
|
||||
},
|
||||
{
|
||||
name: 'send',
|
||||
label: 'Send'
|
||||
label: MailPoet.I18n.t('send')
|
||||
}
|
||||
]
|
||||
};
|
||||
@ -73,4 +72,4 @@ define(
|
||||
|
||||
return Breadcrumb;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -4,100 +4,101 @@ define(
|
||||
'react-router',
|
||||
'listing/listing.jsx',
|
||||
'classnames',
|
||||
'jquery'
|
||||
'jquery',
|
||||
'mailpoet'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
Router,
|
||||
Listing,
|
||||
classNames,
|
||||
jQuery
|
||||
jQuery,
|
||||
MailPoet
|
||||
) {
|
||||
var Link = Router.Link;
|
||||
|
||||
var columns = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: 'Subject',
|
||||
label: MailPoet.I18n.t('subject'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: MailPoet.I18n.t('status')
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: 'Lists'
|
||||
label: MailPoet.I18n.t('lists')
|
||||
},
|
||||
{
|
||||
name: 'statistics',
|
||||
label: MailPoet.I18n.t('statistics')
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
label: 'Created on',
|
||||
label: MailPoet.I18n.t('createdOn'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
label: 'Last modified on',
|
||||
label: MailPoet.I18n.t('lastModifiedOn'),
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
|
||||
var messages = {
|
||||
onTrash: function(response) {
|
||||
var count = ~~response.newsletters;
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 newsletter was moved to the trash.'
|
||||
MailPoet.I18n.t('oneNewsletterTrashed')
|
||||
);
|
||||
} else if(count > 1) {
|
||||
} else {
|
||||
message = (
|
||||
'%$1d newsletters were moved to the trash.'
|
||||
MailPoet.I18n.t('multipleNewslettersTrashed')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete: function(response) {
|
||||
var count = ~~response.newsletters;
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 newsletter was permanently deleted.'
|
||||
MailPoet.I18n.t('oneNewsletterDeleted')
|
||||
);
|
||||
} else if(count > 1) {
|
||||
} else {
|
||||
message = (
|
||||
'%$1d newsletters were permanently deleted.'
|
||||
MailPoet.I18n.t('multipleNewslettersDeleted')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore: function(response) {
|
||||
var count = ~~response.newsletters;
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 newsletter has been restored from the trash.'
|
||||
MailPoet.I18n.t('oneNewsletterRestored')
|
||||
);
|
||||
} else if(count > 1) {
|
||||
} else {
|
||||
message = (
|
||||
'%$1d newsletters have been restored from the trash.'
|
||||
MailPoet.I18n.t('multipleNewslettersRestored')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
var bulk_actions = [
|
||||
{
|
||||
name: 'trash',
|
||||
label: 'Trash',
|
||||
label: MailPoet.I18n.t('trash'),
|
||||
onSuccess: messages.onTrash
|
||||
}
|
||||
];
|
||||
@ -108,28 +109,154 @@ define(
|
||||
link: function(item) {
|
||||
return (
|
||||
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
|
||||
Edit
|
||||
{MailPoet.I18n.t('edit')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash'
|
||||
}
|
||||
];
|
||||
|
||||
var NewsletterList = React.createClass({
|
||||
renderItem: function(newsletter, actions) {
|
||||
pauseSending: function(item) {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'pause',
|
||||
data: item.id
|
||||
}).done(function() {
|
||||
jQuery('#resume_'+item.id).show();
|
||||
jQuery('#pause_'+item.id).hide();
|
||||
});
|
||||
},
|
||||
resumeSending: function(item) {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'resume',
|
||||
data: item.id
|
||||
}).done(function() {
|
||||
jQuery('#pause_'+item.id).show();
|
||||
jQuery('#resume_'+item.id).hide();
|
||||
});
|
||||
},
|
||||
renderStatus: function(item) {
|
||||
if(!item.queue) {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
} else {
|
||||
if (item.queue.status === 'scheduled') {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('scheduledFor')} { item.queue.scheduled_at } </span>
|
||||
)
|
||||
}
|
||||
var progressClasses = classNames(
|
||||
'mailpoet_progress',
|
||||
{ 'mailpoet_progress_complete': item.queue.status === 'completed'}
|
||||
);
|
||||
|
||||
// calculate percentage done
|
||||
var percentage = Math.round(
|
||||
(item.queue.count_processed * 100) / (item.queue.count_total)
|
||||
);
|
||||
|
||||
var label = false;
|
||||
|
||||
if(item.queue.status === 'completed') {
|
||||
label = (
|
||||
<span>
|
||||
{
|
||||
MailPoet.I18n.t('newsletterQueueCompleted')
|
||||
.replace("%$1d", item.queue.count_processed - item.queue.count_failed)
|
||||
.replace("%$2d", item.queue.count_total)
|
||||
}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
label = (
|
||||
<span>
|
||||
{ item.queue.count_processed } / { item.queue.count_total }
|
||||
|
||||
<a
|
||||
id={ 'resume_'+item.id }
|
||||
className="button"
|
||||
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
|
||||
href="javascript:;"
|
||||
onClick={ this.resumeSending.bind(null, item) }
|
||||
>{MailPoet.I18n.t('resume')}</a>
|
||||
<a
|
||||
id={ 'pause_'+item.id }
|
||||
className="button mailpoet_pause"
|
||||
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
|
||||
href="javascript:;"
|
||||
onClick={ this.pauseSending.bind(null, item) }
|
||||
>{MailPoet.I18n.t('pause')}</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={ progressClasses }>
|
||||
<span
|
||||
className="mailpoet_progress_bar"
|
||||
style={ { width: percentage + "%"} }
|
||||
></span>
|
||||
<span className="mailpoet_progress_label">
|
||||
{ percentage + "%" }
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ textAlign:'center' }}>
|
||||
{ label }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
renderStatistics: function(item) {
|
||||
if(!item.statistics || !item.queue || item.queue.count_processed == 0 || item.queue.status === 'scheduled') {
|
||||
return (
|
||||
<span>
|
||||
{MailPoet.I18n.t('notSentYet')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
var percentage_clicked = Math.round(
|
||||
(item.statistics.clicked * 100) / (item.queue.count_processed)
|
||||
);
|
||||
var percentage_opened = Math.round(
|
||||
(item.statistics.opened * 100) / (item.queue.count_processed)
|
||||
);
|
||||
var percentage_unsubscribed = Math.round(
|
||||
(item.statistics.unsubscribed * 100) / (item.queue.count_processed)
|
||||
);
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||
</span>
|
||||
);
|
||||
},
|
||||
renderItem: function(newsletter, actions) {
|
||||
var rowClasses = classNames(
|
||||
'manage-column',
|
||||
'column-primary',
|
||||
'has-row-actions'
|
||||
);
|
||||
|
||||
var segments = mailpoet_segments.filter(function(segment) {
|
||||
return (jQuery.inArray(segment.id, newsletter.segments) !== -1);
|
||||
}).map(function(segment) {
|
||||
return segment.name;
|
||||
var segments = newsletter.segments.map(function(segment) {
|
||||
return segment.name
|
||||
}).join(', ');
|
||||
|
||||
var statistics_column =
|
||||
(!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) ?
|
||||
false :
|
||||
<td className="column {statistics_class}" data-colname="Statistics">
|
||||
{ this.renderStatistics(newsletter) }
|
||||
</td>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<td className={ rowClasses }>
|
||||
@ -138,23 +265,30 @@ define(
|
||||
</strong>
|
||||
{ actions }
|
||||
</td>
|
||||
<td className="column" data-colname="Status">
|
||||
{ this.renderStatus(newsletter) }
|
||||
</td>
|
||||
<td className="column" data-colname="Lists">
|
||||
{ segments }
|
||||
</td>
|
||||
{ statistics_column }
|
||||
<td className="column-date" data-colname="Subscribed on">
|
||||
<abbr>{ newsletter.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Last modified on">
|
||||
<abbr>{ newsletter.updated_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
if (!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) {
|
||||
columns = _.without(columns, _.findWhere(columns, {name: 'statistics'}));
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h2 className="title">
|
||||
Newsletters <Link className="add-new-h2" to="/new">New</Link>
|
||||
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
|
||||
</h2>
|
||||
|
||||
<Listing
|
||||
@ -164,7 +298,8 @@ define(
|
||||
columns={columns}
|
||||
bulk_actions={ bulk_actions }
|
||||
item_actions={ item_actions }
|
||||
messages={ messages } />
|
||||
messages={ messages }
|
||||
auto_refresh={ true } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -172,4 +307,4 @@ define(
|
||||
|
||||
return NewsletterList;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -1,16 +1,16 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Router, Route, IndexRoute, Link } from 'react-router'
|
||||
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
|
||||
import { createHashHistory } from 'history'
|
||||
import NewsletterList from 'newsletters/list.jsx'
|
||||
import NewsletterTypes from 'newsletters/types.jsx'
|
||||
import NewsletterTemplates from 'newsletters/templates.jsx'
|
||||
import NewsletterSend from 'newsletters/send.jsx'
|
||||
import NewsletterStandard from 'newsletters/types/standard.jsx'
|
||||
import NewsletterWelcome from 'newsletters/types/welcome.jsx'
|
||||
import NewsletterNotification from 'newsletters/types/notification.jsx'
|
||||
import createHashHistory from 'history/lib/createHashHistory'
|
||||
import NewsletterWelcome from 'newsletters/types/welcome/welcome.jsx'
|
||||
import NewsletterNotification from 'newsletters/types/notification/notification.jsx'
|
||||
|
||||
let history = createHashHistory({ queryKey: false })
|
||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||
|
||||
const App = React.createClass({
|
||||
render() {
|
||||
@ -18,7 +18,7 @@ const App = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
let container = document.getElementById('newsletters_container');
|
||||
const container = document.getElementById('newsletters_container');
|
||||
|
||||
if(container) {
|
||||
ReactDOM.render((
|
||||
|
@ -2,151 +2,194 @@ define(
|
||||
[
|
||||
'react',
|
||||
'react-router',
|
||||
'underscore',
|
||||
'mailpoet',
|
||||
'form/form.jsx',
|
||||
'form/fields/selection.jsx',
|
||||
'newsletters/send/standard.jsx',
|
||||
'newsletters/send/notification.jsx',
|
||||
'newsletters/send/welcome.jsx',
|
||||
'newsletters/breadcrumb.jsx'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
Router,
|
||||
_,
|
||||
MailPoet,
|
||||
Form,
|
||||
Selection,
|
||||
StandardNewsletterFields,
|
||||
NotificationNewsletterFields,
|
||||
WelcomeNewsletterFields,
|
||||
Breadcrumb
|
||||
) {
|
||||
|
||||
var settings = window.mailpoet_settings || {};
|
||||
|
||||
var fields = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: 'Subject line',
|
||||
tip: "Be creative! It's the first thing your subscribers see."+
|
||||
"Tempt them to open your email.",
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: 'Lists',
|
||||
tip: "The subscriber list that will be used for this campaign.",
|
||||
type: 'selection',
|
||||
placeholder: "Select a list",
|
||||
id: "mailpoet_segments",
|
||||
endpoint: "segments",
|
||||
multiple: true
|
||||
},
|
||||
{
|
||||
name: 'sender',
|
||||
label: 'Sender',
|
||||
tip: "Name & email of yourself or your company.",
|
||||
fields: [
|
||||
{
|
||||
name: 'from_name',
|
||||
type: 'text',
|
||||
placeholder: 'John Doe',
|
||||
defaultValue: settings.from_name
|
||||
},
|
||||
{
|
||||
name: 'from_email',
|
||||
type: 'text',
|
||||
placeholder: 'john.doe@email.com',
|
||||
defaultValue: settings.from_address
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'reply-to',
|
||||
label: 'Reply-to',
|
||||
tip: 'When the subscribers hit "reply" this is who will receive their '+
|
||||
'email.',
|
||||
inline: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'reply_to_name',
|
||||
type: 'text',
|
||||
placeholder: 'John Doe'
|
||||
},
|
||||
{
|
||||
name: 'reply_to_email',
|
||||
type: 'text',
|
||||
placeholder: 'john.doe@email.com'
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var messages = {
|
||||
updated: function() {
|
||||
MailPoet.Notice.success('The newsletter has been updated!');
|
||||
}
|
||||
};
|
||||
|
||||
var NewsletterSend = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
handleSend: function() {
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
fields: [],
|
||||
item: {},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
getFieldsByNewsletter: function(newsletter) {
|
||||
switch(newsletter.type) {
|
||||
case 'notification': return NotificationNewsletterFields;
|
||||
case 'welcome': return WelcomeNewsletterFields;
|
||||
default: return StandardNewsletterFields;
|
||||
}
|
||||
},
|
||||
isAutomatedNewsletter: function() {
|
||||
return this.state.item.type !== 'standard';
|
||||
},
|
||||
isValid: function() {
|
||||
return jQuery('#mailpoet_newsletter').parsley().isValid();
|
||||
},
|
||||
componentDidMount: function() {
|
||||
if(this.isMounted()) {
|
||||
this.loadItem(this.props.params.id);
|
||||
}
|
||||
jQuery('#mailpoet_newsletter').parsley();
|
||||
},
|
||||
componentWillReceiveProps: function(props) {
|
||||
this.loadItem(props.params.id);
|
||||
},
|
||||
loadItem: function(id) {
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'send',
|
||||
data: {
|
||||
id: this.props.params.id,
|
||||
newsletter: jQuery('#mailpoet_newsletter').serializeObject(),
|
||||
segments: jQuery('#mailpoet_segments').val()
|
||||
action: 'get',
|
||||
data: id
|
||||
}).done((response) => {
|
||||
if(response === false) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
item: {},
|
||||
}, function() {
|
||||
this.context.router.push('/new');
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
item: response,
|
||||
fields: this.getFieldsByNewsletter(response),
|
||||
});
|
||||
}
|
||||
}).done(function(response) {
|
||||
if(response === true) {
|
||||
this.history.pushState(null, '/');
|
||||
});
|
||||
},
|
||||
handleSend: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if(!this.isValid()) {
|
||||
jQuery('#mailpoet_newsletter').parsley().validate();
|
||||
} else {
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: this.state.item,
|
||||
}).then((response) => {
|
||||
if (response.result === true) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'add',
|
||||
data: _.extend({}, this.state.item, {
|
||||
newsletter_id: this.props.params.id,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
}).done((response) => {
|
||||
this.setState({ loading: false });
|
||||
if(response.result === true) {
|
||||
this.context.router.push('/');
|
||||
MailPoet.Notice.success(response.data.message);
|
||||
} else {
|
||||
if(response.errors) {
|
||||
MailPoet.Notice.error(response.errors);
|
||||
} else {
|
||||
MailPoet.Notice.error(
|
||||
MailPoet.I18n.t('newsletterSendingError').replace("%$1s", '?page=mailpoet-settings')
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
},
|
||||
handleSave: function(e) {
|
||||
e.preventDefault();
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: this.state.item,
|
||||
}).done((response) => {
|
||||
this.setState({ loading: false });
|
||||
|
||||
if(response.result === true) {
|
||||
this.context.router.push('/');
|
||||
MailPoet.Notice.success(
|
||||
'The newsletter has been sent!'
|
||||
MailPoet.I18n.t('newsletterUpdated')
|
||||
);
|
||||
} else {
|
||||
if(response.errors) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.join("<br />")
|
||||
);
|
||||
} else {
|
||||
MailPoet.Notice.error(
|
||||
'An error occurred while trying to send. '+
|
||||
'<a href="?page=mailpoet-settings">Check your settings.</a>'
|
||||
);
|
||||
MailPoet.Notice.error(response.errors);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
handleFormChange: function(e) {
|
||||
var item = this.state.item,
|
||||
field = e.target.name;
|
||||
|
||||
item[field] = e.target.value;
|
||||
|
||||
this.setState({
|
||||
item: item
|
||||
});
|
||||
return true;
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Final step: last details</h1>
|
||||
<h1>{MailPoet.I18n.t('finalNewsletterStep')}</h1>
|
||||
|
||||
<Breadcrumb step="send" />
|
||||
|
||||
<Form
|
||||
id="mailpoet_newsletter"
|
||||
endpoint="newsletters"
|
||||
fields={ fields }
|
||||
params={ this.props.params }
|
||||
messages={ messages }>
|
||||
|
||||
fields={ this.state.fields }
|
||||
item={ this.state.item }
|
||||
loading={ this.state.loading }
|
||||
onChange={this.handleFormChange}
|
||||
onSubmit={this.handleSave}
|
||||
>
|
||||
<p className="submit">
|
||||
<input
|
||||
className="button button-primary"
|
||||
type="button"
|
||||
onClick={ this.handleSend }
|
||||
value="Send" />
|
||||
value={
|
||||
this.isAutomatedNewsletter()
|
||||
? MailPoet.I18n.t('activate')
|
||||
: MailPoet.I18n.t('send')} />
|
||||
|
||||
<input
|
||||
className="button button-secondary"
|
||||
type="submit"
|
||||
value="Save as draft and close" />
|
||||
or simply
|
||||
value={MailPoet.I18n.t('saveDraftAndClose')} />
|
||||
{MailPoet.I18n.t('orSimply')}
|
||||
<a
|
||||
href={
|
||||
'?page=mailpoet-newsletter-editor&id='+this.props.params.id
|
||||
}>
|
||||
go back to design
|
||||
{MailPoet.I18n.t('goBackToDesign')}
|
||||
</a>.
|
||||
</p>
|
||||
</Form>
|
||||
@ -157,4 +200,4 @@ define(
|
||||
|
||||
return NewsletterSend;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
100
assets/js/src/newsletters/send/notification.jsx
Normal file
@ -0,0 +1,100 @@
|
||||
define(
|
||||
[
|
||||
'mailpoet',
|
||||
'newsletters/types/notification/scheduling.jsx'
|
||||
],
|
||||
function(
|
||||
MailPoet,
|
||||
Scheduling
|
||||
) {
|
||||
|
||||
var settings = window.mailpoet_settings || {};
|
||||
|
||||
var fields = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: MailPoet.I18n.t('subjectLine'),
|
||||
tip: MailPoet.I18n.t('postNotificationSubjectLineTip'),
|
||||
type: 'text',
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'options',
|
||||
label: MailPoet.I18n.t('selectPeriodicity'),
|
||||
type: 'reactComponent',
|
||||
component: Scheduling,
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: MailPoet.I18n.t('segments'),
|
||||
tip: MailPoet.I18n.t('segmentsTip'),
|
||||
type: 'selection',
|
||||
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
|
||||
id: "mailpoet_segments",
|
||||
endpoint: "segments",
|
||||
multiple: true,
|
||||
filter: function(segment) {
|
||||
return !!(!segment.deleted_at);
|
||||
},
|
||||
getLabel: function(segment) {
|
||||
var name = segment.name;
|
||||
if (segment.subscribers > 0) {
|
||||
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
|
||||
}
|
||||
return name;
|
||||
},
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'sender',
|
||||
label: MailPoet.I18n.t('sender'),
|
||||
tip: MailPoet.I18n.t('senderTip'),
|
||||
fields: [
|
||||
{
|
||||
name: 'sender_name',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
|
||||
validation: {
|
||||
'data-parsley-required': true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'sender_address',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-type': 'email'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'reply-to',
|
||||
label: MailPoet.I18n.t('replyTo'),
|
||||
tip: MailPoet.I18n.t('replyToTip'),
|
||||
inline: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'reply_to_name',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
|
||||
},
|
||||
{
|
||||
name: 'reply_to_address',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return fields;
|
||||
}
|
||||
);
|
92
assets/js/src/newsletters/send/standard.jsx
Normal file
@ -0,0 +1,92 @@
|
||||
define(
|
||||
[
|
||||
'mailpoet'
|
||||
],
|
||||
function(
|
||||
MailPoet
|
||||
) {
|
||||
|
||||
var settings = window.mailpoet_settings || {};
|
||||
|
||||
var fields = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: MailPoet.I18n.t('subjectLine'),
|
||||
tip: MailPoet.I18n.t('subjectLineTip'),
|
||||
type: 'text',
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: MailPoet.I18n.t('segments'),
|
||||
tip: MailPoet.I18n.t('segmentsTip'),
|
||||
type: 'selection',
|
||||
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
|
||||
id: "mailpoet_segments",
|
||||
endpoint: "segments",
|
||||
multiple: true,
|
||||
filter: function(segment) {
|
||||
return !!(!segment.deleted_at);
|
||||
},
|
||||
getLabel: function(segment) {
|
||||
var name = segment.name;
|
||||
if (segment.subscribers > 0) {
|
||||
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
|
||||
}
|
||||
return name;
|
||||
},
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'sender',
|
||||
label: MailPoet.I18n.t('sender'),
|
||||
tip: MailPoet.I18n.t('senderTip'),
|
||||
fields: [
|
||||
{
|
||||
name: 'sender_name',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
|
||||
validation: {
|
||||
'data-parsley-required': true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'sender_address',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-type': 'email'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'reply-to',
|
||||
label: MailPoet.I18n.t('replyTo'),
|
||||
tip: MailPoet.I18n.t('replyToTip'),
|
||||
inline: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'reply_to_name',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
|
||||
},
|
||||
{
|
||||
name: 'reply_to_address',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return fields;
|
||||
}
|
||||
);
|
77
assets/js/src/newsletters/send/welcome.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
define(
|
||||
[
|
||||
'mailpoet',
|
||||
'newsletters/types/welcome/scheduling.jsx'
|
||||
],
|
||||
function(
|
||||
MailPoet,
|
||||
Scheduling
|
||||
) {
|
||||
|
||||
var settings = window.mailpoet_settings || {};
|
||||
|
||||
var fields = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: MailPoet.I18n.t('subjectLine'),
|
||||
tip: MailPoet.I18n.t('subjectLineTip'),
|
||||
type: 'text',
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'options',
|
||||
label: MailPoet.I18n.t('sendWelcomeEmailWhen'),
|
||||
type: 'reactComponent',
|
||||
component: Scheduling,
|
||||
},
|
||||
{
|
||||
name: 'sender',
|
||||
label: MailPoet.I18n.t('sender'),
|
||||
tip: MailPoet.I18n.t('senderTip'),
|
||||
fields: [
|
||||
{
|
||||
name: 'sender_name',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
|
||||
validation: {
|
||||
'data-parsley-required': true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'sender_address',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-type': 'email'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'reply-to',
|
||||
label: MailPoet.I18n.t('replyTo'),
|
||||
tip: MailPoet.I18n.t('replyToTip'),
|
||||
inline: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'reply_to_name',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
|
||||
},
|
||||
{
|
||||
name: 'reply_to_address',
|
||||
type: 'text',
|
||||
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return fields;
|
||||
}
|
||||
);
|
||||
|
@ -18,12 +18,21 @@ define(
|
||||
|
||||
var ImportTemplate = React.createClass({
|
||||
saveTemplate: function(template) {
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(template.body)) {
|
||||
template.body = JSON.stringify(template.body);
|
||||
}
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'save',
|
||||
data: template
|
||||
}).done(function(response) {
|
||||
if(response === true) {
|
||||
MailPoet.Modal.loading(false);
|
||||
if(response.result === true) {
|
||||
this.props.onImport(template);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
@ -45,7 +54,7 @@ define(
|
||||
try {
|
||||
saveTemplate(JSON.parse(e.target.result));
|
||||
} catch (err) {
|
||||
MailPoet.Notice.error('This template file appears to be malformed. Please try another one.');
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('templateFileMalformedError'));
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
@ -54,15 +63,15 @@ define(
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Import a template</h2>
|
||||
<h2>{MailPoet.I18n.t('importTemplateTitle')}</h2>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<input type="file" placeholder="Select a .json file to upload" ref="templateFile" />
|
||||
<input type="file" placeholder={MailPoet.I18n.t('selectJsonFileToUpload')} ref="templateFile" />
|
||||
|
||||
<p className="submit">
|
||||
<input
|
||||
className="button button-primary"
|
||||
type="submit"
|
||||
value="Upload" />
|
||||
value={MailPoet.I18n.t('upload')} />
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
@ -71,9 +80,6 @@ define(
|
||||
});
|
||||
|
||||
var NewsletterTemplates = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
getInitialState: function() {
|
||||
return {
|
||||
loading: false,
|
||||
@ -86,20 +92,23 @@ define(
|
||||
getTemplates: function() {
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'getAll',
|
||||
}).done(function(response) {
|
||||
MailPoet.Modal.loading(false);
|
||||
if(this.isMounted()) {
|
||||
|
||||
if(response.length === 0) {
|
||||
response = [
|
||||
{
|
||||
name:
|
||||
"MailPoet's Guide",
|
||||
MailPoet.I18n.t('mailpoetGuideTemplateTitle'),
|
||||
description:
|
||||
"This is the standard template that comes with MailPoet.",
|
||||
readonly: true
|
||||
MailPoet.I18n.t('mailpoetGuideTemplateDescription'),
|
||||
readonly: "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -111,19 +120,26 @@ define(
|
||||
}.bind(this));
|
||||
},
|
||||
handleSelectTemplate: function(template) {
|
||||
var body = template.body;
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(body)) {
|
||||
body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: {
|
||||
id: this.props.params.id,
|
||||
body: template.body
|
||||
body: body
|
||||
}
|
||||
}).done(function(response) {
|
||||
if(response === true) {
|
||||
if(response.result === true) {
|
||||
// TODO: Move this URL elsewhere
|
||||
window.location = 'admin.php?page=mailpoet-newsletter-editor&id=' + this.props.params.id;
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
@ -136,7 +152,9 @@ define(
|
||||
this.setState({ loading: true });
|
||||
if(
|
||||
window.confirm(
|
||||
'You are about to delete the template named "'+ template.name +'"'
|
||||
(
|
||||
MailPoet.I18n.t('confirmTemplateDeletion')
|
||||
).replace("%$1s", template.name)
|
||||
)
|
||||
) {
|
||||
MailPoet.Ajax.post({
|
||||
@ -150,6 +168,13 @@ define(
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
},
|
||||
handleShowTemplate: function(template) {
|
||||
MailPoet.Modal.popup({
|
||||
title: template.name,
|
||||
template: '<div class="mailpoet_boxes_preview" style="background-color: {{ body.globalStyles.body.backgroundColor }}"><img src="{{ thumbnail }}" /></div>',
|
||||
data: template,
|
||||
});
|
||||
},
|
||||
handleTemplateImport: function() {
|
||||
this.getTemplates();
|
||||
},
|
||||
@ -161,14 +186,25 @@ define(
|
||||
href="javascript:;"
|
||||
onClick={ this.handleDeleteTemplate.bind(null, template) }
|
||||
>
|
||||
Delete
|
||||
{MailPoet.I18n.t('delete')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
), thumbnail = '';
|
||||
|
||||
if (typeof template.thumbnail === 'string'
|
||||
&& template.thumbnail.length > 0) {
|
||||
thumbnail = (
|
||||
<a href="javascript:;" onClick={this.handleShowTemplate.bind(null, template)}>
|
||||
<img src={ template.thumbnail } />
|
||||
<div className="mailpoet_overlay"></div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={ 'template-'+index }>
|
||||
<div className="mailpoet_thumbnail">
|
||||
{ thumbnail }
|
||||
</div>
|
||||
|
||||
<div className="mailpoet_description">
|
||||
@ -181,7 +217,7 @@ define(
|
||||
className="button button-primary"
|
||||
onClick={ this.handleSelectTemplate.bind(null, template) }
|
||||
>
|
||||
Select
|
||||
{MailPoet.I18n.t('select')}
|
||||
</a>
|
||||
|
||||
<a
|
||||
@ -189,10 +225,10 @@ define(
|
||||
className="button button-secondary"
|
||||
onClick={ this.handlePreviewTemplate.bind(null, template) }
|
||||
>
|
||||
Preview
|
||||
{MailPoet.I18n.t('preview')}
|
||||
</a>
|
||||
</div>
|
||||
{ (template.readonly) ? false : deleteLink }
|
||||
{ (template.readonly === "1") ? false : deleteLink }
|
||||
</li>
|
||||
);
|
||||
}.bind(this));
|
||||
@ -205,7 +241,7 @@ define(
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Select a template</h1>
|
||||
<h1>{MailPoet.I18n.t('selectTemplateTitle')}</h1>
|
||||
|
||||
<Breadcrumb step="template" />
|
||||
|
||||
|
@ -12,12 +12,12 @@ define(
|
||||
Breadcrumb
|
||||
) {
|
||||
var NewsletterTypes = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
setupNewsletter: function(type) {
|
||||
if(type !== undefined) {
|
||||
this.history.pushState(null, `/new/${type}`);
|
||||
this.context.router.push(`/new/${type}`);
|
||||
}
|
||||
},
|
||||
createNewsletter: function(type) {
|
||||
@ -26,21 +26,24 @@ define(
|
||||
action: 'create',
|
||||
data: {
|
||||
type: type,
|
||||
subject: MailPoet.I18n.t('draftNewsletterTitle'),
|
||||
}
|
||||
}).done(function(response) {
|
||||
if(response.id !== undefined) {
|
||||
this.history.pushState(null, `/template/${response.id}`);
|
||||
if(response.result && response.newsletter.id) {
|
||||
this.context.router.push(`/template/${response.newsletter.id}`);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
if(response.errors.length > 0) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Pick a type of campaign</h1>
|
||||
<h1>{MailPoet.I18n.t('pickCampaignType')}</h1>
|
||||
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
@ -49,10 +52,9 @@ define(
|
||||
<div className="mailpoet_thumbnail"></div>
|
||||
|
||||
<div className="mailpoet_description">
|
||||
<h3>Newsletter</h3>
|
||||
<h3>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h3>
|
||||
<p>
|
||||
Send a newsletter with images, buttons, dividers,
|
||||
and social bookmarks. Or a simple email.
|
||||
{MailPoet.I18n.t('regularNewsletterTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -61,7 +63,7 @@ define(
|
||||
className="button button-primary"
|
||||
onClick={ this.createNewsletter.bind(null, 'standard') }
|
||||
>
|
||||
Create
|
||||
{MailPoet.I18n.t('create')}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
@ -70,9 +72,9 @@ define(
|
||||
<div className="mailpoet_thumbnail"></div>
|
||||
|
||||
<div className="mailpoet_description">
|
||||
<h3>Welcome email</h3>
|
||||
<h3>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h3>
|
||||
<p>
|
||||
Send an email for new users.
|
||||
{MailPoet.I18n.t('welcomeNewsletterTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -81,7 +83,7 @@ define(
|
||||
className="button button-primary"
|
||||
onClick={ this.setupNewsletter.bind(null, 'welcome') }
|
||||
>
|
||||
Set up
|
||||
{MailPoet.I18n.t('setUp')}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
@ -90,9 +92,9 @@ define(
|
||||
<div className="mailpoet_thumbnail"></div>
|
||||
|
||||
<div className="mailpoet_description">
|
||||
<h3>Post notifications</h3>
|
||||
<h3>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h3>
|
||||
<p>
|
||||
Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.
|
||||
{MailPoet.I18n.t('postNotificationsNewsletterTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -101,7 +103,7 @@ define(
|
||||
className="button button-primary"
|
||||
onClick={ this.setupNewsletter.bind(null, 'notification') }
|
||||
>
|
||||
Set up
|
||||
{MailPoet.I18n.t('setUp')}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -1,225 +0,0 @@
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'react',
|
||||
'react-router',
|
||||
'mailpoet',
|
||||
'form/form.jsx',
|
||||
'form/fields/select.jsx',
|
||||
'form/fields/selection.jsx',
|
||||
'form/fields/text.jsx',
|
||||
'newsletters/breadcrumb.jsx'
|
||||
],
|
||||
function(
|
||||
_,
|
||||
React,
|
||||
Router,
|
||||
MailPoet,
|
||||
Form,
|
||||
Select,
|
||||
Selection,
|
||||
Text,
|
||||
Breadcrumb
|
||||
) {
|
||||
|
||||
var intervalField = {
|
||||
name: 'interval',
|
||||
values: {
|
||||
'daily': 'Once a day at...',
|
||||
'weekly': 'Weekly on...',
|
||||
'monthly': 'Monthly on the...',
|
||||
'nthWeekDay': 'Monthly every...',
|
||||
'immediately': 'Immediately.',
|
||||
},
|
||||
};
|
||||
|
||||
var SECONDS_IN_DAY = 86400;
|
||||
var TIME_STEP_SECONDS = 3600; // Default: 3600
|
||||
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
|
||||
var timeOfDayValues = _.object(_.map(
|
||||
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
|
||||
function(seconds) {
|
||||
var date = new Date(null);
|
||||
date.setSeconds(seconds);
|
||||
var timeLabel = date.toISOString().substr(11, 5);
|
||||
return [seconds, timeLabel];
|
||||
}
|
||||
));
|
||||
var timeOfDayField = {
|
||||
name: 'timeOfDay',
|
||||
values: timeOfDayValues,
|
||||
};
|
||||
|
||||
var weekDayField = {
|
||||
name: 'weekDay',
|
||||
values: {
|
||||
0: 'Monday',
|
||||
1: 'Tuesday',
|
||||
2: 'Wednesday',
|
||||
3: 'Thursday',
|
||||
4: 'Friday',
|
||||
5: 'Saturday',
|
||||
6: 'Sunday',
|
||||
},
|
||||
};
|
||||
|
||||
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
|
||||
var monthDayField = {
|
||||
name: 'monthDay',
|
||||
values: _.object(_.map(
|
||||
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
|
||||
function(day) {
|
||||
var suffixes = {
|
||||
0: 'st',
|
||||
1: 'nd',
|
||||
2: 'rd'
|
||||
};
|
||||
var suffix = suffixes[day] || 'th';
|
||||
|
||||
return [day, (day + 1).toString() + suffix];
|
||||
},
|
||||
)),
|
||||
};
|
||||
|
||||
var nthWeekDayField = {
|
||||
name: 'nthWeekDay',
|
||||
values: {
|
||||
'0': '1st',
|
||||
'1': '2nd',
|
||||
'2': '3rd',
|
||||
'3': 'last',
|
||||
},
|
||||
};
|
||||
|
||||
var NewsletterWelcome = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
getInitialState: function() {
|
||||
return {
|
||||
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
|
||||
timeOfDay: 0,
|
||||
weekDay: 0,
|
||||
monthDay: 0,
|
||||
nthWeekDay: 0,
|
||||
};
|
||||
},
|
||||
handleIntervalChange: function(event) {
|
||||
this.setState({
|
||||
intervalType: event.target.value,
|
||||
});
|
||||
},
|
||||
handleTimeOfDayChange: function(event) {
|
||||
this.setState({
|
||||
timeOfDay: event.target.value,
|
||||
});
|
||||
},
|
||||
handleWeekDayChange: function(event) {
|
||||
this.setState({
|
||||
weekDay: event.target.value,
|
||||
});
|
||||
},
|
||||
handleMonthDayChange: function(event) {
|
||||
this.setState({
|
||||
monthDay: event.target.value,
|
||||
});
|
||||
},
|
||||
handleNthWeekDayChange: function(event) {
|
||||
this.setState({
|
||||
nthWeekDay: event.target.value,
|
||||
});
|
||||
},
|
||||
handleNext: function() {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: {
|
||||
type: 'notification',
|
||||
options: this.state,
|
||||
},
|
||||
}).done(function(response) {
|
||||
if(response.id !== undefined) {
|
||||
this.showTemplateSelection(response.id);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
showTemplateSelection: function(newsletterId) {
|
||||
this.history.pushState(null, `/template/${newsletterId}`);
|
||||
},
|
||||
render: function() {
|
||||
var timeOfDaySelection,
|
||||
weekDaySelection,
|
||||
monthDaySelection,
|
||||
nthWeekDaySelection;
|
||||
|
||||
if (this.state.intervalType !== 'immediately') {
|
||||
timeOfDaySelection = (
|
||||
<Select
|
||||
field={timeOfDayField}
|
||||
item={this.state}
|
||||
onValueChange={this.handleTimeOfDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.intervalType === 'weekly'
|
||||
|| this.state.intervalType === 'nthWeekDay') {
|
||||
weekDaySelection = (
|
||||
<Select
|
||||
field={weekDayField}
|
||||
item={this.state}
|
||||
onValueChange={this.handleWeekDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.intervalType === 'monthly') {
|
||||
monthDaySelection = (
|
||||
<Select
|
||||
field={monthDayField}
|
||||
item={this.state}
|
||||
onValueChange={this.handleMonthDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.intervalType === 'nthWeekDay') {
|
||||
nthWeekDaySelection = (
|
||||
<Select
|
||||
field={nthWeekDayField}
|
||||
item={this.state}
|
||||
onValueChange={this.handleNthWeekDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Post notifications</h1>
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
<Select
|
||||
field={intervalField}
|
||||
item={this.state}
|
||||
onValueChange={this.handleIntervalChange} />
|
||||
|
||||
{nthWeekDaySelection}
|
||||
{monthDaySelection}
|
||||
{weekDaySelection}
|
||||
{timeOfDaySelection}
|
||||
|
||||
<p className="submit">
|
||||
<input
|
||||
className="button button-primary"
|
||||
type="button"
|
||||
onClick={ this.handleNext }
|
||||
value="Next" />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return NewsletterWelcome;
|
||||
}
|
||||
);
|
@ -0,0 +1,96 @@
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'react',
|
||||
'react-router',
|
||||
'mailpoet',
|
||||
'newsletters/types/notification/scheduling.jsx',
|
||||
'newsletters/breadcrumb.jsx'
|
||||
],
|
||||
function(
|
||||
_,
|
||||
React,
|
||||
Router,
|
||||
MailPoet,
|
||||
Scheduling,
|
||||
Breadcrumb
|
||||
) {
|
||||
|
||||
var field = {
|
||||
name: 'options',
|
||||
label: 'Periodicity',
|
||||
type: 'reactComponent',
|
||||
component: Scheduling,
|
||||
};
|
||||
|
||||
var NewsletterNotification = React.createClass({
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
options: {
|
||||
intervalType: 'daily',
|
||||
timeOfDay: 0,
|
||||
weekDay: 1,
|
||||
monthDay: 0,
|
||||
nthWeekDay: 1,
|
||||
}
|
||||
};
|
||||
},
|
||||
handleValueChange: function(event) {
|
||||
var state = this.state;
|
||||
state[event.target.name] = event.target.value;
|
||||
this.setState(state);
|
||||
},
|
||||
handleNext: function() {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: _.extend({}, this.state, {
|
||||
type: 'notification',
|
||||
subject: MailPoet.I18n.t('draftNewsletterTitle'),
|
||||
}),
|
||||
}).done(function(response) {
|
||||
if(response.result && response.newsletter.id) {
|
||||
this.showTemplateSelection(response.newsletter.id);
|
||||
} else {
|
||||
if(response.errors.length > 0) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
showTemplateSelection: function(newsletterId) {
|
||||
this.context.router.push(`/template/${newsletterId}`);
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
<h3>{MailPoet.I18n.t('selectPeriodicity')}</h3>
|
||||
|
||||
<Scheduling
|
||||
item={this.state}
|
||||
field={field}
|
||||
onValueChange={this.handleValueChange} />
|
||||
|
||||
<p className="submit">
|
||||
<input
|
||||
className="button button-primary"
|
||||
type="button"
|
||||
onClick={ this.handleNext }
|
||||
value={MailPoet.I18n.t('next')} />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return NewsletterNotification;
|
||||
}
|
||||
);
|
200
assets/js/src/newsletters/types/notification/scheduling.jsx
Normal file
@ -0,0 +1,200 @@
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'react',
|
||||
'react-router',
|
||||
'mailpoet',
|
||||
'form/fields/select.jsx'
|
||||
],
|
||||
function(
|
||||
_,
|
||||
React,
|
||||
Router,
|
||||
MailPoet,
|
||||
Select
|
||||
) {
|
||||
|
||||
var intervalField = {
|
||||
name: 'intervalType',
|
||||
values: {
|
||||
'daily': MailPoet.I18n.t('daily'),
|
||||
'weekly': MailPoet.I18n.t('weekly'),
|
||||
'monthly': MailPoet.I18n.t('monthly'),
|
||||
'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
|
||||
'immediately': MailPoet.I18n.t('immediately'),
|
||||
},
|
||||
};
|
||||
|
||||
var SECONDS_IN_DAY = 86400;
|
||||
var TIME_STEP_SECONDS = 3600; // Default: 3600
|
||||
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
|
||||
var timeOfDayValues = _.object(_.map(
|
||||
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
|
||||
function(seconds) {
|
||||
var date = new Date(null);
|
||||
date.setSeconds(seconds);
|
||||
var timeLabel = date.toISOString().substr(11, 5);
|
||||
return [seconds, timeLabel];
|
||||
}
|
||||
));
|
||||
var timeOfDayField = {
|
||||
name: 'timeOfDay',
|
||||
values: timeOfDayValues,
|
||||
};
|
||||
|
||||
var weekDayField = {
|
||||
name: 'weekDay',
|
||||
values: {
|
||||
0: MailPoet.I18n.t('sunday'),
|
||||
1: MailPoet.I18n.t('monday'),
|
||||
2: MailPoet.I18n.t('tuesday'),
|
||||
3: MailPoet.I18n.t('wednesday'),
|
||||
4: MailPoet.I18n.t('thursday'),
|
||||
5: MailPoet.I18n.t('friday'),
|
||||
6: MailPoet.I18n.t('saturday')
|
||||
},
|
||||
};
|
||||
|
||||
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
|
||||
var monthDayField = {
|
||||
name: 'monthDay',
|
||||
values: _.object(_.map(
|
||||
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
|
||||
function(day) {
|
||||
var labels = {
|
||||
0: MailPoet.I18n.t('first'),
|
||||
1: MailPoet.I18n.t('second'),
|
||||
2: MailPoet.I18n.t('third')
|
||||
},
|
||||
label;
|
||||
if (labels[day] !== undefined) {
|
||||
label = labels[day];
|
||||
} else {
|
||||
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
|
||||
}
|
||||
|
||||
return [day, label];
|
||||
},
|
||||
)),
|
||||
};
|
||||
|
||||
var nthWeekDayField = {
|
||||
name: 'nthWeekDay',
|
||||
values: {
|
||||
'1': MailPoet.I18n.t('first'),
|
||||
'2': MailPoet.I18n.t('second'),
|
||||
'3': MailPoet.I18n.t('third'),
|
||||
'L': MailPoet.I18n.t('last'),
|
||||
},
|
||||
};
|
||||
|
||||
var NotificationScheduling = React.createClass({
|
||||
_getCurrentValue: function() {
|
||||
return this.props.item[this.props.field.name] || {};
|
||||
},
|
||||
handleValueChange: function(name, value) {
|
||||
var oldValue = this._getCurrentValue(),
|
||||
newValue = {};
|
||||
newValue[name] = value;
|
||||
|
||||
return this.props.onValueChange({
|
||||
target: {
|
||||
name: this.props.field.name,
|
||||
value: _.extend({}, oldValue, newValue)
|
||||
}
|
||||
});
|
||||
},
|
||||
handleIntervalChange: function(event) {
|
||||
return this.handleValueChange(
|
||||
'intervalType',
|
||||
event.target.value
|
||||
);
|
||||
},
|
||||
handleTimeOfDayChange: function(event) {
|
||||
return this.handleValueChange(
|
||||
'timeOfDay',
|
||||
event.target.value
|
||||
);
|
||||
},
|
||||
handleWeekDayChange: function(event) {
|
||||
return this.handleValueChange(
|
||||
'weekDay',
|
||||
event.target.value
|
||||
);
|
||||
},
|
||||
handleMonthDayChange: function(event) {
|
||||
return this.handleValueChange(
|
||||
'monthDay',
|
||||
event.target.value
|
||||
);
|
||||
},
|
||||
handleNthWeekDayChange: function(event) {
|
||||
return this.handleValueChange(
|
||||
'nthWeekDay',
|
||||
event.target.value
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
var value = this._getCurrentValue(),
|
||||
timeOfDaySelection,
|
||||
weekDaySelection,
|
||||
monthDaySelection,
|
||||
nthWeekDaySelection;
|
||||
|
||||
|
||||
if (value.intervalType !== 'immediately') {
|
||||
timeOfDaySelection = (
|
||||
<Select
|
||||
field={timeOfDayField}
|
||||
item={this._getCurrentValue()}
|
||||
onValueChange={this.handleTimeOfDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
if (value.intervalType === 'weekly'
|
||||
|| value.intervalType === 'nthWeekDay') {
|
||||
weekDaySelection = (
|
||||
<Select
|
||||
field={weekDayField}
|
||||
item={this._getCurrentValue()}
|
||||
onValueChange={this.handleWeekDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
if (value.intervalType === 'monthly') {
|
||||
monthDaySelection = (
|
||||
<Select
|
||||
field={monthDayField}
|
||||
item={this._getCurrentValue()}
|
||||
onValueChange={this.handleMonthDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
if (value.intervalType === 'nthWeekDay') {
|
||||
nthWeekDaySelection = (
|
||||
<Select
|
||||
field={nthWeekDayField}
|
||||
item={this._getCurrentValue()}
|
||||
onValueChange={this.handleNthWeekDayChange} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
field={intervalField}
|
||||
item={this._getCurrentValue()}
|
||||
onValueChange={this.handleIntervalChange} />
|
||||
|
||||
{nthWeekDaySelection}
|
||||
{monthDaySelection}
|
||||
{weekDaySelection}
|
||||
{timeOfDaySelection}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return NotificationScheduling;
|
||||
}
|
||||
);
|