diff --git a/app/controllers/emails_controller.rb b/app/controllers/emails_controller.rb index 55484de..b960f6b 100644 --- a/app/controllers/emails_controller.rb +++ b/app/controllers/emails_controller.rb @@ -1,6 +1,12 @@ class EmailsController < ApplicationController before_action :set_email, only: [:show, :mark_as_read, :body_preview] + def index + @emails = Email.where(mailbox_id: params[:mailbox_id]) + @emails = @emails.search(params[:q]) if params[:q].present? + render(layout: false) + end + def show @selected = params[:selected] @email.mark_as_read! unless @email.read? diff --git a/app/javascript/controllers/search_controller.js b/app/javascript/controllers/search_controller.js new file mode 100644 index 0000000..1ac5d68 --- /dev/null +++ b/app/javascript/controllers/search_controller.js @@ -0,0 +1,47 @@ +import { Controller } from "stimulus" + +export default class extends Controller { + static targets = [ "query", "output" ] + static values = { url: String } + + connect() { + this.requests = []; + } + + search() { + this.resetTimeout(); + this.cancelPreviousRequests(); + this.searchTimeout = setTimeout(() => { + + let abortController = new AbortController(); + this.requests.push(abortController) + + let signal = abortController.signal; + let url = this.urlValue + "&q=" + this.queryTarget.value; + + fetch(url, { signal }) + .then((response) => { + return response.text(); + }) + .then((html) => { + this.outputTarget.innerHTML = html; + }) + .catch(e => { + console.log(e) + }) + }, 75) + } + + resetTimeout() { + if(this.searchTimeout) { + clearTimeout(this.searchTimeout); + } + } + + cancelPreviousRequests() { + this.requests.forEach(request => { + request.abort(); + }); + this.requests = []; + } +} diff --git a/app/models/email.rb b/app/models/email.rb index 4e37377..1b88563 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -9,6 +9,10 @@ class Email < ApplicationRecord validates :rendered_html, presence: true validates :rendered_plain_text, presence: true + def self.search(query) + Search::EmailSearch.new.search(self, query) + end + def self.unread where(read_at: nil) end diff --git a/app/services/search/email_search.rb b/app/services/search/email_search.rb new file mode 100644 index 0000000..15d3958 --- /dev/null +++ b/app/services/search/email_search.rb @@ -0,0 +1,32 @@ +module Search + class EmailSearch + def call(collection, search_query) + collection.where(where_statement, + search_query_parts: search_query_parts(search_query)) + end + + alias search call + + private + + def search_query_parts(search_query) + return [] if search_query.blank? + search_query.split(/\s+/) + .map { |query| "%#{query}%" } + end + + def where_statement + <<~SQL + request_payload->'personalizations'->>personalization_id + ILIKE + ANY(ARRAY[:search_query_parts]) + OR + subject ILIKE ANY(ARRAY[:search_query_parts]) + OR + template_id ILIKE ANY(ARRAY[:search_query_parts]) + OR + rendered_plain_text ILIKE ANY(ARRAY[:search_query_parts]) + SQL + end + end +end diff --git a/app/views/emails/index.html.erb b/app/views/emails/index.html.erb new file mode 100644 index 0000000..c3517a6 --- /dev/null +++ b/app/views/emails/index.html.erb @@ -0,0 +1,18 @@ +<% if @emails.any? %> +
+ + From + + To + Subject + Received At +
+ <% @emails.each do |email| %> + <%= render(EmailListEntryComponent.new(email: email)) %> + <% end %> +<% else %> +
+

¯\_(ツ)_/¯

+

We couldn't find any emails.

+
+<% end %> diff --git a/app/views/mailboxes/show.html.erb b/app/views/mailboxes/show.html.erb index dfd074c..2d54b27 100644 --- a/app/views/mailboxes/show.html.erb +++ b/app/views/mailboxes/show.html.erb @@ -1,56 +1,68 @@ -
-
-
-

<%= @mailbox.name %> (<%= @mailbox.unread_count %>)

-
-

- <%= link_to '🛈'.html_safe, - details_mailbox_path(@mailbox), - class: "button", - data: { action: 'click->remote-content#load'} %> +

+
+
+
+

<%= @mailbox.name %> (<%= @mailbox.unread_count %>)

+
+

+ <%= link_to '🛈'.html_safe, + details_mailbox_path(@mailbox), + class: "button", + data: { action: 'click->remote-content#load'} %> +

+
+
+
+ <% if @emails.any? %> +

+ <%= link_to 'Mark all as read', + mark_all_as_read_mailbox_path(@mailbox), + class: 'button button--primary', + method: :post, + 'data-confirm': 'Are you sure you want to mark all emails as read?' %> + <%= link_to 'Clear mailbox', + clear_mailbox_mailbox_path(@mailbox), + class: 'button button--danger', + method: :post, + 'data-confirm': 'Are you sure you want to delete all emails?' %>

-
-
-
+ <% end %> +
+
+
+ +
+
<% if @emails.any? %> -

- <%= link_to 'Mark all as read', - mark_all_as_read_mailbox_path(@mailbox), - class: 'button button--primary', - method: :post, - 'data-confirm': 'Are you sure you want to mark all emails as read?' %> - <%= link_to 'Clear mailbox', - clear_mailbox_mailbox_path(@mailbox), - class: 'button button--danger', - method: :post, - 'data-confirm': 'Are you sure you want to delete all emails?' %> -

- <% end %> - - - -<% if @emails.any? %> -
- - - - -
- <% @emails.each do |email| %> - <%= render(EmailListEntryComponent.new(email: email)) %> - <% end %> -<% else %> -
-

¯\_(ツ)_/¯

-

You don't have any emails yet.

-

Use this api token to send emails with sendgrid

-
- -
- Copy + -
+ <% @emails.each do |email| %> + <%= render(EmailListEntryComponent.new(email: email)) %> + <% end %> + <% else %> +
+

¯\_(ツ)_/¯

+

You don't have any emails yet.

+

Use this api token to send emails with sendgrid

+
+ +
+ Copy +
+
+
+ <% end %>
-<% end %> +
diff --git a/config/routes.rb b/config/routes.rb index e557e7e..ada67e7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,7 +7,7 @@ Rails.application.routes.draw do post 'clear_mailbox', to: 'mailboxes#clear_mailbox' end end - resources :emails, only: [:show] do + resources :emails, only: [:index, :show] do member do get '/preview', to: 'emails#body_preview' end