pagy 6.5.0 → 7.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/config/pagy.rb +18 -16
  4. data/lib/javascripts/pagy-dev.js +10 -11
  5. data/lib/javascripts/pagy-module.js +9 -10
  6. data/lib/javascripts/pagy.js +1 -1
  7. data/lib/locales/ar.yml +14 -10
  8. data/lib/locales/be.yml +11 -10
  9. data/lib/locales/bg.yml +9 -10
  10. data/lib/locales/bs.yml +11 -10
  11. data/lib/locales/ca.yml +11 -10
  12. data/lib/locales/ckb.yml +8 -10
  13. data/lib/locales/cs.yml +13 -12
  14. data/lib/locales/da.yml +11 -10
  15. data/lib/locales/de.yml +9 -10
  16. data/lib/locales/en.yml +9 -10
  17. data/lib/locales/es.yml +9 -10
  18. data/lib/locales/fr.yml +9 -10
  19. data/lib/locales/hr.yml +11 -10
  20. data/lib/locales/id.yml +9 -10
  21. data/lib/locales/it.yml +9 -10
  22. data/lib/locales/ja.yml +9 -10
  23. data/lib/locales/km.yml +9 -9
  24. data/lib/locales/ko.yml +9 -10
  25. data/lib/locales/nb.yml +9 -10
  26. data/lib/locales/nl.yml +9 -10
  27. data/lib/locales/nn.yml +9 -10
  28. data/lib/locales/pl.yml +11 -10
  29. data/lib/locales/pt-BR.yml +9 -10
  30. data/lib/locales/pt.yml +9 -10
  31. data/lib/locales/ru.yml +13 -10
  32. data/lib/locales/sr.yml +11 -9
  33. data/lib/locales/sv-SE.yml +10 -12
  34. data/lib/locales/sv.yml +10 -12
  35. data/lib/locales/sw.yml +11 -10
  36. data/lib/locales/ta.yml +11 -10
  37. data/lib/locales/tr.yml +9 -10
  38. data/lib/locales/uk.yml +11 -10
  39. data/lib/locales/vi.yml +7 -10
  40. data/lib/locales/zh-CN.yml +9 -10
  41. data/lib/locales/zh-HK.yml +9 -10
  42. data/lib/locales/zh-TW.yml +9 -10
  43. data/lib/pagy/backend.rb +3 -2
  44. data/lib/pagy/calendar/day.rb +3 -2
  45. data/lib/pagy/calendar/helper.rb +3 -7
  46. data/lib/pagy/calendar/month.rb +3 -2
  47. data/lib/pagy/calendar/quarter.rb +3 -2
  48. data/lib/pagy/calendar/year.rb +2 -1
  49. data/lib/pagy/extras/bootstrap.rb +45 -30
  50. data/lib/pagy/extras/bulma.rb +44 -33
  51. data/lib/pagy/extras/calendar.rb +7 -8
  52. data/lib/pagy/extras/elasticsearch_rails.rb +6 -6
  53. data/lib/pagy/extras/foundation.rb +46 -33
  54. data/lib/pagy/extras/frontend_helpers.rb +8 -8
  55. data/lib/pagy/extras/headers.rb +9 -9
  56. data/lib/pagy/extras/i18n.rb +4 -4
  57. data/lib/pagy/extras/items.rb +11 -10
  58. data/lib/pagy/extras/jsonapi.rb +2 -7
  59. data/lib/pagy/extras/materialize.rb +41 -29
  60. data/lib/pagy/extras/meilisearch.rb +7 -7
  61. data/lib/pagy/extras/metadata.rb +6 -6
  62. data/lib/pagy/extras/navs.rb +26 -38
  63. data/lib/pagy/extras/overflow.rb +7 -5
  64. data/lib/pagy/extras/searchkick.rb +6 -6
  65. data/lib/pagy/extras/semantic.rb +40 -27
  66. data/lib/pagy/extras/standalone.rb +4 -7
  67. data/lib/pagy/extras/support.rb +18 -32
  68. data/lib/pagy/extras/trim.rb +2 -2
  69. data/lib/pagy/extras/uikit.rb +44 -31
  70. data/lib/pagy/frontend.rb +59 -28
  71. data/lib/pagy/i18n.rb +0 -2
  72. data/lib/pagy/url_helpers.rb +1 -2
  73. data/lib/pagy.rb +16 -14
  74. data/lib/stylesheets/pagy.css +61 -0
  75. data/lib/stylesheets/pagy.scss +50 -0
  76. data/lib/stylesheets/pagy.tailwind.scss +24 -0
  77. metadata +7 -19
  78. data/lib/templates/bootstrap_nav.html.erb +0 -24
  79. data/lib/templates/bootstrap_nav.html.haml +0 -34
  80. data/lib/templates/bootstrap_nav.html.slim +0 -34
  81. data/lib/templates/bulma_nav.html.erb +0 -24
  82. data/lib/templates/bulma_nav.html.haml +0 -32
  83. data/lib/templates/bulma_nav.html.slim +0 -32
  84. data/lib/templates/foundation_nav.html.erb +0 -24
  85. data/lib/templates/foundation_nav.html.haml +0 -34
  86. data/lib/templates/foundation_nav.html.slim +0 -34
  87. data/lib/templates/nav.html.erb +0 -22
  88. data/lib/templates/nav.html.haml +0 -30
  89. data/lib/templates/nav.html.slim +0 -29
  90. data/lib/templates/uikit_nav.html.erb +0 -15
  91. data/lib/templates/uikit_nav.html.haml +0 -28
  92. data/lib/templates/uikit_nav.html.slim +0 -28
@@ -7,7 +7,7 @@ class Pagy # :nodoc:
7
7
 
8
8
  # Paginate Searchkick::Results objects
9
9
  module SearchkickExtra
10
- module Searchkick # :nodoc:
10
+ module ModelExtension # :nodoc:
11
11
  # Return an array used to delay the call of #search
12
12
  # after the pagination variables are merged to the options.
13
13
  # It also pushes to the same array an optional method call.
@@ -18,9 +18,10 @@ class Pagy # :nodoc:
18
18
  end
19
19
  alias_method Pagy::DEFAULT[:searchkick_pagy_search], :pagy_searchkick
20
20
  end
21
+ Pagy::Searchkick = ModelExtension
21
22
 
22
23
  # Additions for the Pagy class
23
- module Pagy
24
+ module PagyExtension
24
25
  # Create a Pagy object from a Searchkick::Results object
25
26
  def new_from_searchkick(results, vars = {})
26
27
  vars[:items] = results.options[:per_page]
@@ -29,9 +30,10 @@ class Pagy # :nodoc:
29
30
  new(vars)
30
31
  end
31
32
  end
33
+ Pagy.extend PagyExtension
32
34
 
33
35
  # Add specialized backend methods to paginate Searchkick::Results
34
- module Backend
36
+ module BackendAddOn
35
37
  private
36
38
 
37
39
  # Return Pagy object and results
@@ -60,8 +62,6 @@ class Pagy # :nodoc:
60
62
  vars
61
63
  end
62
64
  end
65
+ Backend.prepend BackendAddOn
63
66
  end
64
- Searchkick = SearchkickExtra::Searchkick
65
- extend SearchkickExtra::Pagy
66
- Backend.prepend SearchkickExtra::Backend
67
67
  end
@@ -8,73 +8,86 @@ class Pagy # :nodoc:
8
8
  # The resulting code may not look very elegant, but produces the best benchmarks
9
9
  module SemanticExtra
10
10
  # Pagination for semantic: it returns the html with the series of links to the pages
11
- def pagy_semantic_nav(pagy, pagy_id: nil, link_extra: '', **vars)
11
+ def pagy_semantic_nav(pagy, pagy_id: nil, link_extra: '',
12
+ nav_aria_label: nil, nav_i18n_key: nil, **vars)
12
13
  p_id = %( id="#{pagy_id}") if pagy_id
13
- link = pagy_link_proc(pagy, link_extra: %(class="item" #{link_extra}))
14
+ link = pagy_link_proc(pagy, link_extra:)
14
15
 
15
- html = +%(<div#{p_id} class="pagy-semantic-nav ui pagination menu">)
16
- html << pagy_semantic_prev_html(pagy, link)
16
+ html = +%(<div#{p_id} role="navigation" class="pagy-semantic-nav ui pagination menu" #{
17
+ nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key)}>)
18
+ html << semantic_prev_html(pagy, link)
17
19
  pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
18
20
  html << case item
19
- when Integer then link.call item
20
- when String then %(<a class="item active">#{pagy.label_for(item)}</a>)
21
- when :gap then %(<div class="disabled item">#{pagy_t 'pagy.nav.gap'}</div>)
22
- else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
21
+ when Integer
22
+ link.call(item, pagy.label_for(item), %(class="item"))
23
+ when String
24
+ %(<a role="link" class="item active" aria-current="page" aria-disabled="true">#{pagy.label_for(item)}</a>)
25
+ when :gap
26
+ %(<div class="disabled item">#{pagy_t 'pagy.gap'}</div>)
27
+ else
28
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
23
29
  end
24
30
  end
25
- html << pagy_semantic_next_html(pagy, link)
31
+ html << semantic_next_html(pagy, link)
26
32
  html << %(</div>)
27
33
  end
28
34
 
29
35
  # Javascript pagination for semantic: it returns a nav and a JSON tag used by the pagy.js file
30
- def pagy_semantic_nav_js(pagy, pagy_id: nil, link_extra: '', **vars)
36
+ def pagy_semantic_nav_js(pagy, pagy_id: nil, link_extra: '',
37
+ nav_aria_label: nil, nav_i18n_key: nil, **vars)
31
38
  sequels = pagy.sequels(**vars)
32
39
  p_id = %( id="#{pagy_id}") if pagy_id
33
- link = pagy_link_proc(pagy, link_extra: %(class="item" #{link_extra}))
34
- tags = { 'before' => pagy_semantic_prev_html(pagy, link),
35
- 'link' => link.call(PAGE_PLACEHOLDER, LABEL_PLACEHOLDER),
36
- 'active' => %(<a class="item active">#{LABEL_PLACEHOLDER}</a>),
37
- 'gap' => %(<div class="disabled item">#{pagy_t('pagy.nav.gap')}</div>),
38
- 'after' => pagy_semantic_next_html(pagy, link) }
40
+ link = pagy_link_proc(pagy, link_extra:)
41
+ tags = { 'before' => semantic_prev_html(pagy, link),
42
+ 'link' => link.call(PAGE_TOKEN, LABEL_TOKEN, %(class="item")),
43
+ 'active' => %(<a role="link" class="item active" aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a>),
44
+ 'gap' => %(<div class="disabled item">#{pagy_t('pagy.gap')}</div>),
45
+ 'after' => semantic_next_html(pagy, link) }
39
46
 
40
47
  %(<div#{p_id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-semantic-nav-js ui pagination menu" role="navigation" #{
41
- pagy_data(pagy, :nav, tags, sequels, pagy.label_sequels(sequels))}></div>)
48
+ nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key)} #{
49
+ pagy_data(pagy, :nav, tags, sequels, pagy.label_sequels(sequels))}></div>)
42
50
  end
43
51
 
44
52
  # Combo pagination for semantic: it returns a nav and a JSON tag used by the pagy.js file
45
- def pagy_semantic_combo_nav_js(pagy, pagy_id: nil, link_extra: '')
53
+ def pagy_semantic_combo_nav_js(pagy, pagy_id: nil, link_extra: '',
54
+ nav_aria_label: nil, nav_i18n_key: nil)
46
55
  p_id = %( id="#{pagy_id}") if pagy_id
47
56
  link = pagy_link_proc(pagy, link_extra: %(class="item" #{link_extra}))
48
57
  p_page = pagy.page
49
58
  p_pages = pagy.pages
50
59
  input = %(<input type="number" min="1" max="#{p_pages}" value="#{
51
- p_page}" style="padding: 0; text-align: center; width: #{p_pages.to_s.length + 1}rem; margin: 0 0.3rem">)
60
+ p_page}" style="padding: 0; text-align: center; width: #{
61
+ p_pages.to_s.length + 1}rem; margin: 0 0.3rem" aria-current="page">)
52
62
 
53
63
  %(<div#{p_id} class="pagy-semantic-combo-nav-js ui compact menu" role="navigation" #{
64
+ nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key)} #{
54
65
  pagy_data(pagy, :combo, pagy_marked_link(link))}>#{
55
- pagy_semantic_prev_html pagy, link
66
+ semantic_prev_html pagy, link
56
67
  }<div class="pagy-combo-input item">#{
57
68
  pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages
58
69
  }</div> #{
59
- pagy_semantic_next_html pagy, link
70
+ semantic_next_html pagy, link
60
71
  }</div>)
61
72
  end
62
73
 
63
74
  private
64
75
 
65
- def pagy_semantic_prev_html(pagy, link)
76
+ def semantic_prev_html(pagy, link)
66
77
  if (p_prev = pagy.prev)
67
- link.call p_prev, '<i class="left small chevron icon"></i>', 'aria-label="previous"'
78
+ link.call(p_prev, pagy_t('pagy.prev'), %(#{prev_aria_label_attr} class="item"))
68
79
  else
69
- +%(<div class="item disabled"><i class="left small chevron icon"></i></div>)
80
+ +%(<div class="item disabled" role="link" aria-disabled="true" #{
81
+ prev_aria_label_attr}>#{pagy_t('pagy.prev')}</div>)
70
82
  end
71
83
  end
72
84
 
73
- def pagy_semantic_next_html(pagy, link)
85
+ def semantic_next_html(pagy, link)
74
86
  if (p_next = pagy.next)
75
- link.call p_next, '<i class="right small chevron icon"></i>', 'aria-label="next"'
87
+ link.call(p_next, pagy_t('pagy.next'), %(#{next_aria_label_attr} class="item"))
76
88
  else
77
- +%(<div class="item disabled"><i class="right small chevron icon"></i></div>)
89
+ +%(<div class="item disabled" role="link" aria-disabled="true" #{
90
+ next_aria_label_attr}>#{pagy_t('pagy.next')}</div>)
78
91
  end
79
92
  end
80
93
  end
@@ -25,11 +25,11 @@ class Pagy # :nodoc:
25
25
  build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
26
26
  end.delete_if(&:empty?).join('&')
27
27
  when nil
28
- prefix
28
+ escape(prefix)
29
29
  else
30
30
  raise ArgumentError, 'value must be a Hash' if prefix.nil?
31
31
 
32
- "#{prefix}=#{escape(value)}"
32
+ "#{escape(prefix)}=#{escape(value)}"
33
33
  end
34
34
  end
35
35
  end
@@ -38,7 +38,7 @@ class Pagy # :nodoc:
38
38
  # Return the URL for the page. If there is no pagy.vars[:url]
39
39
  # it works exactly as the regular #pagy_url_for, relying on the params method and Rack.
40
40
  # If there is a defined pagy.vars[:url] variable it does not need the params method nor Rack.
41
- def pagy_url_for(pagy, page, absolute: false, html_escaped: false, **_)
41
+ def pagy_url_for(pagy, page, absolute: false, **_)
42
42
  return super unless pagy.vars[:url]
43
43
 
44
44
  vars = pagy.vars
@@ -46,13 +46,10 @@ class Pagy # :nodoc:
46
46
  pagy_set_query_params(page, vars, params)
47
47
  params = pagy.params.call(params) if pagy.params.is_a?(Proc)
48
48
  query_string = "?#{QueryUtils.build_nested_query(params)}"
49
- query_string = query_string.gsub('&', '&amp;') if html_escaped # the only unescaped entity
50
49
  "#{vars[:url]}#{query_string}#{vars[:fragment]}"
51
50
  end
52
51
  end
53
- # In ruby 3+ `UrlHelpers.prepend StandaloneExtra` would be enough instead of using the next 2 lines
54
- Frontend.prepend StandaloneExtra
55
- Backend.prepend StandaloneExtra
52
+ UrlHelpers.prepend StandaloneExtra
56
53
 
57
54
  # Define a dummy params method if it's not already defined in the including module
58
55
  module Backend
@@ -5,49 +5,35 @@ class Pagy # :nodoc:
5
5
  # Extra support for features like: incremental, auto-incremental and infinite pagination
6
6
  module SupportExtra
7
7
  # Return the previous page URL string or nil
8
- def pagy_prev_url(pagy)
9
- pagy_url_for(pagy, pagy.prev) if pagy.prev
8
+ def pagy_prev_url(pagy, absolute: false)
9
+ pagy_url_for(pagy, pagy.prev, absolute:) if pagy.prev
10
10
  end
11
11
 
12
12
  # Return the next page URL string or nil
13
- def pagy_next_url(pagy)
14
- pagy_url_for(pagy, pagy.next) if pagy.next
13
+ def pagy_next_url(pagy, absolute: false)
14
+ pagy_url_for(pagy, pagy.next, absolute:) if pagy.next
15
15
  end
16
16
 
17
- # Return the HTML string for the previous page link
18
- def pagy_prev_link(pagy, text: pagy_t('pagy.nav.prev'), link_extra: '')
19
- if pagy.prev
20
- %(<span class="page prev"><a href="#{
21
- pagy_url_for(pagy, pagy.prev, html_escaped: true)
22
- }" rel="prev" aria-label="previous" #{
23
- pagy.vars[:link_extra]
24
- } #{link_extra}>#{text}</a></span>)
25
- else
26
- %(<span class="page prev disabled">#{text}</span>)
27
- end
17
+ # Return the HTML string for the enabled/disabled previous page link
18
+ def pagy_prev_html(pagy, text: pagy_t('pagy.prev'), link_extra: '')
19
+ link = pagy_link_proc(pagy, link_extra:)
20
+ prev_html(pagy, link, text:)
28
21
  end
29
22
 
30
- # Return the HTML string for the next page link
31
- def pagy_next_link(pagy, text: pagy_t('pagy.nav.next'), link_extra: '')
32
- if pagy.next
33
- %(<span class="page next"><a href="#{
34
- pagy_url_for(pagy, pagy.next, html_escaped: true)
35
- }" rel="next" aria-label="next" #{
36
- pagy.vars[:link_extra]
37
- } #{link_extra}>#{text}</a></span>)
38
- else
39
- %(<span class="page next disabled">#{text}</span>)
40
- end
23
+ # Return the HTML string for the enabled/disabled next page link
24
+ def pagy_next_html(pagy, text: pagy_t('pagy.next'), link_extra: '')
25
+ link = pagy_link_proc(pagy, link_extra:)
26
+ next_html(pagy, link, text:)
41
27
  end
42
28
 
43
- # Return the HTML link tag for the previous page or nil
44
- def pagy_prev_link_tag(pagy)
45
- %(<link href="#{pagy_url_for(pagy, pagy.prev, html_escaped: true)}" rel="prev"/>) if pagy.prev
29
+ # Conditionally return the HTML link tag string for the previous page
30
+ def pagy_prev_link_tag(pagy, absolute: false)
31
+ %(<link href="#{pagy_url_for(pagy, pagy.prev, absolute:)}" rel="prev"/>) if pagy.prev
46
32
  end
47
33
 
48
- # Return the HTML link tag for the next page or nil
49
- def pagy_next_link_tag(pagy)
50
- %(<link href="#{pagy_url_for(pagy, pagy.next, html_escaped: true)}" rel="next"/>) if pagy.next
34
+ # Conditionally return the HTML link tag string for the next page
35
+ def pagy_next_link_tag(pagy, absolute: false)
36
+ %(<link href="#{pagy_url_for(pagy, pagy.next, absolute:)}" rel="next"/>) if pagy.next
51
37
  end
52
38
  end
53
39
  Frontend.prepend SupportExtra
@@ -9,7 +9,7 @@ class Pagy # :nodoc:
9
9
  # Override the original pagy_link_proc.
10
10
  # Call the pagy_trim method if the trim_extra is enabled.
11
11
  def pagy_link_proc(pagy, link_extra: '')
12
- link_proc = super(pagy, link_extra: link_extra)
12
+ link_proc = super(pagy, link_extra:)
13
13
  return link_proc unless pagy.vars[:trim_extra]
14
14
 
15
15
  lambda do |page, text = pagy.label_for(page), extra = ''|
@@ -22,7 +22,7 @@ class Pagy # :nodoc:
22
22
 
23
23
  # Remove the the :page_param param from the first page link
24
24
  def pagy_trim(pagy, link)
25
- link.sub!(/(\?|&amp;)#{pagy.vars[:page_param]}=1\b(?!&amp;)|\b#{pagy.vars[:page_param]}=1&amp;/, '')
25
+ link.sub!(/[?&]#{pagy.vars[:page_param]}=1\b(?!&)|\b#{pagy.vars[:page_param]}=1&/, '')
26
26
  end
27
27
  end
28
28
  Frontend.prepend TrimExtra
@@ -8,75 +8,88 @@ class Pagy # :nodoc:
8
8
  # The resulting code may not look very elegant, but produces the best benchmarks
9
9
  module UikitExtra
10
10
  # 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)
11
+ def pagy_uikit_nav(pagy, pagy_id: nil, link_extra: '',
12
+ nav_aria_label: nil, nav_i18n_key: nil, **vars)
12
13
  p_id = %( id="#{pagy_id}") if pagy_id
13
- link = pagy_link_proc(pagy, link_extra: link_extra)
14
+ link = pagy_link_proc(pagy, link_extra:)
14
15
 
15
- html = +%(<ul#{p_id} class="pagy-uikit-nav uk-pagination uk-flex-center">#{pagy_uikit_prev_html pagy, link})
16
+ html = +%(<ul#{p_id} class="pagy-uikit-nav uk-pagination uk-flex-center" role="navigation" #{
17
+
18
+ nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key)}>#{uikit_prev_html(pagy, link)})
16
19
  pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
20
  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}"
21
+ when Integer
22
+ %(<li>#{link.call(item)}</li>)
23
+ when String
24
+ %(<li class="uk-active"><span role="link" aria-current="page" aria-disabled="true">#{
25
+ pagy.label_for(item)}</span></li>)
26
+ when :gap
27
+ %(<li class="uk-disabled"><span>#{pagy_t 'pagy.gap'}</span></li>)
28
+ else
29
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
22
30
  end
23
31
  end
24
- html << pagy_uikit_next_html(pagy, link)
32
+ html << uikit_next_html(pagy, link)
25
33
  html << %(</ul>)
26
34
  end
27
35
 
28
36
  # 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)
37
+ def pagy_uikit_nav_js(pagy, pagy_id: nil, link_extra: '',
38
+ nav_aria_label: nil, nav_i18n_key: nil, **vars)
30
39
  sequels = pagy.sequels(**vars)
31
40
  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) }
41
+ link = pagy_link_proc(pagy, link_extra:)
42
+ tags = { 'before' => uikit_prev_html(pagy, link),
43
+ 'link' => %(<li>#{link.call(PAGE_TOKEN, LABEL_TOKEN)}</li>),
44
+ 'active' => %(<li class="uk-active"><span role="link" aria-current="page" aria-disabled="true">#{
45
+ LABEL_TOKEN}</span></li>),
46
+ 'gap' => %(<li class="uk-disabled"><span>#{pagy_t 'pagy.gap'}</span></li>),
47
+ 'after' => uikit_next_html(pagy, link) }
38
48
 
39
- %(<ul#{p_id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-uikit-nav-js uk-pagination uk-flex-center" #{
40
- pagy_data(pagy, :nav, tags, sequels, pagy.label_sequels(sequels))}></ul>)
49
+ %(<ul#{p_id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-uikit-nav-js uk-pagination uk-flex-center" role="navigation" #{
50
+ nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key)} #{
51
+ pagy_data(pagy, :nav, tags, sequels, pagy.label_sequels(sequels))}></ul>)
41
52
  end
42
53
 
43
54
  # 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: '')
55
+ def pagy_uikit_combo_nav_js(pagy, pagy_id: nil, link_extra: '',
56
+ nav_aria_label: nil, nav_i18n_key: nil)
45
57
  p_id = %( id="#{pagy_id}") if pagy_id
46
- link = pagy_link_proc(pagy, link_extra: link_extra)
58
+ link = pagy_link_proc(pagy, link_extra:)
47
59
  p_page = pagy.page
48
60
  p_pages = pagy.pages
49
61
  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;">)
62
+ p_page}" style="text-align: center; width: #{p_pages.to_s.length + 1}rem;" aria-current="page">)
51
63
 
52
- %(<ul#{p_id} class="pagy-uikit-combo-nav-js uk-button-group uk-pagination uk-flex-center" #{
64
+ %(<ul#{p_id} class="pagy-uikit-combo-nav-js uk-button-group uk-pagination uk-flex-center" role="navigation" #{
65
+ nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key)} #{
53
66
  pagy_data(pagy, :combo, pagy_marked_link(link))
54
67
  }>#{
55
- pagy_uikit_prev_html pagy, link
68
+ uikit_prev_html pagy, link
56
69
  }<li>#{
57
70
  pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages
58
71
  }</li>#{
59
- pagy_uikit_next_html pagy, link
72
+ uikit_next_html pagy, link
60
73
  }</ul>)
61
74
  end
62
75
 
63
76
  private
64
77
 
65
- def pagy_uikit_prev_html(pagy, link)
66
- previous_span = %(<span uk-pagination-previous>#{pagy_t 'pagy.nav.prev'}</span>)
78
+ def uikit_prev_html(pagy, link)
79
+ previous_span = %(<span uk-pagination-previous></span>)
67
80
  if (p_prev = pagy.prev)
68
- %(<li>#{link.call p_prev, previous_span}</li>)
81
+ %(<li>#{link.call(p_prev, previous_span, prev_aria_label_attr)}</li>)
69
82
  else
70
- %(<li class="uk-disabled"><a href="#">#{previous_span}</a></li>)
83
+ %(<li class="uk-disabled"><span role="link" aria-disabled="true" #{prev_aria_label_attr}>#{previous_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, link)
88
+ next_span = %(<span uk-pagination-next></span>)
76
89
  if (p_next = pagy.next)
77
- %(<li>#{link.call p_next, next_span}</li>)
90
+ %(<li>#{link.call(p_next, next_span, next_aria_label_attr)}</li>)
78
91
  else
79
- %(<li class="uk-disabled"><a href="#">#{next_span}</a></li>)
92
+ %(<li class="uk-disabled"><span role="link" aria-disabled="true" #{prev_aria_label_attr}>#{next_span}</span></li>)
80
93
  end
81
94
  end
82
95
  end
data/lib/pagy/frontend.rb CHANGED
@@ -6,8 +6,8 @@ require 'pagy/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,36 +15,31 @@ 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, pagy_id: nil, link_extra: '',
19
+ nav_aria_label: nil, nav_i18n_key: nil, **vars)
20
+ p_id = %( id="#{pagy_id}") if pagy_id
21
+ link = pagy_link_proc(pagy, link_extra:)
23
22
 
24
- html = +%(<nav#{p_id} class="pagy-nav pagination">)
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
23
+ html = +%(<nav#{p_id} class="pagy pagy-nav pagination" #{nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key)}>)
24
+ html << prev_html(pagy, link)
30
25
  pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
31
26
  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}"
27
+ when Integer
28
+ %(<span class="page">#{link.call(item)}</span>)
29
+ when String
30
+ %(<span class="page active">) +
31
+ %(<a role="link" aria-disabled="true" aria-current="page">#{pagy.label_for(item)}</a></span>)
32
+ when :gap
33
+ %(<span class="page gap">#{pagy_t('pagy.gap')}</span>)
34
+ else
35
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
36
36
  end
37
37
  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>)
38
+ html << %(#{next_html(pagy, link)}</nav>)
44
39
  end
45
40
 
46
41
  # 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)
42
+ def pagy_info(pagy, pagy_id: nil, item_name: nil, item_i18n_key: nil)
48
43
  p_id = %( id="#{pagy_id}") if pagy_id
49
44
  p_count = pagy.count
50
45
  key = if p_count.zero? then 'pagy.info.no_items'
@@ -53,22 +48,25 @@ class Pagy
53
48
  end
54
49
 
55
50
  %(<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
+ pagy_t key, item_name: item_name || pagy_t(item_i18n_key || pagy.vars[:item_i18n_key], count: p_count),
57
52
  count: p_count, from: pagy.from, to: pagy.to
58
53
  }</span>)
59
54
  end
60
55
 
61
- # Return a performance optimized proc to generate the HTML links
56
+ # Return a performance optimized lambda to generate the HTML links
62
57
  # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
63
58
  def pagy_link_proc(pagy, link_extra: '')
64
59
  p_prev = pagy.prev
65
60
  p_next = pagy.next
66
- left, right = %(<a href="#{pagy_url_for(pagy, PAGE_PLACEHOLDER, html_escaped: true)}" #{
67
- pagy.vars[:link_extra]} #{link_extra}).split(PAGE_PLACEHOLDER, 2)
61
+ p_page = pagy.page.to_s
62
+ left, right = %(<a href="#{pagy_url_for(pagy, PAGE_TOKEN)}" #{
63
+ pagy.vars[:link_extra]} #{link_extra}).split(PAGE_TOKEN, 2)
64
+ # lambda used by all the helpers
68
65
  lambda do |page, text = pagy.label_for(page), extra_attrs = ''|
69
66
  %(#{left}#{page}#{right}#{ case page
70
67
  when p_prev then ' rel="prev"'
71
68
  when p_next then ' rel="next"'
69
+ when p_page then ' aria-disabled="true" aria-current="page"'
72
70
  else ''
73
71
  end } #{extra_attrs}>#{text}</a>)
74
72
  end
@@ -79,5 +77,38 @@ class Pagy
79
77
  def pagy_t(key, opts = {})
80
78
  Pagy::I18n.translate(@pagy_locale ||= nil, key, opts)
81
79
  end
80
+
81
+ private
82
+
83
+ def nav_aria_label_attr(pagy, nav_aria_label, nav_i18n_key, count: pagy.pages)
84
+ nav_aria_label ||= pagy_t(nav_i18n_key || pagy.vars[:nav_i18n_key], count:)
85
+ %(aria-label="#{nav_aria_label}")
86
+ end
87
+
88
+ def prev_aria_label_attr
89
+ %(aria-label="#{pagy_t('pagy.aria_label.prev')}")
90
+ end
91
+
92
+ def next_aria_label_attr
93
+ %(aria-label="#{pagy_t('pagy.aria_label.next')}")
94
+ end
95
+
96
+ def prev_html(pagy, link, text: pagy_t('pagy.prev'))
97
+ if (p_prev = pagy.prev)
98
+ %(<span class="page prev">#{link.call(p_prev, text, prev_aria_label_attr)}</span>)
99
+ else
100
+ %(<span class="page prev disabled"><a role="link" aria-disabled="true" #{
101
+ prev_aria_label_attr}>#{text}</a></span>)
102
+ end
103
+ end
104
+
105
+ def next_html(pagy, link, text: pagy_t('pagy.next'))
106
+ if (p_next = pagy.next)
107
+ %(<span class="page next">#{link.call(p_next, text, next_aria_label_attr)}</span>)
108
+ else
109
+ %(<span class="page next disabled"><a role="link" aria-disabled="true" #{
110
+ next_aria_label_attr}>#{text}</a></span>)
111
+ end
112
+ end
82
113
  end
83
114
  end
data/lib/pagy/i18n.rb CHANGED
@@ -107,8 +107,6 @@ class Pagy
107
107
  hash['pl'] = RULE[:polish]
108
108
  hash['ru'] = RULE[:east_slavic]
109
109
  hash['sr'] = RULE[:east_slavic]
110
- hash['sv'] = RULE[:one_two_other]
111
- hash['sv-SE'] = RULE[:one_two_other]
112
110
  hash['tr'] = RULE[:other]
113
111
  hash['uk'] = RULE[:east_slavic]
114
112
  hash['vi'] = RULE[:other]
@@ -6,7 +6,7 @@ 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: false, html_escaped: false, **_)
9
+ def pagy_url_for(pagy, page, absolute: false, **_)
10
10
  vars = pagy.vars
11
11
  request_path = vars[:request_path].to_s.empty? ? request.path : vars[:request_path]
12
12
  pagy_params = pagy.params.is_a?(Hash) ? pagy.params.transform_keys(&:to_s) : {}
@@ -14,7 +14,6 @@ class Pagy
14
14
  pagy_set_query_params(page, vars, params)
15
15
  params = pagy.params.call(params) if pagy.params.is_a?(Proc)
16
16
  query_string = "?#{Rack::Utils.build_nested_query(params)}"
17
- query_string = query_string.gsub('&', '&amp;') if html_escaped # the only unescaped entity
18
17
  "#{request.base_url if absolute}#{request_path}#{query_string}#{vars[:fragment]}"
19
18
  end
20
19
 
data/lib/pagy.rb CHANGED
@@ -5,7 +5,7 @@ require 'pathname'
5
5
 
6
6
  # Core class
7
7
  class Pagy
8
- VERSION = '6.5.0'
8
+ VERSION = '7.0.9'
9
9
 
10
10
  # Root pathname to get the path of Pagy files like templates or dictionaries
11
11
  def self.root
@@ -13,17 +13,19 @@ class Pagy
13
13
  end
14
14
 
15
15
  # Default core vars: constant for easy access, but mutable for customizable defaults
16
- DEFAULT = { page: 1, # rubocop:disable Style/MutableConstant
17
- items: 20,
18
- outset: 0,
19
- size: [1, 4, 4, 1],
20
- page_param: :page,
21
- params: {},
22
- fragment: '',
23
- link_extra: '',
24
- i18n_key: 'pagy.item_name',
25
- cycle: false,
26
- request_path: '' }
16
+ DEFAULT = { page: 1, # rubocop:disable Style/MutableConstant
17
+ items: 20,
18
+ outset: 0,
19
+ size: 7,
20
+ page_param: :page,
21
+ params: {},
22
+ fragment: '',
23
+ link_extra: '',
24
+ item_i18n_key: 'pagy.item_name',
25
+ nav_i18n_key: 'pagy.aria_label.nav',
26
+ cycle: false,
27
+ request_path: '',
28
+ count_args: [:all] } # AR friendly
27
29
 
28
30
  attr_reader :count, :page, :items, :vars, :pages, :last, :offset, :in, :from, :to, :prev, :next, :params, :request_path
29
31
 
@@ -87,12 +89,12 @@ class Pagy
87
89
  series
88
90
  end
89
91
 
90
- # Allow the customization of the output (overridden by the calendar extra)
92
+ # Label for any page. Allow the customization of the output (overridden by the calendar extra)
91
93
  def label_for(page)
92
94
  page.to_s
93
95
  end
94
96
 
95
- # Allow the customization of the output (overridden by the calendar extra)
97
+ # Label for the current page. Allow the customization of the output (overridden by the calendar extra)
96
98
  def label
97
99
  @page.to_s
98
100
  end