pagy 9.1.1 → 9.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3728828e8b53f6fdb5e739dec788a3fa49a6b9f2a5e51521ab3bbd27acbd4355
4
- data.tar.gz: 93f7c8ed18a727c33d4c49111bc2dc627e93c394fc58e78d1e451a2cfb67498d
3
+ metadata.gz: 842c521e4025523834f0af3775c8556ffe7183bc2c0fe973f54408e701eec1a2
4
+ data.tar.gz: bbb0f03b93fe17166f9e78a9ab5f99cba7c69c92696879a0c14ed896f40ee960
5
5
  SHA512:
6
- metadata.gz: '0339c6c134bb6bbe8078e905b07e709bd391a922f7532130d91e903e4a771ce4d321c1601b7290ca189fa986faabe450de784c2265551f04efe741d644e6f842'
7
- data.tar.gz: 002b26102cb7682f2d1f08bf9c3ae44f77116abd739def6418a6feebb13317e56695c27798c71f5101d9a52f7897f8fb5cb1f9a7cdc785931a75e2f900c74a17
6
+ metadata.gz: 19f6e4c0c22fb872bc807cc1860bb8e3c4b9d1139c0b1cd9483185b25a49d544aef3dd591ad03b85442823d16611302865371b7f581811d16bac781979202d9b
7
+ data.tar.gz: 46766eca03273e45d3cc959864fd1e164593aef293ac1d87b8ba8a03f7d8ece92c8acf961a4a0db527cd5fc81cce4ac6dc3a6a8b1b11cdfc33f2924e04b86d82
data/apps/calendar.ru CHANGED
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Interactive showcase for the pagy calendar extra (https://ddnexus.github.io/pagy/docs/extras/calendar)
4
-
3
+ # DESCRIPTION
4
+ # Showcase the calendar; reproduce related issues
5
+ #
6
+ # DOC
7
+ # https://ddnexus.github.io/pagy/playground/#5-calendar-app
8
+ #
9
+ # BIN HELP
10
+ # bundle exec pagy -h
11
+ #
5
12
  # DEV USAGE
6
- # pagy clone calendar
7
- # pagy ./calendar.ru
8
-
13
+ # bundle exec pagy clone calendar
14
+ # bundle exec pagy ./calendar.ru
15
+ #
9
16
  # URL
10
17
  # http://0.0.0.0:8000
11
18
 
12
- # HELP
13
- # pagy -h
14
-
15
- # DOC
16
- # https://ddnexus.github.io/pagy/playground/#5-calendar-app
17
-
18
- VERSION = '9.1.1'
19
+ VERSION = '9.2.1'
19
20
 
20
- # Gemfile
21
+ # Bundle
21
22
  require 'bundler/inline'
22
23
  require 'bundler'
23
24
  Bundler.configure
@@ -25,10 +26,8 @@ gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
25
26
  source 'https://rubygems.org'
26
27
  gem 'groupdate'
27
28
  gem 'puma'
28
- gem 'rails'
29
- # activerecord/sqlite3_adapter.rb probably useless) constraint !!!
30
- # https://github.com/rails/rails/blame/v7.1.3.4/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L14
31
- gem 'sqlite3', '~> 1.4.0'
29
+ gem 'rails', '~> 8.0'
30
+ gem 'sqlite3'
32
31
  end
33
32
 
34
33
  # require 'rails/all' # too much stuff
@@ -53,10 +52,8 @@ end
53
52
 
54
53
  # AR config
55
54
  dir = Rails.env.development? ? '.' : Dir.pwd # app dir in dev or pwd otherwise
56
- unless File.writable?(dir)
57
- warn "ERROR: directory #{dir.inspect} is not writable (the calendar-app needs to create DB files)"
58
- exit 1
59
- end
55
+ abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
56
+ unless File.writable?(dir)
60
57
 
61
58
  # Pagy initializer
62
59
  require 'pagy/extras/calendar'
@@ -78,8 +75,7 @@ ActiveRecord::Schema.define do
78
75
  end
79
76
 
80
77
  # Models
81
- class Event < ActiveRecord::Base
82
- end
78
+ class Event < ActiveRecord::Base; end
83
79
 
84
80
  # Helpers
85
81
  module EventsHelper
@@ -173,7 +169,7 @@ TEMPLATE = <<~ERB
173
169
 
174
170
  <div class="container">
175
171
  <h1>Pagy Calendar App</h1>
176
- <p>Self-contained, standalone Rails app implementing nested calendar pagination for year, month, day units.</p>
172
+ <p>Self-contained, standalone app implementing nested calendar pagination for year, month, day units.</p>
177
173
  <p>See the <a href="https://ddnexus.github.io/pagy/docs/extras/calendar">Pagy Calendar Extra</a> for details.</p>
178
174
  <p>Please, report the following versions in any new issue.</p>
179
175
  <h2>Versions</h2>
data/apps/demo.ru CHANGED
@@ -1,25 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Interactive showcase for all the pagy helpers and CSS styles
4
-
3
+ # DESCRIPTION
4
+ # Showcase all the helpers and styles
5
+ #
6
+ # DOC
7
+ # https://ddnexus.github.io/pagy/playground/#3-demo-app
8
+ #
9
+ # BIN HELP
10
+ # bundle exec pagy -h
11
+ #
5
12
  # DEMO USAGE
6
- # pagy demo
7
-
13
+ # bundle exec pagy demo
14
+ #
8
15
  # DEV USAGE
9
- # pagy clone demo
10
- # pagy ./demo.ru
11
-
16
+ # bundle exec pagy clone demo
17
+ # bundle exec pagy ./demo.ru
18
+ #
12
19
  # URL
13
20
  # http://0.0.0.0:8000
14
21
 
15
- # HELP
16
- # pagy -h
17
-
18
- # DOC
19
- # https://ddnexus.github.io/pagy/playground/#3-demo-app
20
-
21
- VERSION = '9.1.1'
22
+ VERSION = '9.2.1'
22
23
 
24
+ # Bundle
23
25
  require 'bundler/inline'
24
26
  require 'bundler'
25
27
  Bundler.configure
@@ -45,10 +47,10 @@ require 'pagy/extras/limit'
45
47
  require 'pagy/extras/trim'
46
48
  Pagy::DEFAULT[:trim_extra] = false # opt-in trim
47
49
 
48
- # sinatra setup
50
+ # Sinatra setup
49
51
  require 'sinatra/base'
50
52
 
51
- # sinatra application
53
+ # Sinatra application
52
54
  class PagyDemo < Sinatra::Base
53
55
  configure do
54
56
  enable :inline_templates
data/apps/keyset_ar.ru CHANGED
@@ -1,69 +1,96 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Starting point to reproduce keyset related pagy issues
4
-
3
+ # DESCRIPTION
4
+ # Showcase the keyset ActiveRecord pagination
5
+ #
6
+ # DOC
7
+ # https://ddnexus.github.io/pagy/playground/#5-keyset-apps
8
+ #
9
+ # BIN HELP
10
+ # bundle exec pagy -h
11
+ #
5
12
  # DEV USAGE
6
- # pagy clone rails
7
- # pagy ./keyset.ru
8
-
13
+ # bundle exec pagy clone keyset_ar
14
+ # bundle exec pagy ./keyset_ar.ru
15
+ #
9
16
  # URL
10
17
  # http://0.0.0.0:8000
11
18
 
12
- # HELP
13
- # pagy -h
14
-
15
- # DOC
16
- # https://ddnexus.github.io/pagy/playground/#5-keyset-app
17
-
18
- VERSION = '8.6.2'
19
+ VERSION = '9.2.1'
19
20
 
20
- # Gemfile
21
+ # Bundle
21
22
  require 'bundler/inline'
22
23
  require 'bundler'
23
24
  Bundler.configure
24
25
  gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
25
26
  source 'https://rubygems.org'
26
- gem 'oj'
27
+ gem 'activerecord'
27
28
  gem 'puma'
28
- gem 'rails'
29
- # activerecord/sqlite3_adapter.rb probably useless) constraint !!!
30
- # https://github.com/rails/rails/blame/v7.1.3.4/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L14
31
- gem 'sqlite3', '~> 1.4.0'
29
+ gem 'sinatra'
30
+ gem 'sinatra-contrib'
31
+ gem 'sqlite3'
32
32
  end
33
33
 
34
- # require 'rails/all' # too much stuff
35
- require 'action_controller/railtie'
36
- require 'active_record'
34
+ # Pagy initializer
35
+ require 'pagy/extras/limit'
36
+ require 'pagy/extras/keyset'
37
+ require 'pagy/extras/pagy'
38
+ Pagy::DEFAULT[:limit] = 10
39
+ Pagy::DEFAULT.freeze
37
40
 
38
- OUTPUT = Rails.env.showcase? ? IO::NULL : $stdout
41
+ # Sinatra setup
42
+ require 'sinatra/base'
43
+ # Sinatra application
44
+ class PagyKeyset < Sinatra::Base
45
+ configure do
46
+ # Templates defined in the __END__ section as @@ ...
47
+ enable :inline_templates
48
+ end
39
49
 
40
- # Rails config
41
- class PagyKeyset < Rails::Application # :nodoc:
42
- config.root = __dir__
43
- config.session_store :cookie_store, key: 'cookie_store_key'
44
- Rails.application.credentials.secret_key_base = 'absolute_secret'
50
+ # Controller
51
+ include Pagy::Backend
52
+ # Root route/action
53
+ get '/' do
54
+ Time.zone = 'UTC'
45
55
 
46
- config.logger = Logger.new(OUTPUT)
47
- Rails.logger = config.logger
56
+ @order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
57
+ @pagy, @pets = pagy_keyset(Pet.order(@order))
58
+ erb :main
59
+ end
60
+ # Helper
61
+ helpers do
62
+ include Pagy::Frontend
48
63
 
49
- routes.draw do
50
- root to: 'pets#index'
64
+ def order_symbol(dir)
65
+ { asc: '&#x2197;', desc: '&#x2198;' }[dir]
66
+ end
51
67
  end
52
68
  end
53
69
 
54
- dir = Rails.env.development? ? '.' : Dir.pwd # app dir in dev or pwd otherwise
55
- unless File.writable?(dir)
56
- warn "ERROR: directory #{dir.inspect} is not writable (the pagy-rails-app needs to create DB files)"
57
- exit 1
70
+ # ActiveRecord setup
71
+ require 'active_record'
72
+ # Log
73
+ output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
74
+ ActiveRecord::Base.logger = Logger.new(output)
75
+ # SQLite DB files
76
+ dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
77
+ abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
78
+ unless File.writable?(dir)
79
+ # Connection
80
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: "#{dir}/tmp/pagy-keyset-ar.sqlite3")
81
+ # Schema
82
+ ActiveRecord::Schema.define do
83
+ create_table :pets, force: true do |t|
84
+ t.string :animal
85
+ t.string :name
86
+ t.date :birthdate
87
+ end
58
88
  end
59
89
 
60
- # Pagy initializer
61
- require 'pagy/extras/pagy'
62
- require 'pagy/extras/limit'
63
- require 'pagy/extras/keyset'
64
- Pagy::DEFAULT[:limit] = 10
65
- Pagy::DEFAULT.freeze
90
+ # Models
91
+ class Pet < ActiveRecord::Base; end
66
92
 
93
+ # Data
67
94
  PETS = <<~PETS
68
95
  Luna | dog | 2018-03-10
69
96
  Coco | cat | 2019-05-15
@@ -117,21 +144,6 @@ PETS = <<~PETS
117
144
  Coco | dog | 2023-05-27
118
145
  PETS
119
146
 
120
- # Activerecord initializer
121
- ActiveRecord::Base.logger = Logger.new(OUTPUT)
122
- ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: "#{dir}/tmp/pagy-keyset-ar.sqlite3")
123
- ActiveRecord::Schema.define do
124
- create_table :pets, force: true do |t|
125
- t.string :animal
126
- t.string :name
127
- t.date :birthdate
128
- end
129
- end
130
-
131
- # Models
132
- class Pet < ActiveRecord::Base
133
- end
134
-
135
147
  # DB seed
136
148
  pets = []
137
149
  PETS.each_line(chomp: true) do |pet|
@@ -140,97 +152,75 @@ PETS.each_line(chomp: true) do |pet|
140
152
  end
141
153
  Pet.insert_all(pets)
142
154
 
143
- # Helpers
144
- module PetsHelper
145
- include Pagy::Frontend
146
-
147
- def order_symbol(dir)
148
- { asc: '&#x2197;', desc: '&#x2198;' }[dir]
149
- end
150
- end
151
-
152
- # Controllers
153
- class PetsController < ActionController::Base # :nodoc:
154
- include Rails.application.routes.url_helpers
155
- include Pagy::Backend
156
-
157
- def index
158
- Time.zone = 'UTC'
159
-
160
- @order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
161
- @pagy, @pets = pagy_keyset(Pet.order(@order))
162
- render inline: TEMPLATE
163
- end
164
- end
165
-
166
- TEMPLATE = <<~ERB
167
- <!DOCTYPE html>
168
- <html lang="en">
169
- <html>
170
- <head>
171
- <title>Pagy Keyset App</title>
172
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
173
- <style type="text/css">
174
- @media screen { html, body {
175
- font-size: 1rem;
176
- line-height: 1.2s;
177
- padding: 0;
178
- margin: 0;
179
- } }
180
- body {
181
- background: white !important;
182
- margin: 0 !important;
183
- font-family: sans-serif !important;
184
- }
185
- .content {
186
- padding: 1rem 1.5rem 2rem !important;
187
- }
188
-
189
- <%== Pagy.root.join('stylesheets', 'pagy.css').read %>
190
- </style>
191
- </head>
192
-
193
- <body>
194
-
195
- <div class="content">
196
- <h1>Pagy Keyset App</h1>
197
- <p>Self-contained, standalone Rails app usable to easily reproduce any keyset related pagy issue with ActiveRecord sets.</p>
198
- <p>Please, report the following versions in any new issue.</p>
199
- <h2>Versions</h2>
200
- <ul>
201
- <li>Ruby: <%== RUBY_VERSION %></li>
202
- <li>Rack: <%== Rack::RELEASE %></li>
203
- <li>Rails: <%== Rails.version %></li>
204
- <li>Pagy: <%== Pagy::VERSION %></li>
205
- </ul>
206
-
207
- <h3>Collection</h3>
208
- <div id="records" class="collection">
209
- <table border="1" cellspacing="0" cellpadding="3">
210
- <tr>
211
- <th>animal <%== order_symbol(@order[:animal]) %></th>
212
- <th>name <%== order_symbol(@order[:name]) %></th>
213
- <th>birthdate <%== order_symbol(@order[:birthdate]) %></th>
214
- <th>id <%== order_symbol(@order[:id]) %></th>
215
- </tr>
216
- <% @pets.each do |pet| %>
217
- <tr>
218
- <td><%= pet.animal %></td>
219
- <td><%= pet.name %></td>
220
- <td><%= pet.birthdate %></td>
221
- <td><%= pet.id %></td>
222
- </tr>
223
- <% end %>
224
- </table>
225
- </div>
226
- <p>
227
- <nav class="pagy" id="next" aria-label="Pagy next">
228
- <%== pagy_next_a(@pagy, text: 'Next page &gt;') %>
229
- </nav>
230
- </div>
231
-
232
- </body>
233
- </html>
234
- ERB
235
-
236
155
  run PagyKeyset
156
+
157
+ __END__
158
+
159
+ @@ layout
160
+ <!DOCTYPE html>
161
+ <html lang="en">
162
+ <html>
163
+ <head>
164
+ <title>Pagy Keyset App</title>
165
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
166
+ <style type="text/css">
167
+ @media screen { html, body {
168
+ font-size: 1rem;
169
+ line-height: 1.2s;
170
+ padding: 0;
171
+ margin: 0;
172
+ } }
173
+ body {
174
+ background: white !important;
175
+ margin: 0 !important;
176
+ font-family: sans-serif !important;
177
+ }
178
+ .content {
179
+ padding: 1rem 1.5rem 2rem !important;
180
+ }
181
+
182
+ <%= Pagy.root.join('stylesheets', 'pagy.css').read %>
183
+ </style>
184
+ </head>
185
+ <body>
186
+ <%= yield %>
187
+ </body>
188
+ </html>
189
+
190
+ @@ main
191
+ <div class="content">
192
+ <h1>Pagy Keyset App</h1>
193
+ <p>Self-contained, standalone app usable to easily reproduce any keyset related pagy issue with ActiveRecord sets.</p>
194
+ <p>Please, report the following versions in any new issue.</p>
195
+ <h2>Versions</h2>
196
+ <ul>
197
+ <li>Ruby: <%= RUBY_VERSION %></li>
198
+ <li>Rack: <%= Rack::RELEASE %></li>
199
+ <li>Sinatra: <%= Sinatra::VERSION %></li>
200
+ <li>Pagy: <%= Pagy::VERSION %></li>
201
+ </ul>
202
+
203
+ <h3>Collection</h3>
204
+ <div id="records" class="collection">
205
+ <table border="1" cellspacing="0" cellpadding="3">
206
+ <tr>
207
+ <th>animal <%= order_symbol(@order[:animal]) %></th>
208
+ <th>name <%= order_symbol(@order[:name]) %></th>
209
+ <th>birthdate <%= order_symbol(@order[:birthdate]) %></th>
210
+ <th>id <%= order_symbol(@order[:id]) %></th>
211
+ </tr>
212
+ <% @pets.each do |pet| %>
213
+ <tr>
214
+ <td><%= pet.animal %></td>
215
+ <td><%= pet.name %></td>
216
+ <td><%= pet.birthdate %></td>
217
+ <td><%= pet.id %></td>
218
+ </tr>
219
+ <% end %>
220
+ </table>
221
+ </div>
222
+ <p>
223
+ <nav class="pagy" id="next" aria-label="Pagy next">
224
+ <%= pagy_next_a(@pagy, text: 'Next page &gt;') %>
225
+ </nav>
226
+ </div>
data/apps/keyset_s.ru CHANGED
@@ -1,70 +1,94 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Starting point to reproduce keyset related pagy issues
4
-
3
+ # DESCRIPTION
4
+ # Showcase the keyset ActiveRecord pagination
5
+ #
6
+ # DOC
7
+ # https://ddnexus.github.io/pagy/playground/#5-keyset-apps
8
+ #
9
+ # BIN HELP
10
+ # bundle exec pagy -h
11
+ #
5
12
  # DEV USAGE
6
- # pagy clone rails
7
- # pagy ./keyset.ru
8
-
13
+ # bundle exec pagy clone keyset_ar
14
+ # bundle exec pagy ./keyset_ar.ru
15
+ #
9
16
  # URL
10
17
  # http://0.0.0.0:8000
11
18
 
12
- # HELP
13
- # pagy -h
14
-
15
- # DOC
16
- # https://ddnexus.github.io/pagy/playground/#5-keyset-app
17
-
18
- VERSION = '8.6.2'
19
+ VERSION = '9.2.1'
19
20
 
20
- # Gemfile
21
+ # Bundle
21
22
  require 'bundler/inline'
22
23
  require 'bundler'
23
24
  Bundler.configure
24
25
  gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
25
26
  source 'https://rubygems.org'
26
- gem 'oj'
27
27
  gem 'puma'
28
- gem 'rails'
29
- # activerecord/sqlite3_adapter.rb probably useless) constraint !!!
30
- # https://github.com/rails/rails/blame/v7.1.3.4/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L14
31
- gem 'sqlite3', '~> 1.4.0'
32
28
  gem 'sequel'
29
+ gem 'sinatra'
30
+ gem 'sinatra-contrib'
31
+ gem 'sqlite3'
33
32
  end
34
33
 
35
- # require 'rails/all' # too much stuff
36
- require 'action_controller/railtie'
37
- require 'sequel'
38
-
39
- OUTPUT = Rails.env.showcase? ? IO::NULL : $stdout
34
+ # Pagy initializer
35
+ require 'pagy/extras/limit'
36
+ require 'pagy/extras/keyset'
37
+ require 'pagy/extras/pagy'
38
+ Pagy::DEFAULT[:limit] = 10
39
+ Pagy::DEFAULT.freeze
40
40
 
41
- # Rails config
42
- class PagyKeyset < Rails::Application # :nodoc:
43
- config.root = __dir__
44
- config.session_store :cookie_store, key: 'cookie_store_key'
45
- Rails.application.credentials.secret_key_base = 'absolute_secret'
41
+ # Sinatra setup
42
+ require 'sinatra/base'
43
+ require 'logger'
44
+ # Sinatra application
45
+ class PagyKeyset < Sinatra::Base
46
+ configure do
47
+ # Templates defined in the __END__ section as @@ ...
48
+ enable :inline_templates
49
+ end
46
50
 
47
- config.logger = Logger.new(OUTPUT)
48
- Rails.logger = config.logger
51
+ # Controller
52
+ include Pagy::Backend
53
+ # Root route/action
54
+ get '/' do
55
+ @order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
56
+ @pagy, @pets = pagy_keyset(Pet.order(:animal, :name, Sequel.desc(:birthdate), :id))
57
+ erb :main
58
+ end
59
+ # Helper
60
+ helpers do
61
+ include Pagy::Frontend
49
62
 
50
- routes.draw do
51
- root to: 'pets#index'
63
+ def order_symbol(dir)
64
+ { asc: '&#x2197;', desc: '&#x2198;' }[dir]
65
+ end
52
66
  end
53
67
  end
54
68
 
55
- dir = Rails.env.development? ? '.' : Dir.pwd # app dir in dev or pwd otherwise
69
+ # Sequel setup
70
+ require 'sequel'
71
+ Sequel.default_timezone = :utc
72
+ # SQLite DB files
73
+ dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
74
+ abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
56
75
  unless File.writable?(dir)
57
- warn "ERROR: directory #{dir.inspect} is not writable (the pagy-rails-app needs to create DB files)"
58
- exit 1
76
+ # Connection
77
+ output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
78
+ DB = Sequel.connect(adapter: 'sqlite', user: 'root', password: 'password', host: 'localhost', port: '3306',
79
+ database: "#{dir}/tmp/pagy-keyset-s.sqlite3", max_connections: 10, loggers: [Logger.new(output)])
80
+ # Schema
81
+ DB.create_table! :pets do
82
+ primary_key :id
83
+ String :animal, unique: false, null: false
84
+ String :name, unique: false, null: false
85
+ Date :birthdate, unique: false, null: false
59
86
  end
60
87
 
61
- # Pagy initializer
62
- require 'pagy/extras/pagy'
63
- require 'pagy/extras/limit'
64
- require 'pagy/extras/keyset'
65
- Pagy::DEFAULT[:limit] = 10
66
- Pagy::DEFAULT.freeze
88
+ # Models
89
+ class Pet < Sequel::Model; end
67
90
 
91
+ # Data
68
92
  PETS = <<~PETS
69
93
  Luna | dog | 2018-03-10
70
94
  Coco | cat | 2019-05-15
@@ -118,121 +142,81 @@ PETS = <<~PETS
118
142
  Coco | dog | 2023-05-27
119
143
  PETS
120
144
 
121
- Sequel.default_timezone = :utc
122
-
123
- ## Sequel initializer
124
- DB = Sequel.connect(adapter: 'sqlite', user: 'root', password: 'password', host: 'localhost', port: '3306',
125
- database: "#{dir}/tmp/pagy-keyset-s.sqlite3", max_connections: 10, loggers: [Logger.new(OUTPUT)])
126
-
127
- DB.create_table! :pets do
128
- primary_key :id
129
- String :animal, unique: false, null: false
130
- String :name, unique: false, null: false
131
- Date :birthdate, unique: false, null: false
132
- end
133
-
134
145
  dataset = DB[:pets]
135
-
136
146
  PETS.each_line(chomp: true) do |pet|
137
147
  name, animal, birthdate = pet.split('|').map(&:strip)
138
148
  dataset.insert(name:, animal:, birthdate:)
139
149
  end
140
150
 
141
- # Models
142
- class Pet < Sequel::Model
143
- end
144
-
145
- # Helpers
146
- module PetsHelper
147
- include Pagy::Frontend
148
-
149
- def order_symbol(dir)
150
- { asc: '&#x2197;', desc: '&#x2198;' }[dir]
151
- end
152
- end
153
-
154
- # Controllers
155
- class PetsController < ActionController::Base # :nodoc:
156
- include Rails.application.routes.url_helpers
157
- include Pagy::Backend
158
-
159
- def index
160
- Time.zone = 'UTC'
161
-
162
- @order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
163
- @pagy, @pets = pagy_keyset(Pet.order(:animal, :name, Sequel.desc(:birthdate), :id))
164
- render inline: TEMPLATE
165
- end
166
- end
167
-
168
- TEMPLATE = <<~ERB
169
- <!DOCTYPE html>
170
- <html lang="en">
171
- <html>
172
- <head>
173
- <title>Pagy Keyset App</title>
174
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
175
- <style type="text/css">
176
- @media screen { html, body {
177
- font-size: 1rem;
178
- line-height: 1.2s;
179
- padding: 0;
180
- margin: 0;
181
- } }
182
- body {
183
- background: white !important;
184
- margin: 0 !important;
185
- font-family: sans-serif !important;
186
- }
187
- .content {
188
- padding: 1rem 1.5rem 2rem !important;
189
- }
190
-
191
- <%== Pagy.root.join('stylesheets', 'pagy.css').read %>
192
- </style>
193
- </head>
194
-
195
- <body>
196
-
197
- <div class="content">
198
- <h1>Pagy Keyset App</h1>
199
- <p>Self-contained, standalone Rails app usable to easily reproduce any keyset related pagy issue with Sequel sets.</p>
200
- <p>Please, report the following versions in any new issue.</p>
201
- <h2>Versions</h2>
202
- <ul>
203
- <li>Ruby: <%== RUBY_VERSION %></li>
204
- <li>Rack: <%== Rack::RELEASE %></li>
205
- <li>Rails: <%== Rails.version %></li>
206
- <li>Pagy: <%== Pagy::VERSION %></li>
207
- </ul>
208
-
209
- <h3>Collection</h3>
210
- <div id="records" class="collection">
211
- <table border="1" cellspacing="0" cellpadding="3">
212
- <tr>
213
- <th>animal <%== order_symbol(@order[:animal]) %></th>
214
- <th>name <%== order_symbol(@order[:name]) %></th>
215
- <th>birthdate <%== order_symbol(@order[:birthdate]) %></th>
216
- <th>id <%== order_symbol(@order[:id]) %></th>
217
- </tr>
218
- <% @pets.each do |pet| %>
219
- <tr>
220
- <td><%= pet.animal %></td>
221
- <td><%= pet.name %></td>
222
- <td><%= pet.birthdate %></td>
223
- <td><%= pet.id %></td>
224
- </tr>
225
- <% end %>
226
- </table>
227
- </div>
228
- <p>
229
- <nav class="pagy" id="next" aria-label="Pagy next">
230
- <%== pagy_next_a(@pagy, text: 'Next page &gt;') %>
231
- </nav>
232
- </div>
233
-
234
- </body>
235
- </html>
236
- ERB
237
-
238
151
  run PagyKeyset
152
+
153
+ __END__
154
+
155
+ @@ layout
156
+ <!DOCTYPE html>
157
+ <html lang="en">
158
+ <html>
159
+ <head>
160
+ <title>Pagy Keyset App</title>
161
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
162
+ <style type="text/css">
163
+ @media screen { html, body {
164
+ font-size: 1rem;
165
+ line-height: 1.2s;
166
+ padding: 0;
167
+ margin: 0;
168
+ } }
169
+ body {
170
+ background: white !important;
171
+ margin: 0 !important;
172
+ font-family: sans-serif !important;
173
+ }
174
+ .content {
175
+ padding: 1rem 1.5rem 2rem !important;
176
+ }
177
+
178
+ <%= Pagy.root.join('stylesheets', 'pagy.css').read %>
179
+ </style>
180
+ </head>
181
+ <body>
182
+ <%= yield %>
183
+ </body>
184
+ </html>
185
+
186
+ @@ main
187
+ <div class="content">
188
+ <h1>Pagy Keyset App</h1>
189
+ <p>Self-contained, standalone app usable to easily reproduce any keyset related pagy issue with ActiveRecord sets.</p>
190
+ <p>Please, report the following versions in any new issue.</p>
191
+ <h2>Versions</h2>
192
+ <ul>
193
+ <li>Ruby: <%= RUBY_VERSION %></li>
194
+ <li>Rack: <%= Rack::RELEASE %></li>
195
+ <li>Sinatra: <%= Sinatra::VERSION %></li>
196
+ <li>Pagy: <%= Pagy::VERSION %></li>
197
+ </ul>
198
+
199
+ <h3>Collection</h3>
200
+ <div id="records" class="collection">
201
+ <table border="1" cellspacing="0" cellpadding="3">
202
+ <tr>
203
+ <th>animal <%= order_symbol(@order[:animal]) %></th>
204
+ <th>name <%= order_symbol(@order[:name]) %></th>
205
+ <th>birthdate <%= order_symbol(@order[:birthdate]) %></th>
206
+ <th>id <%= order_symbol(@order[:id]) %></th>
207
+ </tr>
208
+ <% @pets.each do |pet| %>
209
+ <tr>
210
+ <td><%= pet.animal %></td>
211
+ <td><%= pet.name %></td>
212
+ <td><%= pet.birthdate %></td>
213
+ <td><%= pet.id %></td>
214
+ </tr>
215
+ <% end %>
216
+ </table>
217
+ </div>
218
+ <p>
219
+ <nav class="pagy" id="next" aria-label="Pagy next">
220
+ <%= pagy_next_a(@pagy, text: 'Next page &gt;') %>
221
+ </nav>
222
+ </div>
data/apps/rails.ru CHANGED
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Starting point to reproduce rails related pagy issues
4
-
3
+ # DESCRIPTION
4
+ # Reproduce rails related issues
5
+ #
6
+ # DOC
7
+ # https://ddnexus.github.io/pagy/playground/#2-rails-app
8
+ #
9
+ # BIN HELP
10
+ # bundle exec pagy -h
11
+ #
5
12
  # DEV USAGE
6
- # pagy clone rails
7
- # pagy ./rails.ru
8
-
13
+ # bundle exec pagy clone rails
14
+ # bundle exec pagy ./rails.ru
15
+ #
9
16
  # URL
10
17
  # http://0.0.0.0:8000
11
18
 
12
- # HELP
13
- # pagy -h
14
-
15
- # DOC
16
- # https://ddnexus.github.io/pagy/playground/#2-rails-app
17
-
18
- VERSION = '9.1.1'
19
+ VERSION = '9.2.1'
19
20
 
20
21
  # Gemfile
21
22
  require 'bundler/inline'
@@ -25,10 +26,8 @@ gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
25
26
  source 'https://rubygems.org'
26
27
  gem 'oj'
27
28
  gem 'puma'
28
- gem 'rails'
29
- # activerecord/sqlite3_adapter.rb probably useless) constraint !!!
30
- # https://github.com/rails/rails/blame/v7.1.3.4/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L14
31
- gem 'sqlite3', '~> 1.4.0'
29
+ gem 'rails', '~> 8.0'
30
+ gem 'sqlite3'
32
31
  end
33
32
 
34
33
  # require 'rails/all' # too much stuff
data/apps/repro.ru CHANGED
@@ -1,22 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Starting point app to try pagy or reproduce issues
4
-
3
+ # DESCRIPTION
4
+ # Reproduce generic/simple issues
5
+ #
6
+ # DOC
7
+ # https://ddnexus.github.io/pagy/playground/#1-repro-app
8
+ #
9
+ # BIN HELP
10
+ # bundle exec pagy -h
11
+ #
5
12
  # DEV USAGE
6
- # pagy clone repro
7
- # pagy ./repro.ru
8
-
13
+ # bundle exec pagy clone repro
14
+ # bundle exec pagy ./repro.ru
15
+ #
9
16
  # URL
10
17
  # http://0.0.0.0:8000
11
18
 
12
- # HELP
13
- # pagy -h
14
-
15
- # DOC
16
- # https://ddnexus.github.io/pagy/playground/#1-repro-app
17
-
18
- VERSION = '9.1.1'
19
+ VERSION = '9.2.1'
19
20
 
21
+ # Bundle
20
22
  require 'bundler/inline'
21
23
  require 'bundler'
22
24
  Bundler.configure
@@ -36,6 +38,7 @@ require 'pagy/extras/overflow'
36
38
  Pagy::DEFAULT[:overflow] = :empty_page
37
39
  Pagy::DEFAULT.freeze
38
40
 
41
+ # Sinatra setup
39
42
  require 'sinatra/base'
40
43
  # Sinatra application
41
44
  class PagyRepro < Sinatra::Base
@@ -141,7 +144,7 @@ __END__
141
144
  @@ main
142
145
  <div class="content">
143
146
  <h1>Pagy Repro App</h1>
144
- <p> Self-contained, standalone Sinatra app usable to easily reproduce any pagy issue.</p>
147
+ <p> Self-contained, standalone app usable to easily reproduce any pagy issue.</p>
145
148
  <p>Please, report the following versions in any new issue.</p>
146
149
  <h2>Versions</h4>
147
150
  <ul>
data/bin/pagy CHANGED
@@ -1,24 +1,23 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- VERSION = '9.1.1'
5
- APPS = %w[repro rails demo calendar keyset_ar keyset_s].freeze
4
+ VERSION = '9.2.1'
6
5
  LINUX = RbConfig::CONFIG['host_os'].include?('linux')
7
6
  HOST = '0.0.0.0'
8
7
  PORT = '8000'
9
8
 
10
9
  require_relative '../lib/optimist'
10
+
11
+ apps = Dir[File.expand_path('../apps/*.ru', __dir__)].to_h { |f| [File.basename(f, '.ru'), f] }
11
12
  opts = Optimist.options do
12
13
  text <<~HEAD
13
14
  Pagy #{VERSION} (https://ddnexus.github.io/pagy/playground)
14
15
  Playground to showcase, clone and develop pagy APPs
15
16
  APPs
16
- repro Reproduce generic/simple issues
17
- rails Reproduce rails related issues
18
- demo Showcase all the helpers and styles
19
- calendar Showcase the calendar; reproduce related issues
20
- keyset_ar Showcase the keyset ActiveRecord pagination
21
- keyset_s Showcase the keyset Sequel pagination
17
+ #{ apps.map do |name, path|
18
+ " #{name}#{' ' * (27 - name.length)}#{File.readlines(path)[3].sub('# ', '').strip}"
19
+ end.join("\n")
20
+ }
22
21
  USAGE
23
22
  pagy APP [options] Showcase APP from the installed gem
24
23
  pagy clone APP Clone APP to the current dir
@@ -46,7 +45,7 @@ Optimist.educate if ARGV.empty?
46
45
 
47
46
  run_from_repo = File.exist?(File.expand_path('../pagy.gemspec', __dir__))
48
47
 
49
- # Handles gems
48
+ # Bundle
50
49
  require 'bundler/inline'
51
50
  require 'bundler'
52
51
  Bundler.configure
@@ -56,28 +55,25 @@ gemfile(opts[:install]) do
56
55
  gem 'rerun' if LINUX
57
56
  end
58
57
 
59
- path = ->(app) { File.expand_path("../apps/#{app}.ru", __dir__) }
60
- arg = ARGV.shift
58
+ arg = ARGV.shift
61
59
  if arg.eql?('clone')
62
- arg = ARGV.shift
63
- Optimist.die("Expected APP to be in [#{APPS.join(', ')}]; got #{arg.inspect}") unless APPS.include?(arg)
64
- file = path.(arg)
65
- name = File.basename(file)
60
+ name = ARGV.shift
61
+ Optimist.die("Expected APP to be in [#{apps.keys.join(', ')}]; got #{arg.inspect}") unless apps.key?(arg)
66
62
  if File.exist?(name)
67
63
  print "Do you want to overwrite the #{name.inspect} file? (y/n)> "
68
64
  answer = gets.chomp
69
65
  Optimist.die("#{name.inspect} file already present") unless answer.start_with?(/y/i)
70
66
  end
71
67
  require 'fileutils'
72
- FileUtils.cp(file, '.', verbose: true)
68
+ FileUtils.cp(apps[arg], '.', verbose: true)
73
69
  else
74
- if APPS.include?(arg) # showcase env
70
+ if apps.key?(arg) # showcase env
75
71
  opts[:env] = 'showcase'
76
72
  opts[:rerun] = false
77
73
  opts[:quiet] = true
78
74
  # Avoid the creation of './tmp/local_secret.txt' for showcase env
79
75
  ENV['SECRET_KEY_BASE'] = 'absolute secret!' if arg.eql?('rails')
80
- file = path.(arg)
76
+ file = apps[arg]
81
77
  else # development env
82
78
  file = arg
83
79
  end
data/config/pagy.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Pagy initializer file (9.1.1)
3
+ # Pagy initializer file (9.2.1)
4
4
  # Customize only what you really need and notice that the core Pagy works also without any of the following lines.
5
5
  # Should you just cherry pick part of this file, please maintain the require-order of the extras
6
6
 
@@ -1,4 +1,4 @@
1
- window.Pagy=(()=>{const j=new ResizeObserver((B)=>B.forEach((D)=>D.target.querySelectorAll(".pagy-rjs").forEach((E)=>E.pagyRender()))),x=(B,[D,E,z,G])=>{const F=B.parentElement??B,K=Object.keys(E).map((H)=>parseInt(H)).sort((H,M)=>M-H);let L=-1;const T=(H,M,R)=>H.replace(/__pagy_page__/g,M).replace(/__pagy_label__/g,R);if((B.pagyRender=function(){const H=K.find((Q)=>Q<F.clientWidth)||0;if(H===L)return;let M=D.before;const R=E[H.toString()],X=z?.[H.toString()]??R.map((Q)=>Q.toString());R.forEach((Q,J)=>{const $=X[J];let U;if(typeof Q==="number")U=T(D.a,Q.toString(),$);else if(Q==="gap")U=D.gap;else U=T(D.current,Q,$);M+=typeof G==="string"&&Q==1?Z(U,G):U}),M+=D.after,B.innerHTML="",B.insertAdjacentHTML("afterbegin",M),L=H})(),B.classList.contains("pagy-rjs"))j.observe(F)},A=(B,[D,E])=>Y(B,(z)=>[z,D.replace(/__pagy_page__/,z)],E),C=(B,[D,E,z])=>{Y(B,(G)=>{const F=Math.max(Math.ceil(D/parseInt(G)),1).toString(),K=E.replace(/__pagy_page__/,F).replace(/__pagy_limit__/,G);return[F,K]},z)},Y=(B,D,E)=>{const z=B.querySelector("input"),G=B.querySelector("a"),F=z.value,K=function(){if(z.value===F)return;const[L,T,H]=[z.min,z.value,z.max].map((X)=>parseInt(X)||0);if(T<L||T>H){z.value=F,z.select();return}let[M,R]=D(z.value);if(typeof E==="string"&&M==="1")R=Z(R,E);G.href=R,G.click()};["change","focus"].forEach((L)=>z.addEventListener(L,()=>z.select())),z.addEventListener("focusout",K),z.addEventListener("keypress",(L)=>{if(L.key==="Enter")K()})},Z=(B,D)=>B.replace(new RegExp(`[?&]${D}=1\\b(?!&)|\\b${D}=1&`),"");return{version:"9.1.1",init(B){const E=(B instanceof Element?B:document).querySelectorAll("[data-pagy]");for(let z of E)try{const G=Uint8Array.from(atob(z.getAttribute("data-pagy")),(L)=>L.charCodeAt(0)),[F,...K]=JSON.parse(new TextDecoder().decode(G));if(F==="nav")x(z,K);else if(F==="combo")A(z,K);else if(F==="selector")C(z,K);else console.warn("Skipped Pagy.init() for: %o\nUnknown keyword '%s'",z,F)}catch(G){console.warn("Skipped Pagy.init() for: %o\n%s",z,G)}}}})();
1
+ window.Pagy=(()=>{const j=new ResizeObserver((B)=>B.forEach((D)=>D.target.querySelectorAll(".pagy-rjs").forEach((E)=>E.pagyRender()))),x=(B,[D,E,z,G])=>{const F=B.parentElement??B,K=Object.keys(E).map((H)=>parseInt(H)).sort((H,M)=>M-H);let L=-1;const T=(H,M,R)=>H.replace(/__pagy_page__/g,M).replace(/__pagy_label__/g,R);if((B.pagyRender=function(){const H=K.find((Q)=>Q<F.clientWidth)||0;if(H===L)return;let M=D.before;const R=E[H.toString()],X=z?.[H.toString()]??R.map((Q)=>Q.toString());R.forEach((Q,J)=>{const $=X[J];let U;if(typeof Q==="number")U=T(D.a,Q.toString(),$);else if(Q==="gap")U=D.gap;else U=T(D.current,Q,$);M+=typeof G==="string"&&Q==1?Z(U,G):U}),M+=D.after,B.innerHTML="",B.insertAdjacentHTML("afterbegin",M),L=H})(),B.classList.contains("pagy-rjs"))j.observe(F)},A=(B,[D,E])=>Y(B,(z)=>[z,D.replace(/__pagy_page__/,z)],E),C=(B,[D,E,z])=>{Y(B,(G)=>{const F=Math.max(Math.ceil(D/parseInt(G)),1).toString(),K=E.replace(/__pagy_page__/,F).replace(/__pagy_limit__/,G);return[F,K]},z)},Y=(B,D,E)=>{const z=B.querySelector("input"),G=B.querySelector("a"),F=z.value,K=function(){if(z.value===F)return;const[L,T,H]=[z.min,z.value,z.max].map((X)=>parseInt(X)||0);if(T<L||T>H){z.value=F,z.select();return}let[M,R]=D(z.value);if(typeof E==="string"&&M==="1")R=Z(R,E);G.href=R,G.click()};["change","focus"].forEach((L)=>z.addEventListener(L,()=>z.select())),z.addEventListener("focusout",K),z.addEventListener("keypress",(L)=>{if(L.key==="Enter")K()})},Z=(B,D)=>B.replace(new RegExp(`[?&]${D}=1\\b(?!&)|\\b${D}=1&`),"");return{version:"9.2.1",init(B){const E=(B instanceof Element?B:document).querySelectorAll("[data-pagy]");for(let z of E)try{const G=Uint8Array.from(atob(z.getAttribute("data-pagy")),(L)=>L.charCodeAt(0)),[F,...K]=JSON.parse(new TextDecoder().decode(G));if(F==="nav")x(z,K);else if(F==="combo")A(z,K);else if(F==="selector")C(z,K);else console.warn("Skipped Pagy.init() for: %o\nUnknown keyword '%s'",z,F)}catch(G){console.warn("Skipped Pagy.init() for: %o\n%s",z,G)}}}})();
2
2
 
3
- //# debugId=E5B9B54DD869AFD664756E2164756E21
3
+ //# debugId=54B9F760378D0A6964756E2164756E21
4
4
  //# sourceMappingURL=pagy.min.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/pagy.ts"],
4
4
  "sourcesContent": [
5
- "type NavArgs = readonly [Tokens, Sequels, null | LabelSequels, string?]\ntype ComboArgs = readonly [string, string?]\ntype SelectorArgs = readonly [number, string, string?]\ntype JsonArgs = ['nav', NavArgs] | ['combo', ComboArgs] | ['selector', SelectorArgs]\n\ninterface Tokens {\n readonly before:string\n readonly a:string\n readonly current:string\n readonly gap:string\n readonly after:string\n}\ninterface Sequels {readonly [width:string]:(string | number)[]}\ninterface LabelSequels {readonly [width:string]:string[]}\ninterface NavElement extends Element {pagyRender():void}\n\nconst Pagy = (() => {\n // The observer instance for responsive navs\n const rjsObserver = new ResizeObserver(\n entries => entries.forEach(e => e.target.querySelectorAll<NavElement>(\".pagy-rjs\")\n .forEach(el => el.pagyRender())));\n // Init the *_nav_js helpers\n const initNav = (el:NavElement, [tokens, sequels, labelSequels, trimParam]:NavArgs) => {\n const container = el.parentElement ?? el;\n const widths = Object.keys(sequels).map(w => parseInt(w)).sort((a, b) => b - a);\n let lastWidth = -1;\n const fillIn = (a:string, page:string, label:string):string =>\n a.replace(/__pagy_page__/g, page).replace(/__pagy_label__/g, label);\n (el.pagyRender = function () {\n const width = widths.find(w => w < container.clientWidth) || 0;\n if (width === lastWidth) { return } // no change: abort\n let html = tokens.before; // already trimmed in html\n const series = sequels[width.toString()];\n const labels = labelSequels?.[width.toString()] ?? series.map(l => l.toString());\n series.forEach((item, i) => {\n const label = labels[i];\n let filled;\n if (typeof item === \"number\") {\n filled = fillIn(tokens.a, item.toString(), label);\n } else if (item === \"gap\") {\n filled = tokens.gap;\n } else { // active page\n filled = fillIn(tokens.current, item, label);\n }\n html += (typeof trimParam === \"string\" && item == 1) ? trim(filled, trimParam) : filled;\n });\n html += tokens.after;\n el.innerHTML = \"\";\n el.insertAdjacentHTML(\"afterbegin\", html);\n lastWidth = width;\n })();\n if (el.classList.contains(\"pagy-rjs\")) { rjsObserver.observe(container) }\n };\n\n // Init the *_combo_nav_js helpers\n const initCombo = (el:Element, [url_token, trimParam]:ComboArgs) =>\n initInput(el, inputValue => [inputValue, url_token.replace(/__pagy_page__/, inputValue)], trimParam);\n\n // Init the limit_selector_js helper\n const initSelector = (el:Element, [from, url_token, trimParam]:SelectorArgs) => {\n initInput(el, inputValue => {\n const page = Math.max(Math.ceil(from / parseInt(inputValue)), 1).toString();\n const url = url_token.replace(/__pagy_page__/, page).replace(/__pagy_limit__/, inputValue);\n return [page, url];\n }, trimParam);\n };\n\n // Init the input element\n const initInput = (el:Element, getVars:(v:string) => [string, string], trimParam?:string) => {\n const input = el.querySelector(\"input\") as HTMLInputElement;\n const link = el.querySelector(\"a\") as HTMLAnchorElement;\n const initial = input.value;\n const action = function () {\n if (input.value === initial) { return } // not changed\n const [min, val, max] = [input.min, input.value, input.max].map(n => parseInt(n) || 0);\n if (val < min || val > max) { // reset invalid/out-of-range\n input.value = initial;\n input.select();\n return;\n }\n let [page, url] = getVars(input.value); // eslint-disable-line prefer-const\n if (typeof trimParam === \"string\" && page === \"1\") { url = trim(url, trimParam) }\n link.href = url;\n link.click();\n };\n [\"change\", \"focus\"].forEach(e => input.addEventListener(e, () => input.select())); // auto-select\n input.addEventListener(\"focusout\", action); // trigger action\n input.addEventListener(\"keypress\", e => { if (e.key === \"Enter\") { action() } }); // trigger action\n };\n\n // Trim the ${page-param}=1 params in links\n const trim = (a:string, param:string) =>\n a.replace(new RegExp(`[?&]${param}=1\\\\b(?!&)|\\\\b${param}=1&`), \"\");\n\n // Public interface\n return {\n version: \"9.1.1\",\n\n // Scan for elements with a \"data-pagy\" attribute and call their init functions with the decoded args\n init(arg?:Element) {\n const target = arg instanceof Element ? arg : document;\n const elements = target.querySelectorAll(\"[data-pagy]\");\n for (const el of elements) {\n try {\n const uint8array = Uint8Array.from(atob(el.getAttribute(\"data-pagy\") as string), c => c.charCodeAt(0));\n const [keyword, ...args] = JSON.parse((new TextDecoder()).decode(uint8array)) as JsonArgs; // base64-utf8 -> JSON -> Array\n if (keyword === \"nav\") {\n initNav(el as NavElement, args as unknown as NavArgs);\n } else if (keyword === \"combo\") {\n initCombo(el, args as unknown as ComboArgs);\n } else if (keyword === \"selector\") {\n initSelector(el, args as unknown as SelectorArgs);\n } else {\n console.warn(\"Skipped Pagy.init() for: %o\\nUnknown keyword '%s'\", el, keyword);\n }\n } catch (err) { console.warn(\"Skipped Pagy.init() for: %o\\n%s\", el, err) }\n }\n }\n };\n})();\n\nexport default Pagy;\n"
5
+ "type NavArgs = readonly [Tokens, Sequels, null | LabelSequels, string?]\ntype ComboArgs = readonly [string, string?]\ntype SelectorArgs = readonly [number, string, string?]\ntype JsonArgs = ['nav', NavArgs] | ['combo', ComboArgs] | ['selector', SelectorArgs]\n\ninterface Tokens {\n readonly before:string\n readonly a:string\n readonly current:string\n readonly gap:string\n readonly after:string\n}\ninterface Sequels {readonly [width:string]:(string | number)[]}\ninterface LabelSequels {readonly [width:string]:string[]}\ninterface NavElement extends Element {pagyRender():void}\n\nconst Pagy = (() => {\n // The observer instance for responsive navs\n const rjsObserver = new ResizeObserver(\n entries => entries.forEach(e => e.target.querySelectorAll<NavElement>(\".pagy-rjs\")\n .forEach(el => el.pagyRender())));\n // Init the *_nav_js helpers\n const initNav = (el:NavElement, [tokens, sequels, labelSequels, trimParam]:NavArgs) => {\n const container = el.parentElement ?? el;\n const widths = Object.keys(sequels).map(w => parseInt(w)).sort((a, b) => b - a);\n let lastWidth = -1;\n const fillIn = (a:string, page:string, label:string):string =>\n a.replace(/__pagy_page__/g, page).replace(/__pagy_label__/g, label);\n (el.pagyRender = function () {\n const width = widths.find(w => w < container.clientWidth) || 0;\n if (width === lastWidth) { return } // no change: abort\n let html = tokens.before; // already trimmed in html\n const series = sequels[width.toString()];\n const labels = labelSequels?.[width.toString()] ?? series.map(l => l.toString());\n series.forEach((item, i) => {\n const label = labels[i];\n let filled;\n if (typeof item === \"number\") {\n filled = fillIn(tokens.a, item.toString(), label);\n } else if (item === \"gap\") {\n filled = tokens.gap;\n } else { // active page\n filled = fillIn(tokens.current, item, label);\n }\n html += (typeof trimParam === \"string\" && item == 1) ? trim(filled, trimParam) : filled;\n });\n html += tokens.after;\n el.innerHTML = \"\";\n el.insertAdjacentHTML(\"afterbegin\", html);\n lastWidth = width;\n })();\n if (el.classList.contains(\"pagy-rjs\")) { rjsObserver.observe(container) }\n };\n\n // Init the *_combo_nav_js helpers\n const initCombo = (el:Element, [url_token, trimParam]:ComboArgs) =>\n initInput(el, inputValue => [inputValue, url_token.replace(/__pagy_page__/, inputValue)], trimParam);\n\n // Init the limit_selector_js helper\n const initSelector = (el:Element, [from, url_token, trimParam]:SelectorArgs) => {\n initInput(el, inputValue => {\n const page = Math.max(Math.ceil(from / parseInt(inputValue)), 1).toString();\n const url = url_token.replace(/__pagy_page__/, page).replace(/__pagy_limit__/, inputValue);\n return [page, url];\n }, trimParam);\n };\n\n // Init the input element\n const initInput = (el:Element, getVars:(v:string) => [string, string], trimParam?:string) => {\n const input = el.querySelector(\"input\") as HTMLInputElement;\n const link = el.querySelector(\"a\") as HTMLAnchorElement;\n const initial = input.value;\n const action = function () {\n if (input.value === initial) { return } // not changed\n const [min, val, max] = [input.min, input.value, input.max].map(n => parseInt(n) || 0);\n if (val < min || val > max) { // reset invalid/out-of-range\n input.value = initial;\n input.select();\n return;\n }\n let [page, url] = getVars(input.value); // eslint-disable-line prefer-const\n if (typeof trimParam === \"string\" && page === \"1\") { url = trim(url, trimParam) }\n link.href = url;\n link.click();\n };\n [\"change\", \"focus\"].forEach(e => input.addEventListener(e, () => input.select())); // auto-select\n input.addEventListener(\"focusout\", action); // trigger action\n input.addEventListener(\"keypress\", e => { if (e.key === \"Enter\") { action() } }); // trigger action\n };\n\n // Trim the ${page-param}=1 params in links\n const trim = (a:string, param:string) =>\n a.replace(new RegExp(`[?&]${param}=1\\\\b(?!&)|\\\\b${param}=1&`), \"\");\n\n // Public interface\n return {\n version: \"9.2.1\",\n\n // Scan for elements with a \"data-pagy\" attribute and call their init functions with the decoded args\n init(arg?:Element) {\n const target = arg instanceof Element ? arg : document;\n const elements = target.querySelectorAll(\"[data-pagy]\");\n for (const el of elements) {\n try {\n const uint8array = Uint8Array.from(atob(el.getAttribute(\"data-pagy\") as string), c => c.charCodeAt(0));\n const [keyword, ...args] = JSON.parse((new TextDecoder()).decode(uint8array)) as JsonArgs; // base64-utf8 -> JSON -> Array\n if (keyword === \"nav\") {\n initNav(el as NavElement, args as unknown as NavArgs);\n } else if (keyword === \"combo\") {\n initCombo(el, args as unknown as ComboArgs);\n } else if (keyword === \"selector\") {\n initSelector(el, args as unknown as SelectorArgs);\n } else {\n console.warn(\"Skipped Pagy.init() for: %o\\nUnknown keyword '%s'\", el, keyword);\n }\n } catch (err) { console.warn(\"Skipped Pagy.init() for: %o\\n%s\", el, err) }\n }\n }\n };\n})();\n\nexport default Pagy;\n"
6
6
  ],
7
7
  "mappings": "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",
8
- "debugId": "E5B9B54DD869AFD664756E2164756E21",
8
+ "debugId": "54B9F760378D0A6964756E2164756E21",
9
9
  "names": []
10
10
  }
data/javascripts/pagy.mjs CHANGED
@@ -73,7 +73,7 @@ const Pagy = (() => {
73
73
  };
74
74
  const trim = (a, param) => a.replace(new RegExp(`[?&]${param}=1\\b(?!&)|\\b${param}=1&`), "");
75
75
  return {
76
- version: "9.1.1",
76
+ version: "9.2.1",
77
77
  init(arg) {
78
78
  const target = arg instanceof Element ? arg : document;
79
79
  const elements = target.querySelectorAll("[data-pagy]");
@@ -17,8 +17,8 @@ class Pagy
17
17
  end
18
18
  end
19
19
 
20
- # Filter out the already retrieved records
21
- def after_latest = @set.where(after_latest_query, **@latest)
20
+ # Filter the newest records
21
+ def filter_newest = @set.where(filter_newest_query, **@latest)
22
22
 
23
23
  # Append the missing keyset keys if the set is restricted by select
24
24
  def apply_select
@@ -26,8 +26,8 @@ class Pagy
26
26
  end
27
27
  end
28
28
 
29
- # Filter out the already retrieved records
30
- def after_latest = @set.where(::Sequel.lit(after_latest_query, **@latest))
29
+ # Filter the newest records
30
+ def filter_newest = @set.where(::Sequel.lit(filter_newest_query, **@latest))
31
31
 
32
32
  # Append the missing keyset keys if the set is restricted by select
33
33
  def apply_select
data/lib/pagy/keyset.rb CHANGED
@@ -56,11 +56,17 @@ class Pagy
56
56
  @next ||= B64.urlsafe_encode(latest_from(@records.last).to_json)
57
57
  end
58
58
 
59
- # Retrieve the array of records for the current page
59
+ # Fetch the array of records for the current page
60
60
  def records
61
61
  @records ||= begin
62
- @set = apply_select if select?
63
- @set = @vars[:after_latest]&.(@set, @latest) || after_latest if @latest
62
+ @set = apply_select if select?
63
+ if @latest
64
+ # :nocov:
65
+ @set = @vars[:after_latest]&.(@set, @latest) || # deprecated
66
+ # :nocov:
67
+ @vars[:filter_newest]&.(@set, @latest, @keyset) ||
68
+ filter_newest
69
+ end
64
70
  records = @set.limit(@limit + 1).to_a
65
71
  @more = records.size > @limit && !records.pop.nil?
66
72
  records
@@ -69,8 +75,8 @@ class Pagy
69
75
 
70
76
  protected
71
77
 
72
- # Prepare the literal query to filter out the already fetched records
73
- def after_latest_query
78
+ # Prepare the literal query to filter the newest records
79
+ def filter_newest_query
74
80
  operator = { asc: '>', desc: '<' }
75
81
  directions = @keyset.values
76
82
  if @vars[:tuple_comparison] && (directions.all?(:asc) || directions.all?(:desc))
data/lib/pagy.rb CHANGED
@@ -6,7 +6,7 @@ require_relative 'pagy/shared_methods'
6
6
 
7
7
  # Top superclass: it should define only what's common to all the subclasses
8
8
  class Pagy
9
- VERSION = '9.1.1'
9
+ VERSION = '9.2.1'
10
10
 
11
11
  # Core default: constant for easy access, but mutable for customizable defaults
12
12
  DEFAULT = { count_args: [:all], # rubocop:disable Style/MutableConstant
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pagy
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.1.1
4
+ version: 9.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-29 00:00:00.000000000 Z
11
+ date: 2024-11-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Agnostic pagination in plain ruby. It does it all. Better.
14
14
  email: