Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions app/controllers/messages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
class MessagesController < ApplicationController
PER_PAGE = 50

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

render_threads yyyymm: yyyymm
elsif q
search q, page
render_threads yyyymm: yyyymm, q: q
else
redirect_to List.find_by_name('ruby-core')
end
end

render :search
# GET /messages/search_all
def search_all(q: nil, page: nil)
if q
search q, page
else
@messages = []

render :search
end
end

Expand Down Expand Up @@ -59,38 +62,37 @@ def calculate_navigation_links
@next_message_in_thread = thread_messages[current_index + 1] if current_index
end

def render_threads(yyyymm: nil)
@yyyymms = Message.where(list_id: @list).order('yyyymm').pluck(Arel.sql "distinct to_char(published_at, 'YYYYMM') as yyyymm")
@yyyymm = yyyymm || @yyyymms.last
def render_threads(yyyymm: nil, q: nil)
root_query = Message.where(list_id: @list, parent_id: nil).order(:id)

if q
root_query.where!('body %> ?', q)
else
@yyyymms = Message.where(list_id: @list, parent_id: nil).order('yyyymm').pluck(Arel.sql "distinct to_char(published_at, 'YYYYMM') as yyyymm")
@yyyymm = yyyymm || @yyyymms.last
root_query.where!("to_char(published_at, 'YYYYMM') = ?", @yyyymm)
end

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

@messages = compose_tree(messages)

render :index
end

def get_list_ids(params)
list_ids = []
['ruby-talk', 'ruby-core', 'ruby-list', 'ruby-dev'].each do |name|
if params[name.tr('-', '_').to_sym] != '0'
list_ids << List.find_by_name(name).id
end
if q
@yyyymms = @messages.map { it.published_at.strftime('%Y%m') }.uniq
@yyyymm = @yyyymms.last
end
list_ids

render :index
end

def search(query, page)
list_ids = get_list_ids(params)
if list_ids.empty?
raise "Need to select at least one list"
end
lists = List.all.select { params[it.name] != '0' }
raise "Need to select at least one list" if lists.empty?

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

Expand Down
30 changes: 17 additions & 13 deletions app/models/list.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
class List
include ActiveModel::Model

def initialize(name, id)
@name = name
@id = id
def initialize(id, name, label)
@id, @name, @label = id, name, label
nil
end

attr_reader :name, :id
attr_reader :id, :name, :label

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

class << self
def find_by_name(name)
List::LISTS.find { |list| list.name == name }
end

def find_by_id(id)
List::LISTS.find { |list| list.id == id }
List.all.detect { |list| list.id == id }
end

alias find find_by_id

def find_by_name(name)
List.all.detect { |list| list.name == name }
end

def all
List::LISTS
end
end

def to_param
Expand Down
51 changes: 46 additions & 5 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,52 @@

<body class="bg-gray-50 dark:bg-gray-900">
<header class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div class="container mx-auto px-4 py-4">
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
<%= link_to @list&.name || 'blade.ruby-lang.org', @list || root_path, class: "hover:text-red-600 dark:hover:text-red-400 transition-colors" %>
</h1>
<p class="text-gray-600 dark:text-gray-400 mt-2">Mailing list archive</p>
<div class="py-4">
<div class="flex items-start gap-6">
<div class="flex-shrink-0 pl-4">
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4">
<%= 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' %>
</h1>
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-gray-50 dark:bg-gray-900">
<h2 class="text-xs font-semibold text-gray-500 dark:text-gray-400 tracking-wide mb-2">Mailing Lists</h2>
<div class="flex flex-col gap-1">
<% List.all.each do |list| %>
<%= 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 %>
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
</svg>
<span><%= list.label %></span>
<% end %>
<% end %>
</div>
</div>
</div>

<div class="flex-1 pr-4">
<%= form_with method: :get, class: 'mb-3' do |f| %>
<div class="flex gap-2 max-w-md">
<%= 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' %>
<%= 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' %>
</div>

<% if @list %>
<%= render 'shared/yyyymm_selector' if @yyyymms.present? %>
<% else %>
<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">
<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>
<div class="grid grid-cols-4 gap-2">
<% List.all.each do |list| %>
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 cursor-pointer whitespace-nowrap">
<%= f.check_box list.name, {checked: params[list.name] != '0'} %>
<span><%= list.name %></span>
</label>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
</header>

Expand Down
29 changes: 0 additions & 29 deletions app/views/messages/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,6 @@
</div>
<% end %>

<div class="mb-4 border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-white dark:bg-gray-800">
<%
selected_year = @yyyymm&.[](0, 4)
selected_month = @yyyymm&.[](4, 2)
years_with_months = @yyyymms.group_by {|yyyymm| yyyymm[0, 4] }.sort.reverse
%>

<!-- Year tabs -->
<div class="flex gap-2 overflow-x-auto pb-2 mb-3 border-b border-gray-200 dark:border-gray-700">
<% years_with_months.each do |year, months| %>
<%= 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'}" %>
<% end %>
</div>

<!-- Month tabs -->
<% if selected_year %>
<div class="flex gap-1 flex-wrap">
<% available_months = years_with_months.detect {|y, _| y == selected_year }&.last&.map {|yyyymm| yyyymm[4, 2] } || [] %>
<% ('01'..'12').each do |month| %>
<% if available_months.include?(month) %>
<%= 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'}" %>
<% else %>
<span class="px-2 py-1 text-xs font-medium rounded text-gray-300 dark:text-gray-600 border-2 border-transparent"><%= month %></span>
<% end %>
<% end %>
</div>
<% end %>
</div>

<div class="grid grid-cols-4 gap-6" style="height: calc(100vh - 16rem);" data-controller="message-list">
<div class="col-span-1 overflow-y-auto space-y-6">
<%= render partial: 'thread', collection: @messages, as: :message, locals: {list: @list, depth: 0} %>
Expand Down
36 changes: 0 additions & 36 deletions app/views/messages/search.html.erb

This file was deleted.

35 changes: 35 additions & 0 deletions app/views/messages/search_all.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<% content_for :title, 'Blade Archive Search' %>

<% if notice %>
<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">
<%= notice %>
</div>
<% end %>

<% if @messages.present? %>
<div class="space-y-4">
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">Search Results</h2>
<% @messages.each do |message| %>
<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">
<div class="mb-2">
<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">
<%= message.list.name %>:<%= message.list_seq %>
</span>
</div>
<h3 class="text-lg font-semibold mb-2">
<%= 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" %>
</h3>
<div class="text-sm text-gray-600 dark:text-gray-400 line-clamp-3">
<%= search_snippet(message.body, params[:q]) %>
</div>
</div>
<% end %>
</div>
<% elsif params[:q].present? %>
<div class="text-center py-12 text-gray-500 dark:text-gray-400">
<svg class="w-16 h-16 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
</svg>
<p>No results found for "<%= params[:q] %>"</p>
</div>
<% end %>
28 changes: 28 additions & 0 deletions app/views/shared/_yyyymm_selector.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<%
selected_year = @yyyymm&.[](0, 4)
selected_month = @yyyymm&.[](4, 2)
years_with_months = @yyyymms.group_by {|yyyymm| yyyymm[0, 4] }.sort.reverse
%>

<div class="mt-6 border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-white dark:bg-gray-800">
<!-- Year tabs -->
<div class="flex gap-2 overflow-x-auto pb-2 mb-3 border-b border-gray-200 dark:border-gray-700">
<% years_with_months.each do |year, months| %>
<%= 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'}" %>
<% end %>
</div>

<!-- Month tabs -->
<% if selected_year %>
<div class="flex gap-1 flex-wrap">
<% available_months = years_with_months.detect {|y, _| y == selected_year }&.last&.map {|yyyymm| yyyymm[4, 2] } || [] %>
<% ('01'..'12').each do |month| %>
<% if available_months.include?(month) %>
<%= 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'}" %>
<% else %>
<span class="px-2 py-1 text-xs font-medium rounded text-gray-300 dark:text-gray-600 border-2 border-transparent"><%= month %></span>
<% end %>
<% end %>
</div>
<% end %>
</div>
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
end
get '/attachments/:encoded_key/*filename' => 'attachments#show', as: :attachment

get '/messages/search_all', to: 'messages#search_all', as: :search_all_messages

# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

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