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.
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>