pagy 43.0.0 → 43.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +11 -12
  4. data/apps/demo.ru +5 -5
  5. data/apps/enable_rails_page_segment.rb +54 -0
  6. data/apps/index.rb +1 -1
  7. data/apps/keynav+root_key.ru +316 -0
  8. data/apps/keynav.ru +10 -13
  9. data/apps/keyset.ru +5 -11
  10. data/apps/keyset_sequel.ru +10 -12
  11. data/apps/rails.ru +8 -12
  12. data/apps/repro.ru +11 -11
  13. data/bin/pagy +2 -94
  14. data/config/pagy.rb +8 -7
  15. data/javascripts/ai_widget.js +65 -51
  16. data/javascripts/pagy.js +20 -17
  17. data/javascripts/pagy.js.map +3 -3
  18. data/javascripts/pagy.min.js +2 -1
  19. data/javascripts/pagy.mjs +19 -16
  20. data/javascripts/wand.js +15 -9
  21. data/lib/pagy/classes/calendar/calendar.rb +36 -31
  22. data/lib/pagy/classes/calendar/day.rb +1 -1
  23. data/lib/pagy/classes/calendar/month.rb +1 -1
  24. data/lib/pagy/classes/calendar/quarter.rb +1 -1
  25. data/lib/pagy/classes/calendar/unit.rb +12 -13
  26. data/lib/pagy/classes/calendar/year.rb +1 -1
  27. data/lib/pagy/classes/exceptions.rb +1 -8
  28. data/lib/pagy/classes/keyset/adapters/active_record.rb +3 -1
  29. data/lib/pagy/classes/keyset/adapters/sequel.rb +3 -1
  30. data/lib/pagy/classes/keyset/keynav.rb +9 -4
  31. data/lib/pagy/classes/keyset/keyset.rb +57 -32
  32. data/lib/pagy/classes/offset/countish.rb +17 -0
  33. data/lib/pagy/classes/offset/countless.rb +26 -14
  34. data/lib/pagy/classes/offset/offset.rb +8 -2
  35. data/lib/pagy/classes/offset/search.rb +6 -10
  36. data/lib/pagy/classes/request.rb +29 -20
  37. data/lib/pagy/cli.rb +135 -0
  38. data/lib/pagy/console.rb +6 -0
  39. data/lib/pagy/modules/abilities/configurable.rb +2 -2
  40. data/lib/pagy/modules/abilities/countable.rb +24 -0
  41. data/lib/pagy/modules/abilities/linkable.rb +35 -24
  42. data/lib/pagy/modules/abilities/rangeable.rb +3 -3
  43. data/lib/pagy/modules/b64.rb +9 -3
  44. data/lib/pagy/modules/console.rb +15 -20
  45. data/lib/pagy/modules/i18n/i18n.rb +38 -14
  46. data/lib/pagy/modules/i18n/p11n/arabic.rb +1 -0
  47. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +1 -0
  48. data/lib/pagy/modules/i18n/p11n/polish.rb +1 -0
  49. data/lib/pagy/modules/searcher.rb +9 -8
  50. data/lib/pagy/toolbox/helpers/anchor_tags.rb +11 -15
  51. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +3 -0
  52. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +3 -1
  53. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +2 -0
  54. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +3 -0
  55. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +1 -1
  56. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +3 -1
  57. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +2 -0
  58. data/lib/pagy/toolbox/helpers/data_hash.rb +19 -17
  59. data/lib/pagy/toolbox/helpers/headers_hash.rb +15 -9
  60. data/lib/pagy/toolbox/helpers/info_tag.rb +2 -0
  61. data/lib/pagy/toolbox/helpers/input_nav_js.rb +9 -6
  62. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +4 -3
  63. data/lib/pagy/toolbox/helpers/loader.rb +3 -0
  64. data/lib/pagy/toolbox/helpers/page_url.rb +10 -16
  65. data/lib/pagy/toolbox/helpers/series_nav.rb +5 -4
  66. data/lib/pagy/toolbox/helpers/series_nav_js.rb +2 -1
  67. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +8 -6
  68. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +6 -1
  69. data/lib/pagy/toolbox/helpers/support/series.rb +1 -2
  70. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +1 -1
  71. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +2 -1
  72. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +10 -4
  73. data/lib/pagy/toolbox/helpers/urls_hash.rb +7 -7
  74. data/lib/pagy/toolbox/paginators/calendar.rb +13 -9
  75. data/lib/pagy/toolbox/paginators/countish.rb +39 -0
  76. data/lib/pagy/toolbox/paginators/countless.rb +13 -15
  77. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +43 -18
  78. data/lib/pagy/toolbox/paginators/keynav_js.rb +14 -15
  79. data/lib/pagy/toolbox/paginators/keyset.rb +7 -9
  80. data/lib/pagy/toolbox/paginators/meilisearch.rb +21 -18
  81. data/lib/pagy/toolbox/paginators/method.rb +15 -3
  82. data/lib/pagy/toolbox/paginators/offset.rb +14 -22
  83. data/lib/pagy/toolbox/paginators/searchkick.rb +21 -18
  84. data/lib/pagy/toolbox/paginators/typesense_rails.rb +35 -0
  85. data/lib/pagy.rb +23 -10
  86. data/locales/id.yml +1 -3
  87. data/locales/ja.yml +1 -3
  88. data/locales/km.yml +1 -3
  89. data/locales/sw.yml +2 -2
  90. data/locales/tr.yml +10 -8
  91. data/stylesheets/pagy-tailwind.css +1 -1
  92. data/stylesheets/pagy.css +1 -6
  93. metadata +25 -8
  94. data/lib/optimist.rb +0 -1022
  95. data/lib/pagy/classes/keyset/active_record.rb +0 -11
  96. data/lib/pagy/classes/keyset/keynav/active_record.rb +0 -13
  97. data/lib/pagy/classes/keyset/keynav/sequel.rb +0 -13
  98. data/lib/pagy/classes/keyset/sequel.rb +0 -11
@@ -3,36 +3,31 @@
3
3
  class Pagy
4
4
  # Provide a ready to use pagy environment when included in irb/rails console
5
5
  module Console
6
- class Request
7
- attr_accessor :base_url, :path, :params
8
-
9
- def initialize
10
- @base_url = 'http://www.example.com'
11
- @path = '/path'
12
- @params = { example: '123' }
13
- end
14
-
15
- def GET = @params # rubocop:disable Naming/MethodName
16
-
17
- def cookies = {}
18
- end
19
-
20
6
  class Collection < Array
21
7
  def initialize(arr = Array(1..1000))
22
8
  super
23
9
  @collection = clone
24
10
  end
25
11
 
26
- def offset(value) = tap { @collection = self[value..] }
27
- def limit(value) = @collection[0, value]
28
- def count(*) = size
12
+ def offset(value)
13
+ tap { @collection = self[value..] }
14
+ end
15
+
16
+ def limit(value)
17
+ @collection[0, value]
18
+ end
19
+
20
+ def count(*) = size
29
21
  end
30
22
 
31
23
  include Method
32
24
 
33
- # Direct reference to request.params via a method
34
- def params = request.params
35
- def request = @request ||= Request.new
25
+ def request
26
+ @request ||= { base_url: 'http://www.example.com', path: '/path', params: { example: '123' } }
27
+ end
28
+
29
+ def params = request[:params]
30
+
36
31
  def collection = Collection
37
32
  end
38
33
  end
@@ -6,42 +6,66 @@ require_relative 'p11n'
6
6
  class Pagy
7
7
  # Pagy i18n implementation, compatible with the I18n gem, just a lot faster and lighter
8
8
  module I18n
9
+ class KeyError < KeyError; end
10
+
9
11
  extend self
10
12
 
11
- def pathnames = @pathnames ||= [ROOT.join('locales')]
12
- def locales = @locales ||= {}
13
+ def pathnames
14
+ @pathnames ||= [ROOT.join('locales')]
15
+ end
16
+
17
+ def locales
18
+ @locales ||= {}
19
+ end
13
20
 
14
21
  # Store the variable for the duration of a single request
15
22
  def locale=(value)
16
- Thread.current[:pagy_locale] = value
23
+ Thread.current[:pagy_locale] = value.to_s
17
24
  end
18
25
 
19
- def locale = Thread.current[:pagy_locale] || 'en'
26
+ def locale
27
+ Thread.current[:pagy_locale] || 'en'
28
+ end
20
29
 
21
30
  # Translate and pluralize the key with the locale entries
22
31
  def translate(key, **options)
23
- data, p11n = locales[locale] || self.load
24
- translation = data[key] || (options[:count] && data[key += ".#{p11n.plural_for(options[:count])}"]) \
25
- or return %([translation missing: "#{key}"])
26
- translation.gsub(/%{[^}]+?}/) { |match| options[:"#{match[2..-2]}"] || match }
32
+ data, p11n = locales[locale] || self.load
33
+ key += ".#{p11n.plural_for(options[:count])}" if !data[key] && options[:count]
34
+
35
+ translation = data[key] or return %([translation missing: "#{key}"])
36
+
37
+ translation.gsub(/%{[^}]+?}/) { options.fetch(_1[2..-2].to_sym, _1) } # replace the interpolation placeholders
27
38
  end
28
39
 
29
40
  private
30
41
 
31
- def load
42
+ def load(locale: self.locale)
32
43
  path = pathnames.reverse.map { |p| p.join("#{locale}.yml") }.find(&:exist?)
33
- raise Errno::ENOENT, "missing dictionary file for #{locale.inspect} locale" unless path
44
+ unless path
45
+ warn %(Pagy::I18n: missing dictionary file for #{locale.inspect} locale; using "en" instead)
46
+ return locales[locale] = locales['en'] || load(locale: 'en')
47
+ end
48
+
49
+ dictionary = YAML.load_file(path)[locale]
50
+ raise KeyError, "missing 'pagy' key in #{locale.inspect} locale" unless dictionary['pagy']
51
+
52
+ p11n = dictionary['pagy'].delete('p11n')
53
+ raise KeyError, "missing 'p11n' key in #{locale.inspect} locale" unless p11n
34
54
 
35
- dictionary = YAML.load_file(path)[locale]
36
- p11n = dictionary['pagy'].delete('p11n')
37
55
  locales[locale] = [flatten_to_dot_keys(dictionary), Object.const_get("Pagy::I18n::P11n::#{p11n}")]
38
56
  end
39
57
 
40
58
  # Create a flat hash with dotted notation keys
41
59
  # e.g. { 'a' => { 'b' => {'c' => 3, 'd' => 4 }}} -> { 'a.b.c' => 3, 'a.b.d' => 4 }
42
60
  def flatten_to_dot_keys(initial, prefix = '')
43
- initial.reduce({}) do |hash, (key, value)|
44
- hash.merge!(value.is_a?(Hash) ? flatten_to_dot_keys(value, "#{prefix}#{key}.") : { "#{prefix}#{key}" => value })
61
+ initial.each_with_object({}) do |(key, value), hash|
62
+ key = "#{prefix}#{key}"
63
+
64
+ if value.is_a?(Hash)
65
+ hash.merge!(flatten_to_dot_keys(value, "#{key}."))
66
+ else
67
+ hash[key] = value
68
+ end
45
69
  end
46
70
  end
47
71
  end
@@ -8,6 +8,7 @@ class Pagy
8
8
 
9
9
  def plural_for(n = 0)
10
10
  mod100 = n % 100
11
+
11
12
  case
12
13
  when n == 0 # rubocop:disable Style/NumericPredicate
13
14
  :zero
@@ -9,6 +9,7 @@ class Pagy
9
9
  def plural_for(n = 0)
10
10
  mod10 = n % 10
11
11
  mod100 = n % 100
12
+
12
13
  case
13
14
  when mod10 == 1 && mod100 != 11
14
15
  :one
@@ -9,6 +9,7 @@ class Pagy
9
9
  def plural_for(n = 0)
10
10
  mod10 = n % 10
11
11
  mod100 = n % 100
12
+
12
13
  case
13
14
  when n == 1
14
15
  :one
@@ -6,15 +6,16 @@ class Pagy
6
6
  module_function
7
7
 
8
8
  # Common search logic
9
- def wrap(context, pagy_search_args, options)
10
- context.instance_exec do
11
- options[:request] = Request.new(options[:request] || request, options)
12
- options[:page] ||= options[:request].resolve_page(options)
13
- options[:limit] ||= options[:request].resolve_limit(options)
14
- end
9
+ def wrap(search_arguments, options)
10
+ options[:page] ||= options[:request].resolve_page
11
+ options[:limit] = options[:request].resolve_limit
12
+
15
13
  pagy, results = yield
16
- calling = pagy_search_args[4..]
17
- [pagy, calling.empty? ? results : results.send(*calling)]
14
+
15
+ called = search_arguments[4..]
16
+ results = results.send(*called) unless called.empty?
17
+
18
+ [pagy, results]
18
19
  end
19
20
  end
20
21
  end
@@ -4,22 +4,18 @@ require_relative 'support/a_lambda' # inheritable
4
4
 
5
5
  class Pagy
6
6
  # Return the enabled/disabled previous page anchor tag
7
- def previous_tag(a = nil, text: I18n.translate('pagy.previous'),
8
- aria_label: I18n.translate('pagy.aria_label.previous'), **)
9
- if @previous
10
- (a || a_lambda(**)).(@previous, text, aria_label:)
11
- else
12
- %(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
13
- end
14
- end
7
+ def previous_tag(...) = anchor_tag_for(:previous, ...)
15
8
 
16
9
  # Return the enabled/disabled next page anchor tag
17
- def next_tag(a = nil, text: I18n.translate('pagy.next'),
18
- aria_label: I18n.translate('pagy.aria_label.next'), **)
19
- if @next
20
- (a || a_lambda(**)).(@next, text, aria_label:)
21
- else
22
- %(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
23
- end
10
+ def next_tag(...) = anchor_tag_for(:next, ...)
11
+
12
+ private
13
+
14
+ def anchor_tag_for(which, a = nil, text: I18n.translate("pagy.#{which}"),
15
+ aria_label: I18n.translate("pagy.aria_label.#{which}"), **)
16
+ page = send(which)
17
+ return (a || a_lambda(**)).(page.to_i, text, aria_label:) if page
18
+
19
+ %(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
24
20
  end
25
21
  end
@@ -9,9 +9,11 @@ class Pagy
9
9
  # Javascript combo pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
10
10
  def bootstrap_input_nav_js(classes: 'pagination', **)
11
11
  a_lambda = a_lambda(**)
12
+
12
13
  input = %(<input name="page" type="number" min="1" max="#{last}" value="#{@page}" aria-current="page" ) +
13
14
  %(style="text-align: center; width: #{@page.to_s.length + 1}rem; padding: 0; border-radius: .25rem; ) +
14
15
  %(border: none; display: inline-block;" class="page-link active">#{A_TAG})
16
+
15
17
  html = %(<ul class="#{classes}">#{
16
18
  bootstrap_html_for(:previous, a_lambda)
17
19
  }<li class="page-item"><label class="page-link">#{
@@ -19,6 +21,7 @@ class Pagy
19
21
  }</label></li>#{
20
22
  bootstrap_html_for(:next, a_lambda)
21
23
  }</ul>)
24
+
22
25
  wrap_input_nav_js(html, 'pagy-bootstrap input-nav-js', **)
23
26
  end
24
27
  end
@@ -9,7 +9,8 @@ class Pagy
9
9
  # Pagination for bootstrap: it returns the html with the series of links to the pages
10
10
  def bootstrap_series_nav(classes: 'pagination', **)
11
11
  a_lambda = a_lambda(**)
12
- html = %(<ul class="#{classes}">#{bootstrap_html_for(:previous, a_lambda)})
12
+
13
+ html = %(<ul class="#{classes}">#{bootstrap_html_for(:previous, a_lambda)})
13
14
  series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
14
15
  html << case item
15
16
  when Integer
@@ -24,6 +25,7 @@ class Pagy
24
25
  end
25
26
  end
26
27
  html << %(#{bootstrap_html_for(:next, a_lambda)}</ul>)
28
+
27
29
  wrap_series_nav(html, 'pagy-bootstrap series-nav', **)
28
30
  end
29
31
  end
@@ -9,6 +9,7 @@ class Pagy
9
9
  # Javascript pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
10
10
  def bootstrap_series_nav_js(classes: 'pagination', **)
11
11
  a_lambda = a_lambda(**)
12
+
12
13
  tokens = { before: %(<ul class="#{classes}">#{bootstrap_html_for(:previous, a_lambda)}),
13
14
  anchor: %(<li class="page-item">#{a_lambda.(PAGE_TOKEN, LABEL_TOKEN, classes: 'page-link')}</li>),
14
15
  current: %(<li class="page-item active"><a role="link" class="page-link" ) +
@@ -16,6 +17,7 @@ class Pagy
16
17
  gap: %(<li class="page-item gap disabled"><a role="link" class="page-link" aria-disabled="true">#{
17
18
  I18n.translate('pagy.gap')}</a></li>),
18
19
  after: %(#{bootstrap_html_for(:next, a_lambda)}</ul>) }
20
+
19
21
  wrap_series_nav_js(tokens, 'pagy-bootstrap series-nav-js', **)
20
22
  end
21
23
  end
@@ -9,13 +9,16 @@ class Pagy
9
9
  # Javascript combo pagination for bulma: it returns a nav with a data-pagy attribute used by the pagy.js file
10
10
  def bulma_input_nav_js(classes: 'pagination', **)
11
11
  a_lambda = a_lambda(**)
12
+
12
13
  input = %(<input name="page" type="number" min="1" max="#{@last}" value="#{@page}" aria-current="page") +
13
14
  %(style="text-align: center; width: #{@page.to_s.length + 1}rem; line-height: 1.2rem; ) +
14
15
  %(border: none; border-radius: .25rem; padding: .0625rem; color: white; ) +
15
16
  %(background-color: #485fc7;">#{A_TAG})
17
+
16
18
  html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)}<li class="pagination-link"><label>#{
17
19
  I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)
18
20
  }</label></li>#{bulma_html_for(:next, a_lambda)}</ul>)
21
+
19
22
  wrap_input_nav_js(html, "pagy-bulma input-nav-js #{classes}", **)
20
23
  end
21
24
  end
@@ -8,7 +8,7 @@ class Pagy
8
8
  %(<li>#{
9
9
  if send(which)
10
10
  a_lambda.(send(which), I18n.translate("pagy.#{which}"),
11
- classes: "pagination-#{which}",
11
+ classes: "pagination-#{which}",
12
12
  aria_label: I18n.translate("pagy.aria_label.#{which}"))
13
13
  else
14
14
  %(<a role="link" class="pagination-#{which}" disabled aria-disabled="true" aria-label="#{
@@ -9,7 +9,8 @@ class Pagy
9
9
  # Pagination for bulma: it returns the html with the series of links to the pages
10
10
  def bulma_series_nav(classes: 'pagination', **)
11
11
  a_lambda = a_lambda(**)
12
- html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)})
12
+
13
+ html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)})
13
14
  series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
14
15
  html << case item
15
16
  when Integer
@@ -23,6 +24,7 @@ class Pagy
23
24
  end
24
25
  end
25
26
  html << %(#{bulma_html_for(:next, a_lambda)}</ul>)
27
+
26
28
  wrap_series_nav(html, "pagy-bulma series-nav #{classes}", **)
27
29
  end
28
30
  end
@@ -9,12 +9,14 @@ class Pagy
9
9
  # Javascript pagination for bulma: it returns a nav with a data-pagy attribute used by the Pagy.nav javascript
10
10
  def bulma_series_nav_js(classes: 'pagination', **)
11
11
  a_lambda = a_lambda(**)
12
+
12
13
  tokens = { before: %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)}),
13
14
  anchor: %(<li>#{a_lambda.(PAGE_TOKEN, LABEL_TOKEN, classes: 'pagination-link')}</li>),
14
15
  current: %(<li><a role="link" class="pagination-link is-current" ) +
15
16
  %(aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a></li>),
16
17
  gap: %(<li><span class="pagination-ellipsis">#{I18n.translate('pagy.gap')}</span></li>),
17
18
  after: %(#{bulma_html_for(:next, a_lambda)}</ul>) }
19
+
18
20
  wrap_series_nav_js(tokens, "pagy-bulma series-nav-js #{classes}", **)
19
21
  end
20
22
  end
@@ -1,27 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Pagy
4
- DEFAULT_DATA_KEYS = %i[url_template first_url previous_url page_url next_url last_url
4
+ DEFAULT_DATA_KEYS = %i[url_template first_url previous_url current_url page_url next_url last_url
5
5
  count page limit last in from to previous next options].freeze
6
6
 
7
7
  # Generate a hash of the wanted internal data
8
8
  def data_hash(data_keys: @options[:data_keys] || DEFAULT_DATA_KEYS, **)
9
- data_keys -= %i[count limit] if calendar?
10
- url_template = compose_page_url(PAGE_TOKEN, **)
11
- {}.tap do |data|
12
- data_keys.each do |key|
13
- data[key] = case key
14
- when :url_template then url_template
15
- when :first_url then compose_page_url(nil, **)
16
- when :previous_url then url_template.sub(PAGE_TOKEN, @previous.to_s)
17
- when :page_url then url_template.sub(PAGE_TOKEN, @page.to_s)
18
- when :next_url then url_template.sub(PAGE_TOKEN, @next.to_s)
19
- when :last_url then url_template.sub(PAGE_TOKEN, @last.to_s)
20
- else send(key)
21
- end
22
- rescue NoMethodError
23
- raise OptionError.new(self, :data, 'to contain known keys', key)
24
- end
9
+ template = compose_page_url(PAGE_TOKEN, **)
10
+ to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) if page }
11
+
12
+ data_keys -= %i[count limit] if calendar?
13
+
14
+ data_keys.each_with_object({}) do |key, data|
15
+ value = case key
16
+ when :url_template then template
17
+ when :first_url then compose_page_url(nil, **)
18
+ when :previous_url then to_url.(@previous)
19
+ when :current_url, :page_url then to_url.(@page)
20
+ when :next_url then to_url.(@next)
21
+ when :last_url then to_url.(@last)
22
+ else send(key)
23
+ end
24
+ data[key] = value if value
25
+ rescue NoMethodError
26
+ raise OptionError.new(self, :data_keys, 'to contain known keys/methods', key)
25
27
  end
26
28
  end
27
29
  end
@@ -4,21 +4,27 @@ require_relative 'urls_hash'
4
4
 
5
5
  # Add pagination response headers
6
6
  class Pagy
7
- DEFAULT_HEADERS_MAP = { page: 'current-page',
7
+ DEFAULT_HEADERS_MAP = { page: 'current-page',
8
8
  limit: 'page-limit',
9
9
  count: 'total-count',
10
10
  pages: 'total-pages' }.freeze
11
11
 
12
12
  # Generate a hash of RFC-8288-compliant http headers
13
13
  def headers_hash(headers_map: @options[:headers_map] || DEFAULT_HEADERS_MAP, **)
14
- links = urls_hash(**, absolute: true).map { |key, url| %(<#{url}>; rel="#{key}") }.join(', ')
15
- { 'link' => links }.tap do |hash|
16
- hash[headers_map[:page]] = @page.to_s if @page && headers_map[:page]
17
- hash[headers_map[:limit]] = @limit.to_s if headers_map[:limit] && !calendar?
18
- if @count
19
- hash[headers_map[:pages]] = @last.to_s if headers_map[:pages]
20
- hash[headers_map[:count]] = @count.to_s if headers_map[:count]
21
- end
14
+ links = urls_hash(**, absolute: true).map { %(<#{_2}>; rel="#{_1}") }.join(', ')
15
+
16
+ headers_map.each_with_object('link' => links) do |(key, name), hash|
17
+ next unless name
18
+
19
+ value = case key
20
+ # :nocov:
21
+ when :page then @page
22
+ when :limit then @limit unless calendar?
23
+ when :pages then @last if @count
24
+ when :count then @count
25
+ # :nocov:
26
+ end
27
+ hash[name] = value.to_s if value
22
28
  end
23
29
  end
24
30
  end
@@ -13,6 +13,7 @@ class Pagy
13
13
  else
14
14
  'pagy.info_tag.multiple_pages'
15
15
  end
16
+
16
17
  info_data = if @count.nil?
17
18
  { page: @page, pages: @last }
18
19
  else
@@ -21,6 +22,7 @@ class Pagy
21
22
  from: @from,
22
23
  to: @to }
23
24
  end
25
+
24
26
  %(<span#{%( id="#{id}") if id} class="pagy info">#{I18n.translate(i18n_key, **info_data)}</span>)
25
27
  end
26
28
  end
@@ -5,14 +5,17 @@ require_relative 'support/wrap_input_nav_js'
5
5
  class Pagy
6
6
  # JavaScript input pagination: it returns a nav with a data-pagy attribute used by the pagy.js file
7
7
  def input_nav_js(style = nil, **)
8
- return send(:"#{style}_input_nav_js", **) if style
8
+ return send(:"#{style}_input_nav_js", **) if style && style.to_s != 'pagy'
9
9
 
10
10
  a_lambda = a_lambda(**)
11
- input = %(<input name="page" type="number" min="1" max="#{@last}" value="#{@page}" aria-current="page" ) +
12
- %(style="text-align: center; width: #{@page.to_s.length + 1}rem; padding: 0;">#{A_TAG})
13
- html = %(#{previous_tag(a_lambda)}<label>#{
14
- I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)}</label>#{
15
- next_tag(a_lambda)})
11
+
12
+ input = %(<input name="page" type="number" min="1" max="#{@last}" value="#{@page}" aria-current="page" ) +
13
+ %(style="text-align: center; width: #{@page.to_s.length + 1}rem; padding: 0;">#{A_TAG})
14
+
15
+ html = %(#{previous_tag(a_lambda)}<label>#{
16
+ I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)}</label>#{
17
+ next_tag(a_lambda)})
18
+
16
19
  wrap_input_nav_js(html, 'pagy input-nav-js', **)
17
20
  end
18
21
  end
@@ -7,12 +7,13 @@ class Pagy
7
7
  def limit_tag_js(id: nil, item_name: nil, client_max_limit: @options[:client_max_limit], **)
8
8
  raise OptionError.new(self, :client_max_limit, 'to be truthy', client_max_limit) unless client_max_limit
9
9
 
10
- url_token = compose_page_url(PAGE_TOKEN, limit_token: LIMIT_TOKEN)
11
- limit_input = %(<input name="limit" type="number" min="1" max="#{@options[:client_max_limit]}" value="#{
10
+ limit_input = %(<input name="limit" type="number" min="1" max="#{client_max_limit}" value="#{
12
11
  @limit}" style="padding: 0; text-align: center; width: #{@limit.to_s.length + 1}rem;">#{A_TAG})
13
12
 
13
+ url_token = compose_page_url(PAGE_TOKEN, limit: LIMIT_TOKEN)
14
+
14
15
  %(<span#{%( id="#{id}") if id} class="pagy limit-tag-js" #{
15
- data_pagy_attribute(:ltj, @from, url_token)
16
+ data_pagy_attribute(:ltj, @from, url_token, PAGE_TOKEN, LIMIT_TOKEN)
16
17
  }><label>#{
17
18
  I18n.translate('pagy.limit_tag_js',
18
19
  item_name: item_name || I18n.translate('pagy.item_name', count: @limit),
@@ -24,8 +24,11 @@ class Pagy
24
24
  send(visibility)
25
25
  # Load the method, overriding its own alias. Next requests will call the method directly.
26
26
  define_method(:"load_#{visibility}") do |*args, **kwargs|
27
+ # Tests shadow the usage of these lines
28
+ # :nocov:
27
29
  require_relative methods[__callee__]
28
30
  send(__callee__, *args, **kwargs)
31
+ # :nocov:
29
32
  end
30
33
  methods.each_key { |method| alias_method method, :"load_#{visibility}" }
31
34
  end
@@ -2,22 +2,16 @@
2
2
 
3
3
  class Pagy
4
4
  # Return the page url for any page
5
- # :nocov:
6
5
  def page_url(page, **)
7
- case page
8
- when :first
9
- compose_page_url(nil, **)
10
- when :current
11
- compose_page_url(@page, **) if @page
12
- when :previous
13
- compose_page_url(@previous, **) if @previous
14
- when :next
15
- compose_page_url(@next, **) if @next
16
- when :last
17
- compose_page_url(@last, **) if @last
18
- when Integer, String
19
- compose_page_url(page, **)
20
- end
6
+ target = case page
7
+ when :first then nil
8
+ when :current, :page then @page
9
+ when :previous then @previous
10
+ when :next then @next
11
+ when :last then @last
12
+ else page
13
+ end
14
+
15
+ compose_page_url(target, **) if target || page == :first
21
16
  end
22
- # :nocov:
23
17
  end
@@ -5,12 +5,12 @@ require_relative 'support/wrap_series_nav'
5
5
  class Pagy
6
6
  # Return the HTML with the series of links to the pages
7
7
  def series_nav(style = nil, **)
8
- return send(:"#{style}_series_nav", **) if style
8
+ return send(:"#{style}_series_nav", **) if style && style.to_s != 'pagy'
9
9
 
10
10
  a_lambda = a_lambda(**)
11
- html = previous_tag(a_lambda)
12
- series(**).each do |item|
13
- # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
11
+
12
+ html = previous_tag(a_lambda)
13
+ series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
14
14
  html << case item
15
15
  when Integer
16
16
  a_lambda.(item)
@@ -23,6 +23,7 @@ class Pagy
23
23
  end
24
24
  end
25
25
  html << next_tag(a_lambda)
26
+
26
27
  wrap_series_nav(html, 'pagy series-nav', **)
27
28
  end
28
29
  end
@@ -5,7 +5,7 @@ require_relative 'support/wrap_series_nav_js'
5
5
  class Pagy
6
6
  # Return a nav with a data-pagy attribute used by the pagy.js file
7
7
  def series_nav_js(style = nil, **)
8
- return send(:"#{style}_series_nav_js", **) if style
8
+ return send(:"#{style}_series_nav_js", **) if style && style.to_s != 'pagy'
9
9
 
10
10
  a_lambda = a_lambda(**)
11
11
  tokens = { before: previous_tag(a_lambda),
@@ -13,6 +13,7 @@ class Pagy
13
13
  current: %(<a role="link" aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a>),
14
14
  gap: %(<a role="separator" aria-disabled="true">#{I18n.translate('pagy.gap')}</a>),
15
15
  after: next_tag(a_lambda) }
16
+
16
17
  wrap_series_nav_js(tokens, 'pagy series-nav-js', **)
17
18
  end
18
19
  end
@@ -13,21 +13,23 @@ class Pagy
13
13
 
14
14
  # Return a performance optimized lambda to generate the anchor tag
15
15
  # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
16
- def a_lambda(anchor_string: nil, **)
16
+ def a_lambda(anchor_string: @options[:anchor_string], **)
17
17
  left, right = %(<a href="#{compose_page_url(PAGE_TOKEN, **)}"#{
18
18
  %( #{anchor_string}) if anchor_string}).split(PAGE_TOKEN, 2)
19
19
 
20
20
  lambda do |page, text = page_label(page), classes: nil, aria_label: nil|
21
- title = if (counts = @options[:counts]) # only for calendar + counts
21
+ title = if (counts = @options[:counts]) # only for calendar with counts
22
22
  count = counts[page - 1]
23
23
  classes = classes ? "#{classes} empty-page" : 'empty-page' if count.zero?
24
24
  info_key = count.zero? ? 'pagy.info_tag.no_items' : 'pagy.info_tag.single_page'
25
25
  %( title="#{I18n.translate(info_key, item_name: I18n.translate('pagy.item_name', count:), count:)}")
26
26
  end
27
- rel = case page
28
- when @previous then %( rel="prev")
29
- when @next then %( rel="next")
30
- end
27
+
28
+ rel = case page
29
+ when @previous then %( rel="prev")
30
+ when @next then %( rel="next")
31
+ end
32
+
31
33
  %(#{left}#{page}#{right}#{title}#{
32
34
  %( class="#{classes}") if classes}#{rel}#{%( aria-label="#{aria_label}") if aria_label}>#{text}</a>)
33
35
  end
@@ -9,7 +9,12 @@ class Pagy
9
9
 
10
10
  # Compose the data-pagy attribute, with the base64 encoded JSON-serialized args. Use the faster oj gem if defined.
11
11
  def data_pagy_attribute(*args)
12
- data = defined?(Oj) ? Oj.dump(args, mode: :compat) : JSON.dump(args)
12
+ data = if defined?(Oj)
13
+ Oj.dump(args, mode: :compat)
14
+ else
15
+ JSON.dump(args)
16
+ end
17
+
13
18
  %(data-pagy="#{B64.encode(data)}")
14
19
  end
15
20
  end
@@ -7,8 +7,7 @@ class Pagy
7
7
 
8
8
  # Return the array of page numbers and :gap e.g. [1, :gap, 8, "9", 10, :gap, 36]
9
9
  def series(slots: @options[:slots] || SERIES_SLOTS, compact: @options[:compact], **)
10
- raise OptionError.new(self, :slots, 'to be an Integer >= 0', slots) \
11
- unless slots.is_a?(Integer) && slots >= 0
10
+ raise OptionError.new(self, :slots, 'to be an Integer >= 0', slots) unless slots.is_a?(Integer) && slots >= 0
12
11
  return [] if slots.zero?
13
12
 
14
13
  [].tap do |series|
@@ -12,7 +12,7 @@ class Pagy
12
12
  def wrap_input_nav_js(html, nav_classes, id: nil, aria_label: nil, **)
13
13
  %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
14
14
  nav_aria_label_attribute(aria_label:)} #{
15
- data = [:inj, compose_page_url(PAGE_TOKEN, **)]
15
+ data = [:inj, compose_page_url(PAGE_TOKEN, **), PAGE_TOKEN]
16
16
  data.push(@update) if keynav?
17
17
  data_pagy_attribute(*data)
18
18
  }>#{html}</nav>)