Implement email search
This commit is contained in:
parent
7bd547141b
commit
c644ddbe8d
@ -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?
|
||||
|
47
app/javascript/controllers/search_controller.js
Normal file
47
app/javascript/controllers/search_controller.js
Normal file
@ -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 = [];
|
||||
}
|
||||
}
|
@ -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
|
||||
|
32
app/services/search/email_search.rb
Normal file
32
app/services/search/email_search.rb
Normal file
@ -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
|
18
app/views/emails/index.html.erb
Normal file
18
app/views/emails/index.html.erb
Normal file
@ -0,0 +1,18 @@
|
||||
<% if @emails.any? %>
|
||||
<div class="email email--no-chrome">
|
||||
<b class="email__from">
|
||||
From
|
||||
</b>
|
||||
<b class="email__to">To</b>
|
||||
<b class="email__subject">Subject</b>
|
||||
<b class="email__created-at">Received At</b>
|
||||
</div>
|
||||
<% @emails.each do |email| %>
|
||||
<%= render(EmailListEntryComponent.new(email: email)) %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="text-center">
|
||||
<p>¯\_(ツ)_/¯</p>
|
||||
<p>We couldn't find any emails.</p>
|
||||
</div>
|
||||
<% end %>
|
@ -1,4 +1,5 @@
|
||||
<section>
|
||||
<div data-controller="search" data-search-url-value="<%= emails_path(mailbox_id: @mailbox.id) %>">
|
||||
<section>
|
||||
<header class="header header--with-border">
|
||||
<div class="header--justified-header">
|
||||
<h1><%= @mailbox.name %> (<%= @mailbox.unread_count %>)</h1>
|
||||
@ -27,9 +28,18 @@
|
||||
</p>
|
||||
<% end %>
|
||||
</header>
|
||||
</section>
|
||||
|
||||
<% if @emails.any? %>
|
||||
</section>
|
||||
<div class="form-group">
|
||||
<input class="form-control"
|
||||
data-search-target="query"
|
||||
data-action="keyup->search#search"
|
||||
placeholder="Search..."
|
||||
type="text"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div data-search-target="output">
|
||||
<% if @emails.any? %>
|
||||
<div class="email email--no-chrome">
|
||||
<b class="email__from">
|
||||
From
|
||||
@ -41,7 +51,7 @@
|
||||
<% @emails.each do |email| %>
|
||||
<%= render(EmailListEntryComponent.new(email: email)) %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% else %>
|
||||
<div class="text-center">
|
||||
<p>¯\_(ツ)_/¯</p>
|
||||
<p>You don't have any emails yet.</p>
|
||||
@ -53,4 +63,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user