Compare commits

...

625 Commits

Author SHA1 Message Date
a5300624c2 - Bump up release version to 0.0.36;
- Fix translation strings in unit tests
2016-07-15 15:59:40 +03:00
2714c7fa9a Merge pull request #563 from mailpoet/copy-edit
Copy edit - July 15 2016
2016-07-15 15:31:50 +03:00
49b65729db update 14 July 2016 2016-07-14 20:58:12 +02:00
e053b62a70 Merge pull request #558 from mailpoet/task_scheduler_option
Adds task scheduler option to settings
2016-07-14 16:47:15 +03:00
88113cf22e - Modifes task scheduler setting to include method type 2016-07-14 09:44:16 -04:00
5bf352e9fc Merge pull request #559 from mailpoet/ajax_update
Removes object keys with null values when doing an ajax request
2016-07-14 13:44:43 +03:00
5a7d5ac3f0 - Removes object keys with null values when doing an ajax request 2016-07-13 21:06:13 -04:00
05848ce7aa - Adds task scheduler option to settings
- Closes #553
2016-07-13 19:49:18 -04:00
b58d996ac7 Merge pull request #551 from mailpoet/newsletter_archive
Newsletter archive: link to browser version
2016-07-12 17:19:27 +02:00
16fa4491a5 Merge pull request #550 from mailpoet/empty_newsletter
Fix notices and warnings in newsletter preview when there's no template
2016-07-12 17:05:09 +02:00
456deede14 Change newsletter archive to link to "View in brower" newsletter
versions
2016-07-12 17:55:36 +03:00
4ef50ca551 Fix notices and warnings in newsletter preview when there's no template 2016-07-12 17:11:13 +03:00
5ff7d98b00 Merge pull request #547 from mailpoet/post_notification_fix
Fixes post notifications not being sent
2016-07-12 11:15:37 +02:00
3018dff1ff Edit July 12 2016 2016-07-12 11:07:36 +02:00
9386fe8328 - Fixes post notifications not being sent 2016-07-11 18:56:01 -04:00
e4213437e9 Bump up release version to 0.0.35 2016-07-08 14:57:30 +03:00
386bdceed3 Merge pull request #543 from mailpoet/api_refactor
API refactor
2016-07-08 13:55:01 +03:00
87eda71931 Merge pull request #544 from mailpoet/issue_434
Settings Page
2016-07-08 13:28:06 +03:00
cef9f1dcf8 - Updates constant names 2016-07-07 14:32:05 -04:00
c78b2088eb - Updates the check for invalid API endpoint 2016-07-07 14:20:27 -04:00
150364de3a - Fixes API endpoint naming convention
- Generates/saves cron daemon token as soon as its executed
2016-07-07 14:00:07 -04:00
1cd9f3eb67 - Removes counter from cron daemon
- Invokes token regeneration/comparion at a later stage
2016-07-07 10:24:24 -04:00
89253125af - Fixes a typo 2016-07-07 10:00:12 -04:00
e7ee356f90 cleanup permissions related classes 2016-07-07 15:49:03 +02:00
a88017400b Remove email validation as it was not working properly
- using the MailPoet sending method forces signup confirmation
- save settings when activating a sending method (works when pressing enter in an input when setting up method)
2016-07-07 15:36:59 +02:00
f557881462 - Updates code based on review comments 2016-07-07 09:01:59 -04:00
8dba4727c4 - Updates open/click link generation logic to utilize API's buildRequest
method
2016-07-06 22:57:39 -04:00
8ec094089f - Removes TODO notice 2016-07-06 20:18:02 -04:00
3c353e715b - Fixes view in browser API URL 2016-07-06 19:56:26 -04:00
ab33a9c352 - Updates cron API URL
- Removes cron daemon counter
- Generates/saves cron daemon token as soon as its executed
2016-07-06 19:48:16 -04:00
406b509ac4 Remove saving of roles & permissions when saving settings
- make sure we try to activate the sending method instead of saving settings when pressing enter in an input
- added Default sender row with global from/reply_to
- hide notification emails setting
- removed notification from/reply to email (for the time being, we will reintroduce it if need be later on)
2016-07-06 17:29:24 +02:00
bd814baf28 - Fixes data not being passed to API buildRequest method 2016-07-06 10:02:47 -04:00
2db681d908 - Adds and centralizes API data encoding/decoding method 2016-07-06 09:22:34 -04:00
37e3af584e added parsley validation on settings form - need to fix permissions 2016-07-06 14:12:30 +02:00
5fc863bd82 Settings page update (issue #434) 2016-07-06 14:10:46 +02:00
76649f9590 removed debug mode and roles and permissions from advanced tab 2016-07-06 13:56:11 +02:00
cb2faec8b2 - Refactors API
- Updates existing classes to use the refactored API methods
2016-07-05 20:17:25 -04:00
e8604284fe Merge pull request #540 from mailpoet/issue_431
Bulk actions messages + remaining UI items from issue 431
2016-07-05 17:59:15 +03:00
d2ccdef6c7 better alternative to remove duplicate MailPoet submenu 2016-07-05 16:55:03 +02:00
38199dc96f - Adds validation for API data 2016-07-05 10:20:30 -04:00
630b219e96 Merge pull request #539 from mailpoet/twig_caching
Fix Twig cache regeneration
2016-07-05 09:57:02 -04:00
64155bc121 Merge pull request #541 from mailpoet/populator_settings
Do not reset plugin settings on plugin reactivation
2016-07-05 14:42:28 +02:00
7fb45a15ee Fix code style errors 2016-07-05 15:28:38 +03:00
ed5294477f Fix Populator to not overwrite existing settings 2016-07-05 15:23:11 +03:00
d152b073a6 fixed onSuccess on bulk actions and locale formatted numbers in success messages 2016-07-05 13:58:12 +02:00
f8efb3934b remove 'MailPoet' submenu and make newsletters the default page 2016-07-05 13:16:14 +02:00
5a21d3fdc8 added missing 'row-title' class on listings 2016-07-05 11:44:49 +02:00
710ede15ce sending method daily emails frequency in locale 2016-07-05 10:49:12 +02:00
150286ab6b Enable regenerating templates that have changed 2016-07-04 18:05:50 +03:00
9e758e8a33 Bump up release version to 0.0.34 2016-07-01 16:57:48 +03:00
059165e5d2 Merge pull request #536 from mailpoet/manage_subscriptions
Manage subscriptions
2016-07-01 16:17:50 +03:00
fe154d9251 fixed code sniffer reported errors 2016-07-01 14:17:39 +02:00
a8ffbc2d0e handle empty/unchecked/checked checkboxes properly in both react and forms 2016-07-01 14:14:18 +02:00
9de3a245b0 fixed both radio & checkbox fields so that it selects the proper value 2016-07-01 14:14:18 +02:00
5eef709af5 Uniform date display format for Manage Subscriptions & Subscriber new/edit
- use isWPUser instead of wp_user !== null
2016-07-01 14:14:18 +02:00
7b0c130d0a updated unit test for custom fields of date type 2016-07-01 14:14:18 +02:00
7f265675b0 changed the way custom field date type is handled (react + form + db) 2016-07-01 14:14:18 +02:00
ba15db9829 fixed value loading for textarea 2016-07-01 14:14:18 +02:00
d15473a8e4 disabled first/last name inputs for WP User on manage subscription page 2016-07-01 14:14:18 +02:00
d9f93dc6e7 Merge pull request #537 from mailpoet/qa
QA Tools and improvements
2016-06-30 18:17:05 +02:00
634c5b699d Remove leftover merge conflict, fix empty ALC block message #505 2016-06-30 19:12:26 +03:00
23e8ce38dd Merge remote-tracking branch 'origin/qa' into qa
Conflicts:
	lib/Config/Initializer.php
	lib/Cron/Workers/SendingQueue/SendingQueue.php
	lib/Models/SendingQueue.php
	lib/Router/Router.php
2016-06-30 19:01:44 +03:00
c62ae2ce80 Add PHP CodeSniffer option to reduce severity, fixed syntax error 2016-06-30 18:52:07 +03:00
d0813bb4e2 Fix class and method names to use camel case 2016-06-30 18:52:07 +03:00
0ac701eb20 Change line endings from DOS CRLF to Unix LF 2016-06-30 18:52:07 +03:00
607395be6f Fix spacing around commas 2016-06-30 18:52:07 +03:00
55d48df8a4 Fix indentation issues 2016-06-30 18:50:48 +03:00
e0282ae45b Fix empty catch statement error 2016-06-30 18:50:48 +03:00
235fdea00f Remove commented out code, raise code similarity trigger treshold 2016-06-30 18:50:48 +03:00
b8c6d54f48 Fix "Closing brace must be on a line by itself" code sniffer errors 2016-06-30 18:50:48 +03:00
67661e3aad Remove useless constructors 2016-06-30 18:50:48 +03:00
c03facdc45 Add space after comma in function call parameters 2016-06-30 18:49:50 +03:00
9ddc1ef555 Remove statements that cannot be executed 2016-06-30 18:49:50 +03:00
9cfc2fd940 Remove an unnecessary return statement 2016-06-30 18:49:50 +03:00
24e108bce7 Remove spaces after type casts 2016-06-30 18:49:50 +03:00
48f0c03425 Fix spacing between control structure and opening parenthesis 2016-06-30 18:46:33 +03:00
0bfbe6dc79 Change TRUE, FALSE, NULL capitalization to lowercase 2016-06-30 18:46:33 +03:00
ad0a9838bc Disable line width limits 2016-06-30 18:46:33 +03:00
81ec293e54 Remove rule that prevents statements with only comments in body 2016-06-30 18:46:32 +03:00
b8b3d76a1d Remove PHPMD and PHPCPD tools we don't use 2016-06-30 18:46:32 +03:00
805e641d40 Add PHP lint and PHP code sniffer 2016-06-30 18:46:32 +03:00
18326f9df1 Merge pull request #535 from mailpoet/sending_queue_refactor
Fixes error that resulted in additional newsletter to be sent
2016-06-30 16:39:45 +02:00
46dda84012 - Moves queue subscriber handling logic to the queu model 2016-06-30 10:23:06 -04:00
9979261cb6 fixed a few more warnings 2016-06-30 15:42:58 +02:00
e8887e2aa5 Add PHP CodeSniffer option to reduce severity, fixed syntax error 2016-06-30 15:24:50 +03:00
4a91fae984 Fix class and method names to use camel case 2016-06-30 15:13:48 +03:00
0fe975f614 - Declares array 2016-06-30 07:52:16 -04:00
c7fd7b8a32 Change line endings from DOS CRLF to Unix LF 2016-06-30 14:39:28 +03:00
b7e3c3ae81 Fix spacing around commas 2016-06-30 14:03:07 +03:00
b2c3206185 - Fixes error that resulted in additional newsletter to be sent 2016-06-30 06:42:56 -04:00
b7d8d482fe Fix indentation issues 2016-06-30 13:29:23 +03:00
8a9d14319b Fix empty catch statement error 2016-06-30 12:40:22 +03:00
c396254e64 Remove commented out code, raise code similarity trigger treshold 2016-06-29 21:19:24 +03:00
e4dbeca664 Fix "Closing brace must be on a line by itself" code sniffer errors 2016-06-29 20:48:14 +03:00
168540d6d2 Remove useless constructors 2016-06-29 20:42:03 +03:00
c62cd6c023 Add space after comma in function call parameters 2016-06-29 19:26:07 +03:00
033e0581f1 Remove statements that cannot be executed 2016-06-29 19:20:50 +03:00
9f978d3362 Remove an unnecessary return statement 2016-06-29 19:09:07 +03:00
841340a42d Remove spaces after type casts 2016-06-29 19:04:23 +03:00
9595e9629f Fix spacing between control structure and opening parenthesis 2016-06-29 18:54:01 +03:00
56ba543f8d Change TRUE, FALSE, NULL capitalization to lowercase 2016-06-29 18:38:38 +03:00
1cead6c6cd Disable line width limits 2016-06-29 17:57:38 +03:00
f5f7ce4c42 Remove rule that prevents statements with only comments in body 2016-06-29 17:15:00 +03:00
b13075b8f2 Remove PHPMD and PHPCPD tools we don't use 2016-06-29 16:49:22 +03:00
c4db9e3227 Add PHP lint and PHP code sniffer 2016-06-29 16:19:50 +03:00
f47bfb5439 Merge pull request #532 from mailpoet/newsletter_creation
Newsletter creation: Step 1 and 3 changes
2016-06-28 15:25:14 +02:00
cc4639cb23 Set default values when immediately sending scheduled newsletter 2016-06-28 15:57:02 +03:00
69094f57fd Fix typos 2016-06-28 15:01:31 +03:00
ffe7b80888 Simplify variable declarations 2016-06-28 14:12:09 +03:00
fc846b808e Remove obsolete debugging statement 2016-06-28 14:12:09 +03:00
1cbf6b67b2 Remove console.log statements 2016-06-28 14:12:09 +03:00
286c02bdd9 Fix standard newsletter scheduling to always include scheduleAt 2016-06-28 14:12:08 +03:00
5f1d76225b - Add sorting of segment names in Welcome newsletter segment selector;
- Add an option to FormFieldSelect to allow sorting options;
- Change "Send" button label for scheduled newsletters;
- Disable "Send" button for sending/already sent newsletters.
2016-06-28 14:12:08 +03:00
c05ea1b968 Change "Go back to editor" to save form fields first 2016-06-28 14:12:08 +03:00
2d45ab2e88 Add WP user segment selection to Notification and Standard newsletters 2016-06-28 14:12:08 +03:00
ca9b1e25a7 Change notification newsletter time to be displayed in WP format 2016-06-28 14:12:08 +03:00
2927875e16 Regenerate thumbnails of default newsletter templates 2016-06-28 14:12:07 +03:00
486a97fa30 Vertically center template thumbs and don't enforce min-height for them 2016-06-28 14:12:07 +03:00
c22d434dff Merge pull request #531 from mailpoet/unit_test_catchup
Unit test update
2016-06-28 12:22:50 +03:00
306cdeb68f Models unit tests update 2016-06-27 13:53:56 +02:00
7ee83dad06 Merge pull request #527 from mailpoet/sending_queue_refactor
Sending queue refactor
2016-06-23 18:16:21 +03:00
d414313749 - Fixes const definition for PHP 5.5 2016-06-22 13:35:48 -04:00
66d329f630 - Configures mailer inside the mailer task class 2016-06-22 11:21:11 -04:00
f524ffcb28 - Updates mailer task to store mailer instance 2016-06-22 11:15:40 -04:00
264b7e180b listing handler and bulk actions tests completed 2016-06-22 13:47:54 +02:00
88dc7f4199 removing DKIM and useless classes 2016-06-22 13:47:54 +02:00
9652f75028 Merge pull request #530 from mailpoet/fix_safari_es6_bug
removed ES6 syntax from non converted JS file - fixes #529 (Safari bug)
2016-06-22 14:29:57 +03:00
36c32db2d1 Merge pull request #528 from mailpoet/listing_sorting
Listing sorting + bugfixes
2016-06-22 14:24:05 +03:00
fd12bd557e fixed 'Setup' link in homepage 2016-06-22 12:21:26 +02:00
7bd8ed4639 Use promises for handleBulkAction
- fixed filters not being updated when going back/forward
- improved redirection to "all" group after emptying the trash (former way became buggy)
- fixed error thrown by "onGetItems" -> this logic has to go at some point
- Newsletters listing are sorted by "updated_at" desc
- Subscribers are sorted by "created_at" desc (Subscribed on)
2016-06-21 22:36:13 +02:00
ca0e511efd removed ES6 syntax from non converted JS file - fixes #529 (Safari bug) 2016-06-21 16:47:25 +02:00
e5f3fabcda - Moves mailer logic into Mailer Task class 2016-06-21 10:14:19 -04:00
efc9bac760 - Updates unit tests 2016-06-20 23:36:30 -04:00
ce6327c3d5 - Re-adds the old multidimensional array flatten method 2016-06-20 23:35:47 -04:00
f32d6bb331 - Joins bulk and individual processing into one method
- Refactors code as per code review comments
2016-06-20 23:12:32 -04:00
e807aad814 - Updates array flatten function for multidimensional arrays
- Removes custom array unique method for multidimensional arrays
2016-06-20 11:50:54 -04:00
b87754ca30 Listing setParam only needs to be run when url history is specified
- added missing code to deleteManySubscriptions() so that it doesn't remove from all segments
2016-06-20 17:28:19 +02:00
22dfb372ec - Updates bulk insert logic 2016-06-20 10:34:41 -04:00
674bbd728e updated Subscriber unit test to use model constants - no fix here 2016-06-20 16:30:34 +02:00
68c09b8678 Sorting for all listings & bugfixes for all listings except Newsletters
- newsletters listing now uses hash history
- newsletters are sorted by Subject (a->z)
- segments are sorted by Name (a->z)
- re-added WordPress Users list as a segment you can send a newsletter to
- added explicit error messages when an auto newsletter isn't fully configured
- added missing strings for "selectAll" in Segments listing
- fixed filters() in Subscribers listing (wrong count as it was not taking groups/filters/search into account)
2016-06-20 16:23:27 +02:00
c83ab0886f - Rebases master 2016-06-19 22:10:18 -04:00
999a0b3ede - Refactors sending queue worker by breaking it into smaller tasks
- Adds arrayUnique method to Helpers for multidimensional arrays
2016-06-17 14:52:56 -04:00
6daecd6466 - Fixes URL extraction (undefined index notice)
- Updates link replacement in text body
- Updates links saving logic
2016-06-17 14:52:33 -04:00
7af2775972 Allowed ability to set default sort_by/order on listings
- improved performance of listings (less refresh of items)
- fixed sorting issue where the order would not be reversed
2016-06-17 17:27:40 +02:00
4bb1acf493 Bump up release version to 0.0.33 2016-06-17 17:14:19 +03:00
c8cd3d3eb5 Merge pull request #526 from mailpoet/copy_review
copy_review
2016-06-17 16:30:35 +03:00
2360c4d6e4 Fix periodicity strings 2016-06-17 16:28:40 +03:00
36e9168eef Escape quotes where needed 2016-06-17 16:06:03 +03:00
fb79d189d7 Edits June 17 2016 2016-06-17 15:09:25 +03:00
12330d6d34 Copy edits 6/15/2016 2016-06-17 15:09:25 +03:00
5efbcfd9c1 Update June 13 2016-06-17 15:09:24 +03:00
75240fc2e1 Merge pull request #508 from mailpoet/newsletters_listing
Newsletters multi-listing
2016-06-17 15:01:30 +03:00
b6fabcc739 removing some leftover trailing commas. 2016-06-17 13:16:20 +02:00
269ddae93a Refactored scheduling options for React (semi-converted to ES6 too)
- fixed issue with Pausing sending (missing self::)
2016-06-17 13:05:46 +02:00
90c3f0e4e4 only update status to Sent for Standard newsletters 2016-06-16 20:31:47 +02:00
dd8c54aae3 removed useless newsletters/list.jsx
- removed constant from Scheduler since it's defined on the SendingQueue model
2016-06-16 20:08:42 +02:00
aa3a46b941 Status update of newsletters completed
- duplicate newsletter now includes options as well
- fixed NaN issue in statistics when newsletter is being sent
- use constant for scheduled (and put it as the sendingQueue Model level)
2016-06-16 20:01:53 +02:00
744455f0df removed useless methods 2016-06-16 12:21:54 +02:00
9aa25446d1 fixed unit tests 2016-06-15 16:33:48 +02:00
6199caea29 - Notification settings column
- added "width" option to listing headers/columns
2016-06-15 16:33:48 +02:00
d6a68dd4d0 settings column done for welcome emails + WordPress capitalization fix 2016-06-15 16:33:48 +02:00
ee6e261c42 Conditional display of statistics column (for standard)
- improved duplicate action (for standard)
- moved STATUS_COMPLETED constant from worker to SendingQueue model where it belongs
2016-06-15 16:26:42 +02:00
cabfd8a946 better with the proper type 2016-06-15 16:26:42 +02:00
cf712636ed progress on notification type listing + NL model improvements 2016-06-15 16:26:42 +02:00
873c3d15a0 Fixed Setting::getValue issue where defaults were not returned for single keys
- updated static strings with constants
2016-06-15 16:26:42 +02:00
bc1bd3bad1 commenting on a react quirk 2016-06-15 16:26:42 +02:00
9f971632c9 update status in welcome listing 2016-06-15 16:26:42 +02:00
91bc0505ac Welcome emails progress 2016-06-15 16:26:42 +02:00
90c94624cc added preview link for standard newsletters 2016-06-15 16:26:42 +02:00
cd412894c6 Refactored filtering (groups / status / type)
- standard listing close to completion (missing item actions)
- enabled tracking by default on install
2016-06-15 16:26:42 +02:00
ecf15d53d9 Newsletters listing
- added stylesheet for newsletters listing
- added "status" database column on Newsletters for grouping in listings
- added duplicate link to standard newsletters
2016-06-15 16:15:02 +02:00
a593347336 call groups/filters only if Model has defined those methods 2016-06-15 16:15:02 +02:00
22566869cb tab system for newsletters listing 2016-06-15 16:14:06 +02:00
c959e7ec96 fixed total count and filtering + basic tab implementation in React 2016-06-15 16:14:06 +02:00
86a2846215 Tab system for listings 2016-06-15 16:14:06 +02:00
3b97a26a8a Newsletters multi-listing 2016-06-15 16:14:06 +02:00
dc6c973574 Merge pull request #523 from mailpoet/editor_ui
Email Editor: round 2 fixes
2016-06-14 15:26:22 +02:00
2a3a561464 updated shortcode (user -> subscriber) in FrankRoast template 2016-06-14 15:25:23 +02:00
e5f45fb7ad Merge pull request #524 from mailpoet/link_processing_fix
Prevents URLs in link titles from being processed when tracking is enabled
2016-06-14 15:21:18 +02:00
f22cadd319 - Declares hash length as constant
- Introduces check for nonexistent values/updates loop condition
2016-06-14 09:02:08 -04:00
5ea25ec697 Fix the way we query for WP subscriber in email preview 2016-06-14 15:43:13 +03:00
c0a250fc0f Turn sidebar/sidepanel text size into a variable 2016-06-14 15:26:00 +03:00
e69aa792c4 - Prevents URLs in link titles from being processed when tracking is enabled. Closes #519 2016-06-13 21:13:23 -04:00
3b4ac4d2d2 Change newsletter preview to use current user as subscriber 2016-06-13 15:02:20 +03:00
781973777e Vertically and horizontally center block deletion confirmation dialog 2016-06-13 14:13:47 +03:00
8698d2c6ba Change placeholder text of preheader input 2016-06-13 13:06:59 +03:00
47c15eca83 Change sidebar and sidepanel text font size to 13px 2016-06-13 13:06:59 +03:00
64f4bed080 Bump up release version to 0.0.32 2016-06-10 17:25:38 +03:00
fe47ba8a38 Merge pull request #522 from mailpoet/tests_fix
Fix PHP unit tests
2016-06-10 10:12:53 -04:00
eb02adc7ba Exclude lib/Util/Helpers.php class from unit test coverage calculations 2016-06-10 16:54:11 +03:00
f257b503e9 - Fix unit tests to account for translation changes;
- Exclude 3rd party utility libraries from coverage calculations;
2016-06-10 15:06:44 +03:00
bfdabe3554 Merge pull request #521 from mailpoet/copy_review
Copy review
2016-06-10 12:36:47 +03:00
77dd71935a Update - June 10 2016 2016-06-10 11:13:40 +02:00
4b418f041b Merge pull request #518 from mailpoet/alc_posts_ui
ALC & Posts widgets UI fixes
2016-06-09 16:19:57 +02:00
c8a0e006a0 Fix Select2 placeholder in Posts settings 2016-06-09 15:42:01 +03:00
359119d896 Disable dragging with right click, fixes #517 2016-06-09 13:34:26 +03:00
1a3c767601 - Fix double HR tag issue on ALC/Posts block settings;
- Change "Drop content here" message to a custom one for Posts/ALC blocks
2016-06-09 13:34:26 +03:00
6a97e82d42 Fix select2 placeholder text not appearing for Posts widget 2016-06-09 13:34:26 +03:00
3edfd32879 - Add highlighting of blocks that are being edited;
- Refactor block settings views;
- Change Posts widget to display 8 posts in settings;
- Move ALC/Posts category selector label to Select2 placeholder.
2016-06-09 13:34:26 +03:00
33bdde1156 Merge pull request #516 from mailpoet/unit_tests
Adds unit test for open/unsubscribe statistics
2016-06-09 12:54:16 +03:00
710cab64c3 - Fixes error due to: "the blacklist functionality has been removed from
PHPUnit 5, please remove blacklist section from configuration"
2016-06-08 21:26:23 -04:00
ed707b1738 - Adds unit test for unsubscribe statistics 2016-06-08 21:25:40 -04:00
398903e8b8 - Adds unit test for open statistics 2016-06-08 12:38:52 -04:00
d590f5ea98 Merge pull request #512 from mailpoet/preview_link_refactoring
Extracts browser preview URl logic into a separate class
2016-06-08 17:12:10 +02:00
d6cbe5aac8 - Fixes incorrect shortcode name
- Updates unit test
2016-06-08 11:09:33 -04:00
08e6430c7d June 8 2016 Copy review 2016-06-08 17:02:50 +02:00
945fe66bbb Merge pull request #514 from mailpoet/model_cleanup
Removes unused method from the base model
2016-06-08 15:13:51 +02:00
8e3eb2b795 Merge pull request #515 from mailpoet/unit_tests
Adds unit test for click statistics
2016-06-08 16:02:42 +03:00
52fbc0ee8a Merge pull request #513 from mailpoet/renderer_fix
Rendering fix
2016-06-08 12:56:22 +03:00
a355228b93 Test Commit
This is an initial test commit for the copy review.
2016-06-08 11:31:04 +02:00
2cb0b3b071 - Adds unit test for click statistics 2016-06-07 21:57:04 -04:00
bc1fb235d3 - Removes unused method from the base model. Closes #511 2016-06-07 18:47:00 -04:00
713dda913e - Fixes rendering issue where DOMDocument throws a notice on unescaped
html entity
2016-06-07 12:27:40 -04:00
d182638971 - Updates references to the new view in browser URL class
- Removes unnecessary rtrim condition in URL generation
2016-06-07 10:53:01 -04:00
a5c620acf3 - Updates the way the view in browser URL is constructed 2016-06-07 10:41:19 -04:00
c176ad1d16 - Updates based on code review comments 2016-06-07 10:14:37 -04:00
14c2b4d90f - Changes location for the main view in browser class
- Updates code formattign for case statements
2016-06-07 09:28:29 -04:00
03eb4ad0fc - Changes location for the view in browser URL class 2016-06-07 09:16:48 -04:00
ba9cd15651 - Extracts view in browser URl logic into a separate class 2016-06-07 09:08:01 -04:00
329ec63dfd Bump up release version to 0.0.31 2016-06-03 18:29:40 +03:00
4925c7868e Merge pull request #510 from mailpoet/twig_deprecation
Twig deprecation notice (latest version)
2016-06-03 18:10:53 +03:00
13d28d0aa7 implemented interface in our Twig extension to comply with latest Twig standards 2016-06-03 15:07:30 +02:00
c7d3c79fe3 Merge pull request #509 from mailpoet/unit_tests
- Increases Mailer unit test coverage to 100%
2016-06-02 19:38:43 +03:00
1e9da724ea - Updates exception test logic 2016-06-02 12:33:58 -04:00
645d4e15ab - Updates unit test 2016-06-02 11:03:16 -04:00
cad5b242b2 - Increases Mailer unit test coverage to 100% 2016-06-02 10:11:36 -04:00
99a81042c1 Merge pull request #507 from mailpoet/custom_shortcodes
Implements shortcodes for custom fields
2016-06-01 17:09:38 +03:00
61987a204e - Fixes custom field shortcode matching logic 2016-06-01 09:59:45 -04:00
a208104fc8 - Fixes naming convention 2016-06-01 09:40:59 -04:00
00ccc8adf4 Merge pull request #506 from mailpoet/alc_update
Multiple ALC block support for newsletter editor
2016-06-01 15:15:26 +02:00
df0ed9ce53 Rename mailpoet_custom_fields symlink to mailpoet_shortcodes 2016-06-01 16:00:05 +03:00
26d9b915a2 - Adds unit test for shortcodes helper class 2016-05-31 21:30:38 -04:00
16cb91990b - Updates unit test 2016-05-31 20:08:45 -04:00
9642d3e672 - Renames all references of "custom fields" to "shortcodes" 2016-05-31 11:25:16 -04:00
aed60e6905 - Updates menu/editor view to work with the refactored shortcodes logic 2016-05-31 11:04:10 -04:00
da7615ba4c - Removes redundant shortcode description
- Implements shortcode processing for custom fields
2016-05-31 11:03:04 -04:00
3eb6a21980 - Centralizes a list of all shortcodes
- Returns all shortcodes with custom fields
2016-05-31 11:02:08 -04:00
b4e371302c Fix PHP coding style based on feedback 2016-05-31 17:50:38 +03:00
e6724b1d4a Change unsubscribe verifier to check for "Unsubscribe" shortcode
presence
2016-05-31 16:29:10 +03:00
2b6e87c3a7 Force TinyMCE to use absolute URLs 2016-05-31 16:12:33 +03:00
b01ee80ec2 Update Backbone, Marionette, Backbone Radio, TinyMCE dependency versions 2016-05-31 15:14:36 +03:00
5d48ecac80 Add a method to bulk update ALC blocks in newsletter editor 2016-05-31 13:53:45 +03:00
ebdb826011 Bump up release version to 0.0.30 2016-05-27 18:31:35 +03:00
9dc725e34d Merge pull request #488 from mailpoet/wp_users
Wp users
2016-05-27 18:26:45 +03:00
f47c331a5b updated db schema and fixed unit test missing Segment cleanup after 2016-05-27 15:38:24 +02:00
b45c70f32b removed status from subscribeManyToSegments() query 2016-05-27 14:18:02 +02:00
cf33d6f066 removed extra spaces 2016-05-27 14:15:46 +02:00
8292e9a744 Revert batch processing on bulk actions - too buggy
- minor fixes and cleanup
2016-05-27 14:15:46 +02:00
3c46a5b434 Optimized Bulk actions
- Updated SQL schema for every created_at column so that it has a default value
- Updated unit tests based on recent changes (new methods in SubscriberSegment model)
- Added check for HelpScout initialization code so that it doesn't throw errors
2016-05-27 14:15:46 +02:00
4a4c4e093a Added unit tests for the WP segment
- moved WP segment creation to the Segment model
2016-05-27 14:14:35 +02:00
4fa8a650b8 Added unit tests for SubscriberSegment / Subscriber models 2016-05-27 14:14:35 +02:00
da755b7902 Renamed method names for better clarity + refactoring
- renamed getWPUsers() to getWPSegment()
- renamed SubscriberSegment methods
2016-05-27 14:14:35 +02:00
ceebb18bdf minor spacing fix 2016-05-27 14:14:35 +02:00
d10a29598d prevent deletion of WP Users segment in Segments listing 2016-05-27 14:14:35 +02:00
8c56c8da5e Fixed bulk actions (return false if no items were selected)
- added missing check for WPUsers segment in case it does not exist
2016-05-27 14:14:35 +02:00
c4ddb38d18 Prevent WP users from being trashed/deleted
- return actual rowCount of affected rows for bulk actions (based on PDO last statement)
- prevent removal of WP Users segment relationship with subscribers.
2016-05-27 14:14:35 +02:00
15a21e5745 fix segments loaded on subscribers page + removed counts for bulk actions' segments 2016-05-27 14:14:35 +02:00
7df1a856ea Merge pull request #501 from mailpoet/import_fix
Import fix
2016-05-27 14:19:32 +03:00
69381205a2 - Updates unit test 2016-05-27 07:16:11 -04:00
9e0d8056b3 - Changes success/error notices font size to 13px in import and export 2016-05-27 07:16:11 -04:00
4b85c57436 - Updates Export notification class
- Updates Export "back to subscribers" button language
2016-05-27 07:16:11 -04:00
377498be1d - Removes validation of MailChimp API key
- Refactors import class
- Creates new method in Newsletter model to select welcome notifications
  for specific segments
- Updates Step 2 (error) and Step 3 (success) notices
- Gives MenuBootstrap class a comprehensible name
2016-05-27 07:16:11 -04:00
142421ad48 - Updates unit test 2016-05-27 07:16:11 -04:00
768115b794 - Disables "next step" button on import's step 2 when no segments are
selected
2016-05-27 07:16:11 -04:00
8b9d76db8a - Displays notice on step 3 of import when subscribers are added to a
segment with welcome notification enabled
2016-05-27 07:16:11 -04:00
f17c78fda2 - Updates Segment model to return segments even when there are no
subscribers
2016-05-27 07:16:11 -04:00
3d45a8b7d4 - Updates subscriber/subscriber_segment status using const values 2016-05-27 07:14:34 -04:00
3888241cbd - Simplified date matching logic by using Moment.js 2016-05-27 07:14:34 -04:00
603b6749de - Styles the import results notice to look like WP's "update" 2016-05-27 07:14:34 -04:00
22918ecfd1 - Updates the wording of the "back to list" button 2016-05-27 07:14:34 -04:00
70ded73b51 - Updates the look of the MailChimp API key "verify" button 2016-05-27 07:14:34 -04:00
da147047ec - Updates import email regex to use standard HTML5 regex
- Improves email detection/filtering logic
2016-05-27 07:14:34 -04:00
0e24174373 Merge pull request #486 from mailpoet/export_fix
Fixes segment subscriber count when status is "unsubscribed"
2016-05-26 11:40:38 +02:00
bc9b4eeb19 - Update Segment model/test to use const values for subscriber status 2016-05-25 17:31:38 -04:00
c6b13c5175 Merge pull request #498 from mailpoet/rendering_fix
Fixes a couple of rendering issues
2016-05-25 14:23:15 +03:00
f754b1d1b2 - Applies text alignment to ALC block
- Prevents duplicate column content
2016-05-24 15:41:26 -04:00
bd5300d69a Merge pull request #495 from mailpoet/standard_newsletter_fix
Fix scheduling immediate standard newsletters
2016-05-24 12:02:01 -04:00
9996f3ef41 Change Scheduler to use Newsletter object, not array 2016-05-24 17:57:34 +03:00
0f95d7bc8a Use Scheduler to schedule next post notification sending timestamp 2016-05-24 17:08:34 +03:00
14098643ae Fix scheduling immediate standard newsletters 2016-05-24 16:04:42 +03:00
7c2d5a45c5 - Updates unit test 2016-05-20 12:12:29 -04:00
d25070829d Bump up release version to 0.0.29 2016-05-20 18:49:10 +03:00
9d5902e179 - Fixes segment subscriber count when status is "unsubscribed" 2016-05-20 11:35:58 -04:00
d194502b27 Merge pull request #485 from mailpoet/standard_scheduling_fix
Prevent user from manually editing Standard newsletter scheduling date
2016-05-20 15:09:04 +02:00
2cef99de2b Merge pull request #484 from mailpoet/re_adding_localestring
Re-adding toLocaleString on Segments listing
2016-05-20 08:50:02 -04:00
05d3756a1c Change readOnly field to boolean type 2016-05-20 15:27:46 +03:00
b3d9fc54fe Prevent user from manually editing standard scheduling date 2016-05-20 14:19:38 +03:00
20841eb5e8 Re-adding toLocaleString on Segments listing 2016-05-20 09:25:40 +02:00
9e23326d45 Merge pull request #483 from mailpoet/react_forms
Forms / WP Sync
2016-05-19 10:20:47 -04:00
f81d639b19 Merge pull request #482 from mailpoet/export_fix
Export fix
2016-05-19 15:49:12 +02:00
ab3e272020 extra space 2016-05-19 15:40:14 +02:00
4c265d1339 convert some React fields to ES6
- renamed empty label to placeholder
2016-05-19 15:19:31 +02:00
da08de0e74 - Fixes unit test
- Enables check for ZIP extension
2016-05-19 09:10:26 -04:00
6074aa927b Fix WP Sync
- added missing translations in listings (responsive view)
2016-05-19 14:32:40 +02:00
046127eeba fixed react forms (new bug discovered on new forms with default values not saved) 2016-05-19 14:30:33 +02:00
0c8a8c6854 Merge pull request #473 from mailpoet/standard_scheduling
Standard newsletter scheduling
2016-05-19 13:56:44 +03:00
acfcfdd730 - Refactors scheduler worker and updates logic responsible for scheduling
a new post notification queue
2016-05-18 15:40:41 -04:00
8f935df12a Merge branch 'standard_scheduling' of mailpoet:mailpoet/mailpoet into standard_scheduling 2016-05-18 14:37:11 -04:00
2784bb7282 - Adds queue rescheduling for post notifications 2016-05-18 14:33:55 -04:00
08a8ca4969 Change segments handling for notification newsletters 2016-05-18 21:27:58 +03:00
5a03eb9a17 Fix newsletter calls to use array access and not object access 2016-05-18 19:03:06 +03:00
91bb215e4d Change Date picker to use Wordpress date format 2016-05-18 18:38:59 +03:00
0b3a388a78 - Remove space after if;
- Change Scheduler::standard function name to a more descriptive one
2016-05-18 18:38:58 +03:00
66a93768e1 Remove unnecessary function calls 2016-05-18 18:14:54 +03:00
61df4899cd - Refactor WP DateTime helper
- Add a unit test helper to stub out Wordpress functions
2016-05-18 18:14:54 +03:00
f8b1e153be Make date picker translatable 2016-05-18 18:14:53 +03:00
f322433875 Add standard newsletter scheduling backend 2016-05-18 18:14:53 +03:00
be155c38bc Fix datepicker onChange handler 2016-05-18 17:51:39 +03:00
13ee338fb0 Add jQuery UI datepicker with Melon skin 2016-05-18 17:51:39 +03:00
f8628c1f4e - Add input validation
- Unmount datepicker on destruction
- Change prop propagation pipeline
2016-05-18 17:51:39 +03:00
f7c70be5eb Add standard newsletter scheduling UI 2016-05-18 17:51:39 +03:00
65df28d52e Merge pull request #478 from mailpoet/post_notification_update
Post notification update
2016-05-18 16:10:23 +02:00
3e658033da - Updates conditional check 2016-05-18 10:00:38 -04:00
31e082eb2b - Removes space between IF and statement 2016-05-18 10:00:38 -04:00
bf1ab3a593 - Removes space between (int) and variable 2016-05-18 10:00:38 -04:00
8540c51679 - Changes newsletter object to array in SendingQueue router
- Schedules post notification right after its creation
2016-05-18 10:00:09 -04:00
d2a6b6bd4e - Updates based on code review comments 2016-05-18 10:00:09 -04:00
06417c1e88 - Fix ALC post amount handling of boundary values;
- Change magic number to named constant
2016-05-18 10:00:09 -04:00
be238f4c67 - Fixes unit test
- Corrects method name in scheduler
2016-05-18 10:00:09 -04:00
343da0fdcc - Saves sent posts during rendering by sending queue worker
- Prevents empty notification emails from being sent
- Hooks to WP's post update and rewrite post notification logic
- Prevents scheduling multiple queues of the same newsletter
- Fixes issue with segments not updating when scheduling a newsletter
- Removes depreciated hash field & associated logic
2016-05-18 10:00:09 -04:00
dbb3c96300 Modify ALC to skip already sent posts 2016-05-18 10:00:09 -04:00
c8f7bea419 Let wordpress exclude already used posts in ALC selection 2016-05-18 10:00:09 -04:00
396ab50fa0 - Implements exclusion of duplicate posts from ALC 2016-05-18 10:00:09 -04:00
84e9ecef29 Merge pull request #477 from mailpoet/php7_mysql_fix
Fix for PHP7 & Mysql 5.7
2016-05-18 15:42:36 +02:00
57472d1a2e - Removes comment for an explicitly invoked condition 2016-05-18 09:39:29 -04:00
cb4f055263 - Updates export SQL to always use GROUP BY and aggregate columns 2016-05-17 11:07:12 -04:00
3f1bdd2c59 - Removed debug function
- Corrected comment
2016-05-17 10:01:06 -04:00
a6802a1925 - Fixes GROUP BY when using non-aggregate columns 2016-05-17 09:54:58 -04:00
a02b2d3aa0 - Updates migrator/schema to work with MySQL 5.7
- Fixes unit tests
- Fixes export's SQL query to work with strict ONLY_FULL_GROUP_BY option
2016-05-16 22:26:21 -04:00
607a151c23 Fix for PHP7 & Mysql 5.7
- added default values to not null columns
- fixed passing by reference issue on MailChimp->getDataCenter
- fixed a couple unit tests
2016-05-16 14:50:43 +02:00
c8748e5e6e Merge pull request #475 from mailpoet/uniform_listings
bulk actions / pagination and customizable limit per page for all
2016-05-16 13:37:14 +03:00
6e45a8e788 all listings have bulk actions / pagination and customizable limit per page 2016-05-13 16:53:48 +02:00
395e95bc54 Bump up release version to 0.0.28 2016-05-13 16:18:01 +03:00
a8309b436d Merge pull request #474 from mailpoet/UI_updates
Ui updates
2016-05-13 16:14:23 +03:00
77fe385645 - Disables input fields if subscriber is a WP user. #421 (5)
- Removes 'unconfirmed' status if subscriber is a WP user. #421 (9)
- Displays notice if subscriber is a WP user. #421 (6)
2016-05-12 19:38:34 -04:00
290f749220 - Renames "Update" link to "Force Sync" #421 (12)
- Adds new link "Read more" #421 (13)
2016-05-12 11:48:33 -04:00
8cb5b3729d Merge pull request #472 from mailpoet/UI_updates
Expands MailPoet admin menu for sub-subpages
2016-05-12 17:40:04 +03:00
ce4a3196ee - Highlight main MailPoet menu
- Expands menu for newsletter editor
2016-05-12 10:37:56 -04:00
40ea5a0b84 - Improves menu expansion code
::
2016-05-12 09:59:05 -04:00
af95380a62 - Expands MailPoet admin menu for sub-subpages. #431 2016-05-12 09:10:35 -04:00
483eaffc0e Merge pull request #471 from mailpoet/import_update
Import udate
2016-05-12 12:40:23 +03:00
7e86ee3266 Merge pull request #470 from mailpoet/UI_updates
Various UI fixes
2016-05-12 12:15:52 +03:00
65630e6726 - Formats numbers in export 2016-05-11 15:47:47 -04:00
23682011af - Prevents WP user's first/last name to be updated during import
- Resets "next step" button state when going to step 3 of import
- Closes #469
2016-05-11 14:44:29 -04:00
00eaa768a6 - Updates Segment model to return subscriber count only if subscriber's
status is "subscribed"
2016-05-11 13:06:26 -04:00
bc92d9a61e - Updates all datetime references to use WordPress format
- Removes depreciated datetime conversion method from Helpers
- Fixes translation & error display issue in import
Closes #432
2016-05-11 10:56:34 -04:00
a1d8dec047 - Properly formats subscriber count during import 2016-05-11 10:18:59 -04:00
ff030068b0 Merge pull request #468 from mailpoet/UI_updates
Various UI fixes
2016-05-11 16:39:28 +03:00
79e37018c9 - Fixes unit test
- Updates Settings page heading to h1
2016-05-11 08:56:37 -04:00
7ae8248339 Merge branch 'UI_updates' of mailpoet:mailpoet/mailpoet into UI_updates 2016-05-11 08:52:38 -04:00
30720975ab - Disables "confirm unconfirmed" batch action as per #432 2016-05-11 08:52:13 -04:00
b2359258d4 Update newsletter sending process to format numbers >1000 2016-05-11 11:47:23 +03:00
1bd7639cc2 - Formats all numbers >1000 to use comma
- Removes subscriber count from segments if its === 0
Fixes #431
2016-05-10 20:09:02 -04:00
2dab89135f - Sets MailPoet notice timeout to 5sec
- Removes custom timeout from import
2016-05-10 18:59:31 -04:00
c3368d69fd - Updates Segment model to return ASC sorted results
- Updates Subscriber listing to display segmnets with count
2016-05-10 18:37:37 -04:00
751d8e7852 - Adds missing page titles 2016-05-10 13:01:40 -04:00
8a4dec08e1 - Converts page title headings to H1
- Adds "back to list" to subpages
2016-05-10 10:13:57 -04:00
8b9f5ad5b1 Merge pull request #467 from mailpoet/cron_fix
- Corrects the daemon stop status
2016-05-10 15:51:53 +03:00
fc597a53bb - Corrects the daemon stop status 2016-05-09 20:28:02 -04:00
2f25dc6a20 Bump up release version to 0.0.27 2016-05-06 14:46:12 +03:00
fc38ee2f08 Merge pull request #466 from mailpoet/rendering_engine_update
Fixes empty paragraph spacing
2016-05-05 13:04:26 +03:00
33bebc6629 - Replaces empty paragraphs with single space according to specific
condition
2016-05-04 19:57:10 -04:00
14a8e02f99 Merge pull request #465 from mailpoet/browser_preview
Newsletter preview
2016-05-04 17:58:41 +03:00
0bf6c87ec7 - Fixes shortcode category name for view in browser url
- Updates shortcode regex
2016-05-04 10:45:30 -04:00
422fba2835 - Updates based on code review comments 2016-05-03 18:41:57 -04:00
f36dbb78e6 - Updates unit test 2016-05-02 13:44:36 -04:00
3213dd0d08 - Removes unused exception handlers 2016-05-02 13:19:10 -04:00
3f2199fd63 - Updates link tracking to use the same Public API data format as other
endpoints
2016-05-02 13:15:48 -04:00
a4477a9bd6 - Updates CONST values 2016-05-02 12:27:00 -04:00
52790d7bd6 - Updates code comment 2016-05-02 12:24:56 -04:00
0b9812210f - Updates CONST values 2016-05-02 11:30:37 -04:00
756dbb4641 - Updates daemon status to CONSTs
- Uncomments temporary commented out code
- Removes unnecessary exception handles in Public API
2016-05-02 10:32:34 -04:00
b38742ddc0 - Changes divider to CONST 2016-05-02 10:02:22 -04:00
49e38549ec - Fixes URLs not being properly generated when tracking is enabled 2016-05-02 10:00:35 -04:00
afcb0a0d7f - Upperases all constants 2016-05-02 09:47:54 -04:00
d18f0e50b5 - Removes debug backtrace
- Removes uncommented queue save
2016-05-02 09:23:37 -04:00
6868a07ead - Moves link saving logic into Links class 2016-05-01 21:55:24 -04:00
cca76d0d97 - Adds custom shortcode processing logic
- Updates shortcode categories code
- Completely rewrites shortcodes unit tess
2016-05-01 14:07:06 -04:00
70fa77d333 - Creates temporary folder if it doesn't exist 2016-04-30 22:38:55 -04:00
412201d965 - Updates unit test 2016-04-30 22:38:39 -04:00
045a92c7d6 - Standardizes variable names 2016-04-30 22:21:03 -04:00
2ba2e3eca5 - Refactors sending queue worker 2016-04-30 22:19:59 -04:00
90c294f60e - Updates editor/router to use the new browser preview class 2016-04-30 22:19:59 -04:00
57b953dd14 - Rewrites shortcode processing class to work with other changes 2016-04-30 22:19:59 -04:00
0f81a8db60 - Updates method to utilize constant value 2016-04-30 22:19:59 -04:00
2d6971f8df - Refactors link tracking class 2016-04-30 22:19:59 -04:00
0abe8b5371 - Implements view in browser 2016-04-30 22:19:59 -04:00
5bad682879 - Merges all shortcode links under one category
- Adds filter hooks for custom links
- Renames old shortcodes in the editor and elsewhere
2016-04-30 22:19:59 -04:00
fa9d32c230 Bump up release version to 0.0.26 2016-04-29 21:09:41 +03:00
1b3ceca7b2 Merge pull request #464 from mailpoet/undo_go_back
remove attempt at 'goback' after editing an entity
2016-04-29 21:07:10 +03:00
9be326b45d remove attempt at 'goback' after editing an entity 2016-04-29 18:35:52 +02:00
2d2e1298c4 Merge pull request #462 from mailpoet/segments_listing
React components update + bugfixes
2016-04-29 14:18:05 +03:00
5dc3a4386e Merge pull request #463 from mailpoet/queue_processed_date_fix
Updates queue processed date to account for time offset
2016-04-29 12:16:51 +03:00
b617dde266 - Updates queue processed date to account for time offset 2016-04-28 12:42:16 -04:00
962e91f9dc fixed merge fuck 2016-04-28 17:15:07 +02:00
4047b41a7f Updated all React components to their latest version
- updated code due to deprecated warnings (mostly router stuff & input default value)
- set default sender based on settings when creating new newsletter
- fixed erroneous UTC offset when displaying dates (PHP takes care of it)
2016-04-28 17:02:24 +02:00
59199140bf Merge pull request #448 from mailpoet/twig_template_cache
modified temp path/url for cache/export folders
2016-04-28 10:18:53 -04:00
1079c0beae Merge pull request #458 from mailpoet/click_rate_update
fixes click rate
2016-04-28 17:01:05 +03:00
1694baab7d Merge branch 'click_rate_update' of mailpoet:mailpoet/mailpoet into click_rate_update 2016-04-28 09:37:04 -04:00
335ac9c778 - Updates statistics to display only 1 click event from a given subscriber
Fixes #417 (7)
2016-04-28 09:34:59 -04:00
65efd234f9 - Fixes duplicate status for different newsletters
- Updates statistics not to display NaN values
2016-04-28 09:25:09 -04:00
f726d943db Merge pull request #460 from mailpoet/text_rendering_fix
Text rendering fix
2016-04-28 12:55:08 +03:00
2d1e950097 Merge pull request #459 from mailpoet/scheduled_status
Updates newsletter listing status
2016-04-28 12:52:46 +03:00
ca2f16970b - Fixes and issue with lists not retaining links and other HTML content 2016-04-27 21:22:39 -04:00
4a123f8fe9 - Displays proper status for scheduled newsletters 2016-04-27 20:59:04 -04:00
7b224328e1 - Updates statistics to display only 1 click event from a given subscriber
Fixes #417 (7)
2016-04-27 20:55:19 -04:00
699dfa19f0 - Removes leftover console command 2016-04-27 20:14:06 -04:00
06d56fe19d - Adds "scheduled for" status for notifications
- Adds post notification/welcome activation message
- Implements schedule update when editing newsletter
Implements #405 (3 & 7)
2016-04-27 20:08:45 -04:00
1b7ac62b5c Merge pull request #453 from mailpoet/queue_statistics
Send statistics
2016-04-27 21:28:42 +03:00
ae358ce13e - Updates statistics SQL query
- Fixes tests + expands coverage
- Removes leftover console command
2016-04-27 21:26:47 +03:00
18f35b5e91 - Displays statistics in newsletter listing 2016-04-27 21:26:47 +03:00
67ca305b7f - Adds withStatistics method to the Newsletter model
- Updates Newsletter router to return statistics
- Returns only completed/running queues
2016-04-27 21:26:47 +03:00
cb0908fc70 Merge pull request #454 from mailpoet/post_title_shortcode_fix
Updates newsletter post title shortcode
2016-04-27 21:24:34 +03:00
42df59076d Merge pull request #457 from mailpoet/forms_listing
Forms listing
2016-04-27 15:15:53 +03:00
ff8be3bdc6 Merge pull request #456 from mailpoet/manage_subscription_custom_pages
don't enforce our content on custom subscription pages
2016-04-27 15:03:27 +03:00
f7b6dcf409 - Remove bulk actions from forms
- form name is now a link to the edit form page
- added "row-title" class so that the name is "bigger"
2016-04-27 13:31:30 +02:00
940f3848dd Merge pull request #452 from mailpoet/form_stats
Form subscription stats
2016-04-27 14:14:42 +03:00
e847ad2df2 don't enforce our content on custom subscription pages 2016-04-27 11:13:36 +02:00
2459a103fd - Ignores sticky posts when fetching latest post title
- Updates unit test
2016-04-26 19:53:23 -04:00
0dd3f2178e updated form stats to record individual subscription 2016-04-26 18:12:05 +02:00
de873eca71 Unit test for StatisticsForms model
- improved incrementation of subscriptions count so that we don't need to fetch the record after an update
2016-04-26 15:38:42 +02:00
ef461da77f Statistics for Form Subscriptions
- added statistics_forms table
- added corresponding model to record stats
- record stats whenever someone subscribes via a form
2016-04-26 15:16:37 +02:00
caf6dcddfa Merge pull request #451 from mailpoet/react_select_fix
Subscribers bulk actions with an extra select
2016-04-26 14:00:32 +03:00
9684c88651 refactor 2016-04-26 12:43:48 +02:00
256bca8ed9 Fix selection.jsx by adding a check before trying to destroy the select2 instance 2016-04-26 12:19:39 +02:00
8d56e8582f Merge pull request #450 from mailpoet/unsubscribe_tracking
Implements tracking of unsubscribe events
2016-04-25 21:32:10 +03:00
56959f2f49 Terminate execution after redirecting a click 2016-04-25 20:33:17 +03:00
bbc7de6898 - Removes commit leftovers 2016-04-25 12:02:56 -04:00
0df246da15 Merge pull request #447 from mailpoet/tracking_fix
Fixes click tracking that would end request when calling open tracking
2016-04-25 18:37:15 +03:00
757e18355d Merge pull request #446 from mailpoet/welcome_email_fix
welcome email fix
2016-04-25 18:36:57 +03:00
4b29b04bd1 - Implements tracking of unsubscribe events 2016-04-25 11:06:01 -04:00
6cb94bc413 - Updates open rate tracking class constructor 2016-04-25 09:43:39 -04:00
f624c891ab - Capitalizes CONST values 2016-04-25 09:42:02 -04:00
b83abf0ac5 modified temp path/url 2016-04-25 14:26:27 +02:00
ef1b0036e5 - Fixes click tracking that would end request when calling open tracking 2016-04-22 20:30:16 -04:00
5efa9f65c6 - Fixes an issue with welcome email not being sent to WP user with any
role
2016-04-22 18:51:51 -04:00
a8e3dd424e Bump up release version to 0.0.25 2016-04-22 23:25:28 +03:00
26628ba156 Merge pull request #445 from mailpoet/sending_fix
Fixes check for changed newsletter content
2016-04-22 23:22:30 +03:00
78cabde9e1 Select an existing segment as initial value for Welcome segment select 2016-04-22 16:08:45 -04:00
1ec0372c2d - Fixes check for changed newsletter content 2016-04-22 16:08:45 -04:00
31e2d5e771 Merge pull request #444 from mailpoet/fix_router
fixed Router when data represents a single variable (not an array)
2016-04-22 18:05:38 +03:00
d2dbf86a9c fixed Router when data represents a single variable (not an array) 2016-04-22 16:59:28 +02:00
1c39d39078 Merge pull request #442 from mailpoet/sending_subscriber_recalculation
Dynamically updates queue's subscriber count
2016-04-22 17:03:09 +03:00
6d0795abba Merge pull request #438 from mailpoet/non_ajax_subscription
Non ajax subscription
2016-04-22 09:51:47 -04:00
5f45c6cc74 - Extracts subscriber recalculation into a spearate method
- Updates subscriber recalculation logic
2016-04-22 09:47:53 -04:00
d4d806e247 Merge pull request #443 from mailpoet/editor_rendering
Editor rendering update
2016-04-22 15:38:54 +02:00
758a545eb6 Merge pull request #441 from mailpoet/shortcodes_fix
Updates the newsletter shortcode
2016-04-22 13:45:15 +03:00
578088d2e5 Add fallback fonts for all editor fonts 2016-04-22 12:38:59 +03:00
e17dba2b07 Add blockquote bottom margin 2016-04-22 12:30:57 +03:00
7e5cf533f0 Fixes class name 2016-04-22 12:30:57 +03:00
f7c656aed5 Fixes blockquote content duplication 2016-04-22 12:30:57 +03:00
2e42305710 Updates line breaks in footer/header to match the editor 2016-04-22 12:30:57 +03:00
b3e310652e Remove bottom margin from paragraphs 2016-04-22 12:30:57 +03:00
6737158130 - Change paragraph bottom margin to equal line-height
- Remove horizontal blockquote margin to match Renderer;
- Removed bottom padding from last text elements in column;
- Removed bottom padding from full width images;
2016-04-22 12:30:57 +03:00
906558a772 Apply paragraph styles to LI tags 2016-04-22 12:30:57 +03:00
d559483c7b - Add bottom margin to headings
- Fix top and bottom margin for UL tags
- Add bottom margin to LI tags
2016-04-22 12:30:57 +03:00
bec3e02285 Remove vertical button padding, following renderer 2016-04-22 12:30:57 +03:00
9d6d72dd8c Use fallback font stack for Lucida font 2016-04-22 12:30:57 +03:00
3b76f838d1 Merge pull request #440 from mailpoet/sending_fix
Adds condition that prevents duplicate and/or stuck post notifications
2016-04-22 11:32:14 +03:00
909ad86e33 - Recalculates the number of processed/to process subscribers if the
original count does not the current count from the database
2016-04-21 21:36:37 -04:00
dd8f58e35e - Fixes incorrect newsletter issue number display
- Updates newsletter 'last post title' logic
- Updates shortcode extraction regex to allow limiting extraction of only
  specific categories
- Updates unit tests
- Closes #380
2016-04-21 20:04:02 -04:00
4b2061fcfa - Adds condition that prevents duplicate and/or stuck post notifications 2016-04-21 18:20:13 -04:00
8e6ca502b3 Merge pull request #439 from mailpoet/sending_fix
Sending fix
2016-04-21 19:05:49 +03:00
ef85834db5 - Removes debugging function 2016-04-21 11:32:29 -04:00
2bdcd0eb42 - Prevents duplicate newsletters from being sent 2016-04-21 10:45:00 -04:00
3b90be4122 - Fixes scheduling of notifications 2016-04-21 10:44:43 -04:00
375e70d84b success messages in both context when subscribing to a form 2016-04-21 12:39:14 +02:00
42d586610e form subscription only using Router now 2016-04-20 16:59:18 +02:00
e4a5438512 Merge pull request #435 from mailpoet/open_tracking
Implements open tracking
2016-04-20 14:32:20 +03:00
11b22fa63a - Enables newsletter open tracking when links are clicked 2016-04-19 21:01:27 -04:00
45b933d635 - Implements open tracking 2016-04-19 21:01:27 -04:00
15cf087d40 - Implements post-processing filter during rendering 2016-04-19 21:01:27 -04:00
ed09c3e5d4 Merge pull request #427 from mailpoet/batch_sending_fix
Adds missing rendered subject to the queue during bulk processing
2016-04-18 18:27:24 +03:00
285a556f21 Merge pull request #426 from mailpoet/subscriber_selection_update
Updates subscriber selection logic
2016-04-18 17:41:03 +03:00
dc1ef2af47 - Removes check for signup confirmation when selecting subscribers 2016-04-18 17:33:08 +03:00
6f17f0d2d1 Merge branch 'subscriber_selection_update' 2016-04-18 17:28:53 +03:00
91076580ef - Enables check for subscribers who has "subscribed" status during
newsletter sending
- Enforces "signup_confirmation" option when selecting subscribers for
  newsletter sending
- Updates unit tests
2016-04-18 17:28:37 +03:00
d4abaa7150 Merge branch 'link_tracking_update' 2016-04-18 16:57:56 +03:00
d2bc0fd24a - Removes check for signup confirmation when selecting subscribers 2016-04-18 08:45:38 -04:00
1f26079b5f - Removes unused properties from Links class and converts methods to
static
2016-04-16 16:48:35 -04:00
87a1211a55 - Adds missing rendered subject to the queue during bulk processing 2016-04-15 20:06:35 -04:00
0ca0a7d029 - Enables check for subscribers who has "subscribed" status during
newsletter sending
- Enforces "signup_confirmation" option when selecting subscribers for
  newsletter sending
- Updates unit tests
2016-04-15 20:02:44 -04:00
cb3d49f200 Bump up release version to 0.0.24 2016-04-15 21:27:07 +03:00
c0da428c27 Merge pull request #425 from mailpoet/subscription_pages
Subscription pages
2016-04-15 19:07:20 +02:00
435cd9b777 fixed unsubscribe action + updated public api decodeData to prevent warning 2016-04-15 18:59:20 +02:00
bfde34eb8d - Rebases master
- Updates PubliAPI's logic dealing with request data
2016-04-15 12:10:56 -04:00
a4c1b24c35 fixed unit tests 2016-04-15 11:56:33 -04:00
2cbd2d54f3 Subscription pages 2016-04-15 11:56:33 -04:00
378f6d803a Merge pull request #422 from mailpoet/link_tracking
Implements links tracking
2016-04-15 18:47:14 +03:00
af4d29ebe6 - Updates codes based on Taut's comments 2016-04-15 11:36:57 -04:00
599661e028 - Updates code based on Taut's comments 2016-04-14 20:35:56 -04:00
84294b7ee6 - Adds subsription tracking option to Settings->Advanced
- Updates sending queue worker to not replace links if tracking is
  disabled
Closes #417
2016-04-14 19:37:27 -04:00
a3e6eb5bba - Implementsi tracking clicks & redirecting URLs 2016-04-14 19:37:27 -04:00
67359980e9 - Renames statistics table to the new "statistics_entity" format
- Adds new clicks statistics model
2016-04-14 19:37:27 -04:00
f1b955d74a - Implements link extraction, processing, replacement and saving 2016-04-14 19:37:27 -04:00
809be415c5 Merge pull request #423 from mailpoet/text_block_fix
Fixes 'Can't use method return value in write context' error
2016-04-14 12:32:31 +03:00
457a4d1bba - Fixes 'Can't use method return value in write context' error 2016-04-13 22:46:59 -04:00
1e16912763 Merge pull request #420 from mailpoet/sending_queue_router_fix
Prevents newsletters from being queued when mailer is not configured
2016-04-12 16:02:18 +03:00
e2c9971c99 - Prevents newsletters from being queued when mailer is not configured
Closes #342
2016-04-12 08:58:21 -04:00
c1d31ca400 Merge pull request #414 from mailpoet/sending_last_step
Sending options in last newsletter creation step
2016-04-12 13:14:34 +02:00
a011c3aade Fix standard newsletters to not send when segments are missing 2016-04-12 14:11:26 +03:00
e35e97cdbf Fix Select2 integration in Form component 2016-04-12 14:04:01 +03:00
49a59d35a1 Add number of subscribers to segment selection 2016-04-12 14:04:01 +03:00
9a46640c15 Allow selecting only published segments, fix "Save" translation 2016-04-12 14:04:01 +03:00
678a0b3835 Add form loading state, remove errors and params 2016-04-12 14:04:01 +03:00
0c008325c4 - Fix unmounting select2 component
- Fix subjects of welcome and notification newsletters
- Remove debugging statements
2016-04-12 14:04:01 +03:00
ad5441487b Show different fields based on newsletter type, fix saving across
multiple endpoints
2016-04-12 14:04:01 +03:00
104620a40a Add react component support to Forms, make newsletter scheduling
components reusable
2016-04-12 14:04:01 +03:00
c1a3ba67f5 Merge pull request #418 from mailpoet/fix_initializer
fix cron issue
2016-04-11 09:41:08 -04:00
c42bbf3dc4 fix cron issue 2016-04-11 15:35:47 +02:00
7d34274fbf Merge pull request #416 from mailpoet/rendering_engine_update
Sets text-align to left when it's not center|justify|right
2016-04-11 13:51:42 +03:00
f930b3303b - Sets text-align to left when it's not center|justify|right 2016-04-08 20:37:00 -04:00
a25ea3ddf6 Bump up release version to 0.0.23 2016-04-09 02:08:05 +03:00
021349caee Merge pull request #415 from mailpoet/public_api_update
Updates query parameters for public API
2016-04-09 02:01:20 +03:00
89f2958d23 - Updates cron to use new public API query parameters 2016-04-08 18:53:57 -04:00
8c8435766e - Updates query parameters for public API 2016-04-08 18:40:45 -04:00
40dd1bbb3b Merge pull request #413 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-08 22:27:48 +03:00
ba40437eb9 - Enables dynamic line-height based on font-size 2016-04-08 15:19:14 -04:00
813db1ae33 - Sets LI bottom margin to 10px 2016-04-08 08:45:09 -04:00
9588397e4e - Moves all styles logic to the StylesHelper class
- Sets margin-bottom on h1-4 to be 0.3*font-size
2016-04-07 19:39:09 -04:00
ecf83ca419 - Disables color compression when tidying CSS 2016-04-07 19:39:09 -04:00
9cc494f0fa - Removes image height HTML attribute and sets "height:auto" style 2016-04-07 19:39:09 -04:00
dd4b7e4d1c - Adds lines heights to LIs 2016-04-07 19:39:09 -04:00
738b2f6c17 - Adds dynamic line breaks to paragraphs when the elment is followed by
list
2016-04-07 19:39:09 -04:00
33289342d3 - Implements dynamic margins on headings 2016-04-07 19:39:09 -04:00
a00f1efcfe - Updates unit test due to button style change 2016-04-07 19:38:15 -04:00
b89897e6d4 - Adds line breaks after headings IF they are followed by paragraph
- Adds line breaks after blocksquotes
- Updates margin on lists
- Adds line breaks after paragraphs IF they are not the last element
2016-04-07 19:38:15 -04:00
c539837896 - Makes button's stroke width = border width 2016-04-07 19:38:15 -04:00
27edf5f71d - Updates list styles 2016-04-07 19:38:15 -04:00
32cc5644f9 - Removes height:auto from images 2016-04-07 19:38:15 -04:00
8a664aa7f1 - Set left text alignment on all elements when alignment is otherwise
absent
2016-04-07 19:38:15 -04:00
7e5e8a4282 - Updates button padding 2016-04-07 19:38:15 -04:00
70d5d609e2 Merge pull request #412 from mailpoet/scheduled_sending
Fixes an issue with welcome emails not being sent to confirmed subscribers
2016-04-07 12:09:30 +03:00
19160c99e1 Merge pull request #411 from mailpoet/send_with_review
Send with review
2016-04-06 18:23:50 +03:00
99b2a7457e Merge pull request #410 from mailpoet/newsletter_send_preview_fix
Fixes an issue with shortcodes not rendering when sending newsletter
2016-04-06 18:05:39 +03:00
945d7edc70 Send with review
- updated MailPoet logo
- added SPF
- hide Dkim for beta
- added warning in case the number of emails/sec is too high
2016-04-06 16:57:10 +02:00
6a97badfed - Fixes an issue with welcome emails not being seint to confirmed
subscribers
2016-04-05 14:34:22 -04:00
5ec8e4ed52 - Fixes an issue with shortcodes not rendering when sending newsletter
preview
2016-04-05 09:54:04 -04:00
1569b5f80a Merge pull request #409 from mailpoet/scheduled_sending
Fixed an issue with newsletter schedule not being saved
2016-04-05 12:10:34 +03:00
e62ecc5036 - Fixes duplicate detection check 2016-04-04 21:09:15 -04:00
b0150e184b - Fixed an issue with newsletter schedule not being saved 2016-04-04 19:41:18 -04:00
8b96854f39 Bump up release version to 0.0.22 2016-04-01 14:57:11 +03:00
28d8600078 Merge pull request #408 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-01 12:37:11 +03:00
4f30158722 - Updates button height calculation
- Removes background color when it's set to "transparent"
- Removes second line break from paragraphs
- Introduces other changes based on Becks's testing
- Updates unit tests
2016-03-31 19:19:58 -04:00
4486f9c5b0 Merge pull request #407 from mailpoet/scheduled_sending
Prevents welcome emails to be sent to unconfirmed subsribers
2016-03-31 14:53:41 +03:00
8515dcf29b Merge pull request #406 from mailpoet/rendering_engine_update
Rendering engine update
2016-03-31 14:05:18 +03:00
d16eb87782 - Prevents welcome emails to be sent to unconfirmed subscribers
- Closes #384
2016-03-30 20:59:55 -04:00
3c77e5d25e - Removes dynamic line-height based on column width
- Updates logic behind removing last element bottom padding
- Properly sets font size for H1-4 tags, header, footer and text
2016-03-30 20:09:06 -04:00
aa1a2a0da9 - Updates button padding 2016-03-30 20:09:06 -04:00
7c9029b227 Bump up release version to 0.0.21 2016-03-25 13:10:37 +02:00
1823bf606a Merge pull request #403 from mailpoet/signup_confirmation_review
Signup confirmation review
2016-03-24 14:43:36 -04:00
525b7fdd65 Merge pull request #402 from mailpoet/scheduled_sending
Scheduled sending
2016-03-24 16:10:56 +02:00
11031d2b56 Remove unused cron translations, fix start and stop button translations 2016-03-24 16:10:39 +02:00
4d45635d03 - Implements "in any role" for WP user welcome e-mails 2016-03-24 10:02:04 -04:00
b81764402b - Updates hooks used to schedule welcome email
- Simplifies cron status message
- Implements newsletter change detection between scheduled queues
- Implements detection for subscribers who unsubscribed from list
- Implements detection for WP users who changed roles
- Updates scheduler worker logic
- Various fixes
2016-03-24 10:02:04 -04:00
947e1150d8 - Removes cron worker hooks as the execution timer can't be persisted
between them
2016-03-24 10:00:52 -04:00
97f0e512af populate from/reply_to for signup confirmation
- added loading screen to reinstall process
2016-03-24 14:22:37 +01:00
f15374de43 don't replace title on custom mailpoet pages 2016-03-24 14:22:37 +01:00
f082c065d1 Confirmation email + Subscription pages
- form as an iframe: increased marginY
- fixed issue with page titles (old themes using wp_title hook)
- redirect on reinstall instead of showing a message (to avoid being able to re-save old settings)
2016-03-24 14:22:37 +01:00
72a9951125 Merge pull request #400 from mailpoet/i18n
Javascript translations
2016-03-24 13:11:51 +01:00
a82d9a63d4 Add missing unsubscribedOn translation on subscribers form 2016-03-24 14:09:35 +02:00
a7e9979781 Merge pull request #398 from mailpoet/rendering_engine_update
Updates rendering engine based on Becs's comments
2016-03-24 13:18:06 +02:00
cebd1ee7ae Merge pull request #399 from mailpoet/editor_rendering
Adapts editor rendering to Becs' feedback
2016-03-23 18:21:00 -04:00
5faa467306 - Updates merge issue with template
- Updates button width/height based on border radius
2016-03-23 18:18:21 -04:00
53eb9cd2ae Translate JS code in listings and forms 2016-03-23 18:59:01 +02:00
7f6eed6d66 Translate cron, forms, newsletters, segments and subscribers 2016-03-23 14:52:06 +02:00
74f3fa65cd Add translatiosn for newsletter creation and listings 2016-03-23 14:52:06 +02:00
8723aa4e4e Remove obsolete test file 2016-03-23 14:52:06 +02:00
ccab8b4cf3 Add MailPoet.I18n for basic translation handling, removed MailPoetI18n 2016-03-23 14:52:06 +02:00
4c4a4ab31d Reduce max button width to 288px, max button border to 10px 2016-03-23 14:09:19 +02:00
45df02b0ec Merge pull request #395 from mailpoet/many_improvements
Many improvements
2016-03-23 13:38:40 +02:00
dd7067e590 Merge pull request #397 from mailpoet/scheduled_sending
Updates scheduling logic to work with the new normalized DB time
2016-03-23 11:51:58 +02:00
1219b5ba49 Merge pull request #396 from mailpoet/timezone_fix
Normalizes time difference between WP and database
2016-03-23 11:45:42 +02:00
01496ac813 Merge branch 'scheduled_sending' of mailpoet:mailpoet/mailpoet into scheduled_sending 2016-03-22 20:55:42 -04:00
3b3ccc18ce - Updates scheduling logic to work with the new normalized DB time 2016-03-22 20:43:16 -04:00
3dce951e66 - Updates rendering engine based on Becs's comments
- Prevents removing of side padding
- Enables font size on output
- Sets line-height in footer
- Adds image height:auto
- Adds mobile styles for buttons
- Updates style sheet
2016-03-22 20:21:39 -04:00
db1dc172aa - Updates scheduling logic to work with the new normalized DB time 2016-03-22 18:26:37 -04:00
26c5cc1e43 - Normalizes time difference between WP and database 2016-03-22 13:05:41 -04:00
f91bfbf473 handle form as iframe 2016-03-22 17:25:25 +01:00
3dae0ef13f increased page input in listings 2016-03-22 17:25:25 +01:00
3281ac390e Improved segment selection errors in form editor
- improved error display to make it more obvious (added border on select2 on error)
2016-03-22 17:25:25 +01:00
d731a6b432 toggle segment selection validation on form editor 2016-03-22 17:25:25 +01:00
bb869e8ae8 Fixed setWindowTitle for WP version < 4.4
- fixed variable name Env::temp_url (instead of temp_URL)
- updated cache folder to be in the temp folder (uploads) instead of views (within plugin)
2016-03-22 17:25:25 +01:00
7331e5cabd Merge pull request #392 from mailpoet/shortcodes_update
Updates shortcodes logic
2016-03-21 17:58:38 +02:00
ba05ca35af - Implements shortcodes rendering in subject line 2016-03-21 11:45:08 -04:00
91e7bf6336 Merge branch 'shortcodes_update' of mailpoet:mailpoet/mailpoet into shortcodes_update 2016-03-21 10:12:10 -04:00
ff58067d55 Change array_column to Helpers::arrayColumn method 2016-03-21 16:07:20 +02:00
2ba6bb339e Fix newsletter options format for newsletters 2016-03-21 15:50:48 +02:00
a47afdd313 - Fixes queue worker issue 2016-03-21 09:09:43 -04:00
608b559ee1 - Minor adjustment to the shortcodes logic 2016-03-21 09:09:43 -04:00
181ed45d0b - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-21 09:09:43 -04:00
29fac8d052 Merge pull request #394 from mailpoet/scheduled_sending
Updates scheduled sending
2016-03-21 15:06:30 +02:00
a3d7d53eea - Fixes queue worker issue 2016-03-21 08:58:51 -04:00
3f6caf5fa4 - Implements scheduler worker for welcome and post notifications
- Updates sending queue worker to save rendered newsletter body
- Updates sending queue router to schedule post notification newsletters
2016-03-20 22:01:01 -04:00
42c4139ba5 - Minor adjustment to the shortcodes logic 2016-03-20 11:37:59 -04:00
ad31b143d2 - Updates scheduler worker to process queued newsletters 2016-03-19 12:29:10 -04:00
6ec15bec22 - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-19 11:19:22 -04:00
71d8fb0d93 Bump up version to 0.0.20 2016-03-18 19:40:11 +02:00
2f293da7a3 Merge pull request #391 from mailpoet/scheduled_sending
Scheduled sending
2016-03-18 18:49:47 +02:00
17b56f0160 - Fixes scheduling issues
- Fixes unit test
- Updates as per code review comments
2016-03-18 12:11:38 -04:00
bb9fce7f82 - Implements post notification scheduling 2016-03-18 11:15:31 -04:00
ad4f1f8326 Merge pull request #390 from mailpoet/newsletter_footer_links
Newsletter footer links
2016-03-18 16:10:17 +02:00
8ece62c9a6 fix tests 2016-03-18 14:58:33 +01:00
a9b9e9c631 Updated tests in order to fix WP related issues 2016-03-18 14:30:59 +01:00
6e289b6a8f - Implements welcome e-mail scheduling 2016-03-17 11:22:29 -04:00
20ced8b099 replace mailpoet_title 'hack' by checking the post type 2016-03-17 15:48:41 +01:00
f38b632707 properly handle custom subscriptions pages 2016-03-17 15:48:41 +01:00
8ce0595342 turned static values into constants 2016-03-17 15:48:06 +01:00
f11de2f1ad Updated shortcodes for unsubscribe/manage/browser links
- fixed all issues in #387 except the custom mailpoet pages
2016-03-17 15:48:06 +01:00
e28451d410 removed edit page in settings + manage subscription update 2016-03-17 15:45:05 +01:00
72882aaf2b fixed shortcodes replacement for "global:" in newsletters
- extracted "get subscription pages urls" from models\Subscriber
- added unit tests for subscription\url class (not working because of WP/Codeception issue)
2016-03-17 15:45:05 +01:00
fdf9dd0fa3 Merge pull request #386 from mailpoet/editor_refactor
Editor refactor
2016-03-17 15:19:04 +01:00
97dd0abea2 fixed makepot task
- added proper grunt module (grunt-cli)
- updated translations extract code to remove warnings when no translations are found
2016-03-17 15:08:38 +01:00
a099174226 Revert "Extract text labels from React code for translation in twig views"
This reverts commit 9aef6850c2.

Conflicts:
	views/newsletters.html
2016-03-17 15:24:07 +02:00
a054acc6e6 Merge pull request #389 from mailpoet/import_xss_update
Updates import to santize user input
2016-03-17 12:02:58 +02:00
74254d7e2a - Updates import to santize user input 2016-03-15 13:06:21 -04:00
1795964c69 - Updates composer dependencies 2016-03-15 12:10:09 -04:00
0118b2472a Fix loading of makepot task for pot translation file generation 2016-03-10 19:24:35 +02:00
5fe03f0dee Add event selection explanation to welcome email type 2016-03-10 17:49:06 +02:00
9aef6850c2 Extract text labels from React code for translation in twig views 2016-03-10 17:08:40 +02:00
f688a69f8b Fix translations to be injected at config time 2016-03-07 17:52:14 +02:00
b2682fa0b7 Remove console.log statements from newsletter editor 2016-03-07 16:57:20 +02:00
18b15c5440 Restructure JS loading to not duplicate JS asset loading 2016-03-07 16:15:26 +02:00
c3416977bb Replace editor translations with Twig localize function 2016-03-07 15:42:12 +02:00
88a00bc38c Rename editor form.html to editor.html 2016-03-07 14:42:12 +02:00
379 changed files with 20398 additions and 8797 deletions

View File

@ -89,14 +89,14 @@ class RoboFile extends \Robo\Tasks {
}
function makepot() {
$this->_exec('grunt makepot'.
$this->_exec('./node_modules/.bin/grunt makepot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__
);
}
function pushpot() {
$this->_exec('grunt pushpot'.
$this->_exec('./node_modules/.bin/grunt pushpot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__
);
@ -123,7 +123,7 @@ class RoboFile extends \Robo\Tasks {
$this->compileJs();
$this->_exec(join(' ', array(
'./node_modules/mocha/bin/mocha',
'./node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.js',
'tests/javascript/testBundles/**/*.js'
)));
@ -141,8 +141,28 @@ class RoboFile extends \Robo\Tasks {
$this->_exec('vendor/bin/codecept run -g failed');
}
function qaLint() {
$this->_exec('./tasks/php_lint.sh lib/ tests/');
}
function qaCodeSniffer($severity='errors') {
if ($severity === 'all') {
$severityFlag = '-w';
} else {
$severityFlag = '-n';
}
$this->_exec(
'./vendor/bin/phpcs '.
'--standard=./tasks/code_sniffer/MailPoet '.
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
'./lib/Config/PopulatorData/Templates/* '.
'lib/ '.
$severityFlag
);
}
protected function loadEnv() {
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
}
}
}

View File

@ -1,6 +1,7 @@
@import 'nib'
@require 'select2/dist/css/select2.css'
@require 'datepicker/datepicker'
@require 'common'
@require 'modal'
@ -8,6 +9,8 @@
@require 'form_editor'
@require 'listing'
@require 'listing/newsletters'
@require 'box'
@require 'breadcrumb'
@ -18,4 +21,4 @@
@require 'settings'
@require 'progress_bar'
@require 'subscribers'
@require 'subscribers'

View File

@ -27,13 +27,12 @@
img
min-width: 150px
min-height: 150px
height: auto
width: 110%
position: relative
top: 0
top: 50%
left: 50%
transform: translate(-50%, 0%)
transform: translate(-50%, -50%)
.mailpoet_overlay
position: absolute

View File

@ -0,0 +1,2 @@
@require 'jquery-ui-1.10.1.css'
@require 'melon.datepicker.css'

View File

@ -0,0 +1,649 @@
/*! jQuery UI - v1.10.1 - 2013-03-10
* http://jqueryui.com
* Includes: jquery.ui.core.css, jquery.ui.datepicker.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
* Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-clearfix {
min-height: 0; /* support: IE7 */
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0);
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-datepicker {
width: 17em;
padding: .2em .2em 0;
display: none;
}
.ui-datepicker .ui-datepicker-header {
position: relative;
padding: .2em 0;
}
.ui-datepicker .ui-datepicker-prev,
.ui-datepicker .ui-datepicker-next {
position: absolute;
top: 2px;
width: 1.8em;
height: 1.8em;
}
.ui-datepicker .ui-datepicker-prev-hover,
.ui-datepicker .ui-datepicker-next-hover {
top: 1px;
}
.ui-datepicker .ui-datepicker-prev {
left: 2px;
}
.ui-datepicker .ui-datepicker-next {
right: 2px;
}
.ui-datepicker .ui-datepicker-prev-hover {
left: 1px;
}
.ui-datepicker .ui-datepicker-next-hover {
right: 1px;
}
.ui-datepicker .ui-datepicker-prev span,
.ui-datepicker .ui-datepicker-next span {
display: block;
position: absolute;
left: 50%;
margin-left: -8px;
top: 50%;
margin-top: -8px;
}
.ui-datepicker .ui-datepicker-title {
margin: 0 2.3em;
line-height: 1.8em;
text-align: center;
}
.ui-datepicker .ui-datepicker-title select {
font-size: 1em;
margin: 1px 0;
}
.ui-datepicker select.ui-datepicker-month-year {
width: 100%;
}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year {
width: 49%;
}
.ui-datepicker table {
width: 100%;
font-size: .9em;
border-collapse: collapse;
margin: 0 0 .4em;
}
.ui-datepicker th {
padding: .7em .3em;
text-align: center;
font-weight: bold;
border: 0;
}
.ui-datepicker td {
border: 0;
padding: 1px;
}
.ui-datepicker td span,
.ui-datepicker td a {
display: block;
padding: .2em;
text-align: right;
text-decoration: none;
}
.ui-datepicker .ui-datepicker-buttonpane {
background-image: none;
margin: .7em 0 0 0;
padding: 0 .2em;
border-left: 0;
border-right: 0;
border-bottom: 0;
}
.ui-datepicker .ui-datepicker-buttonpane button {
float: right;
margin: .5em .2em .4em;
cursor: pointer;
padding: .2em .6em .3em .6em;
width: auto;
overflow: visible;
}
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
float: left;
}
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi {
width: auto;
}
.ui-datepicker-multi .ui-datepicker-group {
float: left;
}
.ui-datepicker-multi .ui-datepicker-group table {
width: 95%;
margin: 0 auto .4em;
}
.ui-datepicker-multi-2 .ui-datepicker-group {
width: 50%;
}
.ui-datepicker-multi-3 .ui-datepicker-group {
width: 33.3%;
}
.ui-datepicker-multi-4 .ui-datepicker-group {
width: 25%;
}
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
border-left-width: 0;
}
.ui-datepicker-multi .ui-datepicker-buttonpane {
clear: left;
}
.ui-datepicker-row-break {
clear: both;
width: 100%;
font-size: 0;
}
/* RTL support */
.ui-datepicker-rtl {
direction: rtl;
}
.ui-datepicker-rtl .ui-datepicker-prev {
right: 2px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next {
left: 2px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-prev:hover {
right: 1px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next:hover {
left: 1px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane {
clear: right;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button {
float: left;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
.ui-datepicker-rtl .ui-datepicker-group {
float: right;
}
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
border-right-width: 0;
border-left-width: 1px;
}
/* Component containers
----------------------------------*/
.ui-widget {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1.1em;
}
.ui-widget .ui-widget {
font-size: 1em;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1em;
}
.ui-widget-content {
border: 1px solid #dddddd;
background: #eeeeee url(../img/datepicker/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;
color: #333333;
}
.ui-widget-content a {
color: #333333;
}
.ui-widget-header {
border: 1px solid #e78f08;
background: #f6a828 url(../img/datepicker/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x;
color: #ffffff;
font-weight: bold;
}
.ui-widget-header a {
color: #ffffff;
}
/* Interaction states
----------------------------------*/
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
border: 1px solid #cccccc;
background: #f6f6f6 url(../img/datepicker/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #1c94c4;
}
.ui-state-default a,
.ui-state-default a:link,
.ui-state-default a:visited {
color: #1c94c4;
text-decoration: none;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus {
border: 1px solid #fbcb09;
background: #fdf5ce url(../img/datepicker/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #c77405;
}
.ui-state-hover a,
.ui-state-hover a:hover,
.ui-state-hover a:link,
.ui-state-hover a:visited {
color: #c77405;
text-decoration: none;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #fbd850;
background: #ffffff url(../img/datepicker/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #eb8f00;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #eb8f00;
text-decoration: none;
}
/* Interaction Cues
----------------------------------*/
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
border: 1px solid #fed22f;
background: #ffe45c url(../img/datepicker/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x;
color: #363636;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
.ui-widget-header .ui-state-highlight a {
color: #363636;
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error {
border: 1px solid #cd0a0a;
background: #b81900 url(../img/datepicker/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat;
color: #ffffff;
}
.ui-state-error a,
.ui-widget-content .ui-state-error a,
.ui-widget-header .ui-state-error a {
color: #ffffff;
}
.ui-state-error-text,
.ui-widget-content .ui-state-error-text,
.ui-widget-header .ui-state-error-text {
color: #ffffff;
}
.ui-priority-primary,
.ui-widget-content .ui-priority-primary,
.ui-widget-header .ui-priority-primary {
font-weight: bold;
}
.ui-priority-secondary,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70);
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35);
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* For IE8 - See #6059 */
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
width: 16px;
height: 16px;
background-position: 16px 16px;
}
.ui-icon,
.ui-widget-content .ui-icon {
background-image: url(../img/datepicker/ui-icons_222222_256x240.png);
}
.ui-widget-header .ui-icon {
background-image: url(../img/datepicker/ui-icons_ffffff_256x240.png);
}
.ui-state-default .ui-icon {
background-image: url(../img/datepicker/ui-icons_ef8c08_256x240.png);
}
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon {
background-image: url(../img/datepicker/ui-icons_ef8c08_256x240.png);
}
.ui-state-active .ui-icon {
background-image: url(../img/datepicker/ui-icons_ef8c08_256x240.png);
}
.ui-state-highlight .ui-icon {
background-image: url(../img/datepicker/ui-icons_228ef1_256x240.png);
}
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon {
background-image: url(../img/datepicker/ui-icons_ffd27a_256x240.png);
}
/* positioning */
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-on { background-position: -96px -144px; }
.ui-icon-radio-off { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 4px;
}
/* Overlays */
.ui-widget-overlay {
background: #666666 url(../img/datepicker/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat;
opacity: .5;
filter: Alpha(Opacity=50);
}
.ui-widget-shadow {
margin: -5px 0 0 -5px;
padding: 5px;
background: #000000 url(../img/datepicker/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x;
opacity: .2;
filter: Alpha(Opacity=20);
border-radius: 5px;
}

View File

@ -0,0 +1,115 @@
/**
* Melon skin from: https://github.com/rtsinani/jquery-datepicker-skins
*/
.wp-admin {
font-size: 90%;
}
.wp-admin .ui-widget {
font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
background: #2e3641;
border: none;
border-radius: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
.wp-admin .ui-datepicker {
padding: 0;
}
.wp-admin .ui-datepicker-header {
border: none;
background: transparent;
font-weight: normal;
font-size: 15px;
}
.wp-admin .ui-datepicker-header .ui-state-hover {
background: transparent;
border-color: transparent;
cursor: pointer;
border-radius: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
.wp-admin .ui-datepicker .ui-datepicker-title {
margin-top: .4em;
margin-bottom: .3em;
color: #e9f0f4;
}
.wp-admin .ui-datepicker .ui-datepicker-prev-hover,
.wp-admin .ui-datepicker .ui-datepicker-next-hover,
.wp-admin .ui-datepicker .ui-datepicker-next,
.wp-admin .ui-datepicker .ui-datepicker-prev {
top: .9em;
border:none;
}
.wp-admin .ui-datepicker .ui-datepicker-prev-hover {
left: 2px;
}
.wp-admin .ui-datepicker .ui-datepicker-next-hover {
right: 2px;
}
.wp-admin .ui-datepicker .ui-datepicker-next span,
.wp-admin .ui-datepicker .ui-datepicker-prev span {
background-image: url(../img/datepicker/ui-icons_ffffff_256x240.png);
background-position: -32px 0;
margin-top: 0;
top: 0;
font-weight: normal;
}
.wp-admin .ui-datepicker .ui-datepicker-prev span {
background-position: -96px 0;
}
.wp-admin .ui-datepicker table {
margin: 0;
}
.wp-admin .ui-datepicker th {
padding: 1em 0;
color: #ccc;
font-size: 13px;
font-weight: normal;
border: none;
border-top: 1px solid #3a414d;
}
.wp-admin .ui-datepicker td {
background: #f97e76;
border: none;
padding: 0;
}
.wp-admin td .ui-state-default {
background: transparent;
border: none;
text-align: center;
padding: .5em;
margin: 0;
font-weight: normal;
color: #efefef;
font-size: 16px;
}
.wp-admin .ui-state-disabled {
opacity: 1;
}
.wp-admin .ui-state-disabled .ui-state-default {
color: #fba49e;
}
.wp-admin td .ui-state-active,
.wp-admin td .ui-state-hover {
background: #2e3641;
}

View File

@ -1,2 +1,5 @@
.mailpoet_form
margin: 0 0 20px 0
.mailpoet_form td
vertical-align: top !important
vertical-align: top !important

View File

@ -563,3 +563,6 @@ handle_icon = '../img/handle.png'
.CodeMirror
border: 1px solid #eee
/* Settings */
#mailpoet_form_segments.parsley-error + span .select2-selection
border: 1px solid #b94a48

View File

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

View File

@ -46,6 +46,7 @@ $master-column-tool-width = 24px
opacity: 0
overflow: hidden
display: block
margin: 0
.mailpoet_delete_block_activated
width: auto
@ -125,7 +126,6 @@ $master-column-tool-width = 24px
border-radius(3px)
background-color: $warning-background-color
padding: 3px 5px
line-height: 1.2em
.mailpoet_delete_block_activate
overflow: hidden

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
$block-hover-highlight-color = $primary-active-color
$block-text-line-height = $text-line-height
.mailpoet_block
box-sizing: border-box
@ -22,6 +23,7 @@ $block-hover-highlight-color = $primary-active-color
border: 1px solid $transparent-color
&:hover > .mailpoet_block_highlight
&.mailpoet_highlight > .mailpoet_block_highlight
border: 1px dashed $block-hover-highlight-color
@ -30,8 +32,34 @@ $block-hover-highlight-color = $primary-active-color
.mailpoet_content
position: relative
line-height: 1.61803398875
line-height: $block-text-line-height
p, h1, h2, h3, h4, h5, h6
line-height: 1.61803398875
line-height: $block-text-line-height
padding: 0
margin: 0
font-style: normal
font-weight: normal
h1, h2, h3, h4, h5, h6
margin-bottom: 0.3em
p
margin-top: 0
margin-bottom: 0
font-weight: normal
ul
padding: 0
margin-top: 10px
margin-bottom: 10px
li
margin-top: 0
font-weight: normal
margin-bottom: 10px
blockquote
margin: 0 0 $block-text-line-height
padding-left: 10px
border-left: 2px #565656 solid

View File

@ -1,9 +1,4 @@
$button-vertical-padding = 13px
.mailpoet_button_block
padding-top: $button-vertical-padding
padding-bottom: $button-vertical-padding
overflow: hidden
.mailpoet_editor_button

View File

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

View File

@ -6,5 +6,5 @@
.mailpoet_content
padding: 10px 20px
p
margin: 0
& > *:last-child
margin-bottom: 0

View File

@ -6,5 +6,5 @@
.mailpoet_content
padding: 10px 20px
p
margin: 0
& > *:last-child
margin-bottom: 0

View File

@ -1,4 +1,4 @@
$social-block-vertical-padding = 11px
$social-block-vertical-padding = 0px
$social-icon-width = 32px
$active-social-icon-set-border-color = #adadad

View File

@ -9,16 +9,5 @@
padding-left: 20px
padding-right: 20px
h1, h2, h3, h4, h5, h6
padding: 0
margin: 0
font-weight: normal
p
margin-top: 0
font-weight: normal
blockquote
margin: 1em
padding-left: 1em
border-left: 2px #565656 solid
& > *:last-child
margin-bottom: 0

View File

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

View File

@ -24,3 +24,6 @@ $error-text-color = #d54e21
// Dimensions
$newsletter-width = 660px
$text-line-height = 1.6em
$sidebar-text-size = 13px

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

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

View File

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

View File

@ -1,4 +1,4 @@
define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) {
'use strict';
MailPoet.Ajax = {
version: 0.5,
@ -52,6 +52,13 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
var params = this.getParams();
var jqXHR;
// remove null values from the data object
if (_.isObject(params.data)) {
params.data = _.pick(params.data, function(value) {
return (value !== null)
})
}
// make ajax request depending on method
if(method === 'get') {
jqXHR = jQuery.get(

View File

@ -48,28 +48,22 @@ define(
})
.done(function(response) {
if(!response.result) {
MailPoet.Notice.error(MailPoetI18n.daemonControlError);
MailPoet.Notice.error(MailPoet.I18n.t('daemonControlError'));
}
}.bind(this));
},
render: function() {
if(this.state.status === 'loading') {
return(<div>Loading daemon status...</div>);
return(<div>{MailPoet.I18n.t('loadingDaemonStatus')}</div>);
}
switch(this.state.status) {
case 'started':
return(
<div>
Cron daemon is running.
{MailPoet.I18n.t('cronDaemonIsRunning')}
<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>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>{MailPoet.I18n.t('stop')}</a>
</div>
);
break;
@ -77,17 +71,17 @@ define(
case 'stopping':
return(
<div>
Daemon is {this.state.status}
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
</div>
);
break;
case 'stopped':
return(
<div>
Daemon is {this.state.status}
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>Start</a>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>{MailPoet.I18n.t('start')}</a>
</div>
);
break;
@ -103,4 +97,4 @@ define(
container
);
}
});
});

View File

@ -40,10 +40,18 @@ define('date',
return this;
},
format: function(date, options) {
options = options || {};
this.init(options);
return Moment.utc(date)
.local()
.format(this.convertFormat(this.options.format));
var date = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) date = date.utc();
return date.format(this.convertFormat(this.options.format));
},
toDate: function(date, options) {
options = options || {};
this.init(options);
return Moment(date, this.convertFormat(options.parseFormat)).toDate();
},
short: function(date) {
return this.format(date, {
@ -61,7 +69,7 @@ define('date',
});
},
convertFormat: function(format) {
const format_mappings = {
var format_mappings = {
date: {
D: 'ddd',
l: 'dddd',
@ -115,9 +123,11 @@ define('date',
}
};
const replacements = format_mappings['date'];
if (!format || format.length <= 0) return format;
let outputFormat = '';
var replacements = format_mappings['date'];
var outputFormat = '';
Object.keys(replacements).forEach(function(key) {
if (format.indexOf(key) !== -1) {
@ -133,4 +143,4 @@ define('date',
return outputFormat;
}
};
});
});

View File

@ -6,7 +6,7 @@ function(
) {
const FormFieldCheckbox = React.createClass({
onValueChange: function(e) {
e.target.value = this.refs.checkbox.checked ? '1' : '';
e.target.value = this.refs.checkbox.checked ? '1' : '0';
return this.props.onValueChange(e);
},
render: function() {
@ -14,7 +14,9 @@ function(
return false;
}
const isChecked = !!(this.props.item[this.props.field.name]);
// isChecked will be true only if the value is "1"
// it will be false in case value is "0" or empty
const isChecked = !!(~~(this.props.item[this.props.field.name]));
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return (

View File

@ -9,6 +9,13 @@ define([
render() {
const yearsRange = 100;
const years = [];
if (this.props.placeholder !== undefined) {
years.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option>
));
}
const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i--) {
years.push((
@ -20,7 +27,7 @@ define([
}
return (
<select
name={ this.props.name + '[year]' }
name={ `${this.props.name}[year]` }
value={ this.props.year }
onChange={ this.props.onValueChange }
>
@ -33,6 +40,13 @@ define([
class FormFieldDateMonth extends React.Component {
render() {
const months = [];
if (this.props.placeholder !== undefined) {
months.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option>
));
}
for (let i = 1; i <= 12; i++) {
months.push((
<option
@ -43,7 +57,7 @@ define([
}
return (
<select
name={ this.props.name + '[month]' }
name={ `${this.props.name}[month]` }
value={ this.props.month }
onChange={ this.props.onValueChange }
>
@ -56,6 +70,13 @@ define([
class FormFieldDateDay extends React.Component {
render() {
const days = [];
if (this.props.placeholder !== undefined) {
days.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option>
));
}
for (let i = 1; i <= 31; i++) {
days.push((
<option
@ -67,7 +88,7 @@ define([
return (
<select
name={ this.props.name + '[day]' }
name={ `${this.props.name}[day]` }
value={ this.props.day }
onChange={ this.props.onValueChange }
>
@ -81,47 +102,99 @@ define([
constructor(props) {
super(props);
this.state = {
year: Moment().year(),
month: 1,
day: 1
year: '',
month: '',
day: ''
}
}
componentDidMount() {
this.extractDateParts();
}
componentDidUpdate(prevProps, prevState) {
if (
(this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
this.extractTimeStamp();
this.extractDateParts();
}
}
extractTimeStamp() {
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
extractDateParts() {
const value = (this.props.item[this.props.field.name] !== undefined)
? this.props.item[this.props.field.name].trim()
: '';
if(value === '') {
return;
}
const dateType = this.props.field.params.date_type;
const dateParts = value.split('-');
let year = '';
let month = '';
let day = '';
switch(dateType) {
case 'year_month_day':
year = ~~(dateParts[0]);
month = ~~(dateParts[1]);
day = ~~(dateParts[2]);
break;
case 'year_month':
year = ~~(dateParts[0]);
month = ~~(dateParts[1]);
break;
case 'month':
month = ~~(dateParts[0]);
break;
case 'year':
year = ~~(dateParts[0]);
break;
}
this.setState({
year: Moment.unix(timeStamp).year(),
// Moment returns the month as [0..11]
// We increment it to match PHP's mktime() which expects [1..12]
month: Moment.unix(timeStamp).month() + 1,
day: Moment.unix(timeStamp).date()
year: year,
month: month,
day: day
});
}
updateTimeStamp(field) {
let newTimeStamp = Moment(
`${this.state.month}/${this.state.day}/${this.state.year}`,
'M/D/YYYY'
).valueOf();
if (!isNaN(newTimeStamp) && parseInt(newTimeStamp, 10) > 0) {
// convert milliseconds to seconds
newTimeStamp /= 1000;
return this.props.onValueChange({
target: {
name: field,
value: newTimeStamp
}
});
formatValue() {
const dateType = this.props.field.params.date_type;
let value;
switch(dateType) {
case 'year_month_day':
value = {
'year': this.state.year,
'month': this.state.month,
'day': this.state.day
};
break;
case 'year_month':
value = {
'year': this.state.year,
'month': this.state.month
};
break;
case 'month':
value = {
'month': this.state.month
};
break;
case 'year':
value = {
'year': this.state.year
};
break;
}
return value;
}
onValueChange(e) {
// extract property from name
@ -133,35 +206,40 @@ define([
field = matches[1];
property = matches[2];
let value = parseInt(e.target.value, 10);
let value = ~~(e.target.value);
this.setState({
[`${property}`]: value
}, () => {
this.updateTimeStamp(field);
this.props.onValueChange({
target: {
name: field,
value: this.formatValue()
}
});
});
}
}
render() {
const monthNames = window.mailpoet_month_names || [];
const dateFormats = window.mailpoet_date_formats || {};
const dateType = this.props.field.params.date_type;
const dateSelects = dateType.split('_');
const dateSelects = dateFormats[dateType][0].split('/');
const fields = dateSelects.map(type => {
switch(type) {
case 'year':
case 'yyyy':
return (<FormFieldDateYear
onValueChange={ this.onValueChange.bind(this) }
ref={ 'year' }
key={ 'year' }
name={ this.props.field.name }
year={ this.state.year }
placeholder={ this.props.field.year_placeholder }
/>);
break;
case 'month':
case 'mm':
return (<FormFieldDateMonth
onValueChange={ this.onValueChange.bind(this) }
ref={ 'month' }
@ -169,16 +247,18 @@ define([
name={ this.props.field.name }
month={ this.state.month }
monthNames={ monthNames }
placeholder={ this.props.field.month_placeholder }
/>);
break;
case 'day':
case 'dd':
return (<FormFieldDateDay
onValueChange={ this.onValueChange.bind(this) }
ref={ 'day' }
key={ 'day' }
name={ this.props.field.name }
day={ this.state.day }
placeholder={ this.props.field.day_placeholder }
/>);
break;
}

View File

@ -61,6 +61,10 @@ function(
case 'date':
field = (<FormFieldDate {...data} />);
break;
case 'reactComponent':
field = (<data.field.component {...data} />);
break;
}
if(inline === true) {
@ -121,4 +125,4 @@ function(
});
return FormField;
});
});

View File

@ -1,40 +1,76 @@
define([
'react'
],
function(
React
) {
const FormFieldSelect = React.createClass({
render: function() {
if (this.props.field.values === undefined) {
return false;
}
import React from 'react'
import _ from 'underscore'
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return (
<option
key={ 'option-' + index }
value={ value }>
{ this.props.field.values[value] }
</option>
);
}
);
const FormFieldSelect = React.createClass({
render() {
if (this.props.field.values === undefined) {
return false;
}
return (
<select
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
>
{options}
</select>
let filter = false;
let placeholder = false;
let sortBy = false;
if (this.props.field.placeholder !== undefined) {
placeholder = (
<option value="">{ this.props.field.placeholder }</option>
);
}
});
return FormFieldSelect;
});
if (this.props.field['filter'] !== undefined) {
filter = this.props.field.filter;
}
if (_.isFunction(this.props.field.sortBy)) {
sortBy = this.props.field.sortBy;
}
let keys;
if (sortBy) {
// Extract keys from sorted [key, value] select value pairs, sorted by
// provided sorting order.
keys =
_.map(
_.sortBy(
_.pairs(this.props.field.values),
(item) => sortBy(item[0], item[1])
),
(item) => item[0]
);
} else {
keys = Object.keys(this.props.field.values)
}
const options = keys.map(
(value, index) => {
if (filter !== false && filter(this.props.item, value) === false) {
return;
}
return (
<option
key={ 'option-' + index }
value={ value }>
{ this.props.field.values[value] }
</option>
);
}
);
return (
<select
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
>
{placeholder}
{options}
</select>
);
}
});
module.exports = FormFieldSelect;

View File

@ -13,12 +13,22 @@ function(
getInitialState: function() {
return {
items: [],
initialized: false
}
select2: false
};
},
componentWillMount: function() {
this.loadCachedItems();
},
allowMultipleValues: function() {
return (this.props.field.multiple === true);
},
isSelect2Initialized: function() {
return (this.state.select2 === true);
},
componentDidMount: function() {
this.loadCachedItems();
this.setupSelect2();
if(this.allowMultipleValues()) {
this.setupSelect2();
}
},
componentDidUpdate: function(prevProps, prevState) {
if(
@ -30,12 +40,18 @@ function(
.trigger('change');
}
},
componentWillUnmount: function() {
if(this.allowMultipleValues()) {
this.destroySelect2();
}
},
destroySelect2: function() {
if(this.isSelect2Initialized()) {
jQuery('#'+this.refs.select.id).select2('destroy');
}
},
setupSelect2: function() {
if(
!this.props.field.multiple
|| this.state.initialized === true
|| this.refs.select === undefined
) {
if(this.isSelect2Initialized()) {
return;
}
@ -67,12 +83,7 @@ function(
select2.on('change', this.handleChange);
select2.select2(
'val',
this.getSelectedValues()
);
this.setState({ initialized: true });
this.setState({ select2: true });
},
getSelectedValues: function() {
if(this.props.field['selected'] !== undefined) {
@ -161,4 +172,4 @@ function(
});
return Selection;
});
});

View File

@ -1,33 +1,35 @@
define([
'react'
],
function(
React
) {
var FormFieldText = React.createClass({
render: function() {
var value = this.props.item[this.props.field.name];
if(!value) { value = null; }
return (
<input
type="text"
className={ (this.props.field.size) ? '' : 'regular-text' }
size={
(this.props.field.size !== 'auto' && this.props.field.size > 0)
? this.props.field.size
: false
}
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ value }
placeholder={ this.props.field.placeholder }
defaultValue={ this.props.field.defaultValue }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
}
});
import React from 'react'
return FormFieldText;
});
const FormFieldText = React.createClass({
render() {
let value = this.props.item[this.props.field.name];
if (value === undefined) {
value = this.props.field.defaultValue || '';
}
return (
<input
type="text"
disabled={
(this.props.field['disabled'] !== undefined)
? this.props.field.disabled(this.props.item)
: false
}
className={ (this.props.field.size) ? '' : 'regular-text' }
size={
(this.props.field.size !== 'auto' && this.props.field.size > 0)
? this.props.field.size
: false
}
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ value }
placeholder={ this.props.field.placeholder }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
}
});
module.exports = FormFieldText;

View File

@ -13,10 +13,16 @@ define(
Router,
FormField
) {
var Form = React.createClass({
mixins: [
Router.History
],
contextTypes: {
router: React.PropTypes.object.isRequired
},
getDefaultProps: function() {
return {
params: {},
};
},
getInitialState: function() {
return {
loading: false,
@ -24,10 +30,20 @@ define(
item: {}
};
},
getValues: function() {
return this.props.item ? this.props.item : this.state.item;
},
getErrors: function() {
return this.props.errors ? this.props.errors : this.state.errors;
},
componentDidMount: function() {
if(this.props.params.id !== undefined) {
if(this.isMounted()) {
if(this.isMounted()) {
if(this.props.params.id !== undefined) {
this.loadItem(this.props.params.id);
} else {
this.setState({
item: jQuery('.mailpoet_form').serializeObject()
});
}
}
},
@ -37,7 +53,9 @@ define(
loading: false,
item: {}
});
this.refs.form.reset();
if (props.item === undefined) {
this.refs.form.reset();
}
} else {
this.loadItem(props.params.id);
}
@ -55,7 +73,7 @@ define(
loading: false,
item: {}
}, function() {
this.history.pushState(null, '/new');
this.context.router.push('/new');
}.bind(this));
} else {
this.setState({
@ -88,7 +106,6 @@ define(
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;
@ -105,7 +122,7 @@ define(
if(this.props.onSuccess !== undefined) {
this.props.onSuccess();
} else {
this.history.pushState(null, '/')
this.context.router.push('/');
}
if(this.props.params.id !== undefined) {
@ -123,19 +140,23 @@ define(
}.bind(this));
},
handleValueChange: function(e) {
var item = this.state.item,
field = e.target.name;
if (this.props.onChange) {
return this.props.onChange(e);
} else {
var item = this.state.item,
field = e.target.name;
item[field] = e.target.value;
item[field] = e.target.value;
this.setState({
item: item
});
return true;
this.setState({
item: item
});
return true;
}
},
render: function() {
if(this.state.errors !== undefined) {
var errors = this.state.errors.map(function(error, index) {
if(this.getErrors() !== undefined) {
var errors = this.getErrors().map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
@ -146,14 +167,25 @@ define(
var formClasses = classNames(
'mailpoet_form',
{ 'mailpoet_form_loading': this.state.loading }
{ 'mailpoet_form_loading': this.state.loading || this.props.loading }
);
var beforeFormContent = false;
var afterFormContent = false;
if (this.props.beforeFormContent !== undefined) {
beforeFormContent = this.props.beforeFormContent(this.getValues());
}
if (this.props.afterFormContent !== undefined) {
afterFormContent = this.props.afterFormContent(this.getValues());
}
var fields = this.props.fields.map(function(field, i) {
return (
<FormField
field={ field }
item={ this.state.item }
item={ this.getValues() }
onValueChange={ this.handleValueChange }
key={ 'field-'+i } />
);
@ -167,36 +199,40 @@ define(
<input
className="button button-primary"
type="submit"
value="Save"
value={MailPoet.I18n.t('save')}
disabled={this.state.loading} />
);
}
return (
<form
id={ this.props.id }
ref="form"
className={ formClasses }
onSubmit={
(this.props.onSubmit !== undefined)
? this.props.onSubmit
: this.handleSubmit
}
>
{ errors }
<div>
{ beforeFormContent }
<form
id={ this.props.id }
ref="form"
className={ formClasses }
onSubmit={
(this.props.onSubmit !== undefined)
? this.props.onSubmit
: this.handleSubmit
}
>
{ errors }
<table className="form-table">
<tbody>
{fields}
</tbody>
</table>
<table className="form-table">
<tbody>
{fields}
</tbody>
</table>
{ actions }
</form>
{ actions }
</form>
{ afterFormContent }
</div>
);
}
});
return Form;
}
);
);

View File

@ -402,11 +402,30 @@ var WysijaForm = {
}
});
// hide list selection if a list widget has been dragged into the editor
$('mailpoet_settings_segment_selection')[
(($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0) === true)
? 'hide' : 'show'
]();
var hasSegmentSelection = WysijaForm.hasSegmentSelection();
if(hasSegmentSelection) {
$('mailpoet_form_segments').writeAttribute('required', false).disable();
$('mailpoet_settings_segment_selection').hide();
} else {
$('mailpoet_form_segments').writeAttribute('required', true).enable();
$('mailpoet_settings_segment_selection').show();
}
},
hasSegmentSelection: function() {
return ($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0);
},
isSegmentSelectionValid: function() {
var segment_selection = $$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
if(segment_selection !== undefined) {
var block = WysijaForm.get(segment_selection).block.getData();
return (
(block.params.values !== undefined)
&&
(block.params.values.length > 0)
);
}
return false;
},
setBlockPositions: function(event, target) {
// release dragging lock

View File

@ -1,10 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute } from 'react-router'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import FormList from 'forms/list.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
@ -12,7 +12,7 @@ const App = React.createClass({
}
});
let container = document.getElementById('forms_container');
const container = document.getElementById('forms_container');
if(container) {
ReactDOM.render((

View File

@ -8,17 +8,16 @@ import MailPoet from 'mailpoet'
const columns = [
{
name: 'name',
label: 'Name',
label: MailPoet.I18n.t('formName'),
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
label: MailPoet.I18n.t('segments')
},
{
name: 'created_at',
label: 'Created on',
label: MailPoet.I18n.t('createdOn'),
sortable: true
}
];
@ -30,11 +29,11 @@ const messages = {
if(count === 1) {
message = (
'1 form was moved to the trash.'
MailPoet.I18n.t('oneFormTrashed')
);
} else {
message = (
'%$1d forms were moved to the trash.'
MailPoet.I18n.t('multipleFormsTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@ -45,11 +44,11 @@ const messages = {
if(count === 1) {
message = (
'1 form was permanently deleted.'
MailPoet.I18n.t('oneFormDeleted')
);
} else {
message = (
'%$1d forms were permanently deleted.'
MailPoet.I18n.t('multipleFormsDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@ -60,39 +59,49 @@ const messages = {
if(count === 1) {
message = (
'1 form has been restored from the trash.'
MailPoet.I18n.t('oneFormRestored')
);
} else {
message = (
'%$1d forms have been restored from the trash.'
MailPoet.I18n.t('multipleFormsRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
const item_actions = [
{
name: 'edit',
label: 'Edit',
label: MailPoet.I18n.t('edit'),
link: function(item) {
return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a>
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a>
);
}
},
{
name: 'duplicate_form',
label: 'Duplicate',
name: 'duplicate',
label: MailPoet.I18n.t('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)
);
if (response !== false && response['name'] !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
);
}
refresh();
});
}
@ -102,14 +111,6 @@ const item_actions = [
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const FormList = React.createClass({
createForm() {
MailPoet.Ajax.post({
@ -138,15 +139,18 @@ const FormList = React.createClass({
<div>
<td className={ row_classes }>
<strong>
<a>{ form.name }</a>
<a
className="row-title"
href={ `admin.php?page=mailpoet-form-editor&id=${form.id}` }
>{ form.name }</a>
</strong>
{ actions }
</td>
<td className="column-format" data-colname="Lists">
<td className="column-format" data-colname={MailPoet.I18n.t('segments')}>
{ segments }
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
<td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
<abbr>{ MailPoet.Date.format(form.created_at) }</abbr>
</td>
</div>
);
@ -154,20 +158,20 @@ const FormList = React.createClass({
render() {
return (
<div>
<h2 className="title">
Forms <a
className="add-new-h2"
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <a
className="page-title-action"
href="javascript:;"
onClick={ this.createForm }
>New</a>
</h2>
>{MailPoet.I18n.t('new')}</a>
</h1>
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="forms"
onRenderItem={ this.renderItem }
columns={ columns }
@ -179,4 +183,4 @@ const FormList = React.createClass({
}
});
module.exports = FormList;
module.exports = FormList;

View File

@ -140,5 +140,20 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
return parseInt(string, 10);
});
Handlebars.registerHelper('fontWithFallback', function(font) {
switch(font) {
case 'Arial': return new Handlebars.SafeString("Arial, 'Helvetica Neue', Helvetica, sans-serif");
case 'Comic Sans MS': return new Handlebars.SafeString("'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif");
case 'Courier New': return new Handlebars.SafeString("'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace");
case 'Georgia': return new Handlebars.SafeString("Georgia, Times, 'Times New Roman', serif");
case 'Lucida': return new Handlebars.SafeString("'Lucida Sans Unicode', 'Lucida Grande', sans-serif");
case 'Tahoma': return new Handlebars.SafeString("Tahoma, Verdana, Segoe, sans-serif");
case 'Times New Roman': return new Handlebars.SafeString("'Times New Roman', Times, Baskerville, Georgia, serif");
case 'Trebuchet MS': return new Handlebars.SafeString("'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif");
case 'Verdana': return new Handlebars.SafeString("Verdana, Geneva, sans-serif");
default: return font;
}
});
window.Handlebars = Handlebars;
});

25
assets/js/src/i18n.js Normal file
View File

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

23
assets/js/src/iframe.js Normal file
View File

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

View File

@ -1,8 +1,10 @@
define([
'react'
'react',
'mailpoet'
],
function(
React
React,
MailPoet
) {
var ListingBulkActions = React.createClass({
getInitialState: function() {
@ -45,13 +47,13 @@ function(
data.action = this.state.action;
var callback = function() {};
var onSuccess = function() {};
if(action['onSuccess'] !== undefined) {
callback = action.onSuccess;
onSuccess = action.onSuccess;
}
if(data.action) {
this.props.onBulkAction(selected_ids, data, callback);
this.props.onBulkAction(selected_ids, data).then(onSuccess);
}
this.setState({
@ -82,7 +84,7 @@ function(
<label
className="screen-reader-text"
htmlFor="bulk-action-selector-top">
Select bulk action
{MailPoet.I18n.t('selectBulkAction')}
</label>
<select
@ -91,7 +93,7 @@ function(
value={ this.state.action }
onChange={this.handleChangeAction}
>
<option value="">Bulk Actions</option>
<option value="">{MailPoet.I18n.t('bulkActions')}</option>
{ this.props.bulk_actions.map(function(action, index) {
return (
<option
@ -104,7 +106,7 @@ function(
<input
onClick={ this.handleApplyAction }
type="submit"
defaultValue="Apply"
defaultValue={MailPoet.I18n.t('apply')}
className="button action" />
{ this.state.extra }
@ -114,4 +116,4 @@ function(
});
return ListingBulkActions;
});
});

View File

@ -1,17 +1,19 @@
define([
'react',
'jquery'
'jquery',
'mailpoet'
],
function(
React,
jQuery
jQuery,
MailPoet
) {
var ListingFilters = React.createClass({
handleFilterAction: function() {
let filters = {}
let filters = {};
this.getAvailableFilters().map((filter, i) => {
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value
})
});
return this.props.onSelectFilter(filters);
},
handleEmptyTrash: function() {
@ -19,7 +21,6 @@ function(
},
getAvailableFilters: function() {
let filters = this.props.filters;
return Object.keys(filters).filter(function(filter) {
return !(
filters[filter].length === 0
@ -28,26 +29,29 @@ function(
&& !filters[filter][0].value
)
);
})
});
},
componentDidUpdate: function() {
const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters().map(
function(filter, i) {
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
jQuery(this.refs['filter-'+i])
.val(selected_filters[filter])
.trigger('change');
}
}.bind(this)
);
},
render: function() {
const filters = this.props.filters;
const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters()
.map(function(filter, i) {
let default_value = false;
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
default_value = selected_filters[filter]
} else {
jQuery(`select[name="${filter}"]`).val('');
}
return (
<select
ref={ `filter-${i}` }
key={ `filter-${i}` }
name={ filter }
defaultValue={ default_value }
>
{ filters[filter].map(function(option, j) {
return (
@ -61,7 +65,7 @@ function(
);
}.bind(this));
let button = false;
let button;
if (available_filters.length > 0) {
button = (
@ -69,18 +73,18 @@ function(
id="post-query-submit"
onClick={ this.handleFilterAction }
type="submit"
defaultValue="Filter"
defaultValue={MailPoet.I18n.t('filter')}
className="button" />
);
}
let empty_trash = false;
let empty_trash;
if (this.props.group === 'trash') {
empty_trash = (
<input
onClick={ this.handleEmptyTrash }
type="submit"
value="Empty Trash"
value={MailPoet.I18n.t('emptyTrash')}
className="button"
/>
);

View File

@ -21,7 +21,7 @@ define(['react', 'classnames'], function(React, classNames) {
href="javascript:;"
className={classes}
onClick={this.handleSelect.bind(this, group.name)} >
{group.label} <span className="count">({ group.count })</span>
{group.label} <span className="count">({ group.count.toLocaleString() })</span>
</a>
</li>
);

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,12 @@
define(['react', 'classnames'], function(React, classNames) {
define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
var ListingPages = React.createClass({
getInitialState: function() {
@ -72,7 +80,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setPreviousPage }
className="prev-page">
<span className="screen-reader-text">Previous page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span>
</a>
);
@ -83,7 +91,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setFirstPage }
className="first-page">
<span className="screen-reader-text">First page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
@ -94,7 +102,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setNextPage }
className="next-page">
<span className="screen-reader-text">Next page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
@ -105,7 +113,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setLastPage }
className="last-page">
<span className="screen-reader-text">Last page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span>
</a>
);
@ -125,22 +133,22 @@ define(['react', 'classnames'], function(React, classNames) {
<span className="paging-input">
<label
className="screen-reader-text"
htmlFor="current-page-selector">Current Page</label>
htmlFor="current-page-selector">{MailPoet.I18n.t('currentPage')}</label>
<input
type="text"
onChange={ this.handleChangeManualPage }
onKeyUp={ this.handleSetManualPage }
onBlur={ this.handleBlurManualPage }
aria-describedby="table-paging"
size="1"
size="2"
ref="page"
value={ pageValue }
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;of&nbsp;
&nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit)}
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
@ -158,7 +166,9 @@ define(['react', 'classnames'], function(React, classNames) {
return (
<div className={ classes }>
<span className="displaying-num">{ this.props.count } items</span>
<span className="displaying-num">{
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count.toLocaleString())
}</span>
{ pagination }
</div>
);
@ -167,4 +177,4 @@ define(['react', 'classnames'], function(React, classNames) {
});
return ListingPages;
});
});

View File

@ -1,4 +1,10 @@
define(['react'], function(React) {
define([
'mailpoet',
'react'
], function(
MailPoet,
React
) {
var ListingSearch = React.createClass({
handleSearch: function(e) {
@ -18,7 +24,7 @@ define(['react'], function(React) {
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
{MailPoet.I18n.t('searchLabel')}
</label>
<input
type="search"
@ -28,7 +34,7 @@ define(['react'], function(React) {
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
defaultValue={MailPoet.I18n.t('searchLabel')}
className="button" />
</p>
</form>

View File

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

View File

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

View File

@ -12,15 +12,57 @@ define([
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'newsletter_editor/components/communication',
'mailpoet',
'backbone.supermodel',
'underscore',
'jquery'
], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) {
], function(
App,
BaseBlock,
ButtonBlock,
DividerBlock,
CommunicationComponent,
MailPoet,
SuperModel,
_,
jQuery
) {
"use strict";
var Module = {},
base = BaseBlock;
Module.ALCSupervisor = SuperModel.extend({
initialize: function() {
this.listenTo(App.getChannel(), 'automatedLatestContentRefresh', this.refresh);
},
refresh: function() {
var models = App.findModels(function(model) {
return model.get('type') === 'automatedLatestContent';
}) || [];
if (models.length === 0) return;
var blocks = _.map(models, function(model) {
return model.toJSON();
});
CommunicationComponent.getBulkTransformedPosts({
blocks: blocks,
}).then(_.partial(this.refreshBlocks, models));
},
refreshBlocks: function(models, renderedBlocks) {
_.each(
_.zip(models, renderedBlocks),
function(args) {
var model = args[0],
contents = args[1];
model.trigger('refreshPosts', contents);
}
);
},
});
Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({
stale: ['_container'],
defaults: function() {
@ -62,34 +104,32 @@ define([
},
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
this.fetchPosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
},
fetchPosts: function() {
var that = this;
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged');
}).fail(function(error) {
console.log('ALC fetchPosts error', arguments);
this.on('add remove update reset', function(model, collection, options) {
App.getChannel().trigger('automatedLatestContentRefresh');
});
this.on('refreshPosts', this.updatePosts, this);
},
updatePosts: function(posts) {
this.get('_container.blocks').reset(posts, {parse: true});
},
/**
* Batch more changes during a specific time, instead of fetching
* ALC posts on each model change
*/
_scheduleFetchPosts: function() {
var timeout = 500,
var TIMEOUT = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
}
this._fetchPostsTimer = setTimeout(function() {
that.fetchPosts();
//that.fetchPosts();
App.getChannel().trigger('automatedLatestContentRefresh');
that._fetchPostsTimer = undefined;
}, timeout);
}, TIMEOUT);
},
});
@ -114,6 +154,7 @@ define([
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
};
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView);
@ -154,9 +195,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
@ -171,6 +209,7 @@ define([
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
multiple: true,
allowClear: true,
placeholder: MailPoet.I18n.t('categoriesAndTags'),
ajax: {
data: function (params) {
return {
@ -277,9 +316,11 @@ define([
if (value == 'titleOnly') {
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_separator').addClass('mailpoet_hidden');
} else {
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_separator').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') {
@ -353,5 +394,10 @@ define([
});
});
App.on('start', function() {
App._ALCSupervisor = new Module.ALCSupervisor();
App._ALCSupervisor.refresh();
});
return Module;
});

View File

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

View File

@ -99,23 +99,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
initialize: function(params) {
var panelParams = {
element: this.$el,
template: '',
position: 'right',
width: App.getConfig().get('sidepanelWidth'),
};
this.renderOptions = params.renderOptions || {};
if (this.renderOptions.displayFormat === 'subpanel') {
MailPoet.Modal.subpanel(panelParams);
} else {
MailPoet.Modal.panel(panelParams);
}
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -65,6 +65,13 @@ define([
}
return response;
},
getChildren: function() {
var models = this.get('blocks').map(function(model, index, list) {
return [model, model.getChildren()];
});
return _.flatten(models);
},
});
Module.ContainerBlockView = Marionette.CompositeView.extend({
@ -118,6 +125,7 @@ define([
return view.renderOptions.depth === 1;
},
},
HighlightEditingBehavior: {}
},
onDragSubstituteBy: function() {
// For two and three column layouts display their respective widgets,
@ -286,6 +294,7 @@ define([
templateHelpers: function() {
return {
isRoot: this.renderOptions.depth === 0,
emptyContainerMessage: this.renderOptions.emptyContainerMessage || '',
};
},
});
@ -302,9 +311,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
regions: {
columnsSettingsRegion: '.mailpoet_container_columns_settings',
},

View File

@ -102,23 +102,6 @@ define([
'change:styles.block.borderColor': 'repaintDividerStyleOptions',
};
},
behaviors: {
ColorPickerBehavior: {},
},
initialize: function(params) {
var panelParams = {
element: this.$el,
template: '',
position: 'right',
width: App.getConfig().get('sidepanelWidth'),
};
this.renderOptions = params.renderOptions || {};
if (this.renderOptions.displayFormat === 'subpanel') {
MailPoet.Modal.subpanel(panelParams);
} else {
MailPoet.Modal.panel(panelParams);
}
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'footer',
text: '<a href="[unsubscribeUrl]">Unsubscribe</a> | <a href="[manageSubscriptionUrl]">Manage subscription</a><br /><b>Add your postal address here!</b>',
text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
styles: {
block: {
backgroundColor: 'transparent',
@ -56,13 +56,15 @@ define([
inline: true,
menubar: false,
toolbar: "bold italic link unlink forecolor mailpoet_custom_fields",
toolbar: "bold italic link unlink forecolor mailpoet_shortcodes",
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
invalid_elements: "script",
block_formats: 'Paragraph=p',
relative_urls: false,
remove_script_host: false,
plugins: "link textcolor colorpicker mailpoet_custom_fields",
plugins: "link textcolor colorpicker mailpoet_shortcodes",
setup: function(editor) {
editor.on('change', function(e) {
@ -78,8 +80,8 @@ define([
});
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
});
},
});
@ -104,9 +106,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'header',
text: 'Display problems? <a href="[viewInBrowserUrl]">View it in your browser</a>',
text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
styles: {
block: {
backgroundColor: 'transparent',
@ -56,13 +56,15 @@ define([
inline: true,
menubar: false,
toolbar: "bold italic link unlink forecolor mailpoet_custom_fields",
toolbar: "bold italic link unlink forecolor mailpoet_shortcodes",
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
invalid_elements: "script",
block_formats: 'Paragraph=p',
relative_urls: false,
remove_script_host: false,
plugins: "link textcolor colorpicker mailpoet_custom_fields",
plugins: "link textcolor colorpicker mailpoet_shortcodes",
setup: function(editor) {
editor.on('change', function(e) {
@ -78,8 +80,8 @@ define([
});
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
});
},
});
@ -104,9 +106,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),

View File

@ -23,7 +23,19 @@ define([
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'select2'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, CommunicationComponent, BaseBlock, ButtonBlock, DividerBlock) {
], function(
Backbone,
Marionette,
Radio,
_,
jQuery,
MailPoet,
App,
CommunicationComponent,
BaseBlock,
ButtonBlock,
DividerBlock
) {
"use strict";
@ -101,7 +113,7 @@ define([
that.get('_selectedPosts').reset(); // Empty out the collection
that.trigger('change:_availablePosts');
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
});
},
_refreshTransformedPosts: function() {
@ -118,7 +130,7 @@ define([
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
_insertSelectedPosts: function() {
@ -134,7 +146,7 @@ define([
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
collection.add(posts, { at: index });
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
});
@ -163,6 +175,7 @@ define([
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
};
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
},
@ -195,6 +208,7 @@ define([
};
},
initialize: function() {
this.model.trigger('startEditing');
this.selectionView = new PostSelectionSettingsView({ model: this.model });
this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model });
},
@ -202,21 +216,23 @@ define([
var that = this,
blockView = this.model.request('blockView');
this.selectionRegion.show(this.selectionView);
this.displayOptionsRegion.show(this.displayOptionsView);
this.showChildView('selectionRegion', this.selectionView);
this.showChildView('displayOptionsRegion', this.displayOptionsView);
MailPoet.Modal.panel({
element: this.$el,
template: '',
position: 'right',
overlay: true,
highlight: blockView.$el,
width: App.getConfig().get('sidepanelWidth'),
onCancel: function() {
// Self destroy the block if the user closes settings modal
that.model.destroy();
},
});
// Inform child views that they have been attached to document
this.selectionView.triggerMethod('attach');
this.displayOptionsView.triggerMethod('attach');
},
switchToDisplayOptions: function() {
// Switch content view
@ -266,14 +282,19 @@ define([
Marionette.CompositeView.apply(this, arguments);
},
onRender: function() {
// Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
},
onAttach: function() {
var that = this;
// Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
//CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
this.$('.mailpoet_posts_categories_and_tags').select2({
multiple: true,
allowClear: true,
placeholder: MailPoet.I18n.t('categoriesAndTags'),
ajax: {
data: function (params) {
return {
@ -405,9 +426,6 @@ define([
"change .mailpoet_posts_sort_by": _.partial(this.changeField, "sortBy"),
};
},
behaviors: {
ColorPickerBehavior: {},
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
@ -450,9 +468,11 @@ define([
if (value == 'titleOnly') {
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_separator').addClass('mailpoet_hidden');
} else {
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_separator').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') {

View File

@ -139,6 +139,7 @@ define([
}
},
},
HighlightEditingBehavior: {},
},
onDragSubstituteBy: function() { return Module.SocialWidgetView; },
constructor: function() {

View File

@ -70,9 +70,6 @@ define([
"click .mailpoet_done_editing": "close",
};
},
behaviors: {
ColorPickerBehavior: {},
},
});
Module.SpacerWidgetView = base.WidgetView.extend({

View File

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

View File

@ -62,6 +62,13 @@ define([
});
};
Module.getBulkTransformedPosts = function(options) {
return Module._query({
action: 'getBulkTransformedPosts',
options: options,
});
};
Module.saveNewsletter = function(options) {
return MailPoet.Ajax.post({
endpoint: 'newsletters',

View File

@ -10,7 +10,6 @@ define([
availableStyles: {},
socialIcons: {},
blockDefaults: {},
translations: {},
sidepanelWidth: '331px',
validation: {},
urls: {},

View File

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

View File

@ -191,7 +191,7 @@ define([
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
MailPoet.I18n.t('templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
@ -199,30 +199,27 @@ define([
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
MailPoet.I18n.t('templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} 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'),
MailPoet.I18n.t('templateSaved'),
{
positionAfter: that.$el,
scroll: true,
}
);
}).fail(function() {
console.log('Template save failed', arguments);
MailPoet.Notice.error(
App.getConfig().get('translations.templateSaveFailed'),
MailPoet.I18n.t('templateSaveFailed'),
{
positionAfter: that.$el,
scroll: true,
@ -247,7 +244,7 @@ define([
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
MailPoet.I18n.t('templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
@ -255,14 +252,13 @@ define([
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
MailPoet.I18n.t('templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({
name: templateName,
description: templateDescription,
@ -278,7 +274,6 @@ define([
next: function() {
this.hideOptionContents();
if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
console.log('Next');
window.location.href = App.getConfig().get('urls.send');
}
},
@ -288,9 +283,11 @@ define([
return;
}
var contents = JSON.stringify(jsonObject);
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
JSON.stringify(jsonObject).indexOf("[unsubscribeUrl]") < 0) {
this.showValidationError(App.getConfig().get('translations.unsubscribeLinkMissing'));
contents.indexOf("[link:subscription_unsubscribe_url]") < 0 &&
contents.indexOf("[link:subscription_unsubscribe]") < 0) {
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
return;
}

View File

@ -1,13 +1,24 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
'mailpoet',
'backbone',
'backbone.marionette',
'backbone.supermodel',
'underscore',
'jquery',
'sticky-kit'
], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
], function(
App,
CommunicationComponent,
MailPoet,
Backbone,
Marionette,
SuperModel,
_,
jQuery,
StickyKit
) {
"use strict";
@ -234,19 +245,23 @@ define([
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'render',
action: 'showPreview',
data: json,
}).done(function(response){
MailPoet.Modal.loading(false);
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
if (response.result === true) {
window.open(response.data.url, '_blank')
}
MailPoet.Notice.error(response.errors);
}).fail(function(error) {
MailPoet.Modal.loading(false);
alert('Something went wrong, check console');
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailed')
);
});
},
sendPreview: function() {
// testing sending method
console.log('trying to send a preview');
// get form data
var $emailField = this.$('#mailpoet_preview_to_email');
var data = {
@ -256,7 +271,7 @@ define([
if (data.subscriber.length <= 0) {
MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewEmailMissing'),
MailPoet.I18n.t('newsletterPreviewEmailMissing'),
{
positionAfter: $emailField,
scroll: true,
@ -270,7 +285,7 @@ define([
CommunicationComponent.previewNewsletter(data).done(function(response) {
if(response.result !== undefined && response.result === true) {
MailPoet.Notice.success(App.getConfig().get('translations.newsletterPreviewSent'), { scroll: true });
MailPoet.Notice.success(MailPoet.I18n.t('newsletterPreviewSent'), { scroll: true });
} else {
if (_.isArray(response.errors)) {
response.errors.map(function(error) {
@ -278,7 +293,7 @@ define([
});
} else {
MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewFailedToSend'),
MailPoet.I18n.t('newsletterPreviewFailedToSend'),
{
scroll: true,
static: true,

View File

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

View File

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

View File

@ -2,39 +2,38 @@ define(
[
'react',
'react-router',
'classnames'
'classnames',
'mailpoet'
],
function(
React,
Router,
classNames
classNames,
MailPoet
) {
var Link = Router.Link;
var Breadcrumb = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
step: null,
steps: [
{
name: 'type',
label: 'Select type',
label: MailPoet.I18n.t('selectType'),
link: '/new'
},
{
name: 'template',
label: 'Template'
label: MailPoet.I18n.t('template')
},
{
name: 'editor',
label: 'Designer'
label: MailPoet.I18n.t('designer')
},
{
name: 'send',
label: 'Send'
label: MailPoet.I18n.t('send')
}
]
};
@ -73,4 +72,4 @@ define(
return Breadcrumb;
}
);
);

View File

@ -1,261 +0,0 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'classnames',
'jquery'
],
function(
React,
Router,
Listing,
classNames,
jQuery
) {
var Link = Router.Link;
var columns = [
{
name: 'subject',
label: 'Subject',
sortable: true
},
{
name: 'status',
label: 'Status'
},
{
name: 'segments',
label: 'Lists'
},
{
name: 'created_at',
label: 'Created on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
}
];
var messages = {
onTrash: function(response) {
var count = ~~response;
var message = null;
if(count === 1) {
message = (
'1 newsletter was moved to the trash.'
);
} else {
message = (
'%$1d newsletters were moved to the trash.'
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
var count = ~~response;
var message = null;
if(count === 1) {
message = (
'1 newsletter was permanently deleted.'
);
} else {
message = (
'%$1d newsletters were permanently deleted.'
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
var count = ~~response;
var message = null;
if(count === 1) {
message = (
'1 newsletter has been restored from the trash.'
);
} else {
message = (
'%$1d newsletters have been restored from the trash.'
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
var bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
var item_actions = [
{
name: 'edit',
link: function(item) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
Edit
</a>
);
}
},
{
name: 'trash'
}
];
var NewsletterList = React.createClass({
pauseSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'pause',
data: item.id
}).done(function() {
jQuery('#resume_'+item.id).show();
jQuery('#pause_'+item.id).hide();
});
},
resumeSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'resume',
data: item.id
}).done(function() {
jQuery('#pause_'+item.id).show();
jQuery('#resume_'+item.id).hide();
});
},
renderStatus: function(item) {
if(!item.queue) {
return (
<span>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',
'has-row-actions'
);
var segments = newsletter.segments.map(function(segment) {
return segment.name
}).join(', ');
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ newsletter.subject }</a>
</strong>
{ actions }
</td>
<td className="column" data-colname="Lists">
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Newsletters <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
params={ this.props.params }
endpoint="newsletters"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions }
item_actions={ item_actions }
messages={ messages }
auto_refresh={ true } />
</div>
);
}
});
return NewsletterList;
}
);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,21 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link } from 'react-router'
import NewsletterList from 'newsletters/list.jsx'
import { Router, Route, IndexRedirect, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import NewsletterTypes from 'newsletters/types.jsx'
import NewsletterTemplates from 'newsletters/templates.jsx'
import NewsletterSend from 'newsletters/send.jsx'
import NewsletterStandard from 'newsletters/types/standard.jsx'
import NewsletterWelcome from 'newsletters/types/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
import NewsletterTypeStandard from 'newsletters/types/standard.jsx'
import NewsletterTypeWelcome from 'newsletters/types/welcome/welcome.jsx'
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx'
import NewsletterListStandard from 'newsletters/listings/standard.jsx'
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx'
import NewsletterListNotification from 'newsletters/listings/notification.jsx'
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
@ -18,21 +23,30 @@ const App = React.createClass({
}
});
let container = document.getElementById('newsletters_container');
const container = document.getElementById('newsletters_container');
if(container) {
ReactDOM.render((
<Router history={ history }>
<Route path="/" component={ App }>
<IndexRoute component={ NewsletterList } />
<IndexRedirect to="standard" />
{/* Listings */}
<Route name="listing/standard" path="standard" component={ NewsletterListStandard } />
<Route name="listing/welcome" path="welcome" component={ NewsletterListWelcome } />
<Route name="listing/notification" path="notification" component={ NewsletterListNotification } />
<Route path="standard/*" component={ NewsletterListStandard } />
<Route path="welcome/*" component={ NewsletterListWelcome } />
<Route path="notification/*" component={ NewsletterListNotification } />
{/* Newsletter: type selection */}
<Route path="new" component={ NewsletterTypes } />
<Route name="standard" path="new/standard" component={ NewsletterStandard } />
<Route name="welcome" path="new/welcome" component={ NewsletterWelcome } />
<Route name="notification" path="new/notification" component={ NewsletterNotification } />
{/* New newsletter: types */}
<Route name="new/standard" path="new/standard" component={ NewsletterTypeStandard } />
<Route name="new/welcome" path="new/welcome" component={ NewsletterTypeWelcome } />
<Route name="new/notification" path="new/notification" component={ NewsletterTypeNotification } />
{/* Template selection */}
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
{/* Sending options */}
<Route path="send/:id" component={ NewsletterSend } />
<Route path="filter[:filter]" component={ NewsletterList } />
<Route path="*" component={ NewsletterList } />
</Route>
</Router>
), container);

View File

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

View File

@ -2,190 +2,212 @@ define(
[
'react',
'react-router',
'underscore',
'mailpoet',
'form/form.jsx',
'form/fields/selection.jsx',
'newsletters/send/standard.jsx',
'newsletters/send/notification.jsx',
'newsletters/send/welcome.jsx',
'newsletters/breadcrumb.jsx'
],
function(
React,
Router,
_,
MailPoet,
Form,
Selection,
StandardNewsletterFields,
NotificationNewsletterFields,
WelcomeNewsletterFields,
Breadcrumb
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: 'Subject line',
tip: "Be creative! It's the first thing your subscribers see."+
"Tempt them to open your email.",
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': 'You need to specify a subject.'
}
},
{
name: 'segments',
label: 'Segments',
tip: "The subscriber segment that will be used for this campaign.",
type: 'selection',
placeholder: "Select a segment",
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': 'You need to select a segment.'
}
},
{
name: 'sender',
label: 'Sender',
tip: "Name & email of yourself or your company.",
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: 'John Doe',
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: 'john.doe@email.com',
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: 'Reply-to',
tip: 'When the subscribers hit "reply" this is who will receive their '+
'email.',
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: 'John Doe',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_address',
type: 'text',
placeholder: 'john.doe@email.com',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
var messages = {
onUpdate: function() {
MailPoet.Notice.success('Newsletter successfully updated!');
},
onCreate: function() {
MailPoet.Notice.success('Newsletter successfully added!');
}
};
var NewsletterSend = React.createClass({
mixins: [
Router.History
],
componentDidMount: function() {
jQuery('#mailpoet_newsletter').parsley();
contextTypes: {
router: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
fields: [],
item: {},
loading: false,
};
},
getFieldsByNewsletter: function(newsletter) {
var type = this.getSubtype(newsletter);
return type.getFields(newsletter);
},
getSendButtonOptions: function() {
var type = this.getSubtype(this.state.item);
return type.getSendButtonOptions(this.state.item);
},
getSubtype: function(newsletter) {
switch(newsletter.type) {
case 'notification': return NotificationNewsletterFields;
case 'welcome': return WelcomeNewsletterFields;
default: return StandardNewsletterFields;
}
},
isValid: function() {
return jQuery('#mailpoet_newsletter').parsley().isValid();
},
handleSend: function() {
componentDidMount: function() {
if(this.isMounted()) {
this.loadItem(this.props.params.id);
}
jQuery('#mailpoet_newsletter').parsley();
},
componentWillReceiveProps: function(props) {
this.loadItem(props.params.id);
},
loadItem: function(id) {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'get',
data: id
}).done((response) => {
if(response === false) {
this.setState({
loading: false,
item: {},
}, function() {
this.context.router.push('/new');
}.bind(this));
} else {
this.setState({
loading: false,
item: response,
fields: this.getFieldsByNewsletter(response),
});
}
});
},
handleSend: function(e) {
e.preventDefault();
if(!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
this.setState({ loading: 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()
}
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).then((response) => {
if (response.result === true) {
return MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: _.extend({}, this.state.item, {
newsletter_id: this.props.params.id,
}),
});
} else {
return response;
}
}).done(function(response) {
}).done((response) => {
this.setState({ loading: false });
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
'The newsletter is being sent...'
);
this.context.router.push(`/${ this.state.item.type || '' }`);
MailPoet.Notice.success(response.data.message);
} else {
if(response.errors) {
MailPoet.Notice.error(response.errors);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
MailPoet.I18n.t('newsletterSendingError').replace("%$1s", '?page=mailpoet-settings')
);
}
}
}.bind(this));
});
}
return false;
},
handleSave: function(e) {
e.preventDefault();
this._save(e).done(() => {
this.context.router.push(`/${ this.state.item.type || '' }`);
});
},
handleRedirectToDesign: function(e) {
e.preventDefault();
var redirectTo = e.target.href;
this._save(e).done(() => {
window.location = redirectTo;
});
},
_save: function(e) {
this.setState({ loading: true });
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).done((response) => {
this.setState({ loading: false });
if(response.result === true) {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
} else {
if(response.errors) {
MailPoet.Notice.error(response.errors);
}
}
});
},
handleFormChange: function(e) {
var item = this.state.item,
field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item
});
return true;
},
render: function() {
return (
<div>
<h1>Final step: last details</h1>
<h1>{MailPoet.I18n.t('finalNewsletterStep')}</h1>
<Breadcrumb step="send" />
<Form
id="mailpoet_newsletter"
endpoint="newsletters"
fields={ fields }
params={ this.props.params }
messages={ messages }
isValid={ this.isValid }
fields={ this.state.fields }
item={ this.state.item }
loading={ this.state.loading }
onChange={this.handleFormChange}
onSubmit={this.handleSave}
>
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleSend }
value="Send" />
value={MailPoet.I18n.t('send')}
{...this.getSendButtonOptions()}
/>
&nbsp;
<input
className="button button-secondary"
type="submit"
value="Save as draft and close" />
&nbsp;or simply&nbsp;
value={MailPoet.I18n.t('saveDraftAndClose')} />
&nbsp;{MailPoet.I18n.t('orSimply')}&nbsp;
<a
href={
'?page=mailpoet-newsletter-editor&id='+this.props.params.id
}>
go back to design
}
onClick={this.handleRedirectToDesign}>
{MailPoet.I18n.t('goBackToDesign')}
</a>.
</p>
</Form>
@ -196,4 +218,4 @@ define(
return NewsletterSend;
}
);
);

View File

@ -0,0 +1,109 @@
define(
[
'mailpoet',
'newsletters/types/notification/scheduling.jsx'
],
function(
MailPoet,
Scheduling
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('postNotificationSubjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'options',
label: MailPoet.I18n.t('selectFrequency'),
type: 'reactComponent',
component: Scheduling,
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
}
return name;
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
}
]
}
];
return {
getFields: function(newsletter) {
return fields;
},
getSendButtonOptions: function(newsletter) {
return {
value: MailPoet.I18n.t('activate')
};
},
};
}
);

View File

@ -0,0 +1,436 @@
define(
[
'react',
'jquery',
'underscore',
'mailpoet',
'form/fields/checkbox.jsx',
'form/fields/select.jsx',
'form/fields/text.jsx',
],
function(
React,
jQuery,
_,
MailPoet,
Checkbox,
Select,
Text
) {
var settings = window.mailpoet_settings || {},
currentTime = window.mailpoet_current_time || '00:00',
defaultDateTime = window.mailpoet_current_date + ' ' + '00:00:00';
timeOfDayItems = window.mailpoet_schedule_time_of_day,
dateDisplayFormat = window.mailpoet_date_display_format,
dateStorageFormat = window.mailpoet_date_storage_format;
var datepickerTranslations = {
closeText: MailPoet.I18n.t('close'),
currentText: MailPoet.I18n.t('today'),
nextText: MailPoet.I18n.t('next'),
prevText: MailPoet.I18n.t('previous'),
monthNames: [
MailPoet.I18n.t('january'),
MailPoet.I18n.t('february'),
MailPoet.I18n.t('march'),
MailPoet.I18n.t('april'),
MailPoet.I18n.t('may'),
MailPoet.I18n.t('june'),
MailPoet.I18n.t('july'),
MailPoet.I18n.t('august'),
MailPoet.I18n.t('september'),
MailPoet.I18n.t('october'),
MailPoet.I18n.t('november'),
MailPoet.I18n.t('december')
],
monthNamesShort: [
MailPoet.I18n.t('januaryShort'),
MailPoet.I18n.t('februaryShort'),
MailPoet.I18n.t('marchShort'),
MailPoet.I18n.t('aprilShort'),
MailPoet.I18n.t('mayShort'),
MailPoet.I18n.t('juneShort'),
MailPoet.I18n.t('julyShort'),
MailPoet.I18n.t('augustShort'),
MailPoet.I18n.t('septemberShort'),
MailPoet.I18n.t('octoberShort'),
MailPoet.I18n.t('novemberShort'),
MailPoet.I18n.t('decemberShort')
],
dayNames: [
MailPoet.I18n.t('sunday'),
MailPoet.I18n.t('monday'),
MailPoet.I18n.t('tuesday'),
MailPoet.I18n.t('wednesday'),
MailPoet.I18n.t('thursday'),
MailPoet.I18n.t('friday'),
MailPoet.I18n.t('saturday')
],
dayNamesShort: [
MailPoet.I18n.t('sundayShort'),
MailPoet.I18n.t('mondayShort'),
MailPoet.I18n.t('tuesdayShort'),
MailPoet.I18n.t('wednesdayShort'),
MailPoet.I18n.t('thursdayShort'),
MailPoet.I18n.t('fridayShort'),
MailPoet.I18n.t('saturdayShort')
],
dayNamesMin: [
MailPoet.I18n.t('sundayMin'),
MailPoet.I18n.t('mondayMin'),
MailPoet.I18n.t('tuesdayMin'),
MailPoet.I18n.t('wednesdayMin'),
MailPoet.I18n.t('thursdayMin'),
MailPoet.I18n.t('fridayMin'),
MailPoet.I18n.t('saturdayMin')
],
};
var DateText = React.createClass({
onChange: function(event) {
// Swap display format to storage format
var displayDate = event.target.value,
storageDate = this.getStorageDate(displayDate);
event.target.value = storageDate;
this.props.onChange(event);
},
componentDidMount: function() {
var $element = jQuery(this.refs.dateInput),
that = this;
if ($element.datepicker) {
// Override jQuery UI datepicker Date parsing and formatting
jQuery.datepicker.parseDate = function(format, value) {
// Transform string format to Date object
return MailPoet.Date.toDate(value, {
parseFormat: dateDisplayFormat,
format: format
});
};
jQuery.datepicker.formatDate = function(format, value) {
// Transform Date object to string format
var newValue = MailPoet.Date.format(value, {
format: format
});
return newValue;
};
$element.datepicker(_.extend({
dateFormat: this.props.displayFormat,
isRTL: false,
onSelect: function(value) {
that.onChange({
target: {
name: that.getFieldName(),
value: value,
},
});
},
}, datepickerTranslations));
this.datepickerInitialized = true;
}
},
componentWillUnmount: function() {
if (this.datepickerInitialized) {
jQuery(this.refs.dateInput).datepicker('destroy');
}
},
getFieldName: function() {
return this.props.name || 'date';
},
getDisplayDate: function(date) {
return MailPoet.Date.format(date, {
parseFormat: this.props.storageFormat,
format: this.props.displayFormat
});
},
getStorageDate: function(date) {
return MailPoet.Date.format(date, {
parseFormat: this.props.displayFormat,
format: this.props.storageFormat
});
},
render: function() {
return (
<input
type="text"
size="10"
name={this.getFieldName()}
value={this.getDisplayDate(this.props.value)}
readOnly={ true }
onChange={this.onChange}
ref="dateInput"
{...this.props.validation} />
);
},
});
var TimeSelect = React.createClass({
render: function() {
const options = Object.keys(timeOfDayItems).map(
(value, index) => {
return (
<option
key={ 'option-' + index }
value={ value }>
{ timeOfDayItems[value] }
</option>
);
}
);
return (
<select
name={this.props.name || 'time'}
value={this.props.value}
onChange={this.props.onChange}
{...this.props.validation}
>
{options}
</select>
);
}
});
var DateTime = React.createClass({
_DATE_TIME_SEPARATOR: " ",
getInitialState: function() {
return this._buildStateFromProps(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.setState(this._buildStateFromProps(nextProps));
},
_buildStateFromProps: function(props) {
let value = props.value || defaultDateTime;
const [date, time] = value.split(this._DATE_TIME_SEPARATOR)
return {
date: date,
time: time,
};
},
handleChange: function(event) {
var newState = {};
newState[event.target.name] = event.target.value;
this.setState(newState, function() {
this.propagateChange();
});
},
propagateChange: function() {
if (this.props.onChange) {
return this.props.onChange({
target: {
name: this.props.name || '',
value: this.getDateTime(),
}
})
}
},
getDateTime: function() {
return [this.state.date, this.state.time].join(this._DATE_TIME_SEPARATOR);
},
render: function() {
return (
<span>
<DateText
name="date"
value={this.state.date}
onChange={this.handleChange}
displayFormat={dateDisplayFormat}
storageFormat={dateStorageFormat}
validation={this.props.dateValidation}/>
<TimeSelect
name="time"
value={this.state.time}
onChange={this.handleChange}
validation={this.props.timeValidation} />
</span>
);
}
});
var StandardScheduling = React.createClass({
_getCurrentValue: function() {
return _.defaults(
this.props.item[this.props.field.name] || {},
{
isScheduled: '0',
scheduledAt: defaultDateTime
}
);
},
handleValueChange: function(event) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[event.target.name] = event.target.value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleCheckboxChange: function(event) {
event.target.value = this.refs.isScheduled.checked ? '1' : '0';
return this.handleValueChange(event);
},
isScheduled: function() {
return this._getCurrentValue().isScheduled === '1';
},
getDateValidation: function() {
return {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noScheduledDateError'),
'data-parsley-errors-container': '#mailpoet_scheduling',
};
},
render: function() {
var schedulingOptions;
if (this.isScheduled()) {
schedulingOptions = (
<span id="mailpoet_scheduling">
<DateTime
name="scheduledAt"
value={this._getCurrentValue().scheduledAt}
onChange={this.handleValueChange}
dateValidation={this.getDateValidation()} />
&nbsp;
<span>
{MailPoet.I18n.t('websiteTimeIs')} <code>{currentTime}</code>
</span>
</span>
);
}
return (
<div>
<input
ref="isScheduled"
type="checkbox"
value="1"
checked={this.isScheduled()}
name="isScheduled"
onChange={this.handleCheckboxChange} />
{schedulingOptions}
</div>
);
},
});
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
}
return name;
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
}
]
},
{
name: 'options',
label: MailPoet.I18n.t('scheduleIt'),
type: 'reactComponent',
component: StandardScheduling,
}
];
return {
getFields: function(newsletter) {
return fields;
},
getSendButtonOptions: function(newsletter) {
newsletter = newsletter || {};
let isScheduled = (
typeof newsletter.options === 'object'
&& newsletter.options.isScheduled === '1'
);
let options = {
value: (isScheduled
? MailPoet.I18n.t('schedule')
: MailPoet.I18n.t('send'))
};
if (newsletter.status === 'sent'
|| newsletter.status === 'sending') {
options['disabled'] = 'disabled';
}
return options;
},
};
}
);

View File

@ -0,0 +1,86 @@
define(
[
'mailpoet',
'newsletters/types/welcome/scheduling.jsx'
],
function(
MailPoet,
Scheduling
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'options',
label: MailPoet.I18n.t('sendWelcomeEmailWhen'),
type: 'reactComponent',
component: Scheduling,
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
}
]
}
];
return {
getFields: function(newsletter) {
return fields;
},
getSendButtonOptions: function(newsletter) {
return {
value: MailPoet.I18n.t('activate')
};
},
};
}
);

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