diff --git a/Gemfile b/Gemfile index 5826ff2..2ec0477 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,9 @@ gem 'sqlite3', '>= 1.4' gem 'solid_cache' gem 'solid_queue' +gem 'kaminari' +gem 'ransack' + # Use the Puma web server [https://github.com/puma/puma] gem 'puma', '>= 5.0' diff --git a/Gemfile.lock b/Gemfile.lock index 4f8f150..cef2e58 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -120,6 +120,18 @@ GEM jbuilder (2.13.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) + kaminari (1.2.2) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) + actionview + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) + activerecord + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) logger (1.6.6) loofah (2.24.0) crass (~> 1.0.2) @@ -212,6 +224,10 @@ GEM thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rake (13.2.1) + ransack (4.3.0) + activerecord (>= 6.1.5) + activesupport (>= 6.1.5) + i18n rdoc (6.12.0) psych (>= 4.0.0) regexp_parser (2.10.0) @@ -305,9 +321,11 @@ DEPENDENCIES debug importmap-rails jbuilder + kaminari puma (>= 5.0) rails (= 8.0.1) rails-i18n + ransack selenium-webdriver solid_cache solid_queue diff --git a/app/controllers/memos_controller.rb b/app/controllers/memos_controller.rb index 4c30c81..37117d1 100644 --- a/app/controllers/memos_controller.rb +++ b/app/controllers/memos_controller.rb @@ -2,13 +2,11 @@ class MemosController < ApplicationController before_action :set_memo, only: %i[edit update destroy] def index - page = (params[:page] || 0).to_i - @memos = Memo.search(params[:query]) - .order(created_at: :desc) - .limit(4) - .offset(page * 4) - - @has_next = Memo.search(params[:query]).count > (page + 1) * 4 + @q = Memo.ransack(params[:q]) + @memos = @q.result(distinct: true) + .order(created_at: :desc) + .page(params[:page]) + .per(4) respond_to do |format| format.html diff --git a/app/javascript/controllers/search_controller.js b/app/javascript/controllers/search_controller.js deleted file mode 100644 index 25ca422..0000000 --- a/app/javascript/controllers/search_controller.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Controller } from "@hotwired/stimulus"; - -export default class extends Controller { - submit() { - clearTimeout(this.timeout); - this.timeout = setTimeout(() => { - this.element.requestSubmit(); - }, 300); - } -} diff --git a/app/models/memo.rb b/app/models/memo.rb index d6125fd..901e1f0 100644 --- a/app/models/memo.rb +++ b/app/models/memo.rb @@ -1,11 +1,11 @@ class Memo < ApplicationRecord validates :content, presence: true - def self.search(query) - if query.present? - where('content LIKE ?', "%#{query}%").order(created_at: :desc) - else - order(created_at: :desc) - end + def self.ransackable_attributes(_auth_object = nil) + %w[content created_at id updated_at] + end + + def self.ransackable_associations(_auth_object = nil) + [] end end diff --git a/app/views/kaminari/_first_page.html.erb b/app/views/kaminari/_first_page.html.erb new file mode 100644 index 0000000..0cc83b9 --- /dev/null +++ b/app/views/kaminari/_first_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "First" page + - available local variables + url: url to the first page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +<span class="first"> + <%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote %> +</span> diff --git a/app/views/kaminari/_gap.html.erb b/app/views/kaminari/_gap.html.erb new file mode 100644 index 0000000..bbb0f98 --- /dev/null +++ b/app/views/kaminari/_gap.html.erb @@ -0,0 +1,8 @@ +<%# Non-link tag that stands for skipped pages... + - available local variables + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +<span class="page gap"><%= t('views.pagination.truncate').html_safe %></span> diff --git a/app/views/kaminari/_last_page.html.erb b/app/views/kaminari/_last_page.html.erb new file mode 100644 index 0000000..bc777b4 --- /dev/null +++ b/app/views/kaminari/_last_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Last" page + - available local variables + url: url to the last page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +<span class="last"> + <%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote %> +</span> diff --git a/app/views/kaminari/_next_page.html.erb b/app/views/kaminari/_next_page.html.erb new file mode 100644 index 0000000..ae9875b --- /dev/null +++ b/app/views/kaminari/_next_page.html.erb @@ -0,0 +1,3 @@ +<%= link_to_next_page @memos, "ζ°εεεπ€", + class: "inline-flex items-center px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors duration-200", + data: { turbo_frame: "memos" } %> diff --git a/app/views/kaminari/_page.html.erb b/app/views/kaminari/_page.html.erb new file mode 100644 index 0000000..393bfc4 --- /dev/null +++ b/app/views/kaminari/_page.html.erb @@ -0,0 +1,12 @@ +<%# Link showing page number + - available local variables + page: a page object for "this" page + url: url to this page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +<span class="page<%= ' current' if page.current? %>"> + <%= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel} %> +</span> diff --git a/app/views/kaminari/_paginator.html.erb b/app/views/kaminari/_paginator.html.erb new file mode 100644 index 0000000..ba19e2f --- /dev/null +++ b/app/views/kaminari/_paginator.html.erb @@ -0,0 +1,15 @@ +<%= paginator.render do %> + <nav class="flex justify-center space-x-2"> + <%= first_page_tag unless current_page.first? %> + <%= prev_page_tag unless current_page.first? %> + <% each_page do |page| %> + <% if page.display_tag? %> + <%= page_tag page %> + <% elsif !page.was_truncated? %> + <%= gap_tag %> + <% end %> + <% end %> + <%= next_page_tag unless current_page.last? %> + <%= last_page_tag unless current_page.last? %> + </nav> +<% end %> diff --git a/app/views/kaminari/_prev_page.html.erb b/app/views/kaminari/_prev_page.html.erb new file mode 100644 index 0000000..0f32af4 --- /dev/null +++ b/app/views/kaminari/_prev_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Previous" page + - available local variables + url: url to the previous page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +<span class="prev"> + <%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote %> +</span> diff --git a/app/views/memos/index.html.erb b/app/views/memos/index.html.erb index 586f9cc..43c2f4c 100644 --- a/app/views/memos/index.html.erb +++ b/app/views/memos/index.html.erb @@ -1,37 +1,30 @@ <div class="mb-8 mt-8"> <div class="flex items-center gap-4"> - <%= form_with url: memos_path, method: :get, - data: { turbo_frame: "search-results", controller: "search" } do |f| %> + <%= search_form_for @q, html: { data: { turbo_frame: "search-results", controller: "search" } } do |f| %> <div class="relative"> - <%= f.text_field :query, + <%= f.text_field :content_cont, class: "w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500", placeholder: "ζ€η΄’...", - data: { action: "input->search#submit" }, - value: params[:query] %> + data: { action: "input->search#submit" } %> </div> <% end %> <%= link_to new_memo_path, class: "bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200" do %> <span class="flex items-center"> - </svg> οΌ εΊειε§ </span> <% end %> </div> </div> -<div id="memos"> - <%= turbo_frame_tag "memos" do %> +<%= turbo_frame_tag "memos" do %> <div id="memos-container" class="flex flex-col gap-4"> <%= render @memos %> - <%= render "empty_results", query: params[:query] if @memos.empty? %> + <%= render "empty_results", query: @q&.content_cont if @memos.empty? %> </div> -<% end %> - <div id="load-more" class="text-center mt-8"> - <% if @has_next %> - <%= link_to "ζ°εεεπ€", - memos_path(page: (params[:page] || 0).to_i + 1, query: params[:query]), + + <div class="text-center mt-8"> + <%= link_to_next_page @memos, "ζ°εεεπ€", class: "inline-flex items-center px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors duration-200", data: { turbo_frame: "memos" } %> - <% end %> -</div> -</div> + </div> +<% end %> diff --git a/app/views/memos/index.turbo_stream.erb b/app/views/memos/index.turbo_stream.erb index e1105c2..9ddb0ce 100644 --- a/app/views/memos/index.turbo_stream.erb +++ b/app/views/memos/index.turbo_stream.erb @@ -1,11 +1,3 @@ <%= turbo_stream.append "memos-container" do %> <%= render @memos %> <% end %> -<div id="load-more" class="text-center mt-8"> - <% if @has_next %> - <%= link_to "ζ°εεεπ€", - memos_path(page: (params[:page] || 0).to_i + 1, query: params[:query]), - class: "inline-flex items-center px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors duration-200", - data: { turbo_frame: "memos" } %> - <% end %> -</div> diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb new file mode 100644 index 0000000..4ba6ee3 --- /dev/null +++ b/config/initializers/kaminari_config.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +Kaminari.configure do |config| + # config.default_per_page = 25 + # config.max_per_page = nil + # config.window = 4 + # config.outer_window = 0 + # config.left = 0 + # config.right = 0 + # config.page_method_name = :page + # config.param_name = :page + # config.max_pages = nil + # config.params_on_first_page = false +end