pagy 7.0.11 → 43.5.1

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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +741 -0
  4. data/apps/demo.ru +513 -0
  5. data/apps/enable_rails_page_segment.rb +54 -0
  6. data/apps/index.rb +9 -0
  7. data/apps/keynav+root_key.ru +316 -0
  8. data/apps/keynav.ru +255 -0
  9. data/apps/keyset.ru +219 -0
  10. data/apps/keyset_sequel.ru +212 -0
  11. data/apps/rails.ru +216 -0
  12. data/apps/repro.ru +185 -0
  13. data/bin/pagy +5 -0
  14. data/config/pagy.rb +46 -0
  15. data/javascripts/ai_widget.js +90 -0
  16. data/javascripts/pagy.js +168 -0
  17. data/javascripts/pagy.min.js +2 -0
  18. data/javascripts/pagy.mjs +161 -0
  19. data/javascripts/wand.js +1172 -0
  20. data/lib/pagy/classes/calendar/calendar.rb +101 -0
  21. data/lib/pagy/{calendar → classes/calendar}/day.rb +9 -12
  22. data/lib/pagy/{calendar → classes/calendar}/month.rb +7 -11
  23. data/lib/pagy/{calendar → classes/calendar}/quarter.rb +12 -16
  24. data/lib/pagy/classes/calendar/unit.rb +93 -0
  25. data/lib/pagy/{calendar → classes/calendar}/week.rb +7 -11
  26. data/lib/pagy/{calendar → classes/calendar}/year.rb +9 -9
  27. data/lib/pagy/classes/exceptions.rb +26 -0
  28. data/lib/pagy/classes/keyset/adapters/active_record.rb +50 -0
  29. data/lib/pagy/classes/keyset/adapters/sequel.rb +62 -0
  30. data/lib/pagy/classes/keyset/keynav.rb +85 -0
  31. data/lib/pagy/classes/keyset/keyset.rb +150 -0
  32. data/lib/pagy/classes/offset/countish.rb +17 -0
  33. data/lib/pagy/classes/offset/countless.rb +63 -0
  34. data/lib/pagy/classes/offset/offset.rb +63 -0
  35. data/lib/pagy/classes/offset/search.rb +34 -0
  36. data/lib/pagy/classes/request.rb +48 -0
  37. data/lib/pagy/cli.rb +122 -0
  38. data/lib/pagy/console.rb +5 -20
  39. data/lib/pagy/deprecated.rb +84 -0
  40. data/lib/pagy/modules/abilities/configurable.rb +37 -0
  41. data/lib/pagy/modules/abilities/countable.rb +23 -0
  42. data/lib/pagy/modules/abilities/linkable.rb +72 -0
  43. data/lib/pagy/modules/abilities/rangeable.rb +14 -0
  44. data/lib/pagy/modules/abilities/shiftable.rb +12 -0
  45. data/lib/pagy/modules/b64.rb +35 -0
  46. data/lib/pagy/modules/console.rb +33 -0
  47. data/lib/pagy/modules/i18n/i18n.rb +72 -0
  48. data/lib/pagy/modules/i18n/p11n/arabic.rb +30 -0
  49. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +27 -0
  50. data/lib/pagy/modules/i18n/p11n/one_other.rb +15 -0
  51. data/lib/pagy/modules/i18n/p11n/one_upto_two_other.rb +15 -0
  52. data/lib/pagy/modules/i18n/p11n/other.rb +13 -0
  53. data/lib/pagy/modules/i18n/p11n/polish.rb +27 -0
  54. data/lib/pagy/modules/i18n/p11n/west_slavic.rb +22 -0
  55. data/lib/pagy/modules/i18n/p11n.rb +16 -0
  56. data/lib/pagy/modules/searcher.rb +20 -0
  57. data/lib/pagy/next.rb +25 -0
  58. data/lib/pagy/tasks/sync.rb +20 -0
  59. data/lib/pagy/toolbox/helpers/anchor_tags.rb +21 -0
  60. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +28 -0
  61. data/lib/pagy/toolbox/helpers/bootstrap/previous_next_html.rb +19 -0
  62. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +32 -0
  63. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +24 -0
  64. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +25 -0
  65. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +20 -0
  66. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +31 -0
  67. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +23 -0
  68. data/lib/pagy/toolbox/helpers/data_hash.rb +29 -0
  69. data/lib/pagy/toolbox/helpers/headers_hash.rb +30 -0
  70. data/lib/pagy/toolbox/helpers/info_tag.rb +30 -0
  71. data/lib/pagy/toolbox/helpers/input_nav_js.rb +22 -0
  72. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +25 -0
  73. data/lib/pagy/toolbox/helpers/loaders.rb +55 -0
  74. data/lib/pagy/toolbox/helpers/page_url.rb +16 -0
  75. data/lib/pagy/toolbox/helpers/series_nav.rb +30 -0
  76. data/lib/pagy/toolbox/helpers/series_nav_js.rb +20 -0
  77. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +36 -0
  78. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +19 -0
  79. data/lib/pagy/toolbox/helpers/support/nav_aria_label_attribute.rb +10 -0
  80. data/lib/pagy/toolbox/helpers/support/series.rb +37 -0
  81. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +19 -0
  82. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +17 -0
  83. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +42 -0
  84. data/lib/pagy/toolbox/helpers/urls_hash.rb +12 -0
  85. data/lib/pagy/toolbox/paginators/calendar.rb +34 -0
  86. data/lib/pagy/toolbox/paginators/countish.rb +38 -0
  87. data/lib/pagy/toolbox/paginators/countless.rb +22 -0
  88. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +56 -0
  89. data/lib/pagy/toolbox/paginators/keynav_js.rb +26 -0
  90. data/lib/pagy/toolbox/paginators/keyset.rb +15 -0
  91. data/lib/pagy/toolbox/paginators/meilisearch.rb +34 -0
  92. data/lib/pagy/toolbox/paginators/method.rb +38 -0
  93. data/lib/pagy/toolbox/paginators/offset.rb +24 -0
  94. data/lib/pagy/toolbox/paginators/searchkick.rb +34 -0
  95. data/lib/pagy/toolbox/paginators/typesense_rails.rb +34 -0
  96. data/lib/pagy.rb +67 -131
  97. data/locales/ar.yml +32 -0
  98. data/locales/be.yml +28 -0
  99. data/locales/bg.yml +24 -0
  100. data/locales/bs.yml +28 -0
  101. data/locales/ca.yml +24 -0
  102. data/locales/ckb.yml +20 -0
  103. data/locales/cs.yml +26 -0
  104. data/locales/da.yml +24 -0
  105. data/locales/de.yml +24 -0
  106. data/locales/dz.yml +20 -0
  107. data/locales/en.yml +24 -0
  108. data/{lib/locales → locales}/es.yml +9 -6
  109. data/locales/fr.yml +24 -0
  110. data/locales/hr.yml +28 -0
  111. data/locales/id.yml +20 -0
  112. data/locales/it.yml +24 -0
  113. data/locales/ja.yml +20 -0
  114. data/locales/km.yml +20 -0
  115. data/locales/ko.yml +20 -0
  116. data/locales/nb.yml +24 -0
  117. data/locales/nl.yml +24 -0
  118. data/locales/nn.yml +24 -0
  119. data/locales/pl.yml +28 -0
  120. data/{lib/locales → locales}/pt-BR.yml +10 -7
  121. data/{lib/locales → locales}/pt.yml +10 -7
  122. data/locales/ru.yml +28 -0
  123. data/locales/sk.yml +26 -0
  124. data/locales/sr.yml +28 -0
  125. data/locales/sv-SE.yml +24 -0
  126. data/locales/sv.yml +24 -0
  127. data/locales/sw.yml +28 -0
  128. data/locales/ta.yml +24 -0
  129. data/locales/tr.yml +24 -0
  130. data/locales/uk.yml +28 -0
  131. data/locales/vi.yml +20 -0
  132. data/locales/zh-CN.yml +20 -0
  133. data/locales/zh-HK.yml +20 -0
  134. data/locales/zh-TW.yml +20 -0
  135. data/stylesheets/pagy-tailwind.css +68 -0
  136. data/stylesheets/pagy.css +83 -0
  137. metadata +185 -94
  138. data/lib/config/pagy.rb +0 -258
  139. data/lib/javascripts/pagy-dev.js +0 -112
  140. data/lib/javascripts/pagy-module.js +0 -111
  141. data/lib/javascripts/pagy.js +0 -1
  142. data/lib/locales/ar.yml +0 -30
  143. data/lib/locales/be.yml +0 -25
  144. data/lib/locales/bg.yml +0 -21
  145. data/lib/locales/bs.yml +0 -25
  146. data/lib/locales/ca.yml +0 -23
  147. data/lib/locales/ckb.yml +0 -18
  148. data/lib/locales/cs.yml +0 -23
  149. data/lib/locales/da.yml +0 -23
  150. data/lib/locales/de.yml +0 -21
  151. data/lib/locales/en.yml +0 -21
  152. data/lib/locales/fr.yml +0 -21
  153. data/lib/locales/hr.yml +0 -25
  154. data/lib/locales/id.yml +0 -19
  155. data/lib/locales/it.yml +0 -21
  156. data/lib/locales/ja.yml +0 -19
  157. data/lib/locales/km.yml +0 -19
  158. data/lib/locales/ko.yml +0 -19
  159. data/lib/locales/nb.yml +0 -21
  160. data/lib/locales/nl.yml +0 -21
  161. data/lib/locales/nn.yml +0 -21
  162. data/lib/locales/pl.yml +0 -25
  163. data/lib/locales/ru.yml +0 -27
  164. data/lib/locales/sr.yml +0 -25
  165. data/lib/locales/sv-SE.yml +0 -21
  166. data/lib/locales/sv.yml +0 -21
  167. data/lib/locales/sw.yml +0 -23
  168. data/lib/locales/ta.yml +0 -23
  169. data/lib/locales/tr.yml +0 -19
  170. data/lib/locales/uk.yml +0 -25
  171. data/lib/locales/vi.yml +0 -17
  172. data/lib/locales/zh-CN.yml +0 -19
  173. data/lib/locales/zh-HK.yml +0 -19
  174. data/lib/locales/zh-TW.yml +0 -19
  175. data/lib/pagy/backend.rb +0 -39
  176. data/lib/pagy/calendar/helper.rb +0 -65
  177. data/lib/pagy/calendar.rb +0 -126
  178. data/lib/pagy/countless.rb +0 -37
  179. data/lib/pagy/exceptions.rb +0 -25
  180. data/lib/pagy/extras/arel.rb +0 -36
  181. data/lib/pagy/extras/array.rb +0 -24
  182. data/lib/pagy/extras/bootstrap.rb +0 -108
  183. data/lib/pagy/extras/bulma.rb +0 -105
  184. data/lib/pagy/extras/calendar.rb +0 -53
  185. data/lib/pagy/extras/countless.rb +0 -37
  186. data/lib/pagy/extras/elasticsearch_rails.rb +0 -80
  187. data/lib/pagy/extras/foundation.rb +0 -105
  188. data/lib/pagy/extras/frontend_helpers.rb +0 -67
  189. data/lib/pagy/extras/gearbox.rb +0 -54
  190. data/lib/pagy/extras/headers.rb +0 -53
  191. data/lib/pagy/extras/i18n.rb +0 -26
  192. data/lib/pagy/extras/items.rb +0 -61
  193. data/lib/pagy/extras/jsonapi.rb +0 -79
  194. data/lib/pagy/extras/materialize.rb +0 -96
  195. data/lib/pagy/extras/meilisearch.rb +0 -65
  196. data/lib/pagy/extras/metadata.rb +0 -38
  197. data/lib/pagy/extras/navs.rb +0 -51
  198. data/lib/pagy/extras/overflow.rb +0 -80
  199. data/lib/pagy/extras/searchkick.rb +0 -67
  200. data/lib/pagy/extras/semantic.rb +0 -95
  201. data/lib/pagy/extras/standalone.rb +0 -60
  202. data/lib/pagy/extras/support.rb +0 -40
  203. data/lib/pagy/extras/trim.rb +0 -29
  204. data/lib/pagy/extras/uikit.rb +0 -97
  205. data/lib/pagy/frontend.rb +0 -114
  206. data/lib/pagy/i18n.rb +0 -165
  207. data/lib/pagy/url_helpers.rb +0 -27
  208. data/lib/stylesheets/pagy.css +0 -61
  209. data/lib/stylesheets/pagy.scss +0 -50
  210. data/lib/stylesheets/pagy.tailwind.scss +0 -24
  211. /data/{lib/javascripts/pagy-module.d.ts → javascripts/pagy.d.ts} +0 -0
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'previous_next_html'
4
+ require_relative '../support/wrap_series_nav'
5
+
6
+ class Pagy
7
+ module NumericHelpers
8
+ private
9
+
10
+ def bulma_series_nav(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+
13
+ html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)})
14
+ series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
15
+ html << case item
16
+ when Integer
17
+ %(<li>#{a_lambda.(item, page_label(item), classes: 'pagination-link')}</li>)
18
+ when String
19
+ %(<li><a role="link" class="pagination-link is-current" aria-current="page" aria-disabled="true">#{
20
+ page_label(item)}</a></li>)
21
+ when :gap
22
+ %(<li><span class="pagination-ellipsis">#{I18n.translate('pagy.gap')}</span></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 << %(#{bulma_html_for(:next, a_lambda)}</ul>)
27
+
28
+ wrap_series_nav(html, "pagy-bulma series-nav #{classes}", **)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
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
+ module NumericHelpers
8
+ private
9
+
10
+ def bulma_series_nav_js(classes: 'pagination', **)
11
+ a_lambda = a_lambda(**)
12
+
13
+ tokens = { before: %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)}),
14
+ anchor: %(<li>#{a_lambda.(PAGE_TOKEN, LABEL_TOKEN, classes: 'pagination-link')}</li>),
15
+ current: %(<li><a role="link" class="pagination-link is-current" ) +
16
+ %(aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a></li>),
17
+ gap: %(<li><span class="pagination-ellipsis">#{I18n.translate('pagy.gap')}</span></li>),
18
+ after: %(#{bulma_html_for(:next, a_lambda)}</ul>) }
19
+
20
+ wrap_series_nav_js(tokens, "pagy-bulma series-nav-js #{classes}", **)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ DEFAULT_DATA_KEYS = %i[url_template first_url previous_url current_url page_url next_url last_url
5
+ count page limit last in from to previous next options].freeze
6
+
7
+ # Generate a hash of the wanted internal data
8
+ def data_hash(data_keys: @options[:data_keys] || DEFAULT_DATA_KEYS, **)
9
+ template = compose_page_url(PAGE_TOKEN, **)
10
+ to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) if page }
11
+
12
+ data_keys -= %i[count limit] if calendar?
13
+
14
+ data_keys.each_with_object({}) do |key, data|
15
+ value = case key
16
+ when :url_template then template
17
+ when :first_url then compose_page_url(nil, **)
18
+ when :previous_url then to_url.(@previous)
19
+ when :current_url, :page_url then to_url.(@page)
20
+ when :next_url then to_url.(@next)
21
+ when :last_url then to_url.(@last)
22
+ else send(key)
23
+ end
24
+ data[key] = value if value
25
+ rescue NoMethodError
26
+ raise OptionError.new(self, :data_keys, 'to contain known keys/methods', key)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'urls_hash'
4
+
5
+ # Handle pagination response headers
6
+ class Pagy
7
+ DEFAULT_HEADERS_MAP = { page: 'current-page',
8
+ limit: 'page-limit',
9
+ count: 'total-count',
10
+ pages: 'total-pages' }.freeze
11
+
12
+ # Generate a hash of RFC-8288-compliant http headers
13
+ def headers_hash(headers_map: @options[:headers_map] || DEFAULT_HEADERS_MAP, **)
14
+ links = urls_hash(**, absolute: true).map { %(<#{_2}>; rel="#{_1}") }.join(', ')
15
+
16
+ headers_map.each_with_object('link' => links) do |(key, name), hash|
17
+ next unless name
18
+
19
+ value = case key
20
+ # :nocov:
21
+ when :page then @page
22
+ when :limit then @limit unless calendar?
23
+ when :pages then @last if @count
24
+ when :count then @count
25
+ # :nocov:
26
+ end
27
+ hash[name] = value.to_s if value
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module NumericHelpers
5
+ # Instances with count return "Displaying items 41-60 of 324 in total" or "Displaying Products 41-60 of 324 in total".
6
+ # Instances with no count return only page info: "Page 3 of 100".
7
+ def info_tag(id: nil, item_name: nil)
8
+ i18n_key = if @count.nil?
9
+ 'pagy.info_tag.no_count'
10
+ elsif @count.zero?
11
+ 'pagy.info_tag.no_items'
12
+ elsif @in == @count
13
+ 'pagy.info_tag.single_page'
14
+ else
15
+ 'pagy.info_tag.multiple_pages'
16
+ end
17
+
18
+ info_data = if @count.nil?
19
+ { page: @page, pages: @last }
20
+ else
21
+ { item_name: item_name || I18n.translate('pagy.item_name', count: @count),
22
+ count: @count,
23
+ from: @from,
24
+ to: @to }
25
+ end
26
+
27
+ %(<span#{%( id="#{id}") if id} class="pagy info">#{I18n.translate(i18n_key, **info_data)}</span>)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/wrap_input_nav_js'
4
+
5
+ class Pagy
6
+ module NumericHelpers
7
+ def input_nav_js(style = nil, **)
8
+ return send(:"#{style}_input_nav_js", **) if style && style.to_s != 'pagy'
9
+
10
+ a_lambda = a_lambda(**)
11
+
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;">#{A_TAG})
14
+
15
+ html = %(#{previous_tag(a_lambda)}<label>#{
16
+ I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)}</label>#{
17
+ next_tag(a_lambda)})
18
+
19
+ wrap_input_nav_js(html, 'pagy input-nav-js', **)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/data_pagy_attribute'
4
+
5
+ class Pagy
6
+ module NumericHelpers
7
+ def limit_tag_js(id: nil, item_name: nil, max_limit: @options[:max_limit], **)
8
+ raise OptionError.new(self, :max_limit, 'to be truthy', max_limit) unless max_limit
9
+
10
+ limit_input = %(<input name="limit" type="number" min="1" max="#{max_limit}" value="#{
11
+ @limit}" style="padding: 0; text-align: center; width: #{@limit.to_s.length + 1}rem;">#{A_TAG})
12
+
13
+ url_token = compose_page_url(PAGE_TOKEN, limit: LIMIT_TOKEN)
14
+
15
+ %(<span#{%( id="#{id}") if id} class="pagy limit-tag-js" #{
16
+ data_pagy_attribute(:ltj, @from, url_token, PAGE_TOKEN, LIMIT_TOKEN)
17
+ }><label>#{
18
+ I18n.translate('pagy.limit_tag_js',
19
+ item_name: item_name || I18n.translate('pagy.item_name', count: @limit),
20
+ limit_input:,
21
+ count: @limit)
22
+ }</label></span>)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # The aliaser defines loader methods and aliases them.
5
+ # When a method is called for the first time, its loader will load the full implementation,
6
+ # which will overwrite the alias and will be executed.
7
+ # Subsequent calls will run the full implementation directly.
8
+ aliaser = lambda do |receiver, paths|
9
+ receiver.instance_eval do
10
+ paths.each do |visibility, methods|
11
+ methods.each do |method, path|
12
+ loader_name = :"_pagy_loader_for_#{method}"
13
+ define_method(loader_name) do |*args, **kwargs|
14
+ # Tests shadow the usage of these lines
15
+ # :nocov:
16
+ require_relative path
17
+ send(method, *args, **kwargs)
18
+ # :nocov:
19
+ end
20
+ alias_method method, loader_name
21
+ send(visibility, method)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ module HelperLoader
28
+ end
29
+
30
+ paths = { public: { page_url: 'page_url',
31
+ data_hash: 'data_hash',
32
+ headers_hash: 'headers_hash',
33
+ urls_hash: 'urls_hash',
34
+ next_tag: 'anchor_tags' } }.freeze
35
+
36
+ aliaser.(HelperLoader, paths)
37
+
38
+ module NumericHelperLoader
39
+ end
40
+
41
+ paths = { public: { previous_tag: 'anchor_tags',
42
+ input_nav_js: 'input_nav_js',
43
+ info_tag: 'info_tag',
44
+ limit_tag_js: 'limit_tag_js',
45
+ series_nav: 'series_nav',
46
+ series_nav_js: 'series_nav_js' },
47
+ protected: { bootstrap_series_nav: 'bootstrap/series_nav',
48
+ bootstrap_series_nav_js: 'bootstrap/series_nav_js',
49
+ bootstrap_input_nav_js: 'bootstrap/input_nav_js',
50
+ bulma_series_nav: 'bulma/series_nav',
51
+ bulma_series_nav_js: 'bulma/series_nav_js',
52
+ bulma_input_nav_js: 'bulma/input_nav_js' } }.freeze
53
+
54
+ aliaser.(NumericHelperLoader, paths)
55
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ def page_url(page, **)
5
+ target = case page
6
+ when :first then nil
7
+ when :current, :page then @page
8
+ when :previous then @previous
9
+ when :next then @next
10
+ when :last then @last
11
+ else page
12
+ end
13
+
14
+ compose_page_url(target, **) if target || page == :first
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/wrap_series_nav'
4
+
5
+ class Pagy
6
+ module NumericHelpers
7
+ def series_nav(style = nil, **)
8
+ return send(:"#{style}_series_nav", **) if style && style.to_s != 'pagy'
9
+
10
+ a_lambda = a_lambda(**)
11
+
12
+ html = previous_tag(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
+ 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
+
27
+ wrap_series_nav(html, 'pagy series-nav', **)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/wrap_series_nav_js'
4
+
5
+ class Pagy
6
+ module NumericHelpers
7
+ def series_nav_js(style = nil, **)
8
+ return send(:"#{style}_series_nav_js", **) if style && style.to_s != 'pagy'
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
+
17
+ wrap_series_nav_js(tokens, 'pagy series-nav-js', **)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ protected
5
+
6
+ def page_label(page, **options)
7
+ return page.to_s unless calendar?
8
+
9
+ options[:format] ||= @options[:format]
10
+ localize(starting_time_for(page.to_i), **options) # page could be a string
11
+ end
12
+
13
+ # Return a performance optimized lambda to generate the anchor tag.
14
+ # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to.
15
+ def a_lambda(anchor_string: @options[:anchor_string], **)
16
+ left, right = %(<a href="#{compose_page_url(PAGE_TOKEN, **)}"#{
17
+ %( #{anchor_string}) if anchor_string}).split(PAGE_TOKEN, 2)
18
+
19
+ lambda do |page, text = page_label(page), classes: nil, aria_label: nil|
20
+ title = if calendar? && (counts = @options[: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
+
27
+ rel = case page
28
+ when @previous then %( rel="prev")
29
+ when @next then %( rel="next")
30
+ end
31
+
32
+ %(#{left}#{page}#{right}#{title}#{
33
+ %( class="#{classes}") if classes}#{rel}#{%( aria-label="#{aria_label}") if aria_label}>#{text}</a>)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative '../../../modules/b64'
5
+
6
+ class Pagy
7
+ private
8
+
9
+ # Compose the data-pagy attribute, with the base64 encoded JSON-serialized args. Use the faster oj gem if defined.
10
+ def data_pagy_attribute(*args)
11
+ data = if defined?(Oj)
12
+ Oj.dump(args, mode: :compat)
13
+ else
14
+ JSON.dump(args)
15
+ end
16
+
17
+ %(data-pagy="#{B64.encode(data)}")
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ private
5
+
6
+ def nav_aria_label_attribute(aria_label: nil)
7
+ aria_label ||= I18n.translate('pagy.aria_label.nav', count: @last)
8
+ %(aria-label="#{aria_label}")
9
+ end
10
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ SERIES_SLOTS = 7
5
+
6
+ protected
7
+
8
+ # 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) unless slots.is_a?(Integer) && slots >= 0
11
+ return [] if slots.zero?
12
+
13
+ [].tap do |series|
14
+ if slots >= @last
15
+ series.push(*1..@last)
16
+ else
17
+ half = (slots - 1) / 2 # even slots makes the left half shorter by 1
18
+ start = if @page <= half # @page in the first half
19
+ 1
20
+ elsif @page > (@last - slots + half) # @page in the last half
21
+ @last - slots + 1
22
+ else # @page in the middle
23
+ @page - half
24
+ end
25
+ series.push(*(start...(start + slots)))
26
+ unless compact || slots < SERIES_SLOTS # Set first, last and :gap when needed
27
+ series[0] = 1
28
+ series[1] = :gap unless series[1] == 2
29
+ series[-2] = :gap unless series[-2] == @last - 1
30
+ series[-1] = @last
31
+ end
32
+ end
33
+ current = series.index(@page)
34
+ series[current] = @page.to_s if current
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'nav_aria_label_attribute'
4
+ require_relative 'data_pagy_attribute'
5
+ require_relative 'a_lambda' # inherited use
6
+
7
+ class Pagy
8
+ private
9
+
10
+ # Common input_nav_js logic
11
+ def wrap_input_nav_js(html, nav_classes, id: nil, aria_label: nil, **)
12
+ %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
13
+ nav_aria_label_attribute(aria_label:)} #{
14
+ data = [:inj, compose_page_url(PAGE_TOKEN, **), PAGE_TOKEN]
15
+ data.push(@update) if keynav?
16
+ data_pagy_attribute(*data)
17
+ }>#{html}</nav>)
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'series'
4
+ require_relative 'nav_aria_label_attribute'
5
+ require_relative 'data_pagy_attribute'
6
+ require_relative 'a_lambda' # inherited use
7
+
8
+ class Pagy
9
+ private
10
+
11
+ # Common series_nav logic
12
+ def wrap_series_nav(html, nav_classes, id: nil, aria_label: nil, **)
13
+ data = %( #{data_pagy_attribute(:k, @update)}) if keynav?
14
+
15
+ %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{nav_aria_label_attribute(aria_label:)}#{data}>#{html}</nav>)
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'series'
4
+ require_relative 'nav_aria_label_attribute'
5
+ require_relative 'data_pagy_attribute'
6
+ require_relative 'a_lambda' # inherited use
7
+
8
+ # Relegate internal functions. Make overriding navs easier.
9
+ class Pagy
10
+ private
11
+
12
+ # The reverse-sorted array of widths, series, and labels generated from the :steps hash.
13
+ # If :steps is false it will use the single {0 => @options[:slots]} length
14
+ def sequels(steps: @options[:steps] || { 0 => @options[:slots] || SERIES_SLOTS }, **)
15
+ raise OptionError.new(self, :steps, 'to define the 0 width', steps) unless steps.key?(0)
16
+
17
+ widths, series = steps.sort.reverse.map { |width, slots| [width, series(slots:)] }.transpose
18
+ [widths, series, page_labels(series)]
19
+ end
20
+
21
+ # Support the Calendar API
22
+ def page_labels(series)
23
+ return unless calendar?
24
+
25
+ series.map do |s|
26
+ s.map { _1 == :gap ? :gap : page_label(_1) }
27
+ end
28
+ end
29
+
30
+ # Common series_nav_js logic
31
+ def wrap_series_nav_js(tokens, nav_classes, id: nil, aria_label: nil, **)
32
+ sequels = sequels(**)
33
+ nav_classes = "pagy-rjs #{nav_classes}" if sequels[0].size > 1
34
+
35
+ %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
36
+ nav_aria_label_attribute(aria_label:)} #{
37
+ data = [:snj, tokens.values, PAGE_TOKEN, sequels]
38
+ data.push(@update) if keynav?
39
+ data_pagy_attribute(*data)
40
+ }></nav>)
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ def urls_hash(**)
5
+ template = compose_page_url(PAGE_TOKEN, **)
6
+
7
+ { first: compose_page_url(nil, **),
8
+ previous: @previous && template.sub(PAGE_TOKEN, @previous.to_s),
9
+ next: @next && template.sub(PAGE_TOKEN, @next.to_s),
10
+ last: @count && template.sub(PAGE_TOKEN, @last.to_s) }.compact
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module CalendarPaginator
5
+ module_function
6
+
7
+ # Take a collection and a config Hash and return [calendar, pagy, results]
8
+ def paginate(context, collection, config)
9
+ context.instance_eval do
10
+ allowed_options = Calendar::UNITS + %i[offset disabled request]
11
+
12
+ unless config.is_a?(Hash) && (config.keys - allowed_options).empty?
13
+ raise ArgumentError, "keys must be in #{allowed_options.inspect}"
14
+ end
15
+
16
+ config[:offset] ||= {}
17
+
18
+ unless config[:disabled]
19
+ period = pagy_calendar_period(collection)
20
+ params = config[:request].params
21
+
22
+ calendar, from, to = Calendar.send(:init, config, period, params) do |unit, unit_period|
23
+ pagy_calendar_counts(collection, unit, *unit_period) if respond_to?(:pagy_calendar_counts)
24
+ end
25
+
26
+ collection = pagy_calendar_filter(collection, from, to)
27
+ end
28
+
29
+ pagy, records = pagy(:offset, collection, **config[:offset])
30
+ [calendar, pagy, records]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/abilities/countable'
4
+
5
+ class Pagy
6
+ module CountishPaginator
7
+ module_function
8
+
9
+ def paginate(collection, options)
10
+ options[:page] ||= options[:request].resolve_page(force_integer: false)
11
+
12
+ if options[:page].is_a?(String)
13
+ page, count, epoch = options[:page].split.map(&:to_i)
14
+ options[:page] = page
15
+ end
16
+
17
+ options[:limit] = options[:request].resolve_limit
18
+ setup_options(count, epoch, collection, options)
19
+
20
+ pagy = Offset::Countish.new(**options)
21
+ [pagy, pagy.records(collection)]
22
+ end
23
+
24
+ # Get the count from the page and set epoch when ttl (Time To Live) requires it
25
+ def setup_options(count, epoch, collection, options)
26
+ now = Time.now.to_i
27
+ ongoing = !options[:ttl] || (epoch && epoch <= now && now < (epoch + options[:ttl]))
28
+
29
+ if !options[:count] && count && ongoing
30
+ options[:count] = count
31
+ options[:epoch] = epoch if options[:ttl]
32
+ else # recount
33
+ options[:count] ||= Countable.get_count(collection, options)
34
+ options[:epoch] = now if options[:ttl]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module CountlessPaginator
5
+ module_function
6
+
7
+ def paginate(collection, options)
8
+ options[:page] ||= options[:request].resolve_page(force_integer: false) # accept nil and strings
9
+
10
+ if options[:page].is_a?(String)
11
+ page, last = options[:page].split.map(&:to_i) # ' ' separator, (encoded as '+' by Countless#compose_page_param)
12
+ options[:page] = page
13
+ options[:last] = last if last&.positive?
14
+ end
15
+
16
+ options[:limit] = options[:request].resolve_limit
17
+
18
+ pagy = Offset::Countless.new(**options)
19
+ [pagy, pagy.records(collection)]
20
+ end
21
+ end
22
+ end