Better HTML emails, smarter email digests, new email section in admin with digest preview
This commit is contained in:
parent
f030d9b420
commit
0b97ea6345
|
@ -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 () {
|
|
@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
});
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -10,10 +10,6 @@ Discourse.AdminApiRoute = Discourse.Route.extend({
|
|||
|
||||
model: function() {
|
||||
return Discourse.AdminApi.find();
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render({into: 'admin/templates/admin'});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -10,10 +10,6 @@ Discourse.AdminCustomizeRoute = Discourse.Route.extend({
|
|||
|
||||
model: function() {
|
||||
return Discourse.SiteCustomization.findAll();
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render({into: 'admin/templates/admin'});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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'});
|
||||
}
|
||||
});
|
|
@ -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'});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
});
|
|
@ -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');
|
||||
}
|
||||
});
|
|
@ -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'});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
The base admin route
|
||||
|
||||
@class AdminRoute
|
||||
@class AdminRoute
|
||||
@extends Discourse.Route
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
|
|
|
@ -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'});
|
||||
|
||||
|
|
|
@ -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'});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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'});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
{{/if}}
|
||||
<li>{{#linkTo 'adminUsersList.active'}}{{i18n admin.users.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'admin.groups'}}{{i18n admin.groups.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'admin.email_logs'}}{{i18n admin.email_logs.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'adminEmail'}}{{i18n admin.email.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'adminFlags.active'}}{{i18n admin.flags.title}}{{/linkTo}}</li>
|
||||
{{#if currentUser.admin}}
|
||||
<li>{{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}</li>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<div class='admin-controls'>
|
||||
<div class='span15'>
|
||||
<ul class="nav nav-pills">
|
||||
<li>{{#linkTo adminEmail.index}}{{i18n admin.email.settings}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo adminEmail.logs}}{{i18n admin.email.logs}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo adminEmail.previewDigest}}{{i18n admin.email.preview_digest}}{{/linkTo}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{outlet}}
|
|
@ -0,0 +1,23 @@
|
|||
<table class="table">
|
||||
<tr>
|
||||
<th>{{i18n admin.email.delivery_method}}</th>
|
||||
<td>{{model.delivery_method}}</td>
|
||||
</tr>
|
||||
|
||||
{{#each model.settings}}
|
||||
<tr>
|
||||
<th style='width: 25%'>{{name}}</th>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
|
||||
<div class='admin-controls'>
|
||||
<div class='span5 controls'>
|
||||
{{textField value=testEmailAddress placeholderKey="admin.email.test_email_address"}}
|
||||
</div>
|
||||
<div class='span10 controls'>
|
||||
<button class='btn' {{action sendTestEmail}} {{bindAttr disabled="sendTestEmailDisabled"}}>{{i18n admin.email.send_test}}</button>
|
||||
{{#if sentTestEmail}}<span class='result-message'>{{i18n admin.email.sent_test}}</span>{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,19 +1,9 @@
|
|||
<div class='admin-controls'>
|
||||
<div class='span5 controls'>
|
||||
{{textField value=testEmailAddress placeholderKey="admin.email_logs.test_email_address"}}
|
||||
</div>
|
||||
<div class='span10 controls'>
|
||||
<button class='btn' {{action sendTestEmail}} {{bindAttr disabled="sendTestEmailDisabled"}}>{{i18n admin.email_logs.send_test}}</button>
|
||||
{{#if sentTestEmail}}<span class='result-message'>{{i18n admin.email_logs.sent_test}}</span>{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<th>{{i18n admin.email_logs.sent_at}}</th>
|
||||
<th>{{i18n admin.email.sent_at}}</th>
|
||||
<th>{{i18n user.title}}</th>
|
||||
<th>{{i18n admin.email_logs.to_address}}</th>
|
||||
<th>{{i18n admin.email_logs.email_type}}</th>
|
||||
<th>{{i18n admin.email.to_address}}</th>
|
||||
<th>{{i18n admin.email.email_type}}</th>
|
||||
</tr>
|
||||
|
||||
{{#if model.length}}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<div class='admin-controls'>
|
||||
<div class='span7 controls'>
|
||||
<label for='last-seen'>{{i18n admin.email.last_seen_user}}</label>
|
||||
{{input type="date" value=lastSeen id="last-seen"}}
|
||||
</div>
|
||||
<div class='span5'>
|
||||
<button class='btn' {{action refresh}}>{{i18n admin.email.refresh}}</button>
|
||||
</div>
|
||||
<div class="span7 toggle">
|
||||
<label>{{i18n admin.email.format}}</label>
|
||||
{{#if showHtml}}
|
||||
<span>{{i18n admin.email.html}}</span> | <a href='#' {{action toggleProperty 'showHtml'}}>{{i18n admin.email.text}}</a>
|
||||
{{else}}
|
||||
<a href='#' {{action toggleProperty 'showHtml'}}>{{i18n admin.email.html}}</a> | <span>{{i18n admin.email.text}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if loading}}
|
||||
<div class='admin-loading'>{{i18n loading}}</div>
|
||||
{{else}}
|
||||
{{#if showHtml}}
|
||||
{{{html_content}}}
|
||||
{{else}}
|
||||
<pre>{{{text_content}}}</pre>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
|
|
@ -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 = "";
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
<div class='contents'>
|
||||
<span class='badge-category' style='background-color: #{{unbound view.color}}; color: #{{unbound view.text_color}}'>{{unbound view.name}}</span>
|
||||
|
||||
{{#if view.excerpt}}
|
||||
<div class='description'>
|
||||
{{{view.excerpt}}}
|
||||
<a href="{{unbound view.topic_url}}">{{i18n learn_more}}</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class='figs'>
|
||||
<figure>{{view.topics_year}} <figcaption>{{i18n year}}</figcaption></figure>
|
||||
<figure>{{view.topics_month}} <figcaption>{{i18n month}}</figcaption></figure>
|
||||
<figure>{{view.topics_week}} <figcaption>{{i18n week}}</figcaption></figure>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<footer class='button-row'>
|
||||
{{#if view.can_delete}}
|
||||
<a href='#' {{action deleteCategory target="view"}} class='btn btn-small'>{{i18n category.delete}}</a>
|
||||
{{/if}}
|
||||
{{#if view.can_edit}}
|
||||
<a href='#' {{action editCategory view}} class='btn btn-small'>{{i18n category.edit}}</a>
|
||||
{{/if}}
|
||||
<a href="/category/{{unbound view.slug}}" class='btn btn-small'>{{i18n category.view}}</a>
|
||||
</footer>
|
|
@ -1 +0,0 @@
|
|||
<a class='close' href='#' {{action close target="view.parentView"}}><i class='icon-white icon-remove'></i></a>
|
|
@ -1,21 +0,0 @@
|
|||
<div class='image'>
|
||||
{{avatar view imageSize="large"}}
|
||||
</div>
|
||||
<div class='contents'>
|
||||
{{{unbound view.excerpt}}}
|
||||
<div class='info'>{{unbound view.created_at}}</div>
|
||||
</div>
|
||||
<footer class='button-row'>
|
||||
{{#if view.muted}}
|
||||
<a href='#' {{action unmute target="view"}} class='btn btn-small'>{{i18n unmute}}</a>
|
||||
{{else}}
|
||||
<a href='#' {{action mute target="view"}} class='btn btn-small'>{{i18n mute}}</a>
|
||||
{{/if}}
|
||||
{{#if view.has_multiple_posts}}
|
||||
{{#if view.last_post_url}}
|
||||
<a href='{{unbound view.last_post_url}}' class='btn btn-small'>{{i18n last_post}}</a>
|
||||
{{else}}
|
||||
<a href='{{unbound view.first_post_url}}' class='btn btn-small'>{{i18n first_post}}</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</footer>
|
|
@ -1,10 +0,0 @@
|
|||
<h1>{{view.name}}</h1>
|
||||
{{avatar view imageSize="large"}}
|
||||
<div class='contents'>
|
||||
{{unbound view.excerpt}}
|
||||
</div>
|
||||
<footer class='button-row'>
|
||||
<a {{action privateMessage target="view"}} href="#" class='btn btn-small'>{{i18n user.private_message}}</a>
|
||||
<a href='{{unbound view.url}}' class='btn btn-small'>{{i18n user.profile}}</a>
|
||||
<a href='#' class='btn btn-small' data-not-implemented="true">{{i18n user.mute}}</a>
|
||||
</footer>
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -1,11 +0,0 @@
|
|||
module ExcerptType
|
||||
|
||||
def self.included(base)
|
||||
base.attributes :type
|
||||
end
|
||||
|
||||
def type
|
||||
self.class.name.sub(/ExcerptSerializer/, '')
|
||||
end
|
||||
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,23 @@
|
|||
<table style="width: 100%">
|
||||
<tr>
|
||||
<td style="background-color: #eee; padding: 30px;">
|
||||
|
||||
<center>
|
||||
<table style="width: 90%; max-width: 500px;">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<%= Discourse.base_url %>"><img src="<%= Discourse.base_url %><%= SiteSetting.logo_url %>" style="height: 50px; margin-bottom: 15px;"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color: #fff; padding: 20px; font-family: Arial, Helvetica, sans-serif; font-size: 14px;">
|
||||
<%= raw(html_body) %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
|
@ -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 %>
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -935,7 +935,7 @@ pseudo:
|
|||
delete_confirm: '[[ Ďéłéťé ťĥíš čůšťóɱížáťíóɳ? ]]'
|
||||
about: '[[ Šíťé Čůšťóɱížáťíóɳ áłłóŵ ýóů ťó ɱóďíƒý šťýłéšĥééťš áɳď ĥéáďéřš
|
||||
óɳ ťĥé šíťé. Čĥóóšé óř áďď óɳé ťó šťářť éďíťíɳǧ. ]]'
|
||||
email_logs:
|
||||
email:
|
||||
title: '[[ Éɱáíł Łóǧš ]]'
|
||||
sent_at: '[[ Šéɳť Áť ]]'
|
||||
email_type: '[[ Éɱáíł Ťýƿé ]]'
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -977,7 +977,7 @@ zh_CN:
|
|||
delete_confirm: "删除本定制内容?"
|
||||
about: "站点定制允许你修改样式表和站点头部。选择或者添加一个来开始编辑。"
|
||||
|
||||
email_logs:
|
||||
email:
|
||||
title: "电子邮件"
|
||||
sent_at: "发送时间"
|
||||
email_type: "邮件类型"
|
||||
|
|
|
@ -977,7 +977,7 @@ zh_TW:
|
|||
delete_confirm: "刪除本定制內容?"
|
||||
about: "站點定制允許你修改樣式表和站點頭部。選擇或者添加一個來開始編輯。"
|
||||
|
||||
email_logs:
|
||||
email:
|
||||
title: "電子郵件"
|
||||
sent_at: "發送時間"
|
||||
email_type: "郵件類型"
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 << "</a>" if @in_a
|
||||
throw :done
|
||||
end
|
||||
|
|
|
@ -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(
|
||||
"<p><strong>hello</strong></p>"
|
||||
)
|
||||
expect(message.html_part.body.to_s).to match("<p><strong>hello</strong></p>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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("<h3>hello</h3>", "h3")
|
||||
end
|
||||
|
||||
it "attaches a style to hr tags" do
|
||||
style_exists("hello<hr>", "hr")
|
||||
end
|
||||
|
||||
it "attaches a style to a tags" do
|
||||
style_exists("<a href='#'>wat</a>", "a")
|
||||
end
|
||||
|
||||
it "attaches a style to ul tags" do
|
||||
style_exists("<ul><li>hello</li></ul>", "ul")
|
||||
end
|
||||
|
||||
it "attaches a style to li tags" do
|
||||
style_exists("<ul><li>hello</li></ul>", "li")
|
||||
end
|
||||
|
||||
it "removes pre tags but keeps their contents" do
|
||||
expect(EmailStyles.new("<pre>hello</pre>").format).to eq("hello")
|
||||
end
|
||||
|
||||
end
|
|
@ -146,6 +146,10 @@ test
|
|||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should == "<a href='http://cnn.com'>cnn</a>"
|
||||
end
|
||||
|
||||
it "uses an ellipsis instead of html entities if provided with the option" do
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>", 2, text_entities: true).should == "<a href='http://cnn.com'>cn...</a>"
|
||||
end
|
||||
|
||||
it "should truncate links" do
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should == "<a href='http://cnn.com'>cn…</a>"
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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] }
|
||||
|
|
Loading…
Reference in New Issue