Compare commits

..

762 Commits

Author SHA1 Message Date
22efd2109b Release 3.0.0-rc.1.0.0 2017-08-01 18:04:30 +00:00
0865995d21 Does not remove padding from the last element in a column 2017-08-01 09:47:29 +02:00
7c30192a03 Adds option to replace shortcodes in one string using contents from
another string
Adds unit tests
2017-08-01 09:32:42 +02:00
334c3ff420 Returns false when data-post-id tag is not found
Uses all post types, including custom, when querying posts
2017-08-01 09:32:42 +02:00
6d738ddb3b Merge pull request #1031 from mailpoet/mss-response
Mss response [MAILPOET-1035]
2017-07-31 14:37:13 -04:00
440052cf2c Remove duplicate code
[MAILPOET-1035]
2017-07-31 16:51:11 +02:00
ccb751b44a Adhere to updated MSS /me response
[MAILPOET-1035]
2017-07-31 16:47:20 +02:00
7cf3d0960d Unify API response constants
[MAILPOET-1035]
2017-07-31 13:51:23 +02:00
6e45856622 Merge pull request #1022 from mailpoet/mediumint_to_int
Switch ID fields from mediumint to int in DB [MAILPOET-1022]
2017-07-31 11:03:16 +02:00
1071688924 Update a poll [MAILPOET-1017] 2017-07-31 09:41:45 +02:00
4f3b0234a4 Move exception rule to top
[MAILPOET-1028]
2017-07-31 09:27:26 +02:00
3e2bbeac11 Fix ES6 comma-dangle eslint rule (functions should be ignored pre-ES2017) [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
08f81bd816 Fix ES5 comma-dangle eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
f0c59ff635 Fix tests comma-dangle eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
66c6f52646 Fix ES6 comma-spacing eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
4988aaf14f Fix ES5 comma-spacing eslint rule [MAILPOET-1028] 2017-07-31 09:27:26 +02:00
5773d46f1c Merge pull request #1029 from mailpoet/alc-rendering
Enable blockquotes and fix image positioning in ALC blocks [MAILPOET-1037] [MAILPOET-1038]
2017-07-30 12:01:39 -04:00
f3a1e1b447 Update Robo test debug rule to accept individual test file paths 2017-07-28 14:36:40 +03:00
6de746162e Update Posts transformer to extract images by splitting the DOM tree 2017-07-28 14:36:12 +03:00
60b3c066a5 Allow rendering blockquote tags 2017-07-28 11:10:57 +03:00
63158dc2d5 Merge pull request #1024 from mailpoet/mta-frequency-fix
Mta frequency fix [MAILPOET-1021]
2017-07-27 19:17:14 +03:00
dede6e56c4 Merge pull request #1026 from mailpoet/beta_end
Remove concept of "Beta" on readme.txt, update banners [MAILPOET-1023]
2017-07-27 15:49:22 +03:00
dd366dde18 Remove concept of "Beta" on readme.txt, update banners [MAILPOET-1023] 2017-07-27 14:04:00 +03:00
2fedafe483 Merge pull request #1025 from mailpoet/tests_sniffs
Enable remaining CodeSniffer rules for PHP tests [MAILPOET-1025]
2017-07-26 21:34:48 -04:00
feae98ac3f Use a single ruleset for both the plugin and its tests [MAILPOET-1025] 2017-07-26 18:56:14 +03:00
6fb18ad321 Fix PSR1.Classes.ClassDeclaration sniffer rule in tests [MAILPOET-1025] 2017-07-26 18:28:56 +03:00
6c843e35b0 Update warning text
[MAILPOET-1021]
2017-07-26 16:27:53 +02:00
cfdb86eb6e Replace default option text
[MAILPOET-1021]
2017-07-26 16:24:31 +02:00
41fb93e453 Remove the number of emails per day calculation
[MAILPOET-1021]
2017-07-26 16:22:23 +02:00
19813b5dad Fix host frequency override
[MAILPOET-1021]
2017-07-26 16:08:49 +02:00
321393f119 Fix Squiz.Classes.ClassFileName sniffer rule in tests [MAIPOET-1025] 2017-07-26 17:03:43 +03:00
ab3f41302c Merge pull request #1023 from mailpoet/remove-subquery
Use Joins instead subqueries [MAILPOET-1027]
2017-07-26 13:56:32 +03:00
9dff4539e6 Use Joins instead subqueries
[MAILPOET-1027]
2017-07-26 11:46:36 +02:00
6520d5eca3 Merge branch 'master' of github.com:mailpoet/mailpoet 2017-07-25 16:45:42 +03:00
f5b152cdfa Bump up release version to 3.0.0-beta.37.0.0 2017-07-25 16:40:12 +03:00
1d2cab1249 Merge pull request #1021 from mailpoet/prevent-mss-override
Prevent to override sending group [MAILPOET-1024]
2017-07-25 15:24:28 +03:00
90b93bd76e Merge pull request #1018 from mailpoet/prevent_sending_with_broken_newsletter_body
Prevents sending emails when rendered newsletter is broken [MAILPOET-1020]
2017-07-25 13:13:03 +03:00
563ca3e605 Switch ID fields from mediumint to int in DB [MAILPOET-1022] 2017-07-25 13:11:56 +03:00
08bbfcb5e8 Track MixPanel events for Settings [MAILPOET-998] 2017-07-25 10:53:28 +02:00
5572a7f1e5 Prevent to override sending group
[MAILPOET-1024]
2017-07-25 08:32:10 +02:00
9dd326e7db Updates class to use newly added validate() method on the model
Validates existing queue's rendered newsletter body
Cleans code formatting
2017-07-24 12:49:39 -04:00
cf00813c7f Adds new method to validate model on request
Cleans up code formatting
2017-07-24 11:20:43 -04:00
4c898b76b2 Uses ValidModel to validate rendered newsletter body
Removes previous validation method
Does not serialize null values
2017-07-24 11:15:00 -04:00
81a2ba8e03 Adds new validation method for rendered newsletter body 2017-07-24 11:11:22 -04:00
7e6d900b53 Merge pull request #997 from mailpoet/scheduled_task_subscribers
Extract subscribers to a separate table in the Bounce worker [MAILPOET-987]
2017-07-24 17:14:38 +03:00
1a522794d6 Adds method to validate rendered newsletter body on sending queue
Prevents sending queue worker from sending when sending queue's rendered
newsletter body is invalid
2017-07-23 11:50:35 -04:00
197537d6ca Merge pull request #1015 from mailpoet/custom_field_shortcodes
Add missing brackets to custom field shortcodes [MAILPOET-1013]
2017-07-21 21:40:25 -04:00
b212ca801b Merge pull request #1016 from mailpoet/sync_deleted_wp_users
Remove deleted WP users from subscribers [MAILPOET-1012]
2017-07-21 21:39:53 -04:00
db8f3216d7 Add tests for syncing deleted WP users [MAILPOET-1012] 2017-07-21 18:43:29 +03:00
78f9945296 Remove deleted WP users from subscribers [MAILPOET-1012] 2017-07-21 17:48:48 +03:00
0cebcd3965 Add missing brackets to custom field shortcodes [MAILPOET-1013] 2017-07-21 14:40:56 +03:00
4062e0662d Merge pull request #1009 from mailpoet/share_data_kb_link
Update link to "Share your data" in Welcome tabs [MAILPOET-1010]
2017-07-20 19:24:53 +02:00
20b7e82d3c Merge pull request #1014 from mailpoet/new-poll
Update poll [MAILPOET-1016]
2017-07-20 19:16:36 +02:00
9ab8a80567 Merge pull request #992 from mailpoet/circleci
Set up CircleCI tests to run on PHP 5.6 and 7.1 [MAILPOET-986]
2017-07-20 09:55:44 -04:00
f98d02121b Fix BatchIterator skipping rows when subscribers' processed status is modified [MAILPOET-987] 2017-07-20 16:31:27 +03:00
e68e212ad0 Update poll
[MAILPOET-1016]
2017-07-20 12:48:55 +01:00
cb1a1e51ba Use fixtures instead of IDs in the sheduled task subscriber test [MAILPOET-987] 2017-07-20 13:26:31 +03:00
b42d8e68d9 Rename removeSubscribers() method and encapsulate task completion logic [MAILPOET-987] 2017-07-20 11:22:47 +03:00
e88d130ebb Remove side-effects from Update page styles [MAILPOET-1001]
Example: Premium page comparison table rows centered
2017-07-20 08:59:03 +01:00
c924778d50 Rename unprocessed status, refactor counting a bit more clearly [MAILPOET-987] 2017-07-20 10:56:34 +03:00
a9051c6d09 merging qa_js and php5 builds to gain execution time 2017-07-19 19:11:35 -04:00
f6243b5d79 Merge pull request #1005 from mailpoet/mixpanel-more-data
MixPanel: extend summarized data collection [MAILPOET-974]
2017-07-19 20:25:14 +03:00
68c0b93586 Fix Premium page scaling on mobile screens [MAILPOET-1001] 2017-07-19 18:14:50 +01:00
0e8be8040c Move custom page styles from views to a separate *.styl file [MAILPOET-1001] 2017-07-19 18:14:50 +01:00
f3ea548d65 Update the Premium page [MAILPOET-1001] 2017-07-19 18:14:50 +01:00
1ab6be8acd Fix Notice and remove redundant field
[MAILPOET-974]
2017-07-19 16:16:08 +01:00
c413acd93d display a warning when unable to connect to database 2017-07-19 16:05:34 +01:00
7614a4d8dc Update link to "Share your data" in Welcome tabs [MAILPOET-1010] 2017-07-19 10:27:59 +03:00
4a94c29b85 Merge pull request #1008 from mailpoet/model_unit_test_update
Uses MailPoet's Model class vs. Idiorm's in test
2017-07-19 09:32:51 +03:00
b5b9531ff3 Merge pull request #1006 from mailpoet/premium_mixpanel
Add handlers for Premium events tracking, expose Premium version to JS [PREMIUM-24]
2017-07-18 16:14:24 +02:00
e2a048a65f Uses MP Model class vs. Idiorm's in test 2017-07-18 09:49:01 -04:00
c28726f118 Release 3.0.0-beta.36.3.1 2017-07-18 13:58:35 +03:00
7c74885669 Merge pull request #1003 from mailpoet/php53_listing_fix
Adds asArray() method to the base Model class [MAILPOET-1007]
2017-07-17 20:00:10 +03:00
5f74f34cba Adds notes explaining the fix 2017-07-17 12:15:39 -04:00
d6ef526a9d Add handlers for Premium events tracking, expose Premium version [PREMIUM-24] 2017-07-17 18:57:18 +03:00
0da0507e0a Fixes SMTP timing out on slow hosts
Increases default connection timeout value
Adds filter to specify custom connection timeout value

[MAILPOET-1004]
2017-07-17 15:21:33 +01:00
3f03c985bf Collect plugins info for analytics
[MAILPOET-974]
2017-07-17 14:46:26 +01:00
77f6e13aa3 Merge pull request #999 from mailpoet/url_extraction_fix
Replaces URL extraction regex with DOM parser [MAILPOET-1003]
2017-07-17 16:32:59 +03:00
213d0e8627 Distinguishes between link TYPE and link CATEGORY NAME
Uses $replacement_link variable for clarity
2017-07-17 09:14:11 -04:00
1d019bc11e Merge pull request #1004 from mailpoet/filter_to_whitelist_plugins_from_conflict_resolver
Adds filters to whitelist styles/scripts unloaded by our plugin [MAILPOET-1008]
2017-07-17 15:11:37 +02:00
ca12487416 moving shell code into a bash file 2017-07-17 11:38:41 +00:00
c5d42a5033 Merge pull request #995 from mailpoet/welcome-tab-update
Welcome tab update [MAILPOET-979]
2017-07-17 07:17:26 -04:00
3b079440b5 try without sharing workspace 2017-07-17 10:52:48 +00:00
419697991f Adding workflows to CircleCI
Splitting build into 2 builds:

- `build_qa_js`: Builds the project and runs the qa checks and JS tests.

- `php_5`: Runs the PHP tests with PHP 5.6.3

and adding a new build

- `php_7`: Runs the PHP tests with PHP 7.1
2017-07-17 10:26:17 +00:00
d119a1e5fa Send settings values to Mixpanel
[MAILPOET-974]
2017-07-17 11:15:29 +01:00
1bd4264dd5 Fix code style
[MAILPOET-979]
2017-07-17 09:20:28 +01:00
21ee60b7d7 Merge pull request #1002 from mailpoet/newsletter_duplication_update
Resets sent at data when duplicating a sent newsletter [MAILPOET-1005]
2017-07-17 10:07:29 +02:00
849ca27d1f Adds filters to whitelist styles or scripts 2017-07-15 14:57:40 -04:00
a4dad46fb7 Adds asArray() method to the base Model that's used as proxy for
Idiorm's as_array()
2017-07-15 14:25:50 -04:00
fac4c8fb41 Merge pull request #1000 from mailpoet/fix-frequency-smtp
Fix default frequency
2017-07-15 13:04:40 -04:00
cdc87c23ea Resets sent at data when duplicating a sent newsletter 2017-07-15 13:00:25 -04:00
34d09ce0c9 Sets mailer log error when queue cannot be saved during newsletter
pre-processing
2017-07-14 23:15:14 -04:00
fe9ae392f2 Replaces URL extraction regex with DOM parser
Simplifies link replacement logic
Cleans up code
2017-07-14 23:15:08 -04:00
9501640f4f Prevents double UTF-8 encoding 2017-07-14 14:03:48 -04:00
ad028ab55d Fix devault frequency 2017-07-14 08:01:54 +01:00
74cb8d9735 Merge pull request #998 from mailpoet/settings_flex_fix
Fix broken layout in IE 11 on a big screen + make it look like in previous versions [MAILPOET-1002]
2017-07-13 14:11:24 -04:00
381608df22 Fix broken layout in IE 11 on a big screen + make it look like in previous versions [MAILPOET-1002] 2017-07-13 21:03:24 +03:00
6aca598dc3 Fix tests: unneeded migration was triggered by resetting the DB version to 1.0.0 (after EnvTest)
This caused errors when pinging the daemon [MAILPOET-940]
2017-07-13 16:26:53 +03:00
acbe2e383a Add tests [MAILPOET-940] 2017-07-13 13:27:28 +03:00
55d3b67a2a Extract common and task-specific methods for subscribers [MAILPOET-940] 2017-07-13 13:27:28 +03:00
c02394b576 Prepare subscribers in the Bounce worker DB-side for performance reasons [MAILPOET-940] 2017-07-13 13:27:27 +03:00
6a9b8d88c2 Extract subscribers to a separate table in the Bounce worker [MAILPOET-940] 2017-07-13 13:27:27 +03:00
b24c51d800 Merge pull request #996 from mailpoet/add-extra-text
Add extra text on Send with... [MAILPOET-954]
2017-07-13 13:24:22 +03:00
d3a3d3b277 Merge pull request #993 from mailpoet/duplicate-content
Add an option to duplicate content blocks [MAILPOET-968]
2017-07-13 13:14:36 +03:00
3dcff8eb8a Update styles to handle more text
[MAILPOET-954]
2017-07-13 09:41:08 +01:00
401339244c Merge pull request #991 from mailpoet/fix-duplicate-smtp
Remove a duplicate field [MAILPOET-995]
2017-07-12 19:30:37 +03:00
653ecdc4d0 Add extra copy
[MAILPOET-954]
2017-07-12 16:54:49 +01:00
88d5952684 Remove duplicate id
[MAILPOET-979]
2017-07-12 14:55:53 +01:00
c4d6c19c67 Merge pull request #990 from mailpoet/eslint-no-var
Eslint fixes [MAILPOET-994]
2017-07-12 15:51:26 +02:00
0554a84f77 Apply new styles
[MAILPOET-979]
2017-07-12 13:38:56 +01:00
c01b57a383 Add poll
[MAILPOET-979]
2017-07-12 10:28:35 +01:00
65f4f493f1 Add sharing section
[MAILPOET-979]
2017-07-12 10:09:17 +01:00
8a7ea791b0 Move button to see all changelogs
[MAILPOET-979]
2017-07-12 09:35:10 +01:00
1823985172 Display only one set of changelog
[MAILPOET-979]
2017-07-12 09:19:22 +01:00
e735b5f3e0 Replace title
[MAILPOET-979]
2017-07-12 08:44:04 +01:00
cd5c57b7e9 Merge pull request #994 from mailpoet/fixing-readme
Fixing 'Get in touch' URL [MAILPOET-996]
2017-07-11 13:35:36 -04:00
fbf59723d8 Fixing 'Get in touch' URL 2017-07-11 16:57:56 +00:00
2e166a233b Update duplicate icon
[MAILPOET-968]
2017-07-11 17:19:41 +01:00
1608ba5893 Remove a duplicate field
[MAILPOET-995]
2017-07-11 15:39:22 +01:00
9d7eb8038d Fix eslint rule arrow-parens 2017-07-11 14:31:19 +01:00
c9c78a7160 Fix eslint rule indent 2017-07-11 14:31:19 +01:00
53df10dc2b Fix eslint rule eol-last 2017-07-11 14:31:19 +01:00
a746c124a3 Fix eslint rule object-curly-spacing 2017-07-11 14:31:19 +01:00
ca5a5301a8 Fix eslint rule no-var 2017-07-11 14:31:19 +01:00
7a2aaa86cf Tests that populator method is not run for versions below
3.0.0-beta.36.2.1
2017-07-11 14:26:29 +01:00
b626c7ea3c Release 3.0.0-beta.36.3.0 2017-07-11 12:50:33 +01:00
695152e947 Allow duplicate content
[MAILPOET-968]
2017-07-11 10:53:52 +01:00
a36fe400ed Merge pull request #966 from mailpoet/mp2tomp3migration
Settings migration from MP2 to MP3
2017-07-11 12:43:19 +03:00
22bb971db5 Merge pull request #980 from mailpoet/newsletter_listing_update
Sorts standard/history notification records by sent_at date in listings [MAILPOET-932]
2017-07-11 12:43:06 +03:00
b88452c5a2 Fixed: the "Skip Import" link was hidden after unit tests 2017-07-10 18:37:23 +02:00
4a2f9ad1f9 Remove unused code 2017-07-10 18:37:22 +02:00
593a7de9fe New: Add extra text when the migration is completed 2017-07-10 18:37:21 +02:00
e5ecf870c7 Fixed: SendGrid provider was selected even when Sendgrid was not selected in MP2 2017-07-10 18:37:21 +02:00
11cc97b201 Fixed: Unit tests failed if the database prefix was not "wp_" 2017-07-10 18:37:20 +02:00
e352b5bfad New: Migrate installation date 2017-07-10 18:37:19 +02:00
334448c964 New: Add paragraph below "What will be kept in MailPoet 3" 2017-07-10 18:37:18 +02:00
7d8d535cb3 New: Hide the progress bar and the logs if the import has not started yet 2017-07-10 18:37:17 +02:00
68988edd7e Fixed: Change the emails number if the frequency is greater than 15 minutes 2017-07-10 18:37:17 +02:00
f164e9bb95 Fixed: Leave the Newsletter task scheduler (cron) to its default value 2017-07-10 18:37:16 +02:00
fe83435d14 Fixed: "Subscribe in comments" always on 2017-07-10 18:37:15 +02:00
453b7683bc Fixed: SendGrid method and provider were not selected 2017-07-10 18:37:14 +02:00
25625b1ce1 Add unit tests for the settings migration 2017-07-10 18:37:13 +02:00
048d71164a Migrate the settings 2017-07-10 18:31:51 +02:00
24e682e92e Corrects typo 2017-07-10 12:20:37 -04:00
23d90c9f73 Merge pull request #985 from mailpoet/fix-mailpoet-activation
Fix firefox double submit [MAILPOET-992]
2017-07-10 15:53:17 +03:00
a9f190661f Remove unnecessary return statement [MAILPOET-992] 2017-07-10 15:51:19 +03:00
c1e56e5fa1 Update button type to prevent form submit
[MAILPOET-992]
2017-07-10 13:28:06 +01:00
e2bb2679e4 Fix firefox double submit
[MAILPOET-992]
2017-07-10 12:07:07 +01:00
d49530da0f Merge pull request #979 from mailpoet/update-readme
Update readme [MAILPOET-978]
2017-07-10 13:58:36 +03:00
0bc28ef3c7 Remove duplication
[MAILPOET-978]
2017-07-10 11:27:08 +01:00
563b7eccb0 Update video link in readme
[MAILPOET-978]
2017-07-10 11:06:42 +01:00
94a9b63136 Merge pull request #984 from mailpoet/fix-mailpoet-activation
Send the correct group to server [MAILPOET-992]
2017-07-10 12:53:48 +03:00
91ff008485 Make key check error messages more descriptive [MAILPOET-990] 2017-07-10 10:34:55 +01:00
cb8fa23c3f Send the correct group to server
[MAILPOET-992]
2017-07-10 09:18:43 +01:00
3499de05e8 Merge pull request #983 from mailpoet/link_tags_replacement_update
Updates the way link tags are replaced in translations [MAILPOET-991]
2017-07-09 12:14:42 +03:00
77ed4d34e8 Uses Helper's replaceLinkTags method to replace activation link in
subscription confirmation email
2017-07-08 12:00:45 -04:00
cfc5f5a88d Allows defining custom link tag 2017-07-08 11:56:51 -04:00
2514d87a00 Uses Helper's replaceLinkTags method to replace tags in translation
strings
2017-07-08 10:31:33 -04:00
23c6750ccc Removes leftover debug condition 2017-07-07 13:12:45 -04:00
bb2af5176b Tests that newsletters can be sorted by sent_at column 2017-07-07 11:05:39 -04:00
95b5206e8b Updates sending queue worker to set newsletter's sent_at field when
newsletter is sent
2017-07-07 11:05:39 -04:00
394118f113 Adds sent_at column to the newsletters table
Modifies populator to update existing newsletters' sent_at field
2017-07-07 11:05:33 -04:00
b691fb5a48 Adds sorting by sent_at to standard & notification history newsletter listings 2017-07-07 11:04:19 -04:00
1ba2492929 Combines Twig's "replaceLink" and Util/Helper's "replaceLinkTags"
methods into one
2017-07-07 11:02:20 -04:00
d08243b0ce Merge pull request #981 from mailpoet/tests_lint_rules
Fix ScopeClosingBrace, EmptyNonVariable sniffer rules in tests [MAILPOET-989]
2017-07-07 11:01:21 -04:00
189656fdc8 Fixes malformed URL 2017-07-07 09:52:08 -04:00
0be872eea9 Fix PHPCompatibility.PHP.EmptyNonVariable rule [MAILPOET-989] 2017-07-07 11:30:16 +03:00
22c39c0092 Fix PEAR.WhiteSpace.ScopeClosingBrace rule in tests [MAILPOET-989] 2017-07-07 11:11:16 +03:00
1b54e356b2 Merge pull request #978 from mailpoet/add-missing-callbacks
Add missing callbacks to ajax calls [MAILPOET-982]
2017-07-06 19:29:30 -04:00
9079d7d4db Merge pull request #977 from mailpoet/subscription_confirmation_update
Prevents repeat subscription confirmation/duplicate welcome notifications [MAILPOET-988]
2017-07-06 22:52:44 +03:00
160b28a632 Merge pull request #976 from mailpoet/editor-drag-and-drop
Fix reverting to mouse cursor auto icon after clicking Draggable object [MAILPOET-965]
2017-07-06 21:54:28 +03:00
03ebb30ac2 Update readme
[MAILPOET-978]
2017-07-06 15:43:04 +01:00
56eac1ae86 Add missing callbacks to ajax calls
[MAILPOET-982]
2017-07-06 13:06:14 +01:00
89da4c9aae Prevents repeat subscription confirmation
Prevents scheduling of duplicate welcome notifications
2017-07-05 19:50:57 -04:00
b510071857 Merge pull request #972 from mailpoet/merge-sending-method
Merge sending method [MAILPOET-953]
2017-07-05 19:59:30 +03:00
4bbec4700d Stub out interact.styleCursor method 2017-07-05 19:45:13 +03:00
29a2af2555 Fix reverting to mouse cursor auto icon after clicking Draggable object 2017-07-05 19:26:33 +03:00
94d57b5287 Make sure all frequency changes are displayed correctly
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
6b7bc8a731 Make sure frequency is always set
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
3dd0f00b7f Fix sending method
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
b58f6cc4e0 Fix broken reporting
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
7c942147ec Show frequency fields on initial page render
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
6fca2061e6 Show fields on initial page render
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
0acc41c887 Pre select the sending method
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
20d1ac81e2 Save correct values
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
cdb7b99728 Merge sending methods
[MAILPOET-953]
2017-07-05 16:37:08 +01:00
54cb838d71 Merge pull request #959 from mailpoet/scheduled_tasks
Refactor simple workers to use a ScheduledTask model [MAILPOET-940]
2017-07-05 17:28:08 +03:00
0b9ea23f0b Temporarily remove pause/resume methods from scheduled tasks [MAILPOET-940] 2017-07-05 17:12:35 +03:00
3e40f768b4 Add migration of simple scheduled tasks [MAILPOET-940] 2017-07-05 17:12:34 +03:00
6e929dcf79 Refactor simple workers to use a ScheduledTask model [MAILPOET-940] 2017-07-05 17:00:23 +03:00
7be01f0e4e Merge pull request #975 from mailpoet/eslint-fix-rules-rad
Bunch of fixed eslint rules
2017-07-05 14:20:24 +03:00
9cb08734a3 Return back function which is needed
[MAILPOET-985]
2017-07-05 11:36:39 +01:00
55fda047f6 Fix prefer-const eslint rule
[MAILPOET-985]
2017-07-05 09:55:00 +01:00
4706c5fc25 Fix prefer-arrow-callback eslint rule
[MAILPOET-985]
2017-07-05 09:48:35 +01:00
3ba857d03f Fix no-unused-vars eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
f39cbe6b55 Fix space-before-function-paren eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
6dc1bf4e95 Fix semi eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
5b65a8f0ac Fix radix eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
583adf86da Fix no-plusplus eslint rule
[MAILPOET-985]
2017-07-05 09:46:08 +01:00
3b4a1b686d Make build find command work cross-platform (Linux/Mac/Win) [MAILPOET-984] 2017-07-05 08:33:43 +01:00
5390dd8421 Merge branch 'master' of github.com:mailpoet/mailpoet 2017-07-04 18:15:16 +03:00
fe8452711f Bump up release version to 3.0.0-beta.36.2.0 2017-07-04 18:14:04 +03:00
f7cb53de2c Fix composer.lock (remove AspectMock from prod dependencies) 2017-07-04 18:13:17 +03:00
1ac6dd8ccb Switch to using CircleCI 2.0
We end up using Docker images for PHP 5.6 and MySQL, handle our own
Composer and NPM caches.
2017-07-04 15:56:02 +01:00
b1425198b6 Merge pull request #967 from mailpoet/subscription_form_unit_test
Adds test for subscription form submission class [MAILPOET-961]
2017-07-04 14:46:26 +03:00
00a45f3214 Make Codeception report all errors (incl. E_STRICT and E_DEPRECATED) when running tests [MAILPOET-961] 2017-07-04 14:30:15 +03:00
8e46451337 Fixes non static methods called statically (good practice + PHP 7
compatibility)
2017-07-03 19:39:11 -04:00
b0d0cc09c8 Programmatically sets input width after Select2 initialization
[MAILPOET-973]
2017-07-03 17:05:06 +01:00
af559a6fac Merge pull request #971 from mailpoet/tests_qa
Add a copy of MailPoet PHP coding standard for tests [MAILPOET-981]
2017-07-03 18:03:43 +03:00
67acceb968 Merge pull request #965 from mailpoet/posts_dynamic_loading
Add dynamic post loading in Posts widget settings [MAILPOET-971]
2017-07-03 17:18:12 +03:00
703ee7ff71 Fix line breaks in tests [MAILPOET-981] 2017-07-03 16:29:21 +03:00
29491dfd3e Fix active coding standard rule violations in tests [MAILPOET-981] 2017-07-03 16:20:44 +03:00
a49a230983 Disable a few failing rules for further fixing [MAILPOET-981] 2017-07-03 14:57:48 +03:00
b8d285a1d9 Add a copy of MailPoet PHP coding standard for tests [MAILPOET-981] 2017-07-03 14:54:04 +03:00
d5227a9f2c Add tests for PDO exceptions rethrowing [MAILPOET-966] 2017-07-03 10:59:38 +01:00
693117eb40 Merge pull request #964 from mailpoet/add-help
Add help [MAILPOET-949]
2017-07-03 12:06:51 +03:00
f3aab6095e Standardizes helper string look (https://goo.gl/r3jqJ8) 2017-07-03 09:35:01 +01:00
d70587a550 Adds helper text to the bottom of forms view 2017-07-03 09:35:01 +01:00
82226f2c36 Updates existing code that replaces links with the new "replaceLink"
filter
2017-07-03 09:35:01 +01:00
2774101380 Adds Twig filter to replace [link] tags inside strings
Adds unit tests
2017-07-03 09:35:01 +01:00
ee0e3ff95e Move <h1> from react
Wordpress moves admin notices bellow the first heading. If we re-render
our heading inside react router we remove those notices. This way the
admin notice stays in the page.

[MAILPOET-949]
2017-07-03 09:08:30 +01:00
17584dde43 Fix help rendering
[MAILPOET-949]
2017-07-03 08:39:02 +01:00
3ac416de56 Allows passing request data to form submission class
Adds unit tests
2017-07-01 11:17:04 -04:00
1e4c00169f Adds AspectMock library to mock static methods/etc. 2017-07-01 11:15:15 -04:00
11bbf54aad Merge pull request #963 from mailpoet/new-templates
New templates [MAILPOET-922]
2017-06-30 11:37:29 -04:00
688e78560e Add minor corrections [MAILPOET-949] 2017-06-30 12:47:47 +03:00
5ca577a718 Fix lint errors [MAILPOET-966] 2017-06-30 09:29:48 +01:00
605df7dff1 Don't open a second (actually, third) DB connection [MAILPOET-966] 2017-06-30 09:29:48 +01:00
fb1a3a80ff Catch a DB connection exception earlier [MAILPOET-966] 2017-06-30 09:29:48 +01:00
d3db755489 Handle exceptions during initialization better [MAILPOET-966] 2017-06-30 09:29:48 +01:00
a4282b6a3e Don't leak connection data in PDO exceptions [MAILPOET-966] 2017-06-30 09:29:48 +01:00
bc17984030 Fix coffee shop template
[MAILPOET-922]
2017-06-30 09:19:26 +01:00
b823991867 Fix lint errors [MAILPOET-971] 2017-06-29 16:45:12 +03:00
04e238634d Add unit tests [MAILPOET-971] 2017-06-29 15:34:26 +03:00
eba482cc67 Add dynamic post loading in Posts widget settings [MAILPOET-971] 2017-06-29 13:13:36 +03:00
e6663f0f3e Hide a horizontal scrollbar in Posts widget listing [MAILPOET-971] 2017-06-29 13:13:23 +03:00
7fa94a67c9 Add data to system info help page
[MAILPOET-949]
2017-06-29 09:34:28 +01:00
b4be9e1d28 Add notice to system info
[MAILPOET-949]
2017-06-28 16:50:31 +01:00
a7504136a2 Add knowledge base page content
[MAILPOET-949]
2017-06-28 15:36:51 +01:00
43fa12ec08 Add help without any content
[MAILPOET-949]
2017-06-28 15:00:53 +01:00
6723a563ed Add yoga studio thumb
[MAILPOET-922]
2017-06-28 11:27:33 +01:00
15e3e93c99 Add Foodbox thumb
[MAILPOET-922]
2017-06-28 10:57:16 +01:00
cbce789ac8 Add thumbnails to templates
[MAILPOET-922]
2017-06-28 10:57:16 +01:00
97607993fb Add newsletter assets to plugin folder
[MAILPOET-922]
2017-06-28 10:57:16 +01:00
334b119bb3 Add News Day template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
ed0b2e6231 Add Coffee Shop template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
ad7ad05ec7 Add Travel Nomads template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
bfba6d459c Add Faith template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
344eecbf11 Add Chocolate Store template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
14dc022d2d Add Science Weekly template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
eff75cce94 Add Piece Of Cake template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
51084fc57d Add Piece Of Cake template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
298270f86c Add Festival Event template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
fe6d5d1523 Add Take a Hike template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
3cceb32ec1 Add Kick-Off template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
c8251a3bcd Add Discount template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
58888505b5 Add Food box template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
558c493dd7 Add World Cup template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
8cdb7d77f6 Add App Welcome template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
539e6d3ee1 Add Burger Joint template
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
c9a0939ddd Remove old templates
[MAILPOET-922]
2017-06-28 10:57:15 +01:00
2add301b9f Bumps up version to 3.0.0-beta.36.1.0 and updates changelog 2017-06-27 16:23:22 -04:00
2d217e416a Merge pull request #960 from mailpoet/fix-build-find
Make build script work on Mac os [MAILPOET-969]
2017-06-27 12:21:25 +03:00
77e0ace951 Merge pull request #961 from mailpoet/post_notification_unit_test_update
Updates the way we test for next run date in newsletter scheduler [MAILPOET-967]
2017-06-27 11:42:30 +03:00
933749f8f0 Merge pull request #941 from mailpoet/mp2tomp3migration
Mp2tomp3migration phase 2
2017-06-26 18:47:58 +03:00
7b13babf3f Merge pull request #958 from mailpoet/improve-ajax-errors
Improve ajax errors [MAILPOET-929]
2017-06-26 17:31:15 +03:00
8c673f78d7 Make build script work on Mac os
[MAILPOET-969]
2017-06-26 15:00:48 +01:00
2285c08c01 Revert "Revert "Improve ajax errors [MAILPOET-929]""
This reverts commit 81c3e2facf.
2017-06-26 13:51:53 +01:00
836b7179e9 Improve ajax errors on form submission
[MAILPOET-929]
2017-06-26 13:43:32 +01:00
f89a728c38 Uses predetermined timestamps from which next run date is calculated 2017-06-24 15:59:28 -04:00
58f2c32362 Allows passing custom time value to calculate the next run date from 2017-06-24 15:59:17 -04:00
99c4fc71c4 Release 3.0.0-beta.36.0.1 2017-06-23 15:48:04 +03:00
ce456af962 Merge pull request #957 from mailpoet/forms_assets_fix
Updates subscription forms to use assets manifest [MAILPOET-964]
2017-06-23 14:59:51 +03:00
faa035a7c3 Updates subscription forms to use assets manifest
Adds unit tests
2017-06-22 20:42:04 -04:00
9066be6544 Merge pull request #956 from mailpoet/revert-955-improve-ajax-errors
Revert "Improve ajax errors [MAILPOET-929]"
2017-06-22 20:35:42 -04:00
81c3e2facf Revert "Improve ajax errors [MAILPOET-929]" 2017-06-22 20:04:10 -04:00
3fafe2bd6a Merge pull request #955 from mailpoet/improve-ajax-errors
Improve ajax errors [MAILPOET-929]
2017-06-22 21:37:47 +03:00
2f6fe8c804 Translate error message
[MAILPOET-929]
2017-06-22 16:47:36 +01:00
ec3e88e658 Improve error handling for failed AJAX requests
[MAILPOET-929]
2017-06-22 13:35:55 +01:00
b346a8846a Merge pull request #949 from mailpoet/shortcodes-subject-archive
Print rendered subject in archive [MAILPOET-826]
2017-06-22 07:41:11 -04:00
6ff3895940 Add missing this for stubs expecting a number of method calls to work 2017-06-22 11:55:15 +01:00
0e5401dd68 Fix keys being invalidated after saving settings [MAILPOET-963] 2017-06-22 11:55:15 +01:00
ea059ac0e4 Merge pull request #954 from mailpoet/android-gmail-fix
Hide preheader in gmail on android [MAILPOET-802]
2017-06-22 12:50:42 +03:00
f874ae6ca8 Fix line endings
[MAILPOET-929]
2017-06-22 10:25:07 +01:00
5bd6c6533a Fixed: Skip button is vanishinig by itself in seconds 2017-06-21 19:24:07 +02:00
f539860922 Remove trailing spaces 2017-06-21 19:24:06 +02:00
536267c8f5 Fixed: Form fields were not inactive when used 2017-06-21 19:24:05 +02:00
5b905a60e8 Fixed: Unit test testReplaceMP2Shortcodes 2017-06-21 19:24:05 +02:00
5e152ebaa1 Fixed: Wrong date format on front-end 2017-06-21 19:24:04 +02:00
2c35c7061e Fixed: Display "Invalid e-mail" on the front-end whereas the e-mail is correct 2017-06-21 19:24:03 +02:00
2515dcf4ce Fixed: Wrong HTML encoding of the text fields 2017-06-21 19:24:02 +02:00
9458bf7418 Add Unit tests for MP2 to MP3 migration phase 2 2017-06-21 19:24:01 +02:00
44bf4b98b8 MP2 to MP3 migration phase 2: Forms migration 2017-06-21 19:24:01 +02:00
ff46784adf Merge pull request #952 from mailpoet/assets_manifest
Uses cross-env to set environment variables [MAILPOET-919]
2017-06-21 19:31:20 +03:00
088d4f800d Hide preheader in gmail on android
[MAILPOET-802]
2017-06-21 17:23:57 +01:00
bb73e257db Uses cross-env to set environment variables
Adds manifests to .gitingore
2017-06-21 10:28:40 -04:00
53fd3fe07c Update lstings to display rendered subject line
[MAILPOET-826]
2017-06-21 13:43:16 +01:00
27a734d005 Not include sensitive data in rendered subject
[MAILPOET-826]
2017-06-21 10:25:40 +01:00
fb059f585e Print rendered subject in archive
[MAILPOET-826]
2017-06-21 10:25:40 +01:00
c99f990dbf Fixes undefined index notice when analytics option is not present in the
DB
2017-06-21 08:31:37 +01:00
5600d8087f Release 3.0.0-beta.36.0.0 2017-06-20 17:54:47 +01:00
cac3c30833 Merge pull request #938 from mailpoet/cache_busting_update
Prevent using outdated JS assets in new releases [MAILPOET-919]
2017-06-20 19:35:33 +03:00
454a00c3cb Merge pull request #950 from mailpoet/no-ms-iis
Prevent mailpoet from activating on IIS [MAILPOET-920]
2017-06-20 12:19:12 -04:00
156d05b4cf Corrects identation and rebases master 2017-06-20 11:36:08 -04:00
be63aa727d Adds return statement to CSS compilication task
Swaps is_file with is_readable when checking for manifest's existance
2017-06-20 11:33:56 -04:00
6c270ab095 Reads and stores assets manifest inside Renderer class
Updates unit tests
2017-06-20 11:33:56 -04:00
bb7c9d2667 Adds assets manifest to the list of ignored files 2017-06-20 11:33:56 -04:00
570b9dfb8f Updates coding style 2017-06-20 11:33:07 -04:00
5f9baed195 Modifies Assets class to use manifest file
Adds unit tests
2017-06-20 11:33:07 -04:00
45e6e0e9e6 Adds manifest file to style assets 2017-06-20 11:33:07 -04:00
08395d4365 Adds support for environment flag to asset compilation
Uses production environment when building release
2017-06-20 11:33:07 -04:00
167fb86927 Adds manifest to JS assets
Adds conditional assets caching based on environment
2017-06-20 11:33:06 -04:00
64dbf158a4 Merge pull request #948 from mailpoet/subscription_fix
Corrects reference to outdated API Response class [MAILPOET-959]
2017-06-20 17:43:56 +03:00
89e57479cc Make sure variable is set
[MAILPOET-920]
2017-06-20 15:41:27 +01:00
fccd7f4054 Prevent mailpoet from activating on IIS
[MAILPOET-920]
2017-06-20 11:43:56 +01:00
1d6cdfe2cc Merge pull request #945 from mailpoet/newsletter_preview_email_update
Adds bogus list-unsubscribe link & disables view in browser link in preview emails [MAILPOET-948] [MAILPOET-875]
2017-06-20 12:22:25 +03:00
812c6634ba Mixpanel analytics [MAILPOET-686] (#940)
* Send analytics data to mixpanel

[MAILPOET-686]
2017-06-20 12:20:50 +03:00
419871a6bf Corrects reference to outdated API Response class
Removes unused class reference
2017-06-19 18:56:54 -04:00
823cd3cd07 Make MailPoet pages independent of the WP About page styles
[MAILPOET-951]
2017-06-19 17:38:33 +01:00
24fa8c1d79 Merge pull request #947 from mailpoet/add-eslint
Add eslint [MAILPOET-938]
2017-06-19 19:19:01 +03:00
868f860e26 Use airbnb linter package
Instead of the deprecated package `airbnb-base`
And also fix the new errors
[MAILPOET-938]
2017-06-19 17:00:33 +01:00
cbcd9fb22f Adds support for list-unsubscribe header to the MSS method
[MAILPOET-956]
2017-06-19 16:19:31 +01:00
eeb2fcb2e3 Treat warnings as errors
[MAILPOET-938]
2017-06-19 16:17:09 +01:00
dde0b16cca Update robo file
[MAILPOET-938]
2017-06-19 16:09:59 +01:00
01eaf1ef2d Merge pull request #937 from mailpoet/premium_updates_fix
Add updater to the Free [PREMIUM-22]
2017-06-19 11:09:42 -04:00
43c2908a2b Adds unit test for "preview" mode, which replaces system links with
hashes in preview emails
2017-06-19 11:04:51 -04:00
6f64b9faa2 Add eslint
[MAILPOET-938]
2017-06-19 14:30:09 +01:00
8d496e7aa3 Adds unit test for fake unsubscribe url 2017-06-19 09:08:19 -04:00
25485a366a Merge pull request #942 from mailpoet/scheduler_fix
Fixes sending of scheduled newsletters [MAILPOET-955]
2017-06-19 13:12:38 +03:00
8aabc783e3 Merge pull request #943 from mailpoet/remove-napa
Remove napa [MAILPOET-958]
2017-06-19 12:15:22 +03:00
f72ceb2dbe Disables "view in browser", "manage subscription" and "unsubscribe"
links in preview emails
2017-06-16 18:14:30 -04:00
af3d971154 Uses "fake" unsubscribe URL to set the "list-unsubscribe" header 2017-06-16 18:10:39 -04:00
2d3aae9dd0 Fixes queue not being processed for scheduled newsletters
Sets newsletter status to "sending" when queue was processed
Uses defined constants where appropriate
2017-06-15 19:11:35 -04:00
11af014d9d Remove napa
It caused errors while running npm commands
I found alternatives to those two packages
which were used with napa
2017-06-15 18:11:05 +01:00
6653327f7f Merge pull request #939 from mailpoet/key_check_refactoring
Separate checking and storing of the keys [MAILPOET-939]
2017-06-15 15:47:38 +03:00
3a2adde6c5 Merge pull request #934 from mailpoet/network-activation-notice
Show a notice to admin if network activation
2017-06-15 14:31:57 +03:00
1aba3112e9 Separate checking and storing of the keys [MAILPOET-939] 2017-06-15 13:00:56 +03:00
181d7b9876 Add indexes to stat tables to speed up counting unopened subscribers [PREMIUM-21] 2017-06-14 14:59:20 +01:00
ebe3f0bd00 Rename variables by coding standards
[MAILPOET-923]
2017-06-14 14:35:33 +01:00
1b69fe50ff Rename function flushAll
[MAILPOET-923]
2017-06-14 14:33:21 +01:00
fa3ba609f3 Show a notice to admin if network activation
Mailpoet seems to be working on network activated multisites but we
don't support it.
[MAILPOET-923]
2017-06-14 14:30:20 +01:00
fe9aa03de1 Add updater to the Free [PREMIUM-22] 2017-06-14 14:24:18 +03:00
cdd8e51ef9 Merge pull request #936 from mailpoet/premium_blockage_fix
Allow to access Settings after exceeding the 2000 limit [MAILPOET-950]
2017-06-14 12:33:51 +03:00
1da06bd46a Allow to access Settings after exceeding the 2000 limit [MAILPOET-950] 2017-06-14 10:47:34 +03:00
32b24a83ab Suggest to activate MSS after a successful key check [MAILPOET-937] 2017-06-13 16:01:47 +01:00
5293589eea Release 3.0.0-beta.35.0.0 2017-06-13 17:05:50 +03:00
07f4c0bc1e Merge pull request #923 from mailpoet/fast_svn_publish
Use a faster SVN workflow in release publishing automation [MAILPOET-913]
2017-06-13 07:50:47 -04:00
7d9a072545 Merge pull request #928 from mailpoet/wp_mail_switch
Replaces SwiftMailer's mail() method with WP's PHPMailer [MAILPOET-926]
2017-06-13 14:10:07 +03:00
67ffcb5485 Clean up the SVN dir before checking out for faster operation [MAILPOET-913] 2017-06-13 12:22:32 +03:00
02098a3cf0 Merge pull request #888 from mailpoet/mp2tomp3migration
MP2 to MP3 migration [MAILPOET-946]
2017-06-12 19:41:46 +03:00
eab8065154 Remove the promise of migrating newsletters and statistics.
We will not be offering these options!
2017-06-12 17:36:04 +03:00
fb8ecef1c3 Merge pull request #932 from mailpoet/revert-931-minimum_php_version_fix
Revert "Updates minimum required "release version" from 3 to 30 [MAILPOET-944]"
2017-06-12 10:27:09 -04:00
14955e5022 Revert "Updates minimum required "release version" from 3 to 30"
This reverts commit e83c0ff0bd.
2017-06-12 10:20:01 -04:00
e83c0ff0bd Updates minimum required "release version" from 3 to 30 2017-06-12 14:52:44 +01:00
09db1aac22 Merge pull request #929 from mailpoet/mp_api_fix
Fixes confirmation emails not sent when adding subscriber in MP API [MAILPOET-942]
2017-06-12 14:22:05 +03:00
5fa1eb643a Remove tests that may fail because of the original database content 2017-06-12 13:13:17 +02:00
898913a517 Change: the users belonging to the wp_users segment must be imported as subscribed to the wp_users segment 2017-06-12 13:13:17 +02:00
3a4b364bcd Modify unit tests following previous changes 2017-06-12 13:13:16 +02:00
d373d10f6c Fixed: Segments with a null description were not imported 2017-06-12 13:13:15 +02:00
c07e1eff3c New: Redirect the user to the migration page if the import has started but is not finished to prevent him from using MailPoet with incomplete data. So he can resume the import where it left off. 2017-06-12 13:13:15 +02:00
f5e985baa4 Change: Move the progress bar JSON file to the wp-content/uploads/mailpoet directory and remove "mailpoet" from its filename 2017-06-12 13:13:14 +02:00
2c93a105cc Fixed: Migration script suggests displays "Upgrade completed!" text and allows the user to click the "Go to MailPoet" button while the migration is still being performed. 2017-06-12 13:13:14 +02:00
cbf0a7684f Fixed: If there are WP users that haven't been added as MP2 WP subscribers, they won't be present as MP3 subscribers after migration either. 2017-06-12 13:13:13 +02:00
2632feba31 Fixed: On Migration page, clicking on "Start upgrade No thanks, I'll skip and start from scratch." redirects to the same migration page. 2017-06-12 13:13:12 +02:00
c96bc755c7 Fixed: the progress bar goes over 100% and displays "NaN%" if the MP2 tables are empty. 2017-06-12 13:13:12 +02:00
bbad772d7a PHP Notice: Use of undefined constant IMPORT_TIMEOUT_IN_SECONDS 2017-06-12 13:13:11 +02:00
520a3c43be Changes for API 2017-06-12 13:13:10 +02:00
eb70df1466 The "Start upgrade" button can now resume the import. 2017-06-12 13:13:09 +02:00
5977b8b4bc Translations 2017-06-12 13:13:09 +02:00
2bb7d95e37 Remove the "Start upgrade" button when the upgrade is completed
Add the "Upgrade completed" message
Add the "Go to MailPoet" button
2017-06-12 13:13:08 +02:00
c64959dce0 Fixed: Segments number can be wrong 2017-06-12 13:13:08 +02:00
ead0792b32 Rename getDataToMigrate() to getDataToMigrateAndResetProgressBar() 2017-06-12 13:13:07 +02:00
7bd52d456c Make the Migration page accessible through admin.php?page=mailpoet-migration 2017-06-12 13:13:06 +02:00
8517896660 Rename imported_data_mapping table to mapping_to_external_entities 2017-06-12 13:13:06 +02:00
c78933f7c4 Numerous fixes following the code review:
H1 title should be "Welcome to MailPoet version 3!", as per wireframes
remove logo in top right of the page
remove the classes feature-section one-col to the parent
new styles for #logger
progress bar: ensure that the color and size for the font are the same as our current progress bar.
display the progress bar at 100% when the import of subscribers and lists is complete
move loadSQL() function in tests/_support/Helper/Database.php
remove CSS browser prefixes
use AMD module definition to embed the JavaScript
remove extra whitespace in JavaScript file
remove the redundant functions descriptions
rename objectPlugin to mailpoet_mp2_migrator
replace private $chunks_size by the constant CHUNK_SIZE
add the constant IMPORT_TIMEOUT_IN_SECONDS
replace Helpers::mysqlDate() by $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)
make the log messages translatable
fix PHPDoc
replace Env::$plugin_name by 'mailpoet' in the translation functions
use $snake_case for variable names, use lowercaseCamelCase for method names, use CamelCase for class names
define MP2 table names as constants
add spaces around ternary operators
use the models for MP3 entities, instead of counting via raw SQL queries
use \ORM::for_table('some_table')::count()
2017-06-12 13:13:05 +02:00
707d5efec1 Modify H1 title
Remove logo
Modify progress label font size
Modify progress bar style
2017-06-12 13:13:04 +02:00
acfb3aefba Use snake_case 2017-06-12 13:13:04 +02:00
cb6f4046a2 Removes utf8 decoding that breaks import 2017-06-12 13:13:03 +02:00
667aa91581 Adds character encoding 2017-06-12 13:13:02 +02:00
b7f7dc6728 Add unit tests for the MP2Migrator class
Add the method "invokeMethod" in the MailPoetTest class to be able to test private and protected methods
Move the "rowsCount" method in the Helpers class for reusability
Add the method "loadSQL" in the Helpers class
Store the mapping between MP2 user ID and MP3 subscriber ID
2017-06-12 13:13:02 +02:00
ad1e8feb23 QA fixes 2017-06-12 13:13:01 +02:00
486b382c88 Use API versioning 2017-06-12 13:13:00 +02:00
472be3b071 Import the MP2 users lists
Import the MP2 users
Add the table "imported_data_mapping"
Add the function Helpers::mysql_date
2017-06-12 13:13:00 +02:00
37a9fd9e1b Use Setting::getValue() and Setting::setValue() instead of get_option() and update_option() 2017-06-12 13:12:59 +02:00
c984ac7a66 New: Display the number of data to migrate
Several fixes following the code review:
- For styles we use Stylus. (http://stylus-lang.com/)
  In Stylus styles curly brackets and terminating semicolons are unnecessary. Indentation is sufficient to denote blocks of styles
  You can also use variables to avoid duplication (e.g. mentioning the same color multiple times)
  Vendor prefixes are not necessary. We use the Nib mixin for Stylus to handle those (http://tj.github.io/nib/)
  => DONE

- `admin.js` bundle is included on all admin pages, so we need to ensure that migration fires only on the migration page and nowhere else.
  You can even create a separate bundle only for migration files, as they won't be necessary on other admin pages.
  => DONE

- MP2MigratorAPI => MP2Migrator endpoint would be just as fine
  => DONE

- For storing migration files, you can use the `Env::$temp_path` path
  => DONE

- `proposeMigration()` the method name disagrees with the comment.
  Comment suggests it tests if migration can be or should be performed
  Method name suggests that it proposes doing the migration (to the user?) - not very clear
  => DONE: the new name is isMigrationNeeded()

  And not only does it test, it may also update the `mailpoet_migration_complete` option, which is confusing and is an unexpected side-effect.
  => DONE

  The migration class itself `MP2Migrator` shouldn't even care about _GET, _REQUEST or _POST arguments. It should only work with what is passed to it.
  => DONE

- In views, please make sure all human-friendly texts use WP's gettext functions for translations (e.g. __('text'))
=> DONE
2017-06-12 13:12:58 +02:00
a1ea56f505 Coding standard fixes 2017-06-12 13:12:58 +02:00
dd7f959731 Migration from Mailpoet 2 to Mailpoet 3 : phase 0
Interface
2017-06-12 13:12:57 +02:00
09f4f2e78a Fix a unit test [MAILPOET-942] 2017-06-12 11:38:01 +03:00
502250a1a3 Merge pull request #930 from mailpoet/import_update
Adjusts WP's email validation regex to JS's syntax [MAILPOET-943]
2017-06-12 11:10:38 +03:00
48e37f6797 Adjusts WP's email validation regex syntax to JS 2017-06-11 12:31:54 -04:00
420058a86d Optimizes tests
Adds new tests
2017-06-11 12:18:14 -04:00
6c777ca074 Reloads subscriber or else status and other fields populated at save
time are not returned
Schedules welcome notifications only for subscribers with "subscribed"
status
Extracts confirmation email sending/welcome notification scheduling into
separate functions for easy testing
2017-06-11 12:16:34 -04:00
a481debb77 Adds "html" body to the mailing method test message 2017-06-09 09:53:07 -04:00
c91f8ccc7a Replaces SwiftMailer's mail() method with WP's PHPMailer 2017-06-09 09:52:22 -04:00
6a2b5e28c2 Merge pull request #927 from mailpoet/fix-alc-buttons-settings
Fix ALC buttons settings when updating all buttons [MAILPOET-896]
2017-06-09 09:35:36 +03:00
bc51b6efc8 Add replaceAllButtonStyles test to button
[MAILPOET-896]
2017-06-09 07:24:26 +01:00
152edda03f Add unit test
[MAILPOET-896]
2017-06-08 16:58:30 +01:00
5029b73027 Merge pull request #924 from mailpoet/model_validator_fix
Fixes reference to $this in anonymous function on PHP 5.3 [MAILPOET-934]
2017-06-08 17:13:04 +03:00
35c25d3337 Updates length to support IPv4 mapped IPv6 addresses 2017-06-08 13:45:50 +01:00
ae25e95d51 Updates length of columns with IP addresses to 39 2017-06-08 13:45:50 +01:00
5b8d0c63a1 Fix a changelog link [MAILPOET-935] 2017-06-08 10:47:30 +01:00
94fb8c6096 Merge pull request #918 from mailpoet/beacon_update_with_premium_version
UTF-8 encodes beacon data
2017-06-08 12:38:18 +03:00
93ef1d0197 Fix ALC buttons settings when updating all buttons
[MAILPOET-896]
2017-06-08 10:19:24 +01:00
501d00b0cc Merge pull request #919 from mailpoet/fix-composer-cp-mac
Remove -d option from composer.json script
2017-06-08 12:15:18 +03:00
9af3dc1f9d Fixes reference to $this in anonymous function on PHP 5.3 2017-06-07 11:52:46 -04:00
3705ed7da0 Use a faster SVN workflow in release publishing automation [MAILPOET-913] 2017-06-07 17:46:10 +03:00
5b99e66d8d Merge pull request #922 from mailpoet/fix-tests-problems
Fix tests problems
2017-06-07 10:34:20 -04:00
cbedd5ff40 Bump up release version to 3.0.0-beta.34.0.0 2017-06-07 16:14:47 +03:00
bfcd6f10fc Merge pull request #921 from mailpoet/premium_launch
Add Premium features for the launch, Free-side
2017-06-07 14:54:21 +03:00
79362e9955 Update 'buy Premium only' link URL on the Premium page 2017-06-07 14:42:55 +03:00
fcf272b44a Fix lint error 2017-06-07 11:49:07 +01:00
b80683a9a1 Fix unit tests for PHPUnit v6
Codeception from version 2.3 up comes with PHPUnit v6 which changed
__construct behaviour. Our tests have to call parent __constructor in
order to work. The error was:
[PHPUnit\Framework\Exception] array_merge(): Argument #1 is not an array
2017-06-07 11:32:33 +01:00
5349f3a59a Use ssl while checking url
My internet provider tried to be helpfull and offeres content on
invalid urls. That is only behaviour for http:// scheme and not
for https:// scheme so I need to enforce ssl so that invalid url
fails even in my house.
2017-06-07 11:30:31 +01:00
95072a9ac5 Restore empty license key warnings [MAILPOET-933] 2017-06-07 12:39:45 +03:00
8c372b0909 Update support video, add a border to videos [MAILPOET-885] 2017-06-07 12:39:45 +03:00
580dd38b3a Rename methods for consistency and better readability [PREMIUM-9] 2017-06-07 12:39:44 +03:00
47d4e98aae Integrate installer with the Release API [PREMIUM-9] 2017-06-07 12:39:43 +03:00
7ebb7bac17 Add Premium installation/activation UI [PREMIUM-9] 2017-06-07 12:39:43 +03:00
6cbce2fc97 Replace Premium gif assets with mp4 videos [MAILPOET-885] 2017-06-07 12:39:41 +03:00
e8a950f32c Add Premium page images to plugin repository assets [MAILPOET-885] 2017-06-07 12:39:12 +03:00
4f722ecd8a Add hooks for actions row for standard and notification history newsletter types [PREMIUM-13] 2017-06-07 12:30:50 +03:00
478359f9ff Add minor improvements to stats [PREMIUM-13] 2017-06-07 12:30:48 +03:00
a1720a5cf1 Use videos instead of gifs on the Premium page [MAILPOET-885] 2017-06-07 12:30:47 +03:00
3f0ef3ded7 Replace Premium gif assets with mp4 videos [MAILPOET-885] 2017-06-07 12:30:47 +03:00
dcb25c1a6b Swap welcome emails and support images [MAILPOET-885] 2017-06-07 12:30:45 +03:00
c5dd575324 Add Premium page images to plugin repository assets [MAILPOET-885] 2017-06-07 12:30:45 +03:00
6eca26a4e2 Add UTM params to Premium page links [MAILPOET-885] 2017-06-07 12:30:43 +03:00
e10fa065bd Add Premium page [MAILPOET-885] 2017-06-07 12:30:42 +03:00
49673fabbd Does not display logo when MSS is active or preview is enabled
Adds additional unit tests and optimizes existing
2017-06-07 12:30:42 +03:00
1c1a210542 Adds MailPoet logo to newsletters in the free version 2017-06-07 12:30:41 +03:00
30277d92cd Updates action button 2017-06-07 12:30:41 +03:00
fb940065ea Fix a source value for Premium key in the worker [MAILPOET-890] 2017-06-07 12:30:40 +03:00
afa06342a5 Update link for the Premium page 2017-06-07 12:30:40 +03:00
03d2ff5f26 Make use of returned promises for parallel requests loading modal [MAILPOET-890] 2017-06-07 12:30:40 +03:00
ec71dff40d Change strings on the 2000 limit page for Premium [MAILPOET-888] 2017-06-07 12:30:39 +03:00
58faf64a5c Add a Premium page link to bounced subscribers listing [MAILPOET-887] 2017-06-07 12:30:39 +03:00
65ff14a81d Adds hook to modify newsletter actions 2017-06-07 12:30:38 +03:00
f7efe44f09 Fix ServicesChecker behavior, make MSS check stricter [MAILPOET-890] 2017-06-07 12:30:38 +03:00
cf22e81ae1 Updates exposed components 2017-06-07 12:30:37 +03:00
7aa0f21d11 Rework 'Send with...' tab UI, make a single license key field [MAILPOET-890] 2017-06-07 12:30:37 +03:00
2e31e3d37c Exposes components required for welcome notifications 2017-06-07 12:30:36 +03:00
3e988b7a56 Removes welcome notification creation component and routes
Updates welcome notification template
2017-06-07 12:30:36 +03:00
ce3eb06924 Prevents welcome emails from being created
Adds filter to modify available email types
2017-06-07 12:30:35 +03:00
a37ff8d398 Limits utf8 encoding to php_uname function only (Windows may use an
encoding other than ISO-8859-1 in non-English locales)
2017-06-06 18:16:04 -04:00
d0bdb1a47b Adds XML polyfill for utf8_encode/decode functions 2017-06-06 15:06:39 -04:00
855f2a55d4 Remove -d option from composer.json script
This flag is not present on mac and it makes this script
fail on MacOS.
2017-06-06 15:49:51 +01:00
fc7ec9bded UTF-8 encodes beacon data 2017-06-06 10:10:07 -04:00
028de860a2 Merge pull request #913 from mailpoet/sending_queue_update_on_newsletter_status_change
Prevents processing of sending queues when newsletter is paused [MAILPOET-900]
2017-06-06 15:00:21 +03:00
5af91d028d Merge pull request #916 from mailpoet/unsubscribe_fix
Fixes incorrect dependency that breaks unsubscribe link [MAILPOET-931]
2017-06-06 13:12:16 +03:00
a4bcf870bb Merge pull request #915 from mailpoet/beacon_update_with_premium_version
Adds premium version information to HS beacon [MAILPOET-930]
2017-06-06 09:49:09 +03:00
e06f2f5f0b References the correct class and removes unused dependency 2017-06-05 15:20:14 -04:00
c101645d93 Adds premium version information to HS beacon 2017-06-05 14:59:30 -04:00
b8904c2d51 Merge pull request #914 from mailpoet/subscriber_email_validation_logic_update
Uses WP's function to validate subscriber email address [MAILPOET-925]
2017-06-05 19:25:53 +03:00
099db4e1c8 Removes unused variable
Fixes typo in error message
2017-06-05 11:59:13 -04:00
cdf36ccb20 Trashes/restores/deletes (+ same bulk actions) children newsletters and
associations as per discussion on Slack:
https://mailpoet.slack.com/archives/C02MTKAJL/p1496427873491785
2017-06-05 11:36:04 -04:00
79b6ab1d15 Finishes incomplete test 2017-06-05 10:30:45 -04:00
95114774da Merge pull request #912 from mailpoet/sending_queue_and_post_notification_history_listing_update
Sending queue and post notification history listing update [MAILPOET-928]
2017-06-05 14:53:22 +03:00
7f566fb672 Adds client-side check for invalid characters in email addresses
Adds server-side validation of email addresses using WP's is_email()
2017-06-04 18:48:11 -04:00
d27968a215 Uses WP's is_email() to validate email addresses in Subscriber model 2017-06-04 18:19:37 -04:00
344990d59e Only processes queues when newsletter exists and is active/sending 2017-06-02 12:03:34 -04:00
ea831ef160 Prevents processing scheduled sending queues for inactive newsletters 2017-06-01 19:50:25 -04:00
8314b05fce Displays "not yet sent" as a sent date when post notification has not
yet been sent/being sent
2017-06-01 14:58:25 -04:00
fd33cc7068 Uses parent newsletter's subject when queue's rendered subject is not
available
2017-06-01 14:26:50 -04:00
92e4cc6a24 Sorts sending queue according to their creation date (oldest to newest) 2017-06-01 13:21:03 -04:00
dd4bebb570 Bumps up release version to Beta 33.1 and updates changelog 2017-05-30 14:27:29 -04:00
99aed2cb01 Merge pull request #908 from mailpoet/utf8_fix
Apply charset and collation only if they are specified [MAILPOET-924]
2017-05-30 14:25:33 -04:00
92616063ec Fix unit test for generating DB source name 2017-05-30 21:18:45 +03:00
c56b56f4aa Apply charset and collation only if they are specified 2017-05-30 21:05:01 +03:00
33d6533c64 Bumps up release version to Beta 33 and updates changelog 2017-05-30 09:30:12 -04:00
55d7a0dd01 Merge pull request #907 from mailpoet/welcome_email_scheduler_update
Schedules welcome notifications after subscription is confirmed [MAILPOET-907]
2017-05-30 14:02:48 +03:00
8b2ac99eda Merge pull request #903 from mailpoet/beacon_update
Adds server OS, web server information and cron ping response to HS beacon [MAILPOET-918]
2017-05-30 12:15:46 +03:00
dba21c68fd Schedules welcome notification upon subscription when subscription
confirmation is disabled
Schedules welcome notification upon subscription confirmation
Checks when 'REMOTE_ADDR' is not set
Adds unit tests
2017-05-29 22:04:47 -04:00
5b40652737 Merge pull request #906 from mailpoet/form_widget_rendering
Wrap form widget title as configured by the sidebar [MAILPOET-910]
2017-05-29 19:44:11 -04:00
7f0396747d Adds cron ping URL instead of ping response
Checks for existence of SERVER_SOFTWARE variable
2017-05-29 19:29:56 -04:00
e9dfff8e66 Wrap form widget title as configured by the sidebar 2017-05-29 18:12:51 +03:00
040c4da6c3 Merge pull request #904 from mailpoet/php_version_requirement_update
Requires PHP 5.3.3 in line with composer.json requirements [MAILPOET-921]
2017-05-29 13:15:13 +03:00
80a237504d Requires PHP 5.3.3 in line with composer.json requirements 2017-05-26 11:34:31 -04:00
4e2e09ea24 Adds server OS, web server information and cron ping response to HS
beacon
2017-05-25 15:55:33 -04:00
87b9fbdc16 Merge pull request #900 from mailpoet/utf8
Synchronize MailPoet DB connection charset with WordPress [MAILPOET-748]
2017-05-24 16:09:58 +03:00
a071a14eec Update only those queries, for which conversion will yield correct char
lengths
2017-05-24 15:45:12 +03:00
5ae006b10f Update plugin version [MAILPOET-748] 2017-05-24 15:45:12 +03:00
9d21ebd26e Fix a comment. UTF8MB4 is a superset of UTF8 2017-05-24 15:45:12 +03:00
fcff6de3c3 Skip conversion for charset utf8 -> utf8mb4 2017-05-24 15:45:12 +03:00
3d2168856d Fix unit tests for Env charsets and collations 2017-05-24 15:45:12 +03:00
a6eb1b06da Add connection charset sync with WP and convert existing data to it 2017-05-24 15:45:12 +03:00
21d0c3518e Merge pull request #901 from mailpoet/transifix
Bundle translations completed by 75%, remove PO files in build [MAILPOET-916]
2017-05-23 18:40:46 +03:00
3532a3c8e9 Bundle translations completed by 75%, remove PO files in build [MAILPOET-916] 2017-05-23 18:34:00 +03:00
79cba4cace Release 3.0.0-beta.32 2017-05-23 13:27:19 +03:00
a5dee8da12 Merge pull request #897 from mailpoet/third_party_subscription_methods
Enables subscriber email to be passed when subscribing to list(s) [MAILPOET-809]
2017-05-22 14:25:00 +03:00
3783384ea6 Add a test to ensure subscribers can be identified by their email
address via MPAPI
2017-05-22 13:49:01 +03:00
766c0dfcfc Enables subscriber email to be passed when subscribing to list(s)
List subscription methods return array with subscriber data
2017-05-19 09:51:29 -04:00
83e9de8e95 Merge pull request #887 from mailpoet/third_party_subscription_methods
Adds API methods for third-party plugins [MAILPOET-809]
2017-05-17 14:03:19 +03:00
0a512f6349 Uses the first matching namespace endpoint 2017-05-16 23:17:25 -04:00
a4c1095db7 Moves custom field extraction logic from CustomField model to Subscriber
model where it's used
2017-05-16 20:58:44 -04:00
87a6c7100e Uses real object's ID 2017-05-16 20:58:43 -04:00
fc51d5f98c Sends confirmation email and schedules welcome notification by default
Fixes a typo in text string
2017-05-16 20:58:43 -04:00
a1b3aaf1f8 Adds method to create subscriber 2017-05-16 20:58:43 -04:00
3a1bf88c22 Extracts some logic into resuable methods 2017-05-16 20:58:43 -04:00
bd39c34f03 Adds unit tests 2017-05-16 20:56:56 -04:00
73121c2ca5 Adds method to return all segments minus WP Users segment(s) 2017-05-16 20:56:56 -04:00
5e23fa4295 Adds method to subscribe to single or multiple lists 2017-05-16 20:56:56 -04:00
5e34bbf9d5 Adds method to return subscriber fields 2017-05-16 20:56:56 -04:00
cedd94550f Adds unit test for API entry point 2017-05-16 20:56:55 -04:00
8b13889c7a Adds one entry point for both JSON and MP APIs
Removes endpoints folder and moves versions to the root
JSON API folder
2017-05-16 20:56:55 -04:00
3c7ac5488a Adds MP API facade 2017-05-16 20:56:54 -04:00
398d7d3d80 Moves current API under JSON namespace 2017-05-16 20:56:54 -04:00
b727ba423e Fix a typo in readme.txt 2017-05-16 18:08:58 +03:00
45b9550293 Thank people in changelog 2017-05-16 17:56:25 +03:00
d2e520e2fd Add new translations info to the readme.txt 2017-05-16 17:39:30 +03:00
b9c3ae97cd Bump up release version to 3.0.0-beta.31 2017-05-16 17:23:58 +03:00
b90c0b173b Merge pull request #892 from mailpoet/premium_key_warnings_fix
Temporarily hide invalid key warnings when the license key isn't specified [MAILPOET-911]
2017-05-16 12:56:26 +03:00
f498f4df0c Merge pull request #894 from mailpoet/progress_bar_style
Change sending progress bar style [MAILPOET-753]
2017-05-15 22:20:00 -04:00
2f10f89fc5 Change sending progress bar style [MAILPOET-753] 2017-05-15 21:25:40 +03:00
a49f9d9c80 Merge pull request #893 from mailpoet/newsletter_hash_generation_fix
Fixes newsletter link hash generation logic [MAILPOET-912]
2017-05-15 20:45:54 +03:00
e71e23bbb5 Temporarily hide invalid key warnings when the license key isn't specified [MAILPOET-911] 2017-05-15 20:16:29 +03:00
adc86ef247 Increases hash length and random string size 2017-05-15 13:07:18 -04:00
765b2bad21 Merge pull request #891 from mailpoet/form_list_select_fix
Fix non-text form fields not being sent to server [MAILPOET-909]
2017-05-15 09:15:44 -04:00
2354cac719 Updates unit test 2017-05-15 08:55:32 -04:00
7f509f66ff Changes newsletter link hash generation function 2017-05-15 08:36:43 -04:00
d8ff251c71 Fix WP user first/last name being cleared after subscription management form submit 2017-05-15 10:22:47 +03:00
12979cc2c0 Merge pull request #883 from mailpoet/alc_term_limit
Remove the result limit when searching for post terms in ALC and Posts [MAILPOET-902]
2017-05-14 22:47:16 -04:00
e974c06a89 Fixes subscription management form not saving data 2017-05-14 22:41:16 -04:00
f2ceff8252 Removes unused method 2017-05-14 22:02:24 -04:00
cd5f3165c7 Uses queue ID when fetching newsletter link by hash 2017-05-14 22:02:19 -04:00
6e700b0cfa Moves newsletter hash generating logic into Security helper class
Updates Links class to use Security helper's hash generating method
2017-05-14 20:15:40 -04:00
5b41fc212c Merge pull request #890 from mailpoet/helpscout
Force showing name and email fields in HelpScout Beacon [MAILPOET-908]
2017-05-13 12:52:13 +03:00
2b7a5452b8 Fix non-text form fields not being sent to server [MAILPOET-909] 2017-05-11 18:12:57 +03:00
cfed133fb6 Limit the number of terms returned to 50 and force ordering by name 2017-05-11 13:00:42 +03:00
0beff9a090 Bump up the limit when searching for post terms in ALC and Posts 2017-05-11 12:45:15 +03:00
d6e707fb85 Force showing name and email fields in HelpScout Beacon [MAILPOET-908] 2017-05-11 12:22:07 +03:00
a3e8d47199 Bump up release version to 3.0.0-beta.30 2017-05-09 16:57:29 -04:00
cab3f3a96e Merge pull request #884 from mailpoet/premium_key_check
Add Premium key validation [PREMIUM-4]
2017-05-09 16:30:43 +03:00
5f0d4abe7f Temporarily hide Premium tab in settings [PREMIUM-4] 2017-05-09 16:01:42 +03:00
ff5f87eeca Rename processQueueLogic() method to processQueueStrategy() [PREMIUM-4] 2017-05-09 15:42:37 +03:00
e85b969e11 Rename initApi() to init() in workers [PREMIUM-4] 2017-05-09 09:12:20 +03:00
2eb98905b6 Encapsulate date formatting within the DateTime class [PREMIUM-4] 2017-05-09 08:54:12 +03:00
ac1274c6fd Merge pull request #889 from mailpoet/tinymce_lists_fix
Bring back list buttons to the TinyMCE toolbar [MAILPOET-906]
2017-05-08 14:14:52 -04:00
94f91afce1 Bring back list buttons to TinyMCE toolbar [MAILPOET-906] 2017-05-08 21:01:18 +03:00
73d5fb8cff Merge pull request #886 from mailpoet/strings_fix
Fixes form editor notification not displaying added/updated/removed custom field [MAILPOET-905]
2017-05-08 13:24:59 +03:00
90b2b46db4 Make key check method names consistent [PREMIUM-4] 2017-05-08 13:16:05 +03:00
f2bf61240a Extract a state building method from key check results processing [PREMIUM-4] 2017-05-08 08:01:51 +03:00
3f151fd235 Extract simple workers common code into a base class [PREMIUM-4] 2017-05-08 07:38:56 +03:00
7598363cae Fixes notification message not displaying dynamic value due to JS
encoding
2017-05-05 18:38:49 -04:00
4b1f216cd3 Use WP's date format instead of a hard-coded one [PREMIUM-4] 2017-05-05 18:57:15 +03:00
3d5f13a2b8 Fix code style [PREMIUM-4] 2017-05-05 18:41:19 +03:00
98eab956e9 Rename checkAPIKey to checkMSSKey (MailPoet Sending Service) [PREMIUM-4] 2017-05-05 18:12:48 +03:00
a7260cba3d Make the Premium key check stricter, split a unit test into more granular ones [PREMIUM-4] 2017-05-05 18:09:00 +03:00
787e022382 Rename license key constants and vars, optimize error generation [PREMIUM-4] 2017-05-05 18:04:52 +03:00
d8e1c76155 Remove a leftover hook from Free after the key field removal from Premium [PREMIUM-4] 2017-05-05 17:18:56 +03:00
3cb08e3c09 Rename MSS check methods to better distinguish them from Premium ones [PREMIUM-4] 2017-05-04 09:36:38 +03:00
0474985866 Add unit tests [PREMIUM-4] 2017-05-04 09:25:34 +03:00
8d15ef6d06 Refine license key check UI [PREMIUM-4] 2017-05-04 09:15:21 +03:00
0fbc7fb7eb Add Premium key validation [PREMIUM-4] 2017-05-03 12:20:13 +03:00
1379bdbbeb Bump up release version to 3.0.0-beta.29 2017-05-02 18:10:20 +03:00
64d3e659a4 Merge pull request #879 from mailpoet/newsletter_model_update
Newsletter model update [MAILPOET-830]
2017-05-02 16:19:20 +03:00
19458546a0 Updates unit tests 2017-05-02 08:41:51 -04:00
bba7460423 Merge pull request #882 from mailpoet/conflict_resolver_update
Resolves script conflicts in WP's admin footer [MAILPOET-901]
2017-05-02 13:37:20 +03:00
956fdd5cff Improve a deletion test to handle multiple queues, fix comments [MAILPOET-830] 2017-05-02 09:07:38 +03:00
a0289775cb Trashes/restores multiple associated queues when newsletter is
trashed/restored
2017-05-01 20:15:41 -04:00
4c785902bc Resolves script conflicts in WP's admin footer 2017-05-01 11:57:48 -04:00
e29ae4d7c9 Fixes indentation 2017-05-01 09:31:52 -04:00
1ea915017a Fixes unit test 2017-05-01 09:26:36 -04:00
6441c781a5 Moves relations to the top of the model and delete/save/restore/trash methods close to each other for easy navigation 2017-05-01 09:26:35 -04:00
589c54e205 Checks if associated queue exists before trashing/deleting/restoring it 2017-05-01 09:26:35 -04:00
e10b99eaac Deletes all sending queue and segment associations when newsletters are bulk deleted 2017-05-01 09:26:35 -04:00
0316f3ea3e Restores all sending queue associations when newsletters are bulk restored 2017-05-01 09:26:35 -04:00
166fef899f Trashes all sending queue associations when newsletters are bulk trashed 2017-05-01 09:26:35 -04:00
4e850408fc Restores sending queue association when newsletter is restored 2017-05-01 09:26:35 -04:00
6e2494831c Trashes sending queue association when newsletter is trashed 2017-05-01 09:26:35 -04:00
38a7d8f80a Deletes queue and segment associations when deleting newsletter 2017-05-01 09:26:34 -04:00
abfebc8643 Merge pull request #880 from mailpoet/subscriber_shortcode_fix
Returns shortcode's default value when subscriber's first/last name is blank [MAILPOET-899]
2017-05-01 11:28:54 +03:00
40a3487d3d Remove a redundant condition, fix a typo in a test name [MAILPOET-880] 2017-05-01 11:20:33 +03:00
a93865e594 Merge pull request #874 from mailpoet/trashed_segments_fix
Exclude trashed segments from subscriber listing filter and 'not in list' count [MAILPOET-893]
2017-04-30 20:09:23 -04:00
4e76286b44 Returns shortcode's default value when subscriber's first or last name is empty 2017-04-28 09:49:00 -04:00
fbe57e96c6 Merge pull request #878 from mailpoet/contribution_rules
Update rules/guidelines for contributing to this codebase [MAILPOET-898]
2017-04-27 11:22:10 -04:00
950bfb04d6 Update README.md with more new commands 2017-04-27 17:56:36 +03:00
6d43b7b6a9 Update rules/guidelines for contributing to this codebase 2017-04-27 17:51:50 +03:00
e1991deafd Merge pull request #876 from mailpoet/manage_subscription_shortcode_fix
Depreciates and removes certain link shortcodes [MAILPOET-895]
2017-04-27 15:51:26 +03:00
2f1b31aeb2 Adds missing anchor closing tag 2017-04-27 08:41:05 -04:00
ca29eefd7f Merge pull request #877 from mailpoet/form_placement_update
Removes HTML method from form placement [MAILPOET-897]
2017-04-27 15:40:55 +03:00
1421407a23 Merge pull request #875 from mailpoet/smtp_hooks
Adds filter to SMTP transport agent [MAILPOET-889]
2017-04-27 14:43:40 +03:00
36e4bf468d Merge pull request #871 from mailpoet/editor_marionette
Upgrade Marionette version in newsletter editor [MAILPOET-892]
2017-04-27 11:19:46 +03:00
5cd3917f4d Removes HTML method from form placement 2017-04-26 19:38:19 -04:00
586470e8f9 Updates unit tests 2017-04-26 17:58:45 -04:00
b02e9f5ab3 Depreciates and removes server-side rendering of subscription_unsubscribe,
subscription_manage and newsletter_view_in_browser
2017-04-26 17:47:52 -04:00
4a538e677d Adds filter to SMTP transport agent 2017-04-26 09:55:57 -04:00
cc2fdbe5be Merge pull request #864 from mailpoet/campaign_stats
Add detailed stats page support in Free, change stats style [PREMIUM-1] [MAILPOET-877]
2017-04-26 14:30:51 +03:00
3833688115 Exclude trashed segments from subscriber listing filter and 'not in list' count [MAILPOET-893] 2017-04-26 11:23:16 +03:00
1639741e55 Bump up release version to 3.0.0-beta.28 2017-04-25 19:00:18 +03:00
ab0d573a66 Merge pull request #873 from mailpoet/import_results_fix
Keeps track of the number of updated/created subscribers over multiple server requests [MAILPOET-894]
2017-04-25 18:26:38 +03:00
26c582b19f Keeps track of the number of updated/created subscribers over multiple
server requests
2017-04-25 11:00:52 -04:00
3bc53f9f09 Remove Premium-only styles from Free, cleanup styles [PREMIUM-1] 2017-04-25 12:46:05 +03:00
bb220baf6a Add names to constants, rename vars for clarity [MAILPOET-877] 2017-04-25 12:46:04 +03:00
121a78f42a Update an open rate improvement KB link URL [MAILPOET-877] 2017-04-25 12:46:04 +03:00
4257aa634e Don't show green box and KB link in stats for welcome emails [MAILPOET-877] 2017-04-25 12:46:03 +03:00
95ff83557f Add a green box in stats for recently sent newsletters, add help KB link [MAILPOET-877] 2017-04-25 12:46:03 +03:00
e9070de9c4 Add badges to stats in a newsletter listing, change stats style [PREMIUM-1] [MAILPOET-877] 2017-04-25 12:45:52 +03:00
72aa087411 Localize formatting to 1 decimal [PREMIUM-1] 2017-04-25 12:45:50 +03:00
fbc0a3ad8d Add detailed stats page support in Free [PREMIUM-1] 2017-04-25 12:45:49 +03:00
afedc409f5 Merge pull request #872 from mailpoet/newsletter_id_number_format_fix
Adds intval filter and fixes issue with number format applied on IDs >= 1000 [MAILPOET-891]
2017-04-25 12:27:17 +03:00
0360f16dc8 Merge pull request #865 from mailpoet/api_versioning
Adds versioning to our public API [MAILPOET-881]
2017-04-25 12:07:17 +03:00
f4800dbbae Removes namescape format enforcement 2017-04-24 20:52:01 -04:00
15ddc8454e Adds intval filter and fixes issue with number format applied on IDs
>=1000
2017-04-24 19:25:17 -04:00
f8df4de711 Merge pull request #868 from mailpoet/bounce_doc_link
Add a link to the bounce article in advanced settings [MAILPOET-868]
2017-04-24 19:11:03 -04:00
a0cb18e1a1 Merge pull request #869 from mailpoet/html_notices_fix
Wrap notices containing HTML in a paragraph, upgrade notice classes [MAILPOET-733]
2017-04-24 16:09:50 -04:00
509ec7d3d3 Fix posts block settings to not use deprecated CompositeView 2017-04-24 18:00:23 +03:00
aa2416f353 Simplify settings views to use methods defined in base settings view 2017-04-24 18:00:23 +03:00
167a605658 Switch Container block view to use methods defined by base view 2017-04-24 18:00:23 +03:00
592f11bd5f Fix App activation calls, fix block insertion animations 2017-04-24 18:00:23 +03:00
92b128039a Fix displaying placeholder message on empty containers 2017-04-24 18:00:23 +03:00
5efe611b2d Remove obsolete comments, fix ALC settings 2017-04-24 18:00:23 +03:00
477e2737b1 Fix sorting of social icons in settings 2017-04-24 18:00:23 +03:00
dc8bacc27d Fix drag&drop 2017-04-24 18:00:23 +03:00
0b8c787cda Partially migrate newsletter editor to Marionette 3.x from 2.x 2017-04-24 18:00:23 +03:00
4f5c464659 Merge pull request #866 from mailpoet/list_unsubsribe_url
List unsubsribe url [MAILPOET-797]
2017-04-24 14:58:06 +03:00
4f432645b1 Merge pull request #870 from mailpoet/welcome_page_update
Update the Welcome page [MAILPOET-884]
2017-04-24 13:23:39 +03:00
5fa7930896 Redefines how endpoint namespaces are set
Updates error response to terminate connection only on AJAX requests
Optimizes and cleans up code based on code revew comments
2017-04-20 22:34:18 -04:00
f9efd536d9 Update the Welcome page [MAILPOET-884] 2017-04-20 21:00:09 +03:00
6a65ff5e5d Removes default version
Updates all AJAX requests to include api version
Requires namespaces to have version
Clean up code
2017-04-19 23:34:40 -04:00
b549f83422 Updates form subscription class to use the main API class instead of calling directly API endpoint
Modifies forms to pass api_version
Modifies forms to pass store form-specific values (e.g., form_id, email) inside a separate data array
2017-04-19 15:38:16 -04:00
a9c80c031f Adds version support to public API 2017-04-19 15:38:16 -04:00
405bea3049 Upgrade notice classes from deprecated ones [MAILPOET-733] 2017-04-19 17:11:42 +03:00
6954acd0b3 Wrap notice messages containing HTML in a paragraph to avoid broken styles [MAILPOET-733] 2017-04-19 17:11:14 +03:00
efd15d5d18 Add a link to the bounce article in advanced settings [MAILPOET-868] 2017-04-19 10:34:30 +03:00
6566622167 Bumps up release version to 3.0.0-beta.27
Updates changelog
Updates readme.txt based changes proposed by Kim
Cleans up language in readme.txt
2017-04-18 20:05:24 -04:00
8157780b68 removing uneeded code and moving the url generation to proper class 2017-04-18 21:12:41 +02:00
975546915e Merge pull request #867 from mailpoet/requirements_checker_update
Adds requirement check for ZIP and XML PHP extensions [MAILPOET-874]
2017-04-18 10:46:42 +03:00
319d591662 Merge pull request #863 from mailpoet/editor_tinymce
TinyMCE fixes [MAILPOET-880] [MAILPOET-829] [MAILPOET-862]
2017-04-17 09:13:35 +03:00
1dd6c91529 Updates missing requirements language 2017-04-16 23:54:10 -04:00
c4f0426775 Adds checker for XML and ZIP extensions 2017-04-16 21:10:15 -04:00
53f5a122bd Merge pull request #861 from mailpoet/post_filters
Fix post filters for custom post types in ALC [MAILPOET-838]
2017-04-16 19:52:01 -04:00
a7142ed21b modified SendingQueue Tests to ensure it passes the correct unsubscribe URL to Mailer 2017-04-15 22:14:40 +00:00
771a1bfc44 Adding List-Unsubscribe to header of newsletters 2017-04-15 21:21:28 +00:00
53169bba78 Merge pull request #860 from mailpoet/import_fixes
Fixes SQL errors & next button not working on step 2 of import [MAILPOET-876] [MAILPOET-766] [MAILPOET-879] [MAILPOET-828]
2017-04-14 09:03:59 +03:00
e3b8c1836b Adds additional new and existing subscribers to the test method to
ensure that data between new subscribers does not mix
2017-04-13 15:57:04 -04:00
a4b091dc32 Extends test condition to check for all new subscriber column data 2017-04-13 10:50:36 -04:00
448c9ddaa8 Fixes custom column names not being automatically matched on step 2 of
import
2017-04-13 10:10:00 -04:00
ac574acf8e Merge branch 'import_fixes' of mailpoet:mailpoet/mailpoet into import_fixes 2017-04-13 09:41:46 -04:00
aa15b9420a Replaces redundant search with one-time lookup 2017-04-13 09:28:54 -04:00
2b7f5c321e Merge pull request #862 from mailpoet/remote_images
Fixes Image block to update image dimensions when image src changes [MAILPOET-762]
2017-04-13 13:51:47 +03:00
bee9bfcfcc Fix data being mixed up when splitting subscribers, remove excessive arguments [MAILPOET-828] 2017-04-13 11:08:20 +03:00
b7d73dcfaa Updates unit test 2017-04-12 09:49:42 -04:00
5b4fa4ea2b Fixes custom fields not being updated or causing integrity constraint
error: https://mailpoet.atlassian.net/browse/MAILPOET-828
2017-04-12 09:40:15 -04:00
12e5fe77de Perform caret positioning only on TinyMCE activation click 2017-04-12 14:37:47 +03:00
2dca10c539 Fix cursor positioning when activating TinyMCE on click [MAILPOET-880] 2017-04-12 14:07:19 +03:00
ceba5b3d0b Fix pasting from text editors and word processors [MAILPOET-829] 2017-04-12 13:17:45 +03:00
c05cf3cad4 Update TinyMCE for fixed triple-click behavior [MAILPOET-862] 2017-04-12 13:14:27 +03:00
d6f5a39829 Simplifies subscriber splitting code and adds comments 2017-04-11 22:12:50 -04:00
30d67508cb Fixes Image block to update image dimensions when image src changes 2017-04-11 19:38:57 +03:00
63b8d892f7 Update changelog entries to use asterisks for list items instead of
dashes
2017-04-11 16:05:53 +03:00
10137d8551 Bump up release version to 3.0.0-beta.26 2017-04-11 15:46:32 +03:00
9ef74e0951 Stops execution when there are no subscriber columns to update 2017-04-10 21:41:37 -04:00
89ff93958f Removes subscriber object modification logic from the splitSubscribersData() method
Uses 2 separate objects with its own data for existing and new subscribers
Extends only new subscribers' object when it is missing required fields
2017-04-10 21:41:21 -04:00
8d870e85eb Switch to get_bloginfo() from bloginfo() to prevent output 2017-04-10 19:44:32 +03:00
0cdb426712 Fix ALC filtering for custom taxonomies and post types 2017-04-10 19:23:19 +03:00
b9f7a5673f Removes lefover test code 2017-04-10 11:32:38 -04:00
7ffbf6c378 Updates code style and adds wp_user_id column to the list of columns
that should be ignore when updating existing subscribers
2017-04-09 22:05:02 -04:00
3a9c006cf9 Prevents overwriting existing subscribers' status (and other required fields) unless
the import object contains data for those fields
2017-04-09 22:04:56 -04:00
a9edb383b4 Fixes next button not appearing when list is first unselected and then
selected back
2017-04-09 21:49:24 -04:00
ec23a73edb Merge pull request #859 from mailpoet/trailing_br_rendering_fix
Fix last <br/> removal cutting off text when rendering a text block [MAILPOET-856]
2017-04-06 21:57:07 -04:00
10a164ee0c Merge pull request #858 from mailpoet/customizer_fix
Rename a 'method' field in a form widget so it doesn't break the WP interactive customizer [MAILPOET-851]
2017-04-06 21:55:24 -04:00
37fcd5699b Fix last <br/> removal cutting off text when rendering a text block [MAILPOET-856] 2017-04-06 10:07:11 +03:00
66d969cc2f Merge pull request #857 from mailpoet/settings-css-update
Removes sending method's heading line-height [MAILPOET-873]
2017-04-05 18:51:09 +03:00
9d358f74dd Rename a 'method' field in a form widget so it doesn't break the WP interactive customizer [MAILPOET-851] 2017-04-05 18:35:13 +03:00
57e00e3097 Removes sending method's heading line-height 2017-04-05 10:45:31 -04:00
53afbea6ec Bump up release version to 3.0.0-beta.25 2017-04-04 18:22:44 +03:00
2c2c0b3db4 Merge pull request #856 from mailpoet/sending_limit_enforcement_fix
Fixes sending limit not being enforced [MAILPOET-872]
2017-04-04 17:07:51 +03:00
e235ee66eb Adds regression unit test 2017-04-04 09:59:06 -04:00
0ef430567b Fixes sending limit not being enforced when email frequency limit is
changed to a lesser value OR when it is changed while sending is in
progress
2017-04-04 09:43:27 -04:00
74aef73f75 Merge pull request #855 from mailpoet/php53-fix
Fixes reference to self in anonymous function [MAILPOET-871]
2017-03-31 21:31:20 +03:00
99eb72428f Fixes reference to self in anonymous function 2017-03-31 12:51:58 -04:00
065b160155 Merge pull request #854 from mailpoet/subscriber_listing_performance
Improve performance of a subscriber listing on MySQL 5.5 and lower [MAILPOET-867]
2017-03-30 09:51:47 -04:00
6811d8e38d Improve performance of a subscriber listing on MySQL 5.5 and lower [MAILPOET-867] 2017-03-30 13:12:53 +03:00
5f75efddf1 Updates changelog and bumps version to 3.0.0-beta.24 2017-03-28 14:19:11 -04:00
822a7ac5f5 Merge pull request #852 from mailpoet/translation_string_escaping_fix
Escapes quotation marks in translation results [MAILPOET-864]
2017-03-28 19:28:57 +03:00
06e1ac9bb5 Escapes translations for output in HTML attributes 2017-03-28 12:02:29 -04:00
a3530c3367 Escapes translations for JS output 2017-03-28 11:17:00 -04:00
ec35bfb2d4 Reverts back previous code 2017-03-28 11:16:09 -04:00
ed3e46bebb Merge pull request #853 from mailpoet/tracking_code_update
Makes tracking image transparent and fixes CSS rule parsing logic [MAILPOET-827]
2017-03-28 11:36:54 +03:00
87b270482b Fixes rules with colons (e.g, background-image: url(http://....);) from
being incorrectly parsed
2017-03-26 18:45:07 -04:00
d22ba55858 Outputs transparent gif instead of red color 2017-03-26 18:45:07 -04:00
835f25cc82 Fixes unit test that fails on the last Saturday of the month 2017-03-25 02:24:38 -04:00
11944283b0 Escapes quotation marks in translation results 2017-03-24 20:38:04 -04:00
dc704a92de Merge pull request #851 from mailpoet/import_language_update
Updates example import paste data (textbox hint) [MAILPOET-863]
2017-03-24 18:01:12 +02:00
dca1e9e1a7 Merge pull request #850 from mailpoet/manage_subscription_descr
Add details to the Manage Subscription description [MAILPOET-853]
2017-03-24 17:56:27 +02:00
00781be077 Updates example import paste data 2017-03-23 13:27:48 -04:00
ac80148f5b Merge pull request #844 from mailpoet/ga_tracking
JS hooks & GA tracking support [PREMIUM-2]
2017-03-23 14:58:58 +02:00
ff36833270 Add a hook for reinstalling Premium [PREMIUM-2] 2017-03-23 15:27:15 +03:00
612c7d76a0 Add details to the Manage Subscription description [MAILPOET-853] 2017-03-22 13:19:26 +03:00
32097b4512 Test that Premium hooks are executed [PREMIUM-2] 2017-03-21 21:36:36 +03:00
d686f75222 Swap JS actions with filters for robustness & testability, get rid of URL key hashing [PREMIUM-2] 2017-03-21 13:34:47 +03:00
bcc01df0b8 Merge pull request #849 from mailpoet/transifex_upload
Upload translation files to Transifex via publish command [MAILPOET-855]
2017-03-20 19:40:19 +02:00
ee12f4d304 Move a hook to be always executed after rebasing [PREMIUM-2] 2017-03-20 16:23:16 +03:00
16c1607850 Refactor links processing: isolate core logic for easier substitution [PREMIUM-2] 2017-03-20 12:09:58 +03:00
e2864e2243 Add hooks for GA tracking feature [PREMIUM-2] 2017-03-20 12:09:58 +03:00
16dc81150d Execute mailpoet_initialized hook earlier after setup [PREMIUM-2]
This is done because Router can seize the request and prevent subsequent actions like Premium hooking from being run, so hooks didn't work in cron daemon.
2017-03-20 12:08:40 +03:00
ed4d3d52ed Add hooks for Premium translations and scripts [PREMIUM-2] 2017-03-20 12:08:40 +03:00
37a6a74b6e Extract React libraries to a separate chunk and expose them globally [PREMIUM-2] 2017-03-20 12:08:40 +03:00
136a531047 Rename newsletters 3rd step hook [PREMIUM-2] 2017-03-20 12:08:40 +03:00
bef0097f5b Add front-end WP-style hooks support for Premium [PREMIUM-2] 2017-03-20 12:08:39 +03:00
a0d2be50e8 Upload translation files to Transifex via publish command [MAILPOET-855] 2017-03-16 15:11:38 +03:00
18e2d26587 Bump up release version to 3.0.0-beta.23.2 2017-03-15 14:35:44 +02:00
810a7bf544 Merge pull request #848 from mailpoet/welcome_emails_fix
Fix Welcome email sending [MAILPOET-859]
2017-03-15 13:59:57 +02:00
1010c6f4f0 Remove a space [MAILPOET-859] 2017-03-15 13:15:34 +03:00
8dd698ec75 Fix Welcome email sending [MAILPOET-859] 2017-03-15 12:12:47 +03:00
95e66f1f29 Bump up release version to 3.0.0-beta.23.1 2017-03-14 16:13:07 +02:00
ac0460ab04 Merge pull request #847 from mailpoet/freeze_dependencies
Freeze PHP and JS dependencies to specific versions [MAILPOET-858]
2017-03-14 16:58:23 +03:00
2d059debb7 Freeze PHP and JS dependencies to specific versions 2017-03-14 15:11:35 +02:00
df0ad2df37 Update release version to 3.0.0-beta.23 2017-03-14 13:18:21 +02:00
0f7725e6af Merge pull request #842 from mailpoet/verify_subscription_status_during_sending
Prevents sending to unsubscribed subscribers [MAILPOET-824]
2017-03-14 10:49:43 +03:00
eda346c582 Fix a unit test for PHP 7.1 [MAILPOET-824] 2017-03-14 10:31:44 +03:00
94060a6443 Merge pull request #846 from mailpoet/transifex_import
Add importing of translations from Transifex [MAILPOET-849]
2017-03-13 18:45:10 +02:00
1cd7c5e876 Update README, force translations downloading, set shell script permissions [MAILPOET-849] 2017-03-13 19:34:17 +03:00
b369cadde0 Merge pull request #841 from mailpoet/post_notification_reschedule
Reschedules sending queues when scheduling options change [MAILPOET-837]
2017-03-13 17:21:03 +02:00
5321a136e7 Add importing of translations from Transifex [MAILPOET-849] 2017-03-13 17:43:56 +03:00
d4c04f29bf Merge pull request #845 from mailpoet/readme_update
Update translations section of readme.txt [MAILPOET-854]
2017-03-13 14:56:47 +02:00
20798d8957 Update translations section of readme.txt [MAILPOET-854] 2017-03-13 12:55:52 +03:00
3cde437628 Adds enforcement of global subcriber subscription status and
subscribption to segments to which newsletter is sent
2017-03-09 20:38:34 -05:00
8db7af48cd Merge pull request #843 from mailpoet/http_build_query_fix
Forces ampersand as query separator for mailers [MAILPOET-850]
2017-03-09 13:40:08 +03:00
d05d033727 Forces ampersand as query separator for mailers 2017-03-08 14:34:35 -05:00
ccba1925b1 Prevents sending to unsubscribed subscribers 2017-03-07 18:55:49 -05:00
b590586d4c Reschedules previously scheduled sending queues when post notification's
scheduling options change
2017-03-07 18:39:55 -05:00
44c742402c Bump up release verison to 3.0.0-beta.22 2017-03-07 17:49:11 +03:00
3a9db95c37 Merge pull request #840 from mailpoet/sending_batch_size_update
Reduces sending batch size [MAILPOET-847]
2017-03-07 16:53:52 +03:00
5d88938d94 Reduces sending batch size 2017-03-06 19:05:14 -05:00
67e0f1776d Merge pull request #839 from mailpoet/mailer_endpoint_update
Prevents setting current user's name to recipient of test email [MAILPOET-846]
2017-03-06 10:38:34 +03:00
5b68febb05 Removes first/last name that's based on current user from being sent to
test e-mail
2017-03-02 17:28:44 -05:00
9bf65ca798 Merge pull request #838 from mailpoet/editor_notices
Fix notices on newsletter editor page to not be absorbed into contents [MAILPOET-811]
2017-03-01 20:38:58 +03:00
d95aa40502 Fix notices on newsletter editor page to not be absorbed into contents 2017-03-01 17:26:17 +02:00
a59bf76fb4 Update version information for 3.0.0-beta.21 release 2017-02-28 14:11:03 +02:00
51fdc7f1df Merge pull request #837 from mailpoet/catch_cron_exceptions
Catches exception thrown by cron dependency and prevents a fatal error [MAILPOET-844]
2017-02-28 13:20:58 +02:00
aa51b751d0 Merge pull request #835 from mailpoet/translations
Fix translations [MAILPOET-841]
2017-02-28 14:08:44 +03:00
aff522c5cd Merge pull request #836 from mailpoet/translation_string_update
Removes unnecessary period [MAILPOET-842]
2017-02-28 12:40:06 +02:00
66d039ace3 Catches exception thrown by cron dependency and prevents a fatal error 2017-02-27 19:21:32 -05:00
ed5e6cdd8c Removes unnecessary period 2017-02-27 12:58:54 -05:00
47f5e1e7b4 Translate color picker actions in newsletter editor 2017-02-27 19:32:08 +02:00
626d6c0fa9 Fix "newsletter was updated successfully" translation 2017-02-27 19:15:29 +02:00
2b45d64695 Make numberOfItems translation singular/plural aware 2017-02-27 18:33:09 +02:00
c27446666e Translate untranslated strings 2017-02-27 18:33:09 +02:00
7f0195378c Merge pull request #833 from mailpoet/stop_daemon_with_sending_is_paused
Stops cron daemon when sending is paused due to an error [MAILPOET-839]
2017-02-27 16:49:55 +03:00
6caa3a069b Merge pull request #832 from mailpoet/mailer_output_escape_fix
Fixes double escaping of HTML entities in mailer output [MAILPOET-836]
2017-02-27 10:52:10 +03:00
baaf73b584 Merge pull request #834 from mailpoet/mailpoet_mailer_php53_fix
Fixes 'Using $this when not in object context' MailPoet mailer error on PHP 5.3 [MAILPOET-840]
2017-02-27 10:19:21 +03:00
b2a92feb50 Restore unnecessary $this replacements [MAILPOET-840] 2017-02-27 10:18:14 +03:00
c11b9677d5 Fixes 'Using $this when not in object context' error on PHP 5.3 2017-02-26 22:08:34 -05:00
0e5a26ce1f Stops cron daemon when sending is paused due to an error 2017-02-26 11:26:59 -05:00
40ec5569d0 Removes "unprocessed subscriber" message since MailPoet's mailer method
processes subscribers in batches of 50 and it makes no sense to display
them all
2017-02-25 10:32:14 -05:00
d14ecc982b Does not display unprocessed subscriber when sending a test message 2017-02-24 11:18:27 -05:00
9c27384ba3 Passes responsibility for mailer error message HTML entity escaping from
server to the client side
2017-02-24 11:06:20 -05:00
2268f0ffa6 Merge pull request #831 from mailpoet/php53_bulk_trash_and_restore_fix
Fix bulk trashing/restoring not working for newsletters/forms on PHP 5.3 [MAILPOET-835]
2017-02-24 13:22:30 +01:00
bfc04bfa87 Fix bulk trashing/restoring not working for newsletters/forms on PHP 5.3 [MAILPOET-835] 2017-02-24 15:10:38 +03:00
37ac31cdac Bumps up release version to 3.0.0-beta.20 and updates changelog 2017-02-23 19:04:55 -05:00
15096d483f Merge pull request #830 from mailpoet/newsletter_save_fix
Fixes newsletter options being wiped on Step 3 of creation process [MAILPOET-833]
2017-02-23 23:51:59 +03:00
16724affad Remove an excess count() [MAILPOET-833] 2017-02-23 23:42:28 +03:00
384d59abe0 Update Newsletters.php
Removes request interruption when options are not found
2017-02-23 15:25:34 -05:00
027414b7a2 Updates method data verification condition 2017-02-23 14:39:56 -05:00
a1cd56c419 Updates newsletter option creation logic and fixes a bug that results in
them being wiped clean
2017-02-23 14:30:53 -05:00
6ee1c23f9a Merge pull request #829 from mailpoet/post_title_shortcode_fix
Fix post_title shortcode breaking sending if the post is trashed in the process [MAILPOET-831]
2017-02-23 14:41:24 +01:00
44a223eba1 Fix post_title shortcode breaking sending if the post is trashed in the process [MAILPOET-831] 2017-02-23 16:33:18 +03:00
7c66754541 Merge pull request #828 from mailpoet/premium_hooks
Premium hooks / Additional endpoints in Free API [PREMIUM-4]
2017-02-23 15:26:30 +02:00
bb80fc0860 Add unit tests for API endpoints injection [PREMIUM-4] 2017-02-22 18:02:58 +03:00
6c7cc5de0d Implement support for additional endpoints in Free API [PREMIUM-4] 2017-02-22 11:19:35 +03:00
063cc9edc3 added do_action() helper to twig + settings/basics hook 2017-02-22 10:12:08 +03:00
610 changed files with 31491 additions and 10122 deletions

View File

@ -1,8 +0,0 @@
#!/usr/bin/ruby
path = "/tmp"
Dir.mkdir(path) if !File.exists?(path)
File.open("#{path}/mailpoet-#{Time.now.to_f}.txt", "w") do |f|
sleep 5
f.puts ARGV.inspect
$stdin.each_line { |line| f.puts line }
end

View File

@ -1,3 +0,0 @@
; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
; http://php.net/sendmail-path
sendmail_path = /home/ubuntu/mailpoet/.circle_ci/fake-sendmail.rb

View File

@ -3,11 +3,11 @@ Listen 8080
<VirtualHost *:8080> <VirtualHost *:8080>
UseCanonicalName Off UseCanonicalName Off
ServerName mailpoet.loc ServerName mailpoet.loc
DocumentRoot /home/ubuntu/mailpoet/wordpress DocumentRoot /home/circleci/mailpoet/wordpress
DirectoryIndex index.php DirectoryIndex index.php
LogLevel notice LogLevel notice
<Directory /home/ubuntu/mailpoet/wordpress> <Directory /home/circleci/mailpoet/wordpress>
Require all granted Require all granted
</Directory> </Directory>
</VirtualHost> </VirtualHost>

94
.circleci/config.yml Normal file
View File

@ -0,0 +1,94 @@
version: 2
jobs:
qa_js_php5:
working_directory: /home/circleci/mailpoet
docker:
- image: circleci/php:5.6.30-apache-browsers
- image: circleci/mysql:5.7
environment:
TZ: /usr/share/zoneinfo/Etc/UTC
steps:
- checkout
- run:
name: "Set up virtual host"
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- restore_cache:
key: npm-{{ checksum "package.json" }}
- run:
name: "Set up test environment"
command: source ./.circleci/setup.bash && setup php5
- save_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths:
- vendor
- save_cache:
key: npm-{{ checksum "package.json" }}
paths:
- node_modules
- run:
name: "QA Scripts"
command: ./do qa
- run:
name: "Preparing test results folder"
command: mkdir test-results
- run:
name: "JS tests"
command: |
mkdir test-results/mocha
./do t:j test-results/mocha/junit.xml
- run:
name: "PHP Unit tests"
command: |
WP_TEST_PATH="/home/circleci/mailpoet/wordpress" ./do t:u --xml
- store_test_results:
path: test-results/mocha
- store_artifacts:
path: test-results/mocha
destination: mocha
- store_test_results:
path: tests/_output
- store_artifacts:
path: tests/_output
destination: codeception
- store_artifacts:
path: /tmp/fake-mailer/
destination: fake-mailer
php7:
working_directory: /home/circleci/mailpoet
docker:
- image: circleci/php:7.1-apache-browsers
- image: circleci/mysql:5.7
environment:
TZ: /usr/share/zoneinfo/Etc/UTC
steps:
- checkout
- run:
name: "Set up virtual host"
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- restore_cache:
key: npm-{{ checksum "package.json" }}
- run:
name: "Set up test environment"
command: source ./.circleci/setup.bash && setup php7
- run:
name: "PHP Unit tests"
command: |
WP_TEST_PATH="/home/circleci/mailpoet/wordpress" ./do t:u --xml
- store_test_results:
path: tests/_output
- store_artifacts:
path: tests/_output
destination: codeception
- store_artifacts:
path: /tmp/fake-mailer/
destination: fake-mailer
workflows:
version: 2
build_and_test:
jobs:
- qa_js_php5
- php7

17
.circleci/fake-sendmail.php Executable file
View File

@ -0,0 +1,17 @@
#!/usr/local/bin/php
<?php
$path = "/tmp/fake-mailer";
if(!file_exists($path)) {
mkdir($path);
}
$filename = $path . '/mailpoet-' . microtime(true) . '.txt';
$file_handle = fopen($filename, "w");
$call_arguments = print_r($argv, true) . "\n";
fwrite($file_handle, $call_arguments);
while($line = fgets(STDIN)) {
fwrite($file_handle, $line);
}
fclose($file_handle);

View File

@ -0,0 +1,9 @@
; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
; http://php.net/sendmail-path
sendmail_path = /usr/local/bin/fake-sendmail.php
[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = UTC

48
.circleci/setup.bash Normal file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env bash
function setup {
local version=$1
# install PHP dependencies for WordPress
if [[ $version == "php7" ]]; then
echo "deb http://packages.dotdeb.org jessie all" | sudo tee -a /etc/apt/sources.list.d/dotdeb.list
echo "deb-src http://packages.dotdeb.org jessie all" | sudo tee -a /etc/apt/sources.list.d/dotdeb.list
wget -qO - http://www.dotdeb.org/dotdeb.gpg | sudo apt-key add -
sudo apt-get update
sudo apt-get install mysql-client php7.0-mysql zlib1g-dev
sudo docker-php-ext-install mysqli pdo pdo_mysql zip
else
sudo apt-get update
sudo apt-get install mysql-client php5-mysql zlib1g-dev
sudo docker-php-ext-install mysql mysqli pdo pdo_mysql zip
fi
# Add a fake sendmail mailer
sudo cp ./.circleci/fake-sendmail.php /usr/local/bin/
# configure Apache
sudo cp ./.circleci/mailpoet_php.ini /usr/local/etc/php/conf.d/
sudo cp ./.circleci/apache/mailpoet.loc.conf /etc/apache2/sites-available
sudo a2ensite mailpoet.loc
sudo a2enmod rewrite
sudo service apache2 restart
# Install NodeJS+NPM
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install nodejs build-essential
# install plugin dependencies
curl -sS https://getcomposer.org/installer | php
./composer.phar install
./do install
# Set up Wordpress
mysql -h 127.0.0.1 -u root -e "create database wordpress"
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
./wp-cli.phar core download --allow-root --path=wordpress
# Generate `wp-config.php` file with debugging enabled
echo "define(\"WP_DEBUG\", true);" | ./wp-cli.phar core config --allow-root --dbname=wordpress --dbuser=root --dbhost=127.0.0.1 --path=wordpress --extra-php
# Install WordPress
./wp-cli.phar core install --allow-root --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc:8080 --title=WordPress --path=wordpress
# Softlink plugin to plugin path
ln -s ../../.. wordpress/wp-content/plugins/mailpoet
./wp-cli.phar plugin activate mailpoet --path=wordpress
# Create .env file with correct path to WP installation
# TODO: Remove this line after PR gets merged and CircleCI env variables change
echo "WP_TEST_PATH=\"/home/circleci/mailpoet/wordpress\"" > .env
}

View File

@ -16,3 +16,4 @@ WP_TEST_MAILER_SMTP_LOGIN=""
WP_TEST_MAILER_SMTP_PASSWORD="" WP_TEST_MAILER_SMTP_PASSWORD=""
WP_SVN_USERNAME="" WP_SVN_USERNAME=""
WP_SVN_PASSWORD="" WP_SVN_PASSWORD=""
WP_TRANSIFEX_API_TOKEN=""

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
**/vendor/**
**/testBundles/**
assets/js/src/newsletter_editor/tinymce/wplink/plugin.js

98
.eslintrc.es5.json Normal file
View File

@ -0,0 +1,98 @@
{
"extends": "airbnb/legacy",
"env": {
"amd": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 5
},
"rules": {
"import/no-amd": 0,
"space-before-function-paren": 0,
"prefer-arrow-callback": 0,
"no-undef": 0,
"key-spacing": 0,
"radix": 0,
"no-alert": 0,
"block-scoped-var": 0,
"guard-for-in": 0,
"no-prototype-builtins": 0,
"no-restricted-syntax": 0,
"newline-per-chained-call": 0,
"no-useless-concat": 0,
"no-multi-spaces": 0,
"no-nested-ternary": 0,
"semi-spacing": 0,
"no-sequences": 0,
"no-useless-return": 0,
"array-callback-return": 0,
"new-cap": 0,
"no-return-assign": 0,
"no-continue": 0,
"no-new": 0,
"no-cond-assign": 0,
"space-unary-ops": 0,
"no-redeclare": 0,
"no-console": 0,
"no-empty": 0,
"no-extra-semi": 0,
"no-useless-escape": 0,
"wrap-iife": 0,
"no-unused-expressions": 0,
"block-spacing": 0,
"computed-property-spacing": 0,
"no-plusplus": 0,
"array-bracket-spacing": 0,
"lines-around-directive": 0,
"no-unreachable": 0,
"default-case": 0,
"no-lonely-if": 0,
"space-before-blocks": 0,
"no-unneeded-ternary": 0,
"no-mixed-operators": 0,
"eqeqeq": 0,
"space-in-parens": 0,
"semi": 0,
"max-len": 0,
"no-multi-assign": 0,
"no-trailing-spaces": 0,
"global-require": 0,
"no-throw-literal": 0,
"no-extra-bind": 0,
"one-var-declaration-per-line": 0,
"consistent-return": 0,
"no-shadow": 0,
"no-underscore-dangle": 0,
"brace-style": 0,
"no-else-return": 0,
"no-use-before-define": 0,
"one-var": 0,
"camelcase": 0,
"spaced-comment": 0,
"quotes": 0,
"padded-blocks": 0,
"object-curly-spacing": 0,
"strict": 0,
"vars-on-top": 0,
"no-var": 0,
"space-infix-ops": 0,
"no-unused-vars": 0,
"object-shorthand": 0,
"new-parens": 0,
"no-param-reassign": 0,
"keyword-spacing": 0,
"eol-last": 0,
"dot-notation": 0,
"linebreak-style": 0,
"indent": 0,
"quote-props": 0,
"prefer-template": 0,
"func-names": 0
}
}

97
.eslintrc.es6.json Normal file
View File

@ -0,0 +1,97 @@
{
"extends": "airbnb",
"env": {
"amd": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"comma-dangle": ["error", "always-multiline"],
"import/no-amd": 0,
"react/no-multi-comp": 0,
"react/sort-comp": 0,
"react/jsx-max-props-per-line": 0,
"react/prop-types": 0,
"react/jsx-first-prop-new-line": 0,
"react/jsx-indent-props": 0,
"react/no-is-mounted": 0,
"react/jsx-no-target-blank": 0,
"react/no-render-return-value": 0,
"react/jsx-boolean-value": 0,
"react/jsx-indent": 0,
"react/jsx-no-bind": 0,
"react/no-array-index-key": 0,
"react/self-closing-comp": 0,
"react/jsx-tag-spacing": 0,
"react/jsx-closing-bracket-location": 0,
"react/no-string-refs": 0,
"react/jsx-curly-spacing": 0,
"react/no-did-mount-set-state": 0,
"react/prefer-stateless-function": 0,
"jsx-a11y/label-has-for": 0,
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/alt-text": 0,
"func-names": 0,
"object-shorthand": 0,
"no-bitwise": 0,
"arrow-body-style": 0,
"prefer-template": 0,
"keyword-spacing": 0,
"default-case": 0,
"quote-props": 0,
"array-callback-return": 0,
"consistent-return": 0,
"no-unreachable": 0,
"no-extra-semi": 0,
"import/no-unresolved": 0,
"import/extensions": 0,
"import/no-extraneous-dependencies": 0,
"camelcase": 0,
"template-curly-spacing": 0,
"quotes": 0,
"eqeqeq": 0,
"no-lonely-if": 0,
"space-unary-ops": 0,
"block-scoped-var": 0,
"no-extra-bind": 0,
"no-multi-spaces": 0,
"class-methods-use-this": 0,
"key-spacing": 0,
"no-multiple-empty-lines": 0,
"space-in-parens": 0,
"no-case-declarations": 0,
"array-bracket-spacing": 0,
"newline-per-chained-call": 0,
"no-else-return": 0,
"max-len": 0,
"no-useless-concat": 0,
"no-unused-expressions": 0,
"no-sequences": 0,
"no-extra-boolean-cast": 0,
"dot-notation": 0,
"no-param-reassign": 0,
"no-shadow": 0,
"one-var": 0,
"no-alert": 0,
"one-var-declaration-per-line": 0,
"no-script-url": 0,
"wrap-iife": 0,
"vars-on-top": 0,
"space-infix-ops": 0,
"no-irregular-whitespace": 0,
"padded-blocks": 0,
"no-underscore-dangle": 0,
"no-undef": 0
}
}

46
.eslintrc.tests.json Normal file
View File

@ -0,0 +1,46 @@
{
"extends": "airbnb/legacy",
"env": {
"amd": true,
"mocha": true
},
"parserOptions": {
"ecmaVersion": 6
},
"rules": {
"import/no-amd": 0,
"no-undef": 0,
"one-var": 0,
"indent": 0,
"linebreak-style": 0,
"no-whitespace-before-property": 0,
"object-property-newline": 0,
"global-require": 0,
"semi": 0,
"keyword-spacing": 0,
"no-bitwise": 0,
"no-multi-assign": 0,
"newline-per-chained-call": 0,
"no-spaced-func": 0,
"func-call-spacing": 0,
"max-len": 0,
"space-unary-ops": 0,
"quotes": 0,
"no-unused-vars": 0,
"no-unused-expressions": 0,
"no-underscore-dangle": 0,
"quote-props": 0,
"no-shadow": 0,
"padded-blocks": 0,
"vars-on-top": 0,
"space-before-blocks": 0,
"object-curly-spacing": 0,
"no-param-reassign": 0,
"one-var-declaration-per-line": 0,
"func-names": 0,
"space-before-function-paren": 0
}
}

45
.gitignore vendored
View File

@ -1,21 +1,24 @@
.DS_Store .DS_Store
TODO TODO
composer.phar composer.phar
/vendor /vendor
tests/_output/* tests/_output/*
tests/acceptance.suite.yml tests/acceptance.suite.yml
tests/_support/_generated/* tests/_support/_generated/*
node_modules node_modules
.env .env
npm-debug.log npm-debug.log
!tasks/** !tasks/**
/views/cache/** /views/cache/**
temp temp
.idea .idea
mailpoet.zip mailpoet.zip
tests/javascript/testBundles tests/javascript/testBundles
assets/css/*.css assets/css/*.css
assets/js/*.js assets/css/*.json
.vagrant assets/js/*.js
lang assets/js/*.json
.mp_svn .vagrant
lang
.mp_svn
/nbproject/

9
.tx/config Normal file
View File

@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
[mp3.mailpoet]
source_file = lang/mailpoet.pot
file_filter = lang/mailpoet-<lang>.po
source_lang = en_US
type = PO
minimum_perc = 75

View File

@ -5,30 +5,32 @@
- CamelCase for classes. - CamelCase for classes.
- camelCase for methods. - camelCase for methods.
- snake_case for variables and class properties. - snake_case for variables and class properties.
- Max line length at 80 chars.
- Classes can be no longer than 100 LOC.
- Methods can be no longer than 5 LOC.
- Pass no more than 4 parameters/hash keys into a method.
- Composition over Inheritance. - Composition over Inheritance.
- Comments are a code smell. - Comments are a code smell. If you need to use a comment - see if same idea can be achieved by more clearly expressing code.
- Routes can instantiate only one object.
- Require other classes with 'use' at the beginning of the class file. - Require other classes with 'use' at the beginning of the class file.
- Do not specify 'public' if method is public, it's implicit. - Do not specify 'public' if method is public, it's implicit.
- Always use guard clauses. - Always use guard clauses.
- Ensure compatibility with PHP 5.3 and newer versions.
- Cover your code in tests.
Recommendations:
- Max line length at 80 chars.
- Keep classes under 100 LOC.
- Keep methods under 10 LOC.
- Pass no more than 4 parameters/hash keys into a method.
- Keep Pull Requests small, under 100 LOC changed.
## Git flow. ## Git flow.
- Do not commit to master. - Do not commit to master.
- Open a short-living feature branch. - Open a short-living feature branch.
- Open a pull request. - Open a pull request.
- Add close #issue in pull request description. - Add Jira issue reference in the title of the Pull Request.
- Work on the pull request. - Work on the pull request.
- Wait for confirmation before merging to master. - Wait for review and confirmation from another developer before merging to master.
- No one will accept a pull request that doesn't have 100% test coverage.
- Commit title no more than 80 chars, empty line after. - Commit title no more than 80 chars, empty line after.
- Commit description as long as you want, 80 chars wrap. - Commit description as long as you want, 80 chars wrap.
- Keep the GitHub open issues count at less than 10.
## Issues creation. ## Issues creation.
- Issues are managed on Jira.
- Discuss issues on public Slack chats, discuss code in pull requests. - Discuss issues on public Slack chats, discuss code in pull requests.
- Organize features on Trello. - Open a small Jira issue only when it has been discussed.
- Open a small github issue only when it has been discussed.

View File

@ -46,11 +46,26 @@ $ ./do compile:all
$ ./do test:unit $ ./do test:unit
``` ```
- JS tests (using Mocha):
```sh
$ ./do test:javascript
```
- Debug tests: - Debug tests:
```sh ```sh
$ ./do test:debug $ ./do test:debug
``` ```
- Code linters and quality checkers:
```sh
$ ./do qa
```
- Javascript linter:
```sh
$ ./do lint:javascript
```
# CSS # CSS
- [Stylus](https://learnboost.github.io/stylus/) - [Stylus](https://learnboost.github.io/stylus/)
- [Nib extension](http://tj.github.io/nib/) - [Nib extension](http://tj.github.io/nib/)
@ -109,6 +124,7 @@ Once javascript is compiled with `./do compile:javascript`, your module will be
```php ```php
__() __()
_n() _n()
_x()
``` ```
```html ```html
@ -129,19 +145,38 @@ _n()
You can use Twig i18n functions in Handlebars, just load your template from a Twig view. You can use Twig i18n functions in Handlebars, just load your template from a Twig view.
# Build
To build a plugin , run `./build.sh`.
Some build process steps are described below (their dependencies etc.).
## packtranslations step
This step imports translations from Transifex and generates MO files. It requires:
* `tx` client: https://docs.transifex.com/client/installing-the-client
* `msgfmt` command (from Gettext package)
Finally , a `WP_TRANSIFEX_API_TOKEN` environment variable should be initialized with a valid key.
# Publish # Publish
Before you run a publishing command, you need to: The `publish` command currently does the following:
* Pushes translations POT file to Transifex;
* Publishes the release in SVN.
Before you run it, you need to:
1. Ensure there is an up-to-date local copy of MailPoet SVN repository in `.mp_svn` directory by running `./do svn:checkout`. 1. Ensure there is an up-to-date local copy of MailPoet SVN repository in `.mp_svn` directory by running `./do svn:checkout`.
2. Have all your features merged in Git `master`, your `mailpoet.php` and `readme.txt` tagged with a new version. 2. Have all your features merged in Git `master`, your `mailpoet.php` and `readme.txt` tagged with a new version.
3. Run `./build.sh` to produce a `mailpoet.zip` distributable archive. 3. Run `./build.sh` to produce a `mailpoet.zip` distributable archive.
Everything's ready? Then run `./do svn:publish`. Everything's ready? Then run `./do publish`.
If the job goes fine, you'll get a message like this: If the job goes fine, you'll get a message like this:
``` ```
Go to '.mp_svn' and run 'svn ci -m "Release 3.0.0-beta.9"' to publish the Go to '.mp_svn' and run 'svn ci -m "Release 3.0.0-beta.9"' to publish the
release release
Run 'svn copy ...' to tag the release
``` ```
It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process. It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process.
If you're confident, execute `./do svn:publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables. If you're confident, execute `./do publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables.

View File

@ -1,7 +1,6 @@
<?php <?php
class RoboFile extends \Robo\Tasks { class RoboFile extends \Robo\Tasks {
function install() { function install() {
return $this->taskExecStack() return $this->taskExecStack()
->stopOnFail() ->stopOnFail()
@ -67,18 +66,28 @@ class RoboFile extends \Robo\Tasks {
$this->_exec('./node_modules/webpack/bin/webpack.js --watch'); $this->_exec('./node_modules/webpack/bin/webpack.js --watch');
} }
function compileAll() { function compileAll($opts = ['env' => null]) {
$collection = $this->collectionBuilder(); $collection = $this->collectionBuilder();
$collection->addCode(array($this, 'compileJs')); $collection->addCode(function() use ($opts) {
$collection->addCode(array($this, 'compileCss')); return call_user_func(array($this, 'compileJs'), $opts);
});
$collection->addCode(function() use ($opts) {
return call_user_func(array($this, 'compileCss'), $opts);
});
return $collection->run(); return $collection->run();
} }
function compileJs() { function compileJs($opts = ['env' => null]) {
return $this->_exec('./node_modules/webpack/bin/webpack.js --bail'); $env = ($opts['env']) ?
sprintf('./node_modules/cross-env/dist/bin/cross-env.js NODE_ENV="%s"', $opts['env']) :
null;
return $this->_exec($env . ' ./node_modules/webpack/bin/webpack.js --bail');
} }
function compileCss() { function compileCss($opts = ['env' => null]) {
// Clean up folder from previous files
array_map('unlink', glob("assets/css/*.*"));
$css_files = array( $css_files = array(
'assets/css/src/admin.styl', 'assets/css/src/admin.styl',
'assets/css/src/newsletter_editor/newsletter_editor.styl', 'assets/css/src/newsletter_editor/newsletter_editor.styl',
@ -87,7 +96,7 @@ class RoboFile extends \Robo\Tasks {
'assets/css/src/importExport.styl' 'assets/css/src/importExport.styl'
); );
return $this->_exec(join(' ', array( $compilation_result = $this->_exec(join(' ', array(
'./node_modules/stylus/bin/stylus', './node_modules/stylus/bin/stylus',
'--include ./node_modules', '--include ./node_modules',
'--include-css', '--include-css',
@ -95,6 +104,25 @@ class RoboFile extends \Robo\Tasks {
join(' ', $css_files), join(' ', $css_files),
'-o assets/css/' '-o assets/css/'
))); )));
// Create manifest file
$manifest = [];
foreach(glob('assets/css/*.css') as $style) {
// Hash and rename styles if production environment
if($opts['env'] === 'production') {
$hashed_style = sprintf(
'%s.%s.css',
pathinfo($style)['filename'],
substr(md5_file($style), 0, 8)
);
$manifest[basename($style)] = $hashed_style;
rename($style, str_replace(basename($style), $hashed_style, $style));
} else {
$manifest[basename($style)] = basename($style);
}
}
file_put_contents('assets/css/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT));
return $compilation_result;
} }
function makepot() { function makepot() {
@ -104,6 +132,26 @@ class RoboFile extends \Robo\Tasks {
); );
} }
function pushpot() {
return $this->collectionBuilder()
->addCode(array($this, 'txinit'))
->taskExec('tx push -s')
->run();
}
function packtranslations() {
return $this->collectionBuilder()
->addCode(array($this, 'txinit'))
->taskExec('./tasks/pack_translations.sh')
->run();
}
function txinit() {
// Define WP_TRANSIFEX_API_TOKEN env. variable
$this->loadEnv();
return $this->_exec('./tasks/transifex_init.sh');
}
function testUnit($opts=['file' => null, 'xml' => false]) { function testUnit($opts=['file' => null, 'xml' => false]) {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build'); $this->_exec('vendor/bin/codecept build');
@ -151,10 +199,16 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec($command); return $this->_exec($command);
} }
function testDebug() { function testDebug($opts=['file' => null, 'xml' => false]) {
$this->_exec('vendor/bin/codecept build');
$this->loadEnv(); $this->loadEnv();
return $this->_exec('vendor/bin/codecept run unit --debug'); $this->_exec('vendor/bin/codecept build');
$command = 'vendor/bin/codecept run unit --debug -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
} }
function testFailed() { function testFailed() {
@ -169,6 +223,7 @@ class RoboFile extends \Robo\Tasks {
$collection->addCode(function() { $collection->addCode(function() {
return $this->qaCodeSniffer('all'); return $this->qaCodeSniffer('all');
}); });
$collection->addCode(array($this, 'qaLintJavascript'));
return $collection->run(); return $collection->run();
} }
@ -176,24 +231,58 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec('./tasks/php_lint.sh lib/ tests/ mailpoet.php'); return $this->_exec('./tasks/php_lint.sh lib/ tests/ mailpoet.php');
} }
function qaLintJavascript() {
return $this->_exec('npm run lint');
}
function qaCodeSniffer($severity='errors') { function qaCodeSniffer($severity='errors') {
if ($severity === 'all') { if ($severity === 'all') {
$severityFlag = '-w'; $severityFlag = '-w';
} else { } else {
$severityFlag = '-n'; $severityFlag = '-n';
} }
return $this->_exec( return $this->collectionBuilder()
'./vendor/bin/phpcs '. ->taskExec(
'--standard=./tasks/code_sniffer/MailPoet '. './vendor/bin/phpcs '.
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'. '--standard=./tasks/code_sniffer/MailPoet '.
'./lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '. '--runtime-set testVersion 5.3-7.0 '.
'lib/ '. '--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
$severityFlag './lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '.
); 'lib/ '.
$severityFlag
)
->taskExec(
'./vendor/bin/phpcs '.
'--standard=./tasks/code_sniffer/MailPoet '.
'--runtime-set testVersion 5.4-7.0 '.
'--ignore=./tests/unit/_bootstrap.php '.
'tests/unit/ '.
$severityFlag
)
->run();
} }
function svnCheckout() { function svnCheckout() {
return $this->_exec('svn co https://plugins.svn.wordpress.org/mailpoet/ .mp_svn'); $svn_dir = ".mp_svn";
$collection = $this->collectionBuilder();
// Clean up the SVN dir for faster shallow checkout
if(file_exists($svn_dir)) {
$collection->taskExecStack()
->exec('rm -rf ' . $svn_dir);
}
$collection->taskFileSystemStack()
->mkdir($svn_dir);
return $collection->taskExecStack()
->stopOnFail()
->dir($svn_dir)
->exec('svn co https://plugins.svn.wordpress.org/mailpoet/ -N .')
->exec('svn up trunk')
->exec('svn up assets')
->run();
} }
function svnPublish($opts = ['force' => false]) { function svnPublish($opts = ['force' => false]) {
@ -203,8 +292,13 @@ class RoboFile extends \Robo\Tasks {
$plugin_data = get_plugin_data('mailpoet.php', false, false); $plugin_data = get_plugin_data('mailpoet.php', false, false);
$plugin_version = $plugin_data['Version']; $plugin_version = $plugin_data['Version'];
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']); $plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
$plugin_dist_name = explode('-', $plugin_dist_name);
$plugin_dist_name = $plugin_dist_name[0];
$plugin_dist_file = $plugin_dist_name . '.zip'; $plugin_dist_file = $plugin_dist_name . '.zip';
$this->say('name: '. $plugin_dist_name);
return;
$this->say('Publishing version: ' . $plugin_version); $this->say('Publishing version: ' . $plugin_version);
// Sanity checks // Sanity checks
@ -273,15 +367,15 @@ class RoboFile extends \Robo\Tasks {
// Remove files from SVN repo that have already been removed locally // Remove files from SVN repo that have already been removed locally
->exec("svn st | grep ^! | awk '$awkCmd' | xargs $xargsFlag svn rm") ->exec("svn st | grep ^! | awk '$awkCmd' | xargs $xargsFlag svn rm")
// Recursively add files to SVN that haven't been added yet // Recursively add files to SVN that haven't been added yet
->exec("svn add --force * --auto-props --parents --depth infinity -q") ->exec("svn add --force * --auto-props --parents --depth infinity -q");
// Tag the release
->exec("svn cp trunk tags/$plugin_version");
$result = $collection->run(); $result = $collection->run();
if($result->wasSuccessful()) { if($result->wasSuccessful()) {
// Run or suggest release command depending on a flag // Run or suggest release command depending on a flag
$repo_url = "https://plugins.svn.wordpress.org/$plugin_dist_name";
$release_cmd = "svn ci -m \"Release $plugin_version\""; $release_cmd = "svn ci -m \"Release $plugin_version\"";
$tag_cmd = "svn copy $repo_url/trunk $repo_url/tags/$plugin_version -m \"Tag $plugin_version\"";
if(!empty($opts['force'])) { if(!empty($opts['force'])) {
$svn_login = getenv('WP_SVN_USERNAME'); $svn_login = getenv('WP_SVN_USERNAME');
$svn_password = getenv('WP_SVN_PASSWORD'); $svn_password = getenv('WP_SVN_PASSWORD');
@ -294,17 +388,30 @@ class RoboFile extends \Robo\Tasks {
->stopOnFail() ->stopOnFail()
->dir($svn_dir) ->dir($svn_dir)
->exec($release_cmd) ->exec($release_cmd)
->exec($tag_cmd)
->run(); ->run();
} else { } else {
$this->yell( $this->yell(
"Go to '$svn_dir' and run '$release_cmd' to publish the release" "Go to '$svn_dir' and run '$release_cmd' to publish the release"
); );
$this->yell(
"Run '$tag_cmd' to tag the release"
);
} }
} }
return $result; return $result;
} }
public function publish($opts = ['force' => false]) {
return $this->collectionBuilder()
->addCode(array($this, 'pushpot'))
->addCode(function () use ($opts) {
return $this->svnPublish($opts);
})
->run();
}
protected function loadEnv() { protected function loadEnv() {
$dotenv = new Dotenv\Dotenv(__DIR__); $dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load(); $dotenv->load();

View File

@ -22,3 +22,8 @@
@require 'progress_bar' @require 'progress_bar'
@require 'subscribers' @require 'subscribers'
@require 'pages'
@require 'pages_custom'
@require 'mp2migrator'

View File

@ -17,6 +17,13 @@ a:focus
.mailpoet_hidden .mailpoet_hidden
display: none display: none
// add margins to a div
.mailpoet_spaced_block
margin: 1em 0
.mailpoet_centered
text-align: center
// select 2 // select 2
.select2-container .select2-container
width: 25em !important width: 25em !important

View File

@ -1,3 +1,53 @@
$excellent-badge-color = #2993ab
$good-badge-color = #f0b849
$bad-badge-color = #d54e21
$green-badge-color = #55bd56
#newsletters_container #newsletters_container
h2.nav-tab-wrapper h2.nav-tab-wrapper
margin-bottom: 1rem margin-bottom: 1rem
.mailpoet_stats_text
font-size: 14px
font-weight: 600;
.mailpoet_stat
&_excellent
color: $excellent-badge-color
&_good
color: $good-badge-color
&_bad
color: $bad-badge-color
&_hidden
display: none
&_link_small
text-decoration: underline !important
font-size: 0.75rem
.mailpoet_badge
padding: 4px 6px 3px 6px
color: #FFFFFF
margin-right: 4px
text-transform: uppercase
font-size: 0.5625rem
font-weight: 500
border-radius: 3px
letter-spacing: 1px
vertical-align: middle
&_excellent
background: $excellent-badge-color
&_good
background: $good-badge-color
&_bad
background: $bad-badge-color
&_green
background: $green-badge-color

View File

@ -0,0 +1,33 @@
#logger
width: 100%
height: 300px
background-color: transparent
border: 0
border-top: 1px #aba9a9 solid
padding: 2px
overflow: scroll
resize: both
font-size: 0.85em
margin-top: 20px
#progressbar
width: 50%
background-color: #d8d8d8
border-radius: 5px
progressbar_color = #fecf23
progressbar_gradient_to_color = #fd9215
.ui-progressbar .ui-progressbar-value
height: 100%
background-color: progressbar_color
background-image: linear-gradient(to bottom, progressbar_color, progressbar_gradient_to_color)
border-radius: 3px
box-shadow: 0 1px 0 rgba(255,255,255,0.5) inset
border 0
.mailpoet_progress_label
font-size: 15px
.error_msg
color: #f00

View File

@ -62,3 +62,9 @@ $draggable-widget-z-index = 2
background-color: $primary-active-color background-color: $primary-active-color
box-shadow(inset 1px 2px 1px $primary-inset-shadow-color) box-shadow(inset 1px 2px 1px $primary-inset-shadow-color)
color: $white-color color: $white-color
.mailpoet_droppable_block
cursor: move
&.mailpoet_ignore_drag
cursor: auto

View File

@ -26,10 +26,6 @@ $block-text-line-height = $text-line-height
&.mailpoet_highlight > .mailpoet_block_highlight &.mailpoet_highlight > .mailpoet_block_highlight
border: 1px dashed $block-hover-highlight-color border: 1px dashed $block-hover-highlight-color
.mailpoet_block:last-child
margin-bottom: 0
.mailpoet_content .mailpoet_content
position: relative position: relative
line-height: $block-text-line-height line-height: $block-text-line-height

View File

@ -18,6 +18,7 @@
.mailpoet_settings_posts_display_options .mailpoet_settings_posts_display_options
.mailpoet_settings_posts_selection .mailpoet_settings_posts_selection
animation-slide-open-downwards() animation-slide-open-downwards()
overflow-x: hidden
.mailpoet_settings_posts_show_display_options, .mailpoet_settings_posts_show_display_options,
.mailpoet_settings_posts_show_post_selection .mailpoet_settings_posts_show_post_selection
@ -26,7 +27,12 @@
.mailpoet_post_selection_container .mailpoet_post_selection_container
margin-top: 20px margin-top: 20px
margin-bottom: 20px margin-bottom: 0
.mailpoet_post_scroll_container
overflow-y: scroll
overflow-x: hidden
max-height: 400px
.mailpoet_settings_posts_single_post .mailpoet_settings_posts_single_post
border-radius(1px) border-radius(1px)
@ -45,3 +51,6 @@
.mailpoet_select_post_checkbox .mailpoet_select_post_checkbox
margin-left: 10px margin-left: 10px
margin-right: 8px margin-right: 8px
.mailpoet_post_selection_loading
color: #999

View File

@ -131,6 +131,7 @@ body
display: none display: none
.wrap > .mailpoet_notice, .wrap > .mailpoet_notice,
.notice
.update-nag .update-nag
margin-left: 2px + 15px !important margin-left: 2px + 15px !important

228
assets/css/src/pages.styl Normal file
View File

@ -0,0 +1,228 @@
/*
Based on /wp-admin/css/about.css of WP 4.7.
This is to make MailPoet pages independent of the WordPress
About page styles that may differ across WP versions.
Please add custom styles to pages_custom.styl
*/
.mailpoet-about-wrap
position: relative
margin: 25px 40px 0 20px
max-width: 1050px /* readability */
font-size: 15px
div.updated, div.error, .notice
display: none !important
hr
border: 0
height: 0
margin: 0
border-top: 1px solid rgba(0, 0, 0, 0.1)
img
margin: 0
max-width: 100%
height: auto
vertical-align: middle
.mailpoet-logo
position: absolute
top: .2em
right: 0;
.nav-tab
padding-right: 15px
padding-left: 15px
font-size: 18px
p
line-height: 1.5
font-size: 14px
.feature-section p
max-width: 55em
margin-left: auto
margin-right: auto
h1
margin: 0.2em 200px 0 0
padding: 0
color: #32373c
line-height: 1.2em
font-size: 2.8em
font-weight: 400
h2
margin: 40px 0 .6em
font-size: 2.7em
line-height: 1.3
font-weight: 300
text-align: center
h3
margin: 1.25em 0 .6em
font-size: 1.4em
line-height: 1.5
h4
color: #23282d
.about-description
.about-text
margin-top: 1.4em
font-weight: 400
line-height: 1.6em
font-size: 19px
.about-text
margin: 1em 200px 1em 0
min-height: 60px
color: #555d66
[class$=col]
.col
float: left
position: relative
.two-col
.col
margin-right: 4.799999999%
width: 47.6%
.two-col
img
margin-bottom: 1.5em
.feature-section
&.two-col
.col
display: inline-block
float: none
margin-top: 1em
margin-right: 4.799999999%
width: calc( 47.6% - 4px )
vertical-align: top
.three-col
.col
margin-right: 4.999999999%
width: 29.95%
.two-col .col:nth-of-type(2n)
.three-col .col:nth-of-type(3n)
margin-right: 0
.feature-section
&.two-col
h3
margin-top: 0
.feature-section
h4
margin: 1.4em 0 0.6em 0
font-size: 1em
.feature-section
p
margin-top: 0.6em
.lead-description
font-size: 1.5em
text-align: center
.two-col-text
column-count: 2
column-gap: 40px
.two-col-text
p:first-of-type
margin-top: 0
.headline-feature
&.feature-video
position: relative
margin: 40px 0
padding-bottom: 56.25%
width: 100%
max-width: 100%
height: 0
text-align: center
.feature-video
embed
position: absolute
top: 0
left: 0
width: 100%
height: 100%
.featured-image
text-align: center
.feature-section
overflow: hidden
padding: 0 0 40px
.feature-section
&.no-heading
padding-top: 35px
.headline-feature
margin: 0 auto
max-width: 80%
.feature-section
.media-container
overflow: hidden
.feature-section
img
margin-bottom: 1em
.embed-container
text-align: center
.embed-container
iframe
max-width: 100%
.wp-embedded-content
max-width: 100%
.feature-section
.col
margin-top: 40px
.changelog
margin-bottom: 40px
.changelog
&.feature-section
.col
margin-top: 40px
@media screen and (max-width: 782px)
.two-col-text
column-count: 1
.three-col img
display: block
margin: 0 auto
@media only screen and (max-width: 500px)
margin-right: 20px
margin-left: 10px
h1
.about-text
margin-right: 0
.about-text
margin-bottom: 0.25em
.mailpoet-logo
position: relative
margin: 1em 0
width: 100%
text-align: center
.two-col .col
.three-col .col
width: 100% !important
float: none !important

View File

@ -0,0 +1,35 @@
/*
Custom styles for MailPoet pages.
*/
.mailpoet-about-wrap
.videoWrapper
position: relative
padding-bottom: 56.25% /* 16:9 */
/*padding-top: 25px*/
height: 0
iframe
position: absolute
top: 0
left: 0
width: 100%
height: 100%
.mailpoet_video
border: 1px solid rgba(0, 0, 0, 0.1)
#mailpoet-changelog ul
list-style: disc
padding-left: 20px
h2.mailpoet-feature-top
margin: 50px auto
a.button.go-to-plugin
margin-top: 2em
p.top-space-triple
margin-top: 3em

View File

@ -4,6 +4,7 @@
padding: 0 padding: 0
width: 100% width: 100%
margin: 0 margin: 0
margin-bottom: 10px
border-radius: 5px border-radius: 5px
position: relative position: relative
@ -25,5 +26,5 @@
.mailpoet_progress_complete .mailpoet_progress_complete
.mailpoet_progress_bar .mailpoet_progress_bar
background-color: #fecf23 background-color: hsla(191, 78%, 80%, 1)
background-image: linear-gradient(top, #fecf23, #fd9215) background-image: linear-gradient(top, hsla(191, 78%, 80%, 1), hsla(191, 76%, 67%, 1))

View File

@ -9,29 +9,38 @@
// sending methods // sending methods
.mailpoet_sending_methods .mailpoet_sending_methods
margin 25px 0 0 0 margin 25px 0 0 0
li display flex
float left flex-direction row
position relative justify-content flex-start
padding 15px 15px 0 15px > li
flex-grow 1
flex-shrink 1
display flex
flex-direction column
flex-basis 0
margin 0 25px 25px 0 margin 0 25px 25px 0
width 300px
height 300px
border 1px solid #dedede border 1px solid #dedede
background-color #fff background-color #fff
max-width 500px
.mailpoet_sending_method_description
padding: 25px
flex-grow 1
flex-shrink 0
> li:last-child
margin-right 0
h3 h3
text-align center text-align center
height 54px height 54px
line-height 54px
font-size 1.5em font-size 1.5em
.mailpoet_description .mailpoet_description
font-size 14px font-size 14px
.mailpoet_status .mailpoet_status
display flex
flex-direction row
justify-content space-between
align-items center
background-color #2f2f2f background-color #2f2f2f
color #fff color #fff
position absolute
bottom 0
left 0
right 0
text-overflow ellipsis text-overflow ellipsis
padding 15px padding 15px
span span
@ -39,18 +48,22 @@
font-weight bold font-weight bold
.mailpoet_active .mailpoet_active
.mailpoet_status .mailpoet_status
background-color #088b00
span span
visibility visible visibility visible
#mailpoet_mta_activate #mailpoet_mta_activate
visibility hidden visibility hidden
.mailpoet_actions
bottom 15px ul.sending-method-benefits
color #fff list-style-type: none
padding 0 margin-bottom: 2em
position absolute margin-top: 2em
right 15px
.button-secondary .mailpoet_success_item::before
margin 0 -6px -4px 0 content ' '
.mailpoet_error_item::before
content ' '
// responsive // responsive
@media screen and (max-width: 782px) @media screen and (max-width: 782px)
@ -58,8 +71,10 @@
width auto width auto
.mailpoet_sending_methods .mailpoet_sending_methods
li flex-flow: row wrap
float none justify-content: space-around
width auto > li
margin-right 0 margin-right 0
flex-basis auto

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

View File

@ -1,8 +1,15 @@
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); (function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]); 0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("f683d388fb25fcf331f1b2b5c4449798"); window.mixpanelTrackingId = "8cce373b255e5a76fb22d57b85db0c92";
if (mailpoet_analytics_enabled) {
mixpanel.init(window.mixpanelTrackingId);
if (mailpoet_analytics_data != null) {
mixpanel.track('MailPoet 3', mailpoet_analytics_data);
}
if (typeof mailpoet_analytics_data === 'object') {
mixpanel.track('Wysija Usage', mailpoet_analytics_data || {});
} }

View File

@ -1,72 +1,85 @@
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) { function requestFailed(errorMessage, xhr) {
'use strict'; if (xhr.responseJSON) {
MailPoet.Ajax = { return xhr.responseJSON;
version: 0.5, }
options: {}, var message = errorMessage.replace("%d", xhr.status);
defaults: { return {
url: null, errors: [
endpoint: null, {
action: null, message: message
token: null, }
data: {} ]
}, };
post: function(options) { }
return this.request('post', options);
}, define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) {
init: function(options) {
// merge options MailPoet.Ajax = {
this.options = jQuery.extend({}, this.defaults, options); version: 0.5,
options: {},
// set default url defaults: {
if(this.options.url === null) { url: null,
this.options.url = ajaxurl; api_version: null,
} endpoint: null,
action: null,
// set default token token: null,
if(this.options.token === null) { data: {}
this.options.token = window.mailpoet_token; },
} post: function(options) {
}, return this.request('post', options);
getParams: function() { },
return { init: function(options) {
action: 'mailpoet', // merge options
token: this.options.token, this.options = jQuery.extend({}, this.defaults, options);
endpoint: this.options.endpoint,
method: this.options.action, // set default url
data: this.options.data || {} if(this.options.url === null) {
} this.options.url = ajaxurl;
}, }
request: function(method, options) {
// set options // set default token
this.init(options); if(this.options.token === null) {
this.options.token = window.mailpoet_token;
// set request params }
var params = this.getParams(); },
var deferred = jQuery.Deferred(); getParams: function() {
return {
// remove null values from the data object action: 'mailpoet',
if (_.isObject(params.data)) { api_version: this.options.api_version,
params.data = _.pick(params.data, function(value) { token: this.options.token,
return (value !== null) endpoint: this.options.endpoint,
}) method: this.options.action,
} data: this.options.data || {}
};
// ajax request },
deferred = jQuery.post( request: function(method, options) {
this.options.url, // set options
params, this.init(options);
null,
'json' // set request params
).then(function(data) { var params = this.getParams();
return data;
}, function(xhr) { // remove null values from the data object
return xhr.responseJSON; if (_.isObject(params.data)) {
}); params.data = _.pick(params.data, function(value) {
return (value !== null);
// clear options });
this.options = {}; }
return deferred; // ajax request
} var deferred = jQuery.post(
}; this.options.url,
}); params,
null,
'json'
).then(function(data) {
return data;
}, _.partial(requestFailed, MailPoet.I18n.t('ajaxFailedErrorMessage')));
// clear options
this.options = {};
return deferred;
}
};
});

View File

@ -0,0 +1,71 @@
/*
* This creates two functions and adds them to MailPoet object
* - `trackEvent` which should be used in normal circumstances.
* This function tracks an event and sends it to mixpanel.
* This function does nothing if analytics is disabled.
* - `forceTrackEvent` which sends given event to analytics
* even if it has been disabled.
*
*/
/**
* This is to cache events which are triggered before the mixpanel
* library is loaded. This might happen if an event is tracked
* on page load and the mixpanel library takes a long time to load.
* After it is loaded all events are posted.
* @type {Array.Object}
*/
var eventsCache = [];
function track(name, data){
if (typeof window.mixpanel.track !== "function") {
window.mixpanel.init(window.mixpanelTrackingId);
}
window.mixpanel.track(name, data);
}
function exportMixpanel(MailPoet) {
MailPoet.forceTrackEvent = track;
if (window.mailpoet_analytics_enabled) {
MailPoet.trackEvent = track;
} else {
MailPoet.trackEvent = function () {};
}
}
function trackCachedEvents() {
eventsCache.map(function (event) {
if (window.mailpoet_analytics_enabled || event.forced) {
window.mixpanel.track(event.name, event.data)
}
});
}
function initializeMixpanelWhenLoaded() {
if (typeof window.mixpanel === "object") {
exportMixpanel(MailPoet);
trackCachedEvents();
} else {
setTimeout(initializeMixpanelWhenLoaded, 100);
}
}
function cacheEvent(forced, name, data) {
eventsCache.push({
name: name,
data: data,
forced: forced
});
}
define(
['mailpoet', 'underscore'],
function(MailPoet, _) {
MailPoet.trackEvent = _.partial(cacheEvent, false);
MailPoet.forceTrackEvent = _.partial(cacheEvent, true);
initializeMixpanelWhenLoaded();
}
);

View File

@ -1,15 +1,15 @@
define([ define([
'react' 'react',
], ],
function( (
React React
) { ) => {
const FormFieldCheckbox = React.createClass({ const FormFieldCheckbox = React.createClass({
onValueChange: function(e) { onValueChange: function (e) {
e.target.value = this.refs.checkbox.checked ? '1' : '0'; e.target.value = this.refs.checkbox.checked ? '1' : '0';
return this.props.onValueChange(e); return this.props.onValueChange(e);
}, },
render: function() { render: function () {
if (this.props.field.values === undefined) { if (this.props.field.values === undefined) {
return false; return false;
} }
@ -42,8 +42,8 @@ function(
{ options } { options }
</div> </div>
); );
} },
}); });
return FormFieldCheckbox; return FormFieldCheckbox;
}); });

View File

@ -1,10 +1,10 @@
define([ define([
'react', 'react',
'moment', 'moment',
], function( ], (
React, React,
Moment Moment
) { ) => {
class FormFieldDateYear extends React.Component { class FormFieldDateYear extends React.Component {
render() { render() {
const yearsRange = 100; const yearsRange = 100;
@ -17,7 +17,7 @@ define([
} }
const currentYear = Moment().year(); const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i--) { for (let i = currentYear; i >= currentYear - yearsRange; i -= 1) {
years.push(( years.push((
<option <option
key={ i } key={ i }
@ -47,7 +47,7 @@ define([
)); ));
} }
for (let i = 1; i <= 12; i++) { for (let i = 1; i <= 12; i += 1) {
months.push(( months.push((
<option <option
key={ i } key={ i }
@ -77,7 +77,7 @@ define([
)); ));
} }
for (let i = 1; i <= 31; i++) { for (let i = 1; i <= 31; i += 1) {
days.push(( days.push((
<option <option
key={ i } key={ i }
@ -104,13 +104,13 @@ define([
this.state = { this.state = {
year: '', year: '',
month: '', month: '',
day: '' day: '',
} };
} }
componentDidMount() { componentDidMount() {
this.extractDateParts(); this.extractDateParts();
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps) {
if ( if (
(this.props.item !== undefined && prevProps.item !== undefined) (this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id) && (this.props.item.id !== prevProps.item.id)
@ -132,7 +132,7 @@ define([
this.setState({ this.setState({
year: dateTime.format('YYYY'), year: dateTime.format('YYYY'),
month: dateTime.format('M'), month: dateTime.format('M'),
day: dateTime.format('D') day: dateTime.format('D'),
}); });
} }
formatValue() { formatValue() {
@ -145,28 +145,28 @@ define([
value = { value = {
'year': this.state.year, 'year': this.state.year,
'month': this.state.month, 'month': this.state.month,
'day': this.state.day 'day': this.state.day,
}; };
break; break;
case 'year_month': case 'year_month':
value = { value = {
'year': this.state.year, 'year': this.state.year,
'month': this.state.month 'month': this.state.month,
}; };
break; break;
case 'month': case 'month':
value = { value = {
'month': this.state.month 'month': this.state.month,
}; };
break; break;
case 'year': case 'year':
value = { value = {
'year': this.state.year 'year': this.state.year,
}; };
break; break;
} }
return value; return value;
@ -181,16 +181,16 @@ define([
field = matches[1]; field = matches[1];
property = matches[2]; property = matches[2];
let value = ~~(e.target.value); const value = ~~(e.target.value);
this.setState({ this.setState({
[`${property}`]: value [`${property}`]: value,
}, () => { }, () => {
this.props.onValueChange({ this.props.onValueChange({
target: { target: {
name: field, name: field,
value: this.formatValue() value: this.formatValue(),
} },
}); });
}); });
} }
@ -201,7 +201,7 @@ define([
const dateType = this.props.field.params.date_type; const dateType = this.props.field.params.date_type;
const dateSelects = dateFormats[dateType][0].split('/'); const dateSelects = dateFormats[dateType][0].split('/');
const fields = dateSelects.map(type => { const fields = dateSelects.map((type) => {
switch(type) { switch(type) {
case 'YYYY': case 'YYYY':
return (<FormFieldDateYear return (<FormFieldDateYear
@ -212,7 +212,7 @@ define([
year={ this.state.year } year={ this.state.year }
placeholder={ this.props.field.year_placeholder } placeholder={ this.props.field.year_placeholder }
/>); />);
break; break;
case 'MM': case 'MM':
return (<FormFieldDateMonth return (<FormFieldDateMonth
@ -224,7 +224,7 @@ define([
monthNames={ monthNames } monthNames={ monthNames }
placeholder={ this.props.field.month_placeholder } placeholder={ this.props.field.month_placeholder }
/>); />);
break; break;
case 'DD': case 'DD':
return (<FormFieldDateDay return (<FormFieldDateDay
@ -235,9 +235,9 @@ define([
day={ this.state.day } day={ this.state.day }
placeholder={ this.props.field.day_placeholder } placeholder={ this.props.field.day_placeholder }
/>); />);
break; break;
} }
}); });
return ( return (
<div> <div>
@ -248,4 +248,4 @@ define([
}; };
return FormFieldDate; return FormFieldDate;
}); });

View File

@ -8,7 +8,7 @@ define([
'form/fields/selection.jsx', 'form/fields/selection.jsx',
'form/fields/date.jsx', 'form/fields/date.jsx',
], ],
function( (
React, React,
FormFieldText, FormFieldText,
FormFieldTextarea, FormFieldTextarea,
@ -17,17 +17,17 @@ function(
FormFieldCheckbox, FormFieldCheckbox,
FormFieldSelection, FormFieldSelection,
FormFieldDate FormFieldDate
) { ) => {
var FormField = React.createClass({ const FormField = React.createClass({
renderField: function(data, inline = false) { renderField: function (data, inline = false) {
var description = false; let description = false;
if(data.field.description) { if(data.field.description) {
description = ( description = (
<p className="description">{ data.field.description }</p> <p className="description">{ data.field.description }</p>
); );
} }
var field = false; let field = false;
if(data.field['field'] !== undefined) { if(data.field['field'] !== undefined) {
data.field = jQuery.merge(data.field, data.field.field); data.field = jQuery.merge(data.field, data.field.field);
@ -36,35 +36,35 @@ function(
switch(data.field.type) { switch(data.field.type) {
case 'text': case 'text':
field = (<FormFieldText {...data} />); field = (<FormFieldText {...data} />);
break; break;
case 'textarea': case 'textarea':
field = (<FormFieldTextarea {...data} />); field = (<FormFieldTextarea {...data} />);
break; break;
case 'select': case 'select':
field = (<FormFieldSelect {...data} />); field = (<FormFieldSelect {...data} />);
break; break;
case 'radio': case 'radio':
field = (<FormFieldRadio {...data} />); field = (<FormFieldRadio {...data} />);
break; break;
case 'checkbox': case 'checkbox':
field = (<FormFieldCheckbox {...data} />); field = (<FormFieldCheckbox {...data} />);
break; break;
case 'selection': case 'selection':
field = (<FormFieldSelection {...data} />); field = (<FormFieldSelection {...data} />);
break; break;
case 'date': case 'date':
field = (<FormFieldDate {...data} />); field = (<FormFieldDate {...data} />);
break; break;
case 'reactComponent': case 'reactComponent':
field = (<data.field.component {...data} />); field = (<data.field.component {...data} />);
break; break;
} }
if(inline === true) { if(inline === true) {
@ -83,23 +83,23 @@ function(
); );
} }
}, },
render: function() { render: function () {
var field = false; let field = false;
if(this.props.field['fields'] !== undefined) { if(this.props.field['fields'] !== undefined) {
field = this.props.field.fields.map(function(subfield, index) { field = this.props.field.fields.map((subfield, index) => {
return this.renderField({ return this.renderField({
index: index, index: index,
field: subfield, field: subfield,
item: this.props.item, item: this.props.item,
onValueChange: this.props.onValueChange || false onValueChange: this.props.onValueChange || false,
}); });
}.bind(this)); });
} else { } else {
field = this.renderField(this.props); field = this.renderField(this.props);
} }
var tip = false; let tip = false;
if(this.props.field.tip) { if(this.props.field.tip) {
tip = ( tip = (
<p className="description">{ this.props.field.tip }</p> <p className="description">{ this.props.field.tip }</p>
@ -121,7 +121,7 @@ function(
</td> </td>
</tr> </tr>
); );
} },
}); });
return FormField; return FormField;

View File

@ -1,11 +1,11 @@
define([ define([
'react' 'react',
], ],
function( (
React React
) { ) => {
const FormFieldRadio = React.createClass({ const FormFieldRadio = React.createClass({
render: function() { render: function () {
if (this.props.field.values === undefined) { if (this.props.field.values === undefined) {
return false; return false;
} }
@ -34,8 +34,8 @@ function(
{ options } { options }
</div> </div>
); );
} },
}); });
return FormFieldRadio; return FormFieldRadio;
}); });

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react';
import _ from 'underscore' import _ from 'underscore';
const FormFieldSelect = React.createClass({ const FormFieldSelect = React.createClass({
render() { render() {
@ -33,12 +33,12 @@ const FormFieldSelect = React.createClass({
_.map( _.map(
_.sortBy( _.sortBy(
_.pairs(this.props.field.values), _.pairs(this.props.field.values),
(item) => sortBy(item[0], item[1]) item => sortBy(item[0], item[1])
), ),
(item) => item[0] item => item[0]
); );
} else { } else {
keys = Object.keys(this.props.field.values) keys = Object.keys(this.props.field.values);
} }
const options = keys.map( const options = keys.map(
@ -70,7 +70,7 @@ const FormFieldSelect = React.createClass({
{options} {options}
</select> </select>
); );
} },
}); });
module.exports = FormFieldSelect; module.exports = FormFieldSelect;

View File

@ -2,35 +2,35 @@ define([
'react', 'react',
'react-dom', 'react-dom',
'jquery', 'jquery',
'select2' 'select2',
], ],
function( (
React, React,
ReactDOM, ReactDOM,
jQuery jQuery
) { ) => {
var Selection = React.createClass({ const Selection = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
items: [], items: [],
select2: false select2: false,
}; };
}, },
componentWillMount: function() { componentWillMount: function () {
this.loadCachedItems(); this.loadCachedItems();
}, },
allowMultipleValues: function() { allowMultipleValues: function () {
return (this.props.field.multiple === true); return (this.props.field.multiple === true);
}, },
isSelect2Initialized: function() { isSelect2Initialized: function () {
return (this.state.select2 === true); return (this.state.select2 === true);
}, },
componentDidMount: function() { componentDidMount: function () {
if(this.allowMultipleValues()) { if(this.allowMultipleValues()) {
this.setupSelect2(); this.setupSelect2();
} }
}, },
componentDidUpdate: function(prevProps, prevState) { componentDidUpdate: function (prevProps) {
if( if(
(this.props.item !== undefined && prevProps.item !== undefined) (this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id) && (this.props.item.id !== prevProps.item.id)
@ -40,24 +40,24 @@ function(
.trigger('change'); .trigger('change');
} }
}, },
componentWillUnmount: function() { componentWillUnmount: function () {
if(this.allowMultipleValues()) { if(this.allowMultipleValues()) {
this.destroySelect2(); this.destroySelect2();
} }
}, },
destroySelect2: function() { destroySelect2: function () {
if(this.isSelect2Initialized()) { if(this.isSelect2Initialized()) {
jQuery('#'+this.refs.select.id).select2('destroy'); jQuery('#'+this.refs.select.id).select2('destroy');
} }
}, },
setupSelect2: function() { setupSelect2: function () {
if(this.isSelect2Initialized()) { if(this.isSelect2Initialized()) {
return; return;
} }
var select2 = jQuery('#'+this.refs.select.id).select2({ const select2 = jQuery('#'+this.refs.select.id).select2({
width: (this.props.width || ''), width: (this.props.width || ''),
templateResult: function(item) { templateResult: function (item) {
if(item.element && item.element.selected) { if(item.element && item.element.selected) {
return null; return null;
} else { } else {
@ -67,14 +67,14 @@ function(
return item.text; return item.text;
} }
} }
} },
}); });
var hasRemoved = false; let hasRemoved = false;
select2.on('select2:unselecting', function(e) { select2.on('select2:unselecting', () => {
hasRemoved = true; hasRemoved = true;
}); });
select2.on('select2:opening', function(e) { select2.on('select2:opening', (e) => {
if(hasRemoved === true) { if(hasRemoved === true) {
hasRemoved = false; hasRemoved = false;
e.preventDefault(); e.preventDefault();
@ -85,13 +85,13 @@ function(
this.setState({ select2: true }); this.setState({ select2: true });
}, },
getSelectedValues: function() { getSelectedValues: function () {
if(this.props.field['selected'] !== undefined) { if(this.props.field['selected'] !== undefined) {
return this.props.field['selected'](this.props.item); return this.props.field['selected'](this.props.item);
} else if(this.props.item !== undefined && this.props.field.name !== undefined) { } else if(this.props.item !== undefined && this.props.field.name !== undefined) {
if (this.allowMultipleValues()) { if (this.allowMultipleValues()) {
if (Array.isArray(this.props.item[this.props.field.name])) { if (Array.isArray(this.props.item[this.props.field.name])) {
return this.props.item[this.props.field.name].map(function(item) { return this.props.item[this.props.field.name].map((item) => {
return item.id; return item.id;
}); });
} }
@ -101,9 +101,9 @@ function(
} }
return null; return null;
}, },
loadCachedItems: function() { loadCachedItems: function () {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') { if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
var items = window['mailpoet_'+this.props.field.endpoint]; let items = window['mailpoet_'+this.props.field.endpoint];
if(this.props.field['filter'] !== undefined) { if(this.props.field['filter'] !== undefined) {
@ -111,39 +111,39 @@ function(
} }
this.setState({ this.setState({
items: items items: items,
}); });
} }
}, },
handleChange: function(e) { handleChange: function (e) {
if(this.props.onValueChange !== undefined) { if(this.props.onValueChange !== undefined) {
if(this.props.field.multiple) { if(this.props.field.multiple) {
value = jQuery('#'+this.refs.select.id).val(); value = jQuery('#'+this.refs.select.id).val();
} else { } else {
value = e.target.value; value = e.target.value;
} }
var transformedValue = this.transformChangedValue(value); const transformedValue = this.transformChangedValue(value);
this.props.onValueChange({ this.props.onValueChange({
target: { target: {
value: transformedValue, value: transformedValue,
name: this.props.field.name name: this.props.field.name,
} },
}); });
} }
}, },
getLabel: function(item) { getLabel: function (item) {
if(this.props.field['getLabel'] !== undefined) { if(this.props.field['getLabel'] !== undefined) {
return this.props.field.getLabel(item, this.props.item); return this.props.field.getLabel(item, this.props.item);
} }
return item.name; return item.name;
}, },
getSearchLabel: function(item) { getSearchLabel: function (item) {
if(this.props.field['getSearchLabel'] !== undefined) { if(this.props.field['getSearchLabel'] !== undefined) {
return this.props.field.getSearchLabel(item, this.props.item); return this.props.field.getSearchLabel(item, this.props.item);
} }
return null; return null;
}, },
getValue: function(item) { getValue: function (item) {
if(this.props.field['getValue'] !== undefined) { if(this.props.field['getValue'] !== undefined) {
return this.props.field.getValue(item, this.props.item); return this.props.field.getValue(item, this.props.item);
} }
@ -152,18 +152,18 @@ function(
// When it's impossible to represent the desired value in DOM, // When it's impossible to represent the desired value in DOM,
// this function may be used to transform the placeholder value into // this function may be used to transform the placeholder value into
// desired value. // desired value.
transformChangedValue: function(value) { transformChangedValue: function (value) {
if(typeof this.props.field['transformChangedValue'] === 'function') { if(typeof this.props.field['transformChangedValue'] === 'function') {
return this.props.field.transformChangedValue.call(this, value); return this.props.field.transformChangedValue.call(this, value);
} else { } else {
return value; return value;
} }
}, },
render: function() { render: function () {
const options = this.state.items.map((item, index) => { const options = this.state.items.map((item, index) => {
let label = this.getLabel(item); const label = this.getLabel(item);
let searchLabel = this.getSearchLabel(item); const searchLabel = this.getSearchLabel(item);
let value = this.getValue(item); const value = this.getValue(item);
return ( return (
<option <option
@ -186,7 +186,7 @@ function(
{...this.props.field.validation} {...this.props.field.validation}
>{ options }</select> >{ options }</select>
); );
} },
}); });
return Selection; return Selection;

View File

@ -1,4 +1,4 @@
import React from 'react' import React from 'react';
const FormFieldText = React.createClass({ const FormFieldText = React.createClass({
render() { render() {
@ -29,7 +29,7 @@ const FormFieldText = React.createClass({
{...this.props.field.validation} {...this.props.field.validation}
/> />
); );
} },
}); });
module.exports = FormFieldText; module.exports = FormFieldText;

View File

@ -1,11 +1,11 @@
define([ define([
'react' 'react',
], ],
function( (
React React
) { ) => {
var FormFieldTextarea = React.createClass({ const FormFieldTextarea = React.createClass({
render: function() { render: function () {
return ( return (
<textarea <textarea
type="text" type="text"
@ -19,8 +19,8 @@ function(
{...this.props.field.validation} {...this.props.field.validation}
/> />
); );
} },
}); });
return FormFieldTextarea; return FormFieldTextarea;
}); });

View File

@ -4,54 +4,54 @@ define(
'mailpoet', 'mailpoet',
'classnames', 'classnames',
'react-router', 'react-router',
'form/fields/field.jsx' 'form/fields/field.jsx',
], ],
function( (
React, React,
MailPoet, MailPoet,
classNames, classNames,
Router, Router,
FormField FormField
) { ) => {
var Form = React.createClass({ const Form = React.createClass({
contextTypes: { contextTypes: {
router: React.PropTypes.object.isRequired router: React.PropTypes.object.isRequired,
}, },
getDefaultProps: function() { getDefaultProps: function () {
return { return {
params: {}, params: {},
}; };
}, },
getInitialState: function() { getInitialState: function () {
return { return {
loading: false, loading: false,
errors: [], errors: [],
item: {} item: {},
}; };
}, },
getValues: function() { getValues: function () {
return this.props.item ? this.props.item : this.state.item; return this.props.item ? this.props.item : this.state.item;
}, },
getErrors: function() { getErrors: function () {
return this.props.errors ? this.props.errors : this.state.errors; return this.props.errors ? this.props.errors : this.state.errors;
}, },
componentDidMount: function() { componentDidMount: function () {
if(this.isMounted()) { if(this.isMounted()) {
if(this.props.params.id !== undefined) { if(this.props.params.id !== undefined) {
this.loadItem(this.props.params.id); this.loadItem(this.props.params.id);
} else { } else {
this.setState({ this.setState({
item: jQuery('.mailpoet_form').serializeObject() item: jQuery('.mailpoet_form').serializeObject(),
}); });
} }
} }
}, },
componentWillReceiveProps: function(props) { componentWillReceiveProps: function (props) {
if(props.params.id === undefined) { if(props.params.id === undefined) {
this.setState({ this.setState({
loading: false, loading: false,
item: {} item: {},
}); });
if (props.item === undefined) { if (props.item === undefined) {
this.refs.form.reset(); this.refs.form.reset();
@ -60,30 +60,31 @@ define(
this.loadItem(props.params.id); this.loadItem(props.params.id);
} }
}, },
loadItem: function(id) { loadItem: function (id) {
this.setState({ loading: true }); this.setState({ loading: true });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'get', action: 'get',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
this.setState({ this.setState({
loading: false, loading: false,
item: response.data item: response.data,
}); });
}).fail((response) => { }).fail(() => {
this.setState({ this.setState({
loading: false, loading: false,
item: {} item: {},
}, function() { }, function () {
this.context.router.push('/new'); this.context.router.push('/new');
}); });
}); });
}, },
handleSubmit: function(e) { handleSubmit: function (e) {
e.preventDefault(); e.preventDefault();
// handle validation // handle validation
@ -96,28 +97,29 @@ define(
this.setState({ loading: true }); this.setState({ loading: true });
// only get values from displayed fields // only get values from displayed fields
var item = {}; const item = {};
this.props.fields.map(function(field) { this.props.fields.map((field) => {
if(field['fields'] !== undefined) { if(field['fields'] !== undefined) {
field.fields.map(function(subfield) { field.fields.map((subfield) => {
item[subfield.name] = this.state.item[subfield.name]; item[subfield.name] = this.state.item[subfield.name];
}.bind(this)); });
} else { } else {
item[field.name] = this.state.item[field.name]; item[field.name] = this.state.item[field.name];
} }
}.bind(this)); });
// set id if specified // set id if specified
if(this.props.params.id !== undefined) { if(this.props.params.id !== undefined) {
item.id = this.props.params.id; item.id = this.props.params.id;
} }
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'save', action: 'save',
data: item data: item,
}).always(() => { }).always(() => {
this.setState({ loading: false }); this.setState({ loading: false });
}).done((response) => { }).done(() => {
if(this.props.onSuccess !== undefined) { if(this.props.onSuccess !== undefined) {
this.props.onSuccess(); this.props.onSuccess();
} else { } else {
@ -135,24 +137,25 @@ define(
} }
}); });
}, },
handleValueChange: function(e) { handleValueChange: function (e) {
if (this.props.onChange) { if (this.props.onChange) {
return this.props.onChange(e); return this.props.onChange(e);
} else { } else {
var item = this.state.item, const item = this.state.item;
field = e.target.name; const field = e.target.name;
item[field] = e.target.value; item[field] = e.target.value;
this.setState({ this.setState({
item: item item: item,
}); });
return true; return true;
} }
}, },
render: function() { render: function () {
let errors;
if(this.getErrors() !== undefined) { if(this.getErrors() !== undefined) {
var errors = this.getErrors().map(function(error, index) { errors = this.getErrors().map((error, index) => {
return ( return (
<p key={ 'error-'+index } className="mailpoet_error"> <p key={ 'error-'+index } className="mailpoet_error">
{ error.message } { error.message }
@ -161,13 +164,13 @@ define(
}); });
} }
var formClasses = classNames( const formClasses = classNames(
'mailpoet_form', 'mailpoet_form',
{ 'mailpoet_form_loading': this.state.loading || this.props.loading } { 'mailpoet_form_loading': this.state.loading || this.props.loading }
); );
var beforeFormContent = false; let beforeFormContent = false;
var afterFormContent = false; let afterFormContent = false;
if (this.props.beforeFormContent !== undefined) { if (this.props.beforeFormContent !== undefined) {
beforeFormContent = this.props.beforeFormContent(this.getValues()); beforeFormContent = this.props.beforeFormContent(this.getValues());
@ -177,17 +180,26 @@ define(
afterFormContent = this.props.afterFormContent(this.getValues()); afterFormContent = this.props.afterFormContent(this.getValues());
} }
var fields = this.props.fields.map(function(field, i) { const fields = this.props.fields.map((field, i) => {
// Compose an onChange handler from the default and custom one
let onValueChange = this.handleValueChange;
if (field.onBeforeChange) {
onValueChange = (e) => {
field.onBeforeChange(e);
return this.handleValueChange(e);
};
}
return ( return (
<FormField <FormField
field={ field } field={ field }
item={ this.getValues() } item={ this.getValues() }
onValueChange={ this.handleValueChange } onValueChange={ onValueChange }
key={ 'field-'+i } /> key={ 'field-'+i } />
); );
}.bind(this)); });
var actions = false; let actions = false;
if(this.props.children) { if(this.props.children) {
actions = this.props.children; actions = this.props.children;
} else { } else {
@ -226,7 +238,7 @@ define(
{ afterFormContent } { afterFormContent }
</div> </div>
); );
} },
}); });
return Form; return Form;

View File

@ -1024,7 +1024,7 @@ WysijaForm.Widget = Class.create(WysijaForm.Block, {
}, },
editSettings: function() { editSettings: function() {
MailPoet.Modal.popup({ MailPoet.Modal.popup({
title: 'Edit field settings', // TODO: translate! title: MailPoet.I18n.t('editFieldSettings'),
template: jQuery('#form_template_field_settings').html(), template: jQuery('#form_template_field_settings').html(),
data: this.getData(), data: this.getData(),
onSuccess: function() { onSuccess: function() {
@ -1061,4 +1061,4 @@ function info(value) {
} catch(e) {} } catch(e) {}
} }
module.exports = WysijaForm; module.exports = WysijaForm;

View File

@ -1,15 +1,15 @@
import React from 'react' import React from 'react';
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router' import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history' import { createHashHistory } from 'history';
import FormList from 'forms/list.jsx' import FormList from 'forms/list.jsx';
const history = useRouterHistory(createHashHistory)({ queryKey: false }); const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({ const App = React.createClass({
render() { render() {
return this.props.children return this.props.children;
} },
}); });
const container = document.getElementById('forms_container'); const container = document.getElementById('forms_container');
@ -23,4 +23,4 @@ if(container) {
</Route> </Route>
</Router> </Router>
), container); ), container);
} }

View File

@ -1,29 +1,27 @@
import React from 'react' import React from 'react';
import ReactDOM from 'react-dom' import Listing from 'listing/listing.jsx';
import { Router, Link } from 'react-router' import classNames from 'classnames';
import Listing from 'listing/listing.jsx' import MailPoet from 'mailpoet';
import classNames from 'classnames'
import MailPoet from 'mailpoet'
const columns = [ const columns = [
{ {
name: 'name', name: 'name',
label: MailPoet.I18n.t('formName'), label: MailPoet.I18n.t('formName'),
sortable: true sortable: true,
}, },
{ {
name: 'segments', name: 'segments',
label: MailPoet.I18n.t('segments') label: MailPoet.I18n.t('segments'),
}, },
{ {
name: 'signups', name: 'signups',
label: MailPoet.I18n.t('signups') label: MailPoet.I18n.t('signups'),
}, },
{ {
name: 'created_at', name: 'created_at',
label: MailPoet.I18n.t('createdOn'), label: MailPoet.I18n.t('createdOn'),
sortable: true sortable: true,
} },
]; ];
const messages = { const messages = {
@ -71,37 +69,38 @@ const messages = {
).replace('%$1d', count.toLocaleString()); ).replace('%$1d', count.toLocaleString());
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
} },
}; };
const bulk_actions = [ const bulk_actions = [
{ {
name: 'trash', name: 'trash',
label: MailPoet.I18n.t('moveToTrash'), label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash onSuccess: messages.onTrash,
} },
]; ];
const item_actions = [ const item_actions = [
{ {
name: 'edit', name: 'edit',
label: MailPoet.I18n.t('edit'), label: MailPoet.I18n.t('edit'),
link: function(item) { link: function (item) {
return ( return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a> <a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a>
); );
} },
}, },
{ {
name: 'duplicate', name: 'duplicate',
label: MailPoet.I18n.t('duplicate'), label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) { onClick: function (item, refresh) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'forms', endpoint: 'forms',
action: 'duplicate', action: 'duplicate',
data: { data: {
id: item.id id: item.id,
} },
}).done((response) => { }).done((response) => {
MailPoet.Notice.success( MailPoet.Notice.success(
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.data.name) (MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.data.name)
@ -110,44 +109,45 @@ const item_actions = [
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
} }
}); });
} },
}, },
{ {
name: 'trash' name: 'trash',
} },
]; ];
const FormList = React.createClass({ const FormList = React.createClass({
createForm() { createForm() {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'forms', endpoint: 'forms',
action: 'create' action: 'create',
}).done((response) => { }).done((response) => {
window.location = mailpoet_form_edit_url + response.data.id; window.location = mailpoet_form_edit_url + response.data.id;
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if (response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
} }
}); });
}, },
renderItem(form, actions) { renderItem(form, actions) {
let row_classes = classNames( const row_classes = classNames(
'manage-column', 'manage-column',
'column-primary', 'column-primary',
'has-row-actions' 'has-row-actions'
); );
let segments = mailpoet_segments.filter(function(segment) { let segments = mailpoet_segments.filter((segment) => {
return (jQuery.inArray(segment.id, form.segments) !== -1); return (jQuery.inArray(segment.id, form.segments) !== -1);
}).map(function(segment) { }).map((segment) => {
return segment.name; return segment.name;
}).join(', '); }).join(', ');
@ -203,7 +203,7 @@ const FormList = React.createClass({
/> />
</div> </div>
); );
} },
}); });
module.exports = FormList; module.exports = FormList;

View File

@ -0,0 +1,32 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';
import KnowledgeBase from 'help/knowledge_base.jsx';
import SystemInfo from 'help/system_info.jsx';
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
return this.props.children;
},
});
const container = document.getElementById('help_container');
if(container) {
ReactDOM.render((
<Router history={ history }>
<Route path="/" component={ App }>
<IndexRedirect to="knowledgeBase" />
{/* Pages */}
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={ KnowledgeBase } />
<Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={ SystemInfo } />
</Route>
</Router>
), container);
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import MailPoet from 'mailpoet';
import Tabs from './tabs.jsx';
function KnowledgeBase() {
return (
<div>
<Tabs tab="knowledgeBase" />
<p>{MailPoet.I18n.t('knowledgeBaseIntro')}</p>
<ul>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/116-common-problems">Common Problems</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/165-newsletters">Newsletters</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/156-migration-questions">Migration Questions</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/149-sending-methods">Sending Methods</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/139-subscription-forms">Subscription Forms</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/114-getting-started">Getting Started</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/123-newsletter-designer">Newsletter Designer</a></li>
<li><a target="_blank" href="http://beta.docs.mailpoet.com/category/121-subscribers-and-lists">Subscribers and Lists</a></li>
</ul>
<a target="_blank" href="http://beta.docs.mailpoet.com/" className="button button-primary">{MailPoet.I18n.t('knowledgeBaseButton')}</a>
</div>
);
};
module.exports = KnowledgeBase;

View File

@ -0,0 +1,47 @@
import React from 'react';
import MailPoet from 'mailpoet';
import _ from 'underscore';
import Tabs from './tabs.jsx';
function handleFocus(event) {
event.target.select();
}
function printData(data) {
if (_.isObject(data)) {
const printableData = Object.keys(data).map((key) => {
return `${key}: ${data[key]}`;
});
return (<textarea
readOnly={true}
onFocus={handleFocus}
value={printableData.join("\n")}
style={{
width: "100%",
height: "400px",
}}
/>);
} else {
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
}
}
function KnowledgeBase() {
const data = window.help_scout_data;
return (
<div>
<Tabs tab="systemInfo" />
<div className="mailpoet_notice notice inline notice-success" style={{ marginTop: "1em" }}>
<p>{MailPoet.I18n.t('systemInfoIntro')}</p>
</div>
{printData(data)}
</div>
);
};
module.exports = KnowledgeBase;

View File

@ -0,0 +1,46 @@
import React from 'react';
import { Link } from 'react-router';
import classNames from 'classnames';
import MailPoet from 'mailpoet';
const tabs = [
{
name: 'knowledgeBase',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'),
link: '/knowledgeBase',
},
{
name: 'systemInfo',
label: MailPoet.I18n.t('tabSystemInfoTitle'),
link: '/systemInfo',
},
];
function Tabs(props) {
const tabLinks = tabs.map((tab, index) => {
const tabClasses = classNames(
'nav-tab',
{ 'nav-tab-active': (props.tab === tab.name) }
);
return (
<Link
key={ 'tab-'+index }
className={ tabClasses }
to={ tab.link }
>{ tab.label }</Link>
);
});
return (
<h2 className="nav-tab-wrapper">
{ tabLinks }
</h2>
);
};
Tabs.propTypes = { tab: React.PropTypes.string };
Tabs.defaultProps = { tab: "knowledgeBase" };
module.exports = Tabs;

View File

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

View File

@ -27,7 +27,7 @@ define(
coerce_types = { 'true': !0, 'false': !1, 'null': null }; coerce_types = { 'true': !0, 'false': !1, 'null': null };
// Iterate over all name=value pairs. // Iterate over all name=value pairs.
$.each( this.serializeArray(), function(j,v){ $.each( this.serializeArray(), function(j, v){
var key = v.name, var key = v.name,
val = v.value, val = v.value,
cur = obj, cur = obj,

View File

@ -1,53 +1,53 @@
define([ define([
'react', 'react',
'mailpoet' 'mailpoet',
], ],
function( (
React, React,
MailPoet MailPoet
) { ) => {
var ListingBulkActions = React.createClass({ const ListingBulkActions = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
action: false, action: false,
extra: false extra: false,
} };
}, },
handleChangeAction: function(e) { handleChangeAction: function (e) {
this.setState({ this.setState({
action: e.target.value, action: e.target.value,
extra: false extra: false,
}, function() { }, () => {
var action = this.getSelectedAction(); const action = this.getSelectedAction();
// action on select callback // action on select callback
if(action !== null && action['onSelect'] !== undefined) { if(action !== null && action['onSelect'] !== undefined) {
this.setState({ this.setState({
extra: action.onSelect(e) extra: action.onSelect(e),
}); });
} }
}.bind(this)); });
}, },
handleApplyAction: function(e) { handleApplyAction: function (e) {
e.preventDefault(); e.preventDefault();
var action = this.getSelectedAction(); const action = this.getSelectedAction();
if(action === null) { if(action === null) {
return; return;
} }
var selected_ids = (this.props.selection !== 'all') const selected_ids = (this.props.selection !== 'all')
? this.props.selected_ids ? this.props.selected_ids
: []; : [];
var data = (action['getData'] !== undefined) const data = (action['getData'] !== undefined)
? action.getData() ? action.getData()
: {}; : {};
data.action = this.state.action; data.action = this.state.action;
var onSuccess = function() {}; let onSuccess = function () {};
if(action['onSuccess'] !== undefined) { if(action['onSuccess'] !== undefined) {
onSuccess = action.onSuccess; onSuccess = action.onSuccess;
} }
@ -61,13 +61,13 @@ function(
this.setState({ this.setState({
action: false, action: false,
extra: false extra: false,
}); });
}, },
getSelectedAction: function() { getSelectedAction: function () {
var selected_action = this.refs.action.value; const selected_action = this.refs.action.value;
if(selected_action.length > 0) { if(selected_action.length > 0) {
var action = this.props.bulk_actions.filter(function(action) { const action = this.props.bulk_actions.filter((action) => {
return (action.name === selected_action); return (action.name === selected_action);
}); });
@ -77,7 +77,7 @@ function(
} }
return null; return null;
}, },
render: function() { render: function () {
if(this.props.bulk_actions.length === 0) { if(this.props.bulk_actions.length === 0) {
return null; return null;
} }
@ -97,14 +97,14 @@ function(
onChange={this.handleChangeAction} onChange={this.handleChangeAction}
> >
<option value="">{MailPoet.I18n.t('bulkActions')}</option> <option value="">{MailPoet.I18n.t('bulkActions')}</option>
{ this.props.bulk_actions.map(function(action, index) { { this.props.bulk_actions.map((action, index) => {
return ( return (
<option <option
value={ action.name } value={ action.name }
key={ 'action-' + index } key={ 'action-' + index }
>{ action.label }</option> >{ action.label }</option>
); );
}.bind(this)) } }) }
</select> </select>
<input <input
onClick={ this.handleApplyAction } onClick={ this.handleApplyAction }
@ -115,7 +115,7 @@ function(
{ this.state.extra } { this.state.extra }
</div> </div>
); );
} },
}); });
return ListingBulkActions; return ListingBulkActions;

View File

@ -1,27 +1,30 @@
define([ define([
'react', 'react',
'jquery', 'jquery',
'mailpoet' 'mailpoet',
], ],
function( (
React, React,
jQuery, jQuery,
MailPoet MailPoet
) { ) => {
var ListingFilters = React.createClass({ const ListingFilters = React.createClass({
handleFilterAction: function() { handleFilterAction: function () {
let filters = {}; const filters = {};
this.getAvailableFilters().map((filter, i) => { this.getAvailableFilters().map((filter, i) => {
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value;
}); });
if (this.props.onBeforeSelectFilter) {
this.props.onBeforeSelectFilter(filters);
}
return this.props.onSelectFilter(filters); return this.props.onSelectFilter(filters);
}, },
handleEmptyTrash: function() { handleEmptyTrash: function () {
return this.props.onEmptyTrash(); return this.props.onEmptyTrash();
}, },
getAvailableFilters: function() { getAvailableFilters: function () {
let filters = this.props.filters; const filters = this.props.filters;
return Object.keys(filters).filter(function(filter) { return Object.keys(filters).filter((filter) => {
return !( return !(
filters[filter].length === 0 filters[filter].length === 0
|| ( || (
@ -31,39 +34,39 @@ function(
); );
}); });
}, },
componentDidUpdate: function() { componentDidUpdate: function () {
const selected_filters = this.props.filter; const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters().map( this.getAvailableFilters().map(
function(filter, i) { (filter, i) => {
if (selected_filters[filter] !== undefined && selected_filters[filter]) { if (selected_filters[filter] !== undefined && selected_filters[filter]) {
jQuery(this.refs['filter-'+i]) jQuery(this.refs['filter-'+i])
.val(selected_filters[filter]) .val(selected_filters[filter])
.trigger('change'); .trigger('change');
} }
}.bind(this) }
); );
}, },
render: function() { render: function () {
const filters = this.props.filters; const filters = this.props.filters;
const available_filters = this.getAvailableFilters() const available_filters = this.getAvailableFilters()
.map(function(filter, i) { .map((filter, i) => {
return ( return (
<select <select
ref={ `filter-${i}` } ref={ `filter-${i}` }
key={ `filter-${i}` } key={ `filter-${i}` }
name={ filter } name={ filter }
> >
{ filters[filter].map(function(option, j) { { filters[filter].map((option, j) => {
return ( return (
<option <option
value={ option.value } value={ option.value }
key={ 'filter-option-' + j } key={ 'filter-option-' + j }
>{ option.label }</option> >{ option.label }</option>
); );
}.bind(this)) } }) }
</select> </select>
); );
}.bind(this)); });
let button; let button;
@ -97,7 +100,7 @@ function(
{ empty_trash } { empty_trash }
</div> </div>
); );
} },
}); });
return ListingFilters; return ListingFilters;

View File

@ -1,20 +1,20 @@
define(['react', 'classnames'], function(React, classNames) { define(['react', 'classnames'], (React, classNames) => {
var ListingGroups = React.createClass({ const ListingGroups = React.createClass({
handleSelect: function(group) { handleSelect: function (group) {
return this.props.onSelectGroup(group); return this.props.onSelectGroup(group);
}, },
render: function() { render: function () {
var groups = this.props.groups.map(function(group, index) { const groups = this.props.groups.map((group, index) => {
if(group.name === 'trash' && group.count === 0) { if(group.name === 'trash' && group.count === 0) {
return false; return false;
} }
var classes = classNames( const classes = classNames(
{ 'current' : (group.name === this.props.group) } { 'current' : (group.name === this.props.group) }
); );
return ( return (
<li key={index}> <li key={index}>
{(index > 0) ? ' |' : ''} {(index > 0) ? ' |' : ''}
<a <a
@ -24,17 +24,17 @@ define(['react', 'classnames'], function(React, classNames) {
{group.label} <span className="count">({ group.count.toLocaleString() })</span> {group.label} <span className="count">({ group.count.toLocaleString() })</span>
</a> </a>
</li> </li>
); );
}.bind(this)); });
return ( return (
<ul className="subsubsub"> <ul className="subsubsub">
{ groups } { groups }
</ul> </ul>
); );
} },
}); });
return ListingGroups; return ListingGroups;
} }
); );

View File

@ -1,15 +1,15 @@
import MailPoet from 'mailpoet' import MailPoet from 'mailpoet';
import React from 'react' import React from 'react';
import classNames from 'classnames' import classNames from 'classnames';
const ListingHeader = React.createClass({ const ListingHeader = React.createClass({
handleSelectItems: function() { handleSelectItems: function () {
return this.props.onSelectItems( return this.props.onSelectItems(
this.refs.toggle.checked this.refs.toggle.checked
); );
}, },
render: function() { render: function () {
const columns = this.props.columns.map(function(column, index) { const columns = this.props.columns.map((column, index) => {
column.is_primary = (index === 0); column.is_primary = (index === 0);
column.sorted = (this.props.sort_by === column.name) column.sorted = (this.props.sort_by === column.name)
? this.props.sort_order ? this.props.sort_order
@ -21,7 +21,7 @@ const ListingHeader = React.createClass({
key={ 'column-' + index } key={ 'column-' + index }
column={column} /> column={column} />
); );
}.bind(this)); });
let checkbox; let checkbox;
@ -48,16 +48,16 @@ const ListingHeader = React.createClass({
{columns} {columns}
</tr> </tr>
); );
} },
}); });
const ListingColumn = React.createClass({ const ListingColumn = React.createClass({
handleSort: function() { handleSort: function () {
const sort_by = this.props.column.name; const sort_by = this.props.column.name;
const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc'; const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order); this.props.onSort(sort_by, sort_order);
}, },
render: function() { render: function () {
const classes = classNames( const classes = classNames(
'manage-column', 'manage-column',
{ 'column-primary': this.props.column.is_primary }, { 'column-primary': this.props.column.is_primary },
@ -85,7 +85,7 @@ const ListingColumn = React.createClass({
width={ this.props.column.width || null } width={ this.props.column.width || null }
>{label}</th> >{label}</th>
); );
} },
}); });
module.exports = ListingHeader; module.exports = ListingHeader;

View File

@ -1,23 +1,23 @@
import MailPoet from 'mailpoet' import MailPoet from 'mailpoet';
import jQuery from 'jquery' import jQuery from 'jquery';
import React from 'react' import React from 'react';
import _ from 'underscore' import _ from 'underscore';
import { Router, Link } from 'react-router' import { Link } from 'react-router';
import classNames from 'classnames' import classNames from 'classnames';
import ListingBulkActions from 'listing/bulk_actions.jsx' import ListingBulkActions from 'listing/bulk_actions.jsx';
import ListingHeader from 'listing/header.jsx' import ListingHeader from 'listing/header.jsx';
import ListingPages from 'listing/pages.jsx' import ListingPages from 'listing/pages.jsx';
import ListingSearch from 'listing/search.jsx' import ListingSearch from 'listing/search.jsx';
import ListingGroups from 'listing/groups.jsx' import ListingGroups from 'listing/groups.jsx';
import ListingFilters from 'listing/filters.jsx' import ListingFilters from 'listing/filters.jsx';
const ListingItem = React.createClass({ const ListingItem = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
expanded: false expanded: false,
}; };
}, },
handleSelectItem: function(e) { handleSelectItem: function (e) {
this.props.onSelectItem( this.props.onSelectItem(
parseInt(e.target.value, 10), parseInt(e.target.value, 10),
e.target.checked e.target.checked
@ -25,20 +25,20 @@ const ListingItem = React.createClass({
return !e.target.checked; return !e.target.checked;
}, },
handleRestoreItem: function(id) { handleRestoreItem: function (id) {
this.props.onRestoreItem(id); this.props.onRestoreItem(id);
}, },
handleTrashItem: function(id) { handleTrashItem: function (id) {
this.props.onTrashItem(id); this.props.onTrashItem(id);
}, },
handleDeleteItem: function(id) { handleDeleteItem: function (id) {
this.props.onDeleteItem(id); this.props.onDeleteItem(id);
}, },
handleToggleItem: function(id) { handleToggleItem: function () {
this.setState({ expanded: !this.state.expanded }); this.setState({ expanded: !this.state.expanded });
}, },
render: function() { render: function () {
var checkbox = false; let checkbox = false;
if (this.props.is_selectable === true) { if (this.props.is_selectable === true) {
checkbox = ( checkbox = (
@ -63,7 +63,7 @@ const ListingItem = React.createClass({
if (custom_actions.length > 0) { if (custom_actions.length > 0) {
let is_first = true; let is_first = true;
item_actions = custom_actions.map(function(action, index) { item_actions = custom_actions.map((action, index) => {
if (action.display !== undefined) { if (action.display !== undefined) {
if (action.display(this.props.item) === false) { if (action.display(this.props.item) === false) {
return; return;
@ -125,7 +125,7 @@ const ListingItem = React.createClass({
} }
return custom_action; return custom_action;
}.bind(this)); });
} else { } else {
item_actions = ( item_actions = (
<span className="edit"> <span className="edit">
@ -137,7 +137,7 @@ const ListingItem = React.createClass({
let actions; let actions;
if (this.props.group === 'trash') { if (this.props.group === 'trash') {
actions = ( actions = (
<div> <div>
<div className="row-actions"> <div className="row-actions">
<span> <span>
@ -191,13 +191,24 @@ const ListingItem = React.createClass({
{ this.props.onRenderItem(this.props.item, actions) } { this.props.onRenderItem(this.props.item, actions) }
</tr> </tr>
); );
} },
}); });
const ListingItems = React.createClass({ const ListingItems = React.createClass({
render: function() { render: function () {
if (this.props.items.length === 0) { if (this.props.items.length === 0) {
let message;
if (this.props.loading === true) {
message = (this.props.messages.onLoadingItems
&& this.props.messages.onLoadingItems(this.props.group))
|| MailPoet.I18n.t('loadingItems');
} else {
message = (this.props.messages.onNoItemsFound
&& this.props.messages.onNoItemsFound(this.props.group))
|| MailPoet.I18n.t('noItemsFound');
}
return ( return (
<tbody> <tbody>
<tr className="no-items"> <tr className="no-items">
@ -207,11 +218,7 @@ const ListingItems = React.createClass({
+ (this.props.is_selectable ? 1 : 0) + (this.props.is_selectable ? 1 : 0)
} }
className="colspanchange"> className="colspanchange">
{ {message}
(this.props.loading === true)
? MailPoet.I18n.t('loadingItems')
: MailPoet.I18n.t('noItemsFound')
}
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -222,7 +229,7 @@ const ListingItems = React.createClass({
{ 'mailpoet_hidden': ( { 'mailpoet_hidden': (
this.props.selection === false this.props.selection === false
|| (this.props.count <= this.props.limit) || (this.props.count <= this.props.limit)
) ),
} }
); );
@ -252,7 +259,7 @@ const ListingItems = React.createClass({
</td> </td>
</tr> </tr>
{this.props.items.map(function(item, index) { {this.props.items.map((item, index) => {
item.id = parseInt(item.id, 10); item.id = parseInt(item.id, 10);
item.selected = (this.props.selected_ids.indexOf(item.id) !== -1); item.selected = (this.props.selected_ids.indexOf(item.id) !== -1);
@ -272,18 +279,18 @@ const ListingItems = React.createClass({
key={ `item-${item.id}-${index}` } key={ `item-${item.id}-${index}` }
item={ item } /> item={ item } />
); );
}.bind(this))} })}
</tbody> </tbody>
); );
} }
} },
}); });
const Listing = React.createClass({ const Listing = React.createClass({
contextTypes: { contextTypes: {
router: React.PropTypes.object.isRequired router: React.PropTypes.object.isRequired,
}, },
getInitialState: function() { getInitialState: function () {
return { return {
loading: false, loading: false,
search: '', search: '',
@ -299,31 +306,31 @@ const Listing = React.createClass({
filter: {}, filter: {},
selected_ids: [], selected_ids: [],
selection: false, selection: false,
meta: {} meta: {},
}; };
}, },
getParam: function(param) { getParam: function (param) {
const regex = /(.*)\[(.*)\]/; const regex = /(.*)\[(.*)\]/;
const matches = regex.exec(param); const matches = regex.exec(param);
return [matches[1], matches[2]]; return [matches[1], matches[2]];
}, },
initWithParams: function(params) { initWithParams: function (params) {
let state = this.getInitialState(); const state = this.getInitialState();
// check for url params // check for url params
if (params.splat) { if (params.splat) {
params.splat.split('/').map(param => { params.splat.split('/').map((param) => {
let [key, value] = this.getParam(param); const [key, value] = this.getParam(param);
switch(key) { switch(key) {
case 'filter': case 'filter':
let filters = {}; const filters = {};
value.split('&').map(function(pair) { value.split('&').map((pair) => {
let [k, v] = pair.split('=') const [k, v] = pair.split('=');
filters[k] = v filters[k] = v;
} }
); );
state.filter = filters; state.filter = filters;
break; break;
default: default:
state[key] = value; state[key] = value;
} }
@ -345,13 +352,13 @@ const Listing = React.createClass({
state.sort_order = this.props.sort_order; state.sort_order = this.props.sort_order;
} }
this.setState(state, function() { this.setState(state, () => {
this.getItems(); this.getItems();
}.bind(this)); });
}, },
getParams: function() { getParams: function () {
// get all route parameters (without the "splat") // get all route parameters (without the "splat")
let params = _.omit(this.props.params, 'splat'); const params = _.omit(this.props.params, 'splat');
// TODO: // TODO:
// find a way to set the "type" in the routes definition // find a way to set the "type" in the routes definition
// so that it appears in `this.props.params` // so that it appears in `this.props.params`
@ -360,10 +367,10 @@ const Listing = React.createClass({
} }
return params; return params;
}, },
setParams: function() { setParams: function () {
if (this.props.location) { if (this.props.location) {
let params = Object.keys(this.state) const params = Object.keys(this.state)
.filter(key => { .filter((key) => {
return ( return (
[ [
'group', 'group',
@ -371,34 +378,34 @@ const Listing = React.createClass({
'search', 'search',
'page', 'page',
'sort_by', 'sort_by',
'sort_order' 'sort_order',
].indexOf(key) !== -1 ].indexOf(key) !== -1
) );
}) })
.map(key => { .map((key) => {
let value = this.state[key]; let value = this.state[key];
if (value === Object(value)) { if (value === Object(value)) {
value = jQuery.param(value) value = jQuery.param(value);
} else if (value === Boolean(value)) { } else if (value === Boolean(value)) {
value = value.toString() value = value.toString();
} }
if (value !== '' && value !== null) { if (value !== '' && value !== null) {
return `${key}[${value}]` return `${key}[${value}]`;
} }
}) })
.filter(key => { return (key !== undefined) }) .filter((key) => { return (key !== undefined); })
.join('/'); .join('/');
// set url // set url
let url = this.getUrlWithParams(params); const url = this.getUrlWithParams(params);
if (this.props.location.pathname !== url) { if (this.props.location.pathname !== url) {
this.context.router.push(`${url}`); this.context.router.push(`${url}`);
} }
} }
}, },
getUrlWithParams: function(params) { getUrlWithParams: function (params) {
let base_url = (this.props.base_url !== undefined) let base_url = (this.props.base_url !== undefined)
? this.props.base_url ? this.props.base_url
: null; : null;
@ -410,7 +417,7 @@ const Listing = React.createClass({
return `/${ params }`; return `/${ params }`;
} }
}, },
setBaseUrlParams: function(base_url) { setBaseUrlParams: function (base_url) {
if (base_url.indexOf(':') !== -1) { if (base_url.indexOf(':') !== -1) {
const params = this.getParams(); const params = this.getParams();
Object.keys(params).map((key) => { Object.keys(params).map((key) => {
@ -422,29 +429,30 @@ const Listing = React.createClass({
return base_url; return base_url;
}, },
componentDidMount: function() { componentDidMount: function () {
if (this.isMounted()) { if (this.isMounted()) {
const params = this.props.params || {}; const params = this.props.params || {};
this.initWithParams(params); this.initWithParams(params);
if (this.props.auto_refresh) { if (this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) { jQuery(document).on('heartbeat-tick.mailpoet', () => {
this.getItems(); this.getItems();
}.bind(this)); });
} }
} }
}, },
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function (nextProps) {
const params = nextProps.params || {}; const params = nextProps.params || {};
this.initWithParams(params); this.initWithParams(params);
}, },
getItems: function() { getItems: function () {
if (this.isMounted()) { if (this.isMounted()) {
this.setState({ loading: true }); this.setState({ loading: true });
this.clearSelection(); this.clearSelection();
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'listing', action: 'listing',
data: { data: {
@ -455,8 +463,8 @@ const Listing = React.createClass({
filter: this.state.filter, filter: this.state.filter,
search: this.state.search, search: this.state.search,
sort_by: this.state.sort_by, sort_by: this.state.sort_by,
sort_order: this.state.sort_order sort_order: this.state.sort_order,
} },
}).always(() => { }).always(() => {
this.setState({ loading: false }); this.setState({ loading: false });
}).done((response) => { }).done((response) => {
@ -465,7 +473,7 @@ const Listing = React.createClass({
filters: response.meta.filters || {}, filters: response.meta.filters || {},
groups: response.meta.groups || [], groups: response.meta.groups || [],
count: response.meta.count || 0, count: response.meta.count || 0,
meta: _.omit(response.meta, ['filters', 'groups', 'count']) meta: _.omit(response.meta, ['filters', 'groups', 'count']),
}, () => { }, () => {
// if viewing an empty trash // if viewing an empty trash
if (this.state.group === 'trash' && response.meta.count === 0) { if (this.state.group === 'trash' && response.meta.count === 0) {
@ -479,27 +487,28 @@ const Listing = React.createClass({
} }
}); });
}).fail((response) => { }).fail((response) => {
if (response.errors.length > 0) { if(response.errors.length > 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
} }
}); });
} }
}, },
handleRestoreItem: function(id) { handleRestoreItem: function (id) {
this.setState({ this.setState({
loading: true, loading: true,
page: 1 page: 1,
}); });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'restore', action: 'restore',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
@ -510,23 +519,24 @@ const Listing = React.createClass({
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleTrashItem: function(id) { handleTrashItem: function (id) {
this.setState({ this.setState({
loading: true, loading: true,
page: 1 page: 1,
}); });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'trash', action: 'trash',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
@ -537,23 +547,24 @@ const Listing = React.createClass({
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleDeleteItem: function(id) { handleDeleteItem: function (id) {
this.setState({ this.setState({
loading: true, loading: true,
page: 1 page: 1,
}); });
MailPoet.Ajax.post({ MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'delete', action: 'delete',
data: { data: {
id: id id: id,
} },
}).done((response) => { }).done((response) => {
if ( if (
this.props.messages !== undefined this.props.messages !== undefined
@ -564,15 +575,15 @@ const Listing = React.createClass({
this.getItems(); this.getItems();
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleEmptyTrash: function() { handleEmptyTrash: function () {
return this.handleBulkAction('all', { return this.handleBulkAction('all', {
action: 'delete', action: 'delete',
group: 'trash' group: 'trash',
}).done((response) => { }).done((response) => {
MailPoet.Notice.success( MailPoet.Notice.success(
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response.meta.count) MailPoet.I18n.t('permanentlyDeleted').replace('%d', response.meta.count)
@ -581,12 +592,12 @@ const Listing = React.createClass({
this.handleGroup('all'); this.handleGroup('all');
}).fail((response) => { }).fail((response) => {
MailPoet.Notice.error( MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }), response.errors.map((error) => { return error.message; }),
{ scroll: true } { scroll: true }
); );
}); });
}, },
handleBulkAction: function(selected_ids, params) { handleBulkAction: function (selected_ids, params) {
if ( if (
this.state.selection === false this.state.selection === false
&& this.state.selected_ids.length === 0 && this.state.selected_ids.length === 0
@ -597,48 +608,56 @@ const Listing = React.createClass({
this.setState({ loading: true }); this.setState({ loading: true });
var data = params || {}; const data = params || {};
data.listing = { data.listing = {
params: this.getParams(), params: this.getParams(),
offset: 0, offset: 0,
limit: 0, limit: 0,
filter: this.state.filter, filter: this.state.filter,
group: this.state.group, group: this.state.group,
search: this.state.search search: this.state.search,
} };
if (selected_ids !== 'all') { if (selected_ids !== 'all') {
data.listing.selection = selected_ids; data.listing.selection = selected_ids;
} }
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint, endpoint: this.props.endpoint,
action: 'bulkAction', action: 'bulkAction',
data: data data: data,
}).done(() => { }).done(() => {
this.getItems(); this.getItems();
}).fail((response) => {
if(response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
{ scroll: true }
);
}
}); });
}, },
handleSearch: function(search) { handleSearch: function (search) {
this.setState({ this.setState({
search: search, search: search,
page: 1, page: 1,
selection: false, selection: false,
selected_ids: [] selected_ids: [],
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleSort: function(sort_by, sort_order = 'asc') { handleSort: function (sort_by, sort_order = 'asc') {
this.setState({ this.setState({
sort_by: sort_by, sort_by: sort_by,
sort_order: (sort_order === 'asc') ? 'asc' : 'desc', sort_order: (sort_order === 'asc') ? 'asc' : 'desc',
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleSelectItem: function(id, is_checked) { handleSelectItem: function (id, is_checked) {
var selected_ids = this.state.selected_ids, let selected_ids = this.state.selected_ids,
selection = false; selection = false;
if (is_checked) { if (is_checked) {
selected_ids = jQuery.merge(selected_ids, [ id ]); selected_ids = jQuery.merge(selected_ids, [ id ]);
@ -654,48 +673,48 @@ const Listing = React.createClass({
this.setState({ this.setState({
selection: selection, selection: selection,
selected_ids: selected_ids selected_ids: selected_ids,
}); });
}, },
handleSelectItems: function(is_checked) { handleSelectItems: function (is_checked) {
if (is_checked === false) { if (is_checked === false) {
this.clearSelection(); this.clearSelection();
} else { } else {
var selected_ids = this.state.items.map(function(item) { const selected_ids = this.state.items.map((item) => {
return ~~item.id; return ~~item.id;
}); });
this.setState({ this.setState({
selected_ids: selected_ids, selected_ids: selected_ids,
selection: 'page' selection: 'page',
}); });
} }
}, },
handleSelectAll: function() { handleSelectAll: function () {
if (this.state.selection === 'all') { if (this.state.selection === 'all') {
this.clearSelection(); this.clearSelection();
} else { } else {
this.setState({ this.setState({
selection: 'all', selection: 'all',
selected_ids: [] selected_ids: [],
}); });
} }
}, },
clearSelection: function() { clearSelection: function () {
this.setState({ this.setState({
selection: false, selection: false,
selected_ids: [] selected_ids: [],
}); });
}, },
handleFilter: function(filters) { handleFilter: function (filters) {
this.setState({ this.setState({
filter: filters, filter: filters,
page: 1 page: 1,
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleGroup: function(group) { handleGroup: function (group) {
// reset search // reset search
jQuery('#search_input').val(''); jQuery('#search_input').val('');
@ -703,35 +722,35 @@ const Listing = React.createClass({
group: group, group: group,
filter: {}, filter: {},
search: '', search: '',
page: 1 page: 1,
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleSetPage: function(page) { handleSetPage: function (page) {
this.setState({ this.setState({
page: page, page: page,
selection: false, selection: false,
selected_ids: [] selected_ids: [],
}, function() { }, () => {
this.setParams(); this.setParams();
}.bind(this)); });
}, },
handleRenderItem: function(item, actions) { handleRenderItem: function (item, actions) {
const render = this.props.onRenderItem(item, actions, this.state.meta); const render = this.props.onRenderItem(item, actions, this.state.meta);
return render.props.children; return render.props.children;
}, },
handleRefreshItems: function() { handleRefreshItems: function () {
this.getItems(); this.getItems();
}, },
render: function() { render: function () {
const items = this.state.items; const items = this.state.items;
const sort_by = this.state.sort_by; const sort_by = this.state.sort_by;
const sort_order = this.state.sort_order; const sort_order = this.state.sort_order;
// columns // columns
let columns = this.props.columns || []; let columns = this.props.columns || [];
columns = columns.filter(function(column) { columns = columns.filter((column) => {
return (column.display === undefined || !!(column.display) === true); return (column.display === undefined || !!(column.display) === true);
}); });
@ -743,13 +762,13 @@ const Listing = React.createClass({
{ {
name: 'restore', name: 'restore',
label: MailPoet.I18n.t('restore'), label: MailPoet.I18n.t('restore'),
onSuccess: this.props.messages.onRestore onSuccess: this.props.messages.onRestore,
}, },
{ {
name: 'delete', name: 'delete',
label: MailPoet.I18n.t('deletePermanently'), label: MailPoet.I18n.t('deletePermanently'),
onSuccess: this.props.messages.onDelete onSuccess: this.props.messages.onDelete,
} },
]; ];
} }
@ -788,6 +807,12 @@ const Listing = React.createClass({
groups = false; groups = false;
} }
// messages
let messages = {};
if (this.props.messages !== undefined) {
messages = this.props.messages;
}
return ( return (
<div> <div>
{ groups } { groups }
@ -803,6 +828,7 @@ const Listing = React.createClass({
filters={ this.state.filters } filters={ this.state.filters }
filter={ this.state.filter } filter={ this.state.filter }
group={ this.state.group } group={ this.state.group }
onBeforeSelectFilter={ this.props.onBeforeSelectFilter || null }
onSelectFilter={ this.handleFilter } onSelectFilter={ this.handleFilter }
onEmptyTrash={ this.handleEmptyTrash } onEmptyTrash={ this.handleEmptyTrash }
/> />
@ -841,6 +867,7 @@ const Listing = React.createClass({
count={ this.state.count } count={ this.state.count }
limit={ this.state.limit } limit={ this.state.limit }
item_actions={ item_actions } item_actions={ item_actions }
messages={ messages }
items={ items } /> items={ items } />
<tfoot> <tfoot>
@ -870,7 +897,7 @@ const Listing = React.createClass({
</div> </div>
</div> </div>
); );
} },
}); });
module.exports = Listing; module.exports = Listing;

View File

@ -1,76 +1,76 @@
define([ define([
'react', 'react',
'classnames', 'classnames',
'mailpoet' 'mailpoet',
], function( ], (
React, React,
classNames, classNames,
MailPoet MailPoet
) { ) => {
var ListingPages = React.createClass({ const ListingPages = React.createClass({
getInitialState: function() { getInitialState: function () {
return { return {
page: null page: null,
} };
}, },
setPage: function(page) { setPage: function (page) {
this.setState({ this.setState({
page: null page: null,
}, function () { }, () => {
this.props.onSetPage(this.constrainPage(page)); this.props.onSetPage(this.constrainPage(page));
}.bind(this)); });
}, },
setFirstPage: function() { setFirstPage: function () {
this.setPage(1); this.setPage(1);
}, },
setLastPage: function() { setLastPage: function () {
this.setPage(this.getLastPage()); this.setPage(this.getLastPage());
}, },
setPreviousPage: function() { setPreviousPage: function () {
this.setPage(this.constrainPage( this.setPage(this.constrainPage(
parseInt(this.props.page, 10) - 1) parseInt(this.props.page, 10) - 1)
); );
}, },
setNextPage: function() { setNextPage: function () {
this.setPage(this.constrainPage( this.setPage(this.constrainPage(
parseInt(this.props.page, 10) + 1) parseInt(this.props.page, 10) + 1)
); );
}, },
constrainPage: function(page) { constrainPage: function (page) {
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage()); return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage());
}, },
handleSetManualPage: function(e) { handleSetManualPage: function (e) {
if(e.which === 13) { if(e.which === 13) {
this.setPage(this.state.page); this.setPage(this.state.page);
} }
}, },
handleChangeManualPage: function(e) { handleChangeManualPage: function (e) {
this.setState({ this.setState({
page: e.target.value page: e.target.value,
}); });
}, },
handleBlurManualPage: function(e) { handleBlurManualPage: function (e) {
this.setPage(e.target.value); this.setPage(e.target.value);
}, },
getLastPage: function() { getLastPage: function () {
return Math.ceil(this.props.count / this.props.limit); return Math.ceil(this.props.count / this.props.limit);
}, },
render: function() { render: function () {
if(this.props.count === 0) { if(this.props.count === 0) {
return false; return false;
} else { } else {
var pagination = false; let pagination = false;
var firstPage = ( let firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span> <span aria-hidden="true" className="tablenav-pages-navspan">«</span>
); );
var previousPage = ( let previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span> <span aria-hidden="true" className="tablenav-pages-navspan"></span>
); );
var nextPage = ( let nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span> <span aria-hidden="true" className="tablenav-pages-navspan"></span>
); );
var lastPage = ( let lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span> <span aria-hidden="true" className="tablenav-pages-navspan">»</span>
); );
@ -159,21 +159,26 @@ define([
); );
} }
var classes = classNames( const classes = classNames(
'tablenav-pages', 'tablenav-pages',
{ 'one-page': (this.props.count <= this.props.limit) } { 'one-page': (this.props.count <= this.props.limit) }
); );
let numberOfItemsLabel;
if (this.props.count == 1) {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular');
} else {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple')
.replace('%$1d', this.props.count.toLocaleString());
}
return ( return (
<div className={ classes }> <div className={ classes }>
<span className="displaying-num">{ <span className="displaying-num">{ numberOfItemsLabel }</span>
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count.toLocaleString())
}</span>
{ pagination } { pagination }
</div> </div>
); );
} }
} },
}); });
return ListingPages; return ListingPages;

View File

@ -1,22 +1,22 @@
define([ define([
'mailpoet', 'mailpoet',
'react' 'react',
], function( ], (
MailPoet, MailPoet,
React React
) { ) => {
var ListingSearch = React.createClass({ const ListingSearch = React.createClass({
handleSearch: function(e) { handleSearch: function (e) {
e.preventDefault(); e.preventDefault();
this.props.onSearch( this.props.onSearch(
this.refs.search.value this.refs.search.value
); );
}, },
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function (nextProps) {
this.refs.search.value = nextProps.search this.refs.search.value = nextProps.search;
}, },
render: function() { render: function () {
if(this.props.search === false) { if(this.props.search === false) {
return false; return false;
} else { } else {
@ -40,7 +40,7 @@ define([
</form> </form>
); );
} }
} },
}); });
return ListingSearch; return ListingSearch;

View File

@ -0,0 +1,207 @@
define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
'use strict';
MailPoet.MP2Migrator = {
fatal_error: '',
is_logging: false,
startLogger: function () {
MailPoet.MP2Migrator.is_logging = true;
clearTimeout(MailPoet.MP2Migrator.displayLogs_timeout);
clearTimeout(MailPoet.MP2Migrator.updateProgressbar_timeout);
clearTimeout(MailPoet.MP2Migrator.update_wordpress_info_timeout);
setTimeout(MailPoet.MP2Migrator.updateDisplay, 1000)
},
stopLogger: function () {
MailPoet.MP2Migrator.is_logging = false;
},
updateDisplay: function () {
MailPoet.MP2Migrator.displayLogs();
MailPoet.MP2Migrator.updateProgressbar();
},
displayLogs: function () {
jQuery.ajax({
url: mailpoet_mp2_migrator.log_file_url,
cache: false
}).done(function (result) {
jQuery("#logger").html('');
result.split("\n").forEach(function (row) {
if(row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red
}
// Test if the import is complete
else if(row === MailPoet.I18n.t('import_complete')) {
jQuery('#import-actions').hide();
jQuery('#upgrade-completed').show();
}
jQuery("#logger").append(row + "<br />\n");
});
jQuery("#logger").append('<span class="error_msg">' + MailPoet.MP2Migrator.fatal_error + '</span>' + "<br />\n");
}).always(function () {
if(MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000);
}
});
},
updateProgressbar: function () {
jQuery.ajax({
url: mailpoet_mp2_migrator.progress_url,
cache: false,
dataType: 'json'
}).always(function (result) {
// Move the progress bar
var progress = 0;
if((result.total !== undefined) && (Number(result.total) !== 0)) {
progress = Math.round(Number(result.current) / Number(result.total) * 100);
}
jQuery('#progressbar').progressbar('option', 'value', progress);
jQuery('#progresslabel').html(progress + '%');
if(Number(result.current) !== 0) {
jQuery('#skip-import').hide();
jQuery('#progressbar').show();
jQuery('#logger-container').show();
}
if(MailPoet.MP2Migrator.is_logging) {
MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000);
}
});
},
startImport: function () {
MailPoet.MP2Migrator.fatal_error = '';
// Start displaying the logs
MailPoet.MP2Migrator.startLogger();
// Disable the import button
MailPoet.MP2Migrator.import_button_label = jQuery('#import').val();
jQuery('#import').val(MailPoet.I18n.t('importing')).attr('disabled', 'disabled');
// Hide the Skip button
jQuery('#skip-import').hide();
// Show the stop button
jQuery('#stop-import').show();
// Run the import
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'MP2Migrator',
action: 'import',
data: {
}
}).always(function () {
MailPoet.MP2Migrator.stopLogger();
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
MailPoet.MP2Migrator.reactivateImportButton();
}).done(function (response) {
if(response) {
MailPoet.MP2Migrator.fatal_error = response.data;
}
}).fail(function (response) {
if(response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function (error) {
return error.message;
}),
{scroll: true}
);
}
});
return false;
},
reactivateImportButton: function () {
jQuery('#import').val(MailPoet.MP2Migrator.import_button_label).removeAttr('disabled');
jQuery('#stop-import').hide();
},
stopImport: function () {
jQuery('#stop-import').attr('disabled', 'disabled');
// Stop the import
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'MP2Migrator',
action: 'stopImport',
data: {
}
}).always(function () {
jQuery('#stop-import').removeAttr('disabled'); // Enable the button
MailPoet.MP2Migrator.reactivateImportButton();
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
}).fail(function (response) {
if(response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function (error) {
return error.message;
}),
{scroll: true}
);
}
});
MailPoet.MP2Migrator.stopLogger();
return false;
},
skipImport: function () {
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'MP2Migrator',
action: 'skipImport',
data: {
}
}).done(function () {
MailPoet.MP2Migrator.gotoWelcomePage();
}).fail(function (response) {
if(response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function (error) {
return error.message;
}),
{scroll: true}
);
}
});
return false;
},
gotoWelcomePage: function () {
window.location.href = 'admin.php?page=mailpoet-welcome';
return false;
}
};
/**
* Actions to run when the DOM is ready
*/
jQuery(function () {
jQuery('#progressbar').progressbar({value: 0});
// Import button
jQuery('#import').click(function() {
MailPoet.MP2Migrator.startImport();
});
// Stop import button
jQuery('#stop-import').click(function() {
MailPoet.MP2Migrator.stopImport();
});
// Skip import link
jQuery('#skip-import').click(function() {
MailPoet.MP2Migrator.skipImport();
});
// Go to welcome page
jQuery('#goto-welcome').click(function() {
MailPoet.MP2Migrator.gotoWelcomePage();
});
// Update the display
MailPoet.MP2Migrator.updateDisplay();
});
});

View File

@ -1,35 +1,41 @@
define([ define([
'backbone', 'backbone',
'backbone.marionette', 'backbone.marionette',
'backbone.radio',
'jquery', 'jquery',
'underscore', 'underscore',
'handlebars', 'handlebars',
'handlebars_helpers' 'handlebars_helpers'
], function(Backbone, Marionette, jQuery, _, Handlebars) { ], function(Backbone, Marionette, Radio, jQuery, _, Handlebars) {
var app = new Marionette.Application(), AppView; var AppView = Marionette.View.extend({
// Decoupled communication between application components
app.getChannel = function(channel) {
if (channel === undefined) return app.channel;
return Radio.channel(channel);
};
AppView = Marionette.LayoutView.extend({
el: '#mailpoet_editor', el: '#mailpoet_editor',
regions: { regions: {
stylesRegion: '#mailpoet_editor_styles', stylesRegion: '#mailpoet_editor_styles',
contentRegion: '#mailpoet_editor_content', contentRegion: '#mailpoet_editor_content',
sidebarRegion: '#mailpoet_editor_sidebar', sidebarRegion: '#mailpoet_editor_sidebar',
bottomRegion: '#mailpoet_editor_bottom', bottomRegion: '#mailpoet_editor_bottom',
headingRegion: '#mailpoet_editor_heading', headingRegion: '#mailpoet_editor_heading'
}
});
var EditorApplication = Marionette.Application.extend({
region: '#mailpoet_editor',
onStart: function() {
this._appView = new AppView();
this.showView(this._appView);
}, },
getChannel: function(channel) {
if (channel === undefined) channel = 'global';
return Radio.channel(channel);
}
}); });
app.on('start', function(options) { var app = new EditorApplication();
app._appView = new AppView();
});
window.EditorApplication = app; window.EditorApplication = app;
return app; return app;
}); });

View File

@ -6,8 +6,9 @@
define([ define([
'backbone.marionette', 'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup',
'mailpoet',
'spectrum' 'spectrum'
], function(Marionette, BehaviorsLookup, Spectrum) { ], function(Marionette, BehaviorsLookup, MailPoet, Spectrum) {
BehaviorsLookup.ColorPickerBehavior = Marionette.Behavior.extend({ BehaviorsLookup.ColorPickerBehavior = Marionette.Behavior.extend({
onRender: function() { onRender: function() {
@ -17,7 +18,9 @@ define([
showInitial: true, showInitial: true,
preferredFormat: "hex6", preferredFormat: "hex6",
allowEmpty: true, allowEmpty: true,
chooseText: MailPoet.I18n.t('selectColor'),
cancelText: MailPoet.I18n.t('cancelColorSelection')
}); });
}, }
}); });
}); });

View File

@ -15,7 +15,7 @@ define([
BehaviorsLookup.ContainerDropZoneBehavior = Marionette.Behavior.extend({ BehaviorsLookup.ContainerDropZoneBehavior = Marionette.Behavior.extend({
defaults: { defaults: {
columnLimit: 3, columnLimit: 3
}, },
onRender: function() { onRender: function() {
var dragAndDropDisabled = _.isObject(this.view.options.renderOptions) && this.view.options.renderOptions.disableDragAndDrop === true; var dragAndDropDisabled = _.isObject(this.view.options.renderOptions) && this.view.options.renderOptions.disableDragAndDrop === true;
@ -71,6 +71,7 @@ define([
markerWidth = '', markerWidth = '',
markerHeight = '', markerHeight = '',
containerOffset = element.offset(), containerOffset = element.offset(),
viewCollection = that.getCollection(),
marker, targetModel, targetView, targetElement, marker, targetModel, targetView, targetElement,
topOffset, leftOffset, isLastBlockInsertion, topOffset, leftOffset, isLastBlockInsertion,
$targetBlock, margin; $targetBlock, margin;
@ -80,19 +81,19 @@ define([
element.find('.mailpoet_drop_marker').remove(); element.find('.mailpoet_drop_marker').remove();
// Allow empty collections to handle their own drop marking // Allow empty collections to handle their own drop marking
if (view.model.get('blocks').isEmpty()) return; if (viewCollection.isEmpty()) return;
if (view.collection.length === 0) { if (viewCollection.length === 0) {
targetElement = element.find(view.childViewContainer); targetElement = element.find(view.childViewContainer);
topOffset = targetElement.offset().top - element.offset().top; topOffset = targetElement.offset().top - element.offset().top;
leftOffset = targetElement.offset().left - element.offset().left; leftOffset = targetElement.offset().left - element.offset().left;
markerWidth = targetElement.width(); markerWidth = targetElement.width();
markerHeight = targetElement.height(); markerHeight = targetElement.height();
} else { } else {
isLastBlockInsertion = view.collection.length === dropPosition.index; isLastBlockInsertion = that.getCollection().length === dropPosition.index;
targetModel = isLastBlockInsertion ? view.collection.at(dropPosition.index - 1) : view.collection.at(dropPosition.index); targetModel = isLastBlockInsertion ? viewCollection.at(dropPosition.index - 1) : viewCollection.at(dropPosition.index);
targetView = view.children.findByModel(targetModel); targetView = that.getChildren().findByModel(targetModel);
targetElement = targetView.$el; targetElement = targetView.$el;
topOffset = targetElement.offset().top - containerOffset.top; topOffset = targetElement.offset().top - containerOffset.top;
@ -135,10 +136,10 @@ define([
if (dropPosition.index === 0) { if (dropPosition.index === 0) {
marker.addClass('mailpoet_drop_marker_first'); marker.addClass('mailpoet_drop_marker_first');
} }
if (view.collection.length - 1 === dropPosition.index) { if (viewCollection.length - 1 === dropPosition.index) {
marker.addClass('mailpoet_drop_marker_last'); marker.addClass('mailpoet_drop_marker_last');
} }
if (dropPosition.index > 0 && view.collection.length - 1 > dropPosition.index) { if (dropPosition.index > 0 && viewCollection.length - 1 > dropPosition.index) {
marker.addClass('mailpoet_drop_marker_middle'); marker.addClass('mailpoet_drop_marker_middle');
} }
marker.addClass('mailpoet_drop_marker_' + dropPosition.position); marker.addClass('mailpoet_drop_marker_' + dropPosition.position);
@ -147,9 +148,9 @@ define([
// compensated for to position marker right in the middle of two // compensated for to position marker right in the middle of two
// blocks // blocks
if (dropPosition.position === 'before') { if (dropPosition.position === 'before') {
$targetBlock = view.children.findByModel(view.collection.at(dropPosition.index-1)).$el; $targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index-1)).$el;
} else { } else {
$targetBlock = view.children.findByModel(view.collection.at(dropPosition.index)).$el; $targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index)).$el;
} }
margin = $targetBlock.outerHeight(true) - $targetBlock.outerHeight(); margin = $targetBlock.outerHeight(true) - $targetBlock.outerHeight();
@ -182,6 +183,7 @@ define([
view.model.get('blocks').length view.model.get('blocks').length
), ),
droppableModel = event.draggable.getDropModel(), droppableModel = event.draggable.getDropModel(),
viewCollection = that.getCollection(),
droppedView, droppedModel, index, tempCollection, tempCollection2; droppedView, droppedModel, index, tempCollection, tempCollection2;
if (dropPosition === undefined) return; if (dropPosition === undefined) return;
@ -193,42 +195,42 @@ define([
if (view.model.get('orientation') === 'horizontal' && droppableModel.get('type') !== 'container') { if (view.model.get('orientation') === 'horizontal' && droppableModel.get('type') !== 'container') {
// Regular blocks always need to be inserted into columns - vertical containers // Regular blocks always need to be inserted into columns - vertical containers
tempCollection = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection = new (EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', orientation: 'vertical'
}); });
tempCollection.get('blocks').add(droppableModel); tempCollection.get('blocks').add(droppableModel);
view.collection.add(tempCollection, {at: index}); viewCollection.add(tempCollection, {at: index});
} else { } else {
view.collection.add(droppableModel, {at: index}); viewCollection.add(droppableModel, {at: index});
} }
droppedView = view.children.findByModel(droppableModel); droppedView = that.getChildren().findByModel(droppableModel);
} else { } else {
// Special insertion by replacing target block with collection // Special insertion by replacing target block with collection
// and inserting dropModel into that // and inserting dropModel into that
var tempModel = view.collection.at(dropPosition.index); var tempModel = viewCollection.at(dropPosition.index);
tempCollection = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection = new (EditorApplication.getBlockTypeModel('container'))({
orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical', orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical'
}); });
view.collection.remove(tempModel); viewCollection.remove(tempModel);
if (tempCollection.get('orientation') === 'horizontal') { if (tempCollection.get('orientation') === 'horizontal') {
if (dropPosition.position === 'before') { if (dropPosition.position === 'before') {
tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', orientation: 'vertical'
}); });
tempCollection2.get('blocks').add(droppableModel); tempCollection2.get('blocks').add(droppableModel);
tempCollection.get('blocks').add(tempCollection2); tempCollection.get('blocks').add(tempCollection2);
} }
tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', orientation: 'vertical'
}); });
tempCollection2.get('blocks').add(tempModel); tempCollection2.get('blocks').add(tempModel);
tempCollection.get('blocks').add(tempCollection2); tempCollection.get('blocks').add(tempCollection2);
if (dropPosition.position === 'after') { if (dropPosition.position === 'after') {
tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({ tempCollection2 = new (EditorApplication.getBlockTypeModel('container'))({
orientation: 'vertical', orientation: 'vertical'
}); });
tempCollection2.get('blocks').add(droppableModel); tempCollection2.get('blocks').add(droppableModel);
tempCollection.get('blocks').add(tempCollection2); tempCollection.get('blocks').add(tempCollection2);
@ -242,21 +244,21 @@ define([
tempCollection.get('blocks').add(droppableModel); tempCollection.get('blocks').add(droppableModel);
} }
} }
view.collection.add(tempCollection, {at: dropPosition.index}); viewCollection.add(tempCollection, {at: dropPosition.index});
// Call post add actions // Call post add actions
droppedView = view.children.findByModel(tempCollection).children.findByModel(droppableModel); droppedView = that.getChildren().findByModel(tempCollection).children.findByModel(droppableModel);
} }
// Call post add actions // Call post add actions
event.draggable.onDrop({ event.draggable.onDrop({
dropBehavior: that, dropBehavior: that,
droppedModel: droppableModel, droppedModel: droppableModel,
droppedView: droppedView, droppedView: droppedView
}); });
that.cleanup(); that.cleanup();
}, }
}); });
}, },
cleanup: function() { cleanup: function() {
@ -290,11 +292,11 @@ define([
unsafe = !!unsafe; unsafe = !!unsafe;
if (this.view.collection.length === 0) { if (this.getCollection().length === 0) {
return { return {
insertionType: 'normal', insertionType: 'normal',
index: 0, index: 0,
position: 'inside', position: 'inside'
}; };
} }
@ -327,7 +329,7 @@ define([
index = indexAndPosition.index; index = indexAndPosition.index;
} }
if (!unsafe && orientation === 'vertical' && insertionType === 'special' && this.view.collection.at(index).get('orientation') === 'horizontal') { if (!unsafe && orientation === 'vertical' && insertionType === 'special' && this.getCollection().at(index).get('orientation') === 'horizontal') {
// Prevent placing horizontal container in another horizontal container, // Prevent placing horizontal container in another horizontal container,
// which would allow breaking the column limit. // which would allow breaking the column limit.
// Switch that to normal insertion // Switch that to normal insertion
@ -345,7 +347,7 @@ define([
return { return {
insertionType: insertionType, // 'normal'|'special' insertionType: insertionType, // 'normal'|'special'
index: index, index: index,
position: position, // 'inside'|'before'|'after' position: position // 'inside'|'before'|'after'
}; };
}, },
_computeNormalIndex: function(eventX, eventY) { _computeNormalIndex: function(eventX, eventY) {
@ -356,7 +358,7 @@ define([
var index = this._computeCellIndex(eventX, eventY), var index = this._computeCellIndex(eventX, eventY),
// TODO: Handle case when there are no children, container is empty // TODO: Handle case when there are no children, container is empty
targetView = this.view.children.findByModel(this.view.collection.at(index)), targetView = this.getChildren().findByModel(this.getCollection().at(index)),
orientation = this.view.model.get('orientation'), orientation = this.view.model.get('orientation'),
element = targetView.$el, element = targetView.$el,
eventOffset, closeOffset, elementDimension; eventOffset, closeOffset, elementDimension;
@ -375,13 +377,13 @@ define([
// First half of the element // First half of the element
return { return {
index: index, index: index,
position: 'before', position: 'before'
}; };
} else { } else {
// Second half of the element // Second half of the element
return { return {
index: index, index: index,
position: 'after', position: 'after'
}; };
} }
}, },
@ -391,7 +393,7 @@ define([
_computeCellIndex: function(eventX, eventY) { _computeCellIndex: function(eventX, eventY) {
var orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation'),
eventOffset = (orientation === 'vertical') ? eventY : eventX, eventOffset = (orientation === 'vertical') ? eventY : eventX,
resultView = this.view.children.find(function(view) { resultView = this.getChildren().find(function(view) {
var element = view.$el, var element = view.$el,
closeOffset, farOffset; closeOffset, farOffset;
@ -414,15 +416,24 @@ define([
_canAcceptNormalInsertion: function() { _canAcceptNormalInsertion: function() {
var orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation'),
depth = this.view.renderOptions.depth, depth = this.view.renderOptions.depth,
childCount = this.view.children.length; childCount = this.getChildren().length;
// Note that depth is zero indexed. Root container has depth=0 // Note that depth is zero indexed. Root container has depth=0
return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit); return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit);
}, },
_canAcceptSpecialInsertion: function() { _canAcceptSpecialInsertion: function() {
var orientation = this.view.model.get('orientation'), var orientation = this.view.model.get('orientation'),
depth = this.view.renderOptions.depth, depth = this.view.renderOptions.depth,
childCount = this.view.children.length; childCount = this.getChildren().length;
return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit); return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit);
}, },
getCollectionView: function() {
return this.view.getChildView('blocks');
},
getChildren: function() {
return this.getCollectionView().children;
},
getCollection: function() {
return this.getCollectionView().collection;
}
}); });
}); });

View File

@ -28,7 +28,7 @@ define([
}, },
onDrop: function(model, view) {}, onDrop: function(model, view) {},
testAttachToInstance: function(model, view) { return true; }, testAttachToInstance: function(model, view) { return true; }
}, },
onRender: function() { onRender: function() {
var that = this, var that = this,
@ -38,7 +38,7 @@ define([
if (!this.options.testAttachToInstance(this.view.model, this.view)) return; if (!this.options.testAttachToInstance(this.view.model, this.view)) return;
interactable = interact(this.$el.get(0), { interactable = interact(this.$el.get(0), {
ignoreFrom: this.options.ignoreSelector, ignoreFrom: this.options.ignoreSelector
}).draggable({ }).draggable({
// allow dragging of multple elements at the same time // allow dragging of multple elements at the same time
max: Infinity, max: Infinity,
@ -111,9 +111,10 @@ define([
that.view.$el.removeClass('mailpoet_hidden'); that.view.$el.removeClass('mailpoet_hidden');
} }
} }
}, }
}) })
.preventDefault('auto') .preventDefault('auto')
.styleCursor(false)
.actionChecker(function (pointer, event, action) { .actionChecker(function (pointer, event, action) {
// Disable dragging with right click // Disable dragging with right click
if (event.button !== 0) { if (event.button !== 0) {
@ -136,6 +137,6 @@ define([
// Delegate to view's event handler // Delegate to view's event handler
that.options.onDrop.apply(that, [options]); that.options.onDrop.apply(that, [options]);
}; };
}, }
}); });
}); });

View File

@ -5,19 +5,19 @@
*/ */
define([ define([
'backbone.marionette', 'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, BehaviorsLookup) { ], function(Marionette, BehaviorsLookup) {
BehaviorsLookup.HighlightEditingBehavior = Marionette.Behavior.extend({ BehaviorsLookup.HighlightEditingBehavior = Marionette.Behavior.extend({
modelEvents: { modelEvents: {
'startEditing': 'enableHighlight', 'startEditing': 'enableHighlight',
'stopEditing': 'disableHighlight', 'stopEditing': 'disableHighlight'
}, },
enableHighlight: function() { enableHighlight: function() {
this.$el.addClass('mailpoet_highlight'); this.$el.addClass('mailpoet_highlight');
}, },
disableHighlight: function() { disableHighlight: function() {
this.$el.removeClass('mailpoet_highlight'); this.$el.removeClass('mailpoet_highlight');
}, }
}); });
}); });

View File

@ -15,11 +15,11 @@ define([
resizeHandleSelector: true, // true will use edges of the element itself resizeHandleSelector: true, // true will use edges of the element itself
transformationFunction: function(y) { return y; }, transformationFunction: function(y) { return y; },
minLength: 0, minLength: 0,
modelField: 'styles.block.height', modelField: 'styles.block.height'
}, },
events: { events: {
"mouseenter": 'showResizeHandle', "mouseenter": 'showResizeHandle',
"mouseleave": 'hideResizeHandle', "mouseleave": 'hideResizeHandle'
}, },
onRender: function() { onRender: function() {
this.attachResize(); this.attachResize();
@ -37,8 +37,8 @@ define([
top: false, top: false,
left: false, left: false,
right: false, right: false,
bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector, bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector
}, }
}).on('resizestart', function(event) { }).on('resizestart', function(event) {
that.isBeingResized = true; that.isBeingResized = true;
that.$el.addClass('mailpoet_resize_active'); that.$el.addClass('mailpoet_resize_active');
@ -63,6 +63,6 @@ define([
if (typeof this.options.resizeHandleSelector === 'string') { if (typeof this.options.resizeHandleSelector === 'string') {
this.view.$(this.options.resizeHandleSelector).addClass('mailpoet_hidden'); this.view.$(this.options.resizeHandleSelector).addClass('mailpoet_hidden');
} }
}, }
}); });
}); });

View File

@ -6,15 +6,15 @@
define([ define([
'backbone.marionette', 'backbone.marionette',
'jquery', 'jquery',
'newsletter_editor/behaviors/BehaviorsLookup', 'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, jQuery, BehaviorsLookup) { ], function(Marionette, jQuery, BehaviorsLookup) {
BehaviorsLookup.ShowSettingsBehavior = Marionette.Behavior.extend({ BehaviorsLookup.ShowSettingsBehavior = Marionette.Behavior.extend({
defaults: { defaults: {
ignoreFrom: '', // selector ignoreFrom: '' // selector
}, },
events: { events: {
'click .mailpoet_content': 'showSettings', 'click .mailpoet_content': 'showSettings'
}, },
showSettings: function(event) { showSettings: function(event) {
if(!this.isIgnoredElement(event.target)) { if(!this.isIgnoredElement(event.target)) {
@ -25,7 +25,7 @@ define([
return this.options.ignoreFrom return this.options.ignoreFrom
&& this.options.ignoreFrom.length > 0 && this.options.ignoreFrom.length > 0
&& jQuery(element).is(this.options.ignoreFrom); && jQuery(element).is(this.options.ignoreFrom);
}, }
}); });
}); });

View File

@ -32,7 +32,7 @@ define([
collection.remove(model); collection.remove(model);
collection.add(model, { at: newIndex }); collection.add(model, { at: newIndex });
}, },
items: this.options.items, items: this.options.items
}); });
} }
} }

View File

@ -18,7 +18,7 @@ define([
invalidElements: "script", invalidElements: "script",
blockFormats: 'Paragraph=p', blockFormats: 'Paragraph=p',
plugins: "link textcolor colorpicker mailpoet_shortcodes", plugins: "link textcolor colorpicker mailpoet_shortcodes",
configurationFilter: function(originalConfig) { return originalConfig; }, configurationFilter: function(originalConfig) { return originalConfig; }
}, },
onDomRefresh: function() { onDomRefresh: function() {
var that = this; var that = this;
@ -60,16 +60,23 @@ define([
editor.on('click', function(e) { editor.on('click', function(e) {
editor.focus(); editor.focus();
if (that._isActivationClick) {
editor.selection.setRng(
tinymce.dom.RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc())
);
that._isActivationClick = false;
}
}); });
editor.on('focus', function(e) { editor.on('focus', function(e) {
that.view.triggerMethod('text:editor:focus'); that.view.triggerMethod('text:editor:focus');
that._isActivationClick = true;
}); });
editor.on('blur', function(e) { editor.on('blur', function(e) {
that.view.triggerMethod('text:editor:blur'); that.view.triggerMethod('text:editor:blur');
}); });
}, }
})); }));
} }
}); });

View File

@ -53,7 +53,7 @@ define([
}); });
CommunicationComponent.getBulkTransformedPosts({ CommunicationComponent.getBulkTransformedPosts({
blocks: blocks, blocks: blocks
}).then(_.partial(this.refreshBlocks, models)); }).then(_.partial(this.refreshBlocks, models));
}, },
refreshBlocks: function(models, renderedBlocks) { refreshBlocks: function(models, renderedBlocks) {
@ -65,7 +65,7 @@ define([
model.trigger('refreshPosts', contents); model.trigger('refreshPosts', contents);
} }
); );
}, }
}); });
Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({ Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({
@ -97,14 +97,14 @@ define([
sortBy: 'newest', // 'newest'|'oldest', sortBy: 'newest', // 'newest'|'oldest',
showDivider: true, // true|false showDivider: true, // true|false
divider: {}, divider: {},
_container: new (App.getBlockTypeModel('container'))(), _container: new (App.getBlockTypeModel('container'))()
}, App.getConfig().get('blockDefaults.automatedLatestContent')); }, App.getConfig().get('blockDefaults.automatedLatestContent'));
}, },
relations: function() { relations: function() {
return { return {
readMoreButton: App.getBlockTypeModel('button'), readMoreButton: App.getBlockTypeModel('button'),
divider: App.getBlockTypeModel('divider'), divider: App.getBlockTypeModel('divider'),
_container: App.getBlockTypeModel('container'), _container: App.getBlockTypeModel('container')
}; };
}, },
initialize: function() { initialize: function() {
@ -124,23 +124,29 @@ define([
*/ */
_scheduleFetchPosts: function() { _scheduleFetchPosts: function() {
App.getChannel().trigger('automatedLatestContentRefresh'); App.getChannel().trigger('automatedLatestContentRefresh');
}, }
}); });
Module.AutomatedLatestContentBlockView = base.BlockView.extend({ Module.AutomatedLatestContentBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_automated_latest_content_block mailpoet_droppable_block", className: "mailpoet_block mailpoet_automated_latest_content_block mailpoet_droppable_block",
initialize: function() {
function replaceButtonStylesHandler(data) {
this.model.set({"readMoreButton": data});
}
App.getChannel().on("replaceAllButtonStyles", replaceButtonStylesHandler.bind(this));
},
getTemplate: function() { return templates.automatedLatestContentBlock; }, getTemplate: function() { return templates.automatedLatestContentBlock; },
regions: { regions: {
toolsRegion: '.mailpoet_tools', toolsRegion: '.mailpoet_tools',
postsRegion: '.mailpoet_automated_latest_content_block_posts', postsRegion: '.mailpoet_automated_latest_content_block_posts'
}, },
modelEvents: _.extend( modelEvents: _.extend(
_.omit(base.BlockView.prototype.modelEvents, 'change'), _.omit(base.BlockView.prototype.modelEvents, 'change'),
{ {
'postsChanged': 'render', 'postsChanged': 'render'
}), }),
events: _.extend(base.BlockView.prototype.events, { events: _.extend(base.BlockView.prototype.events, {
'click .mailpoet_automated_latest_content_block_overlay': 'showSettings', 'click .mailpoet_automated_latest_content_block_overlay': 'showSettings'
}), }),
onDragSubstituteBy: function() { return Module.AutomatedLatestContentWidgetView; }, onDragSubstituteBy: function() { return Module.AutomatedLatestContentWidgetView; },
onRender: function() { onRender: function() {
@ -148,16 +154,16 @@ define([
renderOptions = { renderOptions = {
disableTextEditor: true, disableTextEditor: true,
disableDragAndDrop: true, disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'), emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
}; };
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model }); this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
this.postsRegion.show(new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions })); this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions }));
}, }
}); });
Module.AutomatedLatestContentBlockToolsView = base.BlockToolsView.extend({ Module.AutomatedLatestContentBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.AutomatedLatestContentBlockSettingsView; }, getSettingsView: function() { return Module.AutomatedLatestContentBlockSettingsView; }
}); });
// Sidebar view container // Sidebar view container
@ -186,12 +192,7 @@ define([
"input .mailpoet_automated_latest_content_categories": _.partial(this.changeField, "categoriesPrecededBy"), "input .mailpoet_automated_latest_content_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"input .mailpoet_automated_latest_content_read_more_text": _.partial(this.changeField, "readMoreText"), "input .mailpoet_automated_latest_content_read_more_text": _.partial(this.changeField, "readMoreText"),
"change .mailpoet_automated_latest_content_sort_by": _.partial(this.changeField, "sortBy"), "change .mailpoet_automated_latest_content_sort_by": _.partial(this.changeField, "sortBy"),
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
};
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
}; };
}, },
onRender: function() { onRender: function() {
@ -246,8 +247,8 @@ define([
} }
) )
}; };
}, }
}, }
}).on({ }).on({
'select2:select': function(event) { 'select2:select': function(event) {
var terms = that.model.get('terms'); var terms = that.model.get('terms');
@ -260,7 +261,7 @@ define([
terms.remove(event.params.data); terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly // Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON()); that.model.set('terms', terms.toJSON());
}, }
}).trigger( 'change' ); }).trigger( 'change' );
}, },
toggleDisplayOptions: function(event) { toggleDisplayOptions: function(event) {
@ -281,8 +282,8 @@ define([
renderOptions: { renderOptions: {
displayFormat: 'subpanel', displayFormat: 'subpanel',
hideLink: true, hideLink: true,
hideApplyToAll: true, hideApplyToAll: true
}, }
})).render(); })).render();
}, },
showDividerSettings: function(event) { showDividerSettings: function(event) {
@ -291,8 +292,8 @@ define([
model: this.model.get('divider'), model: this.model.get('divider'),
renderOptions: { renderOptions: {
displayFormat: 'subpanel', displayFormat: 'subpanel',
hideApplyToAll: true, hideApplyToAll: true
}, }
})).render(); })).render();
}, },
changeReadMoreType: function(event) { changeReadMoreType: function(event) {
@ -355,11 +356,11 @@ define([
_.each(postTypes, function(type) { _.each(postTypes, function(type) {
select.append(jQuery('<option>', { select.append(jQuery('<option>', {
value: type.name, value: type.name,
text: type.label, text: type.label
})); }));
}); });
select.val(selectedValue); select.val(selectedValue);
}, }
}); });
Module.AutomatedLatestContentWidgetView = base.WidgetView.extend({ Module.AutomatedLatestContentWidgetView = base.WidgetView.extend({
@ -372,25 +373,25 @@ define([
}, },
onDrop: function(options) { onDrop: function(options) {
options.droppedView.triggerMethod('showSettings'); options.droppedView.triggerMethod('showSettings');
}, }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('automatedLatestContent', { App.registerBlockType('automatedLatestContent', {
blockModel: Module.AutomatedLatestContentBlockModel, blockModel: Module.AutomatedLatestContentBlockModel,
blockView: Module.AutomatedLatestContentBlockView, blockView: Module.AutomatedLatestContentBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'automatedLatestContent', name: 'automatedLatestContent',
widgetView: Module.AutomatedLatestContentWidgetView, widgetView: Module.AutomatedLatestContentWidgetView,
priority: 97, priority: 97
}); });
}); });
App.on('start', function() { App.on('start', function(App, options) {
App._ALCSupervisor = new Module.ALCSupervisor(); App._ALCSupervisor = new Module.ALCSupervisor();
App._ALCSupervisor.refresh(); App._ALCSupervisor.refresh();
}); });

View File

@ -17,7 +17,7 @@ define([
"use strict"; "use strict";
var Module = {}, var Module = {},
AugmentedView = Marionette.LayoutView.extend({}); AugmentedView = Marionette.View.extend({});
Module.BlockModel = SuperModel.extend({ Module.BlockModel = SuperModel.extend({
stale: [], // Attributes to be removed upon saving stale: [], // Attributes to be removed upon saving
@ -42,20 +42,21 @@ define([
}, },
getChildren: function() { getChildren: function() {
return []; return [];
}, }
}); });
Module.BlockView = AugmentedView.extend({ Module.BlockView = AugmentedView.extend({
regions: { regions: {
toolsRegion: '> .mailpoet_tools', toolsRegion: '> .mailpoet_tools'
}, },
modelEvents: { modelEvents: {
'change': 'render', 'change': 'render',
'delete': 'deleteBlock', 'delete': 'deleteBlock',
'duplicate': 'duplicateBlock'
}, },
events: { events: {
"mouseenter": "showTools", "mouseenter": "showTools",
"mouseleave": "hideTools", "mouseleave": "hideTools"
}, },
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
@ -78,14 +79,14 @@ define([
WidgetView.destroy(); WidgetView.destroy();
return node; return node;
} }
}, }
}, },
HighlightEditingBehavior: {}, HighlightEditingBehavior: {}
}, },
templateHelpers: function() { templateContext: function() {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
viewCid: this.cid, viewCid: this.cid
}; };
}, },
constructor: function() { constructor: function() {
@ -125,6 +126,12 @@ define([
return this.model.clone(); return this.model.clone();
}.bind(this); }.bind(this);
}, },
disableDragging: function() {
this.$el.addClass('mailpoet_ignore_drag');
},
enableDragging: function() {
this.$el.removeClass('mailpoet_ignore_drag');
},
showBlock: function() { showBlock: function() {
if (this._isFirstRender) { if (this._isFirstRender) {
this.transitionIn(); this.transitionIn();
@ -136,6 +143,9 @@ define([
this.model.destroy(); this.model.destroy();
}.bind(this)); }.bind(this));
}, },
duplicateBlock: function() {
this.model.collection.add(this.model.toJSON(), {at: this.model.collection.findIndex(this.model)});
},
transitionIn: function() { transitionIn: function() {
return this._transition('slideDown', 'fadeIn', 'easeOut'); return this._transition('slideDown', 'fadeIn', 'easeOut');
}, },
@ -152,19 +162,19 @@ define([
easing: easing, easing: easing,
complete: function() { complete: function() {
promise.resolve(); promise.resolve();
}.bind(this), }.bind(this)
} }
).velocity( ).velocity(
fadeDirection, fadeDirection,
{ {
duration: 250, duration: 250,
easing: easing, easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel queue: false // Do not enqueue, trigger animation in parallel
} }
); );
return promise; return promise;
}, }
}); });
Module.BlockToolsView = AugmentedView.extend({ Module.BlockToolsView = AugmentedView.extend({
@ -174,12 +184,14 @@ define([
"click .mailpoet_delete_block_activate": "showDeletionConfirmation", "click .mailpoet_delete_block_activate": "showDeletionConfirmation",
"click .mailpoet_delete_block_cancel": "hideDeletionConfirmation", "click .mailpoet_delete_block_cancel": "hideDeletionConfirmation",
"click .mailpoet_delete_block_confirm": "deleteBlock", "click .mailpoet_delete_block_confirm": "deleteBlock",
"click .mailpoet_duplicate_block": "duplicateBlock"
}, },
// Markers of whether these particular tools will be used for this instance // Markers of whether these particular tools will be used for this instance
tools: { tools: {
settings: true, settings: true,
delete: true, delete: true,
move: true, duplicate: true,
move: true
}, },
getSettingsView: function() { return Module.BlockSettingsView; }, getSettingsView: function() { return Module.BlockSettingsView; },
initialize: function(options) { initialize: function(options) {
@ -193,11 +205,11 @@ define([
this.on('hideTools', this.hideDeletionConfirmation, this); this.on('hideTools', this.hideDeletionConfirmation, this);
this.on('showSettings', this.changeSettings); this.on('showSettings', this.changeSettings);
}, },
templateHelpers: function() { templateContext: function() {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
viewCid: this.cid, viewCid: this.cid,
tools: this.tools, tools: this.tools
}; };
}, },
changeSettings: function(options) { changeSettings: function(options) {
@ -215,12 +227,17 @@ define([
this.model.trigger('delete'); this.model.trigger('delete');
return false; return false;
}, },
duplicateBlock: function(event) {
event.preventDefault();
this.model.trigger('duplicate');
return false;
}
}); });
Module.BlockSettingsView = Marionette.LayoutView.extend({ Module.BlockSettingsView = Marionette.View.extend({
className: 'mailpoet_editor_settings', className: 'mailpoet_editor_settings',
behaviors: { behaviors: {
ColorPickerBehavior: {}, ColorPickerBehavior: {}
}, },
initialize: function(params) { initialize: function(params) {
this.model.trigger('startEditing'); this.model.trigger('startEditing');
@ -231,7 +248,7 @@ define([
width: App.getConfig().get('sidepanelWidth'), width: App.getConfig().get('sidepanelWidth'),
onCancel: function() { onCancel: function() {
this.destroy(); this.destroy();
}.bind(this), }.bind(this)
}; };
this.renderOptions = params.renderOptions || {}; this.renderOptions = params.renderOptions || {};
if (this.renderOptions.displayFormat === 'subpanel') { if (this.renderOptions.displayFormat === 'subpanel') {
@ -240,6 +257,11 @@ define([
MailPoet.Modal.panel(panelParams); MailPoet.Modal.panel(panelParams);
} }
}, },
templateContext: function() {
return {
model: this.model.toJSON()
};
},
close: function(event) { close: function(event) {
this.destroy(); this.destroy();
}, },
@ -268,10 +290,10 @@ define([
onBeforeDestroy: function() { onBeforeDestroy: function() {
MailPoet.Modal.close(); MailPoet.Modal.close();
this.model.trigger('stopEditing'); this.model.trigger('stopEditing');
}, }
}); });
Module.WidgetView = Marionette.ItemView.extend({ Module.WidgetView = Marionette.View.extend({
className: 'mailpoet_widget mailpoet_droppable_block mailpoet_droppable_widget', className: 'mailpoet_widget mailpoet_droppable_block mailpoet_droppable_widget',
behaviors: { behaviors: {
DraggableBehavior: { DraggableBehavior: {
@ -279,7 +301,7 @@ define([
throw "Unsupported operation"; throw "Unsupported operation";
} }
} }
}, }
}); });
return Module; return Module;

View File

@ -33,11 +33,11 @@ define([
fontFamily: 'Arial', fontFamily: 'Arial',
fontSize: '16px', fontSize: '16px',
fontWeight: 'normal', // 'normal'|'bold' fontWeight: 'normal', // 'normal'|'bold'
textAlign: 'center', textAlign: 'center'
}, }
}, }
}, App.getConfig().get('blockDefaults.button')); }, App.getConfig().get('blockDefaults.button'));
}, }
}); });
Module.ButtonBlockView = base.BlockView.extend({ Module.ButtonBlockView = base.BlockView.extend({
@ -45,7 +45,7 @@ define([
getTemplate: function() { return templates.buttonBlock; }, getTemplate: function() { return templates.buttonBlock; },
onDragSubstituteBy: function() { return Module.ButtonWidgetView; }, onDragSubstituteBy: function() { return Module.ButtonWidgetView; },
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {}, ShowSettingsBehavior: {}
}), }),
initialize: function() { initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
@ -56,12 +56,12 @@ define([
}, },
onRender: function() { onRender: function() {
this.toolsView = new Module.ButtonBlockToolsView({ model: this.model }); this.toolsView = new Module.ButtonBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, }
}); });
Module.ButtonBlockToolsView = base.BlockToolsView.extend({ Module.ButtonBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.ButtonBlockSettingsView; }, getSettingsView: function() { return Module.ButtonBlockSettingsView; }
}); });
Module.ButtonBlockSettingsView = base.BlockSettingsView.extend({ Module.ButtonBlockSettingsView = base.BlockSettingsView.extend({
@ -95,15 +95,14 @@ define([
"input .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)), "input .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"click .mailpoet_field_button_replace_all_styles": "applyToAll", "click .mailpoet_field_button_replace_all_styles": "applyToAll",
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
}; };
}, },
templateHelpers: function() { templateContext: function() {
return { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
model: this.model.toJSON(),
availableStyles: App.getAvailableStyles().toJSON(), availableStyles: App.getAvailableStyles().toJSON(),
renderOptions: this.renderOptions, renderOptions: this.renderOptions
}; });
}, },
applyToAll: function() { applyToAll: function() {
App.getChannel().trigger('replaceAllButtonStyles', _.pick(this.model.toJSON(), 'styles', 'type')); App.getChannel().trigger('replaceAllButtonStyles', _.pick(this.model.toJSON(), 'styles', 'type'));
@ -128,21 +127,21 @@ define([
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function() {
return new Module.ButtonBlockModel(); return new Module.ButtonBlockModel();
}, }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('button', { App.registerBlockType('button', {
blockModel: Module.ButtonBlockModel, blockModel: Module.ButtonBlockModel,
blockView: Module.ButtonBlockView, blockView: Module.ButtonBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'button', name: 'button',
widgetView: Module.ButtonWidgetView, widgetView: Module.ButtonWidgetView,
priority: 92, priority: 92
}); });
}); });

View File

@ -30,12 +30,12 @@ define([
// TODO: If type has no registered model, use a backup one // TODO: If type has no registered model, use a backup one
return new Type(block, {parse: true}); return new Type(block, {parse: true});
}); });
}, }
}); });
Module.ContainerBlockModel = base.BlockModel.extend({ Module.ContainerBlockModel = base.BlockModel.extend({
relations: { relations: {
blocks: BlockCollection, blocks: BlockCollection
}, },
defaults: function() { defaults: function() {
return this._getDefaults({ return this._getDefaults({
@ -43,10 +43,10 @@ define([
orientation: 'vertical', orientation: 'vertical',
styles: { styles: {
block: { block: {
backgroundColor: 'transparent', backgroundColor: 'transparent'
}, }
}, },
blocks: new BlockCollection(), blocks: new BlockCollection()
}, App.getConfig().get('blockDefaults.container')); }, App.getConfig().get('blockDefaults.container'));
}, },
validate: function() { validate: function() {
@ -60,7 +60,7 @@ define([
// If container has any blocks - add them to a collection // If container has any blocks - add them to a collection
if (response.type === 'container' && _.has(response, 'blocks')) { if (response.type === 'container' && _.has(response, 'blocks')) {
response.blocks = new BlockCollection(response.blocks, { response.blocks = new BlockCollection(response.blocks, {
parse: true, parse: true
}); });
} }
return response; return response;
@ -71,32 +71,46 @@ define([
}); });
return _.flatten(models); return _.flatten(models);
}, }
}); });
Module.ContainerBlockView = Marionette.CompositeView.extend({ Module.ContainerBlocksView = Marionette.CollectionView.extend({
regionClass: Marionette.Region, className: 'mailpoet_container',
childView: function(model) {
return App.getBlockTypeView(model.get('type'));
},
childViewOptions: function() {
var newRenderOptions = _.clone(this.renderOptions);
if (newRenderOptions.depth !== undefined) {
newRenderOptions.depth += 1;
}
return {
renderOptions: newRenderOptions
};
},
emptyView: function() { return Module.ContainerBlockEmptyView; },
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; },
initialize: function(options) {
this.renderOptions = options.renderOptions;
}
});
Module.ContainerBlockView = base.BlockView.extend({
regions: _.extend({}, base.BlockView.prototype.regions, {
blocks: {
el: '> .mailpoet_container',
replaceElement: true
}
}),
className: 'mailpoet_block mailpoet_container_block mailpoet_droppable_block mailpoet_droppable_layout_block', className: 'mailpoet_block mailpoet_container_block mailpoet_droppable_block mailpoet_droppable_layout_block',
getTemplate: function() { return templates.containerBlock; }, getTemplate: function() { return templates.containerBlock; },
childViewContainer: '> .mailpoet_container', events: _.extend({}, base.BlockView.prototype.events, {
getEmptyView: function() { return Module.ContainerBlockEmptyView; }, "click .mailpoet_newsletter_layer_selector": "toggleEditingLayer"
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; }, }),
modelEvents: {
'change': 'render',
'delete': 'deleteBlock',
},
events: {
"mouseenter": "showTools",
"mouseleave": "hideTools",
"click .mailpoet_newsletter_layer_selector": "toggleEditingLayer",
},
regions: {
toolsRegion: '> .mailpoet_tools',
},
ui: { ui: {
tools: '> .mailpoet_tools' tools: '> .mailpoet_tools'
}, },
behaviors: { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ContainerDropZoneBehavior: {}, ContainerDropZoneBehavior: {},
DraggableBehavior: { DraggableBehavior: {
cloneOriginal: true, cloneOriginal: true,
@ -123,10 +137,9 @@ define([
// Attach Draggable only to layout containers and disable it // Attach Draggable only to layout containers and disable it
// for root and column containers. // for root and column containers.
return view.renderOptions.depth === 1; return view.renderOptions.depth === 1;
}, }
}, }
HighlightEditingBehavior: {} }),
},
onDragSubstituteBy: function() { onDragSubstituteBy: function() {
// For two and three column layouts display their respective widgets, // For two and three column layouts display their respective widgets,
// otherwise always default to one column layout widget // otherwise always default to one column layout widget
@ -137,52 +150,31 @@ define([
return Module.OneColumnContainerWidgetView; return Module.OneColumnContainerWidgetView;
}, },
constructor: function() {
// Set the block collection to be handled by this view as well
arguments[0].collection = arguments[0].model.get('blocks');
Marionette.CompositeView.apply(this, arguments);
this.$el.addClass('mailpoet_editor_view_' + this.cid);
},
initialize: function(options) { initialize: function(options) {
base.BlockView.prototype.initialize.apply(this, arguments);
this.renderOptions = _.defaults(options.renderOptions || {}, {}); this.renderOptions = _.defaults(options.renderOptions || {}, {});
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
// Determines which view type should be used for a child
getChildView: function(model) {
// TODO: If type does not have a type registered, use a generic one
return App.getBlockTypeView(model.get('type'));
},
childViewOptions: function() {
var newRenderOptions = _.clone(this.renderOptions);
if (newRenderOptions.depth !== undefined) {
newRenderOptions.depth += 1;
}
return {
renderOptions: newRenderOptions
};
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
viewCid: this.cid,
};
}, },
onRender: function() { onRender: function() {
this._rebuildRegions();
this.toolsView = new Module.ContainerBlockToolsView({ this.toolsView = new Module.ContainerBlockToolsView({
model: this.model, model: this.model,
tools: { tools: {
settings: this.renderOptions.depth === 1, settings: this.renderOptions.depth === 1,
delete: this.renderOptions.depth === 1, delete: this.renderOptions.depth === 1,
duplicate: true,
move: this.renderOptions.depth === 1, move: this.renderOptions.depth === 1,
layerSelector: false, layerSelector: false
}, }
}); });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, this.showChildView('blocks', new Module.ContainerBlocksView({
onBeforeDestroy: function() { collection: this.model.get('blocks'),
this.regionManager.destroy(); renderOptions: this.renderOptions
}));
// TODO: Look for a better way to do this than here
// Sets child container orientation HTML class here, as child CollectionView won't have access to model and will overwrite existing region element instead
this.$('> .mailpoet_container').attr('class', 'mailpoet_container mailpoet_container_' + this.model.get('orientation'));
}, },
showTools: function() { showTools: function() {
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) { if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
@ -221,86 +213,24 @@ define([
enableContainerLayer(); enableContainerLayer();
} }
event.stopPropagation(); event.stopPropagation();
}, }
_buildRegions: function(regions) {
var that = this;
var defaults = {
regionClass: this.getOption('regionClass'),
parentEl: function() { return that.$el; }
};
return this.regionManager.addRegions(regions, defaults);
},
_rebuildRegions: function() {
if (this.regionManager === undefined) {
this.regionManager = new Marionette.RegionManager();
}
this.regionManager.destroy();
_.extend(this, this._buildRegions(this.regions));
},
getDropFunc: function() {
return function() {
return this.model.clone();
}.bind(this);
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('slideDown', 'fadeIn', 'easeIn');
},
transitionOut: function() {
return this._transition('slideUp', 'fadeOut', 'easeOut');
},
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
return promise;
},
}); });
Module.ContainerBlockEmptyView = Marionette.ItemView.extend({ Module.ContainerBlockEmptyView = Marionette.View.extend({
getTemplate: function() { return templates.containerEmpty; }, getTemplate: function() { return templates.containerEmpty; },
initialize: function(options) { initialize: function(options) {
this.renderOptions = _.defaults(options.renderOptions || {}, {}); this.renderOptions = _.defaults(options.renderOptions || {}, {});
}, },
templateHelpers: function() { templateContext: function() {
return { return {
isRoot: this.renderOptions.depth === 0, isRoot: this.renderOptions.depth === 0,
emptyContainerMessage: this.renderOptions.emptyContainerMessage || '', emptyContainerMessage: this.renderOptions.emptyContainerMessage || ''
}; };
}, }
}); });
Module.ContainerBlockToolsView = base.BlockToolsView.extend({ Module.ContainerBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.ContainerBlockSettingsView; }, getSettingsView: function() { return Module.ContainerBlockSettingsView; }
}); });
Module.ContainerBlockSettingsView = base.BlockSettingsView.extend({ Module.ContainerBlockSettingsView = base.BlockSettingsView.extend({
@ -308,44 +238,44 @@ define([
events: function() { events: function() {
return { return {
"change .mailpoet_field_container_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"), "change .mailpoet_field_container_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
}; };
}, },
regions: { regions: {
columnsSettingsRegion: '.mailpoet_container_columns_settings', columnsSettingsRegion: '.mailpoet_container_columns_settings'
}, },
initialize: function() { initialize: function() {
base.BlockSettingsView.prototype.initialize.apply(this, arguments); base.BlockSettingsView.prototype.initialize.apply(this, arguments);
this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({ this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({
collection: this.model.get('blocks'), collection: this.model.get('blocks')
}); });
}, },
onRender: function() { onRender: function() {
this.columnsSettingsRegion.show(this._columnsSettingsView); this.showChildView('columnsSettingsRegion', this._columnsSettingsView);
}, }
}); });
Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({ Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
getChildView: function() { return Module.ContainerBlockColumnSettingsView; }, childView: function() { return Module.ContainerBlockColumnSettingsView; },
childViewOptions: function(model, index) { childViewOptions: function(model, index) {
return { return {
columnIndex: index, columnIndex: index
}; };
}, }
}); });
Module.ContainerBlockColumnSettingsView = Marionette.ItemView.extend({ Module.ContainerBlockColumnSettingsView = Marionette.View.extend({
getTemplate: function() { return templates.containerBlockColumnSettings; }, getTemplate: function() { return templates.containerBlockColumnSettings; },
initialize: function(options) { initialize: function(options) {
this.columnNumber = (options.columnIndex || 0) + 1; this.columnNumber = (options.columnIndex || 0) + 1;
}, },
templateHelpers: function() { templateContext: function() {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
columnNumber: this.columnNumber, columnNumber: this.columnNumber
}; };
}, }
}); });
Module.OneColumnContainerWidgetView = base.WidgetView.extend({ Module.OneColumnContainerWidgetView = base.WidgetView.extend({
@ -358,12 +288,12 @@ define([
return new Module.ContainerBlockModel({ return new Module.ContainerBlockModel({
orientation: 'horizontal', orientation: 'horizontal',
blocks: [ blocks: [
new Module.ContainerBlockModel(), new Module.ContainerBlockModel()
] ]
}); });
} }
} }
}, }
}); });
Module.TwoColumnContainerWidgetView = base.WidgetView.extend({ Module.TwoColumnContainerWidgetView = base.WidgetView.extend({
@ -377,12 +307,12 @@ define([
orientation: 'horizontal', orientation: 'horizontal',
blocks: [ blocks: [
new Module.ContainerBlockModel(), new Module.ContainerBlockModel(),
new Module.ContainerBlockModel(), new Module.ContainerBlockModel()
] ]
}); });
} }
} }
}, }
}); });
Module.ThreeColumnContainerWidgetView = base.WidgetView.extend({ Module.ThreeColumnContainerWidgetView = base.WidgetView.extend({
@ -397,36 +327,36 @@ define([
blocks: [ blocks: [
new Module.ContainerBlockModel(), new Module.ContainerBlockModel(),
new Module.ContainerBlockModel(), new Module.ContainerBlockModel(),
new Module.ContainerBlockModel(), new Module.ContainerBlockModel()
] ]
}); });
} }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('container', { App.registerBlockType('container', {
blockModel: Module.ContainerBlockModel, blockModel: Module.ContainerBlockModel,
blockView: Module.ContainerBlockView, blockView: Module.ContainerBlockView
}); });
App.registerLayoutWidget({ App.registerLayoutWidget({
name: 'oneColumnLayout', name: 'oneColumnLayout',
priority: 100, priority: 100,
widgetView: Module.OneColumnContainerWidgetView, widgetView: Module.OneColumnContainerWidgetView
}); });
App.registerLayoutWidget({ App.registerLayoutWidget({
name: 'twoColumnLayout', name: 'twoColumnLayout',
priority: 100, priority: 100,
widgetView: Module.TwoColumnContainerWidgetView, widgetView: Module.TwoColumnContainerWidgetView
}); });
App.registerLayoutWidget({ App.registerLayoutWidget({
name: 'threeColumnLayout', name: 'threeColumnLayout',
priority: 100, priority: 100,
widgetView: Module.ThreeColumnContainerWidgetView, widgetView: Module.ThreeColumnContainerWidgetView
}); });
}); });

View File

@ -24,11 +24,11 @@ define([
padding: '12px', padding: '12px',
borderStyle: 'solid', borderStyle: 'solid',
borderWidth: '1px', borderWidth: '1px',
borderColor: '#000000', borderColor: '#000000'
}, }
}, }
}, App.getConfig().get('blockDefaults.divider')); }, App.getConfig().get('blockDefaults.divider'));
}, }
}); });
Module.DividerBlockView = base.BlockView.extend({ Module.DividerBlockView = base.BlockView.extend({
@ -41,11 +41,11 @@ define([
resizeHandleSelector: '.mailpoet_resize_handle', resizeHandleSelector: '.mailpoet_resize_handle',
transformationFunction: function(y) { return y / 2; }, transformationFunction: function(y) { return y / 2; },
minLength: 0, // TODO: Move this number to editor configuration minLength: 0, // TODO: Move this number to editor configuration
modelField: 'styles.block.padding', modelField: 'styles.block.padding'
}, },
ShowSettingsBehavior: { ShowSettingsBehavior: {
ignoreFrom: '.mailpoet_resize_handle' ignoreFrom: '.mailpoet_resize_handle'
}, }
}, base.BlockView.prototype.behaviors), }, base.BlockView.prototype.behaviors),
onDragSubstituteBy: function() { return Module.DividerWidgetView; }, onDragSubstituteBy: function() { return Module.DividerWidgetView; },
initialize: function() { initialize: function() {
@ -59,14 +59,14 @@ define([
this.listenTo(this.model, 'change:src change:styles.block.backgroundColor change:styles.block.borderStyle change:styles.block.borderWidth change:styles.block.borderColor applyToAll', this.render); this.listenTo(this.model, 'change:src change:styles.block.backgroundColor change:styles.block.borderStyle change:styles.block.borderWidth change:styles.block.borderColor applyToAll', this.render);
this.listenTo(this.model, 'change:styles.block.padding', this.changePadding); this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
}, },
templateHelpers: function() { templateContext: function() {
return _.extend({ return _.extend({
totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px', totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px'
}, base.BlockView.prototype.templateHelpers.apply(this)); }, base.BlockView.prototype.templateContext.apply(this));
}, },
onRender: function() { onRender: function() {
this.toolsView = new Module.DividerBlockToolsView({ model: this.model }); this.toolsView = new Module.DividerBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onBeforeDestroy: function() { onBeforeDestroy: function() {
App.getChannel().off('replaceAllDividers', this._replaceDividerHandler); App.getChannel().off('replaceAllDividers', this._replaceDividerHandler);
@ -76,11 +76,11 @@ define([
this.$('.mailpoet_content').css('padding-top', this.model.get('styles.block.padding')); this.$('.mailpoet_content').css('padding-top', this.model.get('styles.block.padding'));
this.$('.mailpoet_content').css('padding-bottom', this.model.get('styles.block.padding')); this.$('.mailpoet_content').css('padding-bottom', this.model.get('styles.block.padding'));
this.$('.mailpoet_resize_handle_text').text(parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px'); this.$('.mailpoet_resize_handle_text').text(parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px');
}, }
}); });
Module.DividerBlockToolsView = base.BlockToolsView.extend({ Module.DividerBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.DividerBlockSettingsView; }, getSettingsView: function() { return Module.DividerBlockSettingsView; }
}); });
Module.DividerBlockSettingsView = base.BlockSettingsView.extend({ Module.DividerBlockSettingsView = base.BlockSettingsView.extend({
@ -96,20 +96,19 @@ define([
"change .mailpoet_field_divider_border_color": _.partial(this.changeColorField, "styles.block.borderColor"), "change .mailpoet_field_divider_border_color": _.partial(this.changeColorField, "styles.block.borderColor"),
"change .mailpoet_field_divider_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"), "change .mailpoet_field_divider_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
"click .mailpoet_button_divider_apply_to_all": "applyToAll", "click .mailpoet_button_divider_apply_to_all": "applyToAll",
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
}; };
}, },
modelEvents: function() { modelEvents: function() {
return { return {
'change:styles.block.borderColor': 'repaintDividerStyleOptions', 'change:styles.block.borderColor': 'repaintDividerStyleOptions'
}; };
}, },
templateHelpers: function() { templateContext: function() {
return { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
model: this.model.toJSON(),
availableStyles: App.getAvailableStyles().toJSON(), availableStyles: App.getAvailableStyles().toJSON(),
renderOptions: this.renderOptions, renderOptions: this.renderOptions
}; });
}, },
changeStyle: function(event) { changeStyle: function(event) {
var style = jQuery(event.currentTarget).data('style'); var style = jQuery(event.currentTarget).data('style');
@ -126,7 +125,7 @@ define([
updateValueAndCall: function(fieldToUpdate, callable, event) { updateValueAndCall: function(fieldToUpdate, callable, event) {
this.$(fieldToUpdate).val(jQuery(event.target).val()); this.$(fieldToUpdate).val(jQuery(event.target).val());
callable(event); callable(event);
}, }
}); });
Module.DividerWidgetView = base.WidgetView.extend({ Module.DividerWidgetView = base.WidgetView.extend({
@ -136,20 +135,20 @@ define([
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function() {
return new Module.DividerBlockModel(); return new Module.DividerBlockModel();
}, }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('divider', { App.registerBlockType('divider', {
blockModel: Module.DividerBlockModel, blockModel: Module.DividerBlockModel,
blockView: Module.DividerBlockView, blockView: Module.DividerBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'divider', name: 'divider',
widgetView: Module.DividerWidgetView, widgetView: Module.DividerWidgetView,
priority: 93, priority: 93
}); });
}); });

View File

@ -19,43 +19,43 @@ define([
text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>', text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
styles: { styles: {
block: { block: {
backgroundColor: 'transparent', backgroundColor: 'transparent'
}, },
text: { text: {
fontColor: '#000000', fontColor: '#000000',
fontFamily: 'Arial', fontFamily: 'Arial',
fontSize: '12px', fontSize: '12px',
textAlign: 'center', textAlign: 'center'
}, },
link: { link: {
fontColor: '#0000ff', fontColor: '#0000ff',
textDecoration: 'none', textDecoration: 'none'
}, }
}, }
}, App.getConfig().get('blockDefaults.footer')); }, App.getConfig().get('blockDefaults.footer'));
}, }
}); });
Module.FooterBlockView = base.BlockView.extend({ Module.FooterBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_footer_block mailpoet_droppable_block", className: "mailpoet_block mailpoet_footer_block mailpoet_droppable_block",
getTemplate: function() { return templates.footerBlock; }, getTemplate: function() { return templates.footerBlock; },
modelEvents: _.extend({ modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render', 'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render'
}, _.omit(base.BlockView.prototype.modelEvents, 'change')), }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
TextEditorBehavior: { TextEditorBehavior: {
configurationFilter: function(originalSettings) { configurationFilter: function(originalSettings) {
return _.extend({}, originalSettings, { return _.extend({}, originalSettings, {
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(), mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'), mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
}); });
} }
}, }
}), }),
onDragSubstituteBy: function() { return Module.FooterWidgetView; }, onDragSubstituteBy: function() { return Module.FooterWidgetView; },
onRender: function() { onRender: function() {
this.toolsView = new Module.FooterBlockToolsView({ model: this.model }); this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onTextEditorChange: function(newContent) { onTextEditorChange: function(newContent) {
this.model.set('text', newContent); this.model.set('text', newContent);
@ -67,17 +67,11 @@ define([
onTextEditorBlur: function() { onTextEditorBlur: function() {
this.enableDragging(); this.enableDragging();
this.enableShowingTools(); this.enableShowingTools();
}, }
disableDragging: function() {
this.$('.mailpoet_content').addClass('mailpoet_ignore_drag');
},
enableDragging: function() {
this.$('.mailpoet_content').removeClass('mailpoet_ignore_drag');
},
}); });
Module.FooterBlockToolsView = base.BlockToolsView.extend({ Module.FooterBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.FooterBlockSettingsView; }, getSettingsView: function() { return Module.FooterBlockSettingsView; }
}); });
Module.FooterBlockSettingsView = base.BlockSettingsView.extend({ Module.FooterBlockSettingsView = base.BlockSettingsView.extend({
@ -93,15 +87,14 @@ define([
}, },
"change .mailpoet_field_footer_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"), "change .mailpoet_field_footer_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
"change .mailpoet_field_footer_alignment": _.partial(this.changeField, "styles.text.textAlign"), "change .mailpoet_field_footer_alignment": _.partial(this.changeField, "styles.text.textAlign"),
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
};
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
availableStyles: App.getAvailableStyles().toJSON(),
}; };
}, },
templateContext: function() {
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
availableStyles: App.getAvailableStyles().toJSON()
});
}
}); });
Module.FooterWidgetView = base.WidgetView.extend({ Module.FooterWidgetView = base.WidgetView.extend({
@ -111,21 +104,21 @@ define([
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function() {
return new Module.FooterBlockModel(); return new Module.FooterBlockModel();
}, }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('footer', { App.registerBlockType('footer', {
blockModel: Module.FooterBlockModel, blockModel: Module.FooterBlockModel,
blockView: Module.FooterBlockView, blockView: Module.FooterBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'footer', name: 'footer',
widgetView: Module.FooterWidgetView, widgetView: Module.FooterWidgetView,
priority: 99, priority: 99
}); });
}); });

View File

@ -19,43 +19,43 @@ define([
text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>', text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
styles: { styles: {
block: { block: {
backgroundColor: 'transparent', backgroundColor: 'transparent'
}, },
text: { text: {
fontColor: '#000000', fontColor: '#000000',
fontFamily: 'Arial', fontFamily: 'Arial',
fontSize: '12px', fontSize: '12px',
textAlign: 'center', textAlign: 'center'
}, },
link: { link: {
fontColor: '#0000ff', fontColor: '#0000ff',
textDecoration: 'underline', textDecoration: 'underline'
}, }
}, }
}, App.getConfig().get('blockDefaults.header')); }, App.getConfig().get('blockDefaults.header'));
}, }
}); });
Module.HeaderBlockView = base.BlockView.extend({ Module.HeaderBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_header_block mailpoet_droppable_block", className: "mailpoet_block mailpoet_header_block mailpoet_droppable_block",
getTemplate: function() { return templates.headerBlock; }, getTemplate: function() { return templates.headerBlock; },
modelEvents: _.extend({ modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render', 'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render'
}, _.omit(base.BlockView.prototype.modelEvents, 'change')), }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
TextEditorBehavior: { TextEditorBehavior: {
configurationFilter: function(originalSettings) { configurationFilter: function(originalSettings) {
return _.extend({}, originalSettings, { return _.extend({}, originalSettings, {
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(), mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'), mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
}); });
} }
}, }
}), }),
onDragSubstituteBy: function() { return Module.HeaderWidgetView; }, onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
onRender: function() { onRender: function() {
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model }); this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onTextEditorChange: function(newContent) { onTextEditorChange: function(newContent) {
this.model.set('text', newContent); this.model.set('text', newContent);
@ -67,17 +67,11 @@ define([
onTextEditorBlur: function() { onTextEditorBlur: function() {
this.enableDragging(); this.enableDragging();
this.enableShowingTools(); this.enableShowingTools();
}, }
disableDragging: function() {
this.$('.mailpoet_content').addClass('mailpoet_ignore_drag');
},
enableDragging: function() {
this.$('.mailpoet_content').removeClass('mailpoet_ignore_drag');
},
}); });
Module.HeaderBlockToolsView = base.BlockToolsView.extend({ Module.HeaderBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.HeaderBlockSettingsView; }, getSettingsView: function() { return Module.HeaderBlockSettingsView; }
}); });
Module.HeaderBlockSettingsView = base.BlockSettingsView.extend({ Module.HeaderBlockSettingsView = base.BlockSettingsView.extend({
@ -93,15 +87,14 @@ define([
}, },
"change .mailpoet_field_header_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"), "change .mailpoet_field_header_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
"change .mailpoet_field_header_alignment": _.partial(this.changeField, "styles.text.textAlign"), "change .mailpoet_field_header_alignment": _.partial(this.changeField, "styles.text.textAlign"),
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
};
},
templateHelpers: function() {
return {
model: this.model.toJSON(),
availableStyles: App.getAvailableStyles().toJSON(),
}; };
}, },
templateContext: function() {
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
availableStyles: App.getAvailableStyles().toJSON()
});
}
}); });
Module.HeaderWidgetView = base.WidgetView.extend({ Module.HeaderWidgetView = base.WidgetView.extend({
@ -111,21 +104,21 @@ define([
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function() {
return new Module.HeaderBlockModel(); return new Module.HeaderBlockModel();
}, }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('header', { App.registerBlockType('header', {
blockModel: Module.HeaderBlockModel, blockModel: Module.HeaderBlockModel,
blockView: Module.HeaderBlockView, blockView: Module.HeaderBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'header', name: 'header',
widgetView: Module.HeaderWidgetView, widgetView: Module.HeaderWidgetView,
priority: 98, priority: 98
}); });
}); });

View File

@ -25,39 +25,39 @@ define([
height: '64px', height: '64px',
styles: { styles: {
block: { block: {
textAlign: 'center', textAlign: 'center'
}, }
}, }
}, App.getConfig().get('blockDefaults.image')); }, App.getConfig().get('blockDefaults.image'));
}, }
}); });
Module.ImageBlockView = base.BlockView.extend({ Module.ImageBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_image_block mailpoet_droppable_block", className: "mailpoet_block mailpoet_image_block mailpoet_droppable_block",
getTemplate: function() { return templates.imageBlock; }, getTemplate: function() { return templates.imageBlock; },
onDragSubstituteBy: function() { return Module.ImageWidgetView; }, onDragSubstituteBy: function() { return Module.ImageWidgetView; },
templateHelpers: function() { templateContext: function() {
return _.extend({ return _.extend({
imageMissingSrc: App.getConfig().get('urls.imageMissing'), imageMissingSrc: App.getConfig().get('urls.imageMissing')
}, base.BlockView.prototype.templateHelpers.apply(this)); }, base.BlockView.prototype.templateContext.apply(this));
}, },
behaviors: _.extend({}, base.BlockView.prototype.behaviors, { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {}, ShowSettingsBehavior: {}
}), }),
onRender: function() { onRender: function() {
this.toolsView = new Module.ImageBlockToolsView({ model: this.model }); this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
if (this.model.get('fullWidth')) { if (this.model.get('fullWidth')) {
this.$el.addClass('mailpoet_full_image'); this.$el.addClass('mailpoet_full_image');
} else { } else {
this.$el.removeClass('mailpoet_full_image'); this.$el.removeClass('mailpoet_full_image');
} }
}, }
}); });
Module.ImageBlockToolsView = base.BlockToolsView.extend({ Module.ImageBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.ImageBlockSettingsView; }, getSettingsView: function() { return Module.ImageBlockSettingsView; }
}); });
Module.ImageBlockSettingsView = base.BlockSettingsView.extend({ Module.ImageBlockSettingsView = base.BlockSettingsView.extend({
@ -65,12 +65,12 @@ define([
events: function() { events: function() {
return { return {
"input .mailpoet_field_image_link": _.partial(this.changeField, "link"), "input .mailpoet_field_image_link": _.partial(this.changeField, "link"),
"input .mailpoet_field_image_address": _.partial(this.changeField, "src"), "input .mailpoet_field_image_address": 'changeAddress',
"input .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"), "input .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
"change .mailpoet_field_image_full_width": _.partial(this.changeBoolCheckboxField, "fullWidth"), "change .mailpoet_field_image_full_width": _.partial(this.changeBoolCheckboxField, "fullWidth"),
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"), "change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"click .mailpoet_field_image_select_another_image": "showMediaManager", "click .mailpoet_field_image_select_another_image": "showMediaManager",
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
}; };
}, },
initialize: function(options) { initialize: function(options) {
@ -141,7 +141,7 @@ define([
// Update user settings when users adjust the // Update user settings when users adjust the
// attachment display settings. // attachment display settings.
displayUserSettings: false displayUserSettings: false
}), })
]); ]);
if(wp.media.view.settings.post.featuredImageId) { if(wp.media.view.settings.post.featuredImageId) {
@ -285,8 +285,8 @@ define([
}, },
displaySettings: false, displaySettings: false,
button: { button: {
text: 'Select', text: 'Select'
}, }
}), }),
that = this; that = this;
@ -318,7 +318,7 @@ define([
height: mainSize.height + 'px', height: mainSize.height + 'px',
width: mainSize.width + 'px', width: mainSize.width + 'px',
src: mainSize.url, src: mainSize.url,
alt: (attachment.get('alt') !== "" && attachment.get('alt') !== undefined) ? attachment.get('alt') : attachment.get('title'), alt: (attachment.get('alt') !== "" && attachment.get('alt') !== undefined) ? attachment.get('alt') : attachment.get('title')
}); });
// Rerender settings view due to changes from outside of settings view // Rerender settings view due to changes from outside of settings view
that.render(); that.render();
@ -327,12 +327,26 @@ define([
this._mediaManager.open(); this._mediaManager.open();
}, },
changeAddress: function(event) {
var src = jQuery(event.target).val();
var image = new Image();
image.onload = function() {
this.model.set({
src: src,
width: image.naturalWidth + 'px',
height: image.naturalHeight + 'px'
});
}.bind(this);
image.src = src;
},
onBeforeDestroy: function() { onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments); base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
if (typeof this._mediaManager === 'object') { if (typeof this._mediaManager === 'object') {
this._mediaManager.remove(); this._mediaManager.remove();
} }
}, }
}); });
ImageWidgetView = base.WidgetView.extend({ ImageWidgetView = base.WidgetView.extend({
@ -345,22 +359,22 @@ define([
}, },
onDrop: function(options) { onDrop: function(options) {
options.droppedView.triggerMethod('showSettings', { showImageManager: true }); options.droppedView.triggerMethod('showSettings', { showImageManager: true });
}, }
} }
}, }
}); });
Module.ImageWidgetView = ImageWidgetView; Module.ImageWidgetView = ImageWidgetView;
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('image', { App.registerBlockType('image', {
blockModel: Module.ImageBlockModel, blockModel: Module.ImageBlockModel,
blockView: Module.ImageBlockView, blockView: Module.ImageBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'image', name: 'image',
widgetView: Module.ImageWidgetView, widgetView: Module.ImageWidgetView,
priority: 91, priority: 91
}); });
}); });

View File

@ -48,6 +48,7 @@ define([
return this._getDefaults({ return this._getDefaults({
type: 'posts', type: 'posts',
amount: '10', amount: '10',
offset: 0,
contentType: 'post', // 'post'|'page'|'mailpoet_page' contentType: 'post', // 'post'|'page'|'mailpoet_page'
postStatus: 'publish', // 'draft'|'pending'|'private'|'publish'|'future' postStatus: 'publish', // 'draft'|'pending'|'private'|'publish'|'future'
terms: [], // List of category and tag objects terms: [], // List of category and tag objects
@ -75,7 +76,7 @@ define([
divider: {}, divider: {},
_selectedPosts: [], _selectedPosts: [],
_availablePosts: [], _availablePosts: [],
_transformedPosts: new (App.getBlockTypeModel('container'))(), _transformedPosts: new (App.getBlockTypeModel('container'))()
}, App.getConfig().get('blockDefaults.posts')); }, App.getConfig().get('blockDefaults.posts'));
}, },
relations: function() { relations: function() {
@ -84,7 +85,7 @@ define([
divider: App.getBlockTypeModel('divider'), divider: App.getBlockTypeModel('divider'),
_selectedPosts: Backbone.Collection, _selectedPosts: Backbone.Collection,
_availablePosts: Backbone.Collection, _availablePosts: Backbone.Collection,
_transformedPosts: App.getBlockTypeModel('container'), _transformedPosts: App.getBlockTypeModel('container')
}; };
}, },
initialize: function() { initialize: function() {
@ -98,6 +99,7 @@ define([
this.fetchAvailablePosts(); this.fetchAvailablePosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts); this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
this.on('loadMorePosts', this._loadMorePosts, this);
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts); this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
this.on('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:showDivider', refreshTransformedPosts); this.on('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:showDivider', refreshTransformedPosts);
@ -108,6 +110,7 @@ define([
}, },
fetchAvailablePosts: function() { fetchAvailablePosts: function() {
var that = this; var that = this;
this.set('offset', 0);
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) { CommunicationComponent.getPosts(this.toJSON()).done(function(posts) {
that.get('_availablePosts').reset(posts); that.get('_availablePosts').reset(posts);
that.get('_selectedPosts').reset(); // Empty out the collection that.get('_selectedPosts').reset(); // Empty out the collection
@ -116,6 +119,27 @@ define([
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts')); MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
}); });
}, },
_loadMorePosts: function() {
var that = this,
postCount = this.get('_availablePosts').length,
nextOffset = this.get('offset') + Number(this.get('amount'));
if(postCount === 0 || postCount < nextOffset) {
// No more posts to load
return false;
}
this.set('offset', nextOffset);
this.trigger('loadingMorePosts');
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) {
that.get('_availablePosts').add(posts);
that.trigger('change:_availablePosts');
}).fail(function() {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
}).always(function() {
that.trigger('morePostsLoaded');
});
},
_refreshTransformedPosts: function() { _refreshTransformedPosts: function() {
var that = this, var that = this,
data = this.toJSON(); data = this.toJSON();
@ -148,7 +172,7 @@ define([
}).fail(function() { }).fail(function() {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts')); MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
}); });
}, }
}); });
Module.PostsBlockView = base.BlockView.extend({ Module.PostsBlockView = base.BlockView.extend({
@ -156,7 +180,7 @@ define([
getTemplate: function() { return templates.postsBlock; }, getTemplate: function() { return templates.postsBlock; },
modelEvents: {}, // Forcefully disable all events modelEvents: {}, // Forcefully disable all events
regions: _.extend({ regions: _.extend({
postsRegion: '.mailpoet_posts_block_posts', postsRegion: '.mailpoet_posts_block_posts'
}, base.BlockView.prototype.regions), }, base.BlockView.prototype.regions),
onDragSubstituteBy: function() { return Module.PostsWidgetView; }, onDragSubstituteBy: function() { return Module.PostsWidgetView; },
initialize: function() { initialize: function() {
@ -166,8 +190,8 @@ define([
this.model.reply('blockView', this.notifyAboutSelf, this); this.model.reply('blockView', this.notifyAboutSelf, this);
}, },
onRender: function() { onRender: function() {
if (!this.toolsRegion.hasView()) { if (!this.getRegion('toolsRegion').hasView()) {
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
} }
this.trigger('showSettings'); this.trigger('showSettings');
@ -175,36 +199,36 @@ define([
renderOptions = { renderOptions = {
disableTextEditor: true, disableTextEditor: true,
disableDragAndDrop: true, disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'), emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
}; };
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions })); this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
}, },
notifyAboutSelf: function() { notifyAboutSelf: function() {
return this; return this;
}, },
onBeforeDestroy: function() { onBeforeDestroy: function() {
this.model.stopReplying('blockView', this.notifyAboutSelf, this); this.model.stopReplying('blockView', this.notifyAboutSelf, this);
}, }
}); });
Module.PostsBlockToolsView = base.BlockToolsView.extend({ Module.PostsBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.PostsBlockSettingsView; }, getSettingsView: function() { return Module.PostsBlockSettingsView; }
}); });
Module.PostsBlockSettingsView = base.BlockSettingsView.extend({ Module.PostsBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return templates.postsBlockSettings; }, getTemplate: function() { return templates.postsBlockSettings; },
regions: { regions: {
selectionRegion: '.mailpoet_settings_posts_selection', selectionRegion: '.mailpoet_settings_posts_selection',
displayOptionsRegion: '.mailpoet_settings_posts_display_options', displayOptionsRegion: '.mailpoet_settings_posts_display_options'
}, },
events: { events: {
'click .mailpoet_settings_posts_show_display_options': 'switchToDisplayOptions', 'click .mailpoet_settings_posts_show_display_options': 'switchToDisplayOptions',
'click .mailpoet_settings_posts_show_post_selection': 'switchToPostSelection', 'click .mailpoet_settings_posts_show_post_selection': 'switchToPostSelection',
'click .mailpoet_settings_posts_insert_selected': 'insertPosts', 'click .mailpoet_settings_posts_insert_selected': 'insertPosts'
}, },
templateHelpers: function() { templateContext: function() {
return { return {
model: this.model.toJSON(), model: this.model.toJSON()
}; };
}, },
initialize: function() { initialize: function() {
@ -227,7 +251,7 @@ define([
onCancel: function() { onCancel: function() {
// Self destroy the block if the user closes settings modal // Self destroy the block if the user closes settings modal
that.model.destroy(); that.model.destroy();
}, }
}); });
// Inform child views that they have been attached to document // Inform child views that they have been attached to document
@ -256,41 +280,72 @@ define([
this.model.trigger('insertSelectedPosts'); this.model.trigger('insertSelectedPosts');
this.model.destroy(); this.model.destroy();
this.close(); this.close();
}, }
}); });
var PostSelectionSettingsView = Marionette.CompositeView.extend({ var PostsSelectionCollectionView = Marionette.CollectionView.extend({
getTemplate: function() { return templates.postSelectionPostsBlockSettings; }, className: 'mailpoet_post_scroll_container',
getChildView: function() { return SinglePostSelectionSettingsView; }, childView: function() { return SinglePostSelectionSettingsView; },
childViewContainer: '.mailpoet_post_selection_container', emptyView: function() { return EmptyPostSelectionSettingsView; },
getEmptyView: function() { return EmptyPostSelectionSettingsView; },
childViewOptions: function() { childViewOptions: function() {
return { return {
blockModel: this.model, blockModel: this.blockModel
}; };
}, },
initialize: function(options) {
this.blockModel = options.blockModel;
},
events: {
'scroll': 'onPostsScroll'
},
onPostsScroll: function(event) {
var $postsBox = jQuery(event.target);
if($postsBox.scrollTop() + $postsBox.innerHeight() >= $postsBox[0].scrollHeight){
// Load more posts if scrolled to bottom
this.blockModel.trigger('loadMorePosts');
}
}
});
var PostSelectionSettingsView = Marionette.View.extend({
getTemplate: function() { return templates.postSelectionPostsBlockSettings; },
regions: {
posts: '.mailpoet_post_selection_container'
},
events: function() { events: function() {
return { return {
'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'), 'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'),
'change .mailpoet_posts_post_status': _.partial(this.changeField, 'postStatus'), 'change .mailpoet_posts_post_status': _.partial(this.changeField, 'postStatus'),
'input .mailpoet_posts_search_term': _.partial(this.changeField, 'search'), 'input .mailpoet_posts_search_term': _.partial(this.changeField, 'search')
}; };
}, },
constructor: function() { modelEvents: {
// Set the block collection to be handled by this view as well 'change:offset': function(model, value) {
arguments[0].collection = arguments[0].model.get('_availablePosts'); // Scroll posts view to top if settings are changed
Marionette.CompositeView.apply(this, arguments); if (value === 0) {
this.$('.mailpoet_post_scroll_container').scrollTop(0);
}
},
'loadingMorePosts': function() {
this.$('.mailpoet_post_selection_loading').css('visibility', 'visible');
},
'morePostsLoaded': function() {
this.$('.mailpoet_post_selection_loading').css('visibility', 'hidden');
}
}, },
onRender: function() { onRender: function() {
// Dynamically update available post types // Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this)); CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
var postsView = new PostsSelectionCollectionView({
collection: this.model.get('_availablePosts'),
blockModel: this.model
});
this.showChildView('posts', postsView);
}, },
onAttach: function() { onAttach: function() {
var that = this; var that = this;
// Dynamically update available post types
//CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
this.$('.mailpoet_posts_categories_and_tags').select2({ this.$('.mailpoet_posts_categories_and_tags').select2({
multiple: true, multiple: true,
allowClear: true, allowClear: true,
@ -337,8 +392,8 @@ define([
} }
) )
}; };
}, }
}, }
}).on({ }).on({
'select2:select': function(event) { 'select2:select': function(event) {
var terms = that.model.get('terms'); var terms = that.model.get('terms');
@ -351,7 +406,7 @@ define([
terms.remove(event.params.data); terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly // Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON()); that.model.set('terms', terms.toJSON());
}, }
}).trigger( 'change' ); }).trigger( 'change' );
}, },
changeField: function(field, event) { changeField: function(field, event) {
@ -365,28 +420,28 @@ define([
_.each(postTypes, function(type) { _.each(postTypes, function(type) {
select.append(jQuery('<option>', { select.append(jQuery('<option>', {
value: type.name, value: type.name,
text: type.label, text: type.label
})); }));
}); });
select.val(selectedValue); select.val(selectedValue);
}, }
}); });
var EmptyPostSelectionSettingsView = Marionette.ItemView.extend({ var EmptyPostSelectionSettingsView = Marionette.View.extend({
getTemplate: function() { return templates.emptyPostPostsBlockSettings; }, getTemplate: function() { return templates.emptyPostPostsBlockSettings; }
}); });
var SinglePostSelectionSettingsView = Marionette.ItemView.extend({ var SinglePostSelectionSettingsView = Marionette.View.extend({
getTemplate: function() { return templates.singlePostPostsBlockSettings; }, getTemplate: function() { return templates.singlePostPostsBlockSettings; },
events: function() { events: function() {
return { return {
'change .mailpoet_select_post_checkbox': 'postSelectionChange', 'change .mailpoet_select_post_checkbox': 'postSelectionChange'
}; };
}, },
templateHelpers: function() { templateContext: function() {
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
index: this._index, index: this._index
}; };
}, },
initialize: function(options) { initialize: function(options) {
@ -400,7 +455,7 @@ define([
} else { } else {
selectedPostsCollection.remove(this.model); selectedPostsCollection.remove(this.model);
} }
}, }
}); });
var PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({ var PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({
@ -425,12 +480,12 @@ define([
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"), "change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
"input .mailpoet_posts_categories": _.partial(this.changeField, "categoriesPrecededBy"), "input .mailpoet_posts_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"input .mailpoet_posts_read_more_text": _.partial(this.changeField, "readMoreText"), "input .mailpoet_posts_read_more_text": _.partial(this.changeField, "readMoreText"),
"change .mailpoet_posts_sort_by": _.partial(this.changeField, "sortBy"), "change .mailpoet_posts_sort_by": _.partial(this.changeField, "sortBy")
}; };
}, },
templateHelpers: function() { templateContext: function() {
return { return {
model: this.model.toJSON(), model: this.model.toJSON()
}; };
}, },
showButtonSettings: function(event) { showButtonSettings: function(event) {
@ -440,8 +495,8 @@ define([
renderOptions: { renderOptions: {
displayFormat: 'subpanel', displayFormat: 'subpanel',
hideLink: true, hideLink: true,
hideApplyToAll: true, hideApplyToAll: true
}, }
})).render(); })).render();
}, },
showDividerSettings: function(event) { showDividerSettings: function(event) {
@ -450,8 +505,8 @@ define([
model: this.model.get('divider'), model: this.model.get('divider'),
renderOptions: { renderOptions: {
displayFormat: 'subpanel', displayFormat: 'subpanel',
hideApplyToAll: true, hideApplyToAll: true
}, }
})).render(); })).render();
}, },
changeReadMoreType: function(event) { changeReadMoreType: function(event) {
@ -505,7 +560,7 @@ define([
this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden'); this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
} }
this.changeField('titleFormat', event); this.changeField('titleFormat', event);
}, }
}); });
Module.PostsWidgetView = base.WidgetView.extend({ Module.PostsWidgetView = base.WidgetView.extend({
@ -517,19 +572,19 @@ define([
return new Module.PostsBlockModel({}, { parse: true }); return new Module.PostsBlockModel({}, { parse: true });
} }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('posts', { App.registerBlockType('posts', {
blockModel: Module.PostsBlockModel, blockModel: Module.PostsBlockModel,
blockView: Module.PostsBlockView, blockView: Module.PostsBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'posts', name: 'posts',
widgetView: Module.PostsWidgetView, widgetView: Module.PostsWidgetView,
priority: 96, priority: 96
}); });
}); });

View File

@ -17,6 +17,7 @@ define([
base = BaseBlock, base = BaseBlock,
SocialBlockSettingsIconSelectorView, SocialBlockSettingsIconSelectorView,
SocialBlockSettingsIconView, SocialBlockSettingsIconView,
SocialBlockSettingsIconCollectionView,
SocialBlockSettingsStylesView; SocialBlockSettingsStylesView;
Module.SocialIconModel = SuperModel.extend({ Module.SocialIconModel = SuperModel.extend({
@ -29,7 +30,7 @@ define([
image: App.getAvailableStyles().get('socialIconSets.default.custom'), image: App.getAvailableStyles().get('socialIconSets.default.custom'),
height: '32px', height: '32px',
width: '32px', width: '32px',
text: defaultValues.get('title'), text: defaultValues.get('title')
}; };
}, },
initialize: function(options) { initialize: function(options) {
@ -41,11 +42,11 @@ define([
this.set({ this.set({
link: defaultValues.get('defaultLink'), link: defaultValues.get('defaultLink'),
image: iconSet.get(that.get('iconType')), image: iconSet.get(that.get('iconType')),
text: defaultValues.get('title'), text: defaultValues.get('title')
}); });
}, this); }, this);
this.on('change', function() { App.getChannel().trigger('autoSave'); }); this.on('change', function() { App.getChannel().trigger('autoSave'); });
}, }
}); });
Module.SocialIconCollectionModel = Backbone.Collection.extend({ Module.SocialIconCollectionModel = Backbone.Collection.extend({
@ -58,11 +59,11 @@ define([
return this._getDefaults({ return this._getDefaults({
type: 'social', type: 'social',
iconSet: 'default', iconSet: 'default',
icons: new Module.SocialIconCollectionModel(), icons: new Module.SocialIconCollectionModel()
}, App.getConfig().get('blockDefaults.social')); }, App.getConfig().get('blockDefaults.social'));
}, },
relations: { relations: {
icons: Module.SocialIconCollectionModel, icons: Module.SocialIconCollectionModel
}, },
initialize: function() { initialize: function() {
this.get('icons').on('add remove change', this._iconsChanged, this); this.get('icons').on('add remove change', this._iconsChanged, this);
@ -79,174 +80,53 @@ define([
}, },
_iconsChanged: function() { _iconsChanged: function() {
App.getChannel().trigger('autoSave'); App.getChannel().trigger('autoSave');
}, }
}); });
var SocialIconView = Marionette.ItemView.extend({ var SocialIconView = Marionette.View.extend({
tagName: 'span', tagName: 'span',
getTemplate: function() { return templates.socialIconBlock; }, getTemplate: function() { return templates.socialIconBlock; },
modelEvents: { modelEvents: {
'change': 'render', 'change': 'render'
}, },
templateHelpers: function() { templateContext: function() {
var allIconSets = App.getAvailableStyles().get('socialIconSets'); var allIconSets = App.getAvailableStyles().get('socialIconSets');
return { return {
model: this.model.toJSON(), model: this.model.toJSON(),
allIconSets: allIconSets.toJSON(), allIconSets: allIconSets.toJSON(),
imageMissingSrc: App.getConfig().get('urls.imageMissing'), imageMissingSrc: App.getConfig().get('urls.imageMissing')
}; };
}, }
}); });
Module.SocialBlockView = Marionette.CompositeView.extend({ Module.SocialIconCollectionView = Marionette.CollectionView.extend({
regionClass: Marionette.Region, childView: SocialIconView
});
Module.SocialBlockView = base.BlockView.extend({
className: 'mailpoet_block mailpoet_social_block mailpoet_droppable_block', className: 'mailpoet_block mailpoet_social_block mailpoet_droppable_block',
getTemplate: function() { return templates.socialBlock; }, getTemplate: function() { return templates.socialBlock; },
childViewContainer: '.mailpoet_social', regions: _.extend({}, base.BlockView.prototype.regions, {
modelEvents: { icons: '.mailpoet_social'
'change': 'render', }),
'delete': 'deleteBlock',
},
events: {
"mouseover": "showTools",
"mouseout": "hideTools",
},
regions: {
toolsRegion: '> .mailpoet_tools',
},
ui: { ui: {
tools: '> .mailpoet_tools' tools: '> .mailpoet_tools'
}, },
behaviors: { behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
DraggableBehavior: { ShowSettingsBehavior: {}
cloneOriginal: true, }),
hideOriginal: true,
onDrop: function(options) {
// After a clone of model has been dropped, cleanup
// and destroy self
options.dragBehavior.view.model.destroy();
},
onDragSubstituteBy: function(behavior) {
var WidgetView, node;
// When block is being dragged, display the widget icon instead.
// This will create an instance of block's widget view and
// use it's rendered DOM element instead of the content block
if (_.isFunction(behavior.view.onDragSubstituteBy)) {
WidgetView = new (behavior.view.onDragSubstituteBy())();
WidgetView.render();
node = WidgetView.$el.get(0).cloneNode(true);
WidgetView.destroy();
return node;
}
},
},
HighlightEditingBehavior: {},
ShowSettingsBehavior: {},
},
onDragSubstituteBy: function() { return Module.SocialWidgetView; }, onDragSubstituteBy: function() { return Module.SocialWidgetView; },
constructor: function() {
// Set the block collection to be handled by this view as well
arguments[0].collection = arguments[0].model.get('icons');
Marionette.CompositeView.apply(this, arguments);
},
initialize: function() {
this.on('showSettings', this.showSettings, this);
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
// Determines which view type should be used for a child
childView: SocialIconView,
templateHelpers: function() {
return {
model: this.model.toJSON(),
viewCid: this.cid,
};
},
onRender: function() { onRender: function() {
this._rebuildRegions();
this.toolsView = new Module.SocialBlockToolsView({ model: this.model }); this.toolsView = new Module.SocialBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, this.showChildView('icons', new Module.SocialIconCollectionView({
onBeforeDestroy: function() { collection: this.model.get('icons')
this.regionManager.destroy(); }))
}, }
showTools: function(_event) {
this.$(this.ui.tools).addClass('mailpoet_display_tools');
_event.stopPropagation();
},
hideTools: function(_event) {
this.$(this.ui.tools).removeClass('mailpoet_display_tools');
_event.stopPropagation();
},
showSettings: function(options) {
this.toolsView.triggerMethod('showSettings', options);
},
getDropFunc: function() {
return function() {
return this.model.clone();
}.bind(this);
},
_buildRegions: function(regions) {
var that = this;
var defaults = {
regionClass: this.getOption('regionClass'),
parentEl: function() { return that.$el; }
};
return this.regionManager.addRegions(regions, defaults);
},
_rebuildRegions: function() {
if (this.regionManager === undefined) {
this.regionManager = new Marionette.RegionManager();
}
this.regionManager.destroy();
_.extend(this, this._buildRegions(this.regions));
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('slideDown', 'fadeIn', 'easeIn');
},
transitionOut: function() {
return this._transition('slideUp', 'fadeOut', 'easeOut');
},
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
return promise;
},
}); });
Module.SocialBlockToolsView = base.BlockToolsView.extend({ Module.SocialBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.SocialBlockSettingsView; }, getSettingsView: function() { return Module.SocialBlockSettingsView; }
}); });
// Sidebar view container // Sidebar view container
@ -254,11 +134,11 @@ define([
getTemplate: function() { return templates.socialBlockSettings; }, getTemplate: function() { return templates.socialBlockSettings; },
regions: { regions: {
iconRegion: '#mailpoet_social_icons_selection', iconRegion: '#mailpoet_social_icons_selection',
stylesRegion: '#mailpoet_social_icons_styles', stylesRegion: '#mailpoet_social_icons_styles'
}, },
events: function() { events: function() {
return { return {
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
}; };
}, },
initialize: function() { initialize: function() {
@ -268,13 +148,13 @@ define([
this._stylesView = new SocialBlockSettingsStylesView({ model: this.model }); this._stylesView = new SocialBlockSettingsStylesView({ model: this.model });
}, },
onRender: function() { onRender: function() {
this.iconRegion.show(this._iconSelectorView); this.showChildView('iconRegion', this._iconSelectorView);
this.stylesRegion.show(this._stylesView); this.showChildView('stylesRegion', this._stylesView);
} }
}); });
// Single icon settings view, used by the selector view // Single icon settings view, used by the selector view
SocialBlockSettingsIconView = Marionette.ItemView.extend({ SocialBlockSettingsIconView = Marionette.View.extend({
getTemplate: function() { return templates.socialSettingsIcon; }, getTemplate: function() { return templates.socialSettingsIcon; },
events: function() { events: function() {
return { return {
@ -282,7 +162,7 @@ define([
"change .mailpoet_social_icon_field_type": _.partial(this.changeField, "iconType"), "change .mailpoet_social_icon_field_type": _.partial(this.changeField, "iconType"),
"input .mailpoet_social_icon_field_image": _.partial(this.changeField, "image"), "input .mailpoet_social_icon_field_image": _.partial(this.changeField, "image"),
"input .mailpoet_social_icon_field_link": this.changeLink, "input .mailpoet_social_icon_field_link": this.changeLink,
"input .mailpoet_social_icon_field_text": _.partial(this.changeField, "text"), "input .mailpoet_social_icon_field_text": _.partial(this.changeField, "text")
}; };
}, },
modelEvents: { modelEvents: {
@ -292,19 +172,18 @@ define([
}, },
'change:text': function() { 'change:text': function() {
this.$('.mailpoet_social_icon_image').attr('alt', this.model.get('text')); this.$('.mailpoet_social_icon_image').attr('alt', this.model.get('text'));
}, }
}, },
templateHelpers: function() { templateContext: function() {
var icons = App.getConfig().get('socialIcons'), var icons = App.getConfig().get('socialIcons'),
// Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...] // Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...]
availableIconTypes = _.map(_.keys(icons.attributes), function(key) { return { iconType: key, title: icons.get(key).get('title') }; }), availableIconTypes = _.map(_.keys(icons.attributes), function(key) { return { iconType: key, title: icons.get(key).get('title') }; }),
allIconSets = App.getAvailableStyles().get('socialIconSets'); allIconSets = App.getAvailableStyles().get('socialIconSets');
return { return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
model: this.model.toJSON(),
iconTypes: availableIconTypes, iconTypes: availableIconTypes,
currentType: icons.get(this.model.get('iconType')).toJSON(), currentType: icons.get(this.model.get('iconType')).toJSON(),
allIconSets: allIconSets.toJSON(), allIconSets: allIconSets.toJSON()
}; });
}, },
deleteIcon: function() { deleteIcon: function() {
this.model.destroy(); this.model.destroy();
@ -318,54 +197,61 @@ define([
}, },
changeField: function(field, event) { changeField: function(field, event) {
this.model.set(field, jQuery(event.target).val()); this.model.set(field, jQuery(event.target).val());
},
});
// Select icons section container view
SocialBlockSettingsIconSelectorView = Marionette.CompositeView.extend({
getTemplate: function() { return templates.socialSettingsIconSelector; },
childView: SocialBlockSettingsIconView,
childViewContainer: '#mailpoet_social_icon_selector_contents',
events: {
'click .mailpoet_add_social_icon': 'addSocialIcon',
},
modelEvents: {
'change:iconSet': 'render',
},
behaviors: {
SortableBehavior: {
items: '#mailpoet_social_icon_selector_contents > div',
},
},
constructor: function() {
// Set the icon collection to be handled by this view as well
arguments[0].collection = arguments[0].model.get('icons');
Marionette.CompositeView.apply(this, arguments);
},
addSocialIcon: function() {
// Add a social icon with default values
this.collection.add({});
} }
}); });
SocialBlockSettingsStylesView = Marionette.ItemView.extend({ SocialBlockSettingsIconCollectionView = Marionette.CollectionView.extend({
getTemplate: function() { return templates.socialSettingsStyles; }, behaviors: {
modelEvents: { SortableBehavior: {
'change': 'render', items: '> div'
}
},
childViewContainer: '#mailpoet_social_icon_selector_contents',
childView: SocialBlockSettingsIconView
});
// Select icons section container view
SocialBlockSettingsIconSelectorView = Marionette.View.extend({
getTemplate: function() { return templates.socialSettingsIconSelector; },
regions: {
'icons': '#mailpoet_social_icon_selector_contents'
}, },
events: { events: {
'click .mailpoet_social_icon_set': 'changeSocialIconSet', 'click .mailpoet_add_social_icon': 'addSocialIcon'
},
modelEvents: {
'change:iconSet': 'render'
},
addSocialIcon: function() {
// Add a social icon with default values
this.model.get('icons').add({});
},
onRender: function() {
this.showChildView('icons', new SocialBlockSettingsIconCollectionView({
collection: this.model.get('icons')
}));
}
});
SocialBlockSettingsStylesView = Marionette.View.extend({
getTemplate: function() { return templates.socialSettingsStyles; },
modelEvents: {
'change': 'render'
},
events: {
'click .mailpoet_social_icon_set': 'changeSocialIconSet'
}, },
initialize: function() { initialize: function() {
this.listenTo(this.model.get('icons'), 'add remove change', this.render); this.listenTo(this.model.get('icons'), 'add remove change', this.render);
}, },
templateHelpers: function() { templateContext: function() {
var allIconSets = App.getAvailableStyles().get('socialIconSets'); var allIconSets = App.getAvailableStyles().get('socialIconSets');
return { return {
activeSet: this.model.get('iconSet'), activeSet: this.model.get('iconSet'),
socialIconSets: allIconSets.toJSON(), socialIconSets: allIconSets.toJSON(),
availableSets: _.keys(allIconSets.toJSON()), availableSets: _.keys(allIconSets.toJSON()),
availableSocialIcons: this.model.get('icons').pluck('iconType'), availableSocialIcons: this.model.get('icons').pluck('iconType')
}; };
}, },
changeSocialIconSet: function(event) { changeSocialIconSet: function(event) {
@ -373,7 +259,7 @@ define([
}, },
onBeforeDestroy: function() { onBeforeDestroy: function() {
this.model.get('icons').off('add remove', this.render, this); this.model.get('icons').off('add remove', this.render, this);
}, }
}); });
Module.SocialWidgetView = base.WidgetView.extend({ Module.SocialWidgetView = base.WidgetView.extend({
@ -393,7 +279,7 @@ define([
image: App.getAvailableStyles().get('socialIconSets.default.facebook'), image: App.getAvailableStyles().get('socialIconSets.default.facebook'),
height: '32px', height: '32px',
width: '32px', width: '32px',
text: 'Facebook', text: 'Facebook'
}, },
{ {
type: 'socialIcon', type: 'socialIcon',
@ -402,25 +288,25 @@ define([
image: App.getAvailableStyles().get('socialIconSets.default.twitter'), image: App.getAvailableStyles().get('socialIconSets.default.twitter'),
height: '32px', height: '32px',
width: '32px', width: '32px',
text: 'Twitter', text: 'Twitter'
}, }
], ]
}, { parse: true }); }, { parse: true });
} }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('social', { App.registerBlockType('social', {
blockModel: Module.SocialBlockModel, blockModel: Module.SocialBlockModel,
blockView: Module.SocialBlockView, blockView: Module.SocialBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'social', name: 'social',
widgetView: Module.SocialWidgetView, widgetView: Module.SocialWidgetView,
priority: 95, priority: 95
}); });
}); });

View File

@ -19,11 +19,11 @@ define([
styles: { styles: {
block: { block: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
height: '40px', height: '40px'
}, }
}, }
}, App.getConfig().get('blockDefaults.spacer')); }, App.getConfig().get('blockDefaults.spacer'));
}, }
}); });
Module.SpacerBlockView = base.BlockView.extend({ Module.SpacerBlockView = base.BlockView.extend({
@ -34,11 +34,11 @@ define([
elementSelector: '.mailpoet_spacer', elementSelector: '.mailpoet_spacer',
resizeHandleSelector: '.mailpoet_resize_handle', resizeHandleSelector: '.mailpoet_resize_handle',
minLength: 20, // TODO: Move this number to editor configuration minLength: 20, // TODO: Move this number to editor configuration
modelField: 'styles.block.height', modelField: 'styles.block.height'
}, },
ShowSettingsBehavior: { ShowSettingsBehavior: {
ignoreFrom: '.mailpoet_resize_handle' ignoreFrom: '.mailpoet_resize_handle'
}, }
}, base.BlockView.prototype.behaviors), }, base.BlockView.prototype.behaviors),
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'),
onDragSubstituteBy: function() { return Module.SpacerWidgetView; }, onDragSubstituteBy: function() { return Module.SpacerWidgetView; },
@ -50,7 +50,7 @@ define([
}, },
onRender: function() { onRender: function() {
this.toolsView = new Module.SpacerBlockToolsView({ model: this.model }); this.toolsView = new Module.SpacerBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
changeHeight: function() { changeHeight: function() {
this.$('.mailpoet_spacer').css('height', this.model.get('styles.block.height')); this.$('.mailpoet_spacer').css('height', this.model.get('styles.block.height'));
@ -58,11 +58,11 @@ define([
}, },
onBeforeDestroy: function() { onBeforeDestroy: function() {
this.stopListening(this.model); this.stopListening(this.model);
}, }
}); });
Module.SpacerBlockToolsView = base.BlockToolsView.extend({ Module.SpacerBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.SpacerBlockSettingsView; }, getSettingsView: function() { return Module.SpacerBlockSettingsView; }
}); });
Module.SpacerBlockSettingsView = base.BlockSettingsView.extend({ Module.SpacerBlockSettingsView = base.BlockSettingsView.extend({
@ -70,9 +70,9 @@ define([
events: function() { events: function() {
return { return {
"change .mailpoet_field_spacer_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"), "change .mailpoet_field_spacer_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
"click .mailpoet_done_editing": "close", "click .mailpoet_done_editing": "close"
}; };
}, }
}); });
Module.SpacerWidgetView = base.WidgetView.extend({ Module.SpacerWidgetView = base.WidgetView.extend({
@ -82,21 +82,21 @@ define([
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function() {
return new Module.SpacerBlockModel(); return new Module.SpacerBlockModel();
}, }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('spacer', { App.registerBlockType('spacer', {
blockModel: Module.SpacerBlockModel, blockModel: Module.SpacerBlockModel,
blockView: Module.SpacerBlockView, blockView: Module.SpacerBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'spacer', name: 'spacer',
widgetView: Module.SpacerWidgetView, widgetView: Module.SpacerWidgetView,
priority: 94, priority: 94
}); });
}); });

View File

@ -16,9 +16,9 @@ define([
defaults: function() { defaults: function() {
return this._getDefaults({ return this._getDefaults({
type: 'text', type: 'text',
text: 'Edit this to insert text', text: 'Edit this to insert text'
}, App.getConfig().get('blockDefaults.text')); }, App.getConfig().get('blockDefaults.text'));
}, }
}); });
Module.TextBlockView = base.BlockView.extend({ Module.TextBlockView = base.BlockView.extend({
@ -32,20 +32,20 @@ define([
validElements: "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]", validElements: "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]",
invalidElements: "script", invalidElements: "script",
blockFormats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p', blockFormats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
plugins: "link code textcolor colorpicker mailpoet_shortcodes", plugins: "link lists code textcolor colorpicker mailpoet_shortcodes paste",
configurationFilter: function(originalSettings) { configurationFilter: function(originalSettings) {
return _.extend({}, originalSettings, { return _.extend({}, originalSettings, {
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(), mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'), mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
}); });
} }
}, }
}), }),
initialize: function(options) { initialize: function(options) {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.renderOptions = _.defaults(options.renderOptions || {}, { this.renderOptions = _.defaults(options.renderOptions || {}, {
disableTextEditor: false, disableTextEditor: false
}); });
this.disableTextEditor = this.renderOptions.disableTextEditor; this.disableTextEditor = this.renderOptions.disableTextEditor;
@ -55,10 +55,10 @@ define([
this.toolsView = new Module.TextBlockToolsView({ this.toolsView = new Module.TextBlockToolsView({
model: this.model, model: this.model,
tools: { tools: {
settings: false, settings: false
}, }
}); });
this.toolsRegion.show(this.toolsView); this.showChildView('toolsRegion', this.toolsView);
}, },
onTextEditorChange: function(newContent) { onTextEditorChange: function(newContent) {
this.model.set('text', newContent); this.model.set('text', newContent);
@ -70,22 +70,15 @@ define([
onTextEditorBlur: function() { onTextEditorBlur: function() {
this.enableDragging(); this.enableDragging();
this.enableShowingTools(); this.enableShowingTools();
}, }
disableDragging: function() {
this.$('.mailpoet_content').addClass('mailpoet_ignore_drag');
},
enableDragging: function() {
this.$('.mailpoet_content').removeClass('mailpoet_ignore_drag');
},
}); });
Module.TextBlockToolsView = base.BlockToolsView.extend({ Module.TextBlockToolsView = base.BlockToolsView.extend({
getSettingsView: function() { return Module.TextBlockSettingsView; }, getSettingsView: function() { return Module.TextBlockSettingsView; }
}); });
Module.TextBlockSettingsView = base.BlockSettingsView.extend({ Module.TextBlockSettingsView = base.BlockSettingsView.extend({
getTemplate: function() { return templates.textBlockSettings; }, getTemplate: function() { return templates.textBlockSettings; }
}); });
Module.TextWidgetView = base.WidgetView.extend({ Module.TextWidgetView = base.WidgetView.extend({
@ -95,21 +88,21 @@ define([
cloneOriginal: true, cloneOriginal: true,
drop: function() { drop: function() {
return new Module.TextBlockModel(); return new Module.TextBlockModel();
}, }
} }
}, }
}); });
App.on('before:start', function() { App.on('before:start', function(App, options) {
App.registerBlockType('text', { App.registerBlockType('text', {
blockModel: Module.TextBlockModel, blockModel: Module.TextBlockModel,
blockView: Module.TextBlockView, blockView: Module.TextBlockView
}); });
App.registerWidget({ App.registerWidget({
name: 'text', name: 'text',
widgetView: Module.TextWidgetView, widgetView: Module.TextWidgetView,
priority: 90, priority: 90
}); });
}); });

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