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
data/lib/pagy/frontend.rb CHANGED
@@ -1,75 +1,83 @@
1
1
  # See Pagy::Frontend API documentation: https://ddnexus.github.io/pagy/api/frontend
2
- # encoding: utf-8
3
2
  # frozen_string_literal: true
4
3
 
5
- require 'yaml'
4
+ require 'pagy/url_helpers'
5
+ require 'pagy/i18n'
6
6
 
7
7
  class Pagy
8
+ # Used for search and replace, hardcoded also in the pagy.js file
9
+ PAGE_PLACEHOLDER = '__pagy_page__'
10
+ LABEL_PLACEHOLDER = '__pagy_label__'
8
11
 
9
- PAGE_PLACEHOLDER = '__pagy_page__' # string used for search and replace, hardcoded also in the pagy.js file
10
-
11
- # I18n static hash loaded at startup, used as default alternative to the i18n gem.
12
- # see https://ddnexus.github.io/pagy/api/frontend#i18n
13
- I18n = eval(Pagy.root.join('locales', 'utils', 'i18n.rb').read) #rubocop:disable Security/Eval
14
-
15
- module Helpers
16
- # This works with all Rack-based frameworks (Sinatra, Padrino, Rails, ...)
17
- def pagy_url_for(page, pagy, url=false)
18
- p_vars = pagy.vars; params = request.GET.merge(p_vars[:params]); params[p_vars[:page_param].to_s] = page
19
- "#{request.base_url if url}#{request.path}?#{Rack::Utils.build_nested_query(pagy_get_params(params))}#{p_vars[:anchor]}"
20
- end
21
-
22
- # Sub-method called only by #pagy_url_for: here for easy customization of params by overriding
23
- def pagy_get_params(params) params end
24
- end
25
-
26
- # All the code here has been optimized for performance: it may not look very pretty
27
- # (as most code dealing with many long strings), but its performance makes it very sexy! ;)
12
+ # Frontend modules are specially optimized for performance.
13
+ # The resulting code may not look very elegant, but produces the best benchmarks
28
14
  module Frontend
29
-
30
- include Helpers
31
-
32
- EMPTY = '' # EMPTY + 'string' is almost as fast as +'string' but is also 1.9 compatible
33
- MARK = PAGE_PLACEHOLDER # backward compatibility in case of helper-overriding in legacy apps
15
+ include UrlHelpers
34
16
 
35
17
  # Generic pagination: it returns the html with the series of links to the pages
36
- def pagy_nav(pagy)
37
- link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next
18
+ def pagy_nav(pagy, pagy_id: nil, link_extra: '', **vars)
19
+ p_id = %( id="#{pagy_id}") if pagy_id
20
+ link = pagy_link_proc(pagy, link_extra: link_extra)
21
+ p_prev = pagy.prev
22
+ p_next = pagy.next
38
23
 
39
- html = EMPTY + (p_prev ? %(<span class="page prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</span> )
40
- : %(<span class="page prev disabled">#{pagy_t('pagy.nav.prev')}</span> ))
41
- pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
42
- html << if item.is_a?(Integer); %(<span class="page">#{link.call item}</span> ) # page link
43
- elsif item.is_a?(String) ; %(<span class="page active">#{item}</span> ) # current page
44
- elsif item == :gap ; %(<span class="page gap">#{pagy_t('pagy.nav.gap')}</span> ) # page gap
24
+ html = +%(<nav#{p_id} class="pagy-nav pagination" aria-label="pager">)
25
+ html << if p_prev
26
+ %(<span class="page prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</span> )
27
+ else
28
+ %(<span class="page prev disabled">#{pagy_t('pagy.nav.prev')}</span> )
29
+ end
30
+ pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
31
+ html << case item
32
+ when Integer then %(<span class="page">#{link.call item}</span> )
33
+ when String then %(<span class="page active">#{pagy.label_for(item)}</span> )
34
+ when :gap then %(<span class="page gap">#{pagy_t('pagy.nav.gap')}</span> )
35
+ else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
45
36
  end
46
37
  end
47
- html << (p_next ? %(<span class="page next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</span>)
48
- : %(<span class="page next disabled">#{pagy_t('pagy.nav.next')}</span>))
49
- %(<nav class="pagy-nav pagination" role="navigation" aria-label="pager">#{html}</nav>)
38
+ html << if p_next
39
+ %(<span class="page next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</span>)
40
+ else
41
+ %(<span class="page next disabled">#{pagy_t('pagy.nav.next')}</span>)
42
+ end
43
+ html << %(</nav>)
50
44
  end
51
45
 
52
- # Return examples: "Displaying items 41-60 of 324 in total" of "Displaying Products 41-60 of 324 in total"
53
- def pagy_info(pagy, item_name=nil)
54
- path = if (count = pagy.count) == 0 ; 'pagy.info.no_items'
55
- else pagy.pages == 1 ? 'pagy.info.single_page' : 'pagy.info.multiple_pages'
56
- end
57
- pagy_t(path, item_name: item_name || pagy_t(pagy.vars[:i18n_key], count: count), count: count, from: pagy.from, to: pagy.to)
46
+ # Return examples: "Displaying items 41-60 of 324 in total" or "Displaying Products 41-60 of 324 in total"
47
+ def pagy_info(pagy, pagy_id: nil, item_name: nil, i18n_key: nil)
48
+ p_id = %( id="#{pagy_id}") if pagy_id
49
+ p_count = pagy.count
50
+ key = if p_count.zero? then 'pagy.info.no_items'
51
+ elsif pagy.pages == 1 then 'pagy.info.single_page'
52
+ else 'pagy.info.multiple_pages' # rubocop:disable Lint/ElseLayout
53
+ end
54
+
55
+ %(<span#{p_id} class="pagy-info">#{
56
+ pagy_t key, item_name: item_name || pagy_t(i18n_key || pagy.vars[:i18n_key], count: p_count),
57
+ count: p_count, from: pagy.from, to: pagy.to
58
+ }</span>)
58
59
  end
59
60
 
60
- # Returns a performance optimized proc to generate the HTML links
61
+ # Return a performance optimized proc to generate the HTML links
61
62
  # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
62
- def pagy_link_proc(pagy, link_extra='')
63
- p_prev, p_next = pagy.prev, pagy.next
64
- a, b = %(<a href="#{pagy_url_for(PAGE_PLACEHOLDER, pagy)}" #{pagy.vars[:link_extra]} #{link_extra}).split(PAGE_PLACEHOLDER, 2)
65
- lambda {|n, text=n, extra=''| "#{a}#{n}#{b}#{ if n == p_prev ; ' rel="prev"'
66
- elsif n == p_next ; ' rel="next"'
67
- else '' end } #{extra}>#{text}</a>"}
63
+ def pagy_link_proc(pagy, link_extra: '')
64
+ p_prev = pagy.prev
65
+ p_next = pagy.next
66
+ left, right = %(<a href="#{pagy_url_for pagy, PAGE_PLACEHOLDER}" #{
67
+ pagy.vars[:link_extra]} #{link_extra}).split(PAGE_PLACEHOLDER, 2)
68
+ lambda do |page, text = pagy.label_for(page), extra_attrs = ''|
69
+ %(#{left}#{page}#{right}#{ case page
70
+ when p_prev then ' rel="prev"'
71
+ when p_next then ' rel="next"'
72
+ else ''
73
+ end } #{extra_attrs}>#{text}</a>)
74
+ end
68
75
  end
69
76
 
70
77
  # Similar to I18n.t: just ~18x faster using ~10x less memory
71
- # (@pagy_locale explicitly initilized in order to avoid warning)
72
- def pagy_t(path, vars={}) Pagy::I18n.t(@pagy_locale||=nil, path, vars) end
73
-
78
+ # (@pagy_locale explicitly initialized in order to avoid warning)
79
+ def pagy_t(key, opts = {})
80
+ Pagy::I18n.translate(@pagy_locale ||= nil, key, opts)
81
+ end
74
82
  end
75
83
  end
data/lib/pagy/i18n.rb ADDED
@@ -0,0 +1,164 @@
1
+ # See Pagy::I18n API documentation https://ddnexus.github.io/pagy/api/i18n
2
+ # frozen_string_literal: true
3
+
4
+ require 'yaml'
5
+
6
+ class Pagy
7
+ # Pagy i18n implementation, compatible with the I18n gem, just a lot faster and lighter
8
+ module I18n
9
+ extend self
10
+
11
+ # Pluralization rules
12
+ module P11n
13
+ # Pluralization variables
14
+ from0to1 = (0..1).to_a.freeze
15
+ from2to4 = (2..4).to_a.freeze
16
+ from3to10 = (3..10).to_a.freeze
17
+ from5to9 = (5..9).to_a.freeze
18
+ from11to14 = (11..14).to_a.freeze
19
+ from11to99 = (11..99).to_a.freeze
20
+ from12to14 = (12..14).to_a.freeze
21
+
22
+ from0to1_from5to9 = from0to1 + from5to9
23
+
24
+ # Store the proc defining each pluralization RULE
25
+ # Logic adapted from https://github.com/svenfuchs/rails-i18n
26
+ RULE = {
27
+ arabic:
28
+ lambda do |n = 0|
29
+ mod100 = n % 100
30
+ case
31
+ when n == 0 then 'zero' # rubocop:disable Style/NumericPredicate
32
+ when n == 1 then 'one'
33
+ when n == 2 then 'two'
34
+ when from3to10.include?(mod100) then 'few'
35
+ when from11to99.include?(mod100) then 'many'
36
+ else 'other'
37
+ end
38
+ end,
39
+
40
+ east_slavic:
41
+ lambda do |n = 0|
42
+ mod10 = n % 10
43
+ mod100 = n % 100
44
+ case
45
+ when mod10 == 1 && mod100 != 11 then 'one'
46
+ when from2to4.include?(mod10) && !from12to14.include?(mod100) then 'few'
47
+ when mod10 == 0 || from5to9.include?(mod10) || from11to14.include?(mod100) then 'many' # rubocop:disable Style/NumericPredicate
48
+ else 'other'
49
+ end
50
+ end,
51
+
52
+ one_other:
53
+ ->(n) { n == 1 ? 'one' : 'other' }, # default RULE
54
+
55
+ one_two_other:
56
+ lambda do |n|
57
+ case n
58
+ when 1 then 'one'
59
+ when 2 then 'two'
60
+ else 'other'
61
+ end
62
+ end,
63
+
64
+ one_upto_two_other:
65
+ ->(n) { n && n >= 0 && n < 2 ? 'one' : 'other' },
66
+
67
+ other:
68
+ ->(*) { 'other' },
69
+
70
+ polish:
71
+ lambda do |n = 0|
72
+ mod10 = n % 10
73
+ mod100 = n % 100
74
+ case
75
+ when n == 1 then 'one'
76
+ when from2to4.include?(mod10) && !from12to14.include?(mod100) then 'few'
77
+ when from0to1_from5to9.include?(mod10) || from12to14.include?(mod100) then 'many'
78
+ else 'other'
79
+ end
80
+ end,
81
+
82
+ west_slavic:
83
+ lambda do |n|
84
+ case n
85
+ when 1 then 'one'
86
+ when *from2to4 then 'few'
87
+ else 'other'
88
+ end
89
+ end
90
+
91
+ }.freeze
92
+
93
+ # Store the RULE to apply to each LOCALE
94
+ # the :one_other RULE is the default for locales missing from this list
95
+ LOCALE = Hash.new(RULE[:one_other]).tap do |hash|
96
+ hash['ar'] = RULE[:arabic]
97
+ hash['bs'] = RULE[:east_slavic]
98
+ hash['cs'] = RULE[:west_slavic]
99
+ hash['id'] = RULE[:other]
100
+ hash['fr'] = RULE[:one_upto_two_other]
101
+ hash['hr'] = RULE[:east_slavic]
102
+ hash['ja'] = RULE[:other]
103
+ hash['km'] = RULE[:other]
104
+ hash['ko'] = RULE[:other]
105
+ hash['pl'] = RULE[:polish]
106
+ hash['ru'] = RULE[:east_slavic]
107
+ hash['sr'] = RULE[:east_slavic]
108
+ hash['sv'] = RULE[:one_two_other]
109
+ hash['sv-SE'] = RULE[:one_two_other]
110
+ hash['tr'] = RULE[:other]
111
+ hash['uk'] = RULE[:east_slavic]
112
+ hash['zh-CN'] = RULE[:other]
113
+ hash['zh-HK'] = RULE[:other]
114
+ hash['zh-TW'] = RULE[:other]
115
+ end.freeze
116
+ end
117
+
118
+ # Stores the i18n DATA structure for each loaded locale
119
+ # default on the first locale DATA
120
+ DATA = Hash.new { |hash, _| hash.first[1] }
121
+
122
+ private
123
+
124
+ # Create a flat hash with dotted notation keys
125
+ def flatten(initial, prefix = '')
126
+ initial.each.reduce({}) do |hash, (key, value)|
127
+ hash.merge!(value.is_a?(Hash) ? flatten(value, "#{prefix}#{key}.") : { "#{prefix}#{key}" => value })
128
+ end
129
+ end
130
+
131
+ # Build the DATA hash out of the passed locales
132
+ def build(*locales)
133
+ locales.each do |locale|
134
+ locale[:filepath] ||= Pagy.root.join('locales', "#{locale[:locale]}.yml")
135
+ locale[:pluralize] ||= P11n::LOCALE[locale[:locale]]
136
+ dictionary = YAML.safe_load(File.read(locale[:filepath], encoding: 'UTF-8'))
137
+ raise I18nError, %(expected :locale "#{locale[:locale]}" not found in :filepath "#{locale[:filepath].inspect}") \
138
+ unless dictionary.key?(locale[:locale])
139
+
140
+ DATA[locale[:locale]] = [flatten(dictionary[locale[:locale]]), locale[:pluralize]]
141
+ end
142
+ end
143
+ # Build the default at require time
144
+ build(locale: 'en')
145
+
146
+ public
147
+
148
+ # Public method to configure the locales: overrides the default, build the DATA and freezes it
149
+ def load(*locales)
150
+ DATA.clear
151
+ build(*locales)
152
+ DATA.freeze
153
+ end
154
+
155
+ # Translate and pluralize the key with the locale DATA
156
+ def translate(locale, key, opts = {})
157
+ data, pluralize = DATA[locale]
158
+ translation = data[key] || (opts[:count] && data[key += ".#{pluralize.call(opts[:count])}"]) \
159
+ or return %([translation missing: "#{key}"])
160
+ translation.gsub(/%{[^}]+?}/) { |match| opts[:"#{match[2..-2]}"] || match }
161
+ end
162
+ alias t translate
163
+ end
164
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Provide the helpers to handle the url in frontend and backend
5
+ module UrlHelpers
6
+ # Return the URL for the page, relying on the params method and Rack by default.
7
+ # It supports all Rack-based frameworks (Sinatra, Padrino, Rails, ...).
8
+ # For non-rack environments you can use the standalone extra
9
+ def pagy_url_for(pagy, page, absolute: nil)
10
+ vars = pagy.vars
11
+ page_param = vars[:page_param].to_s
12
+ items_param = vars[:items_param].to_s
13
+ params = pagy.params.is_a?(Hash) ? pagy.params.transform_keys(&:to_s) : {}
14
+ params = request.GET.merge(params)
15
+ params[page_param] = page
16
+ params[items_param] = vars[:items] if vars[:items_extra]
17
+ query_string = "?#{Rack::Utils.build_nested_query(pagy_deprecated_params(pagy, params))}" # remove in 6.0
18
+ # params = pagy.params.call(params) if pagy.params.is_a?(Proc) # add in 6.0
19
+ # query_string = "?#{Rack::Utils.build_nested_query(params)}" # add in 6.0
20
+ "#{request.base_url if absolute}#{request.path}#{query_string}#{vars[:fragment]}"
21
+ end
22
+
23
+ private
24
+
25
+ # Transitional code to handle params deprecations. It will be removed in version 6.0
26
+ def pagy_deprecated_params(pagy, params) # remove in 6.0
27
+ if pagy.params.is_a?(Proc) # new code
28
+ pagy.params.call(params)
29
+ elsif respond_to?(:pagy_massage_params) # deprecated code
30
+ Warning.warn '[PAGY WARNING] The pagy_massage_params method has been deprecated and it will be ignored from version 6. ' \
31
+ 'Set the :params variable to a Proc with the same code as the pagy_massage_params method.'
32
+ pagy_massage_params(params)
33
+ else
34
+ params # no massage params
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/pagy.rb CHANGED
@@ -1,50 +1,116 @@
1
1
  # See Pagy API documentation: https://ddnexus.github.io/pagy/api/pagy
2
- # encoding: utf-8
3
2
  # frozen_string_literal: true
4
3
 
5
4
  require 'pathname'
6
5
 
7
- class Pagy ; VERSION = '3.10.0'
6
+ # Core class
7
+ class Pagy
8
+ VERSION = '5.7.3'
8
9
 
9
10
  # Root pathname to get the path of Pagy files like templates or dictionaries
10
- def self.root; @root ||= Pathname.new(__FILE__).dirname.freeze end
11
+ def self.root
12
+ @root ||= Pathname.new(__dir__).freeze
13
+ end
11
14
 
12
- # default vars
13
- VARS = { page:1, items:20, outset:0, size:[1,4,4,1], page_param: :page, params:{}, anchor:'', link_extra:'', i18n_key:'pagy.item_name', cycle:false }
15
+ # Default core vars: constant for easy access, but mutable for customizable defaults
16
+ DEFAULT = { page: 1, # rubocop:disable Style/MutableConstant
17
+ items: 20,
18
+ outset: 0,
19
+ size: [1, 4, 4, 1],
20
+ page_param: :page,
21
+ params: {},
22
+ fragment: '',
23
+ link_extra: '',
24
+ i18n_key: 'pagy.item_name',
25
+ cycle: false }
14
26
 
15
- attr_reader :count, :page, :items, :vars, :pages, :last, :offset, :from, :to, :prev, :next
27
+ attr_reader :count, :page, :items, :vars, :pages, :last, :offset, :in, :from, :to, :prev, :next, :params
16
28
 
17
29
  # Merge and validate the options, do some simple arithmetic and set the instance variables
18
30
  def initialize(vars)
19
- @vars = VARS.merge(vars.delete_if{|_,v| v.nil? || v == '' }) # default vars + cleaned vars
20
- { count:0, items:1, outset:0, page:1 }.each do |k,min| # validate instance variables
21
- (@vars[k] && instance_variable_set(:"@#{k}", @vars[k].to_i) >= min) \
22
- or raise(VariableError.new(self), "expected :#{k} >= #{min}; got #{@vars[k].inspect}")
23
- end
24
- @pages = @last = [(@count.to_f / @items).ceil, 1].max # cardinal and ordinal meanings
25
- @page <= @last or raise(OverflowError.new(self), "expected :page in 1..#{@last}; got #{@page.inspect}")
26
- @offset = @items * (@page - 1) + @outset # pagination offset + outset (initial offset)
27
- @items = @count - ((@pages-1) * @items) if @page == @last && @count > 0 # adjust items for last non-empty page
28
- @from = @count == 0 ? 0 : @offset+1 - @outset # page begins from item
29
- @to = @count == 0 ? 0 : @offset + @items - @outset # page ends to item
30
- @prev = (@page-1 unless @page == 1) # nil if no prev page
31
- @next = @page == @last ? (1 if @vars[:cycle]) : @page+1 # nil if no next page, 1 if :cycle
31
+ normalize_vars(vars)
32
+ setup_vars(count: 0, page: 1, outset: 0)
33
+ setup_items_var
34
+ setup_pages_var
35
+ setup_params_var
36
+ raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
37
+
38
+ @offset = (@items * (@page - 1)) + @outset
39
+ @from = [@offset - @outset + 1, @count].min
40
+ @to = [@offset - @outset + @items, @count].min
41
+ @in = [@to - @from + 1, @count].min
42
+ @prev = (@page - 1 unless @page == 1)
43
+ @next = @page == @last ? (1 if @vars[:cycle]) : @page + 1
32
44
  end
33
45
 
34
46
  # Return the array of page numbers and :gap items e.g. [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
35
- def series(size=@vars[:size])
36
- (series = []) and size.empty? and return series
37
- 4.times{|i| (size[i]>=0 rescue nil) or raise(VariableError.new(self), "expected 4 items >= 0 in :size; got #{size.inspect}")}
38
- [*0..size[0], *@page-size[1]..@page+size[2], *@last-size[3]+1..@last+1].sort!.each_cons(2) do |a, b|
39
- if a<0 || a==b || a>@last # skip out of range and duplicates
40
- elsif a+1 == b; series.push(a) # no gap -> no additions
41
- elsif a+2 == b; series.push(a, a+1) # 1 page gap -> fill with missing page
42
- else series.push(a, :gap) # n page gap -> add gap
43
- end # skip the end boundary (last+1)
44
- end # shift the start boundary (0) and
45
- series.shift; series[series.index(@page)] = @page.to_s; series # convert the current page to String
47
+ def series(size: @vars[:size], **_)
48
+ return [] if size.empty?
49
+ raise VariableError.new(self, :size, 'to contain 4 items >= 0', size) \
50
+ unless size.is_a?(Array) && size.size == 4 && size.all? { |num| !num.negative? rescue false } # rubocop:disable Style/RescueModifier
51
+
52
+ # This algorithm is up to ~5x faster and ~2.3x lighter than the previous one (pagy < 4.3)
53
+ left_gap_start = 1 + size[0] # rubocop:disable Layout/ExtraSpacing, Layout/SpaceAroundOperators
54
+ left_gap_end = @page - size[1] - 1
55
+ right_gap_start = @page + size[2] + 1
56
+ right_gap_end = @last - size[3]
57
+ left_gap_end = right_gap_end if left_gap_end > right_gap_end
58
+ right_gap_start = left_gap_start if left_gap_start > right_gap_start
59
+ series = []
60
+ start = 1
61
+ if (left_gap_end - left_gap_start).positive?
62
+ series.push(*start...left_gap_start, :gap)
63
+ start = left_gap_end + 1
64
+ end
65
+ if (right_gap_end - right_gap_start).positive?
66
+ series.push(*start...right_gap_start, :gap)
67
+ start = right_gap_end + 1
68
+ end
69
+ series.push(*start..@last)
70
+ series[series.index(@page)] = @page.to_s
71
+ series
46
72
  end
47
73
 
74
+ # Allow the customization of the output (overridden by the calendar extra)
75
+ def label_for(page)
76
+ page.to_s
77
+ end
78
+
79
+ # Allow the customization of the output (overridden by the calendar extra)
80
+ def label
81
+ @page.to_s
82
+ end
83
+
84
+ protected
85
+
86
+ # Apply defaults, cleanup blanks and set @vars
87
+ def normalize_vars(vars)
88
+ @vars = DEFAULT.merge(vars.delete_if { |k, v| DEFAULT.key?(k) && (v.nil? || v == '') })
89
+ end
90
+
91
+ # Setup and validates the passed vars: var must be present and value.to_i must be >= to min
92
+ def setup_vars(name_min)
93
+ name_min.each do |name, min|
94
+ raise VariableError.new(self, name, ">= #{min}", @vars[name]) \
95
+ unless @vars[name] && instance_variable_set(:"@#{name}", @vars[name].to_i) >= min
96
+ end
97
+ end
98
+
99
+ # Setup and validate the items (overridden by the gearbox extra)
100
+ def setup_items_var
101
+ setup_vars(items: 1)
102
+ end
103
+
104
+ # Setup and validates the pages (overridden by the gearbox extra)
105
+ def setup_pages_var
106
+ @pages = @last = [(@count.to_f / @items).ceil, 1].max
107
+ end
108
+
109
+ # Setup and validates the params
110
+ def setup_params_var
111
+ raise VariableError.new(self, :params, 'must be a Hash or a Proc', @params) \
112
+ unless (@params = @vars[:params]).is_a?(Hash) || @params.is_a?(Proc)
113
+ end
48
114
  end
49
115
 
50
116
  require 'pagy/backend'
@@ -5,7 +5,7 @@
5
5
  The link variable is set to a proc that returns the link tag.
6
6
  Usage: link.call( page_number [, text [, extra_attributes_string ]])
7
7
  -%>
8
- <% link = pagy_link_proc(pagy, 'class="page-link"') -%>
8
+ <% link = pagy_link_proc(pagy, link_extra: 'class="page-link"') -%>
9
9
  <%# -%><nav aria-label="pager" class="pagy-bootstrap-nav" role="navigation">
10
10
  <%# -%> <ul class="pagination">
11
11
  <% if pagy.prev -%> <li class="page-item prev"><%== link.call(pagy.prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"') %></li>
@@ -4,7 +4,7 @@
4
4
  -# The link variable is set to a proc that returns the link tag.
5
5
  -# Usage: link.call( page_number [, text [, extra_attributes_string ]])
6
6
 
7
- - link = pagy_link_proc(pagy, 'class="page-link"')
7
+ - link = pagy_link_proc(pagy, link_extra: 'class="page-link"')
8
8
 
9
9
  %nav.pagy-bootstrap-nav{"aria-label" => "pager", :role => "navigation"}
10
10
 
@@ -4,7 +4,7 @@
4
4
  / The link variable is set to a proc that returns the link tag.
5
5
  / Usage: link.call( page_number [, text [, extra_attributes_string ]])
6
6
 
7
- - link = pagy_link_proc(pagy, 'class="page-link"')
7
+ - link = pagy_link_proc(pagy, link_extra: 'class="page-link"')
8
8
 
9
9
  nav.pagy-bootstrap-nav role="navigation" aria-label="pager"
10
10
 
@@ -13,7 +13,7 @@
13
13
  <% end -%>
14
14
  <% pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36] -%>
15
15
  <% if item.is_a?(Integer) -%> <li><%== link.call(item) %></li>
16
- <% elsif item.is_a?(String) -%> <li class="current"><%= item %></li>
16
+ <% elsif item.is_a?(String) -%> <li class="current"><%= pagy.label_for(item) %></li>
17
17
  <% elsif item == :gap -%> <li class="ellipsis gap" aria-hidden="true"></li>
18
18
  <% end -%>
19
19
  <% end -%>
@@ -22,7 +22,7 @@
22
22
 
23
23
  - elsif item.is_a?(String) # current page
24
24
  %li.current
25
- = item
25
+ = pagy.label_for(item)
26
26
 
27
27
  - elsif item == :gap # page gap
28
28
  %li.ellipsis.gap{"aria-hidden" => true}
@@ -22,7 +22,7 @@ nav.pagy-foundation-nav role="navigation" aria-label="Pagination"
22
22
 
23
23
  - elsif item.is_a?(String) # current page
24
24
  li.current
25
- = item
25
+ = pagy.label_for(item)
26
26
 
27
27
  - elsif item == :gap # page gap
28
28
  li.ellipsis.gap aria-hidden="true"
@@ -5,11 +5,11 @@
5
5
  <% end -%>
6
6
  <% pagy.series.each do |item| -%>
7
7
  <% if item.is_a?(Integer) -%> <li><%== link.call(item) %></li>
8
- <% elsif item.is_a?(String) -%> <li class="uk-active"><span><%== item %></span></li>
8
+ <% elsif item.is_a?(String) -%> <li class="uk-active"><span><%== pagy.label_for(item) %></span></li>
9
9
  <% elsif item == :gap -%> <li class="uk-disabled"><span><%== pagy_t('pagy.nav.gap') %></span></li>
10
10
  <% end -%>
11
11
  <% end -%>
12
- <% if pagy.next -%> <li><%== link.call(p_next, "<span uk-pagination-next>#{pagy_t('pagy.nav.next')}</span>") %></li>
12
+ <% if pagy.next -%> <li><%== link.call(pagy.next, "<span uk-pagination-next>#{pagy_t('pagy.nav.next')}</span>") %></li>
13
13
  <% else -%> <li class="uk-disabled"><a href="#"><span uk-pagination-next><%== pagy_t('pagy.nav.next') %></span></a></li>
14
14
  <% end -%>
15
15
  <%# -%> </ul>
@@ -14,7 +14,7 @@
14
14
 
15
15
  - elsif item.is_a?(String)
16
16
  %li.uk-active
17
- %span!= item
17
+ %span!= pagy.label_for(item)
18
18
 
19
19
  - elsif item == :gap
20
20
  %li.uk-disabled
@@ -14,14 +14,14 @@ ul.uk-pagination.uk-flex-center
14
14
 
15
15
  - elsif item.is_a?(String)
16
16
  li.uk-active
17
- span== item
17
+ span== pagy.label_for(item)
18
18
 
19
19
  - elsif item == :gap
20
20
  li.uk-disabled
21
21
  span== pagy_t('pagy.nav.gap')
22
22
 
23
23
  - if pagy.next
24
- li== link.call(p_next, "<span uk-pagination-next>#{pagy_t('pagy.nav.next')}</span>")
24
+ li== link.call(pagy.next, "<span uk-pagination-next>#{pagy_t('pagy.nav.next')}</span>")
25
25
  - else
26
26
  li.uk-disabled
27
27
  a href="#"