rails_mail 0.9.3 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0111e011c108a5b717fcb3f4074985e58faaeef49d4247587697be29facff12
4
- data.tar.gz: edbcf7e6c0856e325c7d3fba6d0d03ac024eb26874a194d9554b0b39685d2ddb
3
+ metadata.gz: 74639c1eeda1941950ff03fed49be529f335fc3e1e6e9896afd8ce9a636daaa6
4
+ data.tar.gz: bd46e033763529b995f444ef78d0390404d088d816e46300cf59bec3efbf3719
5
5
  SHA512:
6
- metadata.gz: 1d4e8443a570257b4f3fd15b1a807610968968de29ad779a21e2073611b7efdd5f062e2073a250f2b3834a7af2f2b154ff92daba835fd9492747536178d49e42
7
- data.tar.gz: 27d2f45c1ffb9db48e0916078c6afe565aeaa9be29c6d6bf9dddb53fad28706501313539352c1e1857b29c3ba364a5474397a02ab6dd042999e87b149974f104
6
+ metadata.gz: 4d1899db5ec6f5d1373c50e3dcc737be3a0156c01bbb60c8291e32a9ca5a8f0902451c7c90478ff4967624d1e372e842862067e9229ae634757f2518f311f557
7
+ data.tar.gz: 24845fe1ef0b59edcc0257f0b1cf695b2e5872b38d0dd8f5d5afb1989401684cc4e2aa0e5764ea74f90ec4279a829443ee42e6cde6eb9ec84a7d65e321f727bc
data/README.md CHANGED
@@ -15,9 +15,10 @@ RailsMail saves all outgoing emails to your database instead of actually sending
15
15
  * Clean, responsive UI for viewing email contents
16
16
  * Optional authentication support
17
17
  * Trimming emails older than a specified duration or a maximum number of emails
18
- * Ability to manually clear emails out and turn on/off that functionality based on environment (eg, so that in Staging, other stakeholders can't clear emails out, but in dev sometimes you want a clean slate)
18
+ * Ability to manually clear emails in bulk or individually. The bulk delete can be turned on/off based on environment (eg, so that in Staging, other stakeholders can't clear emails out, but in dev sometimes you want a clean slate)
19
19
  * Dynamic time ago in words using date-fns
20
20
  * Ability to customize how the job that trims emails is enqueued
21
+ * Ability to customize the title in the top left of the page via a standard Rails view that overrides the engine's default view.
21
22
 
22
23
  ## Installation
23
24
 
@@ -79,7 +80,7 @@ To use RailsMail in your application:
79
80
  3. **Configure the initializer**
80
81
  See the [Configuration](#configuration) section for more details.
81
82
 
82
- 4. **Visit `/rails_mail` in your browser to view all captured emails.**
83
+ 4. **Visit `/rails_mail` (or where ever you mounted the engine) in your browser to view all captured emails.**
83
84
 
84
85
  ### Configuration
85
86
 
@@ -90,11 +91,17 @@ RailsMail can be configured through an initializer:
90
91
  RailsMail.configure do |config|
91
92
  # Optional authentication callback
92
93
  # (if using Authlogic. If using Devise see the Authentication section)
93
- config.authentication_callback do
94
+ config.authenticate do
94
95
  user_session = UserSession.find
95
96
  raise ActionController::RoutingError.new('Not Found') unless user_session&.user&.admin?
96
97
  end
97
98
 
99
+ # Optional decide whether to show the clear button
100
+ # Useful if you want devs in local envs to be able to clear all emails, but not in staging
101
+ config.show_clear_all_button do
102
+ Rails.env.development?
103
+ end
104
+
98
105
  # Delete emails older than the specified duration
99
106
  config.trim_emails_older_than = 30.days
100
107
 
@@ -113,6 +120,13 @@ end
113
120
  - `trim_emails_max_count`: Keeps only the N most recent emails, deleting older ones.
114
121
  - `sync_via`: Controls whether the trimming job runs synchronously (:now) or asynchronously (:later)
115
122
 
123
+ ### Customize the title
124
+ Since this is a Rails engine, you can customize the title by creating a file at `app/views/layouts/rails_mail/_title.html.erb`.
125
+
126
+ ```erb
127
+ <h1 class="text-xl font-bold">My apps mails</h1>
128
+ ```
129
+
116
130
  ## Authentication
117
131
  Authentication is optional, but recommended and will depend on your application's authentication setup. This gem provides an `authentication_callback` that you can configure in the initializer which is helpful for Authlogic. If you are using Devise, you can simply wrap the mount point of the engine.
118
132
 
@@ -17,10 +17,19 @@ module RailsMail
17
17
  def show
18
18
  @emails = Email.order(created_at: :desc)
19
19
  @email = Email.find(params[:id])
20
- if request.headers["Turbo-Frame"]
21
- render partial: "rails_mail/emails/show", locals: { email: @email }
22
- else
23
- render :index
20
+ session[:current_email_id] = @email.id
21
+
22
+ render :show
23
+ end
24
+
25
+ def destroy
26
+ @email = Email.find(params[:id])
27
+ @current_email_id = session[:current_email_id]
28
+ @email.destroy
29
+
30
+ respond_to do |format|
31
+ format.html { redirect_to emails_path }
32
+ format.turbo_stream
24
33
  end
25
34
  end
26
35
 
@@ -1,14 +1,16 @@
1
1
  import { Application } from "stimulus";
2
2
  import "turbo";
3
3
  import EmailHighlightController from "email_highlight_controller";
4
+ import RedirectController from "redirect_controller";
4
5
  import AutoSubmit from 'auto_submit'
5
6
  import Timeago from 'timeago'
6
7
 
7
8
  const application = Application.start();
8
9
 
9
10
  application.register("email-highlight", EmailHighlightController);
10
- application.register('auto-submit', AutoSubmit)
11
- application.register('timeago', Timeago)
11
+ application.register("redirect", RedirectController);
12
+ application.register('auto-submit', AutoSubmit);
13
+ application.register('timeago', Timeago);
12
14
 
13
15
  window.Stimulus = Application.start();
14
16
 
@@ -19,14 +19,14 @@ export default class extends Controller {
19
19
 
20
20
  // Called when a link is clicked
21
21
  highlight(event) {
22
- // Remove active class from all links
22
+ // Remove active class from all email containers
23
23
  this.linkTargets.forEach(link => {
24
- link.classList.remove(this.constructor.activeClass)
24
+ link.closest(".group").classList.remove(this.constructor.activeClass)
25
25
  })
26
26
 
27
- // Add active class to clicked link
27
+ // Add active class to clicked email's container
28
28
  const clickedLink = event.currentTarget
29
- clickedLink.classList.add(this.constructor.activeClass)
29
+ clickedLink.closest(".group").classList.add(this.constructor.activeClass)
30
30
  }
31
31
 
32
32
  highlightCurrentEmail() {
@@ -35,12 +35,13 @@ export default class extends Controller {
35
35
  if (emailContent) {
36
36
  const currentEmailId = emailContent.dataset.emailId
37
37
 
38
- // Highlight the link with the matching email ID
38
+ // Highlight the container with the matching email ID
39
39
  this.linkTargets.forEach(link => {
40
+ const container = link.closest(".group")
40
41
  if (link.dataset.emailId === currentEmailId) {
41
- link.classList.add(this.constructor.activeClass)
42
+ container.classList.add(this.constructor.activeClass)
42
43
  } else {
43
- link.classList.remove(this.constructor.activeClass)
44
+ container.classList.remove(this.constructor.activeClass)
44
45
  }
45
46
  })
46
47
  }
@@ -0,0 +1,12 @@
1
+ import { Controller } from 'stimulus'
2
+
3
+ export default class extends Controller {
4
+ static values = { url: String }
5
+ connect () {
6
+ // Need to remove the element
7
+ // Otherwise, if we revisit this page using a Turbo page cache
8
+ // it may end up redirecting again
9
+ this.element.remove();
10
+ Turbo.visit(this.urlValue)
11
+ }
12
+ }
@@ -1,6 +1,7 @@
1
1
  module RailsMail
2
2
  class Email < ApplicationRecord
3
- store_accessor :data, :from, :to, :cc, :bcc, :subject, :body, :content_type, :attachments
3
+ include RailsMail::Engine.routes.url_helpers
4
+ store_accessor :data, :from, :to, :cc, :bcc, :subject, :html_part, :text_part, :content_type, :attachments
4
5
 
5
6
  validates :from, presence: true
6
7
  validates :to, presence: true
@@ -13,24 +14,45 @@ module RailsMail
13
14
  }
14
15
 
15
16
  def text?
16
- content_type&.include?("text/plain")
17
+ content_type&.include?("text/plain") || content_type&.include?("multipart/alternative")
17
18
  end
18
19
 
19
20
  def html?
20
- content_type&.include?("text/html")
21
+ content_type&.include?("text/html") || content_type&.include?("multipart/alternative")
22
+ end
23
+
24
+ def next_email
25
+ RailsMail::Email.where("id < ?", id).last || RailsMail::Email.first
26
+ end
27
+
28
+ def html_body
29
+ return nil unless html?
30
+
31
+ html_part["raw_source"]
32
+ end
33
+
34
+ def text_body
35
+ return nil unless text?
36
+
37
+ text_part["raw_source"]
21
38
  end
22
39
 
23
40
  private
24
41
 
25
42
  def broadcast_email
26
- return unless defined?(::Turbo) && defined?(::ActionCable)
43
+ return unless defined?(::ActionCable)
27
44
 
28
- ::Turbo::StreamsChannel.broadcast_prepend_to(
29
- "rails_mail:emails",
30
- target: "email-sidebar",
45
+ html = ApplicationController.render(
31
46
  partial: "rails_mail/shared/email",
32
- locals: { email: self }
47
+ locals: { email: self, email_path: email_path(self) }
48
+ )
49
+
50
+ turbo_stream = RailsMail::TurboHelper::TurboStreamBuilder.new.prepend(
51
+ target: "email-sidebar",
52
+ content: html
33
53
  )
54
+
55
+ ActionCable.server.broadcast("rails_mail:emails", turbo_stream)
34
56
  rescue StandardError => e
35
57
  Rails.logger.error "RailsMail::Email#broadcast_email failed: #{e.message}"
36
58
  end
@@ -0,0 +1,16 @@
1
+
2
+ <head>
3
+ <title>RailsMail - <%= yield(:title) if content_for?(:title) %></title>
4
+ <meta name="viewport" content="width=device-width,initial-scale=1">
5
+ <%= action_cable_meta_tag if defined?(::ActionCable) %>
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <%= tag.link rel: "stylesheet", href: frontend_static_path(:style, format: :css, v: RailsMail::VERSION, locale: nil), nonce: content_security_policy_nonce %>
9
+
10
+ <%= tag.script "", src: frontend_static_path(:tailwind, format: :js, v: RailsMail::VERSION, locale: nil), nonce: content_security_policy_nonce %>
11
+
12
+ <% importmaps = RailsMail::FrontendsController.js_modules.keys.index_with { |module_name| frontend_module_path(module_name, format: :js, locale: nil) } %>
13
+ <%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
14
+ <%= tag.script "", type: "module", nonce: content_security_policy_nonce do %> import "application"; <% end %>
15
+
16
+ </head>
@@ -0,0 +1 @@
1
+ <h1 class="text-xl font-bold">Rails Mail</h1>
@@ -1,34 +1,37 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
- <head>
4
- <title>RailsMail - <%= yield(:title) if content_for?(:title) %></title>
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <%= action_cable_meta_tag if defined?(::ActionCable) %>
7
- <%= csrf_meta_tags %>
8
- <%= csp_meta_tag %>
9
- <%= tag.link rel: "stylesheet", href: frontend_static_path(:style, format: :css, v: RailsMail::VERSION, locale: nil), nonce: content_security_policy_nonce %>
10
-
11
- <%= tag.script "", src: frontend_static_path(:tailwind, format: :js, v: RailsMail::VERSION, locale: nil), nonce: content_security_policy_nonce %>
3
+ <%= render "layouts/rails_mail/head" %>
12
4
 
13
- <% importmaps = RailsMail::FrontendsController.js_modules.keys.index_with { |module_name| frontend_module_path(module_name, format: :js, locale: nil) } %>
14
- <%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
15
- <%= tag.script "", type: "module", nonce: content_security_policy_nonce do %> import "application"; <% end %>
5
+ <body class="bg-gray-100">
6
+ <div class="flex h-screen relative">
7
+ <!-- Mobile menu checkbox (hidden) -->
8
+ <input type="checkbox" class="hidden peer" id="sidebar-toggle">
16
9
 
17
- </head>
10
+ <!-- Collapsed menu button -->
11
+ <label for="sidebar-toggle" class="lg:hidden fixed top-4 left-4 z-20 p-2 rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 cursor-pointer peer-checked:hidden bg-gray-200">
12
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
13
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
14
+ </svg>
15
+ </label>
18
16
 
19
- <body class="bg-gray-100">
20
- <div class="flex h-screen">
21
17
  <!-- Sidebar -->
22
- <div class="w-80 bg-white border-r border-gray-200 flex flex-col">
18
+ <div class="fixed lg:static inset-y-0 left-0 transform -translate-x-full peer-checked:translate-x-0 lg:translate-x-0 transition duration-200 ease-in-out w-80 bg-white border-r border-gray-200 flex flex-col z-10">
23
19
  <div class="flex-none p-4">
24
- <div class="flex items-center justify-between">
25
- <div class="flex items-center">
26
- <%= link_to root_path, class: "text-gray-900" do %>
27
- <h1 class="text-xl font-bold">Rails Mail</h1>
28
- <% end %>
29
- </div>
20
+ <div class="flex items-center justify-between w-full">
21
+ <!-- Menu icon (visible only on mobile) -->
22
+ <label for="sidebar-toggle" class="lg:hidden p-2 rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 cursor-pointer bg-gray-200">
23
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
24
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
25
+ </svg>
26
+ </label>
27
+
28
+ <!-- Title (centered) -->
29
+ <%= link_to root_path, class: "text-gray-900" do %>
30
+ <%= render "layouts/rails_mail/title" %>
31
+ <% end %>
30
32
 
31
- <% if instance_eval(&RailsMail.show_clear_button?) %>
33
+ <!-- Clear All button (or empty div for spacing when button is hidden) -->
34
+ <% if instance_eval(&RailsMail.show_clear_all_button_callback) %>
32
35
  <%= button_to destroy_all_emails_path,
33
36
  class: "text-sm text-gray-600 hover:text-gray-900",
34
37
  form: { data: { turbo_confirm: "Are you sure you want to clear all emails?" } },
@@ -40,6 +43,8 @@
40
43
  Clear All
41
44
  </div>
42
45
  <% end %>
46
+ <% else %>
47
+ <div class="w-[88px]"></div> <!-- Placeholder to maintain spacing when button is hidden -->
43
48
  <% end %>
44
49
  </div>
45
50
  </div>
@@ -60,11 +65,9 @@
60
65
  </div>
61
66
 
62
67
  <!-- Main content -->
63
- <div class="flex-1 overflow-y-auto">
64
- <div class="p-8">
65
- <turbo-frame id="email_content">
66
- <%= yield %>
67
- </turbo-frame>
68
+ <div class="flex-1 overflow-y-auto lg:ml-0">
69
+ <div class="p-8 lg:p-8 sm:p-4">
70
+ <%= yield %>
68
71
  </div>
69
72
  </div>
70
73
  </div>
@@ -0,0 +1,28 @@
1
+ <div class="prose max-w-none break-words">
2
+ <input type="radio" name="email_tab" id="html_tab" class="hidden peer/html" <%= email.html? ? 'checked' : '' %> <%= email.html? ? '' : 'disabled' %> />
3
+ <label
4
+ for="html_tab"
5
+ class="px-4 py-2 text-sm font-medium cursor-pointer peer-checked/html:text-blue-600 peer-checked/html:border-blue-600 peer-checked/html:border-b-2 peer-disabled/html:text-gray-400 peer-disabled/html:cursor-not-allowed">
6
+ HTML
7
+ </label>
8
+
9
+ <input type="radio" name="email_tab" id="text_tab" class="hidden peer/text" <%= email.text? ? (!email.html? ? 'checked' : '') : 'disabled' %> />
10
+ <label
11
+ for="text_tab"
12
+ class="px-4 py-2 text-sm font-medium cursor-pointer peer-checked/text:text-blue-600 peer-checked/text:border-blue-600 peer-checked/text:border-b-2 peer-disabled/text:text-gray-400 peer-disabled/text:cursor-not-allowed">
13
+ Text
14
+ </label>
15
+
16
+ <div class="hidden peer-checked/html:block mt-3">
17
+ <% if email.html? %>
18
+ <%= sanitize(email.html_body,
19
+ tags: ActionView::Base.sanitized_allowed_tags + ['table', 'tbody', 'tr', 'td'],
20
+ attributes: ActionView::Base.sanitized_allowed_attributes + ['style']) %>
21
+ <% end %>
22
+ </div>
23
+ <div class="hidden peer-checked/text:block mt-3 bg-black text-white font-mono p-4 rounded">
24
+ <% if email.text? %>
25
+ <%= simple_format email.text_body %>
26
+ <% end %>
27
+ </div>
28
+ </div>
@@ -0,0 +1,19 @@
1
+ <turbo-stream action="remove" target="<%= dom_id(@email) %>">
2
+ </turbo-stream>
3
+
4
+ <% path_to_visit = if RailsMail::Email.count.zero?
5
+ root_path
6
+ else
7
+ email_path(@email.next_email)
8
+ end
9
+ %>
10
+
11
+ <turbo-stream action="update" target="email_content">
12
+ <template>
13
+ <div class="" data-controller="redirect" data-redirect-url-value="<%= path_to_visit %>">
14
+ <div class="flex justify-center items-center min-h-screen">
15
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
16
+ </div>
17
+ </div>
18
+ </template>
19
+ </turbo-stream>
@@ -4,6 +4,8 @@
4
4
  </turbo-stream>
5
5
  <turbo-stream action="update" target="email_content">
6
6
  <template>
7
- <%= render(partial: "rails_mail/emails/empty_state", formats: [ :html ]) %>
7
+ <div data-controller="redirect" data-redirect-url-value="<%= emails_path %>">
8
+ <%= render(partial: "rails_mail/emails/empty_state", formats: [ :html ]) %>
9
+ </div>
8
10
  </template>
9
11
  </turbo-stream>
@@ -1,7 +1,7 @@
1
1
  <turbo-stream action="update" target="email-sidebar">
2
2
  <template>
3
3
  <% @emails&.each do |email| %>
4
- <%= render partial: "rails_mail/shared/email", locals: {email: email}, formats: [:html] %>
4
+ <%= render partial: "rails_mail/shared/email", locals: {email: email, email_path: email_path(email) }, formats: [:html] %>
5
5
  <% end %>
6
6
  </template>
7
7
  </turbo-stream>
@@ -1,7 +1,8 @@
1
1
  <%= rails_mail_turbo_frame_tag "email_content" do %>
2
- <div class="bg-white shadow rounded-lg">
2
+ <div class="bg-white shadow rounded-lg" data-email-id="<%= @email.id %>"
3
+ data-email-highlight-current-id-value="<%= @email.id %>">
3
4
  <div class="px-6 py-4 border-b border-gray-200">
4
- <h1 class="text-2xl font-semibold text-gray-900"><%= @email.subject %></h1>
5
+ <h1 class="text-2xl font-semibold text-gray-900 break-words"><%= @email.subject %></h1>
5
6
  <div class="mt-2 text-sm text-gray-600">
6
7
  <div>From: <%= @email.from %></div>
7
8
  <div>To: <%= @email.to.join(", ") %></div>
@@ -35,8 +36,8 @@
35
36
  </div>
36
37
  <% end %>
37
38
 
38
- <div class="prose max-w-none">
39
- <%= simple_format @email.body %>
39
+ <div>
40
+ <%= render "email_tabs", email: @email %>
40
41
  </div>
41
42
  </div>
42
43
  </div>
@@ -2,24 +2,42 @@
2
2
  on_current_page = current_page?(RailsMail::Engine.routes.url_helpers.email_path(email))
3
3
  highlight_class = on_current_page ? 'bg-gray-300' : ''
4
4
  %>
5
- <%= link_to RailsMail::Engine.routes.url_helpers.email_path(email),
6
- class: "block px-3 py-2 rounded-md hover:bg-gray-200 #{highlight_class} active:bg-gray-300 focus:bg-gray-300",
5
+ <%= content_tag :div, id: dom_id(email), class: "group flex items-center px-3 py-2 hover:bg-gray-200 #{highlight_class} active:bg-gray-300 focus:bg-gray-300", data: {testid: "email-row"} do %>
6
+ <%= link_to RailsMail::Engine.routes.url_helpers.email_path(email),
7
+ class: "flex-1 min-w-0 rounded-md",
8
+ data: {
9
+ "turbo-frame": "email_content",
10
+ "turbo-action": "advance",
11
+ "turbo-prefetch": "false",
12
+ "email-highlight-target": "link",
13
+ "email-id": email.id,
14
+ action: "click->email-highlight#highlight"
15
+ } do %>
16
+ <div class="min-w-0">
17
+ <div class="text-sm font-medium text-gray-900 truncate"><%= email.subject %></div>
18
+ <div class="text-xs text-gray-500">
19
+ <span class="truncate"><%= email.from %></span>
20
+ <time
21
+ data-controller="timeago"
22
+ data-timeago-datetime-value="<%= email.created_at.iso8601 %>"
23
+ data-timeago-refresh-interval-value="1000"
24
+ data-timeago-include-seconds-value="true"
25
+ class="ml-2 text-gray-400"><%= time_ago_in_words(email.created_at) %> ago</time>
26
+ </div>
27
+ </div>
28
+ <% end %>
29
+
30
+ <%= button_to email_path,
31
+ method: :delete,
32
+ class: "hidden group-hover:block p-1 hover:bg-gray-300 rounded",
33
+ form: {
7
34
  data: {
8
- "turbo-frame": "email_content",
9
- "turbo-action": "advance",
10
- "turbo-prefetch": "false",
11
- "email-highlight-target": "link",
12
- "email-id": email.id,
13
- action: "click->email-highlight#highlight"
14
- } do %>
15
- <div class="text-sm font-medium text-gray-900 truncate"><%= email.subject %></div>
16
- <div class="text-xs text-gray-500">
17
- <span class="truncate"><%= email.from %></span>
18
- <time
19
- data-controller="timeago"
20
- data-timeago-datetime-value="<%= email.created_at.iso8601 %>"
21
- data-timeago-refresh-interval-value="1000"
22
- data-timeago-include-seconds-value="true"
23
- class="ml-2 text-gray-400"><%= time_ago_in_words(email.created_at) %> ago</time>
24
- </div>
35
+ turbo_confirm: "Are you sure you want to delete this email?",
36
+ turbo_frame: "_top"
37
+ },
38
+ } do %>
39
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-500 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor">
40
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
41
+ </svg>
42
+ <% end %>
25
43
  <% end %>
@@ -4,6 +4,6 @@
4
4
 
5
5
  <div id="email-sidebar" data-controller="email-highlight">
6
6
  <% @emails&.each do |email| %>
7
- <%= render "rails_mail/shared/email", email: email %>
7
+ <%= render "rails_mail/shared/email", email: email, email_path: email_path(email) %>
8
8
  <% end %>
9
9
  </div>
data/config/routes.rb CHANGED
@@ -4,7 +4,7 @@ RailsMail::Engine.routes.draw do
4
4
  get "static/:name", action: :static, as: :frontend_static, constraints: { format: %w[css js] }
5
5
  end
6
6
 
7
- resources :emails, only: [ :index, :show ] do
7
+ resources :emails, only: [ :index, :show, :destroy ] do
8
8
  collection do
9
9
  delete "emails/destroy_all", to: "emails#destroy_all", as: :destroy_all
10
10
  end
@@ -4,14 +4,14 @@ Rails.configuration.to_prepare do
4
4
  RailsMail.configure do |config|
5
5
  # Authentication setup
6
6
  # if left blank, authentication is skipped
7
- # config.authentication_callback do
7
+ # config.authenticate do
8
8
  # Example implementation for Authlogic:
9
9
  # user_session = UserSession.find
10
10
  # msg = Rails.env.development? ? 'Forbidden - make sure you have the correct permission in config/initializers/rails_mail.rb' : 'Not Found'
11
11
  # raise ActionController::RoutingError.new(msg) unless user_session&.user&.admin?
12
12
  # end
13
13
 
14
- config.show_clear_button do
14
+ config.show_clear_all_button do
15
15
  # show clear button in development
16
16
  # and prefer to trim in non-development environments
17
17
  # to prevent accidental deletion of emails
@@ -1,29 +1,29 @@
1
1
  module RailsMail
2
2
  class Configuration
3
- attr_accessor :authentication_callback, :show_clear_button,
3
+ attr_accessor :authentication_callback, :show_clear_all_button_callback,
4
4
  :trim_emails_older_than, :trim_emails_max_count,
5
5
  :enqueue_trim_job
6
6
 
7
7
  def initialize
8
8
  @authentication_callback = nil
9
- @show_clear_button = nil
9
+ @show_clear_all_button_callback = nil
10
10
  @trim_emails_older_than = nil
11
11
  @trim_emails_max_count = nil
12
12
  @enqueue_trim_job = ->(email) { RailsMail::TrimEmailsJob.perform_later }
13
13
  end
14
14
 
15
- def authentication_callback=(callback)
15
+ def authenticate(&callback)
16
16
  unless callback.nil? || callback.respond_to?(:call)
17
17
  raise ArgumentError, "authentication_callback must be nil or respond to #call"
18
18
  end
19
19
  @authentication_callback = callback
20
20
  end
21
21
 
22
- def show_clear_button=(callback)
22
+ def show_clear_all_button(&callback)
23
23
  unless callback.nil? || callback.respond_to?(:call)
24
- raise ArgumentError, "show_clear_button must be nil or respond to #call"
24
+ raise ArgumentError, "show_clear_all_button must be nil or respond to #call"
25
25
  end
26
- @show_clear_button = callback
26
+ @show_clear_all_button_callback = callback
27
27
  end
28
28
  end
29
29
  end
@@ -10,7 +10,8 @@ module RailsMail
10
10
  cc: mail.cc,
11
11
  bcc: mail.bcc,
12
12
  subject: mail.subject,
13
- body: mail.body.to_s,
13
+ text_part: extract_mail_body_by_type(mail, "text/plain"),
14
+ html_part: extract_mail_body_by_type(mail, "text/html"),
14
15
  content_type: mail.content_type,
15
16
  attachments: mail.attachments.map { |a| { filename: a.filename, content_type: a.content_type.split(";").first } }
16
17
  )
@@ -18,5 +19,16 @@ module RailsMail
18
19
  Rails.logger.error("Failed to deliver email: #{e.message}")
19
20
  raise e
20
21
  end
22
+
23
+ private
24
+
25
+ def extract_mail_body_by_type(mail, mime_type)
26
+ if mail.multipart?
27
+ part = mail.parts.find { |p| p.content_type.start_with?(mime_type) }
28
+ part ? part.body.as_json : nil
29
+ elsif mail.content_type.start_with?(mime_type)
30
+ mail.body.as_json
31
+ end
32
+ end
21
33
  end
22
34
  end
@@ -1,3 +1,3 @@
1
1
  module RailsMail
2
- VERSION = "0.9.3"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/rails_mail.rb CHANGED
@@ -17,24 +17,12 @@ module RailsMail
17
17
  @configuration = Configuration.new
18
18
  end
19
19
 
20
- def authentication_callback(&block)
21
- @authentication_callback = block if block
22
- @authentication_callback || ->(request) { true }
20
+ def authentication_callback
21
+ configuration.authentication_callback || ->(request) { true }
23
22
  end
24
23
 
25
- def show_clear_button?(&block)
26
- @show_clear_button = block if block
27
- @show_clear_button || ->(request) { true }
24
+ def show_clear_all_button_callback
25
+ configuration.show_clear_all_button || ->(request) { true }
28
26
  end
29
27
  end
30
-
31
- # class Configuration
32
- # def authentication_callback(&block)
33
- # RailsMail.authentication_callback(&block)
34
- # end
35
-
36
- # def show_clear_button(&block)
37
- # RailsMail.show_clear_button?(&block)
38
- # end
39
- # end
40
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_mail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Philips
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-09 00:00:00.000000000 Z
11
+ date: 2025-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -69,6 +69,7 @@ files:
69
69
  - app/frontend/rails_mail/modules/consumer.js
70
70
  - app/frontend/rails_mail/modules/email_highlight_controller.js
71
71
  - app/frontend/rails_mail/modules/emails_channel.js
72
+ - app/frontend/rails_mail/modules/redirect_controller.js
72
73
  - app/frontend/rails_mail/modules/timeago.js
73
74
  - app/frontend/rails_mail/style.css
74
75
  - app/frontend/rails_mail/vendor/action_cable.js
@@ -80,11 +81,14 @@ files:
80
81
  - app/helpers/rails_mail/turbo_helper.rb
81
82
  - app/jobs/rails_mail/trim_emails_job.rb
82
83
  - app/models/rails_mail/email.rb
84
+ - app/views/layouts/rails_mail/_head.html.erb
85
+ - app/views/layouts/rails_mail/_title.html.erb
83
86
  - app/views/layouts/rails_mail/application.html.erb
84
87
  - app/views/rails_mail/emails/_email.html.erb
88
+ - app/views/rails_mail/emails/_email_tabs.html.erb
85
89
  - app/views/rails_mail/emails/_empty_state.html.erb
86
90
  - app/views/rails_mail/emails/_form.html.erb
87
- - app/views/rails_mail/emails/_show.html.erb
91
+ - app/views/rails_mail/emails/destroy.turbo_stream.erb
88
92
  - app/views/rails_mail/emails/destroy_all.turbo_stream.erb
89
93
  - app/views/rails_mail/emails/edit.html.erb
90
94
  - app/views/rails_mail/emails/index.html.erb
@@ -126,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
130
  - !ruby/object:Gem::Version
127
131
  version: '0'
128
132
  requirements: []
129
- rubygems_version: 3.5.16
133
+ rubygems_version: 3.5.22
130
134
  signing_key:
131
135
  specification_version: 4
132
136
  summary: Database-backed Action Mailer delivery method with web interface
@@ -1,50 +0,0 @@
1
- <%= rails_mail_turbo_frame_tag "email_content" do %>
2
- <div class="bg-white shadow rounded-lg" data-email-id="<%= email.id %>"
3
- data-email-highlight-current-id-value="<%= email.id %>">
4
- <div class="px-6 py-4 border-b border-gray-200">
5
- <h1 class="text-2xl font-semibold text-gray-900"><%= email.subject %></h1>
6
- <div class="mt-2 text-sm text-gray-600">
7
- <div>From: <%= email.from %></div>
8
- <div>To: <%= email.to.join(", ") %></div>
9
- <% if email.cc.present? %>
10
- <div>CC: <%= email.cc.join(", ") %></div>
11
- <% end %>
12
- <% if email.bcc.present? %>
13
- <div>BCC: <%= email.bcc.join(", ") %></div>
14
- <% end %>
15
- <div class="text-gray-400 mt-1">
16
- <%= email.created_at.strftime("%B %d, %Y at %I:%M %p") %>
17
- </div>
18
- </div>
19
- </div>
20
-
21
- <div class="px-6 py-4">
22
- <% if email.attachments.present? %>
23
- <div class="mb-4 p-4 bg-gray-50 rounded-md">
24
- <h3 class="text-sm font-medium text-gray-900 mb-2">Attachments</h3>
25
- <div class="space-y-2">
26
- <% email.attachments.each do |attachment| %>
27
- <div class="flex items-center text-sm">
28
- <svg class="h-5 w-5 text-gray-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
29
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
30
- </svg>
31
- <span><%= attachment["filename"] %></span>
32
- <span class="ml-2 text-gray-500">(<%= attachment["content_type"] %>)</span>
33
- </div>
34
- <% end %>
35
- </div>
36
- </div>
37
- <% end %>
38
-
39
- <div class="prose max-w-none">
40
- <% if email.html? %>
41
- <%= sanitize(email.body,
42
- tags: ActionView::Base.sanitized_allowed_tags + ['table', 'tbody', 'tr', 'td'],
43
- attributes: ActionView::Base.sanitized_allowed_attributes + ['style']) %>
44
- <% else %>
45
- <%= simple_format email.body %>
46
- <% end %>
47
- </div>
48
- </div>
49
- </div>
50
- <% end %>