pagy 3.8.2 → 9.4.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 +737 -0
- data/apps/demo.ru +449 -0
- data/apps/index.rb +7 -0
- data/apps/keyset_ar.ru +228 -0
- data/apps/keyset_s.ru +220 -0
- data/apps/rails.ru +217 -0
- data/apps/repro.ru +182 -0
- data/bin/pagy +98 -0
- data/config/pagy.rb +220 -0
- data/javascripts/pagy.d.ts +5 -0
- data/javascripts/pagy.min.js +4 -0
- data/javascripts/pagy.min.js.map +10 -0
- data/javascripts/pagy.mjs +100 -0
- data/lib/optimist.rb +1022 -0
- data/lib/pagy/b64.rb +33 -0
- data/lib/pagy/backend.rb +30 -19
- data/lib/pagy/calendar/day.rb +41 -0
- data/lib/pagy/calendar/month.rb +42 -0
- data/lib/pagy/calendar/quarter.rb +49 -0
- data/lib/pagy/calendar/unit.rb +103 -0
- data/lib/pagy/calendar/week.rb +39 -0
- data/lib/pagy/calendar/year.rb +35 -0
- data/lib/pagy/calendar.rb +84 -0
- data/lib/pagy/console.rb +23 -0
- data/lib/pagy/countless.rb +27 -22
- data/lib/pagy/exceptions.rb +16 -13
- data/lib/pagy/extras/arel.rb +11 -14
- data/lib/pagy/extras/array.rb +12 -16
- data/lib/pagy/extras/bootstrap.rb +83 -41
- data/lib/pagy/extras/bulma.rb +79 -46
- data/lib/pagy/extras/calendar.rb +79 -0
- data/lib/pagy/extras/countless.rb +20 -25
- data/lib/pagy/extras/elasticsearch_rails.rb +59 -38
- data/lib/pagy/extras/gearbox.rb +55 -0
- data/lib/pagy/extras/headers.rb +38 -23
- data/lib/pagy/extras/i18n.rb +19 -18
- data/lib/pagy/extras/js_tools.rb +70 -0
- data/lib/pagy/extras/jsonapi.rb +88 -0
- data/lib/pagy/extras/keyset.rb +30 -0
- data/lib/pagy/extras/limit.rb +63 -0
- data/lib/pagy/extras/meilisearch.rb +57 -0
- data/lib/pagy/extras/metadata.rb +32 -27
- data/lib/pagy/extras/overflow.rb +61 -53
- data/lib/pagy/extras/pagy.rb +82 -0
- data/lib/pagy/extras/searchkick.rb +52 -41
- data/lib/pagy/extras/size.rb +40 -0
- data/lib/pagy/extras/standalone.rb +60 -0
- data/lib/pagy/extras/trim.rb +19 -13
- data/lib/pagy/frontend.rb +76 -51
- data/lib/pagy/i18n.rb +167 -0
- data/lib/pagy/keyset/active_record.rb +44 -0
- data/lib/pagy/keyset/sequel.rb +57 -0
- data/lib/pagy/keyset.rb +118 -0
- data/lib/pagy/shared_methods.rb +26 -0
- data/lib/pagy/url_helpers.rb +26 -0
- data/lib/pagy.rb +91 -37
- data/locales/ar.yml +29 -0
- data/locales/be.yml +25 -0
- data/locales/bg.yml +21 -0
- data/locales/bs.yml +25 -0
- data/locales/ca.yml +21 -0
- data/locales/ckb.yml +18 -0
- data/locales/cs.yml +23 -0
- data/locales/da.yml +21 -0
- data/locales/de.yml +21 -0
- data/locales/dz.yml +17 -0
- data/locales/en.yml +21 -0
- data/{lib/locales → locales}/es.yml +11 -12
- data/locales/fr.yml +21 -0
- data/locales/hr.yml +25 -0
- data/locales/id.yml +19 -0
- data/locales/it.yml +21 -0
- data/locales/ja.yml +19 -0
- data/locales/km.yml +19 -0
- data/locales/ko.yml +17 -0
- data/locales/nb.yml +21 -0
- data/locales/nl.yml +21 -0
- data/locales/nn.yml +21 -0
- data/locales/pl.yml +25 -0
- data/{lib/locales → locales}/pt-BR.yml +11 -12
- data/locales/pt.yml +21 -0
- data/locales/ru.yml +25 -0
- data/locales/sk.yml +23 -0
- data/locales/sr.yml +25 -0
- data/locales/sv-SE.yml +21 -0
- data/locales/sv.yml +21 -0
- data/locales/sw.yml +25 -0
- data/locales/ta.yml +21 -0
- data/locales/tr.yml +19 -0
- data/locales/uk.yml +25 -0
- data/locales/vi.yml +17 -0
- data/locales/zh-CN.yml +17 -0
- data/locales/zh-HK.yml +17 -0
- data/locales/zh-TW.yml +17 -0
- data/stylesheets/pagy.css +46 -0
- data/stylesheets/pagy.scss +48 -0
- data/stylesheets/pagy.tailwind.css +21 -0
- metadata +95 -67
- data/lib/config/pagy.rb +0 -170
- data/lib/javascripts/pagy.js +0 -106
- data/lib/locales/README.md +0 -35
- data/lib/locales/bg.yml +0 -22
- data/lib/locales/ca.yml +0 -22
- data/lib/locales/da.yml +0 -22
- data/lib/locales/de.yml +0 -22
- data/lib/locales/en.yml +0 -22
- data/lib/locales/fr.yml +0 -22
- data/lib/locales/id.yml +0 -20
- data/lib/locales/it.yml +0 -22
- data/lib/locales/ja.yml +0 -20
- data/lib/locales/km.yml +0 -19
- data/lib/locales/ko.yml +0 -20
- data/lib/locales/nb.yml +0 -22
- data/lib/locales/nl.yml +0 -22
- data/lib/locales/pl.yml +0 -24
- data/lib/locales/ru.yml +0 -24
- data/lib/locales/sv-SE.yml +0 -23
- data/lib/locales/sv.yml +0 -23
- data/lib/locales/tr.yml +0 -20
- data/lib/locales/utils/i18n.rb +0 -25
- data/lib/locales/utils/loader.rb +0 -34
- data/lib/locales/utils/p11n.rb +0 -80
- data/lib/locales/zh-CN.yml +0 -20
- data/lib/locales/zh-HK.yml +0 -20
- data/lib/locales/zh-TW.yml +0 -20
- data/lib/pagy/extras/foundation.rb +0 -57
- data/lib/pagy/extras/items.rb +0 -65
- data/lib/pagy/extras/materialize.rb +0 -59
- data/lib/pagy/extras/navs.rb +0 -38
- data/lib/pagy/extras/pagy_search.rb +0 -18
- data/lib/pagy/extras/semantic.rb +0 -55
- data/lib/pagy/extras/shared.rb +0 -53
- data/lib/pagy/extras/support.rb +0 -29
- data/lib/pagy/extras/uikit.rb +0 -62
- data/lib/templates/bootstrap_nav.html.erb +0 -24
- data/lib/templates/bootstrap_nav.html.haml +0 -34
- data/lib/templates/bootstrap_nav.html.slim +0 -34
- data/lib/templates/bulma_nav.html.erb +0 -24
- data/lib/templates/bulma_nav.html.haml +0 -32
- data/lib/templates/bulma_nav.html.slim +0 -32
- data/lib/templates/foundation_nav.html.erb +0 -24
- data/lib/templates/foundation_nav.html.haml +0 -34
- data/lib/templates/foundation_nav.html.slim +0 -34
- data/lib/templates/nav.html.erb +0 -22
- data/lib/templates/nav.html.haml +0 -30
- data/lib/templates/nav.html.slim +0 -29
- data/lib/templates/uikit_nav.html.erb +0 -15
- data/lib/templates/uikit_nav.html.haml +0 -28
- data/lib/templates/uikit_nav.html.slim +0 -28
- data/pagy.gemspec +0 -16
data/lib/pagy/i18n.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
# See Pagy::I18n API documentation https://ddnexus.github.io/pagy/docs/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['be'] = RULE[:east_slavic]
|
98
|
+
hash['bs'] = RULE[:east_slavic]
|
99
|
+
hash['ckb'] = RULE[:other]
|
100
|
+
hash['cs'] = RULE[:west_slavic]
|
101
|
+
hash['id'] = RULE[:other]
|
102
|
+
hash['fr'] = RULE[:one_upto_two_other]
|
103
|
+
hash['hr'] = RULE[:east_slavic]
|
104
|
+
hash['ja'] = RULE[:other]
|
105
|
+
hash['km'] = RULE[:other]
|
106
|
+
hash['ko'] = RULE[:other]
|
107
|
+
hash['pl'] = RULE[:polish]
|
108
|
+
hash['ru'] = RULE[:east_slavic]
|
109
|
+
hash['sk'] = RULE[:west_slavic]
|
110
|
+
hash['sr'] = RULE[:east_slavic]
|
111
|
+
hash['tr'] = RULE[:other]
|
112
|
+
hash['uk'] = RULE[:east_slavic]
|
113
|
+
hash['vi'] = RULE[:other]
|
114
|
+
hash['zh-CN'] = RULE[:other]
|
115
|
+
hash['zh-HK'] = RULE[:other]
|
116
|
+
hash['zh-TW'] = RULE[:other]
|
117
|
+
hash['dz'] = RULE[:other]
|
118
|
+
end.freeze
|
119
|
+
end
|
120
|
+
|
121
|
+
# Stores the i18n DATA structure for each loaded locale
|
122
|
+
# default on the first locale DATA
|
123
|
+
DATA = Hash.new { |hash,| hash.first[1] }
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# Create a flat hash with dotted notation keys
|
128
|
+
def flatten(initial, prefix = '')
|
129
|
+
initial.each.reduce({}) do |hash, (key, value)|
|
130
|
+
hash.merge!(value.is_a?(Hash) ? flatten(value, "#{prefix}#{key}.") : { "#{prefix}#{key}" => value })
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Build the DATA hash out of the passed locales
|
135
|
+
def build(*locales)
|
136
|
+
locales.each do |locale|
|
137
|
+
locale[:filepath] ||= Pagy.root.join('locales', "#{locale[:locale]}.yml")
|
138
|
+
locale[:pluralize] ||= P11n::LOCALE[locale[:locale]]
|
139
|
+
dictionary = YAML.safe_load(File.read(locale[:filepath], encoding: 'UTF-8'))
|
140
|
+
raise I18nError, %(expected :locale "#{locale[:locale]}" not found in :filepath "#{locale[:filepath].inspect}") \
|
141
|
+
unless dictionary.key?(locale[:locale])
|
142
|
+
|
143
|
+
DATA[locale[:locale]] = [flatten(dictionary[locale[:locale]]), locale[:pluralize]]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
# Build the default at require time
|
147
|
+
build(locale: 'en')
|
148
|
+
|
149
|
+
public
|
150
|
+
|
151
|
+
# Public method to configure the locales: overrides the default, build the DATA and freezes it
|
152
|
+
def load(*locales)
|
153
|
+
DATA.clear
|
154
|
+
build(*locales)
|
155
|
+
DATA.freeze
|
156
|
+
end
|
157
|
+
|
158
|
+
# Translate and pluralize the key with the locale DATA
|
159
|
+
def translate(locale, key, **opts)
|
160
|
+
data, pluralize = DATA[locale]
|
161
|
+
translation = data[key] || (opts[:count] && data[key += ".#{pluralize.call(opts[:count])}"]) \
|
162
|
+
or return %([translation missing: "#{key}"])
|
163
|
+
translation.gsub(/%{[^}]+?}/) { |match| opts[:"#{match[2..-2]}"] || match }
|
164
|
+
end
|
165
|
+
alias t translate
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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 from the record
|
11
|
+
def keyset_attributes_from(record) = record.slice(*@keyset.keys)
|
12
|
+
|
13
|
+
# Get the hash of quoted keyset identifiers
|
14
|
+
def quoted_identifiers(table)
|
15
|
+
connection = @set.connection
|
16
|
+
@keyset.to_h { |column| [column, "#{connection.quote_table_name(table)}.#{connection.quote_column_name(column)}"] }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Extract the keyset from the set
|
20
|
+
def extract_keyset
|
21
|
+
@set.order_values.each_with_object({}) do |node, keyset|
|
22
|
+
keyset[node.value.name.to_sym] = node.direction
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Filter the newest records
|
27
|
+
def filter_newest = @set.where(filter_newest_query, **@latest)
|
28
|
+
|
29
|
+
# Append the missing keyset keys if the set is restricted by select
|
30
|
+
def apply_select
|
31
|
+
@set.select(*@keyset.keys)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set with selected columns?
|
35
|
+
def select? = !@set.select_values.empty?
|
36
|
+
|
37
|
+
# Typecast the latest attributes
|
38
|
+
def typecast_latest(latest)
|
39
|
+
@set.model.new(latest).slice(latest.keys)
|
40
|
+
.to_hash.transform_keys(&:to_sym)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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 from the record
|
11
|
+
def keyset_attributes_from(record) = record.to_hash.slice(*@keyset.keys)
|
12
|
+
|
13
|
+
# Get the hash of quoted keyset identifiersAdd commentMore actions
|
14
|
+
def quoted_identifiers(table)
|
15
|
+
db = @set.db
|
16
|
+
@keyset.to_h { |column| [column, "#{db.quote_identifier(table)}.#{db.quote_identifier(column)}"] }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Extract the keyset from the set
|
20
|
+
def extract_keyset
|
21
|
+
return {} unless @set.opts[:order]
|
22
|
+
|
23
|
+
@set.opts[:order].each_with_object({}) do |item, keyset|
|
24
|
+
case item
|
25
|
+
when Symbol
|
26
|
+
keyset[item] = :asc
|
27
|
+
when ::Sequel::SQL::OrderedExpression
|
28
|
+
keyset[item.expression] = item.descending ? :desc : :asc
|
29
|
+
else
|
30
|
+
raise TypeError, "#{item.class.inspect} is not a supported Sequel::SQL::OrderedExpression"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Filter the newest records
|
36
|
+
def filter_newest = @set.where(::Sequel.lit(filter_newest_query, **@latest))
|
37
|
+
|
38
|
+
# Append the missing keyset keys if the set is restricted by select
|
39
|
+
def apply_select
|
40
|
+
selected = @set.opts[:select]
|
41
|
+
@set.select_append(*@keyset.keys.reject { |c| selected.include?(c) })
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set with selected columns?
|
45
|
+
def select? = !@set.opts[:select].nil?
|
46
|
+
|
47
|
+
# Typecast the latest attributes
|
48
|
+
def typecast_latest(latest)
|
49
|
+
model = @set.opts[:model]
|
50
|
+
model.unrestrict_primary_key if (restricted_pk = model.restrict_primary_key?)
|
51
|
+
latest = model.new(latest).to_hash.slice(*latest.keys.map(&:to_sym))
|
52
|
+
model.restrict_primary_key if restricted_pk
|
53
|
+
latest
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/pagy/keyset.rb
ADDED
@@ -0,0 +1,118 @@
|
|
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 = 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 ||= begin
|
57
|
+
hash = keyset_attributes_from(@records.last)
|
58
|
+
json = @vars[:jsonify_keyset_attributes]&.(hash) || hash.to_json
|
59
|
+
B64.urlsafe_encode(json)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Fetch the array of records for the current page
|
64
|
+
def records
|
65
|
+
@records ||= begin
|
66
|
+
@set = apply_select if select?
|
67
|
+
if @latest
|
68
|
+
# :nocov:
|
69
|
+
@set = @vars[:after_latest]&.(@set, @latest) || # deprecated
|
70
|
+
# :nocov:
|
71
|
+
@vars[:filter_newest]&.(@set, @latest, @keyset) ||
|
72
|
+
filter_newest
|
73
|
+
end
|
74
|
+
records = @set.limit(@limit + 1).to_a
|
75
|
+
@more = records.size > @limit && !records.pop.nil?
|
76
|
+
records
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
# Prepare the literal query string (complete with the placeholders for value interpolation)
|
83
|
+
# used to filter the newest records.
|
84
|
+
# For example:
|
85
|
+
# With a set like Pet.order(animal: :asc, name: :desc, id: :asc) it returns the following string:
|
86
|
+
# ( "pets"."animal" = :animal AND "pets"."name" = :name AND "pets"."id" > :id ) OR
|
87
|
+
# ( "pets"."animal" = :animal AND "pets"."name" < :name ) OR
|
88
|
+
# ( "pets"."animal" > :animal )
|
89
|
+
# When :tuple_comparison is enabled, and if the order is all :asc or all :desc,
|
90
|
+
# with a set like Pet.order(:animal, :name, :id) it returns the following string:
|
91
|
+
# ( "pets"."animal", "pets"."name", "pets"."id" ) > ( :animal, :name, :id )
|
92
|
+
def filter_newest_query
|
93
|
+
operator = { asc: '>', desc: '<' }
|
94
|
+
directions = @keyset.values
|
95
|
+
name = quoted_identifiers(@set.model.table_name)
|
96
|
+
|
97
|
+
if @vars[:tuple_comparison] && (directions.all?(:asc) || directions.all?(:desc))
|
98
|
+
placeholders = @keyset.keys.map { |column| ":#{column}" }.join(', ')
|
99
|
+
"( #{name.values.join(', ')} ) #{operator[directions.first]} ( #{placeholders} )"
|
100
|
+
else
|
101
|
+
keyset = @keyset.to_a
|
102
|
+
where = []
|
103
|
+
until keyset.empty?
|
104
|
+
last_column, last_direction = keyset.pop
|
105
|
+
query = +'( '
|
106
|
+
query << (keyset.map { |column, _d| "#{name[column]} = :#{column}" } \
|
107
|
+
<< "#{name[last_column]} #{operator[last_direction]} :#{last_column}").join(' AND ')
|
108
|
+
query << ' )'
|
109
|
+
where << query
|
110
|
+
end
|
111
|
+
where.join(' OR ')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
require_relative 'keyset/active_record'
|
118
|
+
require_relative 'keyset/sequel'
|
@@ -0,0 +1,26 @@
|
|
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) && instance_variable_set(:"@#{name}", @vars[name].to_i) >= min
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Assign @limit (overridden by the gearbox extra)
|
17
|
+
def assign_limit
|
18
|
+
assign_and_check(limit: 1)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Assign @vars
|
22
|
+
def assign_vars(default, vars)
|
23
|
+
@vars = { **default, **vars.delete_if { |k, v| default.key?(k) && (v.nil? || v == '') } }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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: false, fragment: nil, **_)
|
10
|
+
vars = pagy.vars
|
11
|
+
query_params = request.GET.clone(freeze: false)
|
12
|
+
query_params.merge!(vars[:params].transform_keys(&:to_s)) if vars[:params].is_a?(Hash)
|
13
|
+
pagy_set_query_params(page, vars, query_params)
|
14
|
+
query_params = vars[:params].(query_params) if vars[:params].is_a?(Proc)
|
15
|
+
query_string = "?#{Rack::Utils.build_nested_query(query_params)}"
|
16
|
+
"#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{fragment}"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add the page and limit params
|
20
|
+
# Overridable by the jsonapi extra
|
21
|
+
def pagy_set_query_params(page, vars, query_params)
|
22
|
+
query_params[vars[:page_param].to_s] = page
|
23
|
+
query_params[vars[:limit_param].to_s] = vars[:limit] if vars[:limit_extra]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/pagy.rb
CHANGED
@@ -1,52 +1,106 @@
|
|
1
|
-
# See Pagy API documentation: https://ddnexus.github.io/pagy/api/pagy
|
2
|
-
# encoding: utf-8
|
1
|
+
# See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/pagy
|
3
2
|
# frozen_string_literal: true
|
4
3
|
|
5
4
|
require 'pathname'
|
5
|
+
require_relative 'pagy/shared_methods'
|
6
6
|
|
7
|
-
|
7
|
+
# Top superclass: it should define only what's common to all the subclasses
|
8
|
+
class Pagy
|
9
|
+
VERSION = '9.4.0'
|
8
10
|
|
9
|
-
#
|
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
|
11
19
|
|
12
|
-
#
|
13
|
-
|
20
|
+
# Gem root pathname to get the path of Pagy files stylesheets, javascripts, apps, locales, etc.
|
21
|
+
def self.root
|
22
|
+
@root ||= Pathname.new(__dir__).parent.freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
include SharedMethods
|
14
26
|
|
15
|
-
attr_reader :count, :
|
27
|
+
attr_reader :count, :from, :in, :last, :next, :offset, :prev, :to
|
28
|
+
alias pages last
|
16
29
|
|
17
30
|
# Merge and validate the options, do some simple arithmetic and set the instance variables
|
18
|
-
def initialize(vars)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@
|
26
|
-
@
|
27
|
-
@
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
38
|
+
@from = [@offset - @outset + 1, @count].min
|
39
|
+
@to = [@offset - @outset + @limit, @count].min
|
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
|
32
53
|
end
|
33
54
|
|
34
|
-
#
|
35
|
-
def
|
36
|
-
|
37
|
-
|
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
|
55
|
+
# Assign @prev and @next
|
56
|
+
def assign_prev_and_next
|
57
|
+
@prev = (@page - 1 unless @page == 1)
|
58
|
+
@next = @page == @last ? (1 if @vars[:cycle]) : @page + 1
|
46
59
|
end
|
47
60
|
|
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]
|
73
|
+
def series(size: @vars[:size], **_)
|
74
|
+
raise VariableError.new(self, :size, 'to be an Integer >= 0', size) \
|
75
|
+
unless size.is_a?(Integer) && size >= 0
|
76
|
+
return [] if size.zero?
|
77
|
+
|
78
|
+
[].tap do |series|
|
79
|
+
if size >= @last
|
80
|
+
series.push(*1..@last)
|
81
|
+
else
|
82
|
+
left = ((size - 1) / 2.0).floor # left half might be 1 page shorter for even size
|
83
|
+
start = if @page <= left # beginning pages
|
84
|
+
1
|
85
|
+
elsif @page > (@last - size + left) # end pages
|
86
|
+
@last - size + 1
|
87
|
+
else # intermediate pages
|
88
|
+
@page - left
|
89
|
+
end
|
90
|
+
series.push(*start...(start + size))
|
91
|
+
# Set first and last pages plus gaps when needed, respecting the size
|
92
|
+
if vars[:ends] && size >= 7
|
93
|
+
series[0] = 1
|
94
|
+
series[1] = :gap unless series[1] == 2
|
95
|
+
series[-2] = :gap unless series[-2] == @last - 1
|
96
|
+
series[-1] = @last
|
97
|
+
end
|
98
|
+
end
|
99
|
+
series[series.index(@page)] = @page.to_s
|
100
|
+
end
|
101
|
+
end
|
48
102
|
end
|
49
103
|
|
50
|
-
|
51
|
-
|
52
|
-
|
104
|
+
require_relative 'pagy/backend'
|
105
|
+
require_relative 'pagy/frontend'
|
106
|
+
require_relative 'pagy/exceptions'
|
data/locales/ar.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# :arabic pluralization (see https://github.com/ddnexus/pagy/blob/master/gem/lib/pagy/i18n.rb)
|
2
|
+
ar:
|
3
|
+
pagy:
|
4
|
+
aria_label:
|
5
|
+
nav:
|
6
|
+
zero: "لا يوجد صفحات"
|
7
|
+
one: "صفحة"
|
8
|
+
two: "صفحتين"
|
9
|
+
few: "صفحات"
|
10
|
+
many: "صفحات"
|
11
|
+
other: "صفحات"
|
12
|
+
prev: "السابق"
|
13
|
+
next: "التالي"
|
14
|
+
prev: "<"
|
15
|
+
next: ">"
|
16
|
+
gap: "…"
|
17
|
+
item_name:
|
18
|
+
zero: "صفر"
|
19
|
+
one: "عنصر"
|
20
|
+
two: "عنصرين"
|
21
|
+
few: "قليل"
|
22
|
+
many: "كثير"
|
23
|
+
other: "عناصر"
|
24
|
+
info:
|
25
|
+
no_items: "لا يوجد %{item_name}"
|
26
|
+
single_page: "عرض %{count} %{item_name}"
|
27
|
+
multiple_pages: "عرض %{item_name} %{from}-%{to} من اجمالي %{count}"
|
28
|
+
combo_nav_js: "الصفحة %{page_input} من %{pages}"
|
29
|
+
limit_selector_js: "عرض %{limit_input} %{item_name} لكل صفحة"
|
data/locales/be.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# :east_slavic pluralization (see https://github.com/ddnexus/pagy/blob/master/gem/lib/pagy/i18n.rb)
|
2
|
+
be:
|
3
|
+
pagy:
|
4
|
+
aria_label:
|
5
|
+
nav:
|
6
|
+
one: "Старонка"
|
7
|
+
few: "Старонкi"
|
8
|
+
many: "Старонкi"
|
9
|
+
other: "Старонкi"
|
10
|
+
prev: "Назад"
|
11
|
+
next: "Наперад"
|
12
|
+
prev: "<"
|
13
|
+
next: ">"
|
14
|
+
gap: "…"
|
15
|
+
item_name:
|
16
|
+
one: "запіс"
|
17
|
+
few: "запісы"
|
18
|
+
many: "запісаў"
|
19
|
+
other: "запісаў"
|
20
|
+
info:
|
21
|
+
no_items: "Пакуль %{item_name} няма"
|
22
|
+
single_page: "%{count} %{item_name}"
|
23
|
+
multiple_pages: "Усяго %{count} %{item_name}, паказаны з %{from} па %{to}"
|
24
|
+
combo_nav_js: "Старонка %{page_input} з %{pages}"
|
25
|
+
limit_selector_js: "Паказаць %{limit_input} %{item_name} на старонцы"
|
data/locales/bg.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# :one_other pluralization (see https://github.com/ddnexus/pagy/blob/master/gem/lib/pagy/i18n.rb)
|
2
|
+
bg:
|
3
|
+
pagy:
|
4
|
+
aria_label:
|
5
|
+
nav:
|
6
|
+
one: "Страница"
|
7
|
+
other: "Страници"
|
8
|
+
prev: "Предишна"
|
9
|
+
next: "Следваща"
|
10
|
+
prev: "<"
|
11
|
+
next: ">"
|
12
|
+
gap: "…"
|
13
|
+
item_name:
|
14
|
+
one: "резултат"
|
15
|
+
other: "резултати"
|
16
|
+
info:
|
17
|
+
no_items: "Няма намерени %{item_name}"
|
18
|
+
single_page: "Показани са %{count} %{item_name}"
|
19
|
+
multiple_pages: "Показани са %{item_name} %{from}-%{to} от %{count} общо"
|
20
|
+
combo_nav_js: "Страница %{page_input} от %{pages}"
|
21
|
+
limit_selector_js: "Покажи %{limit_input} %{item_name} на страница"
|