Skip to content

Commit 95efa95

Browse files
authored
Merge pull request #89 from amatsuda/search_result_styling_and_more
Search result styling and more
2 parents 7b2ba83 + be237ec commit 95efa95

File tree

8 files changed

+157
-110
lines changed

8 files changed

+157
-110
lines changed

app/controllers/messages_controller.rb

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
class MessagesController < ApplicationController
22
PER_PAGE = 50
33

4-
# GET /ruby-dev or /q=searchterm
5-
def index(list_name: nil, yyyymm: nil, q: nil, page: nil)
4+
# GET /ruby-dev
5+
def index(list_name: nil, yyyymm: nil, q: nil)
66
if list_name
77
@list = List.find_by_name list_name
88

9-
render_threads yyyymm: yyyymm
10-
elsif q
11-
search q, page
9+
render_threads yyyymm: yyyymm, q: q
10+
else
11+
redirect_to List.find_by_name('ruby-core')
12+
end
13+
end
1214

13-
render :search
15+
# GET /messages/search_all
16+
def search_all(q: nil, page: nil)
17+
if q
18+
search q, page
1419
else
1520
@messages = []
16-
17-
render :search
1821
end
1922
end
2023

@@ -59,38 +62,37 @@ def calculate_navigation_links
5962
@next_message_in_thread = thread_messages[current_index + 1] if current_index
6063
end
6164

62-
def render_threads(yyyymm: nil)
63-
@yyyymms = Message.where(list_id: @list).order('yyyymm').pluck(Arel.sql "distinct to_char(published_at, 'YYYYMM') as yyyymm")
64-
@yyyymm = yyyymm || @yyyymms.last
65+
def render_threads(yyyymm: nil, q: nil)
66+
root_query = Message.where(list_id: @list, parent_id: nil).order(:id)
67+
68+
if q
69+
root_query.where!('body %> ?', q)
70+
else
71+
@yyyymms = Message.where(list_id: @list, parent_id: nil).order('yyyymm').pluck(Arel.sql "distinct to_char(published_at, 'YYYYMM') as yyyymm")
72+
@yyyymm = yyyymm || @yyyymms.last
73+
root_query.where!("to_char(published_at, 'YYYYMM') = ?", @yyyymm)
74+
end
6575

66-
root_query = Message.where(list_id: @list, parent_id: nil).where("to_char(published_at, 'YYYYMM') = ?", @yyyymm).order(:id)
6776
messages = Message.with_recursive(parent_and_children: [root_query, Message.joins('inner join parent_and_children on messages.parent_id = parent_and_children.id')])
6877
.joins('inner join parent_and_children on parent_and_children.id = messages.id')
6978

7079
@messages = compose_tree(messages)
7180

72-
render :index
73-
end
74-
75-
def get_list_ids(params)
76-
list_ids = []
77-
['ruby-talk', 'ruby-core', 'ruby-list', 'ruby-dev'].each do |name|
78-
if params[name.tr('-', '_').to_sym] != '0'
79-
list_ids << List.find_by_name(name).id
80-
end
81+
if q
82+
@yyyymms = @messages.map { it.published_at.strftime('%Y%m') }.uniq
83+
@yyyymm = @yyyymms.last
8184
end
82-
list_ids
85+
86+
render :index
8387
end
8488

8589
def search(query, page)
86-
list_ids = get_list_ids(params)
87-
if list_ids.empty?
88-
raise "Need to select at least one list"
89-
end
90+
lists = List.all.select { params[it.name] != '0' }
91+
raise "Need to select at least one list" if lists.empty?
9092

9193
# %> and <-> are defined by pg_trgm.
9294
# https://www.postgresql.org/docs/17/pgtrgm.html
93-
message_where = Message.where('body %> ? AND list_id IN (?)', query, list_ids).order(Arel.sql('body <-> ?', query))
95+
message_where = Message.where('body %> ? AND list_id IN (?)', query, lists.map(&:id)).order(Arel.sql('body <-> ?', query))
9496
@messages = message_where.offset(page.to_i * PER_PAGE).limit(PER_PAGE)
9597
end
9698

app/models/list.rb

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,35 @@
11
class List
22
include ActiveModel::Model
33

4-
def initialize(name, id)
5-
@name = name
6-
@id = id
4+
def initialize(id, name, label)
5+
@id, @name, @label = id, name, label
6+
nil
77
end
88

9-
attr_reader :name, :id
9+
attr_reader :id, :name, :label
1010

1111
# Ordered by the established dates. ruby-list was started in 1995.
1212
LISTS = [
13-
List.new('ruby-list', 1),
14-
List.new('ruby-dev', 2),
15-
List.new('ruby-core', 3),
16-
List.new('ruby-talk', 4),
13+
List.new(1, 'ruby-list', 'ruby-list (For Ruby users, JA)'),
14+
List.new(2, 'ruby-dev', 'ruby-dev (For Ruby developers, JA)'),
15+
List.new(3, 'ruby-core', 'ruby-core (For Ruby developers, EN)'),
16+
List.new(4, 'ruby-talk', 'ruby-talk (For Ruby users, EN)')
1717
]
1818

1919
class << self
20-
def find_by_name(name)
21-
List::LISTS.find { |list| list.name == name }
22-
end
23-
2420
def find_by_id(id)
25-
List::LISTS.find { |list| list.id == id }
21+
List.all.detect { |list| list.id == id }
2622
end
2723

2824
alias find find_by_id
25+
26+
def find_by_name(name)
27+
List.all.detect { |list| list.name == name }
28+
end
29+
30+
def all
31+
List::LISTS
32+
end
2933
end
3034

3135
def to_param

app/views/layouts/application.html.erb

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,52 @@
2020

2121
<body class="bg-gray-50 dark:bg-gray-900">
2222
<header class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
23-
<div class="container mx-auto px-4 py-4">
24-
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
25-
<%= link_to @list&.name || 'blade.ruby-lang.org', @list || root_path, class: "hover:text-red-600 dark:hover:text-red-400 transition-colors" %>
26-
</h1>
27-
<p class="text-gray-600 dark:text-gray-400 mt-2">Mailing list archive</p>
23+
<div class="py-4">
24+
<div class="flex items-start gap-6">
25+
<div class="flex-shrink-0 pl-4">
26+
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4">
27+
<%= link_to @list&.name || yield(:title) || 'blade.ruby-lang.org', @list || root_path, class: 'hover:text-red-600 dark:hover:text-red-400 transition-colors' %>
28+
</h1>
29+
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-gray-50 dark:bg-gray-900">
30+
<h2 class="text-xs font-semibold text-gray-500 dark:text-gray-400 tracking-wide mb-2">Mailing Lists</h2>
31+
<div class="flex flex-col gap-1">
32+
<% List.all.each do |list| %>
33+
<%= link_to list, class: "flex items-center gap-2 px-2 py-1 rounded text-sm transition-colors #{@list&.id == list.id ? 'bg-red-600 dark:bg-red-500 text-white' : 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-red-600 dark:hover:text-red-400'}" do %>
34+
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
35+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
36+
</svg>
37+
<span><%= list.label %></span>
38+
<% end %>
39+
<% end %>
40+
</div>
41+
</div>
42+
</div>
43+
44+
<div class="flex-1 pr-4">
45+
<%= form_with method: :get, class: 'mb-3' do |f| %>
46+
<div class="flex gap-2 max-w-md">
47+
<%= f.text_field :q, value: params[:q], placeholder: "Search messages #{"within #{@list.name}" if @list}...", class: 'flex-1 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-red-500 dark:focus:ring-red-400' %>
48+
<%= f.submit 'Search', name: nil, class: 'px-4 py-2 text-sm font-medium rounded bg-red-600 dark:bg-red-500 text-white hover:bg-red-700 dark:hover:bg-red-600 transition-colors' %>
49+
</div>
50+
51+
<% if @list %>
52+
<%= render 'shared/yyyymm_selector' if @yyyymms.present? %>
53+
<% else %>
54+
<div class="mt-6 bg-white dark:bg-gray-800 rounded-lg shadow-md border border-gray-200 dark:border-gray-700 p-6 inline-block">
55+
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Select mailing lists from below to search from, or choose one from the left to navigate:</p>
56+
<div class="grid grid-cols-4 gap-2">
57+
<% List.all.each do |list| %>
58+
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 cursor-pointer whitespace-nowrap">
59+
<%= f.check_box list.name, {checked: params[list.name] != '0'} %>
60+
<span><%= list.name %></span>
61+
</label>
62+
<% end %>
63+
</div>
64+
</div>
65+
<% end %>
66+
<% end %>
67+
</div>
68+
</div>
2869
</div>
2970
</header>
3071

app/views/messages/index.html.erb

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,6 @@
66
</div>
77
<% end %>
88

9-
<div class="mb-4 border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-white dark:bg-gray-800">
10-
<%
11-
selected_year = @yyyymm&.[](0, 4)
12-
selected_month = @yyyymm&.[](4, 2)
13-
years_with_months = @yyyymms.group_by {|yyyymm| yyyymm[0, 4] }.sort.reverse
14-
%>
15-
16-
<!-- Year tabs -->
17-
<div class="flex gap-2 overflow-x-auto pb-2 mb-3 border-b border-gray-200 dark:border-gray-700">
18-
<% years_with_months.each do |year, months| %>
19-
<%= link_to year, [@list, yyyymm: "#{year}#{months.sort.first[4, 2]}"], class: "px-3 py-1 text-sm font-medium rounded whitespace-nowrap transition-colors #{selected_year == year ? 'bg-red-600 dark:bg-red-500 text-white' : 'bg-gray-100 dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100'}" %>
20-
<% end %>
21-
</div>
22-
23-
<!-- Month tabs -->
24-
<% if selected_year %>
25-
<div class="flex gap-1 flex-wrap">
26-
<% available_months = years_with_months.detect {|y, _| y == selected_year }&.last&.map {|yyyymm| yyyymm[4, 2] } || [] %>
27-
<% ('01'..'12').each do |month| %>
28-
<% if available_months.include?(month) %>
29-
<%= link_to month, [@list, yyyymm: "#{selected_year}#{month}"], class: "px-2 py-1 text-xs font-medium rounded transition-colors #{selected_month == month ? 'bg-red-600 dark:bg-red-500 text-white border-2 border-red-700 dark:border-red-400' : 'bg-gray-100 dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 border-2 border-transparent'}" %>
30-
<% else %>
31-
<span class="px-2 py-1 text-xs font-medium rounded text-gray-300 dark:text-gray-600 border-2 border-transparent"><%= month %></span>
32-
<% end %>
33-
<% end %>
34-
</div>
35-
<% end %>
36-
</div>
37-
389
<div class="grid grid-cols-4 gap-6" style="height: calc(100vh - 16rem);" data-controller="message-list">
3910
<div class="col-span-1 overflow-y-auto space-y-6">
4011
<%= render partial: 'thread', collection: @messages, as: :message, locals: {list: @list, depth: 0} %>

app/views/messages/search.html.erb

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<% content_for :title, 'Blade Archive Search' %>
2+
3+
<% if notice %>
4+
<div class="mb-4 p-4 bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-lg">
5+
<%= notice %>
6+
</div>
7+
<% end %>
8+
9+
<% if @messages.present? %>
10+
<div class="space-y-4">
11+
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">Search Results</h2>
12+
<% @messages.each do |message| %>
13+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md border border-gray-200 dark:border-gray-700 p-5 hover:shadow-lg transition-shadow">
14+
<div class="mb-2">
15+
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200">
16+
<%= message.list.name %>:<%= message.list_seq %>
17+
</span>
18+
</div>
19+
<h3 class="text-lg font-semibold mb-2">
20+
<%= link_to without_list_prefix(message.subject), [message.list, message], class: "text-gray-900 dark:text-gray-100 hover:text-red-600 dark:hover:text-red-400 transition-colors" %>
21+
</h3>
22+
<div class="text-sm text-gray-600 dark:text-gray-400 line-clamp-3">
23+
<%= search_snippet(message.body, params[:q]) %>
24+
</div>
25+
</div>
26+
<% end %>
27+
</div>
28+
<% elsif params[:q].present? %>
29+
<div class="text-center py-12 text-gray-500 dark:text-gray-400">
30+
<svg class="w-16 h-16 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
31+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
32+
</svg>
33+
<p>No results found for "<%= params[:q] %>"</p>
34+
</div>
35+
<% end %>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<%
2+
selected_year = @yyyymm&.[](0, 4)
3+
selected_month = @yyyymm&.[](4, 2)
4+
years_with_months = @yyyymms.group_by {|yyyymm| yyyymm[0, 4] }.sort.reverse
5+
%>
6+
7+
<div class="mt-6 border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-white dark:bg-gray-800">
8+
<!-- Year tabs -->
9+
<div class="flex gap-2 overflow-x-auto pb-2 mb-3 border-b border-gray-200 dark:border-gray-700">
10+
<% years_with_months.each do |year, months| %>
11+
<%= link_to year, [@list, yyyymm: "#{year}#{months.sort.first[4, 2]}"], class: "px-3 py-1 text-sm font-medium rounded whitespace-nowrap transition-colors #{selected_year == year ? 'bg-red-600 dark:bg-red-500 text-white' : 'bg-gray-100 dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100'}" %>
12+
<% end %>
13+
</div>
14+
15+
<!-- Month tabs -->
16+
<% if selected_year %>
17+
<div class="flex gap-1 flex-wrap">
18+
<% available_months = years_with_months.detect {|y, _| y == selected_year }&.last&.map {|yyyymm| yyyymm[4, 2] } || [] %>
19+
<% ('01'..'12').each do |month| %>
20+
<% if available_months.include?(month) %>
21+
<%= link_to month, [@list, yyyymm: "#{selected_year}#{month}"], class: "px-2 py-1 text-xs font-medium rounded transition-colors #{selected_month == month ? 'bg-red-600 dark:bg-red-500 text-white border-2 border-red-700 dark:border-red-400' : 'bg-gray-100 dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 border-2 border-transparent'}" %>
22+
<% else %>
23+
<span class="px-2 py-1 text-xs font-medium rounded text-gray-300 dark:text-gray-600 border-2 border-transparent"><%= month %></span>
24+
<% end %>
25+
<% end %>
26+
</div>
27+
<% end %>
28+
</div>

config/routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
end
66
get '/attachments/:encoded_key/*filename' => 'attachments#show', as: :attachment
77

8+
get '/messages/search_all', to: 'messages#search_all', as: :search_all_messages
9+
810
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
911

1012
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.

0 commit comments

Comments
 (0)