Compare commits

...

524 Commits

Author SHA1 Message Date
1d02c688a2 releasing 3.3.6 2018-01-23 15:16:36 +00:00
be3eb0a7ff Add 2 polls for new and old users respectively
[MAILPOET-1266]
2018-01-23 12:26:06 +00:00
eee01ee3f1 Merge pull request #1240 from mailpoet/acceptance-multisite
Execute acceptance tests on multisites [MAILPOET-1193]
2018-01-22 18:35:41 -05:00
56ad9a8976 Merge pull request #1241 from mailpoet/am_display_available_groups
Makes necessary changes for automatic emails feature to work [PREMIUM-55]
2018-01-22 15:41:47 +02:00
ba45ee7e35 Make acceptance login timeouts longer
[MAILPOET-1193]
2018-01-22 10:14:58 +00:00
a5543d9d78 Add multisite job to workflows
[MAILPOET-1193]
2018-01-22 08:37:34 +00:00
3c2ef4b8ee Merge pull request #1225 from mailpoet/re-captcha
Introduce reCAPTCHA [MAILPOET-1242]
2018-01-21 19:53:31 -05:00
07417391be Allows passing custom data to extra routes 2018-01-20 21:06:28 -05:00
e00d43e506 Displays email type thumbnail image when available 2018-01-20 21:06:22 -05:00
c4f285afca Not use deprecated code 2018-01-18 16:28:30 +00:00
c70097085e Add circle config
[MAILPOET-1193]
2018-01-18 16:02:01 +00:00
ca6bde8a64 Add multisite run for acceptance tests
[MAILPOET-1193]
2018-01-18 12:23:33 +00:00
a1ba783264 reCaptcha works without javascript now 2018-01-18 10:50:07 +00:00
e77d00b179 Add DB variables to compose file
it will be configured automatically and we don't need to run `cli` to configure
2018-01-18 10:00:36 +00:00
b683ae0bc1 Add command to delete docker stuff 2018-01-18 09:59:09 +00:00
f658fd7776 Release 3.3.5 2018-01-16 16:38:06 +02:00
1a08155f54 Merge pull request #1239 from mailpoet/mss_filters
Adds filters to control connection to MSS API [MAILPOET-1267]
2018-01-16 16:04:36 +02:00
a3b3e1f8df Removes filter after use 2018-01-16 08:24:44 -05:00
87aca7c667 Uses a helper to intercept WP functions 2018-01-16 08:21:26 -05:00
a7a8cd2be4 Updates filter name 2018-01-16 08:20:29 -05:00
819d4dc17a Abstracts WP functions 2018-01-15 19:52:51 -05:00
6e94db24a2 minor fixes 2018-01-15 12:43:48 +00:00
5bf532a750 minor UI fix 2018-01-15 10:47:26 +00:00
c63b7d9b91 handling multiple instances of reCaptcha 2018-01-15 10:47:25 +00:00
a8052c118a fixing minor issues and adding unit test 2018-01-15 10:46:07 +00:00
1ad0dce425 applying compact design and updating error messages 2018-01-15 10:46:06 +00:00
2228f60e2a adding recaptcha to form wiget 2018-01-15 10:46:06 +00:00
99a007fb70 fix recaptcha validation 2018-01-15 10:46:06 +00:00
5e5caab4da show error if recaptcha enabled but a key is empty 2018-01-15 10:46:06 +00:00
caa0623112 handling recaptcha on the PHP side 2018-01-15 10:46:06 +00:00
a05f9bf97b showing the recaptcha 2018-01-15 10:46:05 +00:00
7202d9dca1 toggle recaptcha token fields 2018-01-15 10:46:05 +00:00
655dac458a spacing issue 2018-01-15 10:46:05 +00:00
0e487d2c54 added re-captcha settings to UI 2018-01-15 10:46:05 +00:00
d2d1657cb2 added re-captcha settings to database 2018-01-15 10:46:05 +00:00
3bf800b51d Adds filter to set custom batch processing size 2018-01-14 12:39:16 -05:00
9910072e72 Adds filter to set custom request timeout value 2018-01-14 12:36:18 -05:00
8695d147e8 Merge pull request #1237 from mailpoet/eslint5-1
Eslint5 1 [ MAILPOET-1143]
2018-01-11 15:50:23 +01:00
042d47b027 Rename function
[MAILPOET-1143]
2018-01-11 09:55:20 +00:00
a27823ebc5 Merge pull request #1236 from mailpoet/list-report
Add lists and segments to reports [MAILPOET-1256]
2018-01-10 10:16:19 +01:00
1aee4489da Updates changelog and bumps up release version to 3.3.4 2018-01-09 13:30:06 -05:00
e4a37c3c2d Merge pull request #1233 from mailpoet/public-js
fix public.js not included when only shortcode is used [MAILPOET-1258]
2018-01-09 15:06:52 +00:00
ea2a91d15e Fix acceptance tests 2018-01-09 14:36:05 +00:00
72193fefae Loads dependencies only when subscription form (shortcode or widget) is
present
2018-01-09 14:16:04 +00:00
7e60155bdc fixing the acceptance test 2018-01-09 14:16:04 +00:00
7eae7dde11 making sure acceptance tests are executed 2018-01-09 14:16:04 +00:00
d70c6ce453 fix public.js not included when only shortcode is used 2018-01-09 14:16:04 +00:00
3337637a8b Removed redundant order statement
[MAILPOET-1256]
2018-01-09 13:54:11 +00:00
69ca597f24 Add lists and segments to reports
MAILPOET-1256
2018-01-09 13:54:11 +00:00
6f20a402f6 Use alternative dotdeb mirror
The old one is down with no ETA
2018-01-09 12:25:04 +00:00
19c7efc9ef Pauses sending if processed subscribers list can't be updated 2018-01-09 12:25:04 +00:00
6c25fab6d6 Adds option to pause sending after processing sending error 2018-01-09 12:25:04 +00:00
a45a7a7616 Fixes newsletter body being incorrectly saved 2018-01-09 12:25:04 +00:00
3053e21910 Fix acceptance tests
[MAILPOET-1143]
2018-01-08 15:51:12 +00:00
699017532e Fix func-names rule in es5
Please remove those comments if you work on those files
[MAILPOET-1143]
2018-01-08 14:56:00 +00:00
7465398d17 Fix prefer-template rule in es5
[MAILPOET-1143]
2018-01-08 11:49:05 +00:00
ef410fb877 Fix dot-notation rule in es5
[MAILPOET-1143]
2018-01-08 11:38:27 +00:00
f154687c53 Fix eol-last rule in es5
[MAILPOET-1143]
2018-01-08 11:36:17 +00:00
b773263fa4 Fix new-parens rule in es5
[MAILPOET-1143]
2018-01-08 11:35:35 +00:00
9870f62984 Fix object-shorthand rule in es5
[MAILPOET-1143]
2018-01-08 11:34:44 +00:00
1f6430c278 Fix strict rule in es5
[MAILPOET-1143]
2018-01-08 11:29:51 +00:00
4018ca4a65 Merge pull request #1235 from mailpoet/acceptance-fix
Fix acceptance tests on Circle CI
2018-01-03 15:39:47 +01:00
1342389602 Wait for target
MailHog adds target='_blank' to our links.
But we were too quick clicking the links before the target was added

MAILPOET-1260
2018-01-03 11:46:20 +00:00
368f7004c0 Fix acceptance tests on CircleCi
MAILPOET-1260
2018-01-02 14:48:21 +00:00
d33cc9d2b8 Release 3.3.3 2018-01-02 13:53:45 +00:00
e9be62c2d3 Merge pull request #1230 from mailpoet/editor-delete-btn
Improve safety of content deletion in newsletter editor [MAILPOET-1248]
2017-12-27 00:32:30 +03:00
f2733624e4 added transition 2017-12-26 10:09:48 +00:00
46121d74a8 Merge pull request #1232 from mailpoet/sending_queue_rendered_body_encoding
Switches serialize() for json_encode() to store rendered newsletter body [MAILPOET-1216]
2017-12-21 18:17:34 +03:00
cb430673f8 Uses json_encode() instead of serialize() to save rendered newsletter
body
2017-12-20 22:13:00 -05:00
8852f6a3f4 Adds function to test if string is a valid json 2017-12-20 22:13:00 -05:00
56e55c072a Highlight the deletable block in red 2017-12-20 22:11:30 -05:00
77e60d708a swapping cancel and delete links 2017-12-20 22:11:30 -05:00
27fbc25bf9 Fixes Debian missing PHP packages issue 2017-12-20 22:10:25 -05:00
a91929682f Merge pull request #1229 from mailpoet/hs-icon
moving the help icon 20px to the bottom [MAILPOET-1247]
2017-12-20 22:05:48 -05:00
d45c510ab3 Merge pull request #1226 from mailpoet/new_or_updated_user_welcome_email
Schedules welcome notification when adding/editing subscriber [MAILPOET-1249]
2017-12-20 14:26:24 +03:00
57366e972a Updates changelog and bumps up release version to 3.3.2 2017-12-19 11:40:11 -05:00
cbd1ae929e moving the help icon 20px to the bottom 2017-12-19 16:20:36 +00:00
884f476598 Merge pull request #1228 from mailpoet/change-update-header
updated the header string [MAILPOET-1195]
2017-12-19 09:01:13 -05:00
55296d52ec updated the header string 2017-12-19 12:47:57 +00:00
8fb4d2e78f Merge pull request #1227 from mailpoet/status_page_update
Validates ping response when displaying cron URL status [MAILPOET-1253]
2017-12-19 14:29:47 +03:00
840473aae5 Merge pull request #1224 from mailpoet/send_with_language_update
Removes unused string in "send with" tab [MAILPOET-1252]
2017-12-19 09:49:06 +01:00
5d5b61b76f Schedules welcome notification when adding/editing subscriber 2017-12-18 21:19:44 -05:00
0e2a67c203 Validates ping response when displaying cron URL status 2017-12-18 20:28:09 -05:00
aaed5add52 Removes unused string 2017-12-17 10:51:39 -05:00
f616ef4d6c Merge pull request #1218 from mailpoet/widget_update
Prevents loading plugin assets when form widget is not activated [MAILPOET-1241]
2017-12-15 23:15:31 +02:00
083a6fd3a3 Merge pull request #1223 from mailpoet/new-surveys
What's New: 2 new surveys, less call-to-actions [MAILPOET-1243]
2017-12-14 23:28:00 +03:00
9a1ec60750 releasing 3.3.1 2017-12-14 18:09:17 +00:00
bd67f5fd36 New poll
[MAILPOET-1243]
2017-12-14 14:46:18 +00:00
ed2a2a6fa8 Remove additional CTAs
[PREMIUM-1243]
2017-12-14 13:12:53 +00:00
6a28fdcac2 Merge pull request #1222 from mailpoet/number_formatting_fix
Formats number according to user's locale [MAILPOET-1246]
2017-12-14 15:38:37 +03:00
fda1828637 Adds composer.lock security check
[MAILPOET-1226]
2017-12-14 08:47:47 +00:00
ebcc094b4d Formats number according to user's locale 2017-12-13 21:53:03 -05:00
51cde55217 Tests statistics column 2017-12-13 09:39:03 +00:00
23dff7403d Updates variable names to match those in views/newsletters.html 2017-12-13 09:39:03 +00:00
de164f918c releasing 3.3.0 2017-12-12 17:03:21 +00:00
84840c5261 releasing 3.2.5 2017-12-12 16:30:44 +00:00
16b9e0d467 Merge pull request #1219 from mailpoet/new_poll
New poll [MAILPOET-1225]
2017-12-11 18:56:16 +03:00
3c75e7b0bc Merge pull request #1211 from mailpoet/email-segments
Email segments [PREMIUM-44]
2017-12-11 09:29:49 -05:00
c41d5894d0 New poll
[MAILPOET-1225]
2017-12-11 09:39:20 +00:00
1f76f0d98f Allow form fields styling
[PREMIUM-44]
2017-12-11 09:05:29 +00:00
e859c090c8 Allow values for selections to be passed
[PREMIUM-44]
2017-12-11 08:54:43 +00:00
42528ab309 Allow select2 for single selects
[PREMIUM-44]
2017-12-11 08:49:36 +00:00
d761867a7d Enable dynamic segment edits
[PREMIUM-44]
2017-12-11 08:49:36 +00:00
6996615999 Improves tests that would fail due to array_flip and 1 item in array 2017-12-10 19:31:35 -05:00
3450a6fc97 Adds wait condition before assertion; corrects prefix 2017-12-10 18:41:54 -05:00
554dbb8dda Initializes dependencies only when widget is active 2017-12-09 19:53:12 -05:00
d13aa67a07 Centralizes widget logic in one place
Cleans up code
2017-12-09 19:45:19 -05:00
1f9c956637 Removes unused code 2017-12-09 19:27:21 -05:00
59d09866a1 Merge pull request #1217 from mailpoet/eslint-es6-3
Eslint es6 3 [MAILPOET-1139]
2017-12-07 18:49:21 +01:00
f8ade27a6c Improve code
[MAILPOET-1139]
2017-12-07 17:31:15 +00:00
313bcddba1 Split line into multiple lines
[MAILPOET-1139]
2017-12-07 16:39:02 +00:00
fd64702eaa Changes WP DB prefix 2017-12-07 11:08:52 +00:00
e2884ac625 Fix eqeqeq eslint rule in ES6 files
[MAILPOET-1139]
2017-12-07 09:44:58 +00:00
9ce9cb929f Fix camelcase eslint rule in ES6 files
[MAILPOET-1139]
2017-12-07 09:39:05 +00:00
52d33d7fe8 Merge pull request #1215 from mailpoet/fix-template-duplication
Use website language when running populator [MAILPOET-1233]
2017-12-06 20:05:53 -05:00
2ee33b2fc2 Bumps up minimum required WP version 2017-12-06 19:52:12 -05:00
b107be3241 Use website language when running populator
[MAILPOET-1233]
2017-12-06 19:51:09 -05:00
76148d0a85 Fix import/no-extraneous-dependendencies eslint rule in ES6 files
[MAILPOET-1139]
2017-12-06 16:37:45 +00:00
61a7f99e42 Make import/extensions eslint rule an exception in ES6 files
[MAILPOET-1139]
2017-12-06 16:19:48 +00:00
62e3ca8a80 Fix consistent-return eslint rule in ES6 files
[MAILPOET-1139]
2017-12-06 16:05:21 +00:00
d431efb288 Fix array-callback-return eslint rule in ES6 files
[MAILPOET-1139]
2017-12-06 15:55:15 +00:00
adf6485b7d Fix default-case eslint rule in ES6 files
[MAILPOET-1139]
2017-12-06 14:30:54 +00:00
170fd7f051 Fix prefer-template eslint rule in ES6 files
[MAILPOET-1139]
2017-12-06 14:18:24 +00:00
e7ddfc3d29 Fix arrow-body-style eslint rule in ES6 files
[MAILPOET-1139]
2017-12-06 14:16:41 +00:00
b08b53e9c0 Fix no-bitwise eslint rule in ES6 files
[MAILPOET-1139]
2017-12-06 13:58:56 +00:00
570529a8d8 Release 3.2.4 2017-12-05 18:58:18 +02:00
5505ffc783 Merge pull request #1206 from mailpoet/manage_subscription_shortcode_update
Allows using manage_subscription shortcode outside of newsletters [MAILPOET-1122]
2017-12-05 19:38:37 +03:00
6e5fc6ad6b Fix array_key_exists() error if no data is passed to subscription management page [MAILPOET-1122] 2017-12-05 19:22:28 +03:00
e731b261ab Conditionally initializes shortcodes/filters 2017-12-05 10:34:03 -05:00
955f24e71a Load mb_strtoupper() polyfill for MP2 migration
* Load mb_strtoupper() polyfill for MP2 migration
* Add "symfony/polyfill-mbstring" as explicit dependency
* Fixate AspectMock at 2.0.1
* Generate composer.lock with PHP 5.6 to include required dependencies

[MAILPOET-1234]
2017-12-05 11:15:31 +00:00
89ebc6051a Merge pull request #1210 from mailpoet/help_status_tab
Adds system status tab to the Help menu
2017-12-05 10:38:19 +02:00
79fff7b65d Merge pull request #1212 from mailpoet/new-poll
New poll [MAILPOET-1213]
2017-12-04 20:09:15 -05:00
7864e08900 Enables [mailpoet_manage] shortcode
Updates code
2017-12-04 19:58:30 -05:00
878cdf46e6 Update vulnerable Handlebars to newest version [MAILPOET-1235] 2017-12-04 16:12:30 +00:00
131d341744 New poll
[MAILPOET-1213]
2017-12-04 11:13:34 +00:00
513062b15d Cleans up code 2017-12-03 11:38:45 -05:00
4926267e36 Adds system status tab to the Help menu 2017-12-03 11:38:45 -05:00
aa4d78b1c9 Adds method to ping Bridge 2017-12-03 11:38:44 -05:00
8d7289e8ba Merge pull request #1208 from mailpoet/eslint-rules2
Eslint rules2 [MAILPOET-1144]
2017-11-30 19:24:26 -05:00
eb768ff1ee Merge pull request #1209 from mailpoet/mailer_error_code
Make rendering errors during sending more descriptive [MAILPOET-1232]
2017-11-30 12:09:40 -05:00
8afe7f5d97 Make rendering error during sending more descriptive [MAILPOET-1232] 2017-11-30 19:45:53 +03:00
97fb5cf66f Makes string translateable 2017-11-30 09:44:34 +00:00
8ea4a219e2 Fix padded-blocks eslint rule in ES5 files
[MAILPOET-1144]
2017-11-30 09:12:19 +00:00
0e08e58288 Fix camelcase eslint rule in ES5 files
[MAILPOET-1144]
2017-11-30 09:10:08 +00:00
dc52ba0d93 Bump up release version to 3.2.3 2017-11-29 18:35:43 +03:00
dc569672a9 Fix no-use-before-define eslint rule in ES5 files
[MAILPOET-1144]
2017-11-29 15:17:30 +00:00
159e946093 Fix no-else-return eslint rule in ES5 files
[MAILPOET-1144]
2017-11-29 15:04:38 +00:00
3d2a433df1 Moved no-underscore-dangel eslint rule to exceptions
this._appView cannot be renamed

[MAILPOET-1144]
2017-11-29 15:01:16 +00:00
fe0476e1c0 Fix no-shadow eslint rule in ES5 files
[MAILPOET-1144]
2017-11-29 14:49:19 +00:00
ea552508b4 Fix consistent-return eslint rule in ES5 files
[MAILPOET-1144]
2017-11-29 13:43:35 +00:00
dee2ff810c Fixes undefined index error 2017-11-29 10:19:30 +00:00
63ed835d64 Allows using manage_subscription shortcode outside of newsletters 2017-11-28 22:02:29 -05:00
fbf58f23fc Bump up release version to 3.2.2 2017-11-28 20:18:18 +03:00
b6f62bd9bc Catches exceptions during sending 2017-11-28 16:42:22 +00:00
88ef454844 Merge pull request #1203 from mailpoet/nov28_survey
Updates weekly survey [MAILPOET-1224]
2017-11-28 16:07:22 +03:00
3572df6c0c Fixes "other" method test email sending when MSS is enabled
[MAILPOET-1218]
2017-11-28 09:28:56 +00:00
714e6e013f Updates weekly survey 2017-11-27 19:51:40 -05:00
91568cbeb6 Merge pull request #1202 from mailpoet/post_notification_creation_acceptance_test
Tests post notification creation [MAILPOET-1219]
2017-11-27 12:59:48 +02:00
393c89b21f Tests that post notifications can be created 2017-11-23 21:02:42 -05:00
0b491b7943 Adds method to get current URL 2017-11-23 21:02:41 -05:00
e5d7f66561 Adds new id to create standard/notification newsletters 2017-11-23 21:02:36 -05:00
b7a7f40cde Merge pull request #1199 from mailpoet/image_url_space_fix
Encodes URLs in inlined images [MAILPOET-1220]
2017-11-23 16:03:57 +03:00
a267c7524c Close the a tag to make valid HTML [MAILPOET-1220] 2017-11-23 16:02:40 +03:00
20b59d11a7 Merge pull request #1201 from mailpoet/user_profile_language_fix
Changes plugin language based on user's locale [MAILPOET-1211]
2017-11-23 13:40:59 +02:00
94c7e2a5c0 Changes plugin language based on user's locale 2017-11-22 20:13:56 -05:00
d603d99a04 Merge pull request #1190 from mailpoet/multisite_unit_tests
Adds new CI multisite test environment
2017-11-22 15:16:20 +02:00
e7ffe4d694 Replaces spaces in image URLs 2017-11-21 21:02:33 -05:00
018d7bce77 Corrects test to work on multisite environment 2017-11-21 12:26:02 -05:00
64c40d5a1c Adds test for multisite environment 2017-11-21 12:25:54 -05:00
7e49328d5e Corrects test to work on multisite environment 2017-11-21 12:24:19 -05:00
b50b636371 Adds multisite test environment to CI
Adds Robo command to run multisite tests locally and on CI
2017-11-21 12:24:18 -05:00
ec3bb5b95c Release 3.2.1 2017-11-21 15:18:40 +00:00
6b2503fb36 Merge pull request #1196 from mailpoet/minimum_wp_version_notice
Shows notice on WP version < 4.6 and deactivates plugin [MAILPOET-1215]
2017-11-21 16:26:03 +02:00
e71d47f983 Adds link to minimum requirements page for WP version 2017-11-21 08:57:28 -05:00
085d4f566a Merge pull request #1198 from mailpoet/hide-honeypot
Hide honeypot field to prevent Safari autocomplete [MAILPOET-1180]
2017-11-21 16:08:45 +03:00
29b249de6e Remove an attribute we don't need anymore
[MAILPOET-1180]
2017-11-21 12:54:00 +00:00
883ae5b0e4 Hide honeypot field to prevent Safari autocomplete
[MAILPOET-1180]
2017-11-21 11:47:46 +00:00
d78990cda3 Shows notice on WP version < 4.6 and deactivates plugin 2017-11-20 14:26:31 -05:00
e5b5a8df37 Merge pull request #1197 from mailpoet/poll_nov20
Update the poll [MAILPOET-1212]
2017-11-20 11:30:29 -05:00
2ea2db66ec Update the poll [MAILPOET-1212] 2017-11-20 19:03:47 +03:00
7b76ddefd5 Merge pull request #1194 from mailpoet/double_opt-in_test
Add an acceptance test for subscription confirmation [MAILPOET-1205]
2017-11-20 14:36:55 +02:00
6c56ac8509 Clean up the acceptance dump from unused pages [MAILPOET-1206] 2017-11-20 10:42:17 +00:00
dc074bcb14 Add acceptances tests for admin listings [MAILPOET-1206] [MAILPOET-1207] [MAILPOET-1208] [MAILPOET-1209] 2017-11-20 10:42:17 +00:00
0193644b18 Merge pull request #1195 from mailpoet/test_email_send_fix
Fixes "mailer not defined" JS error [MAILPOET-1217]
2017-11-20 13:21:16 +03:00
66de11ecd4 Fixes "mailer not defined" JS error 2017-11-18 19:51:53 -05:00
1238b1711d Add an acceptance test for subscription confirmation [MAILPOET-1205] 2017-11-16 14:57:56 +03:00
0061a9daf9 Move key checks from constructor to init() in Menu class [MAILPOET-1204] 2017-11-15 11:23:39 +00:00
52a55d3bd1 Updates changelog and bumps up release version to 3.2.0 2017-11-14 14:13:49 -05:00
7d686eb1d1 Merge pull request #1191 from mailpoet/post_excerpt_shortcode_fix
Fixes shortcodes not being properly stripped in excerpts [MAILPOET-1210]
2017-11-14 20:51:21 +02:00
962188bdb8 Adds unit test 2017-11-14 13:39:56 -05:00
72c3f763ec Removes shortcodes from full post content 2017-11-14 13:17:47 -05:00
44c5d8490f Fixes shortcodes not being properly stripped on excerpts 2017-11-14 12:35:08 -05:00
fb85623b86 Merge pull request #1185 from mailpoet/mp_api_unsubscribe_from_lists
Adds API method to unsubscribe from lists [MAILPOET-1202]
2017-11-14 17:38:53 +02:00
d7bf6addf1 Fixes error due to throwing exception inside array_map 2017-11-14 09:58:01 -05:00
adea1e9be1 Fixes condition that allowed unsubscribing from WP segments 2017-11-14 09:56:53 -05:00
4f77ca31a3 Rebased master 2017-11-13 12:41:46 -05:00
087f2ebcdd Uses a built-in method to handle plural exception cases
Implements check for empty segments
Implements check for WP segment when unsubscribing
2017-11-13 10:12:50 -05:00
3d2a63f319 Adds methods to unsubscribe from list(s) 2017-11-13 10:12:50 -05:00
54dd3b621a Improves error messages, cleans up code 2017-11-13 10:11:07 -05:00
7fea134109 Merge pull request #1183 from mailpoet/dynamic-segments-listings
Dynamic segments subscribers listings [PREMIUM-38] [PREMIUM-43]
2017-11-13 16:52:48 +03:00
fcf4bd12d9 Merge pull request #1186 from mailpoet/mp_api_get_subscriber
Adds API method to get subscriber [MAILPOET-1203]
2017-11-13 15:38:47 +02:00
6694555592 Adds method to get subscriber 2017-11-10 18:47:53 -05:00
e6eb3d691e Fix quality problems
[PREMIUM-38]
2017-11-09 12:14:01 +00:00
00f2d418cc Load subscribers from dynamic segments
[PREMIUM-38]
2017-11-09 10:11:47 +00:00
c63f218edd Enable to add segments to subscribers listing filter
[PREMIUM-38]
2017-11-09 10:11:47 +00:00
fae849bbfc Merge pull request #1182 from mailpoet/es6_rules_1
Fix ES6 rules 1 [MAILPOET-1137]
2017-11-08 22:32:43 -05:00
2a253ccb8d Move ES6 no-script-url rule to exceptions [MAILPOET-1137] 2017-11-08 21:42:10 -05:00
ff55e55ad2 Fix indentation [MAILPOET-1137] 2017-11-08 21:42:10 -05:00
d798ec446d Fix ES6 no-alert eslint rule (replace confirm() with a custom UI component) [MAILPOET-1137] 2017-11-08 21:41:17 -05:00
56b4038073 Fix padded-blocks after rebasing [MAILPOET-1137] 2017-11-08 21:41:16 -05:00
076a55f1fb Fix ES6 no-underscore-dangle eslint rule [MAILPOET-1137] 2017-11-08 21:41:16 -05:00
10eca98dc3 Fix ES6 padded-blocks eslint rule [MAILPOET-1137] 2017-11-08 21:41:16 -05:00
37aec3ee4f Fix ES6 wrap-iife eslint rule [MAILPOET-1137] 2017-11-08 21:41:16 -05:00
dc7c629e3b Fix ES6 no-shadow eslint rule [MAILPOET-1137] 2017-11-08 21:41:16 -05:00
e4f16eda49 Fix ES6 dot-notation eslint rule [MAILPOET-1137] 2017-11-08 21:41:16 -05:00
bf15bda84f Merge pull request #1181 from mailpoet/norwegian
Update translations in readme [MAILPOET-1196]
2017-11-08 09:19:20 -05:00
8472a837e8 Fix an 'Undefined index: SERVER_NAME' error in ConflictResolverTest [MAILPOET-1187] 2017-11-08 14:18:58 +00:00
fc326131ae Rebased master 2017-11-08 14:04:45 +00:00
a19753205f Increases timeout value when tests are run on slow hosts 2017-11-08 14:04:45 +00:00
ee404e3b84 Uses CLI to truncate table 2017-11-08 14:04:45 +00:00
f1918ac953 Removes timeouts and uses data-automation-id as element selector 2017-11-08 14:04:45 +00:00
d399ddf6b6 Fixed acceptance tests for me 2017-11-08 14:04:45 +00:00
3f06448f37 Uses data-automation-id/iframe id and bypasses throttling for
consecutive signups
2017-11-08 14:04:45 +00:00
83d84e67d6 Adds test for iframe form subscription 2017-11-08 14:04:45 +00:00
46c42c1bb4 Re-adds vendor folder installation (this is needed for Alex's setup) 2017-11-08 14:04:45 +00:00
944bf67190 Updates docker-compose 2017-11-08 14:04:45 +00:00
7cac061a73 Renames test 2017-11-08 14:04:45 +00:00
17764b708f Adds check for JS errors 2017-11-08 14:04:45 +00:00
2e0f4dfb19 Removes the need to reinstall vendor folder 2017-11-08 14:04:45 +00:00
7f5bc8681c Adds acceptance tests for unsubscribe/manage subscription links 2017-11-08 14:04:45 +00:00
bfcb85f744 Updates existing tests format 2017-11-08 14:04:45 +00:00
cb9aaf120e Cleans up DB dump 2017-11-08 14:04:45 +00:00
97a9465db3 Adds MailHog for SMTP testing 2017-11-08 14:04:45 +00:00
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
c9cbfb4317 Update translations in readme [MAILPOET-1196] 2017-11-07 12:04:56 +03: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
fd65117a5d Fix makepot adding extra slashes to escaped characters in views [MAILPOET-1093] 2017-09-12 11:17:04 +03:00
324 changed files with 18395 additions and 6675 deletions

View File

@ -1,6 +1,4 @@
Listen 8080 <VirtualHost *:80>
<VirtualHost *:8080>
UseCanonicalName Off UseCanonicalName Off
ServerName mailpoet.loc ServerName mailpoet.loc
DocumentRoot /home/circleci/mailpoet/wordpress DocumentRoot /home/circleci/mailpoet/wordpress
@ -8,6 +6,9 @@ Listen 8080
LogLevel notice LogLevel notice
<Directory /home/circleci/mailpoet/wordpress> <Directory /home/circleci/mailpoet/wordpress>
Options Indexes FollowSymLinks
AllowOverride All
RewriteEngine On
Require all granted Require all granted
</Directory> </Directory>
</VirtualHost> </VirtualHost>

View File

@ -1,6 +1,6 @@
version: 2 version: 2
jobs: jobs:
qa_js_php5: qa_js_security_php5:
working_directory: /home/circleci/mailpoet working_directory: /home/circleci/mailpoet
docker: docker:
- image: circleci/php:5.6.30-apache-browsers - image: circleci/php:5.6.30-apache-browsers
@ -38,6 +38,10 @@ jobs:
command: | command: |
mkdir test-results/mocha mkdir test-results/mocha
./do t:j test-results/mocha/junit.xml ./do t:j test-results/mocha/junit.xml
- run:
name: "Composer security check"
command: |
./do s:composer
- run: - run:
name: "PHP Unit tests" name: "PHP Unit tests"
command: | command: |
@ -81,6 +85,7 @@ jobs:
curl -sS https://getcomposer.org/installer | php curl -sS https://getcomposer.org/installer | php
php composer.phar install php composer.phar install
./do install ./do install
./do compile:all --env production
- save_cache: - save_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }} key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths: paths:
@ -94,9 +99,52 @@ jobs:
command: | command: |
docker-compose run codeception --steps --debug -vvv --html --xml docker-compose run codeception --steps --debug -vvv --html --xml
- store_artifacts: - store_artifacts:
path: ~/mailpoet/tests/acceptance-tests/_output path: tests/_output
- store_test_results: - store_test_results:
path: ~/mailpoet/tests/acceptance-tests/_output path: tests/_output
acceptance_tests_multisite:
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 -e MULTISITE=1 codeception --steps --debug -vvv --html --xml
- store_artifacts:
path: tests/_output
- store_test_results:
path: tests/_output
php7: php7:
working_directory: /home/circleci/mailpoet working_directory: /home/circleci/mailpoet
docker: docker:
@ -119,7 +167,38 @@ jobs:
- run: - run:
name: "PHP Unit tests" name: "PHP Unit tests"
command: | command: |
WP_TEST_PATH="/home/circleci/mailpoet/wordpress" ./do t:u --xml ./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
php7_multisite:
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_multisite
- run:
name: "PHP Unit tests"
command: |
./do t:multisite-unit --xml
- store_test_results: - store_test_results:
path: tests/_output path: tests/_output
- store_artifacts: - store_artifacts:
@ -132,6 +211,8 @@ workflows:
version: 2 version: 2
build_and_test: build_and_test:
jobs: jobs:
- qa_js_php5 - qa_js_security_php5
- php7 - php7
- php7_multisite
- acceptance_tests - acceptance_tests
- acceptance_tests_multisite

View File

@ -2,11 +2,16 @@
function setup { function setup {
local version=$1 local version=$1
local wp_cli_wordpress_path="--path=wordpress"
local wp_cli_allow_root="--allow-root"
# install PHP dependencies for WordPress # install PHP dependencies for WordPress
if [[ $version == "php7" ]]; then if [[ $version == "php7" ]] || [[ $version == "php7_multisite" ]]; then
echo "deb http://packages.dotdeb.org jessie all" | sudo tee -a /etc/apt/sources.list.d/dotdeb.list echo "deb http://dotdeb.mirror.iphh.net/ 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 echo "deb-src http://dotdeb.mirror.iphh.net/ jessie all" | sudo tee -a /etc/apt/sources.list.d/dotdeb.list
wget -qO - http://www.dotdeb.org/dotdeb.gpg | sudo apt-key add - wget -qO - https://mirror.applebred.net/dotdeb.key | sudo apt-key add -
# ref: https://github.com/docker-library/php/pull/542
sudo rm /etc/apt/preferences.d/no-debian-php
sudo apt-get update sudo apt-get update
sudo apt-get install mysql-client php7.0-mysql zlib1g-dev sudo apt-get install mysql-client php7.0-mysql zlib1g-dev
sudo docker-php-ext-install mysqli pdo pdo_mysql zip sudo docker-php-ext-install mysqli pdo pdo_mysql zip
@ -15,34 +20,66 @@ function setup {
sudo apt-get install mysql-client php5-mysql zlib1g-dev sudo apt-get install mysql-client php5-mysql zlib1g-dev
sudo docker-php-ext-install mysql mysqli pdo pdo_mysql zip sudo docker-php-ext-install mysql mysqli pdo pdo_mysql zip
fi fi
# Add a fake sendmail mailer # Add a fake sendmail mailer
sudo cp ./.circleci/fake-sendmail.php /usr/local/bin/ sudo cp ./.circleci/fake-sendmail.php /usr/local/bin/
# configure Apache # configure Apache
sudo cp ./.circleci/mailpoet_php.ini /usr/local/etc/php/conf.d/ sudo cp ./.circleci/mailpoet_php.ini /usr/local/etc/php/conf.d/
sudo cp ./.circleci/apache/mailpoet.loc.conf /etc/apache2/sites-available sudo cp ./.circleci/apache/mailpoet.loc.conf /etc/apache2/sites-available
sudo a2dissite 000-default.conf
sudo a2ensite mailpoet.loc sudo a2ensite mailpoet.loc
sudo a2enmod rewrite sudo a2enmod rewrite
sudo service apache2 restart sudo service apache2 restart
# Install NodeJS+NPM # Install NodeJS+NPM
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install nodejs build-essential sudo apt-get install nodejs build-essential
# install plugin dependencies # install plugin dependencies
curl -sS https://getcomposer.org/installer | php curl -sS https://getcomposer.org/installer | php
./composer.phar install ./composer.phar install
./do install ./do install
# Set up Wordpress
# Set up WordPress
mysql -h 127.0.0.1 -u root -e "create database 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 curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar chmod +x wp-cli.phar
./wp-cli.phar core download --allow-root --path=wordpress sudo mv wp-cli.phar /usr/local/bin/wp
wp core download $wp_cli_wordpress_path $wp_cli_allow_root
# Generate `wp-config.php` file with debugging enabled # 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 echo "define(\"WP_DEBUG\", true);" | wp core config --dbname=wordpress --dbuser=root --dbhost=127.0.0.1 --extra-php $wp_cli_wordpress_path $wp_cli_allow_root
# Change default table prefix
sed -i "s/\$table_prefix = 'wp_';/\$table_prefix = 'mp_';/" ./wordpress/wp-config.php
# Install WordPress # 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 if [[ $version == "php7_multisite" ]]; then
# Configure multisite environment
wp core multisite-install --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc --title="WordPress MultiSite" $wp_cli_wordpress_path $wp_cli_allow_root
cp ./.circleci/wordpress/.htaccess ./wordpress/
# Add a second blog
wp site create --slug=php7_multisite $wp_cli_wordpress_path $wp_cli_allow_root
echo "WP_TEST_MULTISITE_SLUG=php7_multisite" >> .env
echo "WP_TEST_PATH_MULTISITE=/home/circleci/mailpoet/wordpress" >> .env
echo "HTTP_HOST=mailpoet.loc" >> .env
# Add a third dummy blog
wp site create --slug=dummy_multisite $wp_cli_wordpress_path $wp_cli_allow_root
else
wp core install --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc --title="WordPress Single" $wp_cli_wordpress_path $wp_cli_allow_root
echo "WP_TEST_PATH=/home/circleci/mailpoet/wordpress" >> .env
fi
# Softlink plugin to plugin path # Softlink plugin to plugin path
ln -s ../../.. wordpress/wp-content/plugins/mailpoet ln -s ../../.. wordpress/wp-content/plugins/mailpoet
./wp-cli.phar plugin activate mailpoet --path=wordpress
# Create .env file with correct path to WP installation # Activate plugin
# TODO: Remove this line after PR gets merged and CircleCI env variables change if [[ $version == "php7_multisite" ]]; then
echo "WP_TEST_PATH=\"/home/circleci/mailpoet/wordpress\"" > .env wp plugin activate mailpoet --url=http://mailpoet.loc/php7_multisite/ $wp_cli_wordpress_path $wp_cli_allow_root
} else
wp plugin activate mailpoet $wp_cli_wordpress_path $wp_cli_allow_root
fi
}

View File

@ -0,0 +1,12 @@
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

View File

@ -1,4 +1,6 @@
WP_TEST_PATH="/var/www/wordpress" WP_TEST_PATH="/var/www/wordpress"
WP_TEST_PATH_MULTISITE="/var/www/wordpress"
WP_TEST_MULTISITE_SLUG=""
WP_TEST_ENABLE_NETWORK_TESTS="true" WP_TEST_ENABLE_NETWORK_TESTS="true"
WP_TEST_IMPORT_MAILCHIMP_API="" WP_TEST_IMPORT_MAILCHIMP_API=""
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma) WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
@ -16,4 +18,5 @@ WP_TEST_MAILER_SMTP_LOGIN=""
WP_TEST_MAILER_SMTP_PASSWORD="" WP_TEST_MAILER_SMTP_PASSWORD=""
WP_SVN_USERNAME="" WP_SVN_USERNAME=""
WP_SVN_PASSWORD="" WP_SVN_PASSWORD=""
WP_TRANSIFEX_API_TOKEN="" WP_TRANSIFEX_API_TOKEN=""
HTTP_HOST="" // URL of your site (used for multisite env and equals to DOMAIN_CURRENT_SITE from wp-config.php)

View File

@ -8,18 +8,17 @@
"ecmaVersion": 5 "ecmaVersion": 5
}, },
"rules": { "rules": {
// Exceptions
"no-underscore-dangle": 0, // Backbone uses underscores, we cannot remove them
// Temporary
"import/no-amd": 0, "import/no-amd": 0,
"space-before-function-paren": 0,
"prefer-arrow-callback": 0, "prefer-arrow-callback": 0,
"key-spacing": 0,
"radix": 0, "radix": 0,
"no-alert": 0, "no-alert": 0,
"block-scoped-var": 0,
"guard-for-in": 0, "guard-for-in": 0,
"no-prototype-builtins": 0, "no-prototype-builtins": 0,
"no-restricted-syntax": 0, "no-restricted-syntax": 0,
"no-useless-concat": 0, "no-useless-concat": 0,
"no-multi-spaces": 0,
"no-nested-ternary": 0, "no-nested-ternary": 0,
"no-sequences": 0, "no-sequences": 0,
"no-useless-return": 0, "no-useless-return": 0,
@ -27,50 +26,21 @@
"new-cap": 0, "new-cap": 0,
"no-continue": 0, "no-continue": 0,
"no-new": 0, "no-new": 0,
"space-unary-ops": 0,
"no-redeclare": 0, "no-redeclare": 0,
"no-console": 0, "no-console": 0,
"no-empty": 0, "no-empty": 0,
"no-useless-escape": 0, "no-useless-escape": 0,
"wrap-iife": 0, "wrap-iife": 0,
"block-spacing": 0,
"computed-property-spacing": 0,
"no-plusplus": 0, "no-plusplus": 0,
"array-bracket-spacing": 0,
"default-case": 0, "default-case": 0,
"no-lonely-if": 0, "no-lonely-if": 0,
"space-before-blocks": 0,
"no-mixed-operators": 0, "no-mixed-operators": 0,
"eqeqeq": 0, "eqeqeq": 0,
"space-in-parens": 0,
"max-len": 0, "max-len": 0,
"no-trailing-spaces": 0,
"global-require": 0, "global-require": 0,
"no-throw-literal": 0, "no-throw-literal": 0,
"no-extra-bind": 0, "no-extra-bind": 0,
"one-var-declaration-per-line": 0,
"consistent-return": 0,
"no-shadow": 0,
"no-underscore-dangle": 0,
"brace-style": 0, "brace-style": 0,
"no-else-return": 0, "space-infix-ops": 0
"no-use-before-define": 0,
"one-var": 0,
"camelcase": 0,
"spaced-comment": 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,
"keyword-spacing": 0,
"eol-last": 0,
"dot-notation": 0,
"prefer-template": 0,
"func-names": 0
} }
} }

View File

@ -14,7 +14,11 @@
"import/resolver": "webpack" "import/resolver": "webpack"
}, },
"rules": { "rules": {
// Exceptions
"comma-dangle": ["error", "always-multiline"], "comma-dangle": ["error", "always-multiline"],
"no-script-url": 0,
"import/extensions": 0, // we wouldn't be able to import jQuery without this line
// Temporary
"import/no-amd": 0, "import/no-amd": 0,
"react/no-multi-comp": 0, "react/no-multi-comp": 0,
"react/sort-comp": 0, "react/sort-comp": 0,
@ -28,10 +32,8 @@
"react/jsx-no-bind": 0, "react/jsx-no-bind": 0,
"react/no-array-index-key": 0, "react/no-array-index-key": 0,
"react/self-closing-comp": 0, "react/self-closing-comp": 0,
"react/jsx-tag-spacing": 0,
"react/jsx-closing-bracket-location": 0, "react/jsx-closing-bracket-location": 0,
"react/no-string-refs": 0, "react/no-string-refs": 0,
"react/jsx-curly-spacing": 0,
"react/no-did-mount-set-state": 0, "react/no-did-mount-set-state": 0,
"react/prefer-stateless-function": 0, "react/prefer-stateless-function": 0,
"jsx-a11y/label-has-for": 0, "jsx-a11y/label-has-for": 0,
@ -39,44 +41,8 @@
"jsx-a11y/alt-text": 0, "jsx-a11y/alt-text": 0,
"func-names": 0, "func-names": 0,
"object-shorthand": 0, "object-shorthand": 0,
"no-bitwise": 0,
"arrow-body-style": 0,
"prefer-template": 0,
"keyword-spacing": 0,
"default-case": 0,
"array-callback-return": 0,
"consistent-return": 0,
"import/extensions": 0,
"import/no-extraneous-dependencies": 0,
"camelcase": 0,
"template-curly-spacing": 0,
"eqeqeq": 0,
"no-lonely-if": 0,
"space-unary-ops": 0, "space-unary-ops": 0,
"block-scoped-var": 0,
"no-extra-bind": 0,
"no-multi-spaces": 0,
"class-methods-use-this": 0,
"key-spacing": 0,
"space-in-parens": 0,
"no-case-declarations": 0,
"array-bracket-spacing": 0,
"no-else-return": 0,
"max-len": 0,
"no-useless-concat": 0,
"no-sequences": 0,
"no-extra-boolean-cast": 0,
"dot-notation": 0,
"no-shadow": 0,
"one-var": 0,
"no-alert": 0,
"one-var-declaration-per-line": 0,
"no-script-url": 0,
"wrap-iife": 0,
"vars-on-top": 0,
"space-infix-ops": 0, "space-infix-ops": 0,
"no-irregular-whitespace": 0, "no-irregular-whitespace": 0
"padded-blocks": 0,
"no-underscore-dangle": 0
} }
} }

View File

@ -8,25 +8,9 @@
"ecmaVersion": 6 "ecmaVersion": 6
}, },
"rules": { "rules": {
"import/no-amd": 0, // Exceptions
"one-var": 0,
"no-whitespace-before-property": 0,
"global-require": 0,
"keyword-spacing": 0,
"no-bitwise": 0,
"no-spaced-func": 0,
"func-call-spacing": 0,
"max-len": 0,
"space-unary-ops": 0,
"no-unused-vars": 0,
"no-underscore-dangle": 0,
"no-shadow": 0,
"padded-blocks": 0,
"vars-on-top": 0,
"space-before-blocks": 0,
"object-curly-spacing": 0,
"one-var-declaration-per-line": 0,
"func-names": 0, "func-names": 0,
"space-before-function-paren": 0 // Temporary
"no-underscore-dangle": 0
} }
} }

4
.gitignore vendored
View File

@ -2,6 +2,7 @@
TODO TODO
composer.phar composer.phar
/vendor /vendor
/vendor_backup
tests/_output/* tests/_output/*
tests/_support/_generated/* tests/_support/_generated/*
node_modules node_modules
@ -20,4 +21,5 @@ assets/js/*.json
.vagrant .vagrant
lang lang
.mp_svn .mp_svn
/nbproject/ /nbproject/
tests/_data/acceptanceGenerated.sql

View File

@ -90,6 +90,7 @@ class RoboFile extends \Robo\Tasks {
$css_files = array( $css_files = array(
'assets/css/src/admin.styl', 'assets/css/src/admin.styl',
'assets/css/src/admin-global.styl',
'assets/css/src/newsletter_editor/newsletter_editor.styl', 'assets/css/src/newsletter_editor/newsletter_editor.styl',
'assets/css/src/public.styl', 'assets/css/src/public.styl',
'assets/css/src/rtl.styl', 'assets/css/src/rtl.styl',
@ -152,17 +153,30 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec('./tasks/transifex_init.sh'); return $this->_exec('./tasks/transifex_init.sh');
} }
function testUnit($opts=['file' => null, 'xml' => false]) { function testUnit($opts=['file' => null, 'xml' => false, 'multisite' => false]) {
$this->loadEnv(); $this->loadEnv();
$command = 'vendor/bin/codecept run unit -c codeception.unit.yml -f '.(($opts['file']) ? $opts['file'] : ''); $command = 'vendor/bin/codecept run unit -c codeception.unit.yml';
if($opts['multisite']) {
$command = 'MULTISITE=true ' . $command;
}
if($opts['file']) {
$command .= ' -f ' . $opts['file'];
}
if($opts['xml']) { if($opts['xml']) {
$command .= ' --xml'; $command .= ' --xml';
} }
return $this->_exec($command); return $this->_exec($command);
} }
function testMultisiteUnit($opts=['file' => null, 'xml' => false, 'multisite' => true]) {
return $this->testUnit($opts);
}
function testCoverage($opts=['file' => null, 'xml' => false]) { function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv(); $this->loadEnv();
$command = join(' ', array( $command = join(' ', array(
@ -197,6 +211,10 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec($command); return $this->_exec($command);
} }
function securityComposer() {
return $this->_exec('vendor/bin/security-checker security:check --format=simple');
}
function testDebug($opts=['file' => null, 'xml' => false]) { function testDebug($opts=['file' => null, 'xml' => false]) {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml'); $this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
@ -213,6 +231,14 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec('COMPOSE_HTTP_TIMEOUT=200 docker-compose run codeception --steps --debug -vvv'); return $this->_exec('COMPOSE_HTTP_TIMEOUT=200 docker-compose run codeception --steps --debug -vvv');
} }
function testAcceptanceMultisite() {
return $this->_exec('COMPOSE_HTTP_TIMEOUT=200 docker-compose run -e MULTISITE=1 codeception --steps --debug -vvv');
}
function deleteDocker() {
return $this->_exec('docker-compose down -v --remove-orphans --rmi all');
}
function testFailed() { function testFailed() {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml'); $this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
@ -288,15 +314,16 @@ class RoboFile extends \Robo\Tasks {
} }
function svnPublish($opts = ['force' => false]) { function svnPublish($opts = ['force' => false]) {
$this->loadWPFunctions(); $this->loadEnv();
$svn_dir = ".mp_svn"; $svn_dir = ".mp_svn";
$plugin_data = get_plugin_data('mailpoet.php', false, false); $plugin_version = $this->getPluginVersion('mailpoet.php');
$plugin_version = $plugin_data['Version']; $plugin_dist_name = 'mailpoet';
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
$plugin_dist_name = explode('-', $plugin_dist_name);
$plugin_dist_name = $plugin_dist_name[0];
$plugin_dist_file = $plugin_dist_name . '.zip'; $plugin_dist_file = $plugin_dist_name . '.zip';
if(!$plugin_version) {
throw new \Exception('Could not parse plugin version, check the plugin header');
}
$this->say('Publishing version: ' . $plugin_version); $this->say('Publishing version: ' . $plugin_version);
// Sanity checks // Sanity checks
@ -415,13 +442,9 @@ class RoboFile extends \Robo\Tasks {
$dotenv->load(); $dotenv->load();
} }
protected function loadWPFunctions() { protected function getPluginVersion($file) {
$this->loadEnv(); $data = file_get_contents($file);
define('ABSPATH', getenv('WP_TEST_PATH') . '/'); preg_match('/^[ \t*]*Version:(.*)$/mi', $data, $m);
define('WPINC', 'wp-includes'); return !empty($m[1]) ? trim($m[1]) : false;
require_once(ABSPATH . WPINC . '/functions.php');
require_once(ABSPATH . WPINC . '/formatting.php');
require_once(ABSPATH . WPINC . '/plugin.php');
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
} }
} }

View File

@ -0,0 +1,18 @@
@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;
#wpbody
padding-bottom: 20px;

View File

@ -27,3 +27,5 @@
@require 'pages_custom' @require 'pages_custom'
@require 'mp2migrator' @require 'mp2migrator'
@require '../../../node_modules/react-confirm-alert/src/react-confirm-alert.css'

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

@ -163,6 +163,7 @@ $master-column-tool-width = 24px
.mailpoet_delete_block_confirm .mailpoet_delete_block_confirm
color: $warning-text-color color: $warning-text-color
float: right
&:hover &:hover
color: $warning-text-color color: $warning-text-color
@ -170,6 +171,7 @@ $master-column-tool-width = 24px
.mailpoet_delete_block_cancel .mailpoet_delete_block_cancel
color: $warning-alternate-text-color color: $warning-alternate-text-color
float: right
&:hover &:hover
color: $warning-alternate-text-color color: $warning-alternate-text-color

View File

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

View File

@ -21,6 +21,8 @@ $block-text-line-height = $text-line-height
left: 0 left: 0
pointer-events: none pointer-events: none
border: 1px solid $transparent-color border: 1px solid $transparent-color
-webkit-transition: 0.3s;
transition: 0.3s;
&:hover > .mailpoet_block_highlight &:hover > .mailpoet_block_highlight
border: 1px dashed $block-hover-highlight-color border: 1px dashed $block-hover-highlight-color

View File

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

View File

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

View File

@ -33,3 +33,6 @@ Custom styles for MailPoet pages.
p.top-space-triple p.top-space-triple
margin-top: 3em margin-top: 3em
p.mailpoet-top-text
margin-right: 0

View File

@ -62,6 +62,15 @@
margin-bottom: 2em margin-bottom: 2em
margin-top: 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 .mailpoet_success_item::before
content ' ' content ' '

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.

View File

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

View File

@ -2,17 +2,16 @@ function requestFailed(errorMessage, xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {
return xhr.responseJSON; return xhr.responseJSON;
} }
var message = errorMessage.replace('%d', xhr.status);
return { return {
errors: [ errors: [
{ {
message: message message: errorMessage.replace('%d', xhr.status)
} }
] ]
}; };
} }
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(mp, jQuery, _) { define('ajax', ['mailpoet', 'jquery', 'underscore'], function ajax(mp, jQuery, _) {
var MailPoet = mp; var MailPoet = mp;
MailPoet.Ajax = { MailPoet.Ajax = {
@ -26,24 +25,24 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(mp, jQuery, _) {
token: null, token: null,
data: {} data: {}
}, },
post: function(options) { post: function post(options) {
return this.request('post', options); return this.request('post', options);
}, },
init: function(options) { init: function init(options) {
// merge options // merge options
this.options = jQuery.extend({}, this.defaults, options); this.options = jQuery.extend({}, this.defaults, options);
// set default url // set default url
if(this.options.url === null) { if (this.options.url === null) {
this.options.url = window.ajaxurl; this.options.url = window.ajaxurl;
} }
// set default token // set default token
if(this.options.token === null) { if (this.options.token === null) {
this.options.token = window.mailpoet_token; this.options.token = window.mailpoet_token;
} }
}, },
getParams: function() { getParams: function getParams() {
return { return {
action: 'mailpoet', action: 'mailpoet',
api_version: this.options.api_version, api_version: this.options.api_version,
@ -53,27 +52,29 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(mp, jQuery, _) {
data: this.options.data || {} data: this.options.data || {}
}; };
}, },
request: function(method, options) { request: function request(method, options) {
// set options var params;
var deferred;
// set options
this.init(options); this.init(options);
// set request params // set request params
var params = this.getParams(); params = this.getParams();
// remove null values from the data object // remove null values from the data object
if (_.isObject(params.data)) { if (_.isObject(params.data)) {
params.data = _.pick(params.data, function(value) { params.data = _.pick(params.data, function isNotNull(value) {
return (value !== null); return (value !== null);
}); });
} }
// ajax request // ajax request
var deferred = jQuery.post( deferred = jQuery.post(
this.options.url, this.options.url,
params, params,
null, null,
'json' 'json'
).then(function(data) { ).then(function resultHandler(data) {
return data; return data;
}, _.partial(requestFailed, MailPoet.I18n.t('ajaxFailedErrorMessage'))); }, _.partial(requestFailed, MailPoet.I18n.t('ajaxFailedErrorMessage')));

View File

@ -17,7 +17,7 @@
*/ */
var eventsCache = []; var eventsCache = [];
function track(name, data){ function track(name, data) {
if (typeof window.mixpanel.track !== 'function') { if (typeof window.mixpanel.track !== 'function') {
window.mixpanel.init(window.mixpanelTrackingId); window.mixpanel.init(window.mixpanelTrackingId);
} }
@ -31,12 +31,12 @@ function exportMixpanel(mp) {
if (window.mailpoet_analytics_enabled) { if (window.mailpoet_analytics_enabled) {
MailPoet.trackEvent = track; MailPoet.trackEvent = track;
} else { } else {
MailPoet.trackEvent = function () {}; MailPoet.trackEvent = function emptyFunction() {};
} }
} }
function trackCachedEvents() { function trackCachedEvents() {
eventsCache.map(function (event) { eventsCache.map(function trackIfEnabled(event) {
if (window.mailpoet_analytics_enabled || event.forced) { if (window.mailpoet_analytics_enabled || event.forced) {
window.mixpanel.track(event.name, event.data); window.mixpanel.track(event.name, event.data);
} }
@ -53,7 +53,7 @@ function cacheEvent(forced, name, data) {
define( define(
['mailpoet', 'underscore'], ['mailpoet', 'underscore'],
function(mp, _) { function analyticsEvent(mp, _) {
var MailPoet = mp; var MailPoet = mp;
function initializeMixpanelWhenLoaded() { function initializeMixpanelWhenLoaded() {

View File

@ -3,7 +3,7 @@ define('date',
'mailpoet', 'mailpoet',
'jquery', 'jquery',
'moment' 'moment'
], function( ], function ( // eslint-disable-line func-names
mp, mp,
jQuery, jQuery,
Moment Moment
@ -19,7 +19,7 @@ define('date',
offset: 0, offset: 0,
format: 'F, d Y H:i:s' format: 'F, d Y H:i:s'
}, },
init: function (opts) { init: function init(opts) {
var options = opts || {}; var options = opts || {};
// set UTC offset // set UTC offset
@ -41,37 +41,43 @@ define('date',
return this; return this;
}, },
format: function(date, opts) { format: function format(date, opts) {
var options = opts || {}; var options = opts || {};
var momentDate;
this.init(options); this.init(options);
var momentDate = Moment(date, this.convertFormat(options.parseFormat)); momentDate = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) momentDate = momentDate.utc(); if (options.offset === 0) momentDate = momentDate.utc();
return momentDate.format(this.convertFormat(this.options.format)); return momentDate.format(this.convertFormat(this.options.format));
}, },
toDate: function(date, opts) { toDate: function toDate(date, opts) {
var options = opts || {}; var options = opts || {};
this.init(options); this.init(options);
return Moment(date, this.convertFormat(options.parseFormat)).toDate(); return Moment(date, this.convertFormat(options.parseFormat)).toDate();
}, },
short: function(date) { short: function short(date) {
return this.format(date, { return this.format(date, {
format: 'F, j Y' format: 'F, j Y'
}); });
}, },
full: function(date) { full: function full(date) {
return this.format(date, { return this.format(date, {
format: 'F, j Y H:i:s' format: 'F, j Y H:i:s'
}); });
}, },
time: function(date) { time: function time(date) {
return this.format(date, { return this.format(date, {
format: 'H:i:s' format: 'H:i:s'
}); });
}, },
convertFormat: function(format) { convertFormat: function convertFormat(format) {
var format_mappings = { var replacements;
var convertedFormat;
var escapeToken;
var index;
var token;
var formatMappings = {
date: { date: {
d: 'DD', d: 'DD',
D: 'ddd', D: 'ddd',
@ -140,15 +146,14 @@ define('date',
if (!format || format.length <= 0) return format; if (!format || format.length <= 0) return format;
var replacements = format_mappings['date']; replacements = formatMappings.date;
convertedFormat = [];
escapeToken = false;
var convertedFormat = []; for (index = 0, token = ''; format.charAt(index); index += 1) {
var escapeToken = false;
for(var index = 0, token = ''; format.charAt(index); index += 1){
token = format.charAt(index); token = format.charAt(index);
if (escapeToken === true) { if (escapeToken === true) {
convertedFormat.push('['+token+']'); convertedFormat.push('[' + token + ']');
escapeToken = false; escapeToken = false;
} else { } else {
if (token === '\\') { if (token === '\\') {
@ -158,7 +163,7 @@ define('date',
} else if (replacements[token] !== undefined) { } else if (replacements[token] !== undefined) {
convertedFormat.push(replacements[token]); convertedFormat.push(replacements[token]);
} else { } else {
convertedFormat.push('['+token+']'); convertedFormat.push('[' + token + ']');
} }
} }
} }

View File

@ -16,25 +16,23 @@ define([
// isChecked will be true only if the value is "1" // isChecked will be true only if the value is "1"
// it will be false in case value is "0" or empty // it will be false in case value is "0" or empty
const isChecked = !!(~~(this.props.item[this.props.field.name])); const isChecked = !!(Number(this.props.item[this.props.field.name]));
const options = Object.keys(this.props.field.values).map( const options = Object.keys(this.props.field.values).map(
(value, index) => { (value, index) => (
return ( <p key={`checkbox-${index}`}>
<p key={ 'checkbox-' + index }> <label>
<label> <input
<input ref="checkbox"
ref="checkbox" type="checkbox"
type="checkbox" value="1"
value="1" checked={isChecked}
checked={ isChecked } onChange={this.onValueChange}
onChange={ this.onValueChange } name={this.props.field.name}
name={ this.props.field.name }
/> />
{ this.props.field.values[value] } { this.props.field.values[value] }
</label> </label>
</p> </p>
); )
}
); );
return ( return (

View File

@ -12,7 +12,7 @@ define([
if (this.props.placeholder !== undefined) { if (this.props.placeholder !== undefined) {
years.push(( years.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option> <option value="" key={0}>{ this.props.placeholder }</option>
)); ));
} }
@ -20,16 +20,16 @@ define([
for (let i = currentYear; i >= currentYear - yearsRange; i -= 1) { for (let i = currentYear; i >= currentYear - yearsRange; i -= 1) {
years.push(( years.push((
<option <option
key={ i } key={i}
value={ i } value={i}
>{ i }</option> >{ i }</option>
)); ));
} }
return ( return (
<select <select
name={ `${this.props.name}[year]` } name={`${this.props.name}[year]`}
value={ this.props.year } value={this.props.year}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
> >
{ years } { years }
</select> </select>
@ -43,23 +43,23 @@ define([
if (this.props.placeholder !== undefined) { if (this.props.placeholder !== undefined) {
months.push(( months.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option> <option value="" key={0}>{ this.props.placeholder }</option>
)); ));
} }
for (let i = 1; i <= 12; i += 1) { for (let i = 1; i <= 12; i += 1) {
months.push(( months.push((
<option <option
key={ i } key={i}
value={ i } value={i}
>{ this.props.monthNames[i - 1] }</option> >{ this.props.monthNames[i - 1] }</option>
)); ));
} }
return ( return (
<select <select
name={ `${this.props.name}[month]` } name={`${this.props.name}[month]`}
value={ this.props.month } value={this.props.month}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
> >
{ months } { months }
</select> </select>
@ -73,24 +73,24 @@ define([
if (this.props.placeholder !== undefined) { if (this.props.placeholder !== undefined) {
days.push(( days.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option> <option value="" key={0}>{ this.props.placeholder }</option>
)); ));
} }
for (let i = 1; i <= 31; i += 1) { for (let i = 1; i <= 31; i += 1) {
days.push(( days.push((
<option <option
key={ i } key={i}
value={ i } value={i}
>{ i }</option> >{ i }</option>
)); ));
} }
return ( return (
<select <select
name={ `${this.props.name}[day]` } name={`${this.props.name}[day]`}
value={ this.props.day } value={this.props.day}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
> >
{ days } { days }
</select> </select>
@ -123,7 +123,7 @@ define([
? this.props.item[this.props.field.name].trim() ? this.props.item[this.props.field.name].trim()
: ''; : '';
if(value === '') { if (value === '') {
return; return;
} }
@ -140,7 +140,7 @@ define([
let value; let value;
switch(dateType) { switch (dateType) {
case 'year_month_day': case 'year_month_day':
value = { value = {
year: this.state.year, year: this.state.year,
@ -167,6 +167,11 @@ define([
year: this.state.year, year: this.state.year,
}; };
break; break;
default:
value = {
value: 'invalid type',
};
break;
} }
return value; return value;
@ -181,7 +186,7 @@ define([
field = matches[1]; field = matches[1];
property = matches[2]; property = matches[2];
const value = ~~(e.target.value); const value = Number(e.target.value);
this.setState({ this.setState({
[`${property}`]: value, [`${property}`]: value,
@ -202,37 +207,40 @@ define([
const dateSelects = dateFormats[dateType][0].split('/'); const dateSelects = dateFormats[dateType][0].split('/');
const fields = dateSelects.map((type) => { const fields = dateSelects.map((type) => {
switch(type) { switch (type) {
case 'YYYY': case 'YYYY':
return (<FormFieldDateYear return (<FormFieldDateYear
onValueChange={ this.onValueChange.bind(this) } onValueChange={this.onValueChange.bind(this)}
ref={ 'year' } ref={'year'}
key={ 'year' } key={'year'}
name={ this.props.field.name } name={this.props.field.name}
year={ this.state.year } year={this.state.year}
placeholder={ this.props.field.year_placeholder } placeholder={this.props.field.year_placeholder}
/>); />);
case 'MM': case 'MM':
return (<FormFieldDateMonth return (<FormFieldDateMonth
onValueChange={ this.onValueChange.bind(this) } onValueChange={this.onValueChange.bind(this)}
ref={ 'month' } ref={'month'}
key={ 'month' } key={'month'}
name={ this.props.field.name } name={this.props.field.name}
month={ this.state.month } month={this.state.month}
monthNames={ monthNames } monthNames={monthNames}
placeholder={ this.props.field.month_placeholder } placeholder={this.props.field.month_placeholder}
/>); />);
case 'DD': case 'DD':
return (<FormFieldDateDay return (<FormFieldDateDay
onValueChange={ this.onValueChange.bind(this) } onValueChange={this.onValueChange.bind(this)}
ref={ 'day' } ref={'day'}
key={ 'day' } key={'day'}
name={ this.props.field.name } name={this.props.field.name}
day={ this.state.day } day={this.state.day}
placeholder={ this.props.field.day_placeholder } placeholder={this.props.field.day_placeholder}
/>); />);
default:
return <div>Invalid date type</div>;
} }
}); });

View File

@ -23,7 +23,7 @@ define([
const FormField = React.createClass({ const FormField = React.createClass({
renderField: function (data, inline = false) { renderField: function (data, inline = false) {
let description = false; let description = false;
if(data.field.description) { if (data.field.description) {
description = ( description = (
<p className="description">{ data.field.description }</p> <p className="description">{ data.field.description }</p>
); );
@ -32,11 +32,11 @@ define([
let field = false; let field = false;
let dataField = data.field; let dataField = data.field;
if(data.field['field'] !== undefined) { if (data.field.field !== undefined) {
dataField = jQuery.merge(dataField, data.field.field); dataField = jQuery.merge(dataField, data.field.field);
} }
switch(dataField.type) { switch (dataField.type) {
case 'text': case 'text':
field = (<FormFieldText {...data} />); field = (<FormFieldText {...data} />);
break; break;
@ -68,52 +68,53 @@ define([
case 'reactComponent': case 'reactComponent':
field = (<data.field.component {...data} />); field = (<data.field.component {...data} />);
break; break;
default:
field = 'invalid';
break;
} }
if(inline === true) { if (inline === true) {
return ( return (
<span key={ 'field-' + (data.index || 0) }> <span key={`field-${data.index || 0}`}>
{ field } { field }
{ description } { description }
</span> </span>
); );
} else {
return (
<div key={ 'field-' + (data.index || 0) }>
{ field }
{ description }
</div>
);
} }
return (
<div key={`field-${data.index || 0}`}>
{ field }
{ description }
</div>
);
}, },
render: function () { render: function () {
let field = false; let field = false;
if(this.props.field['fields'] !== undefined) { if (this.props.field.fields !== undefined) {
field = this.props.field.fields.map((subfield, index) => { field = this.props.field.fields.map((subfield, index) => this.renderField({
return this.renderField({ index: index,
index: index, field: subfield,
field: subfield, item: this.props.item,
item: this.props.item, onValueChange: this.props.onValueChange || false,
onValueChange: this.props.onValueChange || false, }));
});
});
} else { } else {
field = this.renderField(this.props); field = this.renderField(this.props);
} }
let tip = false; let tip = false;
if(this.props.field.tip) { if (this.props.field.tip) {
tip = ( tip = (
<p className="description">{ this.props.field.tip }</p> <p className="description">{ this.props.field.tip }</p>
); );
} }
return ( return (
<tr> <tr className={`form-field-row-${this.props.field.name}`}>
<th scope="row"> <th scope="row">
<label <label
htmlFor={ 'field_'+this.props.field.name } htmlFor={`field_${this.props.field.name}`}
> >
{ this.props.field.label } { this.props.field.label }
{ tip } { tip }

View File

@ -10,23 +10,21 @@ define([
return false; return false;
} }
const selected_value = this.props.item[this.props.field.name]; const selectedValue = this.props.item[this.props.field.name];
const options = Object.keys(this.props.field.values).map( const options = Object.keys(this.props.field.values).map(
(value, index) => { (value, index) => (
return ( <p key={`radio-${index}`}>
<p key={ 'radio-' + index }> <label>
<label> <input
<input type="radio"
type="radio" checked={selectedValue === value}
checked={ selected_value === value } value={value}
value={ value } onChange={this.props.onValueChange}
onChange={ this.props.onValueChange } name={this.props.field.name} />
name={ this.props.field.name } /> { this.props.field.values[value] }
{ this.props.field.values[value] } </label>
</label> </p>
</p> )
);
}
); );
return ( return (

View File

@ -17,7 +17,7 @@ const FormFieldSelect = React.createClass({
); );
} }
if (this.props.field['filter'] !== undefined) { if (this.props.field.filter !== undefined) {
filter = this.props.field.filter; filter = this.props.field.filter;
} }
@ -41,29 +41,27 @@ const FormFieldSelect = React.createClass({
keys = Object.keys(this.props.field.values); keys = Object.keys(this.props.field.values);
} }
const options = keys.map( const options = keys
(value, index) => { .filter((value) => {
if (filter === false) return true;
if (filter !== false && filter(this.props.item, value) === false) { return filter(this.props.item, value);
return; })
} .map(
(value, index) => (
return (
<option <option
key={ 'option-' + index } key={`option-${index}`}
value={ value }> value={value}>
{ this.props.field.values[value] } { this.props.field.values[value] }
</option> </option>
); )
} );
);
return ( return (
<select <select
name={ this.props.field.name } name={this.props.field.name}
id={ 'field_'+this.props.field.name } id={`field_${this.props.field.name}`}
value={ this.props.item[this.props.field.name] } value={this.props.item[this.props.field.name]}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
{...this.props.field.validation} {...this.props.field.validation}
> >
{placeholder} {placeholder}

View File

@ -26,47 +26,44 @@ define([
return (this.state.select2 === true); return (this.state.select2 === true);
}, },
componentDidMount: function () { componentDidMount: function () {
if(this.allowMultipleValues()) { if (this.allowMultipleValues() || this.props.field.forceSelect2) {
this.setupSelect2(); this.setupSelect2();
} }
}, },
componentDidUpdate: function (prevProps) { componentDidUpdate: function (prevProps) {
if( if (
(this.props.item !== undefined && prevProps.item !== undefined) (this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id) && (this.props.item.id !== prevProps.item.id)
) { ) {
jQuery('#'+this.refs.select.id) jQuery(`#${this.refs.select.id}`)
.val(this.getSelectedValues()) .val(this.getSelectedValues())
.trigger('change'); .trigger('change');
} }
}, },
componentWillUnmount: function () { componentWillUnmount: function () {
if(this.allowMultipleValues()) { if (this.allowMultipleValues() || this.props.field.forceSelect2) {
this.destroySelect2(); this.destroySelect2();
} }
}, },
destroySelect2: function () { destroySelect2: function () {
if(this.isSelect2Initialized()) { if (this.isSelect2Initialized()) {
jQuery('#'+this.refs.select.id).select2('destroy'); jQuery(`#${this.refs.select.id}`).select2('destroy');
} }
}, },
setupSelect2: function () { setupSelect2: function () {
if(this.isSelect2Initialized()) { if (this.isSelect2Initialized()) {
return; return;
} }
const select2 = jQuery('#'+this.refs.select.id).select2({ const select2 = jQuery(`#${this.refs.select.id}`).select2({
width: (this.props.width || ''), width: (this.props.width || ''),
templateResult: function (item) { templateResult: function (item) {
if(item.element && item.element.selected) { if (item.element && item.element.selected) {
return null; return null;
} else { } else if (item.title) {
if(item.title) { return item.title;
return item.title;
} else {
return item.text;
}
} }
return item.text;
}, },
}); });
@ -75,7 +72,7 @@ define([
hasRemoved = true; hasRemoved = true;
}); });
select2.on('select2:opening', (e) => { select2.on('select2:opening', (e) => {
if(hasRemoved === true) { if (hasRemoved === true) {
hasRemoved = false; hasRemoved = false;
e.preventDefault(); e.preventDefault();
} }
@ -86,14 +83,12 @@ define([
this.setState({ select2: true }); this.setState({ select2: true });
}, },
getSelectedValues: function () { getSelectedValues: function () {
if(this.props.field['selected'] !== undefined) { if (this.props.field.selected !== undefined) {
return this.props.field['selected'](this.props.item); return this.props.field.selected(this.props.item);
} else if(this.props.item !== undefined && this.props.field.name !== undefined) { } else if (this.props.item !== undefined && this.props.field.name !== undefined) {
if (this.allowMultipleValues()) { if (this.allowMultipleValues()) {
if (Array.isArray(this.props.item[this.props.field.name])) { if (Array.isArray(this.props.item[this.props.field.name])) {
return this.props.item[this.props.field.name].map((item) => { return this.props.item[this.props.field.name].map(item => item.id);
return item.id;
});
} }
} else { } else {
return this.props.item[this.props.field.name]; return this.props.item[this.props.field.name];
@ -102,11 +97,15 @@ define([
return null; return null;
}, },
loadCachedItems: function () { loadCachedItems: function () {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') { let items;
let items = window['mailpoet_'+this.props.field.endpoint]; if (typeof (window[`mailpoet_${this.props.field.endpoint}`]) !== 'undefined') {
items = window[`mailpoet_${this.props.field.endpoint}`];
} else if (this.props.field.values !== undefined) {
items = this.props.field.values;
}
if (Array.isArray(items)) {
if(this.props.field['filter'] !== undefined) { if (this.props.field.filter !== undefined) {
items = items.filter(this.props.field.filter); items = items.filter(this.props.field.filter);
} }
@ -117,9 +116,9 @@ define([
}, },
handleChange: function (e) { handleChange: function (e) {
let value; let value;
if(this.props.onValueChange !== undefined) { if (this.props.onValueChange !== undefined) {
if(this.props.field.multiple) { if (this.props.field.multiple) {
value = jQuery('#'+this.refs.select.id).val(); value = jQuery(`#${this.refs.select.id}`).val();
} else { } else {
value = e.target.value; value = e.target.value;
} }
@ -133,19 +132,19 @@ define([
} }
}, },
getLabel: function (item) { getLabel: function (item) {
if(this.props.field['getLabel'] !== undefined) { if (this.props.field.getLabel !== undefined) {
return this.props.field.getLabel(item, this.props.item); return this.props.field.getLabel(item, this.props.item);
} }
return item.name; return item.name;
}, },
getSearchLabel: function (item) { getSearchLabel: function (item) {
if(this.props.field['getSearchLabel'] !== undefined) { if (this.props.field.getSearchLabel !== undefined) {
return this.props.field.getSearchLabel(item, this.props.item); return this.props.field.getSearchLabel(item, this.props.item);
} }
return null; return null;
}, },
getValue: function (item) { getValue: function (item) {
if(this.props.field['getValue'] !== undefined) { if (this.props.field.getValue !== undefined) {
return this.props.field.getValue(item, this.props.item); return this.props.field.getValue(item, this.props.item);
} }
return item.id; return item.id;
@ -154,11 +153,18 @@ define([
// this function may be used to transform the placeholder value into // this function may be used to transform the placeholder value into
// desired value. // desired value.
transformChangedValue: function (value) { transformChangedValue: function (value) {
if(typeof this.props.field['transformChangedValue'] === 'function') { if (typeof this.props.field.transformChangedValue === 'function') {
return this.props.field.transformChangedValue.call(this, value); return this.props.field.transformChangedValue.call(this, value);
} else {
return value;
} }
return value;
},
insertEmptyOption: function () {
// https://select2.org/placeholders
// For single selects only, in order for the placeholder value to appear,
// we must have a blank <option> as the first option in the <select> control.
if (this.allowMultipleValues()) return undefined;
if (this.props.field.placeholder) return (<option />);
return undefined;
}, },
render: function () { render: function () {
const options = this.state.items.map((item, index) => { const options = this.state.items.map((item, index) => {
@ -168,9 +174,9 @@ define([
return ( return (
<option <option
key={ 'option-'+index } key={`option-${index}`}
value={ value } value={value}
title={ searchLabel } title={searchLabel}
> >
{ label } { label }
</option> </option>
@ -179,14 +185,17 @@ define([
return ( return (
<select <select
id={ this.props.field.id || this.props.field.name } id={this.props.field.id || this.props.field.name}
ref="select" ref="select"
disabled={this.props.field.disabled} disabled={this.props.field.disabled}
data-placeholder={ this.props.field.placeholder } data-placeholder={this.props.field.placeholder}
multiple={ this.props.field.multiple } multiple={this.props.field.multiple}
defaultValue={ this.getSelectedValues() } defaultValue={this.getSelectedValues()}
{...this.props.field.validation} {...this.props.field.validation}
>{ options }</select> >
{ this.insertEmptyOption() }
{ options }
</select>
); );
}, },
}); });

View File

@ -11,21 +11,21 @@ const FormFieldText = React.createClass({
<input <input
type="text" type="text"
disabled={ disabled={
(this.props.field['disabled'] !== undefined) (this.props.field.disabled !== undefined)
? this.props.field.disabled(this.props.item) ? this.props.field.disabled(this.props.item)
: false : false
} }
className={ (this.props.field.size) ? '' : 'regular-text' } className={(this.props.field.size) ? '' : 'regular-text'}
size={ size={
(this.props.field.size !== 'auto' && this.props.field.size > 0) (this.props.field.size !== 'auto' && this.props.field.size > 0)
? this.props.field.size ? this.props.field.size
: false : false
} }
name={ this.props.field.name } name={this.props.field.name}
id={ 'field_'+this.props.field.name } id={`field_${this.props.field.name}`}
value={ value } value={value}
placeholder={ this.props.field.placeholder } placeholder={this.props.field.placeholder}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
{...this.props.field.validation} {...this.props.field.validation}
/> />
); );

View File

@ -10,12 +10,12 @@ define([
<textarea <textarea
type="text" type="text"
className="regular-text" className="regular-text"
name={ this.props.field.name } name={this.props.field.name}
id={ 'field_'+this.props.field.name } id={`field_${this.props.field.name}`}
value={ this.props.item[this.props.field.name] } value={this.props.item[this.props.field.name]}
placeholder={ this.props.field.placeholder } placeholder={this.props.field.placeholder}
defaultValue={ this.props.field.defaultValue } defaultValue={this.props.field.defaultValue}
onChange={ this.props.onValueChange } onChange={this.props.onValueChange}
{...this.props.field.validation} {...this.props.field.validation}
/> />
); );

View File

@ -15,7 +15,6 @@ define(
FormField, FormField,
jQuery jQuery
) => { ) => {
const Form = React.createClass({ const Form = React.createClass({
contextTypes: { contextTypes: {
router: React.PropTypes.object.isRequired, router: React.PropTypes.object.isRequired,
@ -39,18 +38,18 @@ define(
return this.props.errors ? this.props.errors : this.state.errors; return this.props.errors ? this.props.errors : this.state.errors;
}, },
componentDidMount: function () { componentDidMount: function () {
if(this.isMounted()) { if (this.isMounted()) {
if(this.props.params.id !== undefined) { if (this.props.params.id !== undefined) {
this.loadItem(this.props.params.id); this.loadItem(this.props.params.id);
} else { } else {
this.setState({ this.setState({
item: jQuery('.mailpoet_form').serializeObject(), item: jQuery('.mailpoet_form').mailpoetSerializeObject(),
}); });
} }
} }
}, },
componentWillReceiveProps: function (props) { componentWillReceiveProps: function (props) {
if(props.params.id === undefined) { if (props.params.id === undefined) {
this.setState({ this.setState({
loading: false, loading: false,
item: {}, item: {},
@ -58,8 +57,6 @@ define(
if (props.item === undefined) { if (props.item === undefined) {
this.refs.form.reset(); this.refs.form.reset();
} }
} else {
this.loadItem(props.params.id);
} }
}, },
loadItem: function (id) { loadItem: function (id) {
@ -77,6 +74,9 @@ define(
loading: false, loading: false,
item: response.data, item: response.data,
}); });
if (typeof this.props.onItemLoad === 'function') {
this.props.onItemLoad(response.data);
}
}).fail(() => { }).fail(() => {
this.setState({ this.setState({
loading: false, loading: false,
@ -90,8 +90,8 @@ define(
e.preventDefault(); e.preventDefault();
// handle validation // handle validation
if(this.props.isValid !== undefined) { if (this.props.isValid !== undefined) {
if(this.props.isValid() === false) { if (this.props.isValid() === false) {
return; return;
} }
} }
@ -100,9 +100,9 @@ define(
// only get values from displayed fields // only get values from displayed fields
const item = {}; const item = {};
this.props.fields.map((field) => { this.props.fields.forEach((field) => {
if(field['fields'] !== undefined) { if (field.fields !== undefined) {
field.fields.map((subfield) => { field.fields.forEach((subfield) => {
item[subfield.name] = this.state.item[subfield.name]; item[subfield.name] = this.state.item[subfield.name];
}); });
} else { } else {
@ -110,7 +110,7 @@ define(
} }
}); });
// set id if specified // set id if specified
if(this.props.params.id !== undefined) { if (this.props.params.id !== undefined) {
item.id = this.props.params.id; item.id = this.props.params.id;
} }
@ -122,19 +122,19 @@ define(
}).always(() => { }).always(() => {
this.setState({ loading: false }); this.setState({ loading: false });
}).done(() => { }).done(() => {
if(this.props.onSuccess !== undefined) { if (this.props.onSuccess !== undefined) {
this.props.onSuccess(); this.props.onSuccess();
} else { } else {
this.context.router.push('/'); this.context.router.push('/');
} }
if(this.props.params.id !== undefined) { if (this.props.params.id !== undefined) {
this.props.messages.onUpdate(); this.props.messages.onUpdate();
} else { } else {
this.props.messages.onCreate(); this.props.messages.onCreate();
} }
}).fail((response) => { }).fail((response) => {
if(response.errors.length > 0) { if (response.errors.length > 0) {
this.setState({ errors: response.errors }); this.setState({ errors: response.errors });
} }
}); });
@ -142,28 +142,25 @@ define(
handleValueChange: function (e) { handleValueChange: function (e) {
if (this.props.onChange) { if (this.props.onChange) {
return this.props.onChange(e); return this.props.onChange(e);
} else {
const item = this.state.item;
const field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item,
});
return true;
} }
const item = this.state.item;
const field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item,
});
return true;
}, },
render: function () { render: function () {
let errors; let errors;
if(this.getErrors() !== undefined) { if (this.getErrors() !== undefined) {
errors = this.getErrors().map((error, index) => { errors = this.getErrors().map((error, index) => (
return ( <p key={`error-${index}`} className="mailpoet_error">
<p key={ 'error-'+index } className="mailpoet_error"> { error.message }
{ error.message } </p>
</p> ));
);
});
} }
const formClasses = classNames( const formClasses = classNames(
@ -194,15 +191,15 @@ define(
return ( return (
<FormField <FormField
field={ field } field={field}
item={ this.getValues() } item={this.getValues()}
onValueChange={ onValueChange } onValueChange={onValueChange}
key={ 'field-'+i } /> key={`field-${i}`} />
); );
}); });
let actions = false; let actions = false;
if(this.props.children) { if (this.props.children) {
actions = this.props.children; actions = this.props.children;
} else { } else {
actions = ( actions = (
@ -218,9 +215,9 @@ define(
<div> <div>
{ beforeFormContent } { beforeFormContent }
<form <form
id={ this.props.id } id={this.props.id}
ref="form" ref="form"
className={ formClasses } className={formClasses}
onSubmit={ onSubmit={
(this.props.onSubmit !== undefined) (this.props.onSubmit !== undefined)
? this.props.onSubmit ? this.props.onSubmit

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,12 @@ const App = React.createClass({
const container = document.getElementById('forms_container'); const container = document.getElementById('forms_container');
if(container) { if (container) {
ReactDOM.render(( ReactDOM.render((
<Router history={ history }> <Router history={history}>
<Route path="/" component={ App }> <Route path="/" component={App}>
<IndexRoute component={ FormList } /> <IndexRoute component={FormList} />
<Route path="*" component={ FormList } /> <Route path="*" component={FormList} />
</Route> </Route>
</Router> </Router>
), container); ), container);

View File

@ -27,7 +27,7 @@ const columns = [
const messages = { const messages = {
onTrash: (response) => { onTrash: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -42,7 +42,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onDelete: (response) => { onDelete: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -57,7 +57,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onRestore: (response) => { onRestore: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -73,7 +73,7 @@ const messages = {
}, },
}; };
const bulk_actions = [ const bulkActions = [
{ {
name: 'trash', name: 'trash',
label: MailPoet.I18n.t('moveToTrash'), label: MailPoet.I18n.t('moveToTrash'),
@ -81,13 +81,13 @@ const bulk_actions = [
}, },
]; ];
const item_actions = [ const itemActions = [
{ {
name: 'edit', name: 'edit',
label: MailPoet.I18n.t('edit'), label: MailPoet.I18n.t('edit'),
link: function (item) { link: function (item) {
return ( return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a> <a href={`admin.php?page=mailpoet-form-editor&id=${item.id}`}>{MailPoet.I18n.t('edit')}</a>
); );
}, },
}, },
@ -110,7 +110,7 @@ const item_actions = [
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
@ -133,36 +133,35 @@ const FormList = React.createClass({
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
}); });
}, },
renderItem(form, actions) { renderItem(form, actions) {
const row_classes = classNames( const rowClasses = classNames(
'manage-column', 'manage-column',
'column-primary', 'column-primary',
'has-row-actions' 'has-row-actions'
); );
let segments = window.mailpoet_segments.filter((segment) => { let segments = window.mailpoet_segments
return (jQuery.inArray(segment.id, form.segments) !== -1); .filter(segment => (jQuery.inArray(segment.id, form.segments) !== -1))
}).map((segment) => { .map(segment => segment.name)
return segment.name; .join(', ');
}).join(', ');
if (form.settings.segments_selected_by === 'user') { if (form.settings.segments_selected_by === 'user') {
segments = MailPoet.I18n.t('userChoice') + ' ' + segments; segments = `${MailPoet.I18n.t('userChoice')} ${segments}`;
} }
return ( return (
<div> <div>
<td className={ row_classes }> <td className={rowClasses}>
<strong> <strong>
<a <a
className="row-title" className="row-title"
href={ `admin.php?page=mailpoet-form-editor&id=${form.id}` } href={`admin.php?page=mailpoet-form-editor&id=${form.id}`}
>{ form.name }</a> >{ form.name }</a>
</strong> </strong>
{ actions } { actions }
@ -186,21 +185,21 @@ const FormList = React.createClass({
{MailPoet.I18n.t('pageTitle')} <a {MailPoet.I18n.t('pageTitle')} <a
className="page-title-action" className="page-title-action"
href="javascript:;" href="javascript:;"
onClick={ this.createForm } onClick={this.createForm}
>{MailPoet.I18n.t('new')}</a> >{MailPoet.I18n.t('new')}</a>
</h1> </h1>
<Listing <Listing
limit={ window.mailpoet_listing_per_page } limit={window.mailpoet_listing_per_page}
location={ this.props.location } location={this.props.location}
params={ this.props.params } params={this.props.params}
messages={ messages } messages={messages}
search={ false } search={false}
endpoint="forms" endpoint="forms"
onRenderItem={ this.renderItem } onRenderItem={this.renderItem}
columns={ columns } columns={columns}
bulk_actions={ bulk_actions } bulk_actions={bulkActions}
item_actions={ item_actions } item_actions={itemActions}
/> />
</div> </div>
); );

View File

@ -1,37 +1,38 @@
define('handlebars_helpers', ['handlebars'], function(Handlebars) { /* eslint-disable func-names */
define('handlebars_helpers', ['handlebars'], function (Handlebars) {
// Handlebars helpers // Handlebars helpers
Handlebars.registerHelper('concat', function() { Handlebars.registerHelper('concat', function () {
var size = (arguments.length - 1), var size = (arguments.length - 1);
output = ''; var output = '';
for(var i = 0; i < size; i++) { var i;
for (i = 0; i < size; i++) {
output += arguments[i]; output += arguments[i];
} }
return output; return output;
}); });
Handlebars.registerHelper('number_format', function(value, block) { Handlebars.registerHelper('number_format', function (value) {
return Number(value).toLocaleString(); return Number(value).toLocaleString();
}); });
Handlebars.registerHelper('date_format', function(timestamp, block) { Handlebars.registerHelper('date_format', function (timestamp, block) {
if(window.moment) { var f;
if(timestamp === undefined || isNaN(timestamp) || timestamp <= 0) { if (window.moment) {
return; if (timestamp === undefined || isNaN(timestamp) || timestamp <= 0) {
return undefined;
} }
// set date format // set date format
var f = block.hash.format || 'MMM Do, YYYY'; f = block.hash.format || 'MMM Do, YYYY';
// check if we passed a timestamp // check if we passed a timestamp
if(parseInt(timestamp, 10) == timestamp) { if (parseInt(timestamp, 10) == timestamp) {
return window.moment.unix(timestamp).format(f); return window.moment.unix(timestamp).format(f);
} else {
return window.moment.utc(timestamp).format(f);
} }
} else { return window.moment.utc(timestamp).format(f);
return timestamp;
} }
return timestamp;
}); });
Handlebars.registerHelper('cycle', function(value, block) { Handlebars.registerHelper('cycle', function (value, block) {
var values = value.split(' '); var values = value.split(' ');
return values[block.data.index % (values.length + 1)]; return values[block.data.index % (values.length + 1)];
}); });
@ -59,47 +60,46 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
case '||': case '||':
return (v1 || v2) ? options.fn(this) : options.inverse(this); return (v1 || v2) ? options.fn(this) : options.inverse(this);
case 'in': case 'in':
var values = v2.split(',');
return (v2.indexOf(v1) !== -1) ? options.fn(this) : options.inverse(this); return (v2.indexOf(v1) !== -1) ? options.fn(this) : options.inverse(this);
default: default:
return options.inverse(this); return options.inverse(this);
} }
}); });
Handlebars.registerHelper('nl2br', function(value, block) { Handlebars.registerHelper('nl2br', function (value) {
return value.gsub('\n', '<br />'); return value.gsub('\n', '<br />');
}); });
Handlebars.registerHelper('json_encode', function(value, block) { Handlebars.registerHelper('json_encode', function (value) {
return JSON.stringify(value); return JSON.stringify(value);
}); });
Handlebars.registerHelper('json_decode', function(value, block) { Handlebars.registerHelper('json_decode', function (value) {
return JSON.parse(value); return JSON.parse(value);
}); });
Handlebars.registerHelper('url', function(value, block) { Handlebars.registerHelper('url', function (value) {
var url = window.location.protocol + '//' + window.location.host + window.location.pathname; var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
return url + value; return url + value;
}); });
Handlebars.registerHelper('emailFromMailto', function(value) { Handlebars.registerHelper('emailFromMailto', function (value) {
var mailtoMatchingRegex = /^mailto\:/i; var mailtoMatchingRegex = /^mailto\:/i;
if (typeof value === 'string' && value.match(mailtoMatchingRegex)) { if (typeof value === 'string' && value.match(mailtoMatchingRegex)) {
return value.replace(mailtoMatchingRegex, ''); return value.replace(mailtoMatchingRegex, '');
} else {
return value;
} }
return value;
}); });
Handlebars.registerHelper('lookup', function(obj, field, options) { Handlebars.registerHelper('lookup', function (obj, field) {
return obj && obj[field]; return obj && obj[field];
}); });
Handlebars.registerHelper('rsa_key', function(value, block) { Handlebars.registerHelper('rsa_key', function (value) {
// extract all lines into an array var lines;
if(value === undefined) return ''; // extract all lines into an array
if (value === undefined) return '';
var lines = value.trim().split('\n'); lines = value.trim().split('\n');
// remove header & footer // remove header & footer
lines.shift(); lines.shift();
@ -109,8 +109,8 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
return lines.join(''); return lines.join('');
}); });
Handlebars.registerHelper('trim', function(value, block) { Handlebars.registerHelper('trim', function (value) {
if(value === null || value === undefined) return ''; if (value === null || value === undefined) return '';
return value.trim(); return value.trim();
}); });
@ -126,23 +126,22 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
*/ */
Handlebars.registerHelper('ellipsis', function (str, limit, append) { Handlebars.registerHelper('ellipsis', function (str, limit, append) {
var strAppend = append; var strAppend = append;
var sanitized = str.replace(/(<([^>]+)>)/g, '');
if (strAppend === undefined) { if (strAppend === undefined) {
strAppend = ''; strAppend = '';
} }
var sanitized = str.replace(/(<([^>]+)>)/g, '');
if (sanitized.length > limit) { if (sanitized.length > limit) {
return sanitized.substr(0, limit - strAppend.length) + strAppend; return sanitized.substr(0, limit - strAppend.length) + strAppend;
} else {
return sanitized;
} }
return sanitized;
}); });
Handlebars.registerHelper('getNumber', function (string) { Handlebars.registerHelper('getNumber', function (string) {
return parseInt(string, 10); return parseInt(string, 10);
}); });
Handlebars.registerHelper('fontWithFallback', function(font) { Handlebars.registerHelper('fontWithFallback', function (font) {
switch(font) { switch (font) {
case 'Arial': return new Handlebars.SafeString("Arial, 'Helvetica Neue', Helvetica, sans-serif"); case 'Arial': return new Handlebars.SafeString("Arial, 'Helvetica Neue', Helvetica, sans-serif");
case 'Comic Sans MS': return new Handlebars.SafeString("'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif"); case 'Comic Sans MS': return new Handlebars.SafeString("'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif");
case 'Courier New': return new Handlebars.SafeString("'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace"); case 'Courier New': return new Handlebars.SafeString("'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace");

View File

@ -1,12 +1,11 @@
define('helpTooltip', ['mailpoet', 'react', 'react-dom', 'help-tooltip.jsx'], define('helpTooltip', ['mailpoet', 'react', 'react-dom', 'help-tooltip.jsx'],
function (mp, React, ReactDOM, TooltipComponent) { function helpTooltip(mp, React, ReactDOM, TooltipComponent) {
'use strict'; 'use strict';
var MailPoet = mp; var MailPoet = mp;
MailPoet.helpTooltip = { MailPoet.helpTooltip = {
show: function (domContainerNode, opts) { show: function show(domContainerNode, opts) {
ReactDOM.render(React.createElement( ReactDOM.render(React.createElement(
TooltipComponent, { TooltipComponent, {
tooltip: opts.tooltip, tooltip: opts.tooltip,
@ -16,7 +15,6 @@ define('helpTooltip', ['mailpoet', 'react', 'react-dom', 'help-tooltip.jsx'],
), domContainerNode); ), domContainerNode);
} }
}; };
} }
); );

View File

@ -6,11 +6,11 @@ function Tooltip(props) {
let tooltipId = props.tooltipId; let tooltipId = props.tooltipId;
let tooltip = props.tooltip; let tooltip = props.tooltip;
// tooltip ID must be unique, defaults to tooltip text // tooltip ID must be unique, defaults to tooltip text
if(!props.tooltipId && typeof props.tooltip === 'string') { if (!props.tooltipId && typeof props.tooltip === 'string') {
tooltipId = props.tooltip; tooltipId = props.tooltip;
} }
if(typeof props.tooltip === 'string') { if (typeof props.tooltip === 'string') {
tooltip = (<span tooltip = (<span
style={{ style={{
pointerEvents: 'all', pointerEvents: 'all',

View File

@ -3,8 +3,9 @@ import ReactDOM from 'react-dom';
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router'; import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history'; import { createHashHistory } from 'history';
import KnowledgeBase from 'help/knowledge_base.jsx'; import SystemStatus from 'help/system_status.jsx';
import SystemInfo from 'help/system_info.jsx'; import SystemInfo from 'help/system_info.jsx';
import KnowledgeBase from 'help/knowledge_base.jsx';
const history = useRouterHistory(createHashHistory)({ queryKey: false }); const history = useRouterHistory(createHashHistory)({ queryKey: false });
@ -16,17 +17,16 @@ const App = React.createClass({
const container = document.getElementById('help_container'); const container = document.getElementById('help_container');
if(container) { if (container) {
ReactDOM.render(( ReactDOM.render((
<Router history={ history }> <Router history={history}>
<Route path="/" component={ App }> <Route path="/" component={App}>
<IndexRedirect to="knowledgeBase" /> <IndexRedirect to="systemStatus" />
{/* Pages */} {/* Pages */}
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={ KnowledgeBase } /> <Route path="systemStatus(/)**" params={{ tab: 'systemStatus' }} component={SystemStatus} />
<Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={ SystemInfo } /> <Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={SystemInfo} />
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={KnowledgeBase} />
</Route> </Route>
</Router> </Router>
), container); ), container);
} }

View File

@ -4,7 +4,6 @@ import MailPoet from 'mailpoet';
import Tabs from './tabs.jsx'; import Tabs from './tabs.jsx';
function KnowledgeBase() { function KnowledgeBase() {
return ( return (
<div> <div>

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import MailPoet from 'mailpoet'; import MailPoet from 'mailpoet';
import _ from 'underscore'; import _ from 'underscore';
import Tabs from './tabs.jsx'; import Tabs from './tabs.jsx';
function handleFocus(event) { function handleFocus(event) {
@ -10,9 +9,7 @@ function handleFocus(event) {
function printData(data) { function printData(data) {
if (_.isObject(data)) { if (_.isObject(data)) {
const printableData = Object.keys(data).map((key) => { const printableData = Object.keys(data).map(key => `${key}: ${data[key]}`);
return `${key}: ${data[key]}`;
});
return (<textarea return (<textarea
readOnly={true} readOnly={true}
@ -23,25 +20,24 @@ function printData(data) {
height: '400px', height: '400px',
}} }}
/>); />);
} else {
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
} }
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
} }
function KnowledgeBase() { function SystemInfo() {
const data = window.help_scout_data; const systemInfoData = window.systemInfoData;
return ( return (
<div> <div>
<Tabs tab="systemInfo" /> <Tabs tab="systemInfo" />
<div className="mailpoet_notice notice inline notice-success" style={{ marginTop: '1em' }}> <div className="mailpoet_notice notice inline" style={{ marginTop: '1em' }}>
<p>{MailPoet.I18n.t('systemInfoIntro')}</p> <p>{MailPoet.I18n.t('systemInfoIntro')}</p>
</div> </div>
{printData(data)} {printData(systemInfoData)}
</div> </div>
); );
} }
module.exports = KnowledgeBase; module.exports = SystemInfo;

View File

@ -0,0 +1,71 @@
import MailPoet from 'mailpoet';
import React from 'react';
import ReactStringReplace from 'react-string-replace';
import Tabs from './tabs.jsx';
function renderStatusMessage(status, error, link) {
const noticeType = (status) ? 'success' : 'error';
let noticeMessage = (status) ?
MailPoet.I18n.t('systemStatusConnectionSuccessful') :
`${MailPoet.I18n.t('systemStatusConnectionUnsuccessful')} ${error}`;
if (link) {
noticeMessage = ReactStringReplace(
noticeMessage,
/\[link\](.*?)\[\/link\]/g,
match => (
<a href={`${link}`} key="kb-link">{ match }</a>
)
);
}
return (
<div className={`mailpoet_notice notice inline notice-${noticeType}`} style={{ marginTop: '1em' }}>
<p>{noticeMessage}</p>
</div>
);
}
function renderCronSection(data) {
const status = data.cron.isReachable;
const url = data.cron.url;
return (
<div>
<h2>{MailPoet.I18n.t('systemStatusCronTitle')}</h2>
<p>
<a href={url} target="_blank">{url}</a>
</p>
{renderStatusMessage(status, MailPoet.I18n.t('systemStatusCronConnectionUnsuccessfulInfo'), '//beta.docs.mailpoet.com/article/231-sending-does-not-work')}
</div>
);
}
function renderMSSSection(data) {
if (!data.mss.enabled) return undefined;
const status = data.mss.enabled.isReachable;
return (
<div>
<h2>{MailPoet.I18n.t('systemStatusMSSTitle')}</h2>
{renderStatusMessage(status, MailPoet.I18n.t('systemStatusMSSConnectionUnsuccessfulInfo'), false)}
</div>
);
}
function SystemStatus() {
const systemStatusData = window.systemStatusData;
return (
<div>
<Tabs tab="systemStatus" />
<div className="mailpoet_notice notice inline" style={{ marginTop: '1em' }}>
<p>{systemStatusData.mss.enabled ? MailPoet.I18n.t('systemStatusIntroCronMSS') : MailPoet.I18n.t('systemStatusIntroCron')}</p>
</div>
{renderCronSection(systemStatusData)}
{renderMSSSection(systemStatusData)}
</div>
);
}
module.exports = SystemStatus;

View File

@ -5,19 +5,23 @@ import MailPoet from 'mailpoet';
const tabs = [ const tabs = [
{ {
name: 'knowledgeBase', name: 'systemStatus',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'), label: MailPoet.I18n.t('tabSystemStatusTitle'),
link: '/knowledgeBase', link: '/systemStatus',
}, },
{ {
name: 'systemInfo', name: 'systemInfo',
label: MailPoet.I18n.t('tabSystemInfoTitle'), label: MailPoet.I18n.t('tabSystemInfoTitle'),
link: '/systemInfo', link: '/systemInfo',
}, },
{
name: 'knowledgeBase',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'),
link: '/knowledgeBase',
},
]; ];
function Tabs(props) { function Tabs(props) {
const tabLinks = tabs.map((tab, index) => { const tabLinks = tabs.map((tab, index) => {
const tabClasses = classNames( const tabClasses = classNames(
'nav-tab', 'nav-tab',
@ -26,9 +30,9 @@ function Tabs(props) {
return ( return (
<Link <Link
key={ 'tab-'+index } key={`tab-${index}`}
className={ tabClasses } className={tabClasses}
to={ tab.link } to={tab.link}
>{ tab.label }</Link> >{ tab.label }</Link>
); );
}); });
@ -41,6 +45,6 @@ function Tabs(props) {
} }
Tabs.propTypes = { tab: React.PropTypes.string }; Tabs.propTypes = { tab: React.PropTypes.string };
Tabs.defaultProps = { tab: 'knowledgeBase' }; Tabs.defaultProps = { tab: 'systemStatus' };
module.exports = Tabs; module.exports = Tabs;

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@ define([
const action = this.getSelectedAction(); const action = this.getSelectedAction();
// action on select callback // action on select callback
if(action !== null && action['onSelect'] !== undefined) { if (action !== null && action.onSelect !== undefined) {
this.setState({ this.setState({
extra: action.onSelect(e), extra: action.onSelect(e),
}); });
@ -33,27 +33,27 @@ define([
const action = this.getSelectedAction(); const action = this.getSelectedAction();
if(action === null) { if (action === null) {
return; return;
} }
const selected_ids = (this.props.selection !== 'all') const selectedIds = (this.props.selection !== 'all')
? this.props.selected_ids ? this.props.selected_ids
: []; : [];
const data = (action['getData'] !== undefined) const data = (action.getData !== undefined)
? action.getData() ? action.getData()
: {}; : {};
data.action = this.state.action; data.action = this.state.action;
let onSuccess = function () {}; let onSuccess = function () {};
if(action['onSuccess'] !== undefined) { if (action.onSuccess !== undefined) {
onSuccess = action.onSuccess; onSuccess = action.onSuccess;
} }
if(data.action) { if (data.action) {
const promise = this.props.onBulkAction(selected_ids, data); const promise = this.props.onBulkAction(selectedIds, data);
if (promise !== false) { if (promise !== false) {
promise.then(onSuccess); promise.then(onSuccess);
} }
@ -65,20 +65,18 @@ define([
}); });
}, },
getSelectedAction: function () { getSelectedAction: function () {
const selected_action = this.refs.action.value; const selectedAction = this.refs.action.value;
if(selected_action.length > 0) { if (selectedAction.length > 0) {
const action = this.props.bulk_actions.filter((action) => { const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
return (action.name === selected_action);
});
if(action.length > 0) { if (action.length > 0) {
return action[0]; return action[0];
} }
} }
return null; return null;
}, },
render: function () { render: function () {
if(this.props.bulk_actions.length === 0) { if (this.props.bulk_actions.length === 0) {
return null; return null;
} }
@ -93,21 +91,19 @@ define([
<select <select
name="bulk_actions" name="bulk_actions"
ref="action" ref="action"
value={ this.state.action } value={this.state.action}
onChange={this.handleChangeAction} onChange={this.handleChangeAction}
> >
<option value="">{MailPoet.I18n.t('bulkActions')}</option> <option value="">{MailPoet.I18n.t('bulkActions')}</option>
{ this.props.bulk_actions.map((action, index) => { { this.props.bulk_actions.map((action, index) => (
return ( <option
<option value={action.name}
value={ action.name } key={`action-${index}`}
key={ 'action-' + index }
>{ action.label }</option> >{ action.label }</option>
); )) }
}) }
</select> </select>
<input <input
onClick={ this.handleApplyAction } onClick={this.handleApplyAction}
type="submit" type="submit"
defaultValue={MailPoet.I18n.t('apply')} defaultValue={MailPoet.I18n.t('apply')}
className="button action" /> className="button action" />

View File

@ -11,8 +11,8 @@ define([
const ListingFilters = React.createClass({ const ListingFilters = React.createClass({
handleFilterAction: function () { handleFilterAction: function () {
const filters = {}; const filters = {};
this.getAvailableFilters().map((filter, i) => { this.getAvailableFilters().forEach((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) { if (this.props.onBeforeSelectFilter) {
this.props.onBeforeSelectFilter(filters); this.props.onBeforeSelectFilter(filters);
@ -24,23 +24,21 @@ define([
}, },
getAvailableFilters: function () { getAvailableFilters: function () {
const filters = this.props.filters; const filters = this.props.filters;
return Object.keys(filters).filter((filter) => { return Object.keys(filters).filter(filter => !(
return !(
filters[filter].length === 0 filters[filter].length === 0
|| ( || (
filters[filter].length === 1 filters[filter].length === 1
&& !filters[filter][0].value && !filters[filter][0].value
) )
); ));
});
}, },
componentDidUpdate: function () { componentDidUpdate: function () {
const selected_filters = this.props.filter; const selectedFilters = this.props.filter;
this.getAvailableFilters().map( this.getAvailableFilters().forEach(
(filter, i) => { (filter, i) => {
if (selected_filters[filter] !== undefined && selected_filters[filter]) { if (selectedFilters[filter] !== undefined && selectedFilters[filter]) {
jQuery(this.refs['filter-'+i]) jQuery(this.refs[`filter-${i}`])
.val(selected_filters[filter]) .val(selectedFilters[filter])
.trigger('change'); .trigger('change');
} }
} }
@ -48,44 +46,40 @@ define([
}, },
render: function () { render: function () {
const filters = this.props.filters; const filters = this.props.filters;
const available_filters = this.getAvailableFilters() const availableFilters = this.getAvailableFilters()
.map((filter, i) => { .map((filter, i) => (
return ( <select
<select ref={`filter-${i}`}
ref={ `filter-${i}` } key={`filter-${i}`}
key={ `filter-${i}` } name={filter}
name={ filter }
> >
{ filters[filter].map((option, j) => { { filters[filter].map((option, j) => (
return ( <option
<option value={option.value}
value={ option.value } key={`filter-option-${j}`}
key={ 'filter-option-' + j }
>{ option.label }</option> >{ option.label }</option>
); )) }
}) } </select>
</select> ));
);
});
let button; let button;
if (available_filters.length > 0) { if (availableFilters.length > 0) {
button = ( button = (
<input <input
id="post-query-submit" id="post-query-submit"
onClick={ this.handleFilterAction } onClick={this.handleFilterAction}
type="submit" type="submit"
defaultValue={MailPoet.I18n.t('filter')} defaultValue={MailPoet.I18n.t('filter')}
className="button" /> className="button" />
); );
} }
let empty_trash; let emptyTrash;
if (this.props.group === 'trash') { if (this.props.group === 'trash') {
empty_trash = ( emptyTrash = (
<input <input
onClick={ this.handleEmptyTrash } onClick={this.handleEmptyTrash}
type="submit" type="submit"
value={MailPoet.I18n.t('emptyTrash')} value={MailPoet.I18n.t('emptyTrash')}
className="button" className="button"
@ -95,9 +89,9 @@ define([
return ( return (
<div className="alignleft actions actions"> <div className="alignleft actions actions">
{ available_filters } { availableFilters }
{ button } { button }
{ empty_trash } { emptyTrash }
</div> </div>
); );
}, },

View File

@ -1,17 +1,16 @@
define(['react', 'classnames'], (React, classNames) => { define(['react', 'classnames'], (React, classNames) => {
const ListingGroups = React.createClass({ const ListingGroups = React.createClass({
handleSelect: function (group) { handleSelect: function (group) {
return this.props.onSelectGroup(group); return this.props.onSelectGroup(group);
}, },
render: function () { render: function () {
const groups = this.props.groups.map((group, index) => { const groups = this.props.groups.map((group, index) => {
if(group.name === 'trash' && group.count === 0) { if (group.name === 'trash' && group.count === 0) {
return false; return false;
} }
const classes = classNames( const classes = classNames(
{ current : (group.name === this.props.group) } { current: (group.name === this.props.group) }
); );
return ( return (
@ -21,7 +20,8 @@ define(['react', 'classnames'], (React, classNames) => {
href="javascript:;" href="javascript:;"
className={classes} className={classes}
onClick={this.handleSelect.bind(this, group.name)} > onClick={this.handleSelect.bind(this, group.name)} >
{group.label} <span className="count">({ group.count.toLocaleString() })</span> {group.label}
<span className="count">({ parseInt(group.count, 10).toLocaleString() })</span>
</a> </a>
</li> </li>
); );

View File

@ -19,14 +19,14 @@ const ListingHeader = React.createClass({
<ListingColumn <ListingColumn
onSort={this.props.onSort} onSort={this.props.onSort}
sort_by={this.props.sort_by} sort_by={this.props.sort_by}
key={ 'column-' + index } key={`column-${index}`}
column={renderColumn} /> column={renderColumn} />
); );
}); });
let checkbox; let checkbox;
if(this.props.is_selectable === true) { if (this.props.is_selectable === true) {
checkbox = ( checkbox = (
<th <th
className="manage-column column-cb check-column"> className="manage-column column-cb check-column">
@ -37,8 +37,8 @@ const ListingHeader = React.createClass({
type="checkbox" type="checkbox"
name="select_all" name="select_all"
ref="toggle" ref="toggle"
checked={ this.props.selection } checked={this.props.selection}
onChange={ this.handleSelectItems } /> onChange={this.handleSelectItems} />
</th> </th>
); );
} }
@ -54,9 +54,9 @@ const ListingHeader = React.createClass({
const ListingColumn = React.createClass({ const ListingColumn = React.createClass({
handleSort: function () { handleSort: function () {
const sort_by = this.props.column.name; const sortBy = this.props.column.name;
const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc'; const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order); this.props.onSort(sortBy, sortOrder);
}, },
render: function () { render: function () {
const classes = classNames( const classes = classNames(
@ -68,9 +68,9 @@ const ListingColumn = React.createClass({
); );
let label; let label;
if(this.props.column.sortable === true) { if (this.props.column.sortable === true) {
label = ( label = (
<a onClick={ this.handleSort }> <a onClick={this.handleSort}>
<span>{ this.props.column.label }</span> <span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span> <span className="sorting-indicator"></span>
</a> </a>
@ -80,10 +80,10 @@ const ListingColumn = React.createClass({
} }
return ( return (
<th <th
className={ classes } className={classes}
id={this.props.column.name } id={this.props.column.name}
scope="col" scope="col"
width={ this.props.column.width || null } width={this.props.column.width || null}
>{label}</th> >{label}</th>
); );
}, },

View File

@ -44,70 +44,66 @@ const ListingItem = React.createClass({
checkbox = ( checkbox = (
<th className="check-column" scope="row"> <th className="check-column" scope="row">
<label className="screen-reader-text">{ <label className="screen-reader-text">{
'Select ' + this.props.item[this.props.columns[0].name] `Select ${this.props.item[this.props.columns[0].name]}`
}</label> }</label>
<input <input
type="checkbox" type="checkbox"
value={ this.props.item.id } value={this.props.item.id}
checked={ checked={
this.props.item.selected || this.props.selection === 'all' this.props.item.selected || this.props.selection === 'all'
} }
onChange={ this.handleSelectItem } onChange={this.handleSelectItem}
disabled={ this.props.selection === 'all' } /> disabled={this.props.selection === 'all'} />
</th> </th>
); );
} }
const custom_actions = this.props.item_actions; const customActions = this.props.item_actions;
let item_actions = false; let itemActions = false;
if (custom_actions.length > 0) { if (customActions.length > 0) {
let is_first = true; let isFirst = true;
item_actions = custom_actions.map((action, index) => { itemActions = customActions
if (action.display !== undefined) { .filter(action => action.display === undefined || action.display(this.props.item))
if (action.display(this.props.item) === false) { .map((action, index) => {
return; let customAction = null;
}
}
let custom_action = null;
if (action.name === 'trash') { if (action.name === 'trash') {
custom_action = ( customAction = (
<span key={ 'action-'+index } className="trash"> <span key={`action-${index}`} className="trash">
{(!is_first) ? ' | ' : ''} {(!isFirst) ? ' | ' : ''}
<a <a
href="javascript:;" href="javascript:;"
onClick={ this.handleTrashItem.bind( onClick={this.handleTrashItem.bind(
null, null,
this.props.item.id this.props.item.id
) }> )}>
{MailPoet.I18n.t('moveToTrash')} {MailPoet.I18n.t('moveToTrash')}
</a> </a>
</span> </span>
); );
} else if (action.refresh) { } else if (action.refresh) {
custom_action = ( customAction = (
<span <span
onClick={ this.props.onRefreshItems } onClick={this.props.onRefreshItems}
key={ 'action-'+index } className={ action.name }> key={`action-${index}`} className={action.name}>
{(!is_first) ? ' | ' : ''} {(!isFirst) ? ' | ' : ''}
{ action.link(this.props.item) } { action.link(this.props.item) }
</span> </span>
); );
} else if (action.link) { } else if (action.link) {
custom_action = ( customAction = (
<span <span
key={ 'action-'+index } className={ action.name }> key={`action-${index}`} className={action.name}>
{(!is_first) ? ' | ' : ''} {(!isFirst) ? ' | ' : ''}
{ action.link(this.props.item) } { action.link(this.props.item) }
</span> </span>
); );
} else { } else {
custom_action = ( customAction = (
<span <span
key={ 'action-'+index } className={ action.name }> key={`action-${index}`} className={action.name}>
{(!is_first) ? ' | ' : ''} {(!isFirst) ? ' | ' : ''}
<a href="javascript:;" onClick={ <a href="javascript:;" onClick={
(action.onClick !== undefined) (action.onClick !== undefined)
? action.onClick.bind(null, ? action.onClick.bind(null,
@ -120,16 +116,16 @@ const ListingItem = React.createClass({
); );
} }
if (custom_action !== null && is_first === true) { if (customAction !== null && isFirst === true) {
is_first = false; isFirst = false;
} }
return custom_action; return customAction;
}); });
} else { } else {
item_actions = ( itemActions = (
<span className="edit"> <span className="edit">
<Link to={ `/edit/${ this.props.item.id }` }>{MailPoet.I18n.t('edit')}</Link> <Link to={`/edit/${this.props.item.id}`}>{MailPoet.I18n.t('edit')}</Link>
</span> </span>
); );
} }
@ -143,7 +139,7 @@ const ListingItem = React.createClass({
<span> <span>
<a <a
href="javascript:;" href="javascript:;"
onClick={ this.handleRestoreItem.bind( onClick={this.handleRestoreItem.bind(
null, null,
this.props.item.id this.props.item.id
)} )}
@ -154,7 +150,7 @@ const ListingItem = React.createClass({
<a <a
className="submitdelete" className="submitdelete"
href="javascript:;" href="javascript:;"
onClick={ this.handleDeleteItem.bind( onClick={this.handleDeleteItem.bind(
null, null,
this.props.item.id this.props.item.id
)} )}
@ -162,7 +158,7 @@ const ListingItem = React.createClass({
</span> </span>
</div> </div>
<button <button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) } onClick={this.handleToggleItem.bind(null, this.props.item.id)}
className="toggle-row" type="button"> className="toggle-row" type="button">
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span> <span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button> </button>
@ -172,10 +168,10 @@ const ListingItem = React.createClass({
actions = ( actions = (
<div> <div>
<div className="row-actions"> <div className="row-actions">
{ item_actions } { itemActions }
</div> </div>
<button <button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) } onClick={this.handleToggleItem.bind(null, this.props.item.id)}
className="toggle-row" type="button"> className="toggle-row" type="button">
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span> <span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button> </button>
@ -183,10 +179,10 @@ const ListingItem = React.createClass({
); );
} }
const row_classes = classNames({ 'is-expanded': this.state.expanded }); const rowClasses = classNames({ 'is-expanded': this.state.expanded });
return ( return (
<tr className={ row_classes }> <tr className={rowClasses} data-automation-id={`listing_item_${this.props.item.id}`}>
{ checkbox } { checkbox }
{ this.props.onRenderItem(this.props.item, actions) } { this.props.onRenderItem(this.props.item, actions) }
</tr> </tr>
@ -223,24 +219,24 @@ const ListingItems = React.createClass({
</tr> </tr>
</tbody> </tbody>
); );
} else { }
const select_all_classes = classNames( const selectAllClasses = classNames(
'mailpoet_select_all', 'mailpoet_select_all',
{ mailpoet_hidden: ( { mailpoet_hidden: (
this.props.selection === false this.props.selection === false
|| (this.props.count <= this.props.limit) || (this.props.count <= this.props.limit)
), ),
} }
); );
return ( return (
<tbody> <tbody>
<tr className={ select_all_classes }> <tr className={selectAllClasses}>
<td colSpan={ <td colSpan={
this.props.columns.length this.props.columns.length
+ (this.props.is_selectable ? 1 : 0) + (this.props.is_selectable ? 1 : 0)
}> }>
{ {
(this.props.selection !== 'all') (this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLabel') ? MailPoet.I18n.t('selectAllLabel')
: MailPoet.I18n.t('selectedAllLabel').replace( : MailPoet.I18n.t('selectedAllLabel').replace(
@ -249,41 +245,40 @@ const ListingItems = React.createClass({
) )
} }
&nbsp; &nbsp;
<a <a
onClick={ this.props.onSelectAll } onClick={this.props.onSelectAll}
href="javascript:;">{ href="javascript:;">{
(this.props.selection !== 'all') (this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLink') ? MailPoet.I18n.t('selectAllLink')
: MailPoet.I18n.t('clearSelection') : MailPoet.I18n.t('clearSelection')
}</a> }</a>
</td> </td>
</tr> </tr>
{this.props.items.map((item, index) => { {this.props.items.map((item, index) => {
const renderItem = item; const renderItem = item;
renderItem.id = parseInt(item.id, 10); renderItem.id = parseInt(item.id, 10);
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1); renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
return ( return (
<ListingItem <ListingItem
columns={ this.props.columns } columns={this.props.columns}
onSelectItem={ this.props.onSelectItem } onSelectItem={this.props.onSelectItem}
onRenderItem={ this.props.onRenderItem } onRenderItem={this.props.onRenderItem}
onDeleteItem={ this.props.onDeleteItem } onDeleteItem={this.props.onDeleteItem}
onRestoreItem={ this.props.onRestoreItem } onRestoreItem={this.props.onRestoreItem}
onTrashItem={ this.props.onTrashItem } onTrashItem={this.props.onTrashItem}
onRefreshItems={ this.props.onRefreshItems } onRefreshItems={this.props.onRefreshItems}
selection={ this.props.selection } selection={this.props.selection}
is_selectable={ this.props.is_selectable } is_selectable={this.props.is_selectable}
item_actions={ this.props.item_actions } item_actions={this.props.item_actions}
group={ this.props.group } group={this.props.group}
key={ `item-${renderItem.id}-${index}` } key={`item-${renderItem.id}-${index}`}
item={ renderItem } /> item={renderItem} />
); );
})} })}
</tbody> </tbody>
); );
}
}, },
}); });
@ -319,16 +314,15 @@ const Listing = React.createClass({
const state = this.getInitialState(); const state = this.getInitialState();
// check for url params // check for url params
if (params.splat) { if (params.splat) {
params.splat.split('/').map((param) => { params.splat.split('/').forEach((param) => {
const [key, value] = this.getParam(param); const [key, value] = this.getParam(param);
switch(key) { const filters = {};
switch (key) {
case 'filter': case 'filter':
const filters = {}; value.split('&').forEach((pair) => {
value.split('&').map((pair) => {
const [k, v] = pair.split('='); const [k, v] = pair.split('=');
filters[k] = v; filters[k] = v;
} });
);
state.filter = filters; state.filter = filters;
break; break;
@ -340,7 +334,7 @@ const Listing = React.createClass({
// limit per page // limit per page
if (this.props.limit !== undefined) { if (this.props.limit !== undefined) {
state.limit = Math.abs(~~this.props.limit); state.limit = Math.abs(Number(this.props.limit));
} }
// sort by // sort by
@ -371,8 +365,7 @@ const Listing = React.createClass({
setParams: function () { setParams: function () {
if (this.props.location) { if (this.props.location) {
const params = Object.keys(this.state) const params = Object.keys(this.state)
.filter((key) => { .filter(key => (
return (
[ [
'group', 'group',
'filter', 'filter',
@ -381,8 +374,7 @@ const Listing = React.createClass({
'sort_by', 'sort_by',
'sort_order', 'sort_order',
].indexOf(key) !== -1 ].indexOf(key) !== -1
); ))
})
.map((key) => { .map((key) => {
let value = this.state[key]; let value = this.state[key];
if (value === Object(value)) { if (value === Object(value)) {
@ -390,12 +382,13 @@ const Listing = React.createClass({
} else if (value === Boolean(value)) { } else if (value === Boolean(value)) {
value = value.toString(); value = value.toString();
} }
return {
if (value !== '' && value !== null) { key,
return `${key}[${value}]`; value,
} };
}) })
.filter((key) => { return (key !== undefined); }) .filter(({ value }) => value !== '' && value !== null)
.map(({ key, value }) => `${key}[${value}]`)
.join('/'); .join('/');
// set url // set url
@ -407,24 +400,23 @@ const Listing = React.createClass({
} }
}, },
getUrlWithParams: function (params) { getUrlWithParams: function (params) {
let base_url = (this.props.base_url !== undefined) let baseUrl = (this.props.base_url !== undefined)
? this.props.base_url ? this.props.base_url
: null; : null;
if (base_url !== null) { if (baseUrl !== null) {
base_url = this.setBaseUrlParams(base_url); baseUrl = this.setBaseUrlParams(baseUrl);
return `/${ base_url }/${ params }`; return `/${baseUrl}/${params}`;
} else {
return `/${ params }`;
} }
return `/${params}`;
}, },
setBaseUrlParams: function (base_url) { setBaseUrlParams: function (baseUrl) {
let ret = base_url; let ret = baseUrl;
if (ret.indexOf(':') !== -1) { if (ret.indexOf(':') !== -1) {
const params = this.getParams(); const params = this.getParams();
Object.keys(params).map((key) => { Object.keys(params).forEach((key) => {
if (ret.indexOf(':'+key) !== -1) { if (ret.indexOf(`:${key}`) !== -1) {
ret = ret.replace(':'+key, params[key]); ret = ret.replace(`:${key}`, params[key]);
} }
}); });
} }
@ -489,9 +481,9 @@ const Listing = React.createClass({
} }
}); });
}).fail((response) => { }).fail((response) => {
if(response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
@ -514,14 +506,14 @@ const Listing = React.createClass({
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
&& this.props.messages['onRestore'] !== undefined && this.props.messages.onRestore !== undefined
) { ) {
this.props.messages.onRestore(response); this.props.messages.onRestore(response);
} }
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
}); });
@ -542,14 +534,14 @@ const Listing = React.createClass({
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined && this.props.messages.onTrash !== undefined
) { ) {
this.props.messages.onTrash(response); this.props.messages.onTrash(response);
} }
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
}); });
@ -570,14 +562,14 @@ const Listing = React.createClass({
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined && this.props.messages.onDelete !== undefined
) { ) {
this.props.messages.onDelete(response); this.props.messages.onDelete(response);
} }
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
}); });
@ -594,16 +586,16 @@ const Listing = React.createClass({
this.handleGroup('all'); this.handleGroup('all');
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleBulkAction: function (selected_ids, params) { handleBulkAction: function (selectedIds, params) {
if ( if (
this.state.selection === false this.state.selection === false
&& this.state.selected_ids.length === 0 && this.state.selected_ids.length === 0
&& selected_ids !== 'all' && selectedIds !== 'all'
) { ) {
return false; return false;
} }
@ -619,8 +611,8 @@ const Listing = React.createClass({
group: this.state.group, group: this.state.group,
search: this.state.search, search: this.state.search,
}; };
if (selected_ids !== 'all') { if (selectedIds !== 'all') {
data.listing.selection = selected_ids; data.listing.selection = selectedIds;
} }
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
@ -631,9 +623,9 @@ const Listing = React.createClass({
}).done(() => { }).done(() => {
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
if(response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
@ -649,20 +641,20 @@ const Listing = React.createClass({
this.setParams(); this.setParams();
}); });
}, },
handleSort: function (sort_by, sort_order = 'asc') { handleSort: function (sortBy, sortOrder = 'asc') {
this.setState({ this.setState({
sort_by: sort_by, sort_by: sortBy,
sort_order: (sort_order === 'asc') ? 'asc' : 'desc', sort_order: (sortOrder === 'asc') ? 'asc' : 'desc',
}, () => { }, () => {
this.setParams(); this.setParams();
}); });
}, },
handleSelectItem: function (id, is_checked) { handleSelectItem: function (id, isChecked) {
let selected_ids = this.state.selected_ids, let selectedIds = this.state.selected_ids;
selection = false; let selection = false;
if (is_checked) { if (isChecked) {
selected_ids = jQuery.merge(selected_ids, [ id ]); selectedIds = jQuery.merge(selectedIds, [id]);
// check whether all items on the page are selected // check whether all items on the page are selected
if ( if (
jQuery('tbody .check-column :checkbox:not(:checked)').length === 0 jQuery('tbody .check-column :checkbox:not(:checked)').length === 0
@ -670,24 +662,22 @@ const Listing = React.createClass({
selection = 'page'; selection = 'page';
} }
} else { } else {
selected_ids.splice(selected_ids.indexOf(id), 1); selectedIds.splice(selectedIds.indexOf(id), 1);
} }
this.setState({ this.setState({
selection: selection, selection: selection,
selected_ids: selected_ids, selected_ids: selectedIds,
}); });
}, },
handleSelectItems: function (is_checked) { handleSelectItems: function (isChecked) {
if (is_checked === false) { if (isChecked === false) {
this.clearSelection(); this.clearSelection();
} else { } else {
const selected_ids = this.state.items.map((item) => { const selectedIds = this.state.items.map(item => Number(item.id));
return ~~item.id;
});
this.setState({ this.setState({
selected_ids: selected_ids, selected_ids: selectedIds,
selection: 'page', selection: 'page',
}); });
} }
@ -747,20 +737,20 @@ const Listing = React.createClass({
}, },
render: function () { render: function () {
const items = this.state.items; const items = this.state.items;
const sort_by = this.state.sort_by; const sortBy = this.state.sort_by;
const sort_order = this.state.sort_order; const sortOrder = this.state.sort_order;
// columns // columns
let columns = this.props.columns || []; let columns = this.props.columns || [];
columns = columns.filter((column) => { columns = columns.filter(
return (column.display === undefined || !!(column.display) === true); column => (column.display === undefined || !!(column.display) === true)
}); );
// bulk actions // bulk actions
let bulk_actions = this.props.bulk_actions || []; let bulkActions = this.props.bulk_actions || [];
if (this.state.group === 'trash' && bulk_actions.length > 0) { if (this.state.group === 'trash' && bulkActions.length > 0) {
bulk_actions = [ bulkActions = [
{ {
name: 'restore', name: 'restore',
label: MailPoet.I18n.t('restore'), label: MailPoet.I18n.t('restore'),
@ -775,9 +765,9 @@ const Listing = React.createClass({
} }
// item actions // item actions
const item_actions = this.props.item_actions || []; const itemActions = this.props.item_actions || [];
const table_classes = classNames( const tableClasses = classNames(
'mailpoet_listing_table', 'mailpoet_listing_table',
'wp-list-table', 'wp-list-table',
'widefat', 'widefat',
@ -789,8 +779,8 @@ const Listing = React.createClass({
// search // search
let search = ( let search = (
<ListingSearch <ListingSearch
onSearch={ this.handleSearch } onSearch={this.handleSearch}
search={ this.state.search } search={this.state.search}
/> />
); );
if (this.props.search === false) { if (this.props.search === false) {
@ -800,9 +790,9 @@ const Listing = React.createClass({
// groups // groups
let groups = ( let groups = (
<ListingGroups <ListingGroups
groups={ this.state.groups } groups={this.state.groups}
group={ this.state.group } group={this.state.group}
onSelectGroup={ this.handleGroup } onSelectGroup={this.handleGroup}
/> />
); );
if (this.props.groups === false) { if (this.props.groups === false) {
@ -821,81 +811,81 @@ const Listing = React.createClass({
{ search } { search }
<div className="tablenav top clearfix"> <div className="tablenav top clearfix">
<ListingBulkActions <ListingBulkActions
count={ this.state.count } count={this.state.count}
bulk_actions={ bulk_actions } bulk_actions={bulkActions}
selection={ this.state.selection } selection={this.state.selection}
selected_ids={ this.state.selected_ids } selected_ids={this.state.selected_ids}
onBulkAction={ this.handleBulkAction } /> onBulkAction={this.handleBulkAction} />
<ListingFilters <ListingFilters
filters={ this.state.filters } filters={this.state.filters}
filter={ this.state.filter } filter={this.state.filter}
group={ this.state.group } group={this.state.group}
onBeforeSelectFilter={ this.props.onBeforeSelectFilter || null } onBeforeSelectFilter={this.props.onBeforeSelectFilter || null}
onSelectFilter={ this.handleFilter } onSelectFilter={this.handleFilter}
onEmptyTrash={ this.handleEmptyTrash } onEmptyTrash={this.handleEmptyTrash}
/> />
<ListingPages <ListingPages
count={ this.state.count } count={this.state.count}
page={ this.state.page } page={this.state.page}
limit={ this.state.limit } limit={this.state.limit}
onSetPage={ this.handleSetPage } /> onSetPage={this.handleSetPage} />
</div> </div>
<table className={ table_classes }> <table className={tableClasses}>
<thead> <thead>
<ListingHeader <ListingHeader
onSort={ this.handleSort } onSort={this.handleSort}
onSelectItems={ this.handleSelectItems } onSelectItems={this.handleSelectItems}
selection={ this.state.selection } selection={this.state.selection}
sort_by={ sort_by } sort_by={sortBy}
sort_order={ sort_order } sort_order={sortOrder}
columns={ columns } columns={columns}
is_selectable={ bulk_actions.length > 0 } /> is_selectable={bulkActions.length > 0} />
</thead> </thead>
<ListingItems <ListingItems
onRenderItem={ this.handleRenderItem } onRenderItem={this.handleRenderItem}
onDeleteItem={ this.handleDeleteItem } onDeleteItem={this.handleDeleteItem}
onRestoreItem={ this.handleRestoreItem } onRestoreItem={this.handleRestoreItem}
onTrashItem={ this.handleTrashItem } onTrashItem={this.handleTrashItem}
onRefreshItems={ this.handleRefreshItems } onRefreshItems={this.handleRefreshItems}
columns={ columns } columns={columns}
is_selectable={ bulk_actions.length > 0 } is_selectable={bulkActions.length > 0}
onSelectItem={ this.handleSelectItem } onSelectItem={this.handleSelectItem}
onSelectAll={ this.handleSelectAll } onSelectAll={this.handleSelectAll}
selection={ this.state.selection } selection={this.state.selection}
selected_ids={ this.state.selected_ids } selected_ids={this.state.selected_ids}
loading={ this.state.loading } loading={this.state.loading}
group={ this.state.group } group={this.state.group}
count={ this.state.count } count={this.state.count}
limit={ this.state.limit } limit={this.state.limit}
item_actions={ item_actions } item_actions={itemActions}
messages={ messages } messages={messages}
items={ items } /> items={items} />
<tfoot> <tfoot>
<ListingHeader <ListingHeader
onSort={ this.handleSort } onSort={this.handleSort}
onSelectItems={ this.handleSelectItems } onSelectItems={this.handleSelectItems}
selection={ this.state.selection } selection={this.state.selection}
sort_by={ sort_by } sort_by={sortBy}
sort_order={ sort_order } sort_order={sortOrder}
columns={ columns } columns={columns}
is_selectable={ bulk_actions.length > 0 } /> is_selectable={bulkActions.length > 0} />
</tfoot> </tfoot>
</table> </table>
<div className="tablenav bottom"> <div className="tablenav bottom">
<ListingBulkActions <ListingBulkActions
count={ this.state.count } count={this.state.count}
bulk_actions={ bulk_actions } bulk_actions={bulkActions}
selection={ this.state.selection } selection={this.state.selection}
selected_ids={ this.state.selected_ids } selected_ids={this.state.selected_ids}
onBulkAction={ this.handleBulkAction } /> onBulkAction={this.handleBulkAction} />
<ListingPages <ListingPages
count={ this.state.count } count={this.state.count}
page={ this.state.page } page={this.state.page}
limit={ this.state.limit } limit={this.state.limit}
onSetPage={ this.handleSetPage } /> onSetPage={this.handleSetPage} />
</div> </div>
</div> </div>
); );

View File

@ -7,7 +7,6 @@ define([
classNames, classNames,
MailPoet MailPoet
) => { ) => {
const ListingPages = React.createClass({ const ListingPages = React.createClass({
getInitialState: function () { getInitialState: function () {
return { return {
@ -38,10 +37,10 @@ define([
); );
}, },
constrainPage: function (page) { constrainPage: function (page) {
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage()); return Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
}, },
handleSetManualPage: function (e) { handleSetManualPage: function (e) {
if(e.which === 13) { if (e.which === 13) {
this.setPage(this.state.page); this.setPage(this.state.page);
} }
}, },
@ -57,127 +56,127 @@ define([
return Math.ceil(this.props.count / this.props.limit); return Math.ceil(this.props.count / this.props.limit);
}, },
render: function () { render: function () {
if(this.props.count === 0) { if (this.props.count === 0) {
return false; return false;
} else { }
let pagination = false; let pagination = false;
let firstPage = ( let firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span> <span aria-hidden="true" className="tablenav-pages-navspan">«</span>
); );
let previousPage = ( let previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span> <span aria-hidden="true" className="tablenav-pages-navspan"></span>
); );
let nextPage = ( let nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span> <span aria-hidden="true" className="tablenav-pages-navspan"></span>
); );
let lastPage = ( let lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span> <span aria-hidden="true" className="tablenav-pages-navspan">»</span>
); );
if(this.props.limit > 0 && this.props.count > this.props.limit) { if (this.props.limit > 0 && this.props.count > this.props.limit) {
if(this.props.page > 1) { if (this.props.page > 1) {
previousPage = ( previousPage = (
<a href="javascript:;" <a href="javascript:;"
onClick={ this.setPreviousPage } onClick={this.setPreviousPage}
className="prev-page"> className="prev-page">
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span> <span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
); );
}
if(this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={ this.setFirstPage }
className="first-page">
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
}
if(this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={ this.setNextPage }
className="next-page">
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
}
if(this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={ this.setLastPage }
className="last-page">
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span>
</a>
);
}
let pageValue = this.props.page;
if(this.state.page !== null) {
pageValue = this.state.page;
}
pagination = (
<span className="pagination-links">
{firstPage}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
htmlFor="current-page-selector">{MailPoet.I18n.t('currentPage')}</label>
<input
type="text"
onChange={ this.handleChangeManualPage }
onKeyUp={ this.handleSetManualPage }
onBlur={ this.handleBlurManualPage }
aria-describedby="table-paging"
size="2"
ref="page"
value={ pageValue }
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
} }
const classes = classNames( if (this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={this.setFirstPage}
className="first-page">
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
}
if (this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={this.setNextPage}
className="next-page">
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
}
if (this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={this.setLastPage}
className="last-page">
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span>
</a>
);
}
let pageValue = this.props.page;
if (this.state.page !== null) {
pageValue = this.state.page;
}
pagination = (
<span className="pagination-links">
{firstPage}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
htmlFor="current-page-selector">{MailPoet.I18n.t('currentPage')}</label>
<input
type="text"
onChange={this.handleChangeManualPage}
onKeyUp={this.handleSetManualPage}
onBlur={this.handleBlurManualPage}
aria-describedby="table-paging"
size="2"
ref="page"
value={pageValue}
name="paged"
id="current-page-selector"
className="current-page" />
{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
}
const classes = classNames(
'tablenav-pages', 'tablenav-pages',
{ 'one-page': (this.props.count <= this.props.limit) } { 'one-page': (this.props.count <= this.props.limit) }
); );
let numberOfItemsLabel; let numberOfItemsLabel;
if (this.props.count == 1) { if (Number(this.props.count) === 1) {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular'); numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular');
} else { } else {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple') numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple')
.replace('%$1d', this.props.count.toLocaleString()); .replace('%$1d', parseInt(this.props.count, 10).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>
);
}, },
}); });

View File

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

View File

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

View File

@ -1,9 +1,10 @@
/* eslint-disable func-names */
define('modal', ['mailpoet', 'jquery'], define('modal', ['mailpoet', 'jquery'],
function(mp, jQuery) { function (mp, jQuery) {
'use strict'; 'use strict';
var MailPoet = mp; var MailPoet = mp;
/*************************************************************************** /** *************************************************************************
MailPoet Modal: MailPoet Modal:
version: 0.9 version: 0.9
@ -20,7 +21,7 @@ define('modal', ['mailpoet', 'jquery'],
// loading mode // loading mode
MailPoet.Modal.loading(bool); MailPoet.Modal.loading(bool);
***************************************************************************/ ************************************************************************** */
MailPoet.Modal = { MailPoet.Modal = {
version: 0.9, version: 0.9,
@ -79,44 +80,44 @@ define('modal', ['mailpoet', 'jquery'],
options: {}, options: {},
templates: { templates: {
overlay: '<div id="mailpoet_modal_overlay" style="display:none;"></div>', overlay: '<div id="mailpoet_modal_overlay" style="display:none;"></div>',
popup: '<div id="mailpoet_popup" tabindex="-1">'+ popup: '<div id="mailpoet_popup" tabindex="-1">' +
'<div class="mailpoet_popup_wrapper">'+ '<div class="mailpoet_popup_wrapper">' +
'<a href="javascript:;" id="mailpoet_modal_close"></a>'+ '<a href="javascript:;" id="mailpoet_modal_close"></a>' +
'<div id="mailpoet_popup_title"><h2></h2></div>'+ '<div id="mailpoet_popup_title"><h2></h2></div>' +
'<div class="mailpoet_popup_body clearfix"></div>'+ '<div class="mailpoet_popup_body clearfix"></div>' +
'</div>'+ '</div>' +
'</div>', '</div>',
loading: '<div id="mailpoet_loading" style="display:none;">'+ loading: '<div id="mailpoet_loading" style="display:none;">' +
'<div id="mailpoet_modal_loading_1" class="mailpoet_modal_loading"></div>'+ '<div id="mailpoet_modal_loading_1" class="mailpoet_modal_loading"></div>' +
'<div id="mailpoet_modal_loading_2" class="mailpoet_modal_loading"></div>'+ '<div id="mailpoet_modal_loading_2" class="mailpoet_modal_loading"></div>' +
'<div id="mailpoet_modal_loading_3" class="mailpoet_modal_loading"></div>'+ '<div id="mailpoet_modal_loading_3" class="mailpoet_modal_loading"></div>' +
'</div>', '</div>',
panel: '<div id="mailpoet_panel">'+ panel: '<div id="mailpoet_panel">' +
'<a href="javascript:;" id="mailpoet_modal_close"></a>'+ '<a href="javascript:;" id="mailpoet_modal_close"></a>' +
'<div class="mailpoet_panel_wrapper" tabindex="-1">'+ '<div class="mailpoet_panel_wrapper" tabindex="-1">' +
'<div class="mailpoet_panel_body clearfix"></div>'+ '<div class="mailpoet_panel_body clearfix"></div>' +
'</div>'+ '</div>' +
'</div>', '</div>',
subpanel: '<div class="mailpoet_panel_wrapper" tabindex="-1">'+ subpanel: '<div class="mailpoet_panel_wrapper" tabindex="-1">' +
'<div class="mailpoet_panel_body clearfix"></div>'+ '<div class="mailpoet_panel_body clearfix"></div>' +
'</div>' '</div>'
}, },
getContentContainer: function() { getContentContainer: function () {
return jQuery('.mailpoet_'+this.options.type+'_body'); return jQuery('.mailpoet_' + this.options.type + '_body');
}, },
setRenderer: function(renderer) { setRenderer: function (renderer) {
this.renderer = renderer; this.renderer = renderer;
return this; return this;
}, },
compileTemplate: function(template) { compileTemplate: function (template) {
if(this.renderer === 'html') { if (this.renderer === 'html') {
return function() { return template; }; return function () { return template; };
} else {
return window.Handlebars.compile(template);
} }
return window.Handlebars.compile(template);
}, },
init: function(options) { init: function (options) {
if(this.initialized === true) { var modal;
if (this.initialized === true) {
this.close(); this.close();
} }
// merge options // merge options
@ -131,10 +132,10 @@ define('modal', ['mailpoet', 'jquery'],
// toggle overlay // toggle overlay
this.toggleOverlay(this.options.overlay); this.toggleOverlay(this.options.overlay);
if(this.options.type !== null) { if (this.options.type !== null) {
// insert modal depending on its type // insert modal depending on its type
if(this.options.type === 'popup') { if (this.options.type === 'popup') {
var modal = this.compileTemplate( modal = this.compileTemplate(
this.templates[this.options.type] this.templates[this.options.type]
); );
// create modal // create modal
@ -143,7 +144,7 @@ define('modal', ['mailpoet', 'jquery'],
// set title // set title
jQuery('#mailpoet_popup_title h2') jQuery('#mailpoet_popup_title h2')
.html(this.options.title); .html(this.options.title);
} else if(this.options.type === 'panel') { } else if (this.options.type === 'panel') {
// create panel // create panel
jQuery('#mailpoet_modal_overlay') jQuery('#mailpoet_modal_overlay')
.after(this.templates[this.options.type]); .after(this.templates[this.options.type]);
@ -152,16 +153,16 @@ define('modal', ['mailpoet', 'jquery'],
// add proper overlay class // add proper overlay class
jQuery('#mailpoet_modal_overlay') jQuery('#mailpoet_modal_overlay')
.removeClass('mailpoet_popup_overlay mailpoet_panel_overlay') .removeClass('mailpoet_popup_overlay mailpoet_panel_overlay')
.addClass('mailpoet_'+this.options.type+'_overlay'); .addClass('mailpoet_' + this.options.type + '_overlay');
} }
// set "success" callback if specified // set "success" callback if specified
if(options.onSuccess !== undefined) { if (options.onSuccess !== undefined) {
this.options.onSuccess = options.onSuccess; this.options.onSuccess = options.onSuccess;
} }
// set "cancel" callback if specified // set "cancel" callback if specified
if(options.onCancel !== undefined) { if (options.onCancel !== undefined) {
this.options.onCancel = options.onCancel; this.options.onCancel = options.onCancel;
} }
@ -178,8 +179,8 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
initOverlay: function(toggle) { initOverlay: function () {
if(jQuery('#mailpoet_modal_overlay').length === 0) { if (jQuery('#mailpoet_modal_overlay').length === 0) {
// insert overlay into the DOM // insert overlay into the DOM
jQuery('body').append(this.templates.overlay); jQuery('body').append(this.templates.overlay);
// insert loading indicator into overlay // insert loading indicator into overlay
@ -187,8 +188,8 @@ define('modal', ['mailpoet', 'jquery'],
} }
return this; return this;
}, },
toggleOverlay: function(toggle) { toggleOverlay: function (toggle) {
if(toggle === true) { if (toggle === true) {
jQuery('#mailpoet_modal_overlay') jQuery('#mailpoet_modal_overlay')
.removeClass('mailpoet_overlay_hidden'); .removeClass('mailpoet_overlay_hidden');
} else { } else {
@ -198,77 +199,78 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
setupEvents: function() { setupEvents: function () {
// close popup when user clicks on close button // close popup when user clicks on close button
jQuery('#mailpoet_modal_close').on('click', this.cancel.bind(this)); jQuery('#mailpoet_modal_close').on('click', this.cancel.bind(this));
// close popup when user clicks on overlay // close popup when user clicks on overlay
jQuery('#mailpoet_modal_overlay').on('click', function(e) { jQuery('#mailpoet_modal_overlay').on('click', function (e) {
// we need to make sure that we are actually clicking on the overlay // we need to make sure that we are actually clicking on the overlay
// because when clicking on the popup content, it will trigger // because when clicking on the popup content, it will trigger
// the click event on the overlay // the click event on the overlay
if(e.target.id === 'mailpoet_modal_overlay') { this.cancel(); } if (e.target.id === 'mailpoet_modal_overlay') { this.cancel(); }
}.bind(this)); }.bind(this));
// close popup when user presses ESC key // close popup when user presses ESC key
jQuery(document).on('keyup.mailpoet_modal', function(e) { jQuery(document).on('keyup.mailpoet_modal', function (e) {
if(this.opened === false) { return false; } if (this.opened === false) { return false; }
if(e.keyCode === 27) { this.cancel(); } if (e.keyCode === 27) { this.cancel(); }
return true;
}.bind(this)); }.bind(this));
// make sure the popup is repositioned when the window is resized // make sure the popup is repositioned when the window is resized
jQuery(window).on('resize.mailpoet_modal', function() { jQuery(window).on('resize.mailpoet_modal', function () {
this.setPosition(); this.setPosition();
}.bind(this)); }.bind(this));
return this; return this;
}, },
removeEvents: function() { removeEvents: function () {
jQuery(document).unbind('keyup.mailpoet_modal'); jQuery(document).unbind('keyup.mailpoet_modal');
jQuery(window).unbind('resize.mailpoet_modal'); jQuery(window).unbind('resize.mailpoet_modal');
jQuery('#mailpoet_modal_close').off('click'); jQuery('#mailpoet_modal_close').off('click');
if(this.options.overlay === true) { if (this.options.overlay === true) {
jQuery('#mailpoet_modal_overlay').off('click'); jQuery('#mailpoet_modal_overlay').off('click');
} }
return this; return this;
}, },
lock: function() { lock: function () {
this.locked = true; this.locked = true;
return this; return this;
}, },
unlock: function() { unlock: function () {
this.locked = false; this.locked = false;
return this; return this;
}, },
isLocked: function() { isLocked: function () {
return this.locked; return this.locked;
}, },
loadTemplate: function() { loadTemplate: function () {
if(this.subpanels.length > 0) { if (this.subpanels.length > 0) {
// hide panel // hide panel
jQuery('.mailpoet_'+this.options.type+'_wrapper').hide(); jQuery('.mailpoet_' + this.options.type + '_wrapper').hide();
// add sub panel wrapper // add sub panel wrapper
jQuery('#mailpoet_'+this.options.type) jQuery('#mailpoet_' + this.options.type)
.append(this.templates['subpanel']); .append(this.templates.subpanel);
// add sub panel content // add sub panel content
jQuery('.mailpoet_'+this.options.type+'_body').last() jQuery('.mailpoet_' + this.options.type + '_body').last()
.html(this.subpanels[(this.subpanels.length - 1)].element); .html(this.subpanels[(this.subpanels.length - 1)].element);
// focus on sub panel // focus on sub panel
if(this.options.focus) { if (this.options.focus) {
this.focus(); this.focus();
} }
} else if (this.options.element) { } else if (this.options.element) {
jQuery('.mailpoet_'+this.options.type+'_body').empty(); jQuery('.mailpoet_' + this.options.type + '_body').empty();
jQuery('.mailpoet_'+this.options.type+'_body') jQuery('.mailpoet_' + this.options.type + '_body')
.append(this.options.element); .append(this.options.element);
} else { } else {
jQuery('.mailpoet_'+this.options.type+'_body') jQuery('.mailpoet_' + this.options.type + '_body')
.html( .html(
this.options.body_template( this.options.body_template(
this.options.data this.options.data
@ -278,11 +280,11 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
loadUrl: function() { loadUrl: function () {
if(this.options.method === 'get') { if (this.options.method === 'get') {
// make ajax request // make ajax request
jQuery.getJSON(this.options.url, jQuery.getJSON(this.options.url,
function(data) { function (data) {
this.options.data = jQuery.extend({}, this.options.data, data); this.options.data = jQuery.extend({}, this.options.data, data);
// load template using fetched data // load template using fetched data
this.loadTemplate(); this.loadTemplate();
@ -290,10 +292,10 @@ define('modal', ['mailpoet', 'jquery'],
this.showModal(); this.showModal();
}.bind(this) }.bind(this)
); );
} else if(this.options.method === 'post') { } else if (this.options.method === 'post') {
// make ajax request // make ajax request
jQuery.post(this.options.url, JSON.stringify(this.options.params), jQuery.post(this.options.url, JSON.stringify(this.options.params),
function(data) { function (data) {
this.options.data = jQuery.extend({}, this.options.data, data); this.options.data = jQuery.extend({}, this.options.data, data);
// load template using fetched data // load template using fetched data
this.loadTemplate(); this.loadTemplate();
@ -306,8 +308,8 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
setDimensions: function() { setDimensions: function () {
switch(this.options.type) { switch (this.options.type) {
case 'popup': case 'popup':
// set popup dimensions // set popup dimensions
jQuery('#mailpoet_popup').css({ jQuery('#mailpoet_popup').css({
@ -321,14 +323,14 @@ define('modal', ['mailpoet', 'jquery'],
break; break;
case 'panel': case 'panel':
// set dimensions // set dimensions
if(this.options.position === 'right') { if (this.options.position === 'right') {
jQuery('#mailpoet_panel').css({ jQuery('#mailpoet_panel').css({
width: this.options.width, width: this.options.width,
right: 0, right: 0,
marginRight: '-' + this.options.width, marginRight: '-' + this.options.width,
left: 'auto' left: 'auto'
}); });
} else if(this.options.position === 'left') { } else if (this.options.position === 'left') {
jQuery('#mailpoet_panel').css({ jQuery('#mailpoet_panel').css({
width: this.options.width, width: this.options.width,
left: 0, left: 0,
@ -342,31 +344,32 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
setPosition: function() { setPosition: function () {
switch(this.options.type) { var screenWidth;
var screenHeight;
var modalWidth;
var modalHeight;
switch (this.options.type) {
case 'popup': case 'popup':
var screenWidth = jQuery(window).width(), screenWidth = jQuery(window).width();
screenHeight = jQuery(window).height(), screenHeight = jQuery(window).height();
modalWidth = jQuery('.mailpoet_'+ this.options.type +'_wrapper').width(), modalWidth = jQuery('.mailpoet_'+ this.options.type +'_wrapper').width();
modalHeight = jQuery('.mailpoet_'+ this.options.type +'_wrapper').height(); modalHeight = jQuery('.mailpoet_'+ this.options.type +'_wrapper').height();
var top = Math.max(48, parseInt((screenHeight / 2) - (modalHeight / 2))), // set position of popup depending on screen dimensions.
left = Math.max(0, parseInt((screenWidth / 2) - (modalWidth / 2)));
// set position of popup depending on screen dimensions.
jQuery('#mailpoet_popup').css({ jQuery('#mailpoet_popup').css({
top: top, top: Math.max(48, parseInt((screenHeight / 2) - (modalHeight / 2))),
left: left left: Math.max(0, parseInt((screenWidth / 2) - (modalWidth / 2)))
}); });
break; break;
case 'panel': case 'panel':
setTimeout(function() { setTimeout(function () {
// set position of popup depending on screen dimensions. // set position of popup depending on screen dimensions.
if(this.options.position === 'right') { if (this.options.position === 'right') {
jQuery('#mailpoet_panel').css({ jQuery('#mailpoet_panel').css({
marginRight: 0 marginRight: 0
}); });
} else if(this.options.position === 'left') { } else if (this.options.position === 'left') {
jQuery('#mailpoet_panel').css({ jQuery('#mailpoet_panel').css({
marginLeft: 0 marginLeft: 0
}); });
@ -377,18 +380,15 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
showModal: function() { showModal: function () {
// set modal dimensions // set modal dimensions
this.setDimensions(); this.setDimensions();
// remember the previously focused element // remember the previously focused element
this.prevFocus = jQuery(':focus'); this.prevFocus = jQuery(':focus');
// add a flag on the body so that we can prevent scrolling
jQuery('body').addClass('mailpoet_modal_opened');
// show popup // show popup
jQuery('#mailpoet_'+this.options.type).show(); jQuery('#mailpoet_' + this.options.type).show();
// display overlay // display overlay
this.showOverlay(); this.showOverlay();
@ -397,13 +397,13 @@ define('modal', ['mailpoet', 'jquery'],
this.setPosition(); this.setPosition();
// add class on highlighted elements // add class on highlighted elements
if(this.options.highlight !== null) { if (this.options.highlight !== null) {
if(this.options.highlight.length > 0) { if (this.options.highlight.length > 0) {
this.highlightOn(this.options.highlight); this.highlightOn(this.options.highlight);
} }
} }
if(this.options.focus) { if (this.options.focus) {
this.focus(); this.focus();
} }
@ -411,55 +411,52 @@ define('modal', ['mailpoet', 'jquery'],
this.opened = true; this.opened = true;
// trigger init event if specified // trigger init event if specified
if(this.options.onInit !== null) { if (this.options.onInit !== null) {
this.options.onInit(this); this.options.onInit(this);
} }
return this; return this;
}, },
focus: function() { focus: function () {
if(this.options.type == 'popup') { if (this.options.type == 'popup') {
jQuery('#mailpoet_'+this.options.type).focus(); jQuery('#mailpoet_' + this.options.type).focus();
} else { } else {
// panel and subpanel // panel and subpanel
jQuery('#mailpoet_'+this.options.type+' .mailpoet_panel_wrapper') jQuery('#mailpoet_' + this.options.type + ' .mailpoet_panel_wrapper')
.filter(':visible').focus(); .filter(':visible').focus();
} }
return this; return this;
}, },
highlightOn: function(element) { highlightOn: function (element) {
jQuery(element).addClass('mailpoet_modal_highlight'); jQuery(element).addClass('mailpoet_modal_highlight');
return this; return this;
}, },
highlightOff: function() { highlightOff: function () {
jQuery('.mailpoet_modal_highlight') jQuery('.mailpoet_modal_highlight')
.removeClass('mailpoet_modal_highlight'); .removeClass('mailpoet_modal_highlight');
return this; return this;
}, },
hideModal: function(callback) { hideModal: function () {
// set modal as closed // set modal as closed
this.opened = false; this.opened = false;
// hide modal // hide modal
jQuery('#mailpoet_'+this.options.type).hide(); jQuery('#mailpoet_' + this.options.type).hide();
// remove class on highlighted elements // remove class on highlighted elements
this.highlightOff(); this.highlightOff();
// remove class from body to let it be scrollable
jQuery('body').removeClass('mailpoet_modal_opened');
return this; return this;
}, },
showOverlay: function(force) { showOverlay: function () {
jQuery('#mailpoet_modal_overlay').show(); jQuery('#mailpoet_modal_overlay').show();
return this; return this;
}, },
hideOverlay: function() { hideOverlay: function () {
jQuery('#mailpoet_modal_overlay').hide(); jQuery('#mailpoet_modal_overlay').hide();
return this; return this;
}, },
popup: function(opts) { popup: function (opts) {
// get options // get options
var options = opts || {}; var options = opts || {};
// set modal type // set modal type
@ -473,7 +470,7 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
panel: function(opts) { panel: function (opts) {
// get options // get options
var options = opts || {}; var options = opts || {};
// reset subpanels // reset subpanels
@ -494,8 +491,8 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
subpanel: function(options) { subpanel: function (options) {
if(this.opened === false) { if (this.opened === false) {
// if no panel is already opened, let's create one instead // if no panel is already opened, let's create one instead
this.panel(options); this.panel(options);
} else { } else {
@ -506,11 +503,11 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
loading: function(toggle) { loading: function (toggle) {
// make sure the overlay is initialized and that it's visible // make sure the overlay is initialized and that it's visible
this.initOverlay(true); this.initOverlay(true);
if(toggle === true) { if (toggle === true) {
this.showLoading(); this.showLoading();
} else { } else {
this.hideLoading(); this.hideLoading();
@ -518,7 +515,7 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
showLoading: function() { showLoading: function () {
jQuery('#mailpoet_loading').show(); jQuery('#mailpoet_loading').show();
// add loading class to overlay // add loading class to overlay
@ -527,7 +524,7 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
hideLoading: function() { hideLoading: function () {
jQuery('#mailpoet_loading').hide(); jQuery('#mailpoet_loading').hide();
// remove loading class from overlay // remove loading class from overlay
@ -536,11 +533,11 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
open: function() { open: function () {
// load template if specified // load template if specified
if(this.options.template !== null) { if (this.options.template !== null) {
// check if a url was specified to get extra data // check if a url was specified to get extra data
if(this.options.url !== null) { if (this.options.url !== null) {
this.loadUrl(); this.loadUrl();
} else { } else {
// load template // load template
@ -555,13 +552,13 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
success: function() { success: function () {
if(this.subpanels.length > 0) { if (this.subpanels.length > 0) {
if(this.subpanels[(this.subpanels.length - 1)].onSuccess !== undefined) { if (this.subpanels[(this.subpanels.length - 1)].onSuccess !== undefined) {
this.subpanels[(this.subpanels.length - 1)].onSuccess(this.subpanels[(this.subpanels.length - 1)].data); this.subpanels[(this.subpanels.length - 1)].onSuccess(this.subpanels[(this.subpanels.length - 1)].data);
} }
} else { } else {
if(this.options.onSuccess !== null) { if (this.options.onSuccess !== null) {
this.options.onSuccess(this.options.data); this.options.onSuccess(this.options.data);
} }
} }
@ -569,13 +566,13 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
cancel: function() { cancel: function () {
if(this.subpanels.length > 0) { if (this.subpanels.length > 0) {
if(this.subpanels[(this.subpanels.length - 1)].onCancel !== undefined) { if (this.subpanels[(this.subpanels.length - 1)].onCancel !== undefined) {
this.subpanels[(this.subpanels.length - 1)].onCancel(this.subpanels[(this.subpanels.length - 1)].data); this.subpanels[(this.subpanels.length - 1)].onCancel(this.subpanels[(this.subpanels.length - 1)].data);
} }
} else { } else {
if(this.options.onCancel !== null) { if (this.options.onCancel !== null) {
this.options.onCancel(this.options.data); this.options.onCancel(this.options.data);
} }
} }
@ -583,33 +580,33 @@ define('modal', ['mailpoet', 'jquery'],
return this; return this;
}, },
destroy: function() { destroy: function () {
this.hideOverlay(); this.hideOverlay();
// remove extra modal // remove extra modal
if(jQuery('#mailpoet_'+this.options.type).length > 0) { if (jQuery('#mailpoet_' + this.options.type).length > 0) {
jQuery('#mailpoet_'+this.options.type).remove(); jQuery('#mailpoet_' + this.options.type).remove();
} }
this.initialized = false; this.initialized = false;
return this; return this;
}, },
close: function() { close: function () {
if(this.isLocked() === true) { return this; } if (this.isLocked() === true) { return this; }
if(this.subpanels.length > 0) { if (this.subpanels.length > 0) {
// close subpanel // close subpanel
jQuery('.mailpoet_'+this.options.type+'_wrapper').last().remove(); jQuery('.mailpoet_' + this.options.type + '_wrapper').last().remove();
// show previous panel // show previous panel
jQuery('.mailpoet_'+this.options.type+'_wrapper').last().show(); jQuery('.mailpoet_' + this.options.type + '_wrapper').last().show();
// remove last subpanels // remove last subpanels
this.subpanels.pop(); this.subpanels.pop();
// focus on previous panel // focus on previous panel
if(this.options.focus) { if (this.options.focus) {
this.focus(); this.focus();
} }
@ -626,7 +623,7 @@ define('modal', ['mailpoet', 'jquery'],
this.destroy(); this.destroy();
// restore the previously focused element // restore the previously focused element
if(this.prevFocus !== undefined){ if (this.prevFocus !== undefined) {
this.prevFocus.focus(); this.prevFocus.focus();
} }

View File

@ -1,4 +1,5 @@
define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) { /* eslint-disable func-names */
define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
'use strict'; 'use strict';
var MailPoet = mp; var MailPoet = mp;
@ -32,20 +33,19 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) {
jQuery('#logger').html(''); jQuery('#logger').html('');
result.split('\n').forEach(function (resultRow) { result.split('\n').forEach(function (resultRow) {
var row = resultRow; var row = resultRow;
if(row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) { if (row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red
} }
// Test if the import is complete // Test if the import is complete
else if(row === MailPoet.I18n.t('import_complete')) { else if (row === MailPoet.I18n.t('import_complete')) {
jQuery('#import-actions').hide(); jQuery('#import-actions').hide();
jQuery('#upgrade-completed').show(); jQuery('#upgrade-completed').show();
} }
jQuery('#logger').append(row + '<br />\n'); jQuery('#logger').append(row + '<br />\n');
}); });
jQuery('#logger').append('<span class="error_msg">' + MailPoet.MP2Migrator.fatal_error + '</span>' + '<br />\n'); jQuery('#logger').append('<span class="error_msg">' + MailPoet.MP2Migrator.fatal_error + '</span>' + '<br />\n');
}).always(function () { }).always(function () {
if(MailPoet.MP2Migrator.is_logging) { if (MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000); MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000);
} }
}); });
@ -59,17 +59,17 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) {
}).always(function (result) { }).always(function (result) {
// Move the progress bar // Move the progress bar
var progress = 0; var progress = 0;
if((result.total !== undefined) && (Number(result.total) !== 0)) { if ((result.total !== undefined) && (Number(result.total) !== 0)) {
progress = Math.round(Number(result.current) / Number(result.total) * 100); progress = Math.round(Number(result.current) / Number(result.total) * 100);
} }
jQuery('#progressbar').progressbar('option', 'value', progress); jQuery('#progressbar').progressbar('option', 'value', progress);
jQuery('#progresslabel').html(progress + '%'); jQuery('#progresslabel').html(progress + '%');
if(Number(result.current) !== 0) { if (Number(result.current) !== 0) {
jQuery('#skip-import').hide(); jQuery('#skip-import').hide();
jQuery('#progressbar').show(); jQuery('#progressbar').show();
jQuery('#logger-container').show(); jQuery('#logger-container').show();
} }
if(MailPoet.MP2Migrator.is_logging) { if (MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000); MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000);
} }
}); });
@ -100,16 +100,16 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) {
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
MailPoet.MP2Migrator.reactivateImportButton(); MailPoet.MP2Migrator.reactivateImportButton();
}).done(function (response) { }).done(function (response) {
if(response) { if (response) {
MailPoet.MP2Migrator.fatal_error = response.data; MailPoet.MP2Migrator.fatal_error = response.data;
} }
}).fail(function (response) { }).fail(function (response) {
if(response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function (error) { response.errors.map(function (error) {
return error.message; return error.message;
}), }),
{scroll: true} { scroll: true }
); );
} }
}); });
@ -135,12 +135,12 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) {
MailPoet.MP2Migrator.reactivateImportButton(); MailPoet.MP2Migrator.reactivateImportButton();
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
}).fail(function (response) { }).fail(function (response) {
if(response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function (error) { response.errors.map(function (error) {
return error.message; return error.message;
}), }),
{scroll: true} { scroll: true }
); );
} }
}); });
@ -158,12 +158,12 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) {
}).done(function () { }).done(function () {
MailPoet.MP2Migrator.gotoWelcomePage(); MailPoet.MP2Migrator.gotoWelcomePage();
}).fail(function (response) { }).fail(function (response) {
if(response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function (error) { response.errors.map(function (error) {
return error.message; return error.message;
}), }),
{scroll: true} { scroll: true }
); );
} }
}); });
@ -176,35 +176,34 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) {
} }
}; };
/** /**
* Actions to run when the DOM is ready * Actions to run when the DOM is ready
*/ */
jQuery(function () { jQuery(function () {
jQuery('#progressbar').progressbar({value: 0}); jQuery('#progressbar').progressbar({ value: 0 });
// Import button // Import button
jQuery('#import').click(function() { jQuery('#import').click(function () {
MailPoet.MP2Migrator.startImport(); MailPoet.MP2Migrator.startImport();
}); });
// Stop import button // Stop import button
jQuery('#stop-import').click(function() { jQuery('#stop-import').click(function () {
MailPoet.MP2Migrator.stopImport(); MailPoet.MP2Migrator.stopImport();
}); });
// Skip import link // Skip import link
jQuery('#skip-import').click(function() { jQuery('#skip-import').click(function () {
MailPoet.MP2Migrator.skipImport(); MailPoet.MP2Migrator.skipImport();
}); });
// Go to welcome page // Go to welcome page
jQuery('#goto-welcome').click(function() { jQuery('#goto-welcome').click(function () {
MailPoet.MP2Migrator.gotoWelcomePage(); MailPoet.MP2Migrator.gotoWelcomePage();
}); });
// Update the display // Update the display
MailPoet.MP2Migrator.updateDisplay(); MailPoet.MP2Migrator.updateDisplay();
}); });
}); });

View File

@ -1,12 +1,8 @@
define([ define([
'backbone', 'backbone',
'backbone.marionette', 'backbone.marionette',
'backbone.radio', 'backbone.radio'
'jquery', ], function (Backbone, Marionette, BackboneRadio) { // eslint-disable-line func-names
'underscore',
'handlebars',
'handlebars_helpers'
], function(Backbone, Marionette, BackboneRadio, jQuery, _, Handlebars) {
var Radio = BackboneRadio; var Radio = BackboneRadio;
var AppView = Marionette.View.extend({ var AppView = Marionette.View.extend({
@ -23,12 +19,12 @@ define([
var EditorApplication = Marionette.Application.extend({ var EditorApplication = Marionette.Application.extend({
region: '#mailpoet_editor', region: '#mailpoet_editor',
onStart: function() { onStart: function () { // eslint-disable-line func-names
this._appView = new AppView(); this._appView = new AppView();
this.showView(this._appView); this.showView(this._appView);
}, },
getChannel: function(channel) { getChannel: function (channel) { // eslint-disable-line func-names
if (channel === undefined) { if (channel === undefined) {
return Radio.channel('global'); return Radio.channel('global');
} }
@ -40,5 +36,4 @@ define([
window.EditorApplication = app; window.EditorApplication = app;
return app; return app;
}); });

View File

@ -6,10 +6,10 @@
*/ */
define([ define([
'backbone.marionette' 'backbone.marionette'
], function(BackboneMarionette) { ], function (BackboneMarionette) { // eslint-disable-line func-names
var Marionette = BackboneMarionette; var Marionette = BackboneMarionette;
var BehaviorsLookup = {}; var BehaviorsLookup = {};
Marionette.Behaviors.behaviorsLookup = function() { Marionette.Behaviors.behaviorsLookup = function () { // eslint-disable-line func-names
return BehaviorsLookup; return BehaviorsLookup;
}; };

View File

@ -8,17 +8,17 @@ define([
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup',
'mailpoet', 'mailpoet',
'spectrum' 'spectrum'
], function(Marionette, BehaviorsLookup, MailPoet, Spectrum) { ], function (Marionette, BehaviorsLookup, MailPoet) { // eslint-disable-line func-names
var BL = BehaviorsLookup; var BL = BehaviorsLookup;
BL.ColorPickerBehavior = Marionette.Behavior.extend({ BL.ColorPickerBehavior = Marionette.Behavior.extend({
onRender: function() { onRender: function () { // eslint-disable-line func-names
var that = this, var that = this;
preferredFormat = 'hex6'; var preferredFormat = 'hex6';
this.view.$('.mailpoet_color').each(function () { this.view.$('.mailpoet_color').each(function () { // eslint-disable-line func-names
var $input = that.view.$(this); var $input = that.view.$(this);
var updateColorInput = function(color) { var updateColorInput = function (color) { // eslint-disable-line func-names
if(color && color.getAlpha() > 0) { if (color && color.getAlpha() > 0) {
$input.val(color.toString(preferredFormat)); $input.val(color.toString(preferredFormat));
} else { } else {
$input.val(''); $input.val('');
@ -41,7 +41,7 @@ define([
move: updateColorInput, move: updateColorInput,
hide: updateColorInput hide: updateColorInput
}); });
}); });
} }
}); });
}); });

View File

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

View File

@ -10,7 +10,7 @@ define([
'jquery', 'jquery',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup',
'interact' 'interact'
], function(Marionette, _, jQuery, BehaviorsLookup, interact) { ], function (Marionette, _, jQuery, BehaviorsLookup, interact) { // eslint-disable-line func-names
var BL = BehaviorsLookup; var BL = BehaviorsLookup;
BL.DraggableBehavior = Marionette.Behavior.extend({ BL.DraggableBehavior = Marionette.Behavior.extend({
@ -24,16 +24,16 @@ define([
* *
* @return Backbone.Model A model that will be passed to the receiver * @return Backbone.Model A model that will be passed to the receiver
*/ */
getDropModel: function() { getDropModel: function () { // eslint-disable-line func-names
throw "Missing 'drop' function for DraggableBehavior"; throw "Missing 'drop' function for DraggableBehavior";
}, },
onDrop: function(model, view) {}, onDrop: function () {}, // eslint-disable-line func-names
testAttachToInstance: function(model, view) { return true; } testAttachToInstance: function () { return true; } // eslint-disable-line func-names
}, },
onRender: function() { onRender: function () { // eslint-disable-line func-names
var that = this, var that = this;
interactable; var interactable;
// Give instances more control over whether Draggable should be applied // Give instances more control over whether Draggable should be applied
if (!this.options.testAttachToInstance(this.view.model, this.view)) return; if (!this.options.testAttachToInstance(this.view.model, this.view)) return;
@ -47,18 +47,21 @@ define([
// Scroll when dragging near edges of a window // Scroll when dragging near edges of a window
autoScroll: true, autoScroll: true,
onstart: function(startEvent) { onstart: function (startEvent) { // eslint-disable-line func-names
var event = startEvent; var event = startEvent;
var centerXOffset;
var centerYOffset;
var tempClone;
var clone;
var $clone;
if (that.options.cloneOriginal === true) { if (that.options.cloneOriginal === true) {
// Use substitution instead of a clone // Use substitution instead of a clone
var tempClone = (_.isFunction(that.options.onDragSubstituteBy)) ? that.options.onDragSubstituteBy(that) : undefined, tempClone = (_.isFunction(that.options.onDragSubstituteBy)) ? that.options.onDragSubstituteBy(that) : undefined;
// Or use a clone // Or use a clone
clone = tempClone || event.target.cloneNode(true), clone = tempClone || event.target.cloneNode(true);
jQuery(event.target);
$original = jQuery(event.target), $clone = jQuery(clone);
$clone = jQuery(clone),
centerXOffset, centerYOffset, parentOffset;
$clone.addClass('mailpoet_droppable_active'); $clone.addClass('mailpoet_droppable_active');
$clone.css('position', 'absolute'); $clone.css('position', 'absolute');
@ -71,7 +74,7 @@ define([
// Accurate dimensions can only be taken after insertion to document // Accurate dimensions can only be taken after insertion to document
centerXOffset = $clone.width() / 2; centerXOffset = $clone.width() / 2;
centerYOffset = $clone.height() / 2; centerYOffset = $clone.height() / 2;
$clone.css('top', event.pageY - centerYOffset); $clone.css('top', event.pageY - centerYOffset);
$clone.css('left', event.pageX - centerXOffset); $clone.css('left', event.pageX - centerXOffset);
event.interaction.element = clone; event.interaction.element = clone;
@ -81,14 +84,13 @@ define([
that.view.$el.addClass('mailpoet_hidden'); that.view.$el.addClass('mailpoet_hidden');
} }
} }
}, },
// call this function on every dragmove event // call this function on every dragmove event
onmove: function (event) { onmove: function (event) { // eslint-disable-line func-names
var target = event.target, var target = event.target;
// keep the dragged position in the data-x/data-y attributes // keep the dragged position in the data-x/data-y attributes
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx, var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
// translate the element // translate the element
target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'; target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
@ -98,7 +100,7 @@ define([
target.setAttribute('data-x', x); target.setAttribute('data-x', x);
target.setAttribute('data-y', y); target.setAttribute('data-y', y);
}, },
onend: function (event) { onend: function (event) { // eslint-disable-line func-names
var target = event.target; var target = event.target;
target.style.transform = ''; target.style.transform = '';
target.style.webkitTransform = target.style.transform; target.style.webkitTransform = target.style.transform;
@ -117,7 +119,7 @@ define([
}) })
.preventDefault('auto') .preventDefault('auto')
.styleCursor(false) .styleCursor(false)
.actionChecker(function (pointer, event, action) { .actionChecker(function (pointer, event, action) { // eslint-disable-line func-names
// Disable dragging with right click // Disable dragging with right click
if (event.button !== 0) { if (event.button !== 0) {
return null; return null;
@ -131,7 +133,7 @@ define([
} else { } else {
interactable.getDropModel = this.view.getDropFunc(); interactable.getDropModel = this.view.getDropFunc();
} }
interactable.onDrop = function(opts) { interactable.onDrop = function (opts) { // eslint-disable-line func-names
var options = opts; var options = opts;
if (_.isObject(options)) { if (_.isObject(options)) {
// Inject Draggable behavior if possible // Inject Draggable behavior if possible

View File

@ -6,7 +6,7 @@
define([ define([
'backbone.marionette', 'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup' 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, BehaviorsLookup) { ], function (Marionette, BehaviorsLookup) { // eslint-disable-line func-names
var BL = BehaviorsLookup; var BL = BehaviorsLookup;
BL.HighlightContainerBehavior = Marionette.Behavior.extend({ BL.HighlightContainerBehavior = Marionette.Behavior.extend({
@ -14,10 +14,10 @@ define([
'mouseenter @ui.tools': 'enableHighlight', 'mouseenter @ui.tools': 'enableHighlight',
'mouseleave @ui.tools': 'disableHighlight' 'mouseleave @ui.tools': 'disableHighlight'
}, },
enableHighlight: function() { enableHighlight: function () { // eslint-disable-line func-names
this.$el.addClass('mailpoet_highlight'); this.$el.addClass('mailpoet_highlight');
}, },
disableHighlight: function() { disableHighlight: function () { // eslint-disable-line func-names
if (!this.view._isBeingEdited) { if (!this.view._isBeingEdited) {
this.$el.removeClass('mailpoet_highlight'); this.$el.removeClass('mailpoet_highlight');
} }

View File

@ -6,7 +6,7 @@
define([ define([
'backbone.marionette', 'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup' 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, BehaviorsLookup) { ], function (Marionette, BehaviorsLookup) { // eslint-disable-line func-names
var BL = BehaviorsLookup; var BL = BehaviorsLookup;
BL.HighlightEditingBehavior = Marionette.Behavior.extend({ BL.HighlightEditingBehavior = Marionette.Behavior.extend({
@ -14,11 +14,11 @@ define([
startEditing: 'enableHighlight', startEditing: 'enableHighlight',
stopEditing: 'disableHighlight' stopEditing: 'disableHighlight'
}, },
enableHighlight: function() { enableHighlight: function () { // eslint-disable-line func-names
this.view._isBeingEdited = true; this.view._isBeingEdited = true;
this.$el.addClass('mailpoet_highlight'); this.$el.addClass('mailpoet_highlight');
}, },
disableHighlight: function() { disableHighlight: function () { // eslint-disable-line func-names
this.view._isBeingEdited = false; this.view._isBeingEdited = false;
this.$el.removeClass('mailpoet_highlight'); this.$el.removeClass('mailpoet_highlight');
} }

View File

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

View File

@ -7,7 +7,7 @@ define([
'backbone.marionette', 'backbone.marionette',
'jquery', 'jquery',
'newsletter_editor/behaviors/BehaviorsLookup' 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, jQuery, BehaviorsLookup) { ], function (Marionette, jQuery, BehaviorsLookup) { // eslint-disable-line func-names
var BL = BehaviorsLookup; var BL = BehaviorsLookup;
BL.ShowSettingsBehavior = Marionette.Behavior.extend({ BL.ShowSettingsBehavior = Marionette.Behavior.extend({
@ -17,12 +17,12 @@ define([
events: { events: {
'click .mailpoet_content': 'showSettings' 'click .mailpoet_content': 'showSettings'
}, },
showSettings: function(event) { showSettings: function (event) { // eslint-disable-line func-names
if(!this.isIgnoredElement(event.target)) { if (!this.isIgnoredElement(event.target)) {
this.view.triggerMethod('showSettings'); this.view.triggerMethod('showSettings');
} }
}, },
isIgnoredElement: function(element) { isIgnoredElement: function (element) { // eslint-disable-line func-names
return this.options.ignoreFrom return this.options.ignoreFrom
&& this.options.ignoreFrom.length > 0 && this.options.ignoreFrom.length > 0
&& jQuery(element).is(this.options.ignoreFrom); && jQuery(element).is(this.options.ignoreFrom);

View File

@ -7,26 +7,26 @@ define([
'backbone.marionette', 'backbone.marionette',
'underscore', 'underscore',
'newsletter_editor/behaviors/BehaviorsLookup' 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, _, BehaviorsLookup) { ], function (Marionette, _, BehaviorsLookup) { // eslint-disable-line func-names
var BL = BehaviorsLookup; var BL = BehaviorsLookup;
BL.SortableBehavior = Marionette.Behavior.extend({ BL.SortableBehavior = Marionette.Behavior.extend({
onRender: function() { onRender: function () { // eslint-disable-line func-names
var collection = this.view.collection; var collection = this.view.collection;
if (_.isFunction(this.$el.sortable)) { if (_.isFunction(this.$el.sortable)) {
this.$el.sortable({ this.$el.sortable({
cursor: 'move', cursor: 'move',
start: function(event, ui) { start: function (event, ui) { // eslint-disable-line func-names
ui.item.data('previousIndex', ui.item.index()); ui.item.data('previousIndex', ui.item.index());
}, },
end: function(event, ui) { end: function (event, ui) { // eslint-disable-line func-names
ui.item.removeData('previousIndex'); ui.item.removeData('previousIndex');
}, },
update: function(event, ui) { update: function (event, ui) { // eslint-disable-line func-names
var previousIndex = ui.item.data('previousIndex'), var previousIndex = ui.item.data('previousIndex');
newIndex = ui.item.index(), var newIndex = ui.item.index();
model = collection.at(previousIndex); var model = collection.at(previousIndex);
// Replicate DOM changes. Move target model to a new position // Replicate DOM changes. Move target model to a new position
// within the collection // within the collection

View File

@ -7,7 +7,7 @@ define([
'backbone.marionette', 'backbone.marionette',
'underscore', 'underscore',
'newsletter_editor/behaviors/BehaviorsLookup' 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, _, BehaviorsLookup) { ], function (Marionette, _, BehaviorsLookup) { // eslint-disable-line func-names
var BL = BehaviorsLookup; var BL = BehaviorsLookup;
BL.TextEditorBehavior = Marionette.Behavior.extend({ BL.TextEditorBehavior = Marionette.Behavior.extend({
@ -19,9 +19,9 @@ define([
invalidElements: 'script', invalidElements: 'script',
blockFormats: 'Paragraph=p', blockFormats: 'Paragraph=p',
plugins: 'link textcolor colorpicker mailpoet_shortcodes', plugins: 'link textcolor colorpicker mailpoet_shortcodes',
configurationFilter: function(originalConfig) { return originalConfig; } configurationFilter: function (originalConfig) { return originalConfig; } // eslint-disable-line func-names
}, },
onDomRefresh: function() { onDomRefresh: function () { // eslint-disable-line func-names
var that = this; var that = this;
if (this.view.disableTextEditor === true) { if (this.view.disableTextEditor === true) {
return; return;
@ -42,7 +42,7 @@ define([
relative_urls: false, relative_urls: false,
remove_script_host: false, remove_script_host: false,
convert_urls: true, convert_urls: true,
urlconverter_callback: function(url, node, on_save, name) { urlconverter_callback: function (url) { // eslint-disable-line func-names
if (url.match(/\[.+\]/g)) { if (url.match(/\[.+\]/g)) {
// Do not convert URLs with shortcodes // Do not convert URLs with shortcodes
return url; return url;
@ -56,12 +56,12 @@ define([
plugins: this.options.plugins, plugins: this.options.plugins,
setup: function(editor) { setup: function (editor) { // eslint-disable-line func-names
editor.on('change', function(e) { editor.on('change', function () { // eslint-disable-line func-names
that.view.triggerMethod('text:editor:change', editor.getContent()); that.view.triggerMethod('text:editor:change', editor.getContent());
}); });
editor.on('click', function(e) { editor.on('click', function (e) { // eslint-disable-line func-names
editor.focus(); editor.focus();
if (that._isActivationClick) { if (that._isActivationClick) {
editor.selection.setRng( editor.selection.setRng(
@ -71,12 +71,12 @@ define([
} }
}); });
editor.on('focus', function(e) { editor.on('focus', function () { // eslint-disable-line func-names
that.view.triggerMethod('text:editor:focus'); that.view.triggerMethod('text:editor:focus');
that._isActivationClick = true; that._isActivationClick = true;
}); });
editor.on('blur', function(e) { editor.on('blur', function () { // eslint-disable-line func-names
that.view.triggerMethod('text:editor:blur'); that.view.triggerMethod('text:editor:blur');
}); });
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/** /**
* Automated latest content block. * Automated latest content block.
* Only query parameters can be modified by the user. Posts pulled by this * Only query parameters can be modified by the user. Posts pulled by this
@ -16,7 +17,7 @@ define([
'backbone.supermodel', 'backbone.supermodel',
'underscore', 'underscore',
'jquery' 'jquery'
], function( ], function (
App, App,
BaseBlock, BaseBlock,
ButtonBlock, ButtonBlock,
@ -27,14 +28,13 @@ define([
_, _,
jQuery jQuery
) { ) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
base = BaseBlock; var base = BaseBlock;
Module.ALCSupervisor = SuperModel.extend({ Module.ALCSupervisor = SuperModel.extend({
initialize: function() { initialize: function () {
var DELAY_REFRESH_FOR_MS = 500; var DELAY_REFRESH_FOR_MS = 500;
this.listenTo( this.listenTo(
App.getChannel(), App.getChannel(),
@ -42,13 +42,14 @@ define([
_.debounce(this.refresh, DELAY_REFRESH_FOR_MS) _.debounce(this.refresh, DELAY_REFRESH_FOR_MS)
); );
}, },
refresh: function() { refresh: function () {
var models = App.findModels(function(model) { var blocks;
var models = App.findModels(function (model) {
return model.get('type') === 'automatedLatestContent'; return model.get('type') === 'automatedLatestContent';
}) || []; }) || [];
if (models.length === 0) return; if (models.length === 0) return;
var blocks = _.map(models, function(model) { blocks = _.map(models, function (model) {
return model.toJSON(); return model.toJSON();
}); });
@ -56,12 +57,12 @@ define([
blocks: blocks blocks: blocks
}).then(_.partial(this.refreshBlocks, models)); }).then(_.partial(this.refreshBlocks, models));
}, },
refreshBlocks: function(models, renderedBlocks) { refreshBlocks: function (models, renderedBlocks) {
_.each( _.each(
_.zip(models, renderedBlocks), _.zip(models, renderedBlocks),
function(args) { function (args) {
var model = args[0], var model = args[0];
contents = args[1]; var contents = args[1];
model.trigger('refreshPosts', contents); model.trigger('refreshPosts', contents);
} }
); );
@ -70,7 +71,7 @@ define([
Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({ Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({
stale: ['_container'], stale: ['_container'],
defaults: function() { defaults: function () {
return this._getDefaults({ return this._getDefaults({
type: 'automatedLatestContent', type: 'automatedLatestContent',
amount: '5', amount: '5',
@ -83,7 +84,7 @@ define([
titleIsLink: false, // false|true titleIsLink: false, // false|true
imageFullWidth: false, // true|false imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none' featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none' // imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText' showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:', authorPrecededBy: 'Author:',
showCategories: 'no', // 'no'|'aboveText'|'belowText' showCategories: 'no', // 'no'|'aboveText'|'belowText'
@ -100,14 +101,14 @@ define([
_container: new (App.getBlockTypeModel('container'))() _container: new (App.getBlockTypeModel('container'))()
}, App.getConfig().get('blockDefaults.automatedLatestContent')); }, App.getConfig().get('blockDefaults.automatedLatestContent'));
}, },
relations: function() { relations: function () {
return { return {
readMoreButton: App.getBlockTypeModel('button'), readMoreButton: App.getBlockTypeModel('button'),
divider: App.getBlockTypeModel('divider'), divider: App.getBlockTypeModel('divider'),
_container: App.getBlockTypeModel('container') _container: App.getBlockTypeModel('container')
}; };
}, },
initialize: function() { initialize: function () {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this); this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts); this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
@ -115,27 +116,27 @@ define([
this.on('add remove update reset', this._scheduleFetchPosts); this.on('add remove update reset', this._scheduleFetchPosts);
this.on('refreshPosts', this.updatePosts, this); this.on('refreshPosts', this.updatePosts, this);
}, },
updatePosts: function(posts) { updatePosts: function (posts) {
this.get('_container.blocks').reset(posts, {parse: true}); this.get('_container.blocks').reset(posts, { parse: true });
}, },
/** /**
* Batch more changes during a specific time, instead of fetching * Batch more changes during a specific time, instead of fetching
* ALC posts on each model change * ALC posts on each model change
*/ */
_scheduleFetchPosts: function() { _scheduleFetchPosts: function () {
App.getChannel().trigger('automatedLatestContentRefresh'); App.getChannel().trigger('automatedLatestContentRefresh');
} }
}); });
Module.AutomatedLatestContentBlockView = base.BlockView.extend({ Module.AutomatedLatestContentBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_automated_latest_content_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_automated_latest_content_block mailpoet_droppable_block',
initialize: function() { initialize: function () {
function replaceButtonStylesHandler(data) { function replaceButtonStylesHandler(data) {
this.model.set({readMoreButton: data}); this.model.set({ readMoreButton: data });
} }
App.getChannel().on('replaceAllButtonStyles', replaceButtonStylesHandler.bind(this)); App.getChannel().on('replaceAllButtonStyles', replaceButtonStylesHandler.bind(this));
}, },
getTemplate: function() { return window.templates.automatedLatestContentBlock; }, getTemplate: function () { return window.templates.automatedLatestContentBlock; },
regions: { regions: {
toolsRegion: '.mailpoet_tools', toolsRegion: '.mailpoet_tools',
postsRegion: '.mailpoet_automated_latest_content_block_posts' postsRegion: '.mailpoet_automated_latest_content_block_posts'
@ -148,14 +149,14 @@ define([
events: _.extend(base.BlockView.prototype.events, { events: _.extend(base.BlockView.prototype.events, {
'click .mailpoet_automated_latest_content_block_overlay': 'showSettings' 'click .mailpoet_automated_latest_content_block_overlay': 'showSettings'
}), }),
onDragSubstituteBy: function() { return Module.AutomatedLatestContentWidgetView; }, onDragSubstituteBy: function () { return Module.AutomatedLatestContentWidgetView; },
onRender: function() { onRender: function () {
var ContainerView = App.getBlockTypeView('container'), var ContainerView = App.getBlockTypeView('container');
renderOptions = { var renderOptions = {
disableTextEditor: true, disableTextEditor: true,
disableDragAndDrop: true, disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay') emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
}; };
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model }); this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions })); this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions }));
@ -163,13 +164,13 @@ define([
}); });
Module.AutomatedLatestContentBlockToolsView = base.BlockToolsView.extend({ Module.AutomatedLatestContentBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.AutomatedLatestContentBlockSettingsView; } getSettingsView: function () { return Module.AutomatedLatestContentBlockSettingsView; }
}); });
// Sidebar view container // Sidebar view container
Module.AutomatedLatestContentBlockSettingsView = base.BlockSettingsView.extend({ Module.AutomatedLatestContentBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.automatedLatestContentBlockSettings; }, getTemplate: function () { return window.templates.automatedLatestContentBlockSettings; },
events: function() { events: function () {
return { return {
'click .mailpoet_automated_latest_content_hide_display_options': 'toggleDisplayOptions', 'click .mailpoet_automated_latest_content_hide_display_options': 'toggleDisplayOptions',
'click .mailpoet_automated_latest_content_show_display_options': 'toggleDisplayOptions', 'click .mailpoet_automated_latest_content_show_display_options': 'toggleDisplayOptions',
@ -195,7 +196,7 @@ define([
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
}; };
}, },
onRender: function() { onRender: function () {
var that = this; var that = this;
// Dynamically update available post types // Dynamically update available post types
@ -211,35 +212,36 @@ define([
term: params.term term: params.term
}; };
}, },
transport: function(options, success, failure) { transport: function (options, success, failure) {
var taxonomies; var taxonomies;
var termsPromise;
var promise = CommunicationComponent.getTaxonomies( var promise = CommunicationComponent.getTaxonomies(
that.model.get('contentType') that.model.get('contentType')
).then(function(tax) { ).then(function (tax) {
taxonomies = tax; taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched // Fetch available terms based on the list of taxonomies already fetched
var promise = CommunicationComponent.getTerms({ termsPromise = CommunicationComponent.getTerms({
search: options.data.term, search: options.data.term,
taxonomies: _.keys(taxonomies) taxonomies: _.keys(taxonomies)
}).then(function(terms) { }).then(function (terms) {
return { return {
taxonomies: taxonomies, taxonomies: taxonomies,
terms: terms terms: terms
}; };
}); });
return promise; return termsPromise;
}); });
promise.then(success); promise.then(success);
promise.fail(failure); promise.fail(failure);
return promise; return promise;
}, },
processResults: function(data) { processResults: function (data) {
// Transform taxonomies and terms into select2 compatible format // Transform taxonomies and terms into select2 compatible format
return { return {
results: _.map( results: _.map(
data.terms, data.terms,
function(item) { function (item) {
return _.defaults({ return _.defaults({
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name, text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id id: item.term_id
@ -250,23 +252,23 @@ define([
} }
} }
}).on({ }).on({
'select2:select': function(event) { 'select2:select': function (event) {
var terms = that.model.get('terms'); var terms = that.model.get('terms');
terms.add(event.params.data); terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly // Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON()); that.model.set('terms', terms.toJSON());
}, },
'select2:unselect': function(event) { 'select2:unselect': function (event) {
var terms = that.model.get('terms'); var terms = that.model.get('terms');
terms.remove(event.params.data); terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly // Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON()); that.model.set('terms', terms.toJSON());
} }
}).trigger( 'change' ); }).trigger('change');
}, },
toggleDisplayOptions: function(event) { toggleDisplayOptions: function () {
var el = this.$('.mailpoet_automated_latest_content_display_options'), var el = this.$('.mailpoet_automated_latest_content_display_options');
showControl = this.$('.mailpoet_automated_latest_content_show_display_options'); var showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
if (el.hasClass('mailpoet_closed')) { if (el.hasClass('mailpoet_closed')) {
el.removeClass('mailpoet_closed'); el.removeClass('mailpoet_closed');
showControl.addClass('mailpoet_hidden'); showControl.addClass('mailpoet_hidden');
@ -275,7 +277,7 @@ define([
showControl.removeClass('mailpoet_hidden'); showControl.removeClass('mailpoet_hidden');
} }
}, },
showButtonSettings: function(event) { showButtonSettings: function () {
var buttonModule = ButtonBlock; var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({ (new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'), model: this.model.get('readMoreButton'),
@ -286,7 +288,7 @@ define([
} }
})).render(); })).render();
}, },
showDividerSettings: function(event) { showDividerSettings: function () {
var dividerModule = DividerBlock; var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({ (new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'), model: this.model.get('divider'),
@ -296,7 +298,7 @@ define([
} }
})).render(); })).render();
}, },
changeReadMoreType: function(event) { changeReadMoreType: function (event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'link') { if (value == 'link') {
this.$('.mailpoet_automated_latest_content_read_more_text').removeClass('mailpoet_hidden'); this.$('.mailpoet_automated_latest_content_read_more_text').removeClass('mailpoet_hidden');
@ -307,7 +309,7 @@ define([
} }
this.changeField('readMoreType', event); this.changeField('readMoreType', event);
}, },
changeDisplayType: function(event) { changeDisplayType: function (event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'titleOnly') { if (value == 'titleOnly') {
@ -334,7 +336,7 @@ define([
} }
this.changeField('displayType', event); this.changeField('displayType', event);
}, },
changeTitleFormat: function(event) { changeTitleFormat: function (event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'ul') { if (value == 'ul') {
this.$('.mailpoet_automated_latest_content_non_title_list_options').addClass('mailpoet_hidden'); this.$('.mailpoet_automated_latest_content_non_title_list_options').addClass('mailpoet_hidden');
@ -348,12 +350,12 @@ define([
} }
this.changeField('titleFormat', event); this.changeField('titleFormat', event);
}, },
_updateContentTypes: function(postTypes) { _updateContentTypes: function (postTypes) {
var select = this.$('.mailpoet_automated_latest_content_content_type'), var select = this.$('.mailpoet_automated_latest_content_content_type');
selectedValue = this.model.get('contentType'); var selectedValue = this.model.get('contentType');
select.find('option').remove(); select.find('option').remove();
_.each(postTypes, function(type) { _.each(postTypes, function (type) {
select.append(jQuery('<option>', { select.append(jQuery('<option>', {
value: type.name, value: type.name,
text: type.label text: type.label
@ -364,35 +366,35 @@ define([
}); });
Module.AutomatedLatestContentWidgetView = base.WidgetView.extend({ Module.AutomatedLatestContentWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.automatedLatestContentInsertion; }, getTemplate: function () { return window.templates.automatedLatestContentInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.AutomatedLatestContentBlockModel({}, { parse: true }); return new Module.AutomatedLatestContentBlockModel({}, { parse: true });
}, },
onDrop: function(options) { onDrop: function (options) {
options.droppedView.triggerMethod('showSettings'); options.droppedView.triggerMethod('showSettings');
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
App.registerBlockType('automatedLatestContent', { BeforeStartApp.registerBlockType('automatedLatestContent', {
blockModel: Module.AutomatedLatestContentBlockModel, blockModel: Module.AutomatedLatestContentBlockModel,
blockView: Module.AutomatedLatestContentBlockView blockView: Module.AutomatedLatestContentBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'automatedLatestContent', name: 'automatedLatestContent',
widgetView: Module.AutomatedLatestContentWidgetView, widgetView: Module.AutomatedLatestContentWidgetView,
priority: 97 priority: 97
}); });
}); });
App.on('start', function(App, options) { App.on('start', function (StartApp) {
var Application = App; var Application = StartApp;
Application._ALCSupervisor = new Module.ALCSupervisor(); Application._ALCSupervisor = new Module.ALCSupervisor();
Application._ALCSupervisor.refresh(); Application._ALCSupervisor.refresh();
}); });

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/** /**
* Defines base classes for actual content blocks to extend. * Defines base classes for actual content blocks to extend.
* Extending content block modules need to at least extend * Extending content block modules need to at least extend
@ -12,22 +13,20 @@ define([
'jquery', 'jquery',
'mailpoet', 'mailpoet',
'modal' 'modal'
], function(App, Marionette, SuperModel, _, jQuery, MailPoet, Modal) { ], function (App, Marionette, SuperModel, _, jQuery, MailPoet) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
AugmentedView = Marionette.View.extend({}); var AugmentedView = Marionette.View.extend({});
Module.BlockModel = SuperModel.extend({ Module.BlockModel = SuperModel.extend({
stale: [], // Attributes to be removed upon saving stale: [], // Attributes to be removed upon saving
initialize: function() { initialize: function () {
var that = this; this.on('change', function () {
this.on('change', function() {
App.getChannel().trigger('autoSave'); App.getChannel().trigger('autoSave');
}); });
}, },
_getDefaults: function(blockDefaults, configDefaults) { _getDefaults: function (blockDefaults, configDefaults) {
var defaults = (_.isObject(configDefaults) && _.isFunction(configDefaults.toJSON)) ? configDefaults.toJSON() : configDefaults; var defaults = (_.isObject(configDefaults) && _.isFunction(configDefaults.toJSON)) ? configDefaults.toJSON() : configDefaults;
// Patch the resulting JSON object and fix it's constructors to be Object. // Patch the resulting JSON object and fix it's constructors to be Object.
@ -36,11 +35,11 @@ define([
// TODO: Investigate for a better solution // TODO: Investigate for a better solution
return JSON.parse(JSON.stringify(jQuery.extend(blockDefaults, defaults || {}))); return JSON.parse(JSON.stringify(jQuery.extend(blockDefaults, defaults || {})));
}, },
toJSON: function() { toJSON: function () {
// Remove stale attributes from resulting JSON object // Remove stale attributes from resulting JSON object
return _.omit(SuperModel.prototype.toJSON.call(this), this.stale); return _.omit(SuperModel.prototype.toJSON.call(this), this.stale);
}, },
getChildren: function() { getChildren: function () {
return []; return [];
} }
}); });
@ -62,13 +61,14 @@ define([
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
hideOriginal: true, hideOriginal: true,
onDrop: function(options) { onDrop: function (options) {
// After a clone of model has been dropped, cleanup // After a clone of model has been dropped, cleanup
// and destroy self // and destroy self
options.dragBehavior.view.model.destroy(); options.dragBehavior.view.model.destroy();
}, },
onDragSubstituteBy: function(behavior) { onDragSubstituteBy: function (behavior) {
var WidgetView, node; var WidgetView;
var node;
// When block is being dragged, display the widget icon instead. // When block is being dragged, display the widget icon instead.
// This will create an instance of block's widget view and // This will create an instance of block's widget view and
// use it's rendered DOM element instead of the content block // use it's rendered DOM element instead of the content block
@ -79,80 +79,81 @@ define([
WidgetView.destroy(); WidgetView.destroy();
return node; return node;
} }
return undefined;
} }
}, },
HighlightEditingBehavior: {} HighlightEditingBehavior: {}
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
viewCid: this.cid viewCid: this.cid
}; };
}, },
constructor: function() { constructor: function () {
AugmentedView.apply(this, arguments); AugmentedView.apply(this, arguments);
this.$el.addClass('mailpoet_editor_view_' + this.cid); this.$el.addClass('mailpoet_editor_view_' + this.cid);
}, },
initialize: function() { initialize: function () {
this.on('showSettings', this.showSettings, this); this.on('showSettings', this.showSettings, this);
this.on('dom:refresh', this.showBlock, this); this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true; this._isFirstRender = true;
}, },
showTools: function(_event) { showTools: function () {
if (!this.showingToolsDisabled) { if (!this.showingToolsDisabled) {
this.$('> .mailpoet_tools').addClass('mailpoet_display_tools'); this.$('> .mailpoet_tools').addClass('mailpoet_display_tools');
this.toolsView.triggerMethod('showTools'); this.toolsView.triggerMethod('showTools');
} }
}, },
hideTools: function(e) { hideTools: function () {
this.$('> .mailpoet_tools').removeClass('mailpoet_display_tools'); this.$('> .mailpoet_tools').removeClass('mailpoet_display_tools');
this.toolsView.triggerMethod('hideTools'); this.toolsView.triggerMethod('hideTools');
}, },
enableShowingTools: function() { enableShowingTools: function () {
this.showingToolsDisabled = false; this.showingToolsDisabled = false;
}, },
disableShowingTools: function() { disableShowingTools: function () {
this.showingToolsDisabled = true; this.showingToolsDisabled = true;
this.hideTools(); this.hideTools();
}, },
showSettings: function(options) { showSettings: function (options) {
this.toolsView.triggerMethod('showSettings', options); this.toolsView.triggerMethod('showSettings', options);
}, },
/** /**
* Defines drop behavior of BlockView instance * Defines drop behavior of BlockView instance
*/ */
getDropFunc: function() { getDropFunc: function () {
return function() { return function () {
return this.model.clone(); return this.model.clone();
}.bind(this); }.bind(this);
}, },
disableDragging: function() { disableDragging: function () {
this.$el.addClass('mailpoet_ignore_drag'); this.$el.addClass('mailpoet_ignore_drag');
}, },
enableDragging: function() { enableDragging: function () {
this.$el.removeClass('mailpoet_ignore_drag'); this.$el.removeClass('mailpoet_ignore_drag');
}, },
showBlock: function() { showBlock: function () {
if (this._isFirstRender) { if (this._isFirstRender) {
this.transitionIn(); this.transitionIn();
this._isFirstRender = false; this._isFirstRender = false;
} }
}, },
deleteBlock: function() { deleteBlock: function () {
this.transitionOut().then(function() { this.transitionOut().then(function () {
this.model.destroy(); this.model.destroy();
}.bind(this)); }.bind(this));
}, },
duplicateBlock: function() { duplicateBlock: function () {
this.model.collection.add(this.model.toJSON(), {at: this.model.collection.findIndex(this.model)}); this.model.collection.add(this.model.toJSON(), { at: this.model.collection.findIndex(this.model) });
}, },
transitionIn: function() { transitionIn: function () {
return this._transition('slideDown', 'fadeIn', 'easeOut'); return this._transition('slideDown', 'fadeIn', 'easeOut');
}, },
transitionOut: function() { transitionOut: function () {
return this._transition('slideUp', 'fadeOut', 'easeIn'); return this._transition('slideUp', 'fadeOut', 'easeIn');
}, },
_transition: function(slideDirection, fadeDirection, easing) { _transition: function (slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred(); var promise = jQuery.Deferred();
this.$el.velocity( this.$el.velocity(
@ -160,7 +161,7 @@ define([
{ {
duration: 250, duration: 250,
easing: easing, easing: easing,
complete: function() { complete: function () {
promise.resolve(); promise.resolve();
}.bind(this) }.bind(this)
} }
@ -178,7 +179,7 @@ define([
}); });
Module.BlockToolsView = AugmentedView.extend({ Module.BlockToolsView = AugmentedView.extend({
getTemplate: function() { return window.templates.genericBlockTools; }, getTemplate: function () { return window.templates.genericBlockTools; },
events: { events: {
'click .mailpoet_edit_block': 'changeSettings', 'click .mailpoet_edit_block': 'changeSettings',
'click .mailpoet_delete_block_activate': 'showDeletionConfirmation', 'click .mailpoet_delete_block_activate': 'showDeletionConfirmation',
@ -193,8 +194,8 @@ define([
duplicate: true, duplicate: true,
move: true move: true
}, },
getSettingsView: function() { return Module.BlockSettingsView; }, getSettingsView: function () { return Module.BlockSettingsView; },
initialize: function(opts) { initialize: function (opts) {
var options = opts || {}; var options = opts || {};
if (!_.isUndefined(options.tools)) { if (!_.isUndefined(options.tools)) {
// Make a new block specific tool config object // Make a new block specific tool config object
@ -205,29 +206,39 @@ define([
this.on('hideTools', this.hideDeletionConfirmation, this); this.on('hideTools', this.hideDeletionConfirmation, this);
this.on('showSettings', this.changeSettings); this.on('showSettings', this.changeSettings);
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
viewCid: this.cid, viewCid: this.cid,
tools: this.tools tools: this.tools
}; };
}, },
changeSettings: function(options) { changeSettings: function (options) {
var ViewType = this.getSettingsView(); var ViewType = this.getSettingsView();
(new ViewType(_.extend({ model: this.model }, options || {}))).render(); (new ViewType(_.extend({ model: this.model }, options || {}))).render();
}, },
showDeletionConfirmation: function() { showDeletionConfirmation: function () {
this.$('.mailpoet_delete_block')
.closest('.mailpoet_block')
.find('> .mailpoet_block_highlight')
.css({ background: '#E64047', opacity: 0.5 });
this.$('.mailpoet_delete_block').addClass('mailpoet_delete_block_activated'); this.$('.mailpoet_delete_block').addClass('mailpoet_delete_block_activated');
}, },
hideDeletionConfirmation: function() { hideDeletionConfirmation: function () {
this.$('.mailpoet_delete_block')
.closest('.mailpoet_block')
.find('> .mailpoet_block_highlight')
.css({ background: 'transparent', opacity: 1 });
this.$('.mailpoet_delete_block').removeClass('mailpoet_delete_block_activated'); this.$('.mailpoet_delete_block').removeClass('mailpoet_delete_block_activated');
}, },
deleteBlock: function(event) { deleteBlock: function (event) {
event.preventDefault(); event.preventDefault();
this.model.trigger('delete'); this.model.trigger('delete');
return false; return false;
}, },
duplicateBlock: function(event) { duplicateBlock: function (event) {
event.preventDefault(); event.preventDefault();
this.model.trigger('duplicate'); this.model.trigger('duplicate');
return false; return false;
@ -239,14 +250,15 @@ define([
behaviors: { behaviors: {
ColorPickerBehavior: {} ColorPickerBehavior: {}
}, },
initialize: function(params) { initialize: function (params) {
var panelParams;
this.model.trigger('startEditing'); this.model.trigger('startEditing');
var panelParams = { panelParams = {
element: this.$el, element: this.$el,
template: '', template: '',
position: 'right', position: 'right',
width: App.getConfig().get('sidepanelWidth'), width: App.getConfig().get('sidepanelWidth'),
onCancel: function() { onCancel: function () {
this.destroy(); this.destroy();
}.bind(this) }.bind(this)
}; };
@ -257,37 +269,37 @@ define([
MailPoet.Modal.panel(panelParams); MailPoet.Modal.panel(panelParams);
} }
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON() model: this.model.toJSON()
}; };
}, },
close: function(event) { close: function () {
this.destroy(); this.destroy();
}, },
changeField: function(field, event) { changeField: function (field, event) {
this.model.set(field, jQuery(event.target).val()); this.model.set(field, jQuery(event.target).val());
}, },
changePixelField: function(field, event) { changePixelField: function (field, event) {
this.changeFieldWithSuffix(field, event, 'px'); this.changeFieldWithSuffix(field, event, 'px');
}, },
changeFieldWithSuffix: function(field, event, suffix) { changeFieldWithSuffix: function (field, event, suffix) {
this.model.set(field, jQuery(event.target).val() + suffix); this.model.set(field, jQuery(event.target).val() + suffix);
}, },
changeBoolField: function(field, event) { changeBoolField: function (field, event) {
this.model.set(field, (jQuery(event.target).val() === 'true')); this.model.set(field, (jQuery(event.target).val() === 'true'));
}, },
changeBoolCheckboxField: function(field, event) { changeBoolCheckboxField: function (field, event) {
this.model.set(field, (!!jQuery(event.target).prop('checked'))); this.model.set(field, (!!jQuery(event.target).prop('checked')));
}, },
changeColorField: function(field, event) { changeColorField: function (field, event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value === '') { if (value === '') {
value = 'transparent'; value = 'transparent';
} }
this.model.set(field, value); this.model.set(field, value);
}, },
onBeforeDestroy: function() { onBeforeDestroy: function () {
MailPoet.Modal.close(); MailPoet.Modal.close();
this.model.trigger('stopEditing'); this.model.trigger('stopEditing');
} }
@ -297,7 +309,7 @@ define([
className: 'mailpoet_widget mailpoet_droppable_block mailpoet_droppable_widget', className: 'mailpoet_widget mailpoet_droppable_block mailpoet_droppable_widget',
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
drop: function() { drop: function () {
throw 'Unsupported operation'; throw 'Unsupported operation';
} }
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/** /**
* Button content block * Button content block
*/ */
@ -7,15 +8,14 @@ define([
'mailpoet', 'mailpoet',
'underscore', 'underscore',
'jquery' 'jquery'
], function(App, BaseBlock, MailPoet, _, jQuery) { ], function (App, BaseBlock, MailPoet, _, jQuery) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
base = BaseBlock; var base = BaseBlock;
Module.ButtonBlockModel = base.BlockModel.extend({ Module.ButtonBlockModel = base.BlockModel.extend({
defaults: function() { defaults: function () {
return this._getDefaults({ return this._getDefaults({
type: 'button', type: 'button',
text: 'Button', text: 'Button',
@ -42,31 +42,31 @@ define([
Module.ButtonBlockView = base.BlockView.extend({ Module.ButtonBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_button_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_button_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.buttonBlock; }, getTemplate: function () { return window.templates.buttonBlock; },
onDragSubstituteBy: function() { return Module.ButtonWidgetView; }, onDragSubstituteBy: function () { return Module.ButtonWidgetView; },
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {} ShowSettingsBehavior: {}
}), }),
initialize: function() { initialize: function () {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
// Listen for attempts to change all dividers in one go // Listen for attempts to change all dividers in one go
this._replaceButtonStylesHandler = function(data) { this.model.set(data); }.bind(this); this._replaceButtonStylesHandler = function (data) { this.model.set(data); }.bind(this);
App.getChannel().on('replaceAllButtonStyles', this._replaceButtonStylesHandler); App.getChannel().on('replaceAllButtonStyles', this._replaceButtonStylesHandler);
}, },
onRender: function() { onRender: function () {
this.toolsView = new Module.ButtonBlockToolsView({ model: this.model }); this.toolsView = new Module.ButtonBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
} }
}); });
Module.ButtonBlockToolsView = base.BlockToolsView.extend({ Module.ButtonBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.ButtonBlockSettingsView; } getSettingsView: function () { return Module.ButtonBlockSettingsView; }
}); });
Module.ButtonBlockSettingsView = base.BlockSettingsView.extend({ Module.ButtonBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.buttonBlockSettings; }, getTemplate: function () { return window.templates.buttonBlockSettings; },
events: function() { events: function () {
return { return {
'input .mailpoet_field_button_text': _.partial(this.changeField, 'text'), 'input .mailpoet_field_button_text': _.partial(this.changeField, 'text'),
'input .mailpoet_field_button_url': _.partial(this.changeField, 'url'), 'input .mailpoet_field_button_url': _.partial(this.changeField, 'url'),
@ -98,20 +98,20 @@ define([
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
}; };
}, },
templateContext: function() { templateContext: function () {
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
availableStyles: App.getAvailableStyles().toJSON(), availableStyles: App.getAvailableStyles().toJSON(),
renderOptions: this.renderOptions renderOptions: this.renderOptions
}); });
}, },
applyToAll: function() { applyToAll: function () {
App.getChannel().trigger('replaceAllButtonStyles', _.pick(this.model.toJSON(), 'styles', 'type')); App.getChannel().trigger('replaceAllButtonStyles', _.pick(this.model.toJSON(), 'styles', 'type'));
}, },
updateValueAndCall: function(fieldToUpdate, callable, event) { updateValueAndCall: function (fieldToUpdate, callable, event) {
this.$(fieldToUpdate).val(jQuery(event.target).val()); this.$(fieldToUpdate).val(jQuery(event.target).val());
callable(event); callable(event);
}, },
changeFontWeight: function(event) { changeFontWeight: function (event) {
var checked = !!jQuery(event.target).prop('checked'); var checked = !!jQuery(event.target).prop('checked');
this.model.set( this.model.set(
'styles.block.fontWeight', 'styles.block.fontWeight',
@ -121,24 +121,24 @@ define([
}); });
Module.ButtonWidgetView = base.WidgetView.extend({ Module.ButtonWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.buttonInsertion; }, getTemplate: function () { return window.templates.buttonInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.ButtonBlockModel(); return new Module.ButtonBlockModel();
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
App.registerBlockType('button', { BeforeStartApp.registerBlockType('button', {
blockModel: Module.ButtonBlockModel, blockModel: Module.ButtonBlockModel,
blockView: Module.ButtonBlockView blockView: Module.ButtonBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'button', name: 'button',
widgetView: Module.ButtonWidgetView, widgetView: Module.ButtonWidgetView,
priority: 92 priority: 92

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/** /**
* Container content block. * Container content block.
* This is a special kind of block, as it can contain content blocks, as well * This is a special kind of block, as it can contain content blocks, as well
@ -10,25 +11,23 @@ define([
'jquery', 'jquery',
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/blocks/base' 'newsletter_editor/blocks/base'
], function(Backbone, Marionette, _, jQuery, App, BaseBlock) { ], function (Backbone, Marionette, _, jQuery, App, BaseBlock) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
base = BaseBlock, var base = BaseBlock;
BlockCollection; var BlockCollection;
BlockCollection = Backbone.Collection.extend({ BlockCollection = Backbone.Collection.extend({
model: base.BlockModel, model: base.BlockModel,
initialize: function() { initialize: function () {
this.on('add change remove', function() { App.getChannel().trigger('autoSave'); }); this.on('add change remove', function () { App.getChannel().trigger('autoSave'); });
}, },
parse: function(response) { parse: function (response) {
var self = this; return _.map(response, function (block) {
return _.map(response, function(block) {
var Type = App.getBlockTypeModel(block.type); var Type = App.getBlockTypeModel(block.type);
// TODO: If type has no registered model, use a backup one // TODO: If type has no registered model, use a backup one
return new Type(block, {parse: true}); return new Type(block, { parse: true });
}); });
} }
}); });
@ -37,7 +36,7 @@ define([
relations: { relations: {
blocks: BlockCollection blocks: BlockCollection
}, },
defaults: function() { defaults: function () {
return this._getDefaults({ return this._getDefaults({
type: 'container', type: 'container',
orientation: 'vertical', orientation: 'vertical',
@ -49,14 +48,15 @@ define([
blocks: new BlockCollection() blocks: new BlockCollection()
}, App.getConfig().get('blockDefaults.container')); }, App.getConfig().get('blockDefaults.container'));
}, },
validate: function() { validate: function () {
// Recursively propagate validation checks to blocks in the tree // Recursively propagate validation checks to blocks in the tree
var invalidBlock = this.get('blocks').find(function(block) { return !block.isValid(); }); var invalidBlock = this.get('blocks').find(function (block) { return !block.isValid(); });
if (invalidBlock) { if (invalidBlock) {
return invalidBlock.validationError; return invalidBlock.validationError;
} }
return undefined;
}, },
parse: function(response) { parse: function (response) {
// If container has any blocks - add them to a collection // If container has any blocks - add them to a collection
if (response.type === 'container' && _.has(response, 'blocks')) { if (response.type === 'container' && _.has(response, 'blocks')) {
response.blocks = new BlockCollection(response.blocks, { response.blocks = new BlockCollection(response.blocks, {
@ -65,8 +65,8 @@ define([
} }
return response; return response;
}, },
getChildren: function() { getChildren: function () {
var models = this.get('blocks').map(function(model, index, list) { var models = this.get('blocks').map(function (model) {
return [model, model.getChildren()]; return [model, model.getChildren()];
}); });
@ -76,10 +76,10 @@ define([
Module.ContainerBlocksView = Marionette.CollectionView.extend({ Module.ContainerBlocksView = Marionette.CollectionView.extend({
className: 'mailpoet_container', className: 'mailpoet_container',
childView: function(model) { childView: function (model) {
return App.getBlockTypeView(model.get('type')); return App.getBlockTypeView(model.get('type'));
}, },
childViewOptions: function() { childViewOptions: function () {
var newRenderOptions = _.clone(this.renderOptions); var newRenderOptions = _.clone(this.renderOptions);
if (newRenderOptions.depth !== undefined) { if (newRenderOptions.depth !== undefined) {
newRenderOptions.depth += 1; newRenderOptions.depth += 1;
@ -88,9 +88,9 @@ define([
renderOptions: newRenderOptions renderOptions: newRenderOptions
}; };
}, },
emptyView: function() { return Module.ContainerBlockEmptyView; }, emptyView: function () { return Module.ContainerBlockEmptyView; },
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; }, emptyViewOptions: function () { return { renderOptions: this.renderOptions }; },
initialize: function(options) { initialize: function (options) {
this.renderOptions = options.renderOptions; this.renderOptions = options.renderOptions;
} }
}); });
@ -103,7 +103,7 @@ define([
} }
}), }),
className: 'mailpoet_block mailpoet_container_block mailpoet_droppable_block mailpoet_droppable_layout_block', className: 'mailpoet_block mailpoet_container_block mailpoet_droppable_block mailpoet_droppable_layout_block',
getTemplate: function() { return window.templates.containerBlock; }, getTemplate: function () { return window.templates.containerBlock; },
events: _.extend({}, base.BlockView.prototype.events, { events: _.extend({}, base.BlockView.prototype.events, {
'click .mailpoet_newsletter_layer_selector': 'toggleEditingLayer' 'click .mailpoet_newsletter_layer_selector': 'toggleEditingLayer'
}), }),
@ -115,13 +115,14 @@ define([
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
hideOriginal: true, hideOriginal: true,
onDrop: function(options) { onDrop: function (options) {
// After a clone of model has been dropped, cleanup // After a clone of model has been dropped, cleanup
// and destroy self // and destroy self
options.dragBehavior.view.model.destroy(); options.dragBehavior.view.model.destroy();
}, },
onDragSubstituteBy: function(behavior) { onDragSubstituteBy: function (behavior) {
var WidgetView, node; var WidgetView;
var node;
// When block is being dragged, display the widget icon instead. // When block is being dragged, display the widget icon instead.
// This will create an instance of block's widget view and // This will create an instance of block's widget view and
// use it's rendered DOM element instead of the content block // use it's rendered DOM element instead of the content block
@ -132,8 +133,9 @@ define([
WidgetView.destroy(); WidgetView.destroy();
return node; return node;
} }
return undefined;
}, },
testAttachToInstance: function(model, view) { testAttachToInstance: function (model, view) {
// Attach Draggable only to layout containers and disable it // Attach Draggable only to layout containers and disable it
// for root and column containers. // for root and column containers.
return view.renderOptions.depth === 1; return view.renderOptions.depth === 1;
@ -141,7 +143,7 @@ define([
}, },
HighlightContainerBehavior: {} HighlightContainerBehavior: {}
}), }),
onDragSubstituteBy: function() { onDragSubstituteBy: function () {
// For two and three column layouts display their respective widgets, // For two and three column layouts display their respective widgets,
// otherwise always default to one column layout widget // otherwise always default to one column layout widget
if (this.renderOptions.depth === 1) { if (this.renderOptions.depth === 1) {
@ -149,14 +151,13 @@ define([
if (this.model.get('blocks').length === 2) return Module.TwoColumnContainerWidgetView; if (this.model.get('blocks').length === 2) return Module.TwoColumnContainerWidgetView;
} }
return Module.OneColumnContainerWidgetView; return Module.OneColumnContainerWidgetView;
}, },
initialize: function(options) { initialize: function (options) {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.renderOptions = _.defaults(options.renderOptions || {}, {}); this.renderOptions = _.defaults(options.renderOptions || {}, {});
}, },
onRender: function() { onRender: function () {
this.toolsView = new Module.ContainerBlockToolsView({ this.toolsView = new Module.ContainerBlockToolsView({
model: this.model, model: this.model,
tools: { tools: {
@ -177,37 +178,37 @@ define([
// Sets child container orientation HTML class here, as child CollectionView won't have access to model and will overwrite existing region element instead // Sets child container orientation HTML class here, as child CollectionView won't have access to model and will overwrite existing region element instead
this.$('> .mailpoet_container').attr('class', 'mailpoet_container mailpoet_container_' + this.model.get('orientation')); this.$('> .mailpoet_container').attr('class', 'mailpoet_container mailpoet_container_' + this.model.get('orientation'));
}, },
showTools: function() { showTools: function () {
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) { if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
this.$(this.ui.tools).addClass('mailpoet_display_tools'); this.$(this.ui.tools).addClass('mailpoet_display_tools');
this.toolsView.triggerMethod('showTools'); this.toolsView.triggerMethod('showTools');
} }
}, },
hideTools: function() { hideTools: function () {
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) { if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
this.$(this.ui.tools).removeClass('mailpoet_display_tools'); this.$(this.ui.tools).removeClass('mailpoet_display_tools');
this.toolsView.triggerMethod('hideTools'); this.toolsView.triggerMethod('hideTools');
} }
}, },
toggleEditingLayer: function(event) { toggleEditingLayer: function (event) {
var that = this, var that = this;
$toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector'), var $toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector');
$overlay = jQuery('.mailpoet_layer_overlay'), var $overlay = jQuery('.mailpoet_layer_overlay');
$container = this.$('> .mailpoet_container'), var $container = this.$('> .mailpoet_container');
enableContainerLayer = function() { var disableContainerLayer = function () {
that.$el.addClass('mailpoet_container_layer_active'); that.$el.removeClass('mailpoet_container_layer_active');
$toggleButton.addClass('mailpoet_container_layer_active'); $toggleButton.removeClass('mailpoet_container_layer_active');
$container.addClass('mailpoet_layer_highlight'); $container.removeClass('mailpoet_layer_highlight');
$overlay.click(disableContainerLayer); $overlay.hide();
$overlay.show(); $overlay.off('click');
}, };
disableContainerLayer = function() { var enableContainerLayer = function () {
that.$el.removeClass('mailpoet_container_layer_active'); that.$el.addClass('mailpoet_container_layer_active');
$toggleButton.removeClass('mailpoet_container_layer_active'); $toggleButton.addClass('mailpoet_container_layer_active');
$container.removeClass('mailpoet_layer_highlight'); $container.addClass('mailpoet_layer_highlight');
$overlay.hide(); $overlay.click(disableContainerLayer);
$overlay.off('click'); $overlay.show();
}; };
if ($toggleButton.hasClass('mailpoet_container_layer_active')) { if ($toggleButton.hasClass('mailpoet_container_layer_active')) {
disableContainerLayer(); disableContainerLayer();
} else { } else {
@ -218,11 +219,11 @@ define([
}); });
Module.ContainerBlockEmptyView = Marionette.View.extend({ Module.ContainerBlockEmptyView = Marionette.View.extend({
getTemplate: function() { return window.templates.containerEmpty; }, getTemplate: function () { return window.templates.containerEmpty; },
initialize: function(options) { initialize: function (options) {
this.renderOptions = _.defaults(options.renderOptions || {}, {}); this.renderOptions = _.defaults(options.renderOptions || {}, {});
}, },
templateContext: function() { templateContext: function () {
return { return {
isRoot: this.renderOptions.depth === 0, isRoot: this.renderOptions.depth === 0,
emptyContainerMessage: this.renderOptions.emptyContainerMessage || '' emptyContainerMessage: this.renderOptions.emptyContainerMessage || ''
@ -231,12 +232,12 @@ define([
}); });
Module.ContainerBlockToolsView = base.BlockToolsView.extend({ Module.ContainerBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.ContainerBlockSettingsView; } getSettingsView: function () { return Module.ContainerBlockSettingsView; }
}); });
Module.ContainerBlockSettingsView = base.BlockSettingsView.extend({ Module.ContainerBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.containerBlockSettings; }, getTemplate: function () { return window.templates.containerBlockSettings; },
events: function() { events: function () {
return { return {
'change .mailpoet_field_container_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'), 'change .mailpoet_field_container_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
@ -245,21 +246,21 @@ define([
regions: { regions: {
columnsSettingsRegion: '.mailpoet_container_columns_settings' columnsSettingsRegion: '.mailpoet_container_columns_settings'
}, },
initialize: function() { initialize: function () {
base.BlockSettingsView.prototype.initialize.apply(this, arguments); base.BlockSettingsView.prototype.initialize.apply(this, arguments);
this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({ this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({
collection: this.model.get('blocks') collection: this.model.get('blocks')
}); });
}, },
onRender: function() { onRender: function () {
this.showChildView('columnsSettingsRegion', this._columnsSettingsView); this.showChildView('columnsSettingsRegion', this._columnsSettingsView);
} }
}); });
Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({ Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
childView: function() { return Module.ContainerBlockColumnSettingsView; }, childView: function () { return Module.ContainerBlockColumnSettingsView; },
childViewOptions: function(model, index) { childViewOptions: function (model, index) {
return { return {
columnIndex: index columnIndex: index
}; };
@ -267,11 +268,11 @@ define([
}); });
Module.ContainerBlockColumnSettingsView = Marionette.View.extend({ Module.ContainerBlockColumnSettingsView = Marionette.View.extend({
getTemplate: function() { return window.templates.containerBlockColumnSettings; }, getTemplate: function () { return window.templates.containerBlockColumnSettings; },
initialize: function(options) { initialize: function (options) {
this.columnNumber = (options.columnIndex || 0) + 1; this.columnNumber = (options.columnIndex || 0) + 1;
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
columnNumber: this.columnNumber columnNumber: this.columnNumber
@ -281,11 +282,11 @@ define([
Module.OneColumnContainerWidgetView = base.WidgetView.extend({ Module.OneColumnContainerWidgetView = base.WidgetView.extend({
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block', className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
getTemplate: function() { return window.templates.oneColumnLayoutInsertion; }, getTemplate: function () { return window.templates.oneColumnLayoutInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.ContainerBlockModel({ return new Module.ContainerBlockModel({
orientation: 'horizontal', orientation: 'horizontal',
blocks: [ blocks: [
@ -299,11 +300,11 @@ define([
Module.TwoColumnContainerWidgetView = base.WidgetView.extend({ Module.TwoColumnContainerWidgetView = base.WidgetView.extend({
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block', className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
getTemplate: function() { return window.templates.twoColumnLayoutInsertion; }, getTemplate: function () { return window.templates.twoColumnLayoutInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.ContainerBlockModel({ return new Module.ContainerBlockModel({
orientation: 'horizontal', orientation: 'horizontal',
blocks: [ blocks: [
@ -318,11 +319,11 @@ define([
Module.ThreeColumnContainerWidgetView = base.WidgetView.extend({ Module.ThreeColumnContainerWidgetView = base.WidgetView.extend({
className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block', className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
getTemplate: function() { return window.templates.threeColumnLayoutInsertion; }, getTemplate: function () { return window.templates.threeColumnLayoutInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.ContainerBlockModel({ return new Module.ContainerBlockModel({
orientation: 'horizontal', orientation: 'horizontal',
blocks: [ blocks: [
@ -336,25 +337,25 @@ define([
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
App.registerBlockType('container', { BeforeStartApp.registerBlockType('container', {
blockModel: Module.ContainerBlockModel, blockModel: Module.ContainerBlockModel,
blockView: Module.ContainerBlockView blockView: Module.ContainerBlockView
}); });
App.registerLayoutWidget({ BeforeStartApp.registerLayoutWidget({
name: 'oneColumnLayout', name: 'oneColumnLayout',
priority: 100, priority: 100,
widgetView: Module.OneColumnContainerWidgetView widgetView: Module.OneColumnContainerWidgetView
}); });
App.registerLayoutWidget({ BeforeStartApp.registerLayoutWidget({
name: 'twoColumnLayout', name: 'twoColumnLayout',
priority: 100, priority: 100,
widgetView: Module.TwoColumnContainerWidgetView widgetView: Module.TwoColumnContainerWidgetView
}); });
App.registerLayoutWidget({ BeforeStartApp.registerLayoutWidget({
name: 'threeColumnLayout', name: 'threeColumnLayout',
priority: 100, priority: 100,
widgetView: Module.ThreeColumnContainerWidgetView widgetView: Module.ThreeColumnContainerWidgetView

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* Divider content block * Divider content block
*/ */
@ -5,17 +7,13 @@ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'underscore', 'underscore',
'jquery', 'jquery'
'mailpoet' ], function (App, BaseBlock, _, jQuery) { // eslint-disable-line func-names
], function(App, BaseBlock, _, jQuery, MailPoet) { var Module = {};
var base = BaseBlock;
'use strict';
var Module = {},
base = BaseBlock;
Module.DividerBlockModel = base.BlockModel.extend({ Module.DividerBlockModel = base.BlockModel.extend({
defaults: function() { defaults: function () { // eslint-disable-line func-names
return this._getDefaults({ return this._getDefaults({
type: 'divider', type: 'divider',
styles: { styles: {
@ -33,13 +31,13 @@ define([
Module.DividerBlockView = base.BlockView.extend({ Module.DividerBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_divider_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_divider_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.dividerBlock; }, getTemplate: function () { return window.templates.dividerBlock; }, // eslint-disable-line func-names
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'),
behaviors: _.defaults({ behaviors: _.defaults({
ResizableBehavior: { ResizableBehavior: {
elementSelector: '.mailpoet_content', elementSelector: '.mailpoet_content',
resizeHandleSelector: '.mailpoet_resize_handle', resizeHandleSelector: '.mailpoet_resize_handle',
transformationFunction: function(y) { return y / 2; }, transformationFunction: function (y) { return y / 2; }, // eslint-disable-line func-names
minLength: 0, // TODO: Move this number to editor configuration minLength: 0, // TODO: Move this number to editor configuration
modelField: 'styles.block.padding' modelField: 'styles.block.padding'
}, },
@ -47,45 +45,45 @@ define([
ignoreFrom: '.mailpoet_resize_handle' ignoreFrom: '.mailpoet_resize_handle'
} }
}, base.BlockView.prototype.behaviors), }, base.BlockView.prototype.behaviors),
onDragSubstituteBy: function() { return Module.DividerWidgetView; }, onDragSubstituteBy: function () { return Module.DividerWidgetView; }, // eslint-disable-line func-names
initialize: function() { initialize: function () { // eslint-disable-line func-names
base.BlockView.prototype.initialize.apply(this, arguments);
var that = this; var that = this;
base.BlockView.prototype.initialize.apply(this, arguments);
// Listen for attempts to change all dividers in one go // Listen for attempts to change all dividers in one go
this._replaceDividerHandler = function(data) { that.model.set(data); that.model.trigger('applyToAll'); }; this._replaceDividerHandler = function (data) { that.model.set(data); that.model.trigger('applyToAll'); }; // eslint-disable-line func-names
App.getChannel().on('replaceAllDividers', this._replaceDividerHandler); App.getChannel().on('replaceAllDividers', this._replaceDividerHandler);
this.listenTo(this.model, 'change:src change:styles.block.backgroundColor change:styles.block.borderStyle change:styles.block.borderWidth change:styles.block.borderColor applyToAll', this.render); this.listenTo(this.model, 'change:src change:styles.block.backgroundColor change:styles.block.borderStyle change:styles.block.borderWidth change:styles.block.borderColor applyToAll', this.render);
this.listenTo(this.model, 'change:styles.block.padding', this.changePadding); this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
}, },
templateContext: function() { templateContext: function () { // eslint-disable-line func-names
return _.extend({ return _.extend({
totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px' totalHeight: parseInt(this.model.get('styles.block.padding'), 10) * 2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px'
}, base.BlockView.prototype.templateContext.apply(this)); }, base.BlockView.prototype.templateContext.apply(this));
}, },
onRender: function() { onRender: function () { // eslint-disable-line func-names
this.toolsView = new Module.DividerBlockToolsView({ model: this.model }); this.toolsView = new Module.DividerBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onBeforeDestroy: function() { onBeforeDestroy: function () { // eslint-disable-line func-names
App.getChannel().off('replaceAllDividers', this._replaceDividerHandler); App.getChannel().off('replaceAllDividers', this._replaceDividerHandler);
this.stopListening(this.model); this.stopListening(this.model);
}, },
changePadding: function() { changePadding: function () { // eslint-disable-line func-names
this.$('.mailpoet_content').css('padding-top', this.model.get('styles.block.padding')); this.$('.mailpoet_content').css('padding-top', this.model.get('styles.block.padding'));
this.$('.mailpoet_content').css('padding-bottom', this.model.get('styles.block.padding')); this.$('.mailpoet_content').css('padding-bottom', this.model.get('styles.block.padding'));
this.$('.mailpoet_resize_handle_text').text(parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px'); this.$('.mailpoet_resize_handle_text').text(parseInt(this.model.get('styles.block.padding'), 10) * 2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px');
} }
}); });
Module.DividerBlockToolsView = base.BlockToolsView.extend({ Module.DividerBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.DividerBlockSettingsView; } getSettingsView: function () { return Module.DividerBlockSettingsView; } // eslint-disable-line func-names
}); });
Module.DividerBlockSettingsView = base.BlockSettingsView.extend({ Module.DividerBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.dividerBlockSettings; }, getTemplate: function () { return window.templates.dividerBlockSettings; }, // eslint-disable-line func-names
events: function() { events: function () { // eslint-disable-line func-names
return { return {
'click .mailpoet_field_divider_style': 'changeStyle', 'click .mailpoet_field_divider_style': 'changeStyle',
@ -99,53 +97,53 @@ define([
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
}; };
}, },
modelEvents: function() { modelEvents: function () { // eslint-disable-line func-names
return { return {
'change:styles.block.borderColor': 'repaintDividerStyleOptions' 'change:styles.block.borderColor': 'repaintDividerStyleOptions'
}; };
}, },
templateContext: function() { templateContext: function () { // eslint-disable-line func-names
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
availableStyles: App.getAvailableStyles().toJSON(), availableStyles: App.getAvailableStyles().toJSON(),
renderOptions: this.renderOptions renderOptions: this.renderOptions
}); });
}, },
changeStyle: function(event) { changeStyle: function (event) { // eslint-disable-line func-names
var style = jQuery(event.currentTarget).data('style'); var style = jQuery(event.currentTarget).data('style');
this.model.set('styles.block.borderStyle', style); this.model.set('styles.block.borderStyle', style);
this.$('.mailpoet_field_divider_style').removeClass('mailpoet_active_divider_style'); this.$('.mailpoet_field_divider_style').removeClass('mailpoet_active_divider_style');
this.$('.mailpoet_field_divider_style[data-style="' + style + '"]').addClass('mailpoet_active_divider_style'); this.$('.mailpoet_field_divider_style[data-style="' + style + '"]').addClass('mailpoet_active_divider_style');
}, },
repaintDividerStyleOptions: function() { repaintDividerStyleOptions: function () { // eslint-disable-line func-names
this.$('.mailpoet_field_divider_style > div').css('border-top-color', this.model.get('styles.block.borderColor')); this.$('.mailpoet_field_divider_style > div').css('border-top-color', this.model.get('styles.block.borderColor'));
}, },
applyToAll: function(event) { applyToAll: function () { // eslint-disable-line func-names
App.getChannel().trigger('replaceAllDividers', this.model.toJSON()); App.getChannel().trigger('replaceAllDividers', this.model.toJSON());
}, },
updateValueAndCall: function(fieldToUpdate, callable, event) { updateValueAndCall: function (fieldToUpdate, callable, event) { // eslint-disable-line func-names
this.$(fieldToUpdate).val(jQuery(event.target).val()); this.$(fieldToUpdate).val(jQuery(event.target).val());
callable(event); callable(event);
} }
}); });
Module.DividerWidgetView = base.WidgetView.extend({ Module.DividerWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.dividerInsertion; }, getTemplate: function () { return window.templates.dividerInsertion; }, // eslint-disable-line func-names
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () { // eslint-disable-line func-names
return new Module.DividerBlockModel(); return new Module.DividerBlockModel();
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) { // eslint-disable-line func-names
App.registerBlockType('divider', { BeforeStartApp.registerBlockType('divider', {
blockModel: Module.DividerBlockModel, blockModel: Module.DividerBlockModel,
blockView: Module.DividerBlockView blockView: Module.DividerBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'divider', name: 'divider',
widgetView: Module.DividerWidgetView, widgetView: Module.DividerWidgetView,
priority: 93 priority: 93

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* Footer content block * Footer content block
*/ */
@ -6,15 +8,12 @@ define([
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'underscore', 'underscore',
'mailpoet' 'mailpoet'
], function(App, BaseBlock, _, MailPoet) { ], function (App, BaseBlock, _, MailPoet) { // eslint-disable-line func-names
var Module = {};
'use strict'; var base = BaseBlock;
var Module = {},
base = BaseBlock;
Module.FooterBlockModel = base.BlockModel.extend({ Module.FooterBlockModel = base.BlockModel.extend({
defaults: function() { defaults: function () { // eslint-disable-line func-names
return this._getDefaults({ return this._getDefaults({
type: 'footer', type: 'footer',
text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>', text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
@ -39,13 +38,13 @@ define([
Module.FooterBlockView = base.BlockView.extend({ Module.FooterBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_footer_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_footer_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.footerBlock; }, getTemplate: function () { return window.templates.footerBlock; }, // eslint-disable-line func-names
modelEvents: _.extend({ modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render' 'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render'
}, _.omit(base.BlockView.prototype.modelEvents, 'change')), }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
TextEditorBehavior: { TextEditorBehavior: {
configurationFilter: function(originalSettings) { configurationFilter: function (originalSettings) { // eslint-disable-line func-names
return _.extend({}, originalSettings, { return _.extend({}, originalSettings, {
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(), mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle') mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
@ -53,37 +52,37 @@ define([
} }
} }
}), }),
onDragSubstituteBy: function() { return Module.FooterWidgetView; }, onDragSubstituteBy: function () { return Module.FooterWidgetView; }, // eslint-disable-line func-names
onRender: function() { onRender: function () { // eslint-disable-line func-names
this.toolsView = new Module.FooterBlockToolsView({ model: this.model }); this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onTextEditorChange: function(newContent) { onTextEditorChange: function (newContent) { // eslint-disable-line func-names
this.model.set('text', newContent); this.model.set('text', newContent);
}, },
onTextEditorFocus: function() { onTextEditorFocus: function () { // eslint-disable-line func-names
this.disableDragging(); this.disableDragging();
this.disableShowingTools(); this.disableShowingTools();
}, },
onTextEditorBlur: function() { onTextEditorBlur: function () { // eslint-disable-line func-names
this.enableDragging(); this.enableDragging();
this.enableShowingTools(); this.enableShowingTools();
} }
}); });
Module.FooterBlockToolsView = base.BlockToolsView.extend({ Module.FooterBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.FooterBlockSettingsView; } getSettingsView: function () { return Module.FooterBlockSettingsView; } // eslint-disable-line func-names
}); });
Module.FooterBlockSettingsView = base.BlockSettingsView.extend({ Module.FooterBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.footerBlockSettings; }, getTemplate: function () { return window.templates.footerBlockSettings; }, // eslint-disable-line func-names
events: function() { events: function () { // eslint-disable-line func-names
return { return {
'change .mailpoet_field_footer_text_color': _.partial(this.changeColorField, 'styles.text.fontColor'), 'change .mailpoet_field_footer_text_color': _.partial(this.changeColorField, 'styles.text.fontColor'),
'change .mailpoet_field_footer_text_font_family': _.partial(this.changeField, 'styles.text.fontFamily'), 'change .mailpoet_field_footer_text_font_family': _.partial(this.changeField, 'styles.text.fontFamily'),
'change .mailpoet_field_footer_text_size': _.partial(this.changeField, 'styles.text.fontSize'), 'change .mailpoet_field_footer_text_size': _.partial(this.changeField, 'styles.text.fontSize'),
'change #mailpoet_field_footer_link_color': _.partial(this.changeColorField, 'styles.link.fontColor'), 'change #mailpoet_field_footer_link_color': _.partial(this.changeColorField, 'styles.link.fontColor'),
'change #mailpoet_field_footer_link_underline': function(event) { 'change #mailpoet_field_footer_link_underline': function (event) { // eslint-disable-line func-names
this.model.set('styles.link.textDecoration', (event.target.checked) ? event.target.value : 'none'); this.model.set('styles.link.textDecoration', (event.target.checked) ? event.target.value : 'none');
}, },
'change .mailpoet_field_footer_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'), 'change .mailpoet_field_footer_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
@ -91,7 +90,7 @@ define([
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
}; };
}, },
templateContext: function() { templateContext: function () { // eslint-disable-line func-names
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
availableStyles: App.getAvailableStyles().toJSON() availableStyles: App.getAvailableStyles().toJSON()
}); });
@ -99,24 +98,24 @@ define([
}); });
Module.FooterWidgetView = base.WidgetView.extend({ Module.FooterWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.footerInsertion; }, getTemplate: function () { return window.templates.footerInsertion; }, // eslint-disable-line func-names
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () { // eslint-disable-line func-names
return new Module.FooterBlockModel(); return new Module.FooterBlockModel();
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) { // eslint-disable-line func-names
App.registerBlockType('footer', { BeforeStartApp.registerBlockType('footer', {
blockModel: Module.FooterBlockModel, blockModel: Module.FooterBlockModel,
blockView: Module.FooterBlockView blockView: Module.FooterBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'footer', name: 'footer',
widgetView: Module.FooterWidgetView, widgetView: Module.FooterWidgetView,
priority: 99 priority: 99

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* Header content block * Header content block
*/ */
@ -6,15 +8,12 @@ define([
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'underscore', 'underscore',
'mailpoet' 'mailpoet'
], function(App, BaseBlock, _, MailPoet) { ], function (App, BaseBlock, _, MailPoet) { // eslint-disable-line func-names
var Module = {};
'use strict'; var base = BaseBlock;
var Module = {},
base = BaseBlock;
Module.HeaderBlockModel = base.BlockModel.extend({ Module.HeaderBlockModel = base.BlockModel.extend({
defaults: function() { defaults: function () { // eslint-disable-line func-names
return this._getDefaults({ return this._getDefaults({
type: 'header', type: 'header',
text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>', text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
@ -39,13 +38,13 @@ define([
Module.HeaderBlockView = base.BlockView.extend({ Module.HeaderBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_header_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_header_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.headerBlock; }, getTemplate: function () { return window.templates.headerBlock; }, // eslint-disable-line func-names
modelEvents: _.extend({ modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render' 'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render'
}, _.omit(base.BlockView.prototype.modelEvents, 'change')), }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
TextEditorBehavior: { TextEditorBehavior: {
configurationFilter: function(originalSettings) { configurationFilter: function (originalSettings) { // eslint-disable-line func-names
return _.extend({}, originalSettings, { return _.extend({}, originalSettings, {
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(), mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle') mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
@ -53,37 +52,37 @@ define([
} }
} }
}), }),
onDragSubstituteBy: function() { return Module.HeaderWidgetView; }, onDragSubstituteBy: function () { return Module.HeaderWidgetView; }, // eslint-disable-line func-names
onRender: function() { onRender: function () { // eslint-disable-line func-names
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model }); this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onTextEditorChange: function(newContent) { onTextEditorChange: function (newContent) { // eslint-disable-line func-names
this.model.set('text', newContent); this.model.set('text', newContent);
}, },
onTextEditorFocus: function() { onTextEditorFocus: function () { // eslint-disable-line func-names
this.disableDragging(); this.disableDragging();
this.disableShowingTools(); this.disableShowingTools();
}, },
onTextEditorBlur: function() { onTextEditorBlur: function () { // eslint-disable-line func-names
this.enableDragging(); this.enableDragging();
this.enableShowingTools(); this.enableShowingTools();
} }
}); });
Module.HeaderBlockToolsView = base.BlockToolsView.extend({ Module.HeaderBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.HeaderBlockSettingsView; } getSettingsView: function () { return Module.HeaderBlockSettingsView; } // eslint-disable-line func-names
}); });
Module.HeaderBlockSettingsView = base.BlockSettingsView.extend({ Module.HeaderBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.headerBlockSettings; }, getTemplate: function () { return window.templates.headerBlockSettings; }, // eslint-disable-line func-names
events: function() { events: function () { // eslint-disable-line func-names
return { return {
'change .mailpoet_field_header_text_color': _.partial(this.changeColorField, 'styles.text.fontColor'), 'change .mailpoet_field_header_text_color': _.partial(this.changeColorField, 'styles.text.fontColor'),
'change .mailpoet_field_header_text_font_family': _.partial(this.changeField, 'styles.text.fontFamily'), 'change .mailpoet_field_header_text_font_family': _.partial(this.changeField, 'styles.text.fontFamily'),
'change .mailpoet_field_header_text_size': _.partial(this.changeField, 'styles.text.fontSize'), 'change .mailpoet_field_header_text_size': _.partial(this.changeField, 'styles.text.fontSize'),
'change #mailpoet_field_header_link_color': _.partial(this.changeColorField, 'styles.link.fontColor'), 'change #mailpoet_field_header_link_color': _.partial(this.changeColorField, 'styles.link.fontColor'),
'change #mailpoet_field_header_link_underline': function(event) { 'change #mailpoet_field_header_link_underline': function (event) { // eslint-disable-line func-names
this.model.set('styles.link.textDecoration', (event.target.checked) ? event.target.value : 'none'); this.model.set('styles.link.textDecoration', (event.target.checked) ? event.target.value : 'none');
}, },
'change .mailpoet_field_header_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'), 'change .mailpoet_field_header_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
@ -91,7 +90,7 @@ define([
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
}; };
}, },
templateContext: function() { templateContext: function () { // eslint-disable-line func-names
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
availableStyles: App.getAvailableStyles().toJSON() availableStyles: App.getAvailableStyles().toJSON()
}); });
@ -99,24 +98,24 @@ define([
}); });
Module.HeaderWidgetView = base.WidgetView.extend({ Module.HeaderWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.headerInsertion; }, getTemplate: function () { return window.templates.headerInsertion; }, // eslint-disable-line func-names
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () { // eslint-disable-line func-names
return new Module.HeaderBlockModel(); return new Module.HeaderBlockModel();
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) { // eslint-disable-line func-names
App.registerBlockType('header', { BeforeStartApp.registerBlockType('header', {
blockModel: Module.HeaderBlockModel, blockModel: Module.HeaderBlockModel,
blockView: Module.HeaderBlockView blockView: Module.HeaderBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'header', name: 'header',
widgetView: Module.HeaderWidgetView, widgetView: Module.HeaderWidgetView,
priority: 98 priority: 98

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/** /**
* Image content block * Image content block
*/ */
@ -7,16 +8,15 @@ define([
'underscore', 'underscore',
'mailpoet', 'mailpoet',
'jquery' 'jquery'
], function(App, BaseBlock, _, MailPoet, jQuery) { ], function (App, BaseBlock, _, MailPoet, jQuery) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
base = BaseBlock, var base = BaseBlock;
ImageWidgetView; var ImageWidgetView;
Module.ImageBlockModel = base.BlockModel.extend({ Module.ImageBlockModel = base.BlockModel.extend({
defaults: function() { defaults: function () {
return this._getDefaults({ return this._getDefaults({
type: 'image', type: 'image',
link: '', link: '',
@ -36,34 +36,45 @@ define([
Module.ImageBlockView = base.BlockView.extend({ Module.ImageBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_image_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_image_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.imageBlock; }, getTemplate: function () { return window.templates.imageBlock; },
onDragSubstituteBy: function() { return Module.ImageWidgetView; }, onDragSubstituteBy: function () { return Module.ImageWidgetView; },
templateContext: function() { templateContext: function () {
return _.extend({ return _.extend({
imageMissingSrc: App.getConfig().get('urls.imageMissing') imageMissingSrc: App.getConfig().get('urls.imageMissing')
}, base.BlockView.prototype.templateContext.apply(this)); }, base.BlockView.prototype.templateContext.apply(this));
}, },
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {} ResizableBehavior: {
elementSelector: '.mailpoet_image',
resizeHandleSelector: '.mailpoet_image_resize_handle',
onResize: function (event) {
var corner = this.$('.mailpoet_image').offset();
var width = event.pageX - corner.left;
this.view.model.set('width', width + 'px');
}
},
ShowSettingsBehavior: {
ignoreFrom: '.mailpoet_image_resize_handle'
}
}), }),
onRender: function() { onRender: function () {
this.toolsView = new Module.ImageBlockToolsView({ model: this.model }); this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
if (this.model.get('fullWidth')) { if (this.model.get('fullWidth')) {
this.$el.addClass('mailpoet_full_image'); this.$el.addClass('mailpoet_full_image');
} else { } else {
this.$el.removeClass('mailpoet_full_image'); this.$el.removeClass('mailpoet_full_image');
} }
this.$('.mailpoet_content').css('width', this.model.get('width'));
} }
}); });
Module.ImageBlockToolsView = base.BlockToolsView.extend({ Module.ImageBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.ImageBlockSettingsView; } getSettingsView: function () { return Module.ImageBlockSettingsView; }
}); });
Module.ImageBlockSettingsView = base.BlockSettingsView.extend({ Module.ImageBlockSettingsView = base.BlockSettingsView.extend({
onRender: function() { onRender: function () {
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-full-width'), { MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-full-width'), {
tooltipId: 'tooltip-editor-full-width', tooltipId: 'tooltip-editor-full-width',
tooltip: MailPoet.I18n.t('helpTooltipDesignerFullWidth') tooltip: MailPoet.I18n.t('helpTooltipDesignerFullWidth')
@ -73,8 +84,8 @@ define([
tooltip: MailPoet.I18n.t('helpTooltipDesignerIdealWidth') tooltip: MailPoet.I18n.t('helpTooltipDesignerIdealWidth')
}); });
}, },
getTemplate: function() { return window.templates.imageBlockSettings; }, getTemplate: function () { return window.templates.imageBlockSettings; },
events: function() { events: function () {
return { return {
'input .mailpoet_field_image_link': _.partial(this.changeField, 'link'), 'input .mailpoet_field_image_link': _.partial(this.changeField, 'link'),
'input .mailpoet_field_image_address': 'changeAddress', 'input .mailpoet_field_image_address': 'changeAddress',
@ -82,26 +93,52 @@ define([
'change .mailpoet_field_image_full_width': _.partial(this.changeBoolCheckboxField, 'fullWidth'), 'change .mailpoet_field_image_full_width': _.partial(this.changeBoolCheckboxField, 'fullWidth'),
'change .mailpoet_field_image_alignment': _.partial(this.changeField, 'styles.block.textAlign'), 'change .mailpoet_field_image_alignment': _.partial(this.changeField, 'styles.block.textAlign'),
'click .mailpoet_field_image_select_another_image': 'showMediaManager', 'click .mailpoet_field_image_select_another_image': 'showMediaManager',
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close',
'input .mailpoet_field_image_width': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width_input', _.partial(this.changePixelField, 'width').bind(this)),
'change .mailpoet_field_image_width': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width_input', _.partial(this.changePixelField, 'width').bind(this)),
'input .mailpoet_field_image_width_input': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width', _.partial(this.changePixelField, 'width').bind(this))
}; };
}, },
initialize: function(options) { modelEvents: function () {
return {
'change:maxWidth': 'updateMaxWidth',
'change:width': 'updateWidth'
};
},
updateValueAndCall: function (fieldToUpdate, callable, event) {
this.$(fieldToUpdate).val(jQuery(event.target).val());
callable(event);
},
updateMaxWidth: function () {
var maxWidth = parseInt(this.model.get('maxWidth'));
this.$('.mailpoet_field_image_width').attr('max', maxWidth);
this.$('.mailpoet_field_image_width_input').attr('max', maxWidth);
},
updateWidth: function () {
var width = parseInt(this.model.get('width'));
this.$('.mailpoet_field_image_width').val(width);
this.$('.mailpoet_field_image_width_input').val(width);
},
initialize: function (options) {
base.BlockSettingsView.prototype.initialize.apply(this, arguments); base.BlockSettingsView.prototype.initialize.apply(this, arguments);
if (options.showImageManager) { if (options.showImageManager) {
this.showMediaManager(); this.showMediaManager();
} }
}, },
showMediaManager: function() { showMediaManager: function () {
var that = this;
var MediaManager;
var theFrame;
if (this._mediaManager) { if (this._mediaManager) {
this._mediaManager.resetSelections(); this._mediaManager.resetSelections();
this._mediaManager.open(); this._mediaManager.open();
return; return;
} }
var MediaManager = window.wp.media.view.MediaFrame.Select.extend({ MediaManager = window.wp.media.view.MediaFrame.Select.extend({
initialize: function() { initialize: function () {
window.wp.media.view.MediaFrame.prototype.initialize.apply(this, arguments); window.wp.media.view.MediaFrame.prototype.initialize.apply(this, arguments);
_.defaults(this.options, { _.defaults(this.options, {
@ -119,16 +156,16 @@ define([
this.$el.addClass('hide-title'); this.$el.addClass('hide-title');
}, },
resetSelections: function() { resetSelections: function () {
this.state().get('selection').reset(); this.state().get('selection').reset();
}, },
createQuery: function(options) { createQuery: function (options) {
var query = window.wp.media.query(options); var query = window.wp.media.query(options);
return query; return query;
}, },
createStates: function() { createStates: function () {
var options = this.options; var options = this.options;
// Add the default states. // Add the default states.
@ -156,12 +193,13 @@ define([
}) })
]); ]);
if(window.wp.media.view.settings.post.featuredImageId) { if (window.wp.media.view.settings.post.featuredImageId) {
this.states.add(new window.wp.media.controller.FeaturedImage()); this.states.add(new window.wp.media.controller.FeaturedImage());
} }
}, },
bindHandlers: function() { bindHandlers: function () {
var handlers;
// from Select // from Select
this.on('router:create:browse', this.createRouter, this); this.on('router:create:browse', this.createRouter, this);
this.on('router:render:browse', this.browseRouter, this); this.on('router:render:browse', this.browseRouter, this);
@ -176,7 +214,7 @@ define([
this.on('updateExcluded', this.browseContent, this); this.on('updateExcluded', this.browseContent, this);
var handlers = { handlers = {
content: { content: {
embed: 'embedContent', embed: 'embedContent',
'edit-selection': 'editSelectionContent' 'edit-selection': 'editSelectionContent'
@ -186,20 +224,20 @@ define([
} }
}; };
_.each(handlers, function(regionHandlers, region) { _.each(handlers, function (regionHandlers, region) {
_.each(regionHandlers, function(callback, handler) { _.each(regionHandlers, function (callback, handler) {
this.on(region + ':render:' + handler, this[callback], this); this.on(region + ':render:' + handler, this[callback], this);
}, this); }, this);
}, this); }, this);
}, },
uploadContent: function() { uploadContent: function () {
window.wp.media.view.MediaFrame.Select.prototype.uploadContent.apply(this, arguments); window.wp.media.view.MediaFrame.Select.prototype.uploadContent.apply(this, arguments);
this.$el.addClass('hide-toolbar'); this.$el.addClass('hide-toolbar');
}, },
// Content // Content
embedContent: function() { embedContent: function () {
var view = new window.wp.media.view.Embed({ var view = new window.wp.media.view.Embed({
controller: this, controller: this,
model: this.state() model: this.state()
@ -209,10 +247,10 @@ define([
view.url.focus(); view.url.focus();
}, },
editSelectionContent: function() { editSelectionContent: function () {
var state = this.state(), var state = this.state();
selection = state.get('selection'), var selection = state.get('selection');
view; var view;
view = new window.wp.media.view.AttachmentsBrowser({ view = new window.wp.media.view.AttachmentsBrowser({
controller: this, controller: this,
@ -230,7 +268,7 @@ define([
text: 'Return to library', text: 'Return to library',
priority: -100, priority: -100,
click: function() { click: function () {
this.controller.content.mode('browse'); this.controller.content.mode('browse');
} }
}); });
@ -240,7 +278,7 @@ define([
}, },
// Toolbars // Toolbars
selectionStatusToolbar: function(view) { selectionStatusToolbar: function (view) {
var editable = this.state().get('editable'); var editable = this.state().get('editable');
view.set('selection', new window.wp.media.view.Selection({ view.set('selection', new window.wp.media.view.Selection({
@ -250,13 +288,13 @@ define([
// If the selection is editable, pass the callback to // If the selection is editable, pass the callback to
// switch the content mode. // switch the content mode.
editable: editable && function() { editable: editable && function () {
this.controller.content.mode('edit-selection'); this.controller.content.mode('edit-selection');
} }
}).render() ); }).render());
}, },
mainInsertToolbar: function(view) { mainInsertToolbar: function (view) {
var controller = this; var controller = this;
this.selectionStatusToolbar(view); this.selectionStatusToolbar(view);
@ -267,9 +305,9 @@ define([
text: 'Select Image', text: 'Select Image',
requires: { selection: true }, requires: { selection: true },
click: function() { click: function () {
var state = controller.state(), var state = controller.state();
selection = state.get('selection'); var selection = state.get('selection');
controller.close(); controller.close();
state.trigger('insert', selection).reset(); state.trigger('insert', selection).reset();
@ -277,7 +315,7 @@ define([
}); });
}, },
mainEmbedToolbar: function(toolbar) { mainEmbedToolbar: function (toolbar) {
var tbar = toolbar; var tbar = toolbar;
tbar.view = new window.wp.media.view.Toolbar.Embed({ tbar.view = new window.wp.media.view.Toolbar.Embed({
controller: this, controller: this,
@ -287,46 +325,42 @@ define([
}); });
var theFrame = new MediaManager({ theFrame = new MediaManager({
id: 'mailpoet-media-manager', id: 'mailpoet-media-manager',
frame: 'select', frame: 'select',
title: 'Select image', title: 'Select image',
editing: false, editing: false,
multiple: false, multiple: false,
library: { library: {
type: 'image' type: 'image'
}, },
displaySettings: false, displaySettings: false,
button: { button: {
text: 'Select' text: 'Select'
} }
}), });
that = this;
this._mediaManager = theFrame; this._mediaManager = theFrame;
this._mediaManager.on('insert', function() { this._mediaManager.on('insert', function () {
// Append media manager image selections to Images tab // Append media manager image selections to Images tab
var selection = theFrame.state().get('selection'); var selection = theFrame.state().get('selection');
selection.each(function(attachment) { selection.each(function (attachment) {
var sizes = attachment.get('sizes'), var sizes = attachment.get('sizes');
// Following advice from Becs, the target width should // Following advice from Becs, the target width should
// be a double of one column width to render well on // be a double of one column width to render well on
// retina screen devices // retina screen devices
targetImageWidth = 1320, var targetImageWidth = 1320;
// For main image use the size, that's closest to being 660px in width // Pick the width that is closest to target width
sizeKeys = _.keys(sizes), var increasingByWidthDifference = _.sortBy(
_.keys(sizes),
function (size) { return Math.abs(targetImageWidth - sizes[size].width); }
);
var bestWidth = sizes[_.first(increasingByWidthDifference)].width;
var imagesOfBestWidth = _.filter(_.values(sizes), function (size) { return size.width === bestWidth; });
// Pick the width that is closest to target width // Maximize the height if there are multiple images with same width
increasingByWidthDifference = _.sortBy( var mainSize = _.max(imagesOfBestWidth, function (size) { return size.height; });
_.keys(sizes),
function(size) { return Math.abs(targetImageWidth - sizes[size].width); }
),
bestWidth = sizes[_.first(increasingByWidthDifference)].width,
imagesOfBestWidth = _.filter(_.values(sizes), function(size) { return size.width === bestWidth; }),
// Maximize the height if there are multiple images with same width
mainSize = _.max(imagesOfBestWidth, function(size) { return size.height; });
that.model.set({ that.model.set({
height: mainSize.height + 'px', height: mainSize.height + 'px',
@ -341,11 +375,11 @@ define([
this._mediaManager.open(); this._mediaManager.open();
}, },
changeAddress: function(event) { changeAddress: function (event) {
var src = jQuery(event.target).val(); var src = jQuery(event.target).val();
var image = new Image(); var image = new Image();
image.onload = function() { image.onload = function () {
this.model.set({ this.model.set({
src: src, src: src,
width: image.naturalWidth + 'px', width: image.naturalWidth + 'px',
@ -355,7 +389,7 @@ define([
image.src = src; image.src = src;
}, },
onBeforeDestroy: function() { onBeforeDestroy: function () {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments); base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
if (typeof this._mediaManager === 'object') { if (typeof this._mediaManager === 'object') {
this._mediaManager.remove(); this._mediaManager.remove();
@ -364,14 +398,14 @@ define([
}); });
ImageWidgetView = base.WidgetView.extend({ ImageWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.imageInsertion; }, getTemplate: function () { return window.templates.imageInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.ImageBlockModel(); return new Module.ImageBlockModel();
}, },
onDrop: function(options) { onDrop: function (options) {
options.droppedView.triggerMethod('showSettings', { showImageManager: true }); options.droppedView.triggerMethod('showSettings', { showImageManager: true });
} }
} }
@ -379,13 +413,13 @@ define([
}); });
Module.ImageWidgetView = ImageWidgetView; Module.ImageWidgetView = ImageWidgetView;
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
App.registerBlockType('image', { BeforeStartApp.registerBlockType('image', {
blockModel: Module.ImageBlockModel, blockModel: Module.ImageBlockModel,
blockView: Module.ImageBlockView blockView: Module.ImageBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'image', name: 'image',
widgetView: Module.ImageWidgetView, widgetView: Module.ImageWidgetView,
priority: 91 priority: 91

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/** /**
* Posts block. * Posts block.
* *
@ -23,7 +24,7 @@ define([
'newsletter_editor/blocks/button', 'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider', 'newsletter_editor/blocks/divider',
'select2' 'select2'
], function( ], function (
Backbone, Backbone,
Marionette, Marionette,
Radio, Radio,
@ -36,15 +37,19 @@ define([
ButtonBlock, ButtonBlock,
DividerBlock DividerBlock
) { ) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
base = BaseBlock; var base = BaseBlock;
var PostsDisplayOptionsSettingsView;
var SinglePostSelectionSettingsView;
var EmptyPostSelectionSettingsView;
var PostSelectionSettingsView;
var PostsSelectionCollectionView;
Module.PostsBlockModel = base.BlockModel.extend({ Module.PostsBlockModel = base.BlockModel.extend({
stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'], stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
defaults: function() { defaults: function () {
return this._getDefaults({ return this._getDefaults({
type: 'posts', type: 'posts',
amount: '10', amount: '10',
@ -60,7 +65,7 @@ define([
titleIsLink: false, // false|true titleIsLink: false, // false|true
imageFullWidth: false, // true|false imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none' featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none' // imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText' showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:', authorPrecededBy: 'Author:',
showCategories: 'no', // 'no'|'aboveText'|'belowText' showCategories: 'no', // 'no'|'aboveText'|'belowText'
@ -79,7 +84,7 @@ define([
_transformedPosts: new (App.getBlockTypeModel('container'))() _transformedPosts: new (App.getBlockTypeModel('container'))()
}, App.getConfig().get('blockDefaults.posts')); }, App.getConfig().get('blockDefaults.posts'));
}, },
relations: function() { relations: function () {
return { return {
readMoreButton: App.getBlockTypeModel('button'), readMoreButton: App.getBlockTypeModel('button'),
divider: App.getBlockTypeModel('divider'), divider: App.getBlockTypeModel('divider'),
@ -88,11 +93,10 @@ define([
_transformedPosts: App.getBlockTypeModel('container') _transformedPosts: App.getBlockTypeModel('container')
}; };
}, },
initialize: function() { initialize: function () {
var that = this, var POST_REFRESH_DELAY_MS = 500;
POST_REFRESH_DELAY_MS = 500, var refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS);
refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS), var refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
// Attach Radio.Requests API primarily for highlighting // Attach Radio.Requests API primarily for highlighting
_.extend(this, Radio.Requests); _.extend(this, Radio.Requests);
@ -108,41 +112,42 @@ define([
this.on('insertSelectedPosts', this._insertSelectedPosts, this); this.on('insertSelectedPosts', this._insertSelectedPosts, this);
}, },
fetchAvailablePosts: function() { fetchAvailablePosts: function () {
var that = this; var that = this;
this.set('offset', 0); this.set('offset', 0);
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) { CommunicationComponent.getPosts(this.toJSON()).done(function (posts) {
that.get('_availablePosts').reset(posts); that.get('_availablePosts').reset(posts);
that.get('_selectedPosts').reset(); // Empty out the collection that.get('_selectedPosts').reset(); // Empty out the collection
that.trigger('change:_availablePosts'); that.trigger('change:_availablePosts');
}).fail(function() { }).fail(function () {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts')); MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
}); });
}, },
_loadMorePosts: function() { _loadMorePosts: function () {
var that = this, var that = this;
postCount = this.get('_availablePosts').length, var postCount = this.get('_availablePosts').length;
nextOffset = this.get('offset') + Number(this.get('amount')); var nextOffset = this.get('offset') + Number(this.get('amount'));
if(postCount === 0 || postCount < nextOffset) { if (postCount === 0 || postCount < nextOffset) {
// No more posts to load // No more posts to load
return false; return false;
} }
this.set('offset', nextOffset); this.set('offset', nextOffset);
this.trigger('loadingMorePosts'); this.trigger('loadingMorePosts');
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) { CommunicationComponent.getPosts(this.toJSON()).done(function (posts) {
that.get('_availablePosts').add(posts); that.get('_availablePosts').add(posts);
that.trigger('change:_availablePosts'); that.trigger('change:_availablePosts');
}).fail(function() { }).fail(function () {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts')); MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
}).always(function() { }).always(function () {
that.trigger('morePostsLoaded'); that.trigger('morePostsLoaded');
}); });
return true;
}, },
_refreshTransformedPosts: function() { _refreshTransformedPosts: function () {
var that = this, var that = this;
data = this.toJSON(); var data = this.toJSON();
data.posts = this.get('_selectedPosts').pluck('ID'); data.posts = this.get('_selectedPosts').pluck('ID');
@ -151,25 +156,24 @@ define([
return; return;
} }
CommunicationComponent.getTransformedPosts(data).done(function(posts) { CommunicationComponent.getTransformedPosts(data).done(function (posts) {
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true}); that.get('_transformedPosts').get('blocks').reset(posts, { parse: true });
}).fail(function() { }).fail(function () {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts')); MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
}); });
}, },
_insertSelectedPosts: function() { _insertSelectedPosts: function () {
var that = this, var data = this.toJSON();
data = this.toJSON(), var index = this.collection.indexOf(this);
index = this.collection.indexOf(this), var collection = this.collection;
collection = this.collection;
data.posts = this.get('_selectedPosts').pluck('ID'); data.posts = this.get('_selectedPosts').pluck('ID');
if (data.posts.length === 0) return; if (data.posts.length === 0) return;
CommunicationComponent.getTransformedPosts(data).done(function(posts) { CommunicationComponent.getTransformedPosts(data).done(function (posts) {
collection.add(posts, { at: index }); collection.add(posts, { at: index });
}).fail(function() { }).fail(function () {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts')); MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
}); });
} }
@ -177,46 +181,48 @@ define([
Module.PostsBlockView = base.BlockView.extend({ Module.PostsBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_posts_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_posts_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.postsBlock; }, getTemplate: function () { return window.templates.postsBlock; },
modelEvents: {}, // Forcefully disable all events modelEvents: {}, // Forcefully disable all events
regions: _.extend({ regions: _.extend({
postsRegion: '.mailpoet_posts_block_posts' postsRegion: '.mailpoet_posts_block_posts'
}, base.BlockView.prototype.regions), }, base.BlockView.prototype.regions),
onDragSubstituteBy: function() { return Module.PostsWidgetView; }, onDragSubstituteBy: function () { return Module.PostsWidgetView; },
initialize: function() { initialize: function () {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.toolsView = new Module.PostsBlockToolsView({ model: this.model }); this.toolsView = new Module.PostsBlockToolsView({ model: this.model });
this.model.reply('blockView', this.notifyAboutSelf, this); this.model.reply('blockView', this.notifyAboutSelf, this);
}, },
onRender: function() { onRender: function () {
var ContainerView;
var renderOptions;
if (!this.getRegion('toolsRegion').hasView()) { if (!this.getRegion('toolsRegion').hasView()) {
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
} }
this.trigger('showSettings'); this.trigger('showSettings');
var ContainerView = App.getBlockTypeView('container'), ContainerView = App.getBlockTypeView('container');
renderOptions = { renderOptions = {
disableTextEditor: true, disableTextEditor: true,
disableDragAndDrop: true, disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay') emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
}; };
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions })); this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
}, },
notifyAboutSelf: function() { notifyAboutSelf: function () {
return this; return this;
}, },
onBeforeDestroy: function() { onBeforeDestroy: function () {
this.model.stopReplying('blockView', this.notifyAboutSelf, this); this.model.stopReplying('blockView', this.notifyAboutSelf, this);
} }
}); });
Module.PostsBlockToolsView = base.BlockToolsView.extend({ Module.PostsBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.PostsBlockSettingsView; } getSettingsView: function () { return Module.PostsBlockSettingsView; }
}); });
Module.PostsBlockSettingsView = base.BlockSettingsView.extend({ Module.PostsBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.postsBlockSettings; }, getTemplate: function () { return window.templates.postsBlockSettings; },
regions: { regions: {
selectionRegion: '.mailpoet_settings_posts_selection', selectionRegion: '.mailpoet_settings_posts_selection',
displayOptionsRegion: '.mailpoet_settings_posts_display_options' displayOptionsRegion: '.mailpoet_settings_posts_display_options'
@ -226,19 +232,19 @@ define([
'click .mailpoet_settings_posts_show_post_selection': 'switchToPostSelection', 'click .mailpoet_settings_posts_show_post_selection': 'switchToPostSelection',
'click .mailpoet_settings_posts_insert_selected': 'insertPosts' 'click .mailpoet_settings_posts_insert_selected': 'insertPosts'
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON() model: this.model.toJSON()
}; };
}, },
initialize: function() { initialize: function () {
this.model.trigger('startEditing'); this.model.trigger('startEditing');
this.selectionView = new PostSelectionSettingsView({ model: this.model }); this.selectionView = new PostSelectionSettingsView({ model: this.model });
this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model }); this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model });
}, },
onRender: function() { onRender: function () {
var that = this, var that = this;
blockView = this.model.request('blockView'); this.model.request('blockView');
this.showChildView('selectionRegion', this.selectionView); this.showChildView('selectionRegion', this.selectionView);
this.showChildView('displayOptionsRegion', this.displayOptionsView); this.showChildView('displayOptionsRegion', this.displayOptionsView);
@ -248,7 +254,7 @@ define([
template: '', template: '',
position: 'right', position: 'right',
width: App.getConfig().get('sidepanelWidth'), width: App.getConfig().get('sidepanelWidth'),
onCancel: function() { onCancel: function () {
// Self destroy the block if the user closes settings modal // Self destroy the block if the user closes settings modal
that.model.destroy(); that.model.destroy();
} }
@ -258,7 +264,7 @@ define([
this.selectionView.triggerMethod('attach'); this.selectionView.triggerMethod('attach');
this.displayOptionsView.triggerMethod('attach'); this.displayOptionsView.triggerMethod('attach');
}, },
switchToDisplayOptions: function() { switchToDisplayOptions: function () {
// Switch content view // Switch content view
this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_closed'); this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_closed');
this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_closed'); this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_closed');
@ -267,7 +273,7 @@ define([
this.$('.mailpoet_settings_posts_show_display_options').addClass('mailpoet_hidden'); this.$('.mailpoet_settings_posts_show_display_options').addClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_show_post_selection').removeClass('mailpoet_hidden'); this.$('.mailpoet_settings_posts_show_post_selection').removeClass('mailpoet_hidden');
}, },
switchToPostSelection: function() { switchToPostSelection: function () {
// Switch content view // Switch content view
this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_closed'); this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_closed');
this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_closed'); this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_closed');
@ -276,43 +282,43 @@ define([
this.$('.mailpoet_settings_posts_show_post_selection').addClass('mailpoet_hidden'); this.$('.mailpoet_settings_posts_show_post_selection').addClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_show_display_options').removeClass('mailpoet_hidden'); this.$('.mailpoet_settings_posts_show_display_options').removeClass('mailpoet_hidden');
}, },
insertPosts: function() { insertPosts: function () {
this.model.trigger('insertSelectedPosts'); this.model.trigger('insertSelectedPosts');
this.model.destroy(); this.model.destroy();
this.close(); this.close();
} }
}); });
var PostsSelectionCollectionView = Marionette.CollectionView.extend({ PostsSelectionCollectionView = Marionette.CollectionView.extend({
className: 'mailpoet_post_scroll_container', className: 'mailpoet_post_scroll_container',
childView: function() { return SinglePostSelectionSettingsView; }, childView: function () { return SinglePostSelectionSettingsView; },
emptyView: function() { return EmptyPostSelectionSettingsView; }, emptyView: function () { return EmptyPostSelectionSettingsView; },
childViewOptions: function() { childViewOptions: function () {
return { return {
blockModel: this.blockModel blockModel: this.blockModel
}; };
}, },
initialize: function(options) { initialize: function (options) {
this.blockModel = options.blockModel; this.blockModel = options.blockModel;
}, },
events: { events: {
scroll: 'onPostsScroll' scroll: 'onPostsScroll'
}, },
onPostsScroll: function(event) { onPostsScroll: function (event) {
var $postsBox = jQuery(event.target); var $postsBox = jQuery(event.target);
if($postsBox.scrollTop() + $postsBox.innerHeight() >= $postsBox[0].scrollHeight){ if ($postsBox.scrollTop() + $postsBox.innerHeight() >= $postsBox[0].scrollHeight) {
// Load more posts if scrolled to bottom // Load more posts if scrolled to bottom
this.blockModel.trigger('loadMorePosts'); this.blockModel.trigger('loadMorePosts');
} }
} }
}); });
var PostSelectionSettingsView = Marionette.View.extend({ PostSelectionSettingsView = Marionette.View.extend({
getTemplate: function() { return window.templates.postSelectionPostsBlockSettings; }, getTemplate: function () { return window.templates.postSelectionPostsBlockSettings; },
regions: { regions: {
posts: '.mailpoet_post_selection_container' posts: '.mailpoet_post_selection_container'
}, },
events: function() { events: function () {
return { return {
'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'), 'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'),
'change .mailpoet_posts_post_status': _.partial(this.changeField, 'postStatus'), 'change .mailpoet_posts_post_status': _.partial(this.changeField, 'postStatus'),
@ -320,30 +326,31 @@ define([
}; };
}, },
modelEvents: { modelEvents: {
'change:offset': function(model, value) { 'change:offset': function (model, value) {
// Scroll posts view to top if settings are changed // Scroll posts view to top if settings are changed
if (value === 0) { if (value === 0) {
this.$('.mailpoet_post_scroll_container').scrollTop(0); this.$('.mailpoet_post_scroll_container').scrollTop(0);
} }
}, },
loadingMorePosts: function() { loadingMorePosts: function () {
this.$('.mailpoet_post_selection_loading').css('visibility', 'visible'); this.$('.mailpoet_post_selection_loading').css('visibility', 'visible');
}, },
morePostsLoaded: function() { morePostsLoaded: function () {
this.$('.mailpoet_post_selection_loading').css('visibility', 'hidden'); this.$('.mailpoet_post_selection_loading').css('visibility', 'hidden');
} }
}, },
onRender: function() { onRender: function () {
var postsView;
// Dynamically update available post types // Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this)); CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
var postsView = new PostsSelectionCollectionView({ postsView = new PostsSelectionCollectionView({
collection: this.model.get('_availablePosts'), collection: this.model.get('_availablePosts'),
blockModel: this.model blockModel: this.model
}); });
this.showChildView('posts', postsView); this.showChildView('posts', postsView);
}, },
onAttach: function() { onAttach: function () {
var that = this; var that = this;
this.$('.mailpoet_posts_categories_and_tags').select2({ this.$('.mailpoet_posts_categories_and_tags').select2({
@ -356,35 +363,36 @@ define([
term: params.term term: params.term
}; };
}, },
transport: function(options, success, failure) { transport: function (options, success, failure) {
var taxonomies; var taxonomies;
var termsPromise;
var promise = CommunicationComponent.getTaxonomies( var promise = CommunicationComponent.getTaxonomies(
that.model.get('contentType') that.model.get('contentType')
).then(function(tax) { ).then(function (tax) {
taxonomies = tax; taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched // Fetch available terms based on the list of taxonomies already fetched
var promise = CommunicationComponent.getTerms({ termsPromise = CommunicationComponent.getTerms({
search: options.data.term, search: options.data.term,
taxonomies: _.keys(taxonomies) taxonomies: _.keys(taxonomies)
}).then(function(terms) { }).then(function (terms) {
return { return {
taxonomies: taxonomies, taxonomies: taxonomies,
terms: terms terms: terms
}; };
}); });
return promise; return termsPromise;
}); });
promise.then(success); promise.then(success);
promise.fail(failure); promise.fail(failure);
return promise; return promise;
}, },
processResults: function(data) { processResults: function (data) {
// Transform taxonomies and terms into select2 compatible format // Transform taxonomies and terms into select2 compatible format
return { return {
results: _.map( results: _.map(
data.terms, data.terms,
function(item) { function (item) {
return _.defaults({ return _.defaults({
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name, text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id id: item.term_id
@ -395,29 +403,29 @@ define([
} }
} }
}).on({ }).on({
'select2:select': function(event) { 'select2:select': function (event) {
var terms = that.model.get('terms'); var terms = that.model.get('terms');
terms.add(event.params.data); terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly // Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON()); that.model.set('terms', terms.toJSON());
}, },
'select2:unselect': function(event) { 'select2:unselect': function (event) {
var terms = that.model.get('terms'); var terms = that.model.get('terms');
terms.remove(event.params.data); terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly // Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON()); that.model.set('terms', terms.toJSON());
} }
}).trigger( 'change' ); }).trigger('change');
}, },
changeField: function(field, event) { changeField: function (field, event) {
this.model.set(field, jQuery(event.target).val()); this.model.set(field, jQuery(event.target).val());
}, },
_updateContentTypes: function(postTypes) { _updateContentTypes: function (postTypes) {
var select = this.$('.mailpoet_settings_posts_content_type'), var select = this.$('.mailpoet_settings_posts_content_type');
selectedValue = this.model.get('contentType'); var selectedValue = this.model.get('contentType');
select.find('option').remove(); select.find('option').remove();
_.each(postTypes, function(type) { _.each(postTypes, function (type) {
select.append(jQuery('<option>', { select.append(jQuery('<option>', {
value: type.name, value: type.name,
text: type.label text: type.label
@ -427,29 +435,29 @@ define([
} }
}); });
var EmptyPostSelectionSettingsView = Marionette.View.extend({ EmptyPostSelectionSettingsView = Marionette.View.extend({
getTemplate: function() { return window.templates.emptyPostPostsBlockSettings; } getTemplate: function () { return window.templates.emptyPostPostsBlockSettings; }
}); });
var SinglePostSelectionSettingsView = Marionette.View.extend({ SinglePostSelectionSettingsView = Marionette.View.extend({
getTemplate: function() { return window.templates.singlePostPostsBlockSettings; }, getTemplate: function () { return window.templates.singlePostPostsBlockSettings; },
events: function() { events: function () {
return { return {
'change .mailpoet_select_post_checkbox': 'postSelectionChange' 'change .mailpoet_select_post_checkbox': 'postSelectionChange'
}; };
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
index: this._index index: this._index
}; };
}, },
initialize: function(options) { initialize: function (options) {
this.blockModel = options.blockModel; this.blockModel = options.blockModel;
}, },
postSelectionChange: function(event) { postSelectionChange: function (event) {
var checkBox = jQuery(event.target), var checkBox = jQuery(event.target);
selectedPostsCollection = this.blockModel.get('_selectedPosts'); var selectedPostsCollection = this.blockModel.get('_selectedPosts');
if (checkBox.prop('checked')) { if (checkBox.prop('checked')) {
selectedPostsCollection.add(this.model); selectedPostsCollection.add(this.model);
} else { } else {
@ -458,9 +466,9 @@ define([
} }
}); });
var PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({ PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.displayOptionsPostsBlockSettings; }, getTemplate: function () { return window.templates.displayOptionsPostsBlockSettings; },
events: function() { events: function () {
return { return {
'click .mailpoet_posts_select_button': 'showButtonSettings', 'click .mailpoet_posts_select_button': 'showButtonSettings',
'click .mailpoet_posts_select_divider': 'showDividerSettings', 'click .mailpoet_posts_select_divider': 'showDividerSettings',
@ -483,12 +491,12 @@ define([
'change .mailpoet_posts_sort_by': _.partial(this.changeField, 'sortBy') 'change .mailpoet_posts_sort_by': _.partial(this.changeField, 'sortBy')
}; };
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON() model: this.model.toJSON()
}; };
}, },
showButtonSettings: function(event) { showButtonSettings: function () {
var buttonModule = ButtonBlock; var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({ (new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'), model: this.model.get('readMoreButton'),
@ -499,7 +507,7 @@ define([
} }
})).render(); })).render();
}, },
showDividerSettings: function(event) { showDividerSettings: function () {
var dividerModule = DividerBlock; var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({ (new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'), model: this.model.get('divider'),
@ -509,7 +517,7 @@ define([
} }
})).render(); })).render();
}, },
changeReadMoreType: function(event) { changeReadMoreType: function (event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'link') { if (value == 'link') {
this.$('.mailpoet_posts_read_more_text').removeClass('mailpoet_hidden'); this.$('.mailpoet_posts_read_more_text').removeClass('mailpoet_hidden');
@ -520,7 +528,7 @@ define([
} }
this.changeField('readMoreType', event); this.changeField('readMoreType', event);
}, },
changeDisplayType: function(event) { changeDisplayType: function (event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'titleOnly') { if (value == 'titleOnly') {
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden'); this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
@ -547,7 +555,7 @@ define([
this.changeField('displayType', event); this.changeField('displayType', event);
}, },
changeTitleFormat: function(event) { changeTitleFormat: function (event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'ul') { if (value == 'ul') {
this.$('.mailpoet_posts_non_title_list_options').addClass('mailpoet_hidden'); this.$('.mailpoet_posts_non_title_list_options').addClass('mailpoet_hidden');
@ -564,24 +572,24 @@ define([
}); });
Module.PostsWidgetView = base.WidgetView.extend({ Module.PostsWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.postsInsertion; }, getTemplate: function () { return window.templates.postsInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.PostsBlockModel({}, { parse: true }); return new Module.PostsBlockModel({}, { parse: true });
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
App.registerBlockType('posts', { BeforeStartApp.registerBlockType('posts', {
blockModel: Module.PostsBlockModel, blockModel: Module.PostsBlockModel,
blockView: Module.PostsBlockView blockView: Module.PostsBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'posts', name: 'posts',
widgetView: Module.PostsWidgetView, widgetView: Module.PostsWidgetView,
priority: 96 priority: 96

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/** /**
* Social icons content block * Social icons content block
*/ */
@ -9,19 +10,19 @@ define([
'backbone.supermodel', 'backbone.supermodel',
'underscore', 'underscore',
'jquery' 'jquery'
], function(App, BaseBlock, Backbone, Marionette, SuperModel, _, jQuery) { ], function (App, BaseBlock, Backbone, Marionette, SuperModel, _, jQuery) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
base = BaseBlock, var base = BaseBlock;
SocialBlockSettingsIconSelectorView, var SocialBlockSettingsIconSelectorView;
SocialBlockSettingsIconView, var SocialBlockSettingsIconView;
SocialBlockSettingsIconCollectionView, var SocialBlockSettingsIconCollectionView;
SocialBlockSettingsStylesView; var SocialBlockSettingsStylesView;
var SocialIconView;
Module.SocialIconModel = SuperModel.extend({ Module.SocialIconModel = SuperModel.extend({
defaults: function() { defaults: function () {
var defaultValues = App.getConfig().get('socialIcons.custom'); var defaultValues = App.getConfig().get('socialIcons.custom');
return { return {
type: 'socialIcon', type: 'socialIcon',
@ -33,19 +34,19 @@ define([
text: defaultValues.get('title') text: defaultValues.get('title')
}; };
}, },
initialize: function(options) { initialize: function () {
var that = this; var that = this;
// Make model swap to default values for that type when iconType changes // Make model swap to default values for that type when iconType changes
this.on('change:iconType', function() { this.on('change:iconType', function () {
var defaultValues = App.getConfig().get('socialIcons').get(that.get('iconType')), var defaultValues = App.getConfig().get('socialIcons').get(that.get('iconType'));
iconSet = that.collection.iconBlockModel.getIconSet(); var iconSet = that.collection.iconBlockModel.getIconSet();
this.set({ this.set({
link: defaultValues.get('defaultLink'), link: defaultValues.get('defaultLink'),
image: iconSet.get(that.get('iconType')), image: iconSet.get(that.get('iconType')),
text: defaultValues.get('title') text: defaultValues.get('title')
}); });
}, this); }, this);
this.on('change', function() { App.getChannel().trigger('autoSave'); }); this.on('change', function () { App.getChannel().trigger('autoSave'); });
} }
}); });
@ -55,7 +56,7 @@ define([
Module.SocialBlockModel = base.BlockModel.extend({ Module.SocialBlockModel = base.BlockModel.extend({
name: 'iconBlockModel', name: 'iconBlockModel',
defaults: function() { defaults: function () {
return this._getDefaults({ return this._getDefaults({
type: 'social', type: 'social',
iconSet: 'default', iconSet: 'default',
@ -65,31 +66,31 @@ define([
relations: { relations: {
icons: Module.SocialIconCollectionModel icons: Module.SocialIconCollectionModel
}, },
initialize: function() { initialize: function () {
this.get('icons').on('add remove change', this._iconsChanged, this); this.get('icons').on('add remove change', this._iconsChanged, this);
this.on('change:iconSet', this.changeIconSet, this); this.on('change:iconSet', this.changeIconSet, this);
}, },
getIconSet: function() { getIconSet: function () {
return App.getAvailableStyles().get('socialIconSets').get(this.get('iconSet')); return App.getAvailableStyles().get('socialIconSets').get(this.get('iconSet'));
}, },
changeIconSet: function() { changeIconSet: function () {
var iconSet = this.getIconSet(); var iconSet = this.getIconSet();
_.each(this.get('icons').models, function(model) { _.each(this.get('icons').models, function (model) {
model.set('image', iconSet.get(model.get('iconType'))); model.set('image', iconSet.get(model.get('iconType')));
}); });
}, },
_iconsChanged: function() { _iconsChanged: function () {
App.getChannel().trigger('autoSave'); App.getChannel().trigger('autoSave');
} }
}); });
var SocialIconView = Marionette.View.extend({ SocialIconView = Marionette.View.extend({
tagName: 'span', tagName: 'span',
getTemplate: function() { return window.templates.socialIconBlock; }, getTemplate: function () { return window.templates.socialIconBlock; },
modelEvents: { modelEvents: {
change: 'render' change: 'render'
}, },
templateContext: function() { templateContext: function () {
var allIconSets = App.getAvailableStyles().get('socialIconSets'); var allIconSets = App.getAvailableStyles().get('socialIconSets');
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
@ -105,7 +106,7 @@ define([
Module.SocialBlockView = base.BlockView.extend({ Module.SocialBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_social_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_social_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.socialBlock; }, getTemplate: function () { return window.templates.socialBlock; },
regions: _.extend({}, base.BlockView.prototype.regions, { regions: _.extend({}, base.BlockView.prototype.regions, {
icons: '.mailpoet_social' icons: '.mailpoet_social'
}), }),
@ -115,8 +116,8 @@ define([
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {} ShowSettingsBehavior: {}
}), }),
onDragSubstituteBy: function() { return Module.SocialWidgetView; }, onDragSubstituteBy: function () { return Module.SocialWidgetView; },
onRender: function() { onRender: function () {
this.toolsView = new Module.SocialBlockToolsView({ model: this.model }); this.toolsView = new Module.SocialBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
this.showChildView('icons', new Module.SocialIconCollectionView({ this.showChildView('icons', new Module.SocialIconCollectionView({
@ -126,28 +127,28 @@ define([
}); });
Module.SocialBlockToolsView = base.BlockToolsView.extend({ Module.SocialBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.SocialBlockSettingsView; } getSettingsView: function () { return Module.SocialBlockSettingsView; }
}); });
// Sidebar view container // Sidebar view container
Module.SocialBlockSettingsView = base.BlockSettingsView.extend({ Module.SocialBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.socialBlockSettings; }, getTemplate: function () { return window.templates.socialBlockSettings; },
regions: { regions: {
iconRegion: '#mailpoet_social_icons_selection', iconRegion: '#mailpoet_social_icons_selection',
stylesRegion: '#mailpoet_social_icons_styles' stylesRegion: '#mailpoet_social_icons_styles'
}, },
events: function() { events: function () {
return { return {
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
}; };
}, },
initialize: function() { initialize: function () {
base.BlockSettingsView.prototype.initialize.apply(this, arguments); base.BlockSettingsView.prototype.initialize.apply(this, arguments);
this._iconSelectorView = new SocialBlockSettingsIconSelectorView({ model: this.model }); this._iconSelectorView = new SocialBlockSettingsIconSelectorView({ model: this.model });
this._stylesView = new SocialBlockSettingsStylesView({ model: this.model }); this._stylesView = new SocialBlockSettingsStylesView({ model: this.model });
}, },
onRender: function() { onRender: function () {
this.showChildView('iconRegion', this._iconSelectorView); this.showChildView('iconRegion', this._iconSelectorView);
this.showChildView('stylesRegion', this._stylesView); this.showChildView('stylesRegion', this._stylesView);
} }
@ -155,8 +156,8 @@ define([
// Single icon settings view, used by the selector view // Single icon settings view, used by the selector view
SocialBlockSettingsIconView = Marionette.View.extend({ SocialBlockSettingsIconView = Marionette.View.extend({
getTemplate: function() { return window.templates.socialSettingsIcon; }, getTemplate: function () { return window.templates.socialSettingsIcon; },
events: function() { events: function () {
return { return {
'click .mailpoet_delete_block': 'deleteIcon', 'click .mailpoet_delete_block': 'deleteIcon',
'change .mailpoet_social_icon_field_type': _.partial(this.changeField, 'iconType'), 'change .mailpoet_social_icon_field_type': _.partial(this.changeField, 'iconType'),
@ -167,35 +168,36 @@ define([
}, },
modelEvents: { modelEvents: {
'change:iconType': 'render', 'change:iconType': 'render',
'change:image': function() { 'change:image': function () {
this.$('.mailpoet_social_icon_image').attr('src', this.model.get('image')); this.$('.mailpoet_social_icon_image').attr('src', this.model.get('image'));
}, },
'change:text': function() { 'change:text': function () {
this.$('.mailpoet_social_icon_image').attr('alt', this.model.get('text')); this.$('.mailpoet_social_icon_image').attr('alt', this.model.get('text'));
} }
}, },
templateContext: function() { templateContext: function () {
var icons = App.getConfig().get('socialIcons'), var icons = App.getConfig().get('socialIcons');
// Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...] // Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...]
availableIconTypes = _.map(_.keys(icons.attributes), function(key) { return { iconType: key, title: icons.get(key).get('title') }; }), var availableIconTypes = _.map(_.keys(icons.attributes), function (key) { return { iconType: key, title: icons.get(key).get('title') }; });
allIconSets = App.getAvailableStyles().get('socialIconSets'); var allIconSets = App.getAvailableStyles().get('socialIconSets');
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
iconTypes: availableIconTypes, iconTypes: availableIconTypes,
currentType: icons.get(this.model.get('iconType')).toJSON(), currentType: icons.get(this.model.get('iconType')).toJSON(),
allIconSets: allIconSets.toJSON() allIconSets: allIconSets.toJSON()
}); });
}, },
deleteIcon: function() { deleteIcon: function () {
this.model.destroy(); this.model.destroy();
}, },
changeLink: function(event) { changeLink: function (event) {
if (this.model.get('iconType') === 'email') { if (this.model.get('iconType') === 'email') {
this.model.set('link', 'mailto:' + jQuery(event.target).val()); this.model.set('link', 'mailto:' + jQuery(event.target).val());
} else { } else {
return this.changeField('link', event); return this.changeField('link', event);
} }
return undefined;
}, },
changeField: function(field, event) { changeField: function (field, event) {
this.model.set(field, jQuery(event.target).val()); this.model.set(field, jQuery(event.target).val());
} }
}); });
@ -212,7 +214,7 @@ define([
// Select icons section container view // Select icons section container view
SocialBlockSettingsIconSelectorView = Marionette.View.extend({ SocialBlockSettingsIconSelectorView = Marionette.View.extend({
getTemplate: function() { return window.templates.socialSettingsIconSelector; }, getTemplate: function () { return window.templates.socialSettingsIconSelector; },
regions: { regions: {
icons: '#mailpoet_social_icon_selector_contents' icons: '#mailpoet_social_icon_selector_contents'
}, },
@ -222,11 +224,11 @@ define([
modelEvents: { modelEvents: {
'change:iconSet': 'render' 'change:iconSet': 'render'
}, },
addSocialIcon: function() { addSocialIcon: function () {
// Add a social icon with default values // Add a social icon with default values
this.model.get('icons').add({}); this.model.get('icons').add({});
}, },
onRender: function() { onRender: function () {
this.showChildView('icons', new SocialBlockSettingsIconCollectionView({ this.showChildView('icons', new SocialBlockSettingsIconCollectionView({
collection: this.model.get('icons') collection: this.model.get('icons')
})); }));
@ -235,17 +237,17 @@ define([
}); });
SocialBlockSettingsStylesView = Marionette.View.extend({ SocialBlockSettingsStylesView = Marionette.View.extend({
getTemplate: function() { return window.templates.socialSettingsStyles; }, getTemplate: function () { return window.templates.socialSettingsStyles; },
modelEvents: { modelEvents: {
change: 'render' change: 'render'
}, },
events: { events: {
'click .mailpoet_social_icon_set': 'changeSocialIconSet' 'click .mailpoet_social_icon_set': 'changeSocialIconSet'
}, },
initialize: function() { initialize: function () {
this.listenTo(this.model.get('icons'), 'add remove change', this.render); this.listenTo(this.model.get('icons'), 'add remove change', this.render);
}, },
templateContext: function() { templateContext: function () {
var allIconSets = App.getAvailableStyles().get('socialIconSets'); var allIconSets = App.getAvailableStyles().get('socialIconSets');
return { return {
activeSet: this.model.get('iconSet'), activeSet: this.model.get('iconSet'),
@ -254,20 +256,20 @@ define([
availableSocialIcons: this.model.get('icons').pluck('iconType') availableSocialIcons: this.model.get('icons').pluck('iconType')
}; };
}, },
changeSocialIconSet: function(event) { changeSocialIconSet: function (event) {
this.model.set('iconSet', jQuery(event.currentTarget).data('setname')); this.model.set('iconSet', jQuery(event.currentTarget).data('setname'));
}, },
onBeforeDestroy: function() { onBeforeDestroy: function () {
this.model.get('icons').off('add remove', this.render, this); this.model.get('icons').off('add remove', this.render, this);
} }
}); });
Module.SocialWidgetView = base.WidgetView.extend({ Module.SocialWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.socialInsertion; }, getTemplate: function () { return window.templates.socialInsertion; },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () {
return new Module.SocialBlockModel({ return new Module.SocialBlockModel({
type: 'social', type: 'social',
iconSet: 'default', iconSet: 'default',
@ -297,13 +299,13 @@ define([
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
App.registerBlockType('social', { BeforeStartApp.registerBlockType('social', {
blockModel: Module.SocialBlockModel, blockModel: Module.SocialBlockModel,
blockView: Module.SocialBlockView blockView: Module.SocialBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'social', name: 'social',
widgetView: Module.SocialWidgetView, widgetView: Module.SocialWidgetView,
priority: 95 priority: 95

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* Spacer content block * Spacer content block
*/ */
@ -5,15 +7,12 @@ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'underscore' 'underscore'
], function(App, BaseBlock, _) { ], function (App, BaseBlock, _) { // eslint-disable-line func-names
var Module = {};
'use strict'; var base = BaseBlock;
var Module = {},
base = BaseBlock;
Module.SpacerBlockModel = base.BlockModel.extend({ Module.SpacerBlockModel = base.BlockModel.extend({
defaults: function() { defaults: function () { // eslint-disable-line func-names
return this._getDefaults({ return this._getDefaults({
type: 'spacer', type: 'spacer',
styles: { styles: {
@ -28,7 +27,7 @@ define([
Module.SpacerBlockView = base.BlockView.extend({ Module.SpacerBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_spacer_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_spacer_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.spacerBlock; }, getTemplate: function () { return window.templates.spacerBlock; }, // eslint-disable-line func-names
behaviors: _.defaults({ behaviors: _.defaults({
ResizableBehavior: { ResizableBehavior: {
elementSelector: '.mailpoet_spacer', elementSelector: '.mailpoet_spacer',
@ -41,33 +40,33 @@ define([
} }
}, base.BlockView.prototype.behaviors), }, base.BlockView.prototype.behaviors),
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'),
onDragSubstituteBy: function() { return Module.SpacerWidgetView; }, onDragSubstituteBy: function () { return Module.SpacerWidgetView; }, // eslint-disable-line func-names
initialize: function() { initialize: function () { // eslint-disable-line func-names
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.listenTo(this.model, 'change:styles.block.backgroundColor', this.render); this.listenTo(this.model, 'change:styles.block.backgroundColor', this.render);
this.listenTo(this.model, 'change:styles.block.height', this.changeHeight); this.listenTo(this.model, 'change:styles.block.height', this.changeHeight);
}, },
onRender: function() { onRender: function () { // eslint-disable-line func-names
this.toolsView = new Module.SpacerBlockToolsView({ model: this.model }); this.toolsView = new Module.SpacerBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
changeHeight: function() { changeHeight: function () { // eslint-disable-line func-names
this.$('.mailpoet_spacer').css('height', this.model.get('styles.block.height')); this.$('.mailpoet_spacer').css('height', this.model.get('styles.block.height'));
this.$('.mailpoet_resize_handle_text').text(this.model.get('styles.block.height')); this.$('.mailpoet_resize_handle_text').text(this.model.get('styles.block.height'));
}, },
onBeforeDestroy: function() { onBeforeDestroy: function () { // eslint-disable-line func-names
this.stopListening(this.model); this.stopListening(this.model);
} }
}); });
Module.SpacerBlockToolsView = base.BlockToolsView.extend({ Module.SpacerBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.SpacerBlockSettingsView; } getSettingsView: function () { return Module.SpacerBlockSettingsView; } // eslint-disable-line func-names
}); });
Module.SpacerBlockSettingsView = base.BlockSettingsView.extend({ Module.SpacerBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.spacerBlockSettings; }, getTemplate: function () { return window.templates.spacerBlockSettings; }, // eslint-disable-line func-names
events: function() { events: function () { // eslint-disable-line func-names
return { return {
'change .mailpoet_field_spacer_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'), 'change .mailpoet_field_spacer_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
'click .mailpoet_done_editing': 'close' 'click .mailpoet_done_editing': 'close'
@ -76,24 +75,24 @@ define([
}); });
Module.SpacerWidgetView = base.WidgetView.extend({ Module.SpacerWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.spacerInsertion; }, getTemplate: function () { return window.templates.spacerInsertion; }, // eslint-disable-line func-names
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () { // eslint-disable-line func-names
return new Module.SpacerBlockModel(); return new Module.SpacerBlockModel();
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) { // eslint-disable-line func-names
App.registerBlockType('spacer', { BeforeStartApp.registerBlockType('spacer', {
blockModel: Module.SpacerBlockModel, blockModel: Module.SpacerBlockModel,
blockView: Module.SpacerBlockView blockView: Module.SpacerBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'spacer', name: 'spacer',
widgetView: Module.SpacerWidgetView, widgetView: Module.SpacerWidgetView,
priority: 94 priority: 94

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* Text content block * Text content block
*/ */
@ -6,15 +8,12 @@ define([
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'underscore', 'underscore',
'mailpoet' 'mailpoet'
], function(App, BaseBlock, _, MailPoet) { ], function (App, BaseBlock, _, MailPoet) { // eslint-disable-line func-names
var Module = {};
'use strict'; var base = BaseBlock;
var Module = {},
base = BaseBlock;
Module.TextBlockModel = base.BlockModel.extend({ Module.TextBlockModel = base.BlockModel.extend({
defaults: function() { defaults: function () { // eslint-disable-line func-names
return this._getDefaults({ return this._getDefaults({
type: 'text', type: 'text',
text: 'Edit this to insert text' text: 'Edit this to insert text'
@ -24,7 +23,7 @@ define([
Module.TextBlockView = base.BlockView.extend({ Module.TextBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_text_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_text_block mailpoet_droppable_block',
getTemplate: function() { return window.templates.textBlock; }, getTemplate: function () { return window.templates.textBlock; }, // eslint-disable-line func-names
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
TextEditorBehavior: { TextEditorBehavior: {
@ -34,7 +33,7 @@ define([
invalidElements: 'script', invalidElements: 'script',
blockFormats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p', blockFormats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
plugins: 'link lists code textcolor colorpicker mailpoet_shortcodes paste', plugins: 'link lists code textcolor colorpicker mailpoet_shortcodes paste',
configurationFilter: function(originalSettings) { configurationFilter: function (originalSettings) { // eslint-disable-line func-names
return _.extend({}, originalSettings, { return _.extend({}, originalSettings, {
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(), mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle') mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
@ -42,7 +41,7 @@ define([
} }
} }
}), }),
initialize: function(options) { initialize: function (options) { // eslint-disable-line func-names
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.renderOptions = _.defaults(options.renderOptions || {}, { this.renderOptions = _.defaults(options.renderOptions || {}, {
@ -51,8 +50,8 @@ define([
this.disableTextEditor = this.renderOptions.disableTextEditor; this.disableTextEditor = this.renderOptions.disableTextEditor;
}, },
onDragSubstituteBy: function() { return Module.TextWidgetView; }, onDragSubstituteBy: function () { return Module.TextWidgetView; }, // eslint-disable-line func-names
onRender: function() { onRender: function () { // eslint-disable-line func-names
this.toolsView = new Module.TextBlockToolsView({ this.toolsView = new Module.TextBlockToolsView({
model: this.model, model: this.model,
tools: { tools: {
@ -61,46 +60,46 @@ define([
}); });
this.showChildView('toolsRegion', this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onTextEditorChange: function(newContent) { onTextEditorChange: function (newContent) { // eslint-disable-line func-names
this.model.set('text', newContent); this.model.set('text', newContent);
}, },
onTextEditorFocus: function() { onTextEditorFocus: function () { // eslint-disable-line func-names
this.disableDragging(); this.disableDragging();
this.disableShowingTools(); this.disableShowingTools();
}, },
onTextEditorBlur: function() { onTextEditorBlur: function () { // eslint-disable-line func-names
this.enableDragging(); this.enableDragging();
this.enableShowingTools(); this.enableShowingTools();
} }
}); });
Module.TextBlockToolsView = base.BlockToolsView.extend({ Module.TextBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.TextBlockSettingsView; } getSettingsView: function () { return Module.TextBlockSettingsView; } // eslint-disable-line func-names
}); });
Module.TextBlockSettingsView = base.BlockSettingsView.extend({ Module.TextBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return window.templates.textBlockSettings; } getTemplate: function () { return window.templates.textBlockSettings; } // eslint-disable-line func-names
}); });
Module.TextWidgetView = base.WidgetView.extend({ Module.TextWidgetView = base.WidgetView.extend({
getTemplate: function() { return window.templates.textInsertion; }, getTemplate: function () { return window.templates.textInsertion; }, // eslint-disable-line func-names
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function () { // eslint-disable-line func-names
return new Module.TextBlockModel(); return new Module.TextBlockModel();
} }
} }
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) { // eslint-disable-line func-names
App.registerBlockType('text', { BeforeStartApp.registerBlockType('text', {
blockModel: Module.TextBlockModel, blockModel: Module.TextBlockModel,
blockView: Module.TextBlockView blockView: Module.TextBlockView
}); });
App.registerWidget({ BeforeStartApp.registerWidget({
name: 'text', name: 'text',
widgetView: Module.TextWidgetView, widgetView: Module.TextWidgetView,
priority: 90 priority: 90

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* This shim replaces the default Backbone.Marionette communication library * This shim replaces the default Backbone.Marionette communication library
* Backbone.Wreqr with Backbone.Radio ahead of time, * Backbone.Wreqr with Backbone.Radio ahead of time,
@ -6,26 +8,24 @@
* Courtesy of https://gist.github.com/jmeas/7992474cdb1c5672d88b * Courtesy of https://gist.github.com/jmeas/7992474cdb1c5672d88b
*/ */
(function(root, factory) { (function (root, factory) { // eslint-disable-line func-names
var Marionette = require('backbone.marionette');
var Radio = require('backbone.radio');
var _ = require('underscore');
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
define(['backbone.marionette', 'backbone.radio', 'underscore'], function(Marionette, Radio, _) { define(['backbone.marionette', 'backbone.radio', 'underscore'], function (BackboneMarionette, BackboneRadio, underscore) { // eslint-disable-line func-names
return factory(Marionette, Radio, _); return factory(BackboneMarionette, BackboneRadio, underscore);
}); });
} }
else if (typeof exports !== 'undefined') { else if (typeof exports !== 'undefined') {
var Marionette = require('backbone.marionette');
var Radio = require('backbone.radio');
var _ = require('underscore');
module.exports = factory(Marionette, Radio, _); module.exports = factory(Marionette, Radio, _);
} }
else { else {
factory(root.Backbone.Marionette, root.Backbone.Radio, root._); factory(root.Backbone.Marionette, root.Backbone.Radio, root._);
} }
}(this, function(Marionette, Radio, _) { }(this, function (Marionette, Radio, _) { // eslint-disable-line func-names
'use strict';
var MarionetteApplication = Marionette.Application; var MarionetteApplication = Marionette.Application;
MarionetteApplication.prototype._initChannel = function () { MarionetteApplication.prototype._initChannel = function () { // eslint-disable-line func-names
this.channelName = _.result(this, 'channelName') || 'global'; this.channelName = _.result(this, 'channelName') || 'global';
this.channel = _.result(this, 'channel') || Radio.channel(this.channelName); this.channel = _.result(this, 'channel') || Radio.channel(this.channelName);
}; };

View File

@ -1,13 +1,13 @@
/* eslint-disable func-names */
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'underscore', 'underscore',
'mailpoet', 'mailpoet',
'ajax' 'ajax'
], function(App, _, MailPoet) { ], function (App, _, MailPoet) {
var Module = {}; var Module = {};
Module._query = function(args) { Module._query = function (args) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
endpoint: 'automatedLatestContent', endpoint: 'automatedLatestContent',
@ -17,70 +17,70 @@ define([
}; };
Module._cachedQuery = _.memoize(Module._query, JSON.stringify); Module._cachedQuery = _.memoize(Module._query, JSON.stringify);
Module.getNewsletter = function(options) { Module.getNewsletter = function (options) {
return Module._query({ return Module._query({
action: 'get', action: 'get',
options: options options: options
}); });
}; };
Module.getPostTypes = function() { Module.getPostTypes = function () {
return Module._cachedQuery({ return Module._cachedQuery({
action: 'getPostTypes', action: 'getPostTypes',
options: {} options: {}
}).then(function(response) { }).then(function (response) {
return _.values(response.data); return _.values(response.data);
}); });
}; };
Module.getTaxonomies = function(postType) { Module.getTaxonomies = function (postType) {
return Module._cachedQuery({ return Module._cachedQuery({
action: 'getTaxonomies', action: 'getTaxonomies',
options: { options: {
postType: postType postType: postType
} }
}).then(function(response) { }).then(function (response) {
return response.data; return response.data;
}); });
}; };
Module.getTerms = function(options) { Module.getTerms = function (options) {
return Module._cachedQuery({ return Module._cachedQuery({
action: 'getTerms', action: 'getTerms',
options: options options: options
}).then(function(response) { }).then(function (response) {
return response.data; return response.data;
}); });
}; };
Module.getPosts = function(options) { Module.getPosts = function (options) {
return Module._cachedQuery({ return Module._cachedQuery({
action: 'getPosts', action: 'getPosts',
options: options options: options
}).then(function(response) { }).then(function (response) {
return response.data; return response.data;
}); });
}; };
Module.getTransformedPosts = function(options) { Module.getTransformedPosts = function (options) {
return Module._cachedQuery({ return Module._cachedQuery({
action: 'getTransformedPosts', action: 'getTransformedPosts',
options: options options: options
}).then(function(response) { }).then(function (response) {
return response.data; return response.data;
}); });
}; };
Module.getBulkTransformedPosts = function(options) { Module.getBulkTransformedPosts = function (options) {
return Module._query({ return Module._query({
action: 'getBulkTransformedPosts', action: 'getBulkTransformedPosts',
options: options options: options
}).then(function(response) { }).then(function (response) {
return response.data; return response.data;
}); });
}; };
Module.saveNewsletter = function(options) { Module.saveNewsletter = function (options) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
endpoint: 'newsletters', endpoint: 'newsletters',
@ -89,7 +89,7 @@ define([
}); });
}; };
Module.previewNewsletter = function(options) { Module.previewNewsletter = function (options) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
endpoint: 'newsletters', endpoint: 'newsletters',
@ -98,7 +98,7 @@ define([
}); });
}; };
App.on('start', function(App, options) { App.on('start', function () {
// Prefetch post types // Prefetch post types
Module.getPostTypes(); Module.getPostTypes();
}); });

View File

@ -1,8 +1,7 @@
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'backbone.supermodel' 'backbone.supermodel'
], function(App, SuperModel) { ], function (App, SuperModel) { // eslint-disable-line func-names
var Module = {}; var Module = {};
Module.ConfigModel = SuperModel.extend({ Module.ConfigModel = SuperModel.extend({
@ -18,14 +17,14 @@ define([
// Global and available styles for access in blocks and their settings // Global and available styles for access in blocks and their settings
Module._config = {}; Module._config = {};
Module.getConfig = function() { return Module._config; }; Module.getConfig = function () { return Module._config; }; // eslint-disable-line func-names
Module.setConfig = function(options) { Module.setConfig = function (options) { // eslint-disable-line func-names
Module._config = new Module.ConfigModel(options, { parse: true }); Module._config = new Module.ConfigModel(options, { parse: true });
return Module._config; return Module._config;
}; };
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp, options) { // eslint-disable-line func-names
var Application = App; var Application = BeforeStartApp;
// Expose config methods globally // Expose config methods globally
Application.getConfig = Module.getConfig; Application.getConfig = Module.getConfig;
Application.setConfig = Module.setConfig; Application.setConfig = Module.setConfig;

View File

@ -1,11 +1,11 @@
'use strict';
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'backbone.supermodel', 'backbone.supermodel',
'underscore', 'underscore',
'mailpoet' 'mailpoet'
], function(App, SuperModel, _, MailPoet) { ], function (App, SuperModel, _, MailPoet) { // eslint-disable-line func-names
'use strict';
var Module = {}; var Module = {};
// Holds newsletter entry fields, such as subject or creation datetime. // Holds newsletter entry fields, such as subject or creation datetime.
@ -13,12 +13,12 @@ define([
// handled by other components. // handled by other components.
Module.NewsletterModel = SuperModel.extend({ Module.NewsletterModel = SuperModel.extend({
whitelisted: ['id', 'subject', 'preheader'], whitelisted: ['id', 'subject', 'preheader'],
initialize: function(options) { initialize: function () { // eslint-disable-line func-names
this.on('change', function() { this.on('change', function () { // eslint-disable-line func-names
App.getChannel().trigger('autoSave'); App.getChannel().trigger('autoSave');
}); });
}, },
toJSON: function() { toJSON: function () { // eslint-disable-line func-names
// Use only whitelisted properties to ensure properties editor // Use only whitelisted properties to ensure properties editor
// doesn't control don't change. // doesn't control don't change.
return _.pick(SuperModel.prototype.toJSON.call(this), this.whitelisted); return _.pick(SuperModel.prototype.toJSON.call(this), this.whitelisted);
@ -27,62 +27,60 @@ define([
// Content block view and model handlers for different content types // Content block view and model handlers for different content types
Module._blockTypes = {}; Module._blockTypes = {};
Module.registerBlockType = function(type, data) { Module.registerBlockType = function (type, data) { // eslint-disable-line func-names
Module._blockTypes[type] = data; Module._blockTypes[type] = data;
}; };
Module.getBlockTypeModel = function(type) { Module.getBlockTypeModel = function (type) { // eslint-disable-line func-names
if (type in Module._blockTypes) { if (type in Module._blockTypes) {
return Module._blockTypes[type].blockModel; return Module._blockTypes[type].blockModel;
} else {
throw 'Block type not supported: ' + type;
} }
throw 'Block type not supported: ' + type;
}; };
Module.getBlockTypeView = function(type) { Module.getBlockTypeView = function (type) { // eslint-disable-line func-names
if (type in Module._blockTypes) { if (type in Module._blockTypes) {
return Module._blockTypes[type].blockView; return Module._blockTypes[type].blockView;
} else {
throw 'Block type not supported: ' + type;
} }
throw 'Block type not supported: ' + type;
}; };
Module.getBody = function() { Module.getBody = function () { // eslint-disable-line func-names
return { return {
content: App._contentContainer.toJSON(), content: App._contentContainer.toJSON(),
globalStyles: App.getGlobalStyles().toJSON() globalStyles: App.getGlobalStyles().toJSON()
}; };
}; };
Module.toJSON = function() { Module.toJSON = function () { // eslint-disable-line func-names
return _.extend({ return _.extend({
body: Module.getBody() body: Module.getBody()
}, App.getNewsletter().toJSON()); }, App.getNewsletter().toJSON());
}; };
Module.getNewsletter = function() { Module.getNewsletter = function () { // eslint-disable-line func-names
return Module.newsletter; return Module.newsletter;
}; };
Module.findModels = function(predicate) { Module.findModels = function (predicate) { // eslint-disable-line func-names
var blocks = App._contentContainer.getChildren(); var blocks = App._contentContainer.getChildren();
return _.filter(blocks, predicate); return _.filter(blocks, predicate);
}; };
App.on('before:start', function(Application, options) { App.on('before:start', function (Application, options) { // eslint-disable-line func-names
var App = Application; var BeforeStartApp = Application;
// Expose block methods globally // Expose block methods globally
App.registerBlockType = Module.registerBlockType; BeforeStartApp.registerBlockType = Module.registerBlockType;
App.getBlockTypeModel = Module.getBlockTypeModel; BeforeStartApp.getBlockTypeModel = Module.getBlockTypeModel;
App.getBlockTypeView = Module.getBlockTypeView; BeforeStartApp.getBlockTypeView = Module.getBlockTypeView;
App.toJSON = Module.toJSON; BeforeStartApp.toJSON = Module.toJSON;
App.getBody = Module.getBody; BeforeStartApp.getBody = Module.getBody;
App.getNewsletter = Module.getNewsletter; BeforeStartApp.getNewsletter = Module.getNewsletter;
App.findModels = Module.findModels; BeforeStartApp.findModels = Module.findModels;
Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body'])); Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body']));
}); });
App.on('start', function(Application, options) { App.on('start', function (Application, options) { // eslint-disable-line func-names
var App = Application; var StartApp = Application;
var body = options.newsletter.body; var body = options.newsletter.body;
var content = (_.has(body, 'content')) ? body.content : {}; var content = (_.has(body, 'content')) ? body.content : {};
@ -93,13 +91,13 @@ define([
); );
} }
App._contentContainer = new (App.getBlockTypeModel('container'))(content, {parse: true}); StartApp._contentContainer = new (StartApp.getBlockTypeModel('container'))(content, { parse: true });
App._contentContainerView = new (App.getBlockTypeView('container'))({ StartApp._contentContainerView = new (StartApp.getBlockTypeView('container'))({
model: App._contentContainer, model: StartApp._contentContainer,
renderOptions: { depth: 0 } renderOptions: { depth: 0 }
}); });
App._appView.showChildView('contentRegion', App._contentContainerView); StartApp._appView.showChildView('contentRegion', StartApp._contentContainerView);
}); });

View File

@ -1,3 +1,5 @@
'use strict';
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'backbone', 'backbone',
@ -5,32 +7,29 @@ define([
'underscore', 'underscore',
'jquery', 'jquery',
'mailpoet' 'mailpoet'
], function(App, Backbone, Marionette, _, jQuery, MailPoet) { ], function (App, Backbone, Marionette, _, jQuery, MailPoet) { // eslint-disable-line func-names
'use strict';
var Module = {}; var Module = {};
Module.HeadingView = Marionette.View.extend({ Module.HeadingView = Marionette.View.extend({
getTemplate: function() { return window.templates.heading; }, getTemplate: function () { return window.templates.heading; }, // eslint-disable-line func-names
templateContext: function() { templateContext: function () { // eslint-disable-line func-names
return { return {
model: this.model.toJSON() model: this.model.toJSON()
}; };
}, },
events: function() { events: function () { // eslint-disable-line func-names
return { return {
'keyup .mailpoet_input_title': _.partial(this.changeField, 'subject'), 'keyup .mailpoet_input_title': _.partial(this.changeField, 'subject'),
'keyup .mailpoet_input_preheader': _.partial(this.changeField, 'preheader') 'keyup .mailpoet_input_preheader': _.partial(this.changeField, 'preheader')
}; };
}, },
changeField: function(field, event) { changeField: function (field, event) { // eslint-disable-line func-names
this.model.set(field, jQuery(event.target).val()); this.model.set(field, jQuery(event.target).val());
} }
}); });
App.on('start', function(App, options) { App.on('start', function (StartApp) { // eslint-disable-line func-names
App._appView.showChildView('headingRegion', new Module.HeadingView({ model: App.getNewsletter() })); StartApp._appView.showChildView('headingRegion', new Module.HeadingView({ model: StartApp.getNewsletter() }));
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-subject-line'), { MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-subject-line'), {
tooltipId: 'tooltip-designer-subject-line-ti', tooltipId: 'tooltip-designer-subject-line-ti',
tooltip: MailPoet.I18n.t('helpTooltipDesignerSubjectLine'), tooltip: MailPoet.I18n.t('helpTooltipDesignerSubjectLine'),

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/communication', 'newsletter_editor/components/communication',
@ -11,7 +12,7 @@ define([
'html2canvas', 'html2canvas',
'underscore', 'underscore',
'jquery' 'jquery'
], function( ], function (
App, App,
CommunicationComponent, CommunicationComponent,
MailPoet, MailPoet,
@ -25,15 +26,13 @@ define([
_, _,
$ $
) { ) {
'use strict'; 'use strict';
var Module = {}, var Module = {};
saveTimeout; var saveTimeout;
// Save editor contents to server // Save editor contents to server
Module.save = function() { Module.save = function () {
var json = App.toJSON(); var json = App.toJSON();
// Stringify to enable transmission of primitive non-string value types // Stringify to enable transmission of primitive non-string value types
@ -44,12 +43,12 @@ define([
App.getChannel().trigger('beforeEditorSave', json); App.getChannel().trigger('beforeEditorSave', json);
// save newsletter // save newsletter
return CommunicationComponent.saveNewsletter(json).done(function(response) { return CommunicationComponent.saveNewsletter(json).done(function (response) {
if(response.success !== undefined && response.success === true) { if (response.success !== undefined && response.success === true) {
// TODO: Handle translations // TODO: Handle translations
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>"); // MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
} else if(response.error !== undefined) { } else if (response.error !== undefined) {
if(response.error.length === 0) { if (response.error.length === 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoet.I18n.t('templateSaveFailed'), MailPoet.I18n.t('templateSaveFailed'),
{ {
@ -57,29 +56,28 @@ define([
} }
); );
} else { } else {
$(response.error).each(function(i, error) { $(response.error).each(function (i, error) {
MailPoet.Notice.error(error, { scroll: true }); MailPoet.Notice.error(error, { scroll: true });
}); });
} }
} }
App.getChannel().trigger('afterEditorSave', json, response); App.getChannel().trigger('afterEditorSave', json, response);
}).fail(function(response) { }).fail(function (response) {
// TODO: Handle saving errors // TODO: Handle saving errors
App.getChannel().trigger('afterEditorSave', {}, response); App.getChannel().trigger('afterEditorSave', {}, response);
}); });
}; };
Module.getThumbnail = function(element, options) { Module.getThumbnail = function (element, options) {
var promise = html2canvas(element, options || {}); var promise = html2canvas(element, options || {});
return promise.then(function(oldCanvas) { return promise.then(function (oldCanvas) {
// Temporary workaround for html2canvas-alpha2. // Temporary workaround for html2canvas-alpha2.
// Removes 1px left transparent border from resulting canvas. // Removes 1px left transparent border from resulting canvas.
var oldContext = oldCanvas.getContext('2d'), var newCanvas = document.createElement('canvas');
newCanvas = document.createElement('canvas'), var newContext = newCanvas.getContext('2d');
newContext = newCanvas.getContext('2d'), var leftBorderWidth = 1;
leftBorderWidth = 1;
newCanvas.width = oldCanvas.width; newCanvas.width = oldCanvas.width;
newCanvas.height = oldCanvas.height; newCanvas.height = oldCanvas.height;
@ -94,11 +92,10 @@ define([
}); });
}; };
Module.saveTemplate = function(options) { Module.saveTemplate = function (options) {
var that = this, var promise = jQuery.Deferred();
promise = jQuery.Deferred();
promise.then(function(thumbnail) { promise.then(function (thumbnail) {
var data = _.extend(options || {}, { var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'), thumbnail: thumbnail.toDataURL('image/jpeg'),
body: JSON.stringify(App.getBody()) body: JSON.stringify(App.getBody())
@ -114,18 +111,17 @@ define([
Module.getThumbnail( Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0) jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function(thumbnail) { ).then(function (thumbnail) {
promise.resolve(thumbnail); promise.resolve(thumbnail);
}); });
return promise; return promise;
}; };
Module.exportTemplate = function(options) { Module.exportTemplate = function (options) {
var that = this;
return Module.getThumbnail( return Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0) jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function(thumbnail) { ).then(function (thumbnail) {
var data = _.extend(options || {}, { var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'), thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody() body: App.getBody()
@ -143,7 +139,7 @@ define([
}; };
Module.SaveView = Marionette.View.extend({ Module.SaveView = Marionette.View.extend({
getTemplate: function() { return window.templates.save; }, getTemplate: function () { return window.templates.save; },
events: { events: {
'click .mailpoet_save_button': 'save', 'click .mailpoet_save_button': 'save',
'click .mailpoet_save_show_options': 'toggleSaveOptions', 'click .mailpoet_save_show_options': 'toggleSaveOptions',
@ -155,46 +151,46 @@ define([
'click .mailpoet_save_export': 'toggleExportTemplate', 'click .mailpoet_save_export': 'toggleExportTemplate',
'click .mailpoet_export_template': 'exportTemplate' 'click .mailpoet_export_template': 'exportTemplate'
}, },
initialize: function(options) { initialize: function () {
App.getChannel().on('beforeEditorSave', this.beforeSave, this); App.getChannel().on('beforeEditorSave', this.beforeSave, this);
App.getChannel().on('afterEditorSave', this.afterSave, this); App.getChannel().on('afterEditorSave', this.afterSave, this);
}, },
onRender: function() { onRender: function () {
this.validateNewsletter(App.toJSON()); this.validateNewsletter(App.toJSON());
}, },
save: function() { save: function () {
this.hideOptionContents(); this.hideOptionContents();
App.getChannel().request('save'); App.getChannel().request('save');
}, },
beforeSave: function() { beforeSave: function () {
// TODO: Add a loading animation instead // TODO: Add a loading animation instead
this.$('.mailpoet_autosaved_at').text(MailPoet.I18n.t('saving')); this.$('.mailpoet_autosaved_at').text(MailPoet.I18n.t('saving'));
}, },
afterSave: function(json, response) { afterSave: function (json) {
this.validateNewsletter(json); this.validateNewsletter(json);
// Update 'Last saved timer' // Update 'Last saved timer'
this.$('.mailpoet_editor_last_saved').removeClass('mailpoet_hidden'); this.$('.mailpoet_editor_last_saved').removeClass('mailpoet_hidden');
this.$('.mailpoet_autosaved_at').text(''); this.$('.mailpoet_autosaved_at').text('');
}, },
toggleSaveOptions: function() { toggleSaveOptions: function () {
this.$('.mailpoet_save_options').toggleClass('mailpoet_hidden'); this.$('.mailpoet_save_options').toggleClass('mailpoet_hidden');
this.$('.mailpoet_save_show_options').toggleClass('mailpoet_save_show_options_active'); this.$('.mailpoet_save_show_options').toggleClass('mailpoet_save_show_options_active');
}, },
toggleSaveAsTemplate: function() { toggleSaveAsTemplate: function () {
this.$('.mailpoet_save_as_template_container').toggleClass('mailpoet_hidden'); this.$('.mailpoet_save_as_template_container').toggleClass('mailpoet_hidden');
this.toggleSaveOptions(); this.toggleSaveOptions();
}, },
showSaveAsTemplate: function() { showSaveAsTemplate: function () {
this.$('.mailpoet_save_as_template_container').removeClass('mailpoet_hidden'); this.$('.mailpoet_save_as_template_container').removeClass('mailpoet_hidden');
this.toggleSaveOptions(); this.toggleSaveOptions();
}, },
hideSaveAsTemplate: function() { hideSaveAsTemplate: function () {
this.$('.mailpoet_save_as_template_container').addClass('mailpoet_hidden'); this.$('.mailpoet_save_as_template_container').addClass('mailpoet_hidden');
}, },
saveAsTemplate: function() { saveAsTemplate: function () {
var templateName = this.$('.mailpoet_save_as_template_name').val(), var templateName = this.$('.mailpoet_save_as_template_name').val();
templateDescription = this.$('.mailpoet_save_as_template_description').val(), var templateDescription = this.$('.mailpoet_save_as_template_description').val();
that = this; var that = this;
if (templateName === '') { if (templateName === '') {
MailPoet.Notice.error( MailPoet.Notice.error(
@ -216,7 +212,7 @@ define([
Module.saveTemplate({ Module.saveTemplate({
name: templateName, name: templateName,
description: templateDescription description: templateDescription
}).done(function() { }).done(function () {
MailPoet.Notice.success( MailPoet.Notice.success(
MailPoet.I18n.t('templateSaved'), MailPoet.I18n.t('templateSaved'),
{ {
@ -227,7 +223,7 @@ define([
MailPoet.trackEvent('Editor > Template saved', { MailPoet.trackEvent('Editor > Template saved', {
'MailPoet Free version': window.mailpoet_version 'MailPoet Free version': window.mailpoet_version
}); });
}).fail(function() { }).fail(function () {
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoet.I18n.t('templateSaveFailed'), MailPoet.I18n.t('templateSaveFailed'),
{ {
@ -238,19 +234,18 @@ define([
}); });
this.hideOptionContents(); this.hideOptionContents();
} }
}, },
toggleExportTemplate: function() { toggleExportTemplate: function () {
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden'); this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
this.toggleSaveOptions(); this.toggleSaveOptions();
}, },
hideExportTemplate: function() { hideExportTemplate: function () {
this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden'); this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden');
}, },
exportTemplate: function() { exportTemplate: function () {
var templateName = this.$('.mailpoet_export_template_name').val(), var templateName = this.$('.mailpoet_export_template_name').val();
templateDescription = this.$('.mailpoet_export_template_description').val(), var templateDescription = this.$('.mailpoet_export_template_description').val();
that = this; var that = this;
if (templateName === '') { if (templateName === '') {
MailPoet.Notice.error( MailPoet.Notice.error(
@ -276,27 +271,28 @@ define([
this.hideExportTemplate(); this.hideExportTemplate();
} }
}, },
hideOptionContents: function() { hideOptionContents: function () {
this.hideSaveAsTemplate(); this.hideSaveAsTemplate();
this.hideExportTemplate(); this.hideExportTemplate();
this.$('.mailpoet_save_options').addClass('mailpoet_hidden'); this.$('.mailpoet_save_options').addClass('mailpoet_hidden');
}, },
next: function() { next: function () {
this.hideOptionContents(); this.hideOptionContents();
if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) { if (!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
Module._cancelAutosave(); Module._cancelAutosave();
Module.save().done(function(response) { Module.save().done(function () {
window.location.href = App.getConfig().get('urls.send'); window.location.href = App.getConfig().get('urls.send');
}); });
} }
}, },
validateNewsletter: function(jsonObject) { validateNewsletter: function (jsonObject) {
var contents;
if (!App._contentContainer.isValid()) { if (!App._contentContainer.isValid()) {
this.showValidationError(App._contentContainer.validationError); this.showValidationError(App._contentContainer.validationError);
return; return;
} }
var contents = JSON.stringify(jsonObject); contents = JSON.stringify(jsonObject);
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') && if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
contents.indexOf('[link:subscription_unsubscribe_url]') < 0 && contents.indexOf('[link:subscription_unsubscribe_url]') < 0 &&
contents.indexOf('[link:subscription_unsubscribe]') < 0) { contents.indexOf('[link:subscription_unsubscribe]') < 0) {
@ -306,43 +302,45 @@ define([
this.hideValidationError(); this.hideValidationError();
}, },
showValidationError: function(message) { showValidationError: function (message) {
var $el = this.$('.mailpoet_save_error'); var $el = this.$('.mailpoet_save_error');
$el.text(message); $el.text(message);
$el.removeClass('mailpoet_hidden'); $el.removeClass('mailpoet_hidden');
this.$('.mailpoet_save_next').addClass('button-disabled'); this.$('.mailpoet_save_next').addClass('button-disabled');
}, },
hideValidationError: function() { hideValidationError: function () {
this.$('.mailpoet_save_error').addClass('mailpoet_hidden'); this.$('.mailpoet_save_error').addClass('mailpoet_hidden');
this.$('.mailpoet_save_next').removeClass('button-disabled'); this.$('.mailpoet_save_next').removeClass('button-disabled');
} }
}); });
Module.autoSave = function() { Module.autoSave = function () {
// Delay in saving editor contents, during which a new autosave // Delay in saving editor contents, during which a new autosave
// may be requested // may be requested
var AUTOSAVE_DELAY_DURATION = 1000; var AUTOSAVE_DELAY_DURATION = 1000;
Module._cancelAutosave(); Module._cancelAutosave();
saveTimeout = setTimeout(function() { saveTimeout = setTimeout(function () {
App.getChannel().request('save').always(function() { App.getChannel().request('save').always(function () {
Module._cancelAutosave(); Module._cancelAutosave();
}); });
}, AUTOSAVE_DELAY_DURATION); }, AUTOSAVE_DELAY_DURATION);
}; };
Module._cancelAutosave = function() { Module._cancelAutosave = function () {
if (!saveTimeout) return; if (!saveTimeout) return;
clearTimeout(saveTimeout); clearTimeout(saveTimeout);
saveTimeout = undefined; saveTimeout = undefined;
}; };
Module.beforeExitWithUnsavedChanges = function(e) { Module.beforeExitWithUnsavedChanges = function (e) {
var message;
var event;
if (saveTimeout) { if (saveTimeout) {
var message = MailPoet.I18n.t('unsavedChangesWillBeLost'); message = MailPoet.I18n.t('unsavedChangesWillBeLost');
var event = e || window.event; event = e || window.event;
if (event) { if (event) {
event.returnValue = message; event.returnValue = message;
@ -350,10 +348,11 @@ define([
return message; return message;
} }
return undefined;
}; };
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
var Application = App; var Application = BeforeStartApp;
Application.save = Module.save; Application.save = Module.save;
Application.getChannel().on('autoSave', Module.autoSave); Application.getChannel().on('autoSave', Module.autoSave);
@ -362,9 +361,9 @@ define([
Application.getChannel().reply('save', Application.save); Application.getChannel().reply('save', Application.save);
}); });
App.on('start', function(App, options) { App.on('start', function (BeforeStartApp) {
var saveView = new Module.SaveView(); var saveView = new Module.SaveView();
App._appView.showChildView('bottomRegion', saveView); BeforeStartApp._appView.showChildView('bottomRegion', saveView);
}); });
return Module; return Module;

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/communication', 'newsletter_editor/components/communication',
@ -6,9 +7,8 @@ define([
'backbone.marionette', 'backbone.marionette',
'backbone.supermodel', 'backbone.supermodel',
'underscore', 'underscore',
'jquery', 'jquery'
'sticky-kit' ], function (
], function(
App, App,
CommunicationComponent, CommunicationComponent,
MailPoet, MailPoet,
@ -16,14 +16,12 @@ define([
Marionette, Marionette,
SuperModel, SuperModel,
_, _,
jQuery, jQuery
StickyKit
) { ) {
'use strict'; 'use strict';
var Module = {}; var Module = {};
var SidebarView;
// Widget handlers for use to create new content blocks via drag&drop // Widget handlers for use to create new content blocks via drag&drop
Module._contentWidgets = new (Backbone.Collection.extend({ Module._contentWidgets = new (Backbone.Collection.extend({
model: SuperModel.extend({ model: SuperModel.extend({
@ -35,8 +33,8 @@ define([
}), }),
comparator: 'priority' comparator: 'priority'
}))(); }))();
Module.registerWidget = function(widget) { return Module._contentWidgets.add(widget); }; Module.registerWidget = function (widget) { return Module._contentWidgets.add(widget); };
Module.getWidgets = function() { return Module._contentWidgets; }; Module.getWidgets = function () { return Module._contentWidgets; };
// Layout widget handlers for use to create new layout blocks via drag&drop // Layout widget handlers for use to create new layout blocks via drag&drop
Module._layoutWidgets = new (Backbone.Collection.extend({ Module._layoutWidgets = new (Backbone.Collection.extend({
@ -49,11 +47,11 @@ define([
}), }),
comparator: 'priority' comparator: 'priority'
}))(); }))();
Module.registerLayoutWidget = function(widget) { return Module._layoutWidgets.add(widget); }; Module.registerLayoutWidget = function (widget) { return Module._layoutWidgets.add(widget); };
Module.getLayoutWidgets = function() { return Module._layoutWidgets; }; Module.getLayoutWidgets = function () { return Module._layoutWidgets; };
var SidebarView = Marionette.View.extend({ SidebarView = Marionette.View.extend({
getTemplate: function() { return window.templates.sidebar; }, getTemplate: function () { return window.templates.sidebar; },
regions: { regions: {
contentRegion: '.mailpoet_content_region', contentRegion: '.mailpoet_content_region',
layoutRegion: '.mailpoet_layout_region', layoutRegion: '.mailpoet_layout_region',
@ -61,16 +59,16 @@ define([
previewRegion: '.mailpoet_preview_region' previewRegion: '.mailpoet_preview_region'
}, },
events: { events: {
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function(event) { 'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function (event) {
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'), var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)');
$targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region'); var $targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
$openRegion.find('.mailpoet_region_content').velocity( $openRegion.find('.mailpoet_region_content').velocity(
'slideUp', 'slideUp',
{ {
duration: 250, duration: 250,
easing: 'easeOut', easing: 'easeOut',
complete: function() { complete: function () {
$openRegion.addClass('closed'); $openRegion.addClass('closed');
}.bind(this) }.bind(this)
} }
@ -82,7 +80,7 @@ define([
{ {
duration: 250, duration: 250,
easing: 'easeIn', easing: 'easeIn',
complete: function() { complete: function () {
$targetRegion.removeClass('closed'); $targetRegion.removeClass('closed');
} }
} }
@ -90,12 +88,12 @@ define([
} }
} }
}, },
initialize: function(options) { initialize: function () {
jQuery(window) jQuery(window)
.on('resize', this.updateHorizontalScroll.bind(this)) .on('resize', this.updateHorizontalScroll.bind(this))
.on('scroll', this.updateHorizontalScroll.bind(this)); .on('scroll', this.updateHorizontalScroll.bind(this));
}, },
onRender: function() { onRender: function () {
this.showChildView('contentRegion', new Module.SidebarWidgetsView( this.showChildView('contentRegion', new Module.SidebarWidgetsView(
App.getWidgets() App.getWidgets()
)); ));
@ -108,24 +106,23 @@ define([
})); }));
this.showChildView('previewRegion', new Module.SidebarPreviewView()); this.showChildView('previewRegion', new Module.SidebarPreviewView());
}, },
updateHorizontalScroll: function() { updateHorizontalScroll: function () {
// Fixes the sidebar so that on narrower screens the horizontal // Fixes the sidebar so that on narrower screens the horizontal
// position of the sidebar would be scrollable and not fixed // position of the sidebar would be scrollable and not fixed
// partially out of visible screen // partially out of visible screen
this.$el.parent().each(function () { this.$el.parent().each(function () {
var calculated_left, self; var calculatedLeft;
var self = jQuery(this);
self = jQuery(this);
if (self.css('position') === 'fixed') { if (self.css('position') === 'fixed') {
calculated_left = self.parent().offset().left - jQuery(window).scrollLeft(); calculatedLeft = self.parent().offset().left - jQuery(window).scrollLeft();
self.css('left', calculated_left + 'px'); self.css('left', calculatedLeft + 'px');
} else { } else {
self.css('left', ''); self.css('left', '');
} }
}); });
}, },
onDomRefresh: function() { onDomRefresh: function () {
this.$el.parent().stick_in_parent({ this.$el.parent().stick_in_parent({
offset_top: 32 offset_top: 32
}); });
@ -140,23 +137,23 @@ define([
* Draggable widget collection view * Draggable widget collection view
*/ */
Module.SidebarWidgetsCollectionView = Marionette.CollectionView.extend({ Module.SidebarWidgetsCollectionView = Marionette.CollectionView.extend({
childView: function(item) { return item.get('widgetView'); } childView: function (item) { return item.get('widgetView'); }
}); });
/** /**
* Responsible for rendering draggable content widgets * Responsible for rendering draggable content widgets
*/ */
Module.SidebarWidgetsView = Marionette.View.extend({ Module.SidebarWidgetsView = Marionette.View.extend({
getTemplate: function() { return window.templates.sidebarContent; }, getTemplate: function () { return window.templates.sidebarContent; },
regions: { regions: {
widgets: '.mailpoet_region_content' widgets: '.mailpoet_region_content'
}, },
initialize: function(widgets) { initialize: function (widgets) {
this.widgets = widgets; this.widgets = widgets;
}, },
onRender: function() { onRender: function () {
this.showChildView('widgets', new Module.SidebarWidgetsCollectionView({ this.showChildView('widgets', new Module.SidebarWidgetsCollectionView({
collection: this.widgets collection: this.widgets
})); }));
@ -167,68 +164,68 @@ define([
* Responsible for rendering draggable layout widgets * Responsible for rendering draggable layout widgets
*/ */
Module.SidebarLayoutWidgetsView = Module.SidebarWidgetsView.extend({ Module.SidebarLayoutWidgetsView = Module.SidebarWidgetsView.extend({
getTemplate: function() { return window.templates.sidebarLayout; } getTemplate: function () { return window.templates.sidebarLayout; }
}); });
/** /**
* Responsible for managing global styles * Responsible for managing global styles
*/ */
Module.SidebarStylesView = Marionette.View.extend({ Module.SidebarStylesView = Marionette.View.extend({
getTemplate: function() { return window.templates.sidebarStyles; }, getTemplate: function () { return window.templates.sidebarStyles; },
behaviors: { behaviors: {
ColorPickerBehavior: {} ColorPickerBehavior: {}
}, },
events: function() { events: function () {
return { return {
'change #mailpoet_text_font_color': _.partial(this.changeColorField, 'text.fontColor'), 'change #mailpoet_text_font_color': _.partial(this.changeColorField, 'text.fontColor'),
'change #mailpoet_text_font_family': function(event) { 'change #mailpoet_text_font_family': function (event) {
this.model.set('text.fontFamily', event.target.value); this.model.set('text.fontFamily', event.target.value);
}, },
'change #mailpoet_text_font_size': function(event) { 'change #mailpoet_text_font_size': function (event) {
this.model.set('text.fontSize', event.target.value); this.model.set('text.fontSize', event.target.value);
}, },
'change #mailpoet_h1_font_color': _.partial(this.changeColorField, 'h1.fontColor'), 'change #mailpoet_h1_font_color': _.partial(this.changeColorField, 'h1.fontColor'),
'change #mailpoet_h1_font_family': function(event) { 'change #mailpoet_h1_font_family': function (event) {
this.model.set('h1.fontFamily', event.target.value); this.model.set('h1.fontFamily', event.target.value);
}, },
'change #mailpoet_h1_font_size': function(event) { 'change #mailpoet_h1_font_size': function (event) {
this.model.set('h1.fontSize', event.target.value); this.model.set('h1.fontSize', event.target.value);
}, },
'change #mailpoet_h2_font_color': _.partial(this.changeColorField, 'h2.fontColor'), 'change #mailpoet_h2_font_color': _.partial(this.changeColorField, 'h2.fontColor'),
'change #mailpoet_h2_font_family': function(event) { 'change #mailpoet_h2_font_family': function (event) {
this.model.set('h2.fontFamily', event.target.value); this.model.set('h2.fontFamily', event.target.value);
}, },
'change #mailpoet_h2_font_size': function(event) { 'change #mailpoet_h2_font_size': function (event) {
this.model.set('h2.fontSize', event.target.value); this.model.set('h2.fontSize', event.target.value);
}, },
'change #mailpoet_h3_font_color': _.partial(this.changeColorField, 'h3.fontColor'), 'change #mailpoet_h3_font_color': _.partial(this.changeColorField, 'h3.fontColor'),
'change #mailpoet_h3_font_family': function(event) { 'change #mailpoet_h3_font_family': function (event) {
this.model.set('h3.fontFamily', event.target.value); this.model.set('h3.fontFamily', event.target.value);
}, },
'change #mailpoet_h3_font_size': function(event) { 'change #mailpoet_h3_font_size': function (event) {
this.model.set('h3.fontSize', event.target.value); this.model.set('h3.fontSize', event.target.value);
}, },
'change #mailpoet_a_font_color': _.partial(this.changeColorField, 'link.fontColor'), 'change #mailpoet_a_font_color': _.partial(this.changeColorField, 'link.fontColor'),
'change #mailpoet_a_font_underline': function(event) { 'change #mailpoet_a_font_underline': function (event) {
this.model.set('link.textDecoration', (event.target.checked) ? event.target.value : 'none'); this.model.set('link.textDecoration', (event.target.checked) ? event.target.value : 'none');
}, },
'change #mailpoet_newsletter_background_color': _.partial(this.changeColorField, 'wrapper.backgroundColor'), 'change #mailpoet_newsletter_background_color': _.partial(this.changeColorField, 'wrapper.backgroundColor'),
'change #mailpoet_background_color': _.partial(this.changeColorField, 'body.backgroundColor') 'change #mailpoet_background_color': _.partial(this.changeColorField, 'body.backgroundColor')
}; };
}, },
templateContext: function() { templateContext: function () {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
availableStyles: this.availableStyles.toJSON() availableStyles: this.availableStyles.toJSON()
}; };
}, },
initialize: function(options) { initialize: function (options) {
this.availableStyles = options.availableStyles; this.availableStyles = options.availableStyles;
}, },
changeField: function(field, event) { changeField: function (field, event) {
this.model.set(field, jQuery(event.target).val()); this.model.set(field, jQuery(event.target).val());
}, },
changeColorField: function(field, event) { changeColorField: function (field, event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value === '') { if (value === '') {
value = 'transparent'; value = 'transparent';
@ -238,18 +235,18 @@ define([
}); });
Module.SidebarPreviewView = Marionette.View.extend({ Module.SidebarPreviewView = Marionette.View.extend({
getTemplate: function() { return window.templates.sidebarPreview; }, getTemplate: function () { return window.templates.sidebarPreview; },
events: { events: {
'click .mailpoet_show_preview': 'showPreview', 'click .mailpoet_show_preview': 'showPreview',
'click #mailpoet_send_preview': 'sendPreview' 'click #mailpoet_send_preview': 'sendPreview'
}, },
onBeforeDestroy: function() { onBeforeDestroy: function () {
if (this.previewView) { if (this.previewView) {
this.previewView.destroy(); this.previewView.destroy();
this.previewView = null; this.previewView = null;
} }
}, },
showPreview: function() { showPreview: function () {
var json = App.toJSON(); var json = App.toJSON();
// Stringify to enable transmission of primitive non-string value types // Stringify to enable transmission of primitive non-string value types
@ -264,23 +261,23 @@ define([
endpoint: 'newsletters', endpoint: 'newsletters',
action: 'showPreview', action: 'showPreview',
data: json data: json
}).always(function() { }).always(function () {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
}).done(function(response) { }).done(function (response) {
this.previewView = new Module.NewsletterPreviewView({ this.previewView = new Module.NewsletterPreviewView({
previewUrl: response.meta.preview_url previewUrl: response.meta.preview_url
}); });
var view = this.previewView.render(); this.previewView.render();
this.previewView.$el.css('height', '100%'); this.previewView.$el.css('height', '100%');
MailPoet.Modal.popup({ MailPoet.Modal.popup({
template: '', template: '',
element: this.previewView.$el, element: this.previewView.$el,
width: '95%', width: '95%',
height: '94%', height: '94%',
title: MailPoet.I18n.t('newsletterPreview'), title: MailPoet.I18n.t('newsletterPreview'),
onCancel: function() { onCancel: function () {
this.previewView.destroy(); this.previewView.destroy();
this.previewView = null; this.previewView = null;
}.bind(this) }.bind(this)
@ -289,16 +286,16 @@ define([
MailPoet.trackEvent('Editor > Browser Preview', { MailPoet.trackEvent('Editor > Browser Preview', {
'MailPoet Free version': window.mailpoet_version 'MailPoet Free version': window.mailpoet_version
}); });
}.bind(this)).fail(function(response) { }.bind(this)).fail(function (response) {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map(function (error) { return error.message; }),
{ scroll: true } { scroll: true }
); );
} }
}); });
}, },
sendPreview: function() { sendPreview: function () {
// get form data // get form data
var $emailField = this.$('#mailpoet_preview_to_email'); var $emailField = this.$('#mailpoet_preview_to_email');
var data = { var data = {
@ -321,10 +318,10 @@ define([
MailPoet.Modal.loading(true); MailPoet.Modal.loading(true);
// save before sending // save before sending
App.getChannel().request('save').always(function() { App.getChannel().request('save').always(function () {
CommunicationComponent.previewNewsletter(data).always(function() { CommunicationComponent.previewNewsletter(data).always(function () {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
}).done(function(response) { }).done(function () {
MailPoet.Notice.success( MailPoet.Notice.success(
MailPoet.I18n.t('newsletterPreviewSent'), MailPoet.I18n.t('newsletterPreviewSent'),
{ scroll: true } { scroll: true }
@ -333,28 +330,29 @@ define([
'MailPoet Free version': window.mailpoet_version, 'MailPoet Free version': window.mailpoet_version,
'Domain name': data.subscriber.substring(data.subscriber.indexOf('@') + 1) 'Domain name': data.subscriber.substring(data.subscriber.indexOf('@') + 1)
}); });
}).fail(function(response) { }).fail(function (response) {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map(function (error) { return error.message; }),
{ scroll: true, static: true } { scroll: true, static: true }
); );
} }
}); });
}); });
return undefined;
} }
}); });
Module.NewsletterPreviewView = Marionette.View.extend({ Module.NewsletterPreviewView = Marionette.View.extend({
getTemplate: function() { return window.templates.newsletterPreview; }, getTemplate: function () { return window.templates.newsletterPreview; },
initialize: function(options) { initialize: function (options) {
this.previewUrl = options.previewUrl; this.previewUrl = options.previewUrl;
this.width = '100%'; this.width = '100%';
this.height = '100%'; this.height = '100%';
// this.width = App.getConfig().get('newsletterPreview.width'); // this.width = App.getConfig().get('newsletterPreview.width');
// this.height = App.getConfig().get('newsletterPreview.height') // this.height = App.getConfig().get('newsletterPreview.height')
}, },
templateContext: function() { templateContext: function () {
return { return {
previewUrl: this.previewUrl, previewUrl: this.previewUrl,
width: this.width, width: this.width,
@ -363,19 +361,18 @@ define([
} }
}); });
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp) {
var Application = App; var Application = BeforeStartApp;
Application.registerWidget = Module.registerWidget; Application.registerWidget = Module.registerWidget;
Application.getWidgets = Module.getWidgets; Application.getWidgets = Module.getWidgets;
Application.registerLayoutWidget = Module.registerLayoutWidget; Application.registerLayoutWidget = Module.registerLayoutWidget;
Application.getLayoutWidgets = Module.getLayoutWidgets; Application.getLayoutWidgets = Module.getLayoutWidgets;
}); });
App.on('start', function(App, options) { App.on('start', function (StartApp) {
var stylesModel = App.getGlobalStyles(), var sidebarView = new SidebarView();
sidebarView = new SidebarView();
App._appView.showChildView('sidebarRegion', sidebarView); StartApp._appView.showChildView('sidebarRegion', sidebarView);
MailPoet.helpTooltip.show(document.getElementById('tooltip-send-preview'), { MailPoet.helpTooltip.show(document.getElementById('tooltip-send-preview'), {
tooltipId: 'tooltip-editor-send-preview', tooltipId: 'tooltip-editor-send-preview',

View File

@ -1,12 +1,12 @@
'use strict';
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'backbone.marionette', 'backbone.marionette',
'backbone.supermodel', 'backbone.supermodel',
'underscore' 'underscore'
], function(App, Marionette, SuperModel, _) { ], function (App, Marionette, SuperModel, _) { // eslint-disable-line func-names
'use strict';
var Module = {}; var Module = {};
Module.StylesModel = SuperModel.extend({ Module.StylesModel = SuperModel.extend({
@ -42,48 +42,50 @@ define([
backgroundColor: '#cccccc' backgroundColor: '#cccccc'
} }
}, },
initialize: function() { initialize: function () { // eslint-disable-line func-names
this.on('change', function() { App.getChannel().trigger('autoSave'); }); this.on('change', function () { App.getChannel().trigger('autoSave'); }); // eslint-disable-line func-names
} }
}); });
Module.StylesView = Marionette.View.extend({ Module.StylesView = Marionette.View.extend({
getTemplate: function() { return window.templates.styles; }, getTemplate: function () { return window.templates.styles; }, // eslint-disable-line func-names
modelEvents: { modelEvents: {
change: 'render' change: 'render'
}, },
serializeData: function() { serializeData: function () { // eslint-disable-line func-names
return this.model.toJSON(); return this.model.toJSON();
} }
}); });
Module._globalStyles = new SuperModel(); Module._globalStyles = new SuperModel();
Module.getGlobalStyles = function() { Module.getGlobalStyles = function () { // eslint-disable-line func-names
return Module._globalStyles; return Module._globalStyles;
}; };
Module.setGlobalStyles = function(options) { Module.setGlobalStyles = function (options) { // eslint-disable-line func-names
Module._globalStyles = new Module.StylesModel(options); Module._globalStyles = new Module.StylesModel(options);
return Module._globalStyles; return Module._globalStyles;
}; };
Module.getAvailableStyles = function() { Module.getAvailableStyles = function () { // eslint-disable-line func-names
return App.getConfig().get('availableStyles'); return App.getConfig().get('availableStyles');
}; };
App.on('before:start', function(App, options) { App.on('before:start', function (BeforeStartApp, options) { // eslint-disable-line func-names
var Application = App; var Application = BeforeStartApp;
var body;
var globalStyles;
// Expose style methods to global application // Expose style methods to global application
Application.getGlobalStyles = Module.getGlobalStyles; Application.getGlobalStyles = Module.getGlobalStyles;
Application.setGlobalStyles = Module.setGlobalStyles; Application.setGlobalStyles = Module.setGlobalStyles;
Application.getAvailableStyles = Module.getAvailableStyles; Application.getAvailableStyles = Module.getAvailableStyles;
var body = options.newsletter.body; body = options.newsletter.body;
var globalStyles = (_.has(body, 'globalStyles')) ? body.globalStyles : {}; globalStyles = (_.has(body, 'globalStyles')) ? body.globalStyles : {};
this.setGlobalStyles(globalStyles); this.setGlobalStyles(globalStyles);
}); });
App.on('start', function(App, options) { App.on('start', function (StartApp) { // eslint-disable-line func-names
var stylesView = new Module.StylesView({ model: App.getGlobalStyles() }); var stylesView = new Module.StylesView({ model: StartApp.getGlobalStyles() });
App._appView.showChildView('stylesRegion', stylesView); StartApp._appView.showChildView('stylesRegion', stylesView);
}); });
return Module; return Module;

View File

@ -8,33 +8,35 @@
* its placeholder into editor text. * its placeholder into editor text.
*/ */
/*jshint unused:false */ /* jshint unused:false */
/*global tinymce:true */ /* global tinymce:true */
tinymce.PluginManager.add('mailpoet_shortcodes', function(editor, url) { tinymce.PluginManager.add('mailpoet_shortcodes', function (editor) { // eslint-disable-line func-names
var appendLabelAndClose = function(shortcode) { var appendLabelAndClose = function (shortcode) { // eslint-disable-line func-names
editor.insertContent(shortcode); editor.insertContent(shortcode);
editor.windowManager.close(); editor.windowManager.close();
}, };
generateOnClickFunc = function(shortcode) { var generateOnClickFunc = function (shortcode) { // eslint-disable-line func-names
return function() { return function () { // eslint-disable-line func-names
appendLabelAndClose(shortcode); appendLabelAndClose(shortcode);
};
}; };
};
editor.addButton('mailpoet_shortcodes', { editor.addButton('mailpoet_shortcodes', {
icon: 'mailpoet_shortcodes', icon: 'mailpoet_shortcodes',
onclick: function() { onclick: function () { // eslint-disable-line func-names
var shortcodes = [], var shortcodes = [];
configShortcodes = editor.settings.mailpoet_shortcodes; var configShortcodes = editor.settings.mailpoet_shortcodes;
var segment;
var i;
for (var segment in configShortcodes) { for (segment in configShortcodes) {
if (configShortcodes.hasOwnProperty(segment)) { if (configShortcodes.hasOwnProperty(segment)) {
shortcodes.push({ shortcodes.push({
type: 'label', type: 'label',
text: segment text: segment
}); });
for (var i = 0; i < configShortcodes[segment].length; i += 1) { for (i = 0; i < configShortcodes[segment].length; i += 1) {
shortcodes.push({ shortcodes.push({
type: 'button', type: 'button',
text: configShortcodes[segment][i].text, text: configShortcodes[segment][i].text,

View File

@ -49,7 +49,7 @@ const stats = {
}; };
class StatsBadge extends React.Component { class StatsBadge extends React.Component {
getBadgeType(stat, rate) { static getBadgeType(stat, rate) {
const len = stat.badgeRanges.length; const len = stat.badgeRanges.length;
for (let i = 0; i < len; i += 1) { for (let i = 0; i < len; i += 1) {
if (rate > stat.badgeRanges[i]) { if (rate > stat.badgeRanges[i]) {
@ -70,7 +70,7 @@ class StatsBadge extends React.Component {
return null; return null;
} }
const badgeType = this.getBadgeType(stat, rate); const badgeType = StatsBadge.getBadgeType(stat, rate);
const badge = badges[badgeType] || null; const badge = badges[badgeType] || null;
if (!badge) { if (!badge) {
return null; return null;

View File

@ -46,18 +46,18 @@ define(
let label = step.label; let label = step.label;
if(step['link'] !== undefined && this.props.step !== step.name) { if (step.link !== undefined && this.props.step !== step.name) {
label = ( label = (
<Link to={ step.link }>{ step.label }</Link> <Link to={step.link}>{ step.label }</Link>
); );
} }
return ( return (
<span key={ 'step-'+index }> <span key={`step-${index}`}>
<span className={ stepClasses }> <span className={stepClasses}>
{ label } { label }
</span> </span>
{ (index < (this.state.steps.length - 1) ) ? ' > ' : '' } { (index < (this.state.steps.length - 1)) ? ' > ' : '' }
</span> </span>
); );
}); });

View File

@ -9,7 +9,7 @@ import jQuery from 'jquery';
import Hooks from 'wp-js-hooks'; import Hooks from 'wp-js-hooks';
import StatsBadge from 'newsletters/badges/stats.jsx'; import StatsBadge from 'newsletters/badges/stats.jsx';
const _QueueMixin = { const QueueMixin = {
pauseSending: function (newsletter) { pauseSending: function (newsletter) {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version, api_version: window.mailpoet_api_version,
@ -19,12 +19,12 @@ const _QueueMixin = {
newsletter_id: newsletter.id, newsletter_id: newsletter.id,
}, },
}).done(() => { }).done(() => {
jQuery('#resume_'+newsletter.id).show(); jQuery(`#resume_${newsletter.id}`).show();
jQuery('#pause_'+newsletter.id).hide(); jQuery(`#pause_${newsletter.id}`).hide();
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
@ -39,113 +39,119 @@ const _QueueMixin = {
newsletter_id: newsletter.id, newsletter_id: newsletter.id,
}, },
}).done(() => { }).done(() => {
jQuery('#pause_'+newsletter.id).show(); jQuery(`#pause_${newsletter.id}`).show();
jQuery('#resume_'+newsletter.id).hide(); jQuery(`#resume_${newsletter.id}`).hide();
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
}); });
}, },
renderQueueStatus: function (newsletter, mailer_log) { renderQueueStatus: function (newsletter, mailerLog) {
if (!newsletter.queue) { if (!newsletter.queue) {
return ( return (
<span>{MailPoet.I18n.t('notSentYet')}</span> <span>{MailPoet.I18n.t('notSentYet')}</span>
); );
} else if (mailer_log.status === 'paused' && newsletter.queue.status !== 'completed') { } else if (mailerLog.status === 'paused' && newsletter.queue.status !== 'completed') {
return ( return (
<span>{MailPoet.I18n.t('paused')}</span> <span>{MailPoet.I18n.t('paused')}</span>
); );
} else { }
if (newsletter.queue.status === 'scheduled') { if (newsletter.queue.status === 'scheduled') {
return ( return (
<span> <span>
{ MailPoet.I18n.t('scheduledFor') } { MailPoet.Date.format(newsletter.queue.scheduled_at) } { MailPoet.I18n.t('scheduledFor') } { MailPoet.Date.format(newsletter.queue.scheduled_at) }
</span> </span>
); );
} }
const progressClasses = classNames( const progressClasses = classNames(
'mailpoet_progress', 'mailpoet_progress',
{ mailpoet_progress_complete: newsletter.queue.status === 'completed' } { mailpoet_progress_complete: newsletter.queue.status === 'completed' }
); );
// calculate percentage done // calculate percentage done
let percentage = Math.round( let percentage = Math.round(
(newsletter.queue.count_processed * 100) / (newsletter.queue.count_total) (newsletter.queue.count_processed * 100) / (newsletter.queue.count_total)
); );
let label; let label;
if (newsletter.queue.status === 'completed') { if (newsletter.queue.status === 'completed') {
label = ( label = (
<span> <span>
{ {
MailPoet.I18n.t('newsletterQueueCompleted') MailPoet.I18n.t('newsletterQueueCompleted')
.replace('%$1d', newsletter.queue.count_processed) .replace('%$1d', parseInt(newsletter.queue.count_processed, 10).toLocaleString())
.replace('%$2d', newsletter.queue.count_total) .replace('%$2d', parseInt(newsletter.queue.count_total, 10).toLocaleString())
} }
</span> </span>
); );
} else { } else {
label = ( label = (
<span> <span>
{ newsletter.queue.count_processed } / { newsletter.queue.count_total } { newsletter.queue.count_processed } / { newsletter.queue.count_total }
&nbsp;&nbsp; &nbsp;&nbsp;
<a <a
id={ 'resume_'+newsletter.id } id={`resume_${newsletter.id}`}
className="button" className="button"
style={{ display: (newsletter.queue.status === 'paused') style={{ display: (newsletter.queue.status === 'paused')
? 'inline-block': 'none' }} ? 'inline-block' : 'none' }}
href="javascript:;" href="javascript:;"
onClick={ this.resumeSending.bind(null, newsletter) } onClick={this.resumeSending.bind(null, newsletter)}
>{MailPoet.I18n.t('resume')}</a> >{MailPoet.I18n.t('resume')}</a>
<a <a
id={ 'pause_'+newsletter.id } id={`pause_${newsletter.id}`}
className="button mailpoet_pause" className="button mailpoet_pause"
style={{ display: (newsletter.queue.status === null) style={{ display: (newsletter.queue.status === null)
? 'inline-block': 'none' }} ? 'inline-block' : 'none' }}
href="javascript:;" href="javascript:;"
onClick={ this.pauseSending.bind(null, newsletter) } onClick={this.pauseSending.bind(null, newsletter)}
>{MailPoet.I18n.t('pause')}</a> >{MailPoet.I18n.t('pause')}</a>
</span> </span>
); );
}
let progress_bar_width = 0;
if (isNaN(percentage)) {
percentage = MailPoet.I18n.t('noSubscribers');
} else {
progress_bar_width = percentage;
percentage += '%';
}
return (
<div>
<div className={ progressClasses }>
<span
className="mailpoet_progress_bar"
style={ { width: progress_bar_width + '%' } }
></span>
<span className="mailpoet_progress_label">
{ percentage }
</span>
</div>
<p style={{ textAlign:'center' }}>
{ label }
</p>
</div>
);
} }
let progressBarWidth = 0;
if (isNaN(percentage)) {
percentage = MailPoet.I18n.t('noSubscribers');
} else {
progressBarWidth = percentage;
percentage += '%';
}
return (
<div>
<div className={progressClasses}>
<span
className="mailpoet_progress_bar"
style={{ width: `${progressBarWidth}%` }}
></span>
<span className="mailpoet_progress_label">
{ percentage }
</span>
</div>
<p style={{ textAlign: 'center' }}>
{ label }
</p>
</div>
);
}, },
}; };
const _StatisticsMixin = { const trackStatsCTAClicked = function () {
renderStatistics: function (newsletter, is_sent, current_time) { MailPoet.trackEvent(
let sent = is_sent; 'User has clicked a CTA to view detailed stats',
{ 'MailPoet Free version': window.mailpoet_version }
);
};
const StatisticsMixin = {
renderStatistics: function (newsletter, isSent, currentTime) {
let sent = isSent;
if (sent === undefined) { if (sent === undefined) {
// condition for standard and post notification listings // condition for standard and post notification listings
sent = newsletter.statistics sent = newsletter.statistics
@ -159,77 +165,78 @@ const _StatisticsMixin = {
} }
let params = {}; let params = {};
Hooks.addFilter('mailpoet_newsletters_listing_stats_before', this.addStatsCTALink);
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter); params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
// welcome emails provide explicit total_sent value // welcome emails provide explicit total_sent value
const total_sent = ~~(newsletter.total_sent || newsletter.queue.count_processed); const totalSent = Number((newsletter.total_sent || newsletter.queue.count_processed));
let percentage_clicked = 0; let percentageClicked = 0;
let percentage_opened = 0; let percentageOpened = 0;
let percentage_unsubscribed = 0; let percentageUnsubscribed = 0;
if (total_sent > 0) { if (totalSent > 0) {
percentage_clicked = (newsletter.statistics.clicked * 100) / total_sent; percentageClicked = (newsletter.statistics.clicked * 100) / totalSent;
percentage_opened = (newsletter.statistics.opened * 100) / total_sent; percentageOpened = (newsletter.statistics.opened * 100) / totalSent;
percentage_unsubscribed = (newsletter.statistics.unsubscribed * 100) / total_sent; percentageUnsubscribed = (newsletter.statistics.unsubscribed * 100) / totalSent;
} }
// format to 1 decimal place // format to 1 decimal place
const percentage_clicked_display = MailPoet.Num.toLocaleFixed(percentage_clicked, 1); const percentageClickedDisplay = MailPoet.Num.toLocaleFixed(percentageClicked, 1);
const percentage_opened_display = MailPoet.Num.toLocaleFixed(percentage_opened, 1); const percentageOpenedDisplay = MailPoet.Num.toLocaleFixed(percentageOpened, 1);
const percentage_unsubscribed_display = MailPoet.Num.toLocaleFixed(percentage_unsubscribed, 1); const percentageUnsubscribedDisplay = MailPoet.Num.toLocaleFixed(percentageUnsubscribed, 1);
let show_stats_timeout, let showStatsTimeout;
newsletter_date, let newsletterDate;
sent_hours_ago, let sentHoursAgo;
too_early_for_stats, let tooEarlyForStats;
show_kb_link; let showKbLink;
if (current_time !== undefined) { if (currentTime !== undefined) {
// standard emails and post notifications: // standard emails and post notifications:
// display green box for newsletters that were just sent // display green box for newsletters that were just sent
show_stats_timeout = 6; // in hours showStatsTimeout = 6; // in hours
newsletter_date = newsletter.queue.scheduled_at || newsletter.queue.created_at; newsletterDate = newsletter.queue.scheduled_at || newsletter.queue.created_at;
sent_hours_ago = moment(current_time).diff(moment(newsletter_date), 'hours'); sentHoursAgo = moment(currentTime).diff(moment(newsletterDate), 'hours');
too_early_for_stats = sent_hours_ago < show_stats_timeout; tooEarlyForStats = sentHoursAgo < showStatsTimeout;
show_kb_link = true; showKbLink = true;
} else { } else {
// welcome emails: no green box and KB link // welcome emails: no green box and KB link
too_early_for_stats = false; tooEarlyForStats = false;
show_kb_link = false; showKbLink = false;
} }
const improveStatsKBLink = 'http://beta.docs.mailpoet.com/article/191-how-to-improve-my-open-and-click-rates'; const improveStatsKBLink = 'http://beta.docs.mailpoet.com/article/191-how-to-improve-my-open-and-click-rates';
// thresholds to display badges // thresholds to display badges
const min_newsletters_sent = 20; const minNewslettersSent = 20;
const min_newsletter_opens = 5; const minNewsletterOpens = 5;
let content; let content;
if (total_sent >= min_newsletters_sent if (totalSent >= minNewslettersSent
&& newsletter.statistics.opened >= min_newsletter_opens && newsletter.statistics.opened >= minNewsletterOpens
&& !too_early_for_stats && !tooEarlyForStats
) { ) {
// display stats with badges // display stats with badges
content = ( content = (
<div className="mailpoet_stats_text"> <div className="mailpoet_stats_text">
<div> <div>
<span>{ percentage_opened_display }% </span> <span>{ percentageOpenedDisplay }% </span>
<StatsBadge <StatsBadge
stat="opened" stat="opened"
rate={percentage_opened} rate={percentageOpened}
tooltipId={`opened-${newsletter.id}`} tooltipId={`opened-${newsletter.id}`}
/> />
</div> </div>
<div> <div>
<span>{ percentage_clicked_display }% </span> <span>{ percentageClickedDisplay }% </span>
<StatsBadge <StatsBadge
stat="clicked" stat="clicked"
rate={percentage_clicked} rate={percentageClicked}
tooltipId={`clicked-${newsletter.id}`} tooltipId={`clicked-${newsletter.id}`}
/> />
</div> </div>
<div> <div>
<span className="mailpoet_stat_hidden">{ percentage_unsubscribed_display }%</span> <span className="mailpoet_stat_hidden">{ percentageUnsubscribedDisplay }%</span>
</div> </div>
</div> </div>
); );
@ -238,17 +245,17 @@ const _StatisticsMixin = {
content = ( content = (
<div> <div>
<span className="mailpoet_stats_text"> <span className="mailpoet_stats_text">
{ percentage_opened_display }%, { percentageOpenedDisplay }%,
{ ' ' } { ' ' }
{ percentage_clicked_display }% { percentageClickedDisplay }%
<span className="mailpoet_stat_hidden"> <span className="mailpoet_stat_hidden">
, { percentage_unsubscribed_display }% , { percentageUnsubscribedDisplay }%
</span> </span>
</span> </span>
{ too_early_for_stats && ( { tooEarlyForStats && (
<div className="mailpoet_badge mailpoet_badge_green"> <div className="mailpoet_badge mailpoet_badge_green">
{MailPoet.I18n.t('checkBackInHours') {MailPoet.I18n.t('checkBackInHours')
.replace('%$1d', show_stats_timeout - sent_hours_ago)} .replace('%$1d', showStatsTimeout - sentHoursAgo)}
</div> </div>
) } ) }
</div> </div>
@ -256,18 +263,18 @@ const _StatisticsMixin = {
} }
// thresholds to display bad open rate help // thresholds to display bad open rate help
const max_percentage_opened = 5; const maxPercentageOpened = 5;
const min_sent_hours_ago = 24; const minSentHoursAgo = 24;
const min_total_sent = 10; const minTotalSent = 10;
let after_content; let afterContent;
if (show_kb_link if (showKbLink
&& percentage_opened < max_percentage_opened && percentageOpened < maxPercentageOpened
&& sent_hours_ago >= min_sent_hours_ago && sentHoursAgo >= minSentHoursAgo
&& total_sent >= min_total_sent && totalSent >= minTotalSent
) { ) {
// help link for bad open rate // help link for bad open rate
after_content = ( afterContent = (
<div> <div>
<a <a
href={improveStatsKBLink} href={improveStatsKBLink}
@ -280,18 +287,32 @@ const _StatisticsMixin = {
); );
} }
if (total_sent > 0 && params.link) { if (totalSent > 0 && params.link) {
// wrap content in a link // wrap content in a link
if (params.externalLink) {
return (
<div>
<a
key={`stats-${newsletter.id}`}
href={params.link}
onClick={params.onClick || null}
>
{content}
</a>
{afterContent}
</div>
);
}
return ( return (
<div> <div>
<Link <Link
key={ `stats-${newsletter.id}` } key={`stats-${newsletter.id}`}
to={ params.link } to={params.link}
onClick={ params.onClick || null } onClick={params.onClick || null}
> >
{content} {content}
</Link> </Link>
{after_content} {afterContent}
</div> </div>
); );
} }
@ -299,13 +320,44 @@ const _StatisticsMixin = {
return ( return (
<div> <div>
{content} {content}
{after_content} {afterContent}
</div> </div>
); );
}, },
addStatsCTAAction: function (actions) {
if (window.mailpoet_premium_active) {
return actions;
}
actions.unshift({
name: 'stats',
link: function () {
return (
<a href={'admin.php?page=mailpoet-premium'} onClick={trackStatsCTAClicked}>
{MailPoet.I18n.t('statsListingActionTitle')}
</a>
);
},
display: function (newsletter) {
// welcome emails provide explicit total_sent value
const countProcessed = newsletter.queue && newsletter.queue.count_processed;
return Number(newsletter.total_sent || countProcessed) > 0;
},
});
return actions;
},
addStatsCTALink: function (params) {
if (window.mailpoet_premium_active) {
return params;
}
const newParams = params;
newParams.link = 'admin.php?page=mailpoet-premium';
newParams.externalLink = true;
newParams.onClick = trackStatsCTAClicked;
return newParams;
},
}; };
const _MailerMixin = { const MailerMixin = {
checkMailerStatus: function (state) { checkMailerStatus: function (state) {
if (state.meta.mta_log.error && state.meta.mta_log.status === 'paused') { if (state.meta.mta_log.error && state.meta.mta_log.status === 'paused') {
MailPoet.Notice.error( MailPoet.Notice.error(
@ -322,8 +374,8 @@ const _MailerMixin = {
} }
}, },
getMailerError(state) { getMailerError(state) {
let mailer_error_notice; let mailerErrorNotice;
const mailer_check_settings_notice = ReactStringReplace( const mailerCheckSettingsNotice = ReactStringReplace(
MailPoet.I18n.t('mailerCheckSettingsNotice'), MailPoet.I18n.t('mailerCheckSettingsNotice'),
/\[link\](.*?)\[\/link\]/g, /\[link\](.*?)\[\/link\]/g,
match => ( match => (
@ -331,23 +383,27 @@ const _MailerMixin = {
) )
); );
if (state.meta.mta_log.error.operation === 'send') { if (state.meta.mta_log.error.operation === 'send') {
mailer_error_notice = mailerErrorNotice =
MailPoet.I18n.t('mailerSendErrorNotice') MailPoet.I18n.t('mailerSendErrorNotice')
.replace('%$1s', state.meta.mta_method) .replace('%$1s', state.meta.mta_method)
.replace('%$2s', state.meta.mta_log.error.error_message); .replace('%$2s', state.meta.mta_log.error.error_message);
} else { } else {
mailer_error_notice = mailerErrorNotice =
MailPoet.I18n.t('mailerConnectionErrorNotice') MailPoet.I18n.t('mailerConnectionErrorNotice')
.replace('%$1s', state.meta.mta_log.error.error_message); .replace('%$1s', state.meta.mta_log.error.error_message);
} }
if (state.meta.mta_log.error.error_code) {
mailerErrorNotice += ` ${MailPoet.I18n.t('mailerErrorCode')
.replace('%$1s', state.meta.mta_log.error.error_code)}`;
}
return ( return (
<div> <div>
<p>{ mailer_error_notice }</p> <p>{ mailerErrorNotice }</p>
<p>{ mailer_check_settings_notice }</p> <p>{ mailerCheckSettingsNotice }</p>
<p> <p>
<a href="javascript:;" <a href="javascript:;"
className="button" className="button"
onClick={ this.resumeMailerSending } onClick={this.resumeMailerSending}
>{ MailPoet.I18n.t('mailerResumeSendingButton') }</a> >{ MailPoet.I18n.t('mailerResumeSendingButton') }</a>
</p> </p>
</div> </div>
@ -365,7 +421,7 @@ const _MailerMixin = {
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
@ -374,6 +430,6 @@ const _MailerMixin = {
}; };
export { _QueueMixin as QueueMixin }; export { QueueMixin };
export { _StatisticsMixin as StatisticsMixin }; export { StatisticsMixin };
export { _MailerMixin as MailerMixin }; export { MailerMixin };

View File

@ -18,7 +18,7 @@ import {
const messages = { const messages = {
onTrash: (response) => { onTrash: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -33,7 +33,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onDelete: (response) => { onDelete: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -48,7 +48,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onRestore: (response) => { onRestore: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -91,7 +91,7 @@ const columns = [
}, },
]; ];
const bulk_actions = [ const bulkActions = [
{ {
name: 'trash', name: 'trash',
label: MailPoet.I18n.t('moveToTrash'), label: MailPoet.I18n.t('moveToTrash'),
@ -99,12 +99,12 @@ const bulk_actions = [
}, },
]; ];
const newsletter_actions = [ const newsletterActions = [
{ {
name: 'view', name: 'view',
link: function (newsletter) { link: function (newsletter) {
return ( return (
<a href={ newsletter.preview_url } target="_blank"> <a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')} {MailPoet.I18n.t('preview')}
</a> </a>
); );
@ -114,7 +114,7 @@ const newsletter_actions = [
name: 'edit', name: 'edit',
link: function (newsletter) { link: function (newsletter) {
return ( return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }> <a href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}>
{MailPoet.I18n.t('edit')} {MailPoet.I18n.t('edit')}
</a> </a>
); );
@ -141,7 +141,7 @@ const newsletter_actions = [
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
@ -154,7 +154,7 @@ const newsletter_actions = [
]; ];
const NewsletterListNotification = React.createClass({ const NewsletterListNotification = React.createClass({
mixins: [ MailerMixin ], mixins: [MailerMixin],
updateStatus: function (e) { updateStatus: function (e) {
// make the event persist so that we can still override the selected value // make the event persist so that we can still override the selected value
// in the ajax callback // in the ajax callback
@ -165,7 +165,7 @@ const NewsletterListNotification = React.createClass({
endpoint: 'newsletters', endpoint: 'newsletters',
action: 'setStatus', action: 'setStatus',
data: { data: {
id: ~~(e.target.getAttribute('data-id')), id: Number(e.target.getAttribute('data-id')),
status: e.target.value, status: e.target.value,
}, },
}).done((response) => { }).done((response) => {
@ -184,9 +184,9 @@ const NewsletterListNotification = React.createClass({
renderStatus: function (newsletter) { renderStatus: function (newsletter) {
return ( return (
<select <select
data-id={ newsletter.id } data-id={newsletter.id}
defaultValue={ newsletter.status } defaultValue={newsletter.status}
onChange={ this.updateStatus } onChange={this.updateStatus}
> >
<option value="active">{ MailPoet.I18n.t('active') }</option> <option value="active">{ MailPoet.I18n.t('active') }</option>
<option value="draft">{ MailPoet.I18n.t('inactive') }</option> <option value="draft">{ MailPoet.I18n.t('inactive') }</option>
@ -195,65 +195,66 @@ const NewsletterListNotification = React.createClass({
}, },
renderSettings: function (newsletter) { renderSettings: function (newsletter) {
let sendingFrequency; let sendingFrequency;
let sendingToSegments;
// get list of segments' name // get list of segments' name
const segments = newsletter.segments.map((segment) => { const segments = newsletter.segments.map(segment => segment.name);
return segment.name; const sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
}); '%$1s', segments.join(', ')
);
// check if the user has specified segments to send to // check if the user has specified segments to send to
if(segments.length === 0) { if (segments.length === 0) {
return ( return (
<span className="mailpoet_error"> <span className="mailpoet_error">
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') } { MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span> </span>
); );
} else { }
sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
'%$1s', segments.join(', ')
);
// set sending frequency // set sending frequency
switch (newsletter.options.intervalType) { switch (newsletter.options.intervalType) {
case 'daily': case 'daily':
sendingFrequency = MailPoet.I18n.t('sendDaily').replace( sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
'%$1s', timeOfDayValues[newsletter.options.timeOfDay] '%$1s', timeOfDayValues[newsletter.options.timeOfDay]
); );
break; break;
case 'weekly': case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace( sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
'%$1s', weekDayValues[newsletter.options.weekDay] '%$1s', weekDayValues[newsletter.options.weekDay]
).replace( ).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay] '%$2s', timeOfDayValues[newsletter.options.timeOfDay]
); );
break; break;
case 'monthly': case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace( sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
'%$1s', monthDayValues[newsletter.options.monthDay] '%$1s', monthDayValues[newsletter.options.monthDay]
).replace( ).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay] '%$2s', timeOfDayValues[newsletter.options.timeOfDay]
); );
break; break;
case 'nthWeekDay': case 'nthWeekDay':
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace( sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
'%$1s', nthWeekDayValues[newsletter.options.nthWeekDay] '%$1s', nthWeekDayValues[newsletter.options.nthWeekDay]
).replace( ).replace(
'%$2s', weekDayValues[newsletter.options.weekDay] '%$2s', weekDayValues[newsletter.options.weekDay]
).replace( ).replace(
'%$3s', timeOfDayValues[newsletter.options.timeOfDay] '%$3s', timeOfDayValues[newsletter.options.timeOfDay]
); );
break; break;
case 'immediately': case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately'); sendingFrequency = MailPoet.I18n.t('sendImmediately');
break; break;
}
default:
sendingFrequency = 'Invalid sending frequency';
break;
} }
return ( return (
<span> <span>
{ sendingFrequency } { sendingToSegments } { sendingFrequency } { sendingToSegments }
@ -261,18 +262,17 @@ const NewsletterListNotification = React.createClass({
); );
}, },
renderHistoryLink: function (newsletter) { renderHistoryLink: function (newsletter) {
const childrenCount = ~~(newsletter.children_count); const childrenCount = Number((newsletter.children_count));
if (childrenCount === 0) { if (childrenCount === 0) {
return ( return (
MailPoet.I18n.t('notSentYet') MailPoet.I18n.t('notSentYet')
); );
} else {
return (
<Link
to={ `/notification/history/${ newsletter.id }` }
>{ MailPoet.I18n.t('viewHistory') }</Link>
);
} }
return (
<Link
to={`/notification/history/${newsletter.id}`}
>{ MailPoet.I18n.t('viewHistory') }</Link>
);
}, },
renderItem: function (newsletter, actions) { renderItem: function (newsletter, actions) {
const rowClasses = classNames( const rowClasses = classNames(
@ -283,25 +283,25 @@ const NewsletterListNotification = React.createClass({
return ( return (
<div> <div>
<td className={ rowClasses }> <td className={rowClasses}>
<strong> <strong>
<a <a
className="row-title" className="row-title"
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` } href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}
>{ newsletter.subject }</a> >{ newsletter.subject }</a>
</strong> </strong>
{ actions } { actions }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }> <td className="column" data-colname={MailPoet.I18n.t('status')}>
{ this.renderStatus(newsletter) } { this.renderStatus(newsletter) }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('settings') }> <td className="column" data-colname={MailPoet.I18n.t('settings')}>
{ this.renderSettings(newsletter) } { this.renderSettings(newsletter) }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('history') }> <td className="column" data-colname={MailPoet.I18n.t('history')}>
{ this.renderHistoryLink(newsletter) } { this.renderHistoryLink(newsletter) }
</td> </td>
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }> <td className="column-date" data-colname={MailPoet.I18n.t('lastModifiedOn')}>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr> <abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td> </td>
</div> </div>
@ -311,27 +311,27 @@ const NewsletterListNotification = React.createClass({
return ( return (
<div> <div>
<h1 className="title"> <h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link> {MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new" data-automation-id="new_email">{MailPoet.I18n.t('new')}</Link>
</h1> </h1>
<ListingTabs tab="notification" /> <ListingTabs tab="notification" />
<Listing <Listing
limit={ window.mailpoet_listing_per_page } limit={window.mailpoet_listing_per_page}
location={ this.props.location } location={this.props.location}
params={ this.props.params } params={this.props.params}
endpoint="newsletters" endpoint="newsletters"
type="notification" type="notification"
base_url="notification" base_url="notification"
onRenderItem={ this.renderItem } onRenderItem={this.renderItem}
columns={ columns } columns={columns}
bulk_actions={ bulk_actions } bulk_actions={bulkActions}
item_actions={ newsletter_actions } item_actions={newsletterActions}
messages={ messages } messages={messages}
auto_refresh={ true } auto_refresh={true}
sort_by="updated_at" sort_by="updated_at"
sort_order="desc" sort_order="desc"
afterGetItems={ this.checkMailerStatus } afterGetItems={this.checkMailerStatus}
/> />
</div> </div>
); );

View File

@ -13,7 +13,7 @@ import {
MailerMixin, MailerMixin,
} from 'newsletters/listings/mixins.jsx'; } from 'newsletters/listings/mixins.jsx';
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled'])); const mailpoetTrackingEnabled = (!!(window.mailpoet_tracking_enabled));
const columns = [ const columns = [
{ {
@ -31,7 +31,7 @@ const columns = [
{ {
name: 'statistics', name: 'statistics',
label: MailPoet.I18n.t('statistics'), label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled, display: mailpoetTrackingEnabled,
}, },
{ {
name: 'sent_at', name: 'sent_at',
@ -39,12 +39,12 @@ const columns = [
}, },
]; ];
let newsletter_actions = [ let newsletterActions = [
{ {
name: 'view', name: 'view',
link: function (newsletter) { link: function (newsletter) {
return ( return (
<a href={ newsletter.preview_url } target="_blank"> <a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')} {MailPoet.I18n.t('preview')}
</a> </a>
); );
@ -52,10 +52,11 @@ let newsletter_actions = [
}, },
]; ];
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_notification_history_actions', newsletter_actions); Hooks.addFilter('mailpoet_newsletters_listings_notification_history_actions', StatisticsMixin.addStatsCTAAction);
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_notification_history_actions', newsletterActions);
const NewsletterListNotificationHistory = React.createClass({ const NewsletterListNotificationHistory = React.createClass({
mixins: [ QueueMixin, StatisticsMixin, MailerMixin ], mixins: [QueueMixin, StatisticsMixin, MailerMixin],
renderItem: function (newsletter, actions, meta) { renderItem: function (newsletter, actions, meta) {
const rowClasses = classNames( const rowClasses = classNames(
'manage-column', 'manage-column',
@ -63,33 +64,31 @@ const NewsletterListNotificationHistory = React.createClass({
'has-row-actions' 'has-row-actions'
); );
const segments = newsletter.segments.map((segment) => { const segments = newsletter.segments.map(segment => segment.name).join(', ');
return segment.name;
}).join(', ');
return ( return (
<div> <div>
<td className={ rowClasses }> <td className={rowClasses}>
<strong> <strong>
<a <a
href={ newsletter.preview_url } href={newsletter.preview_url}
target="_blank" target="_blank"
>{ newsletter.queue.newsletter_rendered_subject || newsletter.subject }</a> >{ newsletter.queue.newsletter_rendered_subject || newsletter.subject }</a>
</strong> </strong>
{ actions } { actions }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }> <td className="column" data-colname={MailPoet.I18n.t('status')}>
{ this.renderQueueStatus(newsletter, meta.mta_log) } { this.renderQueueStatus(newsletter, meta.mta_log) }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('lists') }> <td className="column" data-colname={MailPoet.I18n.t('lists')}>
{ segments } { segments }
</td> </td>
{ (mailpoet_tracking_enabled === true) ? ( { (mailpoetTrackingEnabled === true) ? (
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }> <td className="column" data-colname={MailPoet.I18n.t('statistics')}>
{ this.renderStatistics(newsletter, undefined, meta.current_time) } { this.renderStatistics(newsletter, undefined, meta.current_time) }
</td> </td>
) : null } ) : null }
<td className="column-date" data-colname={ MailPoet.I18n.t('sentOn') }> <td className="column-date" data-colname={MailPoet.I18n.t('sentOn')}>
{ (newsletter.sent_at) ? MailPoet.Date.format(newsletter.sent_at) : MailPoet.I18n.t('notSentYet') } { (newsletter.sent_at) ? MailPoet.Date.format(newsletter.sent_at) : MailPoet.I18n.t('notSentYet') }
</td> </td>
</div> </div>
@ -99,7 +98,7 @@ const NewsletterListNotificationHistory = React.createClass({
return ( return (
<div> <div>
<h1 className="title"> <h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link> {MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new" data-automation-id="new_email">{MailPoet.I18n.t('new')}</Link>
</h1> </h1>
<ListingTabs tab="notification" /> <ListingTabs tab="notification" />
@ -110,19 +109,19 @@ const NewsletterListNotificationHistory = React.createClass({
>{MailPoet.I18n.t('backToPostNotifications')}</Link> >{MailPoet.I18n.t('backToPostNotifications')}</Link>
<Listing <Listing
limit={ window.mailpoet_listing_per_page } limit={window.mailpoet_listing_per_page}
location={ this.props.location } location={this.props.location}
params={ this.props.params } params={this.props.params}
endpoint="newsletters" endpoint="newsletters"
type="notification_history" type="notification_history"
base_url="notification/history/:parent_id" base_url="notification/history/:parent_id"
onRenderItem={ this.renderItem } onRenderItem={this.renderItem}
columns={columns} columns={columns}
item_actions={ newsletter_actions } item_actions={newsletterActions}
auto_refresh={ true } auto_refresh={true}
sort_by="sent_at" sort_by="sent_at"
sort_order="desc" sort_order="desc"
afterGetItems={ this.checkMailerStatus } afterGetItems={this.checkMailerStatus}
/> />
</div> </div>
); );

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { confirmAlert } from 'react-confirm-alert';
import classNames from 'classnames'; import classNames from 'classnames';
import MailPoet from 'mailpoet'; import MailPoet from 'mailpoet';
import Hooks from 'wp-js-hooks'; import Hooks from 'wp-js-hooks';
@ -13,11 +14,11 @@ import {
MailerMixin, MailerMixin,
} from 'newsletters/listings/mixins.jsx'; } from 'newsletters/listings/mixins.jsx';
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled'])); const mailpoetTrackingEnabled = (!!(window.mailpoet_tracking_enabled));
const messages = { const messages = {
onTrash: (response) => { onTrash: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -32,7 +33,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onDelete: (response) => { onDelete: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -47,7 +48,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onRestore: (response) => { onRestore: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -80,7 +81,7 @@ const columns = [
{ {
name: 'statistics', name: 'statistics',
label: MailPoet.I18n.t('statistics'), label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled, display: mailpoetTrackingEnabled,
}, },
{ {
name: 'sent_at', name: 'sent_at',
@ -89,7 +90,7 @@ const columns = [
}, },
]; ];
const bulk_actions = [ const bulkActions = [
{ {
name: 'trash', name: 'trash',
label: MailPoet.I18n.t('moveToTrash'), label: MailPoet.I18n.t('moveToTrash'),
@ -98,22 +99,32 @@ const bulk_actions = [
]; ];
const confirmEdit = (newsletter) => { const confirmEdit = (newsletter) => {
if( const redirectToEditing = () => {
window.location.href = `?page=mailpoet-newsletter-editor&id=${newsletter.id}`;
};
if (
!newsletter.queue !newsletter.queue
|| newsletter.status != 'sending' || newsletter.status !== 'sending'
|| newsletter.queue.status !== null || newsletter.queue.status !== null
|| window.confirm(MailPoet.I18n.t('confirmEdit'))
) { ) {
window.location.href = `?page=mailpoet-newsletter-editor&id=${ newsletter.id }`; redirectToEditing();
} else {
confirmAlert({
title: MailPoet.I18n.t('confirmTitle'),
message: MailPoet.I18n.t('confirmEdit'),
confirmLabel: MailPoet.I18n.t('confirmLabel'),
cancelLabel: MailPoet.I18n.t('cancelLabel'),
onConfirm: redirectToEditing,
});
} }
}; };
let newsletter_actions = [ let newsletterActions = [
{ {
name: 'view', name: 'view',
link: function (newsletter) { link: function (newsletter) {
return ( return (
<a href={ newsletter.preview_url } target="_blank"> <a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')} {MailPoet.I18n.t('preview')}
</a> </a>
); );
@ -145,7 +156,7 @@ let newsletter_actions = [
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }), response.errors.map(error => error.message),
{ scroll: true } { scroll: true }
); );
} }
@ -157,10 +168,11 @@ let newsletter_actions = [
}, },
]; ];
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_standard_actions', newsletter_actions); Hooks.addFilter('mailpoet_newsletters_listings_standard_actions', StatisticsMixin.addStatsCTAAction);
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_standard_actions', newsletterActions);
const NewsletterListStandard = React.createClass({ const NewsletterListStandard = React.createClass({
mixins: [ QueueMixin, StatisticsMixin, MailerMixin ], mixins: [QueueMixin, StatisticsMixin, MailerMixin],
renderItem: function (newsletter, actions, meta) { renderItem: function (newsletter, actions, meta) {
const rowClasses = classNames( const rowClasses = classNames(
'manage-column', 'manage-column',
@ -168,13 +180,11 @@ const NewsletterListStandard = React.createClass({
'has-row-actions' 'has-row-actions'
); );
const segments = newsletter.segments.map((segment) => { const segments = newsletter.segments.map(segment => segment.name).join(', ');
return segment.name;
}).join(', ');
return ( return (
<div> <div>
<td className={ rowClasses }> <td className={rowClasses}>
<strong> <strong>
<a <a
className="row-title" className="row-title"
@ -184,18 +194,18 @@ const NewsletterListStandard = React.createClass({
</strong> </strong>
{ actions } { actions }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }> <td className="column" data-colname={MailPoet.I18n.t('status')}>
{ this.renderQueueStatus(newsletter, meta.mta_log) } { this.renderQueueStatus(newsletter, meta.mta_log) }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('lists') }> <td className="column" data-colname={MailPoet.I18n.t('lists')}>
{ segments } { segments }
</td> </td>
{ (mailpoet_tracking_enabled === true) ? ( { (mailpoetTrackingEnabled === true) ? (
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }> <td className="column" data-colname={MailPoet.I18n.t('statistics')}>
{ this.renderStatistics(newsletter, undefined, meta.current_time) } { this.renderStatistics(newsletter, undefined, meta.current_time) }
</td> </td>
) : null } ) : null }
<td className="column-date" data-colname={ MailPoet.I18n.t('sentOn') }> <td className="column-date" data-colname={MailPoet.I18n.t('sentOn')}>
<abbr>{ (newsletter.sent_at) ? MailPoet.Date.format(newsletter.sent_at) : MailPoet.I18n.t('notSentYet') }</abbr> <abbr>{ (newsletter.sent_at) ? MailPoet.Date.format(newsletter.sent_at) : MailPoet.I18n.t('notSentYet') }</abbr>
</td> </td>
</div> </div>
@ -210,6 +220,7 @@ const NewsletterListStandard = React.createClass({
onClick={() => MailPoet.trackEvent('Emails > Add New', onClick={() => MailPoet.trackEvent('Emails > Add New',
{ 'MailPoet Free version': window.mailpoet_version } { 'MailPoet Free version': window.mailpoet_version }
)} )}
data-automation-id="new_email"
> >
{MailPoet.I18n.t('new')} {MailPoet.I18n.t('new')}
</Link> </Link>
@ -218,21 +229,21 @@ const NewsletterListStandard = React.createClass({
<ListingTabs tab="standard" /> <ListingTabs tab="standard" />
<Listing <Listing
limit={ window.mailpoet_listing_per_page } limit={window.mailpoet_listing_per_page}
location={ this.props.location } location={this.props.location}
params={ this.props.params } params={this.props.params}
endpoint="newsletters" endpoint="newsletters"
type="standard" type="standard"
base_url="standard" base_url="standard"
onRenderItem={this.renderItem} onRenderItem={this.renderItem}
columns={columns} columns={columns}
bulk_actions={ bulk_actions } bulk_actions={bulkActions}
item_actions={ newsletter_actions } item_actions={newsletterActions}
messages={ messages } messages={messages}
auto_refresh={ true } auto_refresh={true}
sort_by="sent_at" sort_by="sent_at"
sort_order="desc" sort_order="desc"
afterGetItems={ this.checkMailerStatus } afterGetItems={this.checkMailerStatus}
/> />
</div> </div>
); );

View File

@ -35,9 +35,9 @@ const ListingTabs = React.createClass({
return ( return (
<Link <Link
key={ 'tab-'+index } key={`tab-${index}`}
className={ tabClasses } className={tabClasses}
to={ tab.link } to={tab.link}
onClick={() => MailPoet.trackEvent(`Tab Emails > ${tab.name} clicked`, onClick={() => MailPoet.trackEvent(`Tab Emails > ${tab.name} clicked`,
{ 'MailPoet Free version': window.mailpoet_version } { 'MailPoet Free version': window.mailpoet_version }
)} )}
@ -46,7 +46,7 @@ const ListingTabs = React.createClass({
}); });
return ( return (
<h2 className="nav-tab-wrapper"> <h2 className="nav-tab-wrapper" data-automation-id="newsletters_listing_tabs">
{ tabs } { tabs }
</h2> </h2>
); );

View File

@ -11,13 +11,13 @@ import MailPoet from 'mailpoet';
import _ from 'underscore'; import _ from 'underscore';
import Hooks from 'wp-js-hooks'; import Hooks from 'wp-js-hooks';
const mailpoet_roles = window.mailpoet_roles || {}; const mailpoetRoles = window.mailpoet_roles || {};
const mailpoet_segments = window.mailpoet_segments || {}; const mailpoetSegments = window.mailpoet_segments || {};
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled'])); const mailpoetTrackingEnabled = (!!(window.mailpoet_tracking_enabled));
const messages = { const messages = {
onTrash: (response) => { onTrash: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -32,7 +32,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onDelete: (response) => { onDelete: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -47,7 +47,7 @@ const messages = {
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
}, },
onRestore: (response) => { onRestore: (response) => {
const count = ~~response.meta.count; const count = Number(response.meta.count);
let message = null; let message = null;
if (count === 1) { if (count === 1) {
@ -81,7 +81,7 @@ const columns = [
{ {
name: 'statistics', name: 'statistics',
label: MailPoet.I18n.t('statistics'), label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled, display: mailpoetTrackingEnabled,
}, },
{ {
name: 'updated_at', name: 'updated_at',
@ -90,7 +90,7 @@ const columns = [
}, },
]; ];
const bulk_actions = [ const bulkActions = [
{ {
name: 'trash', name: 'trash',
label: MailPoet.I18n.t('moveToTrash'), label: MailPoet.I18n.t('moveToTrash'),
@ -98,12 +98,12 @@ const bulk_actions = [
}, },
]; ];
let newsletter_actions = [ let newsletterActions = [
{ {
name: 'view', name: 'view',
link: function (newsletter) { link: function (newsletter) {
return ( return (
<a href={ newsletter.preview_url } target="_blank"> <a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')} {MailPoet.I18n.t('preview')}
</a> </a>
); );
@ -113,7 +113,7 @@ let newsletter_actions = [
name: 'edit', name: 'edit',
link: function (newsletter) { link: function (newsletter) {
return ( return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }> <a href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}>
{MailPoet.I18n.t('edit')} {MailPoet.I18n.t('edit')}
</a> </a>
); );
@ -124,10 +124,11 @@ let newsletter_actions = [
}, },
]; ];
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_welcome_notification_actions', newsletter_actions); Hooks.addFilter('mailpoet_newsletters_listings_welcome_notification_actions', StatisticsMixin.addStatsCTAAction);
newsletterActions = Hooks.applyFilters('mailpoet_newsletters_listings_welcome_notification_actions', newsletterActions);
const NewsletterListWelcome = React.createClass({ const NewsletterListWelcome = React.createClass({
mixins: [ StatisticsMixin, MailerMixin ], mixins: [StatisticsMixin, MailerMixin],
updateStatus: function (e) { updateStatus: function (e) {
// make the event persist so that we can still override the selected value // make the event persist so that we can still override the selected value
// in the ajax callback // in the ajax callback
@ -138,7 +139,7 @@ const NewsletterListWelcome = React.createClass({
endpoint: 'newsletters', endpoint: 'newsletters',
action: 'setStatus', action: 'setStatus',
data: { data: {
id: ~~(e.target.getAttribute('data-id')), id: Number(e.target.getAttribute('data-id')),
status: e.target.value, status: e.target.value,
}, },
}).done((response) => { }).done((response) => {
@ -155,7 +156,7 @@ const NewsletterListWelcome = React.createClass({
}); });
}, },
renderStatus: function (newsletter) { renderStatus: function (newsletter) {
const total_sent = ( const totalSent = (
MailPoet.I18n.t('sentToXSubscribers') MailPoet.I18n.t('sentToXSubscribers')
.replace('%$1d', newsletter.total_sent.toLocaleString()) .replace('%$1d', newsletter.total_sent.toLocaleString())
); );
@ -164,21 +165,22 @@ const NewsletterListWelcome = React.createClass({
<div> <div>
<p> <p>
<select <select
data-id={ newsletter.id } data-id={newsletter.id}
defaultValue={ newsletter.status } defaultValue={newsletter.status}
onChange={ this.updateStatus } onChange={this.updateStatus}
> >
<option value="active">{ MailPoet.I18n.t('active') }</option> <option value="active">{ MailPoet.I18n.t('active') }</option>
<option value="draft">{ MailPoet.I18n.t('inactive') }</option> <option value="draft">{ MailPoet.I18n.t('inactive') }</option>
</select> </select>
</p> </p>
<p>{ total_sent }</p> <p>{ totalSent }</p>
</div> </div>
); );
}, },
renderSettings: function (newsletter) { renderSettings: function (newsletter) {
let sendingEvent; let sendingEvent;
let sendingDelay; let sendingDelay;
let segment;
// set sending event // set sending event
switch (newsletter.options.event) { switch (newsletter.options.event) {
@ -188,16 +190,17 @@ const NewsletterListWelcome = React.createClass({
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserAnyRole'); sendingEvent = MailPoet.I18n.t('welcomeEventWPUserAnyRole');
} else { } else {
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserWithRole').replace( sendingEvent = MailPoet.I18n.t('welcomeEventWPUserWithRole').replace(
'%$1s', mailpoet_roles[newsletter.options.role] '%$1s', mailpoetRoles[newsletter.options.role]
); );
} }
break; break;
case 'segment': default:
// get segment // get segment
const segment = _.find(mailpoet_segments, (segment) => { segment = _.find(
return (~~(segment.id) === ~~(newsletter.options.segment)); mailpoetSegments,
}); seg => (Number(seg.id) === Number(newsletter.options.segment))
);
if (segment === undefined) { if (segment === undefined) {
return ( return (
@ -205,11 +208,11 @@ const NewsletterListWelcome = React.createClass({
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') } { MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span> </span>
); );
} else { }
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace( sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
'%$1s', segment.name '%$1s', segment.name
); );
}
break; break;
} }
@ -234,8 +237,12 @@ const NewsletterListWelcome = React.createClass({
'%$1d', newsletter.options.afterTimeNumber '%$1d', newsletter.options.afterTimeNumber
); );
break; break;
default:
sendingDelay = 'Invalid sending delay';
break;
} }
sendingEvent += ' [' + sendingDelay + ']'; sendingEvent += ` [${sendingDelay}]`;
} }
// add a "period" at the end if we do have a sendingEvent // add a "period" at the end if we do have a sendingEvent
sendingEvent += '.'; sendingEvent += '.';
@ -256,30 +263,30 @@ const NewsletterListWelcome = React.createClass({
return ( return (
<div> <div>
<td className={ rowClasses }> <td className={rowClasses}>
<strong> <strong>
<a <a
className="row-title" className="row-title"
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` } href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}
>{ newsletter.subject }</a> >{ newsletter.subject }</a>
</strong> </strong>
{ actions } { actions }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }> <td className="column" data-colname={MailPoet.I18n.t('status')}>
{ this.renderStatus(newsletter) } { this.renderStatus(newsletter) }
</td> </td>
<td className="column" data-colname={ MailPoet.I18n.t('settings') }> <td className="column" data-colname={MailPoet.I18n.t('settings')}>
{ this.renderSettings(newsletter) } { this.renderSettings(newsletter) }
</td> </td>
{ (mailpoet_tracking_enabled === true) ? ( { (mailpoetTrackingEnabled === true) ? (
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }> <td className="column" data-colname={MailPoet.I18n.t('statistics')}>
{ this.renderStatistics( { this.renderStatistics(
newsletter, newsletter,
newsletter.total_sent > 0 && newsletter.statistics newsletter.total_sent > 0 && newsletter.statistics
) } ) }
</td> </td>
) : null } ) : null }
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }> <td className="column-date" data-colname={MailPoet.I18n.t('lastModifiedOn')}>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr> <abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td> </td>
</div> </div>
@ -289,27 +296,27 @@ const NewsletterListWelcome = React.createClass({
return ( return (
<div> <div>
<h1 className="title"> <h1 className="title">
{ MailPoet.I18n.t('pageTitle') } <Link className="page-title-action" to="/new">{ MailPoet.I18n.t('new') }</Link> { MailPoet.I18n.t('pageTitle') } <Link className="page-title-action" to="/new" data-automation-id="new_email">{ MailPoet.I18n.t('new') }</Link>
</h1> </h1>
<ListingTabs tab="welcome" /> <ListingTabs tab="welcome" />
<Listing <Listing
limit={ window.mailpoet_listing_per_page } limit={window.mailpoet_listing_per_page}
location={ this.props.location } location={this.props.location}
params={ this.props.params } params={this.props.params}
endpoint="newsletters" endpoint="newsletters"
type="welcome" type="welcome"
base_url="welcome" base_url="welcome"
onRenderItem={ this.renderItem } onRenderItem={this.renderItem}
columns={ columns } columns={columns}
bulk_actions={ bulk_actions } bulk_actions={bulkActions}
item_actions={ newsletter_actions } item_actions={newsletterActions}
messages={ messages } messages={messages}
auto_refresh={ true } auto_refresh={true}
sort_by="updated_at" sort_by="updated_at"
sort_order="desc" sort_order="desc"
afterGetItems={ this.checkMailerStatus } afterGetItems={this.checkMailerStatus}
/> />
</div> </div>
); );

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