pagy 8.6.0 → 9.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +5 -5
  3. data/apps/demo.ru +8 -9
  4. data/apps/keyset_ar.ru +236 -0
  5. data/apps/keyset_s.ru +238 -0
  6. data/apps/rails.ru +7 -9
  7. data/apps/repro.ru +5 -6
  8. data/apps/tmp/calendar.sqlite3 +0 -0
  9. data/apps/tmp/calendar.sqlite3-shm +0 -0
  10. data/apps/tmp/calendar.sqlite3-wal +0 -0
  11. data/apps/tmp/local_secret.txt +1 -1
  12. data/apps/tmp/pagy-keyset-ar.sqlite3 +0 -0
  13. data/apps/tmp/{pagy-rails.sqlite3 → pagy-keyset-ar.sqlite3-shm} +0 -0
  14. data/apps/tmp/pagy-keyset-ar.sqlite3-wal +0 -0
  15. data/apps/tmp/pagy-keyset-s.sqlite3 +0 -0
  16. data/bin/pagy +4 -2
  17. data/config/pagy.rb +34 -31
  18. data/javascripts/pagy-module.js +1 -1
  19. data/javascripts/pagy.js +2 -2
  20. data/javascripts/pagy.min.js +2 -2
  21. data/javascripts/pagy.min.js.map +3 -3
  22. data/javascripts/pagy.mjs +2 -2
  23. data/lib/pagy/b64.rb +33 -0
  24. data/lib/pagy/backend.rb +21 -17
  25. data/lib/pagy/calendar/day.rb +1 -1
  26. data/lib/pagy/calendar/month.rb +1 -1
  27. data/lib/pagy/calendar/quarter.rb +1 -1
  28. data/lib/pagy/calendar/unit.rb +7 -10
  29. data/lib/pagy/calendar/week.rb +1 -1
  30. data/lib/pagy/calendar/year.rb +1 -1
  31. data/lib/pagy/calendar.rb +5 -5
  32. data/lib/pagy/countless.rb +11 -15
  33. data/lib/pagy/extras/arel.rb +8 -10
  34. data/lib/pagy/extras/array.rb +4 -6
  35. data/lib/pagy/extras/bootstrap.rb +5 -5
  36. data/lib/pagy/extras/bulma.rb +10 -7
  37. data/lib/pagy/extras/calendar.rb +4 -5
  38. data/lib/pagy/extras/countless.rb +15 -13
  39. data/lib/pagy/extras/elasticsearch_rails.rb +15 -15
  40. data/lib/pagy/extras/gearbox.rb +26 -26
  41. data/lib/pagy/extras/headers.rb +25 -24
  42. data/lib/pagy/extras/js_tools.rb +8 -8
  43. data/lib/pagy/extras/jsonapi.rb +26 -16
  44. data/lib/pagy/extras/keyset.rb +36 -0
  45. data/lib/pagy/extras/limit.rb +63 -0
  46. data/lib/pagy/extras/meilisearch.rb +11 -11
  47. data/lib/pagy/extras/metadata.rb +2 -2
  48. data/lib/pagy/extras/overflow.rb +6 -6
  49. data/lib/pagy/extras/pagy.rb +16 -16
  50. data/lib/pagy/extras/searchkick.rb +11 -11
  51. data/lib/pagy/extras/size.rb +2 -2
  52. data/lib/pagy/extras/standalone.rb +6 -6
  53. data/lib/pagy/extras/trim.rb +2 -2
  54. data/lib/pagy/frontend.rb +32 -33
  55. data/lib/pagy/i18n.rb +1 -1
  56. data/lib/pagy/keyset/active_record.rb +38 -0
  57. data/lib/pagy/keyset/sequel.rb +51 -0
  58. data/lib/pagy/keyset.rb +98 -0
  59. data/lib/pagy/shared_methods.rb +27 -0
  60. data/lib/pagy/url_helpers.rb +4 -4
  61. data/lib/pagy.rb +54 -68
  62. data/locales/ar.yml +2 -1
  63. data/locales/be.yml +1 -1
  64. data/locales/bg.yml +1 -1
  65. data/locales/bs.yml +1 -1
  66. data/locales/ca.yml +1 -1
  67. data/locales/ckb.yml +1 -1
  68. data/locales/cs.yml +1 -1
  69. data/locales/da.yml +1 -1
  70. data/locales/de.yml +1 -1
  71. data/locales/en.yml +1 -1
  72. data/locales/es.yml +1 -1
  73. data/locales/fr.yml +1 -1
  74. data/locales/hr.yml +1 -1
  75. data/locales/id.yml +1 -1
  76. data/locales/it.yml +1 -1
  77. data/locales/ja.yml +1 -1
  78. data/locales/km.yml +1 -1
  79. data/locales/ko.yml +1 -1
  80. data/locales/nb.yml +1 -1
  81. data/locales/nl.yml +1 -1
  82. data/locales/nn.yml +1 -1
  83. data/locales/pl.yml +1 -1
  84. data/locales/pt-BR.yml +1 -1
  85. data/locales/pt.yml +1 -1
  86. data/locales/ru.yml +1 -1
  87. data/locales/sr.yml +1 -1
  88. data/locales/sv-SE.yml +1 -1
  89. data/locales/sv.yml +1 -1
  90. data/locales/sw.yml +1 -1
  91. data/locales/ta.yml +1 -1
  92. data/locales/tr.yml +1 -1
  93. data/locales/uk.yml +1 -1
  94. data/locales/vi.yml +1 -1
  95. data/locales/zh-CN.yml +1 -1
  96. data/locales/zh-HK.yml +1 -1
  97. data/locales/zh-TW.yml +1 -1
  98. metadata +16 -16
  99. data/lib/pagy/extras/foundation.rb +0 -95
  100. data/lib/pagy/extras/items.rb +0 -64
  101. data/lib/pagy/extras/materialize.rb +0 -100
  102. data/lib/pagy/extras/semantic.rb +0 -94
  103. data/lib/pagy/extras/uikit.rb +0 -98
@@ -8,9 +8,10 @@ class Pagy # :nodoc:
8
8
  # The resulting code may not look very elegant, but produces the best benchmarks
9
9
  module BulmaExtra
10
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', aria_label: nil, **vars)
11
+ def pagy_bulma_nav(pagy, id: nil, classes: 'pagy-bulma nav pagination is-centered',
12
+ aria_label: nil, **vars)
12
13
  id = %( id="#{id}") if id
13
- a = pagy_anchor(pagy)
14
+ a = pagy_anchor(pagy, **vars)
14
15
 
15
16
  html = %(<nav#{id} class="#{classes}" #{nav_aria_label(pagy, aria_label:)}>)
16
17
  html << bulma_prev_next_html(pagy, a)
@@ -31,10 +32,11 @@ class Pagy # :nodoc:
31
32
  end
32
33
 
33
34
  # Javascript pagination for bulma: it returns a nav with a data-pagy attribute used by the Pagy.nav javascript
34
- def pagy_bulma_nav_js(pagy, id: nil, classes: 'pagy-bulma nav-js pagination is-centered', aria_label: nil, **vars)
35
+ def pagy_bulma_nav_js(pagy, id: nil, classes: 'pagy-bulma nav-js pagination is-centered',
36
+ aria_label: nil, **vars)
35
37
  sequels = pagy.sequels(**vars)
36
38
  id = %( id="#{id}") if id
37
- a = pagy_anchor(pagy)
39
+ a = pagy_anchor(pagy, **vars)
38
40
  tokens = { 'before' => %(#{bulma_prev_next_html(pagy, a)}<ul class="pagination-list">),
39
41
  'a' => %(<li>#{a.(PAGE_TOKEN, LABEL_TOKEN, classes: 'pagination-link')}</li>),
40
42
  'current' => %(<li><a role="link" class="pagination-link is-current" aria-current="page" aria-disabled="true">#{
@@ -49,9 +51,10 @@ class Pagy # :nodoc:
49
51
  end
50
52
 
51
53
  # Javascript combo pagination for bulma: it returns a nav with a data-pagy attribute used by the pagy.js file
52
- def pagy_bulma_combo_nav_js(pagy, id: nil, classes: 'pagy-bulma combo-nav-js pagination is-centered', aria_label: nil)
54
+ def pagy_bulma_combo_nav_js(pagy, id: nil, classes: 'pagy-bulma combo-nav-js pagination is-centered',
55
+ aria_label: nil, **vars)
53
56
  id = %( id="#{id}") if id
54
- a = pagy_anchor(pagy)
57
+ a = pagy_anchor(pagy, **vars)
55
58
  pages = pagy.pages
56
59
 
57
60
  page_input = %(<input name="page" type="number" min="1" max="#{pages}" value="#{pagy.page}" aria-current="page") <<
@@ -61,7 +64,7 @@ class Pagy # :nodoc:
61
64
 
62
65
  %(<nav#{id} class="#{classes}" #{
63
66
  nav_aria_label(pagy, aria_label:)} #{
64
- pagy_data(pagy, :combo, pagy_url_for(pagy, PAGE_TOKEN))
67
+ pagy_data(pagy, :combo, pagy_url_for(pagy, PAGE_TOKEN, **vars))
65
68
  }>#{
66
69
  bulma_prev_next_html(pagy, a)
67
70
  }<ul class="pagination-list"><li class="pagination-link"><label>#{
@@ -27,7 +27,7 @@ class Pagy # :nodoc:
27
27
  end
28
28
  collection = pagy_calendar_filter(collection, from, to)
29
29
  end
30
- pagy, results = send(conf[:pagy][:backend] || :pagy, collection, conf[:pagy]) # use backend: :pagy when omitted
30
+ pagy, results = send(conf[:pagy][:backend] || :pagy, collection, **conf[:pagy]) # use backend: :pagy when omitted
31
31
  [calendar, pagy, results]
32
32
  end
33
33
 
@@ -47,12 +47,11 @@ class Pagy # :nodoc:
47
47
  # Override the pagy_anchor
48
48
  module FrontendOverride
49
49
  # Consider the vars[:count]
50
- def pagy_anchor(pagy)
50
+ def pagy_anchor(pagy, anchor_string: nil)
51
51
  return super unless (counts = pagy.vars[:counts])
52
52
 
53
- a_string = pagy.vars[:anchor_string]
54
- a_string = %( #{a_string}) if a_string
55
- left, right = %(<a#{a_string} href="#{pagy_url_for(pagy, PAGE_TOKEN)}").split(PAGE_TOKEN, 2)
53
+ anchor_string &&= %( #{anchor_string})
54
+ left, right = %(<a#{anchor_string} href="#{pagy_url_for(pagy, PAGE_TOKEN)}").split(PAGE_TOKEN, 2)
56
55
  # lambda used by all the helpers
57
56
  lambda do |page, text = pagy.label_for(page), classes: nil, aria_label: nil|
58
57
  count = counts[page - 1]
@@ -10,27 +10,29 @@ class Pagy # :nodoc:
10
10
  module CountlessExtra
11
11
  private
12
12
 
13
- # Return Pagy object and items
14
- def pagy_countless(collection, vars = {})
15
- pagy = Countless.new(pagy_countless_get_vars(collection, vars))
13
+ # Return Pagy object and records
14
+ def pagy_countless(collection, **vars)
15
+ pagy = Countless.new(**pagy_countless_get_vars(collection, vars))
16
16
  [pagy, pagy_countless_get_items(collection, pagy)]
17
17
  end
18
18
 
19
- # Sub-method called only by #pagy_countless: here for easy customization of variables by overriding
20
- def pagy_countless_get_vars(_collection, vars)
21
- pagy_set_items_from_params(vars) if defined?(ItemsExtra)
22
- vars[:page] ||= pagy_get_page(vars)
23
- vars
24
- end
25
-
26
19
  # Sub-method called only by #pagy_countless: here for easy customization of record-extraction by overriding
27
20
  # You may need to override this method for collections without offset|limit
28
21
  def pagy_countless_get_items(collection, pagy)
29
- return collection.offset(pagy.offset).limit(pagy.items) if pagy.vars[:countless_minimal]
22
+ return collection.offset(pagy.offset).limit(pagy.limit) if pagy.vars[:countless_minimal]
30
23
 
31
- fetched = collection.offset(pagy.offset).limit(pagy.items + 1).to_a # eager load items + 1
24
+ fetched = collection.offset(pagy.offset).limit(pagy.limit + 1).to_a # eager load limit + 1
32
25
  pagy.finalize(fetched.size) # finalize the pagy object
33
- fetched[0, pagy.items] # ignore eventual extra item
26
+ fetched[0, pagy.limit] # ignore eventual extra item
27
+ end
28
+
29
+ # Sub-method called only by #pagy: here for easy customization of variables by overriding
30
+ # You may need to override the count call for non AR collections
31
+ def pagy_countless_get_vars(_collection, vars)
32
+ vars.tap do |v|
33
+ v[:limit] ||= pagy_get_limit(v)
34
+ v[:page] ||= pagy_get_page(v)
35
+ end
34
36
  end
35
37
  end
36
38
  Backend.prepend CountlessExtra
@@ -28,18 +28,18 @@ class Pagy # :nodoc:
28
28
  args.define_singleton_method(:method_missing) { |*a| args += a }
29
29
  end
30
30
  end
31
- alias_method Pagy::DEFAULT[:elasticsearch_rails_pagy_search], :pagy_elasticsearch_rails
31
+ alias_method DEFAULT[:elasticsearch_rails_pagy_search], :pagy_elasticsearch_rails
32
32
  end
33
33
  Pagy::ElasticsearchRails = ModelExtension
34
34
 
35
35
  # Additions for the Pagy class
36
36
  module PagyAddOn
37
37
  # Create a Pagy object from an Elasticsearch::Model::Response::Response object
38
- def new_from_elasticsearch_rails(response, vars = {})
39
- vars[:items] = response.search.options[:size] || 10
40
- vars[:page] = ((response.search.options[:from] || 0) / vars[:items]) + 1
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
41
  vars[:count] = ElasticsearchRailsExtra.total_count(response)
42
- new(vars)
42
+ Pagy.new(**vars)
43
43
  end
44
44
  end
45
45
  Pagy.extend PagyAddOn
@@ -48,19 +48,19 @@ class Pagy # :nodoc:
48
48
  module BackendAddOn
49
49
  private
50
50
 
51
- # Return Pagy object and items
52
- def pagy_elasticsearch_rails(pagy_search_args, vars = {})
51
+ # Return Pagy object and records
52
+ def pagy_elasticsearch_rails(pagy_search_args, **vars)
53
53
  model, query_or_payload,
54
54
  options, *called = pagy_search_args
55
55
  vars = pagy_elasticsearch_rails_get_vars(nil, vars)
56
- options[:size] = vars[:items]
57
- options[:from] = vars[:items] * (vars[:page] - 1)
56
+ options[:size] = vars[:limit]
57
+ options[:from] = vars[:limit] * ((vars[:page] || 1) - 1)
58
58
  response = model.send(DEFAULT[:elasticsearch_rails_search], query_or_payload, **options)
59
59
  vars[:count] = ElasticsearchRailsExtra.total_count(response)
60
60
 
61
- pagy = ::Pagy.new(vars)
61
+ pagy = ::Pagy.new(**vars)
62
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.merge(page: pagy.page)) \
63
+ return pagy_elasticsearch_rails(pagy_search_args, **vars, page: pagy.page) \
64
64
  if defined?(::Pagy::OverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page
65
65
 
66
66
  [pagy, called.empty? ? response : response.send(*called)]
@@ -69,10 +69,10 @@ class Pagy # :nodoc:
69
69
  # Sub-method called only by #pagy_elasticsearch_rails: here for easy customization of variables by overriding
70
70
  # the _collection argument is not available when the method is called
71
71
  def pagy_elasticsearch_rails_get_vars(_collection, vars)
72
- pagy_set_items_from_params(vars) if defined?(ItemsExtra)
73
- vars[:items] ||= DEFAULT[:items]
74
- vars[:page] ||= pagy_get_page(vars)
75
- vars
72
+ vars.tap do |v|
73
+ v[:page] ||= pagy_get_page(v)
74
+ v[:limit] ||= pagy_get_limit(v) || DEFAULT[:limit]
75
+ end
76
76
  end
77
77
  end
78
78
  Backend.prepend BackendAddOn
@@ -3,27 +3,39 @@
3
3
 
4
4
  class Pagy # :nodoc:
5
5
  DEFAULT[:gearbox_extra] = true # extra enabled by default
6
- DEFAULT[:gearbox_items] = [15, 30, 60, 100]
6
+ DEFAULT[:gearbox_limit] = [15, 30, 60, 100]
7
7
 
8
- # Automatically change the number of items per page depending on the page number
9
- # accepts an array as the :gearbox_items variable, that will determine the items for the first pages
8
+ # Automatically change the limit depending on the page number
9
+ # accepts an array as the :gearbox_limit variable, that will determine the limit for the first pages
10
10
  module GearboxExtra
11
- # Setup @items based on the :gearbox_items variable
12
- def setup_items_var
13
- return super if !@vars[:gearbox_extra] || @vars[:items_extra]
11
+ # Assign @limit based on the :gearbox_limit variable
12
+ def assign_limit
13
+ return super if !@vars[:gearbox_extra] || @vars[:limit_extra]
14
14
 
15
- gears = @vars[:gearbox_items]
16
- raise VariableError.new(self, :gearbox_items, 'to be an Array of positives', gears) \
15
+ gears = @vars[:gearbox_limit]
16
+ raise VariableError.new(self, :gearbox_limit, 'to be an Array of positives', gears) \
17
17
  unless gears.is_a?(Array) && gears.all? { |num| num.positive? rescue false } # rubocop:disable Style/RescueModifier
18
18
 
19
- @items = gears[@page - 1] || gears.last
19
+ @limit = gears[@page - 1] || gears.last
20
20
  end
21
21
 
22
- # Setup Pagy @last based on the :gearbox_items variable and @count
23
- def setup_last_var
24
- return super if !@vars[:gearbox_extra] || @vars[:items_extra]
22
+ # Asgnsi @offset based on the :gearbox_limit variable
23
+ def assign_offset
24
+ return super if !@vars[:gearbox_extra] || @vars[:limit_extra]
25
25
 
26
- gears = @vars[:gearbox_items]
26
+ gears = @vars[:gearbox_limit]
27
+ @offset = if @page <= gears.count
28
+ gears[0, @page - 1].sum
29
+ else
30
+ gears.sum + (gears.last * (@page - gears.count - 1))
31
+ end + @outset
32
+ end
33
+
34
+ # Assign @last based on the :gearbox_limit variable and @count
35
+ def assign_last
36
+ return super if !@vars[:gearbox_extra] || @vars[:limit_extra]
37
+
38
+ gears = @vars[:gearbox_limit]
27
39
  # This algorithm is thousands of times faster than the one in the geared_pagination gem
28
40
  @last = (if count > (sum = gears.sum)
29
41
  [((count - sum).to_f / gears.last).ceil, 1].max + gears.count
@@ -36,19 +48,7 @@ class Pagy # :nodoc:
36
48
  end
37
49
  [pages, 1].max
38
50
  end)
39
- @last = vars[:max_pages] if vars[:max_pages] && @last > vars[:max_pages]
40
- end
41
-
42
- # Setup @offset based on the :gearbox_items variable
43
- def setup_offset_var
44
- return super if !@vars[:gearbox_extra] || @vars[:items_extra]
45
-
46
- gears = @vars[:gearbox_items]
47
- @offset = if @page <= gears.count
48
- gears[0, @page - 1].sum
49
- else
50
- gears.sum + (gears.last * (@page - gears.count - 1))
51
- end + @outset
51
+ @last = @vars[:max_pages] if @vars[:max_pages] && @last > @vars[:max_pages]
52
52
  end
53
53
  end
54
54
  prepend GearboxExtra
@@ -5,7 +5,7 @@ require_relative '../url_helpers'
5
5
 
6
6
  class Pagy # :nodoc:
7
7
  DEFAULT[:headers] = { page: 'current-page',
8
- items: 'page-items',
8
+ limit: 'page-items',
9
9
  count: 'total-count',
10
10
  pages: 'total-pages' }
11
11
  # Add specialized backend methods to add pagination response headers
@@ -21,32 +21,33 @@ class Pagy # :nodoc:
21
21
 
22
22
  # Generate a hash of RFC-8288 compliant http headers
23
23
  def pagy_headers(pagy)
24
- pagy_headers_hash(pagy).tap do |hash|
25
- hash['link'] = hash['link'].map { |rel, link| %(<#{link}>; rel="#{rel}") }.join(', ')
26
- end
27
- end
24
+ headers = pagy.vars[:headers]
25
+ pagy_link_header(pagy).tap do |hash|
26
+ hash[headers[:page]] = pagy.page.to_s if pagy.page && headers[:page]
27
+ hash[headers[:limit]] = pagy.limit.to_s \
28
+ if headers[:limit] && !(defined?(Calendar) && pagy.is_a?(Calendar::Unit))
29
+ return hash if (defined?(Countless) && pagy.is_a?(Countless)) || \
30
+ (defined?(Keyset) && pagy.is_a?(Keyset))
28
31
 
29
- # Generates a hash structure of the headers
30
- def pagy_headers_hash(pagy)
31
- countless = defined?(Countless) && pagy.is_a?(Countless)
32
- rel = { 'first' => 1, 'prev' => pagy.prev, 'next' => pagy.next }
33
- rel['last'] = pagy.last unless countless
34
- url_str = pagy_url_for(pagy, PAGE_TOKEN, absolute: true)
35
- link = rel.filter_map do |r, num|
36
- next unless num # rubocop:disable Layout/EmptyLineAfterGuardClause
37
- [r, url_str.sub(PAGE_TOKEN, num.to_s)]
38
- end.compact.to_h
39
- hash = { 'link' => link }
40
- headers = pagy.vars[:headers]
41
- hash[headers[:page]] = pagy.page.to_s if headers[:page]
42
- if headers[:items] && !(defined?(Calendar) && pagy.is_a?(Calendar)) # items is not for Calendar
43
- hash[headers[:items]] = pagy.vars[:items].to_s
44
- end
45
- unless countless
46
- hash[headers[:pages]] = pagy.pages.to_s if headers[:pages]
32
+ hash[headers[:pages]] = pagy.last.to_s if headers[:pages]
47
33
  hash[headers[:count]] = pagy.count.to_s if pagy.count && headers[:count] # count may be nil with Calendar
48
34
  end
49
- hash
35
+ end
36
+
37
+ def pagy_link_header(pagy)
38
+ { 'link' => [].tap do |link|
39
+ if defined?(Keyset) && pagy.is_a?(Keyset)
40
+ link << %(<#{pagy_url_for(pagy, nil, absolute: true)}>; rel="first")
41
+ link << %(<#{pagy_url_for(pagy, pagy.next, absolute: true)}>; rel="next") if pagy.next
42
+ else
43
+ url_str = pagy_url_for(pagy, PAGE_TOKEN, absolute: true)
44
+ link << %(<#{url_str.sub(PAGE_TOKEN, '1')}>; rel="first")
45
+ link << %(<#{url_str.sub(PAGE_TOKEN, pagy.prev.to_s)}>; rel="prev") if pagy.prev
46
+ link << %(<#{url_str.sub(PAGE_TOKEN, pagy.next.to_s)}>; rel="next") if pagy.next
47
+ link << %(<#{url_str.sub(PAGE_TOKEN, pagy.last.to_s)}>; rel="last") \
48
+ unless defined?(Countless) && pagy.is_a?(Countless)
49
+ end
50
+ end.join(', ') }
50
51
  end
51
52
  end
52
53
  Backend.prepend HeadersExtra
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../b64'
4
+
3
5
  class Pagy # :nodoc:
4
6
  DEFAULT[:steps] = false # default false will use {0 => @vars[:size]}
5
7
 
@@ -13,11 +15,11 @@ class Pagy # :nodoc:
13
15
  # `Pagy` instance method used by the `pagy*_nav_js` helpers.
14
16
  # It returns the sequels of width/series generated from the :steps hash
15
17
  # Example:
16
- # >> pagy = Pagy.new(count:1000, page: 20, steps: {0 => [1,2,2,1], 350 => [2,3,3,2], 550 => [3,4,4,3]})
18
+ # >> pagy = Pagy.new(count:1000, page: 20, steps: {0 => 5, 350 => 7, 550 => 9})
17
19
  # >> pagy.sequels
18
- # #=> { "0" => [1, :gap, 18, 19, "20", 21, 22, :gap, 50],
19
- # "350" => [1, 2, :gap, 17, 18, 19, "20", 21, 22, 23, :gap, 49, 50],
20
- # "550" => [1, 2, 3, :gap, 16, 17, 18, 19, "20", 21, 22, 23, 24, :gap, 48, 49, 50] }
20
+ # #=> { "0" => [18, 19, "20", 21, 22],
21
+ # "350" => [1, :gap, 19, "20", 21, :gap, 50],
22
+ # "550" => [1 :gap, 18, 19, "20", 21, 22, :gap, 50] }
21
23
  # Notice: if :steps is false it will use the single {0 => @vars[:size]} size
22
24
  def sequels(steps: @vars[:steps] || { 0 => @vars[:size] }, **_)
23
25
  raise VariableError.new(self, :steps, 'to define the 0 width', steps) unless steps.key?(0)
@@ -51,8 +53,7 @@ class Pagy # :nodoc:
51
53
  # Base64 encoded JSON is smaller than HTML escaped JSON
52
54
  def pagy_data(pagy, *args)
53
55
  args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
54
- strict_base64_encoded = [Oj.dump(args, mode: :strict)].pack('m0')
55
- %(data-pagy="#{strict_base64_encoded}")
56
+ %(data-pagy="#{B64.encode(Oj.dump(args, mode: :strict))}")
56
57
  end
57
58
  else
58
59
  require 'json'
@@ -60,8 +61,7 @@ class Pagy # :nodoc:
60
61
  # Base64 encoded JSON is smaller than HTML escaped JSON
61
62
  def pagy_data(pagy, *args)
62
63
  args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
63
- strict_base64_encoded = [args.to_json].pack('m0')
64
- %(data-pagy="#{strict_base64_encoded}")
64
+ %(data-pagy="#{B64.encode(args.to_json)}")
65
65
  end
66
66
  end
67
67
  end
@@ -24,10 +24,17 @@ class Pagy # :nodoc:
24
24
 
25
25
  # Return the jsonapi links
26
26
  def pagy_jsonapi_links(pagy, **opts)
27
- { first: pagy_url_for(pagy, 1, **opts),
28
- last: pagy_url_for(pagy, pagy.last, **opts),
29
- prev: pagy.prev ? pagy_url_for(pagy, pagy.prev, **opts) : nil,
30
- next: pagy.next ? pagy_url_for(pagy, pagy.next, **opts) : nil }
27
+ if defined?(Pagy::Keyset) && pagy.is_a?(Pagy::Keyset)
28
+ { first: pagy_url_for(pagy, nil, **opts),
29
+ last: nil,
30
+ prev: nil,
31
+ next: pagy.next ? pagy_url_for(pagy, pagy.next, **opts) : nil }
32
+ else
33
+ { first: pagy_url_for(pagy, 1, **opts),
34
+ last: pagy_url_for(pagy, pagy.last, **opts),
35
+ prev: pagy.prev ? pagy_url_for(pagy, pagy.prev, **opts) : nil,
36
+ next: pagy.next ? pagy_url_for(pagy, pagy.next, **opts) : nil }
37
+ end
31
38
  end
32
39
 
33
40
  # Should skip the jsonapi
@@ -40,38 +47,41 @@ class Pagy # :nodoc:
40
47
  # Override the Backend method
41
48
  def pagy_get_page(vars)
42
49
  return super if pagy_skip_jsonapi?(vars)
43
- return 1 if params[:page].nil?
50
+ return if params[:page].nil?
44
51
 
45
- [params[:page][vars[:page_param] || DEFAULT[:page_param]].to_i, 1].max
52
+ params[:page][vars[:page_param] || DEFAULT[:page_param]]
46
53
  end
47
54
  end
48
55
  Backend.prepend BackendOverride
49
56
 
50
- # Module overriding ItemsExtra
51
- module ItemsExtraOverride
57
+ # Module overriding LimitExtra
58
+ module LimitExtraOverride
52
59
  private
53
60
 
54
- # Override the ItemsExtra::Backend method
55
- def pagy_get_items_size(vars)
61
+ # Override the LimitExtra::Backend method
62
+ def pagy_get_limit_param(vars)
56
63
  return super if pagy_skip_jsonapi?(vars)
57
64
  return if params[:page].nil?
58
65
 
59
- params[:page][vars[:items_param] || DEFAULT[:items_param]]
66
+ params[:page][vars[:limit_param] || DEFAULT[:limit_param]]
60
67
  end
61
68
  end
62
69
  # :nocov:
63
- ItemsExtra::BackendAddOn.prepend ItemsExtraOverride if defined?(ItemsExtra::BackendAddOn)
70
+ LimitExtra::BackendAddOn.prepend LimitExtraOverride if defined?(LimitExtra::BackendAddOn)
64
71
  # :nocov:
65
72
 
66
73
  # Module overriding UrlHelper
67
74
  module UrlHelperOverride
68
75
  # Override UrlHelper method
69
- def pagy_set_query_params(page, vars, params)
76
+ def pagy_set_query_params(page, vars, query_params)
70
77
  return super unless vars[:jsonapi]
71
78
 
72
- params['page'] ||= {}
73
- params['page'][vars[:page_param].to_s] = page
74
- params['page'][vars[:items_param].to_s] = vars[:items] if vars[:items_extra]
79
+ query_params['page'] ||= {}
80
+ query_params['page'][vars[:page_param].to_s] = page if page
81
+ query_params['page'][vars[:limit_param].to_s] = vars[:limit] if vars[:limit_extra]
82
+ # :nocov:
83
+ query_params.delete(:page) if query_params['page'].empty?
84
+ # :nocov:
75
85
  end
76
86
  end
77
87
  UrlHelpers.prepend UrlHelperOverride
@@ -0,0 +1,36 @@
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/keyset
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../keyset'
5
+
6
+ class Pagy # :nodoc:
7
+ # Add keyset pagination
8
+ module KeysetExtra
9
+ private
10
+
11
+ # Return Pagy::Keyset object and paginated records
12
+ def pagy_keyset(set, **vars)
13
+ pagy = Keyset.new(set, **pagy_keyset_get_vars(vars))
14
+ [pagy, pagy.records]
15
+ end
16
+
17
+ # Sub-method called only by #pagy_keyset: here for easy customization of variables by overriding
18
+ def pagy_keyset_get_vars(vars)
19
+ vars.tap do |v|
20
+ v[:page] ||= pagy_get_page(v)
21
+ v[:limit] ||= pagy_get_limit(v)
22
+ end
23
+ end
24
+
25
+ # Return the URL string for the first page
26
+ def pagy_keyset_first_url(pagy, **vars)
27
+ pagy_url_for(pagy, nil, **vars)
28
+ end
29
+
30
+ # Return the URL string for the next page or nil
31
+ def pagy_keyset_next_url(pagy, **vars)
32
+ pagy_url_for(pagy, pagy.next, **vars) if pagy.next
33
+ end
34
+ end
35
+ Backend.prepend KeysetExtra
36
+ end
@@ -0,0 +1,63 @@
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/limit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'js_tools'
5
+
6
+ class Pagy # :nodoc:
7
+ DEFAULT[:limit_param] = :limit
8
+ DEFAULT[:limit_max] = 100
9
+ DEFAULT[:limit_extra] = true # extra enabled by default
10
+
11
+ # Allow the client to request a custom limit per page with an optional selector UI
12
+ module LimitExtra
13
+ # Additions for the Backend module
14
+ module BackendAddOn
15
+ private
16
+
17
+ # Set the limit variable considering the params and other pagy variables
18
+ def pagy_get_limit(vars)
19
+ return unless vars.key?(:limit_extra) ? vars[:limit_extra] : DEFAULT[:limit_extra] # :limit_extra is false
20
+ return unless (limit_count = pagy_get_limit_param(vars)) # no limit from request params
21
+
22
+ vars[:limit] = [limit_count.to_i, vars.key?(:limit_max) ? vars[:limit_max] : DEFAULT[:limit_max]].compact.min
23
+ end
24
+
25
+ # Get the limit count from the params
26
+ # Overridable by the jsonapi extra
27
+ def pagy_get_limit_param(vars)
28
+ params[vars[:limit_param] || DEFAULT[:limit_param]]
29
+ end
30
+ end
31
+ Backend.prepend LimitExtra::BackendAddOn
32
+
33
+ # Additions for the Frontend module
34
+ module FrontendAddOn
35
+ LIMIT_TOKEN = '__pagy_limit__'
36
+
37
+ # Return the limit selector HTML. For example "Show [20] items per page"
38
+ def pagy_limit_selector_js(pagy, id: nil, item_name: nil)
39
+ return '' unless pagy.vars[:limit_extra]
40
+
41
+ id = %( id="#{id}") if id
42
+ vars = pagy.vars
43
+ limit = vars[:limit]
44
+ vars[:limit] = LIMIT_TOKEN
45
+ url_token = pagy_url_for(pagy, PAGE_TOKEN)
46
+ vars[:limit] = limit # restore the limit
47
+
48
+ limit_input = %(<input name="limit" type="number" min="1" max="#{vars[:limit_max]}" value="#{
49
+ limit}" style="padding: 0; text-align: center; width: #{limit.to_s.length + 1}rem;">#{JSTools::A_TAG})
50
+
51
+ %(<span#{id} class="pagy limit-selector-js" #{
52
+ pagy_data(pagy, :selector, pagy.from, url_token)
53
+ }><label>#{
54
+ pagy_t('pagy.limit_selector_js',
55
+ item_name: item_name || pagy_t('pagy.item_name', count: limit),
56
+ limit_input:,
57
+ count: limit)
58
+ }</label></span>)
59
+ end
60
+ end
61
+ Frontend.prepend LimitExtra::FrontendAddOn
62
+ end
63
+ end
@@ -20,12 +20,12 @@ class Pagy # :nodoc:
20
20
  # Extension for the Pagy class
21
21
  module PagyExtension
22
22
  # Create a Pagy object from a Meilisearch results
23
- def new_from_meilisearch(results, vars = {})
24
- vars[:items] = results.raw_answer['hitsPerPage']
23
+ def new_from_meilisearch(results, **vars)
24
+ vars[:limit] = results.raw_answer['hitsPerPage']
25
25
  vars[:page] = results.raw_answer['page']
26
26
  vars[:count] = results.raw_answer['totalHits']
27
27
 
28
- new(vars)
28
+ new(**vars)
29
29
  end
30
30
  end
31
31
  Pagy.extend PagyExtension
@@ -35,17 +35,17 @@ class Pagy # :nodoc:
35
35
  private
36
36
 
37
37
  # Return Pagy object and results
38
- def pagy_meilisearch(pagy_search_args, vars = {})
38
+ def pagy_meilisearch(pagy_search_args, **vars)
39
39
  model, term, options = pagy_search_args
40
40
  vars = pagy_meilisearch_get_vars(nil, vars)
41
- options[:hits_per_page] = vars[:items]
41
+ options[:hits_per_page] = vars[:limit]
42
42
  options[:page] = vars[:page]
43
43
  results = model.send(:ms_search, term, options)
44
44
  vars[:count] = results.raw_answer['totalHits']
45
45
 
46
- pagy = ::Pagy.new(vars)
46
+ pagy = ::Pagy.new(**vars)
47
47
  # with :last_page overflow we need to re-run the method in order to get the hits
48
- return pagy_meilisearch(pagy_search_args, vars.merge(page: pagy.page)) \
48
+ return pagy_meilisearch(pagy_search_args, **vars, page: pagy.page) \
49
49
  if defined?(::Pagy::OverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page
50
50
 
51
51
  [pagy, results]
@@ -54,10 +54,10 @@ class Pagy # :nodoc:
54
54
  # Sub-method called only by #pagy_meilisearch: here for easy customization of variables by overriding.
55
55
  # The _collection argument is not available when the method is called.
56
56
  def pagy_meilisearch_get_vars(_collection, vars)
57
- pagy_set_items_from_params(vars) if defined?(ItemsExtra)
58
- vars[:items] ||= DEFAULT[:items]
59
- vars[:page] ||= pagy_get_page(vars)
60
- vars
57
+ vars.tap do |v|
58
+ v[:page] ||= pagy_get_page(v)
59
+ v[:limit] ||= pagy_get_limit(v) || DEFAULT[:limit]
60
+ end
61
61
  end
62
62
  end
63
63
  Backend.prepend BackendAddOn
@@ -5,7 +5,7 @@ require_relative '../url_helpers'
5
5
 
6
6
  class Pagy # :nodoc:
7
7
  DEFAULT[:metadata] = %i[ scaffold_url first_url prev_url page_url next_url last_url
8
- count page items vars pages last in from to prev next series ]
8
+ count page limit vars pages last in from to prev next series ]
9
9
 
10
10
  # Add a specialized backend method for pagination metadata
11
11
  module MetadataExtra
@@ -18,7 +18,7 @@ class Pagy # :nodoc:
18
18
  scaffold_url = pagy_url_for(pagy, PAGE_TOKEN, absolute:)
19
19
  {}.tap do |metadata|
20
20
  keys = if defined?(Calendar::Unit) && pagy.is_a?(Calendar::Unit)
21
- pagy.vars[:metadata] - %i[count items]
21
+ pagy.vars[:metadata] - %i[count limit]
22
22
  else
23
23
  pagy.vars[:metadata]
24
24
  end
@@ -14,7 +14,7 @@ class Pagy # :nodoc:
14
14
  end
15
15
 
16
16
  # Add rescue clause for different behaviors
17
- def initialize(vars)
17
+ def initialize(**vars)
18
18
  @overflow ||= false # still true if :last_page re-run the method after an overflow
19
19
  super
20
20
  rescue OverflowError
@@ -24,10 +24,10 @@ class Pagy # :nodoc:
24
24
  raise # same as without the extra
25
25
  when :last_page
26
26
  requested_page = @vars[:page] # save the requested page (even after re-run)
27
- initialize vars.merge!(page: @last) # re-run with the last page
27
+ initialize(**vars, page: @last) # re-run with the last page
28
28
  @vars[:page] = requested_page # restore the requested page
29
29
  when :empty_page
30
- @offset = @items = @in = @from = @to = 0 # vars relative to the actual page
30
+ @offset = @limit = @in = @from = @to = 0 # vars relative to the actual page
31
31
  if defined?(Calendar::Unit) \
32
32
  && is_a?(Calendar::Unit) # only for Calendar::Units instances
33
33
  edge = @order == :asc ? @final : @initial # get the edge of the overflow side (neat, but any time would do)
@@ -57,7 +57,7 @@ class Pagy # :nodoc:
57
57
  # Support for Pagy::Countless class
58
58
  module CountlessOverride
59
59
  # Add rescue clause for different behaviors
60
- def finalize(items)
60
+ def finalize(fetched_size)
61
61
  @overflow = false
62
62
  super
63
63
  rescue OverflowError
@@ -66,8 +66,8 @@ class Pagy # :nodoc:
66
66
  when :exception
67
67
  raise # same as without the extra
68
68
  when :empty_page
69
- @offset = @items = @from = @to = 0 # vars relative to the actual page
70
- @vars[:size] = [] # no page in the series
69
+ @offset = @limit = @from = @to = 0 # vars relative to the actual page
70
+ @vars[:size] = 0 # no page in the series
71
71
  self
72
72
  else
73
73
  raise VariableError.new(self, :overflow, 'to be in [:empty_page, :exception]', @vars[:overflow])