Compare commits

...

524 Commits

Author SHA1 Message Date
ef27ac0b84 Update changelog for 0.0.43 2016-09-06 12:27:58 +03:00
2b4adef6c2 Bump up release version to 0.0.43 2016-09-06 12:13:21 +03:00
6223ef77d9 Merge pull request #605 from mailpoet/editor_fixes
Editor fixes
2016-09-02 08:30:59 -04:00
a423123b66 Add 3 new sample templates Becs prepared 2016-09-02 14:12:19 +03:00
1b3d3082b0 Fix text in template selection boxes to not overflow 2016-09-02 12:48:44 +03:00
fa117cc7dd Add an animation to display tools 2016-09-02 12:48:44 +03:00
acd407c1f1 Clarify label of preheader field 2016-09-02 12:48:44 +03:00
9baf4b068f Swapped block tool default and hover colors. Dark normally, light on
hover
2016-09-02 12:48:44 +03:00
18d852e147 Changed Trash and Move SVG icons to new ones for newsletter editor 2016-09-02 12:48:44 +03:00
b8dc306741 Merge pull request #604 from mailpoet/unit_tests
Unit tests (Cron Supervisor and Trigger)
2016-09-02 12:46:39 +03:00
6ea056c042 - Removes references to cron from webpack's configuration 2016-09-01 19:19:47 -04:00
bcf1b37c6a - Adds unit tests for Cron Trigger class 2016-09-01 19:19:47 -04:00
2986cdba85 - Removes Cron status from MailPoet's WP admin panel 2016-09-01 19:19:47 -04:00
53a8ae74e2 - Adds unit tests for Supervisor class
- Updates execution limit condition check in Supervisor
2016-09-01 19:19:47 -04:00
8bab01506c - Updates CircleCI configuration to run Apache 2016-09-01 19:18:47 -04:00
c664045444 Merge pull request #603 from mailpoet/welcome_page
Welcome and Update pages
2016-09-01 14:02:20 +03:00
6b8149210d Remove installed_at default value from default settings 2016-08-31 16:59:50 +03:00
f31d30b318 Merge pull request #602 from mailpoet/cron_update
Cron update
2016-08-31 16:48:05 +03:00
d9fbbdc02d - Updates code comments 2016-08-31 09:23:12 -04:00
8136ee2d9b Add a timestamp to log when the plugin was installed 2016-08-31 14:42:43 +03:00
f7cf6e2131 Welcome and Update pages 2016-08-31 13:51:33 +03:00
f2d1787bd5 - Updates site URL detection logic
- Adds unit test for Cron Helper class
2016-08-30 12:37:30 -04:00
fb51765d3f Bump up release version to 0.0.42 2016-08-30 13:04:08 +03:00
088ad5fb42 Merge pull request #597 from mailpoet/editor_fixes
Editor fixes
2016-08-29 15:43:47 -04:00
2f5b3c0c0a Merge pull request #600 from mailpoet/circle_ci
Adds CircleCI support
2016-08-29 15:15:39 -04:00
04ac4d896c Remove the build testing error 2016-08-29 21:38:26 +03:00
f3b96af863 Attempt to fix command status codes for Robo commands 2016-08-29 21:32:57 +03:00
eb42b0b98d Merge branch 'circle_ci' of github.com:mailpoet/mailpoet into circle_ci 2016-08-29 21:32:14 +03:00
304667eb49 - Testing commit e-mail 2016-08-29 14:27:40 -04:00
dad1082cd7 - Fixes JS unit test 2016-08-29 14:26:44 -04:00
37cf0f3d29 - Fixes JS unit test
:
2016-08-29 13:58:20 -04:00
d61c6dff58 - Fixes PHP unit test
- Fails JS unit test to check if CircleCI will detect it
2016-08-29 13:52:17 -04:00
3734ac578d - Fails test to check if CircleCI will detect it 2016-08-29 13:48:33 -04:00
b3f56c9d8e Merge pull request #599 from mailpoet/router_unit_tests
Router refactoring and unit tests
2016-08-25 17:33:44 +03:00
3603eeee77 - Updates remaining router endpoints to use constructor and new constants 2016-08-25 10:03:52 -04:00
59d30cc139 - Renames router URL query parameter and router class
- Updates other classes to use the new name
- Updates unit tests
2016-08-25 09:57:14 -04:00
6ff3bbbb72 - Fixes type in method name 2016-08-24 23:35:45 -04:00
a561e10156 - Updates tests for view in browser and statistics tracking 2016-08-24 23:35:34 -04:00
99f2cf6702 - Adds unit tests for front router 2016-08-24 23:27:12 -04:00
c6b72e729b - Refactors front router and endpoints to use dynamic methods 2016-08-24 23:26:13 -04:00
c5bc0f36a4 Disable running PHP coverage reports 2016-08-25 00:46:29 +03:00
efc5c34bf9 Add running PHP unit test coverage reports in CircleCI 2016-08-25 00:34:58 +03:00
3929efbdd9 Enable running JS tests 2016-08-25 00:09:34 +03:00
0e0c41882e Generate XML report for unit tests and add it to CircleCI tracking 2016-08-25 00:05:41 +03:00
79cc708fc6 Set UTC timezone for CircleCI PHP version 2016-08-24 23:50:59 +03:00
8fa98879b8 Enable debugging when running tests 2016-08-24 23:46:22 +03:00
331ba385e9 Switch MySQL host to 127.0.0.1 instead of localhost 2016-08-24 23:30:19 +03:00
71ce46d78d Add running PHP tests 2016-08-24 23:13:06 +03:00
c493de6569 Try to output JS test results in jUnit format for CircleCI 2016-08-24 21:44:22 +03:00
ff2c2ace86 Add running JS tests in CircleCI 2016-08-24 21:29:58 +03:00
7fa789cfd1 Merge pull request #598 from mailpoet/view_in_browser_update
View in browser update
2016-08-24 19:02:15 +03:00
ae6269eb63 - Restricts router access to explicitly defined endpoint actions 2016-08-24 11:23:12 -04:00
a8f4779bfe - Updates code formatting 2016-08-24 10:22:10 -04:00
6868142e35 - Extracts view in browser response to the endpoint
- Updates unit tests
2016-08-24 10:20:35 -04:00
133d123919 - Updates front router and endpoints to use dynamic methods 2016-08-24 10:20:10 -04:00
05c128d12d - Fixes errors thrown when there are no shortcodes in the newsletter body 2016-08-24 09:29:17 -04:00
bdab0c12fa Fix debouncing for ALC refresh to not update multiple times immediately 2016-08-24 16:15:53 +03:00
75b94690e2 - Adds unit tests 2016-08-23 23:42:56 -04:00
80fddd6c58 - Refactors view in browser 2016-08-23 23:42:26 -04:00
c807ead5fd - Prepares newsletter renderer for conversion to using modal objects
instead of arrays
2016-08-23 12:48:38 -04:00
f004bb5368 - Set default preview email to be current user's email;
- Change "Preview in browser" form to autocomplete used emails.
2016-08-23 19:32:10 +03:00
1d756e95a7 Bump up release version to 0.0.41 2016-08-23 12:27:14 +03:00
1fb0da9fda Merge pull request #587 from mailpoet/open_stats_fix
Prevents tracking opens/clicks from subscribers who the newsletter was not sent to
2016-08-23 12:17:51 +03:00
a0017b91ee Merge pull request #592 from mailpoet/editor_fixes
Editor fixes
2016-08-22 20:43:24 -04:00
444ab17342 - Updates statistics tracking unit tests 2016-08-22 16:24:33 -04:00
44f3058326 - Adds unit test for tracker router endpoint 2016-08-22 16:24:13 -04:00
ec09fbcb78 - Converts static classes to dynamic 2016-08-22 16:22:33 -04:00
ed352bb1d3 - Passes wp_user_preview parameter to custom link shortcode filter 2016-08-22 09:26:25 -04:00
375bbd2759 - Calls an open tracking class when tracking clicks 2016-08-22 09:26:25 -04:00
9fb9d25132 - Uses model method to get queue 2016-08-22 09:26:25 -04:00
30f79aa589 - Updates unsubscribe tracking logic 2016-08-22 09:26:25 -04:00
69f8daac95 - Updates wp user & preview check condition 2016-08-22 09:26:25 -04:00
03f3a6080c - Returns empty response or an image in all cases 2016-08-22 09:26:25 -04:00
44f84c6cdb - Updates method names 2016-08-22 09:26:25 -04:00
31008a6895 - Updates queue and subscriber check condition 2016-08-22 09:26:25 -04:00
2490d8c919 - Refactors browser preview 2016-08-22 09:26:25 -04:00
5886dbfd25 - Refactors statistics tracking 2016-08-22 09:26:25 -04:00
e48d55f0b1 - Adds new model methods 2016-08-22 09:26:25 -04:00
42339927cf - Extends ORM's isNew() method to work on saved models 2016-08-22 09:26:25 -04:00
b492bcecc0 - Removes requirement of passing newsletter id when tracking clicks
- Extracts common tracking data processing/validation code into the Track
  class
- Refactors Clicks, Opens and View in Browser classes to enforce
  subscriber id and token check
- Allows admin users to preview newsletters without tracking statistics
2016-08-22 09:26:25 -04:00
6ab7debb7b - Fixes code formatting 2016-08-22 09:26:25 -04:00
b76ce6c26f - Adds verification of newsletter-to-queue and subscriber-to-queue
- Prevents tracking open/click rates for subscribers who the newsletter
  was not sent to
2016-08-22 09:26:25 -04:00
6fbc7b1593 Add "View in browser" to happen on the same page, instead of opening new
window
2016-08-19 18:48:29 +03:00
69c8670b01 Add an option to open settings by clicking on the block for image,
button, social, divider and spacer blocks
2016-08-19 14:57:14 +03:00
da44a87415 Change onkeyup to oninput events to correctly detect pasting into
inputs
2016-08-18 16:55:53 +03:00
9fb17d4a6b Fix "Preview in browser" notice to display a proper success message 2016-08-18 15:07:20 +03:00
16dd286f9d Merge pull request #591 from mailpoet/missing_response
missing response in fail and prevent next on MC import
2016-08-18 12:07:05 +03:00
5025f10f9f missing response in fail and prevent next on MC import 2016-08-17 16:15:54 +02:00
1278d9648c Merge pull request #590 from mailpoet/api_uniform_c
Custom fields & Import & Export
2016-08-17 16:18:53 +03:00
289811a595 Updated Import & MailChimp tests 2016-08-17 13:32:29 +02:00
916ae97f73 Updated export unit test 2016-08-17 12:51:50 +02:00
876e386966 converted export 2016-08-17 12:23:15 +02:00
9582e58dda converted import 2016-08-17 12:16:58 +02:00
213bca8050 fixed rendering of date block + fixed validation for dates 2016-08-16 14:41:53 +02:00
dc97d3115e updated Custom Field endpoint + Unit tests + form editor update 2016-08-16 12:40:10 +02:00
90eb443965 Merge pull request #582 from mailpoet/custom_field_fix
Custom field fix
2016-08-16 12:00:28 +02:00
1b40f02715 Bump up release version to 0.0.40 2016-08-16 12:30:53 +03:00
c5a02c6136 - Allows setting empty value for date custom fields 2016-08-14 13:00:28 -04:00
492cd8c96b Merge pull request #583 from mailpoet/editor_fixes
Editor fixes
2016-08-12 16:40:08 +02:00
7f091d7188 - Fixes rebase screwup 2016-08-12 10:38:15 -04:00
1c081623b9 When there's no social icon image URL, revert to "Image not found" image 2016-08-12 17:30:11 +03:00
87332037c2 Do not render images or social icons if image src is not defined 2016-08-12 16:18:44 +03:00
62023397f4 Remove example URLs from social icons, leave placeholders 2016-08-12 15:47:53 +03:00
37ec6dc1a6 Fix handling of images with empty src in newsletter editor 2016-08-12 14:40:21 +03:00
81c277ca93 - Update import to autodetect dates (UI) and convert them to datetime
format (backend)
- Fixes unit test
- Fixes code formatting in Date class
2016-08-12 00:29:57 -04:00
f8fea75130 - Updates date conversion method
- Uses Moment to parse dates in UI
- Updates Custom Field model to utilize date conversion method
- Adds unit test
2016-08-11 21:11:57 -04:00
a4457649f7 Fix saving on last newsletter step to JSON encode newsletter body 2016-08-11 18:58:55 +03:00
1d6a09f010 Modify ALC to ignore posts published before notification newsletter is
created
2016-08-11 16:46:47 +03:00
faec553521 Enable closing of a sidebar section in newsletter editor 2016-08-11 16:46:47 +03:00
37fcf3a234 Fix newsletter template titles to not overlap Delete controls 2016-08-11 16:46:47 +03:00
68a56aada8 Fix default footer to allow toggling bold and italic on sentences 2016-08-11 16:46:47 +03:00
f744305834 - Change default URLs to blank;
- Fix image, button, social icon URL placeholders;
- Remove links to example.org.
2016-08-11 16:46:47 +03:00
7a9402f5b5 Merge pull request #586 from mailpoet/api_uniform_f
updated cron endpoint + cron.jsx
2016-08-11 16:42:31 +03:00
de6d7e0cae updated cron endpoint + cron.jsx 2016-08-11 12:36:17 +02:00
a3c56b84ce Merge pull request #585 from mailpoet/api_uniform_e
Api uniform (NewsletterTemplates)
2016-08-11 12:34:16 +03:00
3d4defd563 Fixed unit tests for newletter templates
- changed null to false for default value of id in nltemplates endpoint
2016-08-10 16:51:04 +02:00
52da08abb2 converted newsletterTemplates endpoint + react 2016-08-10 15:50:07 +02:00
b9637b52e9 Merge pull request #584 from mailpoet/api_uniform_d
Api uniform (Mailer & SendingQueue)
2016-08-10 15:04:17 +03:00
0369a23fe8 Call different actions for standard and automated newsletters on Send step
- converted save and setStatus method of Newsletters endpoint
- updated pause/resume mixin for notification & welcome listings
- use 'newsletter_id' instead of 'id' in SendingQueue endpoint (less confusing)
- added NOT_FOUND constant to APIError
- fixed unit test for Newsletters endpoint save and added test for setStatus
2016-08-10 13:09:35 +02:00
f690e1a095 return sending queue object for standard / newsletter for automated\n- bugfix loading screen not going away\n- fixed not being used in sending queue endpoint 2016-08-10 13:09:35 +02:00
22e8e34213 unify getData() response 2016-08-10 13:09:35 +02:00
12b46736c5 updated sending queue endpoint + react (pause/resume/send) 2016-08-10 13:09:35 +02:00
4950e47297 updated mailer endpoint 2016-08-10 13:09:35 +02:00
d7c5c8c3e7 Bump up release version to 0.0.39 2016-08-09 12:09:38 +03:00
46b0fcf37b - Adds subscriber data validation; specifically for custom fields with
date type
2016-08-08 18:23:11 -04:00
b07c4d0e6e - Adds date validation based on date format 2016-08-08 18:23:11 -04:00
eb107799a7 - Fixes varable name typo
- Declares a new variable during date validate
2016-08-08 18:23:11 -04:00
4eec0a42f9 - Forces date validation to use custom custom field parameter 2016-08-08 18:23:11 -04:00
9a5a3a08c6 - Fixes default date format not being set 2016-08-08 18:23:11 -04:00
151683c632 - Updates Import to use the existing custom field creation code
- Closes #499
2016-08-08 18:23:11 -04:00
fd2103d1aa - Extracts custom field specific templates/code into a separate template
file
2016-08-08 18:23:11 -04:00
7696b6ec5d - Replaces depreciated Notice .error() method with .fail() 2016-08-08 18:23:11 -04:00
d972b96255 - Updates custom field editor to display errors inside the form 2016-08-08 18:23:11 -04:00
35ccfb8bcf Merge pull request #564 from mailpoet/cron_update
Cron update
2016-08-08 16:59:27 +03:00
7ff036b1e9 - Removes depreciated variables
- Updates method names
2016-08-08 09:45:16 -04:00
983d56c29b - Updates default cron trigger method in Populator and Settings 2016-08-08 09:07:01 -04:00
a2528939ba Merge pull request #581 from mailpoet/api_uniform_b
updated ALC endpoint + nl editor + js tests
2016-08-08 15:00:26 +03:00
c136d91dd2 encapsulating in communication component + update js tests 2016-08-08 11:36:29 +02:00
bf00e82596 - Fixes sending limits not being enforced 2016-08-07 11:39:05 -04:00
0e10f6c820 - Fixes merge conflict 2016-08-05 13:03:31 -04:00
c056e95249 - Rebases master
- Updates sending limit logic
2016-08-05 13:03:31 -04:00
1be7fda1cf - Updates daemon request timeouts 2016-08-05 13:03:31 -04:00
0b0c0f5759 - Fixes conditional statement 2016-08-05 13:03:31 -04:00
59a4428965 - Fixes class naming conflict 2016-08-05 13:03:31 -04:00
3f5c36d2d4 - Fixes blocking HTTP request issue
- Simplifies cron supervisor
2016-08-05 13:03:31 -04:00
3cc5812c1d - Removes exception throwing that can disable plugin 2016-08-05 13:03:31 -04:00
3e616201ad - Encasupsulates trigger methods logic into separate classes
- Updates cron router
2016-08-05 13:03:31 -04:00
5558ebad45 - Updates the name of the setting const 2016-08-05 13:03:31 -04:00
63bd093f35 - Renames TaskScheduler to CronTrigger and updates relevant code
- Standardizes setting value const naming convention
2016-08-05 13:03:31 -04:00
ec6559b8be - Removes unused method from Settings model
- Renames method to delete value
2016-08-05 13:03:31 -04:00
3421406dc7 - Removes unused const 2016-08-05 13:03:31 -04:00
a2917c08f6 - Switches methods used to get cron settings 2016-08-05 13:03:31 -04:00
5fa9b5a8dd - Updates method name that returns mailer configuration 2016-08-05 13:03:31 -04:00
41ad86ba1f - Fixes code style 2016-08-05 13:03:31 -04:00
067b3ff3e6 - Updates Mailer class to use default values from Settings 2016-08-05 13:03:31 -04:00
9b9cb1455a - Updates cron/mailer/scheduler code to work with the new sending queue
task scheduler and mailer log
2016-08-05 13:03:31 -04:00
a5569a6a55 - Adds new sendiing queue task responsible for managing task scheduler 2016-08-05 13:03:31 -04:00
71c1026729 - Adds new class responsible for managing mailer log 2016-08-05 13:03:31 -04:00
f102e847bf - Stops cron daemon when settings are changed from MailPoet to WordPress
task scheduler
2016-08-05 13:03:31 -04:00
3158e2c460 - Updates cron router to properly return the daemon status 2016-08-05 13:03:31 -04:00
a438f13bb0 - Modifies cron router/UI to display proper status message when WP task
scheduler is configured and cron is not running
- Updates sending queue worker and related components to stop (delete)
  cron when all processing is done
2016-08-05 13:03:31 -04:00
5ed0a5819c - Updates settings to use task scheduler method names from the newly
introduced config class
2016-08-05 13:03:31 -04:00
6dd3c6acda - Adds new task scheduler configuration class
- Introduces method to start cron on demand when there are
  scheduled newsletters or queues in progress
2016-08-05 13:03:31 -04:00
ca2c1c2e6f Merge pull request #580 from mailpoet/alc_posts
ALC Post filtering
2016-08-05 11:53:55 -04:00
1305a10ee0 updated ALC endpoint + nl editor + js tests 2016-08-05 15:25:54 +02:00
5e36eb818b Fix sending first notification newsletter with ALC posts 2016-08-05 13:40:54 +03:00
cfde82ff5f Declaring class properties of AutomatedLatestContent 2016-08-04 18:32:30 +03:00
af98ade650 Add ALC filter to use posts created after last newsletter was sent 2016-08-04 18:17:49 +03:00
598432466e - Rename wysija-newsetters.pot to mailpoet.pot
- Add pot file regeneration on plugin build
2016-08-04 18:16:43 +03:00
9469ce83f1 Fix creation and update timestamps for notification history newsletters 2016-08-04 18:16:43 +03:00
5624f4c7a0 Add new Robo task to run all QA tasks in one go 2016-08-04 18:16:43 +03:00
82a001dc05 Add timestamp based ALC filter to ignore older posts 2016-08-04 18:16:43 +03:00
a9b424fb79 Merge pull request #579 from mailpoet/notification_history_preview
Enables preview of notification history newsletters
2016-08-04 18:12:50 +03:00
d31af9d71c - Adds additional logic to not exclude sent posts when previewing from
within the newsletter editor
2016-08-04 10:30:53 -04:00
ff7a24590f - Removes unnecessary condition 2016-08-04 09:46:05 -04:00
ea87a7acf8 - Fixes code style
- Removes unnecessary condition
- Closes #576
2016-08-04 09:36:15 -04:00
9d36a17261 - Fixes preview of newsletters with ALC 2016-08-03 21:36:03 -04:00
9c3cb5a509 - Enables preview of notification history newsletters 2016-08-03 20:42:35 -04:00
4dd7f32f3a Merge pull request #578 from mailpoet/api_uniform
Api unification - Step 1
2016-08-03 17:27:09 +03:00
1c6fca7f83 fixed ErrorResponse call in API::setupPublic()
- removed empty constructors in updated endpoints
- added missing keys to Error class (unauthorized, forbidden)
2016-08-03 16:04:45 +02:00
c5b376bd21 satisfy code sniffer rule 2016-08-03 15:08:27 +02:00
5d2800bc25 added API/Error class to hold error keys as constants
- re-added Setting::getAll() to API/Setting::set() in response
- updated settings/setup tests
2016-08-03 15:04:25 +02:00
6675d5a20d added default error messages to errorResponse and badRequest 2016-08-03 14:08:22 +02:00
28c39d301c Added default error response in case no errors were specified
- converted Setup endpoint
- unit tests for Setup endpoint
2016-08-03 12:41:21 +02:00
afa0d3af63 Updated Ajax.js to avoid promise workaround
- Removed get method in ajax.js as it's useless
2016-08-02 18:08:12 +02:00
b05344b1d3 added missing data in deferred.resolve() 2016-08-02 17:18:18 +02:00
2e88d7cce0 Added API/Endpoint abstract class
- (re)Added Endpoints folder to both API and Router
- fixed syntax in namespaces
- xhr.responseJSON is returned to the fail()
- fixed Router endpoints (view in browser, cron,...)
2016-08-02 17:08:43 +02:00
cb558ce2ab Bump release version up to 0.0.38 2016-08-02 16:49:11 +03:00
ed30d8f639 externalize Success/ErrorResponse classes into their own files 2016-08-01 17:22:23 +02:00
9410d4f10a Reorganized new API + added legacy API support + new API
- Updated Settings Router to new standards
- Updated settings.html to reflect API change with better error handling
- Updated Settings API unit tests
2016-08-01 17:00:32 +02:00
354d249e1d Moved current Router files to API
- updated Unit tests to reflect the change
2016-08-01 17:00:32 +02:00
008fdb94c5 Moved lib/API to lib/Router
- renamed lib/API/API.php to lib/Router/Front.php
- updated namespaces in various file to account for namespace change
2016-08-01 17:00:32 +02:00
d0fb94b3f8 Merge pull request #577 from mailpoet/newsletter_templates
Newsletter templates
2016-07-29 16:23:12 +02:00
a451f00ed3 Remove obsolete function 2016-07-29 17:21:14 +03:00
0e0c371b28 Swap Preview and Select buttons in template select page 2016-07-29 17:15:20 +03:00
2e52f3bb92 Add a "Preview" button in template select page 2016-07-29 17:11:30 +03:00
a3a5016278 Allow template titles to be displayed in multiple lines w\o ellipsis 2016-07-29 16:56:07 +03:00
cb5d7cb9a0 Fix incorrect post notification template logo URL 2016-07-29 16:52:55 +03:00
a44d4ed0b5 Add new newsletter sample templates 2016-07-29 16:29:44 +03:00
7bd23288f6 Add newsletter blank 1 column template 2016-07-29 16:28:44 +03:00
08c663759c Merge pull request #574 from mailpoet/copy-edit
Copy edit
2016-07-29 13:34:57 +03:00
c46ee07674 Change form editor page title, swap "New" to "Add New" 2016-07-29 13:30:17 +03:00
18398a3bfb Finish changing segments to lists and columns to fields 2016-07-29 13:30:17 +03:00
88d9315f8b Finish converting Segment to List in language strings 2016-07-29 13:30:17 +03:00
8bc95db0c9 Change items to item(s), fix Twig syntax errors 2016-07-29 13:29:05 +03:00
c05a20cff9 Update 28 July 2016 2016-07-29 13:29:05 +03:00
08cb994252 Merge pull request #573 from mailpoet/text_version_fix
Fixes link rendering in text version of the newsletter
2016-07-28 16:17:13 +03:00
543ad81e28 Merge pull request #575 from mailpoet/archives_page
Newsletter Archives page
2016-07-28 15:23:50 +03:00
641ba04685 Added Newsletter::getArchives() in order to return proper archives
- Archives page: replaced created_at by processed_at as the issue date
2016-07-28 11:52:56 +02:00
8e4d07c658 - Updates regex to not match http/ftp links as shortcodes
- Updates regex to properly replace links in text version of newsletter
2016-07-27 21:57:55 -04:00
420650f37f Merge pull request #572 from mailpoet/error_handling
Display plugin initialization errors to admin
2016-07-27 15:38:57 +02:00
775f7faee4 Declare private properties and swap strings for named constants 2016-07-27 16:35:21 +03:00
13b91ad051 Encapsulate showing WP notices into a separate Notice class 2016-07-27 15:07:09 +03:00
2d3ec13473 Stringify exception to include the stack trace as well 2016-07-27 13:58:44 +03:00
3094cfc076 Prevent plugin from disabling itself on error, display that error to
admin instead
2016-07-26 16:18:33 +03:00
6aadd1fdc4 Bump up release version to 0.0.37 2016-07-22 23:17:45 +03:00
13589a4660 Merge pull request #552 from mailpoet/newsletter_listing
Post notification history listing
2016-07-22 16:20:00 +03:00
5f124659d0 - Fixes PHP static standards error 2016-07-22 08:45:46 -04:00
d3ebc9706c - Updates unit test 2016-07-22 08:45:32 -04:00
e83d01ff28 - Avoids sending duplicate posts 2016-07-21 20:54:32 -04:00
9600e4f220 Merge pull request #569 from mailpoet/manage_subscription_improved
Manage Subscriptions fixes
2016-07-21 17:31:45 +03:00
3e746d1545 fixed API data decoding issue
- added missing features from issue #419
- removed isMailPoetPage() as the logic was flawed
2016-07-21 15:10:25 +02:00
3cc43aa302 Merge pull request #566 from mailpoet/wp_users_fix
Deleting a WP user
2016-07-20 14:32:36 +03:00
c610d87e85 Merge pull request #567 from mailpoet/form_subscription_signups
Number of signups in forms listing
2016-07-20 13:01:59 +03:00
362ee49ce4 Let the statisticsForms model return the total signups instead of the form model
- added unit test for getTotalSignups() method
2016-07-19 17:38:45 +02:00
515515ba9f updated class names of table columns in listing 2016-07-19 17:24:56 +02:00
ed7da1a8fe Deleting a WP user unlinks the subscriber and removes his subscription to the WP Segment 2016-07-19 17:13:20 +02:00
12c036dbef refactored Models/Newsletter::getStatistics method to avoid duplication
- replaced "TO REFACTOR" with more conventional "TODO"
2016-07-19 15:34:14 +02:00
1dd4ade04d added signups to forms listing 2016-07-19 13:44:32 +02:00
0706450f9a Add children() method to Newsletter model to get child newsletters (history in case of post notif)
- added conditional display of "view history" link in Notification listing
- fixed indentation in duplicatePostNotif method according to code sniffer report
2016-07-18 16:47:12 +02:00
b837a153d1 merged post_notification_update 2016-07-18 16:06:04 +02:00
6d22a85fd7 use mixins to render regular newsletters queue status & statistics 2016-07-18 16:01:47 +02:00
3d706414b7 Renamed tab to type
- renamed getExtraParams to getParams
- fixed issue with String.contains by replacing it with indexOf
- removed useless break; statement
2016-07-18 16:01:47 +02:00
ef0cbb3e9f Added "params" to the $data in Listing Handler
- moved "tab" to params
- improved url generation in listing.jsx to allow more flexibility
- added "parent_id" filter in newsletter model to get children of a given newsletter id
2016-07-18 16:01:47 +02:00
f5552847a3 Added parent_id to Newsletters table
- added NOTIFICATION_HISTORY Newsletter's type
- implement basic UI for notification_history
- TODO: implement passing extra parameters in order to handle the :id part
2016-07-18 16:01:47 +02:00
101ef0cff4 Fixed stats for welcome emails
- re-added ORM logging in order to debug queries
- fixed subscription confirmation / unsubscribe due to API refactor
2016-07-18 16:01:47 +02:00
9e70ba5e6e - Reverts back the duplicate method
- Updates post notification creation method with logic to duplicate the
  original newsletter
2016-07-15 13:07:00 -04:00
8f1a7ed3de - Sets notification history status to "sent" upon completion
- Implements #548
2016-07-15 13:06:40 -04:00
6aa976ba1f - Creates a new notification history when processing the queue 2016-07-15 13:06:40 -04:00
db85604f18 - Removes scheduling of post notification during newsletter activation stage 2016-07-15 13:06:40 -04:00
7605fc71ac - Adds a method to create a notification history newsletter 2016-07-15 13:00:47 -04:00
2c98270084 - Adds new column to the newsletter table
- Adds new newsletter notification history type
2016-07-15 13:00:47 -04:00
a5300624c2 - Bump up release version to 0.0.36;
- Fix translation strings in unit tests
2016-07-15 15:59:40 +03:00
2714c7fa9a Merge pull request #563 from mailpoet/copy-edit
Copy edit - July 15 2016
2016-07-15 15:31:50 +03:00
49b65729db update 14 July 2016 2016-07-14 20:58:12 +02:00
e053b62a70 Merge pull request #558 from mailpoet/task_scheduler_option
Adds task scheduler option to settings
2016-07-14 16:47:15 +03:00
88113cf22e - Modifes task scheduler setting to include method type 2016-07-14 09:44:16 -04:00
5bf352e9fc Merge pull request #559 from mailpoet/ajax_update
Removes object keys with null values when doing an ajax request
2016-07-14 13:44:43 +03:00
5a7d5ac3f0 - Removes object keys with null values when doing an ajax request 2016-07-13 21:06:13 -04:00
05848ce7aa - Adds task scheduler option to settings
- Closes #553
2016-07-13 19:49:18 -04:00
b58d996ac7 Merge pull request #551 from mailpoet/newsletter_archive
Newsletter archive: link to browser version
2016-07-12 17:19:27 +02:00
16fa4491a5 Merge pull request #550 from mailpoet/empty_newsletter
Fix notices and warnings in newsletter preview when there's no template
2016-07-12 17:05:09 +02:00
456deede14 Change newsletter archive to link to "View in brower" newsletter
versions
2016-07-12 17:55:36 +03:00
4ef50ca551 Fix notices and warnings in newsletter preview when there's no template 2016-07-12 17:11:13 +03:00
5ff7d98b00 Merge pull request #547 from mailpoet/post_notification_fix
Fixes post notifications not being sent
2016-07-12 11:15:37 +02:00
3018dff1ff Edit July 12 2016 2016-07-12 11:07:36 +02:00
9386fe8328 - Fixes post notifications not being sent 2016-07-11 18:56:01 -04:00
e4213437e9 Bump up release version to 0.0.35 2016-07-08 14:57:30 +03:00
386bdceed3 Merge pull request #543 from mailpoet/api_refactor
API refactor
2016-07-08 13:55:01 +03:00
87eda71931 Merge pull request #544 from mailpoet/issue_434
Settings Page
2016-07-08 13:28:06 +03:00
cef9f1dcf8 - Updates constant names 2016-07-07 14:32:05 -04:00
c78b2088eb - Updates the check for invalid API endpoint 2016-07-07 14:20:27 -04:00
150364de3a - Fixes API endpoint naming convention
- Generates/saves cron daemon token as soon as its executed
2016-07-07 14:00:07 -04:00
1cd9f3eb67 - Removes counter from cron daemon
- Invokes token regeneration/comparion at a later stage
2016-07-07 10:24:24 -04:00
89253125af - Fixes a typo 2016-07-07 10:00:12 -04:00
e7ee356f90 cleanup permissions related classes 2016-07-07 15:49:03 +02:00
a88017400b Remove email validation as it was not working properly
- using the MailPoet sending method forces signup confirmation
- save settings when activating a sending method (works when pressing enter in an input when setting up method)
2016-07-07 15:36:59 +02:00
f557881462 - Updates code based on review comments 2016-07-07 09:01:59 -04:00
8dba4727c4 - Updates open/click link generation logic to utilize API's buildRequest
method
2016-07-06 22:57:39 -04:00
8ec094089f - Removes TODO notice 2016-07-06 20:18:02 -04:00
3c353e715b - Fixes view in browser API URL 2016-07-06 19:56:26 -04:00
ab33a9c352 - Updates cron API URL
- Removes cron daemon counter
- Generates/saves cron daemon token as soon as its executed
2016-07-06 19:48:16 -04:00
406b509ac4 Remove saving of roles & permissions when saving settings
- make sure we try to activate the sending method instead of saving settings when pressing enter in an input
- added Default sender row with global from/reply_to
- hide notification emails setting
- removed notification from/reply to email (for the time being, we will reintroduce it if need be later on)
2016-07-06 17:29:24 +02:00
bd814baf28 - Fixes data not being passed to API buildRequest method 2016-07-06 10:02:47 -04:00
2db681d908 - Adds and centralizes API data encoding/decoding method 2016-07-06 09:22:34 -04:00
37e3af584e added parsley validation on settings form - need to fix permissions 2016-07-06 14:12:30 +02:00
5fc863bd82 Settings page update (issue #434) 2016-07-06 14:10:46 +02:00
76649f9590 removed debug mode and roles and permissions from advanced tab 2016-07-06 13:56:11 +02:00
cb2faec8b2 - Refactors API
- Updates existing classes to use the refactored API methods
2016-07-05 20:17:25 -04:00
e8604284fe Merge pull request #540 from mailpoet/issue_431
Bulk actions messages + remaining UI items from issue 431
2016-07-05 17:59:15 +03:00
d2ccdef6c7 better alternative to remove duplicate MailPoet submenu 2016-07-05 16:55:03 +02:00
38199dc96f - Adds validation for API data 2016-07-05 10:20:30 -04:00
630b219e96 Merge pull request #539 from mailpoet/twig_caching
Fix Twig cache regeneration
2016-07-05 09:57:02 -04:00
64155bc121 Merge pull request #541 from mailpoet/populator_settings
Do not reset plugin settings on plugin reactivation
2016-07-05 14:42:28 +02:00
7fb45a15ee Fix code style errors 2016-07-05 15:28:38 +03:00
ed5294477f Fix Populator to not overwrite existing settings 2016-07-05 15:23:11 +03:00
d152b073a6 fixed onSuccess on bulk actions and locale formatted numbers in success messages 2016-07-05 13:58:12 +02:00
f8efb3934b remove 'MailPoet' submenu and make newsletters the default page 2016-07-05 13:16:14 +02:00
5a21d3fdc8 added missing 'row-title' class on listings 2016-07-05 11:44:49 +02:00
710ede15ce sending method daily emails frequency in locale 2016-07-05 10:49:12 +02:00
150286ab6b Enable regenerating templates that have changed 2016-07-04 18:05:50 +03:00
9e758e8a33 Bump up release version to 0.0.34 2016-07-01 16:57:48 +03:00
059165e5d2 Merge pull request #536 from mailpoet/manage_subscriptions
Manage subscriptions
2016-07-01 16:17:50 +03:00
fe154d9251 fixed code sniffer reported errors 2016-07-01 14:17:39 +02:00
a8ffbc2d0e handle empty/unchecked/checked checkboxes properly in both react and forms 2016-07-01 14:14:18 +02:00
9de3a245b0 fixed both radio & checkbox fields so that it selects the proper value 2016-07-01 14:14:18 +02:00
5eef709af5 Uniform date display format for Manage Subscriptions & Subscriber new/edit
- use isWPUser instead of wp_user !== null
2016-07-01 14:14:18 +02:00
7b0c130d0a updated unit test for custom fields of date type 2016-07-01 14:14:18 +02:00
7f265675b0 changed the way custom field date type is handled (react + form + db) 2016-07-01 14:14:18 +02:00
ba15db9829 fixed value loading for textarea 2016-07-01 14:14:18 +02:00
d15473a8e4 disabled first/last name inputs for WP User on manage subscription page 2016-07-01 14:14:18 +02:00
d9f93dc6e7 Merge pull request #537 from mailpoet/qa
QA Tools and improvements
2016-06-30 18:17:05 +02:00
634c5b699d Remove leftover merge conflict, fix empty ALC block message #505 2016-06-30 19:12:26 +03:00
23e8ce38dd Merge remote-tracking branch 'origin/qa' into qa
Conflicts:
	lib/Config/Initializer.php
	lib/Cron/Workers/SendingQueue/SendingQueue.php
	lib/Models/SendingQueue.php
	lib/Router/Router.php
2016-06-30 19:01:44 +03:00
c62ae2ce80 Add PHP CodeSniffer option to reduce severity, fixed syntax error 2016-06-30 18:52:07 +03:00
d0813bb4e2 Fix class and method names to use camel case 2016-06-30 18:52:07 +03:00
0ac701eb20 Change line endings from DOS CRLF to Unix LF 2016-06-30 18:52:07 +03:00
607395be6f Fix spacing around commas 2016-06-30 18:52:07 +03:00
55d48df8a4 Fix indentation issues 2016-06-30 18:50:48 +03:00
e0282ae45b Fix empty catch statement error 2016-06-30 18:50:48 +03:00
235fdea00f Remove commented out code, raise code similarity trigger treshold 2016-06-30 18:50:48 +03:00
b8c6d54f48 Fix "Closing brace must be on a line by itself" code sniffer errors 2016-06-30 18:50:48 +03:00
67661e3aad Remove useless constructors 2016-06-30 18:50:48 +03:00
c03facdc45 Add space after comma in function call parameters 2016-06-30 18:49:50 +03:00
9ddc1ef555 Remove statements that cannot be executed 2016-06-30 18:49:50 +03:00
9cfc2fd940 Remove an unnecessary return statement 2016-06-30 18:49:50 +03:00
24e108bce7 Remove spaces after type casts 2016-06-30 18:49:50 +03:00
48f0c03425 Fix spacing between control structure and opening parenthesis 2016-06-30 18:46:33 +03:00
0bfbe6dc79 Change TRUE, FALSE, NULL capitalization to lowercase 2016-06-30 18:46:33 +03:00
ad0a9838bc Disable line width limits 2016-06-30 18:46:33 +03:00
81ec293e54 Remove rule that prevents statements with only comments in body 2016-06-30 18:46:32 +03:00
b8b3d76a1d Remove PHPMD and PHPCPD tools we don't use 2016-06-30 18:46:32 +03:00
805e641d40 Add PHP lint and PHP code sniffer 2016-06-30 18:46:32 +03:00
18326f9df1 Merge pull request #535 from mailpoet/sending_queue_refactor
Fixes error that resulted in additional newsletter to be sent
2016-06-30 16:39:45 +02:00
46dda84012 - Moves queue subscriber handling logic to the queu model 2016-06-30 10:23:06 -04:00
9979261cb6 fixed a few more warnings 2016-06-30 15:42:58 +02:00
e8887e2aa5 Add PHP CodeSniffer option to reduce severity, fixed syntax error 2016-06-30 15:24:50 +03:00
4a91fae984 Fix class and method names to use camel case 2016-06-30 15:13:48 +03:00
0fe975f614 - Declares array 2016-06-30 07:52:16 -04:00
c7fd7b8a32 Change line endings from DOS CRLF to Unix LF 2016-06-30 14:39:28 +03:00
b7e3c3ae81 Fix spacing around commas 2016-06-30 14:03:07 +03:00
b2c3206185 - Fixes error that resulted in additional newsletter to be sent 2016-06-30 06:42:56 -04:00
b7d8d482fe Fix indentation issues 2016-06-30 13:29:23 +03:00
8a9d14319b Fix empty catch statement error 2016-06-30 12:40:22 +03:00
c396254e64 Remove commented out code, raise code similarity trigger treshold 2016-06-29 21:19:24 +03:00
e4dbeca664 Fix "Closing brace must be on a line by itself" code sniffer errors 2016-06-29 20:48:14 +03:00
168540d6d2 Remove useless constructors 2016-06-29 20:42:03 +03:00
c62cd6c023 Add space after comma in function call parameters 2016-06-29 19:26:07 +03:00
033e0581f1 Remove statements that cannot be executed 2016-06-29 19:20:50 +03:00
9f978d3362 Remove an unnecessary return statement 2016-06-29 19:09:07 +03:00
841340a42d Remove spaces after type casts 2016-06-29 19:04:23 +03:00
9595e9629f Fix spacing between control structure and opening parenthesis 2016-06-29 18:54:01 +03:00
56ba543f8d Change TRUE, FALSE, NULL capitalization to lowercase 2016-06-29 18:38:38 +03:00
1cead6c6cd Disable line width limits 2016-06-29 17:57:38 +03:00
f5f7ce4c42 Remove rule that prevents statements with only comments in body 2016-06-29 17:15:00 +03:00
b13075b8f2 Remove PHPMD and PHPCPD tools we don't use 2016-06-29 16:49:22 +03:00
c4db9e3227 Add PHP lint and PHP code sniffer 2016-06-29 16:19:50 +03:00
f47bfb5439 Merge pull request #532 from mailpoet/newsletter_creation
Newsletter creation: Step 1 and 3 changes
2016-06-28 15:25:14 +02:00
cc4639cb23 Set default values when immediately sending scheduled newsletter 2016-06-28 15:57:02 +03:00
69094f57fd Fix typos 2016-06-28 15:01:31 +03:00
ffe7b80888 Simplify variable declarations 2016-06-28 14:12:09 +03:00
fc846b808e Remove obsolete debugging statement 2016-06-28 14:12:09 +03:00
1cbf6b67b2 Remove console.log statements 2016-06-28 14:12:09 +03:00
286c02bdd9 Fix standard newsletter scheduling to always include scheduleAt 2016-06-28 14:12:08 +03:00
5f1d76225b - Add sorting of segment names in Welcome newsletter segment selector;
- Add an option to FormFieldSelect to allow sorting options;
- Change "Send" button label for scheduled newsletters;
- Disable "Send" button for sending/already sent newsletters.
2016-06-28 14:12:08 +03:00
c05ea1b968 Change "Go back to editor" to save form fields first 2016-06-28 14:12:08 +03:00
2d45ab2e88 Add WP user segment selection to Notification and Standard newsletters 2016-06-28 14:12:08 +03:00
ca9b1e25a7 Change notification newsletter time to be displayed in WP format 2016-06-28 14:12:08 +03:00
2927875e16 Regenerate thumbnails of default newsletter templates 2016-06-28 14:12:07 +03:00
486a97fa30 Vertically center template thumbs and don't enforce min-height for them 2016-06-28 14:12:07 +03:00
c22d434dff Merge pull request #531 from mailpoet/unit_test_catchup
Unit test update
2016-06-28 12:22:50 +03:00
306cdeb68f Models unit tests update 2016-06-27 13:53:56 +02:00
7ee83dad06 Merge pull request #527 from mailpoet/sending_queue_refactor
Sending queue refactor
2016-06-23 18:16:21 +03:00
d414313749 - Fixes const definition for PHP 5.5 2016-06-22 13:35:48 -04:00
66d329f630 - Configures mailer inside the mailer task class 2016-06-22 11:21:11 -04:00
f524ffcb28 - Updates mailer task to store mailer instance 2016-06-22 11:15:40 -04:00
264b7e180b listing handler and bulk actions tests completed 2016-06-22 13:47:54 +02:00
88dc7f4199 removing DKIM and useless classes 2016-06-22 13:47:54 +02:00
9652f75028 Merge pull request #530 from mailpoet/fix_safari_es6_bug
removed ES6 syntax from non converted JS file - fixes #529 (Safari bug)
2016-06-22 14:29:57 +03:00
36c32db2d1 Merge pull request #528 from mailpoet/listing_sorting
Listing sorting + bugfixes
2016-06-22 14:24:05 +03:00
fd12bd557e fixed 'Setup' link in homepage 2016-06-22 12:21:26 +02:00
7bd8ed4639 Use promises for handleBulkAction
- fixed filters not being updated when going back/forward
- improved redirection to "all" group after emptying the trash (former way became buggy)
- fixed error thrown by "onGetItems" -> this logic has to go at some point
- Newsletters listing are sorted by "updated_at" desc
- Subscribers are sorted by "created_at" desc (Subscribed on)
2016-06-21 22:36:13 +02:00
ca0e511efd removed ES6 syntax from non converted JS file - fixes #529 (Safari bug) 2016-06-21 16:47:25 +02:00
e5f3fabcda - Moves mailer logic into Mailer Task class 2016-06-21 10:14:19 -04:00
efc9bac760 - Updates unit tests 2016-06-20 23:36:30 -04:00
ce6327c3d5 - Re-adds the old multidimensional array flatten method 2016-06-20 23:35:47 -04:00
f32d6bb331 - Joins bulk and individual processing into one method
- Refactors code as per code review comments
2016-06-20 23:12:32 -04:00
e807aad814 - Updates array flatten function for multidimensional arrays
- Removes custom array unique method for multidimensional arrays
2016-06-20 11:50:54 -04:00
b87754ca30 Listing setParam only needs to be run when url history is specified
- added missing code to deleteManySubscriptions() so that it doesn't remove from all segments
2016-06-20 17:28:19 +02:00
22dfb372ec - Updates bulk insert logic 2016-06-20 10:34:41 -04:00
674bbd728e updated Subscriber unit test to use model constants - no fix here 2016-06-20 16:30:34 +02:00
68c09b8678 Sorting for all listings & bugfixes for all listings except Newsletters
- newsletters listing now uses hash history
- newsletters are sorted by Subject (a->z)
- segments are sorted by Name (a->z)
- re-added WordPress Users list as a segment you can send a newsletter to
- added explicit error messages when an auto newsletter isn't fully configured
- added missing strings for "selectAll" in Segments listing
- fixed filters() in Subscribers listing (wrong count as it was not taking groups/filters/search into account)
2016-06-20 16:23:27 +02:00
c83ab0886f - Rebases master 2016-06-19 22:10:18 -04:00
999a0b3ede - Refactors sending queue worker by breaking it into smaller tasks
- Adds arrayUnique method to Helpers for multidimensional arrays
2016-06-17 14:52:56 -04:00
6daecd6466 - Fixes URL extraction (undefined index notice)
- Updates link replacement in text body
- Updates links saving logic
2016-06-17 14:52:33 -04:00
7af2775972 Allowed ability to set default sort_by/order on listings
- improved performance of listings (less refresh of items)
- fixed sorting issue where the order would not be reversed
2016-06-17 17:27:40 +02:00
4bb1acf493 Bump up release version to 0.0.33 2016-06-17 17:14:19 +03:00
c8cd3d3eb5 Merge pull request #526 from mailpoet/copy_review
copy_review
2016-06-17 16:30:35 +03:00
2360c4d6e4 Fix periodicity strings 2016-06-17 16:28:40 +03:00
36e9168eef Escape quotes where needed 2016-06-17 16:06:03 +03:00
fb79d189d7 Edits June 17 2016 2016-06-17 15:09:25 +03:00
12330d6d34 Copy edits 6/15/2016 2016-06-17 15:09:25 +03:00
5efbcfd9c1 Update June 13 2016-06-17 15:09:24 +03:00
75240fc2e1 Merge pull request #508 from mailpoet/newsletters_listing
Newsletters multi-listing
2016-06-17 15:01:30 +03:00
b6fabcc739 removing some leftover trailing commas. 2016-06-17 13:16:20 +02:00
269ddae93a Refactored scheduling options for React (semi-converted to ES6 too)
- fixed issue with Pausing sending (missing self::)
2016-06-17 13:05:46 +02:00
90c3f0e4e4 only update status to Sent for Standard newsletters 2016-06-16 20:31:47 +02:00
dd8c54aae3 removed useless newsletters/list.jsx
- removed constant from Scheduler since it's defined on the SendingQueue model
2016-06-16 20:08:42 +02:00
aa3a46b941 Status update of newsletters completed
- duplicate newsletter now includes options as well
- fixed NaN issue in statistics when newsletter is being sent
- use constant for scheduled (and put it as the sendingQueue Model level)
2016-06-16 20:01:53 +02:00
744455f0df removed useless methods 2016-06-16 12:21:54 +02:00
9aa25446d1 fixed unit tests 2016-06-15 16:33:48 +02:00
6199caea29 - Notification settings column
- added "width" option to listing headers/columns
2016-06-15 16:33:48 +02:00
d6a68dd4d0 settings column done for welcome emails + WordPress capitalization fix 2016-06-15 16:33:48 +02:00
ee6e261c42 Conditional display of statistics column (for standard)
- improved duplicate action (for standard)
- moved STATUS_COMPLETED constant from worker to SendingQueue model where it belongs
2016-06-15 16:26:42 +02:00
cabfd8a946 better with the proper type 2016-06-15 16:26:42 +02:00
cf712636ed progress on notification type listing + NL model improvements 2016-06-15 16:26:42 +02:00
873c3d15a0 Fixed Setting::getValue issue where defaults were not returned for single keys
- updated static strings with constants
2016-06-15 16:26:42 +02:00
bc1bd3bad1 commenting on a react quirk 2016-06-15 16:26:42 +02:00
9f971632c9 update status in welcome listing 2016-06-15 16:26:42 +02:00
91bc0505ac Welcome emails progress 2016-06-15 16:26:42 +02:00
90c94624cc added preview link for standard newsletters 2016-06-15 16:26:42 +02:00
cd412894c6 Refactored filtering (groups / status / type)
- standard listing close to completion (missing item actions)
- enabled tracking by default on install
2016-06-15 16:26:42 +02:00
ecf15d53d9 Newsletters listing
- added stylesheet for newsletters listing
- added "status" database column on Newsletters for grouping in listings
- added duplicate link to standard newsletters
2016-06-15 16:15:02 +02:00
a593347336 call groups/filters only if Model has defined those methods 2016-06-15 16:15:02 +02:00
22566869cb tab system for newsletters listing 2016-06-15 16:14:06 +02:00
c959e7ec96 fixed total count and filtering + basic tab implementation in React 2016-06-15 16:14:06 +02:00
86a2846215 Tab system for listings 2016-06-15 16:14:06 +02:00
3b97a26a8a Newsletters multi-listing 2016-06-15 16:14:06 +02:00
dc6c973574 Merge pull request #523 from mailpoet/editor_ui
Email Editor: round 2 fixes
2016-06-14 15:26:22 +02:00
2a3a561464 updated shortcode (user -> subscriber) in FrankRoast template 2016-06-14 15:25:23 +02:00
e5f45fb7ad Merge pull request #524 from mailpoet/link_processing_fix
Prevents URLs in link titles from being processed when tracking is enabled
2016-06-14 15:21:18 +02:00
f22cadd319 - Declares hash length as constant
- Introduces check for nonexistent values/updates loop condition
2016-06-14 09:02:08 -04:00
5ea25ec697 Fix the way we query for WP subscriber in email preview 2016-06-14 15:43:13 +03:00
c0a250fc0f Turn sidebar/sidepanel text size into a variable 2016-06-14 15:26:00 +03:00
e69aa792c4 - Prevents URLs in link titles from being processed when tracking is enabled. Closes #519 2016-06-13 21:13:23 -04:00
3b4ac4d2d2 Change newsletter preview to use current user as subscriber 2016-06-13 15:02:20 +03:00
781973777e Vertically and horizontally center block deletion confirmation dialog 2016-06-13 14:13:47 +03:00
8698d2c6ba Change placeholder text of preheader input 2016-06-13 13:06:59 +03:00
47c15eca83 Change sidebar and sidepanel text font size to 13px 2016-06-13 13:06:59 +03:00
64f4bed080 Bump up release version to 0.0.32 2016-06-10 17:25:38 +03:00
fe47ba8a38 Merge pull request #522 from mailpoet/tests_fix
Fix PHP unit tests
2016-06-10 10:12:53 -04:00
eb02adc7ba Exclude lib/Util/Helpers.php class from unit test coverage calculations 2016-06-10 16:54:11 +03:00
f257b503e9 - Fix unit tests to account for translation changes;
- Exclude 3rd party utility libraries from coverage calculations;
2016-06-10 15:06:44 +03:00
bfdabe3554 Merge pull request #521 from mailpoet/copy_review
Copy review
2016-06-10 12:36:47 +03:00
77dd71935a Update - June 10 2016 2016-06-10 11:13:40 +02:00
4b418f041b Merge pull request #518 from mailpoet/alc_posts_ui
ALC & Posts widgets UI fixes
2016-06-09 16:19:57 +02:00
c8a0e006a0 Fix Select2 placeholder in Posts settings 2016-06-09 15:42:01 +03:00
359119d896 Disable dragging with right click, fixes #517 2016-06-09 13:34:26 +03:00
1a3c767601 - Fix double HR tag issue on ALC/Posts block settings;
- Change "Drop content here" message to a custom one for Posts/ALC blocks
2016-06-09 13:34:26 +03:00
6a97e82d42 Fix select2 placeholder text not appearing for Posts widget 2016-06-09 13:34:26 +03:00
3edfd32879 - Add highlighting of blocks that are being edited;
- Refactor block settings views;
- Change Posts widget to display 8 posts in settings;
- Move ALC/Posts category selector label to Select2 placeholder.
2016-06-09 13:34:26 +03:00
33bdde1156 Merge pull request #516 from mailpoet/unit_tests
Adds unit test for open/unsubscribe statistics
2016-06-09 12:54:16 +03:00
710cab64c3 - Fixes error due to: "the blacklist functionality has been removed from
PHPUnit 5, please remove blacklist section from configuration"
2016-06-08 21:26:23 -04:00
ed707b1738 - Adds unit test for unsubscribe statistics 2016-06-08 21:25:40 -04:00
398903e8b8 - Adds unit test for open statistics 2016-06-08 12:38:52 -04:00
d590f5ea98 Merge pull request #512 from mailpoet/preview_link_refactoring
Extracts browser preview URl logic into a separate class
2016-06-08 17:12:10 +02:00
d6cbe5aac8 - Fixes incorrect shortcode name
- Updates unit test
2016-06-08 11:09:33 -04:00
08e6430c7d June 8 2016 Copy review 2016-06-08 17:02:50 +02:00
945fe66bbb Merge pull request #514 from mailpoet/model_cleanup
Removes unused method from the base model
2016-06-08 15:13:51 +02:00
8e3eb2b795 Merge pull request #515 from mailpoet/unit_tests
Adds unit test for click statistics
2016-06-08 16:02:42 +03:00
52fbc0ee8a Merge pull request #513 from mailpoet/renderer_fix
Rendering fix
2016-06-08 12:56:22 +03:00
a355228b93 Test Commit
This is an initial test commit for the copy review.
2016-06-08 11:31:04 +02:00
2cb0b3b071 - Adds unit test for click statistics 2016-06-07 21:57:04 -04:00
bc1fb235d3 - Removes unused method from the base model. Closes #511 2016-06-07 18:47:00 -04:00
713dda913e - Fixes rendering issue where DOMDocument throws a notice on unescaped
html entity
2016-06-07 12:27:40 -04:00
d182638971 - Updates references to the new view in browser URL class
- Removes unnecessary rtrim condition in URL generation
2016-06-07 10:53:01 -04:00
a5c620acf3 - Updates the way the view in browser URL is constructed 2016-06-07 10:41:19 -04:00
c176ad1d16 - Updates based on code review comments 2016-06-07 10:14:37 -04:00
14c2b4d90f - Changes location for the main view in browser class
- Updates code formattign for case statements
2016-06-07 09:28:29 -04:00
03eb4ad0fc - Changes location for the view in browser URL class 2016-06-07 09:16:48 -04:00
ba9cd15651 - Extracts view in browser URl logic into a separate class 2016-06-07 09:08:01 -04:00
329ec63dfd Bump up release version to 0.0.31 2016-06-03 18:29:40 +03:00
4925c7868e Merge pull request #510 from mailpoet/twig_deprecation
Twig deprecation notice (latest version)
2016-06-03 18:10:53 +03:00
13d28d0aa7 implemented interface in our Twig extension to comply with latest Twig standards 2016-06-03 15:07:30 +02:00
c7d3c79fe3 Merge pull request #509 from mailpoet/unit_tests
- Increases Mailer unit test coverage to 100%
2016-06-02 19:38:43 +03:00
1e9da724ea - Updates exception test logic 2016-06-02 12:33:58 -04:00
645d4e15ab - Updates unit test 2016-06-02 11:03:16 -04:00
cad5b242b2 - Increases Mailer unit test coverage to 100% 2016-06-02 10:11:36 -04:00
99a81042c1 Merge pull request #507 from mailpoet/custom_shortcodes
Implements shortcodes for custom fields
2016-06-01 17:09:38 +03:00
61987a204e - Fixes custom field shortcode matching logic 2016-06-01 09:59:45 -04:00
a208104fc8 - Fixes naming convention 2016-06-01 09:40:59 -04:00
00ccc8adf4 Merge pull request #506 from mailpoet/alc_update
Multiple ALC block support for newsletter editor
2016-06-01 15:15:26 +02:00
df0ed9ce53 Rename mailpoet_custom_fields symlink to mailpoet_shortcodes 2016-06-01 16:00:05 +03:00
26d9b915a2 - Adds unit test for shortcodes helper class 2016-05-31 21:30:38 -04:00
16cb91990b - Updates unit test 2016-05-31 20:08:45 -04:00
9642d3e672 - Renames all references of "custom fields" to "shortcodes" 2016-05-31 11:25:16 -04:00
aed60e6905 - Updates menu/editor view to work with the refactored shortcodes logic 2016-05-31 11:04:10 -04:00
da7615ba4c - Removes redundant shortcode description
- Implements shortcode processing for custom fields
2016-05-31 11:03:04 -04:00
3eb6a21980 - Centralizes a list of all shortcodes
- Returns all shortcodes with custom fields
2016-05-31 11:02:08 -04:00
b4e371302c Fix PHP coding style based on feedback 2016-05-31 17:50:38 +03:00
e6724b1d4a Change unsubscribe verifier to check for "Unsubscribe" shortcode
presence
2016-05-31 16:29:10 +03:00
2b6e87c3a7 Force TinyMCE to use absolute URLs 2016-05-31 16:12:33 +03:00
b01ee80ec2 Update Backbone, Marionette, Backbone Radio, TinyMCE dependency versions 2016-05-31 15:14:36 +03:00
5d48ecac80 Add a method to bulk update ALC blocks in newsletter editor 2016-05-31 13:53:45 +03:00
ebdb826011 Bump up release version to 0.0.30 2016-05-27 18:31:35 +03:00
9dc725e34d Merge pull request #488 from mailpoet/wp_users
Wp users
2016-05-27 18:26:45 +03:00
f47c331a5b updated db schema and fixed unit test missing Segment cleanup after 2016-05-27 15:38:24 +02:00
b45c70f32b removed status from subscribeManyToSegments() query 2016-05-27 14:18:02 +02:00
cf33d6f066 removed extra spaces 2016-05-27 14:15:46 +02:00
8292e9a744 Revert batch processing on bulk actions - too buggy
- minor fixes and cleanup
2016-05-27 14:15:46 +02:00
3c46a5b434 Optimized Bulk actions
- Updated SQL schema for every created_at column so that it has a default value
- Updated unit tests based on recent changes (new methods in SubscriberSegment model)
- Added check for HelpScout initialization code so that it doesn't throw errors
2016-05-27 14:15:46 +02:00
4a4c4e093a Added unit tests for the WP segment
- moved WP segment creation to the Segment model
2016-05-27 14:14:35 +02:00
4fa8a650b8 Added unit tests for SubscriberSegment / Subscriber models 2016-05-27 14:14:35 +02:00
da755b7902 Renamed method names for better clarity + refactoring
- renamed getWPUsers() to getWPSegment()
- renamed SubscriberSegment methods
2016-05-27 14:14:35 +02:00
ceebb18bdf minor spacing fix 2016-05-27 14:14:35 +02:00
d10a29598d prevent deletion of WP Users segment in Segments listing 2016-05-27 14:14:35 +02:00
8c56c8da5e Fixed bulk actions (return false if no items were selected)
- added missing check for WPUsers segment in case it does not exist
2016-05-27 14:14:35 +02:00
c4ddb38d18 Prevent WP users from being trashed/deleted
- return actual rowCount of affected rows for bulk actions (based on PDO last statement)
- prevent removal of WP Users segment relationship with subscribers.
2016-05-27 14:14:35 +02:00
15a21e5745 fix segments loaded on subscribers page + removed counts for bulk actions' segments 2016-05-27 14:14:35 +02:00
7df1a856ea Merge pull request #501 from mailpoet/import_fix
Import fix
2016-05-27 14:19:32 +03:00
69381205a2 - Updates unit test 2016-05-27 07:16:11 -04:00
9e0d8056b3 - Changes success/error notices font size to 13px in import and export 2016-05-27 07:16:11 -04:00
4b85c57436 - Updates Export notification class
- Updates Export "back to subscribers" button language
2016-05-27 07:16:11 -04:00
377498be1d - Removes validation of MailChimp API key
- Refactors import class
- Creates new method in Newsletter model to select welcome notifications
  for specific segments
- Updates Step 2 (error) and Step 3 (success) notices
- Gives MenuBootstrap class a comprehensible name
2016-05-27 07:16:11 -04:00
142421ad48 - Updates unit test 2016-05-27 07:16:11 -04:00
768115b794 - Disables "next step" button on import's step 2 when no segments are
selected
2016-05-27 07:16:11 -04:00
8b9d76db8a - Displays notice on step 3 of import when subscribers are added to a
segment with welcome notification enabled
2016-05-27 07:16:11 -04:00
f17c78fda2 - Updates Segment model to return segments even when there are no
subscribers
2016-05-27 07:16:11 -04:00
3d45a8b7d4 - Updates subscriber/subscriber_segment status using const values 2016-05-27 07:14:34 -04:00
3888241cbd - Simplified date matching logic by using Moment.js 2016-05-27 07:14:34 -04:00
603b6749de - Styles the import results notice to look like WP's "update" 2016-05-27 07:14:34 -04:00
22918ecfd1 - Updates the wording of the "back to list" button 2016-05-27 07:14:34 -04:00
70ded73b51 - Updates the look of the MailChimp API key "verify" button 2016-05-27 07:14:34 -04:00
da147047ec - Updates import email regex to use standard HTML5 regex
- Improves email detection/filtering logic
2016-05-27 07:14:34 -04:00
0e24174373 Merge pull request #486 from mailpoet/export_fix
Fixes segment subscriber count when status is "unsubscribed"
2016-05-26 11:40:38 +02:00
bc9b4eeb19 - Update Segment model/test to use const values for subscriber status 2016-05-25 17:31:38 -04:00
c6b13c5175 Merge pull request #498 from mailpoet/rendering_fix
Fixes a couple of rendering issues
2016-05-25 14:23:15 +03:00
f754b1d1b2 - Applies text alignment to ALC block
- Prevents duplicate column content
2016-05-24 15:41:26 -04:00
bd5300d69a Merge pull request #495 from mailpoet/standard_newsletter_fix
Fix scheduling immediate standard newsletters
2016-05-24 12:02:01 -04:00
9996f3ef41 Change Scheduler to use Newsletter object, not array 2016-05-24 17:57:34 +03:00
0f95d7bc8a Use Scheduler to schedule next post notification sending timestamp 2016-05-24 17:08:34 +03:00
14098643ae Fix scheduling immediate standard newsletters 2016-05-24 16:04:42 +03:00
7c2d5a45c5 - Updates unit test 2016-05-20 12:12:29 -04:00
9d5902e179 - Fixes segment subscriber count when status is "unsubscribed" 2016-05-20 11:35:58 -04:00
381 changed files with 22782 additions and 13433 deletions

View File

@ -0,0 +1,15 @@
Listen 8080
<VirtualHost *:8080>
UseCanonicalName Off
ServerName mailpoet.loc
DocumentRoot /home/ubuntu/mailpoet/wordpress
DirectoryIndex index.php
LogLevel notice
<Directory /home/ubuntu/mailpoet/wordpress>
AllowOverride All
Allow from All
</Directory>
</VirtualHost>

2
.gitignore vendored
View File

@ -12,7 +12,7 @@ npm-debug.log
/views/cache/**
temp
.idea
wysija-newsletters.zip
mailpoet.zip
tests/javascript/testBundles
assets/css/*.css
assets/js/*.js

View File

@ -102,31 +102,51 @@ class RoboFile extends \Robo\Tasks {
);
}
function testUnit($file = null) {
function testUnit($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
$command = 'vendor/bin/codecept run unit -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
}
function testCoverage($file = null) {
function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec(join(' ', array(
$command = join(' ', array(
'vendor/bin/codecept run',
(($file) ? $file : ''),
(($opts['file']) ? $opts['file'] : ''),
'--coverage',
'--coverage-html'
)));
($opts['xml']) ? '--coverage-xml' : '--coverage-html'
));
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
}
function testJavascript() {
function testJavascript($xml_output_file = null) {
$this->compileJs();
$this->_exec(join(' ', array(
$command = join(' ', array(
'./node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.js',
'tests/javascript/testBundles/**/*.js'
)));
));
if(!empty($xml_output_file)) {
$command .= sprintf(
' --reporter xunit --reporter-options output="%s"',
$xml_output_file
);
}
return $this->_exec($command);
}
function testDebug() {
@ -141,8 +161,33 @@ class RoboFile extends \Robo\Tasks {
$this->_exec('vendor/bin/codecept run -g failed');
}
function qa() {
$this->qaLint();
$this->qaCodeSniffer('all');
}
function qaLint() {
$this->_exec('./tasks/php_lint.sh lib/ tests/ mailpoet.php');
}
function qaCodeSniffer($severity='errors') {
if ($severity === 'all') {
$severityFlag = '-w';
} else {
$severityFlag = '-n';
}
$this->_exec(
'./vendor/bin/phpcs '.
'--standard=./tasks/code_sniffer/MailPoet '.
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
'./lib/Config/PopulatorData/Templates/* '.
'lib/ '.
$severityFlag
);
}
protected function loadEnv() {
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
}
}
}

View File

@ -9,6 +9,8 @@
@require 'form_editor'
@require 'listing'
@require 'listing/newsletters'
@require 'box'
@require 'breadcrumb'

View File

@ -27,13 +27,12 @@
img
min-width: 150px
min-height: 150px
height: auto
width: 110%
position: relative
top: 0
top: 50%
left: 50%
transform: translate(-50%, 0%)
transform: translate(-50%, -50%)
.mailpoet_overlay
position: absolute
@ -61,16 +60,20 @@
.mailpoet_boxes .mailpoet_description
float:left
width: 245px
max-height: calc(110px - 2em)
max-height: calc(115px - 2em)
padding-bottom: 2em
overflow: hidden
.mailpoet_boxes .mailpoet_description h3
margin: 0 0 1em 0
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
max-width: 220px
h3
margin: 0 0 0.7em 0
overflow: hidden
max-width: 210px
line-height: 1.4em
p
font-size: 13px
line-height: 1.5
margin: 1em 0
.mailpoet_boxes .mailpoet_actions
position: absolute

View File

@ -0,0 +1,3 @@
#newsletters_container
h2.nav-tab-wrapper
margin-bottom: 1rem

View File

@ -1,6 +1,6 @@
$tool-inactive-color = #bbbbbb
$tool-inactive-color = #333333
$tool-inactive-secondary-color = #ffffff
$tool-hover-color = #333333
$tool-hover-color = #bbbbbb
$tool-hover-secondary-color = #ffffff
$tool-active-color = #d2d2d4
$tool-active-secondary-color = #ffffff
@ -12,21 +12,42 @@ $master-column-tool-width = 24px
position: absolute
top: 0
right: 0
display: none
z-index: 20
padding: 2px
text-align: right
overflow: hidden
.mailpoet_tool_slider
position: relative
right: -100%
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000)
opacity: 0
&.mailpoet_display_tools
.mailpoet_tool_slider
right: 0
opacity: 1
a
vertical-align: top
.mailpoet_container_horizontal + &
left: 100%
right: initial
padding-left: 5px
.mailpoet_tool_slider
left: -100%
right: initial
&.mailpoet_display_tools
.mailpoet_tool_slider
left: 0
.mailpoet_tool
width: $master-column-tool-width
height: $master-column-tool-width
display: block
.mailpoet_tool_icon
width: $master-column-tool-width
@ -46,6 +67,7 @@ $master-column-tool-width = 24px
opacity: 0
overflow: hidden
display: block
margin: 0
.mailpoet_delete_block_activated
width: auto
@ -125,7 +147,6 @@ $master-column-tool-width = 24px
border-radius(3px)
background-color: $warning-background-color
padding: 3px 5px
line-height: 1.2em
.mailpoet_delete_block_activate
overflow: hidden

View File

@ -4,7 +4,6 @@
.mailpoet_form_field_title
clear: both
font-size: 1.1em
margin-bottom: 5px
.mailpoet_form_field_title_small

View File

@ -17,6 +17,7 @@ $widget-icon-width = 30px
border-left: $content-border-color
border-bottom: $content-border-color
color: $sidebar-text-color
font-size: $sidebar-text-size
.mailpoet_sidebar_region
margin-bottom: 0

View File

@ -3,6 +3,7 @@ $sidepanel-active-heading-color = $primary-active-color
/* Sidepanel */
.mailpoet_editor_settings
color: $sidebar-text-color
font-size: $sidebar-text-size
p
font-size: 1em
@ -18,7 +19,6 @@ $sidepanel-active-heading-color = $primary-active-color
.mailpoet_sidepanel_field_title
clear: both
font-size: 1.1em
margin-bottom: 5px
.mailpoet_sidepanel_field_title_small

View File

@ -23,6 +23,7 @@ $block-text-line-height = $text-line-height
border: 1px solid $transparent-color
&:hover > .mailpoet_block_highlight
&.mailpoet_highlight > .mailpoet_block_highlight
border: 1px dashed $block-hover-highlight-color

View File

@ -17,7 +17,7 @@ $three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
padding-left: 0
padding-right: 0
&:hover
&:hover > .mailpoet_block_highlight
border: 0
.mailpoet_container_vertical > *

View File

@ -5,8 +5,8 @@ $active-social-icon-set-border-color = #adadad
$active-social-icon-set-background-color = #daebf2
$social-icon-set-hover-border-color = $primary-active-color
$tool-inactive-color = #bbbbbb
$tool-hover-color = #333333
$tool-inactive-color = #333333
$tool-hover-color = #bbbbbb
$tool-active-color = #d2d2d4
$tool-width = 16px

View File

@ -31,7 +31,7 @@ div.mce-toolbar-grp.mce-container
box-shadow(0px 0px 3px 1px rgba(0, 0, 0, 0.05))
.mce-window
/* Fix TinyMCE mailpoet_custom_fields window lack of hiding overflow */
/* Fix TinyMCE mailpoet_shortcodes window lack of hiding overflow */
div.mce-container-body.mce-abs-layout
overflow: hidden
@ -40,8 +40,8 @@ div.mce-toolbar-grp.mce-container
width: -webkit-calc( 100% - 36px )
width: calc( 100% - 36px )
/* TinyMCE mailpoet_custom_fields toolbar icon */
.mce-i-mailpoet_custom_fields:before
/* TinyMCE mailpoet_shortcodes toolbar icon */
.mce-i-mailpoet_shortcodes:before
font: 400 20px/1 dashicons!important
content: "\f307"
@ -84,7 +84,7 @@ position: relative
body
overflow-x: auto
/* Hide the "Details" section of Wordpress Media manager */
/* Hide the "Details" section of WordPress Media manager */
.media-sidebar
display: none
@ -98,7 +98,7 @@ body
.attachments-browser .uploader-inline
right: 0
/* Remove max width from date selector in Wordpress Media Manager */
/* Remove max width from date selector in WordPress Media Manager */
#media-attachment-date-filters
max-width: calc(100% - 12px)

View File

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

View File

@ -26,3 +26,4 @@ $error-text-color = #d54e21
$newsletter-width = 660px
$text-line-height = 1.6em
$sidebar-text-size = 13px

View File

@ -18,6 +18,7 @@ textarea.parsley-error
list-style-type none
font-size 0.9em
line-height 0.9em
color #B94A48
opacity 0
transition all .3s ease-in
-o-transition all .3s ease-in

View File

@ -1,14 +1,10 @@
#mailpoet_settings
// common
.mailpoet_panel
display: none
display none
.form-table th
width:20em
// advanced
#mailpoet_role_permissions
margin-top: 20px;
width 20em
// sending methods
.mailpoet_sending_methods
@ -28,8 +24,7 @@
line-height 54px
font-size 1.5em
.mailpoet_description
line-height 1.5em
font-size 1.1em
font-size 14px
.mailpoet_status
background-color #2f2f2f
color #fff
@ -60,11 +55,11 @@
// responsive
@media screen and (max-width: 782px)
.form-table th
width: auto
width auto
.mailpoet_sending_methods
li
float none
width: auto
margin-right: 0
width auto
margin-right 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1 +0,0 @@
../src/newsletter_editor/tinymce/mailpoet_custom_fields

View File

@ -0,0 +1 @@
../src/newsletter_editor/tinymce/mailpoet_shortcodes

View File

@ -1,4 +1,4 @@
define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) {
'use strict';
MailPoet.Ajax = {
version: 0.5,
@ -8,19 +8,11 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
endpoint: null,
action: null,
token: null,
data: {},
onSuccess: function(data, textStatus, xhr) {},
onError: function(xhr, textStatus, errorThrown) {}
},
get: function(options) {
return this.request('get', options);
data: {}
},
post: function(options) {
return this.request('post', options);
},
delete: function(options) {
return this.request('delete', options);
},
init: function(options) {
// merge options
this.options = jQuery.extend({}, this.defaults, options);
@ -50,31 +42,31 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// set request params
var params = this.getParams();
var jqXHR;
var deferred = jQuery.Deferred();
// make ajax request depending on method
if(method === 'get') {
jqXHR = jQuery.get(
this.options.url,
params,
this.options.onSuccess,
'json'
);
} else {
jqXHR = jQuery.ajax({
url: this.options.url,
type : 'post',
data: params,
dataType: 'json',
success : this.options.onSuccess,
error : this.options.onError
});
// remove null values from the data object
if (_.isObject(params.data)) {
params.data = _.pick(params.data, function(value) {
return (value !== null)
})
}
// ajax request
deferred = jQuery.post(
this.options.url,
params,
null,
'json'
).then(function(data) {
return data;
}, function(xhr) {
return xhr.responseJSON;
});
// clear options
this.options = {};
return jqXHR;
return deferred;
}
};
});

View File

@ -1,100 +0,0 @@
define(
[
'react',
'react-dom',
'mailpoet'
],
function(
React,
ReactDOM,
MailPoet
) {
var CronControl = React.createClass({
getInitialState: function() {
return {
status: 'loading'
};
},
getCronData: function() {
MailPoet.Ajax.post({
endpoint: 'cron',
action: 'getStatus'
})
.done(function(response) {
jQuery('.button-primary')
.removeClass('disabled');
if(response.status !== undefined) {
this.setState(response);
} else {
this.replaceState();
}
}.bind(this));
},
componentDidMount: function() {
if(this.isMounted()) {
this.getCronData();
setInterval(this.getCronData, 5000);
}
},
controlCron: function(action) {
if(jQuery('.button-primary').hasClass('disabled')) {
return;
}
jQuery('.button-primary')
.addClass('disabled');
MailPoet.Ajax.post({
endpoint: 'cron',
action: action,
})
.done(function(response) {
if(!response.result) {
MailPoet.Notice.error(MailPoet.I18n.t('daemonControlError'));
}
}.bind(this));
},
render: function() {
if(this.state.status === 'loading') {
return(<div>{MailPoet.I18n.t('loadingDaemonStatus')}</div>);
}
switch(this.state.status) {
case 'started':
return(
<div>
{MailPoet.I18n.t('cronDaemonIsRunning')}
<br/>
<br/>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>{MailPoet.I18n.t('stop')}</a>
</div>
);
break;
case 'starting':
case 'stopping':
return(
<div>
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
</div>
);
break;
case 'stopped':
return(
<div>
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>{MailPoet.I18n.t('start')}</a>
</div>
);
break;
}
}
});
const container = document.getElementById('cron_container');
if(container) {
ReactDOM.render(
<CronControl />,
container
);
}
});

View File

@ -43,8 +43,9 @@ define('date',
options = options || {};
this.init(options);
return Moment(date, this.convertFormat(options.parseFormat))
.format(this.convertFormat(this.options.format));
var date = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) date = date.utc();
return date.format(this.convertFormat(this.options.format));
},
toDate: function(date, options) {
options = options || {};
@ -68,7 +69,7 @@ define('date',
});
},
convertFormat: function(format) {
const format_mappings = {
var format_mappings = {
date: {
D: 'ddd',
l: 'dddd',
@ -124,9 +125,9 @@ define('date',
if (!format || format.length <= 0) return format;
const replacements = format_mappings['date'];
var replacements = format_mappings['date'];
let outputFormat = '';
var outputFormat = '';
Object.keys(replacements).forEach(function(key) {
if (format.indexOf(key) !== -1) {

View File

@ -6,7 +6,7 @@ function(
) {
const FormFieldCheckbox = React.createClass({
onValueChange: function(e) {
e.target.value = this.refs.checkbox.checked ? '1' : '';
e.target.value = this.refs.checkbox.checked ? '1' : '0';
return this.props.onValueChange(e);
},
render: function() {
@ -14,7 +14,9 @@ function(
return false;
}
const isChecked = !!(this.props.item[this.props.field.name]);
// 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 options = Object.keys(this.props.field.values).map(
(value, index) => {
return (

View File

@ -27,7 +27,7 @@ define([
}
return (
<select
name={ this.props.name + '[year]' }
name={ `${this.props.name}[year]` }
value={ this.props.year }
onChange={ this.props.onValueChange }
>
@ -57,7 +57,7 @@ define([
}
return (
<select
name={ this.props.name + '[month]' }
name={ `${this.props.name}[month]` }
value={ this.props.month }
onChange={ this.props.onValueChange }
>
@ -88,7 +88,7 @@ define([
return (
<select
name={ this.props.name + '[day]' }
name={ `${this.props.name}[day]` }
value={ this.props.day }
onChange={ this.props.onValueChange }
>
@ -102,46 +102,74 @@ define([
constructor(props) {
super(props);
this.state = {
year: undefined,
month: undefined,
day: undefined
year: '',
month: '',
day: ''
}
}
componentDidMount() {
this.extractDateParts();
}
componentDidUpdate(prevProps, prevState) {
if (
(this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
this.extractTimeStamp();
this.extractDateParts();
}
}
extractTimeStamp() {
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
extractDateParts() {
const value = (this.props.item[this.props.field.name] !== undefined)
? this.props.item[this.props.field.name].trim()
: '';
if(value === '') {
return;
}
const dateTime = Moment(value);
this.setState({
year: Moment.unix(timeStamp).year(),
// Moment returns the month as [0..11]
// We increment it to match PHP's mktime() which expects [1..12]
month: Moment.unix(timeStamp).month() + 1,
day: Moment.unix(timeStamp).date()
year: dateTime.format('YYYY'),
month: dateTime.format('M'),
day: dateTime.format('D')
});
}
updateTimeStamp(field) {
let newTimeStamp = Moment(
`${this.state.month}/${this.state.day}/${this.state.year}`,
'M/D/YYYY'
).valueOf();
if (~~(newTimeStamp) > 0) {
// convert milliseconds to seconds
newTimeStamp /= 1000;
return this.props.onValueChange({
target: {
name: field,
value: newTimeStamp
}
});
formatValue() {
const dateType = this.props.field.params.date_type;
let value;
switch(dateType) {
case 'year_month_day':
value = {
'year': this.state.year,
'month': this.state.month,
'day': this.state.day
};
break;
case 'year_month':
value = {
'year': this.state.year,
'month': this.state.month
};
break;
case 'month':
value = {
'month': this.state.month
};
break;
case 'year':
value = {
'year': this.state.year
};
break;
}
return value;
}
onValueChange(e) {
// extract property from name
@ -153,25 +181,29 @@ define([
field = matches[1];
property = matches[2];
let value = parseInt(e.target.value, 10);
let value = ~~(e.target.value);
this.setState({
[`${property}`]: value
}, () => {
this.updateTimeStamp(field);
this.props.onValueChange({
target: {
name: field,
value: this.formatValue()
}
});
});
}
}
render() {
const monthNames = window.mailpoet_month_names || [];
const dateFormats = window.mailpoet_date_formats || {};
const dateType = this.props.field.params.date_type;
const dateSelects = dateType.split('_');
const dateSelects = dateFormats[dateType][0].split('/');
const fields = dateSelects.map(type => {
switch(type) {
case 'year':
case 'YYYY':
return (<FormFieldDateYear
onValueChange={ this.onValueChange.bind(this) }
ref={ 'year' }
@ -182,7 +214,7 @@ define([
/>);
break;
case 'month':
case 'MM':
return (<FormFieldDateMonth
onValueChange={ this.onValueChange.bind(this) }
ref={ 'month' }
@ -194,7 +226,7 @@ define([
/>);
break;
case 'day':
case 'DD':
return (<FormFieldDateDay
onValueChange={ this.onValueChange.bind(this) }
ref={ 'day' }

View File

@ -1,4 +1,5 @@
import React from 'react'
import _ from 'underscore'
const FormFieldSelect = React.createClass({
render() {
@ -8,6 +9,7 @@ const FormFieldSelect = React.createClass({
let filter = false;
let placeholder = false;
let sortBy = false;
if (this.props.field.placeholder !== undefined) {
placeholder = (
@ -19,7 +21,27 @@ const FormFieldSelect = React.createClass({
filter = this.props.field.filter;
}
const options = Object.keys(this.props.field.values).map(
if (_.isFunction(this.props.field.sortBy)) {
sortBy = this.props.field.sortBy;
}
let keys;
if (sortBy) {
// Extract keys from sorted [key, value] select value pairs, sorted by
// provided sorting order.
keys =
_.map(
_.sortBy(
_.pairs(this.props.field.values),
(item) => sortBy(item[0], item[1])
),
(item) => item[0]
);
} else {
keys = Object.keys(this.props.field.values)
}
const options = keys.map(
(value, index) => {
if (filter !== false && filter(this.props.item, value) === false) {
@ -51,4 +73,4 @@ const FormFieldSelect = React.createClass({
}
});
module.exports = FormFieldSelect;
module.exports = FormFieldSelect;

View File

@ -13,8 +13,11 @@ const columns = [
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
sortable: false
label: MailPoet.I18n.t('segments')
},
{
name: 'signups',
label: MailPoet.I18n.t('signups')
},
{
name: 'created_at',
@ -90,7 +93,7 @@ const item_actions = [
}
},
{
name: 'duplicate_form',
name: 'duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
@ -98,9 +101,11 @@ const item_actions = [
action: 'duplicate',
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
);
if (response !== false && response['name'] !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
);
}
refresh();
});
}
@ -145,9 +150,12 @@ const FormList = React.createClass({
</strong>
{ actions }
</td>
<td className="column-format" data-colname={MailPoet.I18n.t('segments')}>
<td className="column" data-colname={MailPoet.I18n.t('segments')}>
{ segments }
</td>
<td className="column" data-colname={MailPoet.I18n.t('signups')}>
{ form.signups }
</td>
<td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
<abbr>{ MailPoet.Date.format(form.created_at) }</abbr>
</td>

View File

@ -47,13 +47,13 @@ function(
data.action = this.state.action;
var callback = function() {};
var onSuccess = function() {};
if(action['onSuccess'] !== undefined) {
callback = action.onSuccess;
onSuccess = action.onSuccess;
}
if(data.action) {
this.props.onBulkAction(selected_ids, data, callback);
this.props.onBulkAction(selected_ids, data).then(onSuccess);
}
this.setState({

View File

@ -10,10 +10,10 @@ function(
) {
var ListingFilters = React.createClass({
handleFilterAction: function() {
let filters = {}
let filters = {};
this.getAvailableFilters().map((filter, i) => {
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value
})
});
return this.props.onSelectFilter(filters);
},
handleEmptyTrash: function() {
@ -21,7 +21,6 @@ function(
},
getAvailableFilters: function() {
let filters = this.props.filters;
return Object.keys(filters).filter(function(filter) {
return !(
filters[filter].length === 0
@ -30,26 +29,29 @@ function(
&& !filters[filter][0].value
)
);
})
});
},
componentDidUpdate: function() {
const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters().map(
function(filter, i) {
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
jQuery(this.refs['filter-'+i])
.val(selected_filters[filter])
.trigger('change');
}
}.bind(this)
);
},
render: function() {
const filters = this.props.filters;
const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters()
.map(function(filter, i) {
let default_value = false;
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
default_value = selected_filters[filter]
} else {
jQuery(`select[name="${filter}"]`).val('');
}
return (
<select
ref={ `filter-${i}` }
key={ `filter-${i}` }
name={ filter }
defaultValue={ default_value }
>
{ filters[filter].map(function(option, j) {
return (
@ -63,7 +65,7 @@ function(
);
}.bind(this));
let button = false;
let button;
if (available_filters.length > 0) {
button = (
@ -76,7 +78,7 @@ function(
);
}
let empty_trash = false;
let empty_trash;
if (this.props.group === 'trash') {
empty_trash = (
<input

View File

@ -1,98 +1,91 @@
define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
var ListingHeader = React.createClass({
handleSelectItems: function() {
return this.props.onSelectItems(
this.refs.toggle.checked
);
},
render: function() {
var columns = this.props.columns.map(function(column, index) {
column.is_primary = (index === 0);
column.sorted = (this.props.sort_by === column.name)
? this.props.sort_order
: 'desc';
return (
<ListingColumn
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={ 'column-' + index }
column={column} />
);
}.bind(this));
var checkbox = false;
if(this.props.is_selectable === true) {
checkbox = (
<th
className="manage-column column-cb check-column">
<label className="screen-reader-text">
{MailPoet.I18n.t('selectAll')}
</label>
<input
type="checkbox"
name="select_all"
ref="toggle"
checked={ this.props.selection }
onChange={ this.handleSelectItems } />
</th>
);
}
import MailPoet from 'mailpoet'
import React from 'react'
import classNames from 'classnames'
const ListingHeader = React.createClass({
handleSelectItems: function() {
return this.props.onSelectItems(
this.refs.toggle.checked
);
},
render: function() {
const columns = this.props.columns.map(function(column, index) {
column.is_primary = (index === 0);
column.sorted = (this.props.sort_by === column.name)
? this.props.sort_order
: 'desc';
return (
<tr>
{checkbox}
{columns}
</tr>
<ListingColumn
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={ 'column-' + index }
column={column} />
);
}
});
}.bind(this));
var ListingColumn = React.createClass({
handleSort: function() {
var sort_by = this.props.column.name,
sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order);
},
render: function() {
var classes = classNames(
'manage-column',
{ 'column-primary': this.props.column.is_primary },
{ 'sortable': this.props.column.sortable },
this.props.column.sorted,
{ 'sorted': (this.props.sort_by === this.props.column.name) }
);
var label;
let checkbox;
if(this.props.column.sortable === true) {
label = (
<a onClick={this.handleSort}>
<span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span>
</a>
);
} else {
label = this.props.column.label;
}
return (
if(this.props.is_selectable === true) {
checkbox = (
<th
className={ classes }
id={this.props.column.name }
scope="col">
{label}
className="manage-column column-cb check-column">
<label className="screen-reader-text">
{MailPoet.I18n.t('selectAll')}
</label>
<input
type="checkbox"
name="select_all"
ref="toggle"
checked={ this.props.selection }
onChange={ this.handleSelectItems } />
</th>
);
}
});
return ListingHeader;
return (
<tr>
{checkbox}
{columns}
</tr>
);
}
});
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);
},
render: function() {
const classes = classNames(
'manage-column',
{ 'column-primary': this.props.column.is_primary },
{ 'sortable': this.props.column.sortable },
this.props.column.sorted,
{ 'sorted': (this.props.sort_by === this.props.column.name) }
);
let label;
if(this.props.column.sortable === true) {
label = (
<a onClick={ this.handleSort }>
<span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span>
</a>
);
} else {
label = this.props.column.label;
}
return (
<th
className={ classes }
id={this.props.column.name }
scope="col"
width={ this.props.column.width || null }
>{label}</th>
);
}
});
module.exports = ListingHeader;

File diff suppressed because it is too large Load Diff

View File

@ -112,7 +112,16 @@ define([
}
}
},
}).preventDefault('auto');
})
.preventDefault('auto')
.actionChecker(function (pointer, event, action) {
// Disable dragging with right click
if (event.button !== 0) {
return null;
}
return action;
});
if (this.options.drop !== undefined) {
interactable.getDropModel = this.options.drop;

View File

@ -0,0 +1,23 @@
/**
* Highlight Editing Behavior
*
* Highlights a block that is being edited
*/
define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup',
], function(Marionette, BehaviorsLookup) {
BehaviorsLookup.HighlightEditingBehavior = Marionette.Behavior.extend({
modelEvents: {
'startEditing': 'enableHighlight',
'stopEditing': 'disableHighlight',
},
enableHighlight: function() {
this.$el.addClass('mailpoet_highlight');
},
disableHighlight: function() {
this.$el.removeClass('mailpoet_highlight');
},
});
});

View File

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

View File

@ -13,6 +13,7 @@ define([
'newsletter_editor/blocks/divider',
'newsletter_editor/components/communication',
'mailpoet',
'backbone.supermodel',
'underscore',
'jquery'
], function(
@ -22,6 +23,7 @@ define([
DividerBlock,
CommunicationComponent,
MailPoet,
SuperModel,
_,
jQuery
) {
@ -31,6 +33,41 @@ define([
var Module = {},
base = BaseBlock;
Module.ALCSupervisor = SuperModel.extend({
initialize: function() {
var DELAY_REFRESH_FOR_MS = 500;
this.listenTo(
App.getChannel(),
'automatedLatestContentRefresh',
_.debounce(this.refresh, DELAY_REFRESH_FOR_MS)
);
},
refresh: function() {
var models = App.findModels(function(model) {
return model.get('type') === 'automatedLatestContent';
}) || [];
if (models.length === 0) return;
var blocks = _.map(models, function(model) {
return model.toJSON();
});
CommunicationComponent.getBulkTransformedPosts({
blocks: blocks,
}).then(_.partial(this.refreshBlocks, models));
},
refreshBlocks: function(models, renderedBlocks) {
_.each(
_.zip(models, renderedBlocks),
function(args) {
var model = args[0],
contents = args[1];
model.trigger('refreshPosts', contents);
}
);
},
});
Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({
stale: ['_container'],
defaults: function() {
@ -72,34 +109,21 @@ define([
},
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
this.fetchPosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change: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('refreshPosts', this.updatePosts, this);
},
fetchPosts: function() {
var that = this;
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged');
}).fail(function(error) {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
updatePosts: function(posts) {
this.get('_container.blocks').reset(posts, {parse: true});
},
/**
* Batch more changes during a specific time, instead of fetching
* ALC posts on each model change
*/
_scheduleFetchPosts: function() {
var timeout = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
}
this._fetchPostsTimer = setTimeout(function() {
that.fetchPosts();
that._fetchPostsTimer = undefined;
}, timeout);
App.getChannel().trigger('automatedLatestContentRefresh');
},
});
@ -124,6 +148,7 @@ define([
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
};
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView);
@ -149,24 +174,21 @@ define([
"change .mailpoet_automated_latest_content_title_format": 'changeTitleFormat',
"change .mailpoet_automated_latest_content_title_as_links": _.partial(this.changeBoolField, 'titleIsLink'),
"change .mailpoet_automated_latest_content_show_divider": _.partial(this.changeBoolField, 'showDivider'),
"keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
"input .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_automated_latest_content_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"input .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"),
"keyup .mailpoet_automated_latest_content_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"keyup .mailpoet_automated_latest_content_read_more_text": _.partial(this.changeField, "readMoreText"),
"input .mailpoet_automated_latest_content_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"input .mailpoet_automated_latest_content_read_more_text": _.partial(this.changeField, "readMoreText"),
"change .mailpoet_automated_latest_content_sort_by": _.partial(this.changeField, "sortBy"),
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
@ -181,6 +203,7 @@ define([
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
multiple: true,
allowClear: true,
placeholder: MailPoet.I18n.t('categoriesAndTags'),
ajax: {
data: function (params) {
return {
@ -188,8 +211,10 @@ define([
};
},
transport: function(options, success, failure) {
var taxonomies,
promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
var taxonomies;
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({
@ -198,7 +223,7 @@ define([
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
terms: terms
};
});
return promise;
@ -287,9 +312,11 @@ define([
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');
} else {
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_separator').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') {
@ -363,5 +390,10 @@ define([
});
});
App.on('start', function() {
App._ALCSupervisor = new Module.ALCSupervisor();
App._ALCSupervisor.refresh();
});
return Module;
});

View File

@ -40,6 +40,9 @@ define([
// Remove stale attributes from resulting JSON object
return _.omit(SuperModel.prototype.toJSON.call(this), this.stale);
},
getChildren: function() {
return [];
},
});
Module.BlockView = AugmentedView.extend({
@ -77,6 +80,7 @@ define([
}
},
},
HighlightEditingBehavior: {},
},
templateHelpers: function() {
return {
@ -95,12 +99,12 @@ define([
},
showTools: function(_event) {
if (!this.showingToolsDisabled) {
this.$('> .mailpoet_tools').show();
this.$('> .mailpoet_tools').addClass('mailpoet_display_tools');
this.toolsView.triggerMethod('showTools');
}
},
hideTools: function(e) {
this.$('> .mailpoet_tools').hide();
this.$('> .mailpoet_tools').removeClass('mailpoet_display_tools');
this.toolsView.triggerMethod('hideTools');
},
enableShowingTools: function() {
@ -215,8 +219,12 @@ define([
Module.BlockSettingsView = Marionette.LayoutView.extend({
className: 'mailpoet_editor_settings',
initialize: function() {
MailPoet.Modal.panel({
behaviors: {
ColorPickerBehavior: {},
},
initialize: function(params) {
this.model.trigger('startEditing');
var panelParams = {
element: this.$el,
template: '',
position: 'right',
@ -224,7 +232,13 @@ define([
onCancel: function() {
this.destroy();
}.bind(this),
});
};
this.renderOptions = params.renderOptions || {};
if (this.renderOptions.displayFormat === 'subpanel') {
MailPoet.Modal.subpanel(panelParams);
} else {
MailPoet.Modal.panel(panelParams);
}
},
close: function(event) {
this.destroy();
@ -253,6 +267,7 @@ define([
},
onBeforeDestroy: function() {
MailPoet.Modal.close();
this.model.trigger('stopEditing');
},
});

View File

@ -19,7 +19,7 @@ define([
return this._getDefaults({
type: 'button',
text: 'Button',
url: 'http://google.com',
url: '',
styles: {
block: {
backgroundColor: '#ff0000',
@ -44,6 +44,9 @@ define([
className: "mailpoet_block mailpoet_button_block mailpoet_droppable_block",
getTemplate: function() { return templates.buttonBlock; },
onDragSubstituteBy: function() { return Module.ButtonWidgetView; },
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {},
}),
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
@ -65,8 +68,8 @@ define([
getTemplate: function() { return templates.buttonBlockSettings; },
events: function() {
return {
"keyup .mailpoet_field_button_text": _.partial(this.changeField, "text"),
"keyup .mailpoet_field_button_url": _.partial(this.changeField, "url"),
"input .mailpoet_field_button_text": _.partial(this.changeField, "text"),
"input .mailpoet_field_button_url": _.partial(this.changeField, "url"),
"change .mailpoet_field_button_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"change .mailpoet_field_button_font_color": _.partial(this.changeColorField, "styles.block.fontColor"),
"change .mailpoet_field_button_font_family": _.partial(this.changeField, "styles.block.fontFamily"),
@ -77,45 +80,24 @@ define([
"input .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_button_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"keyup .mailpoet_field_button_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"input .mailpoet_field_button_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"input .mailpoet_field_button_border_radius": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius_input', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"change .mailpoet_field_button_border_radius": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius_input', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"change .mailpoet_field_button_border_radius_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"keyup .mailpoet_field_button_border_radius_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"input .mailpoet_field_button_border_radius_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"input .mailpoet_field_button_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width_input', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"change .mailpoet_field_button_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width_input', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"change .mailpoet_field_button_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"keyup .mailpoet_field_button_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"input .mailpoet_field_button_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"input .mailpoet_field_button_line_height": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height_input', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"change .mailpoet_field_button_line_height": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height_input', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"change .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"keyup .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"input .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"click .mailpoet_field_button_replace_all_styles": "applyToAll",
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
initialize: function(params) {
var panelParams = {
element: this.$el,
template: '',
position: 'right',
width: App.getConfig().get('sidepanelWidth'),
};
this.renderOptions = params.renderOptions || {};
if (this.renderOptions.displayFormat === 'subpanel') {
MailPoet.Modal.subpanel(panelParams);
} else {
MailPoet.Modal.panel(panelParams);
}
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -65,6 +65,13 @@ define([
}
return response;
},
getChildren: function() {
var models = this.get('blocks').map(function(model, index, list) {
return [model, model.getChildren()];
});
return _.flatten(models);
},
});
Module.ContainerBlockView = Marionette.CompositeView.extend({
@ -118,6 +125,7 @@ define([
return view.renderOptions.depth === 1;
},
},
HighlightEditingBehavior: {}
},
onDragSubstituteBy: function() {
// For two and three column layouts display their respective widgets,
@ -178,13 +186,13 @@ define([
},
showTools: function() {
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
this.$(this.ui.tools).show();
this.$(this.ui.tools).addClass('mailpoet_display_tools');
this.toolsView.triggerMethod('showTools');
}
},
hideTools: function() {
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
this.$(this.ui.tools).hide();
this.$(this.ui.tools).removeClass('mailpoet_display_tools');
this.toolsView.triggerMethod('hideTools');
}
},
@ -286,6 +294,7 @@ define([
templateHelpers: function() {
return {
isRoot: this.renderOptions.depth === 0,
emptyContainerMessage: this.renderOptions.emptyContainerMessage || '',
};
},
});
@ -302,9 +311,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
regions: {
columnsSettingsRegion: '.mailpoet_container_columns_settings',
},

View File

@ -43,6 +43,9 @@ define([
minLength: 0, // TODO: Move this number to editor configuration
modelField: 'styles.block.padding',
},
ShowSettingsBehavior: {
ignoreFrom: '.mailpoet_resize_handle'
},
}, base.BlockView.prototype.behaviors),
onDragSubstituteBy: function() { return Module.DividerWidgetView; },
initialize: function() {
@ -88,8 +91,7 @@ define([
"input .mailpoet_field_divider_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_divider_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_divider_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"keyup .mailpoet_field_divider_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"input .mailpoet_field_divider_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_divider_border_color": _.partial(this.changeColorField, "styles.block.borderColor"),
"change .mailpoet_field_divider_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
@ -102,23 +104,6 @@ define([
'change:styles.block.borderColor': 'repaintDividerStyleOptions',
};
},
behaviors: {
ColorPickerBehavior: {},
},
initialize: function(params) {
var panelParams = {
element: this.$el,
template: '',
position: 'right',
width: App.getConfig().get('sidepanelWidth'),
};
this.renderOptions = params.renderOptions || {};
if (this.renderOptions.displayFormat === 'subpanel') {
MailPoet.Modal.subpanel(panelParams);
} else {
MailPoet.Modal.panel(panelParams);
}
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -56,13 +56,15 @@ define([
inline: true,
menubar: false,
toolbar: "bold italic link unlink forecolor mailpoet_custom_fields",
toolbar: "bold italic link unlink forecolor mailpoet_shortcodes",
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
invalid_elements: "script",
block_formats: 'Paragraph=p',
relative_urls: false,
remove_script_host: false,
plugins: "link textcolor colorpicker mailpoet_custom_fields",
plugins: "link textcolor colorpicker mailpoet_shortcodes",
setup: function(editor) {
editor.on('change', function(e) {
@ -78,8 +80,8 @@ define([
});
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
});
},
});
@ -104,9 +106,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -56,13 +56,15 @@ define([
inline: true,
menubar: false,
toolbar: "bold italic link unlink forecolor mailpoet_custom_fields",
toolbar: "bold italic link unlink forecolor mailpoet_shortcodes",
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
invalid_elements: "script",
block_formats: 'Paragraph=p',
relative_urls: false,
remove_script_host: false,
plugins: "link textcolor colorpicker mailpoet_custom_fields",
plugins: "link textcolor colorpicker mailpoet_shortcodes",
setup: function(editor) {
editor.on('change', function(e) {
@ -78,8 +80,8 @@ define([
});
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
});
},
});
@ -104,9 +106,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -17,8 +17,8 @@ define([
defaults: function() {
return this._getDefaults({
type: 'image',
link: 'http://example.org',
src: 'no-image.png',
link: '',
src: '',
alt: 'An image of...',
fullWidth: true, // true | false
width: '64px',
@ -41,6 +41,9 @@ define([
imageMissingSrc: App.getConfig().get('urls.imageMissing'),
}, base.BlockView.prototype.templateHelpers.apply(this));
},
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {},
}),
onRender: function() {
this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView);
@ -61,9 +64,9 @@ define([
getTemplate: function() { return templates.imageBlockSettings; },
events: function() {
return {
"keyup .mailpoet_field_image_link": _.partial(this.changeField, "link"),
"keyup .mailpoet_field_image_address": _.partial(this.changeField, "src"),
"keyup .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
"input .mailpoet_field_image_link": _.partial(this.changeField, "link"),
"input .mailpoet_field_image_address": _.partial(this.changeField, "src"),
"input .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
"change .mailpoet_field_image_full_width": _.partial(this.changeBoolCheckboxField, "fullWidth"),
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"click .mailpoet_field_image_select_another_image": "showMediaManager",

View File

@ -23,7 +23,19 @@ define([
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'select2'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, CommunicationComponent, BaseBlock, ButtonBlock, DividerBlock) {
], function(
Backbone,
Marionette,
Radio,
_,
jQuery,
MailPoet,
App,
CommunicationComponent,
BaseBlock,
ButtonBlock,
DividerBlock
) {
"use strict";
@ -163,6 +175,7 @@ define([
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
};
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
},
@ -195,6 +208,7 @@ define([
};
},
initialize: function() {
this.model.trigger('startEditing');
this.selectionView = new PostSelectionSettingsView({ model: this.model });
this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model });
},
@ -202,21 +216,23 @@ define([
var that = this,
blockView = this.model.request('blockView');
this.selectionRegion.show(this.selectionView);
this.displayOptionsRegion.show(this.displayOptionsView);
this.showChildView('selectionRegion', this.selectionView);
this.showChildView('displayOptionsRegion', this.displayOptionsView);
MailPoet.Modal.panel({
element: this.$el,
template: '',
position: 'right',
overlay: true,
highlight: blockView.$el,
width: App.getConfig().get('sidepanelWidth'),
onCancel: function() {
// Self destroy the block if the user closes settings modal
that.model.destroy();
},
});
// Inform child views that they have been attached to document
this.selectionView.triggerMethod('attach');
this.displayOptionsView.triggerMethod('attach');
},
switchToDisplayOptions: function() {
// Switch content view
@ -257,7 +273,7 @@ define([
return {
'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'),
'change .mailpoet_posts_post_status': _.partial(this.changeField, 'postStatus'),
'keyup .mailpoet_posts_search_term': _.partial(this.changeField, 'search'),
'input .mailpoet_posts_search_term': _.partial(this.changeField, 'search'),
};
},
constructor: function() {
@ -266,14 +282,19 @@ define([
Marionette.CompositeView.apply(this, arguments);
},
onRender: function() {
// Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
},
onAttach: function() {
var that = this;
// Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
//CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
this.$('.mailpoet_posts_categories_and_tags').select2({
multiple: true,
allowClear: true,
placeholder: MailPoet.I18n.t('categoriesAndTags'),
ajax: {
data: function (params) {
return {
@ -281,8 +302,10 @@ define([
};
},
transport: function(options, success, failure) {
var taxonomies,
promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
var taxonomies;
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({
@ -291,7 +314,7 @@ define([
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
terms: terms
};
});
return promise;
@ -391,23 +414,20 @@ define([
"change .mailpoet_posts_title_format": 'changeTitleFormat',
"change .mailpoet_posts_title_as_links": _.partial(this.changeBoolField, 'titleIsLink'),
"change .mailpoet_posts_show_divider": _.partial(this.changeBoolField, 'showDivider'),
"keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
"input .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_posts_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"input .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
"keyup .mailpoet_posts_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"keyup .mailpoet_posts_read_more_text": _.partial(this.changeField, "readMoreText"),
"input .mailpoet_posts_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"input .mailpoet_posts_read_more_text": _.partial(this.changeField, "readMoreText"),
"change .mailpoet_posts_sort_by": _.partial(this.changeField, "sortBy"),
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
@ -450,9 +470,11 @@ define([
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');
} else {
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_separator').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') {

View File

@ -93,6 +93,7 @@ define([
return {
model: this.model.toJSON(),
allIconSets: allIconSets.toJSON(),
imageMissingSrc: App.getConfig().get('urls.imageMissing'),
};
},
});
@ -139,6 +140,8 @@ define([
}
},
},
HighlightEditingBehavior: {},
ShowSettingsBehavior: {},
},
onDragSubstituteBy: function() { return Module.SocialWidgetView; },
constructor: function() {
@ -147,6 +150,7 @@ define([
Marionette.CompositeView.apply(this, arguments);
},
initialize: function() {
this.on('showSettings', this.showSettings, this);
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
@ -167,13 +171,16 @@ define([
this.regionManager.destroy();
},
showTools: function(_event) {
this.$(this.ui.tools).show();
this.$(this.ui.tools).addClass('mailpoet_display_tools');
_event.stopPropagation();
},
hideTools: function(_event) {
this.$(this.ui.tools).hide();
this.$(this.ui.tools).removeClass('mailpoet_display_tools');
_event.stopPropagation();
},
showSettings: function(options) {
this.toolsView.triggerMethod('showSettings', options);
},
getDropFunc: function() {
return function() {
return this.model.clone();
@ -273,9 +280,9 @@ define([
return {
"click .mailpoet_delete_block": "deleteIcon",
"change .mailpoet_social_icon_field_type": _.partial(this.changeField, "iconType"),
"keyup .mailpoet_social_icon_field_image": _.partial(this.changeField, "image"),
"keyup .mailpoet_social_icon_field_link": this.changeLink,
"keyup .mailpoet_social_icon_field_text": _.partial(this.changeField, "text"),
"input .mailpoet_social_icon_field_image": _.partial(this.changeField, "image"),
"input .mailpoet_social_icon_field_link": this.changeLink,
"input .mailpoet_social_icon_field_text": _.partial(this.changeField, "text"),
};
},
modelEvents: {
@ -382,7 +389,7 @@ define([
{
type: 'socialIcon',
iconType: 'facebook',
link: 'http://example.com',
link: 'http://www.facebook.com',
image: App.getAvailableStyles().get('socialIconSets.default.facebook'),
height: '32px',
width: '32px',
@ -391,7 +398,7 @@ define([
{
type: 'socialIcon',
iconType: 'twitter',
link: 'http://example.com',
link: 'http://www.twitter.com',
image: App.getAvailableStyles().get('socialIconSets.default.twitter'),
height: '32px',
width: '32px',

View File

@ -36,6 +36,9 @@ define([
minLength: 20, // TODO: Move this number to editor configuration
modelField: 'styles.block.height',
},
ShowSettingsBehavior: {
ignoreFrom: '.mailpoet_resize_handle'
},
}, base.BlockView.prototype.behaviors),
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'),
onDragSubstituteBy: function() { return Module.SpacerWidgetView; },
@ -70,9 +73,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
});
Module.SpacerWidgetView = base.WidgetView.extend({

View File

@ -53,14 +53,16 @@ define([
menubar: false,
toolbar1: "formatselect bold italic forecolor | link unlink",
toolbar2: "alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_custom_fields",
toolbar2: "alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_shortcodes",
//forced_root_block: 'p',
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],h1[class|style],h2[class|style],h3[class|style],ol[class|style],ul[class|style],li[class|style],strong[class|style],em[class|style],strike,br,blockquote[class|style],table[class|style],tr[class|style],th[class|style],td[class|style]",
invalid_elements: "script",
block_formats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
relative_urls: false,
remove_script_host: false,
plugins: "link code textcolor colorpicker mailpoet_custom_fields",
plugins: "link code textcolor colorpicker mailpoet_shortcodes",
setup: function(editor) {
editor.on('change', function(e) {
@ -76,8 +78,8 @@ define([
});
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
});
}
},

View File

@ -27,8 +27,8 @@ define([
return Module._cachedQuery({
action: 'getPostTypes',
options: {},
}).then(function(types) {
return _.values(types);
}).then(function(response) {
return _.values(response.data);
});
};
@ -36,29 +36,46 @@ define([
return Module._cachedQuery({
action: 'getTaxonomies',
options: {
postType: postType,
},
postType: postType
}
}).then(function(response) {
return response.data;
});
};
Module.getTerms = function(options) {
return Module._cachedQuery({
action: 'getTerms',
options: options,
options: options
}).then(function(response) {
return response.data;
});
};
Module.getPosts = function(options) {
return Module._cachedQuery({
action: 'getPosts',
options: options,
options: options
}).then(function(response) {
return response.data;
});
};
Module.getTransformedPosts = function(options) {
return Module._cachedQuery({
action: 'getTransformedPosts',
options: options,
options: options
}).then(function(response) {
return response.data;
});
};
Module.getBulkTransformedPosts = function(options) {
return Module._query({
action: 'getBulkTransformedPosts',
options: options
}).then(function(response) {
return response.data;
});
};

View File

@ -60,6 +60,11 @@ define([
return Module.newsletter;
};
Module.findModels = function(predicate) {
var blocks = App._contentContainer.getChildren();
return _.filter(blocks, predicate);
};
App.on('before:start', function(options) {
// Expose block methods globally
App.registerBlockType = Module.registerBlockType;
@ -68,6 +73,7 @@ define([
App.toJSON = Module.toJSON;
App.getBody = Module.getBody;
App.getNewsletter = Module.getNewsletter;
App.findModels = Module.findModels;
Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body']));
});

View File

@ -104,7 +104,7 @@ define([
return MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: data,
data: data
});
});
@ -283,8 +283,10 @@ define([
return;
}
var contents = JSON.stringify(jsonObject);
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
JSON.stringify(jsonObject).indexOf("[link:subscription_unsubscribe_url]") < 0) {
contents.indexOf("[link:subscription_unsubscribe_url]") < 0 &&
contents.indexOf("[link:subscription_unsubscribe]") < 0) {
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
return;
}

View File

@ -65,10 +65,6 @@ define([
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'),
$targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
if ($openRegion.get(0) === $targetRegion.get(0)) {
return;
}
$openRegion.find('.mailpoet_region_content').velocity(
'slideUp',
{
@ -79,16 +75,19 @@ define([
}.bind(this)
}
);
$targetRegion.find('.mailpoet_region_content').velocity(
'slideDown',
{
duration: 250,
easing: "easeIn",
complete: function() {
$targetRegion.removeClass('closed');
},
}
);
if ($openRegion.get(0) !== $targetRegion.get(0)) {
$targetRegion.find('.mailpoet_region_content').velocity(
'slideDown',
{
duration: 250,
easing: "easeIn",
complete: function() {
$targetRegion.removeClass('closed');
},
}
);
}
},
},
initialize: function(options) {
@ -233,6 +232,12 @@ define([
'click .mailpoet_show_preview': 'showPreview',
'click #mailpoet_send_preview': 'sendPreview',
},
onBeforeDestroy: function() {
if (this.previewView) {
this.previewView.destroy();
this.previewView = null;
}
},
showPreview: function() {
var json = App.toJSON();
@ -247,18 +252,32 @@ define([
endpoint: 'newsletters',
action: 'showPreview',
data: json,
}).done(function(response){
MailPoet.Modal.loading(false);
}).done(function(response) {
if (response.result === true) {
window.open(response.data.url, '_blank')
this.previewView = new Module.NewsletterPreviewView({
previewUrl: response.data.url
});
var view = this.previewView.render();
MailPoet.Modal.popup({
template: '',
element: this.previewView.$el,
title: MailPoet.I18n.t('newsletterPreview'),
onCancel: function() {
this.previewView.destroy();
this.previewView = null;
}.bind(this)
});
} else {
MailPoet.Notice.error(response.errors);
}
MailPoet.Notice.error(response.errors);
}).fail(function(error) {
MailPoet.Modal.loading(false);
}.bind(this)).fail(function(error) {
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailed')
);
}).always(function() {
MailPoet.Modal.loading(false);
});
},
sendPreview: function() {
@ -309,6 +328,22 @@ define([
},
});
Module.NewsletterPreviewView = Marionette.ItemView.extend({
getTemplate: function() { return templates.newsletterPreview; },
initialize: function(options) {
this.previewUrl = options.previewUrl;
this.width = App.getConfig().get('newsletterPreview.width');
this.height = App.getConfig().get('newsletterPreview.height')
},
templateHelpers: function() {
return {
previewUrl: this.previewUrl,
width: this.width,
height: this.height,
};
}
});
App.on('before:start', function(options) {
App.registerWidget = Module.registerWidget;
App.getWidgets = Module.getWidgets;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,58 +0,0 @@
/**
* wysija_custom_fields/plugin.js
*
* TinyMCE plugin for adding dynamic data placeholders to newsletters.
*
* This adds a button to the editor toolbar which displays a modal window of
* available dynamic data placeholder buttons. On click each button inserts
* its placeholder into editor text.
*/
/*jshint unused:false */
/*global tinymce:true */
tinymce.PluginManager.add('mailpoet_custom_fields', function(editor, url) {
var appendLabelAndClose = function(text) {
editor.insertContent('[' + text + ']');
editor.windowManager.close();
},
generateOnClickFunc = function(id) {
return function() {
appendLabelAndClose(id);
};
};
editor.addButton('mailpoet_custom_fields', {
icon: 'mailpoet_custom_fields',
onclick: function() {
var customFields = [],
configCustomFields = editor.settings.mailpoet_custom_fields;
for (var segment in configCustomFields) {
if (configCustomFields.hasOwnProperty(segment)) {
customFields.push({
type: 'label',
text: segment,
});
for (var i = 0; i < configCustomFields[segment].length; i += 1) {
customFields.push({
type: 'button',
text: configCustomFields[segment][i].text,
onClick: generateOnClickFunc(configCustomFields[segment][i].shortcode)
});
}
}
}
// Open window
editor.windowManager.open({
height: parseInt(editor.getParam("plugin_mailpoet_custom_fields_height", 400)),
width: parseInt(editor.getParam("plugin_mailpoet_custom_fields_width", 450)),
autoScroll: true,
title: editor.settings.mailpoet_custom_fields_window_title,
body: customFields,
buttons: [],
});
},
});
});

View File

@ -0,0 +1,58 @@
/**
* wysija_shortcodes/plugin.js
*
* TinyMCE plugin for adding dynamic data placeholders to newsletters.
*
* This adds a button to the editor toolbar which displays a modal window of
* available dynamic data placeholder buttons. On click each button inserts
* its placeholder into editor text.
*/
/*jshint unused:false */
/*global tinymce:true */
tinymce.PluginManager.add('mailpoet_shortcodes', function(editor, url) {
var appendLabelAndClose = function(text) {
editor.insertContent('[' + text + ']');
editor.windowManager.close();
},
generateOnClickFunc = function(id) {
return function() {
appendLabelAndClose(id);
};
};
editor.addButton('mailpoet_shortcodes', {
icon: 'mailpoet_shortcodes',
onclick: function() {
var shortcodes = [],
configShortcodes = editor.settings.mailpoet_shortcodes;
for (var segment in configShortcodes) {
if (configShortcodes.hasOwnProperty(segment)) {
shortcodes.push({
type: 'label',
text: segment,
});
for (var i = 0; i < configShortcodes[segment].length; i += 1) {
shortcodes.push({
type: 'button',
text: configShortcodes[segment][i].text,
onClick: generateOnClickFunc(configShortcodes[segment][i].shortcode)
});
}
}
}
// Open window
editor.windowManager.open({
height: parseInt(editor.getParam("plugin_mailpoet_shortcodes_height", 400)),
width: parseInt(editor.getParam("plugin_mailpoet_shortcodes_width", 450)),
autoScroll: true,
title: editor.settings.mailpoet_shortcodes_window_title,
body: shortcodes,
buttons: [],
});
},
});
});

View File

@ -1,311 +0,0 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'classnames',
'jquery',
'mailpoet'
],
function(
React,
Router,
Listing,
classNames,
jQuery,
MailPoet
) {
var Link = Router.Link;
var columns = [
{
name: 'subject',
label: MailPoet.I18n.t('subject'),
sortable: true
},
{
name: 'status',
label: MailPoet.I18n.t('status')
},
{
name: 'segments',
label: MailPoet.I18n.t('lists')
},
{
name: 'statistics',
label: MailPoet.I18n.t('statistics')
},
{
name: 'created_at',
label: MailPoet.I18n.t('createdOn'),
sortable: true
},
{
name: 'updated_at',
label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true
}
];
var messages = {
onTrash: function(response) {
var count = ~~response;
var message = null;
if(count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
var count = ~~response;
var message = null;
if(count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
var count = ~~response;
var message = null;
if(count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
var bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
var item_actions = [
{
name: 'edit',
link: function(item) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
{MailPoet.I18n.t('edit')}
</a>
);
}
},
{
name: 'trash'
}
];
var NewsletterList = React.createClass({
pauseSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'pause',
data: item.id
}).done(function() {
jQuery('#resume_'+item.id).show();
jQuery('#pause_'+item.id).hide();
});
},
resumeSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'resume',
data: item.id
}).done(function() {
jQuery('#pause_'+item.id).show();
jQuery('#resume_'+item.id).hide();
});
},
renderStatus: function(item) {
if(!item.queue) {
return (
<span>{MailPoet.I18n.t('notSentYet')}</span>
);
} else {
if (item.queue.status === 'scheduled') {
return (
<span>{MailPoet.I18n.t('scheduledFor')} { MailPoet.Date.format(item.queue.scheduled_at) } </span>
)
}
var progressClasses = classNames(
'mailpoet_progress',
{ 'mailpoet_progress_complete': item.queue.status === 'completed'}
);
// calculate percentage done
var percentage = Math.round(
(item.queue.count_processed * 100) / (item.queue.count_total)
);
var label = false;
if(item.queue.status === 'completed') {
label = (
<span>
{
MailPoet.I18n.t('newsletterQueueCompleted')
.replace("%$1d", item.queue.count_processed - item.queue.count_failed)
.replace("%$2d", item.queue.count_total)
}
</span>
);
} else {
label = (
<span>
{ item.queue.count_processed } / { item.queue.count_total }
&nbsp;&nbsp;
<a
id={ 'resume_'+item.id }
className="button"
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.resumeSending.bind(null, item) }
>{MailPoet.I18n.t('resume')}</a>
<a
id={ 'pause_'+item.id }
className="button mailpoet_pause"
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.pauseSending.bind(null, item) }
>{MailPoet.I18n.t('pause')}</a>
</span>
);
}
return (
<div>
<div className={ progressClasses }>
<span
className="mailpoet_progress_bar"
style={ { width: percentage + "%"} }
></span>
<span className="mailpoet_progress_label">
{ percentage + "%" }
</span>
</div>
<p style={{ textAlign:'center' }}>
{ label }
</p>
</div>
);
}
},
renderStatistics: function(item) {
if(!item.statistics || !item.queue || item.queue.count_processed == 0 || item.queue.status === 'scheduled') {
return (
<span>
{MailPoet.I18n.t('notSentYet')}
</span>
);
}
var percentage_clicked = Math.round(
(item.statistics.clicked * 100) / (item.queue.count_processed)
);
var percentage_opened = Math.round(
(item.statistics.opened * 100) / (item.queue.count_processed)
);
var percentage_unsubscribed = Math.round(
(item.statistics.unsubscribed * 100) / (item.queue.count_processed)
);
return (
<span>
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
</span>
);
},
renderItem: function(newsletter, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
var segments = newsletter.segments.map(function(segment) {
return segment.name
}).join(', ');
var statistics_column =
(!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) ?
false :
<td className="column {statistics_class}" data-colname={ MailPoet.I18n.t('statistics') }>
{ this.renderStatistics(newsletter) }
</td>;
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ newsletter.subject }</a>
</strong>
{ actions }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
{ segments }
</td>
{ statistics_column }
<td className="column-date" data-colname={ MailPoet.I18n.t('createdOn') }>
<abbr>{ MailPoet.Date.format(newsletter.created_at) }</abbr>
</td>
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td>
</div>
);
},
render: function() {
if (!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) {
columns = _.without(columns, _.findWhere(columns, {name: 'statistics'}));
}
return (
<div>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<Listing
limit={ mailpoet_listing_per_page }
params={ this.props.params }
endpoint="newsletters"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions }
item_actions={ item_actions }
messages={ messages }
auto_refresh={ true } />
</div>
);
}
});
return NewsletterList;
}
);

View File

@ -0,0 +1,170 @@
import React from 'react'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
import jQuery from 'jquery'
const _QueueMixin = {
pauseSending: function(newsletter) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'pause',
data: {
newsletter_id: newsletter.id
}
}).done(function() {
jQuery('#resume_'+newsletter.id).show();
jQuery('#pause_'+newsletter.id).hide();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
});
},
resumeSending: function(newsletter) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'resume',
data: {
newsletter_id: newsletter.id
}
}).done(function() {
jQuery('#pause_'+newsletter.id).show();
jQuery('#resume_'+newsletter.id).hide();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
});
},
renderQueueStatus: function(newsletter) {
if (!newsletter.queue) {
return (
<span>{MailPoet.I18n.t('notSentYet')}</span>
);
} else {
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
const percentage = Math.round(
(newsletter.queue.count_processed * 100) / (newsletter.queue.count_total)
);
let label;
if (newsletter.queue.status === 'completed') {
label = (
<span>
{
MailPoet.I18n.t('newsletterQueueCompleted')
.replace(
"%$1d",
newsletter.queue.count_processed - newsletter.queue.count_failed
)
.replace(
"%$2d",
newsletter.queue.count_total
)
}
</span>
);
} 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')
? '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)
? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.pauseSending.bind(null, newsletter) }
>{MailPoet.I18n.t('pause')}</a>
</span>
);
}
return (
<div>
<div className={ progressClasses }>
<span
className="mailpoet_progress_bar"
style={ { width: percentage + "%"} }
></span>
<span className="mailpoet_progress_label">
{ percentage + "%" }
</span>
</div>
<p style={{ textAlign:'center' }}>
{ label }
</p>
</div>
);
}
},
};
const _StatisticsMixin = {
renderStatistics: function(newsletter) {
if (
newsletter.statistics
&& newsletter.queue
&& newsletter.queue.status !== 'scheduled'
) {
const total_sent = ~~(newsletter.queue.count_processed);
let percentage_clicked = 0;
let percentage_opened = 0;
let percentage_unsubscribed = 0;
if (total_sent > 0) {
percentage_clicked = Math.round(
(~~(newsletter.statistics.clicked) * 100) / total_sent
);
percentage_opened = Math.round(
(~~(newsletter.statistics.opened) * 100) / total_sent
);
percentage_unsubscribed = Math.round(
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
);
}
return (
<span>
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
</span>
);
} else {
return (
<span>{MailPoet.I18n.t('notSentYet')}</span>
);
}
}
}
export { _QueueMixin as QueueMixin };
export { _StatisticsMixin as StatisticsMixin };

View File

@ -0,0 +1,330 @@
import React from 'react'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import classNames from 'classnames'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import {
timeOfDayValues,
weekDayValues,
monthDayValues,
nthWeekDayValues
} from 'newsletters/scheduling/common.jsx'
const messages = {
onTrash(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onDelete(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onRestore(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
const columns = [
{
name: 'subject',
label: MailPoet.I18n.t('subject'),
sortable: true
},
{
name: 'status',
label: MailPoet.I18n.t('status'),
width: 100
},
{
name: 'settings',
label: MailPoet.I18n.t('settings')
},
{
name: 'history',
label: MailPoet.I18n.t('history'),
width: 100
},
{
name: 'updated_at',
label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true
}
];
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
const newsletter_actions = [
{
name: 'view',
link: function(newsletter) {
return (
<a href={ newsletter.preview_url } target="_blank">
{MailPoet.I18n.t('preview')}
</a>
);
}
},
{
name: 'edit',
link: function(newsletter) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
{MailPoet.I18n.t('edit')}
</a>
);
}
},
{
name: 'duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function(newsletter, refresh) {
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'duplicate',
data: newsletter.id
}).done(function(response) {
if (response !== false && response.subject !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.subject
)
);
}
refresh();
});
}
},
{
name: 'trash'
}
];
const NewsletterListNotification = React.createClass({
updateStatus: function(e) {
// make the event persist so that we can still override the selected value
// in the ajax callback
e.persist();
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'setStatus',
data: {
id: ~~(e.target.getAttribute('data-id')),
status: e.target.value
}
}).done((response) => {
if (response.data.status === 'active') {
MailPoet.Notice.success(MailPoet.I18n.t('postNotificationActivated'));
}
// force refresh of listing so that groups are updated
this.forceUpdate();
}).fail((response) => {
MailPoet.Notice.error(MailPoet.I18n.t('postNotificationActivationFailed'));
// reset value to actual newsletter's status
e.target.value = response.status;
});
},
renderStatus: function(newsletter) {
return (
<select
data-id={ newsletter.id }
defaultValue={ newsletter.status }
onChange={ this.updateStatus }
>
<option value="active">{ MailPoet.I18n.t('active') }</option>
<option value="draft">{ MailPoet.I18n.t('inactive') }</option>
</select>
);
},
renderSettings: function(newsletter) {
let sendingFrequency;
let sendingToSegments;
// get list of segments' name
const segments = newsletter.segments.map(function(segment) {
return segment.name
});
// check if the user has specified segments to send to
if(segments.length === 0) {
return (
<span className="mailpoet_error">
{ 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(
'%$1s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
'%$1s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
'%$1s', monthDayValues[newsletter.options.monthDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
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;
case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately');
break;
}
}
return (
<span>
{ sendingFrequency } { sendingToSegments }
</span>
);
},
renderHistoryLink: function(newsletter) {
const childrenCount = ~~(newsletter.children_count);
if (childrenCount === 0) {
return (
MailPoet.I18n.t('notSentYet')
);
} else {
return (
<Link
to={ `/notification/history/${ newsletter.id }` }
>{ MailPoet.I18n.t('viewHistory') }</Link>
);
}
},
renderItem: function(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a
className="row-title"
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }
>{ newsletter.subject }</a>
</strong>
{ actions }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('settings') }>
{ this.renderSettings(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('history') }>
{ this.renderHistoryLink(newsletter) }
</td>
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<ListingTabs tab="notification" />
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
endpoint="newsletters"
type="notification"
base_url="notification"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ newsletter_actions }
messages={ messages }
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
/>
</div>
);
}
});
module.exports = NewsletterListNotification;

View File

@ -0,0 +1,125 @@
import React from 'react'
import { Router, Link } from 'react-router'
import classNames from 'classnames'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const columns = [
{
name: 'subject',
label: MailPoet.I18n.t('subject'),
},
{
name: 'status',
label: MailPoet.I18n.t('status')
},
{
name: 'segments',
label: MailPoet.I18n.t('lists')
},
{
name: 'statistics',
label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled
},
{
name: 'processed_at',
label: MailPoet.I18n.t('sentOn'),
}
];
const newsletter_actions = [
{
name: 'view',
link: function(newsletter) {
return (
<a href={ newsletter.preview_url } target="_blank">
{MailPoet.I18n.t('preview')}
</a>
);
}
}
];
const NewsletterListNotificationHistory = React.createClass({
mixins: [QueueMixin, StatisticsMixin],
renderItem: function(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
const segments = newsletter.segments.map(function(segment) {
return segment.name
}).join(', ');
return (
<div>
<td className={ rowClasses }>
<strong>
<a
href={ newsletter.preview_url }
target="_blank"
>{ newsletter.subject }</a>
</strong>
{ actions }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderQueueStatus(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
{ segments }
</td>
{ (mailpoet_tracking_enabled === true) ? (
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
{ this.renderStatistics(newsletter) }
</td>
) : null }
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<ListingTabs tab="notification" />
<Link
className="page-title-action"
to="/notification"
>{MailPoet.I18n.t('backToPostNotifications')}</Link>
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
endpoint="newsletters"
type="notification_history"
base_url="notification/history/:parent_id"
onRenderItem={ this.renderItem }
columns={columns}
item_actions={ newsletter_actions }
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
/>
</div>
);
}
});
module.exports = NewsletterListNotificationHistory;

View File

@ -0,0 +1,214 @@
import React from 'react'
import { Router, Link } from 'react-router'
import classNames from 'classnames'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const messages = {
onTrash(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onDelete(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onRestore(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
const columns = [
{
name: 'subject',
label: MailPoet.I18n.t('subject'),
sortable: true
},
{
name: 'status',
label: MailPoet.I18n.t('status')
},
{
name: 'segments',
label: MailPoet.I18n.t('lists')
},
{
name: 'statistics',
label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled
},
{
name: 'updated_at',
label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true
}
];
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
const newsletter_actions = [
{
name: 'view',
link: function(newsletter) {
return (
<a href={ newsletter.preview_url } target="_blank">
{MailPoet.I18n.t('preview')}
</a>
);
}
},
{
name: 'edit',
link: function(newsletter) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
{MailPoet.I18n.t('edit')}
</a>
);
}
},
{
name: 'duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function(newsletter, refresh) {
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'duplicate',
data: newsletter.id
}).done(function(response) {
if (response !== false && response.subject !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.subject
)
);
}
refresh();
});
}
},
{
name: 'trash'
}
];
const NewsletterListStandard = React.createClass({
mixins: [QueueMixin, StatisticsMixin],
renderItem: function(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
const segments = newsletter.segments.map(function(segment) {
return segment.name
}).join(', ');
return (
<div>
<td className={ rowClasses }>
<strong>
<a
className="row-title"
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }
>{ newsletter.subject }</a>
</strong>
{ actions }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderQueueStatus(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
{ segments }
</td>
{ (mailpoet_tracking_enabled === true) ? (
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
{ this.renderStatistics(newsletter) }
</td>
) : null }
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<ListingTabs tab="standard" />
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
endpoint="newsletters"
type="standard"
base_url="standard"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions }
item_actions={ newsletter_actions }
messages={ messages }
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
/>
</div>
);
}
});
module.exports = NewsletterListStandard;

View File

@ -0,0 +1,53 @@
import React from 'react'
import { Link } from 'react-router'
import classNames from 'classnames'
import MailPoet from 'mailpoet'
const ListingTabs = React.createClass({
getInitialState() {
return {
tab: null,
tabs: [
{
name: 'standard',
label: MailPoet.I18n.t('tabStandardTitle'),
link: '/standard'
},
{
name: 'welcome',
label: MailPoet.I18n.t('tabWelcomeTitle'),
link: '/welcome'
},
{
name: 'notification',
label: MailPoet.I18n.t('tabNotificationTitle'),
link: '/notification'
}
]
};
},
render() {
const tabs = this.state.tabs.map((tab, index) => {
const tabClasses = classNames(
'nav-tab',
{ 'nav-tab-active': (this.props.tab === tab.name) }
);
return (
<Link
key={ 'tab-'+index }
className={ tabClasses }
to={ tab.link }
>{ tab.label }</Link>
);
});
return (
<h2 className="nav-tab-wrapper">
{ tabs }
</h2>
);
}
});
module.exports = ListingTabs;

View File

@ -0,0 +1,360 @@
import React from 'react'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import classNames from 'classnames'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import _ from 'underscore'
const mailpoet_roles = window.mailpoet_roles || {};
const mailpoet_segments = window.mailpoet_segments || {};
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const messages = {
onTrash(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onDelete(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onRestore(response) {
const count = ~~response;
let message = null;
if (count === 1) {
message = (
MailPoet.I18n.t('oneNewsletterRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
const columns = [
{
name: 'subject',
label: MailPoet.I18n.t('subject'),
sortable: true
},
{
name: 'status',
label: MailPoet.I18n.t('status'),
width: 145
},
{
name: 'settings',
label: MailPoet.I18n.t('settings')
},
{
name: 'statistics',
label: MailPoet.I18n.t('statistics'),
display: mailpoet_tracking_enabled
},
{
name: 'updated_at',
label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true
}
];
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
const newsletter_actions = [
{
name: 'view',
link: function(newsletter) {
return (
<a href={ newsletter.preview_url } target="_blank">
{MailPoet.I18n.t('preview')}
</a>
);
}
},
{
name: 'edit',
link: function(newsletter) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
{MailPoet.I18n.t('edit')}
</a>
);
}
},
{
name: 'duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function(newsletter, refresh) {
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'duplicate',
data: newsletter.id
}).done(function(response) {
if (response !== false && response.subject !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.subject
)
);
}
refresh();
});
}
},
{
name: 'trash'
}
];
const NewsletterListWelcome = React.createClass({
updateStatus: function(e) {
// make the event persist so that we can still override the selected value
// in the ajax callback
e.persist();
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'setStatus',
data: {
id: ~~(e.target.getAttribute('data-id')),
status: e.target.value
}
}).done((response) => {
if (response.data.status === 'active') {
MailPoet.Notice.success(MailPoet.I18n.t('welcomeEmailActivated'));
}
// force refresh of listing so that groups are updated
this.forceUpdate();
}).fail((response) => {
MailPoet.Notice.error(MailPoet.I18n.t('welcomeEmailActivationFailed'));
// reset value to actual newsletter's status
e.target.value = response.status;
});
},
renderStatus: function(newsletter) {
let total_sent;
total_sent = (
MailPoet.I18n.t('sentToXSubscribers')
.replace('%$1d', newsletter.total_sent.toLocaleString())
);
return (
<div>
<p>
<select
data-id={ newsletter.id }
defaultValue={ newsletter.status }
onChange={ this.updateStatus }
>
<option value="active">{ MailPoet.I18n.t('active') }</option>
<option value="draft">{ MailPoet.I18n.t('inactive') }</option>
</select>
</p>
<p>{ total_sent }</p>
</div>
);
},
renderSettings: function(newsletter) {
let sendingEvent;
let sendingDelay;
// set sending event
switch (newsletter.options.event) {
case 'user':
// WP User
if (newsletter.options.role === 'mailpoet_all') {
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserAnyRole');
} else {
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserWithRole').replace(
'%$1s', mailpoet_roles[newsletter.options.role]
);
}
break;
case 'segment':
// get segment
const segment = _.find(mailpoet_segments, function(segment) {
return (~~(segment.id) === ~~(newsletter.options.segment));
});
if (segment === undefined) {
return (
<span className="mailpoet_error">
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span>
);
} else {
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
'%$1s', segment.name
);
}
break;
}
// set sending delay
if (sendingEvent) {
if (newsletter.options.afterTimeType !== 'immediate') {
switch (newsletter.options.afterTimeType) {
case 'hours':
sendingDelay = MailPoet.I18n.t('sendingDelayHours').replace(
'%$1d', newsletter.options.afterTimeNumber
);
break;
case 'days':
sendingDelay = MailPoet.I18n.t('sendingDelayDays').replace(
'%$1d', newsletter.options.afterTimeNumber
);
break;
case 'weeks':
sendingDelay = MailPoet.I18n.t('sendingDelayWeeks').replace(
'%$1d', newsletter.options.afterTimeNumber
);
break;
}
sendingEvent += ' [' + sendingDelay + ']';
}
// add a "period" at the end if we do have a sendingEvent
sendingEvent += '.';
}
return (
<span>
{ sendingEvent }
</span>
);
},
renderStatistics: function(newsletter) {
if (mailpoet_tracking_enabled === false) {
return;
}
if (newsletter.total_sent > 0 && newsletter.statistics) {
const total_sent = ~~(newsletter.total_sent);
const percentage_clicked = Math.round(
(~~(newsletter.statistics.clicked) * 100) / total_sent
);
const percentage_opened = Math.round(
(~~(newsletter.statistics.opened) * 100) / total_sent
);
const percentage_unsubscribed = Math.round(
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
);
return (
<span>
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
</span>
);
} else {
return (
<span>{MailPoet.I18n.t('notSentYet')}</span>
);
}
},
renderItem: function(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a
className="row-title"
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }
>{ newsletter.subject }</a>
</strong>
{ actions }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('settings') }>
{ this.renderSettings(newsletter) }
</td>
{ (mailpoet_tracking_enabled === true) ? (
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
{ this.renderStatistics(newsletter) }
</td>
) : null }
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h1 className="title">
{ MailPoet.I18n.t('pageTitle') } <Link className="page-title-action" to="/new">{ MailPoet.I18n.t('new') }</Link>
</h1>
<ListingTabs tab="welcome" />
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
endpoint="newsletters"
type="welcome"
base_url="welcome"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ newsletter_actions }
messages={ messages }
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
/>
</div>
);
}
});
module.exports = NewsletterListWelcome;

View File

@ -1,20 +1,26 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { Router, Route, IndexRedirect, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import NewsletterList from 'newsletters/list.jsx'
import NewsletterTypes from 'newsletters/types.jsx'
import NewsletterTemplates from 'newsletters/templates.jsx'
import NewsletterSend from 'newsletters/send.jsx'
import NewsletterStandard from 'newsletters/types/standard.jsx'
import NewsletterWelcome from 'newsletters/types/welcome/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification/notification.jsx'
import NewsletterTypeStandard from 'newsletters/types/standard.jsx'
import NewsletterTypeWelcome from 'newsletters/types/welcome/welcome.jsx'
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx'
import NewsletterListStandard from 'newsletters/listings/standard.jsx'
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx'
import NewsletterListNotification from 'newsletters/listings/notification.jsx'
import NewsletterListNotificationHistory from 'newsletters/listings/notification_history.jsx'
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
return this.props.children
return this.props.children;
}
});
@ -24,15 +30,22 @@ if(container) {
ReactDOM.render((
<Router history={ history }>
<Route path="/" component={ App }>
<IndexRoute component={ NewsletterList } />
<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 } />
<Route name="standard" path="new/standard" component={ NewsletterStandard } />
<Route name="welcome" path="new/welcome" component={ NewsletterWelcome } />
<Route name="notification" path="new/notification" component={ NewsletterNotification } />
{/* New newsletter: types */}
<Route path="new/standard" component={ NewsletterTypeStandard } />
<Route path="new/welcome" component={ NewsletterTypeWelcome } />
<Route path="new/notification" component={ NewsletterTypeNotification } />
{/* Template selection */}
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
{/* Sending options */}
<Route path="send/:id" component={ NewsletterSend } />
<Route path="filter[:filter]" component={ NewsletterList } />
<Route path="*" component={ NewsletterList } />
</Route>
</Router>
), container);

View File

@ -0,0 +1,82 @@
import _ from 'underscore'
import MailPoet from 'mailpoet'
const timeFormat = window.mailpoet_time_format || 'H:i';
// welcome emails
const _timeDelayValues = {
'immediate': MailPoet.I18n.t('delayImmediately'),
'hours': MailPoet.I18n.t('delayHoursAfter'),
'days': MailPoet.I18n.t('delayDaysAfter'),
'weeks': MailPoet.I18n.t('delayWeeksAfter')
};
const _intervalValues = {
'daily': MailPoet.I18n.t('daily'),
'weekly': MailPoet.I18n.t('weekly'),
'monthly': MailPoet.I18n.t('monthly'),
'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
'immediately': MailPoet.I18n.t('immediately')
};
// notification emails
const SECONDS_IN_DAY = 86400;
const TIME_STEP_SECONDS = 3600;
const numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
const _timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps,function(step) {
return step * TIME_STEP_SECONDS;
}), function(seconds) {
let date = new Date(null);
date.setSeconds(seconds);
const timeLabel = MailPoet.Date.format(date, { format: timeFormat, offset: 0 });
return [seconds, timeLabel];
})
);
const _weekDayValues = {
0: MailPoet.I18n.t('sunday'),
1: MailPoet.I18n.t('monday'),
2: MailPoet.I18n.t('tuesday'),
3: MailPoet.I18n.t('wednesday'),
4: MailPoet.I18n.t('thursday'),
5: MailPoet.I18n.t('friday'),
6: MailPoet.I18n.t('saturday')
};
const NUMBER_OF_DAYS_IN_MONTH = 28;
const _monthDayValues = _.object(
_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) {
return day;
}), function(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, label];
}
)
);
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 };

View File

@ -34,15 +34,20 @@ define(
};
},
getFieldsByNewsletter: function(newsletter) {
var type = this.getSubtype(newsletter);
return type.getFields(newsletter);
},
getSendButtonOptions: function() {
var type = this.getSubtype(this.state.item);
return type.getSendButtonOptions(this.state.item);
},
getSubtype: function(newsletter) {
switch(newsletter.type) {
case 'notification': return NotificationNewsletterFields;
case 'welcome': return WelcomeNewsletterFields;
default: return StandardNewsletterFields;
}
},
isAutomatedNewsletter: function() {
return this.state.item.type !== 'standard';
},
isValid: function() {
return jQuery('#mailpoet_newsletter').parsley().isValid();
},
@ -85,65 +90,110 @@ define(
if(!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).then((response) => {
if (response.result === true) {
return MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: _.extend({}, this.state.item, {
newsletter_id: this.props.params.id,
}),
});
} else {
return response;
}
this._save(e).done(() => {
this.setState({ loading: true });
}).done((response) => {
this.setState({ loading: false });
if(response.result === true) {
this.context.router.push('/');
MailPoet.Notice.success(response.data.message);
} else {
if(response.errors) {
MailPoet.Notice.error(response.errors);
} else {
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterSendingError').replace("%$1s", '?page=mailpoet-settings')
);
}
switch (response.data.type) {
case 'notification':
case 'welcome':
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'setStatus',
data: {
id: this.props.params.id,
status: 'active'
}
}).done((response) => {
// redirect to listing based on newsletter type
this.context.router.push(`/${ this.state.item.type || '' }`);
// display success message depending on newsletter type
if (response.data.type === 'welcome') {
MailPoet.Notice.success(
MailPoet.I18n.t('welcomeEmailActivated')
);
} else if (response.data.type === 'notification') {
MailPoet.Notice.success(
MailPoet.I18n.t('postNotificationActivated')
);
}
}).fail(this._showError);
default:
return MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: {
newsletter_id: this.props.params.id
}
}).done((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')
);
} else {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterBeingSent')
);
}
}).fail(this._showError);
}
}).fail(this._showError).always(() => {
this.setState({ loading: false });
});
}
return false;
},
handleSave: function(e) {
e.preventDefault();
this._save(e).done((response) => {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
}).done(() => {
this.context.router.push(`/${ this.state.item.type || '' }`);
}).fail(this._showError);
},
handleRedirectToDesign: function(e) {
e.preventDefault();
var redirectTo = e.target.href;
this._save(e).done((response) => {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
}).done(() => {
window.location = redirectTo;
}).fail(this._showError);
},
_save: function(e) {
var data = this.state.item;
this.setState({ loading: true });
MailPoet.Ajax.post({
// Ensure that body is JSON encoded
if (!_.isUndefined(data.body)) {
data.body = JSON.stringify(data.body);
}
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).done((response) => {
data: data,
}).always(() => {
this.setState({ loading: false });
if(response.result === true) {
this.context.router.push('/');
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
} else {
if(response.errors) {
MailPoet.Notice.error(response.errors);
}
}
});
},
_showError: (response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
},
handleFormChange: function(e) {
var item = this.state.item,
field = e.target.name;
@ -175,10 +225,9 @@ define(
className="button button-primary"
type="button"
onClick={ this.handleSend }
value={
this.isAutomatedNewsletter()
? MailPoet.I18n.t('activate')
: MailPoet.I18n.t('send')} />
value={MailPoet.I18n.t('send')}
{...this.getSendButtonOptions()}
/>
&nbsp;
<input
className="button button-secondary"
@ -188,7 +237,8 @@ define(
<a
href={
'?page=mailpoet-newsletter-editor&id='+this.props.params.id
}>
}
onClick={this.handleRedirectToDesign}>
{MailPoet.I18n.t('goBackToDesign')}
</a>.
</p>

View File

@ -23,7 +23,7 @@ define(
},
{
name: 'options',
label: MailPoet.I18n.t('selectPeriodicity'),
label: MailPoet.I18n.t('selectFrequency'),
type: 'reactComponent',
component: Scheduling,
},
@ -95,6 +95,15 @@ define(
}
];
return fields;
return {
getFields: function(newsletter) {
return fields;
},
getSendButtonOptions: function(newsletter) {
return {
value: MailPoet.I18n.t('activate')
};
},
};
}
);

View File

@ -253,7 +253,13 @@ define(
var StandardScheduling = React.createClass({
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
return _.defaults(
this.props.item[this.props.field.name] || {},
{
isScheduled: '0',
scheduledAt: defaultDateTime
}
);
},
handleValueChange: function(event) {
var oldValue = this._getCurrentValue(),
@ -290,7 +296,7 @@ define(
<DateTime
name="scheduledAt"
value={this._getCurrentValue().scheduledAt}
onChange={this.handleValueChange}
onChange={this.handleValueChange}
dateValidation={this.getDateValidation()} />
&nbsp;
<span>
@ -401,6 +407,30 @@ define(
}
];
return fields;
return {
getFields: function(newsletter) {
return fields;
},
getSendButtonOptions: function(newsletter) {
newsletter = newsletter || {};
let isScheduled = (
typeof newsletter.options === 'object'
&& newsletter.options.isScheduled === '1'
);
let options = {
value: (isScheduled
? MailPoet.I18n.t('schedule')
: MailPoet.I18n.t('send'))
};
if (newsletter.status === 'sent'
|| newsletter.status === 'sending') {
options['disabled'] = 'disabled';
}
return options;
},
};
}
);

View File

@ -71,7 +71,16 @@ define(
}
];
return fields;
return {
getFields: function(newsletter) {
return fields;
},
getSendButtonOptions: function(newsletter) {
return {
value: MailPoet.I18n.t('activate')
};
},
};
}
);

View File

@ -30,33 +30,35 @@ define(
endpoint: 'newsletterTemplates',
action: 'save',
data: template
}).done(function(response) {
}).always(function() {
MailPoet.Modal.loading(false);
if(response.result === true) {
this.props.onImport(template);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
}).done((response) => {
this.props.onImport(response.data);
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
}.bind(this));
});
},
handleSubmit: function(e) {
e.preventDefault();
if (_.size(this.refs.templateFile.files) <= 0) return false;
var file = _.first(this.refs.templateFile.files),
reader = new FileReader(),
saveTemplate = this.saveTemplate;
var file = _.first(this.refs.templateFile.files);
var reader = new FileReader();
var saveTemplate = this.saveTemplate;
reader.onload = function(e) {
reader.onload = (e) => {
try {
saveTemplate(JSON.parse(e.target.result));
} catch (err) {
MailPoet.Notice.error(MailPoet.I18n.t('templateFileMalformedError'));
}
}.bind(this);
};
reader.readAsText(file);
},
@ -97,12 +99,12 @@ define(
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'getAll',
}).done(function(response) {
}).always(() => {
MailPoet.Modal.loading(false);
if(this.isMounted()) {
if(response.length === 0) {
response = [
}).done((response) => {
if (this.isMounted()) {
if (response.data.length === 0) {
response.data = [
{
name:
MailPoet.I18n.t('mailpoetGuideTemplateTitle'),
@ -110,14 +112,14 @@ define(
MailPoet.I18n.t('mailpoetGuideTemplateDescription'),
readonly: "1"
}
]
];
}
this.setState({
templates: response,
templates: response.data,
loading: false
});
}
}.bind(this));
});
},
handleSelectTemplate: function(template) {
var body = template.body;
@ -134,19 +136,17 @@ define(
id: this.props.params.id,
body: body
}
}).done(function(response) {
if(response.result === true) {
// TODO: Move this URL elsewhere
window.location = 'admin.php?page=mailpoet-newsletter-editor&id=' + this.props.params.id;
} else {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}).done((response) => {
// TODO: Move this URL elsewhere
window.location = 'admin.php?page=mailpoet-newsletter-editor&id=' + response.data.id;
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
}.bind(this));
},
handlePreviewTemplate: function(template) {
console.log('preview template #'+template.id);
});
},
handleDeleteTemplate: function(template) {
this.setState({ loading: true });
@ -160,10 +160,12 @@ define(
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'delete',
data: template.id
}).done(function(response) {
data: {
id: template.id
}
}).done((response) => {
this.getTemplates();
}.bind(this));
});
} else {
this.setState({ loading: false });
}
@ -172,7 +174,7 @@ define(
MailPoet.Modal.popup({
title: template.name,
template: '<div class="mailpoet_boxes_preview" style="background-color: {{ body.globalStyles.body.backgroundColor }}"><img src="{{ thumbnail }}" /></div>',
data: template,
data: template
});
},
handleTemplateImport: function() {
@ -213,20 +215,19 @@ define(
</div>
<div className="mailpoet_actions">
<a
className="button button-secondary"
onClick={ this.handleShowTemplate.bind(null, template) }
>
{MailPoet.I18n.t('preview')}
</a>
&nbsp;
<a
className="button button-primary"
onClick={ this.handleSelectTemplate.bind(null, template) }
>
{MailPoet.I18n.t('select')}
</a>
&nbsp;
<a
style={ { display: 'none' }}
className="button button-secondary"
onClick={ this.handlePreviewTemplate.bind(null, template) }
>
{MailPoet.I18n.t('preview')}
</a>
</div>
{ (template.readonly === "1") ? false : deleteLink }
</li>

View File

@ -18,7 +18,6 @@ define(
var field = {
name: 'options',
label: 'Periodicity',
type: 'reactComponent',
component: Scheduling,
};
@ -72,7 +71,7 @@ define(
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectPeriodicity')}</h3>
<h3>{MailPoet.I18n.t('selectFrequency')}</h3>
<Scheduling
item={this.state}

View File

@ -1,200 +1,144 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'form/fields/select.jsx'
],
function(
_,
React,
Router,
MailPoet,
Select
) {
import _ from 'underscore'
import React from 'react'
import MailPoet from 'mailpoet'
import Select from 'form/fields/select.jsx'
import {
intervalValues,
timeOfDayValues,
weekDayValues,
monthDayValues,
nthWeekDayValues
} from 'newsletters/scheduling/common.jsx'
var intervalField = {
name: 'intervalType',
values: {
'daily': MailPoet.I18n.t('daily'),
'weekly': MailPoet.I18n.t('weekly'),
'monthly': MailPoet.I18n.t('monthly'),
'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
'immediately': MailPoet.I18n.t('immediately'),
},
};
const intervalField = {
name: 'intervalType',
values: intervalValues
};
var SECONDS_IN_DAY = 86400;
var TIME_STEP_SECONDS = 3600; // Default: 3600
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
var timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
function(seconds) {
var date = new Date(null);
date.setSeconds(seconds);
var timeLabel = date.toISOString().substr(11, 5);
return [seconds, timeLabel];
const timeOfDayField = {
name: 'timeOfDay',
values: timeOfDayValues
};
const weekDayField = {
name: 'weekDay',
values: weekDayValues
};
const monthDayField = {
name: 'monthDay',
values: monthDayValues
};
const nthWeekDayField = {
name: 'nthWeekDay',
values: nthWeekDayValues
};
const NotificationScheduling = React.createClass({
_getCurrentValue: function() {
return (this.props.item[this.props.field.name] || {});
},
handleValueChange: function(name, value) {
const oldValue = this._getCurrentValue();
let newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
));
var timeOfDayField = {
name: 'timeOfDay',
values: timeOfDayValues,
};
var weekDayField = {
name: 'weekDay',
values: {
0: MailPoet.I18n.t('sunday'),
1: MailPoet.I18n.t('monday'),
2: MailPoet.I18n.t('tuesday'),
3: MailPoet.I18n.t('wednesday'),
4: MailPoet.I18n.t('thursday'),
5: MailPoet.I18n.t('friday'),
6: MailPoet.I18n.t('saturday')
},
};
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
var monthDayField = {
name: 'monthDay',
values: _.object(_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
function(day) {
var labels = {
0: MailPoet.I18n.t('first'),
1: MailPoet.I18n.t('second'),
2: MailPoet.I18n.t('third')
},
label;
if (labels[day] !== undefined) {
label = labels[day];
} else {
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
}
return [day, label];
},
)),
};
var nthWeekDayField = {
name: 'nthWeekDay',
values: {
'1': MailPoet.I18n.t('first'),
'2': MailPoet.I18n.t('second'),
'3': MailPoet.I18n.t('third'),
'L': MailPoet.I18n.t('last'),
},
};
var NotificationScheduling = React.createClass({
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
},
handleValueChange: function(name, value) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleIntervalChange: function(event) {
return this.handleValueChange(
'intervalType',
event.target.value
);
},
handleTimeOfDayChange: function(event) {
return this.handleValueChange(
'timeOfDay',
event.target.value
);
},
handleWeekDayChange: function(event) {
return this.handleValueChange(
'weekDay',
event.target.value
);
},
handleMonthDayChange: function(event) {
return this.handleValueChange(
'monthDay',
event.target.value
);
},
handleNthWeekDayChange: function(event) {
return this.handleValueChange(
'nthWeekDay',
event.target.value
);
},
render: function() {
var value = this._getCurrentValue(),
timeOfDaySelection,
weekDaySelection,
monthDaySelection,
nthWeekDaySelection;
if (value.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this._getCurrentValue()}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (value.intervalType === 'weekly'
|| value.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this._getCurrentValue()}
onValueChange={this.handleWeekDayChange} />
);
}
if (value.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this._getCurrentValue()}
onValueChange={this.handleMonthDayChange} />
);
}
if (value.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this._getCurrentValue()}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<Select
field={intervalField}
item={this._getCurrentValue()}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
</div>
);
},
});
},
handleIntervalChange: function(event) {
return this.handleValueChange(
'intervalType',
event.target.value
);
},
handleTimeOfDayChange: function(event) {
return this.handleValueChange(
'timeOfDay',
event.target.value
);
},
handleWeekDayChange: function(event) {
return this.handleValueChange(
'weekDay',
event.target.value
);
},
handleMonthDayChange: function(event) {
return this.handleValueChange(
'monthDay',
event.target.value
);
},
handleNthWeekDayChange: function(event) {
return this.handleValueChange(
'nthWeekDay',
event.target.value
);
},
render: function() {
const value = this._getCurrentValue();
let timeOfDaySelection;
let weekDaySelection;
let monthDaySelection;
let nthWeekDaySelection;
return NotificationScheduling;
if (value.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this._getCurrentValue()}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (value.intervalType === 'weekly' || value.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this._getCurrentValue()}
onValueChange={this.handleWeekDayChange} />
);
}
if (value.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this._getCurrentValue()}
onValueChange={this.handleMonthDayChange} />
);
}
if (value.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this._getCurrentValue()}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<Select
field={intervalField}
item={this._getCurrentValue()}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
</div>
);
}
);
});
module.exports = NotificationScheduling;

View File

@ -1,190 +1,180 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'form/fields/select.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Select,
Text,
Breadcrumb
) {
import _ from 'underscore'
import React from 'react'
import MailPoet from 'mailpoet'
import Select from 'form/fields/select.jsx'
import Text from 'form/fields/text.jsx'
import {
timeDelayValues,
intervalValues
} from 'newsletters/scheduling/common.jsx'
var availableRoles = window.mailpoet_roles || {};
var availableSegments = window.mailpoet_segments || {};
var events = {
name: 'event',
values: {
'segment': MailPoet.I18n.t('onSubscriptionToList'),
'user': MailPoet.I18n.t('onWordpressUserRegistration'),
}
};
var availableSegmentValues = _.object(_.map(
availableSegments,
function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
}
return [segment.id, name];
}
));
var segmentField = {
name: 'segment',
values: availableSegmentValues,
};
var roleField = {
name: 'role',
values: availableRoles,
};
var afterTimeNumberField = {
name: 'afterTimeNumber',
size: 3,
};
var afterTimeTypeField = {
name: 'afterTimeType',
values: {
'immediate': MailPoet.I18n.t('delayImmediately'),
'hours': MailPoet.I18n.t('delayHoursAfter'),
'days': MailPoet.I18n.t('delayDaysAfter'),
'weeks': MailPoet.I18n.t('delayWeeksAfter'),
}
};
var WelcomeScheduling = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
},
handleValueChange: function(name, value) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleEventChange: function(event) {
return this.handleValueChange(
'event',
event.target.value
);
},
handleSegmentChange: function(event) {
return this.handleValueChange(
'segment',
event.target.value
);
},
handleRoleChange: function(event) {
return this.handleValueChange(
'role',
event.target.value
);
},
handleAfterTimeNumberChange: function(event) {
return this.handleValueChange(
'afterTimeNumber',
event.target.value
);
},
handleAfterTimeTypeChange: function(event) {
return this.handleValueChange(
'afterTimeType',
event.target.value
);
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: {
type: 'welcome',
options: this.state,
},
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.context.router.push(`/template/${newsletterId}`);
},
render: function() {
var value = this._getCurrentValue(),
roleSegmentSelection, timeNumber;
if (value.event === 'user') {
roleSegmentSelection = (
<Select
field={roleField}
item={this._getCurrentValue()}
onValueChange={this.handleRoleChange} />
);
} else {
roleSegmentSelection = (
<Select
field={segmentField}
item={this._getCurrentValue()}
onValueChange={this.handleSegmentChange} />
);
}
if (value.afterTimeType !== 'immediate') {
timeNumber = (
<Text
field={afterTimeNumberField}
item={this._getCurrentValue()}
onValueChange={this.handleAfterTimeNumberChange} />
);
}
return (
<div>
<Select
field={events}
item={this._getCurrentValue()}
onValueChange={this.handleEventChange} />
{roleSegmentSelection}
{timeNumber}
<Select
field={afterTimeTypeField}
item={this._getCurrentValue()}
onValueChange={this.handleAfterTimeTypeChange}/>
</div>
);
},
});
return WelcomeScheduling;
const availableRoles = window.mailpoet_roles || {};
const availableSegments = _.filter(
window.mailpoet_segments || [],
function (segment) {
return segment.type === 'default';
}
);
const events = {
name: 'event',
values: {
'segment': MailPoet.I18n.t('onSubscriptionToList'),
'user': MailPoet.I18n.t('onWPUserRegistration'),
}
};
const availableSegmentValues = _.object(_.map(
availableSegments,
function(segment) {
let name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
}
return [segment.id, name];
}
));
const segmentField = {
name: 'segment',
values: availableSegmentValues,
sortBy: (key, value) => value.toLowerCase()
};
const roleField = {
name: 'role',
values: availableRoles
};
const afterTimeNumberField = {
name: 'afterTimeNumber',
size: 3
};
const afterTimeTypeField = {
name: 'afterTimeType',
values: timeDelayValues
};
const WelcomeScheduling = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
_getCurrentValue: function() {
return (this.props.item[this.props.field.name] || {});
},
handleValueChange: function(name, value) {
const oldValue = this._getCurrentValue();
let newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleEventChange: function(event) {
return this.handleValueChange(
'event',
event.target.value
);
},
handleSegmentChange: function(event) {
return this.handleValueChange(
'segment',
event.target.value
);
},
handleRoleChange: function(event) {
return this.handleValueChange(
'role',
event.target.value
);
},
handleAfterTimeNumberChange: function(event) {
return this.handleValueChange(
'afterTimeNumber',
event.target.value
);
},
handleAfterTimeTypeChange: function(event) {
return this.handleValueChange(
'afterTimeType',
event.target.value
);
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: {
type: 'welcome',
options: this.state
}
}).done(function(response) {
if (response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if (response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.context.router.push(`/template/${ newsletterId }`);
},
render: function() {
const value = this._getCurrentValue();
let roleSegmentSelection;
let timeNumber;
if (value.event === 'user') {
roleSegmentSelection = (
<Select
field={ roleField }
item={ this._getCurrentValue() }
onValueChange={ this.handleRoleChange } />
);
} else {
roleSegmentSelection = (
<Select
field={ segmentField }
item={ this._getCurrentValue() }
onValueChange={ this.handleSegmentChange } />
);
}
if (value.afterTimeType !== 'immediate') {
timeNumber = (
<Text
field={ afterTimeNumberField }
item={ this._getCurrentValue() }
onValueChange={ this.handleAfterTimeNumberChange } />
);
}
return (
<div>
<Select
field={ events }
item={ this._getCurrentValue() }
onValueChange={ this.handleEventChange } />
{ roleSegmentSelection }
{ timeNumber }
<Select
field={ afterTimeTypeField }
item={ this._getCurrentValue() }
onValueChange={ this.handleAfterTimeTypeChange } />
</div>
);
},
});
module.exports = WelcomeScheduling;

View File

@ -1,6 +1,5 @@
import React from 'react'
import { Router, Link } from 'react-router'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
@ -15,23 +14,19 @@ var columns = [
},
{
name: 'description',
label: MailPoet.I18n.t('description'),
sortable: false
label: MailPoet.I18n.t('description')
},
{
name: 'subscribed',
label: MailPoet.I18n.t('subscribed'),
sortable: false
label: MailPoet.I18n.t('subscribed')
},
{
name: 'unconfirmed',
label: MailPoet.I18n.t('unconfirmed'),
sortable: false
label: MailPoet.I18n.t('unconfirmed')
},
{
name: 'unsubscribed',
label: MailPoet.I18n.t('unsubscribed'),
sortable: false
label: MailPoet.I18n.t('unsubscribed')
},
{
name: 'created_at',
@ -191,13 +186,20 @@ const SegmentList = React.createClass({
const unconfirmed = ~~(segment.subscribers_count.unconfirmed || 0);
const unsubscribed = ~~(segment.subscribers_count.unsubscribed || 0);
let segment_name = (
<Link to={ `/edit/${segment.id}` }>{ segment.name }</Link>
);
let segment_name;
// the WP users segment is not editable so just display its name
if (segment.type === 'wp_users') {
segment_name = segment.name;
// the WP users segment is not editable so just display its name
segment_name = (
<span className="row-title">{ segment.name }</span>
);
} else {
segment_name = (
<Link
className="row-title"
to={ `/edit/${segment.id}` }
>{ segment.name }</Link>
);
}
return (
@ -244,6 +246,8 @@ const SegmentList = React.createClass({
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
sort_by="name"
sort_order="asc"
/>
</div>
);

View File

@ -2,6 +2,7 @@ import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import SegmentList from 'segments/list.jsx'
import SegmentForm from 'segments/form.jsx'

View File

@ -34,16 +34,13 @@ define(
// show sending methods
jQuery('.mailpoet_sending_methods').fadeIn();
} else {
// toggle SPF/DKIM (hidden if the sending method is MailPoet)
// toggle SPF (hidden if the sending method is MailPoet)
jQuery('#mailpoet_mta_spf')[
(group === 'mailpoet')
? 'hide'
: 'show'
]();
// (HIDDEN FOR BETA)
jQuery('#mailpoet_mta_dkim').hide();
// hide sending methods
jQuery('.mailpoet_sending_methods').hide();

View File

@ -73,7 +73,7 @@ define(
});
},
filter: function(segment) {
return !!(!segment.deleted_at);
return !!(!segment.deleted_at && segment.type === 'default');
},
getSearchLabel: function(segment, subscriber) {
let label = '';

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