]> git.openstreetmap.org Git - rails.git/blob - app/helpers/numbered_pagination_helper.rb
Merge pull request #5932 from tomhughes/frozen-strings
[rails.git] / app / helpers / numbered_pagination_helper.rb
1 # frozen_string_literal: true
2
3 module NumberedPaginationHelper
4   def numbered_pagination(top_page, active_id, active_page: top_page + 1, window_half_size: 50, step_size: 50, &)
5     lists = []
6
7     if top_page <= 5
8       lists << tag.ul(:class => "pagination pagination-sm mt-1") do
9         (1..top_page).each do |page|
10           concat numbered_pagination_item(page, **yield(page), :active_id => (active_id if page == active_page))
11         end
12       end
13     else
14       start_list_pages = 1..(active_page < 3 ? active_page + 1 : 1)
15       end_list_pages = (active_page > top_page - 2 ? active_page - 1 : top_page)..top_page
16
17       middle_list_page_sentinels = [start_list_pages.last, end_list_pages.first]
18       middle_list_page_steps = Range.new(*middle_list_page_sentinels).filter { |page| (page % step_size).zero? }
19       middle_list_page_window = Range.new([active_page - window_half_size, start_list_pages.last].max,
20                                           [active_page + window_half_size, end_list_pages.first].min).to_a
21       middle_list_pages_with_sentinels = (middle_list_page_sentinels | middle_list_page_steps | middle_list_page_window).sort
22       middle_list_pages_with_sentinels_and_gaps = [middle_list_pages_with_sentinels.first] +
23                                                   middle_list_pages_with_sentinels.each_cons(2).flat_map do |previous_page, page|
24                                                     page == previous_page + 1 ? [page] : [:gap, page]
25                                                   end
26       middle_list_pages_with_small_gaps_filled = middle_list_pages_with_sentinels_and_gaps.each_cons(3).map do |previous_page, page, next_page|
27         if page == :gap && previous_page != :gap && next_page != :gap && next_page - previous_page == 2
28           previous_page + 1
29         else
30           page
31         end
32       end
33       middle_list_pages = middle_list_pages_with_small_gaps_filled
34
35       lists << tag.ul(:class => "pagination pagination-sm mt-1") do
36         start_list_pages.each do |page|
37           concat numbered_pagination_item(page, **yield(page), :active_id => (active_id if page == active_page),
38                                                                :edge => [false, page == start_list_pages.last],
39                                                                :edge_border => true)
40         end
41       end
42       lists << tag.ul(:class => [
43                         "pagination pagination-sm",
44                         "overflow-x-auto pb-3", # horizontal scrollbar with reserved space below
45                         "pt-1 px-1 mx-n1", # space reserved for focus outlines
46                         "position-relative" # required for centering when clicking current page links ("Version #n" on pages showing element versions)
47                       ]) do
48         middle_list_pages.each_with_index do |page, i|
49           edge = [i.zero?, i == middle_list_pages.length - 1]
50           if page == :gap
51             concat numbered_pagination_item("...", :edge => edge)
52           else
53             concat numbered_pagination_item(page, **yield(page), :active_id => (active_id if page == active_page),
54                                                                  :edge => edge)
55           end
56         end
57       end
58       lists << tag.ul(:class => "pagination pagination-sm mt-1") do
59         end_list_pages.each do |page|
60           concat numbered_pagination_item(page, **yield(page), :active_id => (active_id if page == active_page),
61                                                                :edge => [page == end_list_pages.first, false],
62                                                                :edge_border => true)
63         end
64       end
65     end
66
67     tag.div safe_join(lists), :class => "numbered_pagination d-flex align-items-start"
68   end
69
70   private
71
72   def numbered_pagination_item(body, href: nil, active_id: nil, edge: [false, false], edge_border: false, **link_options)
73     link_class = ["page-link", { "rounded-start-0" => edge.first,
74                                  "border-start-0" => edge.first && !edge_border,
75                                  "rounded-end-0" => edge.last,
76                                  "border-end-0" => edge.last && !edge_border }]
77     link = if href
78              link_to body, href, :class => link_class, "aria-current" => ("page" if active_id), **link_options
79            else
80              tag.span body, :class => link_class
81            end
82     tag.li link, :id => active_id, :class => ["page-item", { "disabled" => !href, "active" => !!active_id }]
83   end
84 end