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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/apps/calendar.ru +11 -12
- data/apps/demo.ru +5 -5
- data/apps/enable_rails_page_segment.rb +54 -0
- data/apps/index.rb +1 -1
- data/apps/keynav+root_key.ru +316 -0
- data/apps/keynav.ru +10 -13
- data/apps/keyset.ru +5 -11
- data/apps/keyset_sequel.ru +10 -12
- data/apps/rails.ru +8 -12
- data/apps/repro.ru +11 -11
- data/bin/pagy +2 -94
- data/config/pagy.rb +8 -7
- data/javascripts/ai_widget.js +65 -51
- data/javascripts/pagy.js +20 -17
- data/javascripts/pagy.js.map +3 -3
- data/javascripts/pagy.min.js +2 -1
- data/javascripts/pagy.mjs +19 -16
- data/javascripts/wand.js +15 -9
- data/lib/pagy/classes/calendar/calendar.rb +36 -31
- data/lib/pagy/classes/calendar/day.rb +1 -1
- data/lib/pagy/classes/calendar/month.rb +1 -1
- data/lib/pagy/classes/calendar/quarter.rb +1 -1
- data/lib/pagy/classes/calendar/unit.rb +12 -13
- data/lib/pagy/classes/calendar/year.rb +1 -1
- data/lib/pagy/classes/exceptions.rb +1 -8
- data/lib/pagy/classes/keyset/adapters/active_record.rb +3 -1
- data/lib/pagy/classes/keyset/adapters/sequel.rb +3 -1
- data/lib/pagy/classes/keyset/keynav.rb +9 -4
- data/lib/pagy/classes/keyset/keyset.rb +57 -32
- data/lib/pagy/classes/offset/countish.rb +17 -0
- data/lib/pagy/classes/offset/countless.rb +26 -14
- data/lib/pagy/classes/offset/offset.rb +8 -2
- data/lib/pagy/classes/offset/search.rb +6 -10
- data/lib/pagy/classes/request.rb +29 -20
- data/lib/pagy/cli.rb +135 -0
- data/lib/pagy/console.rb +6 -0
- data/lib/pagy/modules/abilities/configurable.rb +2 -2
- data/lib/pagy/modules/abilities/countable.rb +24 -0
- data/lib/pagy/modules/abilities/linkable.rb +35 -24
- data/lib/pagy/modules/abilities/rangeable.rb +3 -3
- data/lib/pagy/modules/b64.rb +9 -3
- data/lib/pagy/modules/console.rb +15 -20
- data/lib/pagy/modules/i18n/i18n.rb +38 -14
- data/lib/pagy/modules/i18n/p11n/arabic.rb +1 -0
- data/lib/pagy/modules/i18n/p11n/east_slavic.rb +1 -0
- data/lib/pagy/modules/i18n/p11n/polish.rb +1 -0
- data/lib/pagy/modules/searcher.rb +9 -8
- data/lib/pagy/toolbox/helpers/anchor_tags.rb +11 -15
- data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +3 -0
- data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +3 -1
- data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +2 -0
- data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +3 -0
- data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +1 -1
- data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +3 -1
- data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +2 -0
- data/lib/pagy/toolbox/helpers/data_hash.rb +19 -17
- data/lib/pagy/toolbox/helpers/headers_hash.rb +15 -9
- data/lib/pagy/toolbox/helpers/info_tag.rb +2 -0
- data/lib/pagy/toolbox/helpers/input_nav_js.rb +9 -6
- data/lib/pagy/toolbox/helpers/limit_tag_js.rb +4 -3
- data/lib/pagy/toolbox/helpers/loader.rb +3 -0
- data/lib/pagy/toolbox/helpers/page_url.rb +10 -16
- data/lib/pagy/toolbox/helpers/series_nav.rb +5 -4
- data/lib/pagy/toolbox/helpers/series_nav_js.rb +2 -1
- data/lib/pagy/toolbox/helpers/support/a_lambda.rb +8 -6
- data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +6 -1
- data/lib/pagy/toolbox/helpers/support/series.rb +1 -2
- data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +1 -1
- data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +2 -1
- data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +10 -4
- data/lib/pagy/toolbox/helpers/urls_hash.rb +7 -7
- data/lib/pagy/toolbox/paginators/calendar.rb +13 -9
- data/lib/pagy/toolbox/paginators/countish.rb +39 -0
- data/lib/pagy/toolbox/paginators/countless.rb +13 -15
- data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +43 -18
- data/lib/pagy/toolbox/paginators/keynav_js.rb +14 -15
- data/lib/pagy/toolbox/paginators/keyset.rb +7 -9
- data/lib/pagy/toolbox/paginators/meilisearch.rb +21 -18
- data/lib/pagy/toolbox/paginators/method.rb +15 -3
- data/lib/pagy/toolbox/paginators/offset.rb +14 -22
- data/lib/pagy/toolbox/paginators/searchkick.rb +21 -18
- data/lib/pagy/toolbox/paginators/typesense_rails.rb +35 -0
- data/lib/pagy.rb +23 -10
- data/locales/id.yml +1 -3
- data/locales/ja.yml +1 -3
- data/locales/km.yml +1 -3
- data/locales/sw.yml +2 -2
- data/locales/tr.yml +10 -8
- data/stylesheets/pagy-tailwind.css +1 -1
- data/stylesheets/pagy.css +1 -6
- metadata +25 -8
- data/lib/optimist.rb +0 -1022
- data/lib/pagy/classes/keyset/active_record.rb +0 -11
- data/lib/pagy/classes/keyset/keynav/active_record.rb +0 -13
- data/lib/pagy/classes/keyset/keynav/sequel.rb +0 -13
- data/lib/pagy/classes/keyset/sequel.rb +0 -11
data/lib/pagy/modules/console.rb
CHANGED
|
@@ -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)
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
12
|
-
|
|
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
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
translation
|
|
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
|
-
|
|
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.
|
|
44
|
-
|
|
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
|
|
@@ -6,15 +6,16 @@ class Pagy
|
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
8
|
# Common search logic
|
|
9
|
-
def wrap(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
17
|
-
[
|
|
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(
|
|
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(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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:
|
|
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 {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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)
|
|
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>)
|