simple_email_preview 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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>
|