pagy 9.3.3 → 43.0.0.rc1

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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +547 -551
  4. data/apps/demo.ru +221 -178
  5. data/apps/index.rb +3 -1
  6. data/apps/keynav.ru +258 -0
  7. data/apps/{keyset_ar.ru → keyset.ru} +27 -32
  8. data/apps/{keyset_s.ru → keyset_sequel.ru} +24 -29
  9. data/apps/rails.ru +51 -48
  10. data/apps/repro.ru +46 -43
  11. data/bin/pagy +22 -23
  12. data/config/pagy.rb +35 -207
  13. data/javascripts/ai_widget.js +76 -0
  14. data/javascripts/pagy.js +151 -0
  15. data/javascripts/pagy.js.map +10 -0
  16. data/javascripts/pagy.min.js +1 -4
  17. data/javascripts/pagy.mjs +111 -63
  18. data/javascripts/wand.js +1166 -0
  19. data/lib/pagy/classes/calendar/calendar.rb +98 -0
  20. data/lib/pagy/{calendar → classes/calendar}/day.rb +7 -11
  21. data/lib/pagy/{calendar → classes/calendar}/month.rb +5 -10
  22. data/lib/pagy/{calendar → classes/calendar}/quarter.rb +10 -15
  23. data/lib/pagy/classes/calendar/unit.rb +92 -0
  24. data/lib/pagy/{calendar → classes/calendar}/week.rb +5 -9
  25. data/lib/pagy/{calendar → classes/calendar}/year.rb +5 -6
  26. data/lib/pagy/classes/exceptions.rb +33 -0
  27. data/lib/pagy/classes/keyset/active_record.rb +11 -0
  28. data/lib/pagy/classes/keyset/adapters/active_record.rb +50 -0
  29. data/lib/pagy/classes/keyset/adapters/sequel.rb +63 -0
  30. data/lib/pagy/classes/keyset/keynav/active_record.rb +13 -0
  31. data/lib/pagy/classes/keyset/keynav/sequel.rb +13 -0
  32. data/lib/pagy/classes/keyset/keynav.rb +77 -0
  33. data/lib/pagy/classes/keyset/keyset.rb +126 -0
  34. data/lib/pagy/classes/keyset/sequel.rb +11 -0
  35. data/lib/pagy/classes/offset/countless.rb +56 -0
  36. data/lib/pagy/classes/offset/offset.rb +54 -0
  37. data/lib/pagy/classes/offset/search.rb +38 -0
  38. data/lib/pagy/classes/request.rb +36 -0
  39. data/lib/pagy/modules/abilities/configurable.rb +36 -0
  40. data/lib/pagy/modules/abilities/linkable.rb +59 -0
  41. data/lib/pagy/modules/abilities/rangeable.rb +15 -0
  42. data/lib/pagy/modules/abilities/shiftable.rb +12 -0
  43. data/lib/pagy/{b64.rb → modules/b64.rb} +3 -7
  44. data/lib/pagy/modules/console.rb +38 -0
  45. data/lib/pagy/modules/i18n/i18n.rb +48 -0
  46. data/lib/pagy/modules/i18n/p11n/arabic.rb +29 -0
  47. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +26 -0
  48. data/lib/pagy/modules/i18n/p11n/one_other.rb +15 -0
  49. data/lib/pagy/modules/i18n/p11n/one_upto_two_other.rb +15 -0
  50. data/lib/pagy/modules/i18n/p11n/other.rb +13 -0
  51. data/lib/pagy/modules/i18n/p11n/polish.rb +26 -0
  52. data/lib/pagy/modules/i18n/p11n/west_slavic.rb +22 -0
  53. data/lib/pagy/modules/i18n/p11n.rb +16 -0
  54. data/lib/pagy/modules/searcher.rb +20 -0
  55. data/lib/pagy/toolbox/helpers/anchor_tags.rb +25 -0
  56. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +24 -0
  57. data/lib/pagy/toolbox/helpers/bootstrap/previous_next_html.rb +18 -0
  58. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +29 -0
  59. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +21 -0
  60. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +21 -0
  61. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +19 -0
  62. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +28 -0
  63. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +20 -0
  64. data/lib/pagy/toolbox/helpers/data_hash.rb +26 -0
  65. data/lib/pagy/toolbox/helpers/headers_hash.rb +20 -0
  66. data/lib/pagy/toolbox/helpers/info_tag.rb +26 -0
  67. data/lib/pagy/toolbox/helpers/input_nav_js.rb +19 -0
  68. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +23 -0
  69. data/lib/pagy/toolbox/helpers/loader.rb +33 -0
  70. data/lib/pagy/toolbox/helpers/page_url.rb +23 -0
  71. data/lib/pagy/toolbox/helpers/series_nav.rb +29 -0
  72. data/lib/pagy/toolbox/helpers/series_nav_js.rb +19 -0
  73. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +34 -0
  74. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +15 -0
  75. data/lib/pagy/toolbox/helpers/support/nav_aria_label_attribute.rb +12 -0
  76. data/lib/pagy/toolbox/helpers/support/series.rb +38 -0
  77. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +20 -0
  78. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +17 -0
  79. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +36 -0
  80. data/lib/pagy/toolbox/helpers/urls_hash.rb +13 -0
  81. data/lib/pagy/toolbox/paginators/calendar.rb +30 -0
  82. data/lib/pagy/toolbox/paginators/countless.rb +25 -0
  83. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +32 -0
  84. data/lib/pagy/toolbox/paginators/keynav_js.rb +29 -0
  85. data/lib/pagy/toolbox/paginators/keyset.rb +18 -0
  86. data/lib/pagy/toolbox/paginators/meilisearch.rb +32 -0
  87. data/lib/pagy/toolbox/paginators/method.rb +26 -0
  88. data/lib/pagy/toolbox/paginators/offset.rb +33 -0
  89. data/lib/pagy/toolbox/paginators/searchkick.rb +32 -0
  90. data/lib/pagy.rb +59 -97
  91. data/locales/ar.yml +9 -6
  92. data/locales/be.yml +9 -6
  93. data/locales/bg.yml +9 -6
  94. data/locales/bs.yml +9 -6
  95. data/locales/ca.yml +9 -6
  96. data/locales/ckb.yml +8 -6
  97. data/locales/cs.yml +9 -6
  98. data/locales/da.yml +9 -6
  99. data/locales/de.yml +9 -6
  100. data/locales/dz.yml +9 -6
  101. data/locales/en.yml +9 -6
  102. data/locales/es.yml +9 -6
  103. data/locales/fr.yml +9 -6
  104. data/locales/hr.yml +9 -6
  105. data/locales/id.yml +9 -6
  106. data/locales/it.yml +9 -6
  107. data/locales/ja.yml +9 -6
  108. data/locales/km.yml +9 -6
  109. data/locales/ko.yml +9 -6
  110. data/locales/nb.yml +9 -6
  111. data/locales/nl.yml +9 -6
  112. data/locales/nn.yml +9 -6
  113. data/locales/pl.yml +9 -6
  114. data/locales/pt-BR.yml +10 -7
  115. data/locales/pt.yml +10 -7
  116. data/locales/ru.yml +9 -6
  117. data/locales/sr.yml +9 -6
  118. data/locales/sv-SE.yml +9 -6
  119. data/locales/sv.yml +9 -6
  120. data/locales/sw.yml +12 -9
  121. data/locales/ta.yml +12 -9
  122. data/locales/tr.yml +9 -6
  123. data/locales/uk.yml +9 -6
  124. data/locales/vi.yml +9 -6
  125. data/locales/zh-CN.yml +9 -6
  126. data/locales/zh-HK.yml +9 -6
  127. data/locales/zh-TW.yml +9 -6
  128. data/stylesheets/pagy-tailwind.css +64 -0
  129. data/stylesheets/pagy.css +63 -27
  130. metadata +114 -56
  131. data/javascripts/pagy.min.js.map +0 -10
  132. data/lib/pagy/backend.rb +0 -44
  133. data/lib/pagy/calendar/unit.rb +0 -103
  134. data/lib/pagy/calendar.rb +0 -84
  135. data/lib/pagy/console.rb +0 -23
  136. data/lib/pagy/countless.rb +0 -38
  137. data/lib/pagy/exceptions.rb +0 -25
  138. data/lib/pagy/extras/arel.rb +0 -28
  139. data/lib/pagy/extras/array.rb +0 -19
  140. data/lib/pagy/extras/bootstrap.rb +0 -97
  141. data/lib/pagy/extras/bulma.rb +0 -93
  142. data/lib/pagy/extras/calendar.rb +0 -79
  143. data/lib/pagy/extras/countless.rb +0 -32
  144. data/lib/pagy/extras/elasticsearch_rails.rb +0 -71
  145. data/lib/pagy/extras/gearbox.rb +0 -55
  146. data/lib/pagy/extras/headers.rb +0 -54
  147. data/lib/pagy/extras/i18n.rb +0 -26
  148. data/lib/pagy/extras/js_tools.rb +0 -70
  149. data/lib/pagy/extras/jsonapi.rb +0 -86
  150. data/lib/pagy/extras/keyset.rb +0 -30
  151. data/lib/pagy/extras/limit.rb +0 -63
  152. data/lib/pagy/extras/meilisearch.rb +0 -57
  153. data/lib/pagy/extras/metadata.rb +0 -42
  154. data/lib/pagy/extras/overflow.rb +0 -81
  155. data/lib/pagy/extras/pagy.rb +0 -82
  156. data/lib/pagy/extras/searchkick.rb +0 -59
  157. data/lib/pagy/extras/size.rb +0 -40
  158. data/lib/pagy/extras/standalone.rb +0 -60
  159. data/lib/pagy/extras/trim.rb +0 -29
  160. data/lib/pagy/frontend.rb +0 -99
  161. data/lib/pagy/i18n.rb +0 -166
  162. data/lib/pagy/keyset/active_record.rb +0 -38
  163. data/lib/pagy/keyset/sequel.rb +0 -51
  164. data/lib/pagy/keyset.rb +0 -118
  165. data/lib/pagy/shared_methods.rb +0 -26
  166. data/lib/pagy/url_helpers.rb +0 -26
  167. data/stylesheets/pagy.scss +0 -48
  168. data/stylesheets/pagy.tailwind.css +0 -21
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module I18n
5
+ module P11n
6
+ module Polish
7
+ module_function
8
+
9
+ def plural_for(n = 0)
10
+ mod10 = n % 10
11
+ mod100 = n % 100
12
+ case
13
+ when n == 1
14
+ :one
15
+ when [2, 3, 4].include?(mod10) && ![12, 13, 14].include?(mod100)
16
+ :few
17
+ when [0, 1, 5, 6, 7, 8, 9].include?(mod10) || [12, 13, 14].include?(mod100)
18
+ :many
19
+ else
20
+ :other
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module I18n
5
+ module P11n
6
+ module WestSlavic
7
+ module_function
8
+
9
+ def plural_for(n = 0)
10
+ case n
11
+ when 1
12
+ :one
13
+ when 2, 3, 4
14
+ :few
15
+ else
16
+ :other
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module I18n
5
+ module P11n
6
+ path = ROOT.join('lib/pagy/modules/i18n/p11n')
7
+ autoload :Arabic, path.join('arabic')
8
+ autoload :EastSlavic, path.join('east_slavic')
9
+ autoload :OneOther, path.join('one_other')
10
+ autoload :OneUptoTwoOther, path.join('one_upto_two_other')
11
+ autoload :Other, path.join('other')
12
+ autoload :Polish, path.join('polish')
13
+ autoload :WestSlavic, path.join('west_slavic')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Relegate internal functions. Make overriding search classes easier.
5
+ module Searcher
6
+ module_function
7
+
8
+ # Common search logic
9
+ def wrap(context, pagy_search_args, options)
10
+ context.instance_exec do
11
+ options[:request] = Request.new(options[:request] || request, options)
12
+ options[:page] ||= options[:request].resolve_page(options)
13
+ options[:limit] ||= options[:request].resolve_limit(options)
14
+ end
15
+ pagy, results = yield
16
+ calling = pagy_search_args[4..]
17
+ [pagy, calling.empty? ? results : results.send(*calling)]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/a_lambda' # inheritable
4
+
5
+ class Pagy
6
+ # Return the enabled/disabled previous page anchor tag
7
+ def previous_tag(a = nil, text: I18n.translate('pagy.previous'),
8
+ aria_label: I18n.translate('pagy.aria_label.previous'), **)
9
+ if @previous
10
+ (a || a_lambda(**)).(@previous, text, aria_label:)
11
+ else
12
+ %(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
13
+ end
14
+ end
15
+
16
+ # Return the enabled/disabled next page anchor tag
17
+ def next_tag(a = nil, text: I18n.translate('pagy.next'),
18
+ aria_label: I18n.translate('pagy.aria_label.next'), **)
19
+ if @next
20
+ (a || a_lambda(**)).(@next, text, aria_label:)
21
+ else
22
+ %(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'previous_next_html'
4
+ require_relative '../support/wrap_input_nav_js'
5
+
6
+ class Pagy
7
+ private
8
+
9
+ # Javascript combo pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
10
+ def bootstrap_input_nav_js(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+ input = %(<input name="page" type="number" min="1" max="#{last}" value="#{@page}" aria-current="page" ) +
13
+ %(style="text-align: center; width: #{@page.to_s.length + 1}rem; padding: 0; border-radius: .25rem; ) +
14
+ %(border: none; display: inline-block;" class="page-link active">#{A_TAG})
15
+ html = %(<ul class="#{classes}">#{
16
+ bootstrap_html_for(:previous, a_lambda)
17
+ }<li class="page-item"><label class="page-link">#{
18
+ I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)
19
+ }</label></li>#{
20
+ bootstrap_html_for(:next, a_lambda)
21
+ }</ul>)
22
+ wrap_input_nav_js(html, 'pagy-bootstrap input-nav-js', **)
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ private
5
+
6
+ # Return the enabled/disabled previous/next page anchor tag, embedded in the li tag
7
+ def bootstrap_html_for(which, a_lambda)
8
+ if send(which)
9
+ %(<li class="page-item #{which}">#{
10
+ a_lambda.(send(which), I18n.translate("pagy.#{which}"),
11
+ classes: 'page-link',
12
+ aria_label: I18n.translate("pagy.aria_label.#{which}"))}</li>)
13
+ else
14
+ %(<li class="page-item #{which} disabled"><a role="link" class="page-link" aria-disabled="true" aria-label="#{
15
+ I18n.translate("pagy.aria_label.#{which}")}">#{I18n.translate("pagy.#{which}")}</a></li>)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'previous_next_html'
4
+ require_relative '../support/wrap_series_nav'
5
+
6
+ class Pagy
7
+ private
8
+
9
+ # Pagination for bootstrap: it returns the html with the series of links to the pages
10
+ def bootstrap_series_nav(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+ html = %(<ul class="#{classes}">#{bootstrap_html_for(:previous, a_lambda)})
13
+ series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
14
+ html << case item
15
+ when Integer
16
+ %(<li class="page-item">#{a_lambda.(item, classes: 'page-link')}</li>)
17
+ when String
18
+ %(<li class="page-item active"><a role="link" class="page-link" aria-current="page" aria-disabled="true">#{
19
+ page_label(item)}</a></li>)
20
+ when :gap
21
+ %(<li class="page-item gap disabled"><a role="link" class="page-link" aria-disabled="true">#{
22
+ I18n.translate('pagy.gap')}</a></li>)
23
+ else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
24
+ end
25
+ end
26
+ html << %(#{bootstrap_html_for(:next, a_lambda)}</ul>)
27
+ wrap_series_nav(html, 'pagy-bootstrap series-nav', **)
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'previous_next_html'
4
+ require_relative '../support/wrap_series_nav_js'
5
+
6
+ class Pagy
7
+ private
8
+
9
+ # Javascript pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
10
+ def bootstrap_series_nav_js(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+ tokens = { before: %(<ul class="#{classes}">#{bootstrap_html_for(:previous, a_lambda)}),
13
+ anchor: %(<li class="page-item">#{a_lambda.(PAGE_TOKEN, LABEL_TOKEN, classes: 'page-link')}</li>),
14
+ current: %(<li class="page-item active"><a role="link" class="page-link" ) +
15
+ %(aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a></li>),
16
+ gap: %(<li class="page-item gap disabled"><a role="link" class="page-link" aria-disabled="true">#{
17
+ I18n.translate('pagy.gap')}</a></li>),
18
+ after: %(#{bootstrap_html_for(:next, a_lambda)}</ul>) }
19
+ wrap_series_nav_js(tokens, 'pagy-bootstrap series-nav-js', **)
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'previous_next_html'
4
+ require_relative '../support/wrap_input_nav_js'
5
+
6
+ class Pagy
7
+ private
8
+
9
+ # Javascript combo pagination for bulma: it returns a nav with a data-pagy attribute used by the pagy.js file
10
+ def bulma_input_nav_js(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+ input = %(<input name="page" type="number" min="1" max="#{@last}" value="#{@page}" aria-current="page") +
13
+ %(style="text-align: center; width: #{@page.to_s.length + 1}rem; line-height: 1.2rem; ) +
14
+ %(border: none; border-radius: .25rem; padding: .0625rem; color: white; ) +
15
+ %(background-color: #485fc7;">#{A_TAG})
16
+ html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)}<li class="pagination-link"><label>#{
17
+ I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)
18
+ }</label></li>#{bulma_html_for(:next, a_lambda)}</ul>)
19
+ wrap_input_nav_js(html, "pagy-bulma input-nav-js #{classes}", **)
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ private
5
+
6
+ # Return the enabled/disabled previous/next page anchor tag
7
+ def bulma_html_for(which, a_lambda)
8
+ %(<li>#{
9
+ if send(which)
10
+ a_lambda.(send(which), I18n.translate("pagy.#{which}"),
11
+ classes: "pagination-#{which}",
12
+ aria_label: I18n.translate("pagy.aria_label.#{which}"))
13
+ else
14
+ %(<a role="link" class="pagination-#{which}" disabled aria-disabled="true" aria-label="#{
15
+ I18n.translate("pagy.aria_label.#{which}")}">#{I18n.translate("pagy.#{which}")}</a>)
16
+ end
17
+ }</li>)
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'previous_next_html'
4
+ require_relative '../support/wrap_series_nav'
5
+
6
+ class Pagy
7
+ private
8
+
9
+ # Pagination for bulma: it returns the html with the series of links to the pages
10
+ def bulma_series_nav(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+ html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)})
13
+ series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
14
+ html << case item
15
+ when Integer
16
+ %(<li>#{a_lambda.(item, page_label(item), classes: 'pagination-link')}</li>)
17
+ when String
18
+ %(<li><a role="link" class="pagination-link is-current" aria-current="page" aria-disabled="true">#{
19
+ page_label(item)}</a></li>)
20
+ when :gap
21
+ %(<li><span class="pagination-ellipsis">#{I18n.translate('pagy.gap')}</span></li>)
22
+ else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
23
+ end
24
+ end
25
+ html << %(#{bulma_html_for(:next, a_lambda)}</ul>)
26
+ wrap_series_nav(html, "pagy-bulma series-nav #{classes}", **)
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'previous_next_html'
4
+ require_relative '../support/wrap_series_nav_js'
5
+
6
+ class Pagy
7
+ private
8
+
9
+ # Javascript pagination for bulma: it returns a nav with a data-pagy attribute used by the Pagy.nav javascript
10
+ def bulma_series_nav_js(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+ tokens = { before: %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)}),
13
+ anchor: %(<li>#{a_lambda.(PAGE_TOKEN, LABEL_TOKEN, classes: 'pagination-link')}</li>),
14
+ current: %(<li><a role="link" class="pagination-link is-current" ) +
15
+ %(aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a></li>),
16
+ gap: %(<li><span class="pagination-ellipsis">#{I18n.translate('pagy.gap')}</span></li>),
17
+ after: %(#{bulma_html_for(:next, a_lambda)}</ul>) }
18
+ wrap_series_nav_js(tokens, "pagy-bulma series-nav-js #{classes}", **)
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Generate a hash of the wanted internal data
5
+ def data_hash(data_keys: @options[:data_keys] ||
6
+ %i[url_template first_url previous_url page_url next_url last_url
7
+ count page limit last in from to previous next options], **)
8
+ data_keys -= %i[count limit] if calendar?
9
+ url_template = compose_page_url(PAGE_TOKEN, **)
10
+ {}.tap do |data|
11
+ data_keys.each do |key|
12
+ data[key] = case key
13
+ when :url_template then url_template
14
+ when :first_url then compose_page_url(nil, **)
15
+ when :previous_url then url_template.sub(PAGE_TOKEN, @previous.to_s)
16
+ when :page_url then url_template.sub(PAGE_TOKEN, @page.to_s)
17
+ when :next_url then url_template.sub(PAGE_TOKEN, @next.to_s)
18
+ when :last_url then url_template.sub(PAGE_TOKEN, @last.to_s)
19
+ else send(key)
20
+ end
21
+ rescue NoMethodError
22
+ raise OptionError.new(self, :data, 'to contain known keys', key)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'urls_hash'
4
+
5
+ # Add pagination response headers
6
+ class Pagy
7
+ # Generate a hash of RFC-8288-compliant http headers
8
+ def headers_hash(headers_map: @options[:headers_map] ||
9
+ { page: 'current-page', limit: 'page-limit', count: 'total-count', pages: 'total-pages' }, **)
10
+ links = urls_hash(**, absolute: true).map { |key, url| %(<#{url}>; rel="#{key}") }.join(', ')
11
+ { 'link' => links }.tap do |hash|
12
+ hash[headers_map[:page]] = @page.to_s if @page && headers_map[:page]
13
+ hash[headers_map[:limit]] = @limit.to_s if headers_map[:limit] && !calendar?
14
+ return hash unless @count
15
+
16
+ hash[headers_map[:pages]] = @last.to_s if headers_map[:pages]
17
+ hash[headers_map[:count]] = @count.to_s if headers_map[:count]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Instances with count return "Displaying items 41-60 of 324 in total" or "Displaying Products 41-60 of 324 in total"
5
+ # Instances with no count return only page info: "Page 3 of 100"
6
+ def info_tag(id: nil, item_name: nil)
7
+ i18n_key = if @count.nil?
8
+ 'pagy.info_tag.no_count'
9
+ elsif @count.zero?
10
+ 'pagy.info_tag.no_items'
11
+ elsif @last == 1
12
+ 'pagy.info_tag.single_page'
13
+ else
14
+ 'pagy.info_tag.multiple_pages'
15
+ end
16
+ info_data = if @count.nil?
17
+ { page: @page, pages: @last }
18
+ else
19
+ { item_name: item_name || I18n.translate('pagy.item_name', count: @count),
20
+ count: @count,
21
+ from: @from,
22
+ to: @to }
23
+ end
24
+ %(<span#{%( id="#{id}") if id} class="pagy info">#{I18n.translate(i18n_key, **info_data)}</span>)
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/wrap_input_nav_js'
4
+
5
+ class Pagy
6
+ # Javascript combo pagination: it returns a nav with a data-pagy attribute used by the pagy.js file
7
+ def input_nav_js(style = nil, **)
8
+ return send(:"#{style}_input_nav_js", **) if style
9
+
10
+ a_lambda = a_lambda(**)
11
+ input = %(<input name="page" type="number" min="1" max="#{@last}" value="#{@page}" aria-current="page" ) +
12
+ %(style="text-align: center; width: #{@page.to_s.length + 1}rem; padding: 0;">#{A_TAG})
13
+ html = %(#{previous_tag(a_lambda)}<label>#{
14
+ I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)}</label>#{
15
+ next_tag(a_lambda)})
16
+ wrap_input_nav_js(html, 'pagy input-nav-js', **)
17
+ end
18
+ alias pagy_input_nav_js input_nav_js
19
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/data_pagy_attribute'
4
+
5
+ class Pagy
6
+ # Return the limit selector HTML. For example "Show [20] items per page"
7
+ def limit_tag_js(id: nil, item_name: nil, client_max_limit: @options[:client_max_limit], **)
8
+ raise OptionError.new(self, :client_max_limit, 'to be truthy', client_max_limit) unless client_max_limit
9
+
10
+ url_token = compose_page_url(PAGE_TOKEN, limit_token: LIMIT_TOKEN)
11
+ limit_input = %(<input name="limit" type="number" min="1" max="#{@options[:client_max_limit]}" value="#{
12
+ @limit}" style="padding: 0; text-align: center; width: #{@limit.to_s.length + 1}rem;">#{A_TAG})
13
+
14
+ %(<span#{%( id="#{id}") if id} class="pagy limit-tag-js" #{
15
+ data_pagy_attribute(:ltj, @from, url_token)
16
+ }><label>#{
17
+ I18n.translate('pagy.limit_tag_js',
18
+ item_name: item_name || I18n.translate('pagy.item_name', count: @limit),
19
+ limit_input:,
20
+ count: @limit)
21
+ }</label></span>)
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module Loader
5
+ paths = { public: { page_url: 'page_url',
6
+ data_hash: 'data_hash',
7
+ headers_hash: 'headers_hash',
8
+ urls_hash: 'urls_hash',
9
+ next_tag: 'anchor_tags',
10
+ previous_tag: 'anchor_tags',
11
+ input_nav_js: 'input_nav_js',
12
+ info_tag: 'info_tag',
13
+ limit_tag_js: 'limit_tag_js',
14
+ series_nav: 'series_nav',
15
+ series_nav_js: 'series_nav_js' },
16
+ protected: { bootstrap_series_nav: 'bootstrap/series_nav',
17
+ bootstrap_series_nav_js: 'bootstrap/series_nav_js',
18
+ bootstrap_input_nav_js: 'bootstrap/input_nav_js',
19
+ bulma_series_nav: 'bulma/series_nav',
20
+ bulma_series_nav_js: 'bulma/series_nav_js',
21
+ bulma_input_nav_js: 'bulma/input_nav_js' } }.freeze
22
+
23
+ paths.each do |visibility, methods|
24
+ send(visibility)
25
+ # Load the method, overriding its own alias. Next requests will call the method directly.
26
+ define_method(:"load_#{visibility}") do |*args, **kwargs|
27
+ require_relative methods[__callee__]
28
+ send(__callee__, *args, **kwargs)
29
+ end
30
+ methods.each_key { |method| alias_method method, :"load_#{visibility}" }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Return the page url for any page
5
+ # :nocov:
6
+ def page_url(page, **)
7
+ case page
8
+ when :first
9
+ compose_page_url(nil, **)
10
+ when :current
11
+ compose_page_url(@page, **) if @page
12
+ when :previous
13
+ compose_page_url(@previous, **) if @previous
14
+ when :next
15
+ compose_page_url(@next, **) if @next
16
+ when :last
17
+ compose_page_url(@last, **) if @last
18
+ when Integer, String
19
+ compose_page_url(page, **)
20
+ end
21
+ end
22
+ # :nocov:
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/wrap_series_nav'
4
+
5
+ class Pagy
6
+ # Return the html with the series of links to the pages
7
+ def series_nav(style = nil, **)
8
+ return send(:"#{style}_series_nav", **) if style
9
+
10
+ a_lambda = a_lambda(**)
11
+ html = previous_tag(a_lambda)
12
+ series(**).each do |item|
13
+ # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
14
+ html << case item
15
+ when Integer
16
+ a_lambda.(item)
17
+ when String
18
+ %(<a role="link" aria-disabled="true" aria-current="page">#{page_label(item)}</a>)
19
+ when :gap
20
+ %(<a role="separator" aria-disabled="true">#{I18n.translate('pagy.gap')}</a>)
21
+ else
22
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
23
+ end
24
+ end
25
+ html << next_tag(a_lambda)
26
+ wrap_series_nav(html, 'pagy series-nav', **)
27
+ end
28
+ alias pagy_series_nav series_nav
29
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/wrap_series_nav_js'
4
+
5
+ class Pagy
6
+ # Return a nav with a data-pagy attribute used by the pagy.js file
7
+ def series_nav_js(style = nil, **)
8
+ return send(:"#{style}_series_nav_js", **) if style
9
+
10
+ a_lambda = a_lambda(**)
11
+ tokens = { before: previous_tag(a_lambda),
12
+ anchor: a_lambda.(PAGE_TOKEN, LABEL_TOKEN),
13
+ current: %(<a role="link" aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a>),
14
+ gap: %(<a role="separator" aria-disabled="true">#{I18n.translate('pagy.gap')}</a>),
15
+ after: next_tag(a_lambda) }
16
+ wrap_series_nav_js(tokens, 'pagy series-nav-js', **)
17
+ end
18
+ alias pagy_series_nav_js series_nav_js
19
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ protected
5
+
6
+ # Label for any page. Allow the customization of the output
7
+ def page_label(page, **options)
8
+ return page.to_s unless calendar?
9
+
10
+ options[:format] ||= @options[:format]
11
+ localize(starting_time_for(page.to_i), **options) # page could be a string
12
+ end
13
+
14
+ # Return a performance optimized lambda to generate the anchor tag
15
+ # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
16
+ def a_lambda(**)
17
+ left, right = %(<a href="#{compose_page_url(PAGE_TOKEN, **)}").split(PAGE_TOKEN, 2)
18
+
19
+ lambda do |page, text = page_label(page), classes: nil, aria_label: nil|
20
+ title = if (counts = @options[:counts]) # only for calendar + counts
21
+ count = counts[page - 1]
22
+ classes = classes ? "#{classes} empty-page" : 'empty-page' if count.zero?
23
+ info_key = count.zero? ? 'pagy.info_tag.no_items' : 'pagy.info_tag.single_page'
24
+ %( title="#{I18n.translate(info_key, item_name: I18n.translate('pagy.item_name', count:), count:)}")
25
+ end
26
+ rel = case page
27
+ when @previous then %( rel="prev")
28
+ when @next then %( rel="next")
29
+ end
30
+ %(#{left}#{page}#{right}#{title}#{
31
+ %( class="#{classes}") if classes}#{rel}#{%( aria-label="#{aria_label}") if aria_label}>#{text}</a>)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative '../../../modules/b64'
5
+
6
+ # Relegate internal functions. Make overriding navs easier.
7
+ class Pagy
8
+ private
9
+
10
+ # Compose the data-pagy attribute, with the base64 encoded JSON-serialized args. Use the faster oj gem if defined.
11
+ def data_pagy_attribute(*args)
12
+ data = defined?(Oj) ? Oj.dump(args, mode: :compat) : JSON.dump(args)
13
+ %(data-pagy="#{B64.encode(data)}")
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Relegate internal functions. Make overriding navs easier.
4
+ class Pagy
5
+ private
6
+
7
+ # Compose the aria label attribute for the nav
8
+ def nav_aria_label_attribute(aria_label: nil)
9
+ aria_label ||= I18n.translate('pagy.aria_label.nav', count: @last)
10
+ %(aria-label="#{aria_label}")
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ SERIES_SLOTS = 7
5
+
6
+ protected
7
+
8
+ # Return the array of page numbers and :gap e.g. [1, :gap, 8, "9", 10, :gap, 36]
9
+ def series(slots: @options[:slots] || SERIES_SLOTS, compact: @options[:compact], **)
10
+ raise OptionError.new(self, :slots, 'to be an Integer >= 0', slots) \
11
+ unless slots.is_a?(Integer) && slots >= 0
12
+ return [] if slots.zero?
13
+
14
+ [].tap do |series|
15
+ if slots >= @last
16
+ series.push(*1..@last)
17
+ else
18
+ half = (slots - 1) / 2 # the left half might be 1 page shorter when the slots are even
19
+ start = if @page <= half # @page in the first half
20
+ 1
21
+ elsif @page > (@last - slots + half) # @page in the last half
22
+ @last - slots + 1
23
+ else # @page in the middle
24
+ @page - half
25
+ end
26
+ series.push(*(start...(start + slots)))
27
+ unless compact || slots < SERIES_SLOTS # Set first, last and :gap when needed
28
+ series[0] = 1
29
+ series[1] = :gap unless series[1] == 2
30
+ series[-2] = :gap unless series[-2] == @last - 1
31
+ series[-1] = @last
32
+ end
33
+ end
34
+ current = series.index(@page)
35
+ series[current] = @page.to_s if current
36
+ end
37
+ end
38
+ end