pagy 8.6.3 → 9.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/apps/calendar.ru +5 -5
- data/apps/demo.ru +4 -4
- data/apps/keyset_ar.ru +236 -0
- data/apps/keyset_s.ru +238 -0
- data/apps/rails.ru +5 -5
- data/apps/repro.ru +4 -4
- data/bin/pagy +4 -2
- data/config/pagy.rb +17 -13
- data/javascripts/pagy.min.js +2 -2
- data/javascripts/pagy.min.js.map +2 -2
- data/javascripts/pagy.mjs +2 -2
- data/lib/pagy/b64.rb +33 -0
- data/lib/pagy/backend.rb +19 -19
- data/lib/pagy/calendar/day.rb +1 -1
- data/lib/pagy/calendar/month.rb +1 -1
- data/lib/pagy/calendar/quarter.rb +1 -1
- data/lib/pagy/calendar/unit.rb +7 -10
- data/lib/pagy/calendar/week.rb +1 -1
- data/lib/pagy/calendar/year.rb +1 -1
- data/lib/pagy/calendar.rb +5 -5
- data/lib/pagy/countless.rb +10 -14
- data/lib/pagy/extras/arel.rb +2 -10
- data/lib/pagy/extras/array.rb +5 -10
- data/lib/pagy/extras/bootstrap.rb +5 -5
- data/lib/pagy/extras/bulma.rb +10 -7
- data/lib/pagy/extras/calendar.rb +4 -5
- data/lib/pagy/extras/countless.rb +8 -13
- data/lib/pagy/extras/elasticsearch_rails.rb +16 -25
- data/lib/pagy/extras/gearbox.rb +18 -18
- data/lib/pagy/extras/headers.rb +25 -24
- data/lib/pagy/extras/js_tools.rb +4 -4
- data/lib/pagy/extras/jsonapi.rb +26 -19
- data/lib/pagy/extras/keyset.rb +30 -0
- data/lib/pagy/extras/limit.rb +63 -0
- data/lib/pagy/extras/meilisearch.rb +9 -17
- data/lib/pagy/extras/metadata.rb +2 -2
- data/lib/pagy/extras/overflow.rb +5 -5
- data/lib/pagy/extras/pagy.rb +16 -16
- data/lib/pagy/extras/searchkick.rb +9 -17
- data/lib/pagy/extras/size.rb +1 -1
- data/lib/pagy/extras/standalone.rb +6 -6
- data/lib/pagy/extras/trim.rb +2 -2
- data/lib/pagy/frontend.rb +32 -33
- data/lib/pagy/i18n.rb +2 -2
- data/lib/pagy/keyset/active_record.rb +38 -0
- data/lib/pagy/keyset/sequel.rb +51 -0
- data/lib/pagy/keyset.rb +98 -0
- data/lib/pagy/shared_methods.rb +27 -0
- data/lib/pagy/url_helpers.rb +4 -4
- data/lib/pagy.rb +51 -65
- data/locales/ar.yml +1 -1
- data/locales/be.yml +1 -1
- data/locales/bg.yml +1 -1
- data/locales/bs.yml +1 -1
- data/locales/ca.yml +1 -1
- data/locales/ckb.yml +1 -1
- data/locales/cs.yml +1 -1
- data/locales/da.yml +1 -1
- data/locales/de.yml +1 -1
- data/locales/en.yml +1 -1
- data/locales/es.yml +1 -1
- data/locales/fr.yml +1 -1
- data/locales/hr.yml +1 -1
- data/locales/id.yml +1 -1
- data/locales/it.yml +1 -1
- data/locales/ja.yml +1 -1
- data/locales/km.yml +1 -1
- data/locales/ko.yml +1 -1
- data/locales/nb.yml +1 -1
- data/locales/nl.yml +1 -1
- data/locales/nn.yml +1 -1
- data/locales/pl.yml +1 -1
- data/locales/pt-BR.yml +1 -1
- data/locales/pt.yml +1 -1
- data/locales/ru.yml +1 -1
- data/locales/sr.yml +1 -1
- data/locales/sv-SE.yml +1 -1
- data/locales/sv.yml +1 -1
- data/locales/sw.yml +1 -1
- data/locales/ta.yml +1 -1
- data/locales/tr.yml +1 -1
- data/locales/uk.yml +1 -1
- data/locales/vi.yml +1 -1
- data/locales/zh-CN.yml +1 -1
- data/locales/zh-HK.yml +1 -1
- data/locales/zh-TW.yml +1 -1
- metadata +12 -24
- data/apps/tmp/calendar.sqlite3 +0 -0
- data/apps/tmp/calendar.sqlite3-shm +0 -0
- data/apps/tmp/calendar.sqlite3-wal +0 -0
- data/apps/tmp/local_secret.txt +0 -1
- data/apps/tmp/pagy-keyset.sqlite3 +0 -0
- data/apps/tmp/pagy-keyset.sqlite3-shm +0 -0
- data/apps/tmp/pagy-keyset.sqlite3-wal +0 -0
- data/javascripts/pagy-module.js +0 -100
- data/javascripts/pagy.js +0 -4
- data/lib/pagy/extras/foundation.rb +0 -95
- data/lib/pagy/extras/items.rb +0 -64
- data/lib/pagy/extras/materialize.rb +0 -100
- data/lib/pagy/extras/semantic.rb +0 -94
- data/lib/pagy/extras/uikit.rb +0 -98
@@ -12,10 +12,6 @@ class Pagy # :nodoc:
|
|
12
12
|
module QueryUtils
|
13
13
|
module_function
|
14
14
|
|
15
|
-
def escape(str)
|
16
|
-
URI.encode_www_form_component(str)
|
17
|
-
end
|
18
|
-
|
19
15
|
def build_nested_query(value, prefix = nil)
|
20
16
|
case value
|
21
17
|
when Array
|
@@ -32,13 +28,17 @@ class Pagy # :nodoc:
|
|
32
28
|
"#{escape(prefix)}=#{escape(value)}"
|
33
29
|
end
|
34
30
|
end
|
31
|
+
|
32
|
+
def escape(str)
|
33
|
+
URI.encode_www_form_component(str)
|
34
|
+
end
|
35
35
|
end
|
36
36
|
# :nocov:
|
37
37
|
|
38
38
|
# Return the URL for the page. If there is no pagy.vars[:url]
|
39
39
|
# it works exactly as the regular #pagy_url_for, relying on the params method and Rack.
|
40
40
|
# If there is a defined pagy.vars[:url] variable it does not need the params method nor Rack.
|
41
|
-
def pagy_url_for(pagy, page,
|
41
|
+
def pagy_url_for(pagy, page, fragment: nil, **_)
|
42
42
|
return super unless pagy.vars[:url]
|
43
43
|
|
44
44
|
vars = pagy.vars
|
@@ -46,7 +46,7 @@ class Pagy # :nodoc:
|
|
46
46
|
pagy_set_query_params(page, vars, params)
|
47
47
|
params = vars[:params].(params) if vars[:params].is_a?(Proc)
|
48
48
|
query_string = "?#{QueryUtils.build_nested_query(params)}"
|
49
|
-
"#{vars[:url]}#{query_string}#{
|
49
|
+
"#{vars[:url]}#{query_string}#{fragment}"
|
50
50
|
end
|
51
51
|
end
|
52
52
|
UrlHelpers.prepend StandaloneExtra
|
data/lib/pagy/extras/trim.rb
CHANGED
@@ -6,9 +6,9 @@ class Pagy # :nodoc:
|
|
6
6
|
|
7
7
|
# Remove the page=1 param from the first page link
|
8
8
|
module TrimExtra
|
9
|
-
# Override the original
|
9
|
+
# Override the original pagy_anchor.
|
10
10
|
# Call the pagy_trim method for page 1 if the trim_extra is enabled
|
11
|
-
def pagy_anchor(pagy)
|
11
|
+
def pagy_anchor(pagy, **_)
|
12
12
|
a_proc = super
|
13
13
|
return a_proc unless pagy.vars[:trim_extra]
|
14
14
|
|
data/lib/pagy/frontend.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# See Pagy::Frontend API documentation: https://ddnexus.github.io/pagy/docs/api/frontend
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require_relative 'url_helpers'
|
5
4
|
require_relative 'i18n'
|
5
|
+
require_relative 'url_helpers'
|
6
6
|
|
7
7
|
class Pagy
|
8
8
|
# Used for search and replace, hardcoded also in the pagy.js file
|
@@ -14,26 +14,17 @@ class Pagy
|
|
14
14
|
module Frontend
|
15
15
|
include UrlHelpers
|
16
16
|
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
a.(item)
|
28
|
-
when String
|
29
|
-
%(<a role="link" aria-disabled="true" aria-current="page" class="current">#{pagy.label_for(item)}</a>)
|
30
|
-
when :gap
|
31
|
-
%(<a role="link" aria-disabled="true" class="gap">#{pagy_t('pagy.gap')}</a>)
|
32
|
-
else
|
33
|
-
raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
|
34
|
-
end
|
17
|
+
# Return a performance optimized lambda to generate the HTML anchor element (a tag)
|
18
|
+
# Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
|
19
|
+
def pagy_anchor(pagy, anchor_string: nil, **vars)
|
20
|
+
anchor_string &&= %( #{anchor_string})
|
21
|
+
left, right = %(<a#{anchor_string} href="#{pagy_url_for(pagy, PAGE_TOKEN, **vars)}").split(PAGE_TOKEN, 2)
|
22
|
+
# lambda used by all the helpers
|
23
|
+
lambda do |page, text = pagy.label_for(page), classes: nil, aria_label: nil|
|
24
|
+
classes = %( class="#{classes}") if classes
|
25
|
+
aria_label = %( aria-label="#{aria_label}") if aria_label
|
26
|
+
%(#{left}#{page}#{right}#{classes}#{aria_label}>#{text}</a>)
|
35
27
|
end
|
36
|
-
html << %(#{next_a(pagy, a)}</nav>)
|
37
28
|
end
|
38
29
|
|
39
30
|
# Return examples: "Displaying items 41-60 of 324 in total" or "Displaying Products 41-60 of 324 in total"
|
@@ -54,24 +45,32 @@ class Pagy
|
|
54
45
|
}</span>)
|
55
46
|
end
|
56
47
|
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
48
|
+
# Generic pagination: it returns the html with the series of links to the pages
|
49
|
+
def pagy_nav(pagy, id: nil, aria_label: nil, **vars)
|
50
|
+
id = %( id="#{id}") if id
|
51
|
+
a = pagy_anchor(pagy, **vars)
|
52
|
+
|
53
|
+
html = %(<nav#{id} class="pagy nav" #{nav_aria_label(pagy, aria_label:)}>#{
|
54
|
+
prev_a(pagy, a)})
|
55
|
+
pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
|
56
|
+
html << case item
|
57
|
+
when Integer
|
58
|
+
a.(item)
|
59
|
+
when String
|
60
|
+
%(<a role="link" aria-disabled="true" aria-current="page" class="current">#{pagy.label_for(item)}</a>)
|
61
|
+
when :gap
|
62
|
+
%(<a role="link" aria-disabled="true" class="gap">#{pagy_t('pagy.gap')}</a>)
|
63
|
+
else
|
64
|
+
raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
|
65
|
+
end
|
68
66
|
end
|
67
|
+
html << %(#{next_a(pagy, a)}</nav>)
|
69
68
|
end
|
70
69
|
|
71
70
|
# Similar to I18n.t: just ~18x faster using ~10x less memory
|
72
71
|
# (@pagy_locale explicitly initialized in order to avoid warning)
|
73
|
-
def pagy_t(key, opts
|
74
|
-
Pagy::I18n.translate(@pagy_locale ||= nil, key, opts)
|
72
|
+
def pagy_t(key, **opts)
|
73
|
+
Pagy::I18n.translate(@pagy_locale ||= nil, key, **opts)
|
75
74
|
end
|
76
75
|
|
77
76
|
private
|
data/lib/pagy/i18n.rb
CHANGED
@@ -118,7 +118,7 @@ class Pagy
|
|
118
118
|
|
119
119
|
# Stores the i18n DATA structure for each loaded locale
|
120
120
|
# default on the first locale DATA
|
121
|
-
DATA = Hash.new { |hash
|
121
|
+
DATA = Hash.new { |hash,| hash.first[1] }
|
122
122
|
|
123
123
|
private
|
124
124
|
|
@@ -154,7 +154,7 @@ class Pagy
|
|
154
154
|
end
|
155
155
|
|
156
156
|
# Translate and pluralize the key with the locale DATA
|
157
|
-
def translate(locale, key, opts
|
157
|
+
def translate(locale, key, **opts)
|
158
158
|
data, pluralize = DATA[locale]
|
159
159
|
translation = data[key] || (opts[:count] && data[key += ".#{pluralize.call(opts[:count])}"]) \
|
160
160
|
or return %([translation missing: "#{key}"])
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/keyset
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Pagy
|
5
|
+
class Keyset
|
6
|
+
# Keyset adapter for ActiveRecord
|
7
|
+
class ActiveRecord < Keyset
|
8
|
+
protected
|
9
|
+
|
10
|
+
# Get the keyset attributes of the record
|
11
|
+
def latest_from(latest_record) = latest_record.slice(*@keyset.keys)
|
12
|
+
|
13
|
+
# Extract the keyset from the set
|
14
|
+
def extract_keyset
|
15
|
+
@set.order_values.each_with_object({}) do |node, keyset|
|
16
|
+
keyset[node.value.name.to_sym] = node.direction
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Filter out the already retrieved records
|
21
|
+
def after_latest = @set.where(after_latest_query, **@latest)
|
22
|
+
|
23
|
+
# Append the missing keyset keys if the set is restricted by select
|
24
|
+
def apply_select
|
25
|
+
@set.select(*@keyset.keys)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set with selected columns?
|
29
|
+
def select? = !@set.select_values.empty?
|
30
|
+
|
31
|
+
# Typecast the latest attributes
|
32
|
+
def typecast_latest(latest)
|
33
|
+
@set.model.new(latest).slice(latest.keys)
|
34
|
+
.to_hash.transform_keys(&:to_sym)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/keyset
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Pagy
|
5
|
+
class Keyset
|
6
|
+
# Keyset adapter for sequel
|
7
|
+
class Sequel < Keyset
|
8
|
+
protected
|
9
|
+
|
10
|
+
# Get the keyset attributes of the latest record
|
11
|
+
def latest_from(latest_record) = latest_record.to_hash.slice(*@keyset.keys)
|
12
|
+
|
13
|
+
# Extract the keyset from the set
|
14
|
+
def extract_keyset
|
15
|
+
return {} unless @set.opts[:order]
|
16
|
+
|
17
|
+
@set.opts[:order].each_with_object({}) do |item, keyset|
|
18
|
+
case item
|
19
|
+
when Symbol
|
20
|
+
keyset[item] = :asc
|
21
|
+
when ::Sequel::SQL::OrderedExpression
|
22
|
+
keyset[item.expression] = item.descending ? :desc : :asc
|
23
|
+
else
|
24
|
+
raise TypeError, "#{item.class.inspect} is not a supported Sequel::SQL::OrderedExpression"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Filter out the already retrieved records
|
30
|
+
def after_latest = @set.where(::Sequel.lit(after_latest_query, **@latest))
|
31
|
+
|
32
|
+
# Append the missing keyset keys if the set is restricted by select
|
33
|
+
def apply_select
|
34
|
+
selected = @set.opts[:select]
|
35
|
+
@set.select_append(*@keyset.keys.reject { |c| selected.include?(c) })
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set with selected columns?
|
39
|
+
def select? = !@set.opts[:select].nil?
|
40
|
+
|
41
|
+
# Typecast the latest attributes
|
42
|
+
def typecast_latest(latest)
|
43
|
+
model = @set.opts[:model]
|
44
|
+
model.unrestrict_primary_key if (restricted_pk = model.restrict_primary_key?)
|
45
|
+
latest = model.new(latest).to_hash.slice(*latest.keys.map(&:to_sym))
|
46
|
+
model.restrict_primary_key if restricted_pk
|
47
|
+
latest
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/pagy/keyset.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/keyset
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require_relative 'b64'
|
6
|
+
require_relative 'shared_methods'
|
7
|
+
|
8
|
+
class Pagy
|
9
|
+
# Implement wicked-fast keyset pagination for big data
|
10
|
+
class Keyset
|
11
|
+
class TypeError < ::TypeError; end
|
12
|
+
|
13
|
+
include SharedMethods
|
14
|
+
|
15
|
+
# Pick the right adapter for the set
|
16
|
+
def self.new(set, **vars)
|
17
|
+
if self == Pagy::Keyset
|
18
|
+
if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
|
19
|
+
ActiveRecord
|
20
|
+
elsif defined?(::Sequel) && set.is_a?(::Sequel::Dataset)
|
21
|
+
Sequel
|
22
|
+
else
|
23
|
+
raise TypeError, "expected set to be an instance of ActiveRecord::Relation or Sequel::Dataset; got #{set.class}"
|
24
|
+
end.new(set, **vars)
|
25
|
+
else
|
26
|
+
allocate.tap { |instance| instance.send(:initialize, set, **vars) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :latest # Other readers from SharedMethods
|
31
|
+
|
32
|
+
def initialize(set, **vars)
|
33
|
+
default = DEFAULT.slice(:limit, :page_param, # from pagy
|
34
|
+
:headers, # from headers extra
|
35
|
+
:jsonapi, # from jsonapi extra
|
36
|
+
:limit_param, :limit_max, :limit_extra) # from limit_extra
|
37
|
+
assign_vars({ **default, page: nil }, vars)
|
38
|
+
assign_limit
|
39
|
+
@set = set
|
40
|
+
@page = @vars[:page]
|
41
|
+
@keyset = extract_keyset
|
42
|
+
raise InternalError, 'the set must be ordered' if @keyset.empty?
|
43
|
+
return unless @page
|
44
|
+
|
45
|
+
latest = JSON.parse(B64.urlsafe_decode(@page)).transform_keys(&:to_sym)
|
46
|
+
@latest = @vars[:typecast_latest]&.(latest) || typecast_latest(latest)
|
47
|
+
raise InternalError, 'page and keyset are not consistent' \
|
48
|
+
unless @latest.keys == @keyset.keys
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the next page
|
52
|
+
def next
|
53
|
+
records
|
54
|
+
return unless @more
|
55
|
+
|
56
|
+
@next ||= B64.urlsafe_encode(latest_from(@records.last).to_json)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Retrieve the array of records for the current page
|
60
|
+
def records
|
61
|
+
@records ||= begin
|
62
|
+
@set = apply_select if select?
|
63
|
+
@set = @vars[:after_latest]&.(@set, @latest) || after_latest if @latest
|
64
|
+
records = @set.limit(@limit + 1).to_a
|
65
|
+
@more = records.size > @limit && !records.pop.nil?
|
66
|
+
records
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
# Prepare the literal query to filter out the already fetched records
|
73
|
+
def after_latest_query
|
74
|
+
operator = { asc: '>', desc: '<' }
|
75
|
+
directions = @keyset.values
|
76
|
+
if @vars[:tuple_comparison] && (directions.all?(:asc) || directions.all?(:desc))
|
77
|
+
columns = @keyset.keys
|
78
|
+
placeholders = columns.map { |column| ":#{column}" }.join(', ')
|
79
|
+
"( #{columns.join(', ')} ) #{operator[directions.first]} ( #{placeholders} )"
|
80
|
+
else
|
81
|
+
keyset = @keyset.to_a
|
82
|
+
where = []
|
83
|
+
until keyset.empty?
|
84
|
+
last_column, last_direction = keyset.pop
|
85
|
+
query = +'( '
|
86
|
+
query << (keyset.map { |column, _d| "#{column} = :#{column}" } \
|
87
|
+
<< "#{last_column} #{operator[last_direction]} :#{last_column}").join(' AND ')
|
88
|
+
query << ' )'
|
89
|
+
where << query
|
90
|
+
end
|
91
|
+
where.join(' OR ')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
require_relative 'keyset/active_record'
|
98
|
+
require_relative 'keyset/sequel'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Pagy
|
4
|
+
# Shared with Keyset
|
5
|
+
module SharedMethods
|
6
|
+
attr_reader :page, :limit, :vars
|
7
|
+
|
8
|
+
# Validates and assign the passed vars: var must be present and value.to_i must be >= to min
|
9
|
+
def assign_and_check(name_min)
|
10
|
+
name_min.each do |name, min|
|
11
|
+
raise VariableError.new(self, name, ">= #{min}", @vars[name]) \
|
12
|
+
unless @vars[name]&.respond_to?(:to_i) && \
|
13
|
+
instance_variable_set(:"@#{name}", @vars[name].to_i) >= min
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Assign @limit (overridden by the gearbox extra)
|
18
|
+
def assign_limit
|
19
|
+
assign_and_check(limit: 1)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Assign @vars
|
23
|
+
def assign_vars(default, vars)
|
24
|
+
@vars = { **default, **vars.delete_if { |k, v| default.key?(k) && (v.nil? || v == '') } }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/pagy/url_helpers.rb
CHANGED
@@ -6,21 +6,21 @@ class Pagy
|
|
6
6
|
# Return the URL for the page, relying on the params method and Rack by default.
|
7
7
|
# It supports all Rack-based frameworks (Sinatra, Padrino, Rails, ...).
|
8
8
|
# For non-rack environments you can use the standalone extra
|
9
|
-
def pagy_url_for(pagy, page, absolute: false, **_)
|
9
|
+
def pagy_url_for(pagy, page, absolute: false, fragment: nil, **_)
|
10
10
|
vars = pagy.vars
|
11
11
|
query_params = request.GET.clone
|
12
12
|
query_params.merge!(vars[:params].transform_keys(&:to_s)) if vars[:params].is_a?(Hash)
|
13
13
|
pagy_set_query_params(page, vars, query_params)
|
14
14
|
query_params = vars[:params].(query_params) if vars[:params].is_a?(Proc)
|
15
15
|
query_string = "?#{Rack::Utils.build_nested_query(query_params)}"
|
16
|
-
"#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{
|
16
|
+
"#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{fragment}"
|
17
17
|
end
|
18
18
|
|
19
|
-
# Add the page and
|
19
|
+
# Add the page and limit params
|
20
20
|
# Overridable by the jsonapi extra
|
21
21
|
def pagy_set_query_params(page, vars, query_params)
|
22
22
|
query_params[vars[:page_param].to_s] = page
|
23
|
-
query_params[vars[:
|
23
|
+
query_params[vars[:limit_param].to_s] = vars[:limit] if vars[:limit_extra]
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
data/lib/pagy.rb
CHANGED
@@ -2,48 +2,77 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'pathname'
|
5
|
+
require_relative 'pagy/shared_methods'
|
5
6
|
|
6
|
-
#
|
7
|
+
# Top superclass: it should define only what's common to all the subclasses
|
7
8
|
class Pagy
|
8
|
-
VERSION = '
|
9
|
+
VERSION = '9.0.5'
|
10
|
+
|
11
|
+
# Core default: constant for easy access, but mutable for customizable defaults
|
12
|
+
DEFAULT = { count_args: [:all], # rubocop:disable Style/MutableConstant
|
13
|
+
ends: true,
|
14
|
+
limit: 20,
|
15
|
+
outset: 0,
|
16
|
+
page: 1,
|
17
|
+
page_param: :page,
|
18
|
+
size: 7 } # AR friendly
|
9
19
|
|
10
20
|
# Gem root pathname to get the path of Pagy files stylesheets, javascripts, apps, locales, etc.
|
11
21
|
def self.root
|
12
22
|
@root ||= Pathname.new(__dir__).parent.freeze
|
13
23
|
end
|
14
24
|
|
15
|
-
|
16
|
-
DEFAULT = { page: 1, # rubocop:disable Style/MutableConstant
|
17
|
-
items: 20,
|
18
|
-
outset: 0,
|
19
|
-
size: 7,
|
20
|
-
ends: true,
|
21
|
-
count_args: [:all], # AR friendly
|
22
|
-
page_param: :page }
|
25
|
+
include SharedMethods
|
23
26
|
|
24
|
-
attr_reader :count, :
|
27
|
+
attr_reader :count, :from, :in, :last, :next, :offset, :prev, :to
|
25
28
|
alias pages last
|
26
29
|
|
27
30
|
# Merge and validate the options, do some simple arithmetic and set the instance variables
|
28
|
-
def initialize(vars)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
def initialize(**vars)
|
32
|
+
assign_vars(DEFAULT, vars)
|
33
|
+
assign_and_check(count: 0, page: 1, outset: 0)
|
34
|
+
assign_limit
|
35
|
+
assign_offset
|
36
|
+
assign_last
|
37
|
+
check_overflow
|
36
38
|
@from = [@offset - @outset + 1, @count].min
|
37
|
-
@to = [@offset - @outset + @
|
39
|
+
@to = [@offset - @outset + @limit, @count].min
|
38
40
|
@in = [@to - @from + 1, @count].min
|
41
|
+
assign_prev_and_next
|
42
|
+
end
|
43
|
+
|
44
|
+
# Setup @last (overridden by the gearbox extra)
|
45
|
+
def assign_last
|
46
|
+
@last = [(@count.to_f / @limit).ceil, 1].max
|
47
|
+
@last = @vars[:max_pages] if @vars[:max_pages] && @last > vars[:max_pages]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Assign @offset (overridden by the gearbox extra)
|
51
|
+
def assign_offset
|
52
|
+
@offset = (@limit * (@page - 1)) + @outset # may be already set from gear_box
|
53
|
+
end
|
54
|
+
|
55
|
+
# Assign @prev and @next
|
56
|
+
def assign_prev_and_next
|
39
57
|
@prev = (@page - 1 unless @page == 1)
|
40
58
|
@next = @page == @last ? (1 if @vars[:cycle]) : @page + 1
|
41
59
|
end
|
42
60
|
|
43
|
-
#
|
61
|
+
# Checks the @page <= @last
|
62
|
+
def check_overflow
|
63
|
+
raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
|
64
|
+
end
|
65
|
+
|
66
|
+
# Label for the current page. Allow the customization of the output (overridden by the calendar extra)
|
67
|
+
def label = @page.to_s
|
68
|
+
|
69
|
+
# Label for any page. Allow the customization of the output (overridden by the calendar extra)
|
70
|
+
def label_for(page) = page.to_s
|
71
|
+
|
72
|
+
# Return the array of page numbers and :gap e.g. [1, :gap, 8, "9", 10, :gap, 36]
|
44
73
|
def series(size: @vars[:size], **_)
|
45
74
|
raise VariableError.new(self, :size, 'to be an Integer >= 0', size) \
|
46
|
-
|
75
|
+
unless size.is_a?(Integer) && size >= 0
|
47
76
|
return [] if size.zero?
|
48
77
|
|
49
78
|
[].tap do |series|
|
@@ -70,51 +99,8 @@ class Pagy
|
|
70
99
|
series[series.index(@page)] = @page.to_s
|
71
100
|
end
|
72
101
|
end
|
73
|
-
|
74
|
-
# Label for any page. Allow the customization of the output (overridden by the calendar extra)
|
75
|
-
def label_for(page)
|
76
|
-
page.to_s
|
77
|
-
end
|
78
|
-
|
79
|
-
# Label for the current page. 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]&.respond_to?(:to_i) && instance_variable_set(:"@#{name}", @vars[name].to_i) >= min
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Setup @items (overridden by the gearbox extra)
|
100
|
-
def setup_items_var
|
101
|
-
setup_vars(items: 1)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Setup @offset (overridden by the gearbox extra)
|
105
|
-
def setup_offset_var
|
106
|
-
@offset = (@items * (@page - 1)) + @outset # may be already set from gear_box
|
107
|
-
end
|
108
|
-
|
109
|
-
# Setup @last (overridden by the gearbox extra)
|
110
|
-
def setup_last_var
|
111
|
-
@last = [(@count.to_f / @items).ceil, 1].max
|
112
|
-
@last = vars[:max_pages] if vars[:max_pages] && @last > vars[:max_pages]
|
113
|
-
end
|
114
|
-
alias setup_pages_var setup_last_var
|
115
102
|
end
|
116
103
|
|
117
|
-
require_relative 'pagy/extras/size' # will be opt in in v9.0
|
118
104
|
require_relative 'pagy/backend'
|
119
105
|
require_relative 'pagy/frontend'
|
120
106
|
require_relative 'pagy/exceptions'
|
data/locales/ar.yml
CHANGED
@@ -26,4 +26,4 @@ ar:
|
|
26
26
|
single_page: "عرض %{count} %{item_name}"
|
27
27
|
multiple_pages: "عرض %{item_name} %{from}-%{to} من اجمالي %{count}"
|
28
28
|
combo_nav_js: "الصفحة %{page_input} من %{pages}"
|
29
|
-
|
29
|
+
limit_selector_js: "عرض %{limit_input} %{item_name} لكل صفحة"
|
data/locales/be.yml
CHANGED
@@ -22,4 +22,4 @@ be:
|
|
22
22
|
single_page: "%{count} %{item_name}"
|
23
23
|
multiple_pages: "Усяго %{count} %{item_name}, паказаны з %{from} па %{to}"
|
24
24
|
combo_nav_js: "Старонка %{page_input} з %{pages}"
|
25
|
-
|
25
|
+
limit_selector_js: "Паказаць %{limit_input} %{item_name} на старонцы"
|
data/locales/bg.yml
CHANGED
@@ -18,4 +18,4 @@ bg:
|
|
18
18
|
single_page: "Показани са %{count} %{item_name}"
|
19
19
|
multiple_pages: "Показани са %{item_name} %{from}-%{to} от %{count} общо"
|
20
20
|
combo_nav_js: "Страница %{page_input} от %{pages}"
|
21
|
-
|
21
|
+
limit_selector_js: "Покажи %{limit_input} %{item_name} на страница"
|
data/locales/bs.yml
CHANGED
@@ -22,4 +22,4 @@ bs:
|
|
22
22
|
single_page: "Prikazuje se %{count} %{item_name}"
|
23
23
|
multiple_pages: "Prikaz %{item_name} %{from}-%{to} od %{count} ukupno"
|
24
24
|
combo_nav_js: "Stranica %{page_input} od %{pages}"
|
25
|
-
|
25
|
+
limit_selector_js: "Prikaži %{limit_input} %{item_name} po stranici"
|
data/locales/ca.yml
CHANGED
@@ -18,4 +18,4 @@ ca:
|
|
18
18
|
single_page: "Mostrant %{count} %{item_name}"
|
19
19
|
multiple_pages: "Mostrant %{item_name} %{from}-%{to} de %{count} en total"
|
20
20
|
combo_nav_js: "Pàgina %{page_input} de %{pages}"
|
21
|
-
|
21
|
+
limit_selector_js: "Mostra %{limit_input} %{item_name} per pàgina"
|
data/locales/ckb.yml
CHANGED
@@ -15,4 +15,4 @@ ckb:
|
|
15
15
|
single_page: "پیشاندانی %{count} %{item_name}"
|
16
16
|
multiple_pages: "پشاندانی %{item_name}ی %{from}-%{to} لە کۆی %{count} بە گشتی"
|
17
17
|
combo_nav_js: "پەڕی %{page_input} لە %{pages}"
|
18
|
-
|
18
|
+
limit_selector_js: "نیشاندانی %{limit_input} %{item_name} لە هەر پەڕێک"
|
data/locales/cs.yml
CHANGED
@@ -20,4 +20,4 @@ cs:
|
|
20
20
|
single_page: "Zobrazeno %{count} %{item_name}"
|
21
21
|
multiple_pages: "Zobrazeny položky %{from}-%{to} z %{count} celkem"
|
22
22
|
combo_nav_js: "Strana %{page_input} z %{pages}"
|
23
|
-
|
23
|
+
limit_selector_js: "Zobrazit %{limit_input} %{item_name} na stránce"
|
data/locales/da.yml
CHANGED
@@ -18,4 +18,4 @@ da:
|
|
18
18
|
single_page: "Viser %{count} %{item_name}"
|
19
19
|
multiple_pages: "Viser %{item_name} %{from}-%{to} af %{count} totalt"
|
20
20
|
combo_nav_js: "Side %{page_input} af %{pages}"
|
21
|
-
|
21
|
+
limit_selector_js: "Antal %{limit_input} %{item_name} per side"
|
data/locales/de.yml
CHANGED
@@ -18,4 +18,4 @@ de:
|
|
18
18
|
single_page: "Zeige %{count} %{item_name}"
|
19
19
|
multiple_pages: "Zeige %{item_name} %{from}-%{to} von %{count} gesamt"
|
20
20
|
combo_nav_js: "Seite %{page_input} von %{pages}"
|
21
|
-
|
21
|
+
limit_selector_js: "Zeige %{limit_input} %{item_name} pro Seite"
|
data/locales/en.yml
CHANGED
@@ -18,4 +18,4 @@ en:
|
|
18
18
|
single_page: "Displaying %{count} %{item_name}"
|
19
19
|
multiple_pages: "Displaying %{item_name} %{from}-%{to} of %{count} in total"
|
20
20
|
combo_nav_js: "Page %{page_input} of %{pages}"
|
21
|
-
|
21
|
+
limit_selector_js: "Show %{limit_input} %{item_name} per page"
|
data/locales/es.yml
CHANGED
@@ -18,4 +18,4 @@ es:
|
|
18
18
|
single_page: "Mostrando %{count} %{item_name}"
|
19
19
|
multiple_pages: "Mostrando %{item_name} %{from}-%{to} de %{count} en total"
|
20
20
|
combo_nav_js: "Página %{page_input} de %{pages}"
|
21
|
-
|
21
|
+
limit_selector_js: "Mostrar %{limit_input} %{item_name} por página"
|
data/locales/fr.yml
CHANGED
@@ -18,4 +18,4 @@ fr:
|
|
18
18
|
single_page: "Affichage de %{count} %{item_name}"
|
19
19
|
multiple_pages: "Affichage des %{item_name} %{from} à %{to} sur %{count} au total"
|
20
20
|
combo_nav_js: "Page %{page_input} sur %{pages}"
|
21
|
-
|
21
|
+
limit_selector_js: "Afficher %{limit_input} %{item_name} par page"
|
data/locales/hr.yml
CHANGED
@@ -22,4 +22,4 @@ hr:
|
|
22
22
|
single_page: "Prikazuje se %{count} %{item_name}"
|
23
23
|
multiple_pages: "Prikaz %{item_name} %{from}-%{to} od %{count} ukupno"
|
24
24
|
combo_nav_js: "Stranica %{page_input} od %{pages}"
|
25
|
-
|
25
|
+
limit_selector_js: "Prikaži %{limit_input} %{item_name} po stranici"
|