From 0b97ea63456b96eff48a3928d316ecc46e1649c6 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 3 Jun 2013 16:12:24 -0400 Subject: [PATCH] Better HTML emails, smarter email digests, new email section in admin with digest preview --- ...ler.js => admin_email_index_controller.js} | 24 ++++-- .../admin_email_preview_digest_controller.js | 21 +++++ .../javascripts/admin/models/email_log.js | 2 +- .../javascripts/admin/models/email_preview.js | 21 +++++ .../admin/models/email_settings.js | 17 ++++ app/assets/javascripts/admin/models/group.js | 2 +- .../admin/routes/admin_api_route.js | 4 - .../admin/routes/admin_customize_route.js | 4 - .../admin/routes/admin_dashboard_route.js | 4 - .../admin/routes/admin_email_index_route.js | 20 +++++ .../admin/routes/admin_email_logs_route.js | 4 +- .../admin_email_preview_digest_route.js | 28 +++++++ .../admin/routes/admin_flags_route.js | 13 --- .../admin/routes/admin_groups_routes.js | 12 ++- .../admin/routes/admin_reports_route.js | 14 ++-- .../javascripts/admin/routes/admin_route.js | 2 +- .../javascripts/admin/routes/admin_routes.js | 6 +- .../admin/routes/admin_site_settings_route.js | 4 - .../admin/routes/admin_user_route.js | 2 +- .../admin/templates/admin.js.handlebars | 2 +- .../admin/templates/email.js.handlebars | 11 +++ .../admin/templates/email_index.js.handlebars | 23 ++++++ .../admin/templates/email_logs.js.handlebars | 16 +--- .../email_preview_digest.js.handlebars | 29 +++++++ app/assets/javascripts/discourse.js | 24 ++++++ .../templates/excerpt/category.js.handlebars | 26 ------ .../templates/excerpt/close.handlebars | 1 - .../templates/excerpt/post.js.handlebars | 21 ----- .../templates/excerpt/user.js.handlebars | 10 --- app/assets/stylesheets/admin/admin_base.scss | 12 +++ app/controllers/admin/email_controller.rb | 37 +++++++++ .../admin/email_logs_controller.rb | 15 ---- app/controllers/excerpt_controller.rb | 40 --------- app/helpers/user_notifications_helper.rb | 12 +++ app/mailers/user_notifications.rb | 10 +-- app/models/topic.rb | 12 +-- .../category_excerpt_serializer.rb | 34 -------- app/serializers/excerpt_type.rb | 11 --- app/serializers/post_excerpt_serializer.rb | 37 --------- app/serializers/user_excerpt_serializer.rb | 14 ---- app/views/email/template.html.erb | 23 ++++++ app/views/user_notifications/digest.text.erb | 22 ++--- config/locales/client.cs.yml | 2 +- config/locales/client.da.yml | 2 +- config/locales/client.de.yml | 2 +- config/locales/client.en.yml | 11 ++- config/locales/client.es.yml | 2 +- config/locales/client.fr.yml | 2 +- config/locales/client.id.yml | 2 +- config/locales/client.it.yml | 2 +- config/locales/client.nl.yml | 2 +- config/locales/client.pseudo.yml | 2 +- config/locales/client.pt.yml | 2 +- config/locales/client.sv.yml | 2 +- config/locales/client.zh_CN.yml | 2 +- config/locales/client.zh_TW.yml | 2 +- config/locales/server.en.yml | 3 +- config/routes.rb | 6 +- lib/email_renderer.rb | 23 ++++++ lib/email_sender.rb | 10 +-- lib/email_styles.rb | 42 ++++++++++ lib/excerpt_parser.rb | 6 +- spec/components/email_sender_spec.rb | 4 +- spec/components/email_styles_spec.rb | 40 +++++++++ spec/components/pretty_text_spec.rb | 4 + .../admin/email_controller_spec.rb | 53 ++++++++++++ .../admin/email_logs_controller_spec.rb | 37 --------- spec/controllers/excerpt_controller_spec.rb | 82 ------------------- spec/mailers/user_notifications_spec.rb | 2 +- 69 files changed, 552 insertions(+), 443 deletions(-) rename app/assets/javascripts/admin/controllers/{admin_email_logs_controller.js => admin_email_index_controller.js} (52%) create mode 100644 app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js create mode 100644 app/assets/javascripts/admin/models/email_preview.js create mode 100644 app/assets/javascripts/admin/models/email_settings.js create mode 100644 app/assets/javascripts/admin/routes/admin_email_index_route.js create mode 100644 app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js delete mode 100644 app/assets/javascripts/admin/routes/admin_flags_route.js create mode 100644 app/assets/javascripts/admin/templates/email.js.handlebars create mode 100644 app/assets/javascripts/admin/templates/email_index.js.handlebars create mode 100644 app/assets/javascripts/admin/templates/email_preview_digest.js.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/excerpt/close.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/excerpt/post.js.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/excerpt/user.js.handlebars create mode 100644 app/controllers/admin/email_controller.rb delete mode 100644 app/controllers/admin/email_logs_controller.rb delete mode 100644 app/controllers/excerpt_controller.rb create mode 100644 app/helpers/user_notifications_helper.rb delete mode 100644 app/serializers/category_excerpt_serializer.rb delete mode 100644 app/serializers/excerpt_type.rb delete mode 100644 app/serializers/post_excerpt_serializer.rb delete mode 100644 app/serializers/user_excerpt_serializer.rb create mode 100644 app/views/email/template.html.erb create mode 100644 lib/email_renderer.rb create mode 100644 lib/email_styles.rb create mode 100644 spec/components/email_styles_spec.rb create mode 100644 spec/controllers/admin/email_controller_spec.rb delete mode 100644 spec/controllers/admin/email_logs_controller_spec.rb delete mode 100644 spec/controllers/excerpt_controller_spec.rb diff --git a/app/assets/javascripts/admin/controllers/admin_email_logs_controller.js b/app/assets/javascripts/admin/controllers/admin_email_index_controller.js similarity index 52% rename from app/assets/javascripts/admin/controllers/admin_email_logs_controller.js rename to app/assets/javascripts/admin/controllers/admin_email_index_controller.js index a921f9d2376..673411d8ea2 100644 --- a/app/assets/javascripts/admin/controllers/admin_email_logs_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_email_index_controller.js @@ -1,21 +1,29 @@ /** - This controller supports the interface for reviewing email logs. + This controller supports email functionality. - @class AdminEmailLogsController - @extends Ember.ArrayController + @class AdminEmailIndexController + @extends Discourse.Controller @namespace Discourse @module Discourse **/ -Discourse.AdminEmailLogsController = Ember.ArrayController.extend(Discourse.Presence, { +Discourse.AdminEmailIndexController = Discourse.Controller.extend(Discourse.Presence, { /** Is the "send test email" button disabled? @property sendTestEmailDisabled **/ - sendTestEmailDisabled: function() { - return this.blank('testEmailAddress'); - }.property('testEmailAddress'), + sendTestEmailDisabled: Em.computed.empty('testEmailAddress'), + + /** + Clears the 'sentTestEmail' property on successful send. + + @method testEmailAddressChanged + **/ + testEmailAddressChanged: function() { + this.set('sentTestEmail', false); + }.observes('testEmailAddress'), + /** Sends a test email to the currently entered email address @@ -26,7 +34,7 @@ Discourse.AdminEmailLogsController = Ember.ArrayController.extend(Discourse.Pres this.set('sentTestEmail', false); var adminEmailLogsController = this; - Discourse.ajax("/admin/email_logs/test", { + Discourse.ajax("/admin/email/test", { type: 'POST', data: { email_address: this.get('testEmailAddress') } }).then(function () { diff --git a/app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js b/app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js new file mode 100644 index 00000000000..025f533507e --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js @@ -0,0 +1,21 @@ +/** + This controller previews an email digest + + @class AdminEmailPreviewDigestController + @extends Discourse.ObjectController + @namespace Discourse + @module Discourse +**/ +Discourse.AdminEmailPreviewDigestController = Discourse.ObjectController.extend(Discourse.Presence, { + + refresh: function() { + var model = this.get('model'); + var controller = this; + controller.set('loading', true); + Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(function (email) { + model.setProperties(email.getProperties('html_content', 'text_content')); + controller.set('loading', false); + }) + } + +}); diff --git a/app/assets/javascripts/admin/models/email_log.js b/app/assets/javascripts/admin/models/email_log.js index 50198e3fe1e..d954eeb0440 100644 --- a/app/assets/javascripts/admin/models/email_log.js +++ b/app/assets/javascripts/admin/models/email_log.js @@ -18,7 +18,7 @@ Discourse.EmailLog.reopenClass({ findAll: function(filter) { var result = Em.A(); - Discourse.ajax("/admin/email_logs.json", { + Discourse.ajax("/admin/email/logs.json", { data: { filter: filter } }).then(function(logs) { logs.each(function(log) { diff --git a/app/assets/javascripts/admin/models/email_preview.js b/app/assets/javascripts/admin/models/email_preview.js new file mode 100644 index 00000000000..1650674eb25 --- /dev/null +++ b/app/assets/javascripts/admin/models/email_preview.js @@ -0,0 +1,21 @@ +/** + Our data model for showing a preview of an email + + @class EmailPreview + @extends Discourse.Model + @namespace Discourse + @module Discourse +**/ +Discourse.EmailPreview = Discourse.Model.extend({}); + +Discourse.EmailPreview.reopenClass({ + findDigest: function(last_seen_at) { + return $.ajax("/admin/email/preview-digest.json", { + data: {last_seen_at: last_seen_at} + }).then(function (result) { + return Discourse.EmailPreview.create(result); + }); + } +}); + + diff --git a/app/assets/javascripts/admin/models/email_settings.js b/app/assets/javascripts/admin/models/email_settings.js new file mode 100644 index 00000000000..d061635515d --- /dev/null +++ b/app/assets/javascripts/admin/models/email_settings.js @@ -0,0 +1,17 @@ +/** + Our data model for representing the current email settings + + @class EmailSettings + @extends Discourse.Model + @namespace Discourse + @module Discourse +**/ +Discourse.EmailSettings = Discourse.Model.extend({}); + +Discourse.EmailSettings.reopenClass({ + find: function() { + return Discourse.ajax("/admin/email.json").then(function (settings) { + return Discourse.EmailSettings.create(settings); + }); + } +}); diff --git a/app/assets/javascripts/admin/models/group.js b/app/assets/javascripts/admin/models/group.js index 6ce30de46d9..6947e61be76 100644 --- a/app/assets/javascripts/admin/models/group.js +++ b/app/assets/javascripts/admin/models/group.js @@ -81,7 +81,7 @@ Discourse.Group.reopenClass({ findAll: function(){ var list = Discourse.SelectableArray.create(); - Discourse.ajax("/admin/groups").then(function(groups){ + Discourse.ajax("/admin/groups.json").then(function(groups){ groups.each(function(group){ list.addObject(Discourse.Group.create(group)); }); diff --git a/app/assets/javascripts/admin/routes/admin_api_route.js b/app/assets/javascripts/admin/routes/admin_api_route.js index 5e534ac3ef0..2da894f5998 100644 --- a/app/assets/javascripts/admin/routes/admin_api_route.js +++ b/app/assets/javascripts/admin/routes/admin_api_route.js @@ -10,10 +10,6 @@ Discourse.AdminApiRoute = Discourse.Route.extend({ model: function() { return Discourse.AdminApi.find(); - }, - - renderTemplate: function() { - this.render({into: 'admin/templates/admin'}); } }); diff --git a/app/assets/javascripts/admin/routes/admin_customize_route.js b/app/assets/javascripts/admin/routes/admin_customize_route.js index 6e721b72e20..59e26166cc2 100644 --- a/app/assets/javascripts/admin/routes/admin_customize_route.js +++ b/app/assets/javascripts/admin/routes/admin_customize_route.js @@ -10,10 +10,6 @@ Discourse.AdminCustomizeRoute = Discourse.Route.extend({ model: function() { return Discourse.SiteCustomization.findAll(); - }, - - renderTemplate: function() { - this.render({into: 'admin/templates/admin'}); } }); diff --git a/app/assets/javascripts/admin/routes/admin_dashboard_route.js b/app/assets/javascripts/admin/routes/admin_dashboard_route.js index 721a1e66092..54d10f09c04 100644 --- a/app/assets/javascripts/admin/routes/admin_dashboard_route.js +++ b/app/assets/javascripts/admin/routes/admin_dashboard_route.js @@ -13,10 +13,6 @@ Discourse.AdminDashboardRoute = Discourse.Route.extend({ this.fetchGithubCommits(c); }, - renderTemplate: function() { - this.render({into: 'admin/templates/admin'}); - }, - fetchDashboardData: function(c) { if( !c.get('dashboardFetchedAt') || Date.create('1 hour ago', 'en') > c.get('dashboardFetchedAt') ) { c.set('dashboardFetchedAt', new Date()); diff --git a/app/assets/javascripts/admin/routes/admin_email_index_route.js b/app/assets/javascripts/admin/routes/admin_email_index_route.js new file mode 100644 index 00000000000..d6876b08f42 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin_email_index_route.js @@ -0,0 +1,20 @@ +/** + Handles email routes + + @class AdminEmailRoute + @extends Discourse.Route + @namespace Discourse + @module Discourse +**/ +Discourse.AdminEmailIndexRoute = Discourse.Route.extend({ + + setupController: function(controller) { + Discourse.EmailSettings.find().then(function (model) { + controller.set('model', model); + }) + }, + + renderTemplate: function() { + this.render('admin/templates/email_index', {into: 'adminEmail'}); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin_email_logs_route.js b/app/assets/javascripts/admin/routes/admin_email_logs_route.js index 67a2a558764..af7098d6499 100644 --- a/app/assets/javascripts/admin/routes/admin_email_logs_route.js +++ b/app/assets/javascripts/admin/routes/admin_email_logs_route.js @@ -1,7 +1,7 @@ /** Handles routes related to viewing email logs. - @class AdminEmailLogsRoute + @class AdminEmailLogsRoute @extends Discourse.Route @namespace Discourse @module Discourse @@ -12,6 +12,6 @@ Discourse.AdminEmailLogsRoute = Discourse.Route.extend({ }, renderTemplate: function() { - this.render('admin/templates/email_logs'); + this.render('admin/templates/email_logs', {into: 'adminEmail'}); } }); diff --git a/app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js b/app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js new file mode 100644 index 00000000000..96d90a8cddf --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js @@ -0,0 +1,28 @@ +/** + Previews the Email Digests + + @class AdminEmailPreviewDigest + @extends Discourse.Route + @namespace Discourse + @module Discourse +**/ + +var oneWeekAgo = function() { + // TODO: Take out due to being evil sugar js? + return Date.create(7 + ' days ago', 'en').format('{yyyy}-{MM}-{dd}'); +} + +Discourse.AdminEmailPreviewDigestRoute = Discourse.Route.extend(Discourse.ModelReady, { + + model: function() { + return Discourse.EmailPreview.findDigest(oneWeekAgo()); + }, + + modelReady: function(controller, model) { + controller.setProperties({ + lastSeen: oneWeekAgo(), + showHtml: true + }); + } + +}); diff --git a/app/assets/javascripts/admin/routes/admin_flags_route.js b/app/assets/javascripts/admin/routes/admin_flags_route.js deleted file mode 100644 index 19c7b1889b9..00000000000 --- a/app/assets/javascripts/admin/routes/admin_flags_route.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - Basic route for admin flags - - @class AdminFlagsRoute - @extends Discourse.Route - @namespace Discourse - @module Discourse -**/ -Discourse.AdminFlagsRoute = Discourse.Route.extend({ - renderTemplate: function() { - this.render('admin/templates/flags'); - } -}); diff --git a/app/assets/javascripts/admin/routes/admin_groups_routes.js b/app/assets/javascripts/admin/routes/admin_groups_routes.js index 752dedb7624..0fea1c5020d 100644 --- a/app/assets/javascripts/admin/routes/admin_groups_routes.js +++ b/app/assets/javascripts/admin/routes/admin_groups_routes.js @@ -1,11 +1,15 @@ +/** + Handles routes for admin groups + + @class AdminGroupsRoute + @extends Discourse.Route + @namespace Discourse + @module Discourse +**/ Discourse.AdminGroupsRoute = Discourse.Route.extend({ model: function() { return Discourse.Group.findAll(); - }, - - renderTemplate: function() { - this.render('admin/templates/groups',{into: 'admin/templates/admin'}); } }); diff --git a/app/assets/javascripts/admin/routes/admin_reports_route.js b/app/assets/javascripts/admin/routes/admin_reports_route.js index 46a97b31632..2e9ab9d7cf4 100644 --- a/app/assets/javascripts/admin/routes/admin_reports_route.js +++ b/app/assets/javascripts/admin/routes/admin_reports_route.js @@ -1,9 +1,13 @@ +/** + Handles routes for admin reports + + @class AdminReportsRoute + @extends Discourse.Route + @namespace Discourse + @module Discourse +**/ Discourse.AdminReportsRoute = Discourse.Route.extend({ model: function(params) { - return(Discourse.Report.find(params.type)); - }, - - renderTemplate: function() { - this.render('admin/templates/reports', {into: 'admin/templates/admin'}); + return Discourse.Report.find(params.type); } }); \ No newline at end of file diff --git a/app/assets/javascripts/admin/routes/admin_route.js b/app/assets/javascripts/admin/routes/admin_route.js index 7bee2b1838f..7afc53d3d1a 100644 --- a/app/assets/javascripts/admin/routes/admin_route.js +++ b/app/assets/javascripts/admin/routes/admin_route.js @@ -1,7 +1,7 @@ /** The base admin route - @class AdminRoute + @class AdminRoute @extends Discourse.Route @namespace Discourse @module Discourse diff --git a/app/assets/javascripts/admin/routes/admin_routes.js b/app/assets/javascripts/admin/routes/admin_routes.js index 53a0af5eb3a..38dfbe5f114 100644 --- a/app/assets/javascripts/admin/routes/admin_routes.js +++ b/app/assets/javascripts/admin/routes/admin_routes.js @@ -14,7 +14,11 @@ Discourse.Route.buildRoutes(function() { this.resource('adminSiteContentEdit', {path: '/:content_type'}); }); - this.route('email_logs', { path: '/email_logs' }); + this.resource('adminEmail', { path: '/email'}, function() { + this.route('logs', { path: '/logs' }); + this.route('previewDigest', { path: '/preview-digest' }); + }); + this.route('customize', { path: '/customize' }); this.route('api', {path: '/api'}); diff --git a/app/assets/javascripts/admin/routes/admin_site_settings_route.js b/app/assets/javascripts/admin/routes/admin_site_settings_route.js index 1d6959ebe5e..164bd6361a1 100644 --- a/app/assets/javascripts/admin/routes/admin_site_settings_route.js +++ b/app/assets/javascripts/admin/routes/admin_site_settings_route.js @@ -9,9 +9,5 @@ Discourse.AdminSiteSettingsRoute = Discourse.Route.extend({ model: function() { return Discourse.SiteSetting.findAll(); - }, - - renderTemplate: function() { - this.render('admin/templates/site_settings', {into: 'admin/templates/admin'}); } }); diff --git a/app/assets/javascripts/admin/routes/admin_user_route.js b/app/assets/javascripts/admin/routes/admin_user_route.js index 5cef66f6fc3..d95770f554b 100644 --- a/app/assets/javascripts/admin/routes/admin_user_route.js +++ b/app/assets/javascripts/admin/routes/admin_user_route.js @@ -17,7 +17,7 @@ Discourse.AdminUserRoute = Discourse.Route.extend({ }, renderTemplate: function() { - this.render('admin/templates/user', {into: 'admin/templates/admin'}); + this.render({into: 'admin/templates/admin'}); } }); diff --git a/app/assets/javascripts/admin/templates/admin.js.handlebars b/app/assets/javascripts/admin/templates/admin.js.handlebars index 88c1d35b2bc..462337f8cc5 100644 --- a/app/assets/javascripts/admin/templates/admin.js.handlebars +++ b/app/assets/javascripts/admin/templates/admin.js.handlebars @@ -10,7 +10,7 @@ {{/if}}
  • {{#linkTo 'adminUsersList.active'}}{{i18n admin.users.title}}{{/linkTo}}
  • {{#linkTo 'admin.groups'}}{{i18n admin.groups.title}}{{/linkTo}}
  • -
  • {{#linkTo 'admin.email_logs'}}{{i18n admin.email_logs.title}}{{/linkTo}}
  • +
  • {{#linkTo 'adminEmail'}}{{i18n admin.email.title}}{{/linkTo}}
  • {{#linkTo 'adminFlags.active'}}{{i18n admin.flags.title}}{{/linkTo}}
  • {{#if currentUser.admin}}
  • {{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}
  • diff --git a/app/assets/javascripts/admin/templates/email.js.handlebars b/app/assets/javascripts/admin/templates/email.js.handlebars new file mode 100644 index 00000000000..6d8ebd5d9b6 --- /dev/null +++ b/app/assets/javascripts/admin/templates/email.js.handlebars @@ -0,0 +1,11 @@ +
    +
    + +
    +
    + +{{outlet}} \ No newline at end of file diff --git a/app/assets/javascripts/admin/templates/email_index.js.handlebars b/app/assets/javascripts/admin/templates/email_index.js.handlebars new file mode 100644 index 00000000000..c5e0812c228 --- /dev/null +++ b/app/assets/javascripts/admin/templates/email_index.js.handlebars @@ -0,0 +1,23 @@ + + + + + + + {{#each model.settings}} + + + + + {{/each}} +
    {{i18n admin.email.delivery_method}}{{model.delivery_method}}
    {{name}}{{value}}
    + +
    +
    + {{textField value=testEmailAddress placeholderKey="admin.email.test_email_address"}} +
    +
    + + {{#if sentTestEmail}}{{i18n admin.email.sent_test}}{{/if}} +
    +
    diff --git a/app/assets/javascripts/admin/templates/email_logs.js.handlebars b/app/assets/javascripts/admin/templates/email_logs.js.handlebars index 4d5647c1105..5e57580553e 100644 --- a/app/assets/javascripts/admin/templates/email_logs.js.handlebars +++ b/app/assets/javascripts/admin/templates/email_logs.js.handlebars @@ -1,19 +1,9 @@ -
    -
    - {{textField value=testEmailAddress placeholderKey="admin.email_logs.test_email_address"}} -
    -
    - - {{#if sentTestEmail}}{{i18n admin.email_logs.sent_test}}{{/if}} -
    -
    - - + - - + + {{#if model.length}} diff --git a/app/assets/javascripts/admin/templates/email_preview_digest.js.handlebars b/app/assets/javascripts/admin/templates/email_preview_digest.js.handlebars new file mode 100644 index 00000000000..4f356f99600 --- /dev/null +++ b/app/assets/javascripts/admin/templates/email_preview_digest.js.handlebars @@ -0,0 +1,29 @@ +
    +
    + + {{input type="date" value=lastSeen id="last-seen"}} +
    +
    + +
    +
    + + {{#if showHtml}} + {{i18n admin.email.html}} | {{i18n admin.email.text}} + {{else}} + {{i18n admin.email.html}} | {{i18n admin.email.text}} + {{/if}} +
    +
    + +{{#if loading}} +
    {{i18n loading}}
    +{{else}} + {{#if showHtml}} + {{{html_content}}} + {{else}} +
    {{{text_content}}}
    + {{/if}} +{{/if}} + + diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index 8b4f2dd7554..305a4ccb664 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -34,6 +34,30 @@ Discourse = Ember.Application.createWithMixins({ return u + url; }, + /** + This custom resolver allows us to find admin templates without calling .render + even though our path formats are slightly different than what ember prefers. + */ + resolver: Ember.DefaultResolver.extend({ + resolveTemplate: function(parsedName) { + var resolvedTemplate = this._super(parsedName); + if (resolvedTemplate) { return resolvedTemplate; } + + // If we can't find a template, check to see if it's similar to how discourse + // lays out templates like: adminEmail => admin/templates/email + if (parsedName.fullNameWithoutType.indexOf('admin') === 0) { + var decamelized = parsedName.fullNameWithoutType.decamelize(); + decamelized = decamelized.replace(/^admin\_/, 'admin/templates/'); + decamelized = decamelized.replace(/^admin\./, 'admin/templates/'); + decamelized = decamelized.replace(/\./, '_'); + + resolvedTemplate = Ember.TEMPLATES[decamelized]; + if (resolvedTemplate) { return resolvedTemplate; } + } + return Ember.TEMPLATES.not_found; + } + }), + titleChanged: function() { var title; title = ""; diff --git a/app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars b/app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars deleted file mode 100644 index cb6a93cd972..00000000000 --- a/app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars +++ /dev/null @@ -1,26 +0,0 @@ -
    - {{unbound view.name}} - - {{#if view.excerpt}} -
    - {{{view.excerpt}}} - {{i18n learn_more}} -
    - {{/if}} - -
    -
    {{view.topics_year}}
    {{i18n year}}
    -
    {{view.topics_month}}
    {{i18n month}}
    -
    {{view.topics_week}}
    {{i18n week}}
    -
    - -
    - diff --git a/app/assets/javascripts/discourse/templates/excerpt/close.handlebars b/app/assets/javascripts/discourse/templates/excerpt/close.handlebars deleted file mode 100644 index 54d28953d33..00000000000 --- a/app/assets/javascripts/discourse/templates/excerpt/close.handlebars +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/javascripts/discourse/templates/excerpt/post.js.handlebars b/app/assets/javascripts/discourse/templates/excerpt/post.js.handlebars deleted file mode 100644 index b05611d81a5..00000000000 --- a/app/assets/javascripts/discourse/templates/excerpt/post.js.handlebars +++ /dev/null @@ -1,21 +0,0 @@ -
    - {{avatar view imageSize="large"}} -
    -
    - {{{unbound view.excerpt}}} -
    {{unbound view.created_at}}
    -
    - diff --git a/app/assets/javascripts/discourse/templates/excerpt/user.js.handlebars b/app/assets/javascripts/discourse/templates/excerpt/user.js.handlebars deleted file mode 100644 index 89aee2e823e..00000000000 --- a/app/assets/javascripts/discourse/templates/excerpt/user.js.handlebars +++ /dev/null @@ -1,10 +0,0 @@ -

    {{view.name}}

    -{{avatar view imageSize="large"}} -
    - {{unbound view.excerpt}} -
    - diff --git a/app/assets/stylesheets/admin/admin_base.scss b/app/assets/stylesheets/admin/admin_base.scss index b99fc10432e..d6279712c5c 100644 --- a/app/assets/stylesheets/admin/admin_base.scss +++ b/app/assets/stylesheets/admin/admin_base.scss @@ -111,6 +111,18 @@ margin-top: 5px; } } + .toggle { + margin-top: 8px; + float: right; + + span { + font-weight: bold; + } + } + label { + display: inline-block; + margin-right: 5px; + } } .settings { diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb new file mode 100644 index 00000000000..350facf9a0b --- /dev/null +++ b/app/controllers/admin/email_controller.rb @@ -0,0 +1,37 @@ +require_dependency 'email_renderer' + +class Admin::EmailController < Admin::AdminController + + def index + + # For now, just show the ActionMailer settings + mail_settings = { delivery_method: ActionMailer::Base.delivery_method } + + mail_settings[:settings] = case mail_settings[:delivery_method] + when :smtp + ActionMailer::Base.smtp_settings.map {|k, v| {name: k, value: v}} + when :sendmail + ActionMailer::Base.sendmail_settings.map {|k, v| {name: k, value: v}} + end + + render_json_dump(mail_settings) + end + + def test + params.require(:email_address) + Jobs.enqueue(:test_email, to_address: params[:email_address]) + render nothing: true + end + + def logs + @email_logs = EmailLog.limit(50).includes(:user).order('created_at desc').all + render_serialized(@email_logs, EmailLogSerializer) + end + + def preview_digest + params.require(:last_seen_at) + renderer = EmailRenderer.new(UserNotifications.digest(current_user, since: params[:last_seen_at])) + render json: MultiJson.dump(html_content: renderer.html, text_content: renderer.text) + end + +end diff --git a/app/controllers/admin/email_logs_controller.rb b/app/controllers/admin/email_logs_controller.rb deleted file mode 100644 index 76a3c9b3667..00000000000 --- a/app/controllers/admin/email_logs_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Admin::EmailLogsController < Admin::AdminController - - def index - @email_logs = EmailLog.limit(50).includes(:user).order('created_at desc').all - - render_serialized(@email_logs, EmailLogSerializer) - end - - def test - requires_parameter(:email_address) - Jobs.enqueue(:test_email, to_address: params[:email_address]) - render nothing: true - end - -end diff --git a/app/controllers/excerpt_controller.rb b/app/controllers/excerpt_controller.rb deleted file mode 100644 index 854991faf29..00000000000 --- a/app/controllers/excerpt_controller.rb +++ /dev/null @@ -1,40 +0,0 @@ -require_dependency 'post_excerpt_serializer' - -class ExcerptController < ApplicationController - - - def show - requires_parameter(:url) - - uri = URI.parse(params[:url]) - route = Rails.application.routes.recognize_path(uri.path) - - case route[:controller] - when 'topics' - - # If we have a post number, retrieve the last post. Otherwise, first post. - topic_posts = Post.where(topic_id: route[:topic_id].to_i).order(:post_number) - post = route.has_key?(:post_number) ? topic_posts.last : topic_posts.first - guardian.ensure_can_see!(post) - - render json: post, serializer: PostExcerptSerializer, root: false - when 'users' - user = User.where(username_lower: route[:username].downcase).first - guardian.ensure_can_see!(user) - render json: user, serializer: UserExcerptSerializer, root: false - when 'list' - if route[:action] == 'category' - category = Category.where(slug: route[:category]).first - guardian.ensure_can_see!(category) - render json: category, serializer: CategoryExcerptSerializer, root: false - end - else - render nothing: true, status: 404 - end - - rescue ActionController::RoutingError, Discourse::NotFound - render nothing: true, status: 404 - end - - -end diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb new file mode 100644 index 00000000000..4317fe31f30 --- /dev/null +++ b/app/helpers/user_notifications_helper.rb @@ -0,0 +1,12 @@ +module UserNotificationsHelper + + def indent(text, by=2) + spacer = " " * by + result = "" + text.each_line do |line| + result << spacer << line + end + result + end + +end diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 00a46dba793..d2c6e9dcba5 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -36,20 +36,18 @@ class UserNotifications < ActionMailer::Base @user = user @base_url = Discourse.base_url - min_date = @user.last_emailed_at || @user.last_seen_at || 1.month.ago + min_date = opts[:since] || @user.last_emailed_at || @user.last_seen_at || 1.month.ago @site_name = SiteSetting.title @last_seen_at = I18n.l(@user.last_seen_at || @user.created_at, format: :short) - # A list of new topics to show the user - @new_topics = Topic.new_topics(min_date) - @notifications = @user.notifications.interesting_after(min_date) - + # A list of topics to show the user + @new_topics = Topic.for_digest(user, min_date) @markdown_linker = MarkdownLinker.new(Discourse.base_url) # Don't send email unless there is content in it - if @new_topics.present? || @notifications.present? + if @new_topics.present? mail to: user.email, from: "#{I18n.t('user_notifications.digest.from', site_name: SiteSetting.title)} <#{SiteSetting.notification_email}>", subject: I18n.t('user_notifications.digest.subject_template', diff --git a/app/models/topic.rb b/app/models/topic.rb index 026b818b18d..a7bcc155815 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -21,7 +21,6 @@ class Topic < ActiveRecord::Base versioned if: :new_version_required? - def trash! super update_flagged_posts_count @@ -136,6 +135,10 @@ class Topic < ActiveRecord::Base end end + def best_post + posts.order('score desc').limit(1).first + end + # all users (in groups or directly targetted) that are going to get the pm def all_allowed_users # TODO we should probably change this from 3 queries to 1 @@ -170,15 +173,14 @@ class Topic < ActiveRecord::Base title_changed? || category_id_changed? end - # Returns new topics since a date for display in email digest. - def self.new_topics(since) + # Returns hot topics since a date for display in email digest. + def self.for_digest(user, since) Topic .visible .where(closed: false, archived: false) .created_since(since) .listable_topics - .topic_list_order - .includes(:user) + .order(:percent_rank) .limit(5) end diff --git a/app/serializers/category_excerpt_serializer.rb b/app/serializers/category_excerpt_serializer.rb deleted file mode 100644 index 2117c653454..00000000000 --- a/app/serializers/category_excerpt_serializer.rb +++ /dev/null @@ -1,34 +0,0 @@ -require_dependency 'excerpt_type' - -class CategoryExcerptSerializer < ActiveModel::Serializer - include ExcerptType - - attributes :excerpt, :name, :color, :text_color, :slug, :topic_url, :topics_year, - :topics_month, :topics_week, :category_url, :can_edit, :can_delete - - - def topics_year - object.topics_year || 0 - end - - def topics_month - object.topics_month || 0 - end - - def topics_week - object.topics_week || 0 - end - - def category_url - "/category/#{object.slug}" - end - - def can_edit - scope.can_edit?(object) - end - - def can_delete - scope.can_delete?(object) - end - -end diff --git a/app/serializers/excerpt_type.rb b/app/serializers/excerpt_type.rb deleted file mode 100644 index 25893abec31..00000000000 --- a/app/serializers/excerpt_type.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ExcerptType - - def self.included(base) - base.attributes :type - end - - def type - self.class.name.sub(/ExcerptSerializer/, '') - end - -end diff --git a/app/serializers/post_excerpt_serializer.rb b/app/serializers/post_excerpt_serializer.rb deleted file mode 100644 index c9abe562c73..00000000000 --- a/app/serializers/post_excerpt_serializer.rb +++ /dev/null @@ -1,37 +0,0 @@ -require_dependency 'excerpt_type' - -class PostExcerptSerializer < ActiveModel::Serializer - include ExcerptType - - attributes :topic_id, :muted, :excerpt, :username, :created_at, :has_multiple_posts, :last_post_url, :first_post_url, :avatar_template - - def muted - object.topic.muted?(scope.current_user) - end - - def avatar_template - object.user.avatar_template - end - - def has_multiple_posts - (object.topic.posts_count > 1) - end - - def last_post_url - object.topic.last_post_url - end - - def first_post_url - object.topic.relative_url - end - - def include_last_post_url? - object.post_number == 1 - end - - def include_first_post_url? - object.post_number > 1 - end - - -end diff --git a/app/serializers/user_excerpt_serializer.rb b/app/serializers/user_excerpt_serializer.rb deleted file mode 100644 index 606089076d3..00000000000 --- a/app/serializers/user_excerpt_serializer.rb +++ /dev/null @@ -1,14 +0,0 @@ -require_dependency 'excerpt_type' - -class UserExcerptSerializer < ActiveModel::Serializer - include ExcerptType - - # TODO: Inherit from basic user serializer? - - attributes :bio_cooked, :username, :url, :name, :avatar_template - - def url - user_path(object.username.downcase) - end - -end diff --git a/app/views/email/template.html.erb b/app/views/email/template.html.erb new file mode 100644 index 00000000000..3af349269b2 --- /dev/null +++ b/app/views/email/template.html.erb @@ -0,0 +1,23 @@ +
    {{i18n admin.email_logs.sent_at}}{{i18n admin.email.sent_at}} {{i18n user.title}}{{i18n admin.email_logs.to_address}}{{i18n admin.email_logs.email_type}}{{i18n admin.email.to_address}}{{i18n admin.email.email_type}}
    + + + +
    + +
    + + + + + + + +
    + +
    + <%= raw(html_body) %> +
    +
    + + +
    diff --git a/app/views/user_notifications/digest.text.erb b/app/views/user_notifications/digest.text.erb index c73d5bc3ff4..d9d640453fa 100644 --- a/app/views/user_notifications/digest.text.erb +++ b/app/views/user_notifications/digest.text.erb @@ -3,19 +3,21 @@ site_link: site_link, last_seen_at: @last_seen_at) %> -<%- if @notifications.present? %> -### <%=t 'user_notifications.digest.new_activity' %> - -<%- @notifications.each do |n| %> -* <%= raw(n.text_description { raw(@markdown_linker.create(n.data_hash[:topic_title], n.url)) }) %> -<%- end %> - -<%- end %> <%- if @new_topics.present? %> -### <%=t 'user_notifications.digest.new_topics' %> +### <%=t 'user_notifications.digest.top_topics' %> <%- @new_topics.each do |t| %> -* <%= raw(@markdown_linker.create(t.title, t.relative_url)) %> +<%= raw(@markdown_linker.create(t.title, t.relative_url)) %> + +<%- if t.best_post.present? %> +<%= raw(t.best_post.excerpt(1000, + strip_links: true, + text_entities: true)) %> + +-------------------------------------------------------------------------------- + +<%- end %> + <%- end %> <%- end %> diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 1f51536d55f..2823eda5575 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -1016,7 +1016,7 @@ cs: delete_confirm: "Smazat toto přizpůsobení?" about: "Přizpůsobení webu vám umožní si nastavit vlastní CSS stylesheet a vlastní nadpisy na webu. Vyberte si z nabídky nebo vložte vlastní přizpůsobení a můžete začít editovat." - email_logs: + email: title: "Záznamy o emailech" sent_at: "Odesláno" email_type: "Typ emailu" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index f0345ba75e2..7e14853cf38 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -734,7 +734,7 @@ da: delete: "Delete" delete_confirm: "Delete this customization?" - email_logs: + email: title: "Email" sent_at: "Sent At" email_type: "Email Type" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 53e74c9b11c..7eb8e05de76 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -997,7 +997,7 @@ de: delete_confirm: "Diese Anpassung löschen?" about: "Seite personalisieren erlaubt dir das Anpassen der Stilvorlagen und des Kopfbereich der Seite. Wähle oder füge eine Anpassung hinzu um mit dem Editieren zu beginnen." - email_logs: + email: title: "Mailprotokoll" sent_at: "Gesendet am" email_type: "Mailtyp" diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 94e48591ab3..1347e86cebf 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1048,14 +1048,23 @@ en: delete_confirm: "Delete this customization?" about: "Site Customization allow you to modify stylesheets and headers on the site. Choose or add one to start editing." - email_logs: + email: title: "Email" + settings: "Settings" + logs: "Logs" sent_at: "Sent At" email_type: "Email Type" to_address: "To Address" test_email_address: "email address to test" send_test: "send test email" sent_test: "sent!" + delivery_method: "Delivery Method" + preview_digest: "Preview Digest" + refresh: "Refresh" + format: "Format" + html: "html" + text: "text" + last_seen_user: "Last Seen User:" impersonate: title: "Impersonate User" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index c2c549de73b..d844edb5808 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -726,7 +726,7 @@ es: delete: "Delete" delete_confirm: "Delete this customization?" - email_logs: + email: title: "Email" sent_at: "Sent At" email_type: "Email Type" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 3a148393635..e3776e11331 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -994,7 +994,7 @@ fr: delete_confirm: "Supprimer cette personnalisation" about: "Vous pouvez modifier les feuillets de styles et en-têtes de votre site. Choisissez ou ajouter un style pour commencer l'édition." - email_logs: + email: title: "Historique des mails" sent_at: "Envoyer à" email_type: "Type d'email" diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index 5153daecb7b..a85c5ac3483 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -668,7 +668,7 @@ id: delete: "Delete" delete_confirm: "Delete this customization?" - email_logs: + email: title: "Email" sent_at: "Sent At" email_type: "Email Type" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index ae0e589dd35..f58d461302d 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -977,7 +977,7 @@ it: delete_confirm: "Elimina questa personalizzazione?" about: "La Personalizzazione del Sito di permette di modificare i fogli di stile e le testate del sito." - email_logs: + email: title: "Log Email" sent_at: "Visto il" email_type: "Tipo Email" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index f52bb9ecc45..74a930f7d45 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -1051,7 +1051,7 @@ nl: delete_confirm: Verwijder deze aanpassing? about: Met aanpassingen aan de site kun je stylesheets en headers wijzigen. Kies of voeg een toe om te beginnen. - email_logs: + email: title: E-mail sent_at: Verzonden op email_type: E-mailtype diff --git a/config/locales/client.pseudo.yml b/config/locales/client.pseudo.yml index bedf4dc6d29..88684967ec9 100644 --- a/config/locales/client.pseudo.yml +++ b/config/locales/client.pseudo.yml @@ -935,7 +935,7 @@ pseudo: delete_confirm: '[[ Ďéłéťé ťĥíš čůšťóɱížáťíóɳ? ]]' about: '[[ Šíťé Čůšťóɱížáťíóɳ áłłóŵ ýóů ťó ɱóďíƒý šťýłéšĥééťš áɳď ĥéáďéřš óɳ ťĥé šíťé. Čĥóóšé óř áďď óɳé ťó šťářť éďíťíɳǧ. ]]' - email_logs: + email: title: '[[ Éɱáíł Łóǧš ]]' sent_at: '[[ Šéɳť Áť ]]' email_type: '[[ Éɱáíł Ťýƿé ]]' diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 420f0f65029..ecb8753f3f6 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -624,7 +624,7 @@ pt: delete: "Apagar" delete_confirm: "Apagar esta personalização?" - email_logs: + email: title: "Email" sent_at: "Enviado a" email_type: "Tipo de Email" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index c63d0bfc272..218c87c78c7 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -848,7 +848,7 @@ sv: delete: "Radera" delete_confirm: "Radera denna anpassning?" - email_logs: + email: title: "E-postloggar" sent_at: "Skickat" email_type: "E-posttyp" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index ddc323b1cac..d658485e075 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -977,7 +977,7 @@ zh_CN: delete_confirm: "删除本定制内容?" about: "站点定制允许你修改样式表和站点头部。选择或者添加一个来开始编辑。" - email_logs: + email: title: "电子邮件" sent_at: "发送时间" email_type: "邮件类型" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 6d3a7ab1491..1c6e62b5276 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -977,7 +977,7 @@ zh_TW: delete_confirm: "刪除本定制內容?" about: "站點定制允許你修改樣式表和站點頭部。選擇或者添加一個來開始編輯。" - email_logs: + email: title: "電子郵件" sent_at: "發送時間" email_type: "郵件類型" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 60e81c0d642..8e2977c4370 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -930,10 +930,11 @@ en: why: "Here's a brief summary of what happened on %{site_link} since we last saw you on %{last_seen_at}." subject_template: "[%{site_name}] Forum Activity for %{date}" new_activity: "New activity on your topics and posts:" - new_topics: "New topics:" + top_topics: "Content you might be interested in:" unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while.\nIf you'd like to turn it off or change your email preferences, %{unsubscribe_link}." click_here: "click here" from: "%{site_name} digest" + read_more: "Read More" private_message: subject_template: "[%{site_name}] %{subject_prefix}%{topic_title}" diff --git a/config/routes.rb b/config/routes.rb index 245c8e0a15b..f5f93d8e164 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -57,11 +57,15 @@ Discourse::Application.routes.draw do end resources :impersonate, constraints: AdminConstraint.new - resources :email_logs do + + resources :email do collection do post 'test' + get 'logs' + get 'preview-digest' => 'email#preview_digest' end end + get 'customize' => 'site_customizations#index', constraints: AdminConstraint.new get 'flags' => 'flags#index' get 'flags/:filter' => 'flags#index' diff --git a/lib/email_renderer.rb b/lib/email_renderer.rb new file mode 100644 index 00000000000..560fa8a2721 --- /dev/null +++ b/lib/email_renderer.rb @@ -0,0 +1,23 @@ +require_dependency 'email_styles' + +class EmailRenderer + + def initialize(message) + @message = message + end + + def text + @text ||= @message.body.to_s.force_encoding('UTF-8') + end + + def html + formatted_body = EmailStyles.new(PrettyText.cook(text, environment: 'email')).format + + ActionView::Base.new(Rails.configuration.paths["app/views"]).render( + template: 'email/template', + format: :html, + locals: { html_body: formatted_body } + ) + end + +end diff --git a/lib/email_sender.rb b/lib/email_sender.rb index 5f6f1a392a4..c04aad78e6e 100644 --- a/lib/email_sender.rb +++ b/lib/email_sender.rb @@ -4,8 +4,10 @@ # reason. For example, emailing a user too frequently. A nil to address is also considered # "do nothing" # -# It also adds an HTML part for the plain text body using markdown +# It also adds an HTML part for the plain text body # +require_dependency 'email_renderer' + class EmailSender def initialize(message, email_type, user=nil) @@ -20,15 +22,13 @@ class EmailSender return if @message.body.blank? @message.charset = 'UTF-8' - plain_body = @message.body.to_s.force_encoding('UTF-8') - + renderer = EmailRenderer.new(@message) @message.html_part = Mail::Part.new do content_type 'text/html; charset=UTF-8' - body PrettyText.cook(plain_body, environment: 'email') + body renderer.html end @message.text_part.content_type = 'text/plain; charset=UTF-8' - @message.deliver to_address = @message.to diff --git a/lib/email_styles.rb b/lib/email_styles.rb new file mode 100644 index 00000000000..1deb8d24937 --- /dev/null +++ b/lib/email_styles.rb @@ -0,0 +1,42 @@ +# +# HTML emails don't support CSS, so we can use nokogiri to inline attributes based on +# matchers. +# +class EmailStyles + + def initialize(html) + @html = html + end + + def format + fragment = Nokogiri::HTML.fragment(@html) + + fragment.css('h3').each do |h3| + h3['style'] = 'margin-bottom: 20px; background-color: #eee; padding: 10px; border: 1px solid #ddd;' + end + + fragment.css('hr').each do |hr| + hr['style'] = 'background-color: #ddd; height: 1px; border: 1px;' + end + + fragment.css('a').each do |a| + a['style'] = 'text-decoration: none; font-weight: bold; font-size: 15px; color: #006699;' + end + + fragment.css('ul').each do |ul| + ul['style'] = 'margin: 0 0 0 10px; padding: 0 0 0 20px;' + end + + fragment.css('li').each do |li| + li['style'] = 'padding-bottom: 10px' + end + + fragment.css('pre').each do |pre| + pre.replace(pre.text) + end + + fragment.to_html + end + + +end diff --git a/lib/excerpt_parser.rb b/lib/excerpt_parser.rb index d3c6754eb0e..167a54025a3 100644 --- a/lib/excerpt_parser.rb +++ b/lib/excerpt_parser.rb @@ -2,11 +2,13 @@ class ExcerptParser < Nokogiri::XML::SAX::Document attr_reader :excerpt - def initialize(length,options) + def initialize(length, options=nil) @length = length @excerpt = "" @current_length = 0 + options || {} @strip_links = options[:strip_links] == true + @text_entities = options[:text_entities] == true end def self.get_excerpt(html, length, options) @@ -63,7 +65,7 @@ class ExcerptParser < Nokogiri::XML::SAX::Document if count_it && @current_length + string.length > @length length = [0, @length - @current_length - 1].max @excerpt << encode.call(string[0..length]) if truncate - @excerpt << "…" + @excerpt << (@text_entities ? "..." : "…") @excerpt << "" if @in_a throw :done end diff --git a/spec/components/email_sender_spec.rb b/spec/components/email_sender_spec.rb index f44c11181ab..9b8f1b1e7f1 100644 --- a/spec/components/email_sender_spec.rb +++ b/spec/components/email_sender_spec.rb @@ -78,9 +78,7 @@ describe EmailSender do end it 'converts the html part to html' do - expect(message.html_part.body.to_s).to eq( - "

    hello

    " - ) + expect(message.html_part.body.to_s).to match("

    hello

    ") end end end diff --git a/spec/components/email_styles_spec.rb b/spec/components/email_styles_spec.rb new file mode 100644 index 00000000000..025dd78b272 --- /dev/null +++ b/spec/components/email_styles_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'email' + +describe EmailStyles do + + def style_exists(html, css_rule) + fragment = Nokogiri::HTML.fragment(EmailStyles.new(html).format) + element = fragment.at(css_rule) + expect(element["style"]).not_to be_blank + end + + it "returns blank from an empty string" do + EmailStyles.new("").format.should be_blank + end + + it "attaches a style to h3 tags" do + style_exists("

    hello

    ", "h3") + end + + it "attaches a style to hr tags" do + style_exists("hello
    ", "hr") + end + + it "attaches a style to a tags" do + style_exists("wat", "a") + end + + it "attaches a style to ul tags" do + style_exists("", "ul") + end + + it "attaches a style to li tags" do + style_exists("", "li") + end + + it "removes pre tags but keeps their contents" do + expect(EmailStyles.new("
    hello
    ").format).to eq("hello") + end + +end diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 01db243292f..0d4447e0ec5 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -146,6 +146,10 @@ test PrettyText.excerpt("cnn",3).should == "cnn" end + it "uses an ellipsis instead of html entities if provided with the option" do + PrettyText.excerpt("cnn", 2, text_entities: true).should == "cn..." + end + it "should truncate links" do PrettyText.excerpt("cnn",2).should == "cn…" end diff --git a/spec/controllers/admin/email_controller_spec.rb b/spec/controllers/admin/email_controller_spec.rb new file mode 100644 index 00000000000..c20810902bf --- /dev/null +++ b/spec/controllers/admin/email_controller_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Admin::EmailController do + + it "is a subclass of AdminController" do + (Admin::EmailController < Admin::AdminController).should be_true + end + + let!(:user) { log_in(:admin) } + + context '.index' do + before do + xhr :get, :index + end + + subject { response } + it { should be_success } + end + + context '.logs' do + before do + xhr :get, :logs + end + + subject { response } + it { should be_success } + end + + context '.test' do + it 'raises an error without the email parameter' do + lambda { xhr :post, :test }.should raise_error(ActionController::ParameterMissing) + end + + context 'with an email address' do + it 'enqueues a test email job' do + Jobs.expects(:enqueue).with(:test_email, to_address: 'eviltrout@test.domain') + xhr :post, :test, email_address: 'eviltrout@test.domain' + end + end + end + + context '.preview_digest' do + it 'raises an error without the last_seen_at parameter' do + lambda { xhr :get, :preview_digest }.should raise_error(ActionController::ParameterMissing) + end + + it "previews the digest" do + xhr :get, :preview_digest, last_seen_at: 1.week.ago + expect(response).to be_success + end + end + +end diff --git a/spec/controllers/admin/email_logs_controller_spec.rb b/spec/controllers/admin/email_logs_controller_spec.rb deleted file mode 100644 index c6c45c341c2..00000000000 --- a/spec/controllers/admin/email_logs_controller_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'spec_helper' - -describe Admin::EmailLogsController do - - it "is a subclass of AdminController" do - (Admin::EmailLogsController < Admin::AdminController).should be_true - end - - let!(:user) { log_in(:admin) } - - context '.index' do - before do - xhr :get, :index - end - - subject { response } - it { should be_success } - end - - context '.test' do - - it 'raises an error without the email parameter' do - lambda { xhr :post, :test }.should raise_error(Discourse::InvalidParameters) - end - - context 'with an email address' do - - it 'enqueues a test email job' do - Jobs.expects(:enqueue).with(:test_email, to_address: 'eviltrout@test.domain') - xhr :post, :test, email_address: 'eviltrout@test.domain' - end - - end - - end - -end diff --git a/spec/controllers/excerpt_controller_spec.rb b/spec/controllers/excerpt_controller_spec.rb deleted file mode 100644 index 9647865bfdb..00000000000 --- a/spec/controllers/excerpt_controller_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'spec_helper' - -describe ExcerptController do - - describe 'show' do - it 'raises an error without the url param' do - lambda { xhr :get, :show }.should raise_error(Discourse::InvalidParameters) - end - - it 'returns 404 with a non-existant url' do - xhr :get, :show, url: 'http://madeup.com/url' - response.status.should == 404 - end - - it 'returns 404 from an invalid url' do - xhr :get, :show, url: 'asdfasdf' - response.status.should == 404 - end - - describe 'user excerpt' do - - before do - @user = Fabricate(:user) - @url = "http://test.host/users/#{@user.username}" - xhr :get, :show, url: @url - end - - it 'returns a valid status' do - response.should be_success - end - - it 'returns an excerpt type for the forum topic' do - parsed = JSON.parse(response.body) - parsed['type'].should == 'User' - end - - end - - describe 'forum topic excerpt' do - - before do - @post = Fabricate(:post) - @url = "http://test.host#{@post.topic.relative_url}" - xhr :get, :show, url: @url - end - - it 'returns a valid status' do - response.should be_success - end - - it 'returns an excerpt type for the forum topic' do - parsed = JSON.parse(response.body) - parsed['type'].should == 'Post' - end - - end - - describe 'post excerpt' do - - before do - @post = Fabricate(:post) - @url = "http://test.host#{@post.topic.relative_url}/1" - xhr :get, :show, url: @url - end - - it 'returns a valid status' do - response.should be_success - end - - it 'returns an excerpt type for the forum topic' do - parsed = JSON.parse(response.body) - parsed['type'].should == 'Post' - end - - end - - - end - - - -end diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb index d13db3e5142..26419e35ba7 100644 --- a/spec/mailers/user_notifications_spec.rb +++ b/spec/mailers/user_notifications_spec.rb @@ -31,7 +31,7 @@ describe UserNotifications do context "with new topics" do before do - Topic.expects(:new_topics).returns([Fabricate(:topic, user: Fabricate(:coding_horror))]) + Topic.expects(:for_digest).returns([Fabricate(:topic, user: Fabricate(:coding_horror))]) end its(:to) { should == [user.email] }