Compare commits

...

285 Commits

Author SHA1 Message Date
ffc1d0a61c Set MailPoet version to 0.0.10 for release 2016-01-08 19:38:57 +02:00
d1b160def7 Merge pull request #286 from mailpoet/cron_update
Cron update
2016-01-08 19:23:50 +02:00
493fd01754 Merge pull request #285 from mailpoet/import_export_fix
Import export fix
2016-01-08 19:21:56 +02:00
9ced4b1757 Fix export test after temp URL changed 2016-01-08 19:21:32 +02:00
17010e5ba9 Merge pull request #281 from mailpoet/rendering_engine_update
Rendering engine update
2016-01-08 19:00:12 +02:00
42ad7584d4 - Refactors ColumnsHelper class 2016-01-08 11:35:30 -05:00
dbc0f9b238 - Removes header padding 2016-01-08 11:23:02 -05:00
e62e9a5892 - Fixes issue with temp folder
- Updates formatting
2016-01-08 10:55:09 -05:00
bc25fa61b4 - Updates render method
- Removes unused/commented out code
2016-01-08 10:38:46 -05:00
2590967183 - Formats new line identation
- Formats identations in general
2016-01-08 09:00:09 -05:00
86eafd3c17 - Removes tabs 2016-01-08 08:47:41 -05:00
90a6f160c2 - Removes space after function 2016-01-08 07:53:26 -05:00
c774aec6a2 Revert "- Fixes minor issue when daemon has not yet been created"
This reverts commit 8f2fd1d76e.
2016-01-08 07:49:15 -05:00
8f2fd1d76e - Fixes minor issue when daemon has not yet been created 2016-01-08 07:40:32 -05:00
4df11163a1 automatically update 'updated_at' when saving daemon 2016-01-08 12:23:15 +01:00
82a736ffbb Cron update + removing console.log
- use Setting::getValue for getDaemon method
- added "updated_at" property within cron_daemon value (instead of using the setting's column)
- converted line endings to Unix in notice.js and removed console.log
2016-01-08 12:02:11 +01:00
87052986e8 - Rebases master
- Updates template
2016-01-07 23:53:15 -05:00
0c73c0fadc - Resolves issues identified by @rafaehlers during testing 2016-01-07 22:47:59 -05:00
5c7e11076d - WIP on updating import 2016-01-07 18:11:59 -05:00
d1df94c759 - Addresses issues identified during code review 2016-01-07 17:24:46 -05:00
53cc39c6f5 - Removes spacer from social icon elements 2016-01-07 17:24:45 -05:00
4955c72ee1 - Removes transparent background from divider element 2016-01-07 17:24:44 -05:00
16661af8c3 - Updates text alignemnt for buttons 2016-01-07 17:24:44 -05:00
bc80f69e41 - Updates the template 2016-01-07 17:24:43 -05:00
0192934e65 - Removes debug leftovers 2016-01-07 17:24:42 -05:00
2793e74858 - Rewrites the rendering engine
- Updates tests
Closes #280
2016-01-07 17:24:32 -05:00
5996696cc9 Merge pull request #284 from mailpoet/animations
Animations
2016-01-07 17:31:17 +01:00
7f6cf5bbf3 Remove obsolete clear divs 2016-01-07 18:28:37 +02:00
c0ef2254cd Merge pull request #283 from mailpoet/queue_refactor
Queue refactoring
2016-01-07 13:11:47 +01:00
0dbe04c3f8 - Addresses issues identified during code review 2016-01-06 19:19:06 -05:00
ef1805d9b5 Change obsolete "Arial Black" fonts to "Arial", add Velocity to tests 2016-01-06 16:43:39 +02:00
514f539e83 Remove obsolete Velocity dependence due to it being injected elsewhere 2016-01-06 15:58:14 +02:00
50f072705e Fix double animations when ALC block changes, remove console.log lines 2016-01-06 12:48:45 +02:00
f8f7bc3d3d Handle sidebar animations with Velocity, fix delete button transitions 2016-01-06 12:29:32 +02:00
f1bf2bb097 - Refactors Mailer class
- Refactors SendingQueue worker class
- Adds Maier router with a send() method + ability to specify sending method
- Updates tests
- Introduces 'stopping' and 'starting' cron states
- Improves cron control mechanism
Closes #276
2016-01-05 10:34:57 -05:00
bbe2f69a7f Clean up unused and speed up animations, fix sidebar transitions 2016-01-05 17:32:59 +02:00
c844488b0b Switch to VelocityJS for view transitions, slow down some transitions 2016-01-05 15:01:30 +02:00
112fe0cd6e Merge pull request #282 from mailpoet/sticky_kit
Fix sticky-kit dependency usage
2016-01-04 16:25:59 +01:00
c9e6dce785 Rename vendor_static/ to vendor/ 2016-01-04 17:02:46 +02:00
d1c09c015a Remove remote sticky-kit dep, use static local patched package instead 2016-01-04 13:19:40 +02:00
8d61866b77 Merge pull request #278 from mailpoet/drag_and_drop
Editor: Drag&Drop
2015-12-18 14:54:30 +01:00
0831c748b1 Merge pull request #275 from mailpoet/newsletter_template
Newsletter template preview
2015-12-18 14:54:21 +01:00
b2a0bc3860 Merge pull request #273 from mailpoet/review
Global Code Review.
2015-12-18 14:44:53 +01:00
1f99345e7b Remove obsolete code 2015-12-17 18:03:16 +02:00
89782bc94b Refactor editor JS, change blocks to extend base methods 2015-12-17 16:58:02 +02:00
155ff09280 Fix incorrect insertion order for containers 2015-12-17 15:21:56 +02:00
132e6d3342 Rename Wordpress component to Communication component, fix preview JS
syntax
2015-12-17 12:40:26 +02:00
f02699158f Apply newsletter background color to template preview images 2015-12-17 12:40:26 +02:00
be3462925d Change server returned newsletter body to be a json object, not a string 2015-12-17 12:40:26 +02:00
3d82230d10 Merge pull request #274 from mailpoet/acceptance_tests_update
Acceptance test related fixes
2015-12-15 13:21:34 +01:00
c20c46fd86 Removed useless 'use' in Router/Setup 2015-12-15 13:11:07 +01:00
6e63c72aa5 Reinstall feature
- implemented reinstall in Settings > Advanced
- shorten placeholder for Form name input
2015-12-15 13:07:43 +01:00
e059eec5ea Acceptance tests have been removed from the PHP project. 2015-12-14 16:09:20 +01:00
a2d38c9076 Merge pull request #270 from mailpoet/unit_tests_fix
Unit tests + Welcome page
2015-12-14 15:48:52 +01:00
8d507b2ee0 Merge pull request #271 from mailpoet/history_fix
Fix react-router dependency issue
2015-12-14 15:31:02 +01:00
5e2979c283 Changes history version to 1.13.1 2015-12-14 14:29:51 +02:00
84ec0de3cd Unit tests + Welcome page
- fixed unit tests
- commented out failing tests that require changes in the code
- added new welcome page
2015-12-11 17:17:59 +01:00
ee07780833 Version bump: 0.0.8. 2015-12-11 14:42:59 +01:00
ed104156a9 New build script as new plugin, independent from wysija-newsletters. 2015-12-11 14:42:15 +01:00
90f2e9ff9d Merge branch 'fix_history' 2015-12-11 14:13:15 +01:00
98fb7aa65e Fix react history pushState error. 2015-12-11 14:12:35 +01:00
a51eb59cf8 Merge pull request #266 from mailpoet/newsletter_template
Editor: Template related fixes
2015-12-11 13:09:54 +01:00
270023b89c Display error and success messages for template saving and export 2015-12-10 17:13:23 +02:00
7d77e075e9 Make long template name and description fit in the box 2015-12-10 17:13:23 +02:00
9bbe36b3cb Set subject of new newsletters to "Draft newsletter" 2015-12-10 17:13:23 +02:00
7a904ed093 Add SVG icons for standard, welcome and notification email types 2015-12-10 17:13:23 +02:00
e3c065b353 Add Post Notifications Blank Template 2015-12-10 17:13:22 +02:00
4ca2872e0e Add Welcome newsletter template 2015-12-10 17:13:22 +02:00
ce338f7fe2 Add border to template thumbnails 2015-12-10 17:13:22 +02:00
fa28b0a955 Merge pull request #267 from mailpoet/settings_round_1
Settings
2015-12-10 13:04:37 +01:00
a298650187 Settings
- added default from name & address based on wp_user on install
- fixed issue with Setting::setValue (added auto-serialize of value if is_array?)
- removed daily notifications from basics settings
2015-12-10 11:44:44 +01:00
95772ef68a Merge pull request #261 from mailpoet/form_editor_round_1
Form editor round 1
2015-12-09 14:07:33 +01:00
e1c94db516 edit form name follows newsletter's design 2015-12-08 17:10:45 +01:00
7be1a11d1e Form editor
- fixed validations on radio type
- fixed date format for months
- added custom fields storing on subscribe
- fixed date widget (select today's date)
- fixed validation on form widget
2015-12-08 16:55:30 +01:00
c04b95c09a Merge pull request #262 from mailpoet/editor_transitions
Editor: fix transitions and thumbnails
2015-12-07 20:00:07 +01:00
cdfeb8d512 Fix template thumbnails by reverting to earlier html2canvas version 2015-12-07 18:50:50 +02:00
3ef8067968 Remove transition class after it ends for editor content blocks 2015-12-07 18:50:17 +02:00
9fb04bc3c0 first round of fixes #255 2015-12-07 16:54:08 +01:00
25727ea0ba Version bump: 0.0.7 2015-12-07 14:43:16 +01:00
4c8ac369b7 Merge pull request #260 from mailpoet/newsletter_sending
Bugfixes on sending
2015-12-07 14:35:38 +01:00
268dabdc9f Bugfixes on sending
- added checks to prevent adding to the queue useless items
- fixed issue with mta settings (duplicated "host" input / renamed duplicate to "domain" for MailGun)
- fixed namespace issue on cron daemon/supervisor
- fixed typo in migration preventing the newsletter_templates table to be created.
- partially fixed cron.jsx
2015-12-07 13:29:42 +01:00
c0ba218949 Merge branch 'master' of github.com:mailpoet/mailpoet 2015-12-04 21:41:29 +01:00
ee85139089 Merge pull request #254 from mailpoet/queue
Sending Queue: start & track
2015-12-04 21:36:50 +01:00
b4f83fe1bd Merge pull request #257 from mailpoet/image_size
Custom newsletter image size
2015-12-04 21:22:15 +01:00
bd83001ef5 Merge pull request #258 from mailpoet/helpscout
HelpScout integration fixes
2015-12-04 21:20:28 +01:00
fb3a9f485f Merge pull request #259 from mailpoet/custom_fields
Add custom fields to newsletter editor
2015-12-04 21:20:09 +01:00
fd44776ae9 - Fixes SMTP issue 2015-12-04 14:33:43 -05:00
6dbe338b01 - Updates mailers to use HTML and/or TEXT body 2015-12-04 14:06:43 -05:00
1f06a7dd0b fixed naming of sending_queue & added validation of segments on step 3 2015-12-04 19:54:31 +01:00
37c218f782 fixed sending test email text version only 2015-12-04 19:46:44 +01:00
25016f2a8d test sending method works 2015-12-04 19:28:00 +01:00
792744a270 - Implements check for name/email in mailer router 2015-12-04 12:47:14 -05:00
c5fbfca132 Merge branch 'queue' of mailpoet:mailpoet/mailpoet into queue 2015-12-04 12:32:38 -05:00
c2fde308cb - Renames and updates sending queue worker
- Updates mailer router's send() method
2015-12-04 12:31:54 -05:00
533d9b0d38 Change custom field shortcode to follow MP2 format 2015-12-04 17:48:35 +02:00
2035b802e3 Add shortcodes for custom fields to newsletter editor 2015-12-04 17:47:18 +02:00
a5e66ec6a0 implemented sending test email 2015-12-04 16:20:07 +01:00
beb939df9e updated send with tab 2015-12-04 15:31:04 +01:00
44e342c692 Fix z-index, change icon, add instructional message 2015-12-04 15:30:59 +02:00
3a417d460f Add custom MailPoet image size for newsletters 2015-12-04 14:37:05 +02:00
1950d6661f Step 3 sender and reply to per newsletter
- added sender_address/sender_name/reply_to_address/reply_to_name
- added validation on all form fields (except checkbox and radio)
2015-12-04 12:29:11 +01:00
da6e154642 fixed conflicts 2015-12-04 11:25:06 +01:00
acebf669a7 - Updates queue worker to use mailer router for sending
- Updates mailer router to detect method type
- Rebases master
2015-12-03 22:01:33 -05:00
4a2bbe3f88 Sending Progress
- improved progress bar styles (with completed status)
- add pause/resume buttons
- fixed method case in settings.mta for MailPoet & SMTP Providers
- fixed parsley dependency
- added validation on from name & address on step 3
2015-12-03 22:01:19 -05:00
9b011c0281 - Places supervisor/daemon/worker under the new Cron class
- Updates endpoints
- Integrates queue worker with MailPoet mailer
- Fixes script activation check logic
2015-12-03 22:01:18 -05:00
bf58d8a22d Queue
- updated menu icon for our plugin
- added watchCss command to watch only CSS files
- added Status column in Newsletters listing
- added progress bar styles
- fixed issue with JS assets being loaded twice on non MP pages
- changed subscriber_ids to segment_ids in addQueue
2015-12-03 22:01:17 -05:00
72d1eb79a6 fixed too much recursion issue on selection JSX 2015-12-03 22:01:17 -05:00
bb4893c0a0 added missing messages on newsletter form 2015-12-03 22:01:16 -05:00
9929cf0aee - Adds router methods to add/delete/et queue status 2015-12-03 22:01:15 -05:00
83967e84ba - Implements starting/stopping/pausing daemon 2015-12-03 22:01:15 -05:00
9621cb3ca9 Merge pull request #253 from mailpoet/animations
Editor: Animations
2015-12-03 22:48:43 +01:00
a413f666fe Sending Progress
- improved progress bar styles (with completed status)
- add pause/resume buttons
- fixed method case in settings.mta for MailPoet & SMTP Providers
- fixed parsley dependency
- added validation on from name & address on step 3
2015-12-03 18:26:36 +01:00
d1e2c6c074 Add transition for "Delete block" icon 2015-12-03 15:53:56 +02:00
3b6a9f7a6e Add block, tool and widget transition animations 2015-12-03 14:44:32 +02:00
d2e5fb89c2 - Places supervisor/daemon/worker under the new Cron class
- Updates endpoints
- Integrates queue worker with MailPoet mailer
- Fixes script activation check logic
2015-12-02 22:48:15 -05:00
97d1e95037 Add transitions for content block addition and removal 2015-12-02 17:54:06 +02:00
48fbce22e7 Queue
- updated menu icon for our plugin
- added watchCss command to watch only CSS files
- added Status column in Newsletters listing
- added progress bar styles
- fixed issue with JS assets being loaded twice on non MP pages
- changed subscriber_ids to segment_ids in addQueue
2015-12-02 12:25:28 +01:00
916fe76795 Add transitions: sidebar contents, template preview, sidebar 2015-12-01 16:49:50 +02:00
e10310fb5c fixed too much recursion issue on selection JSX 2015-12-01 13:50:35 +01:00
367afcf814 added missing messages on newsletter form 2015-12-01 13:01:06 +01:00
67fa9e0993 - Adds router methods to add/delete/et queue status 2015-11-30 19:09:06 -05:00
d7553a5f27 Merge pull request #251 from mailpoet/newsletter_templates
Editor: Add two sample templates
2015-11-30 21:13:49 +01:00
8c847825fa - Implements starting/stopping/pausing daemon 2015-11-30 13:01:07 -05:00
d21d9b99b0 Add an option for certain templates to be read only 2015-11-30 18:23:43 +02:00
8461c13532 Include thumbnail on saved templates, add another sample template 2015-11-30 18:05:10 +02:00
3b7ffe9ba7 Update Marionette to 2.4.4 from 2.4.3 2015-11-30 13:21:28 +02:00
1724fa22c1 Merge pull request #250 from mailpoet/default_data_on_install
Default segments on install
2015-11-30 12:17:32 +01:00
01089d7a72 Bump version to 0.0.6 2015-11-27 22:00:54 +01:00
717ebfd20c Bump composer lockfile. 2015-11-27 22:00:17 +01:00
daff3d5016 Merge pull request #249 from mailpoet/sticky_kit_fix
Add back sticky-kit dependency
2015-11-27 19:14:03 +01:00
98fb838169 updated default lists' descriptions 2015-11-27 15:56:18 +01:00
f13a4dd4f3 Add back sticky-kit dependency 2015-11-27 16:44:04 +02:00
62a164e4c6 added creation of WP Users & default list on install 2015-11-27 15:27:50 +01:00
04238f3339 Phoenix Vagrant VM has been replaced with a new VM with test data. 2015-11-27 15:16:06 +01:00
bc62474f3c Merge pull request #240 from mailpoet/queue
Queue
2015-11-27 13:49:18 +01:00
98005a2a6f - Rebases master 2015-11-27 07:46:26 -05:00
b7fe8dc6d6 - Adds Daemon status (under Mailpoet->Queue) 2015-11-27 07:40:23 -05:00
436faea591 - Refactors and renames code
- Adds Queue menu option and displays status
2015-11-27 07:40:22 -05:00
4208b148b4 - Implements queue worker class 2015-11-27 07:35:16 -05:00
5ce5f0bf8a - Refactors code 2015-11-27 07:35:16 -05:00
18518de397 - Refactors some code 2015-11-27 07:35:15 -05:00
68f747211a - Adds a helper method to convert cameCase to under_score
- Removes unused method from Queue daemon
2015-11-27 07:35:15 -05:00
ebd26ddd98 - Silences fsockopen errors 2015-11-27 07:35:14 -05:00
924aa0439f - Minor regex changes
- Adds method to Env class that returns plugin activation status
- Prevents Supervisor from running if the plugin isn't activated
2015-11-27 07:35:13 -05:00
3124d6a61b - Fixes issue with ucwords() function on older PHP versions
- Updates Supervisor/Daemon to strip port from site_url() when it's
  running on localhost inside a virtual machine :)
2015-11-27 07:35:13 -05:00
149d031b52 - Adds session support
- Renames Queue to Daemon
- Updates router methods
2015-11-27 07:35:12 -05:00
fa96c4697d - Adds Queue router
- Updates logic for Queue and Supervisor
- #227
2015-11-27 07:35:12 -05:00
d46c9d5412 - Fixes issue with Supervisor when database tables do not exist 2015-11-27 07:35:11 -05:00
9ab8b1f0c5 added loading on wp users sync 2015-11-27 12:57:52 +01:00
9425ac1593 Merge pull request #244 from mailpoet/wp_users_segment
WP Users segment
2015-11-27 11:39:08 +01:00
4e32479609 Merge pull request #246 from mailpoet/import-fixes
Fixes import issues and updates code
2015-11-27 09:30:19 +01:00
7d95b38dc4 - Renames/refactors Import and Export classes/views/JS
- Updates Import and Export to ignore trashed subscribers
- Updates tests
Closes #245
2015-11-26 20:48:19 -05:00
17c83c5bd4 Merge pull request #242 from mailpoet/newsletter_templates
Newsletter template thumbnails
2015-11-26 21:26:23 +01:00
23bb2f111f Add a sample translatable template 2015-11-26 17:21:10 +02:00
4655b2c64c Switch template thumbnails to be generated in jpeg, not png 2015-11-26 17:20:34 +02:00
84fede11b8 removed dynamic lists from import 2015-11-26 16:02:53 +01:00
f37488fc0b Adds a Preview icon for previewing newsletter templates 2015-11-26 13:09:08 +02:00
20e2e03982 Hide non default segments on some pages
- added getPublic() method on Segment model
- filter out dynamic lists from add/move/remove segment in subscribers
2015-11-26 11:32:10 +01:00
a5d96f1534 WP Users list
- refactored and fixed listing issues (related to Segments)
- removed bulk actions from segments
- added synchronize methods for WP users
- Update action in segments only for WP Users list
- added "type" column to segments (default, wp_users, dynamic...)
- added "status" column to subscriber_segment table (useful soon)
2015-11-25 18:31:57 +01:00
a516eb1a95 Add overlay and newsletter thumbnail preview 2015-11-25 16:44:18 +02:00
6dd8270bec WP Users list
- migration for filters & segment_filter tables
- models for new tables
- update of Listing JSX to allow for conditional display of item actions
2015-11-24 17:12:14 +01:00
981cbd579f Fix clickable labels for editor settings views 2015-11-24 16:51:27 +02:00
52a0aae10f Add template thumbnail generation and display 2015-11-24 16:50:57 +02:00
ebca4257a6 Version bump: 0.0.5 2015-11-21 00:39:11 +01:00
c3944095bc Install npm deps in build command. 2015-11-21 00:36:12 +01:00
a1445d1b6a Vagrant Container and new build process. 2015-11-21 00:28:33 +01:00
e62c24879b Merge pull request #239 from mailpoet/revert-238-queue
Revert "Queue"
2015-11-20 23:51:19 +01:00
00f06ea202 Revert "Queue" 2015-11-20 23:51:02 +01:00
32ca24ce38 Merge pull request #238 from mailpoet/queue
Queue
2015-11-20 23:02:30 +01:00
44e3adb422 Merge pull request #235 from mailpoet/alc
Editor: ALC fixes
2015-11-20 23:00:47 +01:00
a1346ebecc Merge pull request #237 from mailpoet/changelog
welcome/update pages display logic
2015-11-20 23:00:30 +01:00
25b51d0446 - Adds queue management and supervisor. Issue #227 2015-11-20 16:20:54 -05:00
556a170903 - Bootstraps queue 2015-11-20 16:20:35 -05:00
7ac386a8e2 fixed welcome/update pages display logic 2015-11-20 13:37:52 +01:00
d91b55ec52 Include static sticky-kit library, patch it, fix sticky editor sidebar 2015-11-20 14:16:51 +02:00
786fbc36a2 Change post types to plural labels, move Posts/ALC UI elements 2015-11-19 19:08:48 +02:00
160e8b7a12 Merge pull request #232 from mailpoet/export-rename
Rename BootstrapMenu.php to BootStrapMenu.php
2015-11-19 15:01:24 +01:00
0b1f85da09 Rename BootstrapMenu.php to BootStrapMenu.php
- Renames BootStrapMenu class to use proper camelCase
2015-11-19 08:57:37 -05:00
fbc6f54ddc Merge pull request #230 from mailpoet/export
Export
2015-11-19 10:35:27 +01:00
a603e97b8c Merge pull request #229 from mailpoet/editor_posts
Editor: Posts widget
2015-11-19 10:31:31 +01:00
0875b627b6 Merge pull request #231 from mailpoet/newsletters_select_all_fix
fixed missing messages in newsletters listing
2015-11-19 10:27:45 +01:00
035784ece0 fixed missing messages in newsletters listing 2015-11-19 10:24:27 +01:00
aa93c7349f - Rebases master
- Adds tests for all Export class methods
Closes #221
2015-11-18 22:14:48 -05:00
82cf4a28fd - Updates export tests 2015-11-18 14:42:28 -05:00
832e5ef342 - Fixed minor import issue wrt to status not being set 2015-11-18 14:42:27 -05:00
e3de3a123a - Corrects exported subscriber count
- Properly exports subscribers not in any list
- Adds test for export class constructor method
Resolves #221
2015-11-18 14:42:27 -05:00
db91590159 Merge pull request #228 from mailpoet/welcome_redirect
Welcome page redirection
2015-11-18 14:33:48 +01:00
28c61fca0b Newsletters listing
- fixed listing actions on newsletters
2015-11-18 14:04:34 +01:00
e62a8c2ec5 Implement Becs' changes to Posts widget and post rendering 2015-11-18 14:44:57 +02:00
fdbd1245e3 Redirect to welcome or update page 2015-11-17 20:11:03 +01:00
0eef46db57 Fix post transformation to take titleIsLink option into account 2015-11-17 16:55:55 +02:00
080ae88a04 Fix Posts block tests 2015-11-17 16:31:57 +02:00
225be9f3cd Starting welcome page redirection 2015-11-17 13:43:25 +01:00
c9a42ebb76 Add auto-refresh of Posts block contents on option change 2015-11-17 14:40:39 +02:00
ae9b3df92d Merge pull request #223 from mailpoet/build
Fix JS lib inclusion in build step
2015-11-16 13:38:33 +01:00
63a08ebb55 Fix inclusion of js/lib/tinymce by disabling removal of node_modules/ 2015-11-16 14:27:29 +02:00
2ef5096fa9 Optimize symlinks when building. 2015-11-13 23:55:24 +01:00
95cfe2d8ec Version bump: 0.0.4. 2015-11-13 19:57:22 +01:00
49676b3fc5 Merge pull request #219 from mailpoet/export
Export
2015-11-13 18:49:32 +01:00
c96ac06423 - Moves ImportExport under Subscribers namespace
- Updates tests
2015-11-13 12:46:54 -05:00
6a3166c311 - Implements export
Closes #210
2015-11-13 12:25:33 -05:00
0017df1c2d - Work-in-progress on the UI 2015-11-13 12:25:32 -05:00
f2a0d4ce96 fixed wrong count value onGetItems in Listing 2015-11-13 12:25:32 -05:00
cb50517cbc toggle Export button depending on subscribers count 2015-11-13 12:25:31 -05:00
0fedd1779f - Moves Import and Export under ImportExport namespace
- Cretes a single BootStrapMenu class for Import and Export
- Updates tests
- Adds 2 new methods to Segments model
2015-11-13 12:25:31 -05:00
1625e1771b - Bootstraps export 2015-11-13 12:25:30 -05:00
bde78b607b Merge pull request #218 from mailpoet/listings_bugfix
Listings bugfixes & Welcome page
2015-11-13 16:01:23 +01:00
f3c58c27be set welcome page as default page 2015-11-13 15:58:36 +01:00
9fec460295 Merge pull request #220 from mailpoet/helpscout_beacon
Adds a HelpScout beacon to admin pages
2015-11-13 15:23:03 +01:00
162859529e reinstated removed tests 2015-11-13 13:56:59 +01:00
3bdaaf8368 Add a HelpScout beacon to admin pages 2015-11-13 14:45:25 +02:00
f6ab0050b2 added welcome page 2015-11-13 12:59:49 +01:00
10a20935c3 cleanup tests 2015-11-12 14:11:27 +01:00
8135b677f7 Merge pull request #216 from mailpoet/editor_layouts
Add layout settings to newsletter editor
2015-11-12 14:04:04 +01:00
90382bc86d Add layout block bg color, remove bg colors of individual columns 2015-11-11 16:55:44 +02:00
47af8939cc Merge pull request #214 from mailpoet/editor_images
Editor small image handling
2015-11-11 09:32:32 +01:00
4a0deb2182 Preserve image width for smaller than column width images 2015-11-10 18:09:36 +02:00
3a206b2c88 Adapt newsletter template selection to API change 2015-11-10 18:08:16 +02:00
70cfcbe7cc Merge pull request #209 from mailpoet/mixpanel
Add MixPanel analytics tracking
2015-11-10 10:41:24 +01:00
6342cb17bd Merge pull request #208 from mailpoet/listing_tests
Listings
2015-11-10 10:39:53 +01:00
64c35b12c8 Merge pull request #191 from mailpoet/import
Import
2015-11-09 23:23:08 +01:00
6a14f97419 Fix select2 results z-index issue for newsletter editor 2015-11-09 18:17:10 +02:00
dfec34eda9 Add Analytics integration with MixPanel 2015-11-09 18:11:06 +02:00
893231e8e5 List selection fix 2015-11-09 14:19:59 +01:00
e9110680ee Listings
- fixed selection field JSX
- fixed bulk actions (added filter function)
- added getPublished/getTrashed static methods on Model
- fixed step 3 of newsletter process
- updated save/get methods of all listing-able models to conform with the new norm
2015-11-09 13:26:33 +01:00
7b54285ca6 - Adds tests for the main Import class
- Updates tests for Env (proper host detection with port)
- Improves import
2015-11-09 00:25:24 -05:00
33ea16eb0f - Cleans up import
- Adds tests for modified models
- Adds tests for import BootStrapMenu and MailChimp classes
2015-11-08 15:57:43 -05:00
b1ae07d38e - Rebased master
- Cleaned up import & moved it under Subscribers menu
2015-11-07 21:16:38 -05:00
3f168d052f - Finishes import migration
- Updates models
- Improves Notice.js
2015-11-07 11:40:42 -05:00
158d26ef86 - Adds import's step 1 method selection logic 2015-11-07 11:31:40 -05:00
ae03ee2c46 - Bootstrapping import menu 2015-11-07 11:25:05 -05:00
ad0adb48e1 - Updates Migrator with new column for Segments
- Updates Segmnets tests
- Updates MailPoet's Notice.js with additional options
- Updates Import's router, WP menu bootstrap logic, client- and
  server-side logic
2015-11-07 11:24:02 -05:00
ff5353c894 - Completes MailChimp import 2015-11-07 11:15:49 -05:00
abb2389378 - Enables MailChimp key verification (import step 1) 2015-11-07 11:15:04 -05:00
3cf50810f9 - Fixed conflic with backbone router in settings and import
- WIP on step 1
2015-11-07 11:09:13 -05:00
20a6e6d6de - Adds import's step 1 method selection logic 2015-11-07 11:09:13 -05:00
0b1fc8f6c3 - Updates webpack config file
- Adds an option to watch/compile just the JS files with webpack
2015-11-07 11:05:48 -05:00
0a771acb02 - Bootstrapping import menu 2015-11-06 21:38:25 -05:00
0199e2c7e1 Version bump: 0.0.3 2015-11-06 21:49:28 +01:00
d1f407bf19 Merge branch 'master' of github.com:mailpoet/mailpoet 2015-11-06 21:47:49 +01:00
f18d2842b9 Version bump: 0.0.3. 2015-11-06 21:47:17 +01:00
f640cbb307 Merge pull request #203 from mailpoet/form_editor
Form editor
2015-11-06 21:39:03 +01:00
b20d92c9b1 fixed unit tests and added form model unit test 2015-11-06 18:43:56 +01:00
795485d42a fixed sortable segments in form editor 2015-11-06 16:09:56 +01:00
dfadda2d12 converted form renderer validation to Parsley 2015-11-06 16:09:56 +01:00
31305a04c0 form validation with Parsley 2015-11-06 16:09:56 +01:00
cfdb886e88 Date widget
- fixed date widget
- fixed default styles for radio inputs in form rendering
2015-11-06 16:09:09 +01:00
1ce4b16327 custom fields (create and update) 2015-11-06 16:09:09 +01:00
b12f7f29de Custom fields
- added listing of custom fields in form editor
- ability to delete a custom field
- mades changes to the form editor in order to accomodate for the new custom fields system
- fix form editor bugs
- cleanup
2015-11-06 16:09:09 +01:00
5473f94e24 List selection & subscribe
- fixed list selection widget (form editor & rendered form)
- ajax subscription works (minus sending the confirmation email)
- bug fixes / polishing / refactoring / cleanup
2015-11-06 16:09:09 +01:00
a31dce6226 fixed list selection widget + started form submission 2015-11-06 16:09:09 +01:00
d996b78561 fixed form_editor.js being ignored by git 2015-11-06 16:09:09 +01:00
c2e7513381 Form editor
- new form with default data
- load/save in form editor
- widgets -> settings form
- widgets -> shortcode for subscribers count
- widgets -> form rendering
- added useful filters to Subscribers (for status related search)
- refactor & cleanup
2015-11-06 16:09:09 +01:00
541696863e Form editor
- new/edit in forms listing goes to editor
- fixed editor dependencies (via Webpack)
- updated forms table schema
- saving/loading a form works
2015-11-06 16:09:09 +01:00
6c8d2be18c fix for selection field jsx 2015-11-06 16:08:16 +01:00
907fe585de add Form renderer and fixed Newsletter saving issue 2015-11-06 16:08:16 +01:00
e24f8c9653 forms listing complete 2015-11-06 16:08:16 +01:00
5df0475b1a Merge pull request #204 from mailpoet/newsletter_templates
Newsletter template import/export
2015-11-06 12:23:27 +01:00
cf154455e3 Add error reporting for newsletter template export fields 2015-11-05 19:11:32 +02:00
dcfe6357cf Switch to FileSaver lib for downloading Blob files, add Blob polyfill 2015-11-05 17:17:54 +02:00
983df216f3 Add basic template export 2015-11-04 18:02:55 +02:00
f750d2359f Base for template import 2015-11-03 16:33:13 +02:00
d85f51e9fc Update composer lockfile. 2015-10-30 22:26:40 +01:00
40a62687cf Update Composer lockfile. 2015-10-30 22:13:00 +01:00
136e09e9fb Merge pull request #202 from mailpoet/newsletter_width
Increase newsletter width to 660px from 600px
2015-10-30 12:37:16 +01:00
f509dc0d7e Increase newsletter width to 660px from 600px 2015-10-30 13:25:45 +02:00
c100130f39 Merge pull request #201 from mailpoet/forms
Listing/Model/Router refactoring + Forms
2015-10-30 11:45:34 +01:00
9922ecd93c Merge pull request #200 from mailpoet/notification_type
Add notification email type
2015-10-30 11:40:50 +01:00
a4cf2f9c76 Major refactoring of listing/router/model relation
- updated Subscribers listing
- udpated Segments listing
- added Forms router
2015-10-29 15:30:24 +01:00
576fbf2085 Add notification email type 2015-10-29 15:59:09 +02:00
5c63971314 Merge pull request #198 from mailpoet/editor_select2
Update select2 version to 4.0 for newsletter editor
2015-10-28 15:30:08 +01:00
7418923bbc Remove glow and margins from select2 input 2015-10-28 16:01:23 +02:00
a8f8134f67 Adapt select2 integration code to select2 4.0 2015-10-28 16:01:23 +02:00
103da61d45 basic listing files 2015-10-28 13:19:48 +01:00
01e6a5e6b2 forms table 2015-10-28 13:19:48 +01:00
f5ccf3b38a Merge pull request #195 from mailpoet/subscribers_round_1
Subscriber & Segment listings
2015-10-28 12:24:19 +01:00
c8929351ba Merge pull request #196 from mailpoet/openssl
use of Crypt\RSA library in order to generate dkim keys
2015-10-28 12:23:33 +01:00
6ca536e9ca use of Crypt\RSA library in order to generate dkim keys 2015-10-27 16:41:28 +01:00
89b04e8691 Subscriber & Segment listings
- fixed filters
- added load/save state from url
- added goBack on forms in order to get back listing states
wx# Please enter the commit message for your changes. Lines starting
2015-10-27 16:24:00 +01:00
588b441fb1 Merge pull request #194 from mailpoet/lists_round_1
Lists round 1
2015-10-27 10:30:45 +01:00
13dc3577f1 lotta fixes for filtering + listing 2015-10-26 18:23:32 +01:00
505b979ac5 Segment actions
- added duplicate
- added view subscribers
2015-10-23 17:34:35 +02:00
3b4c5c83e1 added segment stats in listing 2015-10-22 20:40:46 +02:00
056e79eeac bugfix 2015-10-22 19:24:58 +02:00
4bde705f04 listing modifications + added description to segments 2015-10-22 19:19:40 +02:00
290 changed files with 24213 additions and 6484 deletions

5
.gitignore vendored
View File

@ -1,7 +1,7 @@
.DS_Store
TODO
composer.phar
vendor
/vendor
tests/_output/*
tests/acceptance.suite.yml
tests/_support/_generated/*
@ -15,4 +15,5 @@ temp
wysija-newsletters.zip
tests/javascript/testBundles
assets/css/*.css
assets/js/*.js
assets/js/*.js
.vagrant

View File

@ -8,7 +8,6 @@ MailPoet done the right way.
```
php
nodejs
phantomjs
wordpress
```
@ -47,16 +46,6 @@ $ ./do compile:all
$ ./do test:unit
```
- Acceptance tests:
```sh
$ ./do test:acceptance
```
- Run all tests:
```sh
$ ./do test:all
```
- Debug tests:
```sh
$ ./do test:debug

View File

@ -47,6 +47,19 @@ class RoboFile extends \Robo\Tasks {
->run();
}
function watchCss() {
$css_files = $this->rsearch('assets/css/src/', array('styl'));
$this->taskWatch()
->monitor($css_files, function() {
$this->compileCss();
})
->run();
}
function watchJs() {
$this->_exec('./node_modules/webpack/bin/webpack.js --watch');
}
function compileAll() {
$this->compileJs();
$this->compileCss();
@ -61,7 +74,8 @@ class RoboFile extends \Robo\Tasks {
'assets/css/src/admin.styl',
'assets/css/src/newsletter_editor/newsletter_editor.styl',
'assets/css/src/public.styl',
'assets/css/src/rtl.styl'
'assets/css/src/rtl.styl',
'assets/css/src/importExport.styl'
);
$this->_exec(join(' ', array(
@ -113,7 +127,6 @@ class RoboFile extends \Robo\Tasks {
function testFailed() {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->startPhantomJS();
$this->_exec('vendor/bin/codecept run -g failed');
}
@ -121,4 +134,4 @@ class RoboFile extends \Robo\Tasks {
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
}
}
}

View File

@ -5,12 +5,15 @@
@require 'common'
@require 'modal'
@require 'notice'
@require 'validation_engine'
@require 'form_editor'
@require 'listing'
@require 'box'
@require 'breadcrumb'
@require 'form'
@require 'settings'
@require 'form'
@require 'parsley'
@require 'form_validation'
@require 'settings'
@require 'progress_bar'

File diff suppressed because one or more lines are too long

View File

@ -19,8 +19,32 @@ a:focus
// select 2
.select2-container
// textareas
textarea.regular-text
width: 25em !important
@media screen and (max-width: 782px)
.select2-container
width: 100% !important
width: 100% !important
// progress bars
progress-border-radius = 5px
progress-background = #efefef
progress-foreground = #69b1e9
progress
background-color: progress-background;
height: 2em
border: 0
width: 100%
progress::-webkit-progress-bar
background-color: progress-background;
progress::-webkit-progress-value
background-color: progress-foreground
border-radius: progress-border-radius
progress::-moz-progress-bar
background-color: progress-foreground
border-radius: progress-border-radius

View File

@ -1,6 +1,12 @@
@require 'codemirror/lib/codemirror.css'
@require 'codemirror/theme/neo.css'
icons = '../img/form_editor_icons.png'
handle_icon = '../img/handle.png'
#mailpoet_form_name
font-size: 23px
#mailpoet_form_history
display: none
@ -96,6 +102,7 @@ handle_icon = '../img/handle.png'
/* MailPoet Form wrapper */
#mailpoet_form_wrapper
position: relative
margin: 20px 0 0 0
/* MailPoet Form container */
#mailpoet_form_container

View File

@ -0,0 +1,6 @@
.parsley-errors-list
margin-top: 8px
.parsley-required
.parsley-custom-error-message
color: #b94a48

View File

@ -0,0 +1,78 @@
.mailpoet_hidden, .mailpoet_validation_error
display none
.form-table
th
width 300px
#paste_input
width 100%
input[type="radio"]
margin-right 0.5em !important
& + span
margin-right 2.5em
span
&.mailpoet_mailchimp-key-status
&.mailpoet_mailchimp-ok
&:before
content "\2713"
color #0e90d2
margin-left 15px
&.mailpoet_mailchimp-error
&:before
content "\2717"
color #900
margin-left 15px
#subscribers_data
overflow auto
table
width auto
td
padding 0.5em
& > table
& > tbody
& > td
padding 0.5em
& > tr
&:nth-child(odd)
background #f9f9f9
.mailpoet_header
text-transform uppercase
font-weight 600
text-decoration underline
#subscribers_data th:first-child, #subscribers_data td:first-child
width 10em !important
text-align center !important
padding 0 1em 0 1em !important
vertical-align inherit !important
#subscribers_data
& > table
& > thead
& > tr
& > th
& > span
width 15em !important
.mailpoet_data_match
color #0e90d2
margin-left 0.25em
.mailpoet_import_error, .mailpoet_validation_error
color #900
tr
&.mailpoet_segments
& > td
& > a
margin-left 15px
span
&.select2-search
&.select2-search--dropdown
display none !important

View File

@ -1,4 +1,4 @@
.mailpoet_listing_loading tbody tr,
.mailpoet_listing_loading tbody tr
.mailpoet_form_loading tbody tr
opacity: 0.2
@ -8,6 +8,20 @@
.mailpoet_select_all td
text-align: center
table.widefat thead .check-column,
table.widefat tfoot .check-column
padding: 10px 0 0 3px
.mailpoet_listing_table
th span
white-space: nowrap
thead .check-column
tfoot .check-column
padding: 10px 0 0 3px
thead th.column-primary
tfoot th.column-primary
width: 25em
// responsive
@media screen and (max-width: 782px)
thead th.column-primary
tfoot th.column-primary
width: 100%

View File

@ -90,7 +90,7 @@ body.mailpoet_modal_opened
padding: 0
margin: 0
width: 100%
transition: margin 0.3s ease-out
transition: margin 250ms ease-out
.mailpoet_panel_wrapper
background-color: #f1f1f1
@ -159,23 +159,6 @@ body.mailpoet_modal_opened
margin: 0
text-align: right
.mailpoet_button
padding: 3px 15px
border: 1px solid #444
font-weight: normal
cursor: pointer
background-color: #222
color: #cfcfcf
font-size: 1em
.mailpoet_button:hover
background-color: #00aacc
color: #fff
.mailpoet_button:active
background-color: #00ccff
color: #fff
@media screen and (max-width: 782px)
#mailpoet_modal_overlay.mailpoet_panel_overlay
top: 46px
@ -217,4 +200,4 @@ body.mailpoet_modal_opened
0%
50%
background-color: #064E6D
100%
100%

View File

@ -76,13 +76,23 @@ $layer-selector-width = 30px
display: inline-block
padding: 2px
vertical-align: top
animation-background-color()
.mailpoet_tool
padding: 0
.mailpoet_delete_block_activate
max-width: 100%
display: inline-block
opacity: 1
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
display: none
max-width: 0
opacity: 0
overflow: hidden
display: inline-block
animation-fade-in-and-scale-horizontally()
.mailpoet_delete_block_activated
width: auto
@ -93,11 +103,17 @@ $layer-selector-width = 30px
height: auto
.mailpoet_delete_block_activate
display: none
overflow: hidden
max-width: 0
opacity: 0
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
display: inline-block
max-width: 100%
opacity: 1
.mailpoet_delete_block_cancel
margin-left: 3px
.mailpoet_delete_block_confirm
color: $warning-text-color

View File

@ -52,6 +52,7 @@ $draggable-widget-z-index = 2
padding: 0
margin: 0
z-index: $draggable-widget-z-index
animation-fade-in()
.mailpoet_widget_icon
padding: 0

View File

@ -45,9 +45,10 @@
&::before
content: '\f140'
.mailpoet_save_as_template_container
.mailpoet_save_as_template_container,
.mailpoet_export_template_container
border-radius(3px)
float: left
display: inline-block
clear: both
margin-top: 5px
@ -55,7 +56,8 @@
background-color: $white-color
border: 1px solid $structure-border-color
.mailpoet_save_as_template_title
.mailpoet_save_as_template_title,
.mailpoet_export_template_title
font-size: 1.1em
.mailpoet_editor_last_saved

View File

@ -26,13 +26,9 @@ $widget-icon-width = 30px
border-right: 0
&.closed .mailpoet_region_content
max-height: 0px
overflow: hidden
margin-top: 0
display: none
.mailpoet_region_content
max-height: 2000px
transition: max-height 0.2s ease
padding: 0 20px
margin-top: 12px

View File

@ -18,3 +18,6 @@
& > .mailpoet_block
width: 100%
.mailpoet_automated_latest_content_display_options
animation-slide-open-downwards()

View File

@ -1,3 +1,8 @@
$column-margin = 20px
$one-column-width = $newsletter-width - (2 * $column-margin)
$two-column-width = ($newsletter-width / 2) - (2 * $column-margin)
$three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
.mailpoet_container
width: 100%
min-height: 15px
@ -44,7 +49,7 @@
.mailpoet_container_horizontal > .mailpoet_container_block
margin-bottom: 0
width: 20px + 560px + 20px
width: $column-margin + $one-column-width + $column-margin
// More than one column
& > .mailpoet_container_block > .mailpoet_container > .mailpoet_container_block > .mailpoet_container_horizontal
@ -57,14 +62,14 @@
& > .mailpoet_block:first-child:nth-last-child(2) ~ .mailpoet_block
//padding-left: 20px
//padding-right: 20px
width: 260px + 20px + 20px
width: $column-margin + $two-column-width + $column-margin
// Three columns
& > .mailpoet_block:first-child:nth-last-child(3)
& > .mailpoet_block:first-child:nth-last-child(3) ~ .mailpoet_block
//padding-left: 20px
//padding-right: 20px
width: 160px + 20px + 20px
width: $column-margin + $three-column-width + $column-margin
.mailpoet_container_empty
text-align: center
@ -74,3 +79,4 @@
box-shadow(inset 1px 2px 1px $primary-inactive-color)
color: #656565
border-radius(3px)
animation-background-color()

View File

@ -19,6 +19,10 @@ $divider-hover-border-color = $primary-active-color
width: 100%
border: 1px solid transparent
.mailpoet_active_divider_style
border: 1px solid $active-divider-border-color
background: $active-divider-background-color
.mailpoet_field_divider_style:hover
border: 1px solid $divider-hover-border-color

View File

@ -1,15 +1,19 @@
.mailpoet_image_block
img
vertical-align: bottom
max-width: 100%
width: auto
height: auto
&.mailpoet_full_image
padding-left: 0
padding-right: 0
margin-bottom: 0
img
width: 100%
.mailpoet_content a:hover
cursor: all-scroll
img
vertical-align: bottom
max-width: $newsletter-width
width: 100%
height: auto

View File

@ -1,15 +1,12 @@
.mailpoet_posts_block
box-shadow(none)
padding-left: 0
padding-right: 0
& > .mailpoet_content
font-size: 1em
text-align: center
background-color: $primary-active-color
margin: 20px 0
padding: 15px
box-shadow(inset 1px 2px 1px $primary-inset-shadow-color)
color: $white-color
border-radius(3px)
.mailpoet_posts_block_posts
overflow: auto
& > .mailpoet_block
width: 100%
.mailpoet_post_selection_filter_row
margin-top: 5px
@ -18,7 +15,11 @@
.mailpoet_posts_categories_and_tags
width: 100%
.mailpoet_settings_posts_show_display_options
.mailpoet_settings_posts_display_options
.mailpoet_settings_posts_selection
animation-slide-open-downwards()
.mailpoet_settings_posts_show_display_options,
.mailpoet_settings_posts_show_post_selection
display: block
margin-top: 10px

View File

@ -1,12 +1,25 @@
/* Fix select2 z-index to work with MailPoet.Modal */
.select2-drop
z-index: 101000
.select2-dropdown
z-index: 101000 !important
/* Remove input field styles from select2 type input */
.select2-container
border: none
padding: 0
/* Fix select2 input glow and margins that wordpress may insert */
.select2 input,
.select2 input:focus
border-color: none
box-shadow: none
margin: 0
padding: 0
/* Fix width overrides for select2 */
.mailpoet_editor_settings .select2-container
width: 100% !important
/* Fix inline TinyMCE toolbar to have minimal width instead of being close to 100% of the screen */
div.mce-toolbar-grp.mce-container
position: absolute

View File

@ -0,0 +1,30 @@
animation-slide-open-downwards()
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
max-height: 2000px
opacity: 1
&.mailpoet_closed
max-height: 0
opacity: 0
overflow-y: hidden
animation-background-color()
transition: background 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
animation-fade-in()
animation-name: fadeIn
animation-duration: 250ms
animation-fill-mode: forwards
animation-fade-in-and-scale-horizontally()
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
@keyframes fadeIn {
0% {
opacity: 0.3
}
100% {
opacity: 1
}
}

View File

@ -6,6 +6,7 @@
@require 'mixins/border-radius'
@require 'mixins/box-shadow'
@require 'mixins/filter-shadow'
@require 'mixins/transitions'
@require 'variables'
@require 'common'

View File

@ -23,4 +23,4 @@ $warning-alternate-text-color = #f4c6c8
$error-text-color = #d54e21
// Dimensions
$newsletter-width = 600px
$newsletter-width = 660px

View File

@ -0,0 +1,27 @@
input.parsley-success,
select.parsley-success,
textarea.parsley-success
color #468847
background-color #DFF0D8
border 1px solid #D6E9C6
input.parsley-error,
select.parsley-error,
textarea.parsley-error
color #B94A48
background-color #F2DEDE
border 1px solid #EED3D7
.parsley-errors-list
margin 2px 0 3px
padding 0
list-style-type none
font-size 0.9em
line-height 0.9em
opacity 0
transition all .3s ease-in
-o-transition all .3s ease-in
-moz-transition all .3s ease-in
-webkit-transition all .3s ease-in
&.filled
opacity 1

View File

@ -0,0 +1,29 @@
.mailpoet_progress
background-color: #efefef
height: 25px
padding: 0
width: 100%
margin: 0
border-radius: 5px
position: relative
.mailpoet_progress_label
position: absolute
width: 100%
text-align: center
display: inline-block
margin: 2px 0 0 0
.mailpoet_progress_bar
position: absolute
display: inline-block
height: 100%
border-radius: 3px
box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset
background-color: #34c2e3
background-image: linear-gradient(top, #34c2e3, darken(#34c2e3, 20%))
.mailpoet_progress_complete
.mailpoet_progress_bar
background-color: #fecf23
background-image: linear-gradient(top, #fecf23, #fd9215)

View File

@ -0,0 +1,4 @@
@import 'nib'
@require 'parsley'
@require 'form_validation'

View File

@ -1,141 +0,0 @@
lesscss-percentage(n)
(n * 100)%
popupBg = rgb(0, 87, 154, 1)
popupTextColor = rgb(255, 255, 255, 1)
borderColor = rgb(255, 255, 255, 1)
borderWidth = 1px
popupFontSize = 12px
popupRadius = 0
popupShadowWidth = 2px
popupShadowColor = rgb(51, 51, 51, 1)
/* Z-INDEX */
.formError
z-index 990
.formError .formErrorContent
z-index 991
.formError .formErrorArrow
z-index 996
.ui-dialog .formError
z-index 5000
.ui-dialog .formError .formErrorContent
z-index 5001
.ui-dialog .formError .formErrorArrow
z-index 5006
.inputContainer
position relative
float left
.formError
position absolute
top 300px
left 300px
display block
cursor pointer
text-align left
.formError.inline
position relative
top 0
left 0
display inline-block
.ajaxSubmit
padding 20px
background #55ea55
border 1px solid #999
display none
.formError .formErrorContent
width 100%
background popupBg
position relative
color popupTextColor
min-width 120px
font-size popupFontSize
border borderWidth solid borderColor
box-shadow 0 0 popupShadowWidth popupShadowColor
-moz-box-shadow 0 0 popupShadowWidth popupShadowColor
-webkit-box-shadow 0 0 popupShadowWidth popupShadowColor
-o-box-shadow 0 0 popupShadowWidth popupShadowColor
padding 4px 10px 4px 10px
border-radius popupRadius
-moz-border-radius popupRadius
-webkit-border-radius popupRadius
-o-border-radius popupRadius
.formError.inline .formErrorContent
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
border none
border-radius 0
-moz-border-radius 0
-webkit-border-radius 0
-o-border-radius 0
.greenPopup .formErrorContent
background #33be40
.blackPopup .formErrorContent
background #393939
color #FFF
.formError .formErrorArrow
width 15px
margin -2px 0 0 13px
position relative
body[dir='rtl'] .formError .formErrorArrow, body.rtl .formError .formErrorArrow
margin -2px 13px 0 0
.formError .formErrorArrowBottom
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
margin 0px 0 0 12px
top 2px
.formError .formErrorArrow div
border-left borderWidth solid borderColor
border-right borderWidth solid borderColor
box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-moz-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-webkit-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-o-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
font-size 0px
height 1px
background popupBg
margin 0 auto
line-height 0
font-size 0
display block
.formError .formErrorArrowBottom div
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
.greenPopup .formErrorArrow div
background #33be40
.blackPopup .formErrorArrow div
background #393939
color #FFF
.formError .formErrorArrow .line10
width 13px
border none
.formError .formErrorArrow .line9
width 11px
border none
.formError .formErrorArrow .line8
width 11px
.formError .formErrorArrow .line7
width 9px
.formError .formErrorArrow .line6
width 7px
.formError .formErrorArrow .line5
width 5px
.formError .formErrorArrow .line4
width 3px
.formError .formErrorArrow .line3
width ceil((borderWidth / 2))
border-left borderWidth solid borderColor
border-right borderWidth solid borderColor
border-bottom 0 solid borderColor
.formError .formErrorArrow .line2
width 3px
border none
background borderColor
.formError .formErrorArrow .line1
width 1px
border none
background borderColor

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="47.002px" height="38.003px" viewBox="0 0 47.002 38.003" enable-background="new 0 0 47.002 38.003" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#565656" d="M46.328,36.365c-1.188,1.725-3.553,2.158-5.273,0.962L25.479,26.52
c-1.104-0.763-1.674-2.007-1.631-3.257c-2.006,2.157-4.642,3.606-7.594,4.145c-3.626,0.663-7.288-0.13-10.311-2.227
c-3.024-2.098-5.054-5.253-5.714-8.887c-0.661-3.636,0.127-7.31,2.221-10.344c4.325-6.264,12.927-7.834,19.177-3.5
c5.672,3.938,7.486,11.412,4.537,17.443c1.152-0.486,2.519-0.392,3.627,0.377l15.58,10.808C47.09,32.274,47.52,34.641,46.328,36.365
z M23.235,12.09c-0.459-2.534-1.874-4.734-3.982-6.196C14.897,2.87,8.896,3.963,5.878,8.331c-3.014,4.373-1.922,10.388,2.435,13.408
c4.356,3.025,10.356,1.932,13.374-2.438C23.146,17.187,23.696,14.625,23.235,12.09z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,8 @@
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
mixpanel.init("f683d388fb25fcf331f1b2b5c4449798");
if (typeof mailpoet_analytics_data === 'object') {
mixpanel.track('Wysija Usage', mailpoet_analytics_data || {});
}

4
assets/js/lib/prototype.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
assets/js/lib/scriptaculous.min.js vendored Normal file

File diff suppressed because one or more lines are too long

106
assets/js/src/cron.jsx Normal file
View File

@ -0,0 +1,106 @@
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(MailPoetI18n.daemonControlError);
}
}.bind(this));
},
render: function() {
if(this.state.status === 'loading') {
return(<div>Loading daemon status...</div>);
}
switch(this.state.status) {
case 'started':
return(
<div>
Cron daemon is running.
<br/>
<br/>
It was started
<strong> {this.state.timeSinceStart} </strong> and last executed
<strong> {this.state.timeSinceUpdate} </strong> for a total of
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted).
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>Stop</a>
</div>
);
break;
case 'starting':
case 'stopping':
return(
<div>
Daemon is {this.state.status}
</div>
);
break;
case 'stopped':
return(
<div>
Daemon is {this.state.status}
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>Start</a>
</div>
);
break;
}
}
});
const container = document.getElementById('cron_container');
if(container) {
ReactDOM.render(
<CronControl />,
container
);
}
});

View File

@ -23,7 +23,9 @@ function(
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
onChange={ this.props.onValueChange }>
onChange={ this.props.onValueChange }
{...this.props.field.validation}
>
{options}
</select>
);

View File

@ -1,10 +1,12 @@
define([
'react',
'react-dom',
'jquery',
'select2'
],
function(
React,
ReactDOM,
jQuery
) {
var Selection = React.createClass({
@ -16,87 +18,114 @@ function(
},
componentDidMount: function() {
this.loadCachedItems();
},
componentDidUpdate: function() {
this.setupSelect2();
},
componentDidUpdate: function(prevProps, prevState) {
if(
(this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
jQuery('#'+this.refs.select.id)
.val(this.props.item[this.props.field.name])
.trigger('change');
}
},
setupSelect2: function() {
if(this.state.initialized === true) {
if(
!this.props.field.multiple
|| this.state.initialized === true
|| this.refs.select === undefined
) {
return;
}
if(this.props.field.select2 && Object.keys(this.props.item).length > 0) {
var select2 = jQuery('#'+this.props.field.id).select2({
width: (this.props.width || ''),
templateResult: function(item) {
if (item.element && item.element.selected) {
return null;
} else {
return item.text;
}
var select2 = jQuery('#'+this.refs.select.id).select2({
width: (this.props.width || ''),
templateResult: function(item) {
if(item.element && item.element.selected) {
return null;
} else {
return item.text;
}
});
}
});
select2.on('change', this.handleChange)
var hasRemoved = false;
select2.on('select2:unselecting', function(e) {
hasRemoved = true;
});
select2.on('select2:opening', function(e) {
if(hasRemoved === true) {
hasRemoved = false;
e.preventDefault();
}
});
select2.select2(
'val',
this.props.item[this.props.field.name]
);
select2.on('change', this.handleChange);
this.setState({ initialized: true });
}
select2.select2(
'val',
this.props.item[this.props.field.name]
);
this.setState({ initialized: true });
},
loadCachedItems: function() {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
var items = window['mailpoet_'+this.props.field.endpoint];
if(this.props.field['filter'] !== undefined) {
items = items.filter(this.props.field.filter);
}
this.setState({
items: items
});
}
},
handleChange: function() {
handleChange: function(e) {
if(this.props.onValueChange !== undefined) {
if(this.props.field.multiple) {
value = jQuery('#'+this.refs.select.id).val();
} else {
value = e.target.value;
}
this.props.onValueChange({
target: {
value: jQuery('#'+this.props.field.id).select2('val'),
value: value,
name: this.props.field.name
}
});
}
return true;
},
render: function() {
if(this.state.items.length === 0) {
return false;
} else {
var options = this.state.items.map(function(item, index) {
return (
<option
key={ item.id }
value={ item.id }
>
{ item.name }
</option>
);
});
var default_value = (
(this.props.item !== undefined && this.props.field.name !== undefined)
? this.props.item[this.props.field.name]
: null
);
var options = this.state.items.map(function(item, index) {
return (
<select
id={ this.props.field.id }
placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
onChange={ this.handleChange }
defaultValue={ default_value }
>{ options }</select>
<option
key={ item.id }
value={ item.id }
>
{ item.name }
</option>
);
}
});
var default_value = (
(this.props.item !== undefined && this.props.field.name !== undefined)
? this.props.item[this.props.field.name]
: null
);
return (
<select
id={ this.props.field.id || this.props.field.name }
ref="select"
placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
defaultValue={ default_value }
{...this.props.field.validation}
>{ options }</select>
);
}
});

View File

@ -6,6 +6,8 @@ function(
) {
var FormFieldText = React.createClass({
render: function() {
var value = this.props.item[this.props.field.name];
if(!value) { value = null; }
return (
<input
type="text"
@ -17,10 +19,12 @@ function(
}
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
value={ value }
placeholder={ this.props.field.placeholder }
defaultValue={ this.props.field.defaultValue }
onChange={ this.props.onValueChange } />
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
}
});

View File

@ -15,7 +15,9 @@ function(
value={ this.props.item[this.props.field.name] }
placeholder={ this.props.field.placeholder }
defaultValue={ this.props.field.defaultValue }
onChange={ this.props.onValueChange } />
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
}
});

View File

@ -68,27 +68,56 @@ define(
handleSubmit: function(e) {
e.preventDefault();
// handle validation
if(this.props.isValid !== undefined) {
if(this.props.isValid() === false) {
return;
}
}
this.setState({ loading: true });
// only get values from displayed fields
var item = {};
this.props.fields.map(function(field) {
if(field['fields'] !== undefined) {
field.fields.map(function(subfield) {
item[subfield.name] = this.state.item[subfield.name];
}.bind(this));
} else {
item[field.name] = this.state.item[field.name];
}
}.bind(this));
// set id if specified
if(this.props.params.id !== undefined) {
item.id = this.props.params.id;
}
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'save',
data: this.state.item
data: item
}).done(function(response) {
this.setState({ loading: false });
if(response === true) {
this.history.pushState(null, '/');
if(this.props.params.id !== undefined) {
this.props.messages['updated']();
if(response.result === true) {
if(this.props.onSuccess !== undefined) {
this.props.onSuccess();
} else {
this.props.messages['created']();
this.history.pushState(null, '/')
}
if(this.props.params.id !== undefined) {
this.props.messages.onUpdate();
} else {
this.props.messages.onCreate();
}
} else {
if(response === false) {
// unknown error occurred
} else {
this.setState({ errors: response });
if(response.result === false) {
if(response.errors.length > 0) {
this.setState({ errors: response.errors });
}
}
}
}.bind(this));
@ -105,13 +134,15 @@ define(
return true;
},
render: function() {
var errors = this.state.errors.map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
</p>
);
});
if(this.state.errors !== undefined) {
var errors = this.state.errors.map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
</p>
);
});
}
var formClasses = classNames(
'mailpoet_form',

View File

@ -1,993 +0,0 @@
/*
* name: MailPoet Form Editor
* author: Jonathan Labreuille
* company: Wysija
* framework: prototype 1.7.2
*/
'use strict';
Event.cacheDelegated = {};
Object.extend(document, (function () {
var cache = Event.cacheDelegated;
function getCacheForSelector(selector) {
return cache[selector] = cache[selector] || {};
}
function getWrappersForSelector(selector, eventName) {
var c = getCacheForSelector(selector);
return c[eventName] = c[eventName] || [];
}
function findWrapper(selector, eventName, handler) {
var c = getWrappersForSelector(selector, eventName);
return c.find(function (wrapper) {
return wrapper.handler === handler
});
}
function destroyWrapper(selector, eventName, handler) {
var c = getCacheForSelector(selector);
if (!c[eventName]) return false;
var wrapper = findWrapper(selector, eventName, handler)
c[eventName] = c[eventName].without(wrapper);
return wrapper;
}
function createWrapper(selector, eventName, handler, context) {
var wrapper, c = getWrappersForSelector(selector, eventName);
if (c.pluck('handler').include(handler)) return false;
wrapper = function (event) {
var element = event.findElement(selector);
if (element) handler.call(context || element, event, element);
};
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
}
return {
delegate: function (selector, eventName, handler, context) {
var wrapper = createWrapper.apply(null, arguments);
if (wrapper) document.observe(eventName, wrapper);
return document;
},
stopDelegating: function (selector, eventName, handler) {
var length = arguments.length;
switch (length) {
case 2:
getWrappersForSelector(selector, eventName).each(function (wrapper) {
document.stopDelegating(selector, eventName, wrapper.handler);
});
break;
case 1:
Object.keys(getCacheForSelector(selector)).each(function (eventName) {
document.stopDelegating(selector, eventName);
});
break;
case 0:
Object.keys(cache).each(function (selector) {
document.stopDelegating(selector);
});
break;
default:
var wrapper = destroyWrapper.apply(null, arguments);
if (wrapper) document.stopObserving(eventName, wrapper);
}
return document;
}
}
})());
var Observable = (function () {
function getEventName(name, namespace) {
name = name.substring(2);
if (namespace) name = namespace + ':' + name;
return name.underscore().split('_').join(':');
}
function getHandlers(klass) {
var proto = klass.prototype,
namespace = proto.namespace;
return Object.keys(proto).grep(/^on/).inject($H(), function (handlers, name) {
if (name === 'onDomLoaded') return handlers;
handlers.set(getEventName(name, namespace), getWrapper(proto[name], klass));
return handlers;
});
}
function getWrapper(handler, klass) {
return function (event) {
return handler.call(new klass(this), event, event.memo);
}
}
function onDomLoad(selector, klass) {
$$(selector).each(function (element) {
new klass(element).onDomLoaded();
});
}
return {
observe: function (selector) {
if (!this.handlers) this.handlers = {};
if (this.handlers[selector]) return;
var klass = this;
if (this.prototype.onDomLoaded) document.loaded ? onDomLoad(selector, klass) : document.observe('dom:loaded', onDomLoad.curry(selector, klass));
this.handlers[selector] = getHandlers(klass).each(function (handler) {
document.delegate(selector, handler.key, handler.value);
});
},
stopObserving: function (selector) {
if (!this.handlers || !this.handlers[selector]) return;
this.handlers[selector].each(function (handler) {
document.stopDelegating(selector, handler.key, handler.value);
});
delete this.handlers[selector];
}
}
})();
// override droppables
Object.extend(Droppables, {
deactivate: Droppables.deactivate.wrap(function (proceed, drop, draggable) {
if (drop.onLeave) drop.onLeave(draggable, drop.element);
return proceed(drop);
}),
activate: Droppables.activate.wrap(function (proceed, drop, draggable) {
if (drop.onEnter) drop.onEnter(draggable, drop.element);
return proceed(drop);
}),
show: function (point, element) {
if (!this.drops.length) return;
var drop, affected = [];
this.drops.each(function (drop) {
if (Droppables.isAffected(point, element, drop)) affected.push(drop);
});
if (affected.length > 0) drop = Droppables.findDeepestChild(affected);
if (this.last_active && this.last_active !== drop) this.deactivate(this.last_active, element);
if (drop) {
Position.within(drop.element, point[0], point[1]);
if (drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if (drop !== this.last_active) Droppables.activate(drop, element);
}
},
displayArea: function(draggable) {
if(!this.drops.length) return;
// hide controls when displaying drop areas.
WysijaForm.hideBlockControls();
this.drops.each(function (drop, iterator) {
if(drop.element.hasClassName('block_placeholder')) {
drop.element.addClassName('active');
}
});
},
hideArea: function() {
if (!this.drops.length) return;
this.drops.each(function (drop, iterator) {
if(drop.element.hasClassName('block_placeholder')) {
drop.element.removeClassName('active');
} else if(drop.element.hasClassName('image_placeholder')) {
drop.element.removeClassName('active');
drop.element.up().removeClassName('active');
} else if(drop.element.hasClassName('text_placeholder')) {
drop.element.removeClassName('active');
}
});
},
reset: function (draggable) {
if (this.last_active) this.deactivate(this.last_active, draggable);
}
});
/*
Wysija History handling
POTENTIAL FEATURES:
- set a maximum number of items to be stored
*/
var WysijaHistory = {
container: 'mailpoet_form_history',
size: 30,
enqueue: function(element) {
// create deep clone (includes child elements) of passed element
var clone = element.clone(true);
// check if the field is unique
if(parseInt(clone.readAttribute('wysija_unique'), 10) === 1) {
// check if the field is already in the queue
$(WysijaHistory.container).select('[wysija_field="'+clone.readAttribute('wysija_field')+'"]').invoke('remove');
}
// check history size
if($(WysijaHistory.container).select('> div').length >= WysijaHistory.size) {
// remove oldest element (last in the list)
$(WysijaHistory.container).select('> div').last().remove();
}
// store block in history
$(WysijaHistory.container).insert({ top: clone });
},
dequeue: function() {
// pop last block off the history
var block = $(WysijaHistory.container).select('div').first();
if(block !== undefined) {
// insert block back into the editor
$(WysijaForm.options.body).insert({top: block});
}
},
clear: function() {
$(WysijaHistory.container).innerHTML = '';
},
remove: function(field) {
$(WysijaHistory.container).select('[wysija_field="'+field+'"]').invoke('remove');
}
};
/* MailPoet Form */
var WysijaForm = {
version: '0.6',
options: {
container: 'mailpoet_form_container',
editor: 'mailpoet_form_editor',
body: 'mailpoet_form_body',
toolbar: 'mailpoet_form_toolbar',
templates: 'wysija_widget_templates',
debug: false
},
toolbar: {
effect: null,
x: null,
y: null,
top: null,
left: null
},
scroll: {
top: 0,
left: 0
},
flags: {
doSave: false
},
locks: {
dragging: false,
selectingColor: false,
showingTools: false
},
encodeHtmlValue: function(str) {
return str.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
// ": fix for FileMerge because the previous line fucks up its syntax coloring
},
decodeHtmlValue: function(str) {
return str.replace(/&amp;/g, '&').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&quot;/g, '"');
// ": fix for FileMerge because the previous line fucks up its syntax coloring
},
loading: function(is_loading) {
if(is_loading) {
$(WysijaForm.options.editor).addClassName('loading');
$(WysijaForm.options.toolbar).addClassName('loading');
} else {
$(WysijaForm.options.editor).removeClassName('loading');
$(WysijaForm.options.toolbar).removeClassName('loading');
}
},
loadStatic: function(blocks) {
$A(blocks).each(function(block) {
// create block
WysijaForm.Block.create(block, $('block_placeholder'));
});
},
load: function(form) {
if(form.data === undefined) return;
// load body
if(form.data.body !== undefined) {
$A(form.data.body).each(function(block) {
// create block
WysijaForm.Block.create(block, $('block_placeholder'));
});
// load settings
var settings_elements = $('mailpoet_form_settings').getElements();
settings_elements.each(function(setting) {
// skip lists
if(setting.name === 'lists') {
return true;
} else if(setting.name === 'on_success') {
// if the input value is equal to the one stored in the settings
if(setting.value === form.data.settings[setting.name]) {
// check selected value
$(setting).checked = true;
}
} else if(form.data.settings[setting.name] !== undefined) {
if(typeof form.data.settings[setting.name] === 'string') {
setting.setValue(WysijaForm.decodeHtmlValue(form.data.settings[setting.name]));
} else {
setting.setValue(form.data.settings[setting.name]);
}
}
});
}
},
save: function() {
var position = 1,
data = {
'version': WysijaForm.version,
'settings': $('mailpoet_form_settings').serialize(true),
'body': [],
'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null
};
// body
WysijaForm.getBlocks().each(function(b) {
var block_data = (typeof(b.block['save']) === 'function') ? b.block.save() : null;
if(block_data !== null) {
// set block position
block_data['position'] = position;
// increment position
position++;
// add block data to body
data['body'].push(block_data);
}
});
return data;
},
init: function() {
// set document scroll
info('init -> set scroll offsets');
WysijaForm.setScrollOffsets();
// position toolbar
info('init -> set toolbar position');
WysijaForm.setToolbarPosition();
// enable droppable targets
info('init -> make droppable');
WysijaForm.makeDroppable();
// enable sortable
info('init -> make sortable');
WysijaForm.makeSortable();
// hide controls
info('init -> hide controls');
WysijaForm.hideControls();
// hide settings
info('init -> hide settings');
WysijaForm.hideSettings();
// set settings buttons position
info('init -> init settings');
WysijaForm.setSettingsPosition();
// toggle widgets
info('init -> toggle widgets');
WysijaForm.toggleWidgets();
},
getFieldData: function(element) {
// get basic field data
var data = {
type: element.readAttribute('wysija_type'),
field: element.readAttribute('wysija_field'),
name: element.readAttribute('wysija_name'),
unique: parseInt(element.readAttribute('wysija_unique') || 0, 10),
static: parseInt(element.readAttribute('wysija_static') || 0, 10),
element: element,
params: ''
};
// get params (may be empty)
if(element.readAttribute('wysija_params') !== null && element.readAttribute('wysija_params').length > 0) {
data.params = JSON.parse(element.readAttribute('wysija_params'));
}
return data;
},
toggleWidgets: function() {
$$('a[wysija_unique="1"]').invoke('removeClassName', 'disabled');
// loop through each unique field already inserted in the editor and disable its toolbar equivalent
$$('#'+WysijaForm.options.editor+' [wysija_unique="1"]').each(function(element) {
var field = $$('#'+WysijaForm.options.toolbar+' [wysija_field="'+element.readAttribute('wysija_field')+'"]').first();
if(field !== undefined) {
field.addClassName('disabled');
}
});
// hide list selection if a list widget has been dragged into the editor
$('mailpoet_settings_list_selection')[(($$('#'+WysijaForm.options.editor+' [wysija_field="list"]').length > 0) === true) ? 'hide': 'show']();
},
setBlockPositions: function(event, target) {
// release dragging lock
WysijaForm.locks.dragging = false;
var index = 1;
WysijaForm.getBlocks().each(function (container) {
container.setPosition(index++);
// remove z-index value to avoid issues when resizing images
if(container['block'] !== undefined) {
container.block.element.setStyle({zIndex: ''});
}
});
if(target !== undefined) {
// get placeholders (previous placeholder matches the placeholder linked to the next block)
var block_placeholder = $(target.element.readAttribute('wysija_placeholder')),
previous_placeholder = target.element.previous('.block_placeholder');
if(block_placeholder !== null) {
// put block placeholder before the current block
target.element.insert({before: block_placeholder});
// if the next block is a wysija_block, insert previous placeholder
if(target.element.next() !== undefined && target.element.next().hasClassName('mailpoet_form_block') && previous_placeholder !== undefined) {
target.element.insert({after: previous_placeholder});
}
}
}
},
setScrollOffsets: function() {
WysijaForm.scroll = document.viewport.getScrollOffsets();
},
hideSettings: function() {
$(WysijaForm.options.container).select('.wysija_settings').invoke('hide');
},
setSettingsPosition: function() {
// get viewport offsets and dimensions
var viewportHeight = document.viewport.getHeight(),
blockPadding = 5;
$(WysijaForm.options.container).select('.wysija_settings').each(function(element) {
// get parent dimensions and position
var parentDim = element.up('.mailpoet_form_block').getDimensions(),
parentPos = element.up('.mailpoet_form_block').cumulativeOffset(),
is_visible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight)) ? true : false,
buttonMargin = 5,
relativeTop = buttonMargin;
if(is_visible) {
// desired position is set to center of viewport
var absoluteTop = parseInt(WysijaForm.scroll.top + ((viewportHeight / 2) - (element.getHeight() / 2)), 10),
parentTop = parseInt(parentPos.top - blockPadding, 10),
parentBottom = parseInt(parentPos.top + parentDim.height - blockPadding, 10);
// always center
relativeTop = parseInt((parentDim.height / 2) - (element.getHeight() / 2), 10);
}
// set position for button
$(element).setStyle({
left: parseInt((parentDim.width / 2) - (element.getWidth() / 2)) + 'px',
top: relativeTop + 'px'
});
});
},
initToolbarPosition: function() {
if(WysijaForm.toolbar.top === null) WysijaForm.toolbar.top = parseInt($(WysijaForm.options.container).positionedOffset().top);
if(WysijaForm.toolbar.y === null) WysijaForm.toolbar.y = parseInt(WysijaForm.toolbar.top);
if(isRtl) {
if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = 0;
} else {
if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = parseInt($(WysijaForm.options.container).positionedOffset().left);
}
if(WysijaForm.toolbar.x === null) WysijaForm.toolbar.x = parseInt(WysijaForm.toolbar.left + $(WysijaForm.options.container).getDimensions().width + 15);
},
setToolbarPosition: function() {
WysijaForm.initToolbarPosition();
var position = { top: WysijaForm.toolbar.y + 'px', visibility: 'visible' };
if(isRtl) {
position.right = WysijaForm.toolbar.x + 'px';
} else {
position.left = WysijaForm.toolbar.x + 'px';
}
$(WysijaForm.options.toolbar).setStyle(position);
},
updateToolbarPosition: function() {
// init toolbar position (updates scroll and toolbar y)
WysijaForm.initToolbarPosition();
// cancel previous effect
if(WysijaForm.toolbar.effect !== null) WysijaForm.toolbar.effect.cancel();
if(WysijaForm.scroll.top >= (WysijaForm.toolbar.top - 20)) {
WysijaForm.toolbar.y = parseInt(20 + WysijaForm.scroll.top);
// start effect
WysijaForm.toolbar.effect = new Effect.Move(WysijaForm.options.toolbar, {
x: WysijaForm.toolbar.x,
y: WysijaForm.toolbar.y,
mode: 'absolute',
duration: 0.2
});
} else {
$(WysijaForm.options.toolbar).setStyle({
left: WysijaForm.toolbar.x + 'px',
top: WysijaForm.toolbar.top + 'px'
});
}
},
blockDropOptions: {
accept: $w('mailpoet_form_field'), // acceptable items (classes array)
onEnter: function (draggable, droppable) {
$(droppable).addClassName('hover');
},
onLeave: function (draggable, droppable) {
$(droppable).removeClassName('hover');
},
onDrop: function (draggable, droppable) {
// custom data for images
droppable.fire('wjfe:item:drop', WysijaForm.getFieldData(draggable));
$(droppable).removeClassName('hover');
}
},
hideControls: function() {
try {
return WysijaForm.getBlocks().invoke('hideControls');
} catch(e) { return; }
},
hideTools: function() {
$$('.wysija_tools').invoke('hide');
WysijaForm.locks.showingTools = false;
},
instances: {},
get: function (element, type) {
if(type === undefined) type = 'block';
// identify element
var id = element.identify();
var instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
WysijaForm.instances[id] = instance;
return instance;
},
makeDroppable: function() {
Droppables.add('block_placeholder', WysijaForm.blockDropOptions);
},
makeSortable: function () {
var body = $(WysijaForm.options.body);
Sortable.create(body, {
tag: 'div',
only: 'mailpoet_form_block',
scroll: window,
handle: 'handle',
constraint: 'vertical'
});
Draggables.removeObserver(body);
Draggables.addObserver({
element: body,
onStart: WysijaForm.startBlockPositions,
onEnd: WysijaForm.setBlockPositions
});
},
hideBlockControls: function() {
$$('.wysija_controls').invoke('hide');
this.getBlockElements().invoke('removeClassName', 'hover');
},
getBlocks: function () {
return WysijaForm.getBlockElements().map(function (element) {
return WysijaForm.get(element);
});
},
getBlockElements: function () {
return $(WysijaForm.options.container).select('.mailpoet_form_block');
},
startBlockPositions: function(event, target) {
if(target.element.hasClassName('mailpoet_form_block')) {
// store block placeholder id for the block that is being repositionned
if(target.element.previous('.block_placeholder') !== undefined) {
target.element.writeAttribute('wysija_placeholder', target.element.previous('.block_placeholder').identify());
}
}
WysijaForm.locks.dragging = true;
},
encodeURIComponent: function(str) {
// check if it's a url and if so, prevent encoding of protocol
var regexp = new RegExp(/^http[s]?:\/\//),
protocol = regexp.exec(str);
if(protocol === null) {
// this is not a url so encode the whole thing
return encodeURIComponent(str).replace(/[!'()*]/g, escape);
} else if(protocol.length === 1) {
// this is a url, so do not encode the protocol
return encodeURI(str).replace(/[!'()*]/g, escape);
}
}
};
WysijaForm.DraggableItem = Class.create({
initialize: function (element) {
this.elementType = $(element).readAttribute('wysija_type');
this.element = $(element).down() || $(element);
this.clone = this.cloneElement();
this.insert();
},
STYLES: new Template('position: absolute; top: #{top}px; left: #{left}px;'),
cloneElement: function () {
var clone = this.element.clone(),
offset = this.element.cumulativeOffset(),
list = this.getList(),
styles = this.STYLES.evaluate({
top: offset.top - list.scrollTop,
left: offset.left - list.scrollLeft
});
clone.setStyle(styles);
clone.addClassName('mailpoet_form_widget');
clone.addClassName(this.elementType);
clone.innerHTML = this.element.innerHTML;
return clone;
},
getOffset: function () {
return this.element.offsetTop - this.getList().scrollTop;
},
getList: function () {
return this.element.up('ul');
},
insert: function () {
$$("body")[0].insert(this.clone);
},
onMousedown: function (event) {
var draggable = new Draggable(this.clone, {
scroll: window,
onStart: function () {
Droppables.displayArea(draggable);
},
onEnd: function (drag) {
drag.destroy();
drag.element.remove();
Droppables.hideArea();
},
starteffect: function (element) {
new Effect.Opacity(element, {
duration: 0.2,
from: element.getOpacity(),
to: 0.7
});
},
endeffect: Prototype.emptyFunction
});
draggable.initDrag(event);
draggable.startDrag(event);
return draggable;
}
});
Object.extend(WysijaForm.DraggableItem, Observable).observe('a[class="mailpoet_form_field"]');
WysijaForm.Block = Class.create({
/* Invoked on load */
initialize: function(element) {
info('block -> init');
this.element = $(element);
this.block = new WysijaForm.Widget(this.element);
// enable block placeholder
this.block.makeBlockDroppable();
// setup events
if(this.block['setup'] !== undefined) {
this.block.setup();
}
return this;
},
setPosition: function(position) {
this.element.writeAttribute('wysija_position', position);
},
hideControls: function() {
if(this['getControls']) {
this.element.removeClassName('hover');
this.getControls().hide();
}
},
showControls: function() {
if(this['getControls']) {
this.element.addClassName('hover');
try {
this.getControls().show();
} catch(e) {
;
}
}
},
makeBlockDroppable: function() {
if(this.isBlockDroppableEnabled() === false) {
var block_placeholder = this.getBlockDroppable();
Droppables.add(block_placeholder.identify(), WysijaForm.blockDropOptions);
block_placeholder.addClassName('enabled');
}
},
removeBlockDroppable: function() {
if(this.isBlockDroppableEnabled()) {
var block_placeholder = this.getBlockDroppable();
Droppables.remove(block_placeholder.identify());
block_placeholder.removeClassName('enabled');
}
},
isBlockDroppableEnabled: function() {
// if the block_placeholder does not exist, create it
var block_placeholder = this.getBlockDroppable();
if(block_placeholder === null) {
return this.createBlockDroppable().hasClassName('enabled');
} else {
return block_placeholder.hasClassName('enabled');
}
},
createBlockDroppable: function() {
info('block -> createBlockDroppable');
this.element.insert({before: '<div class=\"block_placeholder\">'+$('block_placeholder').innerHTML+'</div>'});
return this.element.previous('.block_placeholder');
},
getBlockDroppable: function() {
if(this.element.previous() === undefined || this.element.previous().hasClassName('block_placeholder') === false) {
return null;
} else {
return this.element.previous();
}
},
getControls: function() {
return this.element.down('.wysija_controls');
},
setupControls: function() {
// enable controls
this.controls = this.getControls();
if(this.controls) {
// setup events for block controls
this.element.observe('mouseover', function() {
// special cases where controls shouldn't be displayed
if(WysijaForm.locks.dragging === true || WysijaForm.locks.selectingColor === true || WysijaForm.locks.showingTools === true) return;
// set block flag
this.element.addClassName('hover');
// show controls
this.showControls();
// show settings if present
if(this.element.down('.wysija_settings') !== undefined) {
this.element.down('.wysija_settings').show();
}
}.bind(this));
this.element.observe('mouseout', function() {
// special cases where controls shouldn't hide
if(WysijaForm.locks.dragging === true || WysijaForm.locks.selectingColor === true) return;
// hide controls
this.hideControls();
// hide settings if present
if(this.element.down('.wysija_settings') !== undefined) {
this.element.down('.wysija_settings').hide();
}
}.bind(this));
// setup click event for remove button
this.removeButton = this.controls.down('.remove') || null;
if(this.removeButton !== null) {
this.removeButton.observe('click', function() {
this.removeBlock();
this.removeButton.stopObserving('click');
}.bind(this));
}
// setup click event for settings button
this.settingsButton = this.element.down('.settings') || null;
if(this.settingsButton !== null) {
this.settingsButton.observe('click', function(event) {
// TODO: refactor
var block = $(event.target).up('.mailpoet_form_block') || null;
if(block !== null) {
var field = WysijaForm.getFieldData(block);
this.editSettings();
}
}.bind(this));
}
}
return this;
},
removeBlock: function(callback) {
info('block -> removeBlock');
// save block in history
WysijaHistory.enqueue(this.element);
Effect.Fade(this.element.identify(), {
duration: 0.2,
afterFinish: function(effect) {
if(effect.element.next('.mailpoet_form_block') !== undefined && callback !== false) {
// show controls of next block to allow mass delete
WysijaForm.get(effect.element.next('.mailpoet_form_block')).block.showControls();
}
// remove placeholder
if(effect.element.previous('.block_placeholder') !== undefined) {
effect.element.previous('.block_placeholder').remove();
}
// remove element from the DOM
this.element.remove();
// reset block positions
WysijaForm.setBlockPositions();
// toggle widgets
WysijaForm.toggleWidgets();
// optional callback execution after completely removing block
if(callback !== undefined && typeof(callback) === 'function') {
callback();
}
// remove block instance
delete WysijaForm.instances[this.element.identify()];
}.bind(this)
});
}
});
/* Invoked on item dropped */
WysijaForm.Block.create = function(block, target) {
if($('form_template_'+block.type) === null) {
return false;
}
var body = $(WysijaForm.options.body),
block_template = Handlebars.compile($('form_template_block').innerHTML),
template = Handlebars.compile($('form_template_'+block.type).innerHTML),
output = '';
// set block template (depending on the block type)
block.template = template(block);
output = block_template(block);
// check if the new block is unique and if there's already an instance
// of it in the history. If so, remove its former instance from the history
if(block.unique === 1) {
WysijaHistory.remove(block.field);
}
// if the drop target was the bottom placeholder
if(target.identify() === 'block_placeholder') {
// insert block at the bottom
body.insert(output);
//block = body.childElements().last();
} else {
// insert block before the drop target
target.insert({before: output });
//block = target.previous('.mailpoet_form_block');
}
// refresh sortable items
WysijaForm.makeSortable();
// refresh block positions
WysijaForm.setBlockPositions();
// position settings
WysijaForm.setSettingsPosition();
};
document.observe('wjfe:item:drop', function(event) {
info('create block');
WysijaForm.Block.create(event.memo, event.target);
// hide block controls
info('hide controls');
WysijaForm.hideBlockControls();
// toggle widgets
setTimeout(function() {
WysijaForm.toggleWidgets();
}, 1);
});
/* Form Widget */
WysijaForm.Widget = Class.create(WysijaForm.Block, {
initialize: function(element) {
info('widget -> init');
this.element = $(element);
return this;
},
setup: function() {
info('widget -> setup');
this.setupControls();
},
save: function() {
info('widget -> save');
var data = this.getData();
if(data.element !== undefined) {
delete data.element;
}
return data;
},
setData: function(data) {
var current_data = this.getData(),
params = $H(current_data.params).merge(data.params).toObject();
// update type if it changed
if(data.type !== undefined && data.type !== current_data.type) {
this.element.writeAttribute('wysija_type', data.type);
}
// update params
this.element.writeAttribute('wysija_params', JSON.stringify(params));
},
getData: function() {
var data = WysijaForm.getFieldData(this.element);
// decode params
if(data.params.length > 0) {
data.params = JSON.parse(data.params);
}
return data;
},
getControls: function() {
return this.element.down('.wysija_controls');
},
remove: function() {
this.removeBlock();
},
redraw: function(data) {
// set parameters
this.setData(data);
var options = this.getData();
// redraw block
var block_template = Handlebars.compile($('form_template_block').innerHTML),
template = Handlebars.compile($('form_template_'+options.type).innerHTML),
data = $H(options).merge({ template: template(options) }).toObject();
this.element.replace(block_template(data));
WysijaForm.init();
},
editSettings: function() {
MailPoet.Modal.popup({
title: 'Edit field settings', // TODO: translate!
template: jQuery('#form_template_field_settings').html(),
data: this.getData(),
onSuccess: function() {
var data = jQuery('#form_field_settings').serializeObject();
this.redraw(data);
}.bind(this)
});
},
getSettings: function() {
return this.element.down('.wysija_settings');
}
});
/* When dom is loaded, initialize WysijaForm */
document.observe('dom:loaded', WysijaForm.init);
/* LOGGING */
function info(value) {
if(WysijaForm.options.debug === false) return;
if(!(window.console && console.log)) {
(function() {
var noop = function() {};
var methods = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'markTimeline', 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
var length = methods.length;
var console = window.console = {};
while(length--) {
console[methods[length]] = noop;
}
}());
}
try {
console.log('[DEBUG] '+value);
} catch(e) {}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,191 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Link } from 'react-router'
import Listing from 'listing/listing.jsx'
import classNames from 'classnames'
import MailPoet from 'mailpoet'
const columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were moved to the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a>
);
}
},
{
name: 'duplicate_form',
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'forms',
action: 'duplicate',
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('Form "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
}
},
{
name: 'trash'
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const FormList = React.createClass({
createForm() {
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
}
});
},
renderItem(form, actions) {
let row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, form.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
return (
<div>
<td className={ row_classes }>
<strong>
<a>{ form.name }</a>
</strong>
{ actions }
</td>
<td className="column-format" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr>
</td>
</div>
);
},
render() {
return (
<div>
<h2 className="title">
Forms <a
className="add-new-h2"
href="javascript:;"
onClick={ this.createForm }
>New</a>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="forms"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
module.exports = FormList;

View File

@ -0,0 +1,3 @@
define([], function() {
!function(e,o,n){window.HSCW=o,window.HS=n,n.beacon=n.beacon||{};var t=n.beacon;t.userConfig={},t.readyQueue=[],t.config=function(e){this.userConfig=e},t.ready=function(e){this.readyQueue.push(e)},o.config={docs:{enabled:!1,baseUrl:""},contact:{enabled:!0,formId:"e5c408c7-895e-11e5-9e75-0a7d6919297d"}};var r=e.getElementsByTagName("script")[0],c=e.createElement("script");c.type="text/javascript",c.async=!0,c.src="https://djtflbt20bdde.cloudfront.net/",r.parentNode.insertBefore(c,r)}(document,window.HSCW||{},window.HS||{});
});

View File

@ -15,16 +15,16 @@ function(
this.setState({
action: e.target.value,
extra: false
});
}, function() {
var action = this.getSelectedAction();
var action = this.getSelectedAction();
// action on select callback
if(action !== null && action['onSelect'] !== undefined) {
this.setState({
extra: action.onSelect(e)
});
}
// action on select callback
if(action !== null && action['onSelect'] !== undefined) {
this.setState({
extra: action.onSelect(e)
});
}
}.bind(this));
},
handleApplyAction: function(e) {
e.preventDefault();
@ -45,12 +45,13 @@ function(
data.action = this.state.action;
var callback = function() {};
if(action['onSuccess'] !== undefined) {
data.onSuccess = action.onSuccess;
callback = action.onSuccess;
}
if(data.action) {
this.props.onBulkAction(selected_ids, data);
this.props.onBulkAction(selected_ids, data, callback);
}
this.setState({
@ -84,7 +85,12 @@ function(
Select bulk action
</label>
<select ref="action" value={ this.state.action } onChange={this.handleChangeAction}>
<select
name="bulk_actions"
ref="action"
value={ this.state.action }
onChange={this.handleChangeAction}
>
<option value="">Bulk Actions</option>
{ this.props.bulk_actions.map(function(action, index) {
return (

View File

@ -1,57 +1,66 @@
define([
'react'
'react',
'jquery'
],
function(
React
React,
jQuery
) {
var ListingFilters = React.createClass({
handleFilterAction: function() {
var filters = this.props.filters.map(function(filter, index) {
var value = this.refs['filter-'+index].value;
if(value) {
return {
'name': filter.name,
'value': value
};
}
}.bind(this));
let filters = {}
this.getAvailableFilters().map((filter, i) => {
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value
})
return this.props.onSelectFilter(filters);
},
handleChangeAction: function() {
return true;
getAvailableFilters: function() {
let filters = this.props.filters;
return Object.keys(filters).filter(function(filter) {
return !(
filters[filter].length === 0
|| (
filters[filter].length === 1
&& !filters[filter][0].value
)
);
})
},
render: function() {
var filters = this.props.filters
.filter(function(filter) {
return !(
filter.options.length === 0
|| (
filter.options.length === 1
&& !filter.options[0].value
)
);
})
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 }
onChange={ this.handleChangeAction }>
{ filter.options.map(function(option, j) {
return (
<option
value={ option.value }
key={ 'filter-option-' + j }
>{ option.label }</option>
);
}.bind(this)) }
ref={ `filter-${i}` }
key={ `filter-${i}` }
name={ filter }
defaultValue={ default_value }
>
{ filters[filter].map(function(option, j) {
return (
<option
value={ option.value }
key={ 'filter-option-' + j }
>{ option.label }</option>
);
}.bind(this)) }
</select>
);
}.bind(this));
var button = false;
let button = false;
if(filters.length > 0) {
if(available_filters.length > 0) {
button = (
<input
onClick={ this.handleFilterAction }
@ -63,7 +72,7 @@ function(
return (
<div className="alignleft actions actions">
{ filters }
{ available_filters }
{ button }
</div>
);

View File

@ -9,6 +9,9 @@ define(['react', 'classnames'], function(React, classNames) {
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}
@ -29,6 +32,7 @@ define(['react', 'classnames'], function(React, classNames) {
</label>
<input
type="checkbox"
name="select_all"
ref="toggle"
checked={ this.props.selection }
onChange={ this.handleSelectItems } />

View File

@ -34,11 +34,9 @@ define(
};
},
handleSelectItem: function(e) {
var is_checked = jQuery(e.target).is(':checked');
this.props.onSelectItem(
parseInt(e.target.value, 10),
is_checked
e.target.checked
);
return !e.target.checked;
@ -46,8 +44,11 @@ define(
handleRestoreItem: function(id) {
this.props.onRestoreItem(id);
},
handleDeleteItem: function(id, confirm = false) {
this.props.onDeleteItem(id, confirm);
handleTrashItem: function(id) {
this.props.onTrashItem(id);
},
handleDeleteItem: function(id) {
this.props.onDeleteItem(id);
},
handleToggleItem: function(id) {
this.setState({ toggled: !this.state.toggled });
@ -58,11 +59,12 @@ define(
if(this.props.is_selectable === true) {
checkbox = (
<th className="check-column" scope="row">
<label className="screen-reader-text">
{ 'Select ' + this.props.item.email }</label>
<label className="screen-reader-text">{
'Select ' + this.props.item[this.props.columns[0].name]
}</label>
<input
type="checkbox"
defaultValue={ this.props.item.id }
value={ this.props.item.id }
checked={
this.props.item.selected || this.props.selection === 'all'
}
@ -77,12 +79,59 @@ define(
if(custom_actions.length > 0) {
item_actions = custom_actions.map(function(action, index) {
return (
<span key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item.id) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
if(action.display !== undefined) {
if(action.display(this.props.item) === false) {
return;
}
}
if(action.name === 'trash') {
return (
<span key={ 'action-'+index } className="trash">
{(index > 0) ? ' | ' : ''}
<a
href="javascript:;"
onClick={ this.handleTrashItem.bind(
null,
this.props.item.id
) }>
Trash
</a>
</span>
);
} else if(action.refresh) {
return (
<span
onClick={ this.props.onRefreshItems }
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
{ action.link(this.props.item) }
</span>
);
} else if(action.link) {
return (
<span
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
{ action.link(this.props.item) }
</span>
);
} else {
return (
<span
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
<a href="javascript:;" onClick={
(action.onClick !== undefined)
? action.onClick.bind(null,
this.props.item,
this.props.onRefreshItems
)
: false
}>{ action.label }</a>
</span>
);
}
}.bind(this));
} else {
item_actions = (
@ -112,8 +161,7 @@ define(
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
null,
this.props.item.id,
true
this.props.item.id
)}
>Delete permanently</a>
</span>
@ -130,18 +178,6 @@ define(
<div>
<div className="row-actions">
{ item_actions }
{ ' | ' }
<span className="trash">
<a
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
null,
this.props.item.id,
false
) }>
Trash
</a>
</span>
</div>
<button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
@ -222,7 +258,7 @@ define(
</td>
</tr>
{this.props.items.map(function(item) {
{this.props.items.map(function(item, index) {
item.id = parseInt(item.id, 10);
item.selected = (this.props.selected_ids.indexOf(item.id) !== -1);
@ -233,11 +269,13 @@ define(
onRenderItem={ this.props.onRenderItem }
onDeleteItem={ this.props.onDeleteItem }
onRestoreItem={ this.props.onRestoreItem }
onTrashItem={ this.props.onTrashItem }
onRefreshItems={ this.props.onRefreshItems }
selection={ this.props.selection }
is_selectable={ this.props.is_selectable }
item_actions={ this.props.item_actions }
group={ this.props.group }
key={ 'item-' + item.id }
key={ `item-${item.id}-${index}` }
item={ item } />
);
}.bind(this))}
@ -248,6 +286,9 @@ define(
});
var Listing = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
loading: false,
@ -260,43 +301,148 @@ define(
items: [],
groups: [],
group: 'all',
filters: [],
filter: [],
filters: {},
filter: {},
selected_ids: [],
selection: false
};
},
componentDidUpdate: function(prevProps, prevState) {
// reset group to "all" if trash gets emptied
if(
// we were viewing the trash
(prevState.group === 'trash' && prevState.count > 0)
&&
// we are still viewing the trash but there are no items left
(this.state.group === 'trash' && this.state.count === 0)
&&
// only do this when no filter is set
(Object.keys(this.state.filter).length === 0)
) {
this.handleGroup('all');
}
},
getParam: function(param) {
var regex = /(.*)\[(.*)\]/
var matches = regex.exec(param)
return [matches[1], matches[2]]
},
initWithParams: function(params) {
let state = this.state || {}
let original_state = state
// check for url params
if(params.splat !== undefined) {
params.splat.split('/').map(param => {
let [key, value] = this.getParam(param);
switch(key) {
case 'filter':
let filters = {}
value.split('&').map(function(pair) {
let [k, v] = pair.split('=')
filters[k] = v
}
)
state.filter = filters
break;
default:
state[key] = value
}
})
}
if(this.props.limit !== undefined) {
state.limit = Math.abs(~~this.props.limit);
}
this.setState(state, function() {
this.getItems();
}.bind(this));
},
setParams: function() {
var params = Object.keys(this.state)
.filter(key => {
return (
[
'group',
'filter',
'search',
'page',
'sort_by',
'sort_order'
].indexOf(key) !== -1
)
})
.map(key => {
let value = this.state[key]
if(value === Object(value)) {
value = jQuery.param(value)
} else if(value === Boolean(value)) {
value = value.toString()
}
if(value !== '') {
return `${key}[${value}]`
}
})
.filter(key => { return (key !== undefined) })
.join('/');
params = '/'+params
if(this.props.location) {
if(this.props.location.pathname !== params) {
this.history.pushState(null, `${params}`)
}
}
},
componentDidMount: function() {
this.getItems();
if(this.isMounted()) {
const params = this.props.params || {}
this.initWithParams(params)
if(this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) {
this.getItems();
}.bind(this));
}
}
},
componentWillReceiveProps: function(nextProps) {
const params = nextProps.params || {}
this.initWithParams(params)
},
getItems: function() {
this.setState({ loading: true });
if(this.isMounted()) {
this.setState({ loading: true });
this.clearSelection();
this.clearSelection();
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'listing',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
group: this.state.group,
filter: this.state.filter,
search: this.state.search,
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
}
}).done(function(response) {
if(this.isMounted()) {
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'listing',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
group: this.state.group,
filter: this.state.filter,
search: this.state.search,
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
}
}).done(function(response) {
this.setState({
items: response.items || [],
filters: response.filters || [],
filters: response.filters || {},
groups: response.groups || [],
count: response.count || 0,
loading: false
});
}
}.bind(this));
}, function() {
if(this.props['onGetItems'] !== undefined) {
this.props.onGetItems(
~~(this.state.groups[0]['count'])
);
}
}.bind(this));
}.bind(this));
}
},
handleRestoreItem: function(id) {
this.setState({
@ -318,7 +464,27 @@ define(
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id, confirm = false) {
handleTrashItem: function(id) {
this.setState({
loading: true,
page: 1
});
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'trash',
data: id
}).done(function(response) {
if(
this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined
) {
this.props.messages.onTrash(response);
}
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id) {
this.setState({
loading: true,
page: 1
@ -327,31 +493,18 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'delete',
data: {
id: id,
confirm: confirm
}
data: id
}).done(function(response) {
if(confirm === true) {
if(
this.props.messages !== undefined
&& this.props.messages['onConfirmDelete'] !== undefined
) {
this.props.messages.onConfirmDelete(response);
}
} else {
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
this.getItems();
}.bind(this));
},
handleBulkAction: function(selected_ids, params) {
handleBulkAction: function(selected_ids, params, callback) {
if(
this.state.selection === false
&& this.state.selected_ids.length === 0
@ -362,12 +515,6 @@ define(
this.setState({ loading: true });
var data = params || {};
var callback = ((data['onSuccess'] !== undefined)
? data['onSuccess']
: function() {}
);
delete data.onSuccess;
data.listing = {
offset: 0,
limit: 0,
@ -379,7 +526,7 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'bulk_action',
action: 'bulkAction',
data: data
}).done(function(response) {
this.getItems();
@ -393,6 +540,7 @@ define(
selection: false,
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -401,6 +549,7 @@ define(
sort_by: sort_by,
sort_order: sort_order,
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -460,6 +609,7 @@ define(
filter: filters,
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -469,10 +619,11 @@ define(
this.setState({
group: group,
filter: [],
filter: {},
search: '',
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -482,6 +633,7 @@ define(
selection: false,
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -489,21 +641,18 @@ define(
var render = this.props.onRenderItem(item, actions);
return render.props.children;
},
handleRefreshItems: function() {
this.getItems();
},
render: function() {
var items = this.state.items,
sort_by = this.state.sort_by,
sort_order = this.state.sort_order;
// set sortable columns
columns = this.props.columns.map(function(column) {
column.sorted = (column.name === sort_by) ? sort_order : false;
return column;
});
// bulk actions
var bulk_actions = this.props.bulk_actions || [];
if(this.state.group === 'trash') {
if(this.state.group === 'trash' && bulk_actions.length > 0) {
bulk_actions = [
{
name: 'restore',
@ -511,12 +660,9 @@ define(
onSuccess: this.props.messages.onRestore
},
{
name: 'trash',
name: 'delete',
label: 'Delete permanently',
onSuccess: this.props.messages.onConfirmDelete,
getData: function() {
return { confirm: true };
}
onSuccess: this.props.messages.onDelete
}
];
}
@ -524,22 +670,42 @@ define(
// item actions
var item_actions = this.props.item_actions || [];
var tableClasses = classNames(
var table_classes = classNames(
'mailpoet_listing_table',
'wp-list-table',
'widefat',
'fixed',
'striped',
{ 'mailpoet_listing_loading': this.state.loading }
);
// search
var search = (
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search }
/>
);
if(this.props.search === false) {
search = false;
}
// groups
var groups = (
<ListingGroups
groups={ this.state.groups }
group={ this.state.group }
onSelectGroup={ this.handleGroup }
/>
);
if(this.props.groups === false) {
groups = false;
}
return (
<div>
<ListingGroups
groups={ this.state.groups }
group={ this.state.group }
onSelectGroup={ this.handleGroup } />
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search } />
{ groups }
{ search }
<div className="tablenav top clearfix">
<ListingBulkActions
bulk_actions={ bulk_actions }
@ -548,7 +714,7 @@ define(
onBulkAction={ this.handleBulkAction } />
<ListingFilters
filters={ this.state.filters }
filter={ this.state.filter }
filter={ this.state.filter }
onSelectFilter={ this.handleFilter } />
<ListingPages
count={ this.state.count }
@ -556,7 +722,7 @@ define(
limit={ this.state.limit }
onSetPage={ this.handleSetPage } />
</div>
<table className={ tableClasses }>
<table className={ table_classes }>
<thead>
<ListingHeader
onSort={ this.handleSort }
@ -572,6 +738,8 @@ define(
onRenderItem={ this.handleRenderItem }
onDeleteItem={ this.handleDeleteItem }
onRestoreItem={ this.handleRestoreItem }
onTrashItem={ this.handleTrashItem }
onRefreshItems={ this.handleRefreshItems }
columns={ this.props.columns }
is_selectable={ bulk_actions.length > 0 }
onSelectItem={ this.handleSelectItem }

View File

@ -40,23 +40,23 @@ define(['react', 'classnames'], function(React, classNames) {
},
render: function() {
if(this.props.count === 0) {
return (<div></div>);
return false;
} else {
var pagination,
firstPage = (
var pagination = false;
var firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span>
),
previousPage = (
);
var previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
nextPage = (
);
var nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
lastPage = (
);
var lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span>
);
if(this.props.count > this.props.limit) {
if(this.props.limit > 0 && this.props.count > this.props.limit) {
if(this.props.page > 1) {
previousPage = (
<a href="javascript:;"
@ -104,6 +104,7 @@ define(['react', 'classnames'], function(React, classNames) {
pagination = (
<span className="pagination-links">
{firstPage}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
@ -128,6 +129,7 @@ define(['react', 'classnames'], function(React, classNames) {
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
@ -140,7 +142,7 @@ define(['react', 'classnames'], function(React, classNames) {
return (
<div className={ classes }>
<span className="displaying-num">{ this.props.count } item(s)</span>
<span className="displaying-num">{ this.props.count } items</span>
{ pagination }
</div>
);

View File

@ -7,26 +7,33 @@ define(['react'], function(React) {
this.refs.search.value
);
},
componentWillReceiveProps: function(nextProps) {
this.refs.search.value = nextProps.search
},
render: function() {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
className="button" />
</p>
</form>
);
if(this.props.search === false) {
return false;
} else {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
className="button" />
</p>
</form>
);
}
}
});

View File

@ -47,7 +47,6 @@ define([
autoScroll: true,
onstart: function(event) {
console.log('Drag start', event, this);
if (that.options.cloneOriginal === true) {
// Use substitution instead of a clone

View File

@ -11,10 +11,10 @@ define([
'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'newsletter_editor/components/wordpress',
'newsletter_editor/components/communication',
'underscore',
'jquery'
], function(App, BaseBlock, ButtonBlock, DividerBlock, WordpressComponent, _, jQuery) {
], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) {
"use strict";
@ -69,9 +69,9 @@ define([
},
fetchPosts: function() {
var that = this;
WordpressComponent.getTransformedPosts(this.toJSON()).done(function(content) {
console.log('ALC fetched', arguments);
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged');
}).fail(function(error) {
console.log('ALC fetchPosts error', arguments);
});
@ -100,6 +100,11 @@ define([
toolsRegion: '.mailpoet_tools',
postsRegion: '.mailpoet_automated_latest_content_block_posts',
},
modelEvents: _.extend(
_.omit(base.BlockView.prototype.modelEvents, 'change'),
{
'postsChanged': 'render',
}),
events: _.extend(base.BlockView.prototype.events, {
'click .mailpoet_automated_latest_content_block_overlay': 'showSettings',
}),
@ -161,95 +166,76 @@ define([
var that = this;
// Dynamically update available post types
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
multiple: true,
allowClear: true,
query: function(options) {
var taxonomies = [];
// Delegate data loading to our own endpoints
WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
ajax: {
data: function (params) {
return {
term: params.term
};
},
transport: function(options, success, failure) {
var taxonomies,
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({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
});
return promise;
});
promise.then(success);
promise.fail(failure);
return promise;
}).done(function(args) {
},
processResults: function(data) {
// Transform taxonomies and terms into select2 compatible format
options.callback({
return {
results: _.map(
args.terms,
data.terms,
function(item) {
return _.defaults({
text: args.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id
}, item);
}
)
});
});
};
},
},
initSelection: function(element, callback) {
// On external data load tell select2 which terms to preselect
callback(_.map(
that.model.get('terms').toJSON(),
function(item) {
return {
id: item.id,
text: item.text,
};
}
));
}).on({
'select2:select': function(event) {
var terms = that.model.get('terms');
terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' ).on({
'change': function(e){
var data = jQuery(this).data('selected');
if (typeof data === 'string') {
if (data === '') {
data = [];
} else {
data = JSON.parse(data);
}
}
if ( e.added ){
data.push(e.added);
} else {
data = _.filter(data, function(item) {
return item.id !== e.removed.id;
});
}
// Update ALC model
that.model.set('terms', data);
jQuery(this).data('selected', JSON.stringify(data));
}
});
},
onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
// Force close select2 if it hasn't closed yet
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2('close');
'select2:unselect': function(event) {
var terms = that.model.get('terms');
terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' );
},
toggleDisplayOptions: function(event) {
var el = this.$('.mailpoet_automated_latest_content_display_options'),
showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
if (el.hasClass('mailpoet_hidden')) {
el.removeClass('mailpoet_hidden');
if (el.hasClass('mailpoet_closed')) {
el.removeClass('mailpoet_closed');
showControl.addClass('mailpoet_hidden');
} else {
el.addClass('mailpoet_hidden');
el.addClass('mailpoet_closed');
showControl.removeClass('mailpoet_hidden');
}
},
@ -325,7 +311,7 @@ define([
_.each(postTypes, function(type) {
select.append(jQuery('<option>', {
value: type.name,
text: type.labels.singular_name,
text: type.label,
}));
});
select.val(selectedValue);

View File

@ -48,6 +48,7 @@ define([
},
modelEvents: {
'change': 'render',
'delete': 'deleteBlock',
},
events: {
"mouseenter": "showTools",
@ -88,7 +89,9 @@ define([
this.$el.addClass('mailpoet_editor_view_' + this.cid);
},
initialize: function() {
this.on('showSettings', this.showSettings);
this.on('showSettings', this.showSettings, this);
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
showTools: function(_event) {
if (!this.showingToolsDisabled) {
@ -114,12 +117,49 @@ define([
* Defines drop behavior of BlockView instance
*/
getDropFunc: function() {
var that = this;
return function() {
var newModel = that.model.clone();
//that.model.destroy();
return newModel;
};
return this.model.clone();
}.bind(this);
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().then(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('slideDown', 'fadeIn', 'easeIn');
},
transitionOut: function() {
return this._transition('slideUp', 'fadeOut', 'easeOut');
},
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
return promise;
},
});
@ -168,24 +208,22 @@ define([
},
deleteBlock: function(event) {
event.preventDefault();
this.model.destroy();
this.model.trigger('delete');
return false;
}
},
});
Module.BlockSettingsView = Marionette.LayoutView.extend({
className: 'mailpoet_editor_settings',
initialize: function() {
var that = this;
MailPoet.Modal.panel({
element: this.$el,
template: '',
position: 'right',
width: App.getConfig().get('sidepanelWidth'),
onCancel: function() {
that.destroy();
},
this.destroy();
}.bind(this),
});
},
close: function(event) {
@ -203,6 +241,9 @@ define([
changeBoolField: function(field, event) {
this.model.set(field, (jQuery(event.target).val() === 'true') ? true : false);
},
changeBoolCheckboxField: function(field, event) {
this.model.set(field, (!!jQuery(event.target).prop('checked')));
},
changeColorField: function(field, event) {
var value = jQuery(event.target).val();
if (value === '') {

View File

@ -42,16 +42,12 @@ define([
Module.ButtonBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_button_block mailpoet_droppable_block",
getTemplate: function() { return templates.buttonBlock; },
modelEvents: {
'change': 'render',
},
onDragSubstituteBy: function() { return Module.ButtonWidgetView; },
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
var that = this;
// Listen for attempts to change all dividers in one go
this._replaceButtonStylesHandler = function(data) { that.model.set(data); };
this._replaceButtonStylesHandler = function(data) { this.model.set(data); }.bind(this);
App.getChannel().on('replaceAllButtonStyles', this._replaceButtonStylesHandler);
},
onRender: function() {

View File

@ -75,7 +75,8 @@ define([
getEmptyView: function() { return Module.ContainerBlockEmptyView; },
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; },
modelEvents: {
'change': 'render'
'change': 'render',
'delete': 'deleteBlock',
},
events: {
"mouseenter": "showTools",
@ -136,6 +137,8 @@ define([
},
initialize: function(options) {
this.renderOptions = _.defaults(options.renderOptions || {}, {});
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
// Determines which view type should be used for a child
getChildView: function(model) {
@ -162,10 +165,10 @@ define([
this.toolsView = new Module.ContainerBlockToolsView({
model: this.model,
tools: {
settings: this.renderOptions.depth > 1,
settings: this.renderOptions.depth === 1,
delete: this.renderOptions.depth === 1,
move: this.renderOptions.depth === 1,
layerSelector: this.renderOptions.depth === 1,
layerSelector: false,
},
});
this.toolsRegion.show(this.toolsView);
@ -229,12 +232,49 @@ define([
_.extend(this, this._buildRegions(this.regions));
},
getDropFunc: function() {
var that = this;
return function() {
var newModel = that.model.clone();
that.model.destroy();
return newModel;
};
return this.model.clone();
}.bind(this);
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('slideDown', 'fadeIn', 'easeIn');
},
transitionOut: function() {
return this._transition('slideUp', 'fadeOut', 'easeOut');
},
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
return promise;
},
});
@ -265,6 +305,41 @@ define([
behaviors: {
ColorPickerBehavior: {},
},
regions: {
columnsSettingsRegion: '.mailpoet_container_columns_settings',
},
initialize: function() {
base.BlockSettingsView.prototype.initialize.apply(this, arguments);
this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({
collection: this.model.get('blocks'),
});
},
onRender: function() {
this.columnsSettingsRegion.show(this._columnsSettingsView);
},
});
Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
getChildView: function() { return Module.ContainerBlockColumnSettingsView; },
childViewOptions: function(model, index) {
return {
columnIndex: index,
};
},
});
Module.ContainerBlockColumnSettingsView = Marionette.ItemView.extend({
getTemplate: function() { return templates.containerBlockColumnSettings; },
initialize: function(options) {
this.columnNumber = (options.columnIndex || 0) + 1;
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
columnNumber: this.columnNumber,
};
},
});
Module.OneColumnContainerWidgetView = base.WidgetView.extend({

View File

@ -57,11 +57,9 @@ define([
this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
viewCid: this.cid,
return _.extend({
totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px',
};
}, base.BlockView.prototype.templateHelpers.apply(this));
},
onRender: function() {
this.toolsView = new Module.DividerBlockToolsView({ model: this.model });

View File

@ -39,9 +39,9 @@ define([
Module.FooterBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_footer_block mailpoet_droppable_block",
getTemplate: function() { return templates.footerBlock; },
modelEvents: {
modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
},
}, base.BlockView.prototype.modelEvents),
onDragSubstituteBy: function() { return Module.FooterWidgetView; },
onRender: function() {
this.toolsView = new Module.FooterBlockToolsView({ model: this.model });

View File

@ -39,9 +39,9 @@ define([
Module.HeaderBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_header_block mailpoet_droppable_block",
getTemplate: function() { return templates.headerBlock; },
modelEvents: {
modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
},
}, base.BlockView.prototype.modelEvents),
onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
onRender: function() {
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });

View File

@ -37,11 +37,9 @@ define([
getTemplate: function() { return templates.imageBlock; },
onDragSubstituteBy: function() { return Module.ImageWidgetView; },
templateHelpers: function() {
return {
model: this.model.toJSON(),
viewCid: this.cid,
return _.extend({
imageMissingSrc: App.getConfig().get('urls.imageMissing'),
};
}, base.BlockView.prototype.templateHelpers.apply(this));
},
onRender: function() {
this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
@ -66,7 +64,7 @@ define([
"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"),
"change .mailpoet_field_image_padded": _.partial(this.changeBoolField, "padded"),
"change .mailpoet_field_image_padded": _.partial(this.changeBoolCheckboxField, "padded"),
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"click .mailpoet_field_image_select_another_image": "showMediaManager",
"click .mailpoet_done_editing": "close",
@ -297,9 +295,9 @@ define([
// Following advice from Becs, the target width should
// be a double of one column width to render well on
// retina screen devices
targetImageWidth = 1200,
targetImageWidth = 1320,
// For main image use the size, that's closest to being 600px in width
// For main image use the size, that's closest to being 660px in width
sizeKeys = _.keys(sizes),
// Pick the width that is closest to target width

View File

@ -18,11 +18,12 @@ define([
'jquery',
'mailpoet',
'newsletter_editor/App',
'newsletter_editor/components/wordpress',
'newsletter_editor/components/communication',
'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) {
'newsletter_editor/blocks/divider',
'select2'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, CommunicationComponent, BaseBlock, ButtonBlock, DividerBlock) {
"use strict";
@ -30,7 +31,7 @@ define([
base = BaseBlock;
Module.PostsBlockModel = base.BlockModel.extend({
stale: ['_selectedPosts', '_availablePosts'],
stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
defaults: function() {
return this._getDefaults({
type: 'posts',
@ -62,6 +63,7 @@ define([
divider: {},
_selectedPosts: [],
_availablePosts: [],
_transformedPosts: new (App.getBlockTypeModel('container'))(),
}, App.getConfig().get('blockDefaults.posts'));
},
relations: function() {
@ -70,21 +72,31 @@ define([
divider: App.getBlockTypeModel('divider'),
_selectedPosts: Backbone.Collection,
_availablePosts: Backbone.Collection,
_transformedPosts: App.getBlockTypeModel('container'),
};
},
initialize: function() {
var that = this;
var that = this,
POST_REFRESH_DELAY_MS = 500,
refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS),
refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
// Attach Radio.Requests API primarily for highlighting
_.extend(this, Radio.Requests);
this.fetchAvailablePosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', this._scheduleFetchAvailablePosts, this);
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
this.on('change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
this.on('insertSelectedPosts', this._insertSelectedPosts, this);
},
fetchAvailablePosts: function() {
var that = this;
WordpressComponent.getPosts(this.toJSON()).done(function(posts) {
console.log('Posts fetched', arguments);
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) {
that.get('_availablePosts').reset(posts);
that.get('_selectedPosts').reset(); // Empty out the collection
that.trigger('change:_availablePosts');
@ -92,20 +104,22 @@ define([
console.log('Posts fetchPosts error', arguments);
});
},
/**
* Batch more changes during a specific time, instead of fetching
* ALC posts on each model change
*/
_scheduleFetchAvailablePosts: function() {
var timeout = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
_refreshTransformedPosts: function() {
var that = this,
data = this.toJSON();
data.posts = this.get('_selectedPosts').pluck('ID');
if (data.posts.length === 0) {
this.get('_transformedPosts.blocks').reset();
return;
}
this._fetchPostsTimer = setTimeout(function() {
that.fetchAvailablePosts();
that._fetchPostsTimer = undefined;
}, timeout);
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments);
});
},
_insertSelectedPosts: function() {
var that = this,
@ -117,8 +131,7 @@ define([
if (data.posts.length === 0) return;
WordpressComponent.getTransformedPosts(data).done(function(posts) {
console.log('Available posts fetched', arguments);
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
collection.add(posts, { at: index });
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
@ -129,10 +142,14 @@ define([
Module.PostsBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_posts_block mailpoet_droppable_block",
getTemplate: function() { return templates.postsBlock; },
modelEvents: {},
modelEvents: {}, // Forcefully disable all events
regions: _.extend({
postsRegion: '.mailpoet_posts_block_posts',
}, base.BlockView.prototype.regions),
onDragSubstituteBy: function() { return Module.PostsWidgetView; },
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
this.toolsView = new Module.PostsBlockToolsView({ model: this.model });
this.model.reply('blockView', this.notifyAboutSelf, this);
},
@ -141,6 +158,13 @@ define([
this.toolsRegion.show(this.toolsView);
}
this.trigger('showSettings');
var ContainerView = App.getBlockTypeView('container'),
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
};
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
},
notifyAboutSelf: function() {
return this;
@ -196,8 +220,8 @@ define([
},
switchToDisplayOptions: function() {
// Switch content view
this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_closed');
this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_closed');
// Switch controls
this.$('.mailpoet_settings_posts_show_display_options').addClass('mailpoet_hidden');
@ -205,8 +229,8 @@ define([
},
switchToPostSelection: function() {
// Switch content view
this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_closed');
this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_closed');
// Switch controls
this.$('.mailpoet_settings_posts_show_post_selection').addClass('mailpoet_hidden');
@ -215,6 +239,7 @@ define([
insertPosts: function() {
this.model.trigger('insertSelectedPosts');
this.model.destroy();
this.close();
},
});
@ -244,69 +269,67 @@ define([
var that = this;
// Dynamically update available post types
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
this.$('.mailpoet_posts_categories_and_tags').select2({
multiple: true,
allowClear: true,
query: function(options) {
var taxonomies = [];
// Delegate data loading to our own endpoints
WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
ajax: {
data: function (params) {
return {
term: params.term
};
},
transport: function(options, success, failure) {
var taxonomies,
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({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
});
return promise;
});
promise.then(success);
promise.fail(failure);
return promise;
}).done(function(args) {
},
processResults: function(data) {
// Transform taxonomies and terms into select2 compatible format
options.callback({
return {
results: _.map(
args.terms,
data.terms,
function(item) {
return _.defaults({
text: args.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id
}, item);
}
)
});
});
};
},
},
}).trigger( 'change' ).on({
'change': function(e){
var data = [];
if (typeof data === 'string') {
if (data === '') {
data = [];
} else {
data = JSON.parse(data);
}
}
if ( e.added ){
data.push(e.added);
}
// Update ALC model
that.model.set('terms', data);
jQuery(this).data('selected', JSON.stringify(data));
}
});
},
onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
// Force close select2 if it hasn't closed yet
this.$('.mailpoet_posts_categories_and_tags').select2('close');
}).on({
'select2:select': function(event) {
var terms = that.model.get('terms');
terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
'select2:unselect': function(event) {
var terms = that.model.get('terms');
terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' );
},
changeField: function(field, event) {
this.model.set(field, jQuery(event.target).val());
@ -319,7 +342,7 @@ define([
_.each(postTypes, function(type) {
select.append(jQuery('<option>', {
value: type.name,
text: type.labels.singular_name,
text: type.label,
}));
});
select.val(selectedValue);

View File

@ -103,7 +103,8 @@ define([
getTemplate: function() { return templates.socialBlock; },
childViewContainer: '.mailpoet_social',
modelEvents: {
'change': 'render'
'change': 'render',
'delete': 'deleteBlock',
},
events: {
"mouseover": "showTools",
@ -145,6 +146,10 @@ define([
arguments[0].collection = arguments[0].model.get('icons');
Marionette.CompositeView.apply(this, arguments);
},
initialize: function() {
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
// Determines which view type should be used for a child
childView: SocialIconView,
templateHelpers: function() {
@ -170,12 +175,9 @@ define([
_event.stopPropagation();
},
getDropFunc: function() {
var that = this;
return function() {
var newModel = that.model.clone();
//that.model.destroy();
return newModel;
};
return this.model.clone();
}.bind(this);
},
_buildRegions: function(regions) {
var that = this;
@ -194,6 +196,46 @@ define([
this.regionManager.destroy();
_.extend(this, this._buildRegions(this.regions));
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('slideDown', 'fadeIn', 'easeIn');
},
transitionOut: function() {
return this._transition('slideUp', 'fadeOut', 'easeOut');
},
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
return promise;
},
});
Module.SocialBlockToolsView = base.BlockToolsView.extend({

View File

@ -26,6 +26,8 @@ define([
getTemplate: function() { return templates.textBlock; },
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing
initialize: function(options) {
base.BlockView.prototype.initialize.apply(this, arguments);
this.renderOptions = _.defaults(options.renderOptions || {}, {
disableTextEditor: false,
});

View File

@ -70,9 +70,10 @@ define([
};
Module.previewNewsletter = function(options) {
return Module._query({
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'preview',
options: options,
data: options || {},
});
};

View File

@ -44,10 +44,10 @@ define([
};
Module.getBody = function() {
return JSON.stringify({
return {
content: App._contentContainer.toJSON(),
globalStyles: App.getGlobalStyles().toJSON(),
});
};
};
Module.toJSON = function() {
@ -73,8 +73,7 @@ define([
});
App.on('start', function(options) {
// TODO: Other newsletter information will be needed as well.
var body = JSON.parse(options.newsletter.body);
var body = options.newsletter.body;
App._contentContainer = new (App.getBlockTypeModel('container'))(body.content, {parse: true});
App._contentContainerView = new (App.getBlockTypeView('container'))({
model: App._contentContainer,

View File

@ -1,9 +1,14 @@
define([
'newsletter_editor/App',
'mailpoet',
'notice',
'backbone',
'backbone.marionette'
], function(App, MailPoet, Backbone, Marionette) {
'backbone.marionette',
'jquery',
'blob',
'filesaver',
'html2canvas'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver, html2canvas) {
"use strict";
@ -42,13 +47,51 @@ define([
});
};
Module.getThumbnail = function(element, options) {
return html2canvas(element, options || {});
};
Module.saveTemplate = function(options) {
return MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: _.extend(options || {}, {
var that = this,
promise = jQuery.Deferred();
promise.then(function(thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody(),
}),
});
return MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: data,
});
});
Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function(thumbnail) {
promise.resolve(thumbnail);
});
return promise;
};
Module.exportTemplate = function(options) {
var that = this;
return Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function(thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody(),
});
var blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json;charset=utf-8' }
);
FileSaver.saveAs(blob, 'template.json');
});
};
@ -62,7 +105,8 @@ define([
'click .mailpoet_save_template': 'toggleSaveAsTemplate',
'click .mailpoet_save_as_template': 'saveAsTemplate',
/* Export template */
'click .mailpoet_save_export': 'exportTemplate',
'click .mailpoet_save_export': 'toggleExportTemplate',
'click .mailpoet_export_template': 'exportTemplate',
},
initialize: function(options) {
App.getChannel().on('beforeEditorSave', this.beforeSave, this);
@ -102,27 +146,87 @@ define([
},
saveAsTemplate: function() {
var templateName = this.$('.mailpoet_save_as_template_name').val(),
templateDescription = this.$('.mailpoet_save_as_template_description').val();
templateDescription = this.$('.mailpoet_save_as_template_description').val(),
that = this;
console.log('Saving template with ', templateName, templateDescription);
Module.saveTemplate({
name: templateName,
description: templateDescription,
}).done(function() {
console.log('Template saved', arguments);
}).fail(function() {
// TODO: Handle error messages
console.log('Template save failed', arguments);
});
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
{
positionAfter: that.$el,
}
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
{
positionAfter: that.$el,
}
);
} else {
console.log('Saving template with ', templateName, templateDescription);
Module.saveTemplate({
name: templateName,
description: templateDescription,
}).done(function() {
console.log('Template saved', arguments);
MailPoet.Notice.success(
App.getConfig().get('translations.templateSaved'),
{
positionAfter: that.$el,
}
);
}).fail(function() {
console.log('Template save failed', arguments);
MailPoet.Notice.error(
App.getConfig().get('translations.templateSaveFailed'),
{
positionAfter: that.$el,
}
);
});
this.hideOptionContents();
}
this.hideOptionContents();
},
toggleExportTemplate: function() {
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
this.toggleSaveOptions();
},
hideExportTemplate: function() {
this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden');
},
exportTemplate: function() {
console.log('Exporting template');
this.hideOptionContents();
var templateName = this.$('.mailpoet_export_template_name').val(),
templateDescription = this.$('.mailpoet_export_template_description').val(),
that = this;
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
{
positionAfter: that.$el,
}
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
{
positionAfter: that.$el,
}
);
} else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({
name: templateName,
description: templateDescription,
});
this.hideExportTemplate();
}
},
hideOptionContents: function() {
this.hideSaveAsTemplate();
this.hideExportTemplate();
this.$('.mailpoet_save_options').addClass('mailpoet_hidden');
},
next: function() {

View File

@ -1,12 +1,13 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
'backbone',
'backbone.marionette',
'backbone.supermodel',
'underscore',
'jquery',
'sticky-kit'
], function(App, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
"use strict";
@ -50,8 +51,33 @@ define([
},
events: {
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function(event) {
this.$el.find('.mailpoet_sidebar_region').addClass('closed');
this.$el.find(event.target).parent().parent().removeClass('closed');
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',
{
duration: 250,
easing: "easeOut",
complete: function() {
$openRegion.addClass('closed');
}.bind(this)
}
);
$targetRegion.find('.mailpoet_region_content').velocity(
'slideDown',
{
duration: 250,
easing: "easeIn",
complete: function() {
$targetRegion.removeClass('closed');
},
}
);
},
},
initialize: function(options) {
@ -90,7 +116,6 @@ define([
});
},
onDomRefresh: function() {
var that = this;
this.$el.parent().stick_in_parent({
offset_top: 32,
});
@ -169,10 +194,8 @@ define([
},
initialize: function(options) {
this.availableStyles = options.availableStyles;
var that = this;
},
onRender: function() {
var that = this;
this.$('.mailpoet_color').spectrum({
clickoutFiresChange: true,
showInput: true,
@ -229,7 +252,7 @@ define([
MailPoet.Modal.loading(true);
// TODO: Migrate logic to new AJAX format
Wordpress.previewNewsletter(data).done(function(response) {
CommunicationComponent.previewNewsletter(data).done(function(response) {
if(response.success !== undefined && response.success === true) {
MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent'));
} else if(response.error !== undefined) {

View File

@ -17,7 +17,7 @@ define([
},
h1: {
fontColor: '#111111',
fontFamily: 'Arial Black',
fontFamily: 'Arial',
fontSize: '40px'
},
h2: {
@ -72,7 +72,7 @@ define([
App.getAvailableStyles = Module.getAvailableStyles;
var body = JSON.parse(options.newsletter.body);
var body = options.newsletter.body;
this.setGlobalStyles(body.globalStyles);
});

View File

@ -21,6 +21,10 @@ define(
label: 'Subject',
sortable: true
},
{
name: 'status',
label: 'Status'
},
{
name: 'segments',
label: 'Lists'
@ -37,29 +41,176 @@ define(
}
];
var messages = {
onTrash: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter was moved to the trash.'
);
} else if(count > 1) {
message = (
'%$1d newsletters were moved to the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onDelete: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter was permanently deleted.'
);
} else if(count > 1) {
message = (
'%$1d newsletters were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter has been restored from the trash.'
);
} else if(count > 1) {
message = (
'%$1d newsletters have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
var bulk_actions = [
{
name: 'trash',
label: 'Trash'
label: 'Trash',
onSuccess: messages.onTrash
}
];
var item_actions = [
{
name: 'edit',
link: function(id) {
link: function(item) {
return (
<a href={ '?page=mailpoet-newsletter-editor&id=' + id }>
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
Edit
</a>
);
}
},
{
name: 'trash'
}
];
var NewsletterList = React.createClass({
renderItem: function(newsletter, actions) {
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 === null) {
return (
<span>Not sent yet.</span>
);
} else {
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>
Sent to {
item.queue.count_processed - item.queue.count_failed
} out of { 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) }
>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) }
>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>
);
}
},
renderItem: function(newsletter, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
@ -80,6 +231,9 @@ define(
</strong>
{ actions }
</td>
<td className="column" data-colname="Lists">
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
@ -100,11 +254,14 @@ define(
</h2>
<Listing
params={ this.props.params }
endpoint="newsletters"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions }
item_actions={ item_actions } />
item_actions={ item_actions }
messages={ messages }
auto_refresh={ true } />
</div>
);
}

View File

@ -7,6 +7,7 @@ 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.jsx'
import NewsletterNotification from 'newsletters/types/notification.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
@ -27,8 +28,10 @@ if(container) {
<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 } />
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
<Route path="send/:id" component={ NewsletterSend } />
<Route path="filter[:filter]" component={ NewsletterList } />
<Route path="*" component={ NewsletterList } />
</Route>
</Router>

View File

@ -16,7 +16,7 @@ define(
Breadcrumb
) {
var settings = window.mailpoet_settings || {};
var settings = window.mailpoet_settings || {};
var fields = [
{
@ -24,18 +24,26 @@ define(
label: 'Subject line',
tip: "Be creative! It's the first thing your subscribers see."+
"Tempt them to open your email.",
type: 'text'
type: 'text',
validation: {
'data-parsley-required': true
}
},
{
name: 'segments',
label: 'Lists',
tip: "The subscriber list that will be used for this campaign.",
label: 'Segments',
tip: "The subscriber segment that will be used for this campaign.",
type: 'selection',
placeholder: "Select a list",
placeholder: "Select a segment",
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
select2: true
filter: function(segment) {
return !!(!segment.deleted_at);
},
validation: {
'data-parsley-required': true
}
},
{
name: 'sender',
@ -43,17 +51,24 @@ define(
tip: "Name & email of yourself or your company.",
fields: [
{
name: 'from_name',
name: 'sender_name',
type: 'text',
placeholder: 'John Doe',
defaultValue: settings.from_name
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'from_email',
name: 'sender_address',
type: 'text',
placeholder: 'john.doe@email.com',
defaultValue: settings.from_address
},
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
@ -66,20 +81,25 @@ define(
{
name: 'reply_to_name',
type: 'text',
placeholder: 'John Doe'
placeholder: 'John Doe',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_email',
name: 'reply_to_address',
type: 'text',
placeholder: 'john.doe@email.com'
placeholder: 'john.doe@email.com',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
var messages = {
updated: function() {
MailPoet.Notice.success('The newsletter has been updated!');
onUpdate: function() {
MailPoet.Notice.success('Newsletter successfully updated!');
},
onCreate: function() {
MailPoet.Notice.success('Newsletter successfully added!');
}
};
@ -88,34 +108,50 @@ define(
Router.History
],
handleSend: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'send',
data: {
id: this.props.params.id,
newsletter: jQuery('#mailpoet_newsletter').serializeObject(),
segments: jQuery('#mailpoet_segments').val()
}
}).done(function(response) {
if(response === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
'The newsletter has been sent!'
);
} else {
if(response.errors) {
MailPoet.Notice.error(
response.errors.join("<br />")
if(jQuery('#mailpoet_newsletter').parsley().validate() === true) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: {
newsletter_id: this.props.params.id,
segments: jQuery('#mailpoet_segments').val(),
sender: {
'name': jQuery('#mailpoet_newsletter [name="sender_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="sender_address"]').val()
},
reply_to: {
'name': jQuery('#mailpoet_newsletter [name="reply_to_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="reply_to_address"]').val()
}
}
}).done(function(response) {
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
'The newsletter is being sent...'
);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
);
if(response.errors) {
MailPoet.Notice.error(
response.errors.join("<br />")
);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
);
}
}
}
}.bind(this));
}.bind(this));
}
},
componentDidMount: function() {
if(this.isMounted()) {
jQuery('#mailpoet_newsletter').parsley();
}
},
isValid: function() {
return (jQuery('#mailpoet_newsletter').parsley().validate());
},
render: function() {
return (
@ -129,7 +165,8 @@ define(
endpoint="newsletters"
fields={ fields }
params={ this.props.params }
messages={ messages }>
messages={ messages }
isValid={ this.isValid }>
<p className="submit">
<input

View File

@ -1,6 +1,7 @@
define(
[
'react',
'underscore',
'mailpoet',
'react-router',
'classnames',
@ -8,11 +9,67 @@ define(
],
function(
React,
_,
MailPoet,
Router,
classNames,
Breadcrumb
) {
var ImportTemplate = React.createClass({
saveTemplate: function(template) {
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: template
}).done(function(response) {
if(response === true) {
this.props.onImport(template);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
}
}.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;
reader.onload = function(e) {
try {
saveTemplate(JSON.parse(e.target.result));
} catch (err) {
MailPoet.Notice.error('This template file appears to be malformed. Please try another one.');
}
}.bind(this);
reader.readAsText(file);
},
render: function() {
return (
<div>
<h2>Import a template</h2>
<form onSubmit={this.handleSubmit}>
<input type="file" placeholder="Select a .json file to upload" ref="templateFile" />
<p className="submit">
<input
className="button button-primary"
type="submit"
value="Upload" />
</p>
</form>
</div>
);
},
});
var NewsletterTemplates = React.createClass({
mixins: [
Router.History
@ -42,7 +99,7 @@ define(
"MailPoet's Guide",
description:
"This is the standard template that comes with MailPoet.",
readonly: true
readonly: "1"
}
]
}
@ -62,11 +119,11 @@ define(
body: template.body
}
}).done(function(response) {
if(response === true) {
if(response.result === true) {
// TODO: Move this URL elsewhere
window.location = 'admin.php?page=mailpoet-newsletter-editor&id=' + this.props.params.id;
} else {
response.map(function(error) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
@ -93,6 +150,16 @@ define(
this.setState({ loading: false });
}
},
handleShowTemplate: function(template) {
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,
});
},
handleTemplateImport: function() {
this.getTemplates();
},
render: function() {
var templates = this.state.templates.map(function(template, index) {
var deleteLink = (
@ -104,11 +171,22 @@ define(
Delete
</a>
</div>
);
), thumbnail = '';
if (typeof template.thumbnail === 'string'
&& template.thumbnail.length > 0) {
thumbnail = (
<a href="javascript:;" onClick={this.handleShowTemplate.bind(null, template)}>
<img src={ template.thumbnail } />
<div className="mailpoet_overlay"></div>
</a>
);
}
return (
<li key={ 'template-'+index }>
<div className="mailpoet_thumbnail">
{ thumbnail }
</div>
<div className="mailpoet_description">
@ -132,7 +210,7 @@ define(
Preview
</a>
</div>
{ (template.readonly) ? false : deleteLink }
{ (template.readonly === "1") ? false : deleteLink }
</li>
);
}.bind(this));
@ -152,6 +230,8 @@ define(
<ul className={ boxClasses }>
{ templates }
</ul>
<ImportTemplate onImport={this.handleTemplateImport} />
</div>
);
}

View File

@ -26,6 +26,7 @@ define(
action: 'create',
data: {
type: type,
subject: 'Draft newsletter',
}
}).done(function(response) {
if(response.id !== undefined) {
@ -85,6 +86,26 @@ define(
</a>
</div>
</li>
<li data-type="notification">
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Post notifications</h3>
<p>
Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.
</p>
</div>
<div className="mailpoet_actions">
<a
className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'notification') }
>
Set up
</a>
</div>
</li>
</ul>
</div>
);

View File

@ -0,0 +1,225 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/select.jsx',
'form/fields/selection.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Form,
Select,
Selection,
Text,
Breadcrumb
) {
var intervalField = {
name: 'interval',
values: {
'daily': 'Once a day at...',
'weekly': 'Weekly on...',
'monthly': 'Monthly on the...',
'nthWeekDay': 'Monthly every...',
'immediately': 'Immediately.',
},
};
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];
}
));
var timeOfDayField = {
name: 'timeOfDay',
values: timeOfDayValues,
};
var weekDayField = {
name: 'weekDay',
values: {
0: 'Monday',
1: 'Tuesday',
2: 'Wednesday',
3: 'Thursday',
4: 'Friday',
5: 'Saturday',
6: 'Sunday',
},
};
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 suffixes = {
0: 'st',
1: 'nd',
2: 'rd'
};
var suffix = suffixes[day] || 'th';
return [day, (day + 1).toString() + suffix];
},
)),
};
var nthWeekDayField = {
name: 'nthWeekDay',
values: {
'0': '1st',
'1': '2nd',
'2': '3rd',
'3': 'last',
},
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
timeOfDay: 0,
weekDay: 0,
monthDay: 0,
nthWeekDay: 0,
};
},
handleIntervalChange: function(event) {
this.setState({
intervalType: event.target.value,
});
},
handleTimeOfDayChange: function(event) {
this.setState({
timeOfDay: event.target.value,
});
},
handleWeekDayChange: function(event) {
this.setState({
weekDay: event.target.value,
});
},
handleMonthDayChange: function(event) {
this.setState({
monthDay: event.target.value,
});
},
handleNthWeekDayChange: function(event) {
this.setState({
nthWeekDay: event.target.value,
});
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: {
type: 'notification',
options: this.state,
},
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
},
render: function() {
var timeOfDaySelection,
weekDaySelection,
monthDaySelection,
nthWeekDaySelection;
if (this.state.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this.state}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (this.state.intervalType === 'weekly'
|| this.state.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this.state}
onValueChange={this.handleWeekDayChange} />
);
}
if (this.state.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this.state}
onValueChange={this.handleMonthDayChange} />
);
}
if (this.state.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this.state}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<h1>Post notifications</h1>
<Breadcrumb step="type" />
<Select
field={intervalField}
item={this.state}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value="Next" />
</p>
</div>
);
},
});
return NewsletterWelcome;
}
);

View File

@ -1,191 +1,212 @@
define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
"use strict";
/*==================================================================================================
MailPoet Notice:
description: Handles notices
version: 0.2
author: Jonathan Labreuille
company: Wysija
dependencies: jQuery
Usage:
// success message (static: false)
MailPoet.Notice.success('Yatta!');
// error message (static: false)
MailPoet.Notice.error('Boo!');
// system message (static: true)
MailPoet.Notice.system('You need to updated ASAP!');
Examples:
MailPoet.Notice.success('- success #1 -');
setTimeout(function() {
MailPoet.Notice.success('- success #2 -');
setTimeout(function() {
MailPoet.Notice.error('- error -');
setTimeout(function() {
MailPoet.Notice.system('- system -');
setTimeout(function() {
MailPoet.Notice.hide();
}, 2500);
}, 300);
}, 400);
}, 500);
==================================================================================================*/
MailPoet.Notice = {
version: 0.2,
// default options
defaults: {
type: 'success',
message: '',
static: false,
scroll: false,
timeout: 2000,
onOpen: null,
onClose: null
},
options: {},
init: function(options) {
// set options
this.options = jQuery.extend({}, this.defaults, options);
// clone element
this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
// remove id from clone
this.element.removeAttr('id');
// insert notice after its parent
jQuery('#mailpoet_notice_'+this.options.type).after(this.element);
// setup onClose callback
var onClose = null;
if(this.options.onClose !== null) {
onClose = this.options.onClose;
}
// listen to remove event
var element = this.element;
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
if(onClose !== null) {
onClose();
}
// remove notice
jQuery(this).remove();
});
}.bind(this.element));
// listen to message event
jQuery(this.element).on('message', function(e, message) {
MailPoet.Notice.setMessage(message);
}.bind(this.element));
return this;
},
isHTML: function(str) {
var a = document.createElement('div');
a.innerHTML = str;
for(var c = a.childNodes, i = c.length; i--;) {
if(c[i].nodeType == 1) return true;
}
return false;
},
setMessage: function(message) {
// if it's not an html message, let's sugar coat the message with a fancy <p>
if(this.isHTML(message) === false) {
message = '<p>'+message+'</p>';
}
// set message
return this.element.html(message);
},
show: function(options) {
// initialize
this.init(options);
// show notice
this.showNotice();
// return this;
},
showNotice: function() {
// set message
this.setMessage(this.options.message);
// position notice
this.element.insertAfter(jQuery('h2.title'));
// set class name
switch(this.options.type) {
case 'success':
this.element.addClass('updated');
break;
case 'system':
this.element.addClass('update-nag');
break;
case 'error':
this.element.addClass('error');
break;
}
// make the notice appear
this.element.fadeIn(200);
// if scroll option is enabled, scroll to the notice
if(this.options.scroll === true) {
this.element.get(0).scrollIntoView(false);
}
// if the notice is not static, it has to disappear after a timeout
if(this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
});
}
// call onOpen callback
if(this.options.onOpen !== null) {
this.options.onOpen(this.element);
}
},
hide: function(all) {
if(all !== undefined && all === true) {
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');
}
},
error: function(message, options) {
this.show(jQuery.extend({}, {
type: 'error',
message: '<p>'+message+'</p>'
}, options));
},
success: function(message, options) {
this.show(jQuery.extend({}, {
type: 'success',
message: '<p>'+message+'</p>'
}, options));
},
system: function(message, options) {
this.show(jQuery.extend({}, {
type: 'system',
static: true,
message: message
}, options));
}
};
});
define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
"use strict";
/*==================================================================================================
MailPoet Notice:
description: Handles notices
version: 0.2
author: Jonathan Labreuille
company: Wysija
dependencies: jQuery
Usage:
// success message (static: false)
MailPoet.Notice.success('Yatta!');
// error message (static: false)
MailPoet.Notice.error('Boo!');
// system message (static: true)
MailPoet.Notice.system('You need to updated ASAP!');
Examples:
MailPoet.Notice.success('- success #1 -');
setTimeout(function() {
MailPoet.Notice.success('- success #2 -');
setTimeout(function() {
MailPoet.Notice.error('- error -');
setTimeout(function() {
MailPoet.Notice.system('- system -');
setTimeout(function() {
MailPoet.Notice.hide();
}, 2500);
}, 300);
}, 400);
}, 500);
==================================================================================================*/
MailPoet.Notice = {
version: 0.2,
// default options
defaults: {
type: 'success',
message: '',
static: false,
hideClose: false,
id: null,
positionAfter: false,
scroll: false,
timeout: 2000,
onOpen: null,
onClose: null
},
options: {},
init: function(options) {
// set options
this.options = jQuery.extend({}, this.defaults, options);
// clone element
this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
// add data-id to the element
if (this.options.id) this.element.attr('data-id', 'notice_' + this.options.id);
// remove id from clone
this.element.removeAttr('id');
// insert notice after its parent
var positionAfter;
if (typeof this.options.positionAfter === 'object') {
positionAfter = this.options.positionAfter;
} else if (typeof this.options.positionAfter === 'string') {
positionAfter = jQuery(this.options.positionAfter);
} else {
positionAfter = jQuery('#mailpoet_notice_'+this.options.type);
}
positionAfter.after(this.element);
// setup onClose callback
var onClose = null;
if(this.options.onClose !== null) {
onClose = this.options.onClose;
}
// listen to remove event
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
if(onClose !== null) {
onClose();
}
// remove notice
jQuery(this).remove();
});
}.bind(this.element));
// listen to message event
jQuery(this.element).on('message', function(e, message) {
MailPoet.Notice.setMessage(message);
}.bind(this.element));
return this;
},
isHTML: function(str) {
var a = document.createElement('div');
a.innerHTML = str;
for(var c = a.childNodes, i = c.length; i--;) {
if(c[i].nodeType == 1) return true;
}
return false;
},
setMessage: function(message) {
// if it's not an html message, let's sugar coat the message with a fancy <p>
if(this.isHTML(message) === false) {
message = '<p>'+message+'</p>';
}
// set message
return this.element.html(message);
},
show: function(options) {
// initialize
this.init(options);
// show notice
this.showNotice();
// return this;
},
showNotice: function() {
// set message
this.setMessage(this.options.message);
// position notice
this.element.insertAfter(jQuery('h2.title'));
// set class name
switch(this.options.type) {
case 'success':
this.element.addClass('updated');
break;
case 'system':
this.element.addClass('update-nag');
break;
case 'error':
this.element.addClass('error');
break;
}
// make the notice appear
this.element.fadeIn(200);
// if scroll option is enabled, scroll to the notice
if(this.options.scroll === true) {
this.element.get(0).scrollIntoView(false);
}
// if the notice is not static, it has to disappear after a timeout
if(this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else if (this.options.hideClose === false) {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
});
}
// call onOpen callback
if(this.options.onOpen !== null) {
this.options.onOpen(this.element);
}
},
hide: function(all) {
if(all !== undefined && all === true) {
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else if (all !== undefined && jQuery.isArray(all)) {
for (var id in all) {
jQuery('[data-id="notice_' + all[id] + '"]')
.trigger('close');
}
} if (all !== undefined) {
jQuery('[data-id="notice_' + all + '"]')
.trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');
}
},
error: function(message, options) {
this.show(jQuery.extend({}, {
type: 'error',
message: '<p>'+message+'</p>'
}, options));
},
success: function(message, options) {
this.show(jQuery.extend({}, {
type: 'success',
message: '<p>'+message+'</p>'
}, options));
},
system: function(message, options) {
this.show(jQuery.extend({}, {
type: 'system',
static: true,
message: message
}, options));
}
};
});

View File

@ -1,88 +1,81 @@
define('public', ['mailpoet', 'jquery', 'jquery-validation'],
function(MailPoet, $) {
'use strict';
define([
'mailpoet',
'jquery',
'parsleyjs'
],
function(
MailPoet,
jQuery,
Parsley
) {
jQuery(function($) {
function isSameDomain(url) {
var link = document.createElement('a');
link.href = url;
return (window.location.hostname === link.hostname);
}
function formatData(raw) {
var data = {};
$.each(raw, function(index, value) {
if(value.name.endsWith('[]')) {
var value_name = value.name.substr(0, value.name.length - 2);
// it's an array
if(data[value_name] === undefined) {
data[value_name] = [];
}
data[value_name].push(value.value);
} else {
data[value.name] = value.value;
}
});
return data;
}
$(function() {
// setup form validation
$('form.mailpoet_form').each(function() {
$(this).validate({
submitHandler: function(form) {
var data = $(form).serializeArray() || {};
var form = $(this);
// clear messages
$(form).find('.mailpoet_message').html('');
form.parsley({
errorsWrapper: '<p></p>',
errorTemplate: '<span></span>'
}).on('form:submit', function(parsley) {
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
return true;
} else {
// ajax request
MailPoet.Ajax.post({
url: MailPoetForm.ajax_url,
token: MailPoetForm.token,
endpoint: 'subscribers',
action: 'save',
data: formatData(data),
onSuccess: function(response) {
if(response !== true) {
// errors
$.each(response, function(index, error) {
$(form)
.find('.mailpoet_message')
.append('<p class="mailpoet_validate_error">'+
error+
'</p>');
});
} else {
// successfully subscribed
if(response.page !== undefined) {
// go to page
window.location.href = response.page;
} else if(response.message !== undefined) {
// display success message
$(form)
.find('.mailpoet_message')
.html('<p class="mailpoet_validate_success">'+
response.message+
'</p>');
}
var data = form.serializeObject() || {};
// reset form
$(form).trigger('reset');
}
// clear messages
form.find('.mailpoet_message').html('');
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
return true;
} else {
// ajax request
MailPoet.Ajax.post({
url: MailPoetForm.ajax_url,
token: MailPoetForm.token,
endpoint: 'subscribers',
action: 'subscribe',
data: data
}).done(function(response) {
if(response.result !== true) {
// errors
$.each(response.errors, function(index, error) {
form
.find('.mailpoet_message')
.append('<p class="mailpoet_validate_error">'+
error+
'</p>');
});
} else {
// successfully subscribed
if(response.page !== undefined) {
// go to page
window.location.href = response.page;
} else if(response.message !== undefined) {
// display success message
form
.find('.mailpoet_message')
.html('<p class="mailpoet_validate_success">'+
response.message+
'</p>');
}
});
}
return false;
// reset form
form.trigger('reset');
// reset validation
parsley.reset();
}
});
}
return false;
});
});
});
}
);
});
});

View File

@ -17,33 +17,40 @@ define(
name: 'name',
label: 'Name',
type: 'text'
},
{
name: 'description',
label: 'Description',
type: 'textarea'
}
];
var messages = {
updated: function() {
onUpdate: function() {
MailPoet.Notice.success('Segment successfully updated!');
},
created: function() {
onCreate: function() {
MailPoet.Notice.success('Segment successfully added!');
}
};
var Link = Router.Link;
var SegmentForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Segment <Link className="add-new-h2" to="/">Back to list</Link>
Segment
</h2>
<Form
endpoint="segments"
fields={ fields }
params={ this.props.params }
messages={ messages } />
messages={ messages }
/>
</div>
);
}

View File

@ -1,85 +1,228 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'classnames'
],
function(
React,
Router,
Listing,
classNames
) {
var columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'created_at',
label: 'Created on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
}
];
import React from 'react'
import { Router, Link } from 'react-router'
var bulk_actions = [
{
name: 'trash',
label: 'Trash'
}
];
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
var Link = Router.Link;
import Listing from 'listing/listing.jsx'
var SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ segment.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ segment.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
endpoint="segments"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions } />
</div>
);
}
});
return SegmentList;
var columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'description',
label: 'Description',
sortable: false
},
{
name: 'subscribed',
label: 'Subscribed',
sortable: false
},
{
name: 'unconfirmed',
label: 'Unconfirmed',
sortable: false
},
{
name: 'unsubscribed',
label: 'Unsubscribed',
sortable: false
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
);
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
);
}
},
{
name: 'duplicate_segment',
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'segments',
action: 'duplicate',
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
},
display: function(segment) {
return (segment.type !== 'wp_users');
}
},
{
name: 'synchronize_segment',
label: 'Update',
className: 'update',
onClick: function(item, refresh) {
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'segments',
action: 'synchronize'
}).done(function(response) {
MailPoet.Modal.loading(false);
if(response === true) {
MailPoet.Notice.success(
('List "%$1s" has been synchronized.').replace('%$1s', item.name)
);
refresh();
}
});
},
display: function(segment) {
return (segment.type === 'wp_users');
}
},
{
name: 'view_subscribers',
link: function(item) {
return (
<a href={ item.subscribers_url }>View subscribers</a>
);
}
},
{
name: 'trash',
display: function(segment) {
return (segment.type !== 'wp_users');
}
}
];
const bulk_actions = [
];
const SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Description">
<abbr>{ segment.description }</abbr>
</td>
<td className="column-date" data-colname="Subscribed">
<abbr>{ segment.subscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unconfirmed">
<abbr>{ segment.unconfirmed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unsubscribed">
<abbr>{ segment.unsubscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="segments"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
module.exports = SegmentList;

View File

@ -15,10 +15,10 @@ define(
MailPoet.Router = new (Backbone.Router.extend({
routes: {
'mta(/:method)': 'sendingMethod',
'mta(/:group)': 'sendingMethodGroup',
'(:tab)': 'tabs',
},
sendingMethod: function(method) {
sendingMethodGroup: function(group) {
// display mta tab
this.tabs('mta');
@ -30,13 +30,13 @@ define(
// hide "save settings" button
jQuery('.mailpoet_settings_submit').hide();
if(method === null) {
if(group === null) {
// show sending methods
jQuery('.mailpoet_sending_methods').fadeIn();
} else {
// hide DKIM option when using MailPoet's API
jQuery('#mailpoet_mta_dkim')[
(method === 'mailpoet')
(group === 'mailpoet')
? 'hide'
: 'show'
]();
@ -45,7 +45,7 @@ define(
jQuery('.mailpoet_sending_methods').hide();
// display selected sending method's settings
jQuery('.mailpoet_sending_method[data-method="'+ method +'"]').show();
jQuery('.mailpoet_sending_method[data-group="'+ group +'"]').show();
jQuery('#mailpoet_sending_method_setup').fadeIn();
}
},
@ -73,7 +73,7 @@ define(
}));
jQuery(document).ready(function() {
Backbone.history.start();
if (!Backbone.History.started) Backbone.history.start();
});
}
);

View File

@ -37,14 +37,25 @@ define(
'subscribed': 'Subscribed',
'unsubscribed': 'Unsubscribed'
}
},
{
name: 'segments',
label: 'Lists',
type: 'selection',
placeholder: "Select a list",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
}
}
];
var messages = {
updated: function() {
onUpdate: function() {
MailPoet.Notice.success('Subscriber successfully updated!');
},
created: function() {
onCreate: function() {
MailPoet.Notice.success('Subscriber successfully added!');
}
};
@ -52,18 +63,22 @@ define(
var Link = Router.Link;
var SubscriberForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Subscriber <Link className="add-new-h2" to="/">Back to list</Link>
Subscriber
</h2>
<Form
endpoint="subscribers"
fields={ fields }
params={ this.props.params }
messages={ messages } />
messages={ messages }
/>
</div>
);
}

View File

@ -0,0 +1,170 @@
define(
[
'underscore',
'jquery',
'mailpoet',
'handlebars',
'select2'
],
function (
_,
jQuery,
MailPoet,
Handlebars
) {
if (!jQuery("#mailpoet_subscribers_export").length) {
return;
}
jQuery(document).ready(function () {
if (!exportData.segments) {
return;
}
var subscribers_export_template =
Handlebars.compile(jQuery('#mailpoet_subscribers_export_template').html());
//render template
jQuery('#mailpoet_subscribers_export > div.inside').html(subscribers_export_template(exportData));
// define reusable variables
var segmentsContainerElement = jQuery("#export_lists"),
subscriberFieldsContainerElement = jQuery("#export_columns"),
exportConfirmedOptionElement = jQuery(':radio[name="option_confirmed"]'),
groupBySegmentOptionElement = jQuery(':checkbox[name="option_group_by_list"]'),
nextStepButton = jQuery("a.mailpoet_export_process"),
renderSegmentsAndFields = function (container, data) {
if (container.data('select2')) {
container
.html('')
.select2('destroy');
}
container
.select2({
data: data,
width: '20em',
templateResult: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
: item.name;
},
templateSelection: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
: item.name;
}
})
.on('select2:selecting', function (selectEvent) {
var selectElement = this,
selectedOptionId = selectEvent.params.args.data.id,
fieldsToExclude = [
'select',
'deselect'
];
if (_.contains(fieldsToExclude, selectedOptionId)) {
selectEvent.preventDefault();
if (selectedOptionId === 'deselect') {
jQuery(selectElement).val('').trigger('change');
} else {
var allOptions = [];
_.each(container.find('option'), function (field) {
if (!_.contains(fieldsToExclude, field.value)) {
allOptions.push(field.value);
}
});
jQuery(selectElement).val(allOptions).trigger('change');
}
jQuery(selectElement).select2('close');
}
})
.on('change', function () {
if ((exportData.segments && segmentsContainerElement.select2('data').length && subscriberFieldsContainerElement.select2('data').length)
||
(!exportData.segments && subscriberFieldsContainerElement.select2('data').length)
) {
toggleNextStepButton('on');
}
else {
toggleNextStepButton('off');
}
if (segmentsContainerElement.select2('data').length > 1 && exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').show();
}
else if (exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').hide();
}
});
};
renderSegmentsAndFields(subscriberFieldsContainerElement, subscriberFieldsSelect2);
renderSegmentsAndFields(segmentsContainerElement, segments);
subscriberFieldsContainerElement.select2('val', [
'status',
'email',
'first_name',
'last_name'
]);
exportConfirmedOptionElement.change(function () {
var selectedSegments = segmentsContainerElement.val();
if (this.value == 1) {
exportData.exportConfirmedOption = true;
renderSegmentsAndFields(segmentsContainerElement, segmentsWithConfirmedSubscribers);
}
else {
exportData.exportConfirmedOption = false;
renderSegmentsAndFields(segmentsContainerElement, segments);
}
segmentsContainerElement.select2('val', selectedSegments);
});
function toggleNextStepButton(condition) {
var disabled = 'button-disabled';
if (condition === 'on') {
nextStepButton.removeClass(disabled);
}
else {
nextStepButton.addClass(disabled);
}
}
nextStepButton.click(function () {
if (jQuery(this).hasClass('button-disabled')) {
return;
}
MailPoet.Modal.loading(true);
MailPoet.Ajax
.post({
endpoint: 'ImportExport',
action: 'processExport',
data: JSON.stringify({
'export_confirmed_option': exportData.exportConfirmedOption,
'export_format_option': jQuery(':radio[name="option_format"]:checked').val(),
'group_by_segment_option': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false,
'segments': (exportData.segments) ? segmentsContainerElement.val() : false,
'subscriber_fields': subscriberFieldsContainerElement.val()
})
})
.done(function (response) {
MailPoet.Modal.loading(false);
if (response.result === false) {
MailPoet.Notice.error(response.error);
} else {
resultMessage = MailPoetI18n.exportMessage
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>');
jQuery('#export_result_notice > ul > li').html(resultMessage);
jQuery('#export_result_notice').show();
window.location.href = response.data.exportFileURL;
}
})
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'
);
});
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,313 +1,331 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'form/fields/selection.jsx',
'classnames',
'mailpoet',
'jquery',
'select2'
],
function(
React,
Router,
Listing,
Selection,
classNames,
MailPoet,
jQuery
) {
var Link = Router.Link;
import React from 'react'
import { Router, Route, Link } from 'react-router'
var columns = [
{
name: 'email',
label: 'Email',
sortable: true
},
{
name: 'first_name',
label: 'Firstname',
sortable: true
},
{
name: 'last_name',
label: 'Lastname',
sortable: true
},
{
name: 'status',
label: 'Status',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
import Listing from 'listing/listing.jsx'
import Selection from 'form/fields/selection.jsx'
var messages = {
onDelete: function(response) {
var count = ~~response.subscribers;
var message = null;
const columns = [
{
name: 'email',
label: 'Subscriber',
sortable: true
},
{
name: 'status',
label: 'Status',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
if(count === 1) {
message = (
'1 subscriber was moved to the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', count);
}
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onConfirmDelete: function(response) {
var count = ~~response.subscribers;
var message = null;
if(count === 1) {
message = (
'1 subscriber was permanently deleted.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
var count = ~~response.subscribers;
var message = null;
if(count === 1) {
message = (
'1 subscriber has been restored from the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
var bulk_actions = [
{
name: 'moveToList',
label: 'Move to list...',
onSelect: function() {
var field = {
id: 'move_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#move_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'addToList',
label: 'Add to list...',
onSelect: function() {
var field = {
id: 'add_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#add_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromList',
label: 'Remove from list...',
onSelect: function() {
var field = {
id: 'remove_from_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#remove_from_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromAllLists',
label: 'Remove from all lists',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response.subscribers)
);
}
},
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
}
];
var SubscriberList = React.createClass({
renderItem: function(subscriber, actions) {
var row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
var status = '';
switch(subscriber.status) {
case 'subscribed':
status = 'Subscribed';
break;
case 'unconfirmed':
status = 'Unconfirmed';
break;
case 'unsubscribed':
status = 'Unsubscribed';
break;
}
var segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
var row_actions = false;
return (
<div>
<td className={ row_classes }>
<strong><Link to={ `/edit/${ subscriber.id }` }>
{ subscriber.email }
</Link></strong>
{ actions }
</td>
<td className="column" data-colname="First name">
{ subscriber.first_name }
</td>
<td className="column" data-colname="Last name">
{ subscriber.last_name }
</td>
<td className="column" data-colname="Status">
{ status }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
endpoint="subscribers"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
messages={ messages }
/>
</div>
const messages = {
onTrash: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', ~~response);
}
});
return SubscriberList;
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
);
};
const bulk_actions = [
{
name: 'moveToList',
label: 'Move to list...',
onSelect: function() {
let field = {
id: 'move_to_segment',
endpoint: 'segments',
filter: function(segment) {
return !!(
!segment.deleted_at && segment.type === 'default'
);
}
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#move_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'addToList',
label: 'Add to list...',
onSelect: function() {
let field = {
id: 'add_to_segment',
endpoint: 'segments',
filter: function(segment) {
return !!(
!segment.deleted_at && segment.type === 'default'
);
}
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#add_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromList',
label: 'Remove from list...',
onSelect: function() {
let field = {
id: 'remove_from_segment',
endpoint: 'segments',
filter: function(segment) {
return !!(
segment.type === 'default'
);
}
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#remove_from_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromAllLists',
label: 'Remove from all lists',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response)
);
}
},
{
name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response)
);
}
},
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
);
}
},
{
name: 'trash'
}
];
const SubscriberList = React.createClass({
renderItem: function(subscriber, actions) {
let row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions',
'column-username'
);
let status = '';
switch(subscriber.status) {
case 'subscribed':
status = 'Subscribed';
break;
case 'unconfirmed':
status = 'Unconfirmed';
break;
case 'unsubscribed':
status = 'Unsubscribed';
break;
}
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
let avatar = false;
if(subscriber.avatar_url) {
avatar = (
<img
className="avatar"
src={ subscriber.avatar_url }
title=""
width="32"
height="32"
/>
);
}
return (
<div>
<td className={ row_classes }>
<strong><Link to={ `/edit/${ subscriber.id }` }>
{ subscriber.email }
</Link></strong>
<p style={{margin: 0}}>
{ subscriber.first_name } { subscriber.last_name }
</p>
{ actions }
</td>
<td className="column" data-colname="Status">
{ status }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
</td>
</div>
);
},
onGetItems: function(count) {
jQuery('#mailpoet_export_button')[(count > 0) ? 'show' : 'hide']();
},
render: function() {
return (
<div>
<h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link>
<a className="add-new-h2" href="?page=mailpoet-import#step1">Import</a>
<a id="mailpoet_export_button" className="add-new-h2" href="?page=mailpoet-export">Export</a>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
endpoint="subscribers"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
messages={ messages }
onGetItems={ this.onGetItems }
/>
</div>
)
}
});
module.exports = SubscriberList;

View File

@ -5,15 +5,15 @@ import SubscriberList from 'subscribers/list.jsx'
import SubscriberForm from 'subscribers/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const history = createHashHistory({ queryKey: false })
const App = React.createClass({
render() {
return this.props.children
return this.props.children;
}
});
let container = document.getElementById('subscribers_container');
const container = document.getElementById('subscribers_container')
if(container) {
ReactDOM.render((

View File

@ -0,0 +1,264 @@
// Generated by CoffeeScript 1.9.2
/**
@license Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
*/
(function() {
var $, win;
$ = this.jQuery || window.jQuery;
win = $(window);
$.fn.stick_in_parent = function(opts) {
var doc, elm, enable_bottoming, fn, i, inner_scrolling, len, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class;
if (opts == null) {
opts = {};
}
sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming;
if (offset_top == null) {
offset_top = 0;
}
if (parent_selector == null) {
parent_selector = void 0;
}
if (inner_scrolling == null) {
inner_scrolling = true;
}
if (sticky_class == null) {
sticky_class = "is_stuck";
}
doc = $(document);
if (enable_bottoming == null) {
enable_bottoming = true;
}
outer_width = function(el) {
var _el, computed, w;
if (window.getComputedStyle) {
_el = el[0];
computed = window.getComputedStyle(el[0]);
w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right"));
if (computed.getPropertyValue("box-sizing") !== "border-box") {
w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right"));
}
return w;
} else {
return el.outerWidth(true);
}
};
fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) {
var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick;
if (elm.data("sticky_kit")) {
return;
}
elm.data("sticky_kit", true);
last_scroll_height = doc.height();
parent = elm.parent();
if (parent_selector != null) {
parent = parent.closest(parent_selector);
}
if (!parent.length) {
throw "failed to find stick parent";
}
fixed = false;
bottomed = false;
spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("<div />");
if (spacer) {
spacer.css('position', elm.css('position'));
}
recalc = function() {
var border_top, padding_top, restore;
if (detached) {
return;
}
last_scroll_height = doc.height();
border_top = parseInt(parent.css("border-top-width"), 10);
padding_top = parseInt(parent.css("padding-top"), 10);
padding_bottom = parseInt(parent.css("padding-bottom"), 10);
parent_top = parent.offset().top + border_top + padding_top;
parent_height = parent.height();
if (fixed) {
fixed = false;
bottomed = false;
if (manual_spacer == null) {
elm.insertAfter(spacer);
spacer.detach();
}
elm.css({
position: "",
top: "",
width: "",
bottom: ""
}).removeClass(sticky_class);
restore = true;
}
top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top;
height = elm.outerHeight(true);
el_float = elm.css("float");
if (spacer) {
spacer.css({
width: outer_width(elm),
height: height,
display: elm.css("display"),
"vertical-align": elm.css("vertical-align"),
"float": el_float
});
}
if (restore) {
return tick();
}
};
recalc();
last_pos = void 0;
offset = offset_top;
recalc_counter = recalc_every;
tick = function() {
var css, delta, recalced, scroll, will_bottom, win_height;
if (detached) {
return;
}
recalced = false;
if (recalc_counter != null) {
recalc_counter -= 1;
if (recalc_counter <= 0) {
recalc_counter = recalc_every;
recalc();
recalced = true;
}
}
if (!recalced && doc.height() !== last_scroll_height) {
recalc();
recalced = true;
}
scroll = win.scrollTop();
if (last_pos != null) {
delta = scroll - last_pos;
}
last_pos = scroll;
if (fixed) {
if (enable_bottoming) {
will_bottom = scroll + height + offset > parent_height + parent_top;
if (bottomed && !will_bottom) {
bottomed = false;
elm.css({
position: "fixed",
bottom: "",
top: offset
}).trigger("sticky_kit:unbottom");
}
}
if (scroll < top) {
fixed = false;
offset = offset_top;
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.detach();
}
css = {
position: "",
width: "",
top: ""
};
elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick");
}
if (inner_scrolling) {
win_height = win.height();
if (height + offset_top > win_height) {
if (!bottomed) {
offset -= delta;
offset = Math.max(win_height - height, offset);
offset = Math.min(offset_top, offset);
if (fixed) {
elm.css({
top: offset + "px"
});
}
}
}
}
} else {
if (scroll > top) {
fixed = true;
css = {
position: "fixed",
top: offset
};
css.width = elm.css("box-sizing") === "border-box" ? elm.outerWidth() + "px" : elm.width() + "px";
elm.css(css).addClass(sticky_class);
if (manual_spacer == null) {
elm.after(spacer);
if (el_float === "left" || el_float === "right") {
spacer.append(elm);
}
}
elm.trigger("sticky_kit:stick");
}
}
if (fixed && enable_bottoming) {
if (will_bottom == null) {
will_bottom = scroll + height + offset > parent_height + parent_top;
}
if (!bottomed && will_bottom) {
bottomed = true;
if (parent.css("position") === "static") {
parent.css({
position: "relative"
});
}
return elm.css({
position: "absolute",
bottom: padding_bottom,
top: "auto"
}).trigger("sticky_kit:bottom");
}
}
};
recalc_and_tick = function() {
recalc();
return tick();
};
detach = function() {
detached = true;
win.off("touchmove", tick);
win.off("scroll", tick);
win.off("resize", recalc_and_tick);
$(document.body).off("sticky_kit:recalc", recalc_and_tick);
elm.off("sticky_kit:detach", detach);
elm.removeData("sticky_kit");
elm.css({
position: "",
bottom: "",
top: "",
width: ""
});
parent.position("position", "");
if (fixed) {
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.remove();
}
return elm.removeClass(sticky_class);
}
};
win.on("touchmove", tick);
win.on("scroll", tick);
win.on("resize", recalc_and_tick);
$(document.body).on("sticky_kit:recalc", recalc_and_tick);
elm.on("sticky_kit:detach", detach);
return setTimeout(tick, 0);
};
for (i = 0, len = this.length; i < len; i++) {
elm = this[i];
fn($(elm));
}
return this;
};
}).call(this);

42
build
View File

@ -1,42 +0,0 @@
#!/bin/sh
# Remove previous build.
rm wysija-newsletters.zip;
# Create temp dir.
mkdir wysija-newsletters;
# Production assets.
./do compile:all;
# Production libraries.
rm -rf vendor;
rm composer.lock;
./composer.phar install --no-dev;
# Copy release folders.
cp -rf lang wysija-newsletters;
cp -rfL assets wysija-newsletters;
cp -rf lib wysija-newsletters;
cp -rf vendor wysija-newsletters;
cp -rf views wysija-newsletters;
rm -rf wysija-newsletters/assets/css/src;
rm -rf wysija-newsletters/assets/js/src;
# Copy release files.
cp LICENSE wysija-newsletters;
cp index.php wysija-newsletters;
cp mailpoet.php wysija-newsletters;
cp readme.txt wysija-newsletters;
cp uninstall.php wysija-newsletters;
# Zip final release.
zip -r wysija-newsletters.zip wysija-newsletters;
# Remove temp dir.
rm -rf wysija-newsletters;
# Reinstall dev dependencies.
rm composer.lock;
./composer.phar install;
./do install;

42
build.sh Executable file
View File

@ -0,0 +1,42 @@
#!/bin/sh
plugin_name='mailpoet'
# Remove previous build.
rm $plugin_name.zip
# Create temp dir.
mkdir $plugin_name
# Production assets.
rm -rf node_modules
npm install
./do compile:all
# Production libraries.
./composer.phar install --no-dev
# Copy release folders.
cp -Rf lang $plugin_name
cp -RfL assets $plugin_name
cp -Rf lib $plugin_name
cp -Rf vendor $plugin_name
cp -Rf views $plugin_name
rm -Rf $plugin_name/assets/css/src
rm -Rf $plugin_name/assets/js/src
# Copy release files.
cp LICENSE $plugin_name
cp index.php $plugin_name
cp $plugin_name.php $plugin_name
cp readme.txt $plugin_name
cp uninstall.php $plugin_name
# Zip final release.
zip -r $plugin_name.zip $plugin_name
# Remove temp dir.
rm -rf $plugin_name
# Reinstall dev dependencies.
./composer.phar install

View File

@ -7,7 +7,10 @@
"sunra/php-simple-html-dom-parser": "*",
"tburry/pquery": "*",
"j4mie/paris": "1.5.4",
"swiftmailer/swiftmailer": "^5.4"
"swiftmailer/swiftmailer": "^5.4",
"phpseclib/phpseclib": "*",
"mtdowling/cron-expression": "^1.0",
"nesbot/carbon": "^1.21"
},
"require-dev": {
"codeception/codeception": "*",

710
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
<?php
namespace MailPoet\Analytics;
class Reporter {
private $fields = array(
'Plugin Version' => 'pluginVersion',
);
function __construct() {}
function getData() {
$_this = $this;
$analytics_data = array_map(function($func) use ($_this) {
return $_this->$func();
}, $this->fields);
return $analytics_data;
}
private function pluginVersion() {
return MAILPOET_VERSION;
}
}

View File

@ -23,4 +23,9 @@ class Activator {
$populator = new Populator();
$populator->up();
}
function deactivate() {
$migrator = new Migrator();
$migrator->down();
}
}

33
lib/Config/Analytics.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Analytics\Reporter;
use \MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Analytics {
function __construct() {
}
function init() {
// review: this creates a fatal error when mailpoet tables are dropped.
//add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
}
function setupAdminDependencies() {
if(Setting::getValue('send_analytics_now', false)) {
$analytics = new Reporter();
wp_enqueue_script(
'analytics',
Env::$assets_url . '/js/lib/analytics.js',
array(),
Env::$version
);
wp_localize_script(
'analytics',
'mailpoet_analytics_data',
$analytics->getData()
);
}
}
}

61
lib/Config/Changelog.php Normal file
View File

@ -0,0 +1,61 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Setting;
class Changelog {
function init() {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
// don't run any check when it's an ajax request
if($doing_ajax) {
return;
}
// don't run any check when we're not on our pages
if(
!(isset($_GET['page']))
or
(isset($_GET['page']) && strpos($_GET['page'], 'mailpoet') !== 0)
) {
return;
}
add_action(
'admin_init',
array($this, 'check')
);
}
function check() {
$version = Setting::getValue('version', null);
$redirect_url = null;
if($version === null) {
// new install
$redirect_url = admin_url('admin.php?page=mailpoet-welcome');
} else if($version !== Env::$version) {
// update
$redirect_url = admin_url('admin.php?page=mailpoet-update');
}
if($redirect_url !== null) {
// save version number
Setting::setValue('version', Env::$version);
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
if($redirect_url !== $current_url) {
wp_safe_redirect(
add_query_arg(
array(
'mailpoet_redirect' => urlencode($current_url)
),
$redirect_url
)
);
exit;
}
}
}
}

View File

@ -4,53 +4,87 @@ namespace MailPoet\Config;
if(!defined('ABSPATH')) exit;
class Env {
public static $version;
public static $plugin_name;
public static $file;
public static $path;
public static $views_path;
public static $assets_path;
public static $assets_url;
public static $languages_path;
public static $lib_path;
public static $plugin_prefix;
public static $db_prefix;
public static $db_source_name;
public static $db_host;
public static $db_name;
public static $db_username;
public static $db_password;
public static $db_charset;
static $version;
static $plugin_name;
static $plugin_path;
static $file;
static $path;
static $views_path;
static $assets_path;
static $assets_url;
static $temp_path;
static $temp_URL;
static $languages_path;
static $lib_path;
static $plugin_prefix;
static $db_prefix;
static $db_source_name;
static $db_host;
static $db_socket;
static $db_port;
static $db_name;
static $db_username;
static $db_password;
static $db_charset;
public static function init($file, $version) {
static function init($file, $version) {
global $wpdb;
self::$version = $version;
self::$plugin_name = 'mailpoet';
self::$file = $file;
self::$path = dirname(self::$file);
self::$plugin_name = 'mailpoet';
self::$views_path = self::$path . '/views';
self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file);
self::$temp_path = wp_upload_dir()['path'];
self::$temp_URL = wp_upload_dir()['url'];
self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib';
self::$plugin_prefix = 'mailpoet_';
self::$db_prefix = $wpdb->prefix . self::$plugin_prefix;
self::$db_source_name = self::dbSourceName();
self::$db_host = DB_HOST;
self::$db_port = 3306;
self::$db_socket = false;
if(preg_match('/(?=:\d+$)/', DB_HOST)) {
list(self::$db_host, self::$db_port) = explode(':', DB_HOST);
} else {
if(preg_match('/:/', DB_HOST)) {
self::$db_socket = true;
}
}
self::$db_name = DB_NAME;
self::$db_username = DB_USER;
self::$db_password = DB_PASSWORD;
self::$db_charset = $wpdb->get_charset_collate();
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port);
}
private static function dbSourceName() {
private static function dbSourceName($host, $socket, $port) {
$source_name = array(
'mysql:host=',
DB_HOST,
(!$socket) ? 'mysql:host=' : 'mysql:unix_socket=',
$host,
';',
'port=',
$port,
';',
'dbname=',
DB_NAME
);
return implode('', $source_name);
}
}
static function isPluginActivated() {
$activatesPlugins = get_option('active_plugins');
$isActivated = (
in_array(
sprintf('%s/%s.php', basename(self::$path), self::$plugin_name),
$activatesPlugins
) ||
in_array(
sprintf('%s/%s.php', explode('/', plugin_basename(__FILE__))[0], self::$plugin_name),
$activatesPlugins
)
);
return ($isActivated) ? true : false;
}
}

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