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>
UseCanonicalName Off
ServerName mailpoet.loc
DocumentRoot /home/ubuntu/mailpoet/wordpress
DocumentRoot /home/circleci/mailpoet/wordpress
DirectoryIndex index.php
LogLevel notice
<Directory /home/ubuntu/mailpoet/wordpress>
<Directory /home/circleci/mailpoet/wordpress>
Require all granted
</Directory>
</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": {
"import/no-amd": 0,
"comma-dangle": 0,
"space-before-function-paren": 0,
"prefer-arrow-callback": 0,
"no-undef": 0,
"key-spacing": 0,
"radix": 0,
"no-alert": 0,
"block-scoped-var": 0,
"guard-for-in": 0,
"no-prototype-builtins": 0,
"no-restricted-syntax": 0,
"newline-per-chained-call": 0,
"no-useless-concat": 0,
"no-multi-spaces": 0,
"no-nested-ternary": 0,
"semi-spacing": 0,
"no-sequences": 0,
"no-useless-return": 0,
"array-callback-return": 0,
"new-cap": 0,
"no-return-assign": 0,
"no-continue": 0,
"no-new": 0,
"no-cond-assign": 0,
"space-unary-ops": 0,
"no-redeclare": 0,
"no-console": 0,
"no-empty": 0,
"no-extra-semi": 0,
"no-useless-escape": 0,
"comma-spacing": 0,
"wrap-iife": 0,
"no-unused-expressions": 0,
"block-spacing": 0,
"computed-property-spacing": 0,
"no-plusplus": 0,
"array-bracket-spacing": 0,
"lines-around-directive": 0,
"no-unreachable": 0,
"default-case": 0,
"no-lonely-if": 0,
"space-before-blocks": 0,
"no-unneeded-ternary": 0,
"no-mixed-operators": 0,
"eqeqeq": 0,
"space-in-parens": 0,
"semi": 0,
"max-len": 0,
"no-multi-assign": 0,
"no-trailing-spaces": 0,
"global-require": 0,
"no-throw-literal": 0,
"no-extra-bind": 0,
"one-var-declaration-per-line": 0,
"consistent-return": 0,
"no-shadow": 0,
"no-underscore-dangle": 0,
"brace-style": 0,
"no-else-return": 0,
"no-use-before-define": 0,
"one-var": 0,
"camelcase": 0,
"spaced-comment": 0,
"quotes": 0,
"padded-blocks": 0,
"object-curly-spacing": 0,
"strict": 0,
"vars-on-top": 0,
"no-var": 0,
"space-infix-ops": 0,
"no-unused-vars": 0,
"object-shorthand": 0,
"new-parens": 0,
"no-param-reassign": 0,
"keyword-spacing": 0,
"eol-last": 0,
"dot-notation": 0,
"linebreak-style": 0,
"indent": 0,
"quote-props": 0,
"prefer-template": 0,
"func-names": 0
}

View File

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

View File

@ -8,40 +8,9 @@
"ecmaVersion": 6
},
"rules": {
"import/no-amd": 0,
"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,
// Exceptions
"func-names": 0,
"space-before-function-paren": 0
// Temporary
"no-underscore-dangle": 0
}
}

2
.gitignore vendored
View File

@ -2,8 +2,8 @@
TODO
composer.phar
/vendor
/vendor_backup
tests/_output/*
tests/acceptance.suite.yml
tests/_support/_generated/*
node_modules
.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.
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(
'assets/css/src/admin.styl',
'assets/css/src/admin-global.styl',
'assets/css/src/newsletter_editor/newsletter_editor.styl',
'assets/css/src/public.styl',
'assets/css/src/rtl.styl',
@ -154,9 +155,8 @@ class RoboFile extends \Robo\Tasks {
function testUnit($opts=['file' => null, 'xml' => false]) {
$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']) {
$command .= ' --xml';
@ -166,9 +166,8 @@ class RoboFile extends \Robo\Tasks {
function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$command = join(' ', array(
'vendor/bin/codecept run',
'vendor/bin/codecept run unit -c codeception.unit.yml ',
(($opts['file']) ? $opts['file'] : ''),
'--coverage',
($opts['xml']) ? '--coverage-xml' : '--coverage-html'
@ -199,16 +198,26 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec($command);
}
function testDebug() {
$this->_exec('vendor/bin/codecept build');
function testDebug($opts=['file' => null, 'xml' => false]) {
$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() {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
return $this->_exec('vendor/bin/codecept run -g failed');
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
return $this->_exec('vendor/bin/codecept run -c codeception.unit.yml -g failed');
}
function qa() {
@ -235,14 +244,25 @@ class RoboFile extends \Robo\Tasks {
} else {
$severityFlag = '-n';
}
return $this->_exec(
'./vendor/bin/phpcs '.
'--standard=./tasks/code_sniffer/MailPoet '.
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
'./lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '.
'lib/ '.
$severityFlag
);
return $this->collectionBuilder()
->taskExec(
'./vendor/bin/phpcs '.
'--standard=./tasks/code_sniffer/MailPoet '.
'--runtime-set testVersion 5.3-7.0 '.
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
'./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() {
@ -269,14 +289,16 @@ class RoboFile extends \Robo\Tasks {
}
function svnPublish($opts = ['force' => false]) {
$this->loadWPFunctions();
$this->loadEnv();
$svn_dir = ".mp_svn";
$plugin_data = get_plugin_data('mailpoet.php', false, false);
$plugin_version = $plugin_data['Version'];
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
$plugin_version = $this->getPluginVersion('mailpoet.php');
$plugin_dist_name = 'mailpoet';
$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);
// Sanity checks
@ -395,13 +417,9 @@ class RoboFile extends \Robo\Tasks {
$dotenv->load();
}
protected function loadWPFunctions() {
$this->loadEnv();
define('ABSPATH', getenv('WP_TEST_PATH') . '/');
define('WPINC', 'wp-includes');
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');
protected function getPluginVersion($file) {
$data = file_get_contents($file);
preg_match('/^[ \t*]*Version:(.*)$/mi', $data, $m);
return !empty($m[1]) ? trim($m[1]) : false;
}
}
}

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 'pages'
@require 'pages_custom'
@require 'mp2migrator'

View File

@ -21,6 +21,9 @@ a:focus
.mailpoet_spaced_block
margin: 1em 0
.mailpoet_centered
text-align: center
// select 2
.select2-container
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
z-index: 25
top: 48px
padding-bottom: 48px
margin: 0
.mailpoet_popup_wrapper
@ -54,6 +53,7 @@ body.mailpoet_modal_opened
position: relative
width: 100%
z-index: 0
height: 96%
.mailpoet_overlay_hidden .mailpoet_popup_wrapper
border: 1px solid #333
@ -75,6 +75,7 @@ body.mailpoet_modal_opened
.mailpoet_popup_body
padding: 10px 10px 10px 10px
height: 92%
// modal panel
#mailpoet_modal_overlay.mailpoet_panel_overlay

View File

@ -179,6 +179,28 @@ select.mailpoet_font-size
width: 100%
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
border-color: $button-primary-border-color
background-color: $button-primary-background-color

View File

@ -62,3 +62,9 @@ $draggable-widget-z-index = 2
background-color: $primary-active-color
box-shadow(inset 1px 2px 1px $primary-inset-shadow-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
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
&:hover > .mailpoet_block_highlight
&.mailpoet_highlight > .mailpoet_block_highlight
border: 1px dashed $block-hover-highlight-color
.mailpoet_block:last-child
margin-bottom: 0
&.mailpoet_highlight > .mailpoet_block_highlight
border: 1px dashed $block-hover-highlight-color !important
.mailpoet_content
position: relative

View File

@ -3,14 +3,19 @@
img
vertical-align: bottom
max-width: 100%
width: auto
height: auto
&.mailpoet_full_image
padding-left: 0
padding-right: 0
margin: auto
margin-bottom: 0
.mailpoet_content a:hover
cursor: all-scroll
.mailpoet_content
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_selection
animation-slide-open-downwards()
overflow-x: hidden
.mailpoet_settings_posts_show_display_options,
.mailpoet_settings_posts_show_post_selection
@ -26,7 +27,12 @@
.mailpoet_post_selection_container
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
border-radius(1px)
@ -45,3 +51,6 @@
.mailpoet_select_post_checkbox
margin-left: 10px
margin-right: 8px
.mailpoet_post_selection_loading
color: #999

View File

@ -127,9 +127,6 @@ body
background-color: $primary-background-color
border: 1px solid $content-border-color
#mailpoet_modal_close
display: none
.wrap > .mailpoet_notice,
.notice
.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 */
max-height: $max-height
opacity: 1
overflow-y: hidden
overflow-y: inherit
&.mailpoet_closed
max-height: 0px

View File

@ -2,6 +2,7 @@
Based on /wp-admin/css/about.css of WP 4.7.
This is to make MailPoet pages independent of the WordPress
About page styles that may differ across WP versions.
Please add custom styles to pages_custom.styl
*/
.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
.mailpoet_sending_methods
margin 25px 0 0 0
li
float left
position relative
padding 15px 15px 0 15px
display flex
flex-direction row
justify-content flex-start
> li
flex-grow 1
flex-shrink 1
display flex
flex-direction column
flex-basis 0
margin 0 25px 25px 0
width 300px
height 300px
border 1px solid #dedede
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
text-align center
height 54px
@ -25,12 +35,12 @@
.mailpoet_description
font-size 14px
.mailpoet_status
display flex
flex-direction row
justify-content space-between
align-items center
background-color #2f2f2f
color #fff
position absolute
bottom 0
left 0
right 0
text-overflow ellipsis
padding 15px
span
@ -43,23 +53,29 @@
visibility visible
#mailpoet_mta_activate
visibility hidden
.mailpoet_actions
bottom 15px
color #fff
padding 0
position absolute
right 15px
.button-secondary
margin 0 -6px -4px 0
// premium key
.mailpoet_key
&_valid
&::before
content ' '
&_invalid
&::before
content: ' '
.tooltip.dashicons.dashicons-editor-help
line-height: 1.4
ul.sending-method-benefits
list-style-type: none
margin-bottom: 2em
margin-top: 2em
.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
@media screen and (max-width: 782px)
@ -67,8 +83,10 @@
width auto
.mailpoet_sending_methods
li
float none
width auto
flex-flow: row wrap
justify-content: space-around
> li
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', [
'jquery'
],
function(jQuery) {
jQuery(function($) {
// dom ready
$(function() {
});
});
}
);
define('admin', [
'jquery'
],
function (jQuery) {
jQuery(function ($) {
// dom ready
$(function () {
});
});
}
);

View File

@ -1,74 +1,87 @@
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) {
'use strict';
MailPoet.Ajax = {
version: 0.5,
options: {},
defaults: {
url: null,
api_version: null,
endpoint: null,
action: null,
token: null,
data: {}
},
post: function(options) {
return this.request('post', options);
},
init: function(options) {
// merge options
this.options = jQuery.extend({}, this.defaults, options);
// set default url
if(this.options.url === null) {
this.options.url = ajaxurl;
}
// set default token
if(this.options.token === null) {
this.options.token = window.mailpoet_token;
}
},
getParams: function() {
return {
action: 'mailpoet',
api_version: this.options.api_version,
token: this.options.token,
endpoint: this.options.endpoint,
method: this.options.action,
data: this.options.data || {}
}
},
request: function(method, options) {
// set options
this.init(options);
// set request params
var params = this.getParams();
var deferred = jQuery.Deferred();
// remove null values from the data object
if (_.isObject(params.data)) {
params.data = _.pick(params.data, function(value) {
return (value !== null)
})
}
// ajax request
deferred = jQuery.post(
this.options.url,
params,
null,
'json'
).then(function(data) {
return data;
}, function(xhr) {
return xhr.responseJSON;
});
// clear options
this.options = {};
return deferred;
}
};
});
function requestFailed(errorMessage, xhr) {
if (xhr.responseJSON) {
return xhr.responseJSON;
}
return {
errors: [
{
message: errorMessage.replace('%d', xhr.status)
}
]
};
}
define('ajax', ['mailpoet', 'jquery', 'underscore'], function (mp, jQuery, _) {
var MailPoet = mp;
MailPoet.Ajax = {
version: 0.5,
options: {},
defaults: {
url: null,
api_version: null,
endpoint: null,
action: null,
token: null,
data: {}
},
post: function (options) {
return this.request('post', options);
},
init: function (options) {
// merge options
this.options = jQuery.extend({}, this.defaults, options);
// set default url
if (this.options.url === null) {
this.options.url = window.ajaxurl;
}
// set default token
if (this.options.token === null) {
this.options.token = window.mailpoet_token;
}
},
getParams: function () {
return {
action: 'mailpoet',
api_version: this.options.api_version,
token: this.options.token,
endpoint: this.options.endpoint,
method: this.options.action,
data: this.options.data || {}
};
},
request: function (method, options) {
var params;
var deferred;
// set options
this.init(options);
// set request params
params = this.getParams();
// remove null values from the data object
if (_.isObject(params.data)) {
params.data = _.pick(params.data, function (value) {
return (value !== null);
});
}
// 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 = [];
function track(name, data){
if (typeof window.mixpanel.track !== "function") {
function track(name, data) {
if (typeof window.mixpanel.track !== 'function') {
window.mixpanel.init(window.mixpanelTrackingId);
}
window.mixpanel.track(name, data);
}
function exportMixpanel(MailPoet) {
function exportMixpanel(mp) {
var MailPoet = mp;
MailPoet.forceTrackEvent = track;
if (window.mailpoet_analytics_enabled) {
@ -37,31 +38,32 @@ function exportMixpanel(MailPoet) {
function trackCachedEvents() {
eventsCache.map(function (event) {
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) {
eventsCache.push({
name: name,
data: data,
forced: forced,
forced: forced
});
}
define(
['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.forceTrackEvent = _.partial(cacheEvent, true);

View File

@ -3,164 +3,172 @@ define('date',
'mailpoet',
'jquery',
'moment'
], function(
MailPoet,
], function (
mp,
jQuery,
Moment
) {
'use strict';
'use strict';
MailPoet.Date = {
version: 0.1,
options: {},
defaults: {
offset: 0,
format: 'F, d Y H:i:s'
},
init: function(options) {
options = options || {};
var MailPoet = mp;
MailPoet.Date = {
version: 0.1,
options: {},
defaults: {
offset: 0,
format: 'F, d Y H:i:s'
},
init: function (opts) {
var options = opts || {};
// set UTC offset
if (
if (
options.offset === undefined
&& window.mailpoet_date_offset !== undefined
) {
options.offset = window.mailpoet_date_offset;
}
options.offset = window.mailpoet_date_offset;
}
// set date format
if (
if (
options.format === undefined
&& window.mailpoet_date_format !== undefined
) {
options.format = window.mailpoet_date_format;
}
// merge options
this.options = jQuery.extend({}, this.defaults, options);
return this;
},
format: function(date, options) {
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'
options.format = window.mailpoet_date_format;
}
};
// 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 = [];
var escapeToken = false;
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 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 (escapeToken === true) {
convertedFormat.push('['+token+']');
escapeToken = false;
} else {
if (token === '\\') {
// Slash escapes the next symbol to be treated as literal
escapeToken = true;
continue;
} else if (replacements[token] !== undefined) {
convertedFormat.push(replacements[token]);
if (!format || format.length <= 0) return format;
replacements = format_mappings['date'];
convertedFormat = [];
escapeToken = false;
for (index = 0, token = ''; format.charAt(index); index += 1) {
token = format.charAt(index);
if (escapeToken === true) {
convertedFormat.push('[' + token + ']');
escapeToken = false;
} 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([
'react'
'react',
],
function(
(
React
) {
) => {
const FormFieldCheckbox = React.createClass({
onValueChange: function(e) {
onValueChange: function (e) {
e.target.value = this.refs.checkbox.checked ? '1' : '0';
return this.props.onValueChange(e);
},
render: function() {
render: function () {
if (this.props.field.values === undefined) {
return false;
}
@ -20,15 +20,15 @@ function(
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return (
<p key={ 'checkbox-' + index }>
<p key={'checkbox-' + index}>
<label>
<input
ref="checkbox"
type="checkbox"
value="1"
checked={ isChecked }
onChange={ this.onValueChange }
name={ this.props.field.name }
checked={isChecked}
onChange={this.onValueChange}
name={this.props.field.name}
/>
{ this.props.field.values[value] }
</label>
@ -42,8 +42,8 @@ function(
{ options }
</div>
);
}
},
});
return FormFieldCheckbox;
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -2,9 +2,10 @@ define(
[
'jquery'
],
function(
$
function (
jQuery
) {
var $ = jQuery;
// Combination of jQuery.deparam and jQuery.serializeObject by Ben Alman.
/*!
* 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.
* http://benalman.com/about/license/
*/
$.fn.serializeObject = function(coerce) {
var obj = {},
coerce_types = { 'true': !0, 'false': !1, 'null': null };
$.fn.mailpoetSerializeObject = function (coerce) {
var obj = {};
var coerce_types = { true: !0, false: !1, null: null };
// Iterate over all name=value pairs.
$.each( this.serializeArray(), function(j,v){
var key = v.name,
val = v.value,
cur = obj,
i = 0,
$.each(this.serializeArray(), function (j, v) {
var key = v.name;
var val = v.value;
var cur = obj;
var i = 0;
// If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
// into its component parts.
keys = key.split( '][' ),
keys_last = keys.length - 1;
// If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
// into its component parts.
var keys = key.split('][');
var keys_last = keys.length - 1;
// If the first keys part contains [ and the last ends with ], then []
// 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.
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
// the beginning of the keys array.
keys = keys.shift().split('[').concat( keys );
keys = keys.shift().split('[').concat(keys);
keys_last = keys.length - 1;
} else {
@ -55,14 +56,14 @@ define(
}
// Coerce values.
if ( coerce ) {
val = val && !isNaN(val) ? +val // number
: val === 'undefined' ? undefined // undefined
if (coerce) {
val = val && !isNaN(val) ? +val // number
: val === 'undefined' ? undefined // undefined
: coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
: val; // string
}
if ( keys_last ) {
if (keys_last) {
// Complex key, build deep object structure based on a few rules:
// * The 'cur' pointer starts at the object top-level.
// * [] = 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.
// * Move the 'cur' pointer to the next level.
// * Rinse & repeat.
for ( ; i <= keys_last; i++ ) {
for (; i <= keys_last; i++) {
key = keys[i] === '' ? cur.length : keys[i];
cur = cur[key] = i < keys_last
? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
cur[key] = i < keys_last
? cur[key] || (keys[i + 1] && isNaN(keys[i + 1]) ? {} : [])
: val;
cur = cur[key];
}
} else {
// Simple key, even simpler rules, since only scalars and shallow
// arrays are allowed.
if ( $.isArray( obj[key] ) ) {
if ($.isArray(obj[key])) {
// 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,
// convert val into an array.
obj[key] = [ obj[key], val ];
obj[key] = [obj[key], val];
} else {
// val is a scalar.
@ -104,4 +106,4 @@ define(
return $;
}
);
);

View File

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

View File

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

View File

@ -1,40 +1,40 @@
define(['react', 'classnames'], function(React, classNames) {
define(['react', 'classnames'], (React, classNames) => {
var ListingGroups = React.createClass({
handleSelect: function(group) {
return this.props.onSelectGroup(group);
},
render: function() {
var groups = this.props.groups.map(function(group, index) {
if(group.name === 'trash' && group.count === 0) {
return false;
}
const ListingGroups = React.createClass({
handleSelect: function (group) {
return this.props.onSelectGroup(group);
},
render: function () {
const groups = this.props.groups.map((group, index) => {
if (group.name === 'trash' && group.count === 0) {
return false;
}
var classes = classNames(
{ 'current' : (group.name === this.props.group) }
const classes = classNames(
{ 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 (
<ul className="subsubsub">
{ groups }
</ul>
<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>
);
}
});
});
return ListingGroups;
}
);
return (
<ul className="subsubsub">
{ groups }
</ul>
);
},
});
return ListingGroups;
}
);

View File

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

View File

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

View File

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

View File

@ -1,46 +1,46 @@
define([
'mailpoet',
'react'
], function(
'mailpoet',
'react',
], (
MailPoet,
React
) {
) => {
var ListingSearch = React.createClass({
handleSearch: function(e) {
const ListingSearch = React.createClass({
handleSearch: function (e) {
e.preventDefault();
this.props.onSearch(
this.refs.search.value
this.refs.search.value.trim()
);
},
componentWillReceiveProps: function(nextProps) {
this.refs.search.value = nextProps.search
componentWillReceiveProps: function (nextProps) {
this.refs.search.value = nextProps.search;
},
render: function() {
if(this.props.search === false) {
render: function () {
if (this.props.search === 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;

View File

@ -1,4 +1,4 @@
define('mailpoet', [], function() {
define('mailpoet', [], function () {
// A placeholder for MailPoet object
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';
var MailPoet = mp;
MailPoet.MP2Migrator = {
fatal_error: '',
@ -10,7 +12,7 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
clearTimeout(MailPoet.MP2Migrator.displayLogs_timeout);
clearTimeout(MailPoet.MP2Migrator.updateProgressbar_timeout);
clearTimeout(MailPoet.MP2Migrator.update_wordpress_info_timeout);
setTimeout(MailPoet.MP2Migrator.updateDisplay, 1000)
setTimeout(MailPoet.MP2Migrator.updateDisplay, 1000);
},
stopLogger: function () {
@ -24,25 +26,26 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
displayLogs: function () {
jQuery.ajax({
url: mailpoet_mp2_migrator.log_file_url,
url: window.mailpoet_mp2_migrator.log_file_url,
cache: false
}).done(function (result) {
jQuery("#logger").html('');
result.split("\n").forEach(function (row) {
if(row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
jQuery('#logger').html('');
result.split('\n').forEach(function (resultRow) {
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
}
// 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('#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 () {
if(MailPoet.MP2Migrator.is_logging) {
if (MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000);
}
});
@ -50,21 +53,23 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
updateProgressbar: function () {
jQuery.ajax({
url: mailpoet_mp2_migrator.progress_url,
url: window.mailpoet_mp2_migrator.progress_url,
cache: false,
dataType: 'json'
}).always(function (result) {
// Move the progress bar
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);
}
jQuery('#progressbar').progressbar('option', 'value', progress);
jQuery('#progresslabel').html(progress + '%');
if(Number(result.current !== 0)) {
if (Number(result.current) !== 0) {
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);
}
});
@ -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.reactivateImportButton();
}).done(function (response) {
if(response) {
if (response) {
MailPoet.MP2Migrator.fatal_error = response.data;
}
}).fail(function (response) {
if(response.errors.length > 0) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function (error) {
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
MailPoet.MP2Migrator.reactivateImportButton();
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();
return false;
@ -143,6 +157,15 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
}
}).done(function () {
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;
},
@ -153,33 +176,33 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
}
};
/**
* Actions to run when the DOM is ready
*/
jQuery(function () {
jQuery('#progressbar').progressbar({value: 0});
jQuery('#progressbar').progressbar({ value: 0 });
// Import button
jQuery('#import').click(function() {
jQuery('#import').click(function () {
MailPoet.MP2Migrator.startImport();
});
// Stop import button
jQuery('#stop-import').click(function() {
jQuery('#stop-import').click(function () {
MailPoet.MP2Migrator.stopImport();
});
// Skip import link
jQuery('#skip-import').click(function() {
jQuery('#skip-import').click(function () {
MailPoet.MP2Migrator.skipImport();
});
// Go to welcome page
jQuery('#goto-welcome').click(function() {
jQuery('#goto-welcome').click(function () {
MailPoet.MP2Migrator.gotoWelcomePage();
});
// Update the display
MailPoet.MP2Migrator.updateDisplay();
});

View File

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

View File

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

View File

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

View File

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

View File

@ -4,65 +4,71 @@
* Allows resizing elements within a block
*/
define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup',
'interact'
], function(Marionette, BehaviorsLookup, interact) {
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup',
'interact'
], function (Marionette, BehaviorsLookup, interact) {
var BL = BehaviorsLookup;
BehaviorsLookup.ResizableBehavior = Marionette.Behavior.extend({
BL.ResizableBehavior = Marionette.Behavior.extend({
defaults: {
elementSelector: null,
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,
maxLength: Infinity,
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: {
"mouseenter": 'showResizeHandle',
"mouseleave": 'hideResizeHandle',
mouseenter: 'showResizeHandle',
mouseleave: 'hideResizeHandle'
},
onRender: function() {
onRender: function () {
this.attachResize();
if (this.isBeingResized !== true) {
this.hideResizeHandle();
}
},
attachResize: function() {
var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0),
that = this;
attachResize: function () {
var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0);
var that = this;
interact(domElement).resizable({
//axis: 'y',
// axis: 'y',
edges: {
top: false,
left: false,
right: false,
bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector,
},
}).on('resizestart', function(event) {
bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector
}
})
.on('resizestart', function () {
that.isBeingResized = true;
that.$el.addClass('mailpoet_resize_active');
}).on('resizemove', function(event) {
var currentLength = parseFloat(that.view.model.get(that.options.modelField)),
newLength = currentLength + that.options.transformationFunction(event.dy);
if (newLength < that.options.minLength) newLength = that.options.minLength;
that.view.model.set(that.options.modelField, newLength + 'px');
}).on('resizeend', function(event) {
}).on('resizemove', function (event) {
var onResize = that.options.onResize.bind(that);
return onResize(event);
})
.on('resizeend', function () {
that.isBeingResized = null;
that.$el.removeClass('mailpoet_resize_active');
});
},
showResizeHandle: function() {
showResizeHandle: function () {
if (typeof this.options.resizeHandleSelector === 'string') {
this.view.$(this.options.resizeHandleSelector).removeClass('mailpoet_hidden');
}
},
hideResizeHandle: function() {
hideResizeHandle: function () {
if (typeof this.options.resizeHandleSelector === 'string') {
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
*/
define([
'backbone.marionette',
'jquery',
'newsletter_editor/behaviors/BehaviorsLookup',
], function(Marionette, jQuery, BehaviorsLookup) {
'backbone.marionette',
'jquery',
'newsletter_editor/behaviors/BehaviorsLookup'
], function (Marionette, jQuery, BehaviorsLookup) {
var BL = BehaviorsLookup;
BehaviorsLookup.ShowSettingsBehavior = Marionette.Behavior.extend({
BL.ShowSettingsBehavior = Marionette.Behavior.extend({
defaults: {
ignoreFrom: '', // selector
ignoreFrom: '' // selector
},
events: {
'click .mailpoet_content': 'showSettings',
'click .mailpoet_content': 'showSettings'
},
showSettings: function(event) {
if(!this.isIgnoredElement(event.target)) {
showSettings: function (event) {
if (!this.isIgnoredElement(event.target)) {
this.view.triggerMethod('showSettings');
}
},
isIgnoredElement: function(element) {
isIgnoredElement: function (element) {
return this.options.ignoreFrom
&& this.options.ignoreFrom.length > 0
&& jQuery(element).is(this.options.ignoreFrom);
},
}
});
});

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