pagy 3.8.2 → 9.4.0

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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +737 -0
  4. data/apps/demo.ru +449 -0
  5. data/apps/index.rb +7 -0
  6. data/apps/keyset_ar.ru +228 -0
  7. data/apps/keyset_s.ru +220 -0
  8. data/apps/rails.ru +217 -0
  9. data/apps/repro.ru +182 -0
  10. data/bin/pagy +98 -0
  11. data/config/pagy.rb +220 -0
  12. data/javascripts/pagy.d.ts +5 -0
  13. data/javascripts/pagy.min.js +4 -0
  14. data/javascripts/pagy.min.js.map +10 -0
  15. data/javascripts/pagy.mjs +100 -0
  16. data/lib/optimist.rb +1022 -0
  17. data/lib/pagy/b64.rb +33 -0
  18. data/lib/pagy/backend.rb +30 -19
  19. data/lib/pagy/calendar/day.rb +41 -0
  20. data/lib/pagy/calendar/month.rb +42 -0
  21. data/lib/pagy/calendar/quarter.rb +49 -0
  22. data/lib/pagy/calendar/unit.rb +103 -0
  23. data/lib/pagy/calendar/week.rb +39 -0
  24. data/lib/pagy/calendar/year.rb +35 -0
  25. data/lib/pagy/calendar.rb +84 -0
  26. data/lib/pagy/console.rb +23 -0
  27. data/lib/pagy/countless.rb +27 -22
  28. data/lib/pagy/exceptions.rb +16 -13
  29. data/lib/pagy/extras/arel.rb +11 -14
  30. data/lib/pagy/extras/array.rb +12 -16
  31. data/lib/pagy/extras/bootstrap.rb +83 -41
  32. data/lib/pagy/extras/bulma.rb +79 -46
  33. data/lib/pagy/extras/calendar.rb +79 -0
  34. data/lib/pagy/extras/countless.rb +20 -25
  35. data/lib/pagy/extras/elasticsearch_rails.rb +59 -38
  36. data/lib/pagy/extras/gearbox.rb +55 -0
  37. data/lib/pagy/extras/headers.rb +38 -23
  38. data/lib/pagy/extras/i18n.rb +19 -18
  39. data/lib/pagy/extras/js_tools.rb +70 -0
  40. data/lib/pagy/extras/jsonapi.rb +88 -0
  41. data/lib/pagy/extras/keyset.rb +30 -0
  42. data/lib/pagy/extras/limit.rb +63 -0
  43. data/lib/pagy/extras/meilisearch.rb +57 -0
  44. data/lib/pagy/extras/metadata.rb +32 -27
  45. data/lib/pagy/extras/overflow.rb +61 -53
  46. data/lib/pagy/extras/pagy.rb +82 -0
  47. data/lib/pagy/extras/searchkick.rb +52 -41
  48. data/lib/pagy/extras/size.rb +40 -0
  49. data/lib/pagy/extras/standalone.rb +60 -0
  50. data/lib/pagy/extras/trim.rb +19 -13
  51. data/lib/pagy/frontend.rb +76 -51
  52. data/lib/pagy/i18n.rb +167 -0
  53. data/lib/pagy/keyset/active_record.rb +44 -0
  54. data/lib/pagy/keyset/sequel.rb +57 -0
  55. data/lib/pagy/keyset.rb +118 -0
  56. data/lib/pagy/shared_methods.rb +26 -0
  57. data/lib/pagy/url_helpers.rb +26 -0
  58. data/lib/pagy.rb +91 -37
  59. data/locales/ar.yml +29 -0
  60. data/locales/be.yml +25 -0
  61. data/locales/bg.yml +21 -0
  62. data/locales/bs.yml +25 -0
  63. data/locales/ca.yml +21 -0
  64. data/locales/ckb.yml +18 -0
  65. data/locales/cs.yml +23 -0
  66. data/locales/da.yml +21 -0
  67. data/locales/de.yml +21 -0
  68. data/locales/dz.yml +17 -0
  69. data/locales/en.yml +21 -0
  70. data/{lib/locales → locales}/es.yml +11 -12
  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/{lib/locales → locales}/pt-BR.yml +11 -12
  83. data/locales/pt.yml +21 -0
  84. data/locales/ru.yml +25 -0
  85. data/locales/sk.yml +23 -0
  86. data/locales/sr.yml +25 -0
  87. data/locales/sv-SE.yml +21 -0
  88. data/locales/sv.yml +21 -0
  89. data/locales/sw.yml +25 -0
  90. data/locales/ta.yml +21 -0
  91. data/locales/tr.yml +19 -0
  92. data/locales/uk.yml +25 -0
  93. data/locales/vi.yml +17 -0
  94. data/locales/zh-CN.yml +17 -0
  95. data/locales/zh-HK.yml +17 -0
  96. data/locales/zh-TW.yml +17 -0
  97. data/stylesheets/pagy.css +46 -0
  98. data/stylesheets/pagy.scss +48 -0
  99. data/stylesheets/pagy.tailwind.css +21 -0
  100. metadata +95 -67
  101. data/lib/config/pagy.rb +0 -170
  102. data/lib/javascripts/pagy.js +0 -106
  103. data/lib/locales/README.md +0 -35
  104. data/lib/locales/bg.yml +0 -22
  105. data/lib/locales/ca.yml +0 -22
  106. data/lib/locales/da.yml +0 -22
  107. data/lib/locales/de.yml +0 -22
  108. data/lib/locales/en.yml +0 -22
  109. data/lib/locales/fr.yml +0 -22
  110. data/lib/locales/id.yml +0 -20
  111. data/lib/locales/it.yml +0 -22
  112. data/lib/locales/ja.yml +0 -20
  113. data/lib/locales/km.yml +0 -19
  114. data/lib/locales/ko.yml +0 -20
  115. data/lib/locales/nb.yml +0 -22
  116. data/lib/locales/nl.yml +0 -22
  117. data/lib/locales/pl.yml +0 -24
  118. data/lib/locales/ru.yml +0 -24
  119. data/lib/locales/sv-SE.yml +0 -23
  120. data/lib/locales/sv.yml +0 -23
  121. data/lib/locales/tr.yml +0 -20
  122. data/lib/locales/utils/i18n.rb +0 -25
  123. data/lib/locales/utils/loader.rb +0 -34
  124. data/lib/locales/utils/p11n.rb +0 -80
  125. data/lib/locales/zh-CN.yml +0 -20
  126. data/lib/locales/zh-HK.yml +0 -20
  127. data/lib/locales/zh-TW.yml +0 -20
  128. data/lib/pagy/extras/foundation.rb +0 -57
  129. data/lib/pagy/extras/items.rb +0 -65
  130. data/lib/pagy/extras/materialize.rb +0 -59
  131. data/lib/pagy/extras/navs.rb +0 -38
  132. data/lib/pagy/extras/pagy_search.rb +0 -18
  133. data/lib/pagy/extras/semantic.rb +0 -55
  134. data/lib/pagy/extras/shared.rb +0 -53
  135. data/lib/pagy/extras/support.rb +0 -29
  136. data/lib/pagy/extras/uikit.rb +0 -62
  137. data/lib/templates/bootstrap_nav.html.erb +0 -24
  138. data/lib/templates/bootstrap_nav.html.haml +0 -34
  139. data/lib/templates/bootstrap_nav.html.slim +0 -34
  140. data/lib/templates/bulma_nav.html.erb +0 -24
  141. data/lib/templates/bulma_nav.html.haml +0 -32
  142. data/lib/templates/bulma_nav.html.slim +0 -32
  143. data/lib/templates/foundation_nav.html.erb +0 -24
  144. data/lib/templates/foundation_nav.html.haml +0 -34
  145. data/lib/templates/foundation_nav.html.slim +0 -34
  146. data/lib/templates/nav.html.erb +0 -22
  147. data/lib/templates/nav.html.haml +0 -30
  148. data/lib/templates/nav.html.slim +0 -29
  149. data/lib/templates/uikit_nav.html.erb +0 -15
  150. data/lib/templates/uikit_nav.html.haml +0 -28
  151. data/lib/templates/uikit_nav.html.slim +0 -28
  152. data/pagy.gemspec +0 -16
@@ -1,23 +1,19 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/array
2
- # encoding: utf-8
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/array
3
2
  # frozen_string_literal: true
4
3
 
5
- class Pagy
6
- # Add specialized backend methods to paginate array collections
7
- module Backend ; private
4
+ class Pagy # :nodoc:
5
+ # Paginate arrays efficiently, avoiding expensive array-wrapping and without overriding
6
+ module ArrayExtra
7
+ private
8
8
 
9
- # Return Pagy object and items
10
- def pagy_array(array, vars={})
11
- pagy = Pagy.new(pagy_array_get_vars(array, vars))
12
- return pagy, array[pagy.offset, pagy.items]
13
- end
14
-
15
- # Sub-method called only by #pagy_array: here for easy customization of variables by overriding
16
- def pagy_array_get_vars(array, vars)
9
+ # Return Pagy object and paginated items
10
+ def pagy_array(array, **vars)
11
+ vars[:limit] ||= pagy_get_limit(vars)
12
+ vars[:page] ||= pagy_get_page(vars)
17
13
  vars[:count] ||= array.size
18
- vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ]
19
- vars
14
+ pagy = Pagy.new(**vars)
15
+ [pagy, array[pagy.offset, pagy.limit]]
20
16
  end
21
-
22
17
  end
18
+ Backend.prepend ArrayExtra
23
19
  end
@@ -1,55 +1,97 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/bootstrap
2
- # encoding: utf-8
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/bootstrap
3
2
  # frozen_string_literal: true
4
3
 
5
- require 'pagy/extras/shared'
6
-
7
- class Pagy
8
- module Frontend
4
+ require_relative 'js_tools'
9
5
 
6
+ class Pagy # :nodoc:
7
+ # Frontend modules are specially optimized for performance.
8
+ # The resulting code may not look very elegant, but produces the best benchmarks
9
+ module BootstrapExtra
10
10
  # Pagination for bootstrap: it returns the html with the series of links to the pages
11
- def pagy_bootstrap_nav(pagy)
12
- link, p_prev, p_next = pagy_link_proc(pagy, 'class="page-link"'), pagy.prev, pagy.next
13
-
14
- html = EMPTY + (p_prev ? %(<li class="page-item prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</li>)
15
- : %(<li class="page-item prev disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.prev')}</a></li>))
16
- pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
- html << if item.is_a?(Integer); %(<li class="page-item">#{link.call item}</li>) # page link
18
- elsif item.is_a?(String) ; %(<li class="page-item active">#{link.call item}</li>) # active page
19
- elsif item == :gap ; %(<li class="page-item gap disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.gap')}</a></li>) # page gap
11
+ def pagy_bootstrap_nav(pagy, id: nil, classes: 'pagination', aria_label: nil, **vars)
12
+ id = %( id="#{id}") if id
13
+ a = pagy_anchor(pagy, **vars)
14
+
15
+ html = %(<nav#{id} class="pagy-bootstrap nav" #{nav_aria_label(pagy, aria_label:)}><ul class="#{classes}">#{
16
+ bootstrap_prev_html(pagy, a)})
17
+ pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
18
+ html << case item
19
+ when Integer
20
+ %(<li class="page-item">#{a.(item, classes: 'page-link')}</li>)
21
+ when String
22
+ %(<li class="page-item active"><a role="link" class="page-link" aria-current="page" aria-disabled="true">#{
23
+ pagy.label_for(item)}</a></li>)
24
+ when :gap
25
+ %(<li class="page-item gap disabled"><a role="link" class="page-link" aria-disabled="true">#{
26
+ pagy_t('pagy.gap')}</a></li>)
27
+ else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
20
28
  end
21
29
  end
22
- html << (p_next ? %(<li class="page-item next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</li>)
23
- : %(<li class="page-item next disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.next')}</a></li>))
24
- %(<nav class="pagy-bootstrap-nav" role="navigation" aria-label="pager"><ul class="pagination">#{html}</ul></nav>)
30
+ html << %(#{bootstrap_next_html(pagy, a)}</ul></nav>)
25
31
  end
26
32
 
27
- # Javascript pagination for bootstrap: it returns a nav and a JSON tag used by the Pagy.nav javascript
28
- def pagy_bootstrap_nav_js(pagy, id=pagy_id)
29
- link, p_prev, p_next = pagy_link_proc(pagy, 'class="page-link"'), pagy.prev, pagy.next
30
- tags = { 'before' => p_prev ? %(<ul class="pagination"><li class="page-item prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</li>)
31
- : %(<ul class="pagination"><li class="page-item prev disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.prev')}</a></li>),
32
- 'link' => %(<li class="page-item">#{mark = link.call(PAGE_PLACEHOLDER)}</li>),
33
- 'active' => %(<li class="page-item active">#{mark}</li>),
34
- 'gap' => %(<li class="page-item gap disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.gap')}</a></li>),
35
- 'after' => p_next ? %(<li class="page-item next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</li></ul>)
36
- : %(<li class="page-item next disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.next')}</a></li></ul>) }
37
- %(<nav id="#{id}" class="pagy-bootstrap-nav-js" role="navigation" aria-label="pager"></nav>#{pagy_json_tag(:nav, id, tags, pagy.sequels, defined?(TRIM) && pagy.vars[:page_param])})
33
+ # Javascript pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
34
+ def pagy_bootstrap_nav_js(pagy, id: nil, classes: 'pagination', aria_label: nil, **vars)
35
+ sequels = pagy.sequels(**vars)
36
+ id = %( id="#{id}") if id
37
+ a = pagy_anchor(pagy, **vars)
38
+ tokens = { 'before' => %(<ul class="#{classes}">#{bootstrap_prev_html(pagy, a)}),
39
+ 'a' => %(<li class="page-item">#{a.(PAGE_TOKEN, LABEL_TOKEN, classes: 'page-link')}</li>),
40
+ 'current' => %(<li class="page-item active"><a role="link" class="page-link" ) +
41
+ %(aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a></li>),
42
+ 'gap' => %(<li class="page-item gap disabled"><a role="link" class="page-link" aria-disabled="true">#{
43
+ pagy_t('pagy.gap')}</a></li>),
44
+ 'after' => %(#{bootstrap_next_html pagy, a}</ul>) }
45
+
46
+ %(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-bootstrap nav-js" #{
47
+ nav_aria_label(pagy, aria_label:)} #{
48
+ pagy_data(pagy, :nav, tokens, sequels, pagy.label_sequels(sequels))
49
+ }></nav>)
38
50
  end
39
51
 
40
- # Javascript combo pagination for bootstrap: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript
41
- def pagy_bootstrap_combo_nav_js(pagy, id=pagy_id)
42
- link, p_prev, p_next, p_page, p_pages = pagy_link_proc(pagy), pagy.prev, pagy.next, pagy.page, pagy.pages
43
-
44
- html = %(<nav id="#{id}" class="pagy-bootstrap-combo-nav-js pagination" role="navigation" aria-label="pager">) + %(<div class="btn-group" role="group">)
45
- html << (p_prev ? link.call(p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous" class="prev btn btn-primary"')
46
- : %(<a class="prev btn btn-primary disabled" href="#">#{pagy_t('pagy.nav.prev')}</a>))
47
- input = %(<input type="number" min="1" max="#{p_pages}" value="#{p_page}" class="text-primary" style="padding: 0; border: none; text-align: center; width: #{p_pages.to_s.length+1}rem;">)
48
- html << %(<div class="pagy-combo-input btn btn-primary disabled" style="white-space: nowrap;">#{pagy_t('pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages)}</div>)
49
- html << (p_next ? link.call(p_next, pagy_t('pagy.nav.next'), 'aria-label="next" class="next btn btn-primary"')
50
- : %(<a class="next btn btn-primary disabled" href="#">#{pagy_t('pagy.nav.next')}</a>))
51
- html << %(</div></nav>#{pagy_json_tag(:combo_nav, id, p_page, pagy_marked_link(link), defined?(TRIM) && pagy.vars[:page_param])})
52
+ # Javascript combo pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
53
+ def pagy_bootstrap_combo_nav_js(pagy, id: nil, classes: 'pagination', aria_label: nil, **vars)
54
+ id = %( id="#{id}") if id
55
+ a = pagy_anchor(pagy, **vars)
56
+ pages = pagy.pages
57
+
58
+ page_input = %(<input name="page" type="number" min="1" max="#{pages}" value="#{pagy.page}" aria-current="page" ) <<
59
+ %(style="text-align: center; width: #{pages.to_s.length + 1}rem; padding: 0; ) <<
60
+ %(border: none; display: inline-block;" class="page-link active">#{JSTools::A_TAG})
61
+
62
+ %(<nav#{id} class="pagy-bootstrap combo-nav-js" #{
63
+ nav_aria_label(pagy, aria_label:)} #{
64
+ pagy_data(pagy, :combo, pagy_url_for(pagy, PAGE_TOKEN, **vars))
65
+ }><ul class="#{classes}">#{
66
+ bootstrap_prev_html(pagy, a)
67
+ }<li class="page-item pagy-bootstrap"><label class="page-link">#{
68
+ pagy_t('pagy.combo_nav_js', page_input:, pages:)
69
+ }</label></li>#{
70
+ bootstrap_next_html(pagy, a)
71
+ }</ul></nav>)
52
72
  end
53
73
 
74
+ private
75
+
76
+ def bootstrap_prev_html(pagy, a)
77
+ if (p_prev = pagy.prev)
78
+ %(<li class="page-item prev">#{
79
+ a.(p_prev, pagy_t('pagy.prev'), classes: 'page-link', aria_label: pagy_t('pagy.aria_label.prev'))}</li>)
80
+ else
81
+ %(<li class="page-item prev disabled"><a role="link" class="page-link" aria-disabled="true" aria-label="#{
82
+ pagy_t('pagy.aria_label.prev')}">#{pagy_t('pagy.prev')}</a></li>)
83
+ end
84
+ end
85
+
86
+ def bootstrap_next_html(pagy, a)
87
+ if (p_next = pagy.next)
88
+ %(<li class="page-item next">#{
89
+ a.(p_next, pagy_t('pagy.next'), classes: 'page-link', aria_label: pagy_t('pagy.aria_label.next'))}</li>)
90
+ else
91
+ %(<li class="page-item next disabled"><a role="link" class="page-link" aria-disabled="true" aria-label="#{
92
+ pagy_t('pagy.aria_label.next')}">#{pagy_t('pagy.next')}</a></li>)
93
+ end
94
+ end
54
95
  end
96
+ Frontend.prepend BootstrapExtra
55
97
  end
@@ -1,60 +1,93 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/bulma
2
- # encoding: utf-8
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/bulma
3
2
  # frozen_string_literal: true
4
3
 
5
- require 'pagy/extras/shared'
4
+ require_relative 'js_tools'
6
5
 
7
- class Pagy
8
- module Frontend
6
+ class Pagy # :nodoc:
7
+ # Frontend modules are specially optimized for performance.
8
+ # The resulting code may not look very elegant, but produces the best benchmarks
9
+ module BulmaExtra
10
+ # Pagination for bulma: it returns the html with the series of links to the pages
11
+ def pagy_bulma_nav(pagy, id: nil, classes: 'pagy-bulma nav pagination is-centered',
12
+ aria_label: nil, **vars)
13
+ id = %( id="#{id}") if id
14
+ a = pagy_anchor(pagy, **vars)
9
15
 
10
- # Pagination for Bulma: it returns the html with the series of links to the pages
11
- def pagy_bulma_nav(pagy)
12
- link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next
13
-
14
- html = (p_prev ? link.call(p_prev, pagy_t('pagy.nav.prev'), 'class="pagination-previous" aria-label="previous page"')
15
- : %(<a class="pagination-previous" disabled>#{pagy_t('pagy.nav.prev')}</a>)) \
16
- + (p_next ? link.call(p_next, pagy_t('pagy.nav.next'), 'class="pagination-next" aria-label="next page"')
17
- : %(<a class="pagination-next" disabled>#{pagy_t('pagy.nav.next')}</a>))
18
- html << '<ul class="pagination-list">'
19
- pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
20
- html << if item.is_a?(Integer); %(<li>#{link.call item, item, %(class="pagination-link" aria-label="goto page #{item}") }</li>) # page link
21
- elsif item.is_a?(String) ; %(<li>#{link.call item, item, %(class="pagination-link is-current" aria-label="page #{item}" aria-current="page")}</li>) # active page
22
- elsif item == :gap ; %(<li><span class="pagination-ellipsis">#{pagy_t('pagy.nav.gap')}</span></li>) # page gap
16
+ html = %(<nav#{id} class="#{classes}" #{nav_aria_label(pagy, aria_label:)}>)
17
+ html << bulma_prev_next_html(pagy, a)
18
+ html << %(<ul class="pagination-list">)
19
+ pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
20
+ html << case item
21
+ when Integer
22
+ %(<li>#{a.(item, pagy.label_for(item), classes: 'pagination-link')}</li>)
23
+ when String
24
+ %(<li><a role="link" class="pagination-link is-current" aria-current="page" aria-disabled="true">#{
25
+ pagy.label_for(item)}</a></li>)
26
+ when :gap
27
+ %(<li><span class="pagination-ellipsis">#{pagy_t 'pagy.gap'}</span></li>)
28
+ else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
23
29
  end
24
30
  end
25
- html << '</ul>'
26
- %(<nav class="pagy-bulma-nav pagination is-centered" role="navigation" aria-label="pagination">#{html}</nav>)
31
+ html << %(</ul></nav>)
27
32
  end
28
33
 
29
- # Javascript pagination for bulma: it returns a nav and a JSON tag used by the Pagy.nav javascript
30
- def pagy_bulma_nav_js(pagy, id=pagy_id)
31
- link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next
32
- tags = { 'before' => ( (p_prev ? link.call(p_prev, pagy_t('pagy.nav.prev'), 'class="pagination-previous" aria-label="previous page"')
33
- : %(<a class="pagination-previous" disabled>#{pagy_t('pagy.nav.prev')}</a>)) \
34
- + (p_next ? link.call(p_next, pagy_t('pagy.nav.next'), 'class="pagination-next" aria-label="next page"')
35
- : %(<a class="pagination-next" disabled>#{pagy_t('pagy.nav.next')}</a>)) \
36
- + '<ul class="pagination-list">' ),
37
- 'link' => %(<li>#{link.call(PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link" aria-label="goto page #{PAGE_PLACEHOLDER}"))}</li>),
38
- 'active' => %(<li>#{link.call(PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link is-current" aria-current="page" aria-label="page #{PAGE_PLACEHOLDER}"))}</li>),
39
- 'gap' => %(<li><span class="pagination-ellipsis">#{pagy_t('pagy.nav.gap')}</span></li>),
40
- 'after' => '</ul>' }
41
- %(<nav id="#{id}" class="pagy-bulma-nav-js pagination is-centered" role="navigation" aria-label="pagination"></nav>#{pagy_json_tag(:nav, id, tags, pagy.sequels, defined?(TRIM) && pagy.vars[:page_param])})
34
+ # Javascript pagination for bulma: it returns a nav with a data-pagy attribute used by the Pagy.nav javascript
35
+ def pagy_bulma_nav_js(pagy, id: nil, classes: 'pagy-bulma nav-js pagination is-centered',
36
+ aria_label: nil, **vars)
37
+ sequels = pagy.sequels(**vars)
38
+ id = %( id="#{id}") if id
39
+ a = pagy_anchor(pagy, **vars)
40
+ tokens = { 'before' => %(#{bulma_prev_next_html(pagy, a)}<ul class="pagination-list">),
41
+ 'a' => %(<li>#{a.(PAGE_TOKEN, LABEL_TOKEN, classes: 'pagination-link')}</li>),
42
+ 'current' => %(<li><a role="link" class="pagination-link is-current" aria-current="page" aria-disabled="true">#{
43
+ LABEL_TOKEN}</a></li>),
44
+ 'gap' => %(<li><span class="pagination-ellipsis">#{pagy_t 'pagy.gap'}</span></li>),
45
+ 'after' => '</ul>' }
46
+
47
+ %(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}#{classes}" #{
48
+ nav_aria_label(pagy, aria_label:)} #{
49
+ pagy_data(pagy, :nav, tokens, sequels, pagy.label_sequels(sequels))
50
+ }></nav>)
42
51
  end
43
52
 
44
- # Javascript combo pagination for Bulma: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript
45
- def pagy_bulma_combo_nav_js(pagy, id=pagy_id)
46
- link, p_prev, p_next, p_page, p_pages = pagy_link_proc(pagy), pagy.prev, pagy.next, pagy.page, pagy.pages
47
-
48
- html = %(<nav id="#{id}" class="pagy-bulma-combo-nav-js" role="navigation" aria-label="pagination">) \
49
- + %(<div class="field is-grouped is-grouped-centered" role="group">)
50
- html << (p_prev ? %(<p class="control">#{link.call(p_prev, pagy_t('pagy.nav.prev'), 'class="button" aria-label="previous page"')}</p>)
51
- : %(<p class="control"><a class="button" disabled>#{pagy_t('pagy.nav.prev')}</a></p>))
52
- input = %(<input class="input" type="number" min="1" max="#{p_pages}" value="#{p_page}" style="padding: 0; text-align: center; width: #{p_pages.to_s.length+1}rem; margin:0 0.3rem;">)
53
- html << %(<div class="pagy-combo-input control level is-mobile">#{pagy_t('pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages)}</div>)
54
- html << (p_next ? %(<p class="control">#{link.call(p_next, pagy_t('pagy.nav.next'), 'class="button" aria-label="next page"')}</p>)
55
- : %(<p class="control"><a class="button" disabled>#{pagy_t('pagy.nav.next')}</a></p>))
56
- html << %(</div></nav>#{pagy_json_tag(:combo_nav, id, p_page, pagy_marked_link(link), defined?(TRIM) && pagy.vars[:page_param])})
53
+ # Javascript combo pagination for bulma: it returns a nav with a data-pagy attribute used by the pagy.js file
54
+ def pagy_bulma_combo_nav_js(pagy, id: nil, classes: 'pagy-bulma combo-nav-js pagination is-centered',
55
+ aria_label: nil, **vars)
56
+ id = %( id="#{id}") if id
57
+ a = pagy_anchor(pagy, **vars)
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; height: 1.7rem; margin:0 0.3rem; ) <<
62
+ %(border: none; border-radius: 4px; padding: 0; font-size: 1.1rem; color: white; ) <<
63
+ %(background-color: #485fc7;">#{JSTools::A_TAG})
64
+
65
+ %(<nav#{id} class="#{classes}" #{
66
+ nav_aria_label(pagy, aria_label:)} #{
67
+ pagy_data(pagy, :combo, pagy_url_for(pagy, PAGE_TOKEN, **vars))
68
+ }>#{
69
+ bulma_prev_next_html(pagy, a)
70
+ }<ul class="pagination-list"><li class="pagination-link"><label>#{
71
+ pagy_t('pagy.combo_nav_js', page_input:, pages:)
72
+ }</label></li></ul></nav>)
57
73
  end
58
74
 
75
+ private
76
+
77
+ def bulma_prev_next_html(pagy, a)
78
+ html = if (p_prev = pagy.prev)
79
+ a.(p_prev, pagy_t('pagy.prev'), classes: 'pagination-previous', aria_label: pagy_t('pagy.aria_label.prev'))
80
+ else
81
+ %(<a role="link" class="pagination-previous" disabled aria-disabled="true" aria-label="#{
82
+ pagy_t('pagy.aria_label.prev')}">#{pagy_t 'pagy.prev'}</a>)
83
+ end
84
+ html << if (p_next = pagy.next)
85
+ a.(p_next, pagy_t('pagy.next'), classes: 'pagination-next', aria_label: pagy_t('pagy.aria_label.next'))
86
+ else
87
+ %(<a role="link" class="pagination-next" disabled aria-disabled="true" aria-label="#{
88
+ pagy_t('pagy.aria_label.next')}">#{pagy_t('pagy.next')}</a>)
89
+ end
90
+ end
59
91
  end
92
+ Frontend.prepend BulmaExtra
60
93
  end
@@ -0,0 +1,79 @@
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/calendar
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../calendar'
5
+
6
+ class Pagy # :nodoc:
7
+ # Add pagination filtering by calendar unit (:year, :quarter, :month, :week, :day) to the regular pagination
8
+ module CalendarExtra
9
+ # Additions for the Backend module
10
+ module BackendAddOn
11
+ CONF_KEYS = (Calendar::UNITS + %i[pagy active]).freeze
12
+
13
+ private
14
+
15
+ # Take a collection and a conf Hash with keys in CONF_KEYS and return an array with 3 items: [calendar, pagy, results]
16
+ def pagy_calendar(collection, conf)
17
+ raise ArgumentError, "keys must be in #{CONF_KEYS.inspect}" \
18
+ unless conf.is_a?(Hash) && (conf.keys - CONF_KEYS).empty?
19
+
20
+ conf[:pagy] ||= {}
21
+ unless conf.key?(:active) && !conf[:active]
22
+ calendar, from, to = Calendar.send(:init, conf, pagy_calendar_period(collection), params) do |unit, period|
23
+ pagy_calendar_counts(collection, unit, *period) if respond_to?(:pagy_calendar_counts)
24
+ end
25
+ collection = pagy_calendar_filter(collection, from, to)
26
+ end
27
+ pagy, results = send(conf[:pagy][:backend] || :pagy, collection, **conf[:pagy]) # use backend: :pagy when omitted
28
+ [calendar, pagy, results]
29
+ end
30
+
31
+ # This method must be implemented by the application
32
+ def pagy_calendar_period(*)
33
+ raise NoMethodError, 'the pagy_calendar_period method must be implemented by the application ' \
34
+ '(see https://ddnexus.github.io/pagy/docs/extras/calendar/#pagy-calendar-period-collection)'
35
+ end
36
+
37
+ # This method must be implemented by the application
38
+ def pagy_calendar_filter(*)
39
+ raise NoMethodError, 'the pagy_calendar_filter method must be implemented by the application ' \
40
+ '(see https://ddnexus.github.io/pagy/docs/extras/calendar/#pagy-calendar-filter-collection-from-to)'
41
+ end
42
+ end
43
+
44
+ # Override the pagy_anchor
45
+ module FrontendOverride
46
+ # Consider the vars[:counts]
47
+ def pagy_anchor(pagy, anchor_string: nil)
48
+ return super unless (counts = pagy.vars[:counts])
49
+
50
+ anchor_string &&= %( #{anchor_string})
51
+ left, right = %(<a#{anchor_string} href="#{pagy_url_for(pagy, PAGE_TOKEN)}").split(PAGE_TOKEN, 2)
52
+ # lambda used by all the helpers
53
+ lambda do |page, text = pagy.label_for(page), classes: nil, aria_label: nil|
54
+ count = counts[page - 1]
55
+ if count.zero?
56
+ classes = "#{classes && (classes + ' ')}empty-page"
57
+ info_key = 'pagy.info.no_items'
58
+ else
59
+ info_key = 'pagy.info.single_page'
60
+ end
61
+ title = %( title="#{pagy_t(info_key, item_name: pagy_t('pagy.item_name', count:), count:)}")
62
+ classes = %( class="#{classes}") if classes
63
+ aria_label = %( aria-label="#{aria_label}") if aria_label
64
+ %(#{left}#{page}#{right}#{title}#{classes}#{aria_label}>#{text}</a>)
65
+ end
66
+ end
67
+ end
68
+
69
+ # Additions for the Frontend module
70
+ module UrlHelperAddOn
71
+ # Return the url for the calendar page at time
72
+ def pagy_calendar_url_at(calendar, time, **opts)
73
+ pagy_url_for(calendar.send(:calendar_at, time, **opts), 1, **opts)
74
+ end
75
+ end
76
+ end
77
+ Backend.prepend CalendarExtra::BackendAddOn, CalendarExtra::UrlHelperAddOn
78
+ Frontend.prepend CalendarExtra::UrlHelperAddOn, CalendarExtra::FrontendOverride
79
+ end
@@ -1,37 +1,32 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/countless
2
- # encoding: utf-8
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/countless
3
2
  # frozen_string_literal: true
4
3
 
5
- require 'pagy/countless'
4
+ require_relative '../countless'
6
5
 
7
- class Pagy
6
+ class Pagy # :nodoc:
7
+ DEFAULT[:countless_minimal] = false
8
8
 
9
- # used by the items extra
10
- COUNTLESS = true
9
+ # Paginate without the need of any count, saving one query per rendering
10
+ module CountlessExtra
11
+ private
11
12
 
12
- module Backend ; private # the whole module is private so no problem with including it in a controller
13
-
14
- # Return Pagy object and items
15
- def pagy_countless(collection, vars={})
16
- pagy = Pagy::Countless.new(pagy_countless_get_vars(collection, vars))
17
- return pagy, pagy_countless_get_items(collection, pagy)
18
- end
19
-
20
- # Sub-method called only by #pagy_countless: here for easy customization of variables by overriding
21
- def pagy_countless_get_vars(_collection, vars)
22
- vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ]
23
- vars
13
+ # Return Pagy object and records
14
+ def pagy_countless(collection, **vars)
15
+ vars[:limit] ||= pagy_get_limit(vars)
16
+ vars[:page] ||= pagy_get_page(vars)
17
+ pagy = Countless.new(**vars)
18
+ [pagy, pagy_countless_get_items(collection, pagy)]
24
19
  end
25
20
 
26
21
  # Sub-method called only by #pagy_countless: here for easy customization of record-extraction by overriding
22
+ # You may need to override this method for collections without offset|limit
27
23
  def pagy_countless_get_items(collection, pagy)
28
- # This should work with ActiveRecord, Sequel, Mongoid...
29
- items = collection.offset(pagy.offset).limit(pagy.items + 1).to_a
30
- items_size = items.size
31
- items.pop if items_size == pagy.items + 1
32
- pagy.finalize(items_size) # finalize may adjust pagy.items, so must be used after checking the size
33
- items
34
- end
24
+ return collection.offset(pagy.offset).limit(pagy.limit) if pagy.vars[:countless_minimal]
35
25
 
26
+ fetched = collection.offset(pagy.offset).limit(pagy.limit + 1).to_a # eager load limit + 1
27
+ pagy.finalize(fetched.size) # finalize the pagy object
28
+ fetched[0, pagy.limit] # ignore eventual extra item
29
+ end
36
30
  end
31
+ Backend.prepend CountlessExtra
37
32
  end
@@ -1,50 +1,71 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/elasticsearch_rails
2
- # encoding: utf-8
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/elasticsearch_rails
3
2
  # frozen_string_literal: true
4
3
 
5
- require 'pagy/extras/pagy_search'
4
+ class Pagy # :nodoc:
5
+ DEFAULT[:elasticsearch_rails_search] ||= :search
6
+ DEFAULT[:elasticsearch_rails_pagy_search] ||= :pagy_search
6
7
 
7
- class Pagy
8
+ # Paginate ElasticsearchRails response objects
9
+ module ElasticsearchRailsExtra
10
+ module_function
8
11
 
9
- # used by the items extra
10
- ELASTICSEARCH_RAILS = true
11
-
12
- # create a Pagy object from an Elasticsearch::Model::Response::Response object
13
- def self.new_from_elasticsearch_rails(response, vars={})
14
- vars[:items] = response.search.options[:size] || 10
15
- vars[:page] = (response.search.options[:from] || 0) / vars[:items] + 1
16
- total = response.respond_to?(:raw_response) ? response.raw_response['hits']['total'] : response.response['hits']['total']
17
- vars[:count] = total.is_a?(Hash) ? total['value'] : total
18
- new(vars)
19
- end
12
+ # Get the count from different version of ElasticsearchRails
13
+ def total_count(response)
14
+ total = if response.respond_to?(:raw_response)
15
+ response.raw_response['hits']['total']
16
+ else
17
+ response.response['hits']['total']
18
+ end
19
+ total.is_a?(Hash) ? total['value'] : total
20
+ end
20
21
 
21
- # Add specialized backend methods to paginate ElasticsearchRails searches
22
- module Backend ; private
23
-
24
- # Return Pagy object and items
25
- def pagy_elasticsearch_rails(pagy_search_args, vars={})
26
- model, search_args, _block, *called = pagy_search_args
27
- vars = pagy_elasticsearch_rails_get_vars(nil, vars)
28
- search_args[-1][:size] = vars[:items]
29
- search_args[-1][:from] = vars[:items] * (vars[:page] - 1)
30
- response = model.search(*search_args)
31
- total = response.respond_to?(:raw_response) ? response.raw_response['hits']['total'] : response.response['hits']['total']
32
- vars[:count] = total.is_a?(Hash) ? total['value'] : total
33
- pagy = Pagy.new(vars)
34
- # with :last_page overflow we need to re-run the method in order to get the hits
35
- if defined?(OVERFLOW) && pagy.overflow? && pagy.vars[:overflow] == :last_page
36
- return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page))
22
+ module ModelExtension # :nodoc:
23
+ # Return an array used to delay the call of #search
24
+ # after the pagination variables are merged to the options.
25
+ # It also pushes to the same array an optional method call.
26
+ def pagy_elasticsearch_rails(query_or_payload, **options)
27
+ [self, query_or_payload, options].tap do |args|
28
+ args.define_singleton_method(:method_missing) { |*a| args += a }
29
+ end
37
30
  end
38
- return pagy, called.empty? ? response : response.send(*called)
31
+ alias_method DEFAULT[:elasticsearch_rails_pagy_search], :pagy_elasticsearch_rails
39
32
  end
33
+ Pagy::ElasticsearchRails = ModelExtension
40
34
 
41
- # Sub-method called only by #pagy_elasticsearch_rails: here for easy customization of variables by overriding
42
- # the _collection argument is not available when the method is called
43
- def pagy_elasticsearch_rails_get_vars(_collection, vars)
44
- vars[:items] ||= VARS[:items]
45
- vars[:page] ||= (params[ vars[:page_param] || VARS[:page_param] ] || 1).to_i
46
- vars
35
+ # Additions for the Pagy class
36
+ module PagyAddOn
37
+ # Create a Pagy object from an Elasticsearch::Model::Response::Response object
38
+ def new_from_elasticsearch_rails(response, **vars)
39
+ vars[:limit] = response.search.options[:size] || 10
40
+ vars[:page] = ((response.search.options[:from] || 0) / vars[:limit]) + 1
41
+ vars[:count] = ElasticsearchRailsExtra.total_count(response)
42
+ Pagy.new(**vars)
43
+ end
47
44
  end
45
+ Pagy.extend PagyAddOn
46
+
47
+ # Add specialized backend methods to paginate ElasticsearchRails searches
48
+ module BackendAddOn
49
+ private
48
50
 
51
+ # Return Pagy object and records
52
+ def pagy_elasticsearch_rails(pagy_search_args, **vars)
53
+ vars[:page] ||= pagy_get_page(vars)
54
+ vars[:limit] ||= pagy_get_limit(vars)
55
+ model, query_or_payload, options, *called = pagy_search_args
56
+ options[:size] = vars[:limit]
57
+ options[:from] = vars[:limit] * ((vars[:page] || 1) - 1)
58
+ response = model.send(DEFAULT[:elasticsearch_rails_search], query_or_payload, **options)
59
+ vars[:count] = ElasticsearchRailsExtra.total_count(response)
60
+
61
+ pagy = ::Pagy.new(**vars)
62
+ # with :last_page overflow we need to re-run the method in order to get the hits
63
+ return pagy_elasticsearch_rails(pagy_search_args, **vars, page: pagy.page) \
64
+ if defined?(::Pagy::OverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page
65
+
66
+ [pagy, called.empty? ? response : response.send(*called)]
67
+ end
68
+ end
69
+ Backend.prepend BackendAddOn
49
70
  end
50
71
  end