pagy 9.4.0 → 43.2.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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +548 -552
  3. data/apps/demo.ru +224 -181
  4. data/apps/index.rb +3 -1
  5. data/apps/keynav+root_key.ru +321 -0
  6. data/apps/keynav.ru +260 -0
  7. data/apps/{keyset_ar.ru → keyset.ru} +26 -32
  8. data/apps/{keyset_s.ru → keyset_sequel.ru} +23 -29
  9. data/apps/rails.ru +51 -48
  10. data/apps/repro.ru +51 -47
  11. data/bin/pagy +20 -21
  12. data/config/pagy.rb +35 -207
  13. data/javascripts/ai_widget.js +90 -0
  14. data/javascripts/pagy.js +153 -0
  15. data/javascripts/pagy.js.map +10 -0
  16. data/javascripts/pagy.min.js +2 -4
  17. data/javascripts/pagy.mjs +113 -63
  18. data/javascripts/wand.js +1172 -0
  19. data/lib/pagy/classes/calendar/calendar.rb +96 -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 +93 -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 +25 -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 +79 -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/countish.rb +17 -0
  36. data/lib/pagy/classes/offset/countless.rb +61 -0
  37. data/lib/pagy/classes/offset/offset.rb +56 -0
  38. data/lib/pagy/classes/offset/search.rb +32 -0
  39. data/lib/pagy/classes/request.rb +36 -0
  40. data/lib/pagy/console.rb +3 -20
  41. data/lib/pagy/modules/abilities/configurable.rb +36 -0
  42. data/lib/pagy/modules/abilities/countable.rb +23 -0
  43. data/lib/pagy/modules/abilities/linkable.rb +60 -0
  44. data/lib/pagy/modules/abilities/rangeable.rb +16 -0
  45. data/lib/pagy/modules/abilities/shiftable.rb +12 -0
  46. data/lib/pagy/{b64.rb → modules/b64.rb} +3 -7
  47. data/lib/pagy/modules/console.rb +24 -0
  48. data/lib/pagy/modules/i18n/i18n.rb +48 -0
  49. data/lib/pagy/modules/i18n/p11n/arabic.rb +29 -0
  50. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +26 -0
  51. data/lib/pagy/modules/i18n/p11n/one_other.rb +15 -0
  52. data/lib/pagy/modules/i18n/p11n/one_upto_two_other.rb +15 -0
  53. data/lib/pagy/modules/i18n/p11n/other.rb +13 -0
  54. data/lib/pagy/modules/i18n/p11n/polish.rb +26 -0
  55. data/lib/pagy/modules/i18n/p11n/west_slavic.rb +22 -0
  56. data/lib/pagy/modules/i18n/p11n.rb +16 -0
  57. data/lib/pagy/modules/searcher.rb +17 -0
  58. data/lib/pagy/toolbox/helpers/anchor_tags.rb +25 -0
  59. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +24 -0
  60. data/lib/pagy/toolbox/helpers/bootstrap/previous_next_html.rb +18 -0
  61. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +29 -0
  62. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +21 -0
  63. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +21 -0
  64. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +19 -0
  65. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +28 -0
  66. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +20 -0
  67. data/lib/pagy/toolbox/helpers/data_hash.rb +27 -0
  68. data/lib/pagy/toolbox/helpers/headers_hash.rb +29 -0
  69. data/lib/pagy/toolbox/helpers/info_tag.rb +26 -0
  70. data/lib/pagy/toolbox/helpers/input_nav_js.rb +18 -0
  71. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +23 -0
  72. data/lib/pagy/toolbox/helpers/loader.rb +33 -0
  73. data/lib/pagy/toolbox/helpers/page_url.rb +18 -0
  74. data/lib/pagy/toolbox/helpers/series_nav.rb +28 -0
  75. data/lib/pagy/toolbox/helpers/series_nav_js.rb +18 -0
  76. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +35 -0
  77. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +15 -0
  78. data/lib/pagy/toolbox/helpers/support/nav_aria_label_attribute.rb +12 -0
  79. data/lib/pagy/toolbox/helpers/support/series.rb +38 -0
  80. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +20 -0
  81. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +17 -0
  82. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +37 -0
  83. data/lib/pagy/toolbox/helpers/urls_hash.rb +13 -0
  84. data/lib/pagy/toolbox/paginators/calendar.rb +29 -0
  85. data/lib/pagy/toolbox/paginators/countish.rb +37 -0
  86. data/lib/pagy/toolbox/paginators/countless.rb +20 -0
  87. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +50 -0
  88. data/lib/pagy/toolbox/paginators/keynav_js.rb +26 -0
  89. data/lib/pagy/toolbox/paginators/keyset.rb +15 -0
  90. data/lib/pagy/toolbox/paginators/meilisearch.rb +31 -0
  91. data/lib/pagy/toolbox/paginators/method.rb +35 -0
  92. data/lib/pagy/toolbox/paginators/offset.rb +18 -0
  93. data/lib/pagy/toolbox/paginators/searchkick.rb +31 -0
  94. data/lib/pagy.rb +59 -97
  95. data/locales/ar.yml +9 -6
  96. data/locales/be.yml +9 -6
  97. data/locales/bg.yml +9 -6
  98. data/locales/bs.yml +9 -6
  99. data/locales/ca.yml +9 -6
  100. data/locales/ckb.yml +8 -6
  101. data/locales/cs.yml +9 -6
  102. data/locales/da.yml +9 -6
  103. data/locales/de.yml +9 -6
  104. data/locales/dz.yml +9 -6
  105. data/locales/en.yml +9 -6
  106. data/locales/es.yml +9 -6
  107. data/locales/fr.yml +9 -6
  108. data/locales/hr.yml +9 -6
  109. data/locales/id.yml +10 -9
  110. data/locales/it.yml +9 -6
  111. data/locales/ja.yml +10 -9
  112. data/locales/km.yml +10 -9
  113. data/locales/ko.yml +9 -6
  114. data/locales/nb.yml +9 -6
  115. data/locales/nl.yml +9 -6
  116. data/locales/nn.yml +9 -6
  117. data/locales/pl.yml +9 -6
  118. data/locales/pt-BR.yml +10 -7
  119. data/locales/pt.yml +10 -7
  120. data/locales/ru.yml +9 -6
  121. data/locales/sk.yml +9 -6
  122. data/locales/sr.yml +9 -6
  123. data/locales/sv-SE.yml +9 -6
  124. data/locales/sv.yml +9 -6
  125. data/locales/sw.yml +12 -9
  126. data/locales/ta.yml +9 -6
  127. data/locales/tr.yml +17 -12
  128. data/locales/uk.yml +9 -6
  129. data/locales/vi.yml +9 -6
  130. data/locales/zh-CN.yml +9 -6
  131. data/locales/zh-HK.yml +9 -6
  132. data/locales/zh-TW.yml +9 -6
  133. data/stylesheets/pagy-tailwind.css +64 -0
  134. data/stylesheets/pagy.css +63 -27
  135. metadata +118 -52
  136. data/javascripts/pagy.min.js.map +0 -10
  137. data/lib/pagy/backend.rb +0 -44
  138. data/lib/pagy/calendar/unit.rb +0 -103
  139. data/lib/pagy/calendar.rb +0 -84
  140. data/lib/pagy/countless.rb +0 -38
  141. data/lib/pagy/exceptions.rb +0 -25
  142. data/lib/pagy/extras/arel.rb +0 -28
  143. data/lib/pagy/extras/array.rb +0 -19
  144. data/lib/pagy/extras/bootstrap.rb +0 -97
  145. data/lib/pagy/extras/bulma.rb +0 -93
  146. data/lib/pagy/extras/calendar.rb +0 -79
  147. data/lib/pagy/extras/countless.rb +0 -32
  148. data/lib/pagy/extras/elasticsearch_rails.rb +0 -71
  149. data/lib/pagy/extras/gearbox.rb +0 -55
  150. data/lib/pagy/extras/headers.rb +0 -54
  151. data/lib/pagy/extras/i18n.rb +0 -26
  152. data/lib/pagy/extras/js_tools.rb +0 -70
  153. data/lib/pagy/extras/jsonapi.rb +0 -88
  154. data/lib/pagy/extras/keyset.rb +0 -30
  155. data/lib/pagy/extras/limit.rb +0 -63
  156. data/lib/pagy/extras/meilisearch.rb +0 -57
  157. data/lib/pagy/extras/metadata.rb +0 -42
  158. data/lib/pagy/extras/overflow.rb +0 -81
  159. data/lib/pagy/extras/pagy.rb +0 -82
  160. data/lib/pagy/extras/searchkick.rb +0 -59
  161. data/lib/pagy/extras/size.rb +0 -40
  162. data/lib/pagy/extras/standalone.rb +0 -60
  163. data/lib/pagy/extras/trim.rb +0 -29
  164. data/lib/pagy/frontend.rb +0 -99
  165. data/lib/pagy/i18n.rb +0 -167
  166. data/lib/pagy/keyset/active_record.rb +0 -44
  167. data/lib/pagy/keyset/sequel.rb +0 -57
  168. data/lib/pagy/keyset.rb +0 -118
  169. data/lib/pagy/shared_methods.rb +0 -26
  170. data/lib/pagy/url_helpers.rb +0 -26
  171. data/stylesheets/pagy.scss +0 -48
  172. data/stylesheets/pagy.tailwind.css +0 -21
@@ -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
@@ -0,0 +1,20 @@
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
+ # Relegate internal functions. Make overriding navs easier.
8
+ class Pagy
9
+ private
10
+
11
+ # Build the input_nav_js tag, with the specific inner html for the style
12
+ def wrap_input_nav_js(html, nav_classes, id: nil, aria_label: nil, **)
13
+ %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
14
+ nav_aria_label_attribute(aria_label:)} #{
15
+ data = [:inj, compose_page_url(PAGE_TOKEN, **)]
16
+ data.push(@update) if keynav?
17
+ data_pagy_attribute(*data)
18
+ }>#{html}</nav>)
19
+ end
20
+ 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
+ # Relegate internal functions. Make overriding navs easier.
9
+ class Pagy
10
+ private
11
+
12
+ # Build the nav tag, with the specific inner html for the style
13
+ def wrap_series_nav(html, nav_classes, id: nil, aria_label: nil, **)
14
+ data = %( #{data_pagy_attribute(:k, @update)}) if keynav?
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,37 @@
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
+ # Return 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 for the Calendar API
22
+ def page_labels(series)
23
+ series.map { |s| s.map { |item| item == :gap ? :gap : page_label(item) } } if calendar?
24
+ end
25
+
26
+ # Build the nav_js tag, with the specific tokens for the style
27
+ def wrap_series_nav_js(tokens, nav_classes, id: nil, aria_label: nil, **)
28
+ sequels = sequels(**)
29
+ nav_classes = "pagy-rjs #{nav_classes}" if sequels[0].size > 1
30
+ %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
31
+ nav_aria_label_attribute(aria_label:)} #{
32
+ data = [:snj, tokens.values, sequels]
33
+ data.push(@update) if keynav?
34
+ data_pagy_attribute(*data)
35
+ }></nav>)
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Generate the hash of the pagination links
5
+ def urls_hash(**)
6
+ template = compose_page_url(PAGE_TOKEN, **)
7
+
8
+ { first: compose_page_url(nil, **),
9
+ previous: @previous && template.sub(PAGE_TOKEN, @previous.to_s),
10
+ next: @next && template.sub(PAGE_TOKEN, @next.to_s),
11
+ last: @count && template.sub(PAGE_TOKEN, @last.to_s) }.compact
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module CalendarPaginator
5
+ module_function
6
+
7
+ # Take a collection and a configuration Hash and return an array with 3 items: [calendar, pagy, results]
8
+ def paginate(context, collection, config)
9
+ context.instance_eval do
10
+ allowed_options = Calendar::UNITS + %i[offset disabled request]
11
+ raise ArgumentError, "keys must be in #{allowed_options.inspect}" \
12
+ unless config.is_a?(Hash) && (config.keys - allowed_options).empty?
13
+
14
+ config[:offset] ||= {}
15
+ unless config[:disabled]
16
+ calendar, from, to =
17
+ Calendar.send(:init, config,
18
+ pagy_calendar_period(collection),
19
+ config[:request].params) do |unit, period|
20
+ pagy_calendar_counts(collection, unit, *period) if respond_to?(:pagy_calendar_counts)
21
+ end
22
+ collection = pagy_calendar_filter(collection, from, to)
23
+ end
24
+ pagy, records = pagy(:offset, collection, **config[:offset])
25
+ [calendar, pagy, records]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/abilities/countable'
4
+
5
+ class Pagy
6
+ module CountishPaginator
7
+ module_function
8
+
9
+ # Return the Offset::Countish instance and records
10
+ def paginate(collection, options)
11
+ options[:page] ||= options[:request].resolve_page(force_integer: false)
12
+ if options[:page].is_a?(String)
13
+ page, count, epoch = options[:page].split.map(&:to_i)
14
+ options[:page] = page
15
+ end
16
+ setup_options(count, epoch, collection, options)
17
+ options[:limit] = options[:request].resolve_limit
18
+ pagy = Offset::Countish.new(**options)
19
+ [pagy, pagy.records(collection)]
20
+ end
21
+
22
+ # Get the count from the page and set epoch when ttl (Time To Live) requires it
23
+ def setup_options(count, epoch, collection, options)
24
+ now = Time.now.to_i
25
+ if !options[:count] && count && (!options[:ttl] ||
26
+ (epoch && epoch <= now && now < (epoch + options[:ttl]))) # ongoing
27
+ # puts 'ongoing'
28
+ options[:count] = count
29
+ options[:epoch] = epoch if options[:ttl]
30
+ else # recount
31
+ # puts 'recount'
32
+ options[:count] ||= Countable.get_count(collection, options)
33
+ options[:epoch] = now if options[:ttl]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module CountlessPaginator
5
+ module_function
6
+
7
+ # Return the Offset::Countless instance and records
8
+ def paginate(collection, options)
9
+ options[:page] ||= options[:request].resolve_page(force_integer: false) # accept nil and strings
10
+ if options[:page].is_a?(String)
11
+ page, last = options[:page].split.map(&:to_i) # decoded '+' added by the compose_page_url
12
+ options[:page] = page
13
+ options[:last] = last if last&.positive?
14
+ end
15
+ options[:limit] = options[:request].resolve_limit
16
+ pagy = Offset::Countless.new(**options)
17
+ [pagy, pagy.records(collection)]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/searcher'
4
+
5
+ class Pagy
6
+ module ElasticsearchRailsPaginator
7
+ module_function
8
+
9
+ # Paginate from the search object
10
+ def paginate(search, options)
11
+ if search.is_a?(Search::Arguments)
12
+ # The search is the array of pagy_search arguments
13
+ Searcher.wrap(search, options) do
14
+ model, query_or_payload, search_options = search
15
+ search_options[:size] = options[:limit]
16
+ search_options[:from] = options[:limit] * ((options[:page] || 1) - 1)
17
+ response_object = model.send(options[:search_method] || ElasticsearchRails::DEFAULT[:search_method],
18
+ query_or_payload, **search_options)
19
+ options[:count] = total_count_from(response_object)
20
+ [ElasticsearchRails.new(**options), response_object]
21
+ end
22
+ else
23
+ from, size = pagination_params_from(search)
24
+ options[:limit] = size
25
+ options[:page] = ((from || 0) / options[:limit]) + 1
26
+ options[:count] = total_count_from(search)
27
+ ElasticsearchRails.new(**options)
28
+ end
29
+ end
30
+
31
+ # Get from and size params from the response object, supporting different versions of ElasticsearchRails
32
+ def pagination_params_from(response_object)
33
+ definition = response_object.search.definition
34
+ definition = definition.to_hash if definition.respond_to?(:to_hash)
35
+ container = (definition.is_a?(Hash) && (definition[:body] || definition)) || response_object.search.options
36
+ from = (container[:from] || container['from']).to_i
37
+ size = (container[:size] || container['size']).to_i
38
+ size = 10 if size.zero?
39
+ [from, size]
40
+ end
41
+
42
+ # Get the count from the response object, supporting different versions of ElasticsearchRails
43
+ def total_count_from(response_object)
44
+ total = response_object.instance_eval do
45
+ respond_to?(:response) ? response['hits']['total'] : raw_response['hits']['total']
46
+ end
47
+ total.is_a?(Hash) ? total['value'] : total
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../pagy/modules/b64'
4
+
5
+ class Pagy
6
+ module KeynavJsPaginator
7
+ module_function
8
+
9
+ # Return the Pagy::Keyset::Keynav instance and paginated records.
10
+ # Fall back to :countless if the :page has no client data.
11
+ def paginate(set, options)
12
+ page = options[:request].resolve_page(force_integer: false) # allow nil
13
+ if page&.match(' ') # countless page -> no augmentation -> fallback
14
+ return CountlessPaginator.paginate(set, page:, **options)
15
+ elsif page.is_a?(String) # keynav page param
16
+ page_arguments = JSON.parse(B64.urlsafe_decode(page))
17
+ # Restart the pagination from page 1/nil if the url has been requested from another browser
18
+ options[:page] = page_arguments if options[:request].cookie == page_arguments.shift
19
+ end
20
+
21
+ options[:limit] = options[:request].resolve_limit
22
+ pagy = Keyset::Keynav.new(set, **options)
23
+ [pagy, pagy.records]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ module KeysetPaginator
5
+ module_function
6
+
7
+ # Return Pagy::Keyset instance and paginated records
8
+ def paginate(set, options)
9
+ options[:page] ||= options[:request].resolve_page(force_integer: false) # allow nil
10
+ options[:limit] = options[:request].resolve_limit
11
+ pagy = Keyset.new(set, **options)
12
+ [pagy, pagy.records]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/searcher'
4
+
5
+ class Pagy
6
+ module MeilisearchPaginator
7
+ module_function
8
+
9
+ # Paginate from the search object
10
+ def paginate(search, options)
11
+ if search.is_a?(Search::Arguments)
12
+ # The search is the array of pagy_search arguments
13
+ Searcher.wrap(search, options) do
14
+ model, term, search_options = search
15
+ search_options[:hits_per_page] = options[:limit]
16
+ search_options[:page] = options[:page]
17
+ results = model.send(options[:search_method] || Meilisearch::DEFAULT[:search_method],
18
+ term, search_options)
19
+ options[:count] = results.raw_answer['totalHits']
20
+ [Meilisearch.new(**options), results]
21
+ end
22
+ else
23
+ # The search is a meilisearch results object
24
+ options[:limit] = search.raw_answer['hitsPerPage']
25
+ options[:page] = search.raw_answer['page']
26
+ options[:count] = search.raw_answer['totalHits']
27
+ Meilisearch.new(**options)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../classes/request'
4
+
5
+ class Pagy
6
+ paginators = { offset: :OffsetPaginator,
7
+ countless: :CountlessPaginator,
8
+ countish: :CountishPaginator,
9
+ keyset: :KeysetPaginator,
10
+ keynav_js: :KeynavJsPaginator,
11
+ calendar: :CalendarPaginator,
12
+ elasticsearch_rails: :ElasticsearchRailsPaginator,
13
+ meilisearch: :MeilisearchPaginator,
14
+ searchkick: :SearchkickPaginator }.freeze
15
+
16
+ path = Pathname.new(__dir__)
17
+ paginators.each { |symbol, name| autoload name, path.join(symbol.to_s) }
18
+
19
+ # Pagy::Method defines the #pagy method to be included in the app controller/view.
20
+ Method = Module.new do
21
+ protected
22
+
23
+ define_method :pagy do |paginator = :offset, collection, **options|
24
+ arguments = if paginator == :calendar
25
+ [self, collection, options]
26
+ else
27
+ [collection, options = Pagy.options.merge(options)]
28
+ end
29
+ options[:root_key] = 'page' if options[:jsonapi] # enforce 'page' root_key for JSON:API
30
+ options[:request] ||= request # user set request or self.request
31
+ options[:request] = Request.new(options) # Pagy::Request
32
+ Pagy.const_get(paginators[paginator]).paginate(*arguments)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/abilities/countable'
4
+
5
+ class Pagy
6
+ module OffsetPaginator
7
+ module_function
8
+
9
+ # Return the Pagy::Offset instance and results
10
+ def paginate(collection, options)
11
+ options[:page] ||= options[:request].resolve_page
12
+ options[:limit] = options[:request].resolve_limit
13
+ options[:count] ||= Countable.get_count(collection, options)
14
+ pagy = Offset.new(**options)
15
+ [pagy, collection.instance_of?(Array) ? collection[pagy.offset, pagy.limit] : pagy.records(collection)]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/searcher'
4
+
5
+ class Pagy
6
+ module SearchkickPaginator
7
+ module_function
8
+
9
+ # Paginate from the search object
10
+ def paginate(search, options)
11
+ if search.is_a?(Search::Arguments)
12
+ # The search is the array of pagy_search arguments
13
+ Searcher.wrap(search, options) do
14
+ model, term, search_options, block = search
15
+ search_options[:per_page] = options[:limit]
16
+ search_options[:page] = options[:page]
17
+ results = model.send(options[:search_method] || Searchkick::DEFAULT[:search_method],
18
+ term || '*', **search_options, &block)
19
+ options[:count] = results.total_count
20
+ [Searchkick.new(**options), results]
21
+ end
22
+ else
23
+ # The search is a searchkick results object
24
+ options[:limit] = search.respond_to?(:options) ? search.options[:per_page] : search.per_page
25
+ options[:page] = search.respond_to?(:options) ? search.options[:page] : search.current_page
26
+ options[:count] = search.total_count
27
+ Searchkick.new(**options)
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/pagy.rb CHANGED
@@ -1,106 +1,68 @@
1
- # See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/pagy
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'pathname'
5
- require_relative 'pagy/shared_methods'
4
+ require_relative 'pagy/classes/exceptions'
5
+ require_relative 'pagy/modules/abilities/linkable'
6
+ require_relative 'pagy/modules/abilities/configurable'
7
+ require_relative 'pagy/toolbox/helpers/loader'
6
8
 
7
- # Top superclass: it should define only what's common to all the subclasses
9
+ # Top superclass: it defines only what's common to all the subclasses
8
10
  class Pagy
9
- VERSION = '9.4.0'
10
-
11
- # Core default: constant for easy access, but mutable for customizable defaults
12
- DEFAULT = { count_args: [:all], # rubocop:disable Style/MutableConstant
13
- ends: true,
14
- limit: 20,
15
- outset: 0,
16
- page: 1,
17
- page_param: :page,
18
- size: 7 } # AR friendly
19
-
20
- # Gem root pathname to get the path of Pagy files stylesheets, javascripts, apps, locales, etc.
21
- def self.root
22
- @root ||= Pathname.new(__dir__).parent.freeze
23
- end
24
-
25
- include SharedMethods
26
-
27
- attr_reader :count, :from, :in, :last, :next, :offset, :prev, :to
28
- alias pages last
29
-
30
- # Merge and validate the options, do some simple arithmetic and set the instance variables
31
- def initialize(**vars)
32
- assign_vars(DEFAULT, vars)
33
- assign_and_check(count: 0, page: 1, outset: 0)
34
- assign_limit
35
- assign_offset
36
- assign_last
37
- check_overflow
38
- @from = [@offset - @outset + 1, @count].min
39
- @to = [@offset - @outset + @limit, @count].min
40
- @in = [@to - @from + 1, @count].min
41
- assign_prev_and_next
42
- end
43
-
44
- # Setup @last (overridden by the gearbox extra)
45
- def assign_last
46
- @last = [(@count.to_f / @limit).ceil, 1].max
47
- @last = @vars[:max_pages] if @vars[:max_pages] && @last > vars[:max_pages]
48
- end
49
-
50
- # Assign @offset (overridden by the gearbox extra)
51
- def assign_offset
52
- @offset = (@limit * (@page - 1)) + @outset # may be already set from gear_box
53
- end
54
-
55
- # Assign @prev and @next
56
- def assign_prev_and_next
57
- @prev = (@page - 1 unless @page == 1)
58
- @next = @page == @last ? (1 if @vars[:cycle]) : @page + 1
59
- end
60
-
61
- # Checks the @page <= @last
62
- def check_overflow
63
- raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
11
+ VERSION = '43.2.0'
12
+ ROOT = Pathname.new(__dir__).parent.freeze
13
+ DEFAULT = { limit: 20, limit_key: 'limit', page_key: 'page' }.freeze
14
+ PAGE_TOKEN = EscapedValue.new('P ')
15
+ LIMIT_TOKEN = EscapedValue.new('L ')
16
+ LABEL_TOKEN = 'L'
17
+ A_TAG = '<a style="display: none;">#</a>'
18
+
19
+ path = Pathname.new(__FILE__).sub_ext('')
20
+ autoload :Method, path.join('toolbox/paginators/method')
21
+ autoload :I18n, path.join('modules/i18n/i18n')
22
+ autoload :Console, path.join('modules/console')
23
+ autoload :Calendar, path.join('classes/calendar/calendar')
24
+ autoload :Offset, path.join('classes/offset/offset')
25
+ autoload :Search, path.join('classes/offset/search')
26
+ autoload :ElasticsearchRails, path.join('classes/offset/search')
27
+ autoload :Meilisearch, path.join('classes/offset/search')
28
+ autoload :Searchkick, path.join('classes/offset/search')
29
+ autoload :Keyset, path.join('classes/keyset/keyset')
30
+
31
+ def self.options = @options ||= {}
32
+
33
+ extend Configurable
34
+ include Linkable
35
+ include Loader
36
+
37
+ attr_reader :page, :next, :in, :limit, :options
38
+
39
+ protected
40
+
41
+ # Define the hierarchical identity methods, overridden by the respective classes
42
+ def offset? = false
43
+ def countless? = false
44
+ def calendar? = false
45
+ def search? = false
46
+ def keyset? = false
47
+ def keynav? = false
48
+
49
+ # Validates and assign the passed options: they must be present and value.to_i must be >= min
50
+ def assign_and_check(name_min)
51
+ name_min.each do |name, min|
52
+ raise OptionError.new(self, name, ">= #{min}", @options[name]) \
53
+ unless @options[name].respond_to?(:to_i) && instance_variable_set(:"@#{name}", @options[name].to_i) >= min
54
+ end
64
55
  end
65
56
 
66
- # Label for the current page. Allow the customization of the output (overridden by the calendar extra)
67
- def label = @page.to_s
68
-
69
- # Label for any page. Allow the customization of the output (overridden by the calendar extra)
70
- def label_for(page) = page.to_s
71
-
72
- # Return the array of page numbers and :gap e.g. [1, :gap, 8, "9", 10, :gap, 36]
73
- def series(size: @vars[:size], **_)
74
- raise VariableError.new(self, :size, 'to be an Integer >= 0', size) \
75
- unless size.is_a?(Integer) && size >= 0
76
- return [] if size.zero?
77
-
78
- [].tap do |series|
79
- if size >= @last
80
- series.push(*1..@last)
81
- else
82
- left = ((size - 1) / 2.0).floor # left half might be 1 page shorter for even size
83
- start = if @page <= left # beginning pages
84
- 1
85
- elsif @page > (@last - size + left) # end pages
86
- @last - size + 1
87
- else # intermediate pages
88
- @page - left
89
- end
90
- series.push(*start...(start + size))
91
- # Set first and last pages plus gaps when needed, respecting the size
92
- if vars[:ends] && size >= 7
93
- series[0] = 1
94
- series[1] = :gap unless series[1] == 2
95
- series[-2] = :gap unless series[-2] == @last - 1
96
- series[-1] = @last
97
- end
98
- end
99
- series[series.index(@page)] = @page.to_s
100
- end
57
+ # Merge all the DEFAULT constants of the class hierarchy with the options
58
+ def assign_options(**options)
59
+ @request = options.delete(:request) # internal object
60
+ default = {}
61
+ current = self.class
62
+ begin
63
+ default = current::DEFAULT.merge(default)
64
+ current = current.superclass
65
+ end until current == Object # rubocop:disable Lint/Loop -- see https://github.com/rubocop/rubocop-performance/issues/362
66
+ @options = default.merge!(options.delete_if { |k, v| default.key?(k) && (v.nil? || v == '') }).freeze
101
67
  end
102
68
  end
103
-
104
- require_relative 'pagy/backend'
105
- require_relative 'pagy/frontend'
106
- require_relative 'pagy/exceptions'
data/locales/ar.yml CHANGED
@@ -1,6 +1,8 @@
1
- # :arabic pluralization (see https://github.com/ddnexus/pagy/blob/master/gem/lib/pagy/i18n.rb)
1
+ # See https://ddnexus.github.io/pagy/resources/i18n
2
+
2
3
  ar:
3
4
  pagy:
5
+ p11n: 'Arabic'
4
6
  aria_label:
5
7
  nav:
6
8
  zero: "لا يوجد صفحات"
@@ -9,9 +11,9 @@ ar:
9
11
  few: "صفحات"
10
12
  many: "صفحات"
11
13
  other: "صفحات"
12
- prev: "السابق"
14
+ previous: "السابق"
13
15
  next: "التالي"
14
- prev: "&lt;"
16
+ previous: "&lt;"
15
17
  next: "&gt;"
16
18
  gap: "&hellip;"
17
19
  item_name:
@@ -21,9 +23,10 @@ ar:
21
23
  few: "قليل"
22
24
  many: "كثير"
23
25
  other: "عناصر"
24
- info:
26
+ info_tag:
27
+ no_count: "الصفحة %{page} من %{pages}"
25
28
  no_items: "لا يوجد %{item_name}"
26
29
  single_page: "عرض %{count} %{item_name}"
27
30
  multiple_pages: "عرض %{item_name} %{from}-%{to} من اجمالي %{count}"
28
- combo_nav_js: "الصفحة %{page_input} من %{pages}"
29
- limit_selector_js: "عرض %{limit_input} %{item_name} لكل صفحة"
31
+ input_nav_js: "الصفحة %{page_input} من %{pages}"
32
+ limit_tag_js: "عرض %{limit_input} %{item_name} لكل صفحة"