pagy 3.10.0 → 5.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/config/pagy.rb +121 -52
  4. data/lib/javascripts/pagy-dev.js +117 -0
  5. data/lib/javascripts/pagy.js +1 -106
  6. data/lib/javascripts/pagy.mjs +118 -0
  7. data/lib/locales/ar.yml +26 -0
  8. data/lib/locales/bg.yml +2 -2
  9. data/lib/locales/bs.yml +24 -0
  10. data/lib/locales/ca.yml +2 -2
  11. data/lib/locales/cs.yml +2 -2
  12. data/lib/locales/da.yml +2 -2
  13. data/lib/locales/de.yml +2 -2
  14. data/lib/locales/en.yml +2 -2
  15. data/lib/locales/es.yml +2 -2
  16. data/lib/locales/fr.yml +2 -2
  17. data/lib/locales/hr.yml +24 -0
  18. data/lib/locales/id.yml +2 -2
  19. data/lib/locales/it.yml +2 -2
  20. data/lib/locales/ja.yml +2 -2
  21. data/lib/locales/km.yml +2 -2
  22. data/lib/locales/ko.yml +2 -2
  23. data/lib/locales/nb.yml +2 -2
  24. data/lib/locales/nl.yml +2 -2
  25. data/lib/locales/pl.yml +2 -2
  26. data/lib/locales/pt-BR.yml +2 -2
  27. data/lib/locales/pt.yml +2 -2
  28. data/lib/locales/ru.yml +2 -2
  29. data/lib/locales/sr.yml +23 -0
  30. data/lib/locales/sv-SE.yml +2 -2
  31. data/lib/locales/sv.yml +2 -2
  32. data/lib/locales/sw.yml +22 -0
  33. data/lib/locales/ta.yml +22 -0
  34. data/lib/locales/tr.yml +2 -2
  35. data/lib/locales/uk.yml +24 -0
  36. data/lib/locales/zh-CN.yml +2 -2
  37. data/lib/locales/zh-HK.yml +2 -2
  38. data/lib/locales/zh-TW.yml +3 -3
  39. data/lib/pagy/backend.rb +11 -12
  40. data/lib/pagy/calendar/day.rb +29 -0
  41. data/lib/pagy/calendar/month.rb +16 -0
  42. data/lib/pagy/calendar/month_mixin.rb +49 -0
  43. data/lib/pagy/calendar/quarter.rb +23 -0
  44. data/lib/pagy/calendar/week.rb +39 -0
  45. data/lib/pagy/calendar/year.rb +29 -0
  46. data/lib/pagy/calendar.rb +90 -0
  47. data/lib/pagy/console.rb +23 -0
  48. data/lib/pagy/countless.rb +23 -19
  49. data/lib/pagy/exceptions.rb +16 -13
  50. data/lib/pagy/extras/arel.rb +12 -7
  51. data/lib/pagy/extras/array.rb +10 -9
  52. data/lib/pagy/extras/bootstrap.rb +77 -39
  53. data/lib/pagy/extras/bulma.rb +77 -43
  54. data/lib/pagy/extras/calendar.rb +66 -0
  55. data/lib/pagy/extras/countless.rb +17 -17
  56. data/lib/pagy/extras/elasticsearch_rails.rb +66 -37
  57. data/lib/pagy/extras/foundation.rb +74 -41
  58. data/lib/pagy/extras/frontend_helpers.rb +70 -0
  59. data/lib/pagy/extras/gearbox.rb +42 -0
  60. data/lib/pagy/extras/headers.rb +32 -18
  61. data/lib/pagy/extras/i18n.rb +18 -17
  62. data/lib/pagy/extras/items.rb +42 -53
  63. data/lib/pagy/extras/materialize.rb +68 -43
  64. data/lib/pagy/extras/meilisearch.rb +61 -0
  65. data/lib/pagy/extras/metadata.rb +27 -26
  66. data/lib/pagy/extras/navs.rb +54 -29
  67. data/lib/pagy/extras/overflow.rb +57 -52
  68. data/lib/pagy/extras/searchkick.rb +54 -36
  69. data/lib/pagy/extras/semantic.rb +66 -39
  70. data/lib/pagy/extras/standalone.rb +64 -0
  71. data/lib/pagy/extras/support.rb +34 -17
  72. data/lib/pagy/extras/trim.rb +18 -12
  73. data/lib/pagy/extras/uikit.rb +66 -44
  74. data/lib/pagy/frontend.rb +61 -53
  75. data/lib/pagy/i18n.rb +164 -0
  76. data/lib/pagy/url_helpers.rb +38 -0
  77. data/lib/pagy.rb +96 -30
  78. data/lib/templates/bootstrap_nav.html.erb +1 -1
  79. data/lib/templates/bootstrap_nav.html.haml +1 -1
  80. data/lib/templates/bootstrap_nav.html.slim +1 -1
  81. data/lib/templates/foundation_nav.html.erb +1 -1
  82. data/lib/templates/foundation_nav.html.haml +1 -1
  83. data/lib/templates/foundation_nav.html.slim +1 -1
  84. data/lib/templates/uikit_nav.html.erb +2 -2
  85. data/lib/templates/uikit_nav.html.haml +1 -1
  86. data/lib/templates/uikit_nav.html.slim +2 -2
  87. metadata +37 -16
  88. data/lib/locales/README.md +0 -35
  89. data/lib/locales/utils/i18n.rb +0 -25
  90. data/lib/locales/utils/loader.rb +0 -34
  91. data/lib/locales/utils/p11n.rb +0 -88
  92. data/lib/pagy/extras/pagy_search.rb +0 -18
  93. data/lib/pagy/extras/shared.rb +0 -53
  94. data/pagy.gemspec +0 -16
@@ -0,0 +1,66 @@
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/calendar
2
+ # frozen_string_literal: true
3
+
4
+ require 'pagy/calendar'
5
+
6
+ class Pagy # :nodoc:
7
+ # Add pagination filtering by calendar unit (:year, :quarter, :month, :week, :day) to the regular pagination
8
+ module CalendarExtra
9
+ # Additions for the Backend module
10
+ module Backend
11
+ CONF_KEYS = (Calendar::UNITS + %i[pagy active]).freeze
12
+
13
+ private
14
+
15
+ # Take a collection and a conf Hash with keys in CONF_KEYS and return an array with 3 items: [calendar, pagy, results]
16
+ def pagy_calendar(collection, conf)
17
+ unless conf.is_a?(Hash) && (conf.keys - CONF_KEYS).empty? && conf.all? { |k, v| v.is_a?(Hash) || k == :active }
18
+ raise ArgumentError, "keys must be in #{CONF_KEYS.inspect} and object values must be Hashes; got #{conf.inspect}"
19
+ end
20
+
21
+ conf[:pagy] = {} unless conf[:pagy] # use default Pagy object when omitted
22
+ calendar, collection = pagy_setup_calendar(collection, conf) unless conf.key?(:active) && !conf[:active]
23
+ pagy, results = send(conf[:pagy][:backend] || :pagy, collection, conf[:pagy]) # use backend: :pagy when omitted
24
+ [calendar, pagy, results]
25
+ end
26
+
27
+ # Setup and return the calendar objects and the filtered collection
28
+ def pagy_setup_calendar(collection, conf)
29
+ units = Calendar::UNITS & conf.keys # get the units in time length desc order
30
+ raise ArgumentError, 'no calendar unit found in pagy_calendar configuration' if units.empty?
31
+
32
+ page_param = conf[:pagy][:page_param] || DEFAULT[:page_param]
33
+ units.each do |unit| # set all the :page_param vars for later deletion
34
+ unit_page_param = :"#{unit}_#{page_param}"
35
+ conf[unit][:page_param] = unit_page_param
36
+ conf[unit][:page] = params[unit_page_param]
37
+ end
38
+ calendar = {}
39
+ last_obj = nil
40
+ units.each_with_index do |unit, index|
41
+ params_to_delete = units[(index + 1), units.size].map { |sub| conf[sub][:page_param] } + [page_param]
42
+ conf[unit][:params] = lambda do |params| # delete page_param from the sub-units
43
+ params_to_delete.each { |p| params.delete(p.to_s) } # Hash#except missing from ruby 2.5 baseline
44
+ params
45
+ end
46
+ conf[unit][:period] = last_obj&.send(:active_period) || pagy_calendar_period(collection)
47
+ calendar[unit] = last_obj = Calendar.send(:create, unit, conf[unit])
48
+ end
49
+ [calendar, pagy_calendar_filter(collection, last_obj.from, last_obj.to)]
50
+ end
51
+
52
+ # This method must be implemented by the application
53
+ def pagy_calendar_period(*)
54
+ raise NoMethodError, 'the pagy_calendar_period method must be implemented by the application ' \
55
+ '(see https://ddnexus.github.io/pagy/extras/calendar#pagy_calendar_periodcollection)'
56
+ end
57
+
58
+ # This method must be implemented by the application
59
+ def pagy_calendar_filter(*)
60
+ raise NoMethodError, 'the pagy_calendar_filter method must be implemented by the application ' \
61
+ '(see https://ddnexus.github.io/pagy/extras/calendar#pagy_calendar_filtercollection-from-to)'
62
+ end
63
+ end
64
+ end
65
+ Backend.prepend CalendarExtra::Backend
66
+ end
@@ -1,37 +1,37 @@
1
1
  # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/countless
2
- # encoding: utf-8
3
2
  # frozen_string_literal: true
4
3
 
5
4
  require 'pagy/countless'
6
5
 
7
- class Pagy
6
+ class Pagy # :nodoc:
7
+ DEFAULT[:countless_minimal] = false
8
8
 
9
- # used by the items extra
10
- COUNTLESS = true
11
-
12
- module Backend ; private # the whole module is private so no problem with including it in a controller
9
+ # Paginate without the need of any count, saving one query per rendering
10
+ module CountlessExtra
11
+ private
13
12
 
14
13
  # Return Pagy object and items
15
- def pagy_countless(collection, vars={})
16
- pagy = Pagy::Countless.new(pagy_countless_get_vars(collection, vars))
17
- return pagy, pagy_countless_get_items(collection, pagy)
14
+ def pagy_countless(collection, vars = {})
15
+ pagy = Countless.new(pagy_countless_get_vars(collection, vars))
16
+ [pagy, pagy_countless_get_items(collection, pagy)]
18
17
  end
19
18
 
20
19
  # Sub-method called only by #pagy_countless: here for easy customization of variables by overriding
21
20
  def pagy_countless_get_vars(_collection, vars)
22
- vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ]
21
+ pagy_set_items_from_params(vars) if defined?(ItemsExtra)
22
+ vars[:page] ||= params[vars[:page_param] || DEFAULT[:page_param]]
23
23
  vars
24
24
  end
25
25
 
26
26
  # Sub-method called only by #pagy_countless: here for easy customization of record-extraction by overriding
27
+ # You may need to override this method for collections without offset|limit
27
28
  def pagy_countless_get_items(collection, pagy)
28
- # This should work with ActiveRecord, Sequel, Mongoid...
29
- items = collection.offset(pagy.offset).limit(pagy.items + 1).to_a
30
- items_size = items.size
31
- items.pop if items_size == pagy.items + 1
32
- pagy.finalize(items_size) # finalize may adjust pagy.items, so must be used after checking the size
33
- items
34
- end
29
+ return collection.offset(pagy.offset).limit(pagy.items) if pagy.vars[:countless_minimal]
35
30
 
31
+ fetched = collection.offset(pagy.offset).limit(pagy.items + 1).to_a # eager load items + 1
32
+ pagy.finalize(fetched.size) # finalize the pagy object
33
+ fetched[0, pagy.items] # ignore eventual extra item
34
+ end
36
35
  end
36
+ Backend.prepend CountlessExtra
37
37
  end
@@ -1,50 +1,79 @@
1
1
  # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/elasticsearch_rails
2
- # encoding: utf-8
3
2
  # frozen_string_literal: true
4
3
 
5
- require 'pagy/extras/pagy_search'
4
+ class Pagy # :nodoc:
5
+ DEFAULT[:elasticsearch_rails_search_method] ||= :pagy_search
6
6
 
7
- class Pagy
7
+ # Paginate ElasticsearchRails response objects
8
+ module ElasticsearchRailsExtra
9
+ module_function
8
10
 
9
- # used by the items extra
10
- ELASTICSEARCH_RAILS = true
11
-
12
- # create a Pagy object from an Elasticsearch::Model::Response::Response object
13
- def self.new_from_elasticsearch_rails(response, vars={})
14
- vars[:items] = response.search.options[:size] || 10
15
- vars[:page] = (response.search.options[:from] || 0) / vars[:items] + 1
16
- total = response.respond_to?(:raw_response) ? response.raw_response['hits']['total'] : response.response['hits']['total']
17
- vars[:count] = total.is_a?(Hash) ? total['value'] : total
18
- new(vars)
19
- end
11
+ # Get the count from different version of ElasticsearchRails
12
+ def total_count(response)
13
+ total = if response.respond_to?(:raw_response)
14
+ response.raw_response['hits']['total']
15
+ else
16
+ response.response['hits']['total']
17
+ end
18
+ total.is_a?(Hash) ? total['value'] : total
19
+ end
20
20
 
21
- # Add specialized backend methods to paginate ElasticsearchRails searches
22
- module Backend ; private
23
-
24
- # Return Pagy object and items
25
- def pagy_elasticsearch_rails(pagy_search_args, vars={})
26
- model, search_args, _block, *called = pagy_search_args
27
- vars = pagy_elasticsearch_rails_get_vars(nil, vars)
28
- search_args[-1][:size] = vars[:items]
29
- search_args[-1][:from] = vars[:items] * (vars[:page] - 1)
30
- response = model.search(*search_args)
31
- total = response.respond_to?(:raw_response) ? response.raw_response['hits']['total'] : response.response['hits']['total']
32
- vars[:count] = total.is_a?(Hash) ? total['value'] : total
33
- pagy = Pagy.new(vars)
34
- # with :last_page overflow we need to re-run the method in order to get the hits
35
- if defined?(OVERFLOW) && pagy.overflow? && pagy.vars[:overflow] == :last_page
36
- return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page))
21
+ module ElasticsearchRails # :nodoc:
22
+ # Return an array used to delay the call of #search
23
+ # after the pagination variables are merged to the options.
24
+ # It also pushes to the same array an optional method call.
25
+ def pagy_elasticsearch_rails(query_or_payload, **options)
26
+ [self, query_or_payload, options].tap do |args|
27
+ args.define_singleton_method(:method_missing) { |*a| args += a }
28
+ end
37
29
  end
38
- return pagy, called.empty? ? response : response.send(*called)
30
+ alias_method Pagy::DEFAULT[:elasticsearch_rails_search_method], :pagy_elasticsearch_rails
39
31
  end
40
32
 
41
- # Sub-method called only by #pagy_elasticsearch_rails: here for easy customization of variables by overriding
42
- # the _collection argument is not available when the method is called
43
- def pagy_elasticsearch_rails_get_vars(_collection, vars)
44
- vars[:items] ||= VARS[:items]
45
- vars[:page] ||= (params[ vars[:page_param] || VARS[:page_param] ] || 1).to_i
46
- vars
33
+ # Additions for the Pagy class
34
+ module Pagy
35
+ # Create a Pagy object from an Elasticsearch::Model::Response::Response object
36
+ def new_from_elasticsearch_rails(response, vars = {})
37
+ vars[:items] = response.search.options[:size] || 10
38
+ vars[:page] = ((response.search.options[:from] || 0) / vars[:items]) + 1
39
+ vars[:count] = ElasticsearchRailsExtra.total_count(response)
40
+ new(vars)
41
+ end
47
42
  end
48
43
 
44
+ # Add specialized backend methods to paginate ElasticsearchRails searches
45
+ module Backend
46
+ private
47
+
48
+ # Return Pagy object and items
49
+ def pagy_elasticsearch_rails(pagy_search_args, vars = {})
50
+ model, query_or_payload,
51
+ options, *called = pagy_search_args
52
+ vars = pagy_elasticsearch_rails_get_vars(nil, vars)
53
+ options[:size] = vars[:items]
54
+ options[:from] = vars[:items] * (vars[:page] - 1)
55
+ response = model.search(query_or_payload, **options)
56
+ vars[:count] = ElasticsearchRailsExtra.total_count(response)
57
+
58
+ pagy = ::Pagy.new(vars)
59
+ # with :last_page overflow we need to re-run the method in order to get the hits
60
+ return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page)) \
61
+ if defined?(::Pagy::OverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page
62
+
63
+ [pagy, called.empty? ? response : response.send(*called)]
64
+ end
65
+
66
+ # Sub-method called only by #pagy_elasticsearch_rails: here for easy customization of variables by overriding
67
+ # the _collection argument is not available when the method is called
68
+ def pagy_elasticsearch_rails_get_vars(_collection, vars)
69
+ pagy_set_items_from_params(vars) if defined?(ItemsExtra)
70
+ vars[:items] ||= DEFAULT[:items]
71
+ vars[:page] ||= (params[vars[:page_param] || DEFAULT[:page_param]] || 1).to_i
72
+ vars
73
+ end
74
+ end
49
75
  end
76
+ ElasticsearchRails = ElasticsearchRailsExtra::ElasticsearchRails
77
+ extend ElasticsearchRailsExtra::Pagy
78
+ Backend.prepend ElasticsearchRailsExtra::Backend
50
79
  end
@@ -1,57 +1,90 @@
1
1
  # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/foundation
2
- # encoding: utf-8
3
2
  # frozen_string_literal: true
4
3
 
5
- require 'pagy/extras/shared'
6
-
7
- class Pagy
8
- module Frontend
4
+ require 'pagy/extras/frontend_helpers'
9
5
 
6
+ class Pagy # :nodoc:
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
10
10
  # Pagination for Foundation: it returns the html with the series of links to the pages
11
- def pagy_foundation_nav(pagy)
12
- link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next
13
-
14
- html = EMPTY + (p_prev ? %(<li class="prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</li>)
15
- : %(<li class="prev disabled">#{pagy_t('pagy.nav.prev')}</li>))
16
- pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
- html << if item.is_a?(Integer); %(<li>#{link.call item}</li>) # page link
18
- elsif item.is_a?(String) ; %(<li class="current">#{item}</li>) # active page
19
- elsif item == :gap ; %(<li class="ellipsis gap" aria-hidden="true"></li>) # page gap
11
+ def pagy_foundation_nav(pagy, pagy_id: nil, link_extra: '', **vars)
12
+ p_id = %( id="#{pagy_id}") if pagy_id
13
+ link = pagy_link_proc(pagy, link_extra: link_extra)
14
+
15
+ html = +%(<nav#{p_id} class="pagy-foundation-nav" aria-label="Pagination"><ul class="pagination">)
16
+ html << pagy_foundation_prev_html(pagy, link)
17
+ pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
18
+ html << case item
19
+ when Integer then %(<li>#{link.call item}</li>) # page link
20
+ when String then %(<li class="current">#{pagy.label_for(item)}</li>) # active page
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}"
20
23
  end
21
24
  end
22
- html << (p_next ? %(<li class="next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</li>)
23
- : %(<li class="next disabled">#{pagy_t('pagy.nav.next')}</li>))
24
- %(<nav class="pagy-foundation-nav" role="navigation" aria-label="Pagination"><ul class="pagination">#{html}</ul></nav>)
25
+ html << pagy_foundation_next_html(pagy, link)
26
+ html << %(</ul></nav>)
25
27
  end
26
28
 
27
- # 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, id=pagy_id)
29
- link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next
30
- tags = { 'before' => ( '<ul class="pagination">' \
31
- + (p_prev ? %(<li class="prev">#{link.call(p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"')}</li>)
32
- : %(<li class="prev disabled">#{pagy_t('pagy.nav.prev')}</li>)) ),
33
- 'link' => %(<li>#{link.call(PAGE_PLACEHOLDER)}</li>),
34
- 'active' => %(<li class="current">#{pagy.page}</li>),
29
+ # Javascript pagination for foundation: it returns a nav and a JSON tag used by the pagy.js file
30
+ def pagy_foundation_nav_js(pagy, pagy_id: nil, link_extra: '', **vars)
31
+ sequels = pagy.sequels(**vars)
32
+ p_id = %( id="#{pagy_id}") if pagy_id
33
+ link = pagy_link_proc(pagy, link_extra: link_extra)
34
+ tags = { 'before' => %(<ul class="pagination">#{pagy_foundation_prev_html pagy, link}),
35
+ 'link' => %(<li>#{link.call(PAGE_PLACEHOLDER, LABEL_PLACEHOLDER)}</li>),
36
+ 'active' => %(<li class="current">#{LABEL_PLACEHOLDER}</li>),
35
37
  'gap' => %(<li class="ellipsis gap" aria-hidden="true"></li>),
36
- 'after' => ( (p_next ? %(<li class="next">#{link.call(p_next, pagy_t('pagy.nav.next'), 'aria-label="next"')}</li>)
37
- : %(<li class="next disabled">#{pagy_t('pagy.nav.next')}</li>)) \
38
- + '</ul>' ) }
39
- %(<nav id="#{id}" class="pagy-foundation-nav-js" role="navigation" aria-label="Pagination"></nav>#{pagy_json_tag(:nav, id, tags, pagy.sequels, defined?(TRIM) && pagy.vars[:page_param])})
38
+ 'after' => %(#{pagy_foundation_next_html pagy, link}</ul>) }
39
+
40
+ %(<nav#{p_id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-foundation-nav-js" aria-label="Pagination" #{
41
+ pagy_json_attr(pagy, :nav, tags, sequels, pagy.label_sequels(sequels))}></nav>)
42
+ end
43
+
44
+ # Javascript combo pagination for Foundation: it returns a nav and a JSON tag used by the pagy.js file
45
+ def pagy_foundation_combo_nav_js(pagy, pagy_id: nil, link_extra: '')
46
+ p_id = %( id="#{pagy_id}") if pagy_id
47
+ link = pagy_link_proc(pagy, link_extra: link_extra)
48
+ p_page = pagy.page
49
+ p_pages = pagy.pages
50
+ input = %(<input class="input-group-field cell shrink" type="number" min="1" max="#{
51
+ p_pages}" value="#{p_page}" style="width: #{
52
+ p_pages.to_s.length + 1}rem; padding: 0 0.3rem; margin: 0 0.3rem;">)
53
+
54
+ %(<nav#{p_id} class="pagy-foundation-combo-nav-js" aria-label="Pagination"><div class="input-group" #{
55
+ pagy_json_attr pagy, :combo, pagy_marked_link(link)}>#{
56
+ if (p_prev = pagy.prev)
57
+ link.call p_prev, pagy_t('pagy.nav.prev'),
58
+ 'style="margin-bottom: 0" aria-label="previous" class="prev button primary"'
59
+ else
60
+ %(<a style="margin-bottom: 0" class="prev button primary disabled" href="#">#{pagy_t 'pagy.nav.prev'}</a>)
61
+ end
62
+ }<span class="input-group-label">#{pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages}</span>#{
63
+ if (p_next = pagy.next)
64
+ link.call p_next, pagy_t('pagy.nav.next'), 'style="margin-bottom: 0" aria-label="next" class="next button primary"'
65
+ else
66
+ %(<a style="margin-bottom: 0" class="next button primary disabled" href="#">#{pagy_t 'pagy.nav.next'}</a>)
67
+ end
68
+ }</div></nav>)
40
69
  end
41
70
 
42
- # Javascript combo pagination for Foundation: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript
43
- def pagy_foundation_combo_nav_js(pagy, id=pagy_id)
44
- link, p_prev, p_next, p_page, p_pages = pagy_link_proc(pagy), pagy.prev, pagy.next, pagy.page, pagy.pages
45
-
46
- html = %(<nav id="#{id}" class="pagy-foundation-combo-nav-js" role="navigation" aria-label="Pagination">) + %(<div class="input-group">)
47
- html << (p_prev ? link.call(p_prev, pagy_t('pagy.nav.prev'), 'style="margin-bottom: 0px;" aria-label="previous" class="prev button primary"')
48
- : %(<a style="margin-bottom: 0px;" class="prev button primary disabled" href="#">#{pagy_t('pagy.nav.prev')}</a>))
49
- 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;">)
50
- html << %(<span class="input-group-label">#{pagy_t('pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages)}</span>)
51
- html << (p_next ? link.call(p_next, pagy_t('pagy.nav.next'), 'style="margin-bottom: 0px;" aria-label="next" class="next button primary"')
52
- : %(<a style="margin-bottom: 0px;" class="next button primary disabled" href="#">#{pagy_t('pagy.nav.next')}</a>))
53
- html << %(</div></nav>#{pagy_json_tag(:combo_nav, id, p_page, pagy_marked_link(link), defined?(TRIM) && pagy.vars[:page_param])})
71
+ private
72
+
73
+ def pagy_foundation_prev_html(pagy, link)
74
+ if (p_prev = pagy.prev)
75
+ %(<li class="prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</li>)
76
+ else
77
+ %(<li class="prev disabled">#{pagy_t 'pagy.nav.prev'}</li>)
78
+ end
54
79
  end
55
80
 
81
+ def pagy_foundation_next_html(pagy, link)
82
+ if (p_next = pagy.next)
83
+ %(<li class="next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</li>)
84
+ else
85
+ %(<li class="next disabled">#{pagy_t 'pagy.nav.next'}</li>)
86
+ end
87
+ end
56
88
  end
89
+ Frontend.prepend FoundationExtra
57
90
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ class Pagy # :nodoc:
6
+ DEFAULT[:steps] = false # default false will use {0 => @vars[:size]}
7
+
8
+ # Private module documented in the main classes
9
+ module FrontendHelpers
10
+ # Additions for the Pagy class
11
+ module Pagy
12
+ # `Pagy` instance method used by the `pagy*_nav_js` helpers.
13
+ # It returns the sequels of width/series generated from the :steps hash
14
+ # Example:
15
+ # >> pagy = Pagy.new(count:1000, page: 20, steps: {0 => [1,2,2,1], 350 => [2,3,3,2], 550 => [3,4,4,3]})
16
+ # >> pagy.sequels
17
+ # #=> { "0" => [1, :gap, 18, 19, "20", 21, 22, :gap, 50],
18
+ # "350" => [1, 2, :gap, 17, 18, 19, "20", 21, 22, 23, :gap, 49, 50],
19
+ # "550" => [1, 2, 3, :gap, 16, 17, 18, 19, "20", 21, 22, 23, 24, :gap, 48, 49, 50] }
20
+ # Notice: if :steps is false it will use the single {0 => @vars[:size]} size
21
+ def sequels(steps: @vars[:steps] || { 0 => @vars[:size] }, **_)
22
+ raise VariableError.new(self, :steps, 'to define the 0 width', steps) unless steps.key?(0)
23
+
24
+ {}.tap do |sequels|
25
+ steps.each { |width, step_size| sequels[width.to_s] = series(size: step_size) }
26
+ end
27
+ end
28
+
29
+ # Support for the Calendar API
30
+ def label_sequels(*); end
31
+ end
32
+
33
+ # Additions for Calendar class
34
+ module Calendar
35
+ def label_sequels(sequels = self.sequels)
36
+ {}.tap do |label_sequels|
37
+ sequels.each do |width, series|
38
+ label_sequels[width] = series.map { |item| item == :gap ? :gap : label_for(item) }
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # Additions for the Frontend
45
+ module Frontend
46
+ if defined?(Oj)
47
+ # Return a script tag with the JSON-serialized args generated with the faster oj gem
48
+ def pagy_json_attr(pagy, *args)
49
+ args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
50
+ %(data-pagy-json="#{Oj.dump(args, mode: :strict).gsub('"', '&quot;')}")
51
+ end
52
+ else
53
+ require 'json'
54
+ # Return a script tag with the JSON-serialized args generated with the slower to_json
55
+ def pagy_json_attr(pagy, *args)
56
+ args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
57
+ %(data-pagy-json="#{args.to_json.gsub('"', '&quot;')}")
58
+ end
59
+ end
60
+
61
+ # Return the marked link to used by pagy.js
62
+ def pagy_marked_link(link)
63
+ link.call PAGE_PLACEHOLDER, '', 'style="display: none;"'
64
+ end
65
+ end
66
+ end
67
+ prepend FrontendHelpers::Pagy
68
+ Calendar.prepend FrontendHelpers::Calendar if defined?(Calendar)
69
+ Frontend.prepend FrontendHelpers::Frontend
70
+ 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 # :nodoc:
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 :gearbox_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 :gearbox_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
+ remainder = @count
33
+ while remainder.positive?
34
+ pages += 1
35
+ remainder -= gearbox_items[pages - 1]
36
+ end
37
+ [pages, 1].max
38
+ end)
39
+ end
40
+ end
41
+ prepend GearboxExtra
42
+ end
@@ -1,39 +1,53 @@
1
1
  # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/headers
2
- # encoding: utf-8
3
2
  # frozen_string_literal: true
4
3
 
5
- class Pagy
6
- # Add specialized backend methods to add pagination response headers
7
- module Backend ; private
4
+ require 'pagy/url_helpers'
8
5
 
9
- VARS[:headers] = { page: 'Current-Page', items: 'Page-Items', count: 'Total-Count', pages: 'Total-Pages' }
6
+ class Pagy # :nodoc:
7
+ DEFAULT[:headers] = { page: 'Current-Page',
8
+ items: 'Page-Items',
9
+ count: 'Total-Count',
10
+ pages: 'Total-Pages' }
11
+ # Add specialized backend methods to add pagination response headers
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
- hash = pagy_headers_hash(pagy)
19
- hash['Link'] = hash['Link'].map{|rel, link| %(<#{link}>; rel="#{rel}")}.join(', ')
20
- hash
24
+ pagy_headers_hash(pagy).tap do |hash|
25
+ hash['Link'] = hash['Link'].map { |rel, link| %(<#{link}>; rel="#{rel}") }.join(', ')
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 }; rels['last'] = pagy.last unless countless
26
- url_str = pagy_url_for(PAGE_PLACEHOLDER, pagy, :url)
27
- hash = { 'Link' => Hash[rels.map{|rel, n|[rel, url_str.sub(PAGE_PLACEHOLDER, n.to_s)] if n}.compact] }
28
- headers = pagy.vars[:headers]
29
- hash[headers[:page]] = pagy.page.to_s if headers[:page]
30
- hash[headers[:items]] = pagy.vars[:items].to_s if headers[:items]
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_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]
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
31
45
  unless countless
32
46
  hash[headers[:pages]] = pagy.pages.to_s if headers[:pages]
33
- hash[headers[:count]] = pagy.count.to_s if headers[:count]
47
+ hash[headers[:count]] = pagy.count.to_s if pagy.count && headers[:count] # count may be nil with Calendar
34
48
  end
35
49
  hash
36
50
  end
37
-
38
51
  end
52
+ Backend.prepend HeadersExtra
39
53
  end
@@ -1,25 +1,26 @@
1
1
  # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/i18n
2
- # encoding: utf-8
3
2
  # frozen_string_literal: true
4
3
 
5
- class Pagy
4
+ class Pagy # :nodoc:
6
5
  # Use ::I18n gem
7
- module Frontend
8
-
9
- ::I18n.load_path += Dir[Pagy.root.join('locales', '*.yml')]
10
-
11
- Pagy::I18n.clear.instance_eval { undef :load; undef :t } # unload the pagy default constant for efficiency
12
-
13
- alias_method :pagy_without_i18n, :pagy_t
14
- if Gem::Version.new(::I18n::VERSION) < Gem::Version.new('1.6.0')
15
- def pagy_t_with_i18n(*args) ::I18n.t(*args) end
16
- else
17
- # keep 1.9 compatibility by hiding 2.0+ syntax in string
18
- module_eval <<-RUBY
19
- def pagy_t_with_i18n(key, **opts) ::I18n.t(key, **opts) end
20
- RUBY
6
+ module I18nExtra
7
+ # Frontend overriding for translation
8
+ module Frontend
9
+ def pagy_t(key, **opts)
10
+ ::I18n.t(key, **opts)
11
+ end
21
12
  end
22
- alias_method :pagy_t, :pagy_t_with_i18n
23
13
 
14
+ # Calendar overriding for localization (see also the block in the calendar section of the config/pagy.rb initializer)
15
+ module Calendar
16
+ def localize(time, opts)
17
+ ::I18n.l(time, **opts)
18
+ end
19
+ end
24
20
  end
21
+ Frontend.prepend I18nExtra::Frontend
22
+ Calendar.prepend I18nExtra::Calendar if defined?(Calendar)
23
+
24
+ # Add the pagy locales to the I18n.load_path
25
+ ::I18n.load_path += Dir[Pagy.root.join('locales', '*.yml')]
25
26
  end