Compare commits

..

842 Commits

Author SHA1 Message Date
8dfaf9ba32 Release 3.1.0 2017-11-07 15:20:00 +02:00
89d0da93d3 Merge pull request #1180 from mailpoet/wp_sync_db_error_fix
Fixes "column cannot be null" error during WP sync [MAILPOET-1191]
2017-11-07 12:23:39 +02:00
a8ccfec5c6 Merge pull request #1178 from mailpoet/superadmin_access
Enable permissions for superadmin users [MAILPOET-1200]
2017-11-07 11:15:33 +03:00
d8609c9e84 Merge pull request #1179 from mailpoet/survey_nov7
Updates weekly survey [MAILPOET-1199]
2017-11-07 10:41:07 +03:00
5461c975d4 Fixes "column cannot be null" error during WP sync 2017-11-06 22:29:59 -05:00
11298bc101 Updates weekly survey 2017-11-06 22:25:35 -05:00
c42cf2f622 Switch to using current_user_can function to check capabilities 2017-11-06 18:09:38 +02:00
c9f1d38baa Merge pull request #1177 from mailpoet/mpapi_create_list
Add a method to create a new list to public API [MAILPOET-1197]
2017-11-02 18:54:06 -04:00
3d78d6bbe9 Add a method to create a new list to public API [MAILPOET-1197] 2017-11-02 22:13:50 +03:00
2b4288f301 Merge pull request #1176 from mailpoet/throttling_test_fix
Fix an off-by-one error in some timezones [MAILPOET-1186]
2017-11-02 14:34:03 +02:00
09a2dd231a Fix an off-by-one error in some timezones [MAILPOET-1186] 2017-11-02 14:56:44 +03:00
3fd4ef9985 Cleanup after WP sync test [MAILPOET-1185] 2017-11-01 15:43:31 +00:00
05979965ba Merge pull request #1174 from mailpoet/es6
ESLint: ES6 rules 2 [MAILPOET-1138]
2017-11-01 11:32:34 -04:00
e625a7602a Merge pull request #1171 from mailpoet/mixpanel
Report more system environment data via MixPanel [MAILPOET-1189]
2017-11-01 11:21:29 -04:00
a0c41ad7ab Merge pull request #1168 from mailpoet/jquery
plugin scripts should be loaded with a dependency on jquery [MAILPOET-1149]
2017-11-01 15:01:38 +02:00
7163747eb9 no-extra-boolean-cast 2017-11-01 10:20:13 +00:00
c30e2b6cf3 no-sequences 2017-10-31 17:16:24 +00:00
e9eae92ba9 no-useless-concat 2017-10-31 17:15:09 +00:00
0fd6fa8879 max-len 2017-10-31 17:12:59 +00:00
52ca6eac18 no-else-return 2017-10-31 17:10:10 +00:00
28776b8558 no-case-declarations 2017-10-31 16:52:00 +00:00
588ad3eab7 class-methods-use-this 2017-10-31 16:46:19 +00:00
d791538086 no-lonely-if 2017-10-31 16:40:08 +00:00
62173e7996 Bump up release version to 3.0.9 2017-10-31 19:18:42 +03:00
0acdcd1ca7 Merge pull request #1170 from mailpoet/jquery_serialize_object
Rename jQuery Serialize Object function to prevent conflicts [MAILPOET-1190]
2017-10-31 15:05:00 +01:00
f39a2c8dda added env data to weekly report 2017-10-31 11:17:34 +00:00
b648852ef5 Rename jQuery Serialize Object function to prevent conflicts [MAILPOET-1190] 2017-10-30 22:49:00 +03:00
6e45892118 trim values on listing search 2017-10-30 16:24:00 +00:00
cd145c51f7 public.js now depends on jquery 2017-10-30 14:25:05 +00:00
2c12d9ee2d changes on welcome and update pages 2017-10-30 11:36:40 +00:00
5fe28623f1 Merge pull request #1164 from mailpoet/acceptance_js_errors
Add JS error checking to acceptance tests [MAILPOET-1179]
2017-10-29 21:31:45 -04:00
3086b3cfc2 Merge pull request #1165 from mailpoet/esltests
ESLint: Test rules 2 [MAILPOET-1135]
2017-10-29 21:30:27 -04:00
5fd29872ff Merge pull request #1166 from mailpoet/remove-tags
Remove tags from ALC display
2017-10-26 14:28:02 +03:00
732c8a314f Merge pull request #1160 from mailpoet/ci-tables-prefix
changing the tables prefix to "mp_" on CircleCI [MAILPOET-1178]
2017-10-26 13:43:08 +03:00
23c650bfa6 Merge pull request #1163 from mailpoet/new-poll
new poll [MAILPOET-1172]
2017-10-26 13:34:16 +03:00
f5ced785e0 Tests: padded-blocks 2017-10-25 14:54:28 +00:00
4dc9004303 Tests: no-shaddow 2017-10-25 14:52:53 +00:00
893c6bd72b Remove tags from ALC display
[MAILPOET-1182]
2017-10-25 15:49:23 +01:00
2ac32484e1 Tests: max-len 2017-10-25 12:16:32 +00:00
b31e8ce5f2 Tests: no-bitwise 2017-10-25 12:10:29 +00:00
4270e4c315 Check for missing CSS files in acceptance tests [MAILPOET-1177] 2017-10-25 14:04:11 +03:00
4b13395b0c Add JS error checking to acceptance tests [MAILPOET-1179] 2017-10-25 13:34:16 +03:00
2e67029ef5 Merge pull request #1162 from mailpoet/new
Capitalize new [MAILPOET-1174]
2017-10-24 17:48:05 +03:00
257517b9a9 new poll 2017-10-24 14:13:07 +00:00
88ee64e15d Capitalize new 2017-10-24 14:03:27 +00:00
c2030e9a86 Fix readme.txt rendering 2017-10-24 14:55:21 +01:00
16111a99fb Release 3.0.8 2017-10-24 13:17:49 +01:00
543b3e5a91 changing tables prefix for acceptance tests 2017-10-24 09:13:43 +00:00
e33b60065e Merge pull request #1159 from mailpoet/ci_js_compile
Compile JS and CSS assets on CI before running acceptance tests [MAILPOET-1166]
2017-10-24 11:55:24 +03:00
3bcfadd2ab Merge pull request #1161 from mailpoet/translations_update
Translations update [MAILPOET-1161]
2017-10-24 11:30:59 +03:00
4729583d8d Merge pull request #1158 from mailpoet/scroll
enabling body scroll when settings are open [MAILPOET-1148]
2017-10-23 21:52:39 -04:00
aa3f457595 Updates welcome page translations that were not included in pot file 2017-10-23 21:45:48 -04:00
3ad490f840 Loads translations before the rest of the plugin 2017-10-23 21:45:48 -04:00
eb27ed65ae Adds missing translations to migration page 2017-10-23 21:45:40 -04:00
d543f62c5b Merge pull request #1157 from mailpoet/poll
new poll [MAILPOET-1171]
2017-10-23 20:11:19 +03:00
4e6f7a05de changing the tables prefix to "mp_" on CircleCI 2017-10-23 16:49:36 +00:00
5d467115ad Compile JS and CSS assets on CI before running acceptance tests [MAILPOET-1166] 2017-10-23 19:02:28 +03:00
6858b266fe enabling body scroll when settings are open 2017-10-23 16:01:28 +00:00
852b1a4c08 new poll 2017-10-23 14:59:29 +00:00
b2324db7b4 Merge pull request #1156 from mailpoet/number_formatting_update
Formats number as per locale's convention [MAILPOET-1125]
2017-10-23 15:19:47 +02:00
e8c85e2a54 Merge pull request #1155 from mailpoet/wp_user_sync_db_query_fix
Fixes MySQL's "column is ambiguous" error in WP user sync [MAILPOET-1177]
2017-10-23 14:15:14 +03:00
a4ac74c84a Remove more ambiguity in WP sync queries [MAILPOET-1177] 2017-10-23 13:58:23 +03:00
4e378484ea Formats number as per locale's convention 2017-10-22 21:56:33 -04:00
c55be70b2c Merge pull request #1154 from mailpoet/php_version_mixpanel
Report PHP version in weekly MixPanel analytics [MAILPOET-1175]
2017-10-20 17:10:14 +02:00
70898790f5 Cleans up code & fixes possible MySQL's "column is ambiguous" error 2017-10-19 21:14:45 -04:00
bfdb535f5b Fixes method name typo 2017-10-19 21:00:15 -04:00
996e6b16e7 Merge pull request #1142 from mailpoet/links
Enabling links on header and footer on preview [MAILPOET-1121]
2017-10-19 20:07:32 +03:00
0217e21753 Restore a default argument value [MAILPOET-1121] 2017-10-19 19:43:39 +03:00
f6f79d42e1 Report PHP version in weekly MixPanel analytics [MAILPOET-1175] 2017-10-19 19:03:55 +03:00
4bbd26b098 Merge pull request #1152 from mailpoet/form_translation_fix
Adds translated "required field" message to subscription form [MAILPOET-1173]
2017-10-19 18:55:02 +03:00
028179af37 Merge pull request #1153 from mailpoet/form_acceptance_tests
Add an acceptance test for the public subscription form widget [MAILPOET-1166]
2017-10-19 17:46:54 +03:00
87283bf838 Merge pull request #1143 from mailpoet/image-url
Force absolute path for images [MAILPOET-1160]
2017-10-19 12:38:45 +03:00
c680badaa2 Fix WP uploads folder permissions [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
df4a936e43 Fix exit code returned by acceptance tests [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
e27946ebcc Fix artifacts storage [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
13e64c012f Restore PHP 5 support [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
f69302be48 Add an acceptance test for the public subscription form widget [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
16edfc16ea Merge pull request #1144 from mailpoet/dynamic-segments
Dynamic segments [PREMIUM-38]
2017-10-18 18:57:48 +03:00
229a9c8102 Fix QA issue
[PREMIUM-38]
2017-10-18 15:11:57 +01:00
490091a7e2 Move table definition to premium plugin
[PREMIUM-38]
2017-10-18 15:09:16 +01:00
5f58e5ca82 Add tests
[PREMIUM-38]
2017-10-18 15:04:44 +01:00
16beda530a Fix post notifications to work with dynamic segments
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
f1b373924f Remove subscribers filtering for now
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
6a73c463cb QA fixes
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
0271675cd0 Use dynamic segments for sending queue
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
b7555aa640 Show only default segments in listings
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
fa9ef6e5bd Add 500 status code
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
1c97b004ca Add blank template for premium plugin
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
f0ab42adf1 Expose packages for premium
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
165d8358d4 Add action after lists menu item
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
6bd6e74bcb Add dynamic filters table
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
b23df9e0a4 Adds translated "required field" message 2017-10-17 21:43:55 -04:00
7b12affb77 Bumps up release version to 3.0.7 and updates changelog 2017-10-17 14:08:16 -04:00
8c8c01aa75 shows links when previewing sent newsletters 2017-10-17 14:14:08 +00:00
0150e699a2 Merge pull request #1151 from mailpoet/oct17_poll_update
Updates weekly poll [MAILPOET-1170]
2017-10-17 17:06:39 +03:00
e6943e2638 Updates weekly poll 2017-10-17 09:34:04 -04:00
6f80dcb1de adding unit tests 2017-10-17 13:09:37 +00:00
60ed294302 no need to normalize src on the frontend 2017-10-17 12:57:39 +00:00
e8017b58f5 removing tooltip message 2017-10-17 12:48:20 +00:00
c79bf7d337 forcing absolute image source when rendering 2017-10-17 12:35:11 +00:00
ac268c1ec9 Merge pull request #1150 from mailpoet/beacon_cron_url_update
Uses CronHelper's method to return cron ping URL in beacon [MAILPOET-1164]
2017-10-17 09:54:12 +03:00
1ef131fa2d Merge pull request #1149 from mailpoet/capabilities_fix
Fixes "Call to a member function add_cap() on null" error [MAILPOET-1169]
2017-10-17 09:46:48 +03:00
1873007550 Improve a unit test for non-existent roles' capabilities [MAILPOET-1169] 2017-10-17 09:35:50 +03:00
fa2ccb51c9 Uses CronHelper's method to return cron ping URL in beacon 2017-10-16 23:46:17 -04:00
8f87d654af Merge pull request #1148 from mailpoet/stats_cta
Add CTA links for detailed stats [MAILPOET-1152]
2017-10-16 23:33:10 -04:00
dee6e9fbad Fixes "Call to a member function add_cap() on null" error 2017-10-16 22:55:11 -04:00
ef90264316 Add CTA links for detailed stats [MAILPOET-1152] 2017-10-13 01:11:06 +03:00
70bf4be723 ESLint: Test rules 1
[MAILPOET-1134]
2017-10-12 15:36:40 +01:00
07ef727654 Merge pull request #1139 from mailpoet/throttling
Add progressive throttling of subscriptions from the same IP address [MAILPOET-1128]
2017-10-12 15:56:40 +02:00
a346e5be29 normalizing image URL on render 2017-10-12 13:55:04 +00:00
5ce1eadde7 Merge pull request #1147 from mailpoet/honeypot-autofill
Subscription form honeypot gets filled by Autofill [MAILPOET-1163]
2017-10-12 16:52:22 +03:00
8a4d5395b1 Add attribute for Chrome
[MAILPOET-1163]
2017-10-12 14:38:45 +01:00
b5feed0f46 Remove WP subscribers with empty emails when syncing [MAILPOET-1158] 2017-10-12 10:31:11 +01:00
11f9579101 Don't synchronize WP users without emails [MAILPOET-1158] 2017-10-12 10:31:11 +01:00
c6000c959a Force absolute path for images 2017-10-11 14:21:16 +00:00
19e67ea2b0 Enabling links on header and footer on preview 2017-10-11 12:45:07 +00:00
0f6619e25d Merge pull request #1140 from mailpoet/svn_publish
Remove a dependency on WP in svn:publish command [MAILPOET-1156]
2017-10-11 13:21:10 +03:00
daf747d3be Throw an exception if plugin version could not be determined [MAILPOET-1156] 2017-10-11 13:07:53 +03:00
7393b1f2cf Remove a dependency on WP in svn:publish command [MAILPOET-1156] 2017-10-11 10:54:28 +03:00
efe861a9ba Merge pull request #1137 from mailpoet/eslint4
Eslint for tests [MAILPOET-1083]
2017-10-11 10:00:53 +03:00
2c358ab179 Add progressive throttling of subscriptions from the same IP address [MAILPOET-1128] 2017-10-10 19:36:20 +03:00
ca157fc91d Release 3.0.6 2017-10-10 16:46:37 +03:00
1fbe5d7bc6 Merge pull request #1138 from mailpoet/parsley
fixing missing parsley method [MAILPOET-1157]
2017-10-10 16:34:18 +03:00
71c031ccf9 fixing missing parsley method 2017-10-10 13:10:49 +00:00
6449b7ccca fixed minor issue 2017-10-10 09:29:22 +00:00
6a956472fe Release 3.0.5 2017-10-10 12:27:16 +03:00
d6af88d667 Tests object-curly-spacing 2017-10-10 09:09:03 +00:00
b9184a202f Tests func-call-spacing 2017-10-10 09:09:03 +00:00
f898746967 Tests keyword-spacing 2017-10-10 09:09:03 +00:00
68165b7b78 Tests space-before-function-paren 2017-10-10 09:09:03 +00:00
bb8591a67b Tests space-before-blocks 2017-10-10 09:07:29 +00:00
bda71ae78e Tests space-unary-ops 2017-10-10 09:07:29 +00:00
abd4f6cac2 Tests no-spaced-func 2017-10-10 09:07:29 +00:00
87e6cc2a4f Tests no-whitespace-before-property 2017-10-10 09:07:29 +00:00
dde598eb64 rebasing on master 2017-10-10 09:07:29 +00:00
0c4407f43a Merge pull request #1126 from mailpoet/eslint3
Eslint rules [MAILPOET-1084]
2017-10-10 11:46:45 +03:00
b6c864e7a1 Change the JSON API error message
[MAILPOET-1103]
2017-10-10 09:45:50 +01:00
3d9dc6465d Merge pull request #1132 from mailpoet/image_alignment
Add image alignment option to newsletter editor [MAILPOET-1124]
2017-10-10 11:37:11 +03:00
9a6fec094a fixing divider and spacer resize 2017-10-10 08:35:26 +00:00
61af224d7d Fixing image resize 2017-10-09 12:12:47 +00:00
9b41641e97 Tests vars-on-top 2017-10-09 11:17:23 +00:00
4e2e9f6f8f rebasing on master 2017-10-09 11:17:23 +00:00
c29dc8b4c7 fixing tests 2017-10-09 11:15:58 +00:00
98a3c6b156 Tests no-unused-vars 2017-10-09 11:15:58 +00:00
69c540288b Tests one-var 2017-10-09 11:15:58 +00:00
651c9f5692 Tests one-var-declaration-per-line 2017-10-09 11:15:58 +00:00
9ad3778cf7 ES6 rules 2017-10-09 11:15:58 +00:00
c90e0e9f64 ES5 no-unused-vars 2017-10-09 11:15:58 +00:00
cb1730c4e2 ES5 no-var 2017-10-09 11:15:58 +00:00
3dd8a973fd ES5 vars-on-top 2017-10-09 11:15:58 +00:00
c3ea088fca ES5 one-var 2017-10-09 11:15:58 +00:00
a11d6d7868 ES5 one-var-declaration-per-line 2017-10-09 11:15:58 +00:00
a596add838 ES5 block-scoped-var 2017-10-09 11:15:58 +00:00
7e7103ddab ES5 block-scoped-var 2017-10-09 11:15:58 +00:00
0064970ed7 Add a test for an alignment setting of an image block in the editor [MAILPOET-1124] 2017-10-09 13:00:39 +03:00
9d93f3ea95 Add a test for image alignment rendering [MAILPOET-1124] 2017-10-09 09:37:31 +03:00
ff2a3cd19e Release 3.0.4 2017-10-05 12:09:28 +00:00
8419d95ea1 Fix spacer and divider resizing [MAILPOET-1131] 2017-10-05 10:48:52 +01:00
4ad317ac7b Release MP3 3.0.3 2017-10-03 21:37:51 +03:00
7cccebbf2c Merge pull request #1135 from mailpoet/wp_sync_collations_fix
Get rid of WP user IDs updating query in favor of an insert-update due to collation problems [MAILPOET-1132]
2017-10-03 21:01:44 +03:00
e4f76ee9eb Get rid of WP user IDs updating query in favor of an insert-update due to collation problems [MAILPOET-1132] 2017-10-03 20:52:35 +03:00
7f52f72c25 Releasing 3.0.2 2017-10-03 14:27:56 +00:00
44afcbbeaf Merge pull request #1134 from mailpoet/new-poll
Add a new poll to update page [MAILPOET-1129]
2017-10-02 19:55:36 +03:00
e816c59539 Add a new poll to update page 2017-10-02 18:47:39 +03:00
c74421a42a Merge pull request #1133 from mailpoet/permission_update_fix
Remove the check for a plugin update permission [MAILPOET-1130]
2017-10-02 14:58:08 +02:00
23eb4633c4 Remove the check for a plugin update permission [MAILPOET-1130] 2017-10-02 15:44:06 +03:00
26241afb86 Add image alignment option to newsletter editor [MAILPOET-1124] 2017-10-02 15:30:43 +03:00
92dbf966a1 Add a UI hint for managing capabilities using the Members plugin [MAILPOET-1123] 2017-10-02 10:33:20 +01:00
db226b54a8 Include admin-global.css only on admin pages [MAILPOET-493] 2017-10-02 10:22:40 +01:00
3af059f5c4 Fix MailPoet icon displaying in Members tab on production [MAILPOET-493] 2017-10-02 10:22:40 +01:00
8706abcdf0 Change access_plugin_admin permission label [MAILPOET-493] 2017-10-02 10:22:40 +01:00
2129d041ac Fix indentation [MAILPOET-493] 2017-10-02 10:22:40 +01:00
2a4a44ebb5 Make a condition more easy to read [MAILPOET-493] 2017-10-02 10:22:40 +01:00
a4f2d5402c Manage MP3 permissions with WP role capabilities, add Members plugin support [MAILPOET-493] 2017-10-02 10:22:40 +01:00
9f5fc151b4 Move throttling out of the Subscriber model to the API 'subscribe' method [MAILPOET-1115] 2017-09-28 15:45:35 +01:00
8a91eb46e6 Fix the possibility of repeatedly submitting a form with an existing e-mail address [MAILPOET-1115] 2017-09-28 12:59:57 +01:00
e4ab928e82 Merge pull request #1127 from mailpoet/presubscribed_wp_sync_fix
Fix synchronization of presubscribed WP users [MAILPOET-1127]
2017-09-28 11:24:32 +02:00
a1b02cb862 Fix synchronization of presubscribed WP users [MAILPOET-1127] 2017-09-28 10:44:29 +03:00
84b942b9d2 Merge pull request #1121 from mailpoet/template_sort
Applies sorting by date created and name [MAILPOET-1119]
2017-09-27 11:14:00 +02:00
1ca99a6209 Updates Premium tab language 2017-09-27 10:07:36 +01:00
6b61abe8c0 Removes text domain from plugin header 2017-09-27 10:05:10 +01:00
27028ca1ef Merge pull request #1124 from mailpoet/alc_and_post_exclude_search_results
Prevents excluded post types from being displayed in newsletter editor [MAILPOET-701]
2017-09-27 11:40:51 +03:00
eed88926a2 Merge pull request #1120 from mailpoet/editor_horizontal_scroll_fix
Fixes horizontal scrolling inside post/ALC options panel [MAILPOET-1118]
2017-09-27 11:31:34 +03:00
b25877c514 Bump up release version to 3.0.1 2017-09-26 18:18:17 +03:00
119e574495 Prevents excluded post types from being displayed in newsletter editor 2017-09-25 19:45:33 -04:00
7308d253b2 Applies sorting by date created and name 2017-09-25 18:47:43 -04:00
1c19b71697 Fixes horizontal scrolling inside post/ALC options panel 2017-09-25 18:09:58 -04:00
7551fff93f Merge pull request #1116 from mailpoet/fix-tests
Fixing Shortcodes issue [MAILPOET-1104]
2017-09-25 17:14:35 -04:00
b2aa919574 Merge pull request #1118 from mailpoet/fix-query
Add index to improve query performance [MAILPOET-1117]
2017-09-25 17:04:25 -04:00
1102bbe483 Merge pull request #1056 from mailpoet/resize-image
Add image resize feature [MAILPOET-1047]
2017-09-25 13:07:10 +03:00
b78dd22ba9 Merge pull request #1109 from mailpoet/templates-images-src
Adding https prefix the image sources [MAILPOET-1109]
2017-09-25 12:08:02 +03:00
73110ada46 fixing ESLint tests 2017-09-25 09:06:55 +00:00
74dedd06bc changing missing tempaltes 2017-09-25 08:57:18 +00:00
20c936d13b limitting width with CSS 2017-09-21 18:58:17 +00:00
f135b89de9 Fixed image resize bugs 2017-09-21 18:58:16 +00:00
6a83930ae0 Resizing the image fixed 2017-09-21 18:57:35 +00:00
a1d0acfac2 Add image resize feature 2017-09-21 18:56:47 +00:00
04be06c0cb remove additional new line 2017-09-21 18:47:43 +00:00
78d52d6298 Merge pull request #1117 from mailpoet/eslint
Eslint rules [MAILPOET-1081]
2017-09-21 19:42:19 +03:00
5ff7c28c43 Merge pull request #1110 from mailpoet/es6_spacing
Fix ESLint spacing rules for ES6 [MAILPOET-1082]
2017-09-21 17:15:57 +03:00
5526f315d2 Merge pull request #1115 from mailpoet/scheduled_newsletter_status_fix
Sets newsletter status to draft when it's unscheduled [MAILPOET-1060]
2017-09-21 16:32:12 +03:00
d5e25fdeb1 Merge pull request #1114 from mailpoet/long_email_sql_error_fix
Fixes SQL error resulting from subscription with long email [MAILPOET-1113]
2017-09-21 16:17:21 +03:00
90a7bf5179 Adds back rendered subject clearing test
Removes duplicate line
2017-09-21 09:13:13 -04:00
bf1f696870 Add index to improve query performance
[MAILPOET-1117]
2017-09-21 13:17:02 +01:00
95551ad049 ES5 keyword-spacing 2017-09-21 09:13:36 +00:00
1ad90680f4 ES5 object-curly-spacing 2017-09-21 09:12:52 +00:00
d69d3cb421 ES5 array-bracket-spacing 2017-09-21 09:12:37 +00:00
9adca07393 ES5 computed-property-spacing 2017-09-21 09:12:23 +00:00
a9d129fddc ES5 block-spacing 2017-09-21 09:12:09 +00:00
b4ac09bea3 ES5 key-spacing 2017-09-21 09:11:55 +00:00
b1a403d9b5 ES5 space-infix-ops 2017-09-21 09:09:29 +00:00
28504fb5e3 ES5 spaced-comment 2017-09-21 09:08:52 +00:00
8ebb8e3c02 ES5 no-trailing-spaces 2017-09-21 09:04:47 +00:00
c95c2cd1ae ES5 space-in-parens 2017-09-21 09:04:32 +00:00
946bee2194 ES5 space-before-blocks 2017-09-21 09:02:50 +00:00
1f9bd04308 ES5 space-unary-ops 2017-09-21 08:52:30 +00:00
33572b2dc7 ES5 no-multi-spaces 2017-09-21 08:47:23 +00:00
680446b77e ES5 space-before-function-parens 2017-09-21 08:35:12 +00:00
bf1d76a3a7 Fix readme.txt 2017-09-20 16:57:17 +01:00
c915fcfdff Release 3.0.0 2017-09-20 15:21:55 +01:00
02966c3b93 Sets cron daemon timeout to 5s across the plugin
Adds hook to override cron request arguments
2017-09-20 14:59:48 +01:00
84dc48daec Allow passing cron timeouts
[MAILPOET-1114]
2017-09-20 14:59:48 +01:00
12225004f4 Apply hook on cron timeout
[MAILPOET-1114]
2017-09-20 14:59:48 +01:00
320dfa2ec5 Extracts duplicate code into reusable methods
Updates unit test
2017-09-20 14:59:48 +01:00
b5f3016085 Removes URL from user agent
(https://mailpoet.slack.com/archives/C02MTKAJL/p1505488541000029?thread_ts=1505488163.000795&cid=C02MTKAJL)
2017-09-20 14:59:48 +01:00
cd53e369d0 Allows accessing full URL from within custom cron hook 2017-09-20 14:59:48 +01:00
6fc11af774 Returns error message instead of empty body 2017-09-20 14:59:48 +01:00
42e3a97616 Fixing Shortcodes issue 2017-09-20 12:34:27 +00:00
6b16aa1692 Sets newsletter status to draft when it's unscheduled 2017-09-19 21:59:03 -04:00
03d0de74e4 Merge pull request #1108 from mailpoet/send_twice_tooltip
Show a tooltip about sending an email twice only when the Send button is disabled [MAILPOET-1098]
2017-09-19 20:38:10 -04:00
c3b643df84 Prevents leaking SQL errors in API response 2017-09-19 20:32:26 -04:00
697f9ba5bc Adds min/max email length in UI and backend 2017-09-19 20:04:49 -04:00
28c75c5b96 Adds a thousand separator 2017-09-19 09:55:38 -04:00
6f255854f2 Merge pull request #1112 from mailpoet/copy_update
Update free MSS plan size to 2000 subscribers [MAILPOET-1112]
2017-09-19 12:46:36 +03:00
91c5f9c43e Clarify Premium plugin benefits 2017-09-19 12:41:25 +03:00
62acd6404a Update free MSS plan size to 2k subscribers, update plugin description 2017-09-19 12:15:46 +03:00
adc1461771 Don't show send tooltip for paused newsletters [MAILPOET-1098] 2017-09-19 08:51:16 +03:00
66cc0964ce Merge pull request #1106 from mailpoet/fix-tests
Fixing MP API test
2017-09-18 18:23:20 -04:00
10d77720ad Merge pull request #1107 from mailpoet/hooks_unit_test_update
Updates hooks unit test [MAILPOET-1110]
2017-09-18 17:39:50 +02:00
475114c6f9 Fix ES6 no-irregular-whitespace eslint rule [MAILPOET-1082] 2017-09-18 18:31:13 +03:00
a0fec7d103 Fix ES6 space-infix-ops eslint rule [MAILPOET-1082] 2017-09-18 18:27:43 +03:00
4d9d92a026 Fix ES6 array-bracket-spacing eslint rule [MAILPOET-1082] 2017-09-18 18:26:43 +03:00
e51aa8c271 Fix ES6 space-in-parens eslint rule [MAILPOET-1082] 2017-09-18 18:25:37 +03:00
d44adedade Fix ES6 key-spacing eslint rule [MAILPOET-1082] 2017-09-18 18:24:26 +03:00
9fb3c50aa7 Fix ES6 no-multi-spaces eslint rule [MAILPOET-1082] 2017-09-18 18:23:36 +03:00
907053a349 Fix ES6 space-unary-ops eslint rule [MAILPOET-1082] 2017-09-18 18:22:48 +03:00
f0f85cfb59 Fix ES6 template-curly-spacing eslint rule [MAILPOET-1082] 2017-09-18 18:21:44 +03:00
44d0341fb2 Fix ES6 keyword-spacing eslint rule [MAILPOET-1082] 2017-09-18 18:20:55 +03:00
0cdae52c66 Fix ES6 react/jsx-curly-spacing eslint rule [MAILPOET-1082] 2017-09-18 18:18:45 +03:00
0cd9c8e416 Adding https prefix the image sources 2017-09-18 15:11:55 +00:00
9e3010ab52 Fix ES6 react/jsx-tag-spacing eslint rule [MAILPOET-1082] 2017-09-18 18:05:57 +03:00
d831b2df55 Explicitly sets up hooks instead of assuming they are already set up 2017-09-18 10:43:17 -04:00
16ff630e88 Show a tooltip about sending an email twice only when the Send button is dsiabled [MAILPOET-1098] 2017-09-18 17:42:24 +03:00
d35763662e Fixing MP API test 2017-09-18 12:00:43 +00:00
10be411b12 Prepare release v3.0.0 2017-09-18 11:35:59 +01:00
6ecce192f7 Merge pull request #1105 from mailpoet/wp_sync_multisite
Fix WP sync throwing 'Table "users" doesn't exist' errors on multisite subsites [MAILPOET-1107]
2017-09-18 11:48:30 +03:00
ee07e60fe9 Adds new survey 2017-09-18 09:13:06 +01:00
a35d7a1154 Fix WP sync throwing 'Table users doesn't exist' errors on multisite subsites [MAILPOET-1107] 2017-09-18 10:55:49 +03:00
ebba8dbfd6 Merge pull request #1103 from mailpoet/twig_version_conflict_detection_improvement
Updates Twig version conflict detection logic [MAILPOET-1068]
2017-09-18 10:42:51 +03:00
44c637c06b Merge pull request #1101 from mailpoet/premium_key_in_beacon
Add Premium key to Help Scout beacon [MAILPOET-1090]
2017-09-17 12:08:52 -04:00
d54ba734bf Adds a min-max range of supported Twig versions 2017-09-17 11:25:35 -04:00
b45fc22306 Updates coding style and cleans up JS script inclusion part 2017-09-16 11:34:21 -04:00
994935d4ae Adds check for minimum Twig version loaded by external plugins 2017-09-16 11:33:40 -04:00
ceb5ce850c Removes deprecated Twig_ExtensionInterface::getName()
(https://github.com/twigphp/Twig/blob/2.x/CHANGELOG#L207)
Removes deprecated Twig_Extension_GlobalsInterface
(https://github.com/twigphp/Twig/blob/2.x/CHANGELOG#L259)
2017-09-16 11:30:40 -04:00
97b5ed945d Merge pull request #1100 from mailpoet/update-send-copy
Update send copy [MAILPOET-1101]
2017-09-14 20:02:28 +02:00
873b322245 Translate
[MAILPOET-1101]
2017-09-14 18:52:33 +01:00
12ad9e41e7 Translate a string
[MAILPOET-1101]
2017-09-14 18:40:04 +01:00
96b418e455 Our free plan will be for 1,000 subscribers and not 250
[MAILPOET-1101]
2017-09-14 17:35:15 +01:00
8ea7861f77 Merge pull request #1102 from mailpoet/welcome-tab-update
Update welcome tab [MAILPOET-1099]
2017-09-14 19:34:01 +03:00
821976c881 Make text random
[MAILPOET-1099]
2017-09-14 17:19:55 +01:00
6f1443e43d Update welcome tab
[MAILPOET-1099]
2017-09-14 15:35:07 +01:00
09fcaecdfc Add Premium key to Help Scout beacon [MAILPOET-1090] 2017-09-14 16:24:15 +03:00
efd72ca9f6 Merge pull request #1099 from mailpoet/update-readme
Update readme.txt for launch v3
2017-09-14 15:38:36 +03:00
550b5e9aed More readme.txt updates
[MAILPOET-1105]
2017-09-14 13:37:10 +01:00
4b7ae5fcff Fix typo
[MAILPOET-1105]
2017-09-14 13:28:44 +01:00
fa85e12127 Update premium section in readme.txt
[MAILPOET-1105]
2017-09-14 13:27:07 +01:00
1cce50902b Update Send with... other copy
[MAILPOET-1101]
2017-09-14 11:39:20 +01:00
2048fa5cf9 Update Send with... mailpoet copy
[MAILPOET-1101]
2017-09-14 11:31:43 +01:00
f438c8fd31 Update readme.txt for launch v3
[MAILPOET-1105]
2017-09-14 10:15:00 +01:00
0bfa832dad Merge pull request #1098 from mailpoet/subscribers-fix
Subscribers fix [MAILPOET-1102]
2017-09-13 18:17:56 +03:00
483dfbe1ec Fix removal of WP segment subscribers without wp_user_id [MAILPOET-1102] 2017-09-13 16:46:50 +03:00
561fee491d Merge pull request #1097 from mailpoet/makepot_views_fix
Fix makepot adding extra slashes to escaped characters in views [MAILPOET-1093]
2017-09-13 14:54:25 +02:00
97d157192a Remove orphaned links
[MAILPOET-1102]
2017-09-13 13:30:23 +01:00
6b14a8a057 Remove data from usermeta
[MAILPOET-1102]
2017-09-13 13:18:39 +01:00
d27b187f5e Fix QA problems
[MAILPOET-1102]
2017-09-13 13:15:50 +01:00
02d49ba2ca DELETE subscribers in WP list which are not WP users
[MAILPOET-1102]
2017-09-13 13:07:01 +01:00
f3571a5855 Add another testing scenario
[MAILPOET-1102]
2017-09-13 08:54:54 +01:00
3d5f0df213 Don't delete subscribers with wp_user_id = 0 [MAILPOET-1102] 2017-09-13 10:15:09 +03:00
595a201fe7 Stop deleting subscribers
[MAILPOET-1102]
2017-09-12 17:53:12 +01:00
c3db7d8148 Release 3.0.0-rc.2.0.3 2017-09-12 15:41:06 +03:00
4a2187ff32 Merge pull request #1096 from mailpoet/translation_strings_update
Removes escape character in translations [MAILPOET-1093]
2017-09-12 14:24:38 +03:00
279c36a30d Merge pull request #1094 from mailpoet/initializer_update
Updates plugin initialization logic [MAILPOET-1097]
2017-09-12 12:34:27 +03:00
fd65117a5d Fix makepot adding extra slashes to escaped characters in views [MAILPOET-1093] 2017-09-12 11:17:04 +03:00
9305beace3 Removes escape character in translations 2017-09-11 19:27:03 -04:00
ba86cbfb18 Updates unit test 2017-09-11 11:40:14 -04:00
2ac7d1d4ab Breaks plugin initialization into 3 stages: preInit, init and postInit 2017-09-11 11:33:03 -04:00
e9c55260c8 Merge pull request #1092 from mailpoet/db_version_settings_logic_update
Uses plugin's settings to store DB version [MAILPOET-1095]
2017-09-11 18:15:17 +03:00
99b6a287f8 Highlight a container block when hovering over its tools [MAILPOET-1088] 2017-09-11 15:32:45 +01:00
d645f0cad4 Merge pull request #1087 from mailpoet/eslint-un
Eslint un [MAILPOET-1073]
2017-09-11 16:16:51 +02:00
b0343254c0 Renames db version setting value 2017-09-11 10:05:09 -04:00
cc368f015b Remove redundant argument
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
4e508855fc Fix eslint no-unused-expressions in tests
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
72c0a6f165 Fix eslint no-undef in tests
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
f266508654 Fix eslint no-undef in ES6
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
1893d82b2d Fix eslint no-unused-expressions in ES6
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
bb1dd6a2ec Fix eslint import/no-unresolved in ES6
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
7097ea5d9b Fix eslint no-unreachable in ES6
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
b4092e0641 Fix eslint no-unneeded-ternary in ES5
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
2c068c7bb6 Fix eslint no-unreachable in ES5
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
a1a4fcb978 Fix eslint no-unused-expressions in ES5
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
f77620d649 Fix eslint no-undef in ES5
[MAILPOET-1085]
2017-09-11 15:03:30 +01:00
9e932c9078 Merge pull request #1085 from mailpoet/improve-user-sync
Improve user sync [MAILPOET-1073]
2017-09-11 14:56:41 +03:00
de47dbe41b Removes arrow functions that are not supported in IE 2017-09-11 10:25:43 +01:00
6d36d67a60 Delete orphaned subscribers instead of updating their id
[MAILPOET-1073]
2017-09-11 10:09:32 +01:00
607bf51b37 Merge two test cases for one class
[MAILPOET-1073]
2017-09-11 09:28:39 +01:00
f38ed0272c Merge pull request #1093 from mailpoet/cron_helper_improvement
Cron helper improvement [MAILPOET-1096]
2017-09-11 11:23:01 +03:00
eef969439b Code comment
[MAILPOET-1073]
2017-09-11 09:22:40 +01:00
272f552f3c Clean data befre and after test run
[MAILPOET-1073]
2017-09-11 09:22:18 +01:00
dff9c1012b Moves widget initialization back to widgets_init hook
Removes priority from plugin initialization
2017-09-10 19:49:05 -04:00
52470360a1 Returns ping response body instead of boolean 2017-09-10 18:49:15 -04:00
6c62459ed4 Adds filter to override default cron request URL 2017-09-10 18:36:22 -04:00
98482cebf9 Uses plugin's settings to store DB version 2017-09-10 16:31:36 -04:00
509880119e Merge pull request #1090 from mailpoet/eslint-indent
Eslint indent [MAILPOET-1029]
2017-09-07 20:13:20 -04:00
195ccb5eed Fix indent rule in tests
[MAILPOET-1029]
2017-09-07 16:05:45 +01:00
122af1c943 Fix react/jsx-indent rule in ES6
[MAILPOET-1029]
2017-09-07 15:59:38 +01:00
169940058b Fix react/jsx-indent-props rule in ES6
[MAILPOET-1029]
2017-09-07 15:52:46 +01:00
478e60d60e Fix indent rule in ES5
[MAILPOET-1029]
2017-09-07 15:51:25 +01:00
9fc0ac8c4f Merge pull request #1089 from mailpoet/color_picker
Change color on click, add history to color picker [MAILPOET-1087]
2017-09-07 17:08:09 +03:00
e86b780479 Fix build`
[MAILPOET-1073]
2017-09-07 14:44:55 +01:00
42f08bdc65 Fix handling of empty color and transparent color selected from palette [MAILPOET-1087] 2017-09-07 15:48:14 +03:00
c2789cdac3 Don't load all ids to memory, PHP could crash
[MAILPOET-1073]
2017-09-07 13:42:18 +01:00
8a362f49f8 Add test for users synchronisation
[MAILPOET-1073]
2017-09-07 13:42:18 +01:00
784a80d1a5 Use ->prefix instead of wp_
[MAILPOET-1073]
2017-09-07 13:42:18 +01:00
922d2b4b5f Fix migration
[MAILPOET-1073]
2017-09-07 13:42:18 +01:00
91e8461cac Clean the code
[MAILPOET-1073]
2017-09-07 13:42:18 +01:00
8757598a2d Add index
[MAILPOET-1073]
2017-09-07 13:42:18 +01:00
524aabea1d Split users sync into multiple queries
[MAILPOET-1073]
2017-09-07 13:42:18 +01:00
513181ff47 Change color on click, add history to color picker [MAILPOET-1087] 2017-09-07 13:17:57 +03:00
6bc288ed54 Merge pull request #1088 from mailpoet/depreciated_twig_notice_fix
Fixed Twig's deprecated notice [MAILPOET-1092]
2017-09-07 12:48:11 +03:00
77f3a875dd Does not set global is_rtl variable 2017-09-06 20:56:52 -04:00
6584250d1a Adds is_rtl() function to Twig 2017-09-06 20:55:44 -04:00
e42db162aa Merge pull request #1086 from mailpoet/translation_fixes
Fix translations based on translator feedback on TX [MAILPOET-1091]
2017-09-06 16:30:13 +03:00
2762096167 Switch strtoupper to mb_strtoupper based on feedback 2017-09-06 16:14:52 +03:00
b2ec3e5f7b Fix translations based on translator feedback on TX [MAILPOET-1091] 2017-09-06 13:45:42 +03:00
e012bd6cbe Updates changelog & bumps up release version to 3.0.0-rc.2.0.2 2017-09-05 11:17:32 -04:00
a02e64e805 Merge pull request #1084 from mailpoet/editor_get_post_type_optimization
Removes unused properties from the object used to display post types in editor [MAILPOET-1086]
2017-09-05 17:43:27 +03:00
e4bb3e1133 Adds unit test 2017-09-05 10:33:10 -04:00
998795e0e0 Fix tests semi eslint rule [MAILPOET-1030] 2017-09-05 12:31:51 +01:00
ec44b84cc9 Fix ES6 no-extra-semi eslint rule [MAILPOET-1030] 2017-09-05 12:31:51 +01:00
efece061d0 Fix ES5 semi eslint rule [MAILPOET-1030] 2017-09-05 12:31:51 +01:00
e347fc74a2 Fix ES5 no-extra-semi eslint rule [MAILPOET-1030] 2017-09-05 12:31:51 +01:00
027418a86c Fix ES5 semi-spacing eslint rule [MAILPOET-1030] 2017-09-05 12:31:51 +01:00
864187aa02 Merge pull request #1080 from mailpoet/json_api_method_check_fix
Throws error when JSON API endpoint method is not found [MAILPOET-1074]
2017-09-05 13:08:37 +03:00
59ae6619c0 Browser preview mixed content error fix
Strips protocol from preview URL

[MAILPOET-1080]
2017-09-05 08:54:23 +01:00
6aa0be8d01 Removes unused properties from the object 2017-09-04 21:07:15 -04:00
657658ea2b Adds new poll 2017-09-04 10:49:11 +02:00
8647128e12 Merge pull request #1081 from mailpoet/default_status_update
Default status is set depending on the signup confirmation option [MAILPOET-1079]
2017-09-04 10:02:45 +03:00
c8d92b3cd2 Default status is set depending on the signup confirmation option 2017-08-31 18:48:43 -04:00
cc8b7b45ed Throws error when endpoint method is not found 2017-08-31 15:18:22 -04:00
5b8b8c8441 Merge pull request #1077 from mailpoet/helpscout
Split support inquiries into Free and Premium inboxes [MAILPOET-869]
2017-08-31 12:38:18 -04:00
7106c640ef Refactor HelpScout Beacon embed script 2017-08-31 19:24:32 +03:00
300b84983d Merge pull request #1078 from mailpoet/spellchecker
Enable browser spellchecker [MAILPOET-1078]
2017-08-31 18:14:47 +03:00
49c1b92838 Enable browser spellchecker 2017-08-31 18:03:19 +03:00
d900827850 Merge pull request #1076 from mailpoet/lint-lines
Lint lines [MAILPOET-1031]
2017-08-31 12:49:10 +03:00
1688d4dbe1 Split support inquiries into Free and Premium inboxes [MAILPOET-869] 2017-08-31 12:02:20 +03:00
856c636089 Releasing 3.0.0-rc.2.0.1 2017-08-30 16:06:39 +00:00
8f9e8ea185 Merge pull request #1075 from mailpoet/forms-bug
Fix form issue when using list selection field [MAILPOET-1077]
2017-08-30 18:10:33 +03:00
b0b88693f1 Merge pull request #1071 from mailpoet/initializer_cleanup
Fixes activation on MS environments and cleans up Initializer [MAILPOET-1076]
2017-08-30 17:56:16 +03:00
9916eb9da8 updated tests 2017-08-30 14:52:17 +00:00
79b5426e01 Fix a constant name [MAILPOET-1076] 2017-08-30 17:47:53 +03:00
5807fd2e02 Merge pull request #1067 from mailpoet/emoji
Add emoji support to newsletter body [MAILPOET-1009]
2017-08-30 10:04:26 -04:00
0ee39143f4 Runs hooks setup only when plugin is initilized 2017-08-30 09:50:49 -04:00
10c39bd650 Removes unused constructor 2017-08-30 09:47:34 -04:00
20593cc5a5 Fix form issue when using list selection field 2017-08-30 13:29:41 +00:00
f438eee842 Fix ESLint newline-per-chained-call in tests
[MAILPOET-1031]
2017-08-30 15:26:02 +02:00
cb4b599d97 Merge pull request #1073 from mailpoet/fix-notice
Fix php notice
2017-08-30 16:25:50 +03:00
33733219f6 Fix ESLint object-property-newline in tests
[MAILPOET-1031]
2017-08-30 15:20:39 +02:00
737a83cdf3 Fix ESLint linebreak-style in tests
[MAILPOET-1031]
2017-08-30 15:19:15 +02:00
9061e1b495 Fix ESLint no-multipe-empty-lines ES6
[MAILPOET-1031]
2017-08-30 15:11:56 +02:00
09199e41a1 Fix ESLint linebreak-style ES5
[MAILPOET-1031]
2017-08-30 14:59:34 +02:00
4e91932613 Fix ESLint lines-around-directive ES5
[MAILPOET-1031]
2017-08-30 14:45:25 +02:00
227de4ecfa Fix ESLint newline-per-chained-call ES5
[MAILPOET-1031]
2017-08-30 14:40:42 +02:00
c1ccacf851 Fix ESLint newline-per-chained-call
[MAILPOET-1031]
2017-08-30 14:30:48 +02:00
53f7953566 Fix browser preview bypassing emoji encoding [MAILPOET-1009] 2017-08-30 14:39:38 +03:00
61ae2da1e3 Fix a constant not defined in PHP 5.3 [MAILPOET-1009] 2017-08-30 14:02:29 +03:00
36abd8e5e6 Don't show network activation notice for other plugins
[MAILPOET-1072]
2017-08-30 11:39:42 +02:00
7e9de1fd07 Fix php notice 2017-08-30 11:25:42 +02:00
7ac5e65963 Fix php notice 2017-08-30 10:30:16 +02:00
cf992852b5 Validate for unsubscribe link only for MSS sending method [MAILPOET-1050] 2017-08-30 10:16:24 +02:00
59482b2bfa Uses init hook to initilize AccessControl 2017-08-29 23:30:35 -04:00
053f9e0cdf Adds higher priority to init hook so that it fires before the widgets hook 2017-08-29 23:20:46 -04:00
e1cc25239b Maintains code consistency when setting up JSON API
Updates exception handler return statement
2017-08-29 20:33:50 -04:00
2f4452ad36 Removes redundant exception handler 2017-08-29 20:28:20 -04:00
f453d685d6 Fixes translation not being picked up by makepot 2017-08-29 20:24:04 -04:00
2d2b4ca7f0 Moves setup actions from plugins_loaded hook to init hook
Rearranges class methods to follow the order by which they are called
2017-08-29 20:22:19 -04:00
546e012fbf releasing 3.0.0-rc.2.0.0 2017-08-29 13:43:15 +00:00
83adf089c5 Merge pull request #1070 from mailpoet/permissions
Fix Endpoint to use single global permission and not array of them [MAILPOET-1057]
2017-08-29 14:47:18 +02:00
2f45bb05d7 Fix Endpoint to use single global permission and not array of them
[MAILPOET-1057]
2017-08-29 15:34:35 +03:00
9ef8ab3e28 Merge pull request #1065 from mailpoet/events
Track extra events on MixPanel [MAILPOET-970]
2017-08-29 15:26:24 +03:00
f4db4f05c4 minor fixes 2017-08-29 12:18:12 +00:00
e7e08dbb3a minor fixes 2017-08-29 12:07:57 +00:00
02e2f0c07a Merge pull request #1066 from mailpoet/premium-update-warning
Premium update warning [PREMIUM-28]
2017-08-29 12:52:18 +03:00
c75b6bd7eb Show warning only on plugins page
[PREMIUM-28]
2017-08-29 11:22:51 +02:00
b74be8777a Display warning on Email pages
[PREMIUM-28]
2017-08-29 11:22:51 +02:00
7463e0d1f1 Update invalid key message
[PREMIUM-28]
2017-08-29 11:22:51 +02:00
a378880cf8 Merge pull request #1069 from mailpoet/bridge_forbidden_response
Adds 403 response code that mirrors 401 behavior [MAILPOET-1070]
2017-08-29 11:20:56 +03:00
33fa496913 Add a message for an already used MSS key [MAILPOET-1070] 2017-08-29 11:10:09 +03:00
eff996e1f8 Unify key state constant names, leave back compat with unlocker [MAILPOET-1070] 2017-08-29 10:58:16 +03:00
28b894b26b Add test cases [MAILPOET-1070] 2017-08-29 09:20:03 +03:00
c914aedc0d Simplify API response codes handling [MAILPOET-1070] 2017-08-29 09:18:08 +03:00
1745b67d64 Adds 403 response code that mirrors 401 behavior 2017-08-28 21:25:54 -04:00
2d25974fdc Merge pull request #1068 from mailpoet/helpscout
Update HS Beacon icon to question mark [MAILPOET-869]
2017-08-28 19:49:12 -04:00
51a02784e2 Update HS Beacon icon to question mark [MAILPOET-869] 2017-08-28 19:12:27 +03:00
b9bdc86fd9 Add emoji support to newsletter body [MAILPOET-1009] 2017-08-28 19:07:17 +03:00
e580e6b92b Merge pull request #1041 from mailpoet/access_control
Allows granular control of access to various parts of the plugin [MAILPOET-1057] [MAILPOET-1048]
2017-08-28 16:48:47 +03:00
96f77498f7 Merge pull request #1061 from mailpoet/safari-error-fix
Safari error fix [MAILPOET-1011]
2017-08-28 13:33:43 +02:00
62ce7c0437 Merge pull request #1062 from mailpoet/wp_user_subscription_update_fix
Prevents WP subscribers' first/last name from being erased [MAILPOET-1062]
2017-08-28 12:57:25 +03:00
c1e542cb5a Reschedules past due scheduled queues when reactivating notification 2017-08-28 09:20:17 +02:00
544bf0ddfd Merge pull request #1064 from mailpoet/welcome_alc_fix
Don't track sent ALC posts for welcome and standard emails, always send the latest posts [MAILPOET-1069]
2017-08-25 10:36:46 -04:00
609f1b5e67 Tracking new events 2017-08-25 13:19:52 +00:00
4616cf67bf Fix a test [MAILPOET-1069] 2017-08-25 16:10:01 +03:00
cc5227ca0d Don't track sent ALC posts for welcome and standard emails (always send the latest posts) [MAILPOET-1069] 2017-08-25 15:51:45 +03:00
26bccd95d4 Uses method vs. accessing class internals to get user capability 2017-08-24 13:58:54 -04:00
af58814fe7 Moves AccessControl intialization outside of Router to Initializer 2017-08-24 13:56:17 -04:00
7d9b4b31aa Removes unused constructor parameter 2017-08-24 13:37:49 -04:00
8a8108b41d Prevents WP subscribers' first/last name from being erased when updating
subscription
2017-08-24 13:19:42 -04:00
d92b1f57bd Merge pull request #1060 from mailpoet/mp2_forms_fix
Don't process the wysija_form shortcode to allow MP2 forms to work [MAILPOET-1067]
2017-08-24 10:40:10 -04:00
52ef7bece4 Merge pull request #1053 from mailpoet/editor_save
Save newsletter when clicked on "Next" button [MAILPOET-1051]
2017-08-24 09:03:34 -04:00
dc11046ad1 Fixes undefined JS error when vendor.js is loaded deferred
[MAILPOET-1026]
2017-08-24 13:37:15 +02:00
01f41b9798 Fix App and Application argument naming 2017-08-24 14:17:32 +03:00
9c0d9c31f8 Refactor cancelAutosave to return early 2017-08-24 13:48:13 +03:00
df499095c4 Fix test indentation 2017-08-24 13:40:46 +03:00
05dca3d2ce Excludes honeypot from subscription management form 2017-08-24 11:17:40 +02:00
ec35b90f3e Fix react warning
[MAILPOET-1011]
2017-08-24 11:01:33 +02:00
1b7e3a997f Fix not fully formated selector
[MAILPOET-1011]
2017-08-24 10:23:42 +02:00
c598537025 Remove empty lines 2017-08-24 09:16:51 +02:00
6f149e3ec4 tests: quote-props 2017-08-24 09:16:51 +02:00
d181bde0e9 tests: quotes 2017-08-24 09:16:51 +02:00
2985705b14 ES6: quote-props 2017-08-24 09:16:51 +02:00
6ce925fbe8 ES6: quotes 2017-08-24 09:16:51 +02:00
b8aceff61f ES5: quote-props 2017-08-24 09:16:51 +02:00
cd091c2af6 ES5: quotes 2017-08-24 09:16:51 +02:00
510cacf2fd Don't process the wysija_form shortcode to allow MP2 forms to work [MAILPOET-1067] 2017-08-24 08:22:10 +03:00
eac6b1b414 Corrects coding style 2017-08-23 11:45:33 -04:00
316fa91a10 Moves AccessControl initialization outside of API to Initializer 2017-08-23 11:28:09 -04:00
7c23415d26 Updates unit test as a result of AccessControl implementation 2017-08-23 11:28:09 -04:00
28320cdbb6 Updates permission validation method on AccessControl
Adds/updates unit tests
2017-08-23 11:28:08 -04:00
48f3ae4ea1 Adds access control unit tests 2017-08-23 11:28:08 -04:00
e47c8bc701 Adds access control tests for Router 2017-08-23 11:28:08 -04:00
78429d8f91 Validates global permission at the AccessControl level
Changes error response code on invalid permission
2017-08-23 11:27:54 -04:00
80c4eeed5e Merge pull request #1055 from mailpoet/new-poll
New poll [MAILPOET-1040]
2017-08-23 14:45:05 +02:00
5985d659f9 New poll
[MAILPOET-1040]
2017-08-23 14:11:35 +02:00
638de3cf55 Merge pull request #1054 from mailpoet/coverage-fix
Coverage fix [MAILPOET-1064]
2017-08-23 13:41:43 +03:00
967fa09f4f Remove redundant build
[MAILPOET-1064]
2017-08-23 12:32:34 +02:00
a15e1200b5 Upgrade codeception version
[MAILPOET-1064]
2017-08-23 12:32:34 +02:00
a15b46cbab Fix Code coverage reporting
[MAILPOET-1064]
2017-08-23 12:15:56 +02:00
dcb0b45c21 Save newsletter before navigating away when clicked on "Next" button 2017-08-22 17:34:23 +03:00
14810a22b5 Bump up release version to 3.0.0-rc.1.0.4 2017-08-22 16:26:48 +03:00
1b756ef0b2 Adds access management to router and updates endpoints accordingly 2017-08-22 09:06:20 -04:00
5553817f9a Creates method to get user's first capability 2017-08-22 09:06:20 -04:00
4b7fb3ae3d Updates access permission names to improve clarity 2017-08-22 09:06:20 -04:00
efa231b08f Removes AccessControl from Migrator and Changelog 2017-08-22 09:06:20 -04:00
8d8dfaa11f Uses Intializer to check permissions before running Activator 2017-08-22 09:06:20 -04:00
5ba2c4bc3a Removes AccessControl from individual API endpoints 2017-08-22 09:06:20 -04:00
788494ec47 Updates API initialization 2017-08-22 09:06:20 -04:00
5e7f9e3edf Passes AccessControl to JSON API via constructor parameter
Removes passing AccessControl to individual API endpoints
2017-08-22 09:06:20 -04:00
2e5554a3af Refactors AccessControl and passes it as dependency to JSON API and Menu 2017-08-22 09:06:20 -04:00
51fbf29031 Modifies Activator to use AccessControl 2017-08-22 09:06:19 -04:00
c3c6ce989c Modifies Menu to use AccessControl 2017-08-22 09:06:19 -04:00
a241d0c7bc Modifies JSON API to use AccessControl 2017-08-22 09:06:19 -04:00
632bce7894 Adds AccessControl class that defines permissions for major plugin
operations
2017-08-22 09:04:39 -04:00
1151354278 Conditionally uses set_time_limit() when function is not disabled 2017-08-22 09:04:39 -04:00
c12752403f Fix build 2017-08-22 13:40:11 +02:00
d3ff174e9f Fix inability to deactivate MSS [MAILPOET-1058] 2017-08-22 13:35:51 +02:00
03df7e723c Merge pull request #1051 from mailpoet/composer-fix
Fix composer.lock [PREMIUM-35]
2017-08-22 12:24:40 +03:00
6c8fe8413a Merge pull request #1047 from mailpoet/eslint-assignment
Eslint assignment [MAILPOET-1033]
2017-08-22 09:59:35 +02:00
89b0b51980 Merge pull request #1050 from mailpoet/new-poll
New poll [MAILPOET-1042]
2017-08-21 11:57:26 -04:00
fa1ab733f8 Fix composer.lock
[PREMIUM-35]
2017-08-21 16:34:43 +02:00
127022645e Revert "Update composer.lock"
This reverts commit b1d26b8cee.
2017-08-21 13:39:15 +02:00
b1d26b8cee Update composer.lock
[PREMIUM-35]
2017-08-21 13:26:13 +02:00
f07b90adde Merge pull request #1049 from mailpoet/safari-unsafe
This is a non-secure form. [MAILPOET-1063]
2017-08-21 13:31:50 +03:00
b3884d06a8 New poll
[MAILPOET-1042]
2017-08-21 11:02:49 +02:00
abf1d817f4 Remove form action
[MAILPOET-1063]
2017-08-21 10:37:13 +02:00
c7b7b0abad Merge pull request #1011 from mailpoet/acceptance-tests
Initial acceptance testing setup [MAILPOET-997]
2017-08-17 19:28:27 -04:00
8540e5eea9 Merge pull request #1017 from mailpoet/rerender-newsletter
Rerender newsletter [MAILPOET-675]
2017-08-17 16:19:16 +03:00
09ed3d4fa6 refactoring the code 2017-08-17 12:16:40 +00:00
b96dc8b3f7 Merge pull request #1048 from mailpoet/migration-fix
Skip migration on empty db
2017-08-17 14:04:47 +03:00
0a4dc3eb38 Skip all migration on empty db 2017-08-17 12:58:33 +02:00
a78af28943 Track newsletter UI events with MixPanel
[MAILPOET-999]
2017-08-17 12:45:12 +02:00
f035d12aaf Skip migration on empty db 2017-08-17 11:55:51 +02:00
6353075f1e Don't delete vendor
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
6c91ca9d31 Reinstall vendor
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
6f8634570c Replace repo directory
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
0efcfad3d1 Increase docker compose timeout
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
5d7b54ab22 Add circle ci configuration
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
ad1f6e2a8e Fix caching problem
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
d844b7e47f Initial acceptance testing setup
[MAILPOET-997]
2017-08-17 08:13:10 +02:00
36d4e3eb15 Merge pull request #1034 from mailpoet/ui-help-tooltips
Ui help tooltips [MAILPOET-976]
2017-08-16 19:23:13 -04:00
853f686dde List and scheduling inputs are disabled instead of being hidden 2017-08-16 14:44:42 +00:00
d17486bac4 Merge pull request #1046 from mailpoet/spambot-forms
Add honeypot field for spambot [MAILPOET-1014]
2017-08-16 16:47:21 +03:00
4226684c5a Make tests more obvious
[MAILPOET-1014]
2017-08-16 15:32:07 +02:00
364dd1b2a3 Move field obfuscation into own class
[MAILPOET-1014]
2017-08-16 14:58:31 +02:00
eaf10e8a96 Fix no-param-reassign in tests
[MAILPOET-1033]
2017-08-16 12:34:59 +02:00
bac494ac0d Fix no-multi-assign in tests
[MAILPOET-1033]
2017-08-16 12:25:15 +02:00
acd2b9f51e Fix no-param-reassign in ES5
[MAILPOET-1033]
2017-08-16 12:22:56 +02:00
27c6fa5ff4 Fix no-multi-assign in ES5
[MAILPOET-1033]
2017-08-16 10:44:33 +02:00
89b51b6215 Fix no-cond-assign in ES5
[MAILPOET-1033]
2017-08-16 10:39:43 +02:00
7725391eff Fix no-return-assign in ES5
[MAILPOET-1033]
2017-08-16 10:30:11 +02:00
a37117cfa3 Fix no-param-reassign in ES6
[MAILPOET-1033]
2017-08-16 10:25:18 +02:00
856331caa4 Merge pull request #1044 from mailpoet/post_excerpt_hook
Adds a hook to specify custom max post excerpt length [MAILPOET-1056]
2017-08-15 16:45:26 +02:00
9117ae1a27 Fixing more bugs. Lists and scheduling options are hidden when editing a newsletter that is being sent 2017-08-15 14:39:02 +00:00
4aae8d56e5 Tooltip improvements
[MAILPOET-976]
2017-08-15 16:08:30 +02:00
033d527db9 fix some bugs 2017-08-15 12:55:06 +00:00
b2b1f7ff71 tests fixed 2017-08-15 12:55:06 +00:00
de261d6179 Added confirmation when 'edit' is clicked 2017-08-15 12:55:05 +00:00
a587b0a966 Links are not re-hashed when re-rendering the same newsletter 2017-08-15 12:55:05 +00:00
441aa14bcb fix js typo 2017-08-15 12:55:04 +00:00
4b4b5dd556 show 'Resume' button if the Newsletter was paused. 2017-08-15 12:55:04 +00:00
df9ba7e6c8 clearing the sending queue rendered body and subject
The new body and subject would be automatically rendered when
resuming sending
2017-08-15 12:55:04 +00:00
ca4f1c9387 Pause sending queue when editting the newsletter
An ajax request is sent to pause the sending queue when the editor
is displayed. If the newsletter is still a draft or has been already
sent; the error reponse is simply ignored. Otherwise a notice is
displayed specifying that the Email sending has been paused.
2017-08-15 12:55:03 +00:00
8c151d2d11 Fix failling test
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
78fb9ba46f Fix "Unexpected trailing comma"
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
3a0669e1a2 Fix react/no-danger eslint problem
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
c466e53681 Add tooltip to image heading
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
d02aed870e Add tooltip to image full width
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
fad7ff0018 Add tooltip to preheader
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
84a3f98725 Add tooltip to subject line
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
1c3e968ec4 Add tooltip to editor styles
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
c090a8260b Add tooltip to editor previw
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
65726de7de Add tooltip to send
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
33fe302f0d Add tooltip to import a template
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
2d702dd5d3 Add simple tooltip to help
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
18f208cf47 Add honeypot field for spambot
[MAILPOET-1014]
2017-08-15 14:33:45 +02:00
f7b1016e63 Release 3.0.0-rc.1.0.3 2017-08-15 11:35:44 +02:00
223fedba72 Update vendor dependencies
Bump down codeception version to 2.2.11 for code coverage compatibility
with php 5.6

[MAILPOET-1049]
2017-08-15 10:12:48 +02:00
bf7e7e414f Adds hook to modify rendered form widget 2017-08-15 09:49:38 +02:00
618d0c0c9d Explicitly sets form target to _self as default 2017-08-15 09:49:38 +02:00
49318791fc Adds hook to modify max post excerpt 2017-08-14 20:55:49 -04:00
a5abdd28e1 Removes unused constructor parameter 2017-08-14 20:55:42 -04:00
70860a676c Merge pull request #1036 from mailpoet/date_shortcode_update
Translates and updates date shortcode to display WP time [MAILPOET-1046]
2017-08-14 13:09:08 +03:00
469e9fd8e1 Update poll to "how would you rate MailPoet's reliability?"
[MAILPOET-1043]
2017-08-14 08:49:44 +02:00
715b48df8d Merge pull request #1040 from mailpoet/preview-popup
Fit newsletter browser preview modal popup to screen [MAILPOET-975]
2017-08-13 11:31:05 -04:00
27ae0a9f16 Merge pull request #1039 from mailpoet/set_time_limit_update
Conditionally uses set_time_limit() when function is not disabled [MAILPOET-1054]
2017-08-11 19:52:15 +02:00
b92329a6b5 Fix popup dimensions to the viewport and show the close button 2017-08-11 17:30:13 +00:00
6fe5b7e0c5 Conditionally uses set_time_limit() when function is not disabled 2017-08-11 12:16:31 -04:00
7e0c500e4f Uses WP's date_i18n() to localize date shortcode 2017-08-10 12:46:27 -04:00
eec35c8ab6 Merge pull request #1037 from mailpoet/build-failures
Break the build when errors happen during build steps [MAILPOET-1052]
2017-08-10 16:35:55 +02:00
4096c4b31b Break the build when errors happen during build steps [MAILPOET-1052] 2017-08-10 14:51:37 +03:00
40cbefd1f4 Uses WP time vs. system time 2017-08-09 18:59:55 -04:00
fb5d43e975 Adds helper method to translate shortcodes
Adds translations to Date shortcode
2017-08-09 18:56:33 -04:00
f35b66b3cf Release 3.0.0-rc.1.0.2 2017-08-08 17:45:04 +03:00
7900e7eb8d Merge pull request #1035 from mailpoet/new-poll
New poll [MAILPOET-1018]
2017-08-08 09:17:41 -04:00
849a24ced7 Merge pull request #1019 from mailpoet/translate
translate the DB error message [MAILPOET-1019]
2017-08-08 13:08:54 +03:00
f7e73b06be added missing argument 2017-08-08 10:02:51 +00:00
52cbb9fcb2 Merge pull request #1032 from mailpoet/css-form
Update form css [MAILPOET-593]
2017-08-08 11:48:09 +02:00
966ec0cb7a New poll
[MAILPOET-1018]
2017-08-03 13:17:37 +02:00
2ff0d40d10 Updates reference to the hook that displays IIS error 2017-08-03 08:14:46 +02:00
bb249ebe09 Removes leftover debug code 2017-08-02 10:24:35 -04:00
5a57029b38 Bumps up release version & updates changelog 2017-08-02 10:23:37 -04:00
84d427cc4c Upate poll url 2017-08-02 11:40:30 +02:00
22efd2109b Release 3.0.0-rc.1.0.0 2017-08-01 18:04:30 +00:00
f044db5745 Update form css
[MAILPOET-593]
2017-08-01 11:28:53 +02:00
0865995d21 Does not remove padding from the last element in a column 2017-08-01 09:47:29 +02:00
7c30192a03 Adds option to replace shortcodes in one string using contents from
another string
Adds unit tests
2017-08-01 09:32:42 +02:00
334c3ff420 Returns false when data-post-id tag is not found
Uses all post types, including custom, when querying posts
2017-08-01 09:32:42 +02:00
6d738ddb3b Merge pull request #1031 from mailpoet/mss-response
Mss response [MAILPOET-1035]
2017-07-31 14:37:13 -04:00
440052cf2c Remove duplicate code
[MAILPOET-1035]
2017-07-31 16:51:11 +02:00
ccb751b44a Adhere to updated MSS /me response
[MAILPOET-1035]
2017-07-31 16:47:20 +02:00
7cf3d0960d Unify API response constants
[MAILPOET-1035]
2017-07-31 13:51:23 +02:00
6e45856622 Merge pull request #1022 from mailpoet/mediumint_to_int
Switch ID fields from mediumint to int in DB [MAILPOET-1022]
2017-07-31 11:03:16 +02:00
9859df98b7 translate the DB error message 2017-07-31 08:53:30 +00:00
1071688924 Update a poll [MAILPOET-1017] 2017-07-31 09:41:45 +02:00
4f3b0234a4 Move exception rule to top
[MAILPOET-1028]
2017-07-31 09:27:26 +02:00
3e2bbeac11 Fix ES6 comma-dangle eslint rule (functions should be ignored pre-ES2017) [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
08f81bd816 Fix ES5 comma-dangle eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
f0c59ff635 Fix tests comma-dangle eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
66c6f52646 Fix ES6 comma-spacing eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
4988aaf14f Fix ES5 comma-spacing eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
5773d46f1c Merge pull request #1029 from mailpoet/alc-rendering
Enable blockquotes and fix image positioning in ALC blocks [MAILPOET-1037] [MAILPOET-1038]
2017-07-30 12:01:39 -04:00
f3a1e1b447 Update Robo test debug rule to accept individual test file paths 2017-07-28 14:36:40 +03:00
6de746162e Update Posts transformer to extract images by splitting the DOM tree 2017-07-28 14:36:12 +03:00
60b3c066a5 Allow rendering blockquote tags 2017-07-28 11:10:57 +03:00
63158dc2d5 Merge pull request #1024 from mailpoet/mta-frequency-fix
Mta frequency fix [MAILPOET-1021]
2017-07-27 19:17:14 +03:00
dede6e56c4 Merge pull request #1026 from mailpoet/beta_end
Remove concept of "Beta" on readme.txt, update banners [MAILPOET-1023]
2017-07-27 15:49:22 +03:00
dd366dde18 Remove concept of "Beta" on readme.txt, update banners [MAILPOET-1023] 2017-07-27 14:04:00 +03:00
2fedafe483 Merge pull request #1025 from mailpoet/tests_sniffs
Enable remaining CodeSniffer rules for PHP tests [MAILPOET-1025]
2017-07-26 21:34:48 -04:00
feae98ac3f Use a single ruleset for both the plugin and its tests [MAILPOET-1025] 2017-07-26 18:56:14 +03:00
6fb18ad321 Fix PSR1.Classes.ClassDeclaration sniffer rule in tests [MAILPOET-1025] 2017-07-26 18:28:56 +03:00
6c843e35b0 Update warning text
[MAILPOET-1021]
2017-07-26 16:27:53 +02:00
cfdb86eb6e Replace default option text
[MAILPOET-1021]
2017-07-26 16:24:31 +02:00
41fb93e453 Remove the number of emails per day calculation
[MAILPOET-1021]
2017-07-26 16:22:23 +02:00
19813b5dad Fix host frequency override
[MAILPOET-1021]
2017-07-26 16:08:49 +02:00
321393f119 Fix Squiz.Classes.ClassFileName sniffer rule in tests [MAIPOET-1025] 2017-07-26 17:03:43 +03:00
ab3f41302c Merge pull request #1023 from mailpoet/remove-subquery
Use Joins instead subqueries [MAILPOET-1027]
2017-07-26 13:56:32 +03:00
9dff4539e6 Use Joins instead subqueries
[MAILPOET-1027]
2017-07-26 11:46:36 +02:00
6520d5eca3 Merge branch 'master' of github.com:mailpoet/mailpoet 2017-07-25 16:45:42 +03:00
f5b152cdfa Bump up release version to 3.0.0-beta.37.0.0 2017-07-25 16:40:12 +03:00
1d2cab1249 Merge pull request #1021 from mailpoet/prevent-mss-override
Prevent to override sending group [MAILPOET-1024]
2017-07-25 15:24:28 +03:00
90b93bd76e Merge pull request #1018 from mailpoet/prevent_sending_with_broken_newsletter_body
Prevents sending emails when rendered newsletter is broken [MAILPOET-1020]
2017-07-25 13:13:03 +03:00
563ca3e605 Switch ID fields from mediumint to int in DB [MAILPOET-1022] 2017-07-25 13:11:56 +03:00
08bbfcb5e8 Track MixPanel events for Settings [MAILPOET-998] 2017-07-25 10:53:28 +02:00
5572a7f1e5 Prevent to override sending group
[MAILPOET-1024]
2017-07-25 08:32:10 +02:00
9dd326e7db Updates class to use newly added validate() method on the model
Validates existing queue's rendered newsletter body
Cleans code formatting
2017-07-24 12:49:39 -04:00
cf00813c7f Adds new method to validate model on request
Cleans up code formatting
2017-07-24 11:20:43 -04:00
4c898b76b2 Uses ValidModel to validate rendered newsletter body
Removes previous validation method
Does not serialize null values
2017-07-24 11:15:00 -04:00
81a2ba8e03 Adds new validation method for rendered newsletter body 2017-07-24 11:11:22 -04:00
7e6d900b53 Merge pull request #997 from mailpoet/scheduled_task_subscribers
Extract subscribers to a separate table in the Bounce worker [MAILPOET-987]
2017-07-24 17:14:38 +03:00
1a522794d6 Adds method to validate rendered newsletter body on sending queue
Prevents sending queue worker from sending when sending queue's rendered
newsletter body is invalid
2017-07-23 11:50:35 -04:00
197537d6ca Merge pull request #1015 from mailpoet/custom_field_shortcodes
Add missing brackets to custom field shortcodes [MAILPOET-1013]
2017-07-21 21:40:25 -04:00
b212ca801b Merge pull request #1016 from mailpoet/sync_deleted_wp_users
Remove deleted WP users from subscribers [MAILPOET-1012]
2017-07-21 21:39:53 -04:00
db8f3216d7 Add tests for syncing deleted WP users [MAILPOET-1012] 2017-07-21 18:43:29 +03:00
78f9945296 Remove deleted WP users from subscribers [MAILPOET-1012] 2017-07-21 17:48:48 +03:00
0cebcd3965 Add missing brackets to custom field shortcodes [MAILPOET-1013] 2017-07-21 14:40:56 +03:00
4062e0662d Merge pull request #1009 from mailpoet/share_data_kb_link
Update link to "Share your data" in Welcome tabs [MAILPOET-1010]
2017-07-20 19:24:53 +02:00
20b7e82d3c Merge pull request #1014 from mailpoet/new-poll
Update poll [MAILPOET-1016]
2017-07-20 19:16:36 +02:00
9ab8a80567 Merge pull request #992 from mailpoet/circleci
Set up CircleCI tests to run on PHP 5.6 and 7.1 [MAILPOET-986]
2017-07-20 09:55:44 -04:00
f98d02121b Fix BatchIterator skipping rows when subscribers' processed status is modified [MAILPOET-987] 2017-07-20 16:31:27 +03:00
e68e212ad0 Update poll
[MAILPOET-1016]
2017-07-20 12:48:55 +01:00
cb1a1e51ba Use fixtures instead of IDs in the sheduled task subscriber test [MAILPOET-987] 2017-07-20 13:26:31 +03:00
b42d8e68d9 Rename removeSubscribers() method and encapsulate task completion logic [MAILPOET-987] 2017-07-20 11:22:47 +03:00
e88d130ebb Remove side-effects from Update page styles [MAILPOET-1001]
Example: Premium page comparison table rows centered
2017-07-20 08:59:03 +01:00
c924778d50 Rename unprocessed status, refactor counting a bit more clearly [MAILPOET-987] 2017-07-20 10:56:34 +03:00
a9051c6d09 merging qa_js and php5 builds to gain execution time 2017-07-19 19:11:35 -04:00
f6243b5d79 Merge pull request #1005 from mailpoet/mixpanel-more-data
MixPanel: extend summarized data collection [MAILPOET-974]
2017-07-19 20:25:14 +03:00
68c0b93586 Fix Premium page scaling on mobile screens [MAILPOET-1001] 2017-07-19 18:14:50 +01:00
0e8be8040c Move custom page styles from views to a separate *.styl file [MAILPOET-1001] 2017-07-19 18:14:50 +01:00
f3ea548d65 Update the Premium page [MAILPOET-1001] 2017-07-19 18:14:50 +01:00
1ab6be8acd Fix Notice and remove redundant field
[MAILPOET-974]
2017-07-19 16:16:08 +01:00
c413acd93d display a warning when unable to connect to database 2017-07-19 16:05:34 +01:00
7614a4d8dc Update link to "Share your data" in Welcome tabs [MAILPOET-1010] 2017-07-19 10:27:59 +03:00
4a94c29b85 Merge pull request #1008 from mailpoet/model_unit_test_update
Uses MailPoet's Model class vs. Idiorm's in test
2017-07-19 09:32:51 +03:00
b5b9531ff3 Merge pull request #1006 from mailpoet/premium_mixpanel
Add handlers for Premium events tracking, expose Premium version to JS [PREMIUM-24]
2017-07-18 16:14:24 +02:00
e2a048a65f Uses MP Model class vs. Idiorm's in test 2017-07-18 09:49:01 -04:00
c28726f118 Release 3.0.0-beta.36.3.1 2017-07-18 13:58:35 +03:00
7c74885669 Merge pull request #1003 from mailpoet/php53_listing_fix
Adds asArray() method to the base Model class [MAILPOET-1007]
2017-07-17 20:00:10 +03:00
5f74f34cba Adds notes explaining the fix 2017-07-17 12:15:39 -04:00
d6ef526a9d Add handlers for Premium events tracking, expose Premium version [PREMIUM-24] 2017-07-17 18:57:18 +03:00
0da0507e0a Fixes SMTP timing out on slow hosts
Increases default connection timeout value
Adds filter to specify custom connection timeout value

[MAILPOET-1004]
2017-07-17 15:21:33 +01:00
3f03c985bf Collect plugins info for analytics
[MAILPOET-974]
2017-07-17 14:46:26 +01:00
77f6e13aa3 Merge pull request #999 from mailpoet/url_extraction_fix
Replaces URL extraction regex with DOM parser [MAILPOET-1003]
2017-07-17 16:32:59 +03:00
213d0e8627 Distinguishes between link TYPE and link CATEGORY NAME
Uses $replacement_link variable for clarity
2017-07-17 09:14:11 -04:00
1d019bc11e Merge pull request #1004 from mailpoet/filter_to_whitelist_plugins_from_conflict_resolver
Adds filters to whitelist styles/scripts unloaded by our plugin [MAILPOET-1008]
2017-07-17 15:11:37 +02:00
ca12487416 moving shell code into a bash file 2017-07-17 11:38:41 +00:00
c5d42a5033 Merge pull request #995 from mailpoet/welcome-tab-update
Welcome tab update [MAILPOET-979]
2017-07-17 07:17:26 -04:00
3b079440b5 try without sharing workspace 2017-07-17 10:52:48 +00:00
419697991f Adding workflows to CircleCI
Splitting build into 2 builds:

- `build_qa_js`: Builds the project and runs the qa checks and JS tests.

- `php_5`: Runs the PHP tests with PHP 5.6.3

and adding a new build

- `php_7`: Runs the PHP tests with PHP 7.1
2017-07-17 10:26:17 +00:00
d119a1e5fa Send settings values to Mixpanel
[MAILPOET-974]
2017-07-17 11:15:29 +01:00
1bd4264dd5 Fix code style
[MAILPOET-979]
2017-07-17 09:20:28 +01:00
21ee60b7d7 Merge pull request #1002 from mailpoet/newsletter_duplication_update
Resets sent at data when duplicating a sent newsletter [MAILPOET-1005]
2017-07-17 10:07:29 +02:00
849ca27d1f Adds filters to whitelist styles or scripts 2017-07-15 14:57:40 -04:00
a4dad46fb7 Adds asArray() method to the base Model that's used as proxy for
Idiorm's as_array()
2017-07-15 14:25:50 -04:00
fac4c8fb41 Merge pull request #1000 from mailpoet/fix-frequency-smtp
Fix default frequency
2017-07-15 13:04:40 -04:00
cdc87c23ea Resets sent at data when duplicating a sent newsletter 2017-07-15 13:00:25 -04:00
34d09ce0c9 Sets mailer log error when queue cannot be saved during newsletter
pre-processing
2017-07-14 23:15:14 -04:00
fe9ae392f2 Replaces URL extraction regex with DOM parser
Simplifies link replacement logic
Cleans up code
2017-07-14 23:15:08 -04:00
9501640f4f Prevents double UTF-8 encoding 2017-07-14 14:03:48 -04:00
ad028ab55d Fix devault frequency 2017-07-14 08:01:54 +01:00
74cb8d9735 Merge pull request #998 from mailpoet/settings_flex_fix
Fix broken layout in IE 11 on a big screen + make it look like in previous versions [MAILPOET-1002]
2017-07-13 14:11:24 -04:00
381608df22 Fix broken layout in IE 11 on a big screen + make it look like in previous versions [MAILPOET-1002] 2017-07-13 21:03:24 +03:00
6aca598dc3 Fix tests: unneeded migration was triggered by resetting the DB version to 1.0.0 (after EnvTest)
This caused errors when pinging the daemon [MAILPOET-940]
2017-07-13 16:26:53 +03:00
acbe2e383a Add tests [MAILPOET-940] 2017-07-13 13:27:28 +03:00
55d3b67a2a Extract common and task-specific methods for subscribers [MAILPOET-940] 2017-07-13 13:27:28 +03:00
c02394b576 Prepare subscribers in the Bounce worker DB-side for performance reasons [MAILPOET-940] 2017-07-13 13:27:27 +03:00
6a9b8d88c2 Extract subscribers to a separate table in the Bounce worker [MAILPOET-940] 2017-07-13 13:27:27 +03:00
b24c51d800 Merge pull request #996 from mailpoet/add-extra-text
Add extra text on Send with... [MAILPOET-954]
2017-07-13 13:24:22 +03:00
d3a3d3b277 Merge pull request #993 from mailpoet/duplicate-content
Add an option to duplicate content blocks [MAILPOET-968]
2017-07-13 13:14:36 +03:00
3dcff8eb8a Update styles to handle more text
[MAILPOET-954]
2017-07-13 09:41:08 +01:00
401339244c Merge pull request #991 from mailpoet/fix-duplicate-smtp
Remove a duplicate field [MAILPOET-995]
2017-07-12 19:30:37 +03:00
653ecdc4d0 Add extra copy
[MAILPOET-954]
2017-07-12 16:54:49 +01:00
88d5952684 Remove duplicate id
[MAILPOET-979]
2017-07-12 14:55:53 +01:00
c4d6c19c67 Merge pull request #990 from mailpoet/eslint-no-var
Eslint fixes [MAILPOET-994]
2017-07-12 15:51:26 +02:00
0554a84f77 Apply new styles
[MAILPOET-979]
2017-07-12 13:38:56 +01:00
c01b57a383 Add poll
[MAILPOET-979]
2017-07-12 10:28:35 +01:00
65f4f493f1 Add sharing section
[MAILPOET-979]
2017-07-12 10:09:17 +01:00
8a7ea791b0 Move button to see all changelogs
[MAILPOET-979]
2017-07-12 09:35:10 +01:00
1823985172 Display only one set of changelog
[MAILPOET-979]
2017-07-12 09:19:22 +01:00
e735b5f3e0 Replace title
[MAILPOET-979]
2017-07-12 08:44:04 +01:00
cd5c57b7e9 Merge pull request #994 from mailpoet/fixing-readme
Fixing 'Get in touch' URL [MAILPOET-996]
2017-07-11 13:35:36 -04:00
fbf59723d8 Fixing 'Get in touch' URL 2017-07-11 16:57:56 +00:00
2e166a233b Update duplicate icon
[MAILPOET-968]
2017-07-11 17:19:41 +01:00
1608ba5893 Remove a duplicate field
[MAILPOET-995]
2017-07-11 15:39:22 +01:00
9d7eb8038d Fix eslint rule arrow-parens 2017-07-11 14:31:19 +01:00
c9c78a7160 Fix eslint rule indent 2017-07-11 14:31:19 +01:00
53df10dc2b Fix eslint rule eol-last 2017-07-11 14:31:19 +01:00
a746c124a3 Fix eslint rule object-curly-spacing 2017-07-11 14:31:19 +01:00
ca5a5301a8 Fix eslint rule no-var 2017-07-11 14:31:19 +01:00
7a2aaa86cf Tests that populator method is not run for versions below
3.0.0-beta.36.2.1
2017-07-11 14:26:29 +01:00
b626c7ea3c Release 3.0.0-beta.36.3.0 2017-07-11 12:50:33 +01:00
695152e947 Allow duplicate content
[MAILPOET-968]
2017-07-11 10:53:52 +01:00
a36fe400ed Merge pull request #966 from mailpoet/mp2tomp3migration
Settings migration from MP2 to MP3
2017-07-11 12:43:19 +03:00
22bb971db5 Merge pull request #980 from mailpoet/newsletter_listing_update
Sorts standard/history notification records by sent_at date in listings [MAILPOET-932]
2017-07-11 12:43:06 +03:00
b88452c5a2 Fixed: the "Skip Import" link was hidden after unit tests 2017-07-10 18:37:23 +02:00
4a2f9ad1f9 Remove unused code 2017-07-10 18:37:22 +02:00
593a7de9fe New: Add extra text when the migration is completed 2017-07-10 18:37:21 +02:00
e5ecf870c7 Fixed: SendGrid provider was selected even when Sendgrid was not selected in MP2 2017-07-10 18:37:21 +02:00
11cc97b201 Fixed: Unit tests failed if the database prefix was not "wp_" 2017-07-10 18:37:20 +02:00
e352b5bfad New: Migrate installation date 2017-07-10 18:37:19 +02:00
334448c964 New: Add paragraph below "What will be kept in MailPoet 3" 2017-07-10 18:37:18 +02:00
7d8d535cb3 New: Hide the progress bar and the logs if the import has not started yet 2017-07-10 18:37:17 +02:00
68988edd7e Fixed: Change the emails number if the frequency is greater than 15 minutes 2017-07-10 18:37:17 +02:00
f164e9bb95 Fixed: Leave the Newsletter task scheduler (cron) to its default value 2017-07-10 18:37:16 +02:00
fe83435d14 Fixed: "Subscribe in comments" always on 2017-07-10 18:37:15 +02:00
453b7683bc Fixed: SendGrid method and provider were not selected 2017-07-10 18:37:14 +02:00
25625b1ce1 Add unit tests for the settings migration 2017-07-10 18:37:13 +02:00
048d71164a Migrate the settings 2017-07-10 18:31:51 +02:00
24e682e92e Corrects typo 2017-07-10 12:20:37 -04:00
23d90c9f73 Merge pull request #985 from mailpoet/fix-mailpoet-activation
Fix firefox double submit [MAILPOET-992]
2017-07-10 15:53:17 +03:00
a9f190661f Remove unnecessary return statement [MAILPOET-992] 2017-07-10 15:51:19 +03:00
c1e56e5fa1 Update button type to prevent form submit
[MAILPOET-992]
2017-07-10 13:28:06 +01:00
e2bb2679e4 Fix firefox double submit
[MAILPOET-992]
2017-07-10 12:07:07 +01:00
d49530da0f Merge pull request #979 from mailpoet/update-readme
Update readme [MAILPOET-978]
2017-07-10 13:58:36 +03:00
0bc28ef3c7 Remove duplication
[MAILPOET-978]
2017-07-10 11:27:08 +01:00
563b7eccb0 Update video link in readme
[MAILPOET-978]
2017-07-10 11:06:42 +01:00
94a9b63136 Merge pull request #984 from mailpoet/fix-mailpoet-activation
Send the correct group to server [MAILPOET-992]
2017-07-10 12:53:48 +03:00
91ff008485 Make key check error messages more descriptive [MAILPOET-990] 2017-07-10 10:34:55 +01:00
cb8fa23c3f Send the correct group to server
[MAILPOET-992]
2017-07-10 09:18:43 +01:00
3499de05e8 Merge pull request #983 from mailpoet/link_tags_replacement_update
Updates the way link tags are replaced in translations [MAILPOET-991]
2017-07-09 12:14:42 +03:00
77ed4d34e8 Uses Helper's replaceLinkTags method to replace activation link in
subscription confirmation email
2017-07-08 12:00:45 -04:00
cfc5f5a88d Allows defining custom link tag 2017-07-08 11:56:51 -04:00
2514d87a00 Uses Helper's replaceLinkTags method to replace tags in translation
strings
2017-07-08 10:31:33 -04:00
23c6750ccc Removes leftover debug condition 2017-07-07 13:12:45 -04:00
bb2af5176b Tests that newsletters can be sorted by sent_at column 2017-07-07 11:05:39 -04:00
95b5206e8b Updates sending queue worker to set newsletter's sent_at field when
newsletter is sent
2017-07-07 11:05:39 -04:00
394118f113 Adds sent_at column to the newsletters table
Modifies populator to update existing newsletters' sent_at field
2017-07-07 11:05:33 -04:00
b691fb5a48 Adds sorting by sent_at to standard & notification history newsletter listings 2017-07-07 11:04:19 -04:00
1ba2492929 Combines Twig's "replaceLink" and Util/Helper's "replaceLinkTags"
methods into one
2017-07-07 11:02:20 -04:00
d08243b0ce Merge pull request #981 from mailpoet/tests_lint_rules
Fix ScopeClosingBrace, EmptyNonVariable sniffer rules in tests [MAILPOET-989]
2017-07-07 11:01:21 -04:00
189656fdc8 Fixes malformed URL 2017-07-07 09:52:08 -04:00
0be872eea9 Fix PHPCompatibility.PHP.EmptyNonVariable rule [MAILPOET-989] 2017-07-07 11:30:16 +03:00
22c39c0092 Fix PEAR.WhiteSpace.ScopeClosingBrace rule in tests [MAILPOET-989] 2017-07-07 11:11:16 +03:00
1b54e356b2 Merge pull request #978 from mailpoet/add-missing-callbacks
Add missing callbacks to ajax calls [MAILPOET-982]
2017-07-06 19:29:30 -04:00
9079d7d4db Merge pull request #977 from mailpoet/subscription_confirmation_update
Prevents repeat subscription confirmation/duplicate welcome notifications [MAILPOET-988]
2017-07-06 22:52:44 +03:00
160b28a632 Merge pull request #976 from mailpoet/editor-drag-and-drop
Fix reverting to mouse cursor auto icon after clicking Draggable object [MAILPOET-965]
2017-07-06 21:54:28 +03:00
03ebb30ac2 Update readme
[MAILPOET-978]
2017-07-06 15:43:04 +01:00
56eac1ae86 Add missing callbacks to ajax calls
[MAILPOET-982]
2017-07-06 13:06:14 +01:00
89da4c9aae Prevents repeat subscription confirmation
Prevents scheduling of duplicate welcome notifications
2017-07-05 19:50:57 -04:00
b510071857 Merge pull request #972 from mailpoet/merge-sending-method
Merge sending method [MAILPOET-953]
2017-07-05 19:59:30 +03:00
4bbec4700d Stub out interact.styleCursor method 2017-07-05 19:45:13 +03:00
29a2af2555 Fix reverting to mouse cursor auto icon after clicking Draggable object 2017-07-05 19:26:33 +03:00
94d57b5287 Make sure all frequency changes are displayed correctly
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
6b7bc8a731 Make sure frequency is always set
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
3dd0f00b7f Fix sending method
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
b58f6cc4e0 Fix broken reporting
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
7c942147ec Show frequency fields on initial page render
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
6fca2061e6 Show fields on initial page render
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
0acc41c887 Pre select the sending method
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
20d1ac81e2 Save correct values
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
cdb7b99728 Merge sending methods
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
54cb838d71 Merge pull request #959 from mailpoet/scheduled_tasks
Refactor simple workers to use a ScheduledTask model [MAILPOET-940]
2017-07-05 17:28:08 +03:00
0b9ea23f0b Temporarily remove pause/resume methods from scheduled tasks [MAILPOET-940] 2017-07-05 17:12:35 +03:00
3e40f768b4 Add migration of simple scheduled tasks [MAILPOET-940] 2017-07-05 17:12:34 +03:00
6e929dcf79 Refactor simple workers to use a ScheduledTask model [MAILPOET-940] 2017-07-05 17:00:23 +03:00
7be01f0e4e Merge pull request #975 from mailpoet/eslint-fix-rules-rad
Bunch of fixed eslint rules
2017-07-05 14:20:24 +03:00
9cb08734a3 Return back function which is needed
[MAILPOET-985]
2017-07-05 11:36:39 +01:00
55fda047f6 Fix prefer-const eslint rule
[MAILPOET-985]
2017-07-05 09:55:00 +01:00
4706c5fc25 Fix prefer-arrow-callback eslint rule
[MAILPOET-985]
2017-07-05 09:48:35 +01:00
3ba857d03f Fix no-unused-vars eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
f39cbe6b55 Fix space-before-function-paren eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
6dc1bf4e95 Fix semi eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
5b65a8f0ac Fix radix eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
583adf86da Fix no-plusplus eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
3b4a1b686d Make build find command work cross-platform (Linux/Mac/Win) [MAILPOET-984] 2017-07-05 08:33:43 +01:00
5390dd8421 Merge branch 'master' of github.com:mailpoet/mailpoet 2017-07-04 18:15:16 +03:00
fe8452711f Bump up release version to 3.0.0-beta.36.2.0 2017-07-04 18:14:04 +03:00
f7cb53de2c Fix composer.lock (remove AspectMock from prod dependencies) 2017-07-04 18:13:17 +03:00
1ac6dd8ccb Switch to using CircleCI 2.0
We end up using Docker images for PHP 5.6 and MySQL, handle our own
Composer and NPM caches.
2017-07-04 15:56:02 +01:00
b1425198b6 Merge pull request #967 from mailpoet/subscription_form_unit_test
Adds test for subscription form submission class [MAILPOET-961]
2017-07-04 14:46:26 +03:00
00a45f3214 Make Codeception report all errors (incl. E_STRICT and E_DEPRECATED) when running tests [MAILPOET-961] 2017-07-04 14:30:15 +03:00
8e46451337 Fixes non static methods called statically (good practice + PHP 7
compatibility)
2017-07-03 19:39:11 -04:00
b0d0cc09c8 Programmatically sets input width after Select2 initialization
[MAILPOET-973]
2017-07-03 17:05:06 +01:00
af559a6fac Merge pull request #971 from mailpoet/tests_qa
Add a copy of MailPoet PHP coding standard for tests [MAILPOET-981]
2017-07-03 18:03:43 +03:00
67acceb968 Merge pull request #965 from mailpoet/posts_dynamic_loading
Add dynamic post loading in Posts widget settings [MAILPOET-971]
2017-07-03 17:18:12 +03:00
703ee7ff71 Fix line breaks in tests [MAILPOET-981] 2017-07-03 16:29:21 +03:00
29491dfd3e Fix active coding standard rule violations in tests [MAILPOET-981] 2017-07-03 16:20:44 +03:00
a49a230983 Disable a few failing rules for further fixing [MAILPOET-981] 2017-07-03 14:57:48 +03:00
b8d285a1d9 Add a copy of MailPoet PHP coding standard for tests [MAILPOET-981] 2017-07-03 14:54:04 +03:00
d5227a9f2c Add tests for PDO exceptions rethrowing [MAILPOET-966] 2017-07-03 10:59:38 +01:00
693117eb40 Merge pull request #964 from mailpoet/add-help
Add help [MAILPOET-949]
2017-07-03 12:06:51 +03:00
f3aab6095e Standardizes helper string look (https://goo.gl/r3jqJ8) 2017-07-03 09:35:01 +01:00
d70587a550 Adds helper text to the bottom of forms view 2017-07-03 09:35:01 +01:00
82226f2c36 Updates existing code that replaces links with the new "replaceLink"
filter
2017-07-03 09:35:01 +01:00
2774101380 Adds Twig filter to replace [link] tags inside strings
Adds unit tests
2017-07-03 09:35:01 +01:00
ee0e3ff95e Move <h1> from react
Wordpress moves admin notices bellow the first heading. If we re-render
our heading inside react router we remove those notices. This way the
admin notice stays in the page.

[MAILPOET-949]
2017-07-03 09:08:30 +01:00
17584dde43 Fix help rendering
[MAILPOET-949]
2017-07-03 08:39:02 +01:00
3ac416de56 Allows passing request data to form submission class
Adds unit tests
2017-07-01 11:17:04 -04:00
1e4c00169f Adds AspectMock library to mock static methods/etc. 2017-07-01 11:15:15 -04:00
11bbf54aad Merge pull request #963 from mailpoet/new-templates
New templates [MAILPOET-922]
2017-06-30 11:37:29 -04:00
688e78560e Add minor corrections [MAILPOET-949] 2017-06-30 12:47:47 +03:00
5ca577a718 Fix lint errors [MAILPOET-966] 2017-06-30 09:29:48 +01:00
605df7dff1 Don't open a second (actually, third) DB connection [MAILPOET-966] 2017-06-30 09:29:48 +01:00
fb1a3a80ff Catch a DB connection exception earlier [MAILPOET-966] 2017-06-30 09:29:48 +01:00
d3db755489 Handle exceptions during initialization better [MAILPOET-966] 2017-06-30 09:29:48 +01:00
a4282b6a3e Don't leak connection data in PDO exceptions [MAILPOET-966] 2017-06-30 09:29:48 +01:00
bc17984030 Fix coffee shop template
[MAILPOET-922]
2017-06-30 09:19:26 +01:00
b823991867 Fix lint errors [MAILPOET-971] 2017-06-29 16:45:12 +03:00
04e238634d Add unit tests [MAILPOET-971] 2017-06-29 15:34:26 +03:00
eba482cc67 Add dynamic post loading in Posts widget settings [MAILPOET-971] 2017-06-29 13:13:36 +03:00
e6663f0f3e Hide a horizontal scrollbar in Posts widget listing [MAILPOET-971] 2017-06-29 13:13:23 +03:00
7fa94a67c9 Add data to system info help page
[MAILPOET-949]
2017-06-29 09:34:28 +01:00
b4be9e1d28 Add notice to system info
[MAILPOET-949]
2017-06-28 16:50:31 +01:00
a7504136a2 Add knowledge base page content
[MAILPOET-949]
2017-06-28 15:36:51 +01:00
43fa12ec08 Add help without any content
[MAILPOET-949]
2017-06-28 15:00:53 +01:00
6723a563ed Add yoga studio thumb
[MAILPOET-922]
2017-06-28 11:27:33 +01:00
15e3e93c99 Add Foodbox thumb
[MAILPOET-922]
2017-06-28 10:57:16 +01:00
cbce789ac8 Add thumbnails to templates
[MAILPOET-922]
2017-06-28 10:57:16 +01:00
97607993fb Add newsletter assets to plugin folder
[MAILPOET-922]
2017-06-28 10:57:16 +01:00
334b119bb3 Add News Day template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
ed0b2e6231 Add Coffee Shop template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
ad7ad05ec7 Add Travel Nomads template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
bfba6d459c Add Faith template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
344eecbf11 Add Chocolate Store template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
14dc022d2d Add Science Weekly template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
eff75cce94 Add Piece Of Cake template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
51084fc57d Add Piece Of Cake template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
298270f86c Add Festival Event template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
fe6d5d1523 Add Take a Hike template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
3cceb32ec1 Add Kick-Off template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
c8251a3bcd Add Discount template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
58888505b5 Add Food box template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
558c493dd7 Add World Cup template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
8cdb7d77f6 Add App Welcome template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
539e6d3ee1 Add Burger Joint template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
c9a0939ddd Remove old templates
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
2add301b9f Bumps up version to 3.0.0-beta.36.1.0 and updates changelog 2017-06-27 16:23:22 -04:00
2d217e416a Merge pull request #960 from mailpoet/fix-build-find
Make build script work on Mac os [MAILPOET-969]
2017-06-27 12:21:25 +03:00
77e0ace951 Merge pull request #961 from mailpoet/post_notification_unit_test_update
Updates the way we test for next run date in newsletter scheduler [MAILPOET-967]
2017-06-27 11:42:30 +03:00
933749f8f0 Merge pull request #941 from mailpoet/mp2tomp3migration
Mp2tomp3migration phase 2
2017-06-26 18:47:58 +03:00
7b13babf3f Merge pull request #958 from mailpoet/improve-ajax-errors
Improve ajax errors [MAILPOET-929]
2017-06-26 17:31:15 +03:00
8c673f78d7 Make build script work on Mac os
[MAILPOET-969]
2017-06-26 15:00:48 +01:00
2285c08c01 Revert "Revert "Improve ajax errors [MAILPOET-929]""
This reverts commit 81c3e2facf.
2017-06-26 13:51:53 +01:00
836b7179e9 Improve ajax errors on form submission
[MAILPOET-929]
2017-06-26 13:43:32 +01:00
f89a728c38 Uses predetermined timestamps from which next run date is calculated 2017-06-24 15:59:28 -04:00
58f2c32362 Allows passing custom time value to calculate the next run date from 2017-06-24 15:59:17 -04:00
5bd6c6533a Fixed: Skip button is vanishinig by itself in seconds 2017-06-21 19:24:07 +02:00
f539860922 Remove trailing spaces 2017-06-21 19:24:06 +02:00
536267c8f5 Fixed: Form fields were not inactive when used 2017-06-21 19:24:05 +02:00
5b905a60e8 Fixed: Unit test testReplaceMP2Shortcodes 2017-06-21 19:24:05 +02:00
5e152ebaa1 Fixed: Wrong date format on front-end 2017-06-21 19:24:04 +02:00
2c35c7061e Fixed: Display "Invalid e-mail" on the front-end whereas the e-mail is correct 2017-06-21 19:24:03 +02:00
2515dcf4ce Fixed: Wrong HTML encoding of the text fields 2017-06-21 19:24:02 +02:00
9458bf7418 Add Unit tests for MP2 to MP3 migration phase 2 2017-06-21 19:24:01 +02:00
44bf4b98b8 MP2 to MP3 migration phase 2: Forms migration 2017-06-21 19:24:01 +02:00
627 changed files with 35789 additions and 14913 deletions

View File

@ -1,8 +0,0 @@
#!/usr/bin/ruby
path = "/tmp"
Dir.mkdir(path) if !File.exists?(path)
File.open("#{path}/mailpoet-#{Time.now.to_f}.txt", "w") do |f|
sleep 5
f.puts ARGV.inspect
$stdin.each_line { |line| f.puts line }
end

View File

@ -1,3 +0,0 @@
; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
; http://php.net/sendmail-path
sendmail_path = /home/ubuntu/mailpoet/.circle_ci/fake-sendmail.rb

View File

@ -3,11 +3,11 @@ Listen 8080
<VirtualHost *:8080> <VirtualHost *:8080>
UseCanonicalName Off UseCanonicalName Off
ServerName mailpoet.loc ServerName mailpoet.loc
DocumentRoot /home/ubuntu/mailpoet/wordpress DocumentRoot /home/circleci/mailpoet/wordpress
DirectoryIndex index.php DirectoryIndex index.php
LogLevel notice LogLevel notice
<Directory /home/ubuntu/mailpoet/wordpress> <Directory /home/circleci/mailpoet/wordpress>
Require all granted Require all granted
</Directory> </Directory>
</VirtualHost> </VirtualHost>

138
.circleci/config.yml Normal file
View File

@ -0,0 +1,138 @@
version: 2
jobs:
qa_js_php5:
working_directory: /home/circleci/mailpoet
docker:
- image: circleci/php:5.6.30-apache-browsers
- image: circleci/mysql:5.7
environment:
TZ: /usr/share/zoneinfo/Etc/UTC
steps:
- checkout
- run:
name: "Set up virtual host"
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- restore_cache:
key: npm-{{ checksum "package.json" }}
- run:
name: "Set up test environment"
command: source ./.circleci/setup.bash && setup php5
- save_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths:
- vendor
- save_cache:
key: npm-{{ checksum "package.json" }}
paths:
- node_modules
- run:
name: "QA Scripts"
command: ./do qa
- run:
name: "Preparing test results folder"
command: mkdir test-results
- run:
name: "JS tests"
command: |
mkdir test-results/mocha
./do t:j test-results/mocha/junit.xml
- run:
name: "PHP Unit tests"
command: |
WP_TEST_PATH="/home/circleci/mailpoet/wordpress" ./do t:u --xml
- store_test_results:
path: test-results/mocha
- store_artifacts:
path: test-results/mocha
destination: mocha
- store_test_results:
path: tests/_output
- store_artifacts:
path: tests/_output
destination: codeception
- store_artifacts:
path: /tmp/fake-mailer/
destination: fake-mailer
acceptance_tests:
working_directory: /home/circleci/mailpoet
machine: true
steps:
- checkout
- run:
name: "Set up virtual host"
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- restore_cache:
key: npm-{{ checksum "package.json" }}
- run:
name: "Set up test environment"
command: |
sudo apt-get update
sudo apt-get install circleci-php-5.6.23
sudo rm /usr/bin/php
sudo ln -s /opt/circleci/php/5.6.23/bin/php /usr/bin/php
# Install NodeJS+NPM
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install nodejs build-essential
# install plugin dependencies
curl -sS https://getcomposer.org/installer | php
php composer.phar install
./do install
./do compile:all --env production
- save_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths:
- vendor
- save_cache:
key: npm-{{ checksum "package.json" }}
paths:
- node_modules
- run:
name: Run acceptance tests
command: |
docker-compose run codeception --steps --debug -vvv --html --xml
- store_artifacts:
path: tests/_output
- store_test_results:
path: tests/_output
php7:
working_directory: /home/circleci/mailpoet
docker:
- image: circleci/php:7.1-apache-browsers
- image: circleci/mysql:5.7
environment:
TZ: /usr/share/zoneinfo/Etc/UTC
steps:
- checkout
- run:
name: "Set up virtual host"
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- restore_cache:
key: npm-{{ checksum "package.json" }}
- run:
name: "Set up test environment"
command: source ./.circleci/setup.bash && setup php7
- run:
name: "PHP Unit tests"
command: |
WP_TEST_PATH="/home/circleci/mailpoet/wordpress" ./do t:u --xml
- store_test_results:
path: tests/_output
- store_artifacts:
path: tests/_output
destination: codeception
- store_artifacts:
path: /tmp/fake-mailer/
destination: fake-mailer
workflows:
version: 2
build_and_test:
jobs:
- qa_js_php5
- php7
- acceptance_tests

17
.circleci/fake-sendmail.php Executable file
View File

@ -0,0 +1,17 @@
#!/usr/local/bin/php
<?php
$path = "/tmp/fake-mailer";
if(!file_exists($path)) {
mkdir($path);
}
$filename = $path . '/mailpoet-' . microtime(true) . '.txt';
$file_handle = fopen($filename, "w");
$call_arguments = print_r($argv, true) . "\n";
fwrite($file_handle, $call_arguments);
while($line = fgets(STDIN)) {
fwrite($file_handle, $line);
}
fclose($file_handle);

View File

@ -0,0 +1,9 @@
; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
; http://php.net/sendmail-path
sendmail_path = /usr/local/bin/fake-sendmail.php
[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = UTC

49
.circleci/setup.bash Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env bash
function setup {
local version=$1
# install PHP dependencies for WordPress
if [[ $version == "php7" ]]; then
echo "deb http://packages.dotdeb.org jessie all" | sudo tee -a /etc/apt/sources.list.d/dotdeb.list
echo "deb-src http://packages.dotdeb.org jessie all" | sudo tee -a /etc/apt/sources.list.d/dotdeb.list
wget -qO - http://www.dotdeb.org/dotdeb.gpg | sudo apt-key add -
sudo apt-get update
sudo apt-get install mysql-client php7.0-mysql zlib1g-dev
sudo docker-php-ext-install mysqli pdo pdo_mysql zip
else
sudo apt-get update
sudo apt-get install mysql-client php5-mysql zlib1g-dev
sudo docker-php-ext-install mysql mysqli pdo pdo_mysql zip
fi
# Add a fake sendmail mailer
sudo cp ./.circleci/fake-sendmail.php /usr/local/bin/
# configure Apache
sudo cp ./.circleci/mailpoet_php.ini /usr/local/etc/php/conf.d/
sudo cp ./.circleci/apache/mailpoet.loc.conf /etc/apache2/sites-available
sudo a2ensite mailpoet.loc
sudo a2enmod rewrite
sudo service apache2 restart
# Install NodeJS+NPM
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install nodejs build-essential
# install plugin dependencies
curl -sS https://getcomposer.org/installer | php
./composer.phar install
./do install
# Set up Wordpress
mysql -h 127.0.0.1 -u root -e "create database wordpress"
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
./wp-cli.phar core download --allow-root --path=wordpress
# Generate `wp-config.php` file with debugging enabled
echo "define(\"WP_DEBUG\", true);" | ./wp-cli.phar core config --allow-root --dbname=wordpress --dbuser=root --dbhost=127.0.0.1 --path=wordpress --extra-php
sed -i "s/\$table_prefix = 'wp_';/\$table_prefix = 'mp_';/" ./wordpress/wp-config.php
# Install WordPress
./wp-cli.phar core install --allow-root --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc:8080 --title=WordPress --path=wordpress
# Softlink plugin to plugin path
ln -s ../../.. wordpress/wp-content/plugins/mailpoet
./wp-cli.phar plugin activate mailpoet --path=wordpress
# Create .env file with correct path to WP installation
# TODO: Remove this line after PR gets merged and CircleCI env variables change
echo "WP_TEST_PATH=\"/home/circleci/mailpoet/wordpress\"" > .env
}

View File

@ -9,91 +9,48 @@
}, },
"rules": { "rules": {
"import/no-amd": 0, "import/no-amd": 0,
"comma-dangle": 0,
"space-before-function-paren": 0,
"prefer-arrow-callback": 0, "prefer-arrow-callback": 0,
"no-undef": 0,
"key-spacing": 0,
"radix": 0, "radix": 0,
"no-alert": 0, "no-alert": 0,
"block-scoped-var": 0,
"guard-for-in": 0, "guard-for-in": 0,
"no-prototype-builtins": 0, "no-prototype-builtins": 0,
"no-restricted-syntax": 0, "no-restricted-syntax": 0,
"newline-per-chained-call": 0,
"no-useless-concat": 0, "no-useless-concat": 0,
"no-multi-spaces": 0,
"no-nested-ternary": 0, "no-nested-ternary": 0,
"semi-spacing": 0,
"no-sequences": 0, "no-sequences": 0,
"no-useless-return": 0, "no-useless-return": 0,
"array-callback-return": 0, "array-callback-return": 0,
"new-cap": 0, "new-cap": 0,
"no-return-assign": 0,
"no-continue": 0, "no-continue": 0,
"no-new": 0, "no-new": 0,
"no-cond-assign": 0,
"space-unary-ops": 0,
"no-redeclare": 0, "no-redeclare": 0,
"no-console": 0, "no-console": 0,
"no-empty": 0, "no-empty": 0,
"no-extra-semi": 0,
"no-useless-escape": 0, "no-useless-escape": 0,
"comma-spacing": 0,
"wrap-iife": 0, "wrap-iife": 0,
"no-unused-expressions": 0,
"block-spacing": 0,
"computed-property-spacing": 0,
"no-plusplus": 0, "no-plusplus": 0,
"array-bracket-spacing": 0,
"lines-around-directive": 0,
"no-unreachable": 0,
"default-case": 0, "default-case": 0,
"no-lonely-if": 0, "no-lonely-if": 0,
"space-before-blocks": 0,
"no-unneeded-ternary": 0,
"no-mixed-operators": 0, "no-mixed-operators": 0,
"eqeqeq": 0, "eqeqeq": 0,
"space-in-parens": 0,
"semi": 0,
"max-len": 0, "max-len": 0,
"no-multi-assign": 0,
"no-trailing-spaces": 0,
"global-require": 0, "global-require": 0,
"no-throw-literal": 0, "no-throw-literal": 0,
"no-extra-bind": 0, "no-extra-bind": 0,
"one-var-declaration-per-line": 0,
"consistent-return": 0, "consistent-return": 0,
"no-shadow": 0, "no-shadow": 0,
"no-underscore-dangle": 0, "no-underscore-dangle": 0,
"brace-style": 0, "brace-style": 0,
"no-else-return": 0, "no-else-return": 0,
"no-use-before-define": 0, "no-use-before-define": 0,
"one-var": 0,
"camelcase": 0, "camelcase": 0,
"spaced-comment": 0,
"quotes": 0,
"padded-blocks": 0, "padded-blocks": 0,
"object-curly-spacing": 0,
"strict": 0, "strict": 0,
"vars-on-top": 0,
"no-var": 0,
"space-infix-ops": 0, "space-infix-ops": 0,
"no-unused-vars": 0,
"object-shorthand": 0, "object-shorthand": 0,
"new-parens": 0, "new-parens": 0,
"no-param-reassign": 0,
"keyword-spacing": 0,
"eol-last": 0, "eol-last": 0,
"dot-notation": 0, "dot-notation": 0,
"linebreak-style": 0,
"indent": 0,
"quote-props": 0,
"prefer-template": 0, "prefer-template": 0,
"func-names": 0 "func-names": 0
} }

View File

@ -10,99 +10,52 @@
"jsx": true "jsx": true
} }
}, },
"settings": {
"import/resolver": "webpack"
},
"rules": { "rules": {
"comma-dangle": ["error", "always-multiline"],
"import/no-amd": 0, "import/no-amd": 0,
"react/no-multi-comp": 0, "react/no-multi-comp": 0,
"react/sort-comp": 0, "react/sort-comp": 0,
"react/jsx-max-props-per-line": 0, "react/jsx-max-props-per-line": 0,
"react/prop-types": 0, "react/prop-types": 0,
"react/jsx-first-prop-new-line": 0, "react/jsx-first-prop-new-line": 0,
"react/jsx-indent-props": 0,
"react/no-is-mounted": 0, "react/no-is-mounted": 0,
"react/jsx-no-target-blank": 0, "react/jsx-no-target-blank": 0,
"react/no-render-return-value": 0, "react/no-render-return-value": 0,
"react/jsx-boolean-value": 0, "react/jsx-boolean-value": 0,
"react/jsx-indent": 0,
"react/jsx-no-bind": 0, "react/jsx-no-bind": 0,
"react/no-array-index-key": 0, "react/no-array-index-key": 0,
"react/self-closing-comp": 0, "react/self-closing-comp": 0,
"react/jsx-tag-spacing": 0,
"react/jsx-closing-bracket-location": 0, "react/jsx-closing-bracket-location": 0,
"react/no-string-refs": 0, "react/no-string-refs": 0,
"react/jsx-curly-spacing": 0,
"react/no-did-mount-set-state": 0, "react/no-did-mount-set-state": 0,
"react/prefer-stateless-function": 0, "react/prefer-stateless-function": 0,
"jsx-a11y/label-has-for": 0, "jsx-a11y/label-has-for": 0,
"jsx-a11y/no-static-element-interactions": 0, "jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/alt-text": 0, "jsx-a11y/alt-text": 0,
"comma-dangle": 0,
"prefer-arrow-callback": 0,
"func-names": 0, "func-names": 0,
"space-before-function-paren": 0,
"object-shorthand": 0, "object-shorthand": 0,
"no-bitwise": 0, "no-bitwise": 0,
"arrow-body-style": 0, "arrow-body-style": 0,
"prefer-template": 0, "prefer-template": 0,
"eol-last": 0,
"no-unused-vars": 0,
"no-plusplus": 0,
"semi": 0,
"keyword-spacing": 0,
"default-case": 0, "default-case": 0,
"quote-props": 0,
"indent": 0,
"prefer-const": 0,
"arrow-parens": 0,
"array-callback-return": 0, "array-callback-return": 0,
"consistent-return": 0, "consistent-return": 0,
"no-unreachable": 0,
"no-extra-semi": 0,
"import/no-unresolved": 0,
"import/extensions": 0, "import/extensions": 0,
"import/no-extraneous-dependencies": 0, "import/no-extraneous-dependencies": 0,
"camelcase": 0, "camelcase": 0,
"template-curly-spacing": 0,
"quotes": 0,
"radix": 0,
"eqeqeq": 0, "eqeqeq": 0,
"no-lonely-if": 0,
"space-unary-ops": 0, "space-unary-ops": 0,
"block-scoped-var": 0,
"no-extra-bind": 0,
"no-multi-spaces": 0,
"class-methods-use-this": 0,
"key-spacing": 0,
"no-multiple-empty-lines": 0,
"space-in-parens": 0,
"no-case-declarations": 0,
"array-bracket-spacing": 0,
"newline-per-chained-call": 0,
"no-else-return": 0,
"max-len": 0,
"comma-spacing": 0,
"no-useless-concat": 0,
"no-unused-expressions": 0,
"no-sequences": 0,
"no-extra-boolean-cast": 0,
"dot-notation": 0, "dot-notation": 0,
"no-param-reassign": 0,
"no-shadow": 0, "no-shadow": 0,
"one-var": 0,
"no-alert": 0, "no-alert": 0,
"one-var-declaration-per-line": 0,
"no-script-url": 0, "no-script-url": 0,
"wrap-iife": 0, "wrap-iife": 0,
"no-var": 0,
"vars-on-top": 0,
"space-infix-ops": 0, "space-infix-ops": 0,
"no-irregular-whitespace": 0, "no-irregular-whitespace": 0,
"padded-blocks": 0, "padded-blocks": 0,
"no-underscore-dangle": 0, "no-underscore-dangle": 0
"object-curly-spacing": 0,
"no-undef": 0
} }
} }

View File

@ -8,40 +8,9 @@
"ecmaVersion": 6 "ecmaVersion": 6
}, },
"rules": { "rules": {
"import/no-amd": 0, // Exceptions
"no-undef": 0,
"one-var": 0,
"indent": 0,
"linebreak-style": 0,
"no-whitespace-before-property": 0,
"object-property-newline": 0,
"global-require": 0,
"semi": 0,
"keyword-spacing": 0,
"no-bitwise": 0,
"no-multi-assign": 0,
"newline-per-chained-call": 0,
"no-spaced-func": 0,
"func-call-spacing": 0,
"max-len": 0,
"space-unary-ops": 0,
"quotes": 0,
"no-unused-vars": 0,
"no-unused-expressions": 0,
"no-underscore-dangle": 0,
"quote-props": 0,
"no-shadow": 0,
"padded-blocks": 0,
"comma-dangle": 0,
"vars-on-top": 0,
"space-before-blocks": 0,
"object-curly-spacing": 0,
"no-param-reassign": 0,
"one-var-declaration-per-line": 0,
"func-names": 0, "func-names": 0,
"space-before-function-paren": 0 // Temporary
"no-underscore-dangle": 0
} }
} }

2
.gitignore vendored
View File

@ -2,8 +2,8 @@
TODO TODO
composer.phar composer.phar
/vendor /vendor
/vendor_backup
tests/_output/* tests/_output/*
tests/acceptance.suite.yml
tests/_support/_generated/* tests/_support/_generated/*
node_modules node_modules
.env .env

39
Dockerfile Normal file
View File

@ -0,0 +1,39 @@
FROM php:5.6-cli
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN apt-get update && \
apt-get -y install \
git \
zlib1g-dev \
libssl-dev \
mysql-client \
sudo less \
--no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
docker-php-ext-install bcmath zip mysqli pdo pdo_mysql && \
echo "date.timezone = UTC" >> /usr/local/etc/php/php.ini && \
curl -sS https://getcomposer.org/installer | php -- \
--filename=composer \
--install-dir=/usr/local/bin && \
composer global require --optimize-autoloader "hirak/prestissimo" && \
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
chmod +x wp-cli.phar && \
mv wp-cli.phar /usr/local/bin/wp
# Prepare application
WORKDIR /repo
# Install vendor
COPY ./composer.json /repo/composer.json
# Add source-code
COPY . /repo
WORKDIR /wp-core/wp-content/plugins/mailpoet
ENV WP_TEST_PATH=/wp-core
ADD docker-entrypoint.sh /
RUN ["chmod", "+x", "/docker-entrypoint.sh"]

View File

@ -180,3 +180,21 @@ Run 'svn copy ...' to tag the release
It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process. It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process.
If you're confident, execute `./do publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables. If you're confident, execute `./do publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables.
# Acceptance testing
We are using Gravity Flow plugin's setup as an example for our acceptance test suite: https://www.stevenhenty.com/learn-acceptance-testing-deeply/
From the article above:
_Windows users only: enable hard drive sharing in the Docker settings._
The browser runs in a docker container. You can use a VNC client to watch the test run, follow instructions in official
repo: https://github.com/SeleniumHQ/docker-selenium
If youre on a Mac, you can open vnc://localhost:5900 in Safari to watch the tests running in Chrome. If youre on Windows, youll need a VNC client. Password: secret.
To run tests:
```sh
$ ./do test:acceptance
```

View File

@ -90,6 +90,7 @@ class RoboFile extends \Robo\Tasks {
$css_files = array( $css_files = array(
'assets/css/src/admin.styl', 'assets/css/src/admin.styl',
'assets/css/src/admin-global.styl',
'assets/css/src/newsletter_editor/newsletter_editor.styl', 'assets/css/src/newsletter_editor/newsletter_editor.styl',
'assets/css/src/public.styl', 'assets/css/src/public.styl',
'assets/css/src/rtl.styl', 'assets/css/src/rtl.styl',
@ -154,9 +155,8 @@ class RoboFile extends \Robo\Tasks {
function testUnit($opts=['file' => null, 'xml' => false]) { function testUnit($opts=['file' => null, 'xml' => false]) {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$command = 'vendor/bin/codecept run unit -f '.(($opts['file']) ? $opts['file'] : ''); $command = 'vendor/bin/codecept run unit -c codeception.unit.yml -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) { if($opts['xml']) {
$command .= ' --xml'; $command .= ' --xml';
@ -166,9 +166,8 @@ class RoboFile extends \Robo\Tasks {
function testCoverage($opts=['file' => null, 'xml' => false]) { function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$command = join(' ', array( $command = join(' ', array(
'vendor/bin/codecept run', 'vendor/bin/codecept run unit -c codeception.unit.yml ',
(($opts['file']) ? $opts['file'] : ''), (($opts['file']) ? $opts['file'] : ''),
'--coverage', '--coverage',
($opts['xml']) ? '--coverage-xml' : '--coverage-html' ($opts['xml']) ? '--coverage-xml' : '--coverage-html'
@ -199,16 +198,26 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec($command); return $this->_exec($command);
} }
function testDebug() { function testDebug($opts=['file' => null, 'xml' => false]) {
$this->_exec('vendor/bin/codecept build');
$this->loadEnv(); $this->loadEnv();
return $this->_exec('vendor/bin/codecept run unit --debug'); $this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
$command = 'vendor/bin/codecept run unit -c codeception.unit.yml --debug -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
}
function testAcceptance() {
return $this->_exec('COMPOSE_HTTP_TIMEOUT=200 docker-compose run codeception --steps --debug -vvv');
} }
function testFailed() { function testFailed() {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build'); $this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
return $this->_exec('vendor/bin/codecept run -g failed'); return $this->_exec('vendor/bin/codecept run -c codeception.unit.yml -g failed');
} }
function qa() { function qa() {
@ -235,14 +244,25 @@ class RoboFile extends \Robo\Tasks {
} else { } else {
$severityFlag = '-n'; $severityFlag = '-n';
} }
return $this->_exec( return $this->collectionBuilder()
'./vendor/bin/phpcs '. ->taskExec(
'--standard=./tasks/code_sniffer/MailPoet '. './vendor/bin/phpcs '.
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'. '--standard=./tasks/code_sniffer/MailPoet '.
'./lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '. '--runtime-set testVersion 5.3-7.0 '.
'lib/ '. '--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
$severityFlag './lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '.
); 'lib/ '.
$severityFlag
)
->taskExec(
'./vendor/bin/phpcs '.
'--standard=./tasks/code_sniffer/MailPoet '.
'--runtime-set testVersion 5.4-7.0 '.
'--ignore=./tests/unit/_bootstrap.php '.
'tests/unit/ '.
$severityFlag
)
->run();
} }
function svnCheckout() { function svnCheckout() {
@ -269,14 +289,16 @@ class RoboFile extends \Robo\Tasks {
} }
function svnPublish($opts = ['force' => false]) { function svnPublish($opts = ['force' => false]) {
$this->loadWPFunctions(); $this->loadEnv();
$svn_dir = ".mp_svn"; $svn_dir = ".mp_svn";
$plugin_data = get_plugin_data('mailpoet.php', false, false); $plugin_version = $this->getPluginVersion('mailpoet.php');
$plugin_version = $plugin_data['Version']; $plugin_dist_name = 'mailpoet';
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
$plugin_dist_file = $plugin_dist_name . '.zip'; $plugin_dist_file = $plugin_dist_name . '.zip';
if(!$plugin_version) {
throw new \Exception('Could not parse plugin version, check the plugin header');
}
$this->say('Publishing version: ' . $plugin_version); $this->say('Publishing version: ' . $plugin_version);
// Sanity checks // Sanity checks
@ -395,13 +417,9 @@ class RoboFile extends \Robo\Tasks {
$dotenv->load(); $dotenv->load();
} }
protected function loadWPFunctions() { protected function getPluginVersion($file) {
$this->loadEnv(); $data = file_get_contents($file);
define('ABSPATH', getenv('WP_TEST_PATH') . '/'); preg_match('/^[ \t*]*Version:(.*)$/mi', $data, $m);
define('WPINC', 'wp-includes'); return !empty($m[1]) ? trim($m[1]) : false;
require_once(ABSPATH . WPINC . '/functions.php');
require_once(ABSPATH . WPINC . '/formatting.php');
require_once(ABSPATH . WPINC . '/plugin.php');
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
} }
} }

View File

@ -0,0 +1,15 @@
@import 'nib'
@require 'icons'
/*
Style for Members plugin
*/
.members-tab-title
.mailpoet-icon-logo
vertical-align: middle;
height: 20px;
width: 20px;
font-size: 20px;
margin-right: 3px;

View File

@ -24,5 +24,6 @@
@require 'subscribers' @require 'subscribers'
@require 'pages' @require 'pages'
@require 'pages_custom'
@require 'mp2migrator' @require 'mp2migrator'

View File

@ -21,6 +21,9 @@ a:focus
.mailpoet_spaced_block .mailpoet_spaced_block
margin: 1em 0 margin: 1em 0
.mailpoet_centered
text-align: center
// select 2 // select 2
.select2-container .select2-container
width: 25em !important width: 25em !important

24
assets/css/src/icons.styl Normal file
View File

@ -0,0 +1,24 @@
icon-font-path ?= "../fonts"
@font-face
font-family 'mailpoet'
src url(icon-font-path + '/mailpoet.ttf?mx0b6n') format('truetype'), url(icon-font-path + '/mailpoet.woff?mx0b6n') format('woff'), url(icon-font-path + '/mailpoet.svg?mx0b6n#mailpoet') format('svg')
font-weight normal
font-style normal
[class^="mailpoet-icon-"], [class*=" mailpoet-icon-"]
font-family 'mailpoet' !important
speak none
font-style normal
font-weight normal
font-variant normal
text-transform none
line-height 1
/* Better Font Rendering =========== */
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
.mailpoet-icon-logo
&:before
content "\e900"

View File

@ -45,7 +45,6 @@ body.mailpoet_modal_opened
position: absolute position: absolute
z-index: 25 z-index: 25
top: 48px top: 48px
padding-bottom: 48px
margin: 0 margin: 0
.mailpoet_popup_wrapper .mailpoet_popup_wrapper
@ -54,6 +53,7 @@ body.mailpoet_modal_opened
position: relative position: relative
width: 100% width: 100%
z-index: 0 z-index: 0
height: 96%
.mailpoet_overlay_hidden .mailpoet_popup_wrapper .mailpoet_overlay_hidden .mailpoet_popup_wrapper
border: 1px solid #333 border: 1px solid #333
@ -75,6 +75,7 @@ body.mailpoet_modal_opened
.mailpoet_popup_body .mailpoet_popup_body
padding: 10px 10px 10px 10px padding: 10px 10px 10px 10px
height: 92%
// modal panel // modal panel
#mailpoet_modal_overlay.mailpoet_panel_overlay #mailpoet_modal_overlay.mailpoet_panel_overlay

View File

@ -179,6 +179,28 @@ select.mailpoet_font-size
width: 100% width: 100%
box-sizing: border-box box-sizing: border-box
.tooltip-help-designer-subject-line div, .tooltip-help-designer-preheader div
z-index: 100001
.tooltip-help-send-preview
position: absolute
right: 15px
.tooltip-help-designer-ideal-width
color: #656565
text-transform: none
margin-left: 5px
font-weight: normal
.tooltip-help-designer-styles
position: absolute
top: 40px
.tooltip-help-designer-full-width .dashicons
line-height 34px
.tooltip-help-designer-full-width span
line-height 1.4em
.mailpoet_button_primary .mailpoet_button_primary
border-color: $button-primary-border-color border-color: $button-primary-border-color
background-color: $button-primary-background-color background-color: $button-primary-background-color

View File

@ -62,3 +62,9 @@ $draggable-widget-z-index = 2
background-color: $primary-active-color background-color: $primary-active-color
box-shadow(inset 1px 2px 1px $primary-inset-shadow-color) box-shadow(inset 1px 2px 1px $primary-inset-shadow-color)
color: $white-color color: $white-color
.mailpoet_droppable_block
cursor: move
&.mailpoet_ignore_drag
cursor: auto

View File

@ -48,3 +48,41 @@ $resize-handle-z-index = 2
.mailpoet_resize_handle .mailpoet_resize_handle
display: inline-block display: inline-block
.mailpoet_image_resize_handle_container
position: absolute
bottom: 0
right: 0
width: 20px
height: 20px
.mailpoet_image_resize_handle
position: relative
background: $resize-handle-background-color
border-radius(3px)
display: inline-block
width: 20px
height: 20px
cursor: nwse-resize
z-index: $resize-handle-z-index
.mailpoet_image_resize_handle_text,
.mailpoet_image_resize_handle_icon
pointer-events: none
.mailpoet_image_resize_handle_icon
position: absolute
top: 0
right: 0
& > svg
width: 100%
height: 100%
fill: $resize-handle-font-color
.mailpoet_block.mailpoet_image_resize_active > .mailpoet_block_highlight
border: 1px dashed $resize-active-color
.mailpoet_image_resize_handle
display: inline-block

View File

@ -23,12 +23,10 @@ $block-text-line-height = $text-line-height
border: 1px solid $transparent-color border: 1px solid $transparent-color
&:hover > .mailpoet_block_highlight &:hover > .mailpoet_block_highlight
&.mailpoet_highlight > .mailpoet_block_highlight
border: 1px dashed $block-hover-highlight-color border: 1px dashed $block-hover-highlight-color
&.mailpoet_highlight > .mailpoet_block_highlight
.mailpoet_block:last-child border: 1px dashed $block-hover-highlight-color !important
margin-bottom: 0
.mailpoet_content .mailpoet_content
position: relative position: relative

View File

@ -3,14 +3,19 @@
img img
vertical-align: bottom vertical-align: bottom
max-width: 100% max-width: 100%
width: auto
height: auto height: auto
&.mailpoet_full_image &.mailpoet_full_image
padding-left: 0 padding-left: 0
padding-right: 0 padding-right: 0
margin: auto
margin-bottom: 0 margin-bottom: 0
.mailpoet_content a:hover .mailpoet_content
cursor: all-scroll margin: auto
max-width: 100%
min-width: 36px
a:hover
cursor: all-scroll

View File

@ -18,6 +18,7 @@
.mailpoet_settings_posts_display_options .mailpoet_settings_posts_display_options
.mailpoet_settings_posts_selection .mailpoet_settings_posts_selection
animation-slide-open-downwards() animation-slide-open-downwards()
overflow-x: hidden
.mailpoet_settings_posts_show_display_options, .mailpoet_settings_posts_show_display_options,
.mailpoet_settings_posts_show_post_selection .mailpoet_settings_posts_show_post_selection
@ -26,7 +27,12 @@
.mailpoet_post_selection_container .mailpoet_post_selection_container
margin-top: 20px margin-top: 20px
margin-bottom: 20px margin-bottom: 0
.mailpoet_post_scroll_container
overflow-y: scroll
overflow-x: hidden
max-height: 400px
.mailpoet_settings_posts_single_post .mailpoet_settings_posts_single_post
border-radius(1px) border-radius(1px)
@ -45,3 +51,6 @@
.mailpoet_select_post_checkbox .mailpoet_select_post_checkbox
margin-left: 10px margin-left: 10px
margin-right: 8px margin-right: 8px
.mailpoet_post_selection_loading
color: #999

View File

@ -127,9 +127,6 @@ body
background-color: $primary-background-color background-color: $primary-background-color
border: 1px solid $content-border-color border: 1px solid $content-border-color
#mailpoet_modal_close
display: none
.wrap > .mailpoet_notice, .wrap > .mailpoet_notice,
.notice .notice
.update-nag .update-nag

View File

@ -2,7 +2,7 @@ animation-slide-open-downwards($max-height = 2000px)
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */ transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
max-height: $max-height max-height: $max-height
opacity: 1 opacity: 1
overflow-y: hidden overflow-y: inherit
&.mailpoet_closed &.mailpoet_closed
max-height: 0px max-height: 0px

View File

@ -2,6 +2,7 @@
Based on /wp-admin/css/about.css of WP 4.7. Based on /wp-admin/css/about.css of WP 4.7.
This is to make MailPoet pages independent of the WordPress This is to make MailPoet pages independent of the WordPress
About page styles that may differ across WP versions. About page styles that may differ across WP versions.
Please add custom styles to pages_custom.styl
*/ */
.mailpoet-about-wrap .mailpoet-about-wrap

View File

@ -0,0 +1,38 @@
/*
Custom styles for MailPoet pages.
*/
.mailpoet-about-wrap
.videoWrapper
position: relative
padding-bottom: 56.25% /* 16:9 */
/*padding-top: 25px*/
height: 0
iframe
position: absolute
top: 0
left: 0
width: 100%
height: 100%
.mailpoet_video
border: 1px solid rgba(0, 0, 0, 0.1)
#mailpoet-changelog ul
list-style: disc
padding-left: 20px
h2.mailpoet-feature-top
margin: 50px auto
a.button.go-to-plugin
margin-top: 2em
p.top-space-triple
margin-top: 3em
p.mailpoet-top-text
margin-right: 0

View File

@ -9,15 +9,25 @@
// sending methods // sending methods
.mailpoet_sending_methods .mailpoet_sending_methods
margin 25px 0 0 0 margin 25px 0 0 0
li display flex
float left flex-direction row
position relative justify-content flex-start
padding 15px 15px 0 15px > li
flex-grow 1
flex-shrink 1
display flex
flex-direction column
flex-basis 0
margin 0 25px 25px 0 margin 0 25px 25px 0
width 300px
height 300px
border 1px solid #dedede border 1px solid #dedede
background-color #fff background-color #fff
max-width 500px
.mailpoet_sending_method_description
padding: 25px
flex-grow 1
flex-shrink 0
> li:last-child
margin-right 0
h3 h3
text-align center text-align center
height 54px height 54px
@ -25,12 +35,12 @@
.mailpoet_description .mailpoet_description
font-size 14px font-size 14px
.mailpoet_status .mailpoet_status
display flex
flex-direction row
justify-content space-between
align-items center
background-color #2f2f2f background-color #2f2f2f
color #fff color #fff
position absolute
bottom 0
left 0
right 0
text-overflow ellipsis text-overflow ellipsis
padding 15px padding 15px
span span
@ -43,23 +53,29 @@
visibility visible visibility visible
#mailpoet_mta_activate #mailpoet_mta_activate
visibility hidden visibility hidden
.mailpoet_actions
bottom 15px
color #fff
padding 0
position absolute
right 15px
.button-secondary
margin 0 -6px -4px 0
// premium key .tooltip.dashicons.dashicons-editor-help
.mailpoet_key line-height: 1.4
&_valid
&::before ul.sending-method-benefits
content ' ' list-style-type: none
&_invalid margin-bottom: 2em
&::before margin-top: 2em
content: ' '
.sending-free-plan-button
background: #FF5301
border-color: #e64c03
text-shadow: 0 -1px 1px #e64c03
box-shadow: 0 1px 0 #e64c03
margin: 10px 0
strong
text-transform: uppercase
.mailpoet_success_item::before
content ' '
.mailpoet_error_item::before
content ' '
// responsive // responsive
@media screen and (max-width: 782px) @media screen and (max-width: 782px)
@ -67,8 +83,10 @@
width auto width auto
.mailpoet_sending_methods .mailpoet_sending_methods
li flex-flow: row wrap
float none justify-content: space-around
width auto > li
margin-right 0 margin-right 0
flex-basis auto

11
assets/fonts/mailpoet.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="mailpoet" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="0" d="" />
<glyph unicode="&#xe900;" glyph-name="optimised" horiz-adv-x="972" d="M230.188 949.695c-21.982-3.361-41.376-14.741-48.618-28.188l-5.948-11.637 0.517-265.588c0.776-263.779 0.776-265.847 6.206-273.088 11.12-15.258 40.601-22.24 68.79-16.551 16.551 3.621 25.861 9.827 32.584 21.723 3.879 7.499 4.397 23.791 5.689 185.161l1.293 176.628 60.514-161.111c33.102-88.702 63.875-168.612 68.013-177.404 13.707-29.481 35.687-41.376 72.151-39.049 21.206 1.293 39.308 9.827 47.584 22.499 3.361 5.172 35.947 91.806 72.41 192.662s67.237 185.42 68.53 187.49c1.551 2.587 2.587-69.307 2.587-185.937v-190.335l5.948-11.379c8.533-16.809 20.172-21.982 49.652-21.982 27.929 0.259 39.825 4.914 49.911 20.172l6.982 10.603v530.401l-5.689 9.31c-12.671 20.43-50.17 31.033-91.806 25.601-34.394-4.138-53.79-16.292-66.72-41.118-2.587-5.172-35.947-101.89-73.961-214.902s-69.824-206.367-70.6-207.403c-1.034-1.034-32.326 86.115-69.824 193.954-37.757 107.581-71.892 205.075-76.030 216.453-10.086 26.118-25.601 42.929-45.514 48.877-17.326 5.172-46.807 6.982-64.652 4.138zM54.854 243.443c-20.172-3.879-43.963-19.136-51.204-33.619-6.206-11.379-4.914-32.843 2.587-47.841 23.533-46.807 71.634-86.892 126.717-104.736 17.068-5.689 23.274-5.948 120.252-7.499 97.235-1.551 102.926-1.81 116.373-7.241 29.739-11.896 51.204-35.687 61.807-68.013 3.621-11.12 13.964-21.206 25.861-25.344 4.914-1.551 18.361-2.844 29.739-2.844 16.809 0 23.533 1.293 32.584 5.689 11.896 6.206 13.964 9.31 26.895 38.791 11.896 27.671 39.567 49.652 70.858 56.117 8.533 1.81 47.067 2.844 100.856 2.844 99.563 0 113.786 2.068 151.801 20.689 49.652 24.567 96.978 77.84 101.373 113.529 3.104 26.118-17.326 49.394-51.204 58.187-25.601 6.465-41.635-0.517-54.825-24.050-11.12-19.655-29.998-38.015-47.841-46.29l-14.741-6.982-99.563-1.551c-90.77-1.293-101.373-2.068-120.252-6.982-27.154-7.499-58.444-23.016-80.427-40.084l-17.844-13.964-16.809 13.964c-20.689 16.809-51.462 32.584-78.875 39.825-19.136 5.172-28.705 5.948-120.252 7.241l-99.563 1.551-15.775 7.241c-18.102 8.533-32.584 21.982-48.36 45.773-16.034 24.567-26.895 29.998-50.17 25.601z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/fonts/mailpoet.ttf Normal file

Binary file not shown.

BIN
assets/fonts/mailpoet.woff Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 B

After

Width:  |  Height:  |  Size: 814 B

View File

@ -1,12 +1,12 @@
define('admin', [ define('admin', [
'jquery' 'jquery'
], ],
function(jQuery) { function (jQuery) {
jQuery(function($) { jQuery(function ($) {
// dom ready // dom ready
$(function() { $(function () {
}); });
}); });
} }
); );

View File

@ -1,74 +1,87 @@
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) { function requestFailed(errorMessage, xhr) {
'use strict'; if (xhr.responseJSON) {
MailPoet.Ajax = { return xhr.responseJSON;
version: 0.5, }
options: {}, return {
defaults: { errors: [
url: null, {
api_version: null, message: errorMessage.replace('%d', xhr.status)
endpoint: null, }
action: null, ]
token: null, };
data: {} }
},
post: function(options) { define('ajax', ['mailpoet', 'jquery', 'underscore'], function (mp, jQuery, _) {
return this.request('post', options); var MailPoet = mp;
},
init: function(options) { MailPoet.Ajax = {
// merge options version: 0.5,
this.options = jQuery.extend({}, this.defaults, options); options: {},
defaults: {
// set default url url: null,
if(this.options.url === null) { api_version: null,
this.options.url = ajaxurl; endpoint: null,
} action: null,
token: null,
// set default token data: {}
if(this.options.token === null) { },
this.options.token = window.mailpoet_token; post: function (options) {
} return this.request('post', options);
}, },
getParams: function() { init: function (options) {
return { // merge options
action: 'mailpoet', this.options = jQuery.extend({}, this.defaults, options);
api_version: this.options.api_version,
token: this.options.token, // set default url
endpoint: this.options.endpoint, if (this.options.url === null) {
method: this.options.action, this.options.url = window.ajaxurl;
data: this.options.data || {} }
}
}, // set default token
request: function(method, options) { if (this.options.token === null) {
// set options this.options.token = window.mailpoet_token;
this.init(options); }
},
// set request params getParams: function () {
var params = this.getParams(); return {
var deferred = jQuery.Deferred(); action: 'mailpoet',
api_version: this.options.api_version,
// remove null values from the data object token: this.options.token,
if (_.isObject(params.data)) { endpoint: this.options.endpoint,
params.data = _.pick(params.data, function(value) { method: this.options.action,
return (value !== null) data: this.options.data || {}
}) };
} },
request: function (method, options) {
// ajax request var params;
deferred = jQuery.post( var deferred;
this.options.url, // set options
params, this.init(options);
null,
'json' // set request params
).then(function(data) { params = this.getParams();
return data;
}, function(xhr) { // remove null values from the data object
return xhr.responseJSON; if (_.isObject(params.data)) {
}); params.data = _.pick(params.data, function (value) {
return (value !== null);
// clear options });
this.options = {}; }
return deferred; // ajax request
} deferred = jQuery.post(
}; this.options.url,
}); params,
null,
'json'
).then(function (data) {
return data;
}, _.partial(requestFailed, MailPoet.I18n.t('ajaxFailedErrorMessage')));
// clear options
this.options = {};
return deferred;
}
};
});

View File

@ -17,14 +17,15 @@
*/ */
var eventsCache = []; var eventsCache = [];
function track(name, data){ function track(name, data) {
if (typeof window.mixpanel.track !== "function") { if (typeof window.mixpanel.track !== 'function') {
window.mixpanel.init(window.mixpanelTrackingId); window.mixpanel.init(window.mixpanelTrackingId);
} }
window.mixpanel.track(name, data); window.mixpanel.track(name, data);
} }
function exportMixpanel(MailPoet) { function exportMixpanel(mp) {
var MailPoet = mp;
MailPoet.forceTrackEvent = track; MailPoet.forceTrackEvent = track;
if (window.mailpoet_analytics_enabled) { if (window.mailpoet_analytics_enabled) {
@ -37,31 +38,32 @@ function exportMixpanel(MailPoet) {
function trackCachedEvents() { function trackCachedEvents() {
eventsCache.map(function (event) { eventsCache.map(function (event) {
if (window.mailpoet_analytics_enabled || event.forced) { if (window.mailpoet_analytics_enabled || event.forced) {
window.mixpanel.track(event.name, event.data) window.mixpanel.track(event.name, event.data);
} }
}); });
} }
function initializeMixpanelWhenLoaded() {
if (typeof window.mixpanel === "object") {
exportMixpanel(MailPoet);
trackCachedEvents();
} else {
setTimeout(initializeMixpanelWhenLoaded, 100);
}
}
function cacheEvent(forced, name, data) { function cacheEvent(forced, name, data) {
eventsCache.push({ eventsCache.push({
name: name, name: name,
data: data, data: data,
forced: forced, forced: forced
}); });
} }
define( define(
['mailpoet', 'underscore'], ['mailpoet', 'underscore'],
function(MailPoet, _) { function (mp, _) {
var MailPoet = mp;
function initializeMixpanelWhenLoaded() {
if (typeof window.mixpanel === 'object') {
exportMixpanel(MailPoet);
trackCachedEvents();
} else {
setTimeout(initializeMixpanelWhenLoaded, 100);
}
}
MailPoet.trackEvent = _.partial(cacheEvent, false); MailPoet.trackEvent = _.partial(cacheEvent, false);
MailPoet.forceTrackEvent = _.partial(cacheEvent, true); MailPoet.forceTrackEvent = _.partial(cacheEvent, true);

View File

@ -3,164 +3,172 @@ define('date',
'mailpoet', 'mailpoet',
'jquery', 'jquery',
'moment' 'moment'
], function( ], function (
MailPoet, mp,
jQuery, jQuery,
Moment Moment
) { ) {
'use strict'; 'use strict';
MailPoet.Date = { var MailPoet = mp;
version: 0.1,
options: {}, MailPoet.Date = {
defaults: { version: 0.1,
offset: 0, options: {},
format: 'F, d Y H:i:s' defaults: {
}, offset: 0,
init: function(options) { format: 'F, d Y H:i:s'
options = options || {}; },
init: function (opts) {
var options = opts || {};
// set UTC offset // set UTC offset
if ( if (
options.offset === undefined options.offset === undefined
&& window.mailpoet_date_offset !== undefined && window.mailpoet_date_offset !== undefined
) { ) {
options.offset = window.mailpoet_date_offset; options.offset = window.mailpoet_date_offset;
} }
// set date format // set date format
if ( if (
options.format === undefined options.format === undefined
&& window.mailpoet_date_format !== undefined && window.mailpoet_date_format !== undefined
) { ) {
options.format = window.mailpoet_date_format; options.format = window.mailpoet_date_format;
}
// merge options
this.options = jQuery.extend({}, this.defaults, options);
return this;
},
format: function(date, options) {
options = options || {};
this.init(options);
var date = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) date = date.utc();
return date.format(this.convertFormat(this.options.format));
},
toDate: function(date, options) {
options = options || {};
this.init(options);
return Moment(date, this.convertFormat(options.parseFormat)).toDate();
},
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) {
var format_mappings = {
date: {
d: 'DD',
D: 'ddd',
j: 'D',
l: 'dddd',
N: 'E',
S: 'o',
w: 'e',
z: 'DDD',
W: 'W',
F: 'MMMM',
m: 'MM',
M: 'MMM',
n: 'M',
t: '', // no equivalent
L: '', // no equivalent
o: 'YYYY',
Y: 'YYYY',
y: 'YY',
a: 'a',
A: 'A',
B: '', // no equivalent
g: 'h',
G: 'H',
h: 'hh',
H: 'HH',
i: 'mm',
s: 'ss',
u: 'SSS',
e: 'zz', // deprecated since version 1.6.0 of moment.js
I: '', // no equivalent
O: '', // no equivalent
P: '', // no equivalent
T: '', // no equivalent
Z: '', // no equivalent
c: '', // no equivalent
r: '', // no equivalent
U: 'X'
},
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'
} }
}; // merge options
this.options = jQuery.extend({}, this.defaults, options);
if (!format || format.length <= 0) return format; return this;
},
format: function (date, opts) {
var options = opts || {};
var momentDate;
this.init(options);
var replacements = format_mappings['date']; momentDate = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) momentDate = momentDate.utc();
return momentDate.format(this.convertFormat(this.options.format));
},
toDate: function (date, opts) {
var options = opts || {};
this.init(options);
var convertedFormat = []; return Moment(date, this.convertFormat(options.parseFormat)).toDate();
var escapeToken = false; },
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) {
var replacements;
var convertedFormat;
var escapeToken;
var index;
var token;
var format_mappings = {
date: {
d: 'DD',
D: 'ddd',
j: 'D',
l: 'dddd',
N: 'E',
S: 'o',
w: 'e',
z: 'DDD',
W: 'W',
F: 'MMMM',
m: 'MM',
M: 'MMM',
n: 'M',
t: '', // no equivalent
L: '', // no equivalent
o: 'YYYY',
Y: 'YYYY',
y: 'YY',
a: 'a',
A: 'A',
B: '', // no equivalent
g: 'h',
G: 'H',
h: 'hh',
H: 'HH',
i: 'mm',
s: 'ss',
u: 'SSS',
e: 'zz', // deprecated since version 1.6.0 of moment.js
I: '', // no equivalent
O: '', // no equivalent
P: '', // no equivalent
T: '', // no equivalent
Z: '', // no equivalent
c: '', // no equivalent
r: '', // no equivalent
U: 'X'
},
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'
}
};
for(var index = 0, token = ''; token = format.charAt(index); index++){ if (!format || format.length <= 0) return format;
if (escapeToken === true) {
convertedFormat.push('['+token+']'); replacements = format_mappings['date'];
escapeToken = false; convertedFormat = [];
} else { escapeToken = false;
if (token === '\\') {
// Slash escapes the next symbol to be treated as literal for (index = 0, token = ''; format.charAt(index); index += 1) {
escapeToken = true; token = format.charAt(index);
continue; if (escapeToken === true) {
} else if (replacements[token] !== undefined) { convertedFormat.push('[' + token + ']');
convertedFormat.push(replacements[token]); escapeToken = false;
} else { } else {
convertedFormat.push('['+token+']'); if (token === '\\') {
// Slash escapes the next symbol to be treated as literal
escapeToken = true;
continue;
} else if (replacements[token] !== undefined) {
convertedFormat.push(replacements[token]);
} else {
convertedFormat.push('[' + token + ']');
}
} }
} }
}
return convertedFormat.join(''); return convertedFormat.join('');
} }
}; };
}); });

View File

@ -1,15 +1,15 @@
define([ define([
'react' 'react',
], ],
function( (
React React
) { ) => {
const FormFieldCheckbox = React.createClass({ const FormFieldCheckbox = React.createClass({
onValueChange: function(e) { onValueChange: function (e) {
e.target.value = this.refs.checkbox.checked ? '1' : '0'; e.target.value = this.refs.checkbox.checked ? '1' : '0';
return this.props.onValueChange(e); return this.props.onValueChange(e);
}, },
render: function() { render: function () {
if (this.props.field.values === undefined) { if (this.props.field.values === undefined) {
return false; return false;
} }
@ -20,15 +20,15 @@ function(
const options = Object.keys(this.props.field.values).map( const options = Object.keys(this.props.field.values).map(
(value, index) => { (value, index) => {
return ( return (
<p key={ 'checkbox-' + index }> <p key={'checkbox-' + index}>
<label> <label>
<input <input
ref="checkbox" ref="checkbox"
type="checkbox" type="checkbox"
value="1" value="1"
checked={ isChecked } checked={isChecked}
onChange={ this.onValueChange } onChange={this.onValueChange}
name={ this.props.field.name } name={this.props.field.name}
/> />
{ this.props.field.values[value] } { this.props.field.values[value] }
</label> </label>
@ -42,8 +42,8 @@ function(
{ options } { options }
</div> </div>
); );
} },
}); });
return FormFieldCheckbox; return FormFieldCheckbox;
}); });

View File

@ -1,10 +1,10 @@
define([ define([
'react', 'react',
'moment', 'moment',
], function( ], (
React, React,
Moment Moment
) { ) => {
class FormFieldDateYear extends React.Component { class FormFieldDateYear extends React.Component {
render() { render() {
const yearsRange = 100; const yearsRange = 100;
@ -12,24 +12,24 @@ define([
if (this.props.placeholder !== undefined) { if (this.props.placeholder !== undefined) {
years.push(( years.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option> <option value="" key={0}>{ this.props.placeholder }</option>
)); ));
} }
const currentYear = Moment().year(); const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i--) { for (let i = currentYear; i >= currentYear - yearsRange; i -= 1) {
years.push(( years.push((
<option <option
key={ i } key={i}
value={ i } value={i}
>{ i }</option> >{ i }</option>
)); ));
} }
return ( return (
<select <select
name={ `${this.props.name}[year]` } name={`${this.props.name}[year]`}
value={ this.props.year } value={this.props.year}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
> >
{ years } { years }
</select> </select>
@ -43,23 +43,23 @@ define([
if (this.props.placeholder !== undefined) { if (this.props.placeholder !== undefined) {
months.push(( months.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option> <option value="" key={0}>{ this.props.placeholder }</option>
)); ));
} }
for (let i = 1; i <= 12; i++) { for (let i = 1; i <= 12; i += 1) {
months.push(( months.push((
<option <option
key={ i } key={i}
value={ i } value={i}
>{ this.props.monthNames[i - 1] }</option> >{ this.props.monthNames[i - 1] }</option>
)); ));
} }
return ( return (
<select <select
name={ `${this.props.name}[month]` } name={`${this.props.name}[month]`}
value={ this.props.month } value={this.props.month}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
> >
{ months } { months }
</select> </select>
@ -73,24 +73,24 @@ define([
if (this.props.placeholder !== undefined) { if (this.props.placeholder !== undefined) {
days.push(( days.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option> <option value="" key={0}>{ this.props.placeholder }</option>
)); ));
} }
for (let i = 1; i <= 31; i++) { for (let i = 1; i <= 31; i += 1) {
days.push(( days.push((
<option <option
key={ i } key={i}
value={ i } value={i}
>{ i }</option> >{ i }</option>
)); ));
} }
return ( return (
<select <select
name={ `${this.props.name}[day]` } name={`${this.props.name}[day]`}
value={ this.props.day } value={this.props.day}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
> >
{ days } { days }
</select> </select>
@ -104,13 +104,13 @@ define([
this.state = { this.state = {
year: '', year: '',
month: '', month: '',
day: '' day: '',
} };
} }
componentDidMount() { componentDidMount() {
this.extractDateParts(); this.extractDateParts();
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps) {
if ( if (
(this.props.item !== undefined && prevProps.item !== undefined) (this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id) && (this.props.item.id !== prevProps.item.id)
@ -123,7 +123,7 @@ define([
? this.props.item[this.props.field.name].trim() ? this.props.item[this.props.field.name].trim()
: ''; : '';
if(value === '') { if (value === '') {
return; return;
} }
@ -132,7 +132,7 @@ define([
this.setState({ this.setState({
year: dateTime.format('YYYY'), year: dateTime.format('YYYY'),
month: dateTime.format('M'), month: dateTime.format('M'),
day: dateTime.format('D') day: dateTime.format('D'),
}); });
} }
formatValue() { formatValue() {
@ -140,33 +140,33 @@ define([
let value; let value;
switch(dateType) { switch (dateType) {
case 'year_month_day': case 'year_month_day':
value = { value = {
'year': this.state.year, year: this.state.year,
'month': this.state.month, month: this.state.month,
'day': this.state.day day: this.state.day,
}; };
break; break;
case 'year_month': case 'year_month':
value = { value = {
'year': this.state.year, year: this.state.year,
'month': this.state.month month: this.state.month,
}; };
break; break;
case 'month': case 'month':
value = { value = {
'month': this.state.month month: this.state.month,
}; };
break; break;
case 'year': case 'year':
value = { value = {
'year': this.state.year year: this.state.year,
}; };
break; break;
} }
return value; return value;
@ -181,16 +181,16 @@ define([
field = matches[1]; field = matches[1];
property = matches[2]; property = matches[2];
let value = ~~(e.target.value); const value = ~~(e.target.value);
this.setState({ this.setState({
[`${property}`]: value [`${property}`]: value,
}, () => { }, () => {
this.props.onValueChange({ this.props.onValueChange({
target: { target: {
name: field, name: field,
value: this.formatValue() value: this.formatValue(),
} },
}); });
}); });
} }
@ -201,43 +201,40 @@ define([
const dateType = this.props.field.params.date_type; const dateType = this.props.field.params.date_type;
const dateSelects = dateFormats[dateType][0].split('/'); const dateSelects = dateFormats[dateType][0].split('/');
const fields = dateSelects.map(type => { const fields = dateSelects.map((type) => {
switch(type) { switch (type) {
case 'YYYY': case 'YYYY':
return (<FormFieldDateYear return (<FormFieldDateYear
onValueChange={ this.onValueChange.bind(this) } onValueChange={this.onValueChange.bind(this)}
ref={ 'year' } ref={'year'}
key={ 'year' } key={'year'}
name={ this.props.field.name } name={this.props.field.name}
year={ this.state.year } year={this.state.year}
placeholder={ this.props.field.year_placeholder } placeholder={this.props.field.year_placeholder}
/>); />);
break;
case 'MM': case 'MM':
return (<FormFieldDateMonth return (<FormFieldDateMonth
onValueChange={ this.onValueChange.bind(this) } onValueChange={this.onValueChange.bind(this)}
ref={ 'month' } ref={'month'}
key={ 'month' } key={'month'}
name={ this.props.field.name } name={this.props.field.name}
month={ this.state.month } month={this.state.month}
monthNames={ monthNames } monthNames={monthNames}
placeholder={ this.props.field.month_placeholder } placeholder={this.props.field.month_placeholder}
/>); />);
break;
case 'DD': case 'DD':
return (<FormFieldDateDay return (<FormFieldDateDay
onValueChange={ this.onValueChange.bind(this) } onValueChange={this.onValueChange.bind(this)}
ref={ 'day' } ref={'day'}
key={ 'day' } key={'day'}
name={ this.props.field.name } name={this.props.field.name}
day={ this.state.day } day={this.state.day}
placeholder={ this.props.field.day_placeholder } placeholder={this.props.field.day_placeholder}
/>); />);
break;
} }
}); });
return ( return (
<div> <div>
@ -245,7 +242,7 @@ define([
</div> </div>
); );
} }
}; }
return FormFieldDate; return FormFieldDate;
}); });

View File

@ -7,8 +7,9 @@ define([
'form/fields/checkbox.jsx', 'form/fields/checkbox.jsx',
'form/fields/selection.jsx', 'form/fields/selection.jsx',
'form/fields/date.jsx', 'form/fields/date.jsx',
'jquery',
], ],
function( (
React, React,
FormFieldText, FormFieldText,
FormFieldTextarea, FormFieldTextarea,
@ -16,91 +17,93 @@ function(
FormFieldRadio, FormFieldRadio,
FormFieldCheckbox, FormFieldCheckbox,
FormFieldSelection, FormFieldSelection,
FormFieldDate FormFieldDate,
) { jQuery
var FormField = React.createClass({ ) => {
renderField: function(data, inline = false) { const FormField = React.createClass({
var description = false; renderField: function (data, inline = false) {
if(data.field.description) { let description = false;
if (data.field.description) {
description = ( description = (
<p className="description">{ data.field.description }</p> <p className="description">{ data.field.description }</p>
); );
} }
var field = false; let field = false;
let dataField = data.field;
if(data.field['field'] !== undefined) { if (data.field['field'] !== undefined) {
data.field = jQuery.merge(data.field, data.field.field); dataField = jQuery.merge(dataField, data.field.field);
} }
switch(data.field.type) { switch (dataField.type) {
case 'text': case 'text':
field = (<FormFieldText {...data} />); field = (<FormFieldText {...data} />);
break; break;
case 'textarea': case 'textarea':
field = (<FormFieldTextarea {...data} />); field = (<FormFieldTextarea {...data} />);
break; break;
case 'select': case 'select':
field = (<FormFieldSelect {...data} />); field = (<FormFieldSelect {...data} />);
break; break;
case 'radio': case 'radio':
field = (<FormFieldRadio {...data} />); field = (<FormFieldRadio {...data} />);
break; break;
case 'checkbox': case 'checkbox':
field = (<FormFieldCheckbox {...data} />); field = (<FormFieldCheckbox {...data} />);
break; break;
case 'selection': case 'selection':
field = (<FormFieldSelection {...data} />); field = (<FormFieldSelection {...data} />);
break; break;
case 'date': case 'date':
field = (<FormFieldDate {...data} />); field = (<FormFieldDate {...data} />);
break; break;
case 'reactComponent': case 'reactComponent':
field = (<data.field.component {...data} />); field = (<data.field.component {...data} />);
break; break;
} }
if(inline === true) { if (inline === true) {
return ( return (
<span key={ 'field-' + (data.index || 0) }> <span key={'field-' + (data.index || 0)}>
{ field } { field }
{ description } { description }
</span> </span>
); );
} else {
return (
<div key={ 'field-' + (data.index || 0) }>
{ field }
{ description }
</div>
);
} }
}, return (
render: function() { <div key={'field-' + (data.index || 0)}>
var field = false; { field }
{ description }
</div>
);
if(this.props.field['fields'] !== undefined) { },
field = this.props.field.fields.map(function(subfield, index) { render: function () {
let field = false;
if (this.props.field['fields'] !== undefined) {
field = this.props.field.fields.map((subfield, index) => {
return this.renderField({ return this.renderField({
index: index, index: index,
field: subfield, field: subfield,
item: this.props.item, item: this.props.item,
onValueChange: this.props.onValueChange || false onValueChange: this.props.onValueChange || false,
}); });
}.bind(this)); });
} else { } else {
field = this.renderField(this.props); field = this.renderField(this.props);
} }
var tip = false; let tip = false;
if(this.props.field.tip) { if (this.props.field.tip) {
tip = ( tip = (
<p className="description">{ this.props.field.tip }</p> <p className="description">{ this.props.field.tip }</p>
); );
@ -110,7 +113,7 @@ function(
<tr> <tr>
<th scope="row"> <th scope="row">
<label <label
htmlFor={ 'field_'+this.props.field.name } htmlFor={'field_' + this.props.field.name}
> >
{ this.props.field.label } { this.props.field.label }
{ tip } { tip }
@ -121,7 +124,7 @@ function(
</td> </td>
</tr> </tr>
); );
} },
}); });
return FormField; return FormField;

View File

@ -1,11 +1,11 @@
define([ define([
'react' 'react',
], ],
function( (
React React
) { ) => {
const FormFieldRadio = React.createClass({ const FormFieldRadio = React.createClass({
render: function() { render: function () {
if (this.props.field.values === undefined) { if (this.props.field.values === undefined) {
return false; return false;
} }
@ -14,14 +14,14 @@ function(
const options = Object.keys(this.props.field.values).map( const options = Object.keys(this.props.field.values).map(
(value, index) => { (value, index) => {
return ( return (
<p key={ 'radio-' + index }> <p key={'radio-' + index}>
<label> <label>
<input <input
type="radio" type="radio"
checked={ selected_value === value } checked={selected_value === value}
value={ value } value={value}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
name={ this.props.field.name } /> name={this.props.field.name} />
{ this.props.field.values[value] } { this.props.field.values[value] }
</label> </label>
</p> </p>
@ -34,8 +34,8 @@ function(
{ options } { options }
</div> </div>
); );
} },
}); });
return FormFieldRadio; return FormFieldRadio;
}); });

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react';
import _ from 'underscore' import _ from 'underscore';
const FormFieldSelect = React.createClass({ const FormFieldSelect = React.createClass({
render() { render() {
@ -33,12 +33,12 @@ const FormFieldSelect = React.createClass({
_.map( _.map(
_.sortBy( _.sortBy(
_.pairs(this.props.field.values), _.pairs(this.props.field.values),
(item) => sortBy(item[0], item[1]) item => sortBy(item[0], item[1])
), ),
(item) => item[0] item => item[0]
); );
} else { } else {
keys = Object.keys(this.props.field.values) keys = Object.keys(this.props.field.values);
} }
const options = keys.map( const options = keys.map(
@ -50,8 +50,8 @@ const FormFieldSelect = React.createClass({
return ( return (
<option <option
key={ 'option-' + index } key={'option-' + index}
value={ value }> value={value}>
{ this.props.field.values[value] } { this.props.field.values[value] }
</option> </option>
); );
@ -60,17 +60,17 @@ const FormFieldSelect = React.createClass({
return ( return (
<select <select
name={ this.props.field.name } name={this.props.field.name}
id={ 'field_'+this.props.field.name } id={'field_' + this.props.field.name}
value={ this.props.item[this.props.field.name] } value={this.props.item[this.props.field.name]}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
{...this.props.field.validation} {...this.props.field.validation}
> >
{placeholder} {placeholder}
{options} {options}
</select> </select>
); );
} },
}); });
module.exports = FormFieldSelect; module.exports = FormFieldSelect;

View File

@ -2,80 +2,78 @@ define([
'react', 'react',
'react-dom', 'react-dom',
'jquery', 'jquery',
'select2' 'select2',
], ],
function( (
React, React,
ReactDOM, ReactDOM,
jQuery jQuery
) { ) => {
var Selection = React.createClass({ const Selection = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
items: [], items: [],
select2: false select2: false,
}; };
}, },
componentWillMount: function() { componentWillMount: function () {
this.loadCachedItems(); this.loadCachedItems();
}, },
allowMultipleValues: function() { allowMultipleValues: function () {
return (this.props.field.multiple === true); return (this.props.field.multiple === true);
}, },
isSelect2Initialized: function() { isSelect2Initialized: function () {
return (this.state.select2 === true); return (this.state.select2 === true);
}, },
componentDidMount: function() { componentDidMount: function () {
if(this.allowMultipleValues()) { if (this.allowMultipleValues()) {
this.setupSelect2(); this.setupSelect2();
} }
}, },
componentDidUpdate: function(prevProps, prevState) { componentDidUpdate: function (prevProps) {
if( if (
(this.props.item !== undefined && prevProps.item !== undefined) (this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id) && (this.props.item.id !== prevProps.item.id)
) { ) {
jQuery('#'+this.refs.select.id) jQuery('#' + this.refs.select.id)
.val(this.getSelectedValues()) .val(this.getSelectedValues())
.trigger('change'); .trigger('change');
} }
}, },
componentWillUnmount: function() { componentWillUnmount: function () {
if(this.allowMultipleValues()) { if (this.allowMultipleValues()) {
this.destroySelect2(); this.destroySelect2();
} }
}, },
destroySelect2: function() { destroySelect2: function () {
if(this.isSelect2Initialized()) { if (this.isSelect2Initialized()) {
jQuery('#'+this.refs.select.id).select2('destroy'); jQuery('#' + this.refs.select.id).select2('destroy');
} }
}, },
setupSelect2: function() { setupSelect2: function () {
if(this.isSelect2Initialized()) { if (this.isSelect2Initialized()) {
return; return;
} }
var select2 = jQuery('#'+this.refs.select.id).select2({ const select2 = jQuery('#' + this.refs.select.id).select2({
width: (this.props.width || ''), width: (this.props.width || ''),
templateResult: function(item) { templateResult: function (item) {
if(item.element && item.element.selected) { if (item.element && item.element.selected) {
return null; return null;
} else { } else if (item.title) {
if(item.title) { return item.title;
return item.title;
} else {
return item.text;
}
} }
} return item.text;
},
}); });
var hasRemoved = false; let hasRemoved = false;
select2.on('select2:unselecting', function(e) { select2.on('select2:unselecting', () => {
hasRemoved = true; hasRemoved = true;
}); });
select2.on('select2:opening', function(e) { select2.on('select2:opening', (e) => {
if(hasRemoved === true) { if (hasRemoved === true) {
hasRemoved = false; hasRemoved = false;
e.preventDefault(); e.preventDefault();
} }
@ -85,13 +83,13 @@ function(
this.setState({ select2: true }); this.setState({ select2: true });
}, },
getSelectedValues: function() { getSelectedValues: function () {
if(this.props.field['selected'] !== undefined) { if (this.props.field['selected'] !== undefined) {
return this.props.field['selected'](this.props.item); return this.props.field['selected'](this.props.item);
} else if(this.props.item !== undefined && this.props.field.name !== undefined) { } else if (this.props.item !== undefined && this.props.field.name !== undefined) {
if (this.allowMultipleValues()) { if (this.allowMultipleValues()) {
if (Array.isArray(this.props.item[this.props.field.name])) { if (Array.isArray(this.props.item[this.props.field.name])) {
return this.props.item[this.props.field.name].map(function(item) { return this.props.item[this.props.field.name].map((item) => {
return item.id; return item.id;
}); });
} }
@ -101,50 +99,51 @@ function(
} }
return null; return null;
}, },
loadCachedItems: function() { loadCachedItems: function () {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') { if (typeof (window['mailpoet_' + this.props.field.endpoint]) !== 'undefined') {
var items = window['mailpoet_'+this.props.field.endpoint]; let items = window['mailpoet_' + this.props.field.endpoint];
if(this.props.field['filter'] !== undefined) { if (this.props.field['filter'] !== undefined) {
items = items.filter(this.props.field.filter); items = items.filter(this.props.field.filter);
} }
this.setState({ this.setState({
items: items items: items,
}); });
} }
}, },
handleChange: function(e) { handleChange: function (e) {
if(this.props.onValueChange !== undefined) { let value;
if(this.props.field.multiple) { if (this.props.onValueChange !== undefined) {
value = jQuery('#'+this.refs.select.id).val(); if (this.props.field.multiple) {
value = jQuery('#' + this.refs.select.id).val();
} else { } else {
value = e.target.value; value = e.target.value;
} }
var transformedValue = this.transformChangedValue(value); const transformedValue = this.transformChangedValue(value);
this.props.onValueChange({ this.props.onValueChange({
target: { target: {
value: transformedValue, value: transformedValue,
name: this.props.field.name name: this.props.field.name,
} },
}); });
} }
}, },
getLabel: function(item) { getLabel: function (item) {
if(this.props.field['getLabel'] !== undefined) { if (this.props.field['getLabel'] !== undefined) {
return this.props.field.getLabel(item, this.props.item); return this.props.field.getLabel(item, this.props.item);
} }
return item.name; return item.name;
}, },
getSearchLabel: function(item) { getSearchLabel: function (item) {
if(this.props.field['getSearchLabel'] !== undefined) { if (this.props.field['getSearchLabel'] !== undefined) {
return this.props.field.getSearchLabel(item, this.props.item); return this.props.field.getSearchLabel(item, this.props.item);
} }
return null; return null;
}, },
getValue: function(item) { getValue: function (item) {
if(this.props.field['getValue'] !== undefined) { if (this.props.field['getValue'] !== undefined) {
return this.props.field.getValue(item, this.props.item); return this.props.field.getValue(item, this.props.item);
} }
return item.id; return item.id;
@ -152,24 +151,24 @@ function(
// When it's impossible to represent the desired value in DOM, // When it's impossible to represent the desired value in DOM,
// this function may be used to transform the placeholder value into // this function may be used to transform the placeholder value into
// desired value. // desired value.
transformChangedValue: function(value) { transformChangedValue: function (value) {
if(typeof this.props.field['transformChangedValue'] === 'function') { if (typeof this.props.field['transformChangedValue'] === 'function') {
return this.props.field.transformChangedValue.call(this, value); return this.props.field.transformChangedValue.call(this, value);
} else {
return value;
} }
return value;
}, },
render: function() { render: function () {
const options = this.state.items.map((item, index) => { const options = this.state.items.map((item, index) => {
let label = this.getLabel(item); const label = this.getLabel(item);
let searchLabel = this.getSearchLabel(item); const searchLabel = this.getSearchLabel(item);
let value = this.getValue(item); const value = this.getValue(item);
return ( return (
<option <option
key={ 'option-'+index } key={'option-' + index}
value={ value } value={value}
title={ searchLabel } title={searchLabel}
> >
{ label } { label }
</option> </option>
@ -178,15 +177,16 @@ function(
return ( return (
<select <select
id={ this.props.field.id || this.props.field.name } id={this.props.field.id || this.props.field.name}
ref="select" ref="select"
data-placeholder={ this.props.field.placeholder } disabled={this.props.field.disabled}
multiple={ this.props.field.multiple } data-placeholder={this.props.field.placeholder}
defaultValue={ this.getSelectedValues() } multiple={this.props.field.multiple}
defaultValue={this.getSelectedValues()}
{...this.props.field.validation} {...this.props.field.validation}
>{ options }</select> >{ options }</select>
); );
} },
}); });
return Selection; return Selection;

View File

@ -1,4 +1,4 @@
import React from 'react' import React from 'react';
const FormFieldText = React.createClass({ const FormFieldText = React.createClass({
render() { render() {
@ -15,21 +15,21 @@ const FormFieldText = React.createClass({
? this.props.field.disabled(this.props.item) ? this.props.field.disabled(this.props.item)
: false : false
} }
className={ (this.props.field.size) ? '' : 'regular-text' } className={(this.props.field.size) ? '' : 'regular-text'}
size={ size={
(this.props.field.size !== 'auto' && this.props.field.size > 0) (this.props.field.size !== 'auto' && this.props.field.size > 0)
? this.props.field.size ? this.props.field.size
: false : false
} }
name={ this.props.field.name } name={this.props.field.name}
id={ 'field_'+this.props.field.name } id={'field_' + this.props.field.name}
value={ value } value={value}
placeholder={ this.props.field.placeholder } placeholder={this.props.field.placeholder}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
{...this.props.field.validation} {...this.props.field.validation}
/> />
); );
} },
}); });
module.exports = FormFieldText; module.exports = FormFieldText;

View File

@ -1,26 +1,26 @@
define([ define([
'react' 'react',
], ],
function( (
React React
) { ) => {
var FormFieldTextarea = React.createClass({ const FormFieldTextarea = React.createClass({
render: function() { render: function () {
return ( return (
<textarea <textarea
type="text" type="text"
className="regular-text" className="regular-text"
name={ this.props.field.name } name={this.props.field.name}
id={ 'field_'+this.props.field.name } id={'field_' + this.props.field.name}
value={ this.props.item[this.props.field.name] } value={this.props.item[this.props.field.name]}
placeholder={ this.props.field.placeholder } placeholder={this.props.field.placeholder}
defaultValue={ this.props.field.defaultValue } defaultValue={this.props.field.defaultValue}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
{...this.props.field.validation} {...this.props.field.validation}
/> />
); );
} },
}); });
return FormFieldTextarea; return FormFieldTextarea;
}); });

View File

@ -4,54 +4,56 @@ define(
'mailpoet', 'mailpoet',
'classnames', 'classnames',
'react-router', 'react-router',
'form/fields/field.jsx' 'form/fields/field.jsx',
'jquery',
], ],
function( (
React, React,
MailPoet, MailPoet,
classNames, classNames,
Router, Router,
FormField FormField,
) { jQuery
) => {
var Form = React.createClass({ const Form = React.createClass({
contextTypes: { contextTypes: {
router: React.PropTypes.object.isRequired router: React.PropTypes.object.isRequired,
}, },
getDefaultProps: function() { getDefaultProps: function () {
return { return {
params: {}, params: {},
}; };
}, },
getInitialState: function() { getInitialState: function () {
return { return {
loading: false, loading: false,
errors: [], errors: [],
item: {} item: {},
}; };
}, },
getValues: function() { getValues: function () {
return this.props.item ? this.props.item : this.state.item; return this.props.item ? this.props.item : this.state.item;
}, },
getErrors: function() { getErrors: function () {
return this.props.errors ? this.props.errors : this.state.errors; return this.props.errors ? this.props.errors : this.state.errors;
}, },
componentDidMount: function() { componentDidMount: function () {
if(this.isMounted()) { if (this.isMounted()) {
if(this.props.params.id !== undefined) { if (this.props.params.id !== undefined) {
this.loadItem(this.props.params.id); this.loadItem(this.props.params.id);
} else { } else {
this.setState({ this.setState({
item: jQuery('.mailpoet_form').serializeObject() item: jQuery('.mailpoet_form').mailpoetSerializeObject(),
}); });
} }
} }
}, },
componentWillReceiveProps: function(props) { componentWillReceiveProps: function (props) {
if(props.params.id === undefined) { if (props.params.id === undefined) {
this.setState({ this.setState({
loading: false, loading: false,
item: {} item: {},
}); });
if (props.item === undefined) { if (props.item === undefined) {
this.refs.form.reset(); this.refs.form.reset();
@ -60,7 +62,7 @@ define(
this.loadItem(props.params.id); this.loadItem(props.params.id);
} }
}, },
loadItem: function(id) { loadItem: function (id) {
this.setState({ loading: true }); this.setState({ loading: true });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
@ -68,28 +70,28 @@ define(
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'get', action: 'get',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
this.setState({ this.setState({
loading: false, loading: false,
item: response.data item: response.data,
}); });
}).fail((response) => { }).fail(() => {
this.setState({ this.setState({
loading: false, loading: false,
item: {} item: {},
}, function() { }, function () {
this.context.router.push('/new'); this.context.router.push('/new');
}); });
}); });
}, },
handleSubmit: function(e) { handleSubmit: function (e) {
e.preventDefault(); e.preventDefault();
// handle validation // handle validation
if(this.props.isValid !== undefined) { if (this.props.isValid !== undefined) {
if(this.props.isValid() === false) { if (this.props.isValid() === false) {
return; return;
} }
} }
@ -97,18 +99,18 @@ define(
this.setState({ loading: true }); this.setState({ loading: true });
// only get values from displayed fields // only get values from displayed fields
var item = {}; const item = {};
this.props.fields.map(function(field) { this.props.fields.map((field) => {
if(field['fields'] !== undefined) { if (field['fields'] !== undefined) {
field.fields.map(function(subfield) { field.fields.map((subfield) => {
item[subfield.name] = this.state.item[subfield.name]; item[subfield.name] = this.state.item[subfield.name];
}.bind(this)); });
} else { } else {
item[field.name] = this.state.item[field.name]; item[field.name] = this.state.item[field.name];
} }
}.bind(this)); });
// set id if specified // set id if specified
if(this.props.params.id !== undefined) { if (this.props.params.id !== undefined) {
item.id = this.props.params.id; item.id = this.props.params.id;
} }
@ -116,60 +118,61 @@ define(
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'save', action: 'save',
data: item data: item,
}).always(() => { }).always(() => {
this.setState({ loading: false }); this.setState({ loading: false });
}).done((response) => { }).done(() => {
if(this.props.onSuccess !== undefined) { if (this.props.onSuccess !== undefined) {
this.props.onSuccess(); this.props.onSuccess();
} else { } else {
this.context.router.push('/'); this.context.router.push('/');
} }
if(this.props.params.id !== undefined) { if (this.props.params.id !== undefined) {
this.props.messages.onUpdate(); this.props.messages.onUpdate();
} else { } else {
this.props.messages.onCreate(); this.props.messages.onCreate();
} }
}).fail((response) => { }).fail((response) => {
if(response.errors.length > 0) { if (response.errors.length > 0) {
this.setState({ errors: response.errors }); this.setState({ errors: response.errors });
} }
}); });
}, },
handleValueChange: function(e) { handleValueChange: function (e) {
if (this.props.onChange) { if (this.props.onChange) {
return this.props.onChange(e); return this.props.onChange(e);
} else {
var item = this.state.item,
field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item
});
return true;
} }
const item = this.state.item;
const field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item,
});
return true;
}, },
render: function() { render: function () {
if(this.getErrors() !== undefined) { let errors;
var errors = this.getErrors().map(function(error, index) { if (this.getErrors() !== undefined) {
errors = this.getErrors().map((error, index) => {
return ( return (
<p key={ 'error-'+index } className="mailpoet_error"> <p key={'error-' + index} className="mailpoet_error">
{ error.message } { error.message }
</p> </p>
); );
}); });
} }
var formClasses = classNames( const formClasses = classNames(
'mailpoet_form', 'mailpoet_form',
{ 'mailpoet_form_loading': this.state.loading || this.props.loading } { mailpoet_form_loading: this.state.loading || this.props.loading }
); );
var beforeFormContent = false; let beforeFormContent = false;
var afterFormContent = false; let afterFormContent = false;
if (this.props.beforeFormContent !== undefined) { if (this.props.beforeFormContent !== undefined) {
beforeFormContent = this.props.beforeFormContent(this.getValues()); beforeFormContent = this.props.beforeFormContent(this.getValues());
@ -179,18 +182,27 @@ define(
afterFormContent = this.props.afterFormContent(this.getValues()); afterFormContent = this.props.afterFormContent(this.getValues());
} }
var fields = this.props.fields.map(function(field, i) { const fields = this.props.fields.map((field, i) => {
// Compose an onChange handler from the default and custom one
let onValueChange = this.handleValueChange;
if (field.onBeforeChange) {
onValueChange = (e) => {
field.onBeforeChange(e);
return this.handleValueChange(e);
};
}
return ( return (
<FormField <FormField
field={ field } field={field}
item={ this.getValues() } item={this.getValues()}
onValueChange={ this.handleValueChange } onValueChange={onValueChange}
key={ 'field-'+i } /> key={'field-' + i} />
); );
}.bind(this)); });
var actions = false; let actions = false;
if(this.props.children) { if (this.props.children) {
actions = this.props.children; actions = this.props.children;
} else { } else {
actions = ( actions = (
@ -206,9 +218,9 @@ define(
<div> <div>
{ beforeFormContent } { beforeFormContent }
<form <form
id={ this.props.id } id={this.props.id}
ref="form" ref="form"
className={ formClasses } className={formClasses}
onSubmit={ onSubmit={
(this.props.onSubmit !== undefined) (this.props.onSubmit !== undefined)
? this.props.onSubmit ? this.props.onSubmit
@ -228,7 +240,7 @@ define(
{ afterFormContent } { afterFormContent }
</div> </div>
); );
} },
}); });
return Form; return Form;

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,26 @@
import React from 'react' import React from 'react';
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router' import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history' import { createHashHistory } from 'history';
import FormList from 'forms/list.jsx' import FormList from './list.jsx';
const history = useRouterHistory(createHashHistory)({ queryKey: false }); const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({ const App = React.createClass({
render() { render() {
return this.props.children return this.props.children;
} },
}); });
const container = document.getElementById('forms_container'); const container = document.getElementById('forms_container');
if(container) { if (container) {
ReactDOM.render(( ReactDOM.render((
<Router history={ history }> <Router history={history}>
<Route path="/" component={ App }> <Route path="/" component={App}>
<IndexRoute component={ FormList } /> <IndexRoute component={FormList} />
<Route path="*" component={ FormList } /> <Route path="*" component={FormList} />
</Route> </Route>
</Router> </Router>
), container); ), container);
} }

View File

@ -1,29 +1,28 @@
import React from 'react' import React from 'react';
import ReactDOM from 'react-dom' import classNames from 'classnames';
import { Router, Link } from 'react-router' import MailPoet from 'mailpoet';
import Listing from 'listing/listing.jsx' import jQuery from 'jquery';
import classNames from 'classnames' import Listing from '../listing/listing.jsx';
import MailPoet from 'mailpoet'
const columns = [ const columns = [
{ {
name: 'name', name: 'name',
label: MailPoet.I18n.t('formName'), label: MailPoet.I18n.t('formName'),
sortable: true sortable: true,
}, },
{ {
name: 'segments', name: 'segments',
label: MailPoet.I18n.t('segments') label: MailPoet.I18n.t('segments'),
}, },
{ {
name: 'signups', name: 'signups',
label: MailPoet.I18n.t('signups') label: MailPoet.I18n.t('signups'),
}, },
{ {
name: 'created_at', name: 'created_at',
label: MailPoet.I18n.t('createdOn'), label: MailPoet.I18n.t('createdOn'),
sortable: true sortable: true,
} },
]; ];
const messages = { const messages = {
@ -71,38 +70,38 @@ const messages = {
).replace('%$1d', count.toLocaleString()); ).replace('%$1d', count.toLocaleString());
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
} },
}; };
const bulk_actions = [ const bulk_actions = [
{ {
name: 'trash', name: 'trash',
label: MailPoet.I18n.t('moveToTrash'), label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash onSuccess: messages.onTrash,
} },
]; ];
const item_actions = [ const item_actions = [
{ {
name: 'edit', name: 'edit',
label: MailPoet.I18n.t('edit'), label: MailPoet.I18n.t('edit'),
link: function(item) { link: function (item) {
return ( return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a> <a href={`admin.php?page=mailpoet-form-editor&id=${item.id}`}>{MailPoet.I18n.t('edit')}</a>
); );
} },
}, },
{ {
name: 'duplicate', name: 'duplicate',
label: MailPoet.I18n.t('duplicate'), label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) { onClick: function (item, refresh) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
endpoint: 'forms', endpoint: 'forms',
action: 'duplicate', action: 'duplicate',
data: { data: {
id: item.id id: item.id,
} },
}).done((response) => { }).done((response) => {
MailPoet.Notice.success( MailPoet.Notice.success(
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.data.name) (MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.data.name)
@ -111,16 +110,16 @@ const item_actions = [
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
} }
}); });
} },
}, },
{ {
name: 'trash' name: 'trash',
} },
]; ];
const FormList = React.createClass({ const FormList = React.createClass({
@ -128,28 +127,28 @@ const FormList = React.createClass({
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
endpoint: 'forms', endpoint: 'forms',
action: 'create' action: 'create',
}).done((response) => { }).done((response) => {
window.location = mailpoet_form_edit_url + response.data.id; window.location = window.mailpoet_form_edit_url + response.data.id;
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
} }
}); });
}, },
renderItem(form, actions) { renderItem(form, actions) {
let row_classes = classNames( const row_classes = classNames(
'manage-column', 'manage-column',
'column-primary', 'column-primary',
'has-row-actions' 'has-row-actions'
); );
let segments = mailpoet_segments.filter(function(segment) { let segments = window.mailpoet_segments.filter((segment) => {
return (jQuery.inArray(segment.id, form.segments) !== -1); return (jQuery.inArray(segment.id, form.segments) !== -1);
}).map(function(segment) { }).map((segment) => {
return segment.name; return segment.name;
}).join(', '); }).join(', ');
@ -159,11 +158,11 @@ const FormList = React.createClass({
return ( return (
<div> <div>
<td className={ row_classes }> <td className={row_classes}>
<strong> <strong>
<a <a
className="row-title" className="row-title"
href={ `admin.php?page=mailpoet-form-editor&id=${form.id}` } href={`admin.php?page=mailpoet-form-editor&id=${form.id}`}
>{ form.name }</a> >{ form.name }</a>
</strong> </strong>
{ actions } { actions }
@ -187,25 +186,25 @@ const FormList = React.createClass({
{MailPoet.I18n.t('pageTitle')} <a {MailPoet.I18n.t('pageTitle')} <a
className="page-title-action" className="page-title-action"
href="javascript:;" href="javascript:;"
onClick={ this.createForm } onClick={this.createForm}
>{MailPoet.I18n.t('new')}</a> >{MailPoet.I18n.t('new')}</a>
</h1> </h1>
<Listing <Listing
limit={ mailpoet_listing_per_page } limit={window.mailpoet_listing_per_page}
location={ this.props.location } location={this.props.location}
params={ this.props.params } params={this.props.params}
messages={ messages } messages={messages}
search={ false } search={false}
endpoint="forms" endpoint="forms"
onRenderItem={ this.renderItem } onRenderItem={this.renderItem}
columns={ columns } columns={columns}
bulk_actions={ bulk_actions } bulk_actions={bulk_actions}
item_actions={ item_actions } item_actions={item_actions}
/> />
</div> </div>
); );
} },
}); });
module.exports = FormList; module.exports = FormList;

View File

@ -1,117 +1,119 @@
define('handlebars_helpers', ['handlebars'], function(Handlebars) { define('handlebars_helpers', ['handlebars'], function (Handlebars) {
// Handlebars helpers // Handlebars helpers
Handlebars.registerHelper('concat', function() { Handlebars.registerHelper('concat', function () {
var size = (arguments.length - 1), var size = (arguments.length - 1);
output = ''; var output = '';
for(var i = 0; i < size; i++) { var i;
output += arguments[i]; for (i = 0; i < size; i++) {
}; output += arguments[i];
return output; }
return output;
}); });
Handlebars.registerHelper('number_format', function(value, block) { Handlebars.registerHelper('number_format', function (value) {
return Number(value).toLocaleString(); return Number(value).toLocaleString();
}); });
Handlebars.registerHelper('date_format', function(timestamp, block) { Handlebars.registerHelper('date_format', function (timestamp, block) {
if(window.moment) { var f;
if(timestamp === undefined || isNaN(timestamp) || timestamp <= 0) { if (window.moment) {
return; if (timestamp === undefined || isNaN(timestamp) || timestamp <= 0) {
} return;
}
// set date format // set date format
var f = block.hash.format || "MMM Do, YYYY"; f = block.hash.format || 'MMM Do, YYYY';
// check if we passed a timestamp // check if we passed a timestamp
if(parseInt(timestamp, 10) == timestamp) { if (parseInt(timestamp, 10) == timestamp) {
return moment.unix(timestamp).format(f); return window.moment.unix(timestamp).format(f);
} else { } else {
return moment.utc(timestamp).format(f); return window.moment.utc(timestamp).format(f);
} }
} else { } else {
return timestamp; return timestamp;
}; }
}); });
Handlebars.registerHelper('cycle', function(value, block) { Handlebars.registerHelper('cycle', function (value, block) {
var values = value.split(' '); var values = value.split(' ');
return values[block.data.index % (values.length + 1)]; return values[block.data.index % (values.length + 1)];
}); });
Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) { Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
switch (operator) { switch (operator) {
case '==': case '==':
return (v1 == v2) ? options.fn(this) : options.inverse(this); return (v1 == v2) ? options.fn(this) : options.inverse(this);
case '===': case '===':
return (v1 === v2) ? options.fn(this) : options.inverse(this); return (v1 === v2) ? options.fn(this) : options.inverse(this);
case '!=': case '!=':
return (v1 != v2) ? options.fn(this) : options.inverse(this); return (v1 != v2) ? options.fn(this) : options.inverse(this);
case '!==': case '!==':
return (v1 !== v2) ? options.fn(this) : options.inverse(this); return (v1 !== v2) ? options.fn(this) : options.inverse(this);
case '<': case '<':
return (v1 < v2) ? options.fn(this) : options.inverse(this); return (v1 < v2) ? options.fn(this) : options.inverse(this);
case '<=': case '<=':
return (v1 <= v2) ? options.fn(this) : options.inverse(this); return (v1 <= v2) ? options.fn(this) : options.inverse(this);
case '>': case '>':
return (v1 > v2) ? options.fn(this) : options.inverse(this); return (v1 > v2) ? options.fn(this) : options.inverse(this);
case '>=': case '>=':
return (v1 >= v2) ? options.fn(this) : options.inverse(this); return (v1 >= v2) ? options.fn(this) : options.inverse(this);
case '&&': case '&&':
return (v1 && v2) ? options.fn(this) : options.inverse(this); return (v1 && v2) ? options.fn(this) : options.inverse(this);
case '||': case '||':
return (v1 || v2) ? options.fn(this) : options.inverse(this); return (v1 || v2) ? options.fn(this) : options.inverse(this);
case 'in': case 'in':
var values = v2.split(','); return (v2.indexOf(v1) !== -1) ? options.fn(this) : options.inverse(this);
return (v2.indexOf(v1) !== -1) ? options.fn(this) : options.inverse(this); default:
default: return options.inverse(this);
return options.inverse(this); }
}
}); });
Handlebars.registerHelper('nl2br', function(value, block) { Handlebars.registerHelper('nl2br', function (value) {
return value.gsub("\n", "<br />"); return value.gsub('\n', '<br />');
}); });
Handlebars.registerHelper('json_encode', function(value, block) { Handlebars.registerHelper('json_encode', function (value) {
return JSON.stringify(value); return JSON.stringify(value);
}); });
Handlebars.registerHelper('json_decode', function(value, block) { Handlebars.registerHelper('json_decode', function (value) {
return JSON.parse(value); return JSON.parse(value);
}); });
Handlebars.registerHelper('url', function(value, block) { Handlebars.registerHelper('url', function (value) {
var url = window.location.protocol + "//" + window.location.host + window.location.pathname; var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
return url + value; return url + value;
}); });
Handlebars.registerHelper('emailFromMailto', function(value) { Handlebars.registerHelper('emailFromMailto', function (value) {
var mailtoMatchingRegex = /^mailto\:/i; var mailtoMatchingRegex = /^mailto\:/i;
if (typeof value === 'string' && value.match(mailtoMatchingRegex)) { if (typeof value === 'string' && value.match(mailtoMatchingRegex)) {
return value.replace(mailtoMatchingRegex, ''); return value.replace(mailtoMatchingRegex, '');
} else { } else {
return value; return value;
} }
}); });
Handlebars.registerHelper('lookup', function(obj, field, options) { Handlebars.registerHelper('lookup', function (obj, field) {
return obj && obj[field]; return obj && obj[field];
}); });
Handlebars.registerHelper('rsa_key', function(value, block) { Handlebars.registerHelper('rsa_key', function (value) {
// extract all lines into an array var lines;
if(value === undefined) return ''; // extract all lines into an array
if (value === undefined) return '';
var lines = value.trim().split("\n"); lines = value.trim().split('\n');
// remove header & footer // remove header & footer
lines.shift(); lines.shift();
lines.pop(); lines.pop();
// return concatenated lines // return concatenated lines
return lines.join(''); return lines.join('');
}); });
Handlebars.registerHelper('trim', function(value, block) { Handlebars.registerHelper('trim', function (value) {
if(value === null || value === undefined) return ''; if (value === null || value === undefined) return '';
return value.trim(); return value.trim();
}); });
/** /**
@ -125,32 +127,33 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
* @return {String} The truncated string. * @return {String} The truncated string.
*/ */
Handlebars.registerHelper('ellipsis', function (str, limit, append) { Handlebars.registerHelper('ellipsis', function (str, limit, append) {
if (append === undefined) { var strAppend = append;
append = ''; var sanitized = str.replace(/(<([^>]+)>)/g, '');
} if (strAppend === undefined) {
var sanitized = str.replace(/(<([^>]+)>)/g, ''); strAppend = '';
if (sanitized.length > limit) { }
return sanitized.substr(0, limit - append.length) + append; if (sanitized.length > limit) {
} else { return sanitized.substr(0, limit - strAppend.length) + strAppend;
return sanitized; } else {
} return sanitized;
}
}); });
Handlebars.registerHelper('getNumber', function (string) { Handlebars.registerHelper('getNumber', function (string) {
return parseInt(string, 10); return parseInt(string, 10);
}); });
Handlebars.registerHelper('fontWithFallback', function(font) { Handlebars.registerHelper('fontWithFallback', function (font) {
switch(font) { switch (font) {
case 'Arial': return new Handlebars.SafeString("Arial, 'Helvetica Neue', Helvetica, sans-serif"); 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 '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 '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 '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 'Lucida': return new Handlebars.SafeString("'Lucida Sans Unicode', 'Lucida Grande', sans-serif");
case 'Tahoma': return new Handlebars.SafeString("Tahoma, Verdana, Segoe, 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 'Times New Roman': return new Handlebars.SafeString("'Times New Roman', Times, Baskerville, Georgia, serif");
case 'Trebuchet MS': return new Handlebars.SafeString("'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif"); case 'Trebuchet MS': return new Handlebars.SafeString("'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif");
case 'Verdana': return new Handlebars.SafeString("Verdana, Geneva, sans-serif"); case 'Verdana': return new Handlebars.SafeString('Verdana, Geneva, sans-serif');
default: return font; default: return font;
} }
}); });

View File

@ -0,0 +1,22 @@
define('helpTooltip', ['mailpoet', 'react', 'react-dom', 'help-tooltip.jsx'],
function (mp, React, ReactDOM, TooltipComponent) {
'use strict';
var MailPoet = mp;
MailPoet.helpTooltip = {
show: function (domContainerNode, opts) {
ReactDOM.render(React.createElement(
TooltipComponent, {
tooltip: opts.tooltip,
tooltipId: opts.tooltipId,
place: opts.place
}
), domContainerNode);
}
};
}
);

View File

@ -0,0 +1,63 @@
import React from 'react';
import ReactTooltip from 'react-tooltip';
import ReactHtmlParser from 'react-html-parser';
function Tooltip(props) {
let tooltipId = props.tooltipId;
let tooltip = props.tooltip;
// tooltip ID must be unique, defaults to tooltip text
if (!props.tooltipId && typeof props.tooltip === 'string') {
tooltipId = props.tooltip;
}
if (typeof props.tooltip === 'string') {
tooltip = (<span
style={{
pointerEvents: 'all',
maxWidth: '400',
display: 'inline-block',
}}
>
{ReactHtmlParser(props.tooltip)}
</span>);
}
return (
<span className={props.className}>
<span
style={{
cursor: 'pointer',
}}
className="tooltip dashicons dashicons-editor-help"
data-event="click"
data-tip
data-for={tooltipId}
>
</span>
<ReactTooltip
globalEventOff="click"
multiline={true}
id={tooltipId}
efect="solid"
place={props.place}
>
{tooltip}
</ReactTooltip>
</span>
);
}
Tooltip.propTypes = {
tooltipId: React.PropTypes.string,
tooltip: React.PropTypes.node.isRequired,
place: React.PropTypes.string,
className: React.PropTypes.string,
};
Tooltip.defaultProps = {
tooltipId: undefined,
place: undefined,
className: undefined,
};
module.exports = Tooltip;

View File

@ -0,0 +1,32 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';
import KnowledgeBase from 'help/knowledge_base.jsx';
import SystemInfo from 'help/system_info.jsx';
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
return this.props.children;
},
});
const container = document.getElementById('help_container');
if (container) {
ReactDOM.render((
<Router history={history}>
<Route path="/" component={App}>
<IndexRedirect to="knowledgeBase" />
{/* Pages */}
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={KnowledgeBase} />
<Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={SystemInfo} />
</Route>
</Router>
), container);
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import MailPoet from 'mailpoet';
import Tabs from './tabs.jsx';
function KnowledgeBase() {
return (
<div>
<Tabs tab="knowledgeBase" />
<p>{MailPoet.I18n.t('knowledgeBaseIntro')}</p>
<ul>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/116-common-problems">Common Problems</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/165-newsletters">Newsletters</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/156-migration-questions">Migration Questions</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/149-sending-methods">Sending Methods</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/139-subscription-forms">Subscription Forms</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/114-getting-started">Getting Started</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/123-newsletter-designer">Newsletter Designer</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/121-subscribers-and-lists">Subscribers and Lists</a></li>
</ul>
<a target="_blank" href="http://beta.docs.mailpoet.com/" className="button button-primary">{MailPoet.I18n.t('knowledgeBaseButton')}</a>
</div>
);
}
module.exports = KnowledgeBase;

View File

@ -0,0 +1,47 @@
import React from 'react';
import MailPoet from 'mailpoet';
import _ from 'underscore';
import Tabs from './tabs.jsx';
function handleFocus(event) {
event.target.select();
}
function printData(data) {
if (_.isObject(data)) {
const printableData = Object.keys(data).map((key) => {
return `${key}: ${data[key]}`;
});
return (<textarea
readOnly={true}
onFocus={handleFocus}
value={printableData.join('\n')}
style={{
width: '100%',
height: '400px',
}}
/>);
}
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
}
function KnowledgeBase() {
const data = window.help_scout_data;
return (
<div>
<Tabs tab="systemInfo" />
<div className="mailpoet_notice notice inline notice-success" style={{ marginTop: '1em' }}>
<p>{MailPoet.I18n.t('systemInfoIntro')}</p>
</div>
{printData(data)}
</div>
);
}
module.exports = KnowledgeBase;

View File

@ -0,0 +1,46 @@
import React from 'react';
import { Link } from 'react-router';
import classNames from 'classnames';
import MailPoet from 'mailpoet';
const tabs = [
{
name: 'knowledgeBase',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'),
link: '/knowledgeBase',
},
{
name: 'systemInfo',
label: MailPoet.I18n.t('tabSystemInfoTitle'),
link: '/systemInfo',
},
];
function Tabs(props) {
const tabLinks = tabs.map((tab, index) => {
const tabClasses = classNames(
'nav-tab',
{ 'nav-tab-active': (props.tab === tab.name) }
);
return (
<Link
key={'tab-' + index}
className={tabClasses}
to={tab.link}
>{ tab.label }</Link>
);
});
return (
<h2 className="nav-tab-wrapper">
{ tabLinks }
</h2>
);
}
Tabs.propTypes = { tab: React.PropTypes.string };
Tabs.defaultProps = { tab: 'knowledgeBase' };
module.exports = Tabs;

View File

@ -1,3 +0,0 @@
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:!0,baseUrl:"//mailpoet3.helpscoutdocs.com/"},contact:{enabled:!0,formId:"aa21ca80-a4f5-11e6-91aa-0a5fecc78a4d"}};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||{});
});

View File

@ -1,23 +1,25 @@
define('i18n', define('i18n',
[ [
'mailpoet' 'mailpoet'
], function( ], function (
MailPoet mp
) { ) {
'use strict'; 'use strict';
var translations = {}; var MailPoet = mp;
MailPoet.I18n = { var translations = {};
add: function(key, value) {
translations[key] = value;
},
t: function(key) {
return translations[key] || 'TRANSLATION "%$1s" NOT FOUND'.replace("%$1s", key);
},
all: function() {
return 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;
}
};
});

View File

@ -1,21 +1,24 @@
define('iframe', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { define('iframe', ['mailpoet'], function (mp) {
'use strict'; 'use strict';
var MailPoet = mp;
MailPoet.Iframe = { MailPoet.Iframe = {
marginY: 20, marginY: 20,
autoSize: function(iframe) { autoSize: function (iframe) {
if(!iframe) return; if (!iframe) return;
this.setSize( this.setSize(
iframe, iframe,
iframe.contentWindow.document.body.scrollHeight iframe.contentWindow.document.body.scrollHeight
); );
}, },
setSize: function(iframe, i) { setSize: function (sizeIframe, i) {
if(!iframe) return; var iframe = sizeIframe;
if (!iframe) return;
iframe.style.height = ( iframe.style.height = (
parseInt(i) + this.marginY parseInt(i, 10) + this.marginY
) + "px"; ) + 'px';
} }
}; };

View File

@ -2,9 +2,10 @@ define(
[ [
'jquery' 'jquery'
], ],
function( function (
$ jQuery
) { ) {
var $ = jQuery;
// Combination of jQuery.deparam and jQuery.serializeObject by Ben Alman. // Combination of jQuery.deparam and jQuery.serializeObject by Ben Alman.
/*! /*!
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
@ -22,31 +23,31 @@ define(
* Dual licensed under the MIT and GPL licenses. * Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/ * http://benalman.com/about/license/
*/ */
$.fn.serializeObject = function(coerce) { $.fn.mailpoetSerializeObject = function (coerce) {
var obj = {}, var obj = {};
coerce_types = { 'true': !0, 'false': !1, 'null': null }; var coerce_types = { true: !0, false: !1, null: null };
// Iterate over all name=value pairs. // Iterate over all name=value pairs.
$.each( this.serializeArray(), function(j,v){ $.each(this.serializeArray(), function (j, v) {
var key = v.name, var key = v.name;
val = v.value, var val = v.value;
cur = obj, var cur = obj;
i = 0, var i = 0;
// If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
// into its component parts. // into its component parts.
keys = key.split( '][' ), var keys = key.split('][');
keys_last = keys.length - 1; var keys_last = keys.length - 1;
// If the first keys part contains [ and the last ends with ], then [] // If the first keys part contains [ and the last ends with ], then []
// are correctly balanced. // are correctly balanced.
if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) { if (/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) {
// Remove the trailing ] from the last keys part. // Remove the trailing ] from the last keys part.
keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' ); keys[keys_last] = keys[keys_last].replace(/\]$/, '');
// Split first keys part into two parts on the [ and add them back onto // Split first keys part into two parts on the [ and add them back onto
// the beginning of the keys array. // the beginning of the keys array.
keys = keys.shift().split('[').concat( keys ); keys = keys.shift().split('[').concat(keys);
keys_last = keys.length - 1; keys_last = keys.length - 1;
} else { } else {
@ -55,14 +56,14 @@ define(
} }
// Coerce values. // Coerce values.
if ( coerce ) { if (coerce) {
val = val && !isNaN(val) ? +val // number val = val && !isNaN(val) ? +val // number
: val === 'undefined' ? undefined // undefined : val === 'undefined' ? undefined // undefined
: coerce_types[val] !== undefined ? coerce_types[val] // true, false, null : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
: val; // string : val; // string
} }
if ( keys_last ) { if (keys_last) {
// Complex key, build deep object structure based on a few rules: // Complex key, build deep object structure based on a few rules:
// * The 'cur' pointer starts at the object top-level. // * The 'cur' pointer starts at the object top-level.
// * [] = array push (n is set to array length), [n] = array if n is // * [] = array push (n is set to array length), [n] = array if n is
@ -72,25 +73,26 @@ define(
// object or array based on the type of the next keys part. // object or array based on the type of the next keys part.
// * Move the 'cur' pointer to the next level. // * Move the 'cur' pointer to the next level.
// * Rinse & repeat. // * Rinse & repeat.
for ( ; i <= keys_last; i++ ) { for (; i <= keys_last; i++) {
key = keys[i] === '' ? cur.length : keys[i]; key = keys[i] === '' ? cur.length : keys[i];
cur = cur[key] = i < keys_last cur[key] = i < keys_last
? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] ) ? cur[key] || (keys[i + 1] && isNaN(keys[i + 1]) ? {} : [])
: val; : val;
cur = cur[key];
} }
} else { } else {
// Simple key, even simpler rules, since only scalars and shallow // Simple key, even simpler rules, since only scalars and shallow
// arrays are allowed. // arrays are allowed.
if ( $.isArray( obj[key] ) ) { if ($.isArray(obj[key])) {
// val is already an array, so push on the next value. // val is already an array, so push on the next value.
obj[key].push( val ); obj[key].push(val);
} else if ( obj[key] !== undefined ) { } else if (obj[key] !== undefined) {
// val isn't an array, but since a second value has been specified, // val isn't an array, but since a second value has been specified,
// convert val into an array. // convert val into an array.
obj[key] = [ obj[key], val ]; obj[key] = [obj[key], val];
} else { } else {
// val is a scalar. // val is a scalar.
@ -104,4 +106,4 @@ define(
return $; return $;
} }
); );

View File

@ -1,84 +1,84 @@
define([ define([
'react', 'react',
'mailpoet' 'mailpoet',
], ],
function( (
React, React,
MailPoet MailPoet
) { ) => {
var ListingBulkActions = React.createClass({ const ListingBulkActions = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
action: false, action: false,
extra: false extra: false,
} };
}, },
handleChangeAction: function(e) { handleChangeAction: function (e) {
this.setState({ this.setState({
action: e.target.value, action: e.target.value,
extra: false extra: false,
}, function() { }, () => {
var action = this.getSelectedAction(); const action = this.getSelectedAction();
// action on select callback // action on select callback
if(action !== null && action['onSelect'] !== undefined) { if (action !== null && action['onSelect'] !== undefined) {
this.setState({ this.setState({
extra: action.onSelect(e) extra: action.onSelect(e),
}); });
} }
}.bind(this)); });
}, },
handleApplyAction: function(e) { handleApplyAction: function (e) {
e.preventDefault(); e.preventDefault();
var action = this.getSelectedAction(); const action = this.getSelectedAction();
if(action === null) { if (action === null) {
return; return;
} }
var selected_ids = (this.props.selection !== 'all') const selected_ids = (this.props.selection !== 'all')
? this.props.selected_ids ? this.props.selected_ids
: []; : [];
var data = (action['getData'] !== undefined) const data = (action['getData'] !== undefined)
? action.getData() ? action.getData()
: {}; : {};
data.action = this.state.action; data.action = this.state.action;
var onSuccess = function() {}; let onSuccess = function () {};
if(action['onSuccess'] !== undefined) { if (action['onSuccess'] !== undefined) {
onSuccess = action.onSuccess; onSuccess = action.onSuccess;
} }
if(data.action) { if (data.action) {
const promise = this.props.onBulkAction(selected_ids, data); const promise = this.props.onBulkAction(selected_ids, data);
if (promise !== false) { if (promise !== false) {
promise.then(onSuccess); promise.then(onSuccess);
}; }
} }
this.setState({ this.setState({
action: false, action: false,
extra: false extra: false,
}); });
}, },
getSelectedAction: function() { getSelectedAction: function () {
var selected_action = this.refs.action.value; const selected_action = this.refs.action.value;
if(selected_action.length > 0) { if (selected_action.length > 0) {
var action = this.props.bulk_actions.filter(function(action) { const action = this.props.bulk_actions.filter((action) => {
return (action.name === selected_action); return (action.name === selected_action);
}); });
if(action.length > 0) { if (action.length > 0) {
return action[0]; return action[0];
} }
} }
return null; return null;
}, },
render: function() { render: function () {
if(this.props.bulk_actions.length === 0) { if (this.props.bulk_actions.length === 0) {
return null; return null;
} }
@ -93,29 +93,29 @@ function(
<select <select
name="bulk_actions" name="bulk_actions"
ref="action" ref="action"
value={ this.state.action } value={this.state.action}
onChange={this.handleChangeAction} onChange={this.handleChangeAction}
> >
<option value="">{MailPoet.I18n.t('bulkActions')}</option> <option value="">{MailPoet.I18n.t('bulkActions')}</option>
{ this.props.bulk_actions.map(function(action, index) { { this.props.bulk_actions.map((action, index) => {
return ( return (
<option <option
value={ action.name } value={action.name}
key={ 'action-' + index } key={'action-' + index}
>{ action.label }</option> >{ action.label }</option>
); );
}.bind(this)) } }) }
</select> </select>
<input <input
onClick={ this.handleApplyAction } onClick={this.handleApplyAction}
type="submit" type="submit"
defaultValue={MailPoet.I18n.t('apply')} defaultValue={MailPoet.I18n.t('apply')}
className="button action" /> className="button action" />
{ this.state.extra } { this.state.extra }
</div> </div>
); );
} },
}); });
return ListingBulkActions; return ListingBulkActions;

View File

@ -1,27 +1,30 @@
define([ define([
'react', 'react',
'jquery', 'jquery',
'mailpoet' 'mailpoet',
], ],
function( (
React, React,
jQuery, jQuery,
MailPoet MailPoet
) { ) => {
var ListingFilters = React.createClass({ const ListingFilters = React.createClass({
handleFilterAction: function() { handleFilterAction: function () {
let filters = {}; const filters = {};
this.getAvailableFilters().map((filter, i) => { this.getAvailableFilters().map((filter, i) => {
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value filters[this.refs['filter-' + i].name] = this.refs['filter-' + i].value;
}); });
if (this.props.onBeforeSelectFilter) {
this.props.onBeforeSelectFilter(filters);
}
return this.props.onSelectFilter(filters); return this.props.onSelectFilter(filters);
}, },
handleEmptyTrash: function() { handleEmptyTrash: function () {
return this.props.onEmptyTrash(); return this.props.onEmptyTrash();
}, },
getAvailableFilters: function() { getAvailableFilters: function () {
let filters = this.props.filters; const filters = this.props.filters;
return Object.keys(filters).filter(function(filter) { return Object.keys(filters).filter((filter) => {
return !( return !(
filters[filter].length === 0 filters[filter].length === 0
|| ( || (
@ -31,39 +34,39 @@ function(
); );
}); });
}, },
componentDidUpdate: function() { componentDidUpdate: function () {
const selected_filters = this.props.filter; const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters().map( this.getAvailableFilters().map(
function(filter, i) { (filter, i) => {
if (selected_filters[filter] !== undefined && selected_filters[filter]) { if (selected_filters[filter] !== undefined && selected_filters[filter]) {
jQuery(this.refs['filter-'+i]) jQuery(this.refs['filter-' + i])
.val(selected_filters[filter]) .val(selected_filters[filter])
.trigger('change'); .trigger('change');
} }
}.bind(this) }
); );
}, },
render: function() { render: function () {
const filters = this.props.filters; const filters = this.props.filters;
const available_filters = this.getAvailableFilters() const available_filters = this.getAvailableFilters()
.map(function(filter, i) { .map((filter, i) => {
return ( return (
<select <select
ref={ `filter-${i}` } ref={`filter-${i}`}
key={ `filter-${i}` } key={`filter-${i}`}
name={ filter } name={filter}
> >
{ filters[filter].map(function(option, j) { { filters[filter].map((option, j) => {
return ( return (
<option <option
value={ option.value } value={option.value}
key={ 'filter-option-' + j } key={'filter-option-' + j}
>{ option.label }</option> >{ option.label }</option>
); );
}.bind(this)) } }) }
</select> </select>
); );
}.bind(this)); });
let button; let button;
@ -71,7 +74,7 @@ function(
button = ( button = (
<input <input
id="post-query-submit" id="post-query-submit"
onClick={ this.handleFilterAction } onClick={this.handleFilterAction}
type="submit" type="submit"
defaultValue={MailPoet.I18n.t('filter')} defaultValue={MailPoet.I18n.t('filter')}
className="button" /> className="button" />
@ -82,7 +85,7 @@ function(
if (this.props.group === 'trash') { if (this.props.group === 'trash') {
empty_trash = ( empty_trash = (
<input <input
onClick={ this.handleEmptyTrash } onClick={this.handleEmptyTrash}
type="submit" type="submit"
value={MailPoet.I18n.t('emptyTrash')} value={MailPoet.I18n.t('emptyTrash')}
className="button" className="button"
@ -97,7 +100,7 @@ function(
{ empty_trash } { empty_trash }
</div> </div>
); );
} },
}); });
return ListingFilters; return ListingFilters;

View File

@ -1,40 +1,40 @@
define(['react', 'classnames'], function(React, classNames) { define(['react', 'classnames'], (React, classNames) => {
var ListingGroups = React.createClass({ const ListingGroups = React.createClass({
handleSelect: function(group) { handleSelect: function (group) {
return this.props.onSelectGroup(group); return this.props.onSelectGroup(group);
}, },
render: function() { render: function () {
var groups = this.props.groups.map(function(group, index) { const groups = this.props.groups.map((group, index) => {
if(group.name === 'trash' && group.count === 0) { if (group.name === 'trash' && group.count === 0) {
return false; return false;
} }
var classes = classNames( const classes = classNames(
{ 'current' : (group.name === this.props.group) } { current: (group.name === this.props.group) }
); );
return (
<li key={index}>
{(index > 0) ? ' |' : ''}
<a
href="javascript:;"
className={classes}
onClick={this.handleSelect.bind(this, group.name)} >
{group.label} <span className="count">({ group.count.toLocaleString() })</span>
</a>
</li>
);
}.bind(this));
return ( return (
<ul className="subsubsub"> <li key={index}>
{ groups } {(index > 0) ? ' |' : ''}
</ul> <a
href="javascript:;"
className={classes}
onClick={this.handleSelect.bind(this, group.name)} >
{group.label} <span className="count">({ group.count.toLocaleString() })</span>
</a>
</li>
); );
} });
});
return ListingGroups; return (
} <ul className="subsubsub">
); { groups }
</ul>
);
},
});
return ListingGroups;
}
);

View File

@ -1,31 +1,32 @@
import MailPoet from 'mailpoet' import MailPoet from 'mailpoet';
import React from 'react' import React from 'react';
import classNames from 'classnames' import classNames from 'classnames';
const ListingHeader = React.createClass({ const ListingHeader = React.createClass({
handleSelectItems: function() { handleSelectItems: function () {
return this.props.onSelectItems( return this.props.onSelectItems(
this.refs.toggle.checked this.refs.toggle.checked
); );
}, },
render: function() { render: function () {
const columns = this.props.columns.map(function(column, index) { const columns = this.props.columns.map((column, index) => {
column.is_primary = (index === 0); const renderColumn = column;
column.sorted = (this.props.sort_by === column.name) renderColumn.is_primary = (index === 0);
renderColumn.sorted = (this.props.sort_by === column.name)
? this.props.sort_order ? this.props.sort_order
: 'desc'; : 'desc';
return ( return (
<ListingColumn <ListingColumn
onSort={this.props.onSort} onSort={this.props.onSort}
sort_by={this.props.sort_by} sort_by={this.props.sort_by}
key={ 'column-' + index } key={'column-' + index}
column={column} /> column={renderColumn} />
); );
}.bind(this)); });
let checkbox; let checkbox;
if(this.props.is_selectable === true) { if (this.props.is_selectable === true) {
checkbox = ( checkbox = (
<th <th
className="manage-column column-cb check-column"> className="manage-column column-cb check-column">
@ -36,8 +37,8 @@ const ListingHeader = React.createClass({
type="checkbox" type="checkbox"
name="select_all" name="select_all"
ref="toggle" ref="toggle"
checked={ this.props.selection } checked={this.props.selection}
onChange={ this.handleSelectItems } /> onChange={this.handleSelectItems} />
</th> </th>
); );
} }
@ -48,28 +49,28 @@ const ListingHeader = React.createClass({
{columns} {columns}
</tr> </tr>
); );
} },
}); });
const ListingColumn = React.createClass({ const ListingColumn = React.createClass({
handleSort: function() { handleSort: function () {
const sort_by = this.props.column.name; const sort_by = this.props.column.name;
const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc'; const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order); this.props.onSort(sort_by, sort_order);
}, },
render: function() { render: function () {
const classes = classNames( const classes = classNames(
'manage-column', 'manage-column',
{ 'column-primary': this.props.column.is_primary }, { 'column-primary': this.props.column.is_primary },
{ 'sortable': this.props.column.sortable }, { sortable: this.props.column.sortable },
this.props.column.sorted, this.props.column.sorted,
{ 'sorted': (this.props.sort_by === this.props.column.name) } { sorted: (this.props.sort_by === this.props.column.name) }
); );
let label; let label;
if(this.props.column.sortable === true) { if (this.props.column.sortable === true) {
label = ( label = (
<a onClick={ this.handleSort }> <a onClick={this.handleSort}>
<span>{ this.props.column.label }</span> <span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span> <span className="sorting-indicator"></span>
</a> </a>
@ -79,13 +80,13 @@ const ListingColumn = React.createClass({
} }
return ( return (
<th <th
className={ classes } className={classes}
id={this.props.column.name } id={this.props.column.name}
scope="col" scope="col"
width={ this.props.column.width || null } width={this.props.column.width || null}
>{label}</th> >{label}</th>
); );
} },
}); });
module.exports = ListingHeader; module.exports = ListingHeader;

View File

@ -1,23 +1,23 @@
import MailPoet from 'mailpoet' import MailPoet from 'mailpoet';
import jQuery from 'jquery' import jQuery from 'jquery';
import React from 'react' import React from 'react';
import _ from 'underscore' import _ from 'underscore';
import { Router, Link } from 'react-router' import { Link } from 'react-router';
import classNames from 'classnames' import classNames from 'classnames';
import ListingBulkActions from 'listing/bulk_actions.jsx' import ListingBulkActions from 'listing/bulk_actions.jsx';
import ListingHeader from 'listing/header.jsx' import ListingHeader from 'listing/header.jsx';
import ListingPages from 'listing/pages.jsx' import ListingPages from 'listing/pages.jsx';
import ListingSearch from 'listing/search.jsx' import ListingSearch from 'listing/search.jsx';
import ListingGroups from 'listing/groups.jsx' import ListingGroups from 'listing/groups.jsx';
import ListingFilters from 'listing/filters.jsx' import ListingFilters from 'listing/filters.jsx';
const ListingItem = React.createClass({ const ListingItem = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
expanded: false expanded: false,
}; };
}, },
handleSelectItem: function(e) { handleSelectItem: function (e) {
this.props.onSelectItem( this.props.onSelectItem(
parseInt(e.target.value, 10), parseInt(e.target.value, 10),
e.target.checked e.target.checked
@ -25,20 +25,20 @@ const ListingItem = React.createClass({
return !e.target.checked; return !e.target.checked;
}, },
handleRestoreItem: function(id) { handleRestoreItem: function (id) {
this.props.onRestoreItem(id); this.props.onRestoreItem(id);
}, },
handleTrashItem: function(id) { handleTrashItem: function (id) {
this.props.onTrashItem(id); this.props.onTrashItem(id);
}, },
handleDeleteItem: function(id) { handleDeleteItem: function (id) {
this.props.onDeleteItem(id); this.props.onDeleteItem(id);
}, },
handleToggleItem: function(id) { handleToggleItem: function () {
this.setState({ expanded: !this.state.expanded }); this.setState({ expanded: !this.state.expanded });
}, },
render: function() { render: function () {
var checkbox = false; let checkbox = false;
if (this.props.is_selectable === true) { if (this.props.is_selectable === true) {
checkbox = ( checkbox = (
@ -48,12 +48,12 @@ const ListingItem = React.createClass({
}</label> }</label>
<input <input
type="checkbox" type="checkbox"
value={ this.props.item.id } value={this.props.item.id}
checked={ checked={
this.props.item.selected || this.props.selection === 'all' this.props.item.selected || this.props.selection === 'all'
} }
onChange={ this.handleSelectItem } onChange={this.handleSelectItem}
disabled={ this.props.selection === 'all' } /> disabled={this.props.selection === 'all'} />
</th> </th>
); );
} }
@ -63,7 +63,7 @@ const ListingItem = React.createClass({
if (custom_actions.length > 0) { if (custom_actions.length > 0) {
let is_first = true; let is_first = true;
item_actions = custom_actions.map(function(action, index) { item_actions = custom_actions.map((action, index) => {
if (action.display !== undefined) { if (action.display !== undefined) {
if (action.display(this.props.item) === false) { if (action.display(this.props.item) === false) {
return; return;
@ -74,14 +74,14 @@ const ListingItem = React.createClass({
if (action.name === 'trash') { if (action.name === 'trash') {
custom_action = ( custom_action = (
<span key={ 'action-'+index } className="trash"> <span key={'action-' + index} className="trash">
{(!is_first) ? ' | ' : ''} {(!is_first) ? ' | ' : ''}
<a <a
href="javascript:;" href="javascript:;"
onClick={ this.handleTrashItem.bind( onClick={this.handleTrashItem.bind(
null, null,
this.props.item.id this.props.item.id
) }> )}>
{MailPoet.I18n.t('moveToTrash')} {MailPoet.I18n.t('moveToTrash')}
</a> </a>
</span> </span>
@ -89,8 +89,8 @@ const ListingItem = React.createClass({
} else if (action.refresh) { } else if (action.refresh) {
custom_action = ( custom_action = (
<span <span
onClick={ this.props.onRefreshItems } onClick={this.props.onRefreshItems}
key={ 'action-'+index } className={ action.name }> key={'action-' + index} className={action.name}>
{(!is_first) ? ' | ' : ''} {(!is_first) ? ' | ' : ''}
{ action.link(this.props.item) } { action.link(this.props.item) }
</span> </span>
@ -98,7 +98,7 @@ const ListingItem = React.createClass({
} else if (action.link) { } else if (action.link) {
custom_action = ( custom_action = (
<span <span
key={ 'action-'+index } className={ action.name }> key={'action-' + index} className={action.name}>
{(!is_first) ? ' | ' : ''} {(!is_first) ? ' | ' : ''}
{ action.link(this.props.item) } { action.link(this.props.item) }
</span> </span>
@ -106,7 +106,7 @@ const ListingItem = React.createClass({
} else { } else {
custom_action = ( custom_action = (
<span <span
key={ 'action-'+index } className={ action.name }> key={'action-' + index} className={action.name}>
{(!is_first) ? ' | ' : ''} {(!is_first) ? ' | ' : ''}
<a href="javascript:;" onClick={ <a href="javascript:;" onClick={
(action.onClick !== undefined) (action.onClick !== undefined)
@ -125,11 +125,11 @@ const ListingItem = React.createClass({
} }
return custom_action; return custom_action;
}.bind(this)); });
} else { } else {
item_actions = ( item_actions = (
<span className="edit"> <span className="edit">
<Link to={ `/edit/${ this.props.item.id }` }>{MailPoet.I18n.t('edit')}</Link> <Link to={`/edit/${this.props.item.id}`}>{MailPoet.I18n.t('edit')}</Link>
</span> </span>
); );
} }
@ -137,13 +137,13 @@ const ListingItem = React.createClass({
let actions; let actions;
if (this.props.group === 'trash') { if (this.props.group === 'trash') {
actions = ( actions = (
<div> <div>
<div className="row-actions"> <div className="row-actions">
<span> <span>
<a <a
href="javascript:;" href="javascript:;"
onClick={ this.handleRestoreItem.bind( onClick={this.handleRestoreItem.bind(
null, null,
this.props.item.id this.props.item.id
)} )}
@ -154,7 +154,7 @@ const ListingItem = React.createClass({
<a <a
className="submitdelete" className="submitdelete"
href="javascript:;" href="javascript:;"
onClick={ this.handleDeleteItem.bind( onClick={this.handleDeleteItem.bind(
null, null,
this.props.item.id this.props.item.id
)} )}
@ -162,7 +162,7 @@ const ListingItem = React.createClass({
</span> </span>
</div> </div>
<button <button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) } onClick={this.handleToggleItem.bind(null, this.props.item.id)}
className="toggle-row" type="button"> className="toggle-row" type="button">
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span> <span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button> </button>
@ -175,7 +175,7 @@ const ListingItem = React.createClass({
{ item_actions } { item_actions }
</div> </div>
<button <button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) } onClick={this.handleToggleItem.bind(null, this.props.item.id)}
className="toggle-row" type="button"> className="toggle-row" type="button">
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span> <span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button> </button>
@ -186,17 +186,17 @@ const ListingItem = React.createClass({
const row_classes = classNames({ 'is-expanded': this.state.expanded }); const row_classes = classNames({ 'is-expanded': this.state.expanded });
return ( return (
<tr className={ row_classes }> <tr className={row_classes}>
{ checkbox } { checkbox }
{ this.props.onRenderItem(this.props.item, actions) } { this.props.onRenderItem(this.props.item, actions) }
</tr> </tr>
); );
} },
}); });
const ListingItems = React.createClass({ const ListingItems = React.createClass({
render: function() { render: function () {
if (this.props.items.length === 0) { if (this.props.items.length === 0) {
let message; let message;
if (this.props.loading === true) { if (this.props.loading === true) {
@ -223,24 +223,24 @@ const ListingItems = React.createClass({
</tr> </tr>
</tbody> </tbody>
); );
} else { }
const select_all_classes = classNames( const select_all_classes = classNames(
'mailpoet_select_all', 'mailpoet_select_all',
{ 'mailpoet_hidden': ( { mailpoet_hidden: (
this.props.selection === false this.props.selection === false
|| (this.props.count <= this.props.limit) || (this.props.count <= this.props.limit)
) ),
} }
); );
return ( return (
<tbody> <tbody>
<tr className={ select_all_classes }> <tr className={select_all_classes}>
<td colSpan={ <td colSpan={
this.props.columns.length this.props.columns.length
+ (this.props.is_selectable ? 1 : 0) + (this.props.is_selectable ? 1 : 0)
}> }>
{ {
(this.props.selection !== 'all') (this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLabel') ? MailPoet.I18n.t('selectAllLabel')
: MailPoet.I18n.t('selectedAllLabel').replace( : MailPoet.I18n.t('selectedAllLabel').replace(
@ -249,48 +249,49 @@ const ListingItems = React.createClass({
) )
} }
&nbsp; &nbsp;
<a <a
onClick={ this.props.onSelectAll } onClick={this.props.onSelectAll}
href="javascript:;">{ href="javascript:;">{
(this.props.selection !== 'all') (this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLink') ? MailPoet.I18n.t('selectAllLink')
: MailPoet.I18n.t('clearSelection') : MailPoet.I18n.t('clearSelection')
}</a> }</a>
</td> </td>
</tr> </tr>
{this.props.items.map(function(item, index) { {this.props.items.map((item, index) => {
item.id = parseInt(item.id, 10); const renderItem = item;
item.selected = (this.props.selected_ids.indexOf(item.id) !== -1); renderItem.id = parseInt(item.id, 10);
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
return ( return (
<ListingItem <ListingItem
columns={ this.props.columns } columns={this.props.columns}
onSelectItem={ this.props.onSelectItem } onSelectItem={this.props.onSelectItem}
onRenderItem={ this.props.onRenderItem } onRenderItem={this.props.onRenderItem}
onDeleteItem={ this.props.onDeleteItem } onDeleteItem={this.props.onDeleteItem}
onRestoreItem={ this.props.onRestoreItem } onRestoreItem={this.props.onRestoreItem}
onTrashItem={ this.props.onTrashItem } onTrashItem={this.props.onTrashItem}
onRefreshItems={ this.props.onRefreshItems } onRefreshItems={this.props.onRefreshItems}
selection={ this.props.selection } selection={this.props.selection}
is_selectable={ this.props.is_selectable } is_selectable={this.props.is_selectable}
item_actions={ this.props.item_actions } item_actions={this.props.item_actions}
group={ this.props.group } group={this.props.group}
key={ `item-${item.id}-${index}` } key={`item-${renderItem.id}-${index}`}
item={ item } /> item={renderItem} />
); );
}.bind(this))} })}
</tbody> </tbody>
); );
}
} },
}); });
const Listing = React.createClass({ const Listing = React.createClass({
contextTypes: { contextTypes: {
router: React.PropTypes.object.isRequired router: React.PropTypes.object.isRequired,
}, },
getInitialState: function() { getInitialState: function () {
return { return {
loading: false, loading: false,
search: '', search: '',
@ -306,31 +307,30 @@ const Listing = React.createClass({
filter: {}, filter: {},
selected_ids: [], selected_ids: [],
selection: false, selection: false,
meta: {} meta: {},
}; };
}, },
getParam: function(param) { getParam: function (param) {
const regex = /(.*)\[(.*)\]/; const regex = /(.*)\[(.*)\]/;
const matches = regex.exec(param); const matches = regex.exec(param);
return [matches[1], matches[2]]; return [matches[1], matches[2]];
}, },
initWithParams: function(params) { initWithParams: function (params) {
let state = this.getInitialState(); const state = this.getInitialState();
// check for url params // check for url params
if (params.splat) { if (params.splat) {
params.splat.split('/').map(param => { params.splat.split('/').map((param) => {
let [key, value] = this.getParam(param); const [key, value] = this.getParam(param);
switch(key) { const filters = {};
switch (key) {
case 'filter': case 'filter':
let filters = {}; value.split('&').map((pair) => {
value.split('&').map(function(pair) { const [k, v] = pair.split('=');
let [k, v] = pair.split('=') filters[k] = v;
filters[k] = v });
}
);
state.filter = filters; state.filter = filters;
break; break;
default: default:
state[key] = value; state[key] = value;
} }
@ -352,13 +352,13 @@ const Listing = React.createClass({
state.sort_order = this.props.sort_order; state.sort_order = this.props.sort_order;
} }
this.setState(state, function() { this.setState(state, () => {
this.getItems(); this.getItems();
}.bind(this)); });
}, },
getParams: function() { getParams: function () {
// get all route parameters (without the "splat") // get all route parameters (without the "splat")
let params = _.omit(this.props.params, 'splat'); const params = _.omit(this.props.params, 'splat');
// TODO: // TODO:
// find a way to set the "type" in the routes definition // find a way to set the "type" in the routes definition
// so that it appears in `this.props.params` // so that it appears in `this.props.params`
@ -367,10 +367,10 @@ const Listing = React.createClass({
} }
return params; return params;
}, },
setParams: function() { setParams: function () {
if (this.props.location) { if (this.props.location) {
let params = Object.keys(this.state) const params = Object.keys(this.state)
.filter(key => { .filter((key) => {
return ( return (
[ [
'group', 'group',
@ -378,74 +378,75 @@ const Listing = React.createClass({
'search', 'search',
'page', 'page',
'sort_by', 'sort_by',
'sort_order' 'sort_order',
].indexOf(key) !== -1 ].indexOf(key) !== -1
) );
}) })
.map(key => { .map((key) => {
let value = this.state[key]; let value = this.state[key];
if (value === Object(value)) { if (value === Object(value)) {
value = jQuery.param(value) value = jQuery.param(value);
} else if (value === Boolean(value)) { } else if (value === Boolean(value)) {
value = value.toString() value = value.toString();
} }
if (value !== '' && value !== null) { if (value !== '' && value !== null) {
return `${key}[${value}]` return `${key}[${value}]`;
} }
}) })
.filter(key => { return (key !== undefined) }) .filter((key) => { return (key !== undefined); })
.join('/'); .join('/');
// set url // set url
let url = this.getUrlWithParams(params); const url = this.getUrlWithParams(params);
if (this.props.location.pathname !== url) { if (this.props.location.pathname !== url) {
this.context.router.push(`${url}`); this.context.router.push(`${url}`);
} }
} }
}, },
getUrlWithParams: function(params) { getUrlWithParams: function (params) {
let base_url = (this.props.base_url !== undefined) let base_url = (this.props.base_url !== undefined)
? this.props.base_url ? this.props.base_url
: null; : null;
if (base_url !== null) { if (base_url !== null) {
base_url = this.setBaseUrlParams(base_url); base_url = this.setBaseUrlParams(base_url);
return `/${ base_url }/${ params }`; return `/${base_url}/${params}`;
} else {
return `/${ params }`;
} }
return `/${params}`;
}, },
setBaseUrlParams: function(base_url) { setBaseUrlParams: function (base_url) {
if (base_url.indexOf(':') !== -1) { let ret = base_url;
if (ret.indexOf(':') !== -1) {
const params = this.getParams(); const params = this.getParams();
Object.keys(params).map((key) => { Object.keys(params).map((key) => {
if (base_url.indexOf(':'+key) !== -1) { if (ret.indexOf(':' + key) !== -1) {
base_url = base_url.replace(':'+key, params[key]); ret = ret.replace(':' + key, params[key]);
} }
}); });
} }
return base_url; return ret;
}, },
componentDidMount: function() { componentDidMount: function () {
if (this.isMounted()) { if (this.isMounted()) {
const params = this.props.params || {}; const params = this.props.params || {};
this.initWithParams(params); this.initWithParams(params);
if (this.props.auto_refresh) { if (this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) { jQuery(document).on('heartbeat-tick.mailpoet', () => {
this.getItems(); this.getItems();
}.bind(this)); });
} }
} }
}, },
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function (nextProps) {
const params = nextProps.params || {}; const params = nextProps.params || {};
this.initWithParams(params); this.initWithParams(params);
}, },
getItems: function() { getItems: function () {
if (this.isMounted()) { if (this.isMounted()) {
this.setState({ loading: true }); this.setState({ loading: true });
@ -463,8 +464,8 @@ const Listing = React.createClass({
filter: this.state.filter, filter: this.state.filter,
search: this.state.search, search: this.state.search,
sort_by: this.state.sort_by, sort_by: this.state.sort_by,
sort_order: this.state.sort_order sort_order: this.state.sort_order,
} },
}).always(() => { }).always(() => {
this.setState({ loading: false }); this.setState({ loading: false });
}).done((response) => { }).done((response) => {
@ -473,7 +474,7 @@ const Listing = React.createClass({
filters: response.meta.filters || {}, filters: response.meta.filters || {},
groups: response.meta.groups || [], groups: response.meta.groups || [],
count: response.meta.count || 0, count: response.meta.count || 0,
meta: _.omit(response.meta, ['filters', 'groups', 'count']) meta: _.omit(response.meta, ['filters', 'groups', 'count']),
}, () => { }, () => {
// if viewing an empty trash // if viewing an empty trash
if (this.state.group === 'trash' && response.meta.count === 0) { if (this.state.group === 'trash' && response.meta.count === 0) {
@ -489,17 +490,17 @@ const Listing = React.createClass({
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
} }
}); });
} }
}, },
handleRestoreItem: function(id) { handleRestoreItem: function (id) {
this.setState({ this.setState({
loading: true, loading: true,
page: 1 page: 1,
}); });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
@ -507,8 +508,8 @@ const Listing = React.createClass({
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'restore', action: 'restore',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
@ -519,15 +520,15 @@ const Listing = React.createClass({
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleTrashItem: function(id) { handleTrashItem: function (id) {
this.setState({ this.setState({
loading: true, loading: true,
page: 1 page: 1,
}); });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
@ -535,8 +536,8 @@ const Listing = React.createClass({
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'trash', action: 'trash',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
@ -547,15 +548,15 @@ const Listing = React.createClass({
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleDeleteItem: function(id) { handleDeleteItem: function (id) {
this.setState({ this.setState({
loading: true, loading: true,
page: 1 page: 1,
}); });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
@ -563,8 +564,8 @@ const Listing = React.createClass({
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'delete', action: 'delete',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
@ -575,15 +576,15 @@ const Listing = React.createClass({
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleEmptyTrash: function() { handleEmptyTrash: function () {
return this.handleBulkAction('all', { return this.handleBulkAction('all', {
action: 'delete', action: 'delete',
group: 'trash' group: 'trash',
}).done((response) => { }).done((response) => {
MailPoet.Notice.success( MailPoet.Notice.success(
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response.meta.count) MailPoet.I18n.t('permanentlyDeleted').replace('%d', response.meta.count)
@ -592,12 +593,12 @@ const Listing = React.createClass({
this.handleGroup('all'); this.handleGroup('all');
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleBulkAction: function(selected_ids, params) { handleBulkAction: function (selected_ids, params) {
if ( if (
this.state.selection === false this.state.selection === false
&& this.state.selected_ids.length === 0 && this.state.selected_ids.length === 0
@ -608,15 +609,15 @@ const Listing = React.createClass({
this.setState({ loading: true }); this.setState({ loading: true });
var data = params || {}; const data = params || {};
data.listing = { data.listing = {
params: this.getParams(), params: this.getParams(),
offset: 0, offset: 0,
limit: 0, limit: 0,
filter: this.state.filter, filter: this.state.filter,
group: this.state.group, group: this.state.group,
search: this.state.search search: this.state.search,
} };
if (selected_ids !== 'all') { if (selected_ids !== 'all') {
data.listing.selection = selected_ids; data.listing.selection = selected_ids;
} }
@ -625,35 +626,42 @@ const Listing = React.createClass({
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'bulkAction', action: 'bulkAction',
data: data data: data,
}).done(() => { }).done(() => {
this.getItems(); this.getItems();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
{ scroll: true }
);
}
}); });
}, },
handleSearch: function(search) { handleSearch: function (search) {
this.setState({ this.setState({
search: search, search: search,
page: 1, page: 1,
selection: false, selection: false,
selected_ids: [] selected_ids: [],
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleSort: function(sort_by, sort_order = 'asc') { handleSort: function (sort_by, sort_order = 'asc') {
this.setState({ this.setState({
sort_by: sort_by, sort_by: sort_by,
sort_order: (sort_order === 'asc') ? 'asc' : 'desc', sort_order: (sort_order === 'asc') ? 'asc' : 'desc',
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleSelectItem: function(id, is_checked) { handleSelectItem: function (id, is_checked) {
var selected_ids = this.state.selected_ids, let selected_ids = this.state.selected_ids;
selection = false; let selection = false;
if (is_checked) { if (is_checked) {
selected_ids = jQuery.merge(selected_ids, [ id ]); selected_ids = jQuery.merge(selected_ids, [id]);
// check whether all items on the page are selected // check whether all items on the page are selected
if ( if (
jQuery('tbody .check-column :checkbox:not(:checked)').length === 0 jQuery('tbody .check-column :checkbox:not(:checked)').length === 0
@ -666,48 +674,48 @@ const Listing = React.createClass({
this.setState({ this.setState({
selection: selection, selection: selection,
selected_ids: selected_ids selected_ids: selected_ids,
}); });
}, },
handleSelectItems: function(is_checked) { handleSelectItems: function (is_checked) {
if (is_checked === false) { if (is_checked === false) {
this.clearSelection(); this.clearSelection();
} else { } else {
var selected_ids = this.state.items.map(function(item) { const selected_ids = this.state.items.map((item) => {
return ~~item.id; return ~~item.id;
}); });
this.setState({ this.setState({
selected_ids: selected_ids, selected_ids: selected_ids,
selection: 'page' selection: 'page',
}); });
} }
}, },
handleSelectAll: function() { handleSelectAll: function () {
if (this.state.selection === 'all') { if (this.state.selection === 'all') {
this.clearSelection(); this.clearSelection();
} else { } else {
this.setState({ this.setState({
selection: 'all', selection: 'all',
selected_ids: [] selected_ids: [],
}); });
} }
}, },
clearSelection: function() { clearSelection: function () {
this.setState({ this.setState({
selection: false, selection: false,
selected_ids: [] selected_ids: [],
}); });
}, },
handleFilter: function(filters) { handleFilter: function (filters) {
this.setState({ this.setState({
filter: filters, filter: filters,
page: 1 page: 1,
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleGroup: function(group) { handleGroup: function (group) {
// reset search // reset search
jQuery('#search_input').val(''); jQuery('#search_input').val('');
@ -715,35 +723,35 @@ const Listing = React.createClass({
group: group, group: group,
filter: {}, filter: {},
search: '', search: '',
page: 1 page: 1,
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleSetPage: function(page) { handleSetPage: function (page) {
this.setState({ this.setState({
page: page, page: page,
selection: false, selection: false,
selected_ids: [] selected_ids: [],
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleRenderItem: function(item, actions) { handleRenderItem: function (item, actions) {
const render = this.props.onRenderItem(item, actions, this.state.meta); const render = this.props.onRenderItem(item, actions, this.state.meta);
return render.props.children; return render.props.children;
}, },
handleRefreshItems: function() { handleRefreshItems: function () {
this.getItems(); this.getItems();
}, },
render: function() { render: function () {
const items = this.state.items; const items = this.state.items;
const sort_by = this.state.sort_by; const sort_by = this.state.sort_by;
const sort_order = this.state.sort_order; const sort_order = this.state.sort_order;
// columns // columns
let columns = this.props.columns || []; let columns = this.props.columns || [];
columns = columns.filter(function(column) { columns = columns.filter((column) => {
return (column.display === undefined || !!(column.display) === true); return (column.display === undefined || !!(column.display) === true);
}); });
@ -755,13 +763,13 @@ const Listing = React.createClass({
{ {
name: 'restore', name: 'restore',
label: MailPoet.I18n.t('restore'), label: MailPoet.I18n.t('restore'),
onSuccess: this.props.messages.onRestore onSuccess: this.props.messages.onRestore,
}, },
{ {
name: 'delete', name: 'delete',
label: MailPoet.I18n.t('deletePermanently'), label: MailPoet.I18n.t('deletePermanently'),
onSuccess: this.props.messages.onDelete onSuccess: this.props.messages.onDelete,
} },
]; ];
} }
@ -774,14 +782,14 @@ const Listing = React.createClass({
'widefat', 'widefat',
'fixed', 'fixed',
'striped', 'striped',
{ 'mailpoet_listing_loading': this.state.loading } { mailpoet_listing_loading: this.state.loading }
); );
// search // search
let search = ( let search = (
<ListingSearch <ListingSearch
onSearch={ this.handleSearch } onSearch={this.handleSearch}
search={ this.state.search } search={this.state.search}
/> />
); );
if (this.props.search === false) { if (this.props.search === false) {
@ -791,9 +799,9 @@ const Listing = React.createClass({
// groups // groups
let groups = ( let groups = (
<ListingGroups <ListingGroups
groups={ this.state.groups } groups={this.state.groups}
group={ this.state.group } group={this.state.group}
onSelectGroup={ this.handleGroup } onSelectGroup={this.handleGroup}
/> />
); );
if (this.props.groups === false) { if (this.props.groups === false) {
@ -812,84 +820,85 @@ const Listing = React.createClass({
{ search } { search }
<div className="tablenav top clearfix"> <div className="tablenav top clearfix">
<ListingBulkActions <ListingBulkActions
count={ this.state.count } count={this.state.count}
bulk_actions={ bulk_actions } bulk_actions={bulk_actions}
selection={ this.state.selection } selection={this.state.selection}
selected_ids={ this.state.selected_ids } selected_ids={this.state.selected_ids}
onBulkAction={ this.handleBulkAction } /> onBulkAction={this.handleBulkAction} />
<ListingFilters <ListingFilters
filters={ this.state.filters } filters={this.state.filters}
filter={ this.state.filter } filter={this.state.filter}
group={ this.state.group } group={this.state.group}
onSelectFilter={ this.handleFilter } onBeforeSelectFilter={this.props.onBeforeSelectFilter || null}
onEmptyTrash={ this.handleEmptyTrash } onSelectFilter={this.handleFilter}
onEmptyTrash={this.handleEmptyTrash}
/> />
<ListingPages <ListingPages
count={ this.state.count } count={this.state.count}
page={ this.state.page } page={this.state.page}
limit={ this.state.limit } limit={this.state.limit}
onSetPage={ this.handleSetPage } /> onSetPage={this.handleSetPage} />
</div> </div>
<table className={ table_classes }> <table className={table_classes}>
<thead> <thead>
<ListingHeader <ListingHeader
onSort={ this.handleSort } onSort={this.handleSort}
onSelectItems={ this.handleSelectItems } onSelectItems={this.handleSelectItems}
selection={ this.state.selection } selection={this.state.selection}
sort_by={ sort_by } sort_by={sort_by}
sort_order={ sort_order } sort_order={sort_order}
columns={ columns } columns={columns}
is_selectable={ bulk_actions.length > 0 } /> is_selectable={bulk_actions.length > 0} />
</thead> </thead>
<ListingItems <ListingItems
onRenderItem={ this.handleRenderItem } onRenderItem={this.handleRenderItem}
onDeleteItem={ this.handleDeleteItem } onDeleteItem={this.handleDeleteItem}
onRestoreItem={ this.handleRestoreItem } onRestoreItem={this.handleRestoreItem}
onTrashItem={ this.handleTrashItem } onTrashItem={this.handleTrashItem}
onRefreshItems={ this.handleRefreshItems } onRefreshItems={this.handleRefreshItems}
columns={ columns } columns={columns}
is_selectable={ bulk_actions.length > 0 } is_selectable={bulk_actions.length > 0}
onSelectItem={ this.handleSelectItem } onSelectItem={this.handleSelectItem}
onSelectAll={ this.handleSelectAll } onSelectAll={this.handleSelectAll}
selection={ this.state.selection } selection={this.state.selection}
selected_ids={ this.state.selected_ids } selected_ids={this.state.selected_ids}
loading={ this.state.loading } loading={this.state.loading}
group={ this.state.group } group={this.state.group}
count={ this.state.count } count={this.state.count}
limit={ this.state.limit } limit={this.state.limit}
item_actions={ item_actions } item_actions={item_actions}
messages={ messages } messages={messages}
items={ items } /> items={items} />
<tfoot> <tfoot>
<ListingHeader <ListingHeader
onSort={ this.handleSort } onSort={this.handleSort}
onSelectItems={ this.handleSelectItems } onSelectItems={this.handleSelectItems}
selection={ this.state.selection } selection={this.state.selection}
sort_by={ sort_by } sort_by={sort_by}
sort_order={ sort_order } sort_order={sort_order}
columns={ columns } columns={columns}
is_selectable={ bulk_actions.length > 0 } /> is_selectable={bulk_actions.length > 0} />
</tfoot> </tfoot>
</table> </table>
<div className="tablenav bottom"> <div className="tablenav bottom">
<ListingBulkActions <ListingBulkActions
count={ this.state.count } count={this.state.count}
bulk_actions={ bulk_actions } bulk_actions={bulk_actions}
selection={ this.state.selection } selection={this.state.selection}
selected_ids={ this.state.selected_ids } selected_ids={this.state.selected_ids}
onBulkAction={ this.handleBulkAction } /> onBulkAction={this.handleBulkAction} />
<ListingPages <ListingPages
count={ this.state.count } count={this.state.count}
page={ this.state.page } page={this.state.page}
limit={ this.state.limit } limit={this.state.limit}
onSetPage={ this.handleSetPage } /> onSetPage={this.handleSetPage} />
</div> </div>
</div> </div>
); );
} },
}); });
module.exports = Listing; module.exports = Listing;

View File

@ -1,184 +1,184 @@
define([ define([
'react', 'react',
'classnames', 'classnames',
'mailpoet' 'mailpoet',
], function( ], (
React, React,
classNames, classNames,
MailPoet MailPoet
) { ) => {
var ListingPages = React.createClass({ const ListingPages = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
page: null page: null,
} };
}, },
setPage: function(page) { setPage: function (page) {
this.setState({ this.setState({
page: null page: null,
}, function () { }, () => {
this.props.onSetPage(this.constrainPage(page)); this.props.onSetPage(this.constrainPage(page));
}.bind(this)); });
}, },
setFirstPage: function() { setFirstPage: function () {
this.setPage(1); this.setPage(1);
}, },
setLastPage: function() { setLastPage: function () {
this.setPage(this.getLastPage()); this.setPage(this.getLastPage());
}, },
setPreviousPage: function() { setPreviousPage: function () {
this.setPage(this.constrainPage( this.setPage(this.constrainPage(
parseInt(this.props.page, 10) - 1) parseInt(this.props.page, 10) - 1)
); );
}, },
setNextPage: function() { setNextPage: function () {
this.setPage(this.constrainPage( this.setPage(this.constrainPage(
parseInt(this.props.page, 10) + 1) parseInt(this.props.page, 10) + 1)
); );
}, },
constrainPage: function(page) { constrainPage: function (page) {
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage()); return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage());
}, },
handleSetManualPage: function(e) { handleSetManualPage: function (e) {
if(e.which === 13) { if (e.which === 13) {
this.setPage(this.state.page); this.setPage(this.state.page);
} }
}, },
handleChangeManualPage: function(e) { handleChangeManualPage: function (e) {
this.setState({ this.setState({
page: e.target.value page: e.target.value,
}); });
}, },
handleBlurManualPage: function(e) { handleBlurManualPage: function (e) {
this.setPage(e.target.value); this.setPage(e.target.value);
}, },
getLastPage: function() { getLastPage: function () {
return Math.ceil(this.props.count / this.props.limit); return Math.ceil(this.props.count / this.props.limit);
}, },
render: function() { render: function () {
if(this.props.count === 0) { if (this.props.count === 0) {
return false; return false;
} else { }
var pagination = false; let pagination = false;
var firstPage = ( let firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span> <span aria-hidden="true" className="tablenav-pages-navspan">«</span>
); );
var previousPage = ( let previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span> <span aria-hidden="true" className="tablenav-pages-navspan"></span>
); );
var nextPage = ( let nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span> <span aria-hidden="true" className="tablenav-pages-navspan"></span>
); );
var lastPage = ( let lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span> <span aria-hidden="true" className="tablenav-pages-navspan">»</span>
); );
if(this.props.limit > 0 && this.props.count > this.props.limit) { if (this.props.limit > 0 && this.props.count > this.props.limit) {
if(this.props.page > 1) { if (this.props.page > 1) {
previousPage = ( previousPage = (
<a href="javascript:;" <a href="javascript:;"
onClick={ this.setPreviousPage } onClick={this.setPreviousPage}
className="prev-page"> className="prev-page">
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span> <span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
); );
}
if(this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={ this.setFirstPage }
className="first-page">
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
}
if(this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={ this.setNextPage }
className="next-page">
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
}
if(this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={ this.setLastPage }
className="last-page">
<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}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
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="2"
ref="page"
value={ pageValue }
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
} }
var classes = classNames( if (this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={this.setFirstPage}
className="first-page">
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
}
if (this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={this.setNextPage}
className="next-page">
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
}
if (this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={this.setLastPage}
className="last-page">
<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}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
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="2"
ref="page"
value={pageValue}
name="paged"
id="current-page-selector"
className="current-page" />
{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
}
const classes = classNames(
'tablenav-pages', 'tablenav-pages',
{ 'one-page': (this.props.count <= this.props.limit) } { 'one-page': (this.props.count <= this.props.limit) }
); );
var numberOfItemsLabel; let numberOfItemsLabel;
if (this.props.count == 1) { if (this.props.count == 1) {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular'); numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular');
} else { } else {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple') numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple')
.replace('%$1d', this.props.count.toLocaleString()); .replace('%$1d', this.props.count.toLocaleString());
}
return (
<div className={ classes }>
<span className="displaying-num">{ numberOfItemsLabel }</span>
{ pagination }
</div>
);
} }
} return (
<div className={classes}>
<span className="displaying-num">{ numberOfItemsLabel }</span>
{ pagination }
</div>
);
},
}); });
return ListingPages; return ListingPages;

View File

@ -1,46 +1,46 @@
define([ define([
'mailpoet', 'mailpoet',
'react' 'react',
], function( ], (
MailPoet, MailPoet,
React React
) { ) => {
var ListingSearch = React.createClass({ const ListingSearch = React.createClass({
handleSearch: function(e) { handleSearch: function (e) {
e.preventDefault(); e.preventDefault();
this.props.onSearch( this.props.onSearch(
this.refs.search.value this.refs.search.value.trim()
); );
}, },
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function (nextProps) {
this.refs.search.value = nextProps.search this.refs.search.value = nextProps.search;
}, },
render: function() { render: function () {
if(this.props.search === false) { if (this.props.search === false) {
return false; return false;
} else {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
{MailPoet.I18n.t('searchLabel')}
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoet.I18n.t('searchLabel')}
className="button" />
</p>
</form>
);
} }
} return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
{MailPoet.I18n.t('searchLabel')}
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoet.I18n.t('searchLabel')}
className="button" />
</p>
</form>
);
},
}); });
return ListingSearch; return ListingSearch;

View File

@ -1,4 +1,4 @@
define('mailpoet', [], function() { define('mailpoet', [], function () {
// A placeholder for MailPoet object // A placeholder for MailPoet object
var MailPoet = {}; var MailPoet = {};

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
'use strict'; 'use strict';
var MailPoet = mp;
MailPoet.MP2Migrator = { MailPoet.MP2Migrator = {
fatal_error: '', fatal_error: '',
@ -10,7 +12,7 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
clearTimeout(MailPoet.MP2Migrator.displayLogs_timeout); clearTimeout(MailPoet.MP2Migrator.displayLogs_timeout);
clearTimeout(MailPoet.MP2Migrator.updateProgressbar_timeout); clearTimeout(MailPoet.MP2Migrator.updateProgressbar_timeout);
clearTimeout(MailPoet.MP2Migrator.update_wordpress_info_timeout); clearTimeout(MailPoet.MP2Migrator.update_wordpress_info_timeout);
setTimeout(MailPoet.MP2Migrator.updateDisplay, 1000) setTimeout(MailPoet.MP2Migrator.updateDisplay, 1000);
}, },
stopLogger: function () { stopLogger: function () {
@ -24,25 +26,26 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
displayLogs: function () { displayLogs: function () {
jQuery.ajax({ jQuery.ajax({
url: mailpoet_mp2_migrator.log_file_url, url: window.mailpoet_mp2_migrator.log_file_url,
cache: false cache: false
}).done(function (result) { }).done(function (result) {
jQuery("#logger").html(''); jQuery('#logger').html('');
result.split("\n").forEach(function (row) { result.split('\n').forEach(function (resultRow) {
if(row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) { var row = resultRow;
if (row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red
} }
// Test if the import is complete // Test if the import is complete
else if(row === MailPoet.I18n.t('import_complete')) { else if (row === MailPoet.I18n.t('import_complete')) {
jQuery('#import-actions').hide(); jQuery('#import-actions').hide();
jQuery('#upgrade-completed').show(); jQuery('#upgrade-completed').show();
} }
jQuery("#logger").append(row + "<br />\n"); jQuery('#logger').append(row + '<br />\n');
}); });
jQuery("#logger").append('<span class="error_msg">' + MailPoet.MP2Migrator.fatal_error + '</span>' + "<br />\n"); jQuery('#logger').append('<span class="error_msg">' + MailPoet.MP2Migrator.fatal_error + '</span>' + '<br />\n');
}).always(function () { }).always(function () {
if(MailPoet.MP2Migrator.is_logging) { if (MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000); MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000);
} }
}); });
@ -50,21 +53,23 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
updateProgressbar: function () { updateProgressbar: function () {
jQuery.ajax({ jQuery.ajax({
url: mailpoet_mp2_migrator.progress_url, url: window.mailpoet_mp2_migrator.progress_url,
cache: false, cache: false,
dataType: 'json' dataType: 'json'
}).always(function (result) { }).always(function (result) {
// Move the progress bar // Move the progress bar
var progress = 0; var progress = 0;
if((result.total !== undefined) && (Number(result.total) !== 0)) { if ((result.total !== undefined) && (Number(result.total) !== 0)) {
progress = Math.round(Number(result.current) / Number(result.total) * 100); progress = Math.round(Number(result.current) / Number(result.total) * 100);
} }
jQuery('#progressbar').progressbar('option', 'value', progress); jQuery('#progressbar').progressbar('option', 'value', progress);
jQuery('#progresslabel').html(progress + '%'); jQuery('#progresslabel').html(progress + '%');
if(Number(result.current !== 0)) { if (Number(result.current) !== 0) {
jQuery('#skip-import').hide(); jQuery('#skip-import').hide();
jQuery('#progressbar').show();
jQuery('#logger-container').show();
} }
if(MailPoet.MP2Migrator.is_logging) { if (MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000); MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000);
} }
}); });
@ -95,16 +100,16 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
MailPoet.MP2Migrator.reactivateImportButton(); MailPoet.MP2Migrator.reactivateImportButton();
}).done(function (response) { }).done(function (response) {
if(response) { if (response) {
MailPoet.MP2Migrator.fatal_error = response.data; MailPoet.MP2Migrator.fatal_error = response.data;
} }
}).fail(function (response) { }).fail(function (response) {
if(response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function (error) { response.errors.map(function (error) {
return error.message; return error.message;
}), }),
{scroll: true} { scroll: true }
); );
} }
}); });
@ -129,6 +134,15 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
jQuery('#stop-import').removeAttr('disabled'); // Enable the button jQuery('#stop-import').removeAttr('disabled'); // Enable the button
MailPoet.MP2Migrator.reactivateImportButton(); MailPoet.MP2Migrator.reactivateImportButton();
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
}).fail(function (response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function (error) {
return error.message;
}),
{ scroll: true }
);
}
}); });
MailPoet.MP2Migrator.stopLogger(); MailPoet.MP2Migrator.stopLogger();
return false; return false;
@ -143,6 +157,15 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
} }
}).done(function () { }).done(function () {
MailPoet.MP2Migrator.gotoWelcomePage(); MailPoet.MP2Migrator.gotoWelcomePage();
}).fail(function (response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function (error) {
return error.message;
}),
{ scroll: true }
);
}
}); });
return false; return false;
}, },
@ -153,33 +176,33 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
} }
}; };
/** /**
* Actions to run when the DOM is ready * Actions to run when the DOM is ready
*/ */
jQuery(function () { jQuery(function () {
jQuery('#progressbar').progressbar({value: 0}); jQuery('#progressbar').progressbar({ value: 0 });
// Import button // Import button
jQuery('#import').click(function() { jQuery('#import').click(function () {
MailPoet.MP2Migrator.startImport(); MailPoet.MP2Migrator.startImport();
}); });
// Stop import button // Stop import button
jQuery('#stop-import').click(function() { jQuery('#stop-import').click(function () {
MailPoet.MP2Migrator.stopImport(); MailPoet.MP2Migrator.stopImport();
}); });
// Skip import link // Skip import link
jQuery('#skip-import').click(function() { jQuery('#skip-import').click(function () {
MailPoet.MP2Migrator.skipImport(); MailPoet.MP2Migrator.skipImport();
}); });
// Go to welcome page // Go to welcome page
jQuery('#goto-welcome').click(function() { jQuery('#goto-welcome').click(function () {
MailPoet.MP2Migrator.gotoWelcomePage(); MailPoet.MP2Migrator.gotoWelcomePage();
}); });
// Update the display // Update the display
MailPoet.MP2Migrator.updateDisplay(); MailPoet.MP2Migrator.updateDisplay();
}); });

View File

@ -1,12 +1,9 @@
define([ define([
'backbone', 'backbone',
'backbone.marionette', 'backbone.marionette',
'backbone.radio', 'backbone.radio'
'jquery', ], function (Backbone, Marionette, BackboneRadio) {
'underscore', var Radio = BackboneRadio;
'handlebars',
'handlebars_helpers'
], function(Backbone, Marionette, Radio, jQuery, _, Handlebars) {
var AppView = Marionette.View.extend({ var AppView = Marionette.View.extend({
el: '#mailpoet_editor', el: '#mailpoet_editor',
@ -15,20 +12,22 @@ define([
contentRegion: '#mailpoet_editor_content', contentRegion: '#mailpoet_editor_content',
sidebarRegion: '#mailpoet_editor_sidebar', sidebarRegion: '#mailpoet_editor_sidebar',
bottomRegion: '#mailpoet_editor_bottom', bottomRegion: '#mailpoet_editor_bottom',
headingRegion: '#mailpoet_editor_heading', headingRegion: '#mailpoet_editor_heading'
}, }
}); });
var EditorApplication = Marionette.Application.extend({ var EditorApplication = Marionette.Application.extend({
region: '#mailpoet_editor', region: '#mailpoet_editor',
onStart: function() { onStart: function () {
this._appView = new AppView(); this._appView = new AppView();
this.showView(this._appView); this.showView(this._appView);
}, },
getChannel: function(channel) { getChannel: function (channel) {
if (channel === undefined) channel = 'global'; if (channel === undefined) {
return Radio.channel('global');
}
return Radio.channel(channel); return Radio.channel(channel);
} }
}); });

View File

@ -5,11 +5,11 @@
* For more check: http://marionettejs.com/docs/marionette.behaviors.html#behaviorslookup * For more check: http://marionettejs.com/docs/marionette.behaviors.html#behaviorslookup
*/ */
define([ define([
'backbone.marionette' 'backbone.marionette'
], function(Marionette) { ], function (BackboneMarionette) {
var Marionette = BackboneMarionette;
var BehaviorsLookup = {}; var BehaviorsLookup = {};
Marionette.Behaviors.behaviorsLookup = function() { Marionette.Behaviors.behaviorsLookup = function () {
return BehaviorsLookup; return BehaviorsLookup;
}; };

View File

@ -4,23 +4,44 @@
* Adds a color picker integration with the view * Adds a color picker integration with the view
*/ */
define([ define([
'backbone.marionette', 'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup',
'mailpoet', 'mailpoet',
'spectrum' 'spectrum'
], function(Marionette, BehaviorsLookup, MailPoet, Spectrum) { ], function (Marionette, BehaviorsLookup, MailPoet) {
var BL = BehaviorsLookup;
BehaviorsLookup.ColorPickerBehavior = Marionette.Behavior.extend({ BL.ColorPickerBehavior = Marionette.Behavior.extend({
onRender: function() { onRender: function () {
this.view.$('.mailpoet_color').spectrum({ var that = this;
clickoutFiresChange: true, var preferredFormat = 'hex6';
showInput: true, this.view.$('.mailpoet_color').each(function () {
showInitial: true, var $input = that.view.$(this);
preferredFormat: "hex6", var updateColorInput = function (color) {
allowEmpty: true, if (color && color.getAlpha() > 0) {
chooseText: MailPoet.I18n.t('selectColor'), $input.val(color.toString(preferredFormat));
cancelText: MailPoet.I18n.t('cancelColorSelection') } else {
$input.val('');
}
$input.trigger('change');
};
$input.spectrum({
clickoutFiresChange: true,
showInput: true,
showInitial: true,
showPalette: true,
showSelectionPalette: true,
palette: [],
localStorageKey: 'newsletter_editor.spectrum.palette',
preferredFormat: preferredFormat,
allowEmpty: true,
chooseText: MailPoet.I18n.t('selectColor'),
cancelText: MailPoet.I18n.t('cancelColorSelection'),
change: updateColorInput,
move: updateColorInput,
hide: updateColorInput
});
}); });
}, }
}); });
}); });

View File

@ -1,3 +1,4 @@
/** /**
* ContainerDropZoneBehavior * ContainerDropZoneBehavior
* *
@ -6,28 +7,29 @@
* accept droppables * accept droppables
*/ */
define([ define([
'backbone.marionette', 'backbone.marionette',
'underscore', 'underscore',
'jquery', 'jquery',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup',
'interact' 'interact'
], function(Marionette, _, jQuery, BehaviorsLookup, interact) { ], function (Marionette, _, jQuery, BL, interact) {
var BehaviorsLookup = BL;
BehaviorsLookup.ContainerDropZoneBehavior = Marionette.Behavior.extend({ BehaviorsLookup.ContainerDropZoneBehavior = Marionette.Behavior.extend({
defaults: { defaults: {
columnLimit: 3, columnLimit: 3
}, },
onRender: function() { onRender: function () {
var dragAndDropDisabled = _.isObject(this.view.options.renderOptions) && this.view.options.renderOptions.disableDragAndDrop === true; var dragAndDropDisabled = _.isObject(this.view.options.renderOptions) && this.view.options.renderOptions.disableDragAndDrop === true;
if (!dragAndDropDisabled) { if (!dragAndDropDisabled) {
this.addDropZone(); this.addDropZone();
} }
}, },
addDropZone: function(_event) { addDropZone: function () {
var that = this, var that = this;
view = this.view, var view = this.view;
domElement = that.$el.get(0), var domElement = that.$el.get(0);
acceptableElementSelector; var acceptableElementSelector;
// TODO: Extract this limitation code to be controlled from containers // TODO: Extract this limitation code to be controlled from containers
if (this.view.renderOptions.depth === 0) { if (this.view.renderOptions.depth === 0) {
@ -45,36 +47,42 @@ define([
interact(domElement).dropzone({ interact(domElement).dropzone({
accept: acceptableElementSelector, accept: acceptableElementSelector,
overlap: 'pointer', // Mouse pointer denotes location of a droppable overlap: 'pointer', // Mouse pointer denotes location of a droppable
ondragenter: function(event) { ondragenter: function () {
// 1. Visually mark block as active for dropping // 1. Visually mark block as active for dropping
view.$el.addClass('mailpoet_drop_active'); view.$el.addClass('mailpoet_drop_active');
}, },
ondragleave: function(event) { ondragleave: function () {
// 1. Remove visual markings of active dropping container // 1. Remove visual markings of active dropping container
// 2. Remove visual markings of drop position visualization // 2. Remove visual markings of drop position visualization
that.cleanup(); that.cleanup();
}, },
ondropmove: function(event) { ondropmove: function (event) {
// 1. Compute actual location of the mouse within the container // 1. Compute actual location of the mouse within the container
// 2. Check if insertion is regular (between blocks) or special (with container insertion) // 2. Check if insertion is regular (between blocks) or special (with container insertion)
// 3a. If insertion is regular, compute position where insertion should happen // 3a. If insertion is regular, compute position where insertion should happen
// 3b. If insertion is special, compute position (which side) and which cell the insertion belongs to // 3b. If insertion is special, compute position (which side) and which cell the insertion belongs to
// 4. If insertion at that position is not visualized, display position visualization there, remove other visualizations from this container // 4. If insertion at that position is not visualized, display position visualization there, remove other visualizations from this container
var dropPosition = that.getDropPosition( var dropPosition = that.getDropPosition(
event.dragmove.pageX, event.dragmove.pageX,
event.dragmove.pageY, event.dragmove.pageY,
view.$el, view.$el,
view.model.get('orientation'), view.model.get('orientation'),
view.model.get('blocks').length view.model.get('blocks').length
), );
element = view.$el, var element = view.$el;
markerWidth = '', var markerWidth = '';
markerHeight = '', var markerHeight = '';
containerOffset = element.offset(), var containerOffset = element.offset();
viewCollection = that.getCollection(), var viewCollection = that.getCollection();
marker, targetModel, targetView, targetElement, var marker;
topOffset, leftOffset, isLastBlockInsertion, var targetModel;
$targetBlock, margin; var targetView;
var targetElement;
var topOffset;
var leftOffset;
var isLastBlockInsertion;
var $targetBlock;
var margin;
if (dropPosition === undefined) return; if (dropPosition === undefined) return;
@ -148,7 +156,7 @@ define([
// compensated for to position marker right in the middle of two // compensated for to position marker right in the middle of two
// blocks // blocks
if (dropPosition.position === 'before') { if (dropPosition.position === 'before') {
$targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index-1)).$el; $targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index - 1)).$el;
} else { } else {
$targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index)).$el; $targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index)).$el;
} }
@ -161,7 +169,7 @@ define([
element.append(marker); element.append(marker);
}, },
ondrop: function(event) { ondrop: function (event) {
// 1. Compute actual location of the mouse // 1. Compute actual location of the mouse
// 2. Check if insertion is regular (between blocks) or special (with container insertion) // 2. Check if insertion is regular (between blocks) or special (with container insertion)
// 3a. If insertion is regular // 3a. If insertion is regular
@ -176,15 +184,19 @@ define([
// 4. Perform cleanup actions // 4. Perform cleanup actions
var dropPosition = that.getDropPosition( var dropPosition = that.getDropPosition(
event.dragEvent.pageX, event.dragEvent.pageX,
event.dragEvent.pageY, event.dragEvent.pageY,
view.$el, view.$el,
view.model.get('orientation'), view.model.get('orientation'),
view.model.get('blocks').length view.model.get('blocks').length
), );
droppableModel = event.draggable.getDropModel(), var droppableModel = event.draggable.getDropModel();
viewCollection = that.getCollection(), var viewCollection = that.getCollection();
droppedView, droppedModel, index, tempCollection, tempCollection2; var droppedView;
var index;
var tempCollection;
var tempCollection2;
var tempModel;
if (dropPosition === undefined) return; if (dropPosition === undefined) return;
@ -194,43 +206,44 @@ define([
if (view.model.get('orientation') === 'horizontal' && droppableModel.get('type') !== 'container') { if (view.model.get('orientation') === 'horizontal' && droppableModel.get('type') !== 'container') {
// Regular blocks always need to be inserted into columns - vertical containers // Regular blocks always need to be inserted into columns - vertical containers
tempCollection = new (EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', tempCollection = new (window.EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical'
}); });
tempCollection.get('blocks').add(droppableModel); tempCollection.get('blocks').add(droppableModel);
viewCollection.add(tempCollection, {at: index}); viewCollection.add(tempCollection, { at: index });
} else { } else {
viewCollection.add(droppableModel, {at: index}); viewCollection.add(droppableModel, { at: index });
} }
droppedView = that.getChildren().findByModel(droppableModel); droppedView = that.getChildren().findByModel(droppableModel);
} else { } else {
// Special insertion by replacing target block with collection // Special insertion by replacing target block with collection
// and inserting dropModel into that // and inserting dropModel into that
var tempModel = viewCollection.at(dropPosition.index); tempModel = viewCollection.at(dropPosition.index);
tempCollection = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection = new (window.EditorApplication.getBlockTypeModel('container'))({
orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical', orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical'
}); });
viewCollection.remove(tempModel); viewCollection.remove(tempModel);
if (tempCollection.get('orientation') === 'horizontal') { if (tempCollection.get('orientation') === 'horizontal') {
if (dropPosition.position === 'before') { if (dropPosition.position === 'before') {
tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection2 = new (window.EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', orientation: 'vertical'
}); });
tempCollection2.get('blocks').add(droppableModel); tempCollection2.get('blocks').add(droppableModel);
tempCollection.get('blocks').add(tempCollection2); tempCollection.get('blocks').add(tempCollection2);
} }
tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection2 = new (window.EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', orientation: 'vertical'
}); });
tempCollection2.get('blocks').add(tempModel); tempCollection2.get('blocks').add(tempModel);
tempCollection.get('blocks').add(tempCollection2); tempCollection.get('blocks').add(tempCollection2);
if (dropPosition.position === 'after') { if (dropPosition.position === 'after') {
tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection2 = new (window.EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', orientation: 'vertical'
}); });
tempCollection2.get('blocks').add(droppableModel); tempCollection2.get('blocks').add(droppableModel);
tempCollection.get('blocks').add(tempCollection2); tempCollection.get('blocks').add(tempCollection2);
@ -244,7 +257,7 @@ define([
tempCollection.get('blocks').add(droppableModel); tempCollection.get('blocks').add(droppableModel);
} }
} }
viewCollection.add(tempCollection, {at: dropPosition.index}); viewCollection.add(tempCollection, { at: dropPosition.index });
// Call post add actions // Call post add actions
droppedView = that.getChildren().findByModel(tempCollection).children.findByModel(droppableModel); droppedView = that.getChildren().findByModel(tempCollection).children.findByModel(droppableModel);
@ -254,49 +267,53 @@ define([
event.draggable.onDrop({ event.draggable.onDrop({
dropBehavior: that, dropBehavior: that,
droppedModel: droppableModel, droppedModel: droppableModel,
droppedView: droppedView, droppedView: droppedView
}); });
that.cleanup(); that.cleanup();
}, }
}); });
}, },
cleanup: function() { cleanup: function () {
// 1. Remove visual markings of active dropping container // 1. Remove visual markings of active dropping container
this.view.$el.removeClass('mailpoet_drop_active'); this.view.$el.removeClass('mailpoet_drop_active');
// 2. Remove visual markings of drop position visualization // 2. Remove visual markings of drop position visualization
this.view.$('.mailpoet_drop_marker').remove(); this.view.$('.mailpoet_drop_marker').remove();
}, },
getDropPosition: function(eventX, eventY, unsafe) { getDropPosition: function (eventX, eventY, is_unsafe) {
var SPECIAL_AREA_INSERTION_WIDTH = 0.00, // Disable special insertion. Default: 0.3 var SPECIAL_AREA_INSERTION_WIDTH = 0.00; // Disable special insertion. Default: 0.3
element = this.view.$el, var element = this.view.$el;
orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation');
elementOffset = element.offset(), var elementOffset = element.offset();
elementPageX = elementOffset.left, var elementPageX = elementOffset.left;
elementPageY = elementOffset.top, var elementPageY = elementOffset.top;
elementWidth = element.outerWidth(true), var elementWidth = element.outerWidth(true);
elementHeight = element.outerHeight(true), var elementHeight = element.outerHeight(true);
relativeX = eventX - elementPageX, var relativeX = eventX - elementPageX;
relativeY = eventY - elementPageY, var relativeY = eventY - elementPageY;
relativeOffset, elementLength, var relativeOffset;
var elementLength;
canAcceptNormalInsertion = this._canAcceptNormalInsertion(), var canAcceptNormalInsertion = this._canAcceptNormalInsertion();
canAcceptSpecialInsertion = this._canAcceptSpecialInsertion(), var canAcceptSpecialInsertion = this._canAcceptSpecialInsertion();
insertionType, index, position, indexAndPosition; var insertionType;
var index;
var position;
var indexAndPosition;
unsafe = !!unsafe; var unsafe = !!is_unsafe;
if (this.getCollection().length === 0) { if (this.getCollection().length === 0) {
return { return {
insertionType: 'normal', insertionType: 'normal',
index: 0, index: 0,
position: 'inside', position: 'inside'
}; };
} }
@ -347,21 +364,23 @@ define([
return { return {
insertionType: insertionType, // 'normal'|'special' insertionType: insertionType, // 'normal'|'special'
index: index, index: index,
position: position, // 'inside'|'before'|'after' position: position // 'inside'|'before'|'after'
}; };
}, },
_computeNormalIndex: function(eventX, eventY) { _computeNormalIndex: function (eventX, eventY) {
// Normal insertion inserts dropModel before target element if // Normal insertion inserts dropModel before target element if
// event happens on the first half of the element and after the // event happens on the first half of the element and after the
// target element if event happens on the second half of the element. // target element if event happens on the second half of the element.
// Halves depend on orientation. // Halves depend on orientation.
var index = this._computeCellIndex(eventX, eventY), var index = this._computeCellIndex(eventX, eventY);
// TODO: Handle case when there are no children, container is empty // TODO: Handle case when there are no children, container is empty
targetView = this.getChildren().findByModel(this.getCollection().at(index)), var targetView = this.getChildren().findByModel(this.getCollection().at(index));
orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation');
element = targetView.$el, var element = targetView.$el;
eventOffset, closeOffset, elementDimension; var eventOffset;
var closeOffset;
var elementDimension;
if (orientation === 'vertical') { if (orientation === 'vertical') {
eventOffset = eventY; eventOffset = eventY;
@ -376,63 +395,64 @@ define([
if (eventOffset <= closeOffset + elementDimension / 2) { if (eventOffset <= closeOffset + elementDimension / 2) {
// First half of the element // First half of the element
return { return {
index: index, index: index,
position: 'before', position: 'before'
}; };
} else { } else {
// Second half of the element // Second half of the element
return { return {
index: index, index: index,
position: 'after', position: 'after'
}; };
} }
}, },
_computeSpecialIndex: function(eventX, eventY) { _computeSpecialIndex: function (eventX, eventY) {
return this._computeCellIndex(eventX, eventY); return this._computeCellIndex(eventX, eventY);
}, },
_computeCellIndex: function(eventX, eventY) { _computeCellIndex: function (eventX, eventY) {
var orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation');
eventOffset = (orientation === 'vertical') ? eventY : eventX, var eventOffset = (orientation === 'vertical') ? eventY : eventX;
resultView = this.getChildren().find(function(view) { var resultView = this.getChildren().find(function (view) {
var element = view.$el, var element = view.$el;
closeOffset, farOffset; var closeOffset;
var farOffset;
if (orientation === 'vertical') { if (orientation === 'vertical') {
closeOffset = element.offset().top; closeOffset = element.offset().top;
farOffset = element.outerHeight(true); farOffset = element.outerHeight(true);
} else { } else {
closeOffset = element.offset().left; closeOffset = element.offset().left;
farOffset = element.outerWidth(true); farOffset = element.outerWidth(true);
} }
farOffset += closeOffset; farOffset += closeOffset;
return closeOffset <= eventOffset && eventOffset <= farOffset; return closeOffset <= eventOffset && eventOffset <= farOffset;
}); });
var index = (typeof resultView === 'object') ? resultView._index : 0; var index = (typeof resultView === 'object') ? resultView._index : 0;
return index; return index;
}, },
_canAcceptNormalInsertion: function() { _canAcceptNormalInsertion: function () {
var orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation');
depth = this.view.renderOptions.depth, var depth = this.view.renderOptions.depth;
childCount = this.getChildren().length; var childCount = this.getChildren().length;
// Note that depth is zero indexed. Root container has depth=0 // Note that depth is zero indexed. Root container has depth=0
return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit); return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit);
}, },
_canAcceptSpecialInsertion: function() { _canAcceptSpecialInsertion: function () {
var orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation');
depth = this.view.renderOptions.depth, var depth = this.view.renderOptions.depth;
childCount = this.getChildren().length; var childCount = this.getChildren().length;
return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit); return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit);
}, },
getCollectionView: function() { getCollectionView: function () {
return this.view.getChildView('blocks'); return this.view.getChildView('blocks');
}, },
getChildren: function() { getChildren: function () {
return this.getCollectionView().children; return this.getCollectionView().children;
}, },
getCollection: function() { getCollection: function () {
return this.getCollectionView().collection; return this.getCollectionView().collection;
} }
}); });

View File

@ -5,14 +5,15 @@
* Part of the drag&drop behavior. * Part of the drag&drop behavior.
*/ */
define([ define([
'backbone.marionette', 'backbone.marionette',
'underscore', 'underscore',
'jquery', 'jquery',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup',
'interact' 'interact'
], function(Marionette, _, jQuery, BehaviorsLookup, interact) { ], function (Marionette, _, jQuery, BehaviorsLookup, interact) {
var BL = BehaviorsLookup;
BehaviorsLookup.DraggableBehavior = Marionette.Behavior.extend({ BL.DraggableBehavior = Marionette.Behavior.extend({
defaults: { defaults: {
cloneOriginal: false, cloneOriginal: false,
hideOriginal: false, hideOriginal: false,
@ -23,22 +24,22 @@ define([
* *
* @return Backbone.Model A model that will be passed to the receiver * @return Backbone.Model A model that will be passed to the receiver
*/ */
getDropModel: function() { getDropModel: function () {
throw "Missing 'drop' function for DraggableBehavior"; throw "Missing 'drop' function for DraggableBehavior";
}, },
onDrop: function(model, view) {}, onDrop: function () {},
testAttachToInstance: function(model, view) { return true; }, testAttachToInstance: function () { return true; }
}, },
onRender: function() { onRender: function () {
var that = this, var that = this;
interactable; var interactable;
// Give instances more control over whether Draggable should be applied // Give instances more control over whether Draggable should be applied
if (!this.options.testAttachToInstance(this.view.model, this.view)) return; if (!this.options.testAttachToInstance(this.view.model, this.view)) return;
interactable = interact(this.$el.get(0), { interactable = interact(this.$el.get(0), {
ignoreFrom: this.options.ignoreSelector, ignoreFrom: this.options.ignoreSelector
}).draggable({ }).draggable({
// allow dragging of multple elements at the same time // allow dragging of multple elements at the same time
max: Infinity, max: Infinity,
@ -46,17 +47,21 @@ define([
// Scroll when dragging near edges of a window // Scroll when dragging near edges of a window
autoScroll: true, autoScroll: true,
onstart: function(event) { onstart: function (startEvent) {
var event = startEvent;
var centerXOffset;
var centerYOffset;
var tempClone;
var clone;
var $clone;
if (that.options.cloneOriginal === true) { if (that.options.cloneOriginal === true) {
// Use substitution instead of a clone // Use substitution instead of a clone
var tempClone = (_.isFunction(that.options.onDragSubstituteBy)) ? that.options.onDragSubstituteBy(that) : undefined, tempClone = (_.isFunction(that.options.onDragSubstituteBy)) ? that.options.onDragSubstituteBy(that) : undefined;
// Or use a clone // Or use a clone
clone = tempClone ? tempClone : event.target.cloneNode(true), clone = tempClone || event.target.cloneNode(true);
jQuery(event.target);
$original = jQuery(event.target), $clone = jQuery(clone);
$clone = jQuery(clone),
centerXOffset, centerYOffset, parentOffset;
$clone.addClass('mailpoet_droppable_active'); $clone.addClass('mailpoet_droppable_active');
$clone.css('position', 'absolute'); $clone.css('position', 'absolute');
@ -69,7 +74,7 @@ define([
// Accurate dimensions can only be taken after insertion to document // Accurate dimensions can only be taken after insertion to document
centerXOffset = $clone.width() / 2; centerXOffset = $clone.width() / 2;
centerYOffset = $clone.height() / 2; centerYOffset = $clone.height() / 2;
$clone.css('top', event.pageY - centerYOffset); $clone.css('top', event.pageY - centerYOffset);
$clone.css('left', event.pageX - centerXOffset); $clone.css('left', event.pageX - centerXOffset);
event.interaction.element = clone; event.interaction.element = clone;
@ -83,15 +88,14 @@ define([
}, },
// call this function on every dragmove event // call this function on every dragmove event
onmove: function (event) { onmove: function (event) {
var target = event.target, var target = event.target;
// keep the dragged position in the data-x/data-y attributes // keep the dragged position in the data-x/data-y attributes
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx, var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
// translate the element // translate the element
target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
target.style.transform = target.style.webkitTransform = target.style.transform;
'translate(' + x + 'px, ' + y + 'px)';
// update the posiion attributes // update the posiion attributes
target.setAttribute('data-x', x); target.setAttribute('data-x', x);
@ -99,7 +103,8 @@ define([
}, },
onend: function (event) { onend: function (event) {
var target = event.target; var target = event.target;
target.style.webkitTransform = target.style.transform = ''; target.style.transform = '';
target.style.webkitTransform = target.style.transform;
target.removeAttribute('data-x'); target.removeAttribute('data-x');
target.removeAttribute('data-y'); target.removeAttribute('data-y');
jQuery(event.interaction.element).addClass('mailpoet_droppable_active'); jQuery(event.interaction.element).addClass('mailpoet_droppable_active');
@ -111,9 +116,10 @@ define([
that.view.$el.removeClass('mailpoet_hidden'); that.view.$el.removeClass('mailpoet_hidden');
} }
} }
}, }
}) })
.preventDefault('auto') .preventDefault('auto')
.styleCursor(false)
.actionChecker(function (pointer, event, action) { .actionChecker(function (pointer, event, action) {
// Disable dragging with right click // Disable dragging with right click
if (event.button !== 0) { if (event.button !== 0) {
@ -128,7 +134,8 @@ define([
} else { } else {
interactable.getDropModel = this.view.getDropFunc(); interactable.getDropModel = this.view.getDropFunc();
} }
interactable.onDrop = function(options) { interactable.onDrop = function (opts) {
var options = opts;
if (_.isObject(options)) { if (_.isObject(options)) {
// Inject Draggable behavior if possible // Inject Draggable behavior if possible
options.dragBehavior = that; options.dragBehavior = that;
@ -136,6 +143,6 @@ define([
// Delegate to view's event handler // Delegate to view's event handler
that.options.onDrop.apply(that, [options]); that.options.onDrop.apply(that, [options]);
}; };
}, }
}); });
}); });

View File

@ -0,0 +1,26 @@
/**
* Highlight Container Behavior
*
* Highlights a container block when hovering over its tools
*/
define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup'
], function (Marionette, BehaviorsLookup) {
var BL = BehaviorsLookup;
BL.HighlightContainerBehavior = Marionette.Behavior.extend({
events: {
'mouseenter @ui.tools': 'enableHighlight',
'mouseleave @ui.tools': 'disableHighlight'
},
enableHighlight: function () {
this.$el.addClass('mailpoet_highlight');
},
disableHighlight: function () {
if (!this.view._isBeingEdited) {
this.$el.removeClass('mailpoet_highlight');
}
}
});
});

View File

@ -4,20 +4,23 @@
* Highlights a block that is being edited * Highlights a block that is being edited
*/ */
define([ define([
'backbone.marionette', 'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, BehaviorsLookup) { ], function (Marionette, BehaviorsLookup) {
var BL = BehaviorsLookup;
BehaviorsLookup.HighlightEditingBehavior = Marionette.Behavior.extend({ BL.HighlightEditingBehavior = Marionette.Behavior.extend({
modelEvents: { modelEvents: {
'startEditing': 'enableHighlight', startEditing: 'enableHighlight',
'stopEditing': 'disableHighlight', stopEditing: 'disableHighlight'
}, },
enableHighlight: function() { enableHighlight: function () {
this.view._isBeingEdited = true;
this.$el.addClass('mailpoet_highlight'); this.$el.addClass('mailpoet_highlight');
}, },
disableHighlight: function() { disableHighlight: function () {
this.view._isBeingEdited = false;
this.$el.removeClass('mailpoet_highlight'); this.$el.removeClass('mailpoet_highlight');
}, }
}); });
}); });

View File

@ -4,65 +4,71 @@
* Allows resizing elements within a block * Allows resizing elements within a block
*/ */
define([ define([
'backbone.marionette', 'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup',
'interact' 'interact'
], function(Marionette, BehaviorsLookup, interact) { ], function (Marionette, BehaviorsLookup, interact) {
var BL = BehaviorsLookup;
BehaviorsLookup.ResizableBehavior = Marionette.Behavior.extend({ BL.ResizableBehavior = Marionette.Behavior.extend({
defaults: { defaults: {
elementSelector: null, elementSelector: null,
resizeHandleSelector: true, // true will use edges of the element itself resizeHandleSelector: true, // true will use edges of the element itself
transformationFunction: function(y) { return y; }, transformationFunction: function (y) { return y; }, // for blocks that use the default onResize function
minLength: 0, minLength: 0,
maxLength: Infinity,
modelField: 'styles.block.height', modelField: 'styles.block.height',
onResize: function (event) {
var currentLength = parseFloat(this.view.model.get(this.options.modelField));
var newLength = currentLength + this.options.transformationFunction(event.dy);
newLength = Math.min(this.options.maxLength, Math.max(this.options.minLength, newLength));
this.view.model.set(this.options.modelField, newLength + 'px');
}
}, },
events: { events: {
"mouseenter": 'showResizeHandle', mouseenter: 'showResizeHandle',
"mouseleave": 'hideResizeHandle', mouseleave: 'hideResizeHandle'
}, },
onRender: function() { onRender: function () {
this.attachResize(); this.attachResize();
if (this.isBeingResized !== true) { if (this.isBeingResized !== true) {
this.hideResizeHandle(); this.hideResizeHandle();
} }
}, },
attachResize: function() { attachResize: function () {
var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0), var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0);
that = this; var that = this;
interact(domElement).resizable({ interact(domElement).resizable({
//axis: 'y', // axis: 'y',
edges: { edges: {
top: false, top: false,
left: false, left: false,
right: false, right: false,
bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector, bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector
}, }
}).on('resizestart', function(event) { })
.on('resizestart', function () {
that.isBeingResized = true; that.isBeingResized = true;
that.$el.addClass('mailpoet_resize_active'); that.$el.addClass('mailpoet_resize_active');
}).on('resizemove', function(event) { }).on('resizemove', function (event) {
var currentLength = parseFloat(that.view.model.get(that.options.modelField)), var onResize = that.options.onResize.bind(that);
newLength = currentLength + that.options.transformationFunction(event.dy); return onResize(event);
})
if (newLength < that.options.minLength) newLength = that.options.minLength; .on('resizeend', function () {
that.view.model.set(that.options.modelField, newLength + 'px');
}).on('resizeend', function(event) {
that.isBeingResized = null; that.isBeingResized = null;
that.$el.removeClass('mailpoet_resize_active'); that.$el.removeClass('mailpoet_resize_active');
}); });
}, },
showResizeHandle: function() { showResizeHandle: function () {
if (typeof this.options.resizeHandleSelector === 'string') { if (typeof this.options.resizeHandleSelector === 'string') {
this.view.$(this.options.resizeHandleSelector).removeClass('mailpoet_hidden'); this.view.$(this.options.resizeHandleSelector).removeClass('mailpoet_hidden');
} }
}, },
hideResizeHandle: function() { hideResizeHandle: function () {
if (typeof this.options.resizeHandleSelector === 'string') { if (typeof this.options.resizeHandleSelector === 'string') {
this.view.$(this.options.resizeHandleSelector).addClass('mailpoet_hidden'); this.view.$(this.options.resizeHandleSelector).addClass('mailpoet_hidden');
} }
}, }
}); });
}); });

View File

@ -4,28 +4,29 @@
* Opens up settings of a BlockView if contents are clicked upon * Opens up settings of a BlockView if contents are clicked upon
*/ */
define([ define([
'backbone.marionette', 'backbone.marionette',
'jquery', 'jquery',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, jQuery, BehaviorsLookup) { ], function (Marionette, jQuery, BehaviorsLookup) {
var BL = BehaviorsLookup;
BehaviorsLookup.ShowSettingsBehavior = Marionette.Behavior.extend({ BL.ShowSettingsBehavior = Marionette.Behavior.extend({
defaults: { defaults: {
ignoreFrom: '', // selector ignoreFrom: '' // selector
}, },
events: { events: {
'click .mailpoet_content': 'showSettings', 'click .mailpoet_content': 'showSettings'
}, },
showSettings: function(event) { showSettings: function (event) {
if(!this.isIgnoredElement(event.target)) { if (!this.isIgnoredElement(event.target)) {
this.view.triggerMethod('showSettings'); this.view.triggerMethod('showSettings');
} }
}, },
isIgnoredElement: function(element) { isIgnoredElement: function (element) {
return this.options.ignoreFrom return this.options.ignoreFrom
&& this.options.ignoreFrom.length > 0 && this.options.ignoreFrom.length > 0
&& jQuery(element).is(this.options.ignoreFrom); && jQuery(element).is(this.options.ignoreFrom);
}, }
}); });
}); });

Some files were not shown because too many files have changed in this diff Show More