Compare commits

..

644 Commits
3.0.2 ... 3.6.0

Author SHA1 Message Date
3b4edd5c95 Release 3.6.0 2018-03-20 16:30:01 +02:00
a5782a32bc Merge pull request #1310 from mailpoet/tracked_url_shortcodes
Processes shortcodes in tracked URLs [MAILPOET-1329]
2018-03-20 15:10:34 +02:00
a3cabad752 Merge pull request #1268 from mailpoet/sending_queue_refactoring
Refactor sending queues to use scheduled task and task subscriber tables [MAILPOET-903]
2018-03-20 08:47:26 -04:00
16b2b73727 Merge pull request #1307 from mailpoet/eslint6-4
Eslint6 4 [MAILPOET-1140]
2018-03-20 12:16:11 +01:00
c1f5bf9836 Declare depenency directly
[MAILPOET-1140]
2018-03-20 10:50:56 +00:00
5e49624685 Merge pull request #1306 from mailpoet/poll-update
Update polls [MAILPOET-1327] [MAILPOET-1302]
2018-03-19 17:41:13 +01:00
985e3a9a68 Merge pull request #1303 from mailpoet/automatic-block-defaults
Widgets in designer: save previous settings [MAILPOET-1304]
2018-03-19 12:42:46 +02:00
58f92045c0 Processes shortcodes in tracked URLs 2018-03-16 13:00:34 -04:00
8f9299afe3 Merge pull request #1308 from mailpoet/expiring-message-fix
Make message clearer [MAILPOET-1321]
2018-03-15 16:10:59 +02:00
7775b5ace3 Make message clearer
[MAILPOET-1321]
2018-03-15 13:48:09 +00:00
78f9fea2b0 Sends welcome notifications when subscribing to lists via MP's API
[MAILPOET-1295]
2018-03-15 13:04:30 +00:00
783f33b092 Fix eslint6 object-shorthand
[MAILPOET-1140]
2018-03-15 11:55:57 +00:00
d3250030ba Fix eslint6 func-names
[MAILPOET-1140]
2018-03-15 11:55:03 +00:00
9e685efd41 Fix eslint6 jsx-a11y/alt-text
[MAILPOET-1140]
2018-03-15 11:28:20 +00:00
756367dd50 Fix eslint6 jsx-a11y/no-static-element-interactions
[MAILPOET-1140]
2018-03-15 11:19:48 +00:00
b5a322a8e1 Fix eslint6 jsx-a11y/label-has-for
[MAILPOET-1140]
2018-03-15 10:56:31 +00:00
3f219379d5 Fix eslint6 react/no-string-refs
[MAILPOET-1140]
2018-03-15 10:47:03 +00:00
4037800c10 Fix eslint6 react/prefer-stateless-function
[MAILPOET-1140]
2018-03-15 10:44:13 +00:00
7144d4afa8 Fix eslint6 react/no-did-mount-set-state
[MAILPOET-1140]
2018-03-15 10:28:50 +00:00
658cdd37ac editor: Communication fix removed since bb.radio is in marionette since 3.0 2018-03-15 11:23:55 +01:00
05dcaeba76 editor: Store and load blockDefaults in newsletter data 2018-03-15 11:23:55 +01:00
fbfcfcc85b editor: Save button, divider defaults by its context of usage 2018-03-15 11:23:55 +01:00
8582c19ac0 editor: Update block defaults for ALC changes 2018-03-15 11:23:55 +01:00
5e35a724f0 editor: Fixed posts setting sellection view context and behavior 2018-03-15 11:23:55 +01:00
c7e83377ba editor: Update blockDefaults for posts on change 2018-03-15 11:23:55 +01:00
f6374d0602 editor: Update defaults in social block and use stored defaults for new social block 2018-03-15 11:23:54 +01:00
1df25aef76 editor: Disable update blockDefaults for texts and images 2018-03-15 11:23:54 +01:00
3a8b64e11c editor: Omit text property from block defaults for header and footer 2018-03-15 11:23:54 +01:00
1d9b4839f7 editor: Update config blockDefaults by latest used value per type 2018-03-15 11:23:54 +01:00
1c5a0bb5bd editor: Unified default config styles basic, post and ALC buttons 2018-03-15 11:23:54 +01:00
eca7de854d Fix eslint6 react/no-string-refs
[MAILPOET-1140]
2018-03-15 09:56:27 +00:00
0adaf22eef Fix eslint6 react/self-closing-bracket-location
[MAILPOET-1140]
2018-03-15 09:32:48 +00:00
c39aeb6283 Fix eslint6 react/self-closing-comp
[MAILPOET-1140]
2018-03-15 09:31:41 +00:00
7528aba2ef Update poll
[MAILPOET-1327]
2018-03-15 08:57:25 +00:00
c3fff8ddbe Update poll for new users
[MAILPOET-1302]
2018-03-15 08:52:44 +00:00
4177701044 Merge pull request #1282 from mailpoet/screenshot
fixing the screenshot issue [MAILPOET-1276]
2018-03-14 12:52:29 +02:00
65a201c848 Bumps up release version and updates changelog 2018-03-13 20:16:03 -04:00
1c4b6a9ab6 Merge pull request #1304 from mailpoet/segment-admin-fix
Not pass undefined as default value for select [PREMIUM-72]
2018-03-13 18:17:09 -04:00
9173b114a7 Merge pull request #1301 from mailpoet/call-for-rating
Call for rating [MAILPOET-1299]
2018-03-13 19:21:39 +01:00
cd6828d19e Not pass undefined as default value for select
[PREMIUM-72]
2018-03-13 15:52:00 +00:00
1c021ac0a0 fixing chrome conflict 2018-03-13 16:09:39 +01:00
4469cef647 Fixes cast statement format 2018-03-13 11:28:25 +00:00
057f0376a5 Adds unit test 2018-03-13 11:28:25 +00:00
7e6b29896f Removes type check given that Newsletter model object is not persisted 2018-03-13 11:28:25 +00:00
4c4f6be59b Passes object as value instead of reference 2018-03-13 11:28:25 +00:00
669d072853 Not show call for rating if user already clicked
[MAILPOET-1299]
2018-03-12 11:36:19 +00:00
1033c1c1cf fixing width by removing scrollbar 2018-03-12 12:14:10 +01:00
330067c3f2 Save user clicked on rating button
[MAILPOET-1299]
2018-03-12 11:14:10 +00:00
9a942a0bd9 Merge pull request #1299 from mailpoet/sync-segments-collation-error
Sync segments collation error
2018-03-11 20:28:54 -04:00
d079002b09 Subscribers import: Step1 also uses the email regexp to validate emails [MAILPOET-1288] 2018-03-08 11:44:07 +01:00
77248e6890 Ask for rating
[MAILPOET-1299]
2018-03-07 13:27:21 +00:00
755179e0d1 segments/migrate: Avoiding collation error in subscribers sync MAILPOET-1288 2018-03-06 21:17:16 +01:00
46493b991c Checking invalid emails on the input [MAILPOET-1288] 2018-03-06 19:54:44 +01:00
2f05eaf528 Release 3.5.0 2018-03-06 14:36:30 +00:00
f7397d04ed Merge pull request #1298 from mailpoet/revert-1290-invalid-email-MAILPOET-1288
Revert "Checking invalid emails on the input [MAILPOET-1288]"
2018-03-06 15:47:50 +02:00
3d43970fdc Revert "Checking invalid emails on the input [MAILPOET-1288]" 2018-03-06 13:24:42 +00:00
92864c2d4f Merge pull request #1296 from mailpoet/stats_badges_css_fix
Fixes word-wrapping of stats badges [MAILPOET-1323]
2018-03-06 14:57:43 +02:00
41e028c0d4 Merge pull request #1297 from mailpoet/polyfill
adding polyfill for `mb_detect_order` [MAILPOET-1303]
2018-03-06 07:52:46 -05:00
cfbf29b60a Merge pull request #1290 from mailpoet/invalid-email-MAILPOET-1288
Checking invalid emails on the input [MAILPOET-1288]
2018-03-06 07:50:21 -05:00
a5137c8d62 adding polyfill for mb_detect_order 2018-03-06 12:15:18 +01:00
aceb9bb031 Hardened UI email address check MAILPOET-1288 2018-03-06 09:51:41 +01:00
8bc0ad48c0 Email valiation added to reply-to on newsletter send screen 2018-03-06 09:50:55 +01:00
449568b37b Refactor JS email regexp to be accessible from every page MAILPOET-1288 2018-03-06 09:48:39 +01:00
c164066522 Check valid filled email addresses in plugin settings MAILPOET-1288 2018-03-06 09:15:48 +01:00
5c10a66444 Subscribers import: Email validation unified with subscribers sync MAILPOET-1288 2018-03-06 09:15:48 +01:00
afed408297 Synchronize segments removes updated/inserted subscribers with invalid emails MAILPOET-1288 2018-03-06 09:15:37 +01:00
6e7c7ae8c8 modelValidator: Add more strict Sudzy email validation next to is_email check MAILPOET-1288 2018-03-06 09:06:57 +01:00
d3f9fb5f06 fixing url 2018-03-05 18:51:52 +01:00
53b4a7ba31 Isolates white-space rule to badges displayed in boxes 2018-03-05 12:50:51 -05:00
39212a30e8 Merge pull request #1293 from mailpoet/redirect-to-404-403-MAILPOET-782
Show WP's 404/403 page instead of displaying a blank page [MAILPOET-782]
2018-03-05 11:16:43 -05:00
7c7e5bbaa6 Merge pull request #1292 from mailpoet/fixed-bulk-add-subscribers-created-at-MAILPOET-1311
Fixed created_at in bulk add action in subscribers [MAILPOET-1311]
2018-03-05 14:34:48 +01:00
831804d35e Fix eslint no-useless-escape in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
9af98f0afb Fix eslint no-empty in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
5b96789e04 Fix eslint no-console in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
3b2355d570 Fix eslint no-redeclare in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
fa44127efa Fix eslint no-new in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
86e01ce5de Fix eslint new-cap in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
981e4a56e3 Fix eslint new-cap in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
d89eb07b3c Fix eslint array-callback-return in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
e4b79616ee Fix eslint no-sequences in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
21e45a39b4 Fix eslint no-sequences in es5 files
[MAILPOET-1146]
2018-03-05 10:41:31 +01:00
b7e492e20e endpoints/track: Exit with 403 code and eventually display 403 page when subscriber token doesn't match [MAILPOET-782] 2018-03-03 16:46:01 +01:00
c2c74d7524 Show default WP 404 when aborting link with 404 code [MAILPOET-782] 2018-03-03 16:45:41 +01:00
4d80bdb322 Merge pull request #1287 from mailpoet/dynamic-segments-bulk
Handle bulk operations on subscribers filtered by dynamic segments [PREMIUM-69]
2018-03-02 16:59:33 +02:00
a58a3c9574 Fixed created_at date in bulk add action in subscribers list [MAILPOET-1311] 2018-03-02 13:52:29 +01:00
734d1ded1f Check processed subscribers for old and new queues differently [MAILPOET-903] 2018-03-01 21:17:51 +03:00
e0f989f6a8 Revert "Migrate subscribers for all tasks types [MAILPOET-903]"
This reverts commit f1f69c9835.
2018-03-01 21:17:40 +03:00
2265de454e Updates KB link and tip language 2018-03-01 08:50:14 +00:00
381fc76d52 Adds description with KB link 2018-03-01 08:50:14 +00:00
32fdc11c88 Adds glabal status to default export fields 2018-03-01 08:50:14 +00:00
99edc35b08 Handle bulk operations on subscribers filtered by dynamic segments
[PREMIUM-69]
2018-02-28 17:12:56 +00:00
66fa22d489 Merge pull request #1285 from mailpoet/am_display_events_chunk2
Makes necessary changes to enable the display of automatic emails [PREMIUM-65] [PREMIUM-63]]
2018-02-28 18:21:36 +02:00
f1f69c9835 Migrate subscribers for all tasks types [MAILPOET-903] 2018-02-28 11:21:23 +03:00
a6f8953bd3 Make migration mailer notice clearer [MAILPOET-903] 2018-02-28 10:26:29 +03:00
8b25acf82c Updates component's name 2018-02-27 16:43:41 -05:00
a9b40802a9 Uses 'slug' instead of 'id' for newsletter types 2018-02-27 16:43:41 -05:00
9eda024b02 release 3.4.4 2018-02-27 17:28:52 +00:00
003ddc3b92 Merge pull request #1281 from mailpoet/fix-mixpanel-segments
Fix analytics reporting on segments [MAILPOET-1279]
2018-02-27 10:44:29 +01:00
b381923903 Add a comment about adding large batches of subscribers [MAILPOET-903] 2018-02-27 11:39:24 +03:00
8837c9e14c Merge pull request #1284 from mailpoet/default-selection
Make sure preselected value is active [PREMIUM-59]
2018-02-26 16:48:41 +02:00
8cd83575f2 Make sure preselected value is active
[PREMIUM-59]
2018-02-26 14:04:42 +00:00
03015c0bde Fix code review syntax remarks [MAILPOET-903] 2018-02-26 16:30:53 +03:00
6b36156db5 Merge pull request #1283 from mailpoet/subscriber-to-list-migrate-fix-MAILPOET-1285
MP2Migration: Fixed null updated_at error in importSubscriberSegment [MAILPOET-1285]
2018-02-26 14:31:54 +02:00
4ed39c9620 Code fix
[MAILPOET-1279]
2018-02-26 09:36:55 +00:00
f210b15652 MP2Migration: Fixed null updated_at error by fallback to created_at in importSubscriberSegment MAILPOET-1285 2018-02-23 20:22:21 +01:00
ef5d777fac fixing tests 2018-02-23 15:23:37 +01:00
ab2b380f2d fixing the screenshot issue 2018-02-22 17:27:49 +01:00
c3f974a3c0 Make code compatible with 5.3 :(
[MAILPOET-1279]
2018-02-21 14:17:27 +00:00
2c020bf283 Fix analytics reporting on segments
[MAILPOET-1279]
2018-02-21 13:30:29 +00:00
17e13c0956 Merge pull request #1276 from mailpoet/post-labels-fix
Display only terms with labels [MAILPOET-1292]
2018-02-20 20:19:30 -05:00
52a6c4f701 Merge pull request #1277 from mailpoet/eslint-es5-3
Eslint es5 3  [MAILPOET-1145]
2018-02-20 17:27:33 +01:00
f09ad47911 bind methods in the constructor 2018-02-20 13:11:45 +00:00
0da2be21c8 fix code style issues 2018-02-20 13:11:45 +00:00
8603e2a96a seperating components into multiple files 2018-02-20 13:11:45 +00:00
ddbabcfcb5 don't show "No items" when loading 2018-02-20 13:11:45 +00:00
92f733d30d rename function to getEditorUrl 2018-02-20 13:11:45 +00:00
93a67e6882 make callbacks props consistent and generic 2018-02-20 13:11:45 +00:00
86aff26d5f fixing code quality 2018-02-20 13:11:45 +00:00
cfc425dfd7 sort templates by id desc 2018-02-20 13:11:45 +00:00
57b2c577c6 add imported templates to "Your saved templates"
select "Your saved templates" after importing a template
removing template from all categories when deleted
2018-02-20 13:11:45 +00:00
be4224411f refactoring the code and move import form to separate tab 2018-02-20 13:11:45 +00:00
b2fa79fcbf Release 3.4.3 2018-02-20 13:48:59 +02:00
1d3102e54b Merge pull request #1278 from mailpoet/new-poll
New poll [MAILPOET-1300]
2018-02-20 12:59:56 +02:00
847916eb1f New poll
[MAILPOET-1300]
2018-02-20 10:44:39 +00:00
adb579a26c Fix code issues
[MAILPOET-1145]
2018-02-20 09:39:14 +00:00
16607dd4be Merge pull request #1270 from mailpoet/woo-category
Allow select2 to be destroyed and re-rendered [PREMIUM-60]
2018-02-19 17:56:10 +01:00
485c6008c1 Merge pull request #1274 from mailpoet/segment-style
Update forms errors style [MAILPOET-1278]
2018-02-19 17:53:22 +01:00
8f9a0239b1 Allow select2 to be destroyed and re-rendered
[PREMIUM-60]
2018-02-19 16:19:13 +00:00
989baf2aa4 Fix eslint wrap-iife in es5 files
[MAILPOET-1145]
2018-02-19 16:09:27 +00:00
de91c90ac2 Fix eslint no-plusplus in es5 files
[MAILPOET-1145]
2018-02-19 16:08:29 +00:00
e10901d939 Fix eslint default-case in es5 files
[MAILPOET-1145]
2018-02-19 15:58:25 +00:00
426a72c557 Fix eslint no-lonely-if in es5 files
[MAILPOET-1145]
2018-02-19 15:54:19 +00:00
704e4e40f7 Fix eslint no-mixed-operators in es5 files
[MAILPOET-1145]
2018-02-19 15:51:01 +00:00
0ff9c006d8 Fix eslint eqeqeq in es5 files
[MAILPOET-1145]
2018-02-19 15:43:58 +00:00
f7fcec7953 Fix eslint max-len in es5 files
[MAILPOET-1145]
2018-02-19 15:17:57 +00:00
e484705ac1 Fix eslint errors and warnings
[MAILPOET-1145]
2018-02-19 13:40:01 +00:00
6a8337c67d Fix eslint errors and warnings
[MAILPOET-1145]
2018-02-19 13:23:09 +00:00
f98e58eb99 Fix eslint no-extra-bind
[MAILPOET-1145]
2018-02-19 13:05:43 +00:00
476af99130 Display only terms with labels
[MAILPOET-1292]
2018-02-19 11:59:41 +00:00
0d69b05ac0 Removes option to group by lists during export 2018-02-19 09:53:34 +00:00
e0052bbb93 Sanitize correct cron ping response [MAILPOET-1291] 2018-02-16 18:06:41 +00:00
b5b0a02ea4 Removes option to export only confirmed subscribers 2018-02-16 14:46:34 +00:00
3c43c04586 Imports all new subscribers with subscribed status as default 2018-02-16 14:46:34 +00:00
e87134eca4 Adds IP address field to export 2018-02-16 14:46:34 +00:00
c8f334d782 Prevents overwriting global subscription status 2018-02-16 14:46:34 +00:00
7cf63965a0 Adds distinction between global and list status
Exports subscribers with any list status
Limits "confirmed only" option to global status
2018-02-16 14:46:34 +00:00
eb3cf66958 Update forms errors style
[MAILPOET-1278]
2018-02-16 13:46:08 +00:00
ba306ecf66 Merge pull request #1271 from mailpoet/tooltip-fix
Update incorrect tooltip [MAILPOET-1274]
2018-02-15 18:30:38 +01:00
a58151fee6 Update incorrect tooltip
[MAILPOET-1274]
2018-02-15 14:50:42 +00:00
9eb20f96b2 Merge pull request #1263 from mailpoet/am_display_events_chunk2
Adds necessary changes in order to display automatic events in Free and Premium [PREMIUM-65] [PREMIUM-63]
2018-02-14 12:41:06 -05:00
57f00bbe0c Merge pull request #1267 from mailpoet/MAILPOET-1263-force-alc-for-notifications
[MAILPOET-1263] Force user to create ALC block for post notification
2018-02-14 19:28:19 +02:00
287c89749d Removes WooCommerce events until we're ready to release them 2018-02-14 12:20:32 -05:00
b9546b61a8 Fix acceptance tests, update counters [MAILPOET-903] 2018-02-14 19:57:14 +03:00
606177baaf Fix daemon ping timeout in tests [MAILPOET-903] 2018-02-14 01:29:14 +03:00
b7704c90e8 [MAILPOET-1263] Fixed inconsistency in type of body property on newsletter json after saving and little bit more strict validation check 2018-02-13 23:01:34 +01:00
9d4f996a6a [MAILPOET-1263] Force user to create ALC block for post notification 2018-02-13 23:01:29 +01:00
774e8d94b5 Displays automatic email types and events
Adds WooCommerce email type for testing purposes
2018-02-13 14:43:19 -05:00
4ffadc990e Exposes components necessary for displaying events' scheduling options 2018-02-13 14:41:30 -05:00
8ab2cd75c0 Prevents line breaks when badge has multiple words 2018-02-13 14:41:30 -05:00
d59ebbbc57 Allows dynamically changing breadcrumbs 2018-02-13 14:41:30 -05:00
84638b2818 Passes element id to the onChange callback 2018-02-13 14:41:30 -05:00
a8305adf8d Allows setting custom id and class 2018-02-13 14:41:30 -05:00
243ed9d61b Prevents error when item prop is not set
Allows setting custom value and defaultValue
2018-02-13 14:41:30 -05:00
84dfa88a1a Don't load subscriber IDs in memory when preparing tasks from static segments [MAILPOET-903] 2018-02-13 20:36:01 +03:00
f9204f289f Updates changelog and bumps up version to 3.4.2 2018-02-13 12:33:19 -05:00
408b64dc0d Merge pull request #1264 from mailpoet/preview-link
fixing the preview link issue with https [MAILPOET-1296]
2018-02-13 17:01:01 +03:00
83b24f053e Fix spelling in a string [MAILPOET-1296] 2018-02-13 16:35:12 +03:00
9acef55352 fixing the preview link issue with https 2018-02-13 09:56:46 +00:00
4c0f5bb456 Add new unit tests [MAILPOET-903] 2018-02-13 11:39:50 +03:00
be6647d763 Fix unit tests [MAILPOET-903] 2018-02-13 11:39:50 +03:00
99732ac14d Remove beta migrations [MAILPOET-903] 2018-02-13 11:39:50 +03:00
0b17a1099b Bring back a check from MAILPOET-1261 (was omitted during rebase) 2018-02-13 11:39:50 +03:00
c0c57f6b67 Sending queue refactoring WIP [MAILPOET-903] 2018-02-13 11:39:50 +03:00
bf8b0c81df Merge pull request #1262 from mailpoet/recently-sent-templates
limitting the recently sent templates to 12 [MAILPOET-1265]
2018-02-13 11:28:42 +03:00
d82e3a25ac Merge pull request #1260 from mailpoet/get-terms-filter
Use filter for getTerms search [MAILPOET-1293]
2018-02-13 03:03:39 +03:00
98ed295896 Merge pull request #1261 from mailpoet/refactoring-models
refactoring createOrUpdate method of models [MAILPOET-1294]
2018-02-12 13:59:26 +02:00
b076e03a75 using static instead of self
renaming `internalCreateOrUpdate` to `_createOrUpdate`
renaming `$conditions` to `$keys`
adding block comment for `_createOrUpdate` method.
2018-02-12 11:15:46 +00:00
a754cfaa52 limitting the recently sent templates to 12 2018-02-09 16:43:40 +00:00
3721694f4d Merge pull request #1259 from mailpoet/empty-trash-msg
"Empty Trash" now shows the correct notice message [MAILPOET-1289]
2018-02-09 09:36:41 -05:00
91622f8cd9 refactoring createOrUpdate method of models 2018-02-07 18:10:49 +00:00
4a3ba73406 Use filter for getTerms search
[MAILPOET-1293]
2018-02-07 12:58:00 +00:00
ab3a2f064f "Empty Trash" now shows the correct notice message 2018-02-06 14:35:37 +00:00
a2270d6689 Release 3.4.1 2018-02-06 13:23:16 +00:00
e09acbe649 Merge pull request #1258 from mailpoet/stats-segmentation
Return error code for duplication errors [PREMIUM-42]
2018-02-05 21:10:54 -05:00
b54f002635 Merge pull request #1254 from mailpoet/am_display_events_chunk1
Makes necessary changes for automatic emails feature to work [PREMIUM-63]
2018-02-05 17:48:23 +02:00
aaaf49d071 Merge pull request #1256 from mailpoet/bugfix
fixing Segment::bulkDelete not deleting subscribers assiciations [MAILPOET-1287]
2018-02-05 17:53:04 +03:00
8ff28fd974 Return error code for duplication errors
[PREMIUM-42]
2018-02-05 14:34:15 +00:00
126b37f2bb Merge pull request #1257 from mailpoet/scheduled_emails_to_dynamic_segments_fix
Fix sending of scheduled standard emails to dynamic segments [MAILPOET-1286]
2018-02-05 11:25:30 +01:00
bebd835790 Merge pull request #1253 from costasovo/jsdom-upgrade
js-tests: jsdom upgrade
2018-02-05 10:56:16 +01:00
9526a6b470 Fix sending of scheduled standard email to dynamic segments [MAILPOET-1286] 2018-02-02 19:19:49 +03:00
7eb8ff756b fixing Subscriber::bulkDelete not deleting subscribers assiciations 2018-02-02 12:43:34 +00:00
9159e8d21d Merge pull request #1249 from mailpoet/stats-segmentation
Stats segmentation [PREMIUM-42]
2018-02-01 20:34:39 -05:00
f4ba45490d Corrects identation 2018-02-01 18:27:57 -05:00
9f9b44fd7e Adds a method that cleans up after Select2 2018-02-01 18:26:30 -05:00
ae9f373cff Removes state and updates method to test if Select2 is initialized 2018-02-01 17:53:16 -05:00
80a3bbd030 Changes method name to get selection items 2018-02-01 17:46:26 -05:00
aba75849e1 Standardizes placeholder and border colors for Select2 elements 2018-02-01 08:59:12 -05:00
874a37303a Exposes Selection form field 2018-02-01 08:59:12 -05:00
f467479438 Allows overriding Select2 options 2018-02-01 08:59:12 -05:00
bb76a8c6aa Cleans up DOM elements manually added by Select2 and not tracked by
React
2018-02-01 08:59:12 -05:00
63000c48c4 Allows reinitializing Select2 upon component rerender 2018-02-01 08:59:12 -05:00
b88dec06d9 Adds getter for field ID 2018-02-01 08:59:11 -05:00
a0667adace Prevents component from rerendering when Select2 is initialized 2018-02-01 08:59:11 -05:00
fef8017134 Loads select items before render 2018-02-01 08:59:11 -05:00
87de314c18 Adds option to query remote source for data 2018-02-01 08:59:11 -05:00
c8a20ec4ae Merge pull request #1252 from mailpoet/templates
filtering unknown templates categories [MAILPOET-1281]
2018-02-01 14:44:19 +02:00
9739159a6f Merge pull request #1251 from mailpoet/test
Fix acceptance tests flakiness [MAILPOET-1284]
2018-02-01 14:31:41 +02:00
0aa4c37ad3 js-tests: jsdom upgrade 2018-01-31 23:44:12 +01:00
2c8c8b3d0e filtering unknown templates categories 2018-01-31 16:04:28 +00:00
12434b412f Add code comment 2018-01-31 10:30:33 +00:00
789c2a7d4e Wait before login 2018-01-31 09:41:15 +00:00
907356bb0f Wait for login field to be loaded 2018-01-31 09:15:20 +00:00
8800799234 Bump up release version to 3.4.0 2018-01-30 22:21:01 +03:00
9c2aa569ea After login ensure MailPoet present on the page 2018-01-30 15:39:30 +00:00
9ee6920ebb Use our login function 2018-01-30 15:03:03 +00:00
5d8d9567c7 Merge pull request #1234 from mailpoet/tempales-categories
Add template categorization [MAILPOET-1159]
2018-01-30 16:20:01 +02:00
5bddc79576 generating thumb for all newsletter types 2018-01-30 13:51:03 +00:00
e2f23df829 close loading modal 2018-01-30 13:26:51 +00:00
43fbbca050 moving width to css 2018-01-30 12:49:51 +00:00
5109bc3922 Merge pull request #1250 from mailpoet/new-poll
Add a new poll [MAILPOET-1275]
2018-01-30 13:01:58 +02:00
65b4aeffc8 Add a new poll
[MAILPOET-1275]
2018-01-30 10:38:28 +00:00
17a25120d4 Add conflict HTTP code
[PREMIUM-42]
2018-01-30 10:16:12 +00:00
cb055a2b06 Allow extra action buttons in filter rows
[PREMIUM-42]
2018-01-29 15:37:52 +00:00
7361613bba minor fixes 2018-01-29 15:30:17 +00:00
a8f33d9a7e Merge pull request #1248 from mailpoet/update-name
Update plugin name [MAILPOET-1272]
2018-01-26 11:44:52 +03:00
c497a72aab Update plugin name
[MAILPOET-1272]
2018-01-25 15:48:11 +00:00
d0f55a1d1b Merge pull request #1247 from mailpoet/revert-dotdeb
Revert "Use alternative dotdeb mirror" [MAILPOET-1262]
2018-01-25 11:26:50 +03:00
3b92caf677 Merge pull request #1246 from mailpoet/segment-sending-fix
Fix segments sending [MAILPOET-1270]
2018-01-24 22:21:32 -05:00
d3c64810a8 Merge pull request #1243 from mailpoet/subscribers_strict_mode_fix
Fix a fatal error when creating a subscribers table in MySQL strict mode [MAILPOET-1268]
2018-01-24 22:19:55 -05:00
f85a169248 Revert "Use alternative dotdeb mirror"
This reverts commit a4af2b9c65.
2018-01-24 16:22:36 +00:00
2dd08c6820 Fix segments sending
[MAILPOET-1270]
2018-01-24 15:30:52 +00:00
d7609f48d4 Merge pull request #1244 from mailpoet/import
Normalizing emails during the import to avoid integrity constraint violation [MAILPOET-1271]
2018-01-23 19:35:45 +03:00
1d02c688a2 releasing 3.3.6 2018-01-23 15:16:36 +00:00
37c58c6aa4 Add a comment about testing normalization [MAILPOET-1271] 2018-01-23 18:08:13 +03:00
4abd034880 normalizing emails in lowercase 2018-01-23 13:46:10 +00:00
e8c7584b9b Fix a fatal error when creating a subscribers table in MySQL strict mode [MAILPOET-1268] 2018-01-23 15:59:20 +03: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
20d0ed1aac fixing js tests 2018-01-22 11:36:46 +00:00
f42b61a49e fixing unit test 2018-01-22 11:03:32 +00:00
5e152e920e fix screenshots 2018-01-22 11:03:31 +00:00
0d26b62416 minor fixes 2018-01-22 11:03:31 +00:00
8e60b2b317 renaming and modifying categories 2018-01-22 11:01:20 +00:00
cae46d9acd fixed duplicated templates when scheduling/sending same newsletter twice 2018-01-22 11:01:19 +00:00
bfad5509c1 fixed acceptance test 2018-01-22 11:01:19 +00:00
3ae7b436de returning preview url in meta 2018-01-22 11:01:19 +00:00
30b2eda29f fix eslint errors 2018-01-22 11:01:19 +00:00
a519eff231 saving recently sent templates 2018-01-22 11:01:19 +00:00
fe2ab08a49 adding template to corresponding categories when saved 2018-01-22 11:01:18 +00:00
9b914a471b adding templates categories to the display 2018-01-22 11:01:18 +00:00
74f008517b adding categories to templates 2018-01-22 11:01:18 +00: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
26241afb86 Add image alignment option to newsletter editor [MAILPOET-1124] 2017-10-02 15:30:43 +03:00
362 changed files with 20773 additions and 6909 deletions

View File

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

View File

@ -1,6 +1,6 @@
version: 2
jobs:
qa_js_php5:
qa_js_security_php5:
working_directory: /home/circleci/mailpoet
docker:
- image: circleci/php:5.6.30-apache-browsers
@ -38,6 +38,10 @@ jobs:
command: |
mkdir test-results/mocha
./do t:j test-results/mocha/junit.xml
- run:
name: "Composer security check"
command: |
./do s:composer
- run:
name: "PHP Unit tests"
command: |
@ -81,6 +85,7 @@ jobs:
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:
@ -94,9 +99,52 @@ jobs:
command: |
docker-compose run codeception --steps --debug -vvv --html --xml
- store_artifacts:
path: ~/mailpoet/tests/acceptance-tests/_output
path: tests/_output
- 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:
working_directory: /home/circleci/mailpoet
docker:
@ -119,7 +167,38 @@ jobs:
- run:
name: "PHP Unit tests"
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:
path: tests/_output
- store_artifacts:
@ -132,6 +211,8 @@ workflows:
version: 2
build_and_test:
jobs:
- qa_js_php5
- qa_js_security_php5
- php7
- php7_multisite
- acceptance_tests
- acceptance_tests_multisite

View File

@ -2,11 +2,16 @@
function setup {
local version=$1
local wp_cli_wordpress_path="--path=wordpress"
local wp_cli_allow_root="--allow-root"
# 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-src http://packages.dotdeb.org jessie all" | sudo tee -a /etc/apt/sources.list.d/dotdeb.list
wget -qO - http://www.dotdeb.org/dotdeb.gpg | sudo apt-key add -
# 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 install mysql-client php7.0-mysql zlib1g-dev
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 docker-php-ext-install mysql mysqli pdo pdo_mysql zip
fi
# Add a fake sendmail mailer
sudo cp ./.circleci/fake-sendmail.php /usr/local/bin/
# configure Apache
sudo cp ./.circleci/mailpoet_php.ini /usr/local/etc/php/conf.d/
sudo cp ./.circleci/apache/mailpoet.loc.conf /etc/apache2/sites-available
sudo a2dissite 000-default.conf
sudo a2ensite mailpoet.loc
sudo a2enmod rewrite
sudo service apache2 restart
# Install NodeJS+NPM
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install nodejs build-essential
# install plugin dependencies
curl -sS https://getcomposer.org/installer | php
./composer.phar install
./do install
# Set up Wordpress
# Set up WordPress
mysql -h 127.0.0.1 -u root -e "create database wordpress"
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
./wp-cli.phar core download --allow-root --path=wordpress
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
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
./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
ln -s ../../.. wordpress/wp-content/plugins/mailpoet
./wp-cli.phar plugin activate mailpoet --path=wordpress
# Create .env file with correct path to WP installation
# TODO: Remove this line after PR gets merged and CircleCI env variables change
echo "WP_TEST_PATH=\"/home/circleci/mailpoet/wordpress\"" > .env
}
# Activate plugin
if [[ $version == "php7_multisite" ]]; then
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_MULTISITE="/var/www/wordpress"
WP_TEST_MULTISITE_SLUG=""
WP_TEST_ENABLE_NETWORK_TESTS="true"
WP_TEST_IMPORT_MAILCHIMP_API=""
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
@ -16,4 +18,5 @@ WP_TEST_MAILER_SMTP_LOGIN=""
WP_TEST_MAILER_SMTP_PASSWORD=""
WP_SVN_USERNAME=""
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,55 +8,19 @@
"ecmaVersion": 5
},
"rules": {
// Exceptions
"no-underscore-dangle": 0, // Backbone uses underscores, we cannot remove them
// Temporary
"import/no-amd": 0,
"prefer-arrow-callback": 0,
"radix": 0,
"no-alert": 0,
"block-scoped-var": 0,
"guard-for-in": 0,
"no-prototype-builtins": 0,
"no-restricted-syntax": 0,
"no-useless-concat": 0,
"no-nested-ternary": 0,
"no-sequences": 0,
"no-useless-return": 0,
"array-callback-return": 0,
"new-cap": 0,
"no-continue": 0,
"no-new": 0,
"no-redeclare": 0,
"no-console": 0,
"no-empty": 0,
"no-useless-escape": 0,
"wrap-iife": 0,
"no-plusplus": 0,
"default-case": 0,
"no-lonely-if": 0,
"no-mixed-operators": 0,
"eqeqeq": 0,
"max-len": 0,
"global-require": 0,
"no-throw-literal": 0,
"no-extra-bind": 0,
"one-var-declaration-per-line": 0,
"consistent-return": 0,
"no-shadow": 0,
"no-underscore-dangle": 0,
"brace-style": 0,
"no-else-return": 0,
"no-use-before-define": 0,
"one-var": 0,
"camelcase": 0,
"padded-blocks": 0,
"strict": 0,
"vars-on-top": 0,
"no-var": 0,
"no-unused-vars": 0,
"object-shorthand": 0,
"new-parens": 0,
"eol-last": 0,
"dot-notation": 0,
"prefer-template": 0,
"func-names": 0
"space-infix-ops": 0
}
}

View File

@ -14,7 +14,11 @@
"import/resolver": "webpack"
},
"rules": {
// Exceptions
"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,
"react/no-multi-comp": 0,
"react/sort-comp": 0,
@ -27,45 +31,8 @@
"react/jsx-boolean-value": 0,
"react/jsx-no-bind": 0,
"react/no-array-index-key": 0,
"react/self-closing-comp": 0,
"react/jsx-closing-bracket-location": 0,
"react/no-string-refs": 0,
"react/no-did-mount-set-state": 0,
"react/prefer-stateless-function": 0,
"jsx-a11y/label-has-for": 0,
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/alt-text": 0,
"func-names": 0,
"object-shorthand": 0,
"no-bitwise": 0,
"arrow-body-style": 0,
"prefer-template": 0,
"default-case": 0,
"array-callback-return": 0,
"consistent-return": 0,
"import/extensions": 0,
"import/no-extraneous-dependencies": 0,
"camelcase": 0,
"eqeqeq": 0,
"no-lonely-if": 0,
"block-scoped-var": 0,
"no-extra-bind": 0,
"class-methods-use-this": 0,
"no-case-declarations": 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,
"padded-blocks": 0,
"no-underscore-dangle": 0
"space-unary-ops": 0,
"space-infix-ops": 0,
"no-irregular-whitespace": 0
}
}

View File

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

4
.gitignore vendored
View File

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

View File

@ -153,17 +153,30 @@ class RoboFile extends \Robo\Tasks {
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();
$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']) {
$command .= ' --xml';
}
return $this->_exec($command);
}
function testMultisiteUnit($opts=['file' => null, 'xml' => false, 'multisite' => true]) {
return $this->testUnit($opts);
}
function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$command = join(' ', array(
@ -198,6 +211,10 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec($command);
}
function securityComposer() {
return $this->_exec('vendor/bin/security-checker security:check --format=simple');
}
function testDebug($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
@ -214,6 +231,14 @@ class RoboFile extends \Robo\Tasks {
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() {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
@ -289,15 +314,16 @@ class RoboFile extends \Robo\Tasks {
}
function svnPublish($opts = ['force' => false]) {
$this->loadWPFunctions();
$this->loadEnv();
$svn_dir = ".mp_svn";
$plugin_data = get_plugin_data('mailpoet.php', false, false);
$plugin_version = $plugin_data['Version'];
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
$plugin_dist_name = explode('-', $plugin_dist_name);
$plugin_dist_name = $plugin_dist_name[0];
$plugin_version = $this->getPluginVersion('mailpoet.php');
$plugin_dist_name = 'mailpoet';
$plugin_dist_file = $plugin_dist_name . '.zip';
if(!$plugin_version) {
throw new \Exception('Could not parse plugin version, check the plugin header');
}
$this->say('Publishing version: ' . $plugin_version);
// Sanity checks
@ -416,13 +442,9 @@ class RoboFile extends \Robo\Tasks {
$dotenv->load();
}
protected function loadWPFunctions() {
$this->loadEnv();
define('ABSPATH', getenv('WP_TEST_PATH') . '/');
define('WPINC', 'wp-includes');
require_once(ABSPATH . WPINC . '/functions.php');
require_once(ABSPATH . WPINC . '/formatting.php');
require_once(ABSPATH . WPINC . '/plugin.php');
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
protected function getPluginVersion($file) {
$data = file_get_contents($file);
preg_match('/^[ \t*]*Version:(.*)$/mi', $data, $m);
return !empty($m[1]) ? trim($m[1]) : false;
}
}

View File

@ -13,3 +13,6 @@ Style for Members plugin
width: 20px;
font-size: 20px;
margin-right: 3px;
#wpbody
padding-bottom: 20px;

View File

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

View File

@ -71,15 +71,17 @@ $box-description-font-size = $box-description-line-height
.mailpoet_boxes .mailpoet_description
float:left
width: 245px
width: 258px
max-height: $box-description-height
padding-bottom: 0
overflow: hidden
word-wrap: break-word
overflow-wrap: break-word
h3
margin: 0 0 $box-description-space-between-heading-and-paragraph 0
overflow: hidden
max-width: 210px
max-width: 223px
line-height: $box-heading-line-height
font-size: $box-heading-font-size
@ -117,6 +119,21 @@ $box-description-font-size = $box-description-line-height
[data-type="standard"] .mailpoet_thumbnail
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20viewBox%3D%220%200%2070%2070%22%20enable-background%3D%22new%200%200%2070%2070%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23ffffff%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M62.751%2C65.368L43.923%2C50.955l18.832-14.377l-5.395-4.118v-3.479l5.983%2C4.568%20v3.479l0.166%2C26.269C63.51%2C64.089%2C63.198%2C64.783%2C62.751%2C65.368z%20M31.433%2C49.5l-15.955-12v-30c0-1.657%2C1.339-3%2C2.992-3h33.905%20c1.652%2C0%2C2.992%2C1.343%2C2.992%2C3v30l-15.955%2C12H31.433z%20M18.469%2C35.5H33.5v-2H18.469V35.5z%20M18.469%2C31.5H33.5v-2H18.469V31.5z%20M18.469%2C27.5H33.5v-2H18.469V27.5z%20M38.406%2C7.5H18.469v14h19.937V7.5z%20M52.375%2C7.5H40.408v2h11.967V7.5z%20M52.375%2C11.5H40.408v2%20h11.967V11.5z%20M52.375%2C15.5H40.408v2h11.967V15.5z%20M52.375%2C19.5H40.408v2h11.967V19.5z%20M52.375%2C25.5H37.5v2h14.875V25.5z%20M52.375%2C29.5H37.5v2h14.875V29.5z%20M52.375%2C33.5H37.5v2h14.875V33.5z%20M26.14%2C17.192L28.442%2C9.5l3.277%2C6.571l1.71-2.571l2.992%2C6%20h-2.992h-3.989h-0.997H25.45h-4.986l3.989-4L26.14%2C17.192z%20M22.469%2C12.5h-1c-0.552%2C0-1-0.448-1-1v-1c0-0.552%2C0.448-1%2C1-1h1%20c0.552%2C0%2C1%2C0.448%2C1%2C1v1C23.469%2C12.052%2C23.021%2C12.5%2C22.469%2C12.5z%20M26.795%2C50.955L8.05%2C65.305c-0.419-0.574-0.55-1.41-0.55-2.174%20V37.015l-0.015%2C0.011v-3.479l5.998-4.579v3.479L8.017%2C36.62L26.795%2C50.955z%20M29.137%2C52.659v-0.157h12.462v0.144l0.047-0.036%20L59.73%2C66.5H10.989l18.084-13.889L29.137%2C52.659z%22%2F%3E%3C%2Fsvg%3E%0A")
[data-type="woocommerce"] .mailpoet_thumbnail
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUwMCIgaGVpZ2h0PSIxNDk0IiB2aWV3Qm94PSIwIDAgMjU2IDE1MyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTIzLjc1OSAwaDIwOC4zNzhDMjQ1LjMyNSAwIDI1NiAxMC42NzUgMjU2IDIzLjg2M3Y3OS41NDFjMCAxMy4xODgtMTAuNjc1IDIzLjg2My0yMy44NjMgMjMuODYzSDE1Ny40MWwxMC4yNTcgMjUuMTE4LTQ1LjEwOS0yNS4xMThIMjMuODYzYy0xMy4xODcgMC0yMy44NjItMTAuNjc1LTIzLjg2Mi0yMy44NjNWMjMuODYzQy0uMTA0IDEwLjc4IDEwLjU3IDAgMjMuNzU5IDB6IiBmaWxsPSIjOUI1QzhGIi8+PHBhdGggZD0iTTE0LjU3OCAyMS43NWMxLjQ1Ny0xLjk3OCAzLjY0Mi0zLjAxOCA2LjU1Ni0zLjIyNiA1LjMwOC0uNDE3IDguMzI2IDIuMDggOS4wNTQgNy40OTIgMy4yMjYgMjEuNzUgNi43NjQgNDAuMTcgMTAuNTEgNTUuMjU5bDIyLjc5LTQzLjM5NWMyLjA4Mi0zLjk1NSA0LjY4NC02LjAzNiA3LjgwNi02LjI0NCA0LjU3OS0uMzEyIDcuMzg4IDIuNjAxIDguNTMzIDguNzQxIDIuNjAyIDEzLjg0IDUuOTMyIDI1LjYgOS44ODYgMzUuNTkgMi43MDYtMjYuNDMyIDcuMjg1LTQ1LjQ3NiAxMy43MzctNTcuMjM1IDEuNTYtMi45MTQgMy44NS00LjM3MSA2Ljg2OC00LjU4IDIuMzk0LS4yMDcgNC41NzkuNTIxIDYuNTU2IDIuMDgyIDEuOTc3IDEuNTYxIDMuMDE4IDMuNTM4IDMuMjI2IDUuOTMyLjEwNCAxLjg3My0uMjA4IDMuNDM0LTEuMDQgNC45OTUtNC4wNTkgNy40OTMtNy4zOSAyMC4wODUtMTAuMDk1IDM3LjU2Ny0yLjYwMSAxNi45NjMtMy41MzggMzAuMTgtMi45MTQgMzkuNjUuMjA5IDIuNi0uMjA4IDQuODktMS4yNDggNi44NjgtMS4yNSAyLjI4OS0zLjEyMiAzLjUzOC01LjUxNiAzLjc0Ni0yLjcwNi4yMDgtNS41MTUtMS4wNC04LjIyMS0zLjg1LTkuNjc4LTkuODg3LTE3LjM3OS0yNC42NjQtMjIuOTk4LTQ0LjMzMi02Ljc2NSAxMy4zMi0xMS43NiAyMy4zMS0xNC45ODYgMjkuOTctNi4xNCAxMS43Ni0xMS4zNDMgMTcuNzk2LTE1LjcxNCAxOC4xMDgtMi44MS4yMDgtNS4yMDMtMi4xODYtNy4yODQtNy4xOC01LjMwNy0xMy42MzMtMTEuMDMxLTM5Ljk2Mi0xNy4xNy03OC45ODYtLjQxNy0yLjcwNi4yMDctNS4xIDEuNjY0LTYuOTcyem0yMjMuNjM2IDE2LjMzOGMtMy43NDYtNi41NTYtOS4yNjItMTAuNTEtMTYuNjUtMTIuMDcyLTEuOTc4LS40MTYtMy44NS0uNjI0LTUuNjItLjYyNC05Ljk5IDAtMTguMTA3IDUuMjAzLTI0LjQ1NSAxNS42MS01LjQxMiA4Ljg0NS04LjExNyAxOC42MjctOC4xMTcgMjkuMzQ2IDAgOC4wMTMgMS42NjUgMTQuODgxIDQuOTk1IDIwLjYwNSAzLjc0NiA2LjU1NiA5LjI2MiAxMC41MSAxNi42NSAxMi4wNzEgMS45NzcuNDE3IDMuODUuNjI1IDUuNjIuNjI1IDEwLjA5NCAwIDE4LjIxMS01LjIwMyAyNC40NTUtMTUuNjEgNS40MTEtOC45NSA4LjExNy0xOC43MzIgOC4xMTctMjkuNDUuMTA0LTguMTE3LTEuNjY1LTE0Ljg4Mi00Ljk5NS0yMC41MDF6bS0xMy4xMTIgMjguODI2Yy0xLjQ1NyA2Ljg2OC00LjA1OSAxMS45NjctNy45MSAxNS40MDEtMy4wMTcgMi43MDYtNS44MjcgMy44NS04LjQyOCAzLjMzLTIuNDk4LS41Mi00LjU4LTIuNzA1LTYuMTQtNi43NjQtMS4yNS0zLjIyNi0xLjg3My02LjQ1Mi0xLjg3My05LjQ3IDAtMi42MDEuMjA4LTUuMjAzLjcyOC03LjU5Ni45MzctNC4yNjcgMi43MDYtOC40MyA1LjUxNS0xMi4zODQgMy40MzUtNS4xIDcuMDc3LTcuMTggMTAuODIzLTYuNDUyIDIuNDk4LjUyIDQuNTggMi43MDYgNi4xNCA2Ljc2NCAxLjI0OSAzLjIyNiAxLjg3MyA2LjQ1MiAxLjg3MyA5LjQ3IDAgMi43MDYtLjIwOCA1LjMwNy0uNzI4IDcuN3ptLTUyLjAzMy0yOC44MjZjLTMuNzQ2LTYuNTU2LTkuMzY2LTEwLjUxLTE2LjY1LTEyLjA3Mi0xLjk3Ny0uNDE2LTMuODUtLjYyNC01LjYyLS42MjQtOS45OSAwLTE4LjEwNyA1LjIwMy0yNC40NTUgMTUuNjEtNS40MTEgOC44NDUtOC4xMTcgMTguNjI3LTguMTE3IDI5LjM0NiAwIDguMDEzIDEuNjY1IDE0Ljg4MSA0Ljk5NSAyMC42MDUgMy43NDYgNi41NTYgOS4yNjIgMTAuNTEgMTYuNjUgMTIuMDcxIDEuOTc4LjQxNyAzLjg1LjYyNSA1LjYyLjYyNSAxMC4wOTQgMCAxOC4yMTEtNS4yMDMgMjQuNDU1LTE1LjYxIDUuNDEyLTguOTUgOC4xMTctMTguNzMyIDguMTE3LTI5LjQ1IDAtOC4xMTctMS42NjUtMTQuODgyLTQuOTk1LTIwLjUwMXptLTEzLjIxNiAyOC44MjZjLTEuNDU3IDYuODY4LTQuMDU5IDExLjk2Ny03LjkwOSAxNS40MDEtMy4wMTggMi43MDYtNS44MjggMy44NS04LjQzIDMuMzMtMi40OTctLjUyLTQuNTc4LTIuNzA1LTYuMTQtNi43NjQtMS4yNDgtMy4yMjYtMS44NzItNi40NTItMS44NzItOS40NyAwLTIuNjAxLjIwOC01LjIwMy43MjgtNy41OTYuOTM3LTQuMjY3IDIuNzA2LTguNDMgNS41MTYtMTIuMzg0IDMuNDM0LTUuMSA3LjA3Ni03LjE4IDEwLjgyMi02LjQ1MiAyLjQ5OC41MiA0LjU4IDIuNzA2IDYuMTQgNi43NjQgMS4yNSAzLjIyNiAxLjg3MyA2LjQ1MiAxLjg3MyA5LjQ3LjEwNSAyLjcwNi0uMjA4IDUuMzA3LS43MjggNy43eiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==')
.mailpoet_boxes_preview
margin: -10px
padding: 10px 20px
.mailpoet_boxes
.title_and_badge
display: flex
flex-direction: row
justify-content: space-between
.mailpoet_badge
margin: 0 0 0 10px
padding: 0 6px 0 6px
max-height: 21px
white-space: nowrap

View File

@ -28,6 +28,20 @@ a:focus
.select2-container
width: 25em !important
placeholder-color = #999 /* default Select2 placeholder color for single dropdown */
input.select2-search__field::-webkit-input-placeholder
color: placeholder-color
input.select2-search__field:-moz-placeholder
color: placeholder-color
input.select2-search__field::-moz-placeholder
color: placeholder-color
input.select2-search__field:-ms-input-placeholder
color: placeholder-color
.select2-container--default.select2-container--focus .select2-selection--multiple
border: 1px solid #aaa; /* default Select2 border for single dropdown */
// textareas
textarea.regular-text
width: 25em !important

View File

@ -40,13 +40,13 @@ $green-badge-color = #55bd56
letter-spacing: 1px
vertical-align: middle
&_excellent
&_excellent, &_teal
background: $excellent-badge-color
&_good
&_good, &_yellow
background: $good-badge-color
&_bad
&_bad, &_red
background: $bad-badge-color
&_green

View File

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

View File

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

View File

@ -0,0 +1,9 @@
@require 'newsletter_editor/variables'
.mailpoet_template_iframe
position: absolute
z-index: -9999
top: 0
left: 0
width: $newsletter-width
max-width: $newsletter-width

View File

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

View File

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

View File

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

View File

@ -0,0 +1,16 @@
import React from 'react';
import MailPoet from 'mailpoet';
class Loading extends React.Component {
componentWillMount() {
MailPoet.Modal.loading(true);
}
componentWillUnmount() {
MailPoet.Modal.loading(false);
}
render() {
return null;
}
}
export default Loading;

View File

@ -0,0 +1,76 @@
import _ from 'underscore';
import MailPoet from 'mailpoet';
import html2canvas from 'html2canvas';
/**
* Generates a thumbnail from a DOM element.
*
* @param {DOMElement} element
* @return {Promise<String>} DataURL of the generated image.
*/
export const fromDom = element =>
html2canvas(element, {
logging: false,
}).then(canvas => canvas.toDataURL('image/jpeg'));
/**
* Generates a thumbnail from an URL.
*
* @param {String} url
* @return {Promise<String>} DataURL of the generated image.
*/
export const fromUrl = url =>
new Promise((resolve, reject) => {
const iframe = document.createElement('iframe');
const protocol = location.href.startsWith('https://') ? 'https:' : 'http:';
iframe.src = protocol + url.replace(/^https?:/, '');
iframe.style.opacity = 0;
iframe.scrolling = 'no';
iframe.onload = () => {
fromDom(iframe.contentDocument.documentElement)
.then((image) => {
document.body.removeChild(iframe);
resolve(image);
})
.catch(() => {
document.body.removeChild(iframe);
reject(MailPoet.I18n.t('errorWhileTakingScreenshot'));
});
};
const onError = () => {
document.body.removeChild(iframe);
reject(MailPoet.I18n.t('errorWhileTakingScreenshot'));
};
iframe.onerror = onError;
iframe.onError = onError;
iframe.className = 'mailpoet_template_iframe';
try {
document.body.appendChild(iframe);
} catch (err) {
onError();
}
});
/**
* Generates a thumbnail from a newsletter's data.
*
* @param {Object} data
* @return {Promise<String>} DataURL of the generated image.
*/
export const fromNewsletter = data =>
new Promise((resolve, reject) => {
const json = data;
if (!_.isUndefined(json.body)) {
json.body = JSON.stringify(json.body);
}
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletters',
action: 'showPreview',
data: json,
}).done(response => fromUrl(response.meta.preview_url)
.then(resolve)
.catch(reject)
).fail(response => reject(response.errors));
});

View File

@ -3,7 +3,7 @@ define('date',
'mailpoet',
'jquery',
'moment'
], function (
], function ( // eslint-disable-line func-names
mp,
jQuery,
Moment
@ -19,7 +19,7 @@ define('date',
offset: 0,
format: 'F, d Y H:i:s'
},
init: function (opts) {
init: function init(opts) {
var options = opts || {};
// set UTC offset
@ -41,37 +41,43 @@ define('date',
return this;
},
format: function (date, opts) {
format: function format(date, opts) {
var options = opts || {};
var momentDate;
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();
return momentDate.format(this.convertFormat(this.options.format));
},
toDate: function (date, opts) {
toDate: function toDate(date, opts) {
var options = opts || {};
this.init(options);
return Moment(date, this.convertFormat(options.parseFormat)).toDate();
},
short: function (date) {
short: function short(date) {
return this.format(date, {
format: 'F, j Y'
});
},
full: function (date) {
full: function full(date) {
return this.format(date, {
format: 'F, j Y H:i:s'
});
},
time: function (date) {
time: function time(date) {
return this.format(date, {
format: 'H:i:s'
});
},
convertFormat: function (format) {
var format_mappings = {
convertFormat: function convertFormat(format) {
var replacements;
var convertedFormat;
var escapeToken;
var index;
var token;
var formatMappings = {
date: {
d: 'DD',
D: 'ddd',
@ -140,26 +146,22 @@ define('date',
if (!format || format.length <= 0) return format;
var replacements = format_mappings['date'];
replacements = formatMappings.date;
convertedFormat = [];
escapeToken = false;
var convertedFormat = [];
var escapeToken = false;
for (var index = 0, token = ''; format.charAt(index); index += 1) {
for (index = 0, token = ''; format.charAt(index); index += 1) {
token = format.charAt(index);
if (escapeToken === true) {
convertedFormat.push('[' + token + ']');
escapeToken = false;
} else {
if (token === '\\') {
} else if (token === '\\') {
// Slash escapes the next symbol to be treated as literal
escapeToken = true;
continue;
} else if (replacements[token] !== undefined) {
convertedFormat.push(replacements[token]);
} else {
convertedFormat.push('[' + token + ']');
}
escapeToken = true;
} else if (replacements[token] !== undefined) {
convertedFormat.push(replacements[token]);
} else {
convertedFormat.push('[' + token + ']');
}
}

View File

@ -5,36 +5,35 @@ define([
React
) => {
const FormFieldCheckbox = React.createClass({
onValueChange: function (e) {
e.target.value = this.refs.checkbox.checked ? '1' : '0';
onValueChange: function onValueChange(e) {
e.target.value = this.checkbox.checked ? '1' : '0';
return this.props.onValueChange(e);
},
render: function () {
render: function render() {
if (this.props.field.values === undefined) {
return false;
}
// isChecked will be true only if the value is "1"
// 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(
(value, index) => {
return (
<p key={'checkbox-' + index}>
<label>
<input
ref="checkbox"
type="checkbox"
value="1"
checked={isChecked}
onChange={this.onValueChange}
name={this.props.field.name}
/>
{ this.props.field.values[value] }
</label>
</p>
);
}
(value, index) => (
<p key={`checkbox-${index}`}>
<label htmlFor={this.props.field.name}>
<input
ref={(c) => { this.checkbox = c; }}
type="checkbox"
value="1"
checked={isChecked}
onChange={this.onValueChange}
name={this.props.field.name}
id={this.props.field.name}
/>
{ this.props.field.values[value] }
</label>
</p>
)
);
return (

View File

@ -5,97 +5,91 @@ define([
React,
Moment
) => {
class FormFieldDateYear extends React.Component {
render() {
const yearsRange = 100;
const years = [];
function FormFieldDateYear(props) {
const yearsRange = 100;
const years = [];
if (this.props.placeholder !== undefined) {
years.push((
<option value="" key={0}>{ this.props.placeholder }</option>
));
}
const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i -= 1) {
years.push((
<option
key={i}
value={i}
>{ i }</option>
));
}
return (
<select
name={`${this.props.name}[year]`}
value={this.props.year}
onChange={this.props.onValueChange}
>
{ years }
</select>
);
if (props.placeholder !== undefined) {
years.push((
<option value="" key={0}>{ props.placeholder }</option>
));
}
const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i -= 1) {
years.push((
<option
key={i}
value={i}
>{ i }</option>
));
}
return (
<select
name={`${props.name}[year]`}
value={props.year}
onChange={props.onValueChange}
>
{ years }
</select>
);
}
class FormFieldDateMonth extends React.Component {
render() {
const months = [];
function FormFieldDateMonth(props) {
const months = [];
if (this.props.placeholder !== undefined) {
months.push((
<option value="" key={0}>{ this.props.placeholder }</option>
));
}
for (let i = 1; i <= 12; i += 1) {
months.push((
<option
key={i}
value={i}
>{ this.props.monthNames[i - 1] }</option>
));
}
return (
<select
name={`${this.props.name}[month]`}
value={this.props.month}
onChange={this.props.onValueChange}
>
{ months }
</select>
);
if (props.placeholder !== undefined) {
months.push((
<option value="" key={0}>{ props.placeholder }</option>
));
}
for (let i = 1; i <= 12; i += 1) {
months.push((
<option
key={i}
value={i}
>{ props.monthNames[i - 1] }</option>
));
}
return (
<select
name={`${props.name}[month]`}
value={props.month}
onChange={props.onValueChange}
>
{ months }
</select>
);
}
class FormFieldDateDay extends React.Component {
render() {
const days = [];
function FormFieldDateDay(props) {
const days = [];
if (this.props.placeholder !== undefined) {
days.push((
<option value="" key={0}>{ this.props.placeholder }</option>
));
}
for (let i = 1; i <= 31; i += 1) {
days.push((
<option
key={i}
value={i}
>{ i }</option>
));
}
return (
<select
name={`${this.props.name}[day]`}
value={this.props.day}
onChange={this.props.onValueChange}
>
{ days }
</select>
);
if (props.placeholder !== undefined) {
days.push((
<option value="" key={0}>{ props.placeholder }</option>
));
}
for (let i = 1; i <= 31; i += 1) {
days.push((
<option
key={i}
value={i}
>{ i }</option>
));
}
return (
<select
name={`${props.name}[day]`}
value={props.day}
onChange={props.onValueChange}
>
{ days }
</select>
);
}
class FormFieldDate extends React.Component {
@ -167,6 +161,11 @@ define([
year: this.state.year,
};
break;
default:
value = {
value: 'invalid type',
};
break;
}
return value;
@ -181,7 +180,7 @@ define([
field = matches[1];
property = matches[2];
const value = ~~(e.target.value);
const value = Number(e.target.value);
this.setState({
[`${property}`]: value,
@ -206,7 +205,6 @@ define([
case 'YYYY':
return (<FormFieldDateYear
onValueChange={this.onValueChange.bind(this)}
ref={'year'}
key={'year'}
name={this.props.field.name}
year={this.state.year}
@ -216,7 +214,6 @@ define([
case 'MM':
return (<FormFieldDateMonth
onValueChange={this.onValueChange.bind(this)}
ref={'month'}
key={'month'}
name={this.props.field.name}
month={this.state.month}
@ -227,12 +224,14 @@ define([
case 'DD':
return (<FormFieldDateDay
onValueChange={this.onValueChange.bind(this)}
ref={'day'}
key={'day'}
name={this.props.field.name}
day={this.state.day}
placeholder={this.props.field.day_placeholder}
/>);
default:
return <div>Invalid date type</div>;
}
});

View File

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

View File

@ -5,28 +5,28 @@ define([
React
) => {
const FormFieldRadio = React.createClass({
render: function () {
render: function render() {
if (this.props.field.values === undefined) {
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(
(value, index) => {
return (
<p key={'radio-' + index}>
<label>
<input
type="radio"
checked={selected_value === value}
value={value}
onChange={this.props.onValueChange}
name={this.props.field.name} />
{ this.props.field.values[value] }
</label>
</p>
);
}
(value, index) => (
<p key={`radio-${index}`}>
<label htmlFor={this.props.field.name}>
<input
type="radio"
checked={selectedValue === value}
value={value}
onChange={this.props.onValueChange}
name={this.props.field.name}
id={this.props.field.name}
/>
{ this.props.field.values[value] }
</label>
</p>
)
);
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;
}
@ -41,28 +41,27 @@ const FormFieldSelect = React.createClass({
keys = Object.keys(this.props.field.values);
}
const options = keys.map(
(value, index) => {
if (filter !== false && filter(this.props.item, value) === false) {
return;
}
return (
const options = keys
.filter((value) => {
if (filter === false) return true;
return filter(this.props.item, value);
})
.map(
(value, index) => (
<option
key={'option-' + index}
value={value}>
key={`option-${index}`}
value={value}
>
{ this.props.field.values[value] }
</option>
);
}
);
)
);
return (
<select
name={this.props.field.name}
id={'field_' + this.props.field.name}
value={this.props.item[this.props.field.name]}
id={`field_${this.props.field.name}`}
value={this.props.item[this.props.field.name] || ''}
onChange={this.props.onValueChange}
{...this.props.field.validation}
>

View File

@ -10,65 +10,124 @@ define([
jQuery
) => {
const Selection = React.createClass({
getInitialState: function () {
return {
items: [],
select2: false,
};
},
componentWillMount: function () {
this.loadCachedItems();
},
allowMultipleValues: function () {
allowMultipleValues: function allowMultipleValues() {
return (this.props.field.multiple === true);
},
isSelect2Initialized: function () {
return (this.state.select2 === true);
isSelect2Initialized: function isSelect2Initialized() {
return (jQuery(`#${this.select.id}`).hasClass('select2-hidden-accessible') === true);
},
componentDidMount: function () {
if (this.allowMultipleValues()) {
isSelect2Component: function isSelect2Component() {
return this.allowMultipleValues() || this.props.field.forceSelect2;
},
componentDidMount: function componentDidMount() {
if (this.isSelect2Component()) {
this.setupSelect2();
}
},
componentDidUpdate: function (prevProps) {
if (
(this.props.item !== undefined && prevProps.item !== undefined)
componentDidUpdate: function componentDidUpdate(prevProps) {
if ((this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
jQuery('#' + this.refs.select.id)
jQuery(`#${this.select.id}`)
.val(this.getSelectedValues())
.trigger('change');
}
if (this.isSelect2Initialized() &&
(this.getFieldId(this.props) !== this.getFieldId(prevProps)) &&
this.props.field.resetSelect2OnUpdate !== undefined
) {
this.resetSelect2();
}
},
componentWillUnmount: function () {
if (this.allowMultipleValues()) {
componentWillUnmount: function componentWillUnmount() {
if (this.isSelect2Component()) {
this.destroySelect2();
}
},
destroySelect2: function () {
getFieldId: function getFieldId(data) {
const props = data || this.props;
return props.field.id || props.field.name;
},
resetSelect2: function resetSelect2() {
this.destroySelect2();
this.setupSelect2();
},
destroySelect2: function destroySelect2() {
if (this.isSelect2Initialized()) {
jQuery('#' + this.refs.select.id).select2('destroy');
jQuery(`#${this.select.id}`).select2('destroy');
this.cleanupAfterSelect2();
}
},
setupSelect2: function () {
cleanupAfterSelect2: function cleanupAfterSelect2() {
// remove DOM elements created by Select2 that are not tracked by React
jQuery(`#${this.select.id}`)
.find('option:not(.default)')
.remove();
// unbind events (https://select2.org/programmatic-control/methods#event-unbinding)
jQuery(`#${this.select.id}`)
.off('select2:unselecting')
.off('select2:opening');
},
setupSelect2: function setupSelect2() {
if (this.isSelect2Initialized()) {
return;
}
const select2 = jQuery('#' + this.refs.select.id).select2({
let select2Options = {
width: (this.props.width || ''),
templateResult: function (item) {
placeholder: {
id: '', // the value of the option
text: this.props.field.placeholder,
},
templateResult: function templateResult(item) {
if (item.element && item.element.selected) {
return null;
} else {
if (item.title) {
return item.title;
} else {
return item.text;
}
} else if (item.title) {
return item.title;
}
return item.text;
},
});
};
const remoteQuery = this.props.field.remoteQuery || null;
if (remoteQuery) {
select2Options = Object.assign(select2Options, {
ajax: {
url: window.ajaxurl,
type: 'POST',
dataType: 'json',
data: function data(params) {
return {
action: 'mailpoet',
api_version: window.mailpoet_api_version,
token: window.mailpoet_token,
endpoint: remoteQuery.endpoint,
method: remoteQuery.method,
data: Object.assign(
remoteQuery.data,
{ query: params.term }
),
};
},
processResults: function processResults(response) {
return {
results: response.data.map(item => (
{ id: item.id || item.value, text: item.name || item.text }
)),
};
},
},
minimumInputLength: remoteQuery.minimumInputLength || 2,
});
}
if (this.props.field.extendSelect2Options !== undefined) {
select2Options = Object.assign(select2Options, this.props.field.extendSelect2Options);
}
const select2 = jQuery(`#${this.select.id}`).select2(select2Options);
let hasRemoved = false;
select2.on('select2:unselecting', () => {
@ -82,18 +141,14 @@ define([
});
select2.on('change', this.handleChange);
this.setState({ select2: true });
},
getSelectedValues: function () {
if (this.props.field['selected'] !== undefined) {
return this.props.field['selected'](this.props.item);
getSelectedValues: function getSelectedValues() {
if (this.props.field.selected !== undefined) {
return this.props.field.selected(this.props.item);
} else if (this.props.item !== undefined && this.props.field.name !== undefined) {
if (this.allowMultipleValues()) {
if (Array.isArray(this.props.item[this.props.field.name])) {
return this.props.item[this.props.field.name].map((item) => {
return item.id;
});
return this.props.item[this.props.field.name].map(item => item.id);
}
} else {
return this.props.item[this.props.field.name];
@ -101,25 +156,27 @@ define([
}
return null;
},
loadCachedItems: function () {
if (typeof (window['mailpoet_' + this.props.field.endpoint]) !== 'undefined') {
let items = window['mailpoet_' + this.props.field.endpoint];
getItems: function getItems() {
let items;
if (typeof (window[`mailpoet_${this.props.field.endpoint}`]) !== 'undefined') {
items = window[`mailpoet_${this.props.field.endpoint}`];
} else if (this.props.field.values !== undefined) {
items = this.props.field.values;
}
if (this.props.field['filter'] !== undefined) {
if (Array.isArray(items)) {
if (this.props.field.filter !== undefined) {
items = items.filter(this.props.field.filter);
}
this.setState({
items: items,
});
}
return items;
},
handleChange: function (e) {
handleChange: function handleChange(e) {
let value;
if (this.props.onValueChange !== undefined) {
if (this.props.field.multiple) {
value = jQuery('#' + this.refs.select.id).val();
value = jQuery(`#${this.select.id}`).val();
} else {
value = e.target.value;
}
@ -128,24 +185,25 @@ define([
target: {
value: transformedValue,
name: this.props.field.name,
id: e.target.id,
},
});
}
},
getLabel: function (item) {
if (this.props.field['getLabel'] !== undefined) {
getLabel: function getLabel(item) {
if (this.props.field.getLabel !== undefined) {
return this.props.field.getLabel(item, this.props.item);
}
return item.name;
},
getSearchLabel: function (item) {
if (this.props.field['getSearchLabel'] !== undefined) {
getSearchLabel: function getSearchLabel(item) {
if (this.props.field.getSearchLabel !== undefined) {
return this.props.field.getSearchLabel(item, this.props.item);
}
return null;
},
getValue: function (item) {
if (this.props.field['getValue'] !== undefined) {
getValue: function getValue(item) {
if (this.props.field.getValue !== undefined) {
return this.props.field.getValue(item, this.props.item);
}
return item.id;
@ -153,24 +211,35 @@ define([
// When it's impossible to represent the desired value in DOM,
// this function may be used to transform the placeholder value into
// desired value.
transformChangedValue: function (value) {
if (typeof this.props.field['transformChangedValue'] === 'function') {
transformChangedValue: function transformChangedValue(value) {
if (typeof this.props.field.transformChangedValue === 'function') {
return this.props.field.transformChangedValue.call(this, value);
} else {
return value;
}
return value;
},
render: function () {
const options = this.state.items.map((item, index) => {
insertEmptyOption: function insertEmptyOption() {
// 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 className="default" />);
return undefined;
},
render: function render() {
const items = this.getItems(this.props.field);
const selectedValues = this.getSelectedValues();
const options = items.map((item, index) => {
const label = this.getLabel(item);
const searchLabel = this.getSearchLabel(item);
const value = this.getValue(item);
return (
<option
key={'option-' + index}
key={`option-${index}`}
className="default"
value={value}
title={searchLabel}
selected={value === selectedValues}
>
{ label }
</option>
@ -179,14 +248,17 @@ define([
return (
<select
id={this.props.field.id || this.props.field.name}
ref="select"
id={this.getFieldId()}
ref={(c) => { this.select = c; }}
disabled={this.props.field.disabled}
data-placeholder={this.props.field.placeholder}
multiple={this.props.field.multiple}
defaultValue={this.getSelectedValues()}
defaultValue={selectedValues}
{...this.props.field.validation}
>{ options }</select>
>
{ this.insertEmptyOption() }
{ options }
</select>
);
},
});

View File

@ -2,28 +2,50 @@ import React from 'react';
const FormFieldText = React.createClass({
render() {
let value = this.props.item[this.props.field.name];
if (value === undefined) {
value = this.props.field.defaultValue || '';
const name = this.props.field.name || null;
const item = this.props.item || {};
let value;
let defaultValue;
// value should only be set when onChangeValue is configured
if (this.props.onValueChange instanceof Function) {
value = item[this.props.field.name];
// set value to defaultValue if available
value = (value === undefined && this.props.field.defaultValue) ?
this.props.field.defaultValue : value;
}
// defaultValue should only be set only when value is not set
if (!value && this.props.field.defaultValue) {
defaultValue = this.props.field.defaultValue;
}
let id = this.props.field.id || null;
if (!id && this.props.field.name) {
id = `field_${this.props.field.name}`;
}
let className = this.props.field.class || null;
if (!className && !this.props.field.size) {
className = 'regular-text';
}
return (
<input
type="text"
disabled={
(this.props.field['disabled'] !== undefined)
(this.props.field.disabled !== undefined)
? this.props.field.disabled(this.props.item)
: false
}
className={(this.props.field.size) ? '' : 'regular-text'}
className={className}
size={
(this.props.field.size !== 'auto' && this.props.field.size > 0)
? this.props.field.size
: false
}
name={this.props.field.name}
id={'field_' + this.props.field.name}
name={name}
id={id}
value={value}
defaultValue={defaultValue}
placeholder={this.props.field.placeholder}
onChange={this.props.onValueChange}
{...this.props.field.validation}

View File

@ -5,13 +5,13 @@ define([
React
) => {
const FormFieldTextarea = React.createClass({
render: function () {
render: function render() {
return (
<textarea
type="text"
className="regular-text"
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]}
placeholder={this.props.field.placeholder}
defaultValue={this.props.field.defaultValue}

View File

@ -15,54 +15,55 @@ define(
FormField,
jQuery
) => {
const Form = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired,
},
getDefaultProps: function () {
getDefaultProps: function getDefaultProps() {
return {
params: {},
};
},
getInitialState: function () {
getInitialState: function getInitialState() {
return {
loading: false,
errors: [],
item: {},
};
},
getValues: function () {
getValues: function getValues() {
return this.props.item ? this.props.item : this.state.item;
},
getErrors: function () {
getErrors: function getErrors() {
return this.props.errors ? this.props.errors : this.state.errors;
},
componentDidMount: function () {
componentDidMount: function componentDidMount() {
if (this.isMounted()) {
if (this.props.params.id !== undefined) {
this.loadItem(this.props.params.id);
} else {
this.setState({
item: jQuery('.mailpoet_form').serializeObject(),
setImmediate(() => {
this.setState({
item: jQuery('.mailpoet_form').mailpoetSerializeObject(),
});
});
}
}
},
componentWillReceiveProps: function (props) {
componentWillReceiveProps: function componentWillReceiveProps(props) {
if (props.params.id === undefined) {
this.setState({
loading: false,
item: {},
setImmediate(() => {
this.setState({
loading: false,
item: {},
});
});
if (props.item === undefined) {
this.refs.form.reset();
this.form.reset();
}
} else {
this.loadItem(props.params.id);
}
},
loadItem: function (id) {
loadItem: function loadItem(id) {
this.setState({ loading: true });
MailPoet.Ajax.post({
@ -70,23 +71,26 @@ define(
endpoint: this.props.endpoint,
action: 'get',
data: {
id: id,
id,
},
}).done((response) => {
this.setState({
loading: false,
item: response.data,
});
if (typeof this.props.onItemLoad === 'function') {
this.props.onItemLoad(response.data);
}
}).fail(() => {
this.setState({
loading: false,
item: {},
}, function () {
}, function failSetStateCallback() {
this.context.router.push('/new');
});
});
},
handleSubmit: function (e) {
handleSubmit: function handleSubmit(e) {
e.preventDefault();
// handle validation
@ -100,9 +104,9 @@ define(
// only get values from displayed fields
const item = {};
this.props.fields.map((field) => {
if (field['fields'] !== undefined) {
field.fields.map((subfield) => {
this.props.fields.forEach((field) => {
if (field.fields !== undefined) {
field.fields.forEach((subfield) => {
item[subfield.name] = this.state.item[subfield.name];
});
} else {
@ -139,31 +143,28 @@ define(
}
});
},
handleValueChange: function (e) {
handleValueChange: function handleValueChange(e) {
if (this.props.onChange) {
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,
});
return true;
},
render: function () {
render: function render() {
let errors;
if (this.getErrors() !== undefined) {
errors = this.getErrors().map((error, index) => {
return (
<p key={'error-' + index} className="mailpoet_error">
{ error.message }
</p>
);
});
errors = this.getErrors().map((error, index) => (
<div className="mailpoet_notice notice inline error is-dismissible" key={`error-${index}`}>
<p>{ error.message }</p>
</div>
));
}
const formClasses = classNames(
@ -197,7 +198,8 @@ define(
field={field}
item={this.getValues()}
onValueChange={onValueChange}
key={'field-' + i} />
key={`field-${i}`}
/>
);
});
@ -210,7 +212,8 @@ define(
className="button button-primary"
type="submit"
value={MailPoet.I18n.t('save')}
disabled={this.state.loading} />
disabled={this.state.loading}
/>
);
}
@ -219,7 +222,7 @@ define(
{ beforeFormContent }
<form
id={this.props.id}
ref="form"
ref={(c) => { this.form = c; }}
className={formClasses}
onSubmit={
(this.props.onSubmit !== undefined)

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/*
* name: MailPoet Form Editor
* author: Jonathan Labreuille
@ -7,6 +8,58 @@
'use strict';
var Observable;
var WysijaHistory;
var WysijaForm;
/* LOGGING */
function info(value) {
if (WysijaForm.options.debug === false) return;
if (!(window.console && console.log)) { // eslint-disable-line no-console
(function () {
var noop = function () {};
var methods = [
'assert',
'clear',
'count',
'debug',
'dir',
'dirxml',
'error',
'exception',
'group',
'groupCollapsed',
'groupEnd',
'info',
'log',
'markTimeline',
'profile',
'profileEnd',
'markTimeline',
'table',
'time',
'timeEnd',
'timeStamp',
'trace',
'warn'
];
var length = methods.length;
var console = {};
window.console = {};
while (length) {
length -= 1;
console[methods[length]] = noop;
}
}());
}
try {
console.log('[DEBUG] ' + value); // eslint-disable-line no-console
} catch (e) {
// continue regardless of error
}
}
Event.cacheDelegated = {};
Object.extend(document, (function () {
var cache = Event.cacheDelegated;
@ -30,18 +83,21 @@ Object.extend(document, (function () {
}
function destroyWrapper(selector, eventName, handler) {
var wrapper;
var c = getCacheForSelector(selector);
if (!c[eventName]) return false;
var wrapper = findWrapper(selector, eventName, handler);
wrapper = findWrapper(selector, eventName, handler);
c[eventName] = c[eventName].without(wrapper);
return wrapper;
}
function createWrapper(selector, eventName, handler, context) {
var wrapper, c = getWrappersForSelector(selector, eventName);
var wrapper;
var element;
var c = getWrappersForSelector(selector, eventName);
if (c.pluck('handler').include(handler)) return false;
wrapper = function (event) {
var element = event.findElement(selector);
element = event.findElement(selector);
if (element) handler.call(context || element, event, element);
};
wrapper.handler = handler;
@ -49,48 +105,55 @@ Object.extend(document, (function () {
return wrapper;
}
return {
delegate: function (selector, eventName, handler, context) {
delegate: function (selector, eventName) {
var wrapper = createWrapper.apply(null, arguments);
if (wrapper) document.observe(eventName, wrapper);
return document;
},
stopDelegating: function (selector, eventName, handler) {
stopDelegating: function (selector, eventName) {
var length = arguments.length;
var wrapper;
switch (length) {
case 2:
getWrappersForSelector(selector, eventName).each(function (wrapper) {
document.stopDelegating(selector, eventName, wrapper.handler);
getWrappersForSelector(selector, eventName).each(function (selectorWrapper) {
document.stopDelegating(selector, eventName, selectorWrapper.handler);
});
break;
case 1:
Object.keys(getCacheForSelector(selector)).each(function (eventName) {
document.stopDelegating(selector, eventName);
Object.keys(getCacheForSelector(selector)).each(function (event) {
document.stopDelegating(selector, event);
});
break;
case 0:
Object.keys(cache).each(function (selector) {
document.stopDelegating(selector);
Object.keys(cache).each(function (cacheSelector) {
document.stopDelegating(cacheSelector);
});
break;
default:
var wrapper = destroyWrapper.apply(null, arguments);
wrapper = destroyWrapper.apply(null, arguments);
if (wrapper) document.stopObserving(eventName, wrapper);
}
return document;
}
};
})());
}()));
var Observable = (function () {
Observable = (function () {
function getEventName(nameA, namespace) {
var name = nameA.substring(2);
if (namespace) name = namespace + ':' + name;
return name.underscore().split('_').join(':');
}
function getWrapper(handler, Klass) {
return function (event) {
return handler.call(new Klass(this), event, event.memo);
};
}
function getHandlers(klass) {
var proto = klass.prototype,
namespace = proto.namespace;
var proto = klass.prototype;
var namespace = proto.namespace;
return Object.keys(proto).grep(/^on/).inject(window.$H(), function (handlers, name) {
if (name === 'onDomLoaded') return handlers;
handlers.set(getEventName(name, namespace), getWrapper(proto[name], klass));
@ -98,22 +161,16 @@ var Observable = (function () {
});
}
function getWrapper(handler, klass) {
return function (event) {
return handler.call(new klass(this), event, event.memo);
};
}
function onDomLoad(selector, klass) {
function onDomLoad(selector, Klass) {
window.$$(selector).each(function (element) {
new klass(element).onDomLoaded();
new Klass(element).onDomLoaded();
});
}
return {
observe: function (selector) {
var klass = this;
if (!this.handlers) this.handlers = {};
if (this.handlers[selector]) return;
var klass = this;
if (this.prototype.onDomLoaded) {
if (document.loaded) {
onDomLoad(selector, klass);
@ -133,7 +190,7 @@ var Observable = (function () {
delete this.handlers[selector];
}
};
})();
}());
// override droppables
Object.extend(window.Droppables, {
@ -146,26 +203,29 @@ Object.extend(window.Droppables, {
return proceed(drop);
}),
show: function (point, element) {
var drop;
var affected = [];
if (!this.drops.length) return;
var drop, affected = [];
this.drops.each(function (drop) {
if (window.Droppables.isAffected(point, element, drop)) affected.push(drop);
this.drops.each(function (dropsDrop) {
if (window.Droppables.isAffected(point, element, dropsDrop)) affected.push(dropsDrop);
});
if (affected.length > 0) drop = window.Droppables.findDeepestChild(affected);
if (this.last_active && this.last_active !== drop) this.deactivate(this.last_active, element);
if (drop) {
window.Position.within(drop.element, point[0], point[1]);
if (drop.onHover) drop.onHover(element, drop.element, window.Position.overlap(drop.overlap, drop.element));
if (drop.onHover) {
drop.onHover(element, drop.element, window.Position.overlap(drop.overlap, drop.element));
}
if (drop !== this.last_active) window.Droppables.activate(drop, element);
}
},
displayArea: function (draggable) {
displayArea: function () {
if (!this.drops.length) return;
// hide controls when displaying drop areas.
WysijaForm.hideBlockControls();
this.drops.each(function (drop, iterator) {
this.drops.each(function (drop) {
if (drop.element.hasClassName('block_placeholder')) {
drop.element.addClassName('active');
}
@ -173,7 +233,7 @@ Object.extend(window.Droppables, {
},
hideArea: function () {
if (!this.drops.length) return;
this.drops.each(function (drop, iterator) {
this.drops.each(function (drop) {
if (drop.element.hasClassName('block_placeholder')) {
drop.element.removeClassName('active');
} else if (drop.element.hasClassName('image_placeholder')) {
@ -195,7 +255,7 @@ Object.extend(window.Droppables, {
- set a maximum number of items to be stored
*/
var WysijaHistory = {
WysijaHistory = {
container: 'mailpoet_form_history',
size: 30,
enqueue: function (element) {
@ -239,7 +299,7 @@ var WysijaHistory = {
};
/* MailPoet Form */
var WysijaForm = {
WysijaForm = {
version: '0.7',
options: {
container: 'mailpoet_form_container',
@ -276,8 +336,8 @@ var WysijaForm = {
return str.replace(/&amp;/g, '&').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&quot;/g, '"');
// ": fix for FileMerge because the previous line fucks up its syntax coloring
},
loading: function (is_loading) {
if (is_loading) {
loading: function (isLoading) {
if (isLoading) {
window.$(WysijaForm.options.editor).addClassName('loading');
window.$(WysijaForm.options.toolbar).addClassName('loading');
} else {
@ -292,6 +352,7 @@ var WysijaForm = {
});
},
load: function (data) {
var settingsElements;
if (data === undefined) return;
// load body
@ -302,8 +363,8 @@ var WysijaForm = {
});
// load settings
var settings_elements = window.$('mailpoet_form_settings').getElements();
settings_elements.each(function (setting) {
settingsElements = window.$('mailpoet_form_settings').getElements();
settingsElements.each(function (setting) {
// skip lists
if (setting.name === 'segments') {
return true;
@ -320,30 +381,36 @@ var WysijaForm = {
setting.setValue(data.settings[setting.name]);
}
}
return true;
});
}
},
save: function () {
var position = 1,
data = {
name: window.$F('mailpoet_form_name'),
settings: window.$('mailpoet_form_settings').serialize(true),
body: [],
styles: (window.MailPoet.CodeEditor !== undefined) ? window.MailPoet.CodeEditor.getValue() : null
};
var position = 1;
var styles = null;
var data;
if (window.MailPoet.CodeEditor !== undefined) {
styles = window.MailPoet.CodeEditor.getValue();
}
data = {
name: window.$F('mailpoet_form_name'),
settings: window.$('mailpoet_form_settings').serialize(true),
body: [],
styles: styles
};
// body
WysijaForm.getBlocks().each(function (b) {
var block_data = (typeof (b.block['save']) === 'function') ? b.block.save() : null;
var blockData = (typeof (b.block.save) === 'function') ? b.block.save() : null;
if (block_data !== null) {
if (blockData !== null) {
// set block position
block_data['position'] = position;
blockData.position = position;
// increment position
position++;
position += 1;
// add block data to body
data['body'].push(block_data);
data.body.push(blockData);
}
});
@ -401,17 +468,21 @@ var WysijaForm = {
return data;
},
toggleWidgets: function () {
var hasSegmentSelection;
window.$$('a[wysija_unique="1"]').invoke('removeClassName', 'disabled');
// loop through each unique field already inserted in the editor and disable its toolbar equivalent
window.$$('#' + WysijaForm.options.editor + ' [wysija_unique="1"]').map(function (element) {
var field = window.$$('#' + WysijaForm.options.toolbar + ' [wysija_id="' + element.readAttribute('wysija_id') + '"]');
// loop through each unique field already inserted in the editor
// and disable its toolbar equivalent
window.$$('#' + WysijaForm.options.editor + ' [wysija_unique="1"]').forEach(function (element) {
var field = window.$$(
'#' + WysijaForm.options.toolbar + ' [wysija_id="' + element.readAttribute('wysija_id') + '"]'
);
if (field.length > 0) {
field.first().addClassName('disabled');
}
});
var hasSegmentSelection = WysijaForm.hasSegmentSelection();
hasSegmentSelection = WysijaForm.hasSegmentSelection();
if (hasSegmentSelection) {
window.$('mailpoet_form_segments').writeAttribute('required', false).disable();
@ -425,9 +496,10 @@ var WysijaForm = {
return (window.$$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0);
},
isSegmentSelectionValid: function () {
var segment_selection = window.$$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
if (segment_selection !== undefined) {
var block = WysijaForm.get(segment_selection).block.getData();
var segmentSelection = window.$$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
var block;
if (segmentSelection !== undefined) {
block = WysijaForm.get(segmentSelection).block.getData();
return (
(block.params.values !== undefined)
&&
@ -437,14 +509,17 @@ var WysijaForm = {
return false;
},
setBlockPositions: function (event, target) {
var index = 1;
var blockPlaceholder;
var previousPlaceholder;
// release dragging lock
WysijaForm.locks.dragging = false;
var index = 1;
WysijaForm.getBlocks().each(function (container) {
container.setPosition(index++);
container.setPosition(index);
index += 1;
// remove z-index value to avoid issues when resizing images
if (container['block'] !== undefined) {
if (container.block !== undefined) {
container.block.element.setStyle({
zIndex: ''
});
@ -453,19 +528,19 @@ var WysijaForm = {
if (target !== undefined) {
// get placeholders (previous placeholder matches the placeholder linked to the next block)
var block_placeholder = window.$(target.element.readAttribute('wysija_placeholder')),
previous_placeholder = target.element.previous('.block_placeholder');
blockPlaceholder = window.$(target.element.readAttribute('wysija_placeholder'));
previousPlaceholder = target.element.previous('.block_placeholder');
if (block_placeholder !== null) {
if (blockPlaceholder !== null) {
// put block placeholder before the current block
target.element.insert({
before: block_placeholder
before: blockPlaceholder
});
// if the next block is a wysija_block, insert previous placeholder
if (target.element.next() !== undefined && target.element.next().hasClassName('mailpoet_form_block') && previous_placeholder !== undefined) {
if (target.element.next() !== undefined && target.element.next().hasClassName('mailpoet_form_block') && previousPlaceholder !== undefined) {
target.element.insert({
after: previous_placeholder
after: previousPlaceholder
});
}
}
@ -479,23 +554,17 @@ var WysijaForm = {
},
setSettingsPosition: function () {
// get viewport offsets and dimensions
var viewportHeight = document.viewport.getHeight(),
blockPadding = 5;
var viewportHeight = document.viewport.getHeight();
window.$(WysijaForm.options.container).select('.wysija_settings').each(function (element) {
// get parent dimensions and position
var parentDim = element.up('.mailpoet_form_block').getDimensions(),
parentPos = element.up('.mailpoet_form_block').cumulativeOffset(),
is_visible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight)),
buttonMargin = 5,
relativeTop = buttonMargin;
if (is_visible) {
// desired position is set to center of viewport
var absoluteTop = parseInt(WysijaForm.scroll.top + ((viewportHeight / 2) - (element.getHeight() / 2)), 10),
parentTop = parseInt(parentPos.top - blockPadding, 10),
parentBottom = parseInt(parentPos.top + parentDim.height - blockPadding, 10);
var parentDim = element.up('.mailpoet_form_block').getDimensions();
var parentPos = element.up('.mailpoet_form_block').cumulativeOffset();
var isVisible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight));
var buttonMargin = 5;
var relativeTop = buttonMargin;
if (isVisible) {
// always center
relativeTop = parseInt((parentDim.height / 2) - (element.getHeight() / 2), 10);
}
@ -507,21 +576,34 @@ var WysijaForm = {
});
},
initToolbarPosition: function () {
if (WysijaForm.toolbar.top === null) WysijaForm.toolbar.top = parseInt(window.$(WysijaForm.options.container).positionedOffset().top);
if (WysijaForm.toolbar.y === null) WysijaForm.toolbar.y = parseInt(WysijaForm.toolbar.top);
if (WysijaForm.toolbar.top === null) {
WysijaForm.toolbar.top =
parseInt(window.$(WysijaForm.options.container).positionedOffset().top);
}
if (WysijaForm.toolbar.y === null) {
WysijaForm.toolbar.y = parseInt(WysijaForm.toolbar.top);
}
if (window.isRtl) {
if (WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = 0;
} else {
if (WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = parseInt(window.$(WysijaForm.options.container).positionedOffset().left);
} else if (WysijaForm.toolbar.left === null) {
WysijaForm.toolbar.left =
parseInt(window.$(WysijaForm.options.container).positionedOffset().left);
}
if (WysijaForm.toolbar.x === null) {
WysijaForm.toolbar.x =
parseInt(
WysijaForm.toolbar.left
+ window.$(WysijaForm.options.container).getDimensions().width
+ 15
, 10);
}
if (WysijaForm.toolbar.x === null) WysijaForm.toolbar.x = parseInt(WysijaForm.toolbar.left + window.$(WysijaForm.options.container).getDimensions().width + 15);
},
setToolbarPosition: function () {
var position;
WysijaForm.initToolbarPosition();
var position = {
position = {
top: WysijaForm.toolbar.y + 'px',
visibility: 'visible'
};
@ -573,9 +655,9 @@ var WysijaForm = {
},
hideControls: function () {
try {
return WysijaForm.getBlocks().invoke('hideControls');
WysijaForm.getBlocks().invoke('hideControls');
} catch (e) {
return;
// continue regardless of error
}
},
hideTools: function () {
@ -585,10 +667,12 @@ var WysijaForm = {
instances: {},
get: function (element, typ) {
var type = typ;
var id;
var instance;
if (type === undefined) type = 'block';
// identify element
var id = element.identify();
var instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
id = element.identify();
instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
WysijaForm.instances[id] = instance;
return instance;
@ -636,8 +720,8 @@ var WysijaForm = {
},
encodeURIComponent: function (str) {
// check if it's a url and if so, prevent encoding of protocol
var regexp = new RegExp(/^http[s]?:\/\//),
protocol = regexp.exec(str);
var regexp = new RegExp(/^http[s]?:\/\//);
var protocol = regexp.exec(str);
if (protocol === null) {
// this is not a url so encode the whole thing
@ -646,6 +730,7 @@ var WysijaForm = {
// this is a url, so do not encode the protocol
return encodeURI(str).replace(/[!'()*]/g, escape);
}
return str;
},
updateBlock: function (field) {
var hasUpdated = false;
@ -680,13 +765,13 @@ WysijaForm.DraggableItem = window.Class.create({
},
STYLES: new window.Template('position: absolute; top: #{top}px; left: #{left}px;'),
cloneElement: function () {
var clone = this.element.clone(),
offset = this.element.cumulativeOffset(),
list = this.getList(),
styles = this.STYLES.evaluate({
top: offset.top - list.scrollTop,
left: offset.left - list.scrollLeft
});
var clone = this.element.clone();
var offset = this.element.cumulativeOffset();
var list = this.getList();
var styles = this.STYLES.evaluate({
top: offset.top - list.scrollTop,
left: offset.left - list.scrollLeft
});
clone.setStyle(styles);
clone.addClassName('mailpoet_form_widget');
@ -715,7 +800,7 @@ WysijaForm.DraggableItem = window.Class.create({
window.Droppables.hideArea();
},
starteffect: function (element) {
new window.Effect.Opacity(element, {
new window.Effect.Opacity(element, {// eslint-disable-line no-new
duration: 0.2,
from: element.getOpacity(),
to: 0.7
@ -743,7 +828,7 @@ WysijaForm.Block = window.Class.create({
this.block.makeBlockDroppable();
// setup events
if (this.block['setup'] !== undefined) {
if (this.block.setup !== undefined) {
this.block.setup();
}
return this;
@ -752,61 +837,63 @@ WysijaForm.Block = window.Class.create({
this.element.writeAttribute('wysija_position', position);
},
hideControls: function () {
if (this['getControls']) {
if (this.getControls) {
this.element.removeClassName('hover');
this.getControls().hide();
}
},
showControls: function () {
if (this['getControls']) {
if (this.getControls) {
this.element.addClassName('hover');
try {
this.getControls().show();
} catch (e) {
// continue regardless of error
}
}
},
makeBlockDroppable: function () {
var blockPlaceholder;
if (this.isBlockDroppableEnabled() === false) {
var block_placeholder = this.getBlockDroppable();
window.Droppables.add(block_placeholder.identify(), WysijaForm.blockDropOptions);
block_placeholder.addClassName('enabled');
blockPlaceholder = this.getBlockDroppable();
window.Droppables.add(blockPlaceholder.identify(), WysijaForm.blockDropOptions);
blockPlaceholder.addClassName('enabled');
}
},
removeBlockDroppable: function () {
var blockPlaceholder;
if (this.isBlockDroppableEnabled()) {
var block_placeholder = this.getBlockDroppable();
window.Droppables.remove(block_placeholder.identify());
block_placeholder.removeClassName('enabled');
blockPlaceholder = this.getBlockDroppable();
window.Droppables.remove(blockPlaceholder.identify());
blockPlaceholder.removeClassName('enabled');
}
},
isBlockDroppableEnabled: function () {
// if the block_placeholder does not exist, create it
var block_placeholder = this.getBlockDroppable();
if (block_placeholder === null) {
var blockPlaceholder = this.getBlockDroppable();
if (blockPlaceholder === null) {
return this.createBlockDroppable().hasClassName('enabled');
} else {
return block_placeholder.hasClassName('enabled');
}
return blockPlaceholder.hasClassName('enabled');
},
createBlockDroppable: function () {
info('block -> createBlockDroppable');
this.element.insert({
before: '<div class=\"block_placeholder\">' + window.$('block_placeholder').innerHTML + '</div>'
before: '<div class="block_placeholder">' + window.$('block_placeholder').innerHTML + '</div>'
});
return this.element.previous('.block_placeholder');
},
getBlockDroppable: function () {
if (this.element.previous() === undefined || this.element.previous().hasClassName('block_placeholder') === false) {
return null;
} else {
return this.element.previous();
}
return this.element.previous();
},
getControls: function () {
return this.element.down('.wysija_controls');
},
setupControls: function () {
var block;
// enable controls
this.controls = this.getControls();
@ -814,7 +901,11 @@ WysijaForm.Block = window.Class.create({
// setup events for block controls
this.element.observe('mouseover', function () {
// special cases where controls shouldn't be displayed
if (WysijaForm.locks.dragging === true || WysijaForm.locks.selectingColor === true || WysijaForm.locks.showingTools === true) return;
if (
WysijaForm.locks.dragging === true
|| WysijaForm.locks.selectingColor === true
|| WysijaForm.locks.showingTools === true
) return;
// set block flag
this.element.addClassName('hover');
@ -857,9 +948,8 @@ WysijaForm.Block = window.Class.create({
if (this.settingsButton !== null) {
this.settingsButton.observe('click', function (event) {
// TODO: refactor
var block = window.$(event.target).up('.mailpoet_form_block') || null;
block = window.$(event.target).up('.mailpoet_form_block') || null;
if (block !== null) {
var field = WysijaForm.getFieldData(block);
this.editSettings();
}
}.bind(this));
@ -905,21 +995,26 @@ WysijaForm.Block = window.Class.create({
/* Invoked on item dropped */
WysijaForm.Block.create = function (createBlock, target) {
var block = createBlock;
var body;
var blockTemplate;
var template;
var output;
var settingsSegments;
if (window.$('form_template_' + block.type) === null) {
return false;
}
var body = window.$(WysijaForm.options.body),
block_template = window.Handlebars.compile(window.$('form_template_block').innerHTML),
template = window.Handlebars.compile(window.$('form_template_' + block.type).innerHTML),
output = '';
body = window.$(WysijaForm.options.body);
blockTemplate = window.Handlebars.compile(window.$('form_template_block').innerHTML);
template = window.Handlebars.compile(window.$('form_template_' + block.type).innerHTML);
output = '';
if (block.type === 'segment') {
if (block.params.values === undefined) {
var settings_segments = window.jQuery('#mailpoet_form_segments').val();
if (settings_segments !== null && settings_segments.length > 0) {
settingsSegments = window.jQuery('#mailpoet_form_segments').val();
if (settingsSegments !== null && settingsSegments.length > 0) {
block.params.values = window.mailpoet_segments.filter(function (segment) {
return (settings_segments.indexOf(segment.id) !== -1);
return (settingsSegments.indexOf(segment.id) !== -1);
});
}
}
@ -927,7 +1022,7 @@ WysijaForm.Block.create = function (createBlock, target) {
// set block template (depending on the block type)
block.template = template(block);
output = block_template(block);
output = blockTemplate(block);
// check if the new block is unique and if there's already an instance
// of it in the history. If so, remove its former instance from the history
@ -936,14 +1031,13 @@ WysijaForm.Block.create = function (createBlock, target) {
}
// if the drop target was the bottom placeholder
var element = null;
if (target.identify() === 'block_placeholder') {
// insert block at the bottom
element = body.insert(output);
body.insert(output);
// block = body.childElements().last();
} else {
// insert block before the drop target
element = target.insert({
target.insert({
before: output
});
// block = target.previous('.mailpoet_form_block');
@ -956,6 +1050,7 @@ WysijaForm.Block.create = function (createBlock, target) {
// position settings
WysijaForm.setSettingsPosition();
return true;
};
document.observe('wjfe:item:drop', function (event) {
@ -984,8 +1079,8 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
this.setupControls();
},
save: function () {
info('widget -> save');
var data = this.getData();
info('widget -> save');
if (data.element !== undefined) {
delete data.element;
@ -994,11 +1089,11 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
return data;
},
setData: function (data) {
var current_data = this.getData(),
params = window.$H(current_data.params).merge(data.params).toObject();
var currentData = this.getData();
var params = window.$H(currentData.params).merge(data.params).toObject();
// update type if it changed
if (data.type !== undefined && data.type !== current_data.type) {
if (data.type !== undefined && data.type !== currentData.type) {
this.element.writeAttribute('wysija_type', data.type);
}
@ -1020,16 +1115,20 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
this.removeBlock();
},
redraw: function (data) {
var options;
var blockTemplate;
var template;
var params;
// set parameters
this.setData(data);
var options = this.getData();
options = this.getData();
// redraw block
var block_template = window.Handlebars.compile(window.$('form_template_block').innerHTML),
template = window.Handlebars.compile(window.$('form_template_' + options.type).innerHTML),
data = window.$H(options).merge({
template: template(options)
}).toObject();
this.element.replace(block_template(data));
blockTemplate = window.Handlebars.compile(window.$('form_template_block').innerHTML);
template = window.Handlebars.compile(window.$('form_template_' + options.type).innerHTML);
params = window.$H(options).merge({
template: template(options)
}).toObject();
this.element.replace(blockTemplate(params));
WysijaForm.init();
},
@ -1039,7 +1138,7 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
template: window.jQuery('#form_template_field_settings').html(),
data: this.getData(),
onSuccess: function () {
var data = window.jQuery('#form_field_settings').serializeObject();
var data = window.jQuery('#form_field_settings').mailpoetSerializeObject();
this.redraw(data);
}.bind(this)
});
@ -1052,25 +1151,4 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
/* When dom is loaded, initialize WysijaForm */
document.observe('dom:loaded', WysijaForm.init);
/* LOGGING */
function info(value) {
if (WysijaForm.options.debug === false) return;
if (!(window.console && console.log)) {
(function () {
var noop = function () {};
var methods = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'markTimeline', 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
var length = methods.length;
window.console = {};
var console = {};
while (length--) {
console[methods[length]] = noop;
}
}());
}
try {
console.log('[DEBUG] ' + value);
} catch (e) {}
}
module.exports = WysijaForm;

View File

@ -27,7 +27,7 @@ const columns = [
const messages = {
onTrash: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -42,7 +42,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onDelete: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -57,7 +57,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onRestore: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -73,7 +73,7 @@ const messages = {
},
};
const bulk_actions = [
const bulkActions = [
{
name: 'trash',
label: MailPoet.I18n.t('moveToTrash'),
@ -81,11 +81,11 @@ const bulk_actions = [
},
];
const item_actions = [
const itemActions = [
{
name: 'edit',
label: MailPoet.I18n.t('edit'),
link: function (item) {
link: function link(item) {
return (
<a href={`admin.php?page=mailpoet-form-editor&id=${item.id}`}>{MailPoet.I18n.t('edit')}</a>
);
@ -94,7 +94,7 @@ const item_actions = [
{
name: 'duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function (item, refresh) {
onClick: function onClick(item, refresh) {
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'forms',
@ -110,7 +110,7 @@ const item_actions = [
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
@ -133,32 +133,31 @@ const FormList = React.createClass({
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
});
},
renderItem(form, actions) {
const row_classes = classNames(
const rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
let segments = window.mailpoet_segments.filter((segment) => {
return (jQuery.inArray(segment.id, form.segments) !== -1);
}).map((segment) => {
return segment.name;
}).join(', ');
let segments = window.mailpoet_segments
.filter(segment => (jQuery.inArray(segment.id, form.segments) !== -1))
.map(segment => segment.name)
.join(', ');
if (form.settings.segments_selected_by === 'user') {
segments = MailPoet.I18n.t('userChoice') + ' ' + segments;
segments = `${MailPoet.I18n.t('userChoice')} ${segments}`;
}
return (
<div>
<td className={row_classes}>
<td className={rowClasses}>
<strong>
<a
className="row-title"
@ -199,8 +198,8 @@ const FormList = React.createClass({
endpoint="forms"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={bulk_actions}
item_actions={item_actions}
bulk_actions={bulkActions}
item_actions={itemActions}
/>
</div>
);

View File

@ -1,34 +1,35 @@
/* eslint-disable func-names */
define('handlebars_helpers', ['handlebars'], function (Handlebars) {
// Handlebars helpers
Handlebars.registerHelper('concat', function () {
var size = (arguments.length - 1),
output = '';
for (var i = 0; i < size; i++) {
var size = (arguments.length - 1);
var output = '';
var i;
for (i = 0; i < size; i += 1) {
output += arguments[i];
}
return output;
});
Handlebars.registerHelper('number_format', function (value, block) {
Handlebars.registerHelper('number_format', function (value) {
return Number(value).toLocaleString();
});
Handlebars.registerHelper('date_format', function (timestamp, block) {
var f;
if (window.moment) {
if (timestamp === undefined || isNaN(timestamp) || timestamp <= 0) {
return;
return undefined;
}
// set date format
var f = block.hash.format || 'MMM Do, YYYY';
// check if we passed a timestamp
if (parseInt(timestamp, 10) == timestamp) {
// set date format
f = block.hash.format || 'MMM Do, YYYY';
// check if we passed a timestamp
if (/^\s*\d+\s*$/.test(timestamp)) {
return window.moment.unix(timestamp).format(f);
} else {
return window.moment.utc(timestamp).format(f);
}
} else {
return timestamp;
return window.moment.utc(timestamp).format(f);
}
return timestamp;
});
Handlebars.registerHelper('cycle', function (value, block) {
@ -39,11 +40,11 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
switch (operator) {
case '==':
return (v1 == v2) ? options.fn(this) : options.inverse(this);
return (v1 == v2) ? options.fn(this) : options.inverse(this); // eslint-disable-line eqeqeq
case '===':
return (v1 === v2) ? options.fn(this) : options.inverse(this);
case '!=':
return (v1 != v2) ? options.fn(this) : options.inverse(this);
return (v1 != v2) ? options.fn(this) : options.inverse(this); // eslint-disable-line eqeqeq
case '!==':
return (v1 !== v2) ? options.fn(this) : options.inverse(this);
case '<':
@ -59,47 +60,46 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
case '||':
return (v1 || v2) ? options.fn(this) : options.inverse(this);
case 'in':
var values = v2.split(',');
return (v2.indexOf(v1) !== -1) ? options.fn(this) : options.inverse(this);
default:
return options.inverse(this);
}
});
Handlebars.registerHelper('nl2br', function (value, block) {
Handlebars.registerHelper('nl2br', function (value) {
return value.gsub('\n', '<br />');
});
Handlebars.registerHelper('json_encode', function (value, block) {
Handlebars.registerHelper('json_encode', function (value) {
return JSON.stringify(value);
});
Handlebars.registerHelper('json_decode', function (value, block) {
Handlebars.registerHelper('json_decode', function (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;
return url + value;
});
Handlebars.registerHelper('emailFromMailto', function (value) {
var mailtoMatchingRegex = /^mailto\:/i;
var mailtoMatchingRegex = /^mailto:/i;
if (typeof value === 'string' && value.match(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];
});
Handlebars.registerHelper('rsa_key', function (value, block) {
// extract all lines into an array
Handlebars.registerHelper('rsa_key', function (value) {
var lines;
// extract all lines into an array
if (value === undefined) return '';
var lines = value.trim().split('\n');
lines = value.trim().split('\n');
// remove header & footer
lines.shift();
@ -109,7 +109,7 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
return lines.join('');
});
Handlebars.registerHelper('trim', function (value, block) {
Handlebars.registerHelper('trim', function (value) {
if (value === null || value === undefined) return '';
return value.trim();
});
@ -126,15 +126,14 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
*/
Handlebars.registerHelper('ellipsis', function (str, limit, append) {
var strAppend = append;
var sanitized = str.replace(/(<([^>]+)>)/g, '');
if (strAppend === undefined) {
strAppend = '';
}
var sanitized = str.replace(/(<([^>]+)>)/g, '');
if (sanitized.length > limit) {
return sanitized.substr(0, limit - strAppend.length) + strAppend;
} else {
return sanitized;
}
return sanitized;
});
Handlebars.registerHelper('getNumber', function (string) {

View File

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

View File

@ -32,15 +32,14 @@ function Tooltip(props) {
data-event="click"
data-tip
data-for={tooltipId}
>
</span>
/>
<ReactTooltip
globalEventOff="click"
multiline={true}
id={tooltipId}
efect="solid"
place={props.place}
>
>
{tooltip}
</ReactTooltip>
</span>

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import React from 'react';
import MailPoet from 'mailpoet';
import _ from 'underscore';
import Tabs from './tabs.jsx';
function handleFocus(event) {
@ -10,9 +9,7 @@ function handleFocus(event) {
function printData(data) {
if (_.isObject(data)) {
const printableData = Object.keys(data).map((key) => {
return `${key}: ${data[key]}`;
});
const printableData = Object.keys(data).map(key => `${key}: ${data[key]}`);
return (<textarea
readOnly={true}
@ -23,25 +20,24 @@ function printData(data) {
height: '400px',
}}
/>);
} else {
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
}
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
}
function KnowledgeBase() {
const data = window.help_scout_data;
function SystemInfo() {
const systemInfoData = window.systemInfoData;
return (
<div>
<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>
</div>
{printData(data)}
{printData(systemInfoData)}
</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 = [
{
name: 'knowledgeBase',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'),
link: '/knowledgeBase',
name: 'systemStatus',
label: MailPoet.I18n.t('tabSystemStatusTitle'),
link: '/systemStatus',
},
{
name: 'systemInfo',
label: MailPoet.I18n.t('tabSystemInfoTitle'),
link: '/systemInfo',
},
{
name: 'knowledgeBase',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'),
link: '/knowledgeBase',
},
];
function Tabs(props) {
const tabLinks = tabs.map((tab, index) => {
const tabClasses = classNames(
'nav-tab',
@ -26,7 +30,7 @@ function Tabs(props) {
return (
<Link
key={'tab-' + index}
key={`tab-${index}`}
className={tabClasses}
to={tab.link}
>{ tab.label }</Link>
@ -41,6 +45,6 @@ function Tabs(props) {
}
Tabs.propTypes = { tab: React.PropTypes.string };
Tabs.defaultProps = { tab: 'knowledgeBase' };
Tabs.defaultProps = { tab: 'systemStatus' };
module.exports = Tabs;

View File

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

View File

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

View File

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

View File

@ -7,13 +7,13 @@ define([
MailPoet
) => {
const ListingBulkActions = React.createClass({
getInitialState: function () {
getInitialState: function getInitialState() {
return {
action: false,
extra: false,
};
},
handleChangeAction: function (e) {
handleChangeAction: function handleChangeAction(e) {
this.setState({
action: e.target.value,
extra: false,
@ -21,14 +21,14 @@ define([
const action = this.getSelectedAction();
// action on select callback
if (action !== null && action['onSelect'] !== undefined) {
if (action !== null && action.onSelect !== undefined) {
this.setState({
extra: action.onSelect(e),
});
}
});
},
handleApplyAction: function (e) {
handleApplyAction: function handleApplyAction(e) {
e.preventDefault();
const action = this.getSelectedAction();
@ -37,23 +37,23 @@ define([
return;
}
const selected_ids = (this.props.selection !== 'all')
const selectedIds = (this.props.selection !== 'all')
? this.props.selected_ids
: [];
const data = (action['getData'] !== undefined)
const data = (action.getData !== undefined)
? action.getData()
: {};
data.action = this.state.action;
let onSuccess = function () {};
if (action['onSuccess'] !== undefined) {
let onSuccess = () => {};
if (action.onSuccess !== undefined) {
onSuccess = action.onSuccess;
}
if (data.action) {
const promise = this.props.onBulkAction(selected_ids, data);
const promise = this.props.onBulkAction(selectedIds, data);
if (promise !== false) {
promise.then(onSuccess);
}
@ -64,12 +64,10 @@ define([
extra: false,
});
},
getSelectedAction: function () {
const selected_action = this.refs.action.value;
if (selected_action.length > 0) {
const action = this.props.bulk_actions.filter((action) => {
return (action.name === selected_action);
});
getSelectedAction: function getSelectedAction() {
const selectedAction = this.action.value;
if (selectedAction.length > 0) {
const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
if (action.length > 0) {
return action[0];
@ -77,7 +75,7 @@ define([
}
return null;
},
render: function () {
render: function render() {
if (this.props.bulk_actions.length === 0) {
return null;
}
@ -86,31 +84,31 @@ define([
<div className="alignleft actions bulkactions">
<label
className="screen-reader-text"
htmlFor="bulk-action-selector-top">
htmlFor="bulk-action-selector-top"
>
{MailPoet.I18n.t('selectBulkAction')}
</label>
<select
name="bulk_actions"
ref="action"
ref={(c) => { this.action = c; }}
value={this.state.action}
onChange={this.handleChangeAction}
>
<option value="">{MailPoet.I18n.t('bulkActions')}</option>
{ this.props.bulk_actions.map((action, index) => {
return (
<option
value={action.name}
key={'action-' + index}
>{ action.label }</option>
);
}) }
{ this.props.bulk_actions.map((action, index) => (
<option
value={action.name}
key={`action-${index}`}
>{ action.label }</option>
)) }
</select>
<input
onClick={this.handleApplyAction}
type="submit"
defaultValue={MailPoet.I18n.t('apply')}
className="button action" />
className="button action"
/>
{ this.state.extra }
</div>

View File

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

View File

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

View File

@ -3,12 +3,12 @@ import React from 'react';
import classNames from 'classnames';
const ListingHeader = React.createClass({
handleSelectItems: function () {
handleSelectItems: function handleSelectItems() {
return this.props.onSelectItems(
this.refs.toggle.checked
this.toggle.checked
);
},
render: function () {
render: function render() {
const columns = this.props.columns.map((column, index) => {
const renderColumn = column;
renderColumn.is_primary = (index === 0);
@ -19,8 +19,9 @@ const ListingHeader = React.createClass({
<ListingColumn
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={'column-' + index}
column={renderColumn} />
key={`column-${index}`}
column={renderColumn}
/>
);
});
@ -29,16 +30,19 @@ const ListingHeader = React.createClass({
if (this.props.is_selectable === true) {
checkbox = (
<th
className="manage-column column-cb check-column">
<label className="screen-reader-text">
className="manage-column column-cb check-column"
>
<label className="screen-reader-text" htmlFor="select_all">
{MailPoet.I18n.t('selectAll')}
</label>
<input
type="checkbox"
name="select_all"
ref="toggle"
id="select_all"
ref={(c) => { this.toggle = c; }}
checked={this.props.selection}
onChange={this.handleSelectItems} />
onChange={this.handleSelectItems}
/>
</th>
);
}
@ -53,12 +57,12 @@ const ListingHeader = React.createClass({
});
const ListingColumn = React.createClass({
handleSort: function () {
const sort_by = this.props.column.name;
const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order);
handleSort: function handleSort() {
const sortBy = this.props.column.name;
const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sortBy, sortOrder);
},
render: function () {
render: function render() {
const classes = classNames(
'manage-column',
{ 'column-primary': this.props.column.is_primary },
@ -70,9 +74,13 @@ const ListingColumn = React.createClass({
if (this.props.column.sortable === true) {
label = (
<a onClick={this.handleSort}>
<a
onClick={this.handleSort}
role="button"
tabIndex={0}
>
<span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span>
<span className="sorting-indicator" />
</a>
);
} else {
@ -80,6 +88,7 @@ const ListingColumn = React.createClass({
}
return (
<th
role="columnheader"
className={classes}
id={this.props.column.name}
scope="col"

View File

@ -12,12 +12,12 @@ import ListingGroups from 'listing/groups.jsx';
import ListingFilters from 'listing/filters.jsx';
const ListingItem = React.createClass({
getInitialState: function () {
getInitialState: function getInitialState() {
return {
expanded: false,
};
},
handleSelectItem: function (e) {
handleSelectItem: function handleSelectItem(e) {
this.props.onSelectItem(
parseInt(e.target.value, 10),
e.target.checked
@ -25,26 +25,26 @@ const ListingItem = React.createClass({
return !e.target.checked;
},
handleRestoreItem: function (id) {
handleRestoreItem: function handleRestoreItem(id) {
this.props.onRestoreItem(id);
},
handleTrashItem: function (id) {
handleTrashItem: function handleTrashItem(id) {
this.props.onTrashItem(id);
},
handleDeleteItem: function (id) {
handleDeleteItem: function handleDeleteItem(id) {
this.props.onDeleteItem(id);
},
handleToggleItem: function () {
handleToggleItem: function handleToggleItem() {
this.setState({ expanded: !this.state.expanded });
},
render: function () {
render: function render() {
let checkbox = false;
if (this.props.is_selectable === true) {
checkbox = (
<th className="check-column" scope="row">
<label className="screen-reader-text">{
'Select ' + this.props.item[this.props.columns[0].name]
<label className="screen-reader-text" htmlFor={`listing-row-checkbox-${this.props.item.id}`}>{
`Select ${this.props.item[this.props.columns[0].name]}`
}</label>
<input
type="checkbox"
@ -53,61 +53,65 @@ const ListingItem = React.createClass({
this.props.item.selected || this.props.selection === 'all'
}
onChange={this.handleSelectItem}
disabled={this.props.selection === 'all'} />
disabled={this.props.selection === 'all'}
id={`listing-row-checkbox-${this.props.item.id}`}
/>
</th>
);
}
const custom_actions = this.props.item_actions;
let item_actions = false;
const customActions = this.props.item_actions;
let itemActions = false;
if (custom_actions.length > 0) {
let is_first = true;
item_actions = custom_actions.map((action, index) => {
if (action.display !== undefined) {
if (action.display(this.props.item) === false) {
return;
}
}
let custom_action = null;
if (customActions.length > 0) {
let isFirst = true;
itemActions = customActions
.filter(action => action.display === undefined || action.display(this.props.item))
.map((action, index) => {
let customAction = null;
if (action.name === 'trash') {
custom_action = (
<span key={'action-' + index} className="trash">
{(!is_first) ? ' | ' : ''}
customAction = (
<span key={`action-${index}`} className="trash">
{(!isFirst) ? ' | ' : ''}
<a
href="javascript:;"
onClick={this.handleTrashItem.bind(
null,
this.props.item.id
)}>
)}
>
{MailPoet.I18n.t('moveToTrash')}
</a>
</span>
);
} else if (action.refresh) {
custom_action = (
customAction = (
<span
onClick={this.props.onRefreshItems}
key={'action-' + index} className={action.name}>
{(!is_first) ? ' | ' : ''}
key={`action-${index}`} className={action.name}
role="button"
tabIndex={index}
>
{(!isFirst) ? ' | ' : ''}
{ action.link(this.props.item) }
</span>
);
} else if (action.link) {
custom_action = (
customAction = (
<span
key={'action-' + index} className={action.name}>
{(!is_first) ? ' | ' : ''}
key={`action-${index}`} className={action.name}
>
{(!isFirst) ? ' | ' : ''}
{ action.link(this.props.item) }
</span>
);
} else {
custom_action = (
customAction = (
<span
key={'action-' + index} className={action.name}>
{(!is_first) ? ' | ' : ''}
key={`action-${index}`} className={action.name}
>
{(!isFirst) ? ' | ' : ''}
<a href="javascript:;" onClick={
(action.onClick !== undefined)
? action.onClick.bind(null,
@ -115,19 +119,20 @@ const ListingItem = React.createClass({
this.props.onRefreshItems
)
: false
}>{ action.label }</a>
}
>{ action.label }</a>
</span>
);
}
if (custom_action !== null && is_first === true) {
is_first = false;
if (customAction !== null && isFirst === true) {
isFirst = false;
}
return custom_action;
return customAction;
});
} else {
item_actions = (
itemActions = (
<span className="edit">
<Link to={`/edit/${this.props.item.id}`}>{MailPoet.I18n.t('edit')}</Link>
</span>
@ -163,7 +168,8 @@ const ListingItem = React.createClass({
</div>
<button
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>
</button>
</div>
@ -172,21 +178,22 @@ const ListingItem = React.createClass({
actions = (
<div>
<div className="row-actions">
{ item_actions }
{ itemActions }
</div>
<button
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>
</button>
</div>
);
}
const row_classes = classNames({ 'is-expanded': this.state.expanded });
const rowClasses = classNames({ 'is-expanded': this.state.expanded });
return (
<tr className={row_classes}>
<tr className={rowClasses} data-automation-id={`listing_item_${this.props.item.id}`}>
{ checkbox }
{ this.props.onRenderItem(this.props.item, actions) }
</tr>
@ -196,7 +203,7 @@ const ListingItem = React.createClass({
const ListingItems = React.createClass({
render: function () {
render: function render() {
if (this.props.items.length === 0) {
let message;
if (this.props.loading === true) {
@ -217,30 +224,32 @@ const ListingItems = React.createClass({
this.props.columns.length
+ (this.props.is_selectable ? 1 : 0)
}
className="colspanchange">
className="colspanchange"
>
{message}
</td>
</tr>
</tbody>
);
} else {
const select_all_classes = classNames(
}
const selectAllClasses = classNames(
'mailpoet_select_all',
{ mailpoet_hidden: (
{ mailpoet_hidden: (
this.props.selection === false
|| (this.props.count <= this.props.limit)
),
}
}
);
return (
<tbody>
<tr className={select_all_classes}>
<td colSpan={
return (
<tbody>
<tr className={selectAllClasses}>
<td colSpan={
this.props.columns.length
+ (this.props.is_selectable ? 1 : 0)
}>
{
}
>
{
(this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLabel')
: MailPoet.I18n.t('selectedAllLabel').replace(
@ -249,41 +258,42 @@ const ListingItems = React.createClass({
)
}
&nbsp;
<a
onClick={this.props.onSelectAll}
href="javascript:;">{
<a
onClick={this.props.onSelectAll}
href="javascript:;"
>{
(this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLink')
: MailPoet.I18n.t('clearSelection')
}</a>
</td>
</tr>
</td>
</tr>
{this.props.items.map((item, index) => {
const renderItem = item;
renderItem.id = parseInt(item.id, 10);
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
{this.props.items.map((item, index) => {
const renderItem = item;
renderItem.id = parseInt(item.id, 10);
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
return (
<ListingItem
columns={this.props.columns}
onSelectItem={this.props.onSelectItem}
onRenderItem={this.props.onRenderItem}
onDeleteItem={this.props.onDeleteItem}
onRestoreItem={this.props.onRestoreItem}
onTrashItem={this.props.onTrashItem}
onRefreshItems={this.props.onRefreshItems}
selection={this.props.selection}
is_selectable={this.props.is_selectable}
item_actions={this.props.item_actions}
group={this.props.group}
key={`item-${renderItem.id}-${index}`}
item={renderItem} />
);
})}
</tbody>
);
}
return (
<ListingItem
columns={this.props.columns}
onSelectItem={this.props.onSelectItem}
onRenderItem={this.props.onRenderItem}
onDeleteItem={this.props.onDeleteItem}
onRestoreItem={this.props.onRestoreItem}
onTrashItem={this.props.onTrashItem}
onRefreshItems={this.props.onRefreshItems}
selection={this.props.selection}
is_selectable={this.props.is_selectable}
item_actions={this.props.item_actions}
group={this.props.group}
key={`item-${renderItem.id}-${index}`}
item={renderItem}
/>
);
})}
</tbody>
);
},
});
@ -291,7 +301,7 @@ const Listing = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired,
},
getInitialState: function () {
getInitialState: function getInitialState() {
return {
loading: false,
search: '',
@ -310,25 +320,24 @@ const Listing = React.createClass({
meta: {},
};
},
getParam: function (param) {
getParam: function getParam(param) {
const regex = /(.*)\[(.*)\]/;
const matches = regex.exec(param);
return [matches[1], matches[2]];
},
initWithParams: function (params) {
initWithParams: function initWithParams(params) {
const state = this.getInitialState();
// check for url params
if (params.splat) {
params.splat.split('/').map((param) => {
params.splat.split('/').forEach((param) => {
const [key, value] = this.getParam(param);
const filters = {};
switch (key) {
case 'filter':
const filters = {};
value.split('&').map((pair) => {
value.split('&').forEach((pair) => {
const [k, v] = pair.split('=');
filters[k] = v;
}
);
});
state.filter = filters;
break;
@ -340,7 +349,7 @@ const Listing = React.createClass({
// limit per page
if (this.props.limit !== undefined) {
state.limit = Math.abs(~~this.props.limit);
state.limit = Math.abs(Number(this.props.limit));
}
// sort by
@ -357,7 +366,7 @@ const Listing = React.createClass({
this.getItems();
});
},
getParams: function () {
getParams: function getParams() {
// get all route parameters (without the "splat")
const params = _.omit(this.props.params, 'splat');
// TODO:
@ -368,11 +377,10 @@ const Listing = React.createClass({
}
return params;
},
setParams: function () {
setParams: function setParams() {
if (this.props.location) {
const params = Object.keys(this.state)
.filter((key) => {
return (
.filter(key => (
[
'group',
'filter',
@ -381,8 +389,7 @@ const Listing = React.createClass({
'sort_by',
'sort_order',
].indexOf(key) !== -1
);
})
))
.map((key) => {
let value = this.state[key];
if (value === Object(value)) {
@ -390,12 +397,13 @@ const Listing = React.createClass({
} else if (value === Boolean(value)) {
value = value.toString();
}
if (value !== '' && value !== null) {
return `${key}[${value}]`;
}
return {
key,
value,
};
})
.filter((key) => { return (key !== undefined); })
.filter(({ value }) => value !== '' && value !== null)
.map(({ key, value }) => `${key}[${value}]`)
.join('/');
// set url
@ -406,32 +414,31 @@ const Listing = React.createClass({
}
}
},
getUrlWithParams: function (params) {
let base_url = (this.props.base_url !== undefined)
getUrlWithParams: function getUrlWithParams(params) {
let baseUrl = (this.props.base_url !== undefined)
? this.props.base_url
: null;
if (base_url !== null) {
base_url = this.setBaseUrlParams(base_url);
return `/${base_url}/${params}`;
} else {
return `/${params}`;
if (baseUrl !== null) {
baseUrl = this.setBaseUrlParams(baseUrl);
return `/${baseUrl}/${params}`;
}
return `/${params}`;
},
setBaseUrlParams: function (base_url) {
let ret = base_url;
setBaseUrlParams: function setBaseUrlParams(baseUrl) {
let ret = baseUrl;
if (ret.indexOf(':') !== -1) {
const params = this.getParams();
Object.keys(params).map((key) => {
if (ret.indexOf(':' + key) !== -1) {
ret = ret.replace(':' + key, params[key]);
Object.keys(params).forEach((key) => {
if (ret.indexOf(`:${key}`) !== -1) {
ret = ret.replace(`:${key}`, params[key]);
}
});
}
return ret;
},
componentDidMount: function () {
componentDidMount: function componentDidMount() {
if (this.isMounted()) {
const params = this.props.params || {};
this.initWithParams(params);
@ -443,11 +450,11 @@ const Listing = React.createClass({
}
}
},
componentWillReceiveProps: function (nextProps) {
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
const params = nextProps.params || {};
this.initWithParams(params);
},
getItems: function () {
getItems: function getItems() {
if (this.isMounted()) {
this.setState({ loading: true });
@ -491,14 +498,14 @@ const Listing = React.createClass({
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
});
}
},
handleRestoreItem: function (id) {
handleRestoreItem: function handleRestoreItem(id) {
this.setState({
loading: true,
page: 1,
@ -509,24 +516,24 @@ const Listing = React.createClass({
endpoint: this.props.endpoint,
action: 'restore',
data: {
id: id,
id,
},
}).done((response) => {
if (
this.props.messages !== undefined
&& this.props.messages['onRestore'] !== undefined
&& this.props.messages.onRestore !== undefined
) {
this.props.messages.onRestore(response);
}
this.getItems();
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
});
},
handleTrashItem: function (id) {
handleTrashItem: function handleTrashItem(id) {
this.setState({
loading: true,
page: 1,
@ -537,24 +544,24 @@ const Listing = React.createClass({
endpoint: this.props.endpoint,
action: 'trash',
data: {
id: id,
id,
},
}).done((response) => {
if (
this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined
&& this.props.messages.onTrash !== undefined
) {
this.props.messages.onTrash(response);
}
this.getItems();
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
});
},
handleDeleteItem: function (id) {
handleDeleteItem: function handleDeleteItem(id) {
this.setState({
loading: true,
page: 1,
@ -565,45 +572,48 @@ const Listing = React.createClass({
endpoint: this.props.endpoint,
action: 'delete',
data: {
id: id,
id,
},
}).done((response) => {
if (
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
&& this.props.messages.onDelete !== undefined
) {
this.props.messages.onDelete(response);
}
this.getItems();
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
});
},
handleEmptyTrash: function () {
handleEmptyTrash: function handleEmptyTrash() {
return this.handleBulkAction('all', {
action: 'delete',
group: 'trash',
}).done((response) => {
MailPoet.Notice.success(
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response.meta.count)
);
if (
this.props.messages !== undefined
&& this.props.messages.onDelete !== undefined
) {
this.props.messages.onDelete(response);
}
// redirect to default group
this.handleGroup('all');
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
});
},
handleBulkAction: function (selected_ids, params) {
handleBulkAction: function handleBulkAction(selectedIds, params) {
if (
this.state.selection === false
&& this.state.selected_ids.length === 0
&& selected_ids !== 'all'
&& selectedIds !== 'all'
) {
return false;
}
@ -619,29 +629,29 @@ const Listing = React.createClass({
group: this.state.group,
search: this.state.search,
};
if (selected_ids !== 'all') {
data.listing.selection = selected_ids;
if (selectedIds !== 'all') {
data.listing.selection = selectedIds;
}
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint,
action: 'bulkAction',
data: data,
data,
}).done(() => {
this.getItems();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
});
},
handleSearch: function (search) {
handleSearch: function handleSearch(search) {
this.setState({
search: search,
search,
page: 1,
selection: false,
selected_ids: [],
@ -649,20 +659,20 @@ const Listing = React.createClass({
this.setParams();
});
},
handleSort: function (sort_by, sort_order = 'asc') {
handleSort: function handleSort(sortBy, sortOrder = 'asc') {
this.setState({
sort_by: sort_by,
sort_order: (sort_order === 'asc') ? 'asc' : 'desc',
sort_by: sortBy,
sort_order: (sortOrder === 'asc') ? 'asc' : 'desc',
}, () => {
this.setParams();
});
},
handleSelectItem: function (id, is_checked) {
let selected_ids = this.state.selected_ids,
selection = false;
handleSelectItem: function handleSelectItem(id, isChecked) {
let selectedIds = this.state.selected_ids;
let selection = false;
if (is_checked) {
selected_ids = jQuery.merge(selected_ids, [id]);
if (isChecked) {
selectedIds = jQuery.merge(selectedIds, [id]);
// check whether all items on the page are selected
if (
jQuery('tbody .check-column :checkbox:not(:checked)').length === 0
@ -670,29 +680,27 @@ const Listing = React.createClass({
selection = 'page';
}
} else {
selected_ids.splice(selected_ids.indexOf(id), 1);
selectedIds.splice(selectedIds.indexOf(id), 1);
}
this.setState({
selection: selection,
selected_ids: selected_ids,
selection,
selected_ids: selectedIds,
});
},
handleSelectItems: function (is_checked) {
if (is_checked === false) {
handleSelectItems: function handleSelectItems(isChecked) {
if (isChecked === false) {
this.clearSelection();
} else {
const selected_ids = this.state.items.map((item) => {
return ~~item.id;
});
const selectedIds = this.state.items.map(item => Number(item.id));
this.setState({
selected_ids: selected_ids,
selected_ids: selectedIds,
selection: 'page',
});
}
},
handleSelectAll: function () {
handleSelectAll: function handleSelectAll() {
if (this.state.selection === 'all') {
this.clearSelection();
} else {
@ -702,13 +710,13 @@ const Listing = React.createClass({
});
}
},
clearSelection: function () {
clearSelection: function clearSelection() {
this.setState({
selection: false,
selected_ids: [],
});
},
handleFilter: function (filters) {
handleFilter: function handleFilter(filters) {
this.setState({
filter: filters,
page: 1,
@ -716,12 +724,12 @@ const Listing = React.createClass({
this.setParams();
});
},
handleGroup: function (group) {
handleGroup: function handleGroup(group) {
// reset search
jQuery('#search_input').val('');
this.setState({
group: group,
group,
filter: {},
search: '',
page: 1,
@ -729,38 +737,38 @@ const Listing = React.createClass({
this.setParams();
});
},
handleSetPage: function (page) {
handleSetPage: function handleSetPage(page) {
this.setState({
page: page,
page,
selection: false,
selected_ids: [],
}, () => {
this.setParams();
});
},
handleRenderItem: function (item, actions) {
handleRenderItem: function handleRenderItem(item, actions) {
const render = this.props.onRenderItem(item, actions, this.state.meta);
return render.props.children;
},
handleRefreshItems: function () {
handleRefreshItems: function handleRefreshItems() {
this.getItems();
},
render: function () {
render: function render() {
const items = this.state.items;
const sort_by = this.state.sort_by;
const sort_order = this.state.sort_order;
const sortBy = this.state.sort_by;
const sortOrder = this.state.sort_order;
// columns
let columns = this.props.columns || [];
columns = columns.filter((column) => {
return (column.display === undefined || !!(column.display) === true);
});
columns = columns.filter(
column => (column.display === undefined || !!(column.display) === true)
);
// bulk actions
let bulk_actions = this.props.bulk_actions || [];
let bulkActions = this.props.bulk_actions || [];
if (this.state.group === 'trash' && bulk_actions.length > 0) {
bulk_actions = [
if (this.state.group === 'trash' && bulkActions.length > 0) {
bulkActions = [
{
name: 'restore',
label: MailPoet.I18n.t('restore'),
@ -775,9 +783,9 @@ const Listing = React.createClass({
}
// 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',
'wp-list-table',
'widefat',
@ -814,6 +822,10 @@ const Listing = React.createClass({
if (this.props.messages !== undefined) {
messages = this.props.messages;
}
let extraActions;
if (typeof this.props.renderExtraActions === 'function') {
extraActions = this.props.renderExtraActions(this.state);
}
return (
<div>
@ -822,10 +834,11 @@ const Listing = React.createClass({
<div className="tablenav top clearfix">
<ListingBulkActions
count={this.state.count}
bulk_actions={bulk_actions}
bulk_actions={bulkActions}
selection={this.state.selection}
selected_ids={this.state.selected_ids}
onBulkAction={this.handleBulkAction} />
onBulkAction={this.handleBulkAction}
/>
<ListingFilters
filters={this.state.filters}
filter={this.state.filter}
@ -834,22 +847,25 @@ const Listing = React.createClass({
onSelectFilter={this.handleFilter}
onEmptyTrash={this.handleEmptyTrash}
/>
{extraActions}
<ListingPages
count={this.state.count}
page={this.state.page}
limit={this.state.limit}
onSetPage={this.handleSetPage} />
onSetPage={this.handleSetPage}
/>
</div>
<table className={table_classes}>
<table className={tableClasses}>
<thead>
<ListingHeader
onSort={this.handleSort}
onSelectItems={this.handleSelectItems}
selection={this.state.selection}
sort_by={sort_by}
sort_order={sort_order}
sort_by={sortBy}
sort_order={sortOrder}
columns={columns}
is_selectable={bulk_actions.length > 0} />
is_selectable={bulkActions.length > 0}
/>
</thead>
<ListingItems
@ -859,7 +875,7 @@ const Listing = React.createClass({
onTrashItem={this.handleTrashItem}
onRefreshItems={this.handleRefreshItems}
columns={columns}
is_selectable={bulk_actions.length > 0}
is_selectable={bulkActions.length > 0}
onSelectItem={this.handleSelectItem}
onSelectAll={this.handleSelectAll}
selection={this.state.selection}
@ -868,34 +884,38 @@ const Listing = React.createClass({
group={this.state.group}
count={this.state.count}
limit={this.state.limit}
item_actions={item_actions}
item_actions={itemActions}
messages={messages}
items={items} />
items={items}
/>
<tfoot>
<ListingHeader
onSort={this.handleSort}
onSelectItems={this.handleSelectItems}
selection={this.state.selection}
sort_by={sort_by}
sort_order={sort_order}
sort_by={sortBy}
sort_order={sortOrder}
columns={columns}
is_selectable={bulk_actions.length > 0} />
is_selectable={bulkActions.length > 0}
/>
</tfoot>
</table>
<div className="tablenav bottom">
<ListingBulkActions
count={this.state.count}
bulk_actions={bulk_actions}
bulk_actions={bulkActions}
selection={this.state.selection}
selected_ids={this.state.selected_ids}
onBulkAction={this.handleBulkAction} />
onBulkAction={this.handleBulkAction}
/>
<ListingPages
count={this.state.count}
page={this.state.page}
limit={this.state.limit}
onSetPage={this.handleSetPage} />
onSetPage={this.handleSetPage}
/>
</div>
</div>
);

View File

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

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
define('modal', ['mailpoet', 'jquery'],
function (mp, jQuery) {
'use strict';
@ -111,11 +112,11 @@ define('modal', ['mailpoet', 'jquery'],
compileTemplate: function (template) {
if (this.renderer === 'html') {
return function () { return template; };
} else {
return window.Handlebars.compile(template);
}
return window.Handlebars.compile(template);
},
init: function (options) {
var modal;
if (this.initialized === true) {
this.close();
}
@ -134,7 +135,7 @@ define('modal', ['mailpoet', 'jquery'],
if (this.options.type !== null) {
// insert modal depending on its type
if (this.options.type === 'popup') {
var modal = this.compileTemplate(
modal = this.compileTemplate(
this.templates[this.options.type]
);
// create modal
@ -178,7 +179,7 @@ define('modal', ['mailpoet', 'jquery'],
return this;
},
initOverlay: function (toggle) {
initOverlay: function () {
if (jQuery('#mailpoet_modal_overlay').length === 0) {
// insert overlay into the DOM
jQuery('body').append(this.templates.overlay);
@ -214,6 +215,7 @@ define('modal', ['mailpoet', 'jquery'],
jQuery(document).on('keyup.mailpoet_modal', function (e) {
if (this.opened === false) { return false; }
if (e.keyCode === 27) { this.cancel(); }
return true;
}.bind(this));
// make sure the popup is repositioned when the window is resized
@ -253,7 +255,7 @@ define('modal', ['mailpoet', 'jquery'],
// add sub panel wrapper
jQuery('#mailpoet_' + this.options.type)
.append(this.templates['subpanel']);
.append(this.templates.subpanel);
// add sub panel content
jQuery('.mailpoet_' + this.options.type + '_body').last()
@ -338,25 +340,27 @@ define('modal', ['mailpoet', 'jquery'],
}
jQuery('#mailpoet_panel').css({ minHeight: 'auto' });
break;
default: throw new Error('Incorrect type');
}
return this;
},
setPosition: function () {
var screenWidth;
var screenHeight;
var modalWidth;
var modalHeight;
switch (this.options.type) {
case 'popup':
var screenWidth = jQuery(window).width(),
screenHeight = jQuery(window).height(),
modalWidth = jQuery('.mailpoet_' + this.options.type + '_wrapper').width(),
modalHeight = jQuery('.mailpoet_' + this.options.type + '_wrapper').height();
screenWidth = jQuery(window).width();
screenHeight = jQuery(window).height();
modalWidth = jQuery('.mailpoet_'+ this.options.type +'_wrapper').width();
modalHeight = jQuery('.mailpoet_'+ this.options.type +'_wrapper').height();
var top = Math.max(48, parseInt((screenHeight / 2) - (modalHeight / 2))),
left = Math.max(0, parseInt((screenWidth / 2) - (modalWidth / 2)));
// set position of popup depending on screen dimensions.
// set position of popup depending on screen dimensions.
jQuery('#mailpoet_popup').css({
top: top,
left: left
top: Math.max(48, parseInt((screenHeight / 2) - (modalHeight / 2))),
left: Math.max(0, parseInt((screenWidth / 2) - (modalWidth / 2)))
});
break;
case 'panel':
@ -373,6 +377,7 @@ define('modal', ['mailpoet', 'jquery'],
}
}.bind(this), 0);
break;
default: throw new Error('Incorrect type');
}
return this;
@ -384,9 +389,6 @@ define('modal', ['mailpoet', 'jquery'],
// remember the previously focused element
this.prevFocus = jQuery(':focus');
// add a flag on the body so that we can prevent scrolling
jQuery('body').addClass('mailpoet_modal_opened');
// show popup
jQuery('#mailpoet_' + this.options.type).show();
@ -418,7 +420,7 @@ define('modal', ['mailpoet', 'jquery'],
return this;
},
focus: function () {
if (this.options.type == 'popup') {
if (this.options.type === 'popup') {
jQuery('#mailpoet_' + this.options.type).focus();
} else {
// panel and subpanel
@ -436,7 +438,7 @@ define('modal', ['mailpoet', 'jquery'],
.removeClass('mailpoet_modal_highlight');
return this;
},
hideModal: function (callback) {
hideModal: function () {
// set modal as closed
this.opened = false;
@ -446,12 +448,9 @@ define('modal', ['mailpoet', 'jquery'],
// remove class on highlighted elements
this.highlightOff();
// remove class from body to let it be scrollable
jQuery('body').removeClass('mailpoet_modal_opened');
return this;
},
showOverlay: function (force) {
showOverlay: function () {
jQuery('#mailpoet_modal_overlay').show();
return this;
},
@ -558,12 +557,12 @@ define('modal', ['mailpoet', 'jquery'],
success: function () {
if (this.subpanels.length > 0) {
if (this.subpanels[(this.subpanels.length - 1)].onSuccess !== undefined) {
this.subpanels[(this.subpanels.length - 1)].onSuccess(this.subpanels[(this.subpanels.length - 1)].data);
}
} else {
if (this.options.onSuccess !== null) {
this.options.onSuccess(this.options.data);
this
.subpanels[(this.subpanels.length - 1)]
.onSuccess(this.subpanels[(this.subpanels.length - 1)].data);
}
} else if (this.options.onSuccess !== null) {
this.options.onSuccess(this.options.data);
}
this.close();
@ -572,12 +571,12 @@ define('modal', ['mailpoet', 'jquery'],
cancel: function () {
if (this.subpanels.length > 0) {
if (this.subpanels[(this.subpanels.length - 1)].onCancel !== undefined) {
this.subpanels[(this.subpanels.length - 1)].onCancel(this.subpanels[(this.subpanels.length - 1)].data);
}
} else {
if (this.options.onCancel !== null) {
this.options.onCancel(this.options.data);
this
.subpanels[(this.subpanels.length - 1)]
.onCancel(this.subpanels[(this.subpanels.length - 1)].data);
}
} else if (this.options.onCancel !== null) {
this.options.onCancel(this.options.data);
}
this.close();

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
'use strict';
@ -41,12 +42,14 @@ define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
jQuery('#upgrade-completed').show();
}
jQuery('#logger').append(row + '<br />\n');
});
jQuery('#logger').append('<span class="error_msg">' + MailPoet.MP2Migrator.fatal_error + '</span>' + '<br />\n');
}).always(function () {
if (MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000);
MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(
MailPoet.MP2Migrator.displayLogs,
1000
);
}
});
},
@ -60,7 +63,7 @@ define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
// Move the progress bar
var progress = 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('#progresslabel').html(progress + '%');
@ -70,7 +73,10 @@ define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
jQuery('#logger-container').show();
}
if (MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000);
MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(
MailPoet.MP2Migrator.updateProgressbar,
1000
);
}
});
},
@ -97,7 +103,8 @@ define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
}
}).always(function () {
MailPoet.MP2Migrator.stopLogger();
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
// Get the latest information after the import was stopped
MailPoet.MP2Migrator.updateDisplay();
MailPoet.MP2Migrator.reactivateImportButton();
}).done(function (response) {
if (response) {
@ -133,7 +140,8 @@ define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
}).always(function () {
jQuery('#stop-import').removeAttr('disabled'); // Enable the button
MailPoet.MP2Migrator.reactivateImportButton();
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
// Get the latest information after the import was stopped
MailPoet.MP2Migrator.updateDisplay();
}).fail(function (response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
@ -206,5 +214,4 @@ define('mp2migrator', ['mailpoet', 'jquery'], function (mp, jQuery) {
// Update the display
MailPoet.MP2Migrator.updateDisplay();
});
});

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names */
/**
* ContainerDropZoneBehavior
*
@ -20,16 +20,19 @@ define([
columnLimit: 3
},
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) {
this.addDropZone();
}
},
addDropZone: function (_event) {
var that = this,
view = this.view,
domElement = that.$el.get(0),
acceptableElementSelector;
addDropZone: function () {
var that = this;
var view = this.view;
var domElement = that.$el.get(0);
var acceptableElementSelector;
// TODO: Extract this limitation code to be controlled from containers
if (this.view.renderOptions.depth === 0) {
@ -47,11 +50,11 @@ define([
interact(domElement).dropzone({
accept: acceptableElementSelector,
overlap: 'pointer', // Mouse pointer denotes location of a droppable
ondragenter: function (event) {
ondragenter: function () {
// 1. Visually mark block as active for dropping
view.$el.addClass('mailpoet_drop_active');
},
ondragleave: function (event) {
ondragleave: function () {
// 1. Remove visual markings of active dropping container
// 2. Remove visual markings of drop position visualization
that.cleanup();
@ -60,23 +63,32 @@ define([
// 1. Compute actual location of the mouse within the container
// 2. Check if insertion is regular (between blocks) or special (with container insertion)
// 3a. If insertion is regular, compute position where insertion should happen
// 3b. If insertion is special, compute position (which side) and which cell the insertion belongs to
// 4. If insertion at that position is not visualized, display position visualization there, remove other visualizations from this container
// 3b. If insertion is special,
// compute position (which side) and which cell the insertion belongs to
// 4. If insertion at that position is not visualized,
// display position visualization there,
// remove other visualizations from this container
var dropPosition = that.getDropPosition(
event.dragmove.pageX,
event.dragmove.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
),
element = view.$el,
markerWidth = '',
markerHeight = '',
containerOffset = element.offset(),
viewCollection = that.getCollection(),
marker, targetModel, targetView, targetElement,
topOffset, leftOffset, isLastBlockInsertion,
$targetBlock, margin;
event.dragmove.pageX,
event.dragmove.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
);
var element = view.$el;
var markerWidth = '';
var markerHeight = '';
var containerOffset = element.offset();
var viewCollection = that.getCollection();
var marker;
var targetModel;
var targetView;
var targetElement;
var topOffset;
var leftOffset;
var isLastBlockInsertion;
var $targetBlock;
var margin;
if (dropPosition === undefined) return;
@ -93,7 +105,11 @@ define([
markerHeight = targetElement.height();
} else {
isLastBlockInsertion = that.getCollection().length === dropPosition.index;
targetModel = isLastBlockInsertion ? viewCollection.at(dropPosition.index - 1) : viewCollection.at(dropPosition.index);
if (isLastBlockInsertion) {
targetModel = viewCollection.at(dropPosition.index - 1);
} else {
targetModel = viewCollection.at(dropPosition.index);
}
targetView = that.getChildren().findByModel(targetModel);
targetElement = targetView.$el;
@ -150,9 +166,15 @@ define([
// compensated for to position marker right in the middle of two
// blocks
if (dropPosition.position === 'before') {
$targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index - 1)).$el;
$targetBlock = that
.getChildren()
.findByModel(viewCollection.at(dropPosition.index - 1))
.$el;
} else {
$targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index)).$el;
$targetBlock = that
.getChildren()
.findByModel(viewCollection.at(dropPosition.index))
.$el;
}
margin = $targetBlock.outerHeight(true) - $targetBlock.outerHeight();
@ -173,20 +195,25 @@ define([
// 3b1. compute position (which side) and which cell the insertion belongs to
// 3b2. remove element at that position from the collection
// 3b3. create a new collection, insert the removed element to it
// 3b4. insert the droppable model at the start or end of the new collection, depending on 3b1. position
// 3b4. insert the droppable model at the start or end of the new collection,
// depending on 3b1. position
// 3b5. insert the new collection into the old collection to cell from 3b1.
// 4. Perform cleanup actions
var dropPosition = that.getDropPosition(
event.dragEvent.pageX,
event.dragEvent.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
),
droppableModel = event.draggable.getDropModel(),
viewCollection = that.getCollection(),
droppedView, droppedModel, index, tempCollection, tempCollection2;
event.dragEvent.pageX,
event.dragEvent.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
);
var droppableModel = event.draggable.getDropModel();
var viewCollection = that.getCollection();
var droppedView;
var index;
var tempCollection;
var tempCollection2;
var tempModel;
if (dropPosition === undefined) return;
@ -210,7 +237,7 @@ define([
} else {
// Special insertion by replacing target block with collection
// and inserting dropModel into that
var tempModel = viewCollection.at(dropPosition.index);
tempModel = viewCollection.at(dropPosition.index);
tempCollection = new (window.EditorApplication.getBlockTypeModel('container'))({
orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical'
@ -250,7 +277,11 @@ define([
viewCollection.add(tempCollection, { at: dropPosition.index });
// Call post add actions
droppedView = that.getChildren().findByModel(tempCollection).children.findByModel(droppableModel);
droppedView = that
.getChildren()
.findByModel(tempCollection)
.children
.findByModel(droppableModel);
}
// Call post add actions
@ -271,29 +302,33 @@ define([
// 2. Remove visual markings of drop position visualization
this.view.$('.mailpoet_drop_marker').remove();
},
getDropPosition: function (eventX, eventY, is_unsafe) {
var SPECIAL_AREA_INSERTION_WIDTH = 0.00, // Disable special insertion. Default: 0.3
getDropPosition: function (eventX, eventY, isUnsafe) {
var SPECIAL_AREA_INSERTION_WIDTH = 0.00; // Disable special insertion. Default: 0.3
element = this.view.$el,
orientation = this.view.model.get('orientation'),
var element = this.view.$el;
var orientation = this.view.model.get('orientation');
elementOffset = element.offset(),
elementPageX = elementOffset.left,
elementPageY = elementOffset.top,
elementWidth = element.outerWidth(true),
elementHeight = element.outerHeight(true),
var elementOffset = element.offset();
var elementPageX = elementOffset.left;
var elementPageY = elementOffset.top;
var elementWidth = element.outerWidth(true);
var elementHeight = element.outerHeight(true);
relativeX = eventX - elementPageX,
relativeY = eventY - elementPageY,
var relativeX = eventX - elementPageX;
var relativeY = eventY - elementPageY;
relativeOffset, elementLength,
var relativeOffset;
var elementLength;
canAcceptNormalInsertion = this._canAcceptNormalInsertion(),
canAcceptSpecialInsertion = this._canAcceptSpecialInsertion(),
var canAcceptNormalInsertion = this._canAcceptNormalInsertion();
var canAcceptSpecialInsertion = this._canAcceptSpecialInsertion();
insertionType, index, position, indexAndPosition;
var insertionType;
var index;
var position;
var indexAndPosition;
var unsafe = !!is_unsafe;
var unsafe = !!isUnsafe;
if (this.getCollection().length === 0) {
return {
@ -317,11 +352,17 @@ define([
SPECIAL_AREA_INSERTION_WIDTH = 0.5;
}
if (relativeOffset <= elementLength * SPECIAL_AREA_INSERTION_WIDTH && (unsafe || canAcceptSpecialInsertion)) {
if (
relativeOffset <= elementLength * SPECIAL_AREA_INSERTION_WIDTH
&& (unsafe || canAcceptSpecialInsertion)
) {
insertionType = 'special';
position = 'before';
index = this._computeSpecialIndex(eventX, eventY);
} else if (relativeOffset > elementLength * (1 - SPECIAL_AREA_INSERTION_WIDTH) && (unsafe || canAcceptSpecialInsertion)) {
} else if (
relativeOffset > elementLength * (1 - SPECIAL_AREA_INSERTION_WIDTH)
&& (unsafe || canAcceptSpecialInsertion)
) {
insertionType = 'special';
position = 'after';
index = this._computeSpecialIndex(eventX, eventY);
@ -344,7 +385,7 @@ define([
if (orientation === 'horizontal' && insertionType === 'special') {
// Disable special insertion for horizontal containers
return;
return undefined;
}
return {
@ -359,12 +400,14 @@ define([
// target element if event happens on the second half of the element.
// Halves depend on orientation.
var index = this._computeCellIndex(eventX, eventY),
// TODO: Handle case when there are no children, container is empty
targetView = this.getChildren().findByModel(this.getCollection().at(index)),
orientation = this.view.model.get('orientation'),
element = targetView.$el,
eventOffset, closeOffset, elementDimension;
var index = this._computeCellIndex(eventX, eventY);
// TODO: Handle case when there are no children, container is empty
var targetView = this.getChildren().findByModel(this.getCollection().at(index));
var orientation = this.view.model.get('orientation');
var element = targetView.$el;
var eventOffset;
var closeOffset;
var elementDimension;
if (orientation === 'vertical') {
eventOffset = eventY;
@ -376,57 +419,57 @@ define([
elementDimension = element.outerWidth(true);
}
if (eventOffset <= closeOffset + elementDimension / 2) {
if (eventOffset <= closeOffset + (elementDimension / 2)) {
// First half of the element
return {
index: index,
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) {
return this._computeCellIndex(eventX, eventY);
},
_computeCellIndex: function (eventX, eventY) {
var orientation = this.view.model.get('orientation'),
eventOffset = (orientation === 'vertical') ? eventY : eventX,
resultView = this.getChildren().find(function (view) {
var element = view.$el,
closeOffset, farOffset;
var orientation = this.view.model.get('orientation');
var eventOffset = (orientation === 'vertical') ? eventY : eventX;
var resultView = this.getChildren().find(function (view) {
var element = view.$el;
var closeOffset;
var farOffset;
if (orientation === 'vertical') {
closeOffset = element.offset().top;
farOffset = element.outerHeight(true);
} else {
closeOffset = element.offset().left;
farOffset = element.outerWidth(true);
}
farOffset += closeOffset;
if (orientation === 'vertical') {
closeOffset = element.offset().top;
farOffset = element.outerHeight(true);
} else {
closeOffset = element.offset().left;
farOffset = element.outerWidth(true);
}
farOffset += closeOffset;
return closeOffset <= eventOffset && eventOffset <= farOffset;
});
return closeOffset <= eventOffset && eventOffset <= farOffset;
});
var index = (typeof resultView === 'object') ? resultView._index : 0;
return index;
},
_canAcceptNormalInsertion: function () {
var orientation = this.view.model.get('orientation'),
depth = this.view.renderOptions.depth,
childCount = this.getChildren().length;
var orientation = this.view.model.get('orientation');
var depth = this.view.renderOptions.depth;
var childCount = this.getChildren().length;
// Note that depth is zero indexed. Root container has depth=0
return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit);
},
_canAcceptSpecialInsertion: function () {
var orientation = this.view.model.get('orientation'),
depth = this.view.renderOptions.depth,
childCount = this.getChildren().length;
var orientation = this.view.model.get('orientation');
var depth = this.view.renderOptions.depth;
var childCount = this.getChildren().length;
return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit);
},
getCollectionView: function () {

View File

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

View File

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

View File

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

View File

@ -7,19 +7,21 @@ define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup',
'interact'
], function (Marionette, BehaviorsLookup, interact) {
], function (Marionette, BehaviorsLookup, interact) { // eslint-disable-line func-names
var BL = BehaviorsLookup;
BL.ResizableBehavior = Marionette.Behavior.extend({
defaults: {
elementSelector: null,
resizeHandleSelector: true, // true will use edges of the element itself
// for blocks that use the default onResize function
transformationFunction: function transformationFunction(y) { return y; },
minLength: 0,
maxLength: Infinity,
modelField: 'styles.block.height',
onResize: function (event) {
var currentLength = parseFloat(this.view.model.get(this.options.modelField)),
newLength = currentLength + event.y;
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');
}
@ -28,16 +30,21 @@ define([
mouseenter: 'showResizeHandle',
mouseleave: 'hideResizeHandle'
},
onRender: function () {
onRender: function () { // eslint-disable-line func-names
this.attachResize();
if (this.isBeingResized !== true) {
this.hideResizeHandle();
}
},
attachResize: function () {
var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0),
that = this;
attachResize: function () { // eslint-disable-line func-names
var domElement;
var that = this;
if (this.options.elementSelector === null) {
domElement = this.view.$el.get(0);
} else {
domElement = this.view.$(this.options.elementSelector).get(0);
}
interact(domElement).resizable({
// axis: 'y',
edges: {
@ -47,25 +54,24 @@ define([
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.$el.addClass('mailpoet_resize_active');
})
.on('resizemove', function (event) {
}).on('resizemove', function (event) { // eslint-disable-line func-names
var onResize = that.options.onResize.bind(that);
return onResize(event);
})
.on('resizeend', function (event) {
.on('resizeend', function () { // eslint-disable-line func-names
that.isBeingResized = null;
that.$el.removeClass('mailpoet_resize_active');
});
},
showResizeHandle: function () {
showResizeHandle: function () { // eslint-disable-line func-names
if (typeof this.options.resizeHandleSelector === 'string') {
this.view.$(this.options.resizeHandleSelector).removeClass('mailpoet_hidden');
}
},
hideResizeHandle: function () {
hideResizeHandle: function () { // eslint-disable-line func-names
if (typeof this.options.resizeHandleSelector === 'string') {
this.view.$(this.options.resizeHandleSelector).addClass('mailpoet_hidden');
}

View File

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

View File

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

View File

@ -7,7 +7,7 @@ define([
'backbone.marionette',
'underscore',
'newsletter_editor/behaviors/BehaviorsLookup'
], function (Marionette, _, BehaviorsLookup) {
], function textEditorBehavior(Marionette, _, BehaviorsLookup) {
var BL = BehaviorsLookup;
BL.TextEditorBehavior = Marionette.Behavior.extend({
@ -19,9 +19,9 @@ define([
invalidElements: 'script',
blockFormats: 'Paragraph=p',
plugins: 'link textcolor colorpicker mailpoet_shortcodes',
configurationFilter: function (originalConfig) { return originalConfig; }
configurationFilter: function configurationFilter(originalConfig) { return originalConfig; }
},
onDomRefresh: function () {
onDomRefresh: function onDomRefresh() {
var that = this;
if (this.view.disableTextEditor === true) {
return;
@ -42,7 +42,7 @@ define([
relative_urls: false,
remove_script_host: false,
convert_urls: true,
urlconverter_callback: function (url, node, on_save, name) {
urlconverter_callback: function urlconverterCallback(url) {
if (url.match(/\[.+\]/g)) {
// Do not convert URLs with shortcodes
return url;
@ -56,27 +56,31 @@ define([
plugins: this.options.plugins,
setup: function (editor) {
editor.on('change', function (e) {
setup: function setup(editor) {
editor.on('change', function onChange() {
that.view.triggerMethod('text:editor:change', editor.getContent());
});
editor.on('click', function (e) {
editor.on('click', function onClick(e) {
editor.focus();
if (that._isActivationClick) {
editor.selection.setRng(
window.tinymce.dom.RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc())
window.tinymce.dom.RangeUtils.getCaretRangeFromPoint(
e.clientX,
e.clientY,
editor.getDoc()
)
);
that._isActivationClick = false;
}
});
editor.on('focus', function (e) {
editor.on('focus', function onFocus() {
that.view.triggerMethod('text:editor:focus');
that._isActivationClick = true;
});
editor.on('blur', function (e) {
editor.on('blur', function onBlur() {
that.view.triggerMethod('text:editor:blur');
});
}

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/**
* Automated latest content block.
* Only query parameters can be modified by the user. Posts pulled by this
@ -27,11 +28,10 @@ define([
_,
jQuery
) {
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.ALCSupervisor = SuperModel.extend({
initialize: function () {
@ -43,12 +43,13 @@ define([
);
},
refresh: function () {
var blocks;
var models = App.findModels(function (model) {
return model.get('type') === 'automatedLatestContent';
}) || [];
if (models.length === 0) return;
var blocks = _.map(models, function (model) {
blocks = _.map(models, function (model) {
return model.toJSON();
});
@ -60,8 +61,8 @@ define([
_.each(
_.zip(models, renderedBlocks),
function (args) {
var model = args[0],
contents = args[1];
var model = args[0];
var contents = args[1];
model.trigger('refreshPosts', contents);
}
);
@ -83,7 +84,6 @@ define([
titleIsLink: false, // false|true
imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
// imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:',
showCategories: 'no', // 'no'|'aboveText'|'belowText'
@ -109,10 +109,10 @@ define([
},
initialize: function () {
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.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
this.on('add remove update reset', this._scheduleFetchPosts);
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._handleChanges, this);
this.listenTo(this.get('readMoreButton'), 'change', this._handleChanges);
this.listenTo(this.get('divider'), 'change', this._handleChanges);
this.on('add remove update reset', this._handleChanges);
this.on('refreshPosts', this.updatePosts, this);
},
updatePosts: function (posts) {
@ -122,7 +122,8 @@ define([
* Batch more changes during a specific time, instead of fetching
* ALC posts on each model change
*/
_scheduleFetchPosts: function () {
_handleChanges: function () {
this._updateDefaults();
App.getChannel().trigger('automatedLatestContentRefresh');
}
});
@ -150,12 +151,12 @@ define([
}),
onDragSubstituteBy: function () { return Module.AutomatedLatestContentWidgetView; },
onRender: function () {
var ContainerView = App.getBlockTypeView('container'),
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
var ContainerView = App.getBlockTypeView('container');
var renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView);
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions }));
@ -213,12 +214,13 @@ define([
},
transport: function (options, success, failure) {
var taxonomies;
var termsPromise;
var promise = CommunicationComponent.getTaxonomies(
that.model.get('contentType')
).then(function (tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = CommunicationComponent.getTerms({
termsPromise = CommunicationComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function (terms) {
@ -227,7 +229,7 @@ define([
terms: terms
};
});
return promise;
return termsPromise;
});
promise.then(success);
@ -264,9 +266,9 @@ define([
}
}).trigger('change');
},
toggleDisplayOptions: function (event) {
var el = this.$('.mailpoet_automated_latest_content_display_options'),
showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
toggleDisplayOptions: function () {
var el = this.$('.mailpoet_automated_latest_content_display_options');
var showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
if (el.hasClass('mailpoet_closed')) {
el.removeClass('mailpoet_closed');
showControl.addClass('mailpoet_hidden');
@ -275,7 +277,7 @@ define([
showControl.removeClass('mailpoet_hidden');
}
},
showButtonSettings: function (event) {
showButtonSettings: function () {
var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'),
@ -286,7 +288,7 @@ define([
}
})).render();
},
showDividerSettings: function (event) {
showDividerSettings: function () {
var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'),
@ -298,10 +300,10 @@ define([
},
changeReadMoreType: function (event) {
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_select_button').addClass('mailpoet_hidden');
} else if (value == 'button') {
} else if (value === 'button') {
this.$('.mailpoet_automated_latest_content_read_more_text').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_select_button').removeClass('mailpoet_hidden');
}
@ -310,7 +312,7 @@ define([
changeDisplayType: function (event) {
var value = jQuery(event.target).val();
if (value == 'titleOnly') {
if (value === 'titleOnly') {
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_separator').addClass('mailpoet_hidden');
@ -336,7 +338,7 @@ define([
},
changeTitleFormat: function (event) {
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.model.set('titleIsLink', true);
@ -349,8 +351,8 @@ define([
this.changeField('titleFormat', event);
},
_updateContentTypes: function (postTypes) {
var select = this.$('.mailpoet_automated_latest_content_content_type'),
selectedValue = this.model.get('contentType');
var select = this.$('.mailpoet_automated_latest_content_content_type');
var selectedValue = this.model.get('contentType');
select.find('option').remove();
_.each(postTypes, function (type) {
@ -378,21 +380,21 @@ define([
}
});
App.on('before:start', function (App, options) {
App.registerBlockType('automatedLatestContent', {
App.on('before:start', function (BeforeStartApp) {
BeforeStartApp.registerBlockType('automatedLatestContent', {
blockModel: Module.AutomatedLatestContentBlockModel,
blockView: Module.AutomatedLatestContentBlockView
});
App.registerWidget({
BeforeStartApp.registerWidget({
name: 'automatedLatestContent',
widgetView: Module.AutomatedLatestContentWidgetView,
priority: 97
});
});
App.on('start', function (App, options) {
var Application = App;
App.on('start', function (StartApp) {
var Application = StartApp;
Application._ALCSupervisor = new Module.ALCSupervisor();
Application._ALCSupervisor.refresh();
});

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/**
* Button content block
*/
@ -8,11 +9,10 @@ define([
'underscore',
'jquery'
], function (App, BaseBlock, MailPoet, _, jQuery) {
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.ButtonBlockModel = base.BlockModel.extend({
defaults: function () {
@ -132,13 +132,13 @@ define([
}
});
App.on('before:start', function (App, options) {
App.registerBlockType('button', {
App.on('before:start', function (BeforeStartApp) {
BeforeStartApp.registerBlockType('button', {
blockModel: Module.ButtonBlockModel,
blockView: Module.ButtonBlockView
});
App.registerWidget({
BeforeStartApp.registerWidget({
name: 'button',
widgetView: Module.ButtonWidgetView,
priority: 92

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/**
* Container content block.
* This is a special kind of block, as it can contain content blocks, as well
@ -11,12 +12,11 @@ define([
'newsletter_editor/App',
'newsletter_editor/blocks/base'
], function (Backbone, Marionette, _, jQuery, App, BaseBlock) {
'use strict';
var Module = {},
base = BaseBlock,
BlockCollection;
var Module = {};
var base = BaseBlock;
var BlockCollection;
BlockCollection = Backbone.Collection.extend({
model: base.BlockModel,
@ -24,7 +24,6 @@ define([
this.on('add change remove', function () { App.getChannel().trigger('autoSave'); });
},
parse: function (response) {
var self = this;
return _.map(response, function (block) {
var Type = App.getBlockTypeModel(block.type);
// TODO: If type has no registered model, use a backup one
@ -55,6 +54,7 @@ define([
if (invalidBlock) {
return invalidBlock.validationError;
}
return undefined;
},
parse: function (response) {
// If container has any blocks - add them to a collection
@ -66,7 +66,7 @@ define([
return response;
},
getChildren: function () {
var models = this.get('blocks').map(function (model, index, list) {
var models = this.get('blocks').map(function (model) {
return [model, model.getChildren()];
});
@ -121,7 +121,8 @@ define([
options.dragBehavior.view.model.destroy();
},
onDragSubstituteBy: function (behavior) {
var WidgetView, node;
var WidgetView;
var node;
// When block is being dragged, display the widget icon instead.
// This will create an instance of block's widget view and
// use it's rendered DOM element instead of the content block
@ -132,6 +133,7 @@ define([
WidgetView.destroy();
return node;
}
return undefined;
},
testAttachToInstance: function (model, view) {
// Attach Draggable only to layout containers and disable it
@ -149,7 +151,6 @@ define([
if (this.model.get('blocks').length === 2) return Module.TwoColumnContainerWidgetView;
}
return Module.OneColumnContainerWidgetView;
},
initialize: function (options) {
base.BlockView.prototype.initialize.apply(this, arguments);
@ -174,7 +175,9 @@ define([
}));
// TODO: Look for a better way to do this than here
// 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'));
},
showTools: function () {
@ -190,24 +193,24 @@ define([
}
},
toggleEditingLayer: function (event) {
var that = this,
$toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector'),
$overlay = jQuery('.mailpoet_layer_overlay'),
$container = this.$('> .mailpoet_container'),
enableContainerLayer = function () {
that.$el.addClass('mailpoet_container_layer_active');
$toggleButton.addClass('mailpoet_container_layer_active');
$container.addClass('mailpoet_layer_highlight');
$overlay.click(disableContainerLayer);
$overlay.show();
},
disableContainerLayer = function () {
that.$el.removeClass('mailpoet_container_layer_active');
$toggleButton.removeClass('mailpoet_container_layer_active');
$container.removeClass('mailpoet_layer_highlight');
$overlay.hide();
$overlay.off('click');
};
var that = this;
var $toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector');
var $overlay = jQuery('.mailpoet_layer_overlay');
var $container = this.$('> .mailpoet_container');
var disableContainerLayer = function () {
that.$el.removeClass('mailpoet_container_layer_active');
$toggleButton.removeClass('mailpoet_container_layer_active');
$container.removeClass('mailpoet_layer_highlight');
$overlay.hide();
$overlay.off('click');
};
var enableContainerLayer = function () {
that.$el.addClass('mailpoet_container_layer_active');
$toggleButton.addClass('mailpoet_container_layer_active');
$container.addClass('mailpoet_layer_highlight');
$overlay.click(disableContainerLayer);
$overlay.show();
};
if ($toggleButton.hasClass('mailpoet_container_layer_active')) {
disableContainerLayer();
} else {
@ -336,25 +339,25 @@ define([
}
});
App.on('before:start', function (App, options) {
App.registerBlockType('container', {
App.on('before:start', function (BeforeStartApp) {
BeforeStartApp.registerBlockType('container', {
blockModel: Module.ContainerBlockModel,
blockView: Module.ContainerBlockView
});
App.registerLayoutWidget({
BeforeStartApp.registerLayoutWidget({
name: 'oneColumnLayout',
priority: 100,
widgetView: Module.OneColumnContainerWidgetView
});
App.registerLayoutWidget({
BeforeStartApp.registerLayoutWidget({
name: 'twoColumnLayout',
priority: 100,
widgetView: Module.TwoColumnContainerWidgetView
});
App.registerLayoutWidget({
BeforeStartApp.registerLayoutWidget({
name: 'threeColumnLayout',
priority: 100,
widgetView: Module.ThreeColumnContainerWidgetView

View File

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

View File

@ -1,3 +1,5 @@
'use strict';
/**
* Footer content block
*/
@ -6,15 +8,12 @@ define([
'newsletter_editor/blocks/base',
'underscore',
'mailpoet'
], function (App, BaseBlock, _, MailPoet) {
'use strict';
var Module = {},
base = BaseBlock;
], function footerBlock(App, BaseBlock, _, MailPoet) {
var Module = {};
var base = BaseBlock;
Module.FooterBlockModel = base.BlockModel.extend({
defaults: function () {
defaults: function defaults() {
return this._getDefaults({
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>',
@ -34,18 +33,21 @@ define([
}
}
}, App.getConfig().get('blockDefaults.footer'));
},
_updateDefaults: function updateDefaults() {
App.getConfig().set('blockDefaults.footer', _.omit(this.toJSON(), 'text'));
}
});
Module.FooterBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_footer_block mailpoet_droppable_block',
getTemplate: function () { return window.templates.footerBlock; },
getTemplate: function getTemplate() { return window.templates.footerBlock; },
modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render'
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
TextEditorBehavior: {
configurationFilter: function (originalSettings) {
configurationFilter: function configurationFilter(originalSettings) {
return _.extend({}, originalSettings, {
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
@ -53,37 +55,37 @@ define([
}
}
}),
onDragSubstituteBy: function () { return Module.FooterWidgetView; },
onRender: function () {
onDragSubstituteBy: function onDragSubstituteBy() { return Module.FooterWidgetView; },
onRender: function onRender() {
this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView);
},
onTextEditorChange: function (newContent) {
onTextEditorChange: function onTextEditorChange(newContent) {
this.model.set('text', newContent);
},
onTextEditorFocus: function () {
onTextEditorFocus: function onTextEditorFocus() {
this.disableDragging();
this.disableShowingTools();
},
onTextEditorBlur: function () {
onTextEditorBlur: function onTextEditorBlur() {
this.enableDragging();
this.enableShowingTools();
}
});
Module.FooterBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function () { return Module.FooterBlockSettingsView; }
getSettingsView: function getSettingsView() { return Module.FooterBlockSettingsView; }
});
Module.FooterBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function () { return window.templates.footerBlockSettings; },
events: function () {
getTemplate: function getTemplate() { return window.templates.footerBlockSettings; },
events: function events() {
return {
'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_size': _.partial(this.changeField, 'styles.text.fontSize'),
'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 linkUnderline(event) {
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'),
@ -91,7 +93,7 @@ define([
'click .mailpoet_done_editing': 'close'
};
},
templateContext: function () {
templateContext: function templateContext() {
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
availableStyles: App.getAvailableStyles().toJSON()
});
@ -99,24 +101,24 @@ define([
});
Module.FooterWidgetView = base.WidgetView.extend({
getTemplate: function () { return window.templates.footerInsertion; },
getTemplate: function getTemplate() { return window.templates.footerInsertion; },
behaviors: {
DraggableBehavior: {
cloneOriginal: true,
drop: function () {
drop: function drop() {
return new Module.FooterBlockModel();
}
}
}
});
App.on('before:start', function (App, options) {
App.registerBlockType('footer', {
App.on('before:start', function beforeAppStart(BeforeStartApp) {
BeforeStartApp.registerBlockType('footer', {
blockModel: Module.FooterBlockModel,
blockView: Module.FooterBlockView
});
App.registerWidget({
BeforeStartApp.registerWidget({
name: 'footer',
widgetView: Module.FooterWidgetView,
priority: 99

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/**
* Image content block
*/
@ -8,12 +9,11 @@ define([
'mailpoet',
'jquery'
], function (App, BaseBlock, _, MailPoet, jQuery) {
'use strict';
var Module = {},
base = BaseBlock,
ImageWidgetView;
var Module = {};
var base = BaseBlock;
var ImageWidgetView;
Module.ImageBlockModel = base.BlockModel.extend({
defaults: function () {
@ -31,7 +31,8 @@ define([
}
}
}, App.getConfig().get('blockDefaults.image'));
}
},
_updateDefaults: function () {}
});
Module.ImageBlockView = base.BlockView.extend({
@ -48,8 +49,8 @@ define([
elementSelector: '.mailpoet_image',
resizeHandleSelector: '.mailpoet_image_resize_handle',
onResize: function (event) {
var corner = this.$('.mailpoet_image').offset(),
width = event.pageX - corner.left;
var corner = this.$('.mailpoet_image').offset();
var width = event.pageX - corner.left;
this.view.model.set('width', width + 'px');
}
},
@ -127,13 +128,16 @@ define([
}
},
showMediaManager: function () {
var that = this;
var MediaManager;
var theFrame;
if (this._mediaManager) {
this._mediaManager.resetSelections();
this._mediaManager.open();
return;
}
var MediaManager = window.wp.media.view.MediaFrame.Select.extend({
MediaManager = window.wp.media.view.MediaFrame.Select.extend({
initialize: function () {
window.wp.media.view.MediaFrame.prototype.initialize.apply(this, arguments);
@ -196,6 +200,7 @@ define([
},
bindHandlers: function () {
var handlers;
// from Select
this.on('router:create:browse', this.createRouter, this);
this.on('router:render:browse', this.browseRouter, this);
@ -210,7 +215,7 @@ define([
this.on('updateExcluded', this.browseContent, this);
var handlers = {
handlers = {
content: {
embed: 'embedContent',
'edit-selection': 'editSelectionContent'
@ -244,9 +249,9 @@ define([
},
editSelectionContent: function () {
var state = this.state(),
selection = state.get('selection'),
view;
var state = this.state();
var selection = state.get('selection');
var view;
view = new window.wp.media.view.AttachmentsBrowser({
controller: this,
@ -302,8 +307,8 @@ define([
requires: { selection: true },
click: function () {
var state = controller.state(),
selection = state.get('selection');
var state = controller.state();
var selection = state.get('selection');
controller.close();
state.trigger('insert', selection).reset();
@ -321,46 +326,45 @@ define([
});
var theFrame = new MediaManager({
id: 'mailpoet-media-manager',
frame: 'select',
title: 'Select image',
editing: false,
multiple: false,
library: {
type: 'image'
},
displaySettings: false,
button: {
text: 'Select'
}
}),
that = this;
theFrame = new MediaManager({
id: 'mailpoet-media-manager',
frame: 'select',
title: 'Select image',
editing: false,
multiple: false,
library: {
type: 'image'
},
displaySettings: false,
button: {
text: 'Select'
}
});
this._mediaManager = theFrame;
this._mediaManager.on('insert', function () {
// Append media manager image selections to Images tab
var selection = theFrame.state().get('selection');
selection.each(function (attachment) {
var sizes = attachment.get('sizes'),
// Following advice from Becs, the target width should
// be a double of one column width to render well on
// retina screen devices
targetImageWidth = 1320,
var sizes = attachment.get('sizes');
// Following advice from Becs, the target width should
// be a double of one column width to render well on
// retina screen devices
var targetImageWidth = 1320;
// For main image use the size, that's closest to being 660px in width
sizeKeys = _.keys(sizes),
// Pick the width that is closest to target width
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
increasingByWidthDifference = _.sortBy(
_.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; });
// Maximize the height if there are multiple images with same width
var mainSize = _.max(imagesOfBestWidth, function (size) { return size.height; });
that.model.set({
height: mainSize.height + 'px',
@ -413,13 +417,13 @@ define([
});
Module.ImageWidgetView = ImageWidgetView;
App.on('before:start', function (App, options) {
App.registerBlockType('image', {
App.on('before:start', function (BeforeStartApp) {
BeforeStartApp.registerBlockType('image', {
blockModel: Module.ImageBlockModel,
blockView: Module.ImageBlockView
});
App.registerWidget({
BeforeStartApp.registerWidget({
name: 'image',
widgetView: Module.ImageWidgetView,
priority: 91

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/**
* Posts block.
*
@ -36,11 +37,15 @@ define([
ButtonBlock,
DividerBlock
) {
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
var PostsDisplayOptionsSettingsView;
var SinglePostSelectionSettingsView;
var EmptyPostSelectionSettingsView;
var PostSelectionSettingsView;
var PostsSelectionCollectionView;
Module.PostsBlockModel = base.BlockModel.extend({
stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
@ -60,7 +65,6 @@ define([
titleIsLink: false, // false|true
imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
// imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:',
showCategories: 'no', // 'no'|'aboveText'|'belowText'
@ -89,15 +93,21 @@ define([
};
},
initialize: function () {
var that = this,
POST_REFRESH_DELAY_MS = 500,
refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS),
refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
var POST_REFRESH_DELAY_MS = 500;
var refreshAvailablePosts = _.debounce(
this.fetchAvailablePosts.bind(this),
POST_REFRESH_DELAY_MS
);
var refreshTransformedPosts = _.debounce(
this._refreshTransformedPosts.bind(this),
POST_REFRESH_DELAY_MS
);
// Attach Radio.Requests API primarily for highlighting
_.extend(this, Radio.Requests);
this.fetchAvailablePosts();
this.on('change', this._updateDefaults, this);
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
this.on('loadMorePosts', this._loadMorePosts, this);
@ -120,9 +130,9 @@ define([
});
},
_loadMorePosts: function () {
var that = this,
postCount = this.get('_availablePosts').length,
nextOffset = this.get('offset') + Number(this.get('amount'));
var that = this;
var postCount = this.get('_availablePosts').length;
var nextOffset = this.get('offset') + Number(this.get('amount'));
if (postCount === 0 || postCount < nextOffset) {
// No more posts to load
@ -139,10 +149,11 @@ define([
}).always(function () {
that.trigger('morePostsLoaded');
});
return true;
},
_refreshTransformedPosts: function () {
var that = this,
data = this.toJSON();
var that = this;
var data = this.toJSON();
data.posts = this.get('_selectedPosts').pluck('ID');
@ -158,10 +169,9 @@ define([
});
},
_insertSelectedPosts: function () {
var that = this,
data = this.toJSON(),
index = this.collection.indexOf(this),
collection = this.collection;
var data = this.toJSON();
var index = this.collection.indexOf(this);
var collection = this.collection;
data.posts = this.get('_selectedPosts').pluck('ID');
@ -190,17 +200,19 @@ define([
this.model.reply('blockView', this.notifyAboutSelf, this);
},
onRender: function () {
var ContainerView;
var renderOptions;
if (!this.getRegion('toolsRegion').hasView()) {
this.showChildView('toolsRegion', this.toolsView);
}
this.trigger('showSettings');
var ContainerView = App.getBlockTypeView('container'),
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
ContainerView = App.getBlockTypeView('container');
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
},
notifyAboutSelf: function () {
@ -237,8 +249,8 @@ define([
this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model });
},
onRender: function () {
var that = this,
blockView = this.model.request('blockView');
var that = this;
this.model.request('blockView');
this.showChildView('selectionRegion', this.selectionView);
this.showChildView('displayOptionsRegion', this.displayOptionsView);
@ -283,7 +295,7 @@ define([
}
});
var PostsSelectionCollectionView = Marionette.CollectionView.extend({
PostsSelectionCollectionView = Marionette.CollectionView.extend({
className: 'mailpoet_post_scroll_container',
childView: function () { return SinglePostSelectionSettingsView; },
emptyView: function () { return EmptyPostSelectionSettingsView; },
@ -307,7 +319,7 @@ define([
}
});
var PostSelectionSettingsView = Marionette.View.extend({
PostSelectionSettingsView = Marionette.View.extend({
getTemplate: function () { return window.templates.postSelectionPostsBlockSettings; },
regions: {
posts: '.mailpoet_post_selection_container'
@ -333,10 +345,16 @@ define([
this.$('.mailpoet_post_selection_loading').css('visibility', 'hidden');
}
},
templateContext: function () {
return {
model: this.model.toJSON()
};
},
onRender: function () {
var postsView;
// Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
var postsView = new PostsSelectionCollectionView({
postsView = new PostsSelectionCollectionView({
collection: this.model.get('_availablePosts'),
blockModel: this.model
});
@ -358,12 +376,13 @@ define([
},
transport: function (options, success, failure) {
var taxonomies;
var termsPromise;
var promise = CommunicationComponent.getTaxonomies(
that.model.get('contentType')
).then(function (tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = CommunicationComponent.getTerms({
termsPromise = CommunicationComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function (terms) {
@ -372,7 +391,7 @@ define([
terms: terms
};
});
return promise;
return termsPromise;
});
promise.then(success);
@ -413,8 +432,8 @@ define([
this.model.set(field, jQuery(event.target).val());
},
_updateContentTypes: function (postTypes) {
var select = this.$('.mailpoet_settings_posts_content_type'),
selectedValue = this.model.get('contentType');
var select = this.$('.mailpoet_settings_posts_content_type');
var selectedValue = this.model.get('contentType');
select.find('option').remove();
_.each(postTypes, function (type) {
@ -427,11 +446,11 @@ define([
}
});
var EmptyPostSelectionSettingsView = Marionette.View.extend({
EmptyPostSelectionSettingsView = Marionette.View.extend({
getTemplate: function () { return window.templates.emptyPostPostsBlockSettings; }
});
var SinglePostSelectionSettingsView = Marionette.View.extend({
SinglePostSelectionSettingsView = Marionette.View.extend({
getTemplate: function () { return window.templates.singlePostPostsBlockSettings; },
events: function () {
return {
@ -448,8 +467,8 @@ define([
this.blockModel = options.blockModel;
},
postSelectionChange: function (event) {
var checkBox = jQuery(event.target),
selectedPostsCollection = this.blockModel.get('_selectedPosts');
var checkBox = jQuery(event.target);
var selectedPostsCollection = this.blockModel.get('_selectedPosts');
if (checkBox.prop('checked')) {
selectedPostsCollection.add(this.model);
} else {
@ -458,7 +477,7 @@ define([
}
});
var PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({
PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({
getTemplate: function () { return window.templates.displayOptionsPostsBlockSettings; },
events: function () {
return {
@ -488,7 +507,7 @@ define([
model: this.model.toJSON()
};
},
showButtonSettings: function (event) {
showButtonSettings: function () {
var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'),
@ -499,7 +518,7 @@ define([
}
})).render();
},
showDividerSettings: function (event) {
showDividerSettings: function () {
var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'),
@ -511,10 +530,10 @@ define([
},
changeReadMoreType: function (event) {
var value = jQuery(event.target).val();
if (value == 'link') {
if (value === 'link') {
this.$('.mailpoet_posts_read_more_text').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_select_button').addClass('mailpoet_hidden');
} else if (value == 'button') {
} else if (value === 'button') {
this.$('.mailpoet_posts_read_more_text').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_select_button').removeClass('mailpoet_hidden');
}
@ -522,7 +541,7 @@ define([
},
changeDisplayType: function (event) {
var value = jQuery(event.target).val();
if (value == 'titleOnly') {
if (value === 'titleOnly') {
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_separator').addClass('mailpoet_hidden');
@ -549,7 +568,7 @@ define([
},
changeTitleFormat: function (event) {
var value = jQuery(event.target).val();
if (value == 'ul') {
if (value === 'ul') {
this.$('.mailpoet_posts_non_title_list_options').addClass('mailpoet_hidden');
this.model.set('titleIsLink', true);
@ -575,13 +594,13 @@ define([
}
});
App.on('before:start', function (App, options) {
App.registerBlockType('posts', {
App.on('before:start', function (BeforeStartApp) {
BeforeStartApp.registerBlockType('posts', {
blockModel: Module.PostsBlockModel,
blockView: Module.PostsBlockView
});
App.registerWidget({
BeforeStartApp.registerWidget({
name: 'posts',
widgetView: Module.PostsWidgetView,
priority: 96

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
/**
* Social icons content block
*/
@ -10,15 +11,15 @@ define([
'underscore',
'jquery'
], function (App, BaseBlock, Backbone, Marionette, SuperModel, _, jQuery) {
'use strict';
var Module = {},
base = BaseBlock,
SocialBlockSettingsIconSelectorView,
SocialBlockSettingsIconView,
SocialBlockSettingsIconCollectionView,
SocialBlockSettingsStylesView;
var Module = {};
var base = BaseBlock;
var SocialBlockSettingsIconSelectorView;
var SocialBlockSettingsIconView;
var SocialBlockSettingsIconCollectionView;
var SocialBlockSettingsStylesView;
var SocialIconView;
Module.SocialIconModel = SuperModel.extend({
defaults: function () {
@ -33,12 +34,12 @@ define([
text: defaultValues.get('title')
};
},
initialize: function (options) {
initialize: function () {
var that = this;
// Make model swap to default values for that type when iconType changes
this.on('change:iconType', function () {
var defaultValues = App.getConfig().get('socialIcons').get(that.get('iconType')),
iconSet = that.collection.iconBlockModel.getIconSet();
var defaultValues = App.getConfig().get('socialIcons').get(that.get('iconType'));
var iconSet = that.collection.iconBlockModel.getIconSet();
this.set({
link: defaultValues.get('defaultLink'),
image: iconSet.get(that.get('iconType')),
@ -68,6 +69,7 @@ define([
initialize: function () {
this.get('icons').on('add remove change', this._iconsChanged, this);
this.on('change:iconSet', this.changeIconSet, this);
this.on('change', this._updateDefaults, this);
},
getIconSet: function () {
return App.getAvailableStyles().get('socialIconSets').get(this.get('iconSet'));
@ -79,11 +81,12 @@ define([
});
},
_iconsChanged: function () {
this._updateDefaults();
App.getChannel().trigger('autoSave');
}
});
var SocialIconView = Marionette.View.extend({
SocialIconView = Marionette.View.extend({
tagName: 'span',
getTemplate: function () { return window.templates.socialIconBlock; },
modelEvents: {
@ -175,10 +178,10 @@ define([
}
},
templateContext: function () {
var icons = App.getConfig().get('socialIcons'),
// 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') }; }),
allIconSets = App.getAvailableStyles().get('socialIconSets');
var icons = App.getConfig().get('socialIcons');
// Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...]
var availableIconTypes = _.map(_.keys(icons.attributes), function (key) { return { iconType: key, title: icons.get(key).get('title') }; });
var allIconSets = App.getAvailableStyles().get('socialIconSets');
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
iconTypes: availableIconTypes,
currentType: icons.get(this.model.get('iconType')).toJSON(),
@ -194,6 +197,7 @@ define([
} else {
return this.changeField('link', event);
}
return undefined;
},
changeField: function (field, event) {
this.model.set(field, jQuery(event.target).val());
@ -268,42 +272,19 @@ define([
DraggableBehavior: {
cloneOriginal: true,
drop: function () {
return new Module.SocialBlockModel({
type: 'social',
iconSet: 'default',
icons: [
{
type: 'socialIcon',
iconType: 'facebook',
link: 'http://www.facebook.com',
image: App.getAvailableStyles().get('socialIconSets.default.facebook'),
height: '32px',
width: '32px',
text: 'Facebook'
},
{
type: 'socialIcon',
iconType: 'twitter',
link: 'http://www.twitter.com',
image: App.getAvailableStyles().get('socialIconSets.default.twitter'),
height: '32px',
width: '32px',
text: 'Twitter'
}
]
}, { parse: true });
return new Module.SocialBlockModel();
}
}
}
});
App.on('before:start', function (App, options) {
App.registerBlockType('social', {
App.on('before:start', function (BeforeStartApp) {
BeforeStartApp.registerBlockType('social', {
blockModel: Module.SocialBlockModel,
blockView: Module.SocialBlockView
});
App.registerWidget({
BeforeStartApp.registerWidget({
name: 'social',
widgetView: Module.SocialWidgetView,
priority: 95

View File

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

View File

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

View File

@ -1,32 +0,0 @@
/**
* This shim replaces the default Backbone.Marionette communication library
* Backbone.Wreqr with Backbone.Radio ahead of time,
* since this libraries will be switched in Marionette 3.x anyway
*
* Courtesy of https://gist.github.com/jmeas/7992474cdb1c5672d88b
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['backbone.marionette', 'backbone.radio', 'underscore'], function (Marionette, Radio, _) {
return factory(Marionette, Radio, _);
});
}
else if (typeof exports !== 'undefined') {
var Marionette = require('backbone.marionette');
var Radio = require('backbone.radio');
var _ = require('underscore');
module.exports = factory(Marionette, Radio, _);
}
else {
factory(root.Backbone.Marionette, root.Backbone.Radio, root._);
}
}(this, function (Marionette, Radio, _) {
'use strict';
var MarionetteApplication = Marionette.Application;
MarionetteApplication.prototype._initChannel = function () {
this.channelName = _.result(this, 'channelName') || 'global';
this.channel = _.result(this, 'channel') || Radio.channel(this.channelName);
};
}));

View File

@ -1,10 +1,10 @@
/* eslint-disable func-names */
define([
'newsletter_editor/App',
'underscore',
'mailpoet',
'ajax'
], function (App, _, MailPoet) {
var Module = {};
Module._query = function (args) {
@ -98,7 +98,7 @@ define([
});
};
App.on('start', function (App, options) {
App.on('start', function () {
// Prefetch post types
Module.getPostTypes();
});

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
@ -8,7 +9,7 @@ define([
'jquery',
'blob',
'file-saver',
'html2canvas',
'common/thumbnail.jsx',
'underscore',
'jquery'
], function (
@ -21,19 +22,17 @@ define([
jQuery,
Blob,
FileSaver,
html2canvas,
Thumbnail,
_,
$
) {
'use strict';
var Module = {},
saveTimeout;
var Module = {};
var saveTimeout;
// Save editor contents to server
Module.save = function () {
var json = App.toJSON();
// Stringify to enable transmission of primitive non-string value types
@ -62,6 +61,9 @@ define([
});
}
}
if (!_.isUndefined(json.body)) {
json.body = JSON.parse(json.body);
}
App.getChannel().trigger('afterEditorSave', json, response);
}).fail(function (response) {
// TODO: Handle saving errors
@ -69,77 +71,45 @@ define([
});
};
Module.getThumbnail = function (element, options) {
var promise = html2canvas(element, options || {});
return promise.then(function (oldCanvas) {
// Temporary workaround for html2canvas-alpha2.
// Removes 1px left transparent border from resulting canvas.
var oldContext = oldCanvas.getContext('2d'),
newCanvas = document.createElement('canvas'),
newContext = newCanvas.getContext('2d'),
leftBorderWidth = 1;
newCanvas.width = oldCanvas.width;
newCanvas.height = oldCanvas.height;
newContext.drawImage(
oldCanvas,
leftBorderWidth, 0, oldCanvas.width - leftBorderWidth, oldCanvas.height,
0, 0, oldCanvas.width, oldCanvas.height
);
return newCanvas;
});
};
Module.saveTemplate = function (options) {
var that = this,
promise = jQuery.Deferred();
return Thumbnail.fromNewsletter(App.toJSON())
.then(function (thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail,
body: JSON.stringify(App.getBody()),
categories: JSON.stringify([
'saved',
App.getNewsletter().get('type')
])
});
promise.then(function (thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: JSON.stringify(App.getBody())
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletterTemplates',
action: 'save',
data: data
});
});
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletterTemplates',
action: 'save',
data: data
});
});
Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function (thumbnail) {
promise.resolve(thumbnail);
});
return promise;
};
Module.exportTemplate = function (options) {
var that = this;
return Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function (thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody()
});
var blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json;charset=utf-8' }
);
return Thumbnail.fromNewsletter(App.toJSON())
.then(function (thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail,
body: App.getBody(),
categories: JSON.stringify(['saved', App.getNewsletter().get('type')])
});
var blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json;charset=utf-8' }
);
FileSaver.saveAs(blob, 'template.json');
MailPoet.trackEvent('Editor > Template exported', {
'MailPoet Free version': window.mailpoet_version
FileSaver.saveAs(blob, 'template.json');
MailPoet.trackEvent('Editor > Template exported', {
'MailPoet Free version': window.mailpoet_version
});
});
});
};
Module.SaveView = Marionette.View.extend({
@ -155,7 +125,7 @@ define([
'click .mailpoet_save_export': 'toggleExportTemplate',
'click .mailpoet_export_template': 'exportTemplate'
},
initialize: function (options) {
initialize: function () {
App.getChannel().on('beforeEditorSave', this.beforeSave, this);
App.getChannel().on('afterEditorSave', this.afterSave, this);
},
@ -170,7 +140,7 @@ define([
// TODO: Add a loading animation instead
this.$('.mailpoet_autosaved_at').text(MailPoet.I18n.t('saving'));
},
afterSave: function (json, response) {
afterSave: function (json) {
this.validateNewsletter(json);
// Update 'Last saved timer'
this.$('.mailpoet_editor_last_saved').removeClass('mailpoet_hidden');
@ -192,9 +162,9 @@ define([
this.$('.mailpoet_save_as_template_container').addClass('mailpoet_hidden');
},
saveAsTemplate: function () {
var templateName = this.$('.mailpoet_save_as_template_name').val(),
templateDescription = this.$('.mailpoet_save_as_template_description').val(),
that = this;
var templateName = this.$('.mailpoet_save_as_template_name').val();
var templateDescription = this.$('.mailpoet_save_as_template_description').val();
var that = this;
if (templateName === '') {
MailPoet.Notice.error(
@ -216,7 +186,7 @@ define([
Module.saveTemplate({
name: templateName,
description: templateDescription
}).done(function () {
}).then(function () {
MailPoet.Notice.success(
MailPoet.I18n.t('templateSaved'),
{
@ -227,7 +197,7 @@ define([
MailPoet.trackEvent('Editor > Template saved', {
'MailPoet Free version': window.mailpoet_version
});
}).fail(function () {
}).catch(function () {
MailPoet.Notice.error(
MailPoet.I18n.t('templateSaveFailed'),
{
@ -238,7 +208,6 @@ define([
});
this.hideOptionContents();
}
},
toggleExportTemplate: function () {
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
@ -248,9 +217,9 @@ define([
this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden');
},
exportTemplate: function () {
var templateName = this.$('.mailpoet_export_template_name').val(),
templateDescription = this.$('.mailpoet_export_template_description').val(),
that = this;
var templateName = this.$('.mailpoet_export_template_name').val();
var templateDescription = this.$('.mailpoet_export_template_description').val();
var that = this;
if (templateName === '') {
MailPoet.Notice.error(
@ -285,18 +254,19 @@ define([
this.hideOptionContents();
if (!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
Module._cancelAutosave();
Module.save().done(function (response) {
Module.save().done(function () {
window.location.href = App.getConfig().get('urls.send');
});
}
},
validateNewsletter: function (jsonObject) {
var contents;
if (!App._contentContainer.isValid()) {
this.showValidationError(App._contentContainer.validationError);
return;
}
var contents = JSON.stringify(jsonObject);
contents = JSON.stringify(jsonObject);
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
contents.indexOf('[link:subscription_unsubscribe_url]') < 0 &&
contents.indexOf('[link:subscription_unsubscribe]') < 0) {
@ -304,6 +274,13 @@ define([
return;
}
if ((App.getNewsletter().get('type') === 'notification') &&
contents.indexOf('"type":"automatedLatestContent"') < 0
) {
this.showValidationError(MailPoet.I18n.t('automatedLatestContentMissing'));
return;
}
this.hideValidationError();
},
showValidationError: function (message) {
@ -340,9 +317,11 @@ define([
};
Module.beforeExitWithUnsavedChanges = function (e) {
var message;
var event;
if (saveTimeout) {
var message = MailPoet.I18n.t('unsavedChangesWillBeLost');
var event = e || window.event;
message = MailPoet.I18n.t('unsavedChangesWillBeLost');
event = e || window.event;
if (event) {
event.returnValue = message;
@ -350,10 +329,11 @@ define([
return message;
}
return undefined;
};
App.on('before:start', function (App, options) {
var Application = App;
App.on('before:start', function (BeforeStartApp) {
var Application = BeforeStartApp;
Application.save = Module.save;
Application.getChannel().on('autoSave', Module.autoSave);
@ -362,9 +342,9 @@ define([
Application.getChannel().reply('save', Application.save);
});
App.on('start', function (App, options) {
App.on('start', function (BeforeStartApp) {
var saveView = new Module.SaveView();
App._appView.showChildView('bottomRegion', saveView);
BeforeStartApp._appView.showChildView('bottomRegion', saveView);
});
return Module;

View File

@ -1,3 +1,4 @@
/* eslint-disable func-names */
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
@ -6,8 +7,7 @@ define([
'backbone.marionette',
'backbone.supermodel',
'underscore',
'jquery',
'sticky-kit'
'jquery'
], function (
App,
CommunicationComponent,
@ -16,14 +16,12 @@ define([
Marionette,
SuperModel,
_,
jQuery,
StickyKit
jQuery
) {
'use strict';
var Module = {};
var SidebarView;
// Widget handlers for use to create new content blocks via drag&drop
Module._contentWidgets = new (Backbone.Collection.extend({
model: SuperModel.extend({
@ -52,7 +50,7 @@ define([
Module.registerLayoutWidget = function (widget) { return Module._layoutWidgets.add(widget); };
Module.getLayoutWidgets = function () { return Module._layoutWidgets; };
var SidebarView = Marionette.View.extend({
SidebarView = Marionette.View.extend({
getTemplate: function () { return window.templates.sidebar; },
regions: {
contentRegion: '.mailpoet_content_region',
@ -62,8 +60,8 @@ define([
},
events: {
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function (event) {
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'),
$targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)');
var $targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
$openRegion.find('.mailpoet_region_content').velocity(
'slideUp',
@ -72,7 +70,7 @@ define([
easing: 'easeOut',
complete: function () {
$openRegion.addClass('closed');
}.bind(this)
}
}
);
@ -90,7 +88,7 @@ define([
}
}
},
initialize: function (options) {
initialize: function () {
jQuery(window)
.on('resize', this.updateHorizontalScroll.bind(this))
.on('scroll', this.updateHorizontalScroll.bind(this));
@ -113,13 +111,12 @@ define([
// position of the sidebar would be scrollable and not fixed
// partially out of visible screen
this.$el.parent().each(function () {
var calculated_left, self;
self = jQuery(this);
var calculatedLeft;
var self = jQuery(this);
if (self.css('position') === 'fixed') {
calculated_left = self.parent().offset().left - jQuery(window).scrollLeft();
self.css('left', calculated_left + 'px');
calculatedLeft = self.parent().offset().left - jQuery(window).scrollLeft();
self.css('left', calculatedLeft + 'px');
} else {
self.css('left', '');
}
@ -271,7 +268,7 @@ define([
previewUrl: response.meta.preview_url
});
var view = this.previewView.render();
this.previewView.render();
this.previewView.$el.css('height', '100%');
MailPoet.Modal.popup({
@ -324,7 +321,7 @@ define([
App.getChannel().request('save').always(function () {
CommunicationComponent.previewNewsletter(data).always(function () {
MailPoet.Modal.loading(false);
}).done(function (response) {
}).done(function () {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterPreviewSent'),
{ scroll: true }
@ -342,6 +339,7 @@ define([
}
});
});
return undefined;
}
});
@ -363,19 +361,18 @@ define([
}
});
App.on('before:start', function (App, options) {
var Application = App;
App.on('before:start', function (BeforeStartApp) {
var Application = BeforeStartApp;
Application.registerWidget = Module.registerWidget;
Application.getWidgets = Module.getWidgets;
Application.registerLayoutWidget = Module.registerLayoutWidget;
Application.getLayoutWidgets = Module.getLayoutWidgets;
});
App.on('start', function (App, options) {
var stylesModel = App.getGlobalStyles(),
sidebarView = new SidebarView();
App.on('start', function (StartApp) {
var sidebarView = new SidebarView();
App._appView.showChildView('sidebarRegion', sidebarView);
StartApp._appView.showChildView('sidebarRegion', sidebarView);
MailPoet.helpTooltip.show(document.getElementById('tooltip-send-preview'), {
tooltipId: 'tooltip-editor-send-preview',

View File

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

View File

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

View File

@ -2,36 +2,34 @@ import React from 'react';
import classNames from 'classnames';
import ReactTooltip from 'react-tooltip';
class Badge extends React.Component {
render() {
const badgeClasses = classNames(
'mailpoet_badge',
this.props.type ? `mailpoet_badge_${this.props.type}` : ''
);
function Badge(props) {
const badgeClasses = classNames(
'mailpoet_badge',
props.type ? `mailpoet_badge_${props.type}` : ''
);
const tooltip = this.props.tooltip ? this.props.tooltip.replace(/\n/g, '<br />') : false;
// tooltip ID must be unique, defaults to tooltip text
const tooltipId = this.props.tooltipId || tooltip;
const tooltip = props.tooltip ? props.tooltip.replace(/\n/g, '<br />') : false;
// tooltip ID must be unique, defaults to tooltip text
const tooltipId = props.tooltipId || tooltip;
return (
<span>
<span
className={badgeClasses}
data-tip={tooltip}
data-for={tooltipId}
>
{this.props.name}
</span>
{ tooltip && (
<ReactTooltip
place="right"
multiline={true}
id={tooltipId}
/>
) }
return (
<span>
<span
className={badgeClasses}
data-tip={tooltip}
data-for={tooltipId}
>
{props.name}
</span>
);
}
{ tooltip && (
<ReactTooltip
place="right"
multiline={true}
id={tooltipId}
/>
) }
</span>
);
}
export default Badge;

View File

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

View File

@ -14,31 +14,32 @@ define(
const Link = Router.Link;
const Breadcrumb = React.createClass({
getInitialState: function () {
getInitialState: function getInitialState() {
const steps = this.props.steps || [
{
name: 'type',
label: MailPoet.I18n.t('selectType'),
link: '/new',
},
{
name: 'template',
label: MailPoet.I18n.t('template'),
},
{
name: 'editor',
label: MailPoet.I18n.t('designer'),
},
{
name: 'send',
label: MailPoet.I18n.t('send'),
},
];
return {
step: null,
steps: [
{
name: 'type',
label: MailPoet.I18n.t('selectType'),
link: '/new',
},
{
name: 'template',
label: MailPoet.I18n.t('template'),
},
{
name: 'editor',
label: MailPoet.I18n.t('designer'),
},
{
name: 'send',
label: MailPoet.I18n.t('send'),
},
],
steps,
};
},
render: function () {
render: function render() {
const steps = this.state.steps.map((step, index) => {
const stepClasses = classNames(
{ mailpoet_current: (this.props.step === step.name) }
@ -46,14 +47,14 @@ define(
let label = step.label;
if (step['link'] !== undefined && this.props.step !== step.name) {
if (step.link !== undefined && this.props.step !== step.name) {
label = (
<Link to={step.link}>{ step.label }</Link>
);
}
return (
<span key={'step-' + index}>
<span key={`step-${index}`}>
<span className={stepClasses}>
{ label }
</span>

View File

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

View File

@ -18,7 +18,7 @@ import {
const messages = {
onTrash: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -33,7 +33,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onDelete: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -48,7 +48,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onRestore: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -91,7 +91,7 @@ const columns = [
},
];
const bulk_actions = [
const bulkActions = [
{
name: 'trash',
label: MailPoet.I18n.t('moveToTrash'),
@ -99,10 +99,10 @@ const bulk_actions = [
},
];
const newsletter_actions = [
const newsletterActions = [
{
name: 'view',
link: function (newsletter) {
link: function link(newsletter) {
return (
<a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')}
@ -112,7 +112,7 @@ const newsletter_actions = [
},
{
name: 'edit',
link: function (newsletter) {
link: function link(newsletter) {
return (
<a href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}>
{MailPoet.I18n.t('edit')}
@ -123,7 +123,7 @@ const newsletter_actions = [
{
name: 'duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function (newsletter, refresh) {
onClick: function onClick(newsletter, refresh) {
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletters',
@ -141,7 +141,7 @@ const newsletter_actions = [
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
@ -155,7 +155,7 @@ const newsletter_actions = [
const NewsletterListNotification = React.createClass({
mixins: [MailerMixin],
updateStatus: function (e) {
updateStatus: function updateStatus(e) {
// make the event persist so that we can still override the selected value
// in the ajax callback
e.persist();
@ -165,7 +165,7 @@ const NewsletterListNotification = React.createClass({
endpoint: 'newsletters',
action: 'setStatus',
data: {
id: ~~(e.target.getAttribute('data-id')),
id: Number(e.target.getAttribute('data-id')),
status: e.target.value,
},
}).done((response) => {
@ -181,7 +181,7 @@ const NewsletterListNotification = React.createClass({
e.target.value = response.status;
});
},
renderStatus: function (newsletter) {
renderStatus: function renderStatus(newsletter) {
return (
<select
data-id={newsletter.id}
@ -193,14 +193,14 @@ const NewsletterListNotification = React.createClass({
</select>
);
},
renderSettings: function (newsletter) {
renderSettings: function renderSettings(newsletter) {
let sendingFrequency;
let sendingToSegments;
// get list of segments' name
const segments = newsletter.segments.map((segment) => {
return segment.name;
});
const segments = newsletter.segments.map(segment => segment.name);
const sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
'%$1s', segments.join(', ')
);
// check if the user has specified segments to send to
if (segments.length === 0) {
@ -209,72 +209,72 @@ const NewsletterListNotification = React.createClass({
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span>
);
} else {
sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
'%$1s', segments.join(', ')
);
}
// set sending frequency
switch (newsletter.options.intervalType) {
case 'daily':
sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
// set sending frequency
switch (newsletter.options.intervalType) {
case 'daily':
sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
'%$1s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
'%$1s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
'%$1s', monthDayValues[newsletter.options.monthDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'nthWeekDay':
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
case 'nthWeekDay':
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
'%$1s', nthWeekDayValues[newsletter.options.nthWeekDay]
).replace(
'%$2s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$3s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately');
break;
}
case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately');
break;
default:
sendingFrequency = 'Invalid sending frequency';
break;
}
return (
<span>
{ sendingFrequency } { sendingToSegments }
</span>
);
},
renderHistoryLink: function (newsletter) {
const childrenCount = ~~(newsletter.children_count);
renderHistoryLink: function renderHistoryLink(newsletter) {
const childrenCount = Number((newsletter.children_count));
if (childrenCount === 0) {
return (
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 renderItem(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
'column-primary',
@ -307,11 +307,11 @@ const NewsletterListNotification = React.createClass({
</div>
);
},
render: function () {
render: function render() {
return (
<div>
<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>
<ListingTabs tab="notification" />
@ -325,8 +325,8 @@ const NewsletterListNotification = React.createClass({
base_url="notification"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={bulk_actions}
item_actions={newsletter_actions}
bulk_actions={bulkActions}
item_actions={newsletterActions}
messages={messages}
auto_refresh={true}
sort_by="updated_at"

View File

@ -13,7 +13,7 @@ import {
MailerMixin,
} from 'newsletters/listings/mixins.jsx';
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const mailpoetTrackingEnabled = (!!(window.mailpoet_tracking_enabled));
const columns = [
{
@ -31,7 +31,7 @@ const columns = [
{
name: 'statistics',
label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled,
display: mailpoetTrackingEnabled,
},
{
name: 'sent_at',
@ -39,10 +39,10 @@ const columns = [
},
];
let newsletter_actions = [
let newsletterActions = [
{
name: 'view',
link: function (newsletter) {
link: function link(newsletter) {
return (
<a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')}
@ -52,20 +52,19 @@ 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({
mixins: [QueueMixin, StatisticsMixin, MailerMixin],
renderItem: function (newsletter, actions, meta) {
renderItem: function renderItem(newsletter, actions, meta) {
const rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
const segments = newsletter.segments.map((segment) => {
return segment.name;
}).join(', ');
const segments = newsletter.segments.map(segment => segment.name).join(', ');
return (
<div>
@ -84,7 +83,7 @@ const NewsletterListNotificationHistory = React.createClass({
<td className="column" data-colname={MailPoet.I18n.t('lists')}>
{ segments }
</td>
{ (mailpoet_tracking_enabled === true) ? (
{ (mailpoetTrackingEnabled === true) ? (
<td className="column" data-colname={MailPoet.I18n.t('statistics')}>
{ this.renderStatistics(newsletter, undefined, meta.current_time) }
</td>
@ -95,11 +94,11 @@ const NewsletterListNotificationHistory = React.createClass({
</div>
);
},
render: function () {
render: function render() {
return (
<div>
<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>
<ListingTabs tab="notification" />
@ -118,7 +117,7 @@ const NewsletterListNotificationHistory = React.createClass({
base_url="notification/history/:parent_id"
onRenderItem={this.renderItem}
columns={columns}
item_actions={newsletter_actions}
item_actions={newsletterActions}
auto_refresh={true}
sort_by="sent_at"
sort_order="desc"

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Link } from 'react-router';
import { confirmAlert } from 'react-confirm-alert';
import classNames from 'classnames';
import MailPoet from 'mailpoet';
import Hooks from 'wp-js-hooks';
@ -13,11 +14,11 @@ import {
MailerMixin,
} from 'newsletters/listings/mixins.jsx';
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const mailpoetTrackingEnabled = (!!(window.mailpoet_tracking_enabled));
const messages = {
onTrash: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -32,7 +33,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onDelete: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -47,7 +48,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onRestore: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -80,7 +81,7 @@ const columns = [
{
name: 'statistics',
label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled,
display: mailpoetTrackingEnabled,
},
{
name: 'sent_at',
@ -89,7 +90,7 @@ const columns = [
},
];
const bulk_actions = [
const bulkActions = [
{
name: 'trash',
label: MailPoet.I18n.t('moveToTrash'),
@ -98,20 +99,30 @@ const bulk_actions = [
];
const confirmEdit = (newsletter) => {
const redirectToEditing = () => {
window.location.href = `?page=mailpoet-newsletter-editor&id=${newsletter.id}`;
};
if (
!newsletter.queue
|| newsletter.status != 'sending'
|| newsletter.status !== 'sending'
|| 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',
link: function (newsletter) {
link: function link(newsletter) {
return (
<a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')}
@ -127,7 +138,7 @@ let newsletter_actions = [
{
name: 'duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function (newsletter, refresh) {
onClick: function onClick(newsletter, refresh) {
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletters',
@ -145,7 +156,7 @@ let newsletter_actions = [
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
@ -157,20 +168,19 @@ 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({
mixins: [QueueMixin, StatisticsMixin, MailerMixin],
renderItem: function (newsletter, actions, meta) {
renderItem: function renderItem(newsletter, actions, meta) {
const rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
const segments = newsletter.segments.map((segment) => {
return segment.name;
}).join(', ');
const segments = newsletter.segments.map(segment => segment.name).join(', ');
return (
<div>
@ -190,7 +200,7 @@ const NewsletterListStandard = React.createClass({
<td className="column" data-colname={MailPoet.I18n.t('lists')}>
{ segments }
</td>
{ (mailpoet_tracking_enabled === true) ? (
{ (mailpoetTrackingEnabled === true) ? (
<td className="column" data-colname={MailPoet.I18n.t('statistics')}>
{ this.renderStatistics(newsletter, undefined, meta.current_time) }
</td>
@ -201,7 +211,7 @@ const NewsletterListStandard = React.createClass({
</div>
);
},
render: function () {
render: function render() {
return (
<div>
<h1 className="title">
@ -210,6 +220,7 @@ const NewsletterListStandard = React.createClass({
onClick={() => MailPoet.trackEvent('Emails > Add New',
{ 'MailPoet Free version': window.mailpoet_version }
)}
data-automation-id="new_email"
>
{MailPoet.I18n.t('new')}
</Link>
@ -226,8 +237,8 @@ const NewsletterListStandard = React.createClass({
base_url="standard"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={bulk_actions}
item_actions={newsletter_actions}
bulk_actions={bulkActions}
item_actions={newsletterActions}
messages={messages}
auto_refresh={true}
sort_by="sent_at"

View File

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

View File

@ -11,13 +11,13 @@ import MailPoet from 'mailpoet';
import _ from 'underscore';
import Hooks from 'wp-js-hooks';
const mailpoet_roles = window.mailpoet_roles || {};
const mailpoet_segments = window.mailpoet_segments || {};
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const mailpoetRoles = window.mailpoet_roles || {};
const mailpoetSegments = window.mailpoet_segments || {};
const mailpoetTrackingEnabled = (!!(window.mailpoet_tracking_enabled));
const messages = {
onTrash: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -32,7 +32,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onDelete: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -47,7 +47,7 @@ const messages = {
MailPoet.Notice.success(message);
},
onRestore: (response) => {
const count = ~~response.meta.count;
const count = Number(response.meta.count);
let message = null;
if (count === 1) {
@ -81,7 +81,7 @@ const columns = [
{
name: 'statistics',
label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled,
display: mailpoetTrackingEnabled,
},
{
name: 'updated_at',
@ -90,7 +90,7 @@ const columns = [
},
];
const bulk_actions = [
const bulkActions = [
{
name: 'trash',
label: MailPoet.I18n.t('moveToTrash'),
@ -98,10 +98,10 @@ const bulk_actions = [
},
];
let newsletter_actions = [
let newsletterActions = [
{
name: 'view',
link: function (newsletter) {
link: function link(newsletter) {
return (
<a href={newsletter.preview_url} target="_blank">
{MailPoet.I18n.t('preview')}
@ -111,7 +111,7 @@ let newsletter_actions = [
},
{
name: 'edit',
link: function (newsletter) {
link: function link(newsletter) {
return (
<a href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}>
{MailPoet.I18n.t('edit')}
@ -124,11 +124,12 @@ 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({
mixins: [StatisticsMixin, MailerMixin],
updateStatus: function (e) {
updateStatus: function updateStatus(e) {
// make the event persist so that we can still override the selected value
// in the ajax callback
e.persist();
@ -138,7 +139,7 @@ const NewsletterListWelcome = React.createClass({
endpoint: 'newsletters',
action: 'setStatus',
data: {
id: ~~(e.target.getAttribute('data-id')),
id: Number(e.target.getAttribute('data-id')),
status: e.target.value,
},
}).done((response) => {
@ -154,8 +155,8 @@ const NewsletterListWelcome = React.createClass({
e.target.value = response.status;
});
},
renderStatus: function (newsletter) {
const total_sent = (
renderStatus: function renderStatus(newsletter) {
const totalSent = (
MailPoet.I18n.t('sentToXSubscribers')
.replace('%$1d', newsletter.total_sent.toLocaleString())
);
@ -172,13 +173,14 @@ const NewsletterListWelcome = React.createClass({
<option value="draft">{ MailPoet.I18n.t('inactive') }</option>
</select>
</p>
<p>{ total_sent }</p>
<p>{ totalSent }</p>
</div>
);
},
renderSettings: function (newsletter) {
renderSettings: function renderSettings(newsletter) {
let sendingEvent;
let sendingDelay;
let segment;
// set sending event
switch (newsletter.options.event) {
@ -188,16 +190,17 @@ const NewsletterListWelcome = React.createClass({
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserAnyRole');
} else {
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserWithRole').replace(
'%$1s', mailpoet_roles[newsletter.options.role]
'%$1s', mailpoetRoles[newsletter.options.role]
);
}
break;
case 'segment':
default:
// get segment
const segment = _.find(mailpoet_segments, (segment) => {
return (~~(segment.id) === ~~(newsletter.options.segment));
});
segment = _.find(
mailpoetSegments,
seg => (Number(seg.id) === Number(newsletter.options.segment))
);
if (segment === undefined) {
return (
@ -205,11 +208,11 @@ const NewsletterListWelcome = React.createClass({
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span>
);
} else {
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
}
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
'%$1s', segment.name
);
}
break;
}
@ -234,8 +237,12 @@ const NewsletterListWelcome = React.createClass({
'%$1d', newsletter.options.afterTimeNumber
);
break;
default:
sendingDelay = 'Invalid sending delay';
break;
}
sendingEvent += ' [' + sendingDelay + ']';
sendingEvent += ` [${sendingDelay}]`;
}
// add a "period" at the end if we do have a sendingEvent
sendingEvent += '.';
@ -247,7 +254,7 @@ const NewsletterListWelcome = React.createClass({
</span>
);
},
renderItem: function (newsletter, actions) {
renderItem: function renderItem(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
'column-primary',
@ -271,7 +278,7 @@ const NewsletterListWelcome = React.createClass({
<td className="column" data-colname={MailPoet.I18n.t('settings')}>
{ this.renderSettings(newsletter) }
</td>
{ (mailpoet_tracking_enabled === true) ? (
{ (mailpoetTrackingEnabled === true) ? (
<td className="column" data-colname={MailPoet.I18n.t('statistics')}>
{ this.renderStatistics(
newsletter,
@ -285,11 +292,11 @@ const NewsletterListWelcome = React.createClass({
</div>
);
},
render: function () {
render: function render() {
return (
<div>
<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>
<ListingTabs tab="welcome" />
@ -303,8 +310,8 @@ const NewsletterListWelcome = React.createClass({
base_url="welcome"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={bulk_actions}
item_actions={newsletter_actions}
bulk_actions={bulkActions}
item_actions={newsletterActions}
messages={messages}
auto_refresh={true}
sort_by="updated_at"

View File

@ -3,14 +3,14 @@ import ReactDOM from 'react-dom';
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';
import Hooks from 'wp-js-hooks';
import _ from 'underscore';
import NewsletterTypes from 'newsletters/types.jsx';
import NewsletterTemplates from 'newsletters/templates.jsx';
import NewsletterSend from 'newsletters/send.jsx';
import NewsletterTypeStandard from 'newsletters/types/standard.jsx';
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx';
import AutomaticEmailEventsList from 'newsletters/types/automatic_emails/events_list.jsx';
import NewsletterListStandard from 'newsletters/listings/standard.jsx';
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx';
import NewsletterListNotification from 'newsletters/listings/notification.jsx';
@ -26,33 +26,85 @@ const App = React.createClass({
const container = document.getElementById('newsletters_container');
if (container) {
let extra_routes = [];
extra_routes = Hooks.applyFilters('mailpoet_newsletters_before_router', extra_routes);
const getAutomaticEmailsRoutes = () => {
if (!window.mailpoet_automatic_emails) return null;
const mailpoet_listing = ReactDOM.render((
return _.map(window.mailpoet_automatic_emails, automaticEmail => ({
path: `new/${automaticEmail.slug}`,
name: automaticEmail.slug,
component: AutomaticEmailEventsList,
data: {
automaticEmail,
},
}));
};
if (container) {
let routes = [
/* Listings */
{
path: 'standard(/)**',
params: { tab: 'standard' },
component: NewsletterListStandard,
},
{
path: 'welcome(/)**',
component: NewsletterListWelcome,
},
{
path: 'notification/history/:parent_id(/)**',
component: NewsletterListNotificationHistory,
},
{
path: 'notification(/)**',
component: NewsletterListNotification,
},
/* Newsletter: type selection */
{
path: 'new',
component: NewsletterTypes,
},
/* New newsletter: types */
{
path: 'new/standard',
component: NewsletterTypeStandard,
},
{
path: 'new/notification',
component: NewsletterTypeNotification,
},
/* Template selection */
{
name: 'template',
path: 'template/:id',
component: NewsletterTemplates,
},
/* Sending options */
{
path: 'send/:id',
component: NewsletterSend,
},
];
routes = Hooks.applyFilters('mailpoet_newsletters_before_router', [...routes, ...getAutomaticEmailsRoutes()]);
const mailpoetListing = ReactDOM.render((
<Router history={history}>
<Route path="/" component={App}>
<IndexRedirect to="standard" />
{/* Listings */}
<Route path="standard(/)**" params={{ tab: 'standard' }} component={NewsletterListStandard} />
<Route path="welcome(/)**" component={NewsletterListWelcome} />
<Route path="notification/history/:parent_id(/)**" component={NewsletterListNotificationHistory} />
<Route path="notification(/)**" component={NewsletterListNotification} />
{/* Newsletter: type selection */}
<Route path="new" component={NewsletterTypes} />
{/* New newsletter: types */}
<Route path="new/standard" component={NewsletterTypeStandard} />
<Route path="new/notification" component={NewsletterTypeNotification} />
{/* Template selection */}
<Route name="template" path="template/:id" component={NewsletterTemplates} />
{/* Sending options */}
<Route path="send/:id" component={NewsletterSend} />
{/* Extra routes */}
{ extra_routes.map(rt => <Route key={rt.path} path={rt.path} component={rt.component} />) }
{routes.map(route => (
<Route
key={route.path}
path={route.path}
component={route.component}
name={route.name || null}
params={route.params || null}
data={route.data || null}
/>
))}
</Route>
</Router>
), container);
window.mailpoet_listing = mailpoet_listing;
window.mailpoet_listing = mailpoetListing;
}

View File

@ -4,14 +4,14 @@ import MailPoet from 'mailpoet';
const timeFormat = window.mailpoet_time_format || 'H:i';
// welcome emails
const _timeDelayValues = {
const timeDelayValues = {
immediate: MailPoet.I18n.t('delayImmediately'),
hours: MailPoet.I18n.t('delayHoursAfter'),
days: MailPoet.I18n.t('delayDaysAfter'),
weeks: MailPoet.I18n.t('delayWeeksAfter'),
};
const _intervalValues = {
const intervalValues = {
daily: MailPoet.I18n.t('daily'),
weekly: MailPoet.I18n.t('weekly'),
monthly: MailPoet.I18n.t('monthly'),
@ -24,18 +24,16 @@ const SECONDS_IN_DAY = 86400;
const TIME_STEP_SECONDS = 3600;
const numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
const _timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps, (step) => {
return step * TIME_STEP_SECONDS;
}), (seconds) => {
const date = new Date(null);
date.setSeconds(seconds);
const timeLabel = MailPoet.Date.format(date, { format: timeFormat, offset: 0 });
return [seconds, timeLabel];
})
const timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps, step => step * TIME_STEP_SECONDS), (seconds) => {
const date = new Date(null);
date.setSeconds(seconds);
const timeLabel = MailPoet.Date.format(date, { format: timeFormat, offset: 0 });
return [seconds, timeLabel];
})
);
const _weekDayValues = {
const weekDayValues = {
0: MailPoet.I18n.t('sunday'),
1: MailPoet.I18n.t('monday'),
2: MailPoet.I18n.t('tuesday'),
@ -46,37 +44,35 @@ const _weekDayValues = {
};
const NUMBER_OF_DAYS_IN_MONTH = 28;
const _monthDayValues = _.object(
const monthDayValues = _.object(
_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, (day) => {
return day;
}), (day) => {
const labels = {
0: MailPoet.I18n.t('first'),
1: MailPoet.I18n.t('second'),
2: MailPoet.I18n.t('third'),
};
let label;
if (labels[day] !== undefined) {
label = labels[day];
} else {
label = MailPoet.I18n.t('nth').replace('%$1d', day + 1);
_.times(NUMBER_OF_DAYS_IN_MONTH, day => day), (day) => {
const labels = {
0: MailPoet.I18n.t('first'),
1: MailPoet.I18n.t('second'),
2: MailPoet.I18n.t('third'),
};
let label;
if (labels[day] !== undefined) {
label = labels[day];
} else {
label = MailPoet.I18n.t('nth').replace('%$1d', day + 1);
}
return [day + 1, label];
}
return [day + 1, label];
}
)
);
const _nthWeekDayValues = {
const nthWeekDayValues = {
1: MailPoet.I18n.t('first'),
2: MailPoet.I18n.t('second'),
3: MailPoet.I18n.t('third'),
L: MailPoet.I18n.t('last'),
};
export { _timeDelayValues as timeDelayValues };
export { _intervalValues as intervalValues };
export { _timeOfDayValues as timeOfDayValues };
export { _weekDayValues as weekDayValues };
export { _monthDayValues as monthDayValues };
export { _nthWeekDayValues as nthWeekDayValues };
export { timeDelayValues };
export { intervalValues };
export { timeOfDayValues };
export { weekDayValues };
export { monthDayValues };
export { nthWeekDayValues };

View File

@ -11,6 +11,7 @@ define(
'newsletters/breadcrumb.jsx',
'help-tooltip.jsx',
'jquery',
'common/thumbnail.jsx',
],
(
React,
@ -23,48 +24,48 @@ define(
WelcomeNewsletterFields,
Breadcrumb,
HelpTooltip,
jQuery
jQuery,
Thumbnail
) => {
const NewsletterSend = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired,
},
getInitialState: function () {
getInitialState: function getInitialState() {
return {
fields: [],
item: {},
loading: false,
};
},
getFieldsByNewsletter: function (newsletter) {
getFieldsByNewsletter: function getFieldsByNewsletter(newsletter) {
const type = this.getSubtype(newsletter);
return type.getFields(newsletter);
},
getSendButtonOptions: function () {
getSendButtonOptions: function getSendButtonOptions() {
const type = this.getSubtype(this.state.item);
return type.getSendButtonOptions(this.state.item);
},
getSubtype: function (newsletter) {
getSubtype: function getSubtype(newsletter) {
switch (newsletter.type) {
case 'notification': return NotificationNewsletterFields;
case 'welcome': return WelcomeNewsletterFields;
default: return StandardNewsletterFields;
}
},
isValid: function () {
isValid: function isValid() {
return jQuery('#mailpoet_newsletter').parsley().isValid();
},
componentDidMount: function () {
componentDidMount: function componentDidMount() {
if (this.isMounted()) {
this.loadItem(this.props.params.id);
}
jQuery('#mailpoet_newsletter').parsley();
},
componentWillReceiveProps: function (props) {
componentWillReceiveProps: function componentWillReceiveProps(props) {
this.loadItem(props.params.id);
},
loadItem: function (id) {
loadItem: function loadItem(id) {
this.setState({ loading: true });
MailPoet.Ajax.post({
@ -72,7 +73,7 @@ define(
endpoint: 'newsletters',
action: 'get',
data: {
id: id,
id,
},
}).done((response) => {
this.setState({
@ -89,13 +90,33 @@ define(
});
});
},
handleSend: function (e) {
saveTemplate: function saveTemplate(response, done) {
Thumbnail.fromUrl(response.meta.preview_url)
.then(function saveTemplateAjax(thumbnail) {
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletterTemplates',
action: 'save',
data: {
newsletter_id: response.data.id,
name: response.data.subject,
description: response.data.preheader,
thumbnail,
body: JSON.stringify(response.data.body),
categories: '["recent"]',
},
}).then(done).fail(this.showError);
})
.catch(err => this.showError({ errors: [err] }));
},
handleSend: function handleSend(e) {
e.preventDefault();
if (!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
this._save(e).done(() => {
MailPoet.Modal.loading(true);
this.saveNewsletter(e).done(() => {
this.setState({ loading: true });
}).done((response) => {
switch (response.data.type) {
@ -109,30 +130,38 @@ define(
id: this.props.params.id,
status: 'active',
},
}).done((response) => {
// redirect to listing based on newsletter type
this.context.router.push(`/${this.state.item.type || ''}`);
const opts = this.state.item.options;
// display success message depending on newsletter type
if (response.data.type === 'welcome') {
MailPoet.Notice.success(
MailPoet.I18n.t('welcomeEmailActivated')
);
MailPoet.trackEvent('Emails > Welcome email activated', {
'MailPoet Free version': window.mailpoet_version,
'List type': opts.event,
Delay: opts.afterTimeNumber + ' ' + opts.afterTimeType,
});
} else if (response.data.type === 'notification') {
MailPoet.Notice.success(
MailPoet.I18n.t('postNotificationActivated')
);
MailPoet.trackEvent('Emails > Post notifications activated', {
'MailPoet Free version': window.mailpoet_version,
Frequency: opts.intervalType,
});
}
}).fail(this._showError);
}).done((response2) => {
// save template in recently sent category
this.saveTemplate(response, () => {
// redirect to listing based on newsletter type
this.context.router.push(`/${this.state.item.type || ''}`);
const opts = this.state.item.options;
// display success message depending on newsletter type
if (response2.data.type === 'welcome') {
MailPoet.Notice.success(
MailPoet.I18n.t('welcomeEmailActivated')
);
MailPoet.trackEvent('Emails > Welcome email activated', {
'MailPoet Free version': window.mailpoet_version,
'List type': opts.event,
Delay: `${opts.afterTimeNumber} ${opts.afterTimeType}`,
});
} else if (response2.data.type === 'notification') {
MailPoet.Notice.success(
MailPoet.I18n.t('postNotificationActivated')
);
MailPoet.trackEvent('Emails > Post notifications activated', {
'MailPoet Free version': window.mailpoet_version,
Frequency: opts.intervalType,
});
}
MailPoet.Modal.loading(false);
});
}).fail((err) => {
this.showError(err);
this.setState({ loading: false });
MailPoet.Modal.loading(false);
});
default:
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
@ -141,43 +170,53 @@ define(
data: {
newsletter_id: this.props.params.id,
},
}).done((response) => {
// redirect to listing based on newsletter type
this.context.router.push(`/${this.state.item.type || ''}`);
}).done((response2) => {
// save template in recently sent category
this.saveTemplate(response, () => {
// redirect to listing based on newsletter type
this.context.router.push(`/${this.state.item.type || ''}`);
if (response.data.status === 'scheduled') {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterHasBeenScheduled')
);
MailPoet.trackEvent('Emails > Newsletter sent', {
scheduled: true,
'MailPoet Free version': window.mailpoet_version,
});
} else {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterBeingSent')
);
MailPoet.trackEvent('Emails > Newsletter sent', {
scheduled: false,
'MailPoet Free version': window.mailpoet_version,
});
}
}).fail(this._showError);
if (response2.data.status === 'scheduled') {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterHasBeenScheduled')
);
MailPoet.trackEvent('Emails > Newsletter sent', {
scheduled: true,
'MailPoet Free version': window.mailpoet_version,
});
} else {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterBeingSent')
);
MailPoet.trackEvent('Emails > Newsletter sent', {
scheduled: false,
'MailPoet Free version': window.mailpoet_version,
});
}
MailPoet.Modal.loading(false);
});
})
.fail((err) => {
this.showError(err);
this.setState({ loading: false });
MailPoet.Modal.loading(false);
});
}
})
.fail(this._showError)
.always(() => {
.fail((err) => {
this.showError(err);
this.setState({ loading: false });
MailPoet.Modal.loading(false);
});
}
return false;
},
handleResume: function (e) {
handleResume: function handleResume(e) {
e.preventDefault();
if (!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
this._save(e).done(() => {
this.saveNewsletter(e).done(() => {
this.setState({ loading: true });
}).done(() => {
MailPoet.Ajax.post({
@ -195,43 +234,43 @@ define(
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
});
})
.fail(this._showError)
.fail(this.showError)
.always(() => {
this.setState({ loading: false });
});
}
return false;
},
handleSave: function (e) {
handleSave: function handleSave(e) {
e.preventDefault();
this._save(e).done(() => {
this.saveNewsletter(e).done(() => {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
}).done(() => {
this.context.router.push(`/${this.state.item.type || ''}`);
}).fail(this._showError);
}).fail(this.showError);
},
handleRedirectToDesign: function (e) {
handleRedirectToDesign: function handleRedirectToDesign(e) {
e.preventDefault();
const redirectTo = e.target.href;
this._save(e).done(() => {
this.saveNewsletter(e).done(() => {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
}).done(() => {
window.location = redirectTo;
}).fail(this._showError);
}).fail(this.showError);
},
_save: function () {
saveNewsletter: function saveNewsletter() {
const data = this.state.item;
data.queue = undefined;
this.setState({ loading: true });
@ -255,32 +294,32 @@ define(
this.setState({ loading: false });
});
},
_showError: (response) => {
showError: (response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
response.errors.map(error => error.message),
{ scroll: true }
);
}
},
handleFormChange: function (e) {
handleFormChange: function handleFormChange(e) {
const item = this.state.item;
const field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item,
item,
});
return true;
},
render: function () {
const isPaused = this.state.item.status == 'sending'
render: function render() {
const isPaused = this.state.item.status === 'sending'
&& this.state.item.queue
&& this.state.item.queue.status == 'paused';
&& this.state.item.queue.status === 'paused';
const fields = this.state.fields.map((field) => {
const newField = field;
if (field.name == 'segments' || field.name == 'options') {
if (field.name === 'segments' || field.name === 'options') {
newField.disabled = isPaused;
}
return newField;
@ -307,7 +346,8 @@ define(
className="button button-primary"
type="button"
onClick={this.handleResume}
value={MailPoet.I18n.t('resume')} />
value={MailPoet.I18n.t('resume')}
/>
:
<input
className="button button-primary"
@ -315,23 +355,25 @@ define(
onClick={this.handleSend}
value={MailPoet.I18n.t('send')}
{...sendButtonOptions}
/>
/>
}
&nbsp;
<input
className="button button-secondary"
type="submit"
value={MailPoet.I18n.t('saveDraftAndClose')} />
value={MailPoet.I18n.t('saveDraftAndClose')}
/>
&nbsp;{MailPoet.I18n.t('orSimply')}&nbsp;
<a
href={
'?page=mailpoet-newsletter-editor&id=' + this.props.params.id
`?page=mailpoet-newsletter-editor&id=${this.props.params.id}`
}
onClick={this.handleRedirectToDesign}>
onClick={this.handleRedirectToDesign}
>
{MailPoet.I18n.t('goBackToDesign')}
</a>.
</p>
{ !isPaused && sendButtonOptions['disabled'] && sendButtonOptions['disabled'] === 'disabled' && (
{ !isPaused && sendButtonOptions.disabled && sendButtonOptions.disabled === 'disabled' && (
<HelpTooltip
tooltip={MailPoet.I18n.t('helpTooltipSendEmail')}
tooltipId="helpTooltipSendEmail"

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