diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 60345eb..17d90ae 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -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 @@ -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 diff --git a/app/models/list.rb b/app/models/list.rb index 31cbcc8..f64e263 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -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 diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 42cc57d..61459ce 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -20,11 +20,52 @@
-
-

- <%= link_to @list&.name || 'blade.ruby-lang.org', @list || root_path, class: "hover:text-red-600 dark:hover:text-red-400 transition-colors" %> -

-

Mailing list archive

+
+
+
+

+ <%= 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' %> +

+
+

Mailing Lists

+
+ <% 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 %> + + + + <%= list.label %> + <% end %> + <% end %> +
+
+
+ +
+ <%= form_with method: :get, class: 'mb-3' do |f| %> +
+ <%= 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' %> +
+ + <% if @list %> + <%= render 'shared/yyyymm_selector' if @yyyymms.present? %> + <% else %> +
+

Select mailing lists from below to search from, or choose one from the left to navigate:

+
+ <% List.all.each do |list| %> + + <% end %> +
+
+ <% end %> + <% end %> +
+
diff --git a/app/views/messages/index.html.erb b/app/views/messages/index.html.erb index e97cd38..48e6f08 100644 --- a/app/views/messages/index.html.erb +++ b/app/views/messages/index.html.erb @@ -6,35 +6,6 @@ <% end %> -
- <% - selected_year = @yyyymm&.[](0, 4) - selected_month = @yyyymm&.[](4, 2) - years_with_months = @yyyymms.group_by {|yyyymm| yyyymm[0, 4] }.sort.reverse - %> - - -
- <% 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 %> -
- - - <% if selected_year %> -
- <% 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 %> - <%= month %> - <% end %> - <% end %> -
- <% end %> -
-
<%= render partial: 'thread', collection: @messages, as: :message, locals: {list: @list, depth: 0} %> diff --git a/app/views/messages/search.html.erb b/app/views/messages/search.html.erb deleted file mode 100644 index e9a8089..0000000 --- a/app/views/messages/search.html.erb +++ /dev/null @@ -1,36 +0,0 @@ -

<%= notice %>

- -

blade.ruby-lang.org

- - - -<% @messages.each do |message| %> -
-

- <%= message.list.name %>:<%= message.list_seq %> - <%= link_to without_list_prefix(message.subject), [message.list, message] %> -

-
<%= search_snippet(message.body, params[:q]) %>
-
-<% end %> diff --git a/app/views/messages/search_all.html.erb b/app/views/messages/search_all.html.erb new file mode 100644 index 0000000..86bcbfd --- /dev/null +++ b/app/views/messages/search_all.html.erb @@ -0,0 +1,35 @@ +<% content_for :title, 'Blade Archive Search' %> + +<% if notice %> +
+ <%= notice %> +
+<% end %> + +<% if @messages.present? %> +
+

Search Results

+ <% @messages.each do |message| %> +
+
+ + <%= message.list.name %>:<%= message.list_seq %> + +
+

+ <%= 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" %> +

+
+ <%= search_snippet(message.body, params[:q]) %> +
+
+ <% end %> +
+<% elsif params[:q].present? %> +
+ + + +

No results found for "<%= params[:q] %>"

+
+<% end %> diff --git a/app/views/shared/_yyyymm_selector.html.erb b/app/views/shared/_yyyymm_selector.html.erb new file mode 100644 index 0000000..f98d391 --- /dev/null +++ b/app/views/shared/_yyyymm_selector.html.erb @@ -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 +%> + +
+ +
+ <% 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 %> +
+ + + <% if selected_year %> +
+ <% 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 %> + <%= month %> + <% end %> + <% end %> +
+ <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index 309f23c..62a4d95 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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.