pagy 4.10.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/pagy.rb +71 -44
  3. data/lib/javascripts/pagy.js +15 -6
  4. data/lib/pagy/backend.rb +9 -12
  5. data/lib/pagy/console.rb +6 -4
  6. data/lib/pagy/countless.rb +21 -21
  7. data/lib/pagy/exceptions.rb +16 -16
  8. data/lib/pagy/extras/arel.rb +6 -6
  9. data/lib/pagy/extras/array.rb +6 -6
  10. data/lib/pagy/extras/bootstrap.rb +35 -29
  11. data/lib/pagy/extras/bulma.rb +43 -32
  12. data/lib/pagy/extras/countless.rb +14 -14
  13. data/lib/pagy/extras/elasticsearch_rails.rb +64 -47
  14. data/lib/pagy/extras/foundation.rb +29 -26
  15. data/lib/pagy/extras/gearbox.rb +42 -0
  16. data/lib/pagy/extras/headers.rb +24 -16
  17. data/lib/pagy/extras/i18n.rb +7 -16
  18. data/lib/pagy/extras/items.rb +38 -39
  19. data/lib/pagy/extras/materialize.rb +31 -30
  20. data/lib/pagy/extras/meilisearch.rb +50 -45
  21. data/lib/pagy/extras/metadata.rb +14 -20
  22. data/lib/pagy/extras/navs.rb +26 -26
  23. data/lib/pagy/extras/overflow.rb +56 -60
  24. data/lib/pagy/extras/searchkick.rb +51 -45
  25. data/lib/pagy/extras/semantic.rb +31 -30
  26. data/lib/pagy/extras/shared.rb +43 -40
  27. data/lib/pagy/extras/standalone.rb +41 -45
  28. data/lib/pagy/extras/support.rb +20 -13
  29. data/lib/pagy/extras/trim.rb +11 -11
  30. data/lib/pagy/extras/uikit.rb +30 -28
  31. data/lib/pagy/frontend.rb +25 -49
  32. data/lib/pagy/i18n.rb +159 -0
  33. data/lib/pagy/url_helpers.rb +24 -0
  34. data/lib/pagy.rb +54 -29
  35. data/lib/templates/uikit_nav.html.erb +1 -1
  36. data/lib/templates/uikit_nav.html.slim +1 -1
  37. metadata +7 -10
  38. data/lib/locales/utils/i18n.rb +0 -17
  39. data/lib/locales/utils/loader.rb +0 -31
  40. data/lib/locales/utils/p11n.rb +0 -112
  41. data/lib/pagy/deprecation.rb +0 -27
@@ -4,8 +4,9 @@
4
4
  require 'pagy/extras/shared'
5
5
 
6
6
  class Pagy
7
- module Frontend
8
-
7
+ # Frontend modules are specially optimized for performance.
8
+ # The resulting code may not look very elegant, but produces the best benchmarks
9
+ module BulmaExtra
9
10
  # Pagination for Bulma: it returns the html with the series of links to the pages
10
11
  def pagy_bulma_nav(pagy, pagy_id: nil, link_extra: '')
11
12
  p_id = %( id="#{pagy_id}") if pagy_id
@@ -16,67 +17,77 @@ class Pagy
16
17
  html << %(<ul class="pagination-list">)
17
18
  pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
18
19
  html << case item
19
- when Integer then %(<li>#{link.call item, item, %(class="pagination-link" aria-label="goto page #{item}") }</li>) # page link
20
- when String then %(<li>#{link.call item, item, %(class="pagination-link is-current" aria-label="page #{item}" aria-current="page")}</li>) # active page
21
- when :gap then %(<li><span class="pagination-ellipsis">#{pagy_t 'pagy.nav.gap'}</span></li>) # page gap
20
+ when Integer
21
+ %(<li>#{link.call item, item, %(class="pagination-link" aria-label="goto page #{item}")}</li>)
22
+ when String
23
+ %(<li>#{link.call item, item,
24
+ %(class="pagination-link is-current" aria-label="page #{item}" aria-current="page")}</li>)
25
+ when :gap
26
+ %(<li><span class="pagination-ellipsis">#{pagy_t 'pagy.nav.gap'}</span></li>)
27
+ else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
22
28
  end
23
29
  end
24
30
  html << %(</ul></nav>)
25
31
  end
26
32
 
27
- def pagy_bulma_nav_js(pagy, deprecated_id=nil, pagy_id: nil, link_extra: '', steps: nil)
28
- pagy_id = Pagy.deprecated_arg(:id, deprecated_id, :pagy_id, pagy_id) if deprecated_id
33
+ def pagy_bulma_nav_js(pagy, pagy_id: nil, link_extra: '', steps: nil)
29
34
  p_id = %( id="#{pagy_id}") if pagy_id
30
35
  link = pagy_link_proc(pagy, link_extra: link_extra)
31
36
  tags = { 'before' => %(#{pagy_bulma_prev_next_html(pagy, link)}<ul class="pagination-list">),
32
- 'link' => %(<li>#{link.call PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link" aria-label="goto page #{PAGE_PLACEHOLDER}")}</li>),
33
- 'active' => %(<li>#{link.call PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link is-current" aria-current="page" aria-label="page #{PAGE_PLACEHOLDER}")}</li>),
34
- 'gap' => %(<li><span class="pagination-ellipsis">#{pagy_t 'pagy.nav.gap' }</span></li>),
37
+ 'link' => %(<li>#{link.call PAGE_PLACEHOLDER, PAGE_PLACEHOLDER,
38
+ %(class="pagination-link" aria-label="goto page #{PAGE_PLACEHOLDER}")}</li>),
39
+ 'active' => %(<li>#{link.call PAGE_PLACEHOLDER, PAGE_PLACEHOLDER,
40
+ %(class="pagination-link is-current" aria-current="page" aria-label="page #{
41
+ PAGE_PLACEHOLDER}")}</li>),
42
+ 'gap' => %(<li><span class="pagination-ellipsis">#{pagy_t 'pagy.nav.gap'}</span></li>),
35
43
  'after' => '</ul>' }
36
44
 
37
- %(<nav#{p_id} class="pagy-njs pagy-bulma-nav-js pagination is-centered" aria-label="pagination" #{pagy_json_attr(pagy, :nav, tags, pagy.sequels(steps))}></nav>)
45
+ %(<nav#{p_id} class="pagy-njs pagy-bulma-nav-js pagination is-centered" aria-label="pagination" #{pagy_json_attr(
46
+ pagy, :nav, tags, pagy.sequels(steps)
47
+ )}></nav>)
38
48
  end
39
49
 
40
50
  # Javascript combo pagination for Bulma: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript
41
- def pagy_bulma_combo_nav_js(pagy, deprecated_id=nil, pagy_id: nil, link_extra: '')
42
- pagy_id = Pagy.deprecated_arg(:id, deprecated_id, :pagy_id, pagy_id) if deprecated_id
51
+ def pagy_bulma_combo_nav_js(pagy, pagy_id: nil, link_extra: '')
43
52
  p_id = %( id="#{pagy_id}") if pagy_id
44
53
  link = pagy_link_proc(pagy, link_extra: link_extra)
45
54
  p_page = pagy.page
46
55
  p_pages = pagy.pages
47
- input = %(<input class="input" type="number" min="1" max="#{p_pages}" value="#{p_page}" style="padding: 0; text-align: center; width: #{p_pages.to_s.length+1}rem; margin:0 0.3rem;">)
56
+ input = %(<input class="input" type="number" min="1" max="#{p_pages}" value="#{
57
+ p_page}" style="padding: 0; text-align: center; width: #{p_pages.to_s.length + 1}rem; margin:0 0.3rem;">)
48
58
 
49
- %(<nav#{p_id} class="pagy-bulma-combo-nav-js" aria-label="pagination"><div class="field is-grouped is-grouped-centered" role="group" #{
50
- pagy_json_attr pagy, :combo_nav, p_page, pagy_marked_link(link)
51
- }>#{
59
+ html = %(<nav#{p_id} class="pagy-bulma-combo-nav-js" aria-label="pagination">)
60
+ %(#{html}<div class="field is-grouped is-grouped-centered" role="group" #{
61
+ pagy_json_attr pagy, :combo_nav, p_page, pagy_marked_link(link)}>#{
52
62
  if (p_prev = pagy.prev)
53
63
  %(<p class="control">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'class="button" aria-label="previous page"'}</p>)
54
64
  else
55
65
  %(<p class="control"><a class="button" disabled>#{pagy_t 'pagy.nav.prev'}</a></p>)
56
66
  end
57
- }<div class="pagy-combo-input control level is-mobile">#{pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages}</div>#{
67
+ }<div class="pagy-combo-input control level is-mobile">#{
68
+ pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages}</div>#{
58
69
  if (p_next = pagy.next)
59
70
  %(<p class="control">#{link.call p_next, pagy_t('pagy.nav.next'), 'class="button" aria-label="next page"'}</p>)
60
71
  else
61
72
  %(<p class="control"><a class="button" disabled>#{pagy_t 'pagy.nav.next'}</a></p>)
62
73
  end
63
- }</div></nav>)
74
+ }</div></nav>)
64
75
  end
65
76
 
66
77
  private
67
78
 
68
- def pagy_bulma_prev_next_html(pagy, link)
69
- html = +if (p_prev = pagy.prev)
70
- link.call p_prev, pagy_t('pagy.nav.prev'), 'class="pagination-previous" aria-label="previous page"'
71
- else
72
- %(<a class="pagination-previous" disabled>#{pagy_t 'pagy.nav.prev'}</a>)
73
- end
74
- html << if (p_next = pagy.next)
75
- link.call p_next, pagy_t('pagy.nav.next'), 'class="pagination-next" aria-label="next page"'
76
- else
77
- %(<a class="pagination-next" disabled>#{pagy_t 'pagy.nav.next' }</a>)
78
- end
79
- end
80
-
79
+ def pagy_bulma_prev_next_html(pagy, link)
80
+ html = +if (p_prev = pagy.prev)
81
+ link.call p_prev, pagy_t('pagy.nav.prev'), 'class="pagination-previous" aria-label="previous page"'
82
+ else
83
+ %(<a class="pagination-previous" disabled>#{pagy_t 'pagy.nav.prev'}</a>)
84
+ end
85
+ html << if (p_next = pagy.next)
86
+ link.call p_next, pagy_t('pagy.nav.next'), 'class="pagination-next" aria-label="next page"'
87
+ else
88
+ %(<a class="pagination-next" disabled>#{pagy_t 'pagy.nav.next'}</a>)
89
+ end
90
+ end
81
91
  end
92
+ Frontend.prepend BulmaExtra
82
93
  end
@@ -4,33 +4,33 @@
4
4
  require 'pagy/countless'
5
5
 
6
6
  class Pagy
7
+ DEFAULT[:countless_minimal] = false
7
8
 
8
- module Backend
9
- private # the whole module is private so no problem with including it in a controller
9
+ module CountlessExtra
10
+ private
10
11
 
11
12
  # Return Pagy object and items
12
- def pagy_countless(collection, vars={})
13
+ def pagy_countless(collection, vars = {})
13
14
  pagy = Pagy::Countless.new(pagy_countless_get_vars(collection, vars))
14
- [ pagy, pagy_countless_get_items(collection, pagy) ]
15
+ [pagy, pagy_countless_get_items(collection, pagy)]
15
16
  end
16
17
 
17
18
  # Sub-method called only by #pagy_countless: here for easy customization of variables by overriding
18
19
  def pagy_countless_get_vars(_collection, vars)
19
- pagy_set_items_from_params(vars) if defined?(UseItemsExtra)
20
- vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ]
20
+ pagy_set_items_from_params(vars) if defined?(ItemsExtra)
21
+ vars[:page] ||= params[vars[:page_param] || DEFAULT[:page_param]]
21
22
  vars
22
23
  end
23
24
 
24
25
  # Sub-method called only by #pagy_countless: here for easy customization of record-extraction by overriding
26
+ # You may need to override this method for collections without offset|limit
25
27
  def pagy_countless_get_items(collection, pagy)
26
- # This should work with ActiveRecord, Sequel, Mongoid...
27
- items = collection.offset(pagy.offset).limit(pagy.items + 1).to_a
28
- items_size = items.size
29
- items.pop if items_size == pagy.items + 1
30
- # finalize may adjust pagy.items, so must be used after checking the size
31
- pagy.finalize(items_size)
32
- items
33
- end
28
+ return collection.offset(pagy.offset).limit(pagy.items) if pagy.vars[:countless_minimal]
34
29
 
30
+ fetched = collection.offset(pagy.offset).limit(pagy.items + 1).to_a # eager load items + 1
31
+ pagy.finalize(fetched.size) # finalize the pagy object
32
+ fetched[0, pagy.items] # ignore eventual extra item
33
+ end
35
34
  end
35
+ Backend.prepend CountlessExtra
36
36
  end
@@ -2,60 +2,77 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class Pagy
5
+ DEFAULT[:elasticsearch_rails_search_method] ||= :pagy_search
5
6
 
6
- VARS[:elasticsearch_rails_search_method] ||= :pagy_search
7
+ module ElasticsearchRailsExtra
8
+ module_function
7
9
 
8
- module ElasticsearchRails
9
- # returns an array used to delay the call of #search
10
- # after the pagination variables are merged to the options
11
- # it also pushes to the same array an eventually called method
12
- def pagy_elasticsearch_rails(query_or_payload, **options)
13
- [self, query_or_payload, options].tap do |args|
14
- args.define_singleton_method(:method_missing){|*a| args += a}
15
- end
10
+ # Get the count from different version of ElasticsearchRails
11
+ def total_count(response)
12
+ total = if response.respond_to?(:raw_response)
13
+ response.raw_response['hits']['total']
14
+ else
15
+ response.response['hits']['total']
16
+ end
17
+ total.is_a?(Hash) ? total['value'] : total
16
18
  end
17
- alias_method VARS[:elasticsearch_rails_search_method], :pagy_elasticsearch_rails
18
- end
19
-
20
- # create a Pagy object from an Elasticsearch::Model::Response::Response object
21
- def self.new_from_elasticsearch_rails(response, vars={})
22
- vars[:items] = response.search.options[:size] || 10
23
- vars[:page] = (response.search.options[:from] || 0) / vars[:items] + 1
24
- total = response.respond_to?(:raw_response) ? response.raw_response['hits']['total'] : response.response['hits']['total']
25
- vars[:count] = total.is_a?(Hash) ? total['value'] : total
26
- new(vars)
27
- end
28
19
 
29
- # Add specialized backend methods to paginate ElasticsearchRails searches
30
- module Backend
31
- private
32
-
33
- # Return Pagy object and items
34
- def pagy_elasticsearch_rails(pagy_search_args, vars={})
35
- model, query_or_payload, options, *called = pagy_search_args
36
- vars = pagy_elasticsearch_rails_get_vars(nil, vars)
37
- options[:size] = vars[:items]
38
- options[:from] = vars[:items] * (vars[:page] - 1)
39
- response = model.search(query_or_payload, **options)
40
- total = response.respond_to?(:raw_response) ? response.raw_response['hits']['total'] : response.response['hits']['total']
41
- vars[:count] = total.is_a?(Hash) ? total['value'] : total
42
-
43
- pagy = Pagy.new(vars)
44
- # with :last_page overflow we need to re-run the method in order to get the hits
45
- return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page)) \
46
- if defined?(Pagy::UseOverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page
47
-
48
- [ pagy, called.empty? ? response : response.send(*called) ]
20
+ module ElasticsearchRails
21
+ # Return an array used to delay the call of #search
22
+ # after the pagination variables are merged to the options.
23
+ # It also pushes to the same array an optional method call.
24
+ def pagy_elasticsearch_rails(query_or_payload, **options)
25
+ [self, query_or_payload, options].tap do |args|
26
+ args.define_singleton_method(:method_missing) { |*a| args += a }
27
+ end
28
+ end
29
+ alias_method Pagy::DEFAULT[:elasticsearch_rails_search_method], :pagy_elasticsearch_rails
49
30
  end
50
31
 
51
- # Sub-method called only by #pagy_elasticsearch_rails: here for easy customization of variables by overriding
52
- # the _collection argument is not available when the method is called
53
- def pagy_elasticsearch_rails_get_vars(_collection, vars)
54
- pagy_set_items_from_params(vars) if defined?(UseItemsExtra)
55
- vars[:items] ||= VARS[:items]
56
- vars[:page] ||= (params[ vars[:page_param] || VARS[:page_param] ] || 1).to_i
57
- vars
32
+ # Additions for the Pagy class
33
+ module Pagy
34
+ # Create a Pagy object from an Elasticsearch::Model::Response::Response object
35
+ def new_from_elasticsearch_rails(response, vars = {})
36
+ vars[:items] = response.search.options[:size] || 10
37
+ vars[:page] = ((response.search.options[:from] || 0) / vars[:items]) + 1
38
+ vars[:count] = ElasticsearchRailsExtra.total_count(response)
39
+ new(vars)
40
+ end
58
41
  end
59
42
 
43
+ # Add specialized backend methods to paginate ElasticsearchRails searches
44
+ module Backend
45
+ private
46
+
47
+ # Return Pagy object and items
48
+ def pagy_elasticsearch_rails(pagy_search_args, vars = {})
49
+ model, query_or_payload,
50
+ options, *called = pagy_search_args
51
+ vars = pagy_elasticsearch_rails_get_vars(nil, vars)
52
+ options[:size] = vars[:items]
53
+ options[:from] = vars[:items] * (vars[:page] - 1)
54
+ response = model.search(query_or_payload, **options)
55
+ vars[:count] = ElasticsearchRailsExtra.total_count(response)
56
+
57
+ pagy = ::Pagy.new(vars)
58
+ # with :last_page overflow we need to re-run the method in order to get the hits
59
+ return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page)) \
60
+ if defined?(::Pagy::OverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page
61
+
62
+ [pagy, called.empty? ? response : response.send(*called)]
63
+ end
64
+
65
+ # Sub-method called only by #pagy_elasticsearch_rails: here for easy customization of variables by overriding
66
+ # the _collection argument is not available when the method is called
67
+ def pagy_elasticsearch_rails_get_vars(_collection, vars)
68
+ pagy_set_items_from_params(vars) if defined?(ItemsExtra)
69
+ vars[:items] ||= DEFAULT[:items]
70
+ vars[:page] ||= (params[vars[:page_param] || DEFAULT[:page_param]] || 1).to_i
71
+ vars
72
+ end
73
+ end
60
74
  end
75
+ ElasticsearchRails = ElasticsearchRailsExtra::ElasticsearchRails
76
+ extend ElasticsearchRailsExtra::Pagy
77
+ Backend.prepend ElasticsearchRailsExtra::Backend
61
78
  end
@@ -4,8 +4,9 @@
4
4
  require 'pagy/extras/shared'
5
5
 
6
6
  class Pagy
7
- module Frontend
8
-
7
+ # Frontend modules are specially optimized for performance.
8
+ # The resulting code may not look very elegant, but produces the best benchmarks
9
+ module FoundationExtra
9
10
  # Pagination for Foundation: it returns the html with the series of links to the pages
10
11
  def pagy_foundation_nav(pagy, pagy_id: nil, link_extra: '')
11
12
  p_id = %( id="#{pagy_id}") if pagy_id
@@ -18,6 +19,7 @@ class Pagy
18
19
  when Integer then %(<li>#{link.call item}</li>) # page link
19
20
  when String then %(<li class="current">#{item}</li>) # active page
20
21
  when :gap then %(<li class="ellipsis gap" aria-hidden="true"></li>) # page gap
22
+ else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
21
23
  end
22
24
  end
23
25
  html << pagy_foundation_next_html(pagy, link)
@@ -25,8 +27,7 @@ class Pagy
25
27
  end
26
28
 
27
29
  # Javascript pagination for foundation: it returns a nav and a JSON tag used by the Pagy.nav javascript
28
- def pagy_foundation_nav_js(pagy, deprecated_id=nil, pagy_id: nil, link_extra: '', steps: nil)
29
- pagy_id = Pagy.deprecated_arg(:id, deprecated_id, :pagy_id, pagy_id) if deprecated_id
30
+ def pagy_foundation_nav_js(pagy, pagy_id: nil, link_extra: '', steps: nil)
30
31
  p_id = %( id="#{pagy_id}") if pagy_id
31
32
  link = pagy_link_proc(pagy, link_extra: link_extra)
32
33
  tags = { 'before' => %(<ul class="pagination">#{pagy_foundation_prev_html pagy, link}),
@@ -35,52 +36,54 @@ class Pagy
35
36
  'gap' => %(<li class="ellipsis gap" aria-hidden="true"></li>),
36
37
  'after' => %(#{pagy_foundation_next_html pagy, link}</ul>) }
37
38
 
38
- %(<nav#{p_id} class="pagy-njs pagy-foundation-nav-js" aria-label="Pagination" #{pagy_json_attr(pagy, :nav, tags, pagy.sequels(steps))}></nav>)
39
+ %(<nav#{p_id} class="pagy-njs pagy-foundation-nav-js" aria-label="Pagination" #{
40
+ pagy_json_attr(pagy, :nav, tags, pagy.sequels(steps))}></nav>)
39
41
  end
40
42
 
41
43
  # Javascript combo pagination for Foundation: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript
42
- def pagy_foundation_combo_nav_js(pagy, deprecated_id=nil, pagy_id: nil, link_extra: '')
43
- pagy_id = Pagy.deprecated_arg(:id, deprecated_id, :pagy_id, pagy_id) if deprecated_id
44
+ def pagy_foundation_combo_nav_js(pagy, pagy_id: nil, link_extra: '')
44
45
  p_id = %( id="#{pagy_id}") if pagy_id
45
46
  link = pagy_link_proc(pagy, link_extra: link_extra)
46
47
  p_page = pagy.page
47
48
  p_pages = pagy.pages
48
- input = %(<input class="input-group-field cell shrink" type="number" min="1" max="#{p_pages}" value="#{p_page}" style="width: #{p_pages.to_s.length+1}rem; padding: 0 0.3rem; margin: 0 0.3rem;">)
49
+ input = %(<input class="input-group-field cell shrink" type="number" min="1" max="#{
50
+ p_pages}" value="#{p_page}" style="width: #{
51
+ p_pages.to_s.length + 1}rem; padding: 0 0.3rem; margin: 0 0.3rem;">)
49
52
 
50
53
  %(<nav#{p_id} class="pagy-foundation-combo-nav-js" aria-label="Pagination"><div class="input-group" #{
51
- pagy_json_attr pagy, :combo_nav, p_page, pagy_marked_link(link)
52
- }>#{
54
+ pagy_json_attr pagy, :combo_nav, p_page, pagy_marked_link(link)}>#{
53
55
  if (p_prev = pagy.prev)
54
- link.call p_prev, pagy_t('pagy.nav.prev'), 'style="margin-bottom: 0" aria-label="previous" class="prev button primary"'
56
+ link.call p_prev, pagy_t('pagy.nav.prev'),
57
+ 'style="margin-bottom: 0" aria-label="previous" class="prev button primary"'
55
58
  else
56
59
  %(<a style="margin-bottom: 0" class="prev button primary disabled" href="#">#{pagy_t 'pagy.nav.prev'}</a>)
57
60
  end
58
- }<span class="input-group-label">#{pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages}</span>#{
61
+ }<span class="input-group-label">#{pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages}</span>#{
59
62
  if (p_next = pagy.next)
60
63
  link.call p_next, pagy_t('pagy.nav.next'), 'style="margin-bottom: 0" aria-label="next" class="next button primary"'
61
64
  else
62
65
  %(<a style="margin-bottom: 0" class="next button primary disabled" href="#">#{pagy_t 'pagy.nav.next'}</a>)
63
66
  end
64
- }</div></nav>)
67
+ }</div></nav>)
65
68
  end
66
69
 
67
70
  private
68
71
 
69
- def pagy_foundation_prev_html(pagy, link)
70
- if (p_prev = pagy.prev)
71
- %(<li class="prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</li>)
72
- else
73
- %(<li class="prev disabled">#{pagy_t 'pagy.nav.prev' }</li>)
74
- end
72
+ def pagy_foundation_prev_html(pagy, link)
73
+ if (p_prev = pagy.prev)
74
+ %(<li class="prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</li>)
75
+ else
76
+ %(<li class="prev disabled">#{pagy_t 'pagy.nav.prev'}</li>)
75
77
  end
78
+ end
76
79
 
77
- def pagy_foundation_next_html(pagy, link)
78
- if (p_next = pagy.next)
79
- %(<li class="next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</li>)
80
- else
81
- %(<li class="next disabled">#{pagy_t 'pagy.nav.next'}</li>)
82
- end
80
+ def pagy_foundation_next_html(pagy, link)
81
+ if (p_next = pagy.next)
82
+ %(<li class="next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</li>)
83
+ else
84
+ %(<li class="next disabled">#{pagy_t 'pagy.nav.next'}</li>)
83
85
  end
84
-
86
+ end
85
87
  end
88
+ Frontend.prepend FoundationExtra
86
89
  end
@@ -0,0 +1,42 @@
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/gearbox
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy
5
+ DEFAULT[:gearbox_extra] = true # extra enabled by default
6
+ DEFAULT[:gearbox_items] = [15, 30, 60, 100]
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
10
+ module GearboxExtra
11
+ # Setup @items based on the :items variable
12
+ def setup_items_var
13
+ return super if !@vars[:gearbox_extra] || @vars[:items_extra]
14
+
15
+ gearbox_items = @vars[:gearbox_items]
16
+ raise VariableError.new(self, :gearbox_items, 'to be an Array of positives', gearbox_items) \
17
+ unless gearbox_items.is_a?(Array) && gearbox_items.all? { |num| num.positive? rescue false } # rubocop:disable Style/RescueModifier
18
+
19
+ @items = gearbox_items[@page - 1] || gearbox_items.last
20
+ end
21
+
22
+ # Setup @pages and @last based on the :items variable
23
+ def setup_pages_var
24
+ return super if !@vars[:gearbox_extra] || @vars[:items_extra]
25
+
26
+ gearbox_items = @vars[:gearbox_items]
27
+ # This algorithm is thousands of times faster than the one in the geared_pagination gem
28
+ @pages = @last = (if @count > (sum = gearbox_items.sum)
29
+ [((@count - sum).to_f / gearbox_items.last).ceil, 1].max + gearbox_items.count
30
+ else
31
+ pages = 0
32
+ reminder = @count
33
+ while reminder.positive?
34
+ pages += 1
35
+ reminder -= gearbox_items[pages - 1]
36
+ end
37
+ [pages, 1].max
38
+ end)
39
+ end
40
+ end
41
+ prepend GearboxExtra
42
+ end
@@ -1,35 +1,43 @@
1
1
  # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/headers
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'pagy/url_helpers'
5
+
4
6
  class Pagy
7
+ DEFAULT[:headers] = { page: 'Current-Page',
8
+ items: 'Page-Items',
9
+ count: 'Total-Count',
10
+ pages: 'Total-Pages' }
5
11
  # Add specialized backend methods to add pagination response headers
6
- module Backend
7
- private
8
-
9
- VARS[:headers] = { page: 'Current-Page', items: 'Page-Items', count: 'Total-Count', pages: 'Total-Pages' }
12
+ module HeadersExtra
13
+ include UrlHelpers
10
14
 
11
- include Helpers
15
+ private
12
16
 
17
+ # Merge the pagy headers into the response.headers
13
18
  def pagy_headers_merge(pagy)
14
19
  response.headers.merge!(pagy_headers(pagy))
15
20
  end
16
21
 
22
+ # Generate a hash of RFC-8288 compliant http headers
17
23
  def pagy_headers(pagy)
18
24
  pagy_headers_hash(pagy).tap do |hash|
19
- hash['Link'] = hash['Link'].map{|rel, link| %(<#{link}>; rel="#{rel}")}.join(', ')
25
+ hash['Link'] = hash['Link'].map { |rel, link| %(<#{link}>; rel="#{rel}") }.join(', ')
20
26
  end
21
27
  end
22
28
 
29
+ # Generates a hash structure of the headers
23
30
  def pagy_headers_hash(pagy)
24
- countless = defined?(Pagy::Countless) && pagy.is_a?(Pagy::Countless)
25
- rels = { 'first' => 1, 'prev' => pagy.prev, 'next' => pagy.next }
26
- rels['last'] = pagy.last unless countless
27
- url_str = pagy_url_for(pagy, PAGE_PLACEHOLDER, absolute: true)
28
- hash = { 'Link' => rels.map do |rel, num| # filter_map if ruby >=2.7
29
- next unless num
30
- [ rel, url_str.sub(PAGE_PLACEHOLDER, num.to_s) ]
31
- end.compact.to_h }
32
- headers = pagy.vars[:headers]
31
+ countless = defined?(Pagy::Countless) && pagy.is_a?(Pagy::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_PLACEHOLDER, absolute: true)
35
+ link = rel.map do |r, num| # filter_map if ruby >=2.7
36
+ next unless num # rubocop:disable Layout/EmptyLineAfterGuardClause
37
+ [r, url_str.sub(PAGE_PLACEHOLDER, num.to_s)]
38
+ end.compact.to_h
39
+ hash = { 'Link' => link }
40
+ headers = pagy.vars[:headers]
33
41
  hash[headers[:page]] = pagy.page.to_s if headers[:page]
34
42
  hash[headers[:items]] = pagy.vars[:items].to_s if headers[:items]
35
43
  unless countless
@@ -38,6 +46,6 @@ class Pagy
38
46
  end
39
47
  hash
40
48
  end
41
-
42
49
  end
50
+ Backend.prepend HeadersExtra
43
51
  end
@@ -3,22 +3,13 @@
3
3
 
4
4
  class Pagy
5
5
  # Use ::I18n gem
6
- module Frontend
7
-
8
- ::I18n.load_path += Dir[Pagy.root.join('locales', '*.yml')]
9
-
10
- # unload the pagy default constant for efficiency
11
- Pagy::I18n.clear.instance_eval do
12
- undef :load
13
- undef :t
14
- end
15
-
16
- module UseI18nGem
17
- def pagy_t(key, **opts)
18
- ::I18n.t(key, **opts)
19
- end
6
+ module I18nExtra
7
+ def pagy_t(key, **opts)
8
+ ::I18n.t(key, **opts)
20
9
  end
21
- prepend UseI18nGem
22
-
23
10
  end
11
+ Frontend.prepend I18nExtra
12
+
13
+ # Add the pagy locales to the I18n.load_path
14
+ ::I18n.load_path += Dir[Pagy.root.join('locales', '*.yml')]
24
15
  end
@@ -3,50 +3,49 @@
3
3
 
4
4
  require 'pagy/extras/shared'
5
5
 
6
- class Pagy
6
+ class Pagy # Default variables for this extra
7
+ DEFAULT[:items_param] = :items
8
+ DEFAULT[:max_items] = 100
9
+ DEFAULT[:items_extra] = true # extra enabled by default
7
10
 
8
- # Default variables for this extra
9
- VARS[:items_param] = :items
10
- VARS[:max_items] = 100
11
-
12
- VARS[:enable_items_extra] = true
13
-
14
- ITEMS_PLACEHOLDER = '__pagy_items__'
15
-
16
- module UseItemsExtra; end
17
-
18
- module Backend
19
- private
11
+ module ItemsExtra
12
+ module Backend
13
+ private
20
14
 
15
+ # Set the items variable considering the params and other pagy variables
21
16
  def pagy_set_items_from_params(vars)
22
- return if vars[:items]
23
- return unless vars.key?(:enable_item_extra) ? vars[:enable_item_extra] : VARS[:enable_items_extra]
24
- return unless (items = params[vars[:items_param] || VARS[:items_param]]) # :items from :items_param
25
- vars[:items] = [items.to_i, vars.key?(:max_items) ? vars[:max_items] : VARS[:max_items]].compact.min # :items capped to :max_items
26
- end
17
+ return if vars[:items] # :items explicitly set
18
+ return unless vars.key?(:items_extra) ? vars[:items_extra] : DEFAULT[:items_extra] # :items_extra is false
19
+ return unless (items = params[vars[:items_param] || DEFAULT[:items_param]]) # no items from request params
27
20
 
28
- end
29
-
30
- module Frontend
31
-
32
- # Return the items selector HTML. For example "Show [20] items per page"
33
- def pagy_items_selector_js(pagy, deprecated_id=nil, pagy_id: nil, item_name: nil, i18n_key: nil, link_extra: '')
34
- return '' unless pagy.vars[:enable_items_extra]
35
- pagy_id = Pagy.deprecated_arg(:id, deprecated_id, :pagy_id, pagy_id) if deprecated_id
36
- p_id = %( id="#{pagy_id}") if pagy_id
37
- p_vars = pagy.vars
38
- p_items = p_vars[:items]
39
- p_vars[:items] = ITEMS_PLACEHOLDER
40
- link = pagy_marked_link(pagy_link_proc(pagy, link_extra: link_extra))
41
- p_vars[:items] = p_items # restore the items
42
-
43
- html = +%(<span#{p_id} class="pagy-items-selector-js" #{pagy_json_attr pagy, :items_selector, pagy.from, link}>)
44
- input = %(<input type="number" min="1" max="#{p_vars[:max_items]}" value="#{p_items}" style="padding: 0; text-align: center; width: #{p_items.to_s.length+1}rem;">)
45
- html << pagy_t('pagy.items_selector_js', item_name: item_name || pagy_t(i18n_key || p_vars[:i18n_key], count: p_items),
46
- items_input: input,
47
- count: p_items)
48
- html << %(</span>)
21
+ vars[:items] = [items.to_i, vars.key?(:max_items) ? vars[:max_items] : DEFAULT[:max_items]].compact.min
22
+ end
49
23
  end
50
24
 
25
+ module Frontend
26
+ ITEMS_PLACEHOLDER = '__pagy_items__'
27
+
28
+ # Return the items selector HTML. For example "Show [20] items per page"
29
+ def pagy_items_selector_js(pagy, pagy_id: nil, item_name: nil, i18n_key: nil, link_extra: '')
30
+ return '' unless pagy.vars[:items_extra]
31
+
32
+ p_id = %( id="#{pagy_id}") if pagy_id
33
+ p_vars = pagy.vars
34
+ p_items = p_vars[:items]
35
+ p_vars[:items] = ITEMS_PLACEHOLDER
36
+ link = pagy_marked_link(pagy_link_proc(pagy, link_extra: link_extra))
37
+ p_vars[:items] = p_items # restore the items
38
+
39
+ html = +%(<span#{p_id} class="pagy-items-selector-js" #{pagy_json_attr pagy, :items_selector, pagy.from, link}>)
40
+ input = %(<input type="number" min="1" max="#{p_vars[:max_items]}" value="#{
41
+ p_items}" style="padding: 0; text-align: center; width: #{p_items.to_s.length + 1}rem;">)
42
+ html << pagy_t('pagy.items_selector_js', item_name: item_name || pagy_t(i18n_key || p_vars[:i18n_key], count: p_items),
43
+ items_input: input,
44
+ count: p_items)
45
+ html << %(</span>)
46
+ end
47
+ end
51
48
  end
49
+ Backend.prepend ItemsExtra::Backend
50
+ Frontend.prepend ItemsExtra::Frontend
52
51
  end