simple_email_preview 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +7 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +162 -0
  5. data/Rakefile +62 -0
  6. data/app/assets/images/simple_email_preview/favicon.png +0 -0
  7. data/app/assets/javascripts/simple_email_preview/application.js +1 -0
  8. data/app/assets/stylesheets/simple_email_preview/_bootstrap3.sass +68 -0
  9. data/app/assets/stylesheets/simple_email_preview/_default.sass +230 -0
  10. data/app/assets/stylesheets/simple_email_preview/application.scss +10 -0
  11. data/app/controllers/simple_email_preview/application_controller.rb +19 -0
  12. data/app/controllers/simple_email_preview/emails_controller.rb +194 -0
  13. data/app/helpers/simple_email_preview/emails_helper.rb +85 -0
  14. data/app/models/simple_email_preview/preview.rb +86 -0
  15. data/app/presenters/simple_email_preview/preview_list_presenter.rb +38 -0
  16. data/app/views/layouts/simple_email_preview/_flash_notices.html.erb +7 -0
  17. data/app/views/layouts/simple_email_preview/application.html.erb +16 -0
  18. data/app/views/layouts/simple_email_preview/email.html.erb +30 -0
  19. data/app/views/simple_email_preview/emails/_email_iframe.html.erb +88 -0
  20. data/app/views/simple_email_preview/emails/_format_nav.html.erb +4 -0
  21. data/app/views/simple_email_preview/emails/_headers.html.erb +10 -0
  22. data/app/views/simple_email_preview/emails/_headers_and_nav.html.erb +12 -0
  23. data/app/views/simple_email_preview/emails/_i18n_nav.html.erb +3 -0
  24. data/app/views/simple_email_preview/emails/_nav.html.erb +18 -0
  25. data/app/views/simple_email_preview/emails/_send_form.html.erb +11 -0
  26. data/app/views/simple_email_preview/emails/index.html.erb +36 -0
  27. data/app/views/simple_email_preview/emails/show.html.erb +23 -0
  28. data/config/i18n-tasks.yml +10 -0
  29. data/config/initializers/simple_email_preview.rb +14 -0
  30. data/config/locales/de.yml +47 -0
  31. data/config/locales/en.yml +47 -0
  32. data/config/locales/es.yml +47 -0
  33. data/config/locales/ja.yml +47 -0
  34. data/config/locales/ru.yml +51 -0
  35. data/config/routes.rb +23 -0
  36. data/lib/generators/simple_email_preview/install_generator.rb +17 -0
  37. data/lib/generators/simple_email_preview/update_previews_generator.rb +45 -0
  38. data/lib/simple_email_preview.rb +105 -0
  39. data/lib/simple_email_preview/delivery_handler.rb +15 -0
  40. data/lib/simple_email_preview/engine.rb +14 -0
  41. data/lib/simple_email_preview/main_app_route_delegator.rb +20 -0
  42. data/lib/simple_email_preview/version.rb +3 -0
  43. data/lib/simple_email_preview/view_hooks.rb +102 -0
  44. metadata +231 -0
@@ -0,0 +1,10 @@
1
+ @import "simple_email_preview/default";
2
+
3
+ body {
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+
8
+ .rep-standalone-container {
9
+ padding: 0 15px;
10
+ }
@@ -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> &times;</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>