pagy 5.7.5 → 8.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +745 -0
  4. data/apps/demo.ru +435 -0
  5. data/apps/rails.ru +213 -0
  6. data/apps/repro.ru +177 -0
  7. data/apps/tmp/calendar.sqlite3 +0 -0
  8. data/apps/tmp/calendar.sqlite3-shm +0 -0
  9. data/apps/tmp/calendar.sqlite3-wal +0 -0
  10. data/apps/tmp/local_secret.txt +1 -0
  11. data/bin/pagy +100 -0
  12. data/{lib/config → config}/pagy.rb +72 -96
  13. data/javascripts/pagy-module.js +100 -0
  14. data/javascripts/pagy.d.ts +5 -0
  15. data/javascripts/pagy.js +4 -0
  16. data/javascripts/pagy.min.js +4 -0
  17. data/javascripts/pagy.min.js.map +10 -0
  18. data/javascripts/pagy.mjs +100 -0
  19. data/lib/optimist.rb +1022 -0
  20. data/lib/pagy/backend.rb +16 -4
  21. data/lib/pagy/calendar/day.rb +22 -10
  22. data/lib/pagy/calendar/month.rb +35 -9
  23. data/lib/pagy/calendar/quarter.rb +36 -10
  24. data/lib/pagy/calendar/unit.rb +106 -0
  25. data/lib/pagy/calendar/week.rb +16 -16
  26. data/lib/pagy/calendar/year.rb +15 -9
  27. data/lib/pagy/calendar.rb +60 -68
  28. data/lib/pagy/console.rb +3 -3
  29. data/lib/pagy/countless.rb +17 -12
  30. data/lib/pagy/exceptions.rb +1 -1
  31. data/lib/pagy/extras/arel.rb +2 -2
  32. data/lib/pagy/extras/array.rb +2 -2
  33. data/lib/pagy/extras/bootstrap.rb +57 -53
  34. data/lib/pagy/extras/bulma.rb +53 -57
  35. data/lib/pagy/extras/calendar.rb +54 -37
  36. data/lib/pagy/extras/countless.rb +3 -3
  37. data/lib/pagy/extras/elasticsearch_rails.rb +12 -11
  38. data/lib/pagy/extras/foundation.rb +59 -54
  39. data/lib/pagy/extras/gearbox.rb +32 -19
  40. data/lib/pagy/extras/headers.rb +11 -11
  41. data/lib/pagy/extras/i18n.rb +5 -5
  42. data/lib/pagy/extras/items.rb +34 -24
  43. data/lib/pagy/extras/{frontend_helpers.rb → js_tools.rb} +24 -24
  44. data/lib/pagy/extras/jsonapi.rb +79 -0
  45. data/lib/pagy/extras/materialize.rb +63 -47
  46. data/lib/pagy/extras/meilisearch.rb +26 -22
  47. data/lib/pagy/extras/metadata.rb +13 -9
  48. data/lib/pagy/extras/overflow.rb +13 -10
  49. data/lib/pagy/extras/pagy.rb +82 -0
  50. data/lib/pagy/extras/searchkick.rb +12 -11
  51. data/lib/pagy/extras/semantic.rb +57 -45
  52. data/lib/pagy/extras/size.rb +40 -0
  53. data/lib/pagy/extras/standalone.rb +12 -16
  54. data/lib/pagy/extras/trim.rb +13 -13
  55. data/lib/pagy/extras/uikit.rb +60 -46
  56. data/lib/pagy/frontend.rb +62 -45
  57. data/lib/pagy/i18n.rb +4 -3
  58. data/lib/pagy/url_helpers.rb +13 -25
  59. data/lib/pagy.rb +61 -59
  60. data/locales/ar.yml +28 -0
  61. data/locales/be.yml +25 -0
  62. data/locales/bg.yml +21 -0
  63. data/locales/bs.yml +25 -0
  64. data/locales/ca.yml +21 -0
  65. data/locales/ckb.yml +18 -0
  66. data/locales/cs.yml +23 -0
  67. data/locales/da.yml +21 -0
  68. data/locales/de.yml +21 -0
  69. data/locales/en.yml +21 -0
  70. data/locales/es.yml +21 -0
  71. data/locales/fr.yml +21 -0
  72. data/locales/hr.yml +25 -0
  73. data/locales/id.yml +19 -0
  74. data/locales/it.yml +21 -0
  75. data/locales/ja.yml +19 -0
  76. data/locales/km.yml +19 -0
  77. data/locales/ko.yml +17 -0
  78. data/locales/nb.yml +21 -0
  79. data/locales/nl.yml +21 -0
  80. data/locales/nn.yml +21 -0
  81. data/locales/pl.yml +25 -0
  82. data/locales/pt-BR.yml +21 -0
  83. data/locales/pt.yml +21 -0
  84. data/locales/ru.yml +25 -0
  85. data/locales/sr.yml +25 -0
  86. data/locales/sv-SE.yml +21 -0
  87. data/locales/sv.yml +21 -0
  88. data/locales/sw.yml +23 -0
  89. data/locales/ta.yml +23 -0
  90. data/locales/tr.yml +19 -0
  91. data/locales/uk.yml +25 -0
  92. data/locales/vi.yml +17 -0
  93. data/locales/zh-CN.yml +19 -0
  94. data/locales/zh-HK.yml +19 -0
  95. data/locales/zh-TW.yml +19 -0
  96. data/stylesheets/pagy.css +46 -0
  97. data/stylesheets/pagy.scss +48 -0
  98. data/stylesheets/pagy.tailwind.css +21 -0
  99. metadata +79 -66
  100. data/lib/javascripts/pagy-dev.js +0 -118
  101. data/lib/javascripts/pagy-module.d.ts +0 -30
  102. data/lib/javascripts/pagy-module.js +0 -117
  103. data/lib/javascripts/pagy.js +0 -1
  104. data/lib/locales/ar.yml +0 -26
  105. data/lib/locales/bg.yml +0 -22
  106. data/lib/locales/bs.yml +0 -24
  107. data/lib/locales/ca.yml +0 -22
  108. data/lib/locales/cs.yml +0 -22
  109. data/lib/locales/da.yml +0 -22
  110. data/lib/locales/de.yml +0 -22
  111. data/lib/locales/en.yml +0 -22
  112. data/lib/locales/es.yml +0 -22
  113. data/lib/locales/fr.yml +0 -22
  114. data/lib/locales/hr.yml +0 -24
  115. data/lib/locales/id.yml +0 -20
  116. data/lib/locales/it.yml +0 -22
  117. data/lib/locales/ja.yml +0 -20
  118. data/lib/locales/km.yml +0 -19
  119. data/lib/locales/ko.yml +0 -20
  120. data/lib/locales/nb.yml +0 -22
  121. data/lib/locales/nl.yml +0 -22
  122. data/lib/locales/pl.yml +0 -24
  123. data/lib/locales/pt-BR.yml +0 -22
  124. data/lib/locales/pt.yml +0 -22
  125. data/lib/locales/ru.yml +0 -24
  126. data/lib/locales/sr.yml +0 -23
  127. data/lib/locales/sv-SE.yml +0 -23
  128. data/lib/locales/sv.yml +0 -23
  129. data/lib/locales/sw.yml +0 -22
  130. data/lib/locales/ta.yml +0 -22
  131. data/lib/locales/tr.yml +0 -20
  132. data/lib/locales/uk.yml +0 -24
  133. data/lib/locales/zh-CN.yml +0 -20
  134. data/lib/locales/zh-HK.yml +0 -20
  135. data/lib/locales/zh-TW.yml +0 -20
  136. data/lib/pagy/calendar/month_mixin.rb +0 -49
  137. data/lib/pagy/extras/navs.rb +0 -63
  138. data/lib/pagy/extras/support.rb +0 -54
  139. data/lib/templates/bootstrap_nav.html.erb +0 -24
  140. data/lib/templates/bootstrap_nav.html.haml +0 -34
  141. data/lib/templates/bootstrap_nav.html.slim +0 -34
  142. data/lib/templates/bulma_nav.html.erb +0 -24
  143. data/lib/templates/bulma_nav.html.haml +0 -32
  144. data/lib/templates/bulma_nav.html.slim +0 -32
  145. data/lib/templates/foundation_nav.html.erb +0 -24
  146. data/lib/templates/foundation_nav.html.haml +0 -34
  147. data/lib/templates/foundation_nav.html.slim +0 -34
  148. data/lib/templates/nav.html.erb +0 -22
  149. data/lib/templates/nav.html.haml +0 -30
  150. data/lib/templates/nav.html.slim +0 -29
  151. data/lib/templates/uikit_nav.html.erb +0 -15
  152. data/lib/templates/uikit_nav.html.haml +0 -28
  153. data/lib/templates/uikit_nav.html.slim +0 -28
@@ -0,0 +1,40 @@
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/size
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy # :nodoc:
5
+ # Implement the legacy bar using the array size.
6
+ # Unless you have very specific requirements, use the faster and better looking default bar.
7
+ module SizeExtra
8
+ # Setup @items based on the :gearbox_items variable
9
+ def series(size: @vars[:size], **_)
10
+ return super unless size.is_a?(Array)
11
+ return [] if size == []
12
+ raise VariableError.new(self, :size, 'to be an Array of 4 Integers or []', size) \
13
+ unless size.is_a?(Array) && size.size == 4 && size.all? { |num| !num.negative? rescue false } # rubocop:disable Style/RescueModifier
14
+
15
+ [].tap do |series|
16
+ # This algorithm is up to ~5x faster and ~2.3x lighter than the previous one (pagy < 4.3)
17
+ # However the behavior of the legacy nav bar was taken straight from WillPaginate and Kaminari:
18
+ # it's ill-concieved and complicates the experience of devs and users.
19
+ left_gap_start = 1 + size[0]
20
+ left_gap_end = @page - size[1] - 1
21
+ right_gap_start = @page + size[2] + 1
22
+ right_gap_end = @last - size[3]
23
+ left_gap_end = right_gap_end if left_gap_end > right_gap_end
24
+ right_gap_start = left_gap_start if left_gap_start > right_gap_start
25
+ start = 1
26
+ if (left_gap_end - left_gap_start).positive?
27
+ series.push(*start...left_gap_start, :gap)
28
+ start = left_gap_end + 1
29
+ end
30
+ if (right_gap_end - right_gap_start).positive?
31
+ series.push(*start...right_gap_start, :gap)
32
+ start = right_gap_end + 1
33
+ end
34
+ series.push(*start..@last)
35
+ series[series.index(@page)] = @page.to_s
36
+ end
37
+ end
38
+ end
39
+ prepend SizeExtra
40
+ end
@@ -1,4 +1,4 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/standalone
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/standalone
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'uri'
@@ -8,6 +8,7 @@ class Pagy # :nodoc:
8
8
  # even in the irb/rails console without any app or config.
9
9
  module StandaloneExtra
10
10
  # Extracted from Rack::Utils and reformatted for rubocop
11
+ # :nocov:
11
12
  module QueryUtils
12
13
  module_function
13
14
 
@@ -24,36 +25,31 @@ class Pagy # :nodoc:
24
25
  build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
25
26
  end.delete_if(&:empty?).join('&')
26
27
  when nil
27
- prefix
28
+ escape(prefix)
28
29
  else
29
30
  raise ArgumentError, 'value must be a Hash' if prefix.nil?
30
31
 
31
- "#{prefix}=#{escape(value)}"
32
+ "#{escape(prefix)}=#{escape(value)}"
32
33
  end
33
34
  end
34
35
  end
36
+ # :nocov:
35
37
 
36
38
  # Return the URL for the page. If there is no pagy.vars[:url]
37
39
  # it works exactly as the regular #pagy_url_for, relying on the params method and Rack.
38
40
  # If there is a defined pagy.vars[:url] variable it does not need the params method nor Rack.
39
- def pagy_url_for(pagy, page, absolute: nil)
41
+ def pagy_url_for(pagy, page, absolute: false, **_)
40
42
  return super unless pagy.vars[:url]
41
43
 
42
- vars = pagy.vars
43
- page_param = vars[:page_param].to_s
44
- items_param = vars[:items_param].to_s
45
- params = pagy.params.is_a?(Hash) ? pagy.params.clone : {} # safe when it gets reused
46
- params[page_param] = page
47
- params[items_param] = vars[:items] if vars[:items_extra]
48
- query_string = "?#{QueryUtils.build_nested_query(pagy_deprecated_params(pagy, params))}" # remove in 6.0
49
- # params = pagy.params.call(params) if pagy.params.is_a?(Proc) # add in 6.0
50
- # query_string = "?#{Rack::Utils.build_nested_query(params)}" # add in 6.0
44
+ vars = pagy.vars
45
+ params = vars[:params].is_a?(Hash) ? vars[:params].clone : {} # safe when it gets reused
46
+ pagy_set_query_params(page, vars, params)
47
+ params = vars[:params].(params) if vars[:params].is_a?(Proc)
48
+ query_string = "?#{QueryUtils.build_nested_query(params)}"
51
49
  "#{vars[:url]}#{query_string}#{vars[:fragment]}"
52
50
  end
53
51
  end
54
- # In ruby 3+ `UrlHelpers.prepend StandaloneExtra` would be enough instead of using the next 2 lines
55
- Frontend.prepend StandaloneExtra
56
- Backend.prepend StandaloneExtra
52
+ UrlHelpers.prepend StandaloneExtra
57
53
 
58
54
  # Define a dummy params method if it's not already defined in the including module
59
55
  module Backend
@@ -1,4 +1,4 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/trim
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/trim
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class Pagy # :nodoc:
@@ -6,23 +6,23 @@ class Pagy # :nodoc:
6
6
 
7
7
  # Remove the page=1 param from the first page link
8
8
  module TrimExtra
9
- # Override the original pagy_link_proc.
10
- # Call the pagy_trim method if the trim_extra is enabled.
11
- def pagy_link_proc(pagy, link_extra: '')
12
- link_proc = super(pagy, link_extra: link_extra)
13
- return link_proc unless pagy.vars[:trim_extra]
9
+ # Override the original pagy_a_proc.
10
+ # Call the pagy_trim method for page 1 if the trim_extra is enabled
11
+ def pagy_anchor(pagy)
12
+ a_proc = super
13
+ return a_proc unless pagy.vars[:trim_extra]
14
14
 
15
- lambda do |page, text = pagy.label_for(page), extra = ''|
16
- link = +link_proc.call(page, text, extra)
17
- return link unless page == 1
15
+ lambda do |page, text = pagy.label_for(page), **opts|
16
+ a = +a_proc.(page, text, **opts)
17
+ return a unless page.to_s == '1'
18
18
 
19
- pagy_trim(pagy, link)
19
+ pagy_trim(pagy, a) # in method for isolated testing
20
20
  end
21
21
  end
22
22
 
23
- # Remove the the :page_param param from the first page link
24
- def pagy_trim(pagy, link)
25
- link.sub!(/[?&]#{pagy.vars[:page_param]}=1\b(?!&)|\b#{pagy.vars[:page_param]}=1&/, '')
23
+ # Remove the the :page_param param from the first page anchor
24
+ def pagy_trim(pagy, a)
25
+ a.sub!(/[?&]#{pagy.vars[:page_param]}=1\b(?!&)|\b#{pagy.vars[:page_param]}=1&/, '')
26
26
  end
27
27
  end
28
28
  Frontend.prepend TrimExtra
@@ -1,82 +1,96 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/uikit
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'pagy/extras/frontend_helpers'
3
+ warn '[PAGY WARNING] The uikit extra has been discontinued and it will be removed in v9 ' \
4
+ '(https://github.com/ddnexus/pagy/discussions/672#discussioncomment-9212328)'
5
+
6
+ require_relative 'js_tools'
5
7
 
6
8
  class Pagy # :nodoc:
7
9
  # Frontend modules are specially optimized for performance.
8
10
  # The resulting code may not look very elegant, but produces the best benchmarks
9
11
  module UikitExtra
10
12
  # Pagination for uikit: it returns the html with the series of links to the pages
11
- def pagy_uikit_nav(pagy, pagy_id: nil, link_extra: '', **vars)
12
- p_id = %( id="#{pagy_id}") if pagy_id
13
- link = pagy_link_proc(pagy, link_extra: link_extra)
13
+ def pagy_uikit_nav(pagy, id: nil, aria_label: nil, **vars)
14
+ id = %( id="#{id}") if id
15
+ a = pagy_anchor(pagy)
14
16
 
15
- html = +%(<ul#{p_id} class="pagy-uikit-nav uk-pagination uk-flex-center">#{pagy_uikit_prev_html pagy, link})
17
+ html = %(<ul#{id} class="pagy-uikit nav uk-pagination uk-flex-center" role="navigation" #{
18
+ nav_aria_label(pagy, aria_label:)}>#{
19
+ uikit_prev_html(pagy, a)})
16
20
  pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
21
  html << case item
18
- when Integer then %(<li>#{link.call item}</li>)
19
- when String then %(<li class="uk-active"><span>#{pagy.label_for(item)}</span></li>)
20
- when :gap then %(<li class="uk-disabled"><span>#{pagy_t 'pagy.nav.gap'}</span></li>)
21
- else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
22
+ when Integer
23
+ %(<li>#{a.(item)}</li>)
24
+ when String
25
+ %(<li class="uk-active"><span role="link" aria-current="page" aria-disabled="true">#{
26
+ pagy.label_for(item)}</span></li>)
27
+ when :gap
28
+ %(<li class="uk-disabled"><span>#{pagy_t 'pagy.gap'}</span></li>)
29
+ else
30
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
22
31
  end
23
32
  end
24
- html << pagy_uikit_next_html(pagy, link)
25
- html << %(</ul>)
33
+ html << %(#{uikit_next_html(pagy, a)}</ul>)
26
34
  end
27
35
 
28
- # Javascript pagination for uikit: it returns a nav and a JSON tag used by the pagy.js file
29
- def pagy_uikit_nav_js(pagy, pagy_id: nil, link_extra: '', **vars)
36
+ # Javascript pagination for uikit: it returns a nav with a data-pagy attribute used by the pagy.js file
37
+ def pagy_uikit_nav_js(pagy, id: nil, aria_label: nil, **vars)
30
38
  sequels = pagy.sequels(**vars)
31
- p_id = %( id="#{pagy_id}") if pagy_id
32
- link = pagy_link_proc(pagy, link_extra: link_extra)
33
- tags = { 'before' => pagy_uikit_prev_html(pagy, link),
34
- 'link' => %(<li>#{link.call(PAGE_PLACEHOLDER, LABEL_PLACEHOLDER)}</li>),
35
- 'active' => %(<li class="uk-active"><span>#{LABEL_PLACEHOLDER}</span></li>),
36
- 'gap' => %(<li class="uk-disabled"><span>#{pagy_t 'pagy.nav.gap'}</span></li>),
37
- 'after' => pagy_uikit_next_html(pagy, link) }
39
+ id = %( id="#{id}") if id
40
+ a = pagy_anchor(pagy)
41
+ tokens = { 'before' => uikit_prev_html(pagy, a),
42
+ 'a' => %(<li>#{a.(PAGE_TOKEN, LABEL_TOKEN)}</li>),
43
+ 'current' => %(<li class="uk-active"><span role="link" aria-current="page" aria-disabled="true">#{
44
+ LABEL_TOKEN}</span></li>),
45
+ 'gap' => %(<li class="uk-disabled"><span>#{pagy_t 'pagy.gap'}</span></li>),
46
+ 'after' => uikit_next_html(pagy, a) }
38
47
 
39
- %(<ul#{p_id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-uikit-nav-js uk-pagination uk-flex-center" #{
40
- pagy_json_attr(pagy, :nav, tags, sequels, pagy.label_sequels(sequels))}></ul>)
48
+ %(<ul#{id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-uikit nav-js uk-pagination uk-flex-center" role="navigation" #{
49
+ nav_aria_label(pagy, aria_label:)} #{
50
+ pagy_data(pagy, :nav, tokens, sequels, pagy.label_sequels(sequels))
51
+ }></ul>)
41
52
  end
42
53
 
43
- # Javascript combo pagination for uikit: it returns a nav and a JSON tag used by the pagy.js file
44
- def pagy_uikit_combo_nav_js(pagy, pagy_id: nil, link_extra: '')
45
- p_id = %( id="#{pagy_id}") if pagy_id
46
- link = pagy_link_proc(pagy, link_extra: link_extra)
47
- p_page = pagy.page
48
- p_pages = pagy.pages
49
- input = %(<input type="number" min="1" max="#{p_pages}" value="#{
50
- p_page}" style="text-align: center; width: #{p_pages.to_s.length + 1}rem;">)
54
+ # Javascript combo pagination for uikit: it returns a nav with a data-pagy attribute used by the pagy.js file
55
+ def pagy_uikit_combo_nav_js(pagy, id: nil, aria_label: nil)
56
+ id = %( id="#{id}") if id
57
+ a = pagy_anchor(pagy)
58
+ pages = pagy.pages
59
+
60
+ page_input = %(<input name="page" type="number" min="1" max="#{pages}" value="#{pagy.page}" aria-current="page" ) <<
61
+ %(style="text-align: center; width: #{pages.to_s.length + 1}rem;">#{JSTools::A_TAG})
51
62
 
52
- %(<ul#{p_id} class="pagy-uikit-combo-nav-js uk-button-group uk-pagination uk-flex-center" #{
53
- pagy_json_attr pagy, :combo, pagy_marked_link(link)
63
+ %(<ul#{id} class="pagy-uikit combo-nav-js uk-button-group uk-pagination uk-flex-center" role="navigation" #{
64
+ nav_aria_label(pagy, aria_label:)} #{
65
+ pagy_data(pagy, :combo, pagy_url_for(pagy, PAGE_TOKEN))
54
66
  }>#{
55
- pagy_uikit_prev_html pagy, link
56
- }<li>#{
57
- pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages
58
- }</li>#{
59
- pagy_uikit_next_html pagy, link
67
+ uikit_prev_html(pagy, a)
68
+ }<li><label>#{
69
+ pagy_t('pagy.combo_nav_js', page_input:, pages:)
70
+ }</label></li>#{
71
+ uikit_next_html(pagy, a)
60
72
  }</ul>)
61
73
  end
62
74
 
63
75
  private
64
76
 
65
- def pagy_uikit_prev_html(pagy, link)
66
- previous_span = %(<span uk-pagination-previous>#{pagy_t 'pagy.nav.prev'}</span>)
77
+ def uikit_prev_html(pagy, a)
78
+ span = %(<span uk-pagination-previous></span>)
67
79
  if (p_prev = pagy.prev)
68
- %(<li>#{link.call p_prev, previous_span}</li>)
80
+ %(<li>#{a.(p_prev, span, aria_label: pagy_t('pagy.aria_label.prev'))}</li>)
69
81
  else
70
- %(<li class="uk-disabled"><a href="#">#{previous_span}</a></li>)
82
+ %(<li class="uk-disabled"><a role="link" aria-disabled="true" aria-label="#{
83
+ pagy_t('pagy.aria_label.prev')}">#{span}</a></li>)
71
84
  end
72
85
  end
73
86
 
74
- def pagy_uikit_next_html(pagy, link)
75
- next_span = %(<span uk-pagination-next>#{pagy_t 'pagy.nav.next'}</span>)
87
+ def uikit_next_html(pagy, a)
88
+ span = %(<span uk-pagination-next></span>)
76
89
  if (p_next = pagy.next)
77
- %(<li>#{link.call p_next, next_span}</li>)
90
+ %(<li>#{a.(p_next, span, aria_label: pagy_t('pagy.aria_label.prev'))}</li>)
78
91
  else
79
- %(<li class="uk-disabled"><a href="#">#{next_span}</a></li>)
92
+ %(<li class="uk-disabled"><a role="link" aria-disabled="true" aria-label="#{
93
+ pagy_t('pagy.aria_label.next')}">#{span}</a></li>)
80
94
  end
81
95
  end
82
96
  end
data/lib/pagy/frontend.rb CHANGED
@@ -1,13 +1,13 @@
1
- # See Pagy::Frontend API documentation: https://ddnexus.github.io/pagy/api/frontend
1
+ # See Pagy::Frontend API documentation: https://ddnexus.github.io/pagy/docs/api/frontend
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'pagy/url_helpers'
5
- require 'pagy/i18n'
4
+ require_relative 'url_helpers'
5
+ require_relative 'i18n'
6
6
 
7
7
  class Pagy
8
8
  # Used for search and replace, hardcoded also in the pagy.js file
9
- PAGE_PLACEHOLDER = '__pagy_page__'
10
- LABEL_PLACEHOLDER = '__pagy_label__'
9
+ PAGE_TOKEN = '__pagy_page__'
10
+ LABEL_TOKEN = '__pagy_label__'
11
11
 
12
12
  # Frontend modules are specially optimized for performance.
13
13
  # The resulting code may not look very elegant, but produces the best benchmarks
@@ -15,62 +15,56 @@ class Pagy
15
15
  include UrlHelpers
16
16
 
17
17
  # Generic pagination: it returns the html with the series of links to the pages
18
- def pagy_nav(pagy, pagy_id: nil, link_extra: '', **vars)
19
- p_id = %( id="#{pagy_id}") if pagy_id
20
- link = pagy_link_proc(pagy, link_extra: link_extra)
21
- p_prev = pagy.prev
22
- p_next = pagy.next
18
+ def pagy_nav(pagy, id: nil, aria_label: nil, **vars)
19
+ id = %( id="#{id}") if id
20
+ a = pagy_anchor(pagy)
23
21
 
24
- html = +%(<nav#{p_id} class="pagy-nav pagination" aria-label="pager">)
25
- html << if p_prev
26
- %(<span class="page prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</span> )
27
- else
28
- %(<span class="page prev disabled">#{pagy_t('pagy.nav.prev')}</span> )
29
- end
22
+ html = %(<nav#{id} class="pagy nav" #{nav_aria_label(pagy, aria_label:)}>#{
23
+ prev_a(pagy, a)})
30
24
  pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
31
25
  html << case item
32
- when Integer then %(<span class="page">#{link.call item}</span> )
33
- when String then %(<span class="page active">#{pagy.label_for(item)}</span> )
34
- when :gap then %(<span class="page gap">#{pagy_t('pagy.nav.gap')}</span> )
35
- else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
26
+ when Integer
27
+ a.(item)
28
+ when String
29
+ %(<a role="link" aria-disabled="true" aria-current="page" class="current">#{pagy.label_for(item)}</a>)
30
+ when :gap
31
+ %(<a role="link" aria-disabled="true" class="gap">#{pagy_t('pagy.gap')}</a>)
32
+ else
33
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
36
34
  end
37
35
  end
38
- html << if p_next
39
- %(<span class="page next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</span>)
40
- else
41
- %(<span class="page next disabled">#{pagy_t('pagy.nav.next')}</span>)
42
- end
43
- html << %(</nav>)
36
+ html << %(#{next_a(pagy, a)}</nav>)
44
37
  end
45
38
 
46
39
  # Return examples: "Displaying items 41-60 of 324 in total" or "Displaying Products 41-60 of 324 in total"
47
- def pagy_info(pagy, pagy_id: nil, item_name: nil, i18n_key: nil)
48
- p_id = %( id="#{pagy_id}") if pagy_id
40
+ def pagy_info(pagy, id: nil, item_name: nil)
41
+ id = %( id="#{id}") if id
49
42
  p_count = pagy.count
50
- key = if p_count.zero? then 'pagy.info.no_items'
51
- elsif pagy.pages == 1 then 'pagy.info.single_page'
52
- else 'pagy.info.multiple_pages' # rubocop:disable Lint/ElseLayout
43
+ key = if p_count.zero?
44
+ 'pagy.info.no_items'
45
+ elsif pagy.pages == 1
46
+ 'pagy.info.single_page'
47
+ else
48
+ 'pagy.info.multiple_pages'
53
49
  end
54
50
 
55
- %(<span#{p_id} class="pagy-info">#{
56
- pagy_t key, item_name: item_name || pagy_t(i18n_key || pagy.vars[:i18n_key], count: p_count),
51
+ %(<span#{id} class="pagy info">#{
52
+ pagy_t key, item_name: item_name || pagy_t('pagy.item_name', count: p_count),
57
53
  count: p_count, from: pagy.from, to: pagy.to
58
54
  }</span>)
59
55
  end
60
56
 
61
- # Return a performance optimized proc to generate the HTML links
57
+ # Return a performance optimized lambda to generate the HTML anchor element (a tag)
62
58
  # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
63
- def pagy_link_proc(pagy, link_extra: '')
64
- p_prev = pagy.prev
65
- p_next = pagy.next
66
- left, right = %(<a href="#{pagy_url_for pagy, PAGE_PLACEHOLDER}" #{
67
- pagy.vars[:link_extra]} #{link_extra}).split(PAGE_PLACEHOLDER, 2)
68
- lambda do |page, text = pagy.label_for(page), extra_attrs = ''|
69
- %(#{left}#{page}#{right}#{ case page
70
- when p_prev then ' rel="prev"'
71
- when p_next then ' rel="next"'
72
- else ''
73
- end } #{extra_attrs}>#{text}</a>)
59
+ def pagy_anchor(pagy)
60
+ a_string = pagy.vars[:anchor_string]
61
+ a_string = %( #{a_string}) if a_string
62
+ left, right = %(<a#{a_string} href="#{pagy_url_for(pagy, PAGE_TOKEN)}").split(PAGE_TOKEN, 2)
63
+ # lambda used by all the helpers
64
+ lambda do |page, text = pagy.label_for(page), classes: nil, aria_label: nil|
65
+ classes = %( class="#{classes}") if classes
66
+ aria_label = %( aria-label="#{aria_label}") if aria_label
67
+ %(#{left}#{page}#{right}#{classes}#{aria_label}>#{text}</a>)
74
68
  end
75
69
  end
76
70
 
@@ -79,5 +73,28 @@ class Pagy
79
73
  def pagy_t(key, opts = {})
80
74
  Pagy::I18n.translate(@pagy_locale ||= nil, key, opts)
81
75
  end
76
+
77
+ private
78
+
79
+ def nav_aria_label(pagy, aria_label: nil)
80
+ aria_label ||= pagy_t('pagy.aria_label.nav', count: pagy.pages)
81
+ %(aria-label="#{aria_label}")
82
+ end
83
+
84
+ def prev_a(pagy, a, text: pagy_t('pagy.prev'), aria_label: pagy_t('pagy.aria_label.prev'))
85
+ if (p_prev = pagy.prev)
86
+ a.(p_prev, text, aria_label:)
87
+ else
88
+ %(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
89
+ end
90
+ end
91
+
92
+ def next_a(pagy, a, text: pagy_t('pagy.next'), aria_label: pagy_t('pagy.aria_label.next'))
93
+ if (p_next = pagy.next)
94
+ a.(p_next, text, aria_label:)
95
+ else
96
+ %(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
97
+ end
98
+ end
82
99
  end
83
100
  end
data/lib/pagy/i18n.rb CHANGED
@@ -1,4 +1,4 @@
1
- # See Pagy::I18n API documentation https://ddnexus.github.io/pagy/api/i18n
1
+ # See Pagy::I18n API documentation https://ddnexus.github.io/pagy/docs/api/i18n
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'yaml'
@@ -94,7 +94,9 @@ class Pagy
94
94
  # the :one_other RULE is the default for locales missing from this list
95
95
  LOCALE = Hash.new(RULE[:one_other]).tap do |hash|
96
96
  hash['ar'] = RULE[:arabic]
97
+ hash['be'] = RULE[:east_slavic]
97
98
  hash['bs'] = RULE[:east_slavic]
99
+ hash['ckb'] = RULE[:other]
98
100
  hash['cs'] = RULE[:west_slavic]
99
101
  hash['id'] = RULE[:other]
100
102
  hash['fr'] = RULE[:one_upto_two_other]
@@ -105,10 +107,9 @@ class Pagy
105
107
  hash['pl'] = RULE[:polish]
106
108
  hash['ru'] = RULE[:east_slavic]
107
109
  hash['sr'] = RULE[:east_slavic]
108
- hash['sv'] = RULE[:one_two_other]
109
- hash['sv-SE'] = RULE[:one_two_other]
110
110
  hash['tr'] = RULE[:other]
111
111
  hash['uk'] = RULE[:east_slavic]
112
+ hash['vi'] = RULE[:other]
112
113
  hash['zh-CN'] = RULE[:other]
113
114
  hash['zh-HK'] = RULE[:other]
114
115
  hash['zh-TW'] = RULE[:other]
@@ -6,33 +6,21 @@ class Pagy
6
6
  # Return the URL for the page, relying on the params method and Rack by default.
7
7
  # It supports all Rack-based frameworks (Sinatra, Padrino, Rails, ...).
8
8
  # For non-rack environments you can use the standalone extra
9
- def pagy_url_for(pagy, page, absolute: nil)
10
- vars = pagy.vars
11
- page_param = vars[:page_param].to_s
12
- items_param = vars[:items_param].to_s
13
- params = pagy.params.is_a?(Hash) ? pagy.params.transform_keys(&:to_s) : {}
14
- params = request.GET.merge(params)
15
- params[page_param] = page
16
- params[items_param] = vars[:items] if vars[:items_extra]
17
- query_string = "?#{Rack::Utils.build_nested_query(pagy_deprecated_params(pagy, params))}" # remove in 6.0
18
- # params = pagy.params.call(params) if pagy.params.is_a?(Proc) # add in 6.0
19
- # query_string = "?#{Rack::Utils.build_nested_query(params)}" # add in 6.0
20
- "#{request.base_url if absolute}#{request.path}#{query_string}#{vars[:fragment]}"
9
+ def pagy_url_for(pagy, page, absolute: false, **_)
10
+ vars = pagy.vars
11
+ query_params = request.GET.clone
12
+ query_params.merge!(vars[:params].transform_keys(&:to_s)) if vars[:params].is_a?(Hash)
13
+ pagy_set_query_params(page, vars, query_params)
14
+ query_params = vars[:params].(query_params) if vars[:params].is_a?(Proc)
15
+ query_string = "?#{Rack::Utils.build_nested_query(query_params)}"
16
+ "#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{vars[:fragment]}"
21
17
  end
22
18
 
23
- private
24
-
25
- # Transitional code to handle params deprecations. It will be removed in version 6.0
26
- def pagy_deprecated_params(pagy, params) # remove in 6.0
27
- if pagy.params.is_a?(Proc) # new code
28
- pagy.params.call(params)
29
- elsif respond_to?(:pagy_massage_params) # deprecated code
30
- Warning.warn '[PAGY WARNING] The pagy_massage_params method has been deprecated and it will be ignored from version 6. ' \
31
- 'Set the :params variable to a Proc with the same code as the pagy_massage_params method.'
32
- pagy_massage_params(params)
33
- else
34
- params # no massage params
35
- end
19
+ # Add the page and items params
20
+ # Overridable by the jsonapi extra
21
+ def pagy_set_query_params(page, vars, query_params)
22
+ query_params[vars[:page_param].to_s] = page
23
+ query_params[vars[:items_param].to_s] = vars[:items] if vars[:items_extra]
36
24
  end
37
25
  end
38
26
  end