simple_email_preview 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +162 -0
- data/Rakefile +62 -0
- data/app/assets/images/simple_email_preview/favicon.png +0 -0
- data/app/assets/javascripts/simple_email_preview/application.js +1 -0
- data/app/assets/stylesheets/simple_email_preview/_bootstrap3.sass +68 -0
- data/app/assets/stylesheets/simple_email_preview/_default.sass +230 -0
- data/app/assets/stylesheets/simple_email_preview/application.scss +10 -0
- data/app/controllers/simple_email_preview/application_controller.rb +19 -0
- data/app/controllers/simple_email_preview/emails_controller.rb +194 -0
- data/app/helpers/simple_email_preview/emails_helper.rb +85 -0
- data/app/models/simple_email_preview/preview.rb +86 -0
- data/app/presenters/simple_email_preview/preview_list_presenter.rb +38 -0
- data/app/views/layouts/simple_email_preview/_flash_notices.html.erb +7 -0
- data/app/views/layouts/simple_email_preview/application.html.erb +16 -0
- data/app/views/layouts/simple_email_preview/email.html.erb +30 -0
- data/app/views/simple_email_preview/emails/_email_iframe.html.erb +88 -0
- data/app/views/simple_email_preview/emails/_format_nav.html.erb +4 -0
- data/app/views/simple_email_preview/emails/_headers.html.erb +10 -0
- data/app/views/simple_email_preview/emails/_headers_and_nav.html.erb +12 -0
- data/app/views/simple_email_preview/emails/_i18n_nav.html.erb +3 -0
- data/app/views/simple_email_preview/emails/_nav.html.erb +18 -0
- data/app/views/simple_email_preview/emails/_send_form.html.erb +11 -0
- data/app/views/simple_email_preview/emails/index.html.erb +36 -0
- data/app/views/simple_email_preview/emails/show.html.erb +23 -0
- data/config/i18n-tasks.yml +10 -0
- data/config/initializers/simple_email_preview.rb +14 -0
- data/config/locales/de.yml +47 -0
- data/config/locales/en.yml +47 -0
- data/config/locales/es.yml +47 -0
- data/config/locales/ja.yml +47 -0
- data/config/locales/ru.yml +51 -0
- data/config/routes.rb +23 -0
- data/lib/generators/simple_email_preview/install_generator.rb +17 -0
- data/lib/generators/simple_email_preview/update_previews_generator.rb +45 -0
- data/lib/simple_email_preview.rb +105 -0
- data/lib/simple_email_preview/delivery_handler.rb +15 -0
- data/lib/simple_email_preview/engine.rb +14 -0
- data/lib/simple_email_preview/main_app_route_delegator.rb +20 -0
- data/lib/simple_email_preview/version.rb +3 -0
- data/lib/simple_email_preview/view_hooks.rb +102 -0
- metadata +231 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module SimpleEmailPreview
|
2
|
+
class ApplicationController < ::SimpleEmailPreview.parent_controller.constantize
|
3
|
+
layout 'simple_email_preview/application'
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def prevent_browser_caching
|
8
|
+
# Prevent back-button browser caching:
|
9
|
+
# HTTP/1.1
|
10
|
+
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate'
|
11
|
+
# Date in the past
|
12
|
+
response.headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT'
|
13
|
+
# Always modified
|
14
|
+
response.headers['Last-Modified'] = Time.now.httpdate
|
15
|
+
# HTTP/1.0
|
16
|
+
response.headers['Pragma'] = 'no-cache'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module SimpleEmailPreview
|
2
|
+
class EmailsController < ::SimpleEmailPreview::ApplicationController
|
3
|
+
include ERB::Util
|
4
|
+
include Rails.application.routes.url_helpers
|
5
|
+
before_action :load_preview, except: :index
|
6
|
+
# around_action :set_locale
|
7
|
+
before_action :set_email_preview_locale
|
8
|
+
helper_method :with_email_locale
|
9
|
+
helper_method :preview_params
|
10
|
+
before_action :add_breadcrumbs
|
11
|
+
before_action :set_title
|
12
|
+
|
13
|
+
def add_breadcrumbs
|
14
|
+
super if defined?(super)
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_title
|
18
|
+
if defined?(super)
|
19
|
+
super
|
20
|
+
else
|
21
|
+
@title = t('email_preview')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# List of emails
|
26
|
+
def index
|
27
|
+
if defined?(super)
|
28
|
+
super
|
29
|
+
else
|
30
|
+
@previews = Preview.all
|
31
|
+
@list = PreviewListPresenter.new(@previews)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Preview an email
|
36
|
+
def show
|
37
|
+
if defined?(super)
|
38
|
+
super
|
39
|
+
else
|
40
|
+
prevent_browser_caching
|
41
|
+
# with_email_locale do
|
42
|
+
if @preview.respond_to?(:preview_mail)
|
43
|
+
@mail, body = mail_and_body
|
44
|
+
@mail_body_html = render_to_string(inline: body, layout: 'simple_email_preview/email')
|
45
|
+
else
|
46
|
+
raise ArgumentError, "#{@preview} is not a preview class, does not respond_to?(:preview_mail)"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Really deliver an email
|
52
|
+
def test_deliver
|
53
|
+
redirect_url = simple_email_preview.rep_email_url(preview_params.except(:recipient_email))
|
54
|
+
if (address = params[:recipient_email]).blank? || address !~ /@/
|
55
|
+
redirect_to redirect_url, alert: t('rep.test_deliver.provide_email')
|
56
|
+
return
|
57
|
+
end
|
58
|
+
with_email_locale do
|
59
|
+
delivery_handler = SimpleEmailPreview::DeliveryHandler.new(preview_mail, to: address, cc: nil, bcc: nil)
|
60
|
+
deliver_email!(delivery_handler.mail)
|
61
|
+
end
|
62
|
+
delivery_method = Rails.application.config.action_mailer.delivery_method
|
63
|
+
if delivery_method
|
64
|
+
redirect_to redirect_url, notice: t('rep.test_deliver.sent_notice', address: address, delivery_method: delivery_method)
|
65
|
+
else
|
66
|
+
redirect_to redirect_url, alert: t('rep.test_deliver.no_delivery_method', environment: Rails.env)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Download attachment
|
71
|
+
def show_attachment
|
72
|
+
with_email_locale do
|
73
|
+
filename = "#{params[:filename]}.#{params[:format]}"
|
74
|
+
attachment = preview_mail(false).attachments.find { |a| a.filename == filename }
|
75
|
+
send_data attachment.body.raw_source, filename: filename
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Render headers partial. Used by the CMS integration to refetch headers after editing.
|
80
|
+
def show_headers
|
81
|
+
mail = with_email_locale { mail_and_body.first }
|
82
|
+
render partial: 'simple_email_preview/emails/headers', locals: { mail: mail }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Render email body iframe HTML. Used by the CMS integration to provide a link back to Show from Edit.
|
86
|
+
def show_body
|
87
|
+
prevent_browser_caching
|
88
|
+
cms_edit_links!
|
89
|
+
with_email_locale do
|
90
|
+
_, body = mail_and_body
|
91
|
+
render inline: body, layout: 'simple_email_preview/email'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def preview_params
|
98
|
+
if Rails::VERSION::MAJOR >= 5
|
99
|
+
params.to_unsafe_h.except(*(request.path_parameters.keys - [:email_locale]))
|
100
|
+
else
|
101
|
+
params.except(*(request.path_parameters.keys - [:email_locale]))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def deliver_email!(mail)
|
106
|
+
if mail.respond_to?(:deliver_now!)
|
107
|
+
# ActiveJob
|
108
|
+
mail.deliver_now!
|
109
|
+
elsif mail.respond_to?(:deliver!)
|
110
|
+
# support deliver! if present (resque-mailer etc)
|
111
|
+
mail.deliver!
|
112
|
+
else
|
113
|
+
mail.deliver
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Load mail and its body for preview
|
118
|
+
# @return [[Mail, String]] the mail object and its body
|
119
|
+
def mail_and_body
|
120
|
+
mail = preview_mail
|
121
|
+
body = mail_body_content(mail, @part_type)
|
122
|
+
[mail, body]
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param [Boolean] run_handlers whether to run the registered handlers for Mail object
|
126
|
+
# @return [Mail]
|
127
|
+
def preview_mail(run_handlers = true)
|
128
|
+
@preview.preview_mail(run_handlers, preview_params)
|
129
|
+
end
|
130
|
+
|
131
|
+
# @param [Mail] mail
|
132
|
+
# @param ['html', 'plain', 'raw']
|
133
|
+
# @return [String] version of the email for HTML
|
134
|
+
def mail_body_content(mail, part_type)
|
135
|
+
return "<pre id='raw_message'>#{html_escape(mail.to_s)}</pre>".html_safe if part_type == 'raw'
|
136
|
+
|
137
|
+
body_part = if mail.multipart?
|
138
|
+
(part_type =~ /html/ ? mail.html_part : mail.text_part)
|
139
|
+
else
|
140
|
+
mail
|
141
|
+
end
|
142
|
+
return "<pre id='error'>#{html_escape(t('rep.errors.email_missing_format', locale: @ui_locale))}</pre>" unless body_part
|
143
|
+
if body_part.content_type =~ /plain/
|
144
|
+
"<pre id='message_body'>#{html_escape(body_part.body.to_s)}</pre>".html_safe
|
145
|
+
else
|
146
|
+
body_content = body_part.body.to_s
|
147
|
+
|
148
|
+
mail.attachments.each do |attachment|
|
149
|
+
web_url = simple_email_preview.rep_raw_email_attachment_url(params[:preview_id], attachment.filename)
|
150
|
+
body_content.gsub!(attachment.url, web_url)
|
151
|
+
end
|
152
|
+
|
153
|
+
body_content.html_safe
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def with_email_locale(&block)
|
158
|
+
I18n.with_locale @email_locale, &block
|
159
|
+
end
|
160
|
+
|
161
|
+
# Email content locale
|
162
|
+
def set_email_preview_locale
|
163
|
+
@email_locale = (params[:email_locale] || SimpleEmailPreview.default_email_locale || I18n.default_locale).to_s
|
164
|
+
end
|
165
|
+
|
166
|
+
# UI locale
|
167
|
+
def set_locale
|
168
|
+
@ui_locale = SimpleEmailPreview.locale
|
169
|
+
unless I18n.available_locales.map(&:to_s).include?(@ui_locale.to_s)
|
170
|
+
@ui_locale = :en
|
171
|
+
end
|
172
|
+
begin
|
173
|
+
locale_was = I18n.locale
|
174
|
+
I18n.locale = @ui_locale
|
175
|
+
yield if block_given?
|
176
|
+
ensure
|
177
|
+
I18n.locale = locale_was
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Let REP's `cms_email_snippet` know to render an Edit link
|
182
|
+
# Todo: Refactoring is especially welcome here
|
183
|
+
def cms_edit_links!
|
184
|
+
RequestStore.store[:rep_edit_links] = (@part_type == 'html')
|
185
|
+
end
|
186
|
+
|
187
|
+
def load_preview
|
188
|
+
@preview = ::SimpleEmailPreview::Preview[params[:preview_id]]
|
189
|
+
raise ActionController::RoutingError.new('Not Found') unless @preview
|
190
|
+
# @preview = ::RailsEmailPreview::Preview[params[:preview_id]] || raise ActionController::RoutingError.new('Not Found')
|
191
|
+
@part_type = params[:part_type] || 'text'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module SimpleEmailPreview::EmailsHelper
|
2
|
+
FORMAT_LABELS = { 'html' => 'HTML', 'plain' => 'Text', 'raw' => 'Raw' }.freeze
|
3
|
+
|
4
|
+
def format_label(mime_type)
|
5
|
+
FORMAT_LABELS[mime_type]
|
6
|
+
end
|
7
|
+
|
8
|
+
def change_locale_attr(locale)
|
9
|
+
{ href: simple_email_preview.rep_email_path(preview_params.merge(part_type: @part_type, email_locale: locale)),
|
10
|
+
class: rep_btn_class(@email_locale == locale.to_s) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def change_format_attr(format)
|
14
|
+
{ href: simple_email_preview.rep_email_path(preview_params.merge(part_type: format)),
|
15
|
+
class: rep_btn_class(@part_type == format) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def locale_name(locale)
|
19
|
+
if defined?(TwitterCldr)
|
20
|
+
TwitterCldr::Shared::LanguageCodes.to_language(locale.to_s, :bcp_47)
|
21
|
+
else
|
22
|
+
locale.to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def human_headers(mail)
|
27
|
+
{ t('rep.headers.subject') => mail.subject || '(no subject)',
|
28
|
+
t('rep.headers.from') => mail.from,
|
29
|
+
t('rep.headers.reply_to') => mail.reply_to,
|
30
|
+
t('rep.headers.to') => mail.to,
|
31
|
+
t('rep.headers.cc') => mail.cc,
|
32
|
+
t('rep.headers.bcc') => mail.bcc,
|
33
|
+
t('rep.headers.attachments') => attachment_links(mail) }.each do |name, value|
|
34
|
+
yield(name, format_header_value(value)) unless value.blank?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def attachment_links(mail)
|
39
|
+
mail.attachments.map do |attachment|
|
40
|
+
url = simple_email_preview.rep_raw_email_attachment_path(preview_params.merge(filename: attachment.filename))
|
41
|
+
link_to(attachment.filename, url, title: attachment.header.to_s)
|
42
|
+
end.to_sentence.html_safe
|
43
|
+
end
|
44
|
+
|
45
|
+
def format_header_value(value)
|
46
|
+
if value.is_a?(Array)
|
47
|
+
value.map(&:to_s) * ', '
|
48
|
+
else
|
49
|
+
value.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# style
|
54
|
+
def rep_style
|
55
|
+
SimpleEmailPreview.style
|
56
|
+
end
|
57
|
+
|
58
|
+
def rep_btn_class(active = false)
|
59
|
+
[rep_style[:btn_default_class], (rep_style[:btn_active_class_modifier] if active)].compact * ' '
|
60
|
+
end
|
61
|
+
|
62
|
+
def rep_btn_group_class
|
63
|
+
rep_style[:btn_group_class]
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_index_hook(key, &block)
|
67
|
+
render_hook key, list: @list, previews: @previews, &block
|
68
|
+
end
|
69
|
+
|
70
|
+
def with_show_hook(key, &block)
|
71
|
+
render_hook key, mail: @mail, preview: @preview, &block
|
72
|
+
end
|
73
|
+
|
74
|
+
def render_hook(key, args, &block)
|
75
|
+
view_hooks.render(key, args, self, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
def hook?(key)
|
79
|
+
view_hooks.for?(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
def view_hooks
|
83
|
+
SimpleEmailPreview.view_hooks
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module SimpleEmailPreview
|
2
|
+
# Preview for one mailer method
|
3
|
+
class Preview
|
4
|
+
attr_accessor :id, :preview_class_name, :preview_method
|
5
|
+
|
6
|
+
def initialize(attr = {})
|
7
|
+
attr.each { |k, v| send "#{k}=", v }
|
8
|
+
end
|
9
|
+
|
10
|
+
def locales
|
11
|
+
I18n.available_locales
|
12
|
+
end
|
13
|
+
|
14
|
+
def formats
|
15
|
+
%w[plain raw]
|
16
|
+
end
|
17
|
+
|
18
|
+
def preview_mail(run_hooks = false, search_query_params = {})
|
19
|
+
preview_instance = preview_class_name.constantize.new
|
20
|
+
setup_instance_variables(preview_instance, search_query_params)
|
21
|
+
|
22
|
+
preview_instance.send(preview_method).tap do |mail|
|
23
|
+
SimpleEmailPreview.run_before_render(mail, self) if run_hooks
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
@name ||= "#{group_name}: #{humanized_method_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def humanized_method_name
|
32
|
+
@action_name ||= preview_method.to_s.humanize
|
33
|
+
end
|
34
|
+
|
35
|
+
# @deprecated {#method_name} is deprecated and will be removed in v3
|
36
|
+
alias method_name humanized_method_name
|
37
|
+
|
38
|
+
def group_name
|
39
|
+
@group_name ||= preview_class_name.to_s.underscore.gsub('/', ': ').sub(/(_mailer)?_preview$/, '').humanize
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def find(email_id)
|
44
|
+
@by_id[email_id]
|
45
|
+
end
|
46
|
+
|
47
|
+
alias [] find
|
48
|
+
|
49
|
+
attr_reader :all
|
50
|
+
|
51
|
+
def mail_methods(mailer)
|
52
|
+
mailer.public_instance_methods(false).map(&:to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
def load_all(class_names)
|
56
|
+
@all = []
|
57
|
+
@by_id = {}
|
58
|
+
class_names.each do |preview_class_name|
|
59
|
+
preview_class = preview_class_name.constantize
|
60
|
+
|
61
|
+
mail_methods(preview_class).sort.each do |preview_method|
|
62
|
+
mailer_method = preview_method
|
63
|
+
id = "#{preview_class_name.underscore.gsub('/', '__')}-#{mailer_method}"
|
64
|
+
|
65
|
+
email = new(
|
66
|
+
id: id,
|
67
|
+
preview_class_name: preview_class_name,
|
68
|
+
preview_method: preview_method
|
69
|
+
)
|
70
|
+
@all << email
|
71
|
+
@by_id[id] = email
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@all.sort_by!(&:name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def setup_instance_variables(object, params)
|
81
|
+
unless params.empty?
|
82
|
+
params.each { |k, v| object.instance_variable_set("@#{k}", v) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module SimpleEmailPreview
|
3
|
+
class PreviewListPresenter
|
4
|
+
attr_reader :previews
|
5
|
+
|
6
|
+
def initialize(previews)
|
7
|
+
@previews = previews
|
8
|
+
end
|
9
|
+
|
10
|
+
def columns(&block)
|
11
|
+
return to_enum(:columns) unless block_given?
|
12
|
+
split_in_halves(groups) { |_k, v| v.length }.each do |column_groups|
|
13
|
+
block.yield(column_groups)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def groups
|
18
|
+
@groups ||= by_class_name.inject({}) do |h, (_class_name, previews)|
|
19
|
+
h.update previews.first.group_name => previews
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def split_in_halves(xs, &weight)
|
26
|
+
xs = xs.to_a
|
27
|
+
ws = xs.map(&weight)
|
28
|
+
col_w = (ws.sum + 1) / 2
|
29
|
+
cur_w = 0
|
30
|
+
mid = ws.find_index { |w| (cur_w += w) >= col_w + w } || [ws.length - 1, 0].max
|
31
|
+
[xs.first(mid), xs.from(mid)]
|
32
|
+
end
|
33
|
+
|
34
|
+
def by_class_name
|
35
|
+
@by_class_name ||= previews.group_by(&:preview_class_name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<% flash.each do |name, msg| %>
|
2
|
+
<% next unless msg.is_a?(String) %>
|
3
|
+
<div class="alert alert-<%= name.to_s =~ /notice/ ? 'success' : 'error' %>">
|
4
|
+
<button class="close" type="button" data-dismiss="alert"><i class="icon-remove"></i> ×</button>
|
5
|
+
<%= content_tag :div, name.to_s.end_with?('_html') ? msg.html_safe : h(msg).gsub("\n", "<br>").html_safe, id: "flash_#{name.to_s.sub(/_html$/, '')}" %>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= t('.head_title') %></title>
|
5
|
+
<%= stylesheet_link_tag 'simple_email_preview/application' %>
|
6
|
+
<%= javascript_include_tag 'simple_email_preview/application' %>
|
7
|
+
<%= favicon_link_tag 'simple_email_preview/favicon.png' %>
|
8
|
+
<%= yield(:head) %>
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<div class="rep-standalone-container">
|
12
|
+
<%= render 'layouts/simple_email_preview/flash_notices' %>
|
13
|
+
<%= yield %>
|
14
|
+
</div>
|
15
|
+
</body>
|
16
|
+
</html>
|