pagy 43.2.1 → 43.2.2
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/apps/calendar.ru +5 -6
- data/apps/demo.ru +1 -1
- data/apps/enable_rails_page_segment.rb +50 -0
- data/apps/keynav+root_key.ru +2 -6
- data/apps/keynav.ru +2 -6
- data/apps/keyset.ru +2 -6
- data/apps/keyset_sequel.ru +7 -7
- data/apps/rails.ru +6 -11
- data/apps/repro.ru +1 -1
- data/bin/pagy +2 -94
- data/config/pagy.rb +1 -1
- data/javascripts/pagy.js +2 -2
- data/javascripts/pagy.js.map +2 -2
- data/javascripts/pagy.min.js +1 -1
- data/javascripts/pagy.mjs +1 -1
- data/lib/pagy/cli.rb +156 -0
- data/lib/pagy.rb +1 -1
- metadata +3 -3
- data/apps/rails_page_segment.rb +0 -71
- data/lib/optimist.rb +0 -1022
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8e46eb6e0538fa7b8f8bf5eeffd17dbe5699a878a11fbb56c0d87e2d4cdf231
|
|
4
|
+
data.tar.gz: 5e7ff81fdefb19102cfd91e3b6863a98ef2b82a28b393e8a6ae9c9c6523a3637
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64d39bdf7be486eab1372e065dbed034f601b05d5293145f45c6b53e8098619ca5896332370566dd8a540d62781e055564005c61ee4f572bf613fea542effd75
|
|
7
|
+
data.tar.gz: 5c9a71e48ca7d133e7240ac587cd429a28875d0c8b857b426fa331c266fc66483884ba4e16cec7a03734ef2b3547a0ccce29f61dddd07436b715431c2b1a7796
|
data/apps/calendar.ru
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# URL
|
|
17
17
|
# http://127.0.0.1:8000
|
|
18
18
|
|
|
19
|
-
VERSION = '43.2.
|
|
19
|
+
VERSION = '43.2.2'
|
|
20
20
|
|
|
21
21
|
if VERSION != Pagy::VERSION
|
|
22
22
|
Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
|
|
@@ -190,15 +190,14 @@ end
|
|
|
190
190
|
|
|
191
191
|
# ActiveRecord setup
|
|
192
192
|
require 'active_record'
|
|
193
|
+
|
|
193
194
|
# Log
|
|
194
195
|
output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
|
|
195
196
|
ActiveRecord::Base.logger = Logger.new(output)
|
|
196
|
-
|
|
197
|
-
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
|
|
198
|
-
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
|
|
199
|
-
unless File.writable?(dir)
|
|
197
|
+
|
|
200
198
|
# Connection
|
|
201
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database:
|
|
199
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'file:memdb1?mode=memory&cache=shared')
|
|
200
|
+
|
|
202
201
|
Date.beginning_of_week = :monday # just for rails default compatibiity
|
|
203
202
|
# Groupdate initializer (https://github.com/ankane/groupdate)
|
|
204
203
|
# Groupdate week_start default is :sunday, while rails and pagy default to :monday
|
data/apps/demo.ru
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ################# IMPORTANT WARNING #################
|
|
4
|
+
# This setup forces Pagy to use the Rails `url_for` method,
|
|
5
|
+
# which is significantly slower (~20x) than Pagy's native URL generation.
|
|
6
|
+
|
|
7
|
+
# Use this file ONLY if you absolutely want to support the page param as a dynamic segment.
|
|
8
|
+
# (e.g. get '/comments(/:page)', to: 'comments#index').
|
|
9
|
+
# #####################################################
|
|
10
|
+
|
|
11
|
+
# USAGE
|
|
12
|
+
|
|
13
|
+
# initializers/pagy.rb
|
|
14
|
+
# require Pagy::ROOT.join('apps/enable_rails_page_segment.rb')
|
|
15
|
+
|
|
16
|
+
# config/routes.rb (example)
|
|
17
|
+
# get '/comments(/:page)', to: 'comments#index'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Use plain Strings tokens instead of Pagy::EscapedValue strings
|
|
21
|
+
Pagy.send(:remove_const, :PAGE_TOKEN) ; Pagy::PAGE_TOKEN = '___PAGY_PAGE___'
|
|
22
|
+
Pagy.send(:remove_const, :LIMIT_TOKEN) ; Pagy::LIMIT_TOKEN = '___PAGY_LIMIT___'
|
|
23
|
+
|
|
24
|
+
# Require the pagy sources to override
|
|
25
|
+
require_relative '../lib/pagy/toolbox/paginators/method'
|
|
26
|
+
require_relative '../lib/pagy/modules/abilities/linkable'
|
|
27
|
+
|
|
28
|
+
class Pagy
|
|
29
|
+
# Switch to the `request.params` to get access to rails-added path parameters
|
|
30
|
+
module RequestOverride
|
|
31
|
+
def get_params(request) = request.params
|
|
32
|
+
end
|
|
33
|
+
Request.prepend RequestOverride
|
|
34
|
+
|
|
35
|
+
# Inject the caller context into the Pagy instance
|
|
36
|
+
module MethodOverride
|
|
37
|
+
def pagy(...) = super.tap { |result| result[0].instance_variable_set(:@context, self) }
|
|
38
|
+
end
|
|
39
|
+
Method.prepend MethodOverride
|
|
40
|
+
|
|
41
|
+
# Compose the final URL using `url_for`.
|
|
42
|
+
module LinkableOverride
|
|
43
|
+
def compose_url(absolute, _path, params, fragment)
|
|
44
|
+
params[:anchor] = fragment if fragment
|
|
45
|
+
params[:only_path] = !absolute
|
|
46
|
+
@context.url_for(params)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
Linkable.prepend LinkableOverride
|
|
50
|
+
end
|
data/apps/keynav+root_key.ru
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# URL
|
|
17
17
|
# http://127.0.0.1:8000
|
|
18
18
|
|
|
19
|
-
VERSION = '43.2.
|
|
19
|
+
VERSION = '43.2.2'
|
|
20
20
|
|
|
21
21
|
if VERSION != Pagy::VERSION
|
|
22
22
|
Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
|
|
@@ -239,12 +239,8 @@ require 'active_record'
|
|
|
239
239
|
# Log
|
|
240
240
|
output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
|
|
241
241
|
ActiveRecord::Base.logger = Logger.new(output)
|
|
242
|
-
# SQLite DB files
|
|
243
|
-
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
|
|
244
|
-
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
|
|
245
|
-
unless File.writable?(dir)
|
|
246
242
|
# Connection
|
|
247
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database:
|
|
243
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'file:memdb1?mode=memory&cache=shared')
|
|
248
244
|
# Schema
|
|
249
245
|
ActiveRecord::Schema.define do
|
|
250
246
|
create_table :pets, force: true do |t|
|
data/apps/keynav.ru
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# URL
|
|
17
17
|
# http://127.0.0.1:8000
|
|
18
18
|
|
|
19
|
-
VERSION = '43.2.
|
|
19
|
+
VERSION = '43.2.2'
|
|
20
20
|
|
|
21
21
|
if VERSION != Pagy::VERSION
|
|
22
22
|
Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
|
|
@@ -178,12 +178,8 @@ require 'active_record'
|
|
|
178
178
|
# Log
|
|
179
179
|
output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
|
|
180
180
|
ActiveRecord::Base.logger = Logger.new(output)
|
|
181
|
-
# SQLite DB files
|
|
182
|
-
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
|
|
183
|
-
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
|
|
184
|
-
unless File.writable?(dir)
|
|
185
181
|
# Connection
|
|
186
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database:
|
|
182
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'file:memdb1?mode=memory&cache=shared')
|
|
187
183
|
# Schema
|
|
188
184
|
ActiveRecord::Schema.define do
|
|
189
185
|
create_table :pets, force: true do |t|
|
data/apps/keyset.ru
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# URL
|
|
17
17
|
# http://127.0.0.1:8000
|
|
18
18
|
|
|
19
|
-
VERSION = '43.2.
|
|
19
|
+
VERSION = '43.2.2'
|
|
20
20
|
|
|
21
21
|
if VERSION != Pagy::VERSION
|
|
22
22
|
Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
|
|
@@ -140,12 +140,8 @@ require 'active_record'
|
|
|
140
140
|
# Log
|
|
141
141
|
output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
|
|
142
142
|
ActiveRecord::Base.logger = Logger.new(output)
|
|
143
|
-
# SQLite DB files
|
|
144
|
-
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
|
|
145
|
-
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
|
|
146
|
-
unless File.writable?(dir)
|
|
147
143
|
# Connection
|
|
148
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database:
|
|
144
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'file:memdb1?mode=memory&cache=shared')
|
|
149
145
|
# Schema
|
|
150
146
|
ActiveRecord::Schema.define do
|
|
151
147
|
create_table :pets, force: true do |t|
|
data/apps/keyset_sequel.ru
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# URL
|
|
17
17
|
# http://127.0.0.1:8000
|
|
18
18
|
|
|
19
|
-
VERSION = '43.2.
|
|
19
|
+
VERSION = '43.2.2'
|
|
20
20
|
|
|
21
21
|
if VERSION != Pagy::VERSION
|
|
22
22
|
Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
|
|
@@ -133,14 +133,14 @@ end
|
|
|
133
133
|
# Sequel setup
|
|
134
134
|
require 'sequel'
|
|
135
135
|
Sequel.default_timezone = :utc
|
|
136
|
-
# SQLite DB files
|
|
137
|
-
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
|
|
138
|
-
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
|
|
139
|
-
unless File.writable?(dir)
|
|
140
136
|
# Connection
|
|
141
137
|
output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
# Use 'sqlite' adapter (Sequel uses 'sqlite', AR uses 'sqlite3')
|
|
139
|
+
# Use the same shared memory URI string for the database
|
|
140
|
+
DB = Sequel.connect(adapter: 'sqlite',
|
|
141
|
+
database: 'file:memdb1?mode=memory&cache=shared',
|
|
142
|
+
max_connections: 10,
|
|
143
|
+
loggers: [Logger.new(output)])
|
|
144
144
|
# Schema
|
|
145
145
|
DB.create_table! :pets do
|
|
146
146
|
primary_key :id
|
data/apps/rails.ru
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# URL
|
|
17
17
|
# http://127.0.0.1:8000
|
|
18
18
|
|
|
19
|
-
VERSION = '43.2.
|
|
19
|
+
VERSION = '43.2.2'
|
|
20
20
|
|
|
21
21
|
if VERSION != Pagy::VERSION
|
|
22
22
|
Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
|
|
@@ -35,7 +35,6 @@ end
|
|
|
35
35
|
# require 'rails/all' # too much stuff
|
|
36
36
|
require 'action_controller/railtie'
|
|
37
37
|
require 'active_record'
|
|
38
|
-
# require_relative 'rails_page_segment' # Uncomment to test the rails_page_segment.rb override
|
|
39
38
|
|
|
40
39
|
OUTPUT = Rails.env.showcase? ? IO::NULL : $stdout
|
|
41
40
|
|
|
@@ -48,23 +47,19 @@ class PagyRails < Rails::Application # :nodoc:
|
|
|
48
47
|
config.logger = Logger.new(OUTPUT)
|
|
49
48
|
Rails.logger = config.logger
|
|
50
49
|
|
|
50
|
+
# Pagy initializer
|
|
51
|
+
# require Pagy::ROOT.join('apps/enable_rails_page_segment.rb') # Uncomment to test the enable_rails_page_segment.rb override
|
|
52
|
+
|
|
51
53
|
routes.draw do
|
|
52
54
|
root to: 'comments#index'
|
|
53
|
-
# get '/comments(/:page)', to: 'comments#index' # Uncomment to test the
|
|
55
|
+
# get '/comments(/:page)', to: 'comments#index' # Uncomment to test the enable_rails_page_segment.rb override
|
|
54
56
|
get '/javascripts/:file', to: 'pagy#javascripts', file: /.*/
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
|
|
58
|
-
# AR config
|
|
59
|
-
dir = Rails.env.development? ? '.' : Dir.pwd # app dir in dev or pwd otherwise
|
|
60
|
-
unless File.writable?(dir)
|
|
61
|
-
warn "ERROR: directory #{dir.inspect} is not writable (the pagy-rails-app needs to create DB files)"
|
|
62
|
-
exit 1
|
|
63
|
-
end
|
|
64
|
-
|
|
65
60
|
# Activerecord initializer
|
|
66
61
|
ActiveRecord::Base.logger = Logger.new(OUTPUT)
|
|
67
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database:
|
|
62
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'file:memdb1?mode=memory&cache=shared')
|
|
68
63
|
ActiveRecord::Schema.define do
|
|
69
64
|
create_table :posts, force: true do |t|
|
|
70
65
|
t.string :title
|
data/apps/repro.ru
CHANGED
data/bin/pagy
CHANGED
|
@@ -1,97 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
HOST = 'localhost'
|
|
7
|
-
PORT = '8000'
|
|
8
|
-
|
|
9
|
-
require_relative '../lib/optimist'
|
|
10
|
-
require_relative '../lib/pagy'
|
|
11
|
-
require_relative '../apps/index'
|
|
12
|
-
|
|
13
|
-
apps = PagyApps::INDEX
|
|
14
|
-
opts = Optimist.options do
|
|
15
|
-
text <<~HEAD
|
|
16
|
-
Pagy #{VERSION} (https://ddnexus.github.io/pagy/playground)
|
|
17
|
-
Playground to showcase, clone and develop pagy APPs
|
|
18
|
-
APPs
|
|
19
|
-
#{ apps.map do |name, path|
|
|
20
|
-
" #{name}#{' ' * (18 - name.length)}#{File.readlines(path)[3].sub('# ', '').strip}"
|
|
21
|
-
end.join("\n") }
|
|
22
|
-
USAGE
|
|
23
|
-
pagy APP [opts] Showcase APP from the installed gem
|
|
24
|
-
pagy clone APP Clone APP to the current dir
|
|
25
|
-
pagy FILE [opts] Develop app FILE from local path
|
|
26
|
-
EXAMPLES
|
|
27
|
-
pagy demo Showcase demo at http://#{HOST}:#{PORT}
|
|
28
|
-
pagy clone repro Clone repro to ./repro.ru (rename it)
|
|
29
|
-
pagy ~/myapp.ru Develop ~/myapp.ru at #{HOST}:#{PORT}
|
|
30
|
-
HEAD
|
|
31
|
-
text 'Rackup options'
|
|
32
|
-
opt :env, 'Environment', default: 'development'
|
|
33
|
-
opt :host, 'Host', default: HOST, short: :o
|
|
34
|
-
opt :port, 'Port', default: PORT
|
|
35
|
-
if LINUX
|
|
36
|
-
text 'Rerun options'
|
|
37
|
-
opt :rerun, 'Enable rerun for development'
|
|
38
|
-
opt :clear, 'Clear screen before each rerun'
|
|
39
|
-
end
|
|
40
|
-
text 'Other options'
|
|
41
|
-
opt :quiet, 'Quiet mode for development'
|
|
42
|
-
version VERSION
|
|
43
|
-
end
|
|
44
|
-
Optimist.educate if ARGV.empty?
|
|
45
|
-
|
|
46
|
-
run_from_repo = Pagy::ROOT.join('pagy.gemspec').exist?
|
|
47
|
-
|
|
48
|
-
# Bundle
|
|
49
|
-
require 'bundler/inline'
|
|
50
|
-
gemfile(!run_from_repo) do
|
|
51
|
-
source 'https://rubygems.org'
|
|
52
|
-
gem 'logger'
|
|
53
|
-
gem 'rackup'
|
|
54
|
-
gem 'rerun' if LINUX
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
arg = ARGV.shift
|
|
58
|
-
if arg.eql?('clone')
|
|
59
|
-
name = ARGV.shift
|
|
60
|
-
Optimist.die("Expected APP to be in [#{apps.keys.join(', ')}]; got #{name.inspect}") unless apps.key?(name)
|
|
61
|
-
if File.exist?(name)
|
|
62
|
-
print "Do you want to overwrite the #{name.inspect} file? (y/n)> "
|
|
63
|
-
answer = gets.chomp
|
|
64
|
-
Optimist.die("#{name.inspect} file already present") unless answer.start_with?(/y/i)
|
|
65
|
-
end
|
|
66
|
-
require 'fileutils'
|
|
67
|
-
FileUtils.cp(apps[name], '.', verbose: true)
|
|
68
|
-
else
|
|
69
|
-
if apps.key?(arg) # showcase env
|
|
70
|
-
opts[:env] = 'showcase'
|
|
71
|
-
opts[:rerun] = false
|
|
72
|
-
opts[:quiet] = true
|
|
73
|
-
# Avoid the creation of './tmp/local_secret.txt' for showcase env
|
|
74
|
-
ENV['SECRET_KEY_BASE'] = 'absolute secret!' if arg.eql?('rails')
|
|
75
|
-
file = apps[arg]
|
|
76
|
-
else # development env
|
|
77
|
-
file = arg
|
|
78
|
-
end
|
|
79
|
-
Optimist.die("#{file.inspect} app not found") unless File.exist?(file)
|
|
80
|
-
# Run command
|
|
81
|
-
gem_dir = File.expand_path('..', __dir__)
|
|
82
|
-
rackup = "rackup -I #{gem_dir}/lib -r pagy -o #{opts[:host]} -p #{opts[:port]} -E #{opts[:env]} #{file}"
|
|
83
|
-
rackup << ' -q' if opts[:quiet]
|
|
84
|
-
if opts[:rerun]
|
|
85
|
-
name = File.basename(file)
|
|
86
|
-
dir = File.dirname(file)
|
|
87
|
-
rerun = if run_from_repo # rerun app also when gem dir files change (for pagy devs)
|
|
88
|
-
"rerun --name #{name} -d #{dir},#{gem_dir} -p **/*.{rb,js,css,scss,ru,yml}"
|
|
89
|
-
else
|
|
90
|
-
"rerun --name #{name} -d #{dir} -p #{name}" # rerun only when app.ru changes
|
|
91
|
-
end
|
|
92
|
-
rerun << ' -q' if opts[:quiet]
|
|
93
|
-
rerun << ' -c' if opts[:clear]
|
|
94
|
-
rerun << " -- #{rackup}"
|
|
95
|
-
end
|
|
96
|
-
exec(rerun || rackup)
|
|
97
|
-
end
|
|
4
|
+
require_relative '../lib/pagy/cli'
|
|
5
|
+
Pagy::CLI.new.start
|
data/config/pagy.rb
CHANGED
data/javascripts/pagy.js
CHANGED
|
@@ -126,7 +126,7 @@ window.Pagy = (() => {
|
|
|
126
126
|
});
|
|
127
127
|
};
|
|
128
128
|
return {
|
|
129
|
-
version: "43.2.
|
|
129
|
+
version: "43.2.2",
|
|
130
130
|
init(arg) {
|
|
131
131
|
const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
|
|
132
132
|
for (const element of elements) {
|
|
@@ -150,5 +150,5 @@ window.Pagy = (() => {
|
|
|
150
150
|
};
|
|
151
151
|
})();
|
|
152
152
|
|
|
153
|
-
//# debugId=
|
|
153
|
+
//# debugId=98E0CC9BFBB1A85D64756E2164756E21
|
|
154
154
|
//# sourceMappingURL=pagy.js.map
|
data/javascripts/pagy.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/pagy.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"interface SyncData {\n from?: number\n to?: number\n key: string\n str?: string\n}\ntype InitArgs = [\"k\", KeynavArgs] | // series_nav[_js] with keynav instance\n [\"snj\", SeriesNavJsArgs] | // series_nav_js\n [\"inj\", InputNavJsArgs] | // input_nav_js\n [\"ltj\", LimitTagJsArgs] // limit_tag_js\ntype AugmentKeynav = (nav:HTMLElement, keynavArgs:KeynavArgs) => Promise<((page: string) => string)>\ntype KeynavArgs = readonly [storageKey: string | null,\n rootKey: string | null,\n pageKey: string,\n last: number,\n spliceArgs?: SpliceArgs]\ntype SpliceArgs = readonly [start: number,\n deleteCount: number, // it would be optional, but ts complains\n ...items: Cutoff[]]\ntype Cutoff = readonly (string | number | boolean)[]\ntype AugmentedPage = [browserId: string,\n storageKey: string,\n pageNumber: number,\n pages: number,\n priorCutoff: Cutoff | null,\n pageCutoff: Cutoff | null]\ntype SeriesNavJsArgs = readonly [NavJsTokens, pageToken: string, NavJsSeries, KeynavArgs?]\ntype NavJsSeries = readonly [widths: number[],\n series: (string | number)[][],\n labels: string[][] | null]\ntype InputNavJsArgs = readonly [urlToken: string,\n pageToken: string,\n KeynavArgs?]\ntype LimitTagJsArgs = readonly [from: number,\n urlToken: string,\n pageToken: string,\n limitToken: string]\ntype NavJsTokens = readonly [before: string,\n anchor: string,\n current: string,\n gap: string,\n after: string]\ninterface NavJsElement extends HTMLElement {\n render(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst Pagy = (() => {\n const storageSupport = 'sessionStorage' in window && 'BroadcastChannel' in window;\n // eslint-disable-next-line prefer-const\n let pagy = \"pagy\", storage: Storage, sync: BroadcastChannel, tabId: number;\n if (storageSupport) {\n storage = sessionStorage; // shorten the compiled size\n sync = new BroadcastChannel(pagy);\n tabId = Date.now();\n // Sync the sessionStorage keys for the cutoffs opened in a new tab/window\n sync.addEventListener(\"message\", (e:MessageEvent<SyncData>) => {\n if (e.data.from) { // request cutoffs\n const cutoffs = storage.getItem(e.data.key);\n if (cutoffs) {\n sync.postMessage(<SyncData>{to: e.data.from, key: e.data.key, str: cutoffs});\n } // send response\n } else if (e.data.to) { // receive cutoffs\n if (e.data.to == tabId) {\n storage.setItem(e.data.key, <string>e.data.str);\n }\n }\n });\n }\n // The observer instance for responsive navs\n const rjsObserver = new ResizeObserver(\n entries => entries.forEach(e => {\n e.target.querySelectorAll<NavJsElement>(\".pagy-rjs\").forEach(el => el.render());\n }));\n\n /* Full set of B64 functions\n const B64Encode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode))),\n B64Safe = (unsafe:string) => unsafe.replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64SafeEncode = (unicode:string) => B64Safe(B64Encode(unicode)),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0))),\n B64Unsafe = (safe:string) => safe.replace(/[-_]/g, (match) => match == \"-\" ? \"+\" : \"/\"),\n B64SafeDecode = (base64:string) => B64Decode(B64Unsafe(base64))\n */\n const B64SafeEncode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode)))\n .replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));\n\n // Return a random key: 3 chars max, base-36 number < 36**3\n const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);\n\n // Manage the page augmentation for Keynav, called only if storageSupport\n const augmentKeynav: AugmentKeynav = async (nav, [storageKey, rootKey, pageKey, last, spliceArgs]) => {\n let augmentPage:(page: string) => string;\n const browserKey = document.cookie.split(/;\\s+/) // it works even if malformed\n .find((row) => row.startsWith(pagy + \"=\"))\n ?.split(\"=\")[1] ?? randKey();\n document.cookie = pagy + \"=\" + browserKey; // Smaller .min size: set the cookie without checking\n if (storageKey && !(storageKey in storage)) {\n // Sync the sessiongStorage from other tabs/windows (e.g., open page in the new tab/window)\n sync.postMessage(<SyncData>{ from: tabId, key: storageKey });\n // Wait for the listener to copy the cutoffs in the current sessionStorage\n await new Promise<string|null>((resolve) => setTimeout(() => resolve(\"\"), 100));\n if (!(storageKey in storage)) { // the storageKey didn't get copied: fallback to countless pagination\n augmentPage = (page: string) => page + '+' + last;\n }\n }\n // @ts-expect-error If it is not assigned it means it supports keynav\n if (!augmentPage) { // regular keynav pagination\n if (!storageKey) { do { storageKey = randKey() } while (storageKey in storage) } // no dup keys\n const data = storage.getItem(storageKey),\n cutoffs = <Cutoff[]>(data ? JSON.parse(data) : [undefined]);\n if (spliceArgs) {\n cutoffs.splice(...spliceArgs);\n storage.setItem(storageKey, JSON.stringify(cutoffs));\n }\n // Augment function\n augmentPage = (page:string) => {\n const pageNum = parseInt(page);\n return B64SafeEncode(JSON.stringify(\n <AugmentedPage>[browserKey,\n storageKey,\n pageNum,\n cutoffs.length, // pages/last\n cutoffs[pageNum - 1], // priorCutoff\n cutoffs[pageNum]])); // pageCutoff\n };\n }\n const search = (rootKey) ? `${rootKey}%5B${pageKey}%5D` : pageKey;\n const re = new RegExp(`(?<=\\\\?.*)(\\\\b${search}=)(\\\\d+)`);\n // Augment the page param of each href\n for (const a of <NodeListOf<HTMLAnchorElement>><unknown>nav.querySelectorAll('a[href]')) {\n a.href = a.href.replace(re, (_match, prefix, digit): string => `${prefix}${augmentPage(<string>digit)}`);\n }\n // Return the augment function for further augmentation (i.e., url token in input_nav_js)\n return augmentPage;\n };\n\n // Build the series_nav_js helper\n const buildNavJs = (nav:NavJsElement, [[before, anchor, current, gap, after], pageToken,\n [widths, series, labels], keynavArgs]:SeriesNavJsArgs) => {\n const parent = <HTMLElement>nav.parentElement;\n let lastWidth = -1;\n (nav.render = () => {\n const index = widths.findIndex(w => w < parent.clientWidth);\n if (widths[index] === lastWidth) { return } // no change: abort\n\n let html = before;\n series[index].forEach((item, i) => {\n // Avoid the if blocks and chain the results (shorter pagy.min.js and easier reading)\n html += item == \"gap\" ? gap :\n // @ts-expect-error the item may be a number, but the 'replace' converts it to string (shorter pagy.min.js)\n (typeof item == \"number\" ? anchor.replace(pageToken, item) : current)\n .replace(\"L<\", labels?.[index][i] ?? item + \"<\");\n });\n html += after;\n nav.innerHTML = \"\";\n nav.insertAdjacentHTML(\"afterbegin\", html);\n lastWidth = widths[index];\n if (keynavArgs && storageSupport) { void augmentKeynav(nav, keynavArgs) }\n })();\n if (nav.classList.contains(pagy + \"-rjs\")) { rjsObserver.observe(parent) }\n };\n\n // Init the input_nav_js helpers\n const initInputNavJs = async (nav:HTMLElement, [url_token, pageToken, keynavArgs]:InputNavJsArgs) => {\n const augment = keynavArgs && storageSupport\n ? await augmentKeynav(nav, keynavArgs)\n : (page: string) => page;\n initInput(nav, inputValue => url_token.replace(pageToken, augment(inputValue)));\n };\n\n // Init the limit_tag_js helper\n const initLimitTagJs = (span:HTMLSpanElement, [from, url_token, page_token, limitToken]:LimitTagJsArgs) => {\n initInput(span, inputValue => {\n // @ts-expect-error the page is a number, but the 'replace' converts it to string (shorter pagy.min.js)\n return url_token.replace(page_token, Math.max(Math.ceil(from / parseInt(inputValue)), 1))\n .replace(limitToken, inputValue);\n });\n };\n\n // Init the input element\n const initInput = (element:HTMLElement, getUrl:(v:string) => string) => {\n const input = <HTMLInputElement>element.querySelector(\"input\"),\n link = <HTMLAnchorElement>element.querySelector(\"a\"),\n initial = input.value,\n action = () => {\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 link.href = getUrl(input.value);\n link.click();\n };\n input.addEventListener(\"focus\", () => input.select());\n input.addEventListener(\"focusout\", action);\n input.addEventListener(\"keypress\", e => { if (e.key == \"Enter\") { action() } });\n };\n\n // Public interface\n return {\n version: \"43.2.1\",\n\n // Scan for elements with a \"data-pagy\" attribute and call their init functions with the decoded args\n init(arg?:HTMLElement) {\n const target = arg instanceof HTMLElement ? arg : document,\n elements = target.querySelectorAll(\"[data-pagy]\");\n for (const element of <NodeListOf<HTMLElement>>elements) {\n try {\n const [helperId, ...args] = <InitArgs>JSON.parse(B64Decode(<string>element.getAttribute(\"data-pagy\")));\n if (helperId == \"k\") {\n // @ts-expect-error spread 2 arguments, not 3 as it complains about\n void augmentKeynav(element, ...<KeynavArgs><unknown>args);\n } else if (helperId == \"snj\") {\n buildNavJs(<NavJsElement>element, <SeriesNavJsArgs><unknown>args);\n } else if (helperId == \"inj\") {\n void initInputNavJs(element, <InputNavJsArgs><unknown>args);\n } else if (helperId == \"ltj\") {\n initLimitTagJs(element, <LimitTagJsArgs><unknown>args);\n }\n // else { console.warn(\"Pagy.init: %o\\nUnknown helperId '%s'\", element, helperId) }\n } catch (err) { console.warn(\"Pagy.init: %o\\n%s\", element, err) }\n }\n }\n };\n})();\n"
|
|
5
|
+
"interface SyncData {\n from?: number\n to?: number\n key: string\n str?: string\n}\ntype InitArgs = [\"k\", KeynavArgs] | // series_nav[_js] with keynav instance\n [\"snj\", SeriesNavJsArgs] | // series_nav_js\n [\"inj\", InputNavJsArgs] | // input_nav_js\n [\"ltj\", LimitTagJsArgs] // limit_tag_js\ntype AugmentKeynav = (nav:HTMLElement, keynavArgs:KeynavArgs) => Promise<((page: string) => string)>\ntype KeynavArgs = readonly [storageKey: string | null,\n rootKey: string | null,\n pageKey: string,\n last: number,\n spliceArgs?: SpliceArgs]\ntype SpliceArgs = readonly [start: number,\n deleteCount: number, // it would be optional, but ts complains\n ...items: Cutoff[]]\ntype Cutoff = readonly (string | number | boolean)[]\ntype AugmentedPage = [browserId: string,\n storageKey: string,\n pageNumber: number,\n pages: number,\n priorCutoff: Cutoff | null,\n pageCutoff: Cutoff | null]\ntype SeriesNavJsArgs = readonly [NavJsTokens, pageToken: string, NavJsSeries, KeynavArgs?]\ntype NavJsSeries = readonly [widths: number[],\n series: (string | number)[][],\n labels: string[][] | null]\ntype InputNavJsArgs = readonly [urlToken: string,\n pageToken: string,\n KeynavArgs?]\ntype LimitTagJsArgs = readonly [from: number,\n urlToken: string,\n pageToken: string,\n limitToken: string]\ntype NavJsTokens = readonly [before: string,\n anchor: string,\n current: string,\n gap: string,\n after: string]\ninterface NavJsElement extends HTMLElement {\n render(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst Pagy = (() => {\n const storageSupport = 'sessionStorage' in window && 'BroadcastChannel' in window;\n // eslint-disable-next-line prefer-const\n let pagy = \"pagy\", storage: Storage, sync: BroadcastChannel, tabId: number;\n if (storageSupport) {\n storage = sessionStorage; // shorten the compiled size\n sync = new BroadcastChannel(pagy);\n tabId = Date.now();\n // Sync the sessionStorage keys for the cutoffs opened in a new tab/window\n sync.addEventListener(\"message\", (e:MessageEvent<SyncData>) => {\n if (e.data.from) { // request cutoffs\n const cutoffs = storage.getItem(e.data.key);\n if (cutoffs) {\n sync.postMessage(<SyncData>{to: e.data.from, key: e.data.key, str: cutoffs});\n } // send response\n } else if (e.data.to) { // receive cutoffs\n if (e.data.to == tabId) {\n storage.setItem(e.data.key, <string>e.data.str);\n }\n }\n });\n }\n // The observer instance for responsive navs\n const rjsObserver = new ResizeObserver(\n entries => entries.forEach(e => {\n e.target.querySelectorAll<NavJsElement>(\".pagy-rjs\").forEach(el => el.render());\n }));\n\n /* Full set of B64 functions\n const B64Encode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode))),\n B64Safe = (unsafe:string) => unsafe.replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64SafeEncode = (unicode:string) => B64Safe(B64Encode(unicode)),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0))),\n B64Unsafe = (safe:string) => safe.replace(/[-_]/g, (match) => match == \"-\" ? \"+\" : \"/\"),\n B64SafeDecode = (base64:string) => B64Decode(B64Unsafe(base64))\n */\n const B64SafeEncode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode)))\n .replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));\n\n // Return a random key: 3 chars max, base-36 number < 36**3\n const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);\n\n // Manage the page augmentation for Keynav, called only if storageSupport\n const augmentKeynav: AugmentKeynav = async (nav, [storageKey, rootKey, pageKey, last, spliceArgs]) => {\n let augmentPage:(page: string) => string;\n const browserKey = document.cookie.split(/;\\s+/) // it works even if malformed\n .find((row) => row.startsWith(pagy + \"=\"))\n ?.split(\"=\")[1] ?? randKey();\n document.cookie = pagy + \"=\" + browserKey; // Smaller .min size: set the cookie without checking\n if (storageKey && !(storageKey in storage)) {\n // Sync the sessiongStorage from other tabs/windows (e.g., open page in the new tab/window)\n sync.postMessage(<SyncData>{ from: tabId, key: storageKey });\n // Wait for the listener to copy the cutoffs in the current sessionStorage\n await new Promise<string|null>((resolve) => setTimeout(() => resolve(\"\"), 100));\n if (!(storageKey in storage)) { // the storageKey didn't get copied: fallback to countless pagination\n augmentPage = (page: string) => page + '+' + last;\n }\n }\n // @ts-expect-error If it is not assigned it means it supports keynav\n if (!augmentPage) { // regular keynav pagination\n if (!storageKey) { do { storageKey = randKey() } while (storageKey in storage) } // no dup keys\n const data = storage.getItem(storageKey),\n cutoffs = <Cutoff[]>(data ? JSON.parse(data) : [undefined]);\n if (spliceArgs) {\n cutoffs.splice(...spliceArgs);\n storage.setItem(storageKey, JSON.stringify(cutoffs));\n }\n // Augment function\n augmentPage = (page:string) => {\n const pageNum = parseInt(page);\n return B64SafeEncode(JSON.stringify(\n <AugmentedPage>[browserKey,\n storageKey,\n pageNum,\n cutoffs.length, // pages/last\n cutoffs[pageNum - 1], // priorCutoff\n cutoffs[pageNum]])); // pageCutoff\n };\n }\n const search = (rootKey) ? `${rootKey}%5B${pageKey}%5D` : pageKey;\n const re = new RegExp(`(?<=\\\\?.*)(\\\\b${search}=)(\\\\d+)`);\n // Augment the page param of each href\n for (const a of <NodeListOf<HTMLAnchorElement>><unknown>nav.querySelectorAll('a[href]')) {\n a.href = a.href.replace(re, (_match, prefix, digit): string => `${prefix}${augmentPage(<string>digit)}`);\n }\n // Return the augment function for further augmentation (i.e., url token in input_nav_js)\n return augmentPage;\n };\n\n // Build the series_nav_js helper\n const buildNavJs = (nav:NavJsElement, [[before, anchor, current, gap, after], pageToken,\n [widths, series, labels], keynavArgs]:SeriesNavJsArgs) => {\n const parent = <HTMLElement>nav.parentElement;\n let lastWidth = -1;\n (nav.render = () => {\n const index = widths.findIndex(w => w < parent.clientWidth);\n if (widths[index] === lastWidth) { return } // no change: abort\n\n let html = before;\n series[index].forEach((item, i) => {\n // Avoid the if blocks and chain the results (shorter pagy.min.js and easier reading)\n html += item == \"gap\" ? gap :\n // @ts-expect-error the item may be a number, but the 'replace' converts it to string (shorter pagy.min.js)\n (typeof item == \"number\" ? anchor.replace(pageToken, item) : current)\n .replace(\"L<\", labels?.[index][i] ?? item + \"<\");\n });\n html += after;\n nav.innerHTML = \"\";\n nav.insertAdjacentHTML(\"afterbegin\", html);\n lastWidth = widths[index];\n if (keynavArgs && storageSupport) { void augmentKeynav(nav, keynavArgs) }\n })();\n if (nav.classList.contains(pagy + \"-rjs\")) { rjsObserver.observe(parent) }\n };\n\n // Init the input_nav_js helpers\n const initInputNavJs = async (nav:HTMLElement, [url_token, pageToken, keynavArgs]:InputNavJsArgs) => {\n const augment = keynavArgs && storageSupport\n ? await augmentKeynav(nav, keynavArgs)\n : (page: string) => page;\n initInput(nav, inputValue => url_token.replace(pageToken, augment(inputValue)));\n };\n\n // Init the limit_tag_js helper\n const initLimitTagJs = (span:HTMLSpanElement, [from, url_token, page_token, limitToken]:LimitTagJsArgs) => {\n initInput(span, inputValue => {\n // @ts-expect-error the page is a number, but the 'replace' converts it to string (shorter pagy.min.js)\n return url_token.replace(page_token, Math.max(Math.ceil(from / parseInt(inputValue)), 1))\n .replace(limitToken, inputValue);\n });\n };\n\n // Init the input element\n const initInput = (element:HTMLElement, getUrl:(v:string) => string) => {\n const input = <HTMLInputElement>element.querySelector(\"input\"),\n link = <HTMLAnchorElement>element.querySelector(\"a\"),\n initial = input.value,\n action = () => {\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 link.href = getUrl(input.value);\n link.click();\n };\n input.addEventListener(\"focus\", () => input.select());\n input.addEventListener(\"focusout\", action);\n input.addEventListener(\"keypress\", e => { if (e.key == \"Enter\") { action() } });\n };\n\n // Public interface\n return {\n version: \"43.2.2\",\n\n // Scan for elements with a \"data-pagy\" attribute and call their init functions with the decoded args\n init(arg?:HTMLElement) {\n const target = arg instanceof HTMLElement ? arg : document,\n elements = target.querySelectorAll(\"[data-pagy]\");\n for (const element of <NodeListOf<HTMLElement>>elements) {\n try {\n const [helperId, ...args] = <InitArgs>JSON.parse(B64Decode(<string>element.getAttribute(\"data-pagy\")));\n if (helperId == \"k\") {\n // @ts-expect-error spread 2 arguments, not 3 as it complains about\n void augmentKeynav(element, ...<KeynavArgs><unknown>args);\n } else if (helperId == \"snj\") {\n buildNavJs(<NavJsElement>element, <SeriesNavJsArgs><unknown>args);\n } else if (helperId == \"inj\") {\n void initInputNavJs(element, <InputNavJsArgs><unknown>args);\n } else if (helperId == \"ltj\") {\n initLimitTagJs(element, <LimitTagJsArgs><unknown>args);\n }\n // else { console.warn(\"Pagy.init: %o\\nUnknown helperId '%s'\", element, helperId) }\n } catch (err) { console.warn(\"Pagy.init: %o\\n%s\", element, err) }\n }\n }\n };\n})();\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "98E0CC9BFBB1A85D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
data/javascripts/pagy.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
window.Pagy=(()=>{let B="sessionStorage"in window&&"BroadcastChannel"in window,L="pagy",Z,O,W;if(B)Z=sessionStorage,O=new BroadcastChannel(L),W=Date.now(),O.addEventListener("message",(q)=>{if(q.data.from){let z=Z.getItem(q.data.key);if(z)O.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==W)Z.setItem(q.data.key,q.data.str)}});let P=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),V=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),S=()=>Math.floor(Math.random()*46656).toString(36),J=async(q,[z,C,F,H,G])=>{let Q,X=document.cookie.split(/;\s+/).find((M)=>M.startsWith(L+"="))?.split("=")[1]??S();if(document.cookie=L+"="+X,z&&!(z in Z)){if(O.postMessage({from:W,key:z}),await new Promise((M)=>setTimeout(()=>M(""),100)),!(z in Z))Q=(M)=>M+"+"+H}if(!Q){if(!z)do z=S();while(z in Z);let M=Z.getItem(z),Y=M?JSON.parse(M):[void 0];if(G)Y.splice(...G),Z.setItem(z,JSON.stringify(Y));Q=($)=>{let R=parseInt($);return V(JSON.stringify([X,z,R,Y.length,Y[R-1],Y[R]]))}}let D=C?`${C}%5B${F}%5D`:F,E=new RegExp(`(?<=\\?.*)(\\b${D}=)(\\d+)`);for(let M of q.querySelectorAll("a[href]"))M.href=M.href.replace(E,(Y,$,R)=>`${$}${Q(R)}`);return Q},x=(q,[[z,C,F,H,G],Q,[X,D,E],M])=>{let Y=q.parentElement,$=-1;if((q.render=()=>{let R=X.findIndex((j)=>j<Y.clientWidth);if(X[R]===$)return;let U=z;if(D[R].forEach((j,I)=>{U+=j=="gap"?H:(typeof j=="number"?C.replace(Q,j):F).replace("L<",E?.[R][I]??j+"<")}),U+=G,q.innerHTML="",q.insertAdjacentHTML("afterbegin",U),$=X[R],M&&B)J(q,M)})(),q.classList.contains(L+"-rjs"))P.observe(Y)},T=async(q,[z,C,F])=>{let H=F&&B?await J(q,F):(G)=>G;N(q,(G)=>z.replace(C,H(G)))},A=(q,[z,C,F,H])=>{N(q,(G)=>{return C.replace(F,Math.max(Math.ceil(z/parseInt(G)),1)).replace(H,G)})},N=(q,z)=>{let C=q.querySelector("input"),F=q.querySelector("a"),H=C.value,G=()=>{if(C.value===H)return;let[Q,X,D]=[C.min,C.value,C.max].map((E)=>parseInt(E)||0);if(X<Q||X>D){C.value=H,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",G),C.addEventListener("keypress",(Q)=>{if(Q.key=="Enter")G()})};return{version:"43.2.
|
|
1
|
+
window.Pagy=(()=>{let B="sessionStorage"in window&&"BroadcastChannel"in window,L="pagy",Z,O,W;if(B)Z=sessionStorage,O=new BroadcastChannel(L),W=Date.now(),O.addEventListener("message",(q)=>{if(q.data.from){let z=Z.getItem(q.data.key);if(z)O.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==W)Z.setItem(q.data.key,q.data.str)}});let P=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),V=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),S=()=>Math.floor(Math.random()*46656).toString(36),J=async(q,[z,C,F,H,G])=>{let Q,X=document.cookie.split(/;\s+/).find((M)=>M.startsWith(L+"="))?.split("=")[1]??S();if(document.cookie=L+"="+X,z&&!(z in Z)){if(O.postMessage({from:W,key:z}),await new Promise((M)=>setTimeout(()=>M(""),100)),!(z in Z))Q=(M)=>M+"+"+H}if(!Q){if(!z)do z=S();while(z in Z);let M=Z.getItem(z),Y=M?JSON.parse(M):[void 0];if(G)Y.splice(...G),Z.setItem(z,JSON.stringify(Y));Q=($)=>{let R=parseInt($);return V(JSON.stringify([X,z,R,Y.length,Y[R-1],Y[R]]))}}let D=C?`${C}%5B${F}%5D`:F,E=new RegExp(`(?<=\\?.*)(\\b${D}=)(\\d+)`);for(let M of q.querySelectorAll("a[href]"))M.href=M.href.replace(E,(Y,$,R)=>`${$}${Q(R)}`);return Q},x=(q,[[z,C,F,H,G],Q,[X,D,E],M])=>{let Y=q.parentElement,$=-1;if((q.render=()=>{let R=X.findIndex((j)=>j<Y.clientWidth);if(X[R]===$)return;let U=z;if(D[R].forEach((j,I)=>{U+=j=="gap"?H:(typeof j=="number"?C.replace(Q,j):F).replace("L<",E?.[R][I]??j+"<")}),U+=G,q.innerHTML="",q.insertAdjacentHTML("afterbegin",U),$=X[R],M&&B)J(q,M)})(),q.classList.contains(L+"-rjs"))P.observe(Y)},T=async(q,[z,C,F])=>{let H=F&&B?await J(q,F):(G)=>G;N(q,(G)=>z.replace(C,H(G)))},A=(q,[z,C,F,H])=>{N(q,(G)=>{return C.replace(F,Math.max(Math.ceil(z/parseInt(G)),1)).replace(H,G)})},N=(q,z)=>{let C=q.querySelector("input"),F=q.querySelector("a"),H=C.value,G=()=>{if(C.value===H)return;let[Q,X,D]=[C.min,C.value,C.max].map((E)=>parseInt(E)||0);if(X<Q||X>D){C.value=H,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",G),C.addEventListener("keypress",(Q)=>{if(Q.key=="Enter")G()})};return{version:"43.2.2",init(q){let z=q instanceof HTMLElement?q:document,C=z.querySelectorAll("[data-pagy]");for(let F of C)try{let[H,...G]=JSON.parse(_(F.getAttribute("data-pagy")));if(H=="k")J(F,...G);else if(H=="snj")x(F,G);else if(H=="inj")T(F,G);else if(H=="ltj")A(F,G)}catch(H){console.warn(`Pagy.init: %o
|
|
2
2
|
%s`,F,H)}}}})();
|
data/javascripts/pagy.mjs
CHANGED
|
@@ -125,7 +125,7 @@ const Pagy = (() => {
|
|
|
125
125
|
});
|
|
126
126
|
};
|
|
127
127
|
return {
|
|
128
|
-
version: "43.2.
|
|
128
|
+
version: "43.2.2",
|
|
129
129
|
init(arg) {
|
|
130
130
|
const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
|
|
131
131
|
for (const element of elements) {
|
data/lib/pagy/cli.rb
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'rbconfig'
|
|
6
|
+
require 'pagy'
|
|
7
|
+
require_relative '../../apps/index'
|
|
8
|
+
|
|
9
|
+
class Pagy
|
|
10
|
+
class CLI
|
|
11
|
+
LINUX = RbConfig::CONFIG['host_os'].include?('linux')
|
|
12
|
+
HOST = 'localhost'
|
|
13
|
+
PORT = '8000'
|
|
14
|
+
|
|
15
|
+
def start(args = ARGV)
|
|
16
|
+
options = parse_options(args)
|
|
17
|
+
run_command(args, options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def parse_options(args)
|
|
23
|
+
options = { env: 'development', host: HOST, port: PORT,
|
|
24
|
+
rerun: false, clear: false, quiet: false }
|
|
25
|
+
|
|
26
|
+
parser = OptionParser.new do |opts|
|
|
27
|
+
opts.banner = <<~BANNER
|
|
28
|
+
Pagy #{VERSION} (https://ddnexus.github.io/pagy/playground)
|
|
29
|
+
Playground to showcase, clone and develop Pagy APPs
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
pagy APP [opts] Showcase APP from the installed gem
|
|
33
|
+
pagy clone APP Clone APP to the current dir
|
|
34
|
+
pagy FILE [opts] Develop app FILE from local path
|
|
35
|
+
BANNER
|
|
36
|
+
|
|
37
|
+
opts.summary_indent = ' '
|
|
38
|
+
opts.summary_width = 18
|
|
39
|
+
|
|
40
|
+
opts.separator "\nAPPs"
|
|
41
|
+
PagyApps::INDEX.each do |name, path|
|
|
42
|
+
desc = File.readlines(path)[3].sub('# ', '').strip
|
|
43
|
+
opts.separator " #{name.ljust(18)}#{desc}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
opts.separator "\nRackup options"
|
|
47
|
+
opts.on('-e', '--env ENV', 'Environment') { |v| options[:env] = v }
|
|
48
|
+
opts.on('-o', '--host HOST', 'Host') { |v| options[:host] = v }
|
|
49
|
+
opts.on('-p', '--port PORT', 'Port') { |v| options[:port] = v }
|
|
50
|
+
|
|
51
|
+
if LINUX
|
|
52
|
+
opts.separator "\nRerun options"
|
|
53
|
+
opts.on('--rerun', 'Enable rerun for development') { options[:rerun] = true }
|
|
54
|
+
opts.on('--clear', 'Clear screen before each rerun') { options[:clear] = true }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
opts.separator "\nOther options"
|
|
58
|
+
opts.on('-q', '--quiet', 'Quiet mode for development') { options[:quiet] = true }
|
|
59
|
+
opts.on('-v', '--version', 'Show version') do
|
|
60
|
+
puts VERSION
|
|
61
|
+
exit
|
|
62
|
+
end
|
|
63
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
64
|
+
puts opts
|
|
65
|
+
exit
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
opts.separator "\nExamples"
|
|
69
|
+
opts.separator " pagy demo Showcase demo at http://#{HOST}:#{PORT}"
|
|
70
|
+
opts.separator ' pagy clone repro Clone repro to ./repro.ru (rename it)'
|
|
71
|
+
opts.separator " pagy ~/myapp.ru Develop ~/myapp.ru at #{HOST}:#{PORT}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
begin
|
|
75
|
+
parser.parse!(args)
|
|
76
|
+
rescue OptionParser::InvalidOption => e
|
|
77
|
+
abort e.message
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if args.empty?
|
|
81
|
+
puts parser
|
|
82
|
+
exit
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
options
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def run_command(args, options)
|
|
89
|
+
run_from_repo = Pagy::ROOT.join('pagy.gemspec').exist?
|
|
90
|
+
setup_gems(run_from_repo)
|
|
91
|
+
|
|
92
|
+
arg = args.shift
|
|
93
|
+
|
|
94
|
+
if arg.eql?('clone')
|
|
95
|
+
clone_app(args.shift)
|
|
96
|
+
else
|
|
97
|
+
serve_app(arg, options, run_from_repo)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def clone_app(name)
|
|
102
|
+
abort "Expected APP to be in [#{PagyApps::INDEX.keys.join(', ')}]; got #{name.inspect}" unless PagyApps::INDEX.key?(name)
|
|
103
|
+
|
|
104
|
+
if File.exist?(name)
|
|
105
|
+
print "Do you want to overwrite the #{name.inspect} file? (y/n)> "
|
|
106
|
+
answer = gets.chomp
|
|
107
|
+
abort "#{name.inspect} file already present" unless answer.start_with?(/y/i)
|
|
108
|
+
end
|
|
109
|
+
FileUtils.cp(PagyApps::INDEX[name], '.', verbose: true)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def serve_app(arg, options, run_from_repo)
|
|
113
|
+
if PagyApps::INDEX.key?(arg)
|
|
114
|
+
options[:env] = 'showcase'
|
|
115
|
+
options[:rerun] = false
|
|
116
|
+
options[:quiet] = true
|
|
117
|
+
# Avoid the creation of './tmp/local_secret.txt' for showcase env
|
|
118
|
+
ENV['SECRET_KEY_BASE'] = 'absolute secret!' if arg.eql?('rails')
|
|
119
|
+
file = PagyApps::INDEX[arg]
|
|
120
|
+
else
|
|
121
|
+
file = arg
|
|
122
|
+
end
|
|
123
|
+
abort "#{file.inspect} app not found" unless File.exist?(file)
|
|
124
|
+
|
|
125
|
+
gem_dir = File.expand_path('../..', __dir__)
|
|
126
|
+
rackup = "rackup -I #{gem_dir}/lib -r pagy -o #{options[:host]} -p #{options[:port]} -E #{options[:env]} #{file}"
|
|
127
|
+
rackup << ' -q' if options[:quiet]
|
|
128
|
+
|
|
129
|
+
if options[:rerun]
|
|
130
|
+
name = File.basename(file)
|
|
131
|
+
dir = File.dirname(file)
|
|
132
|
+
rerun = if run_from_repo
|
|
133
|
+
"rerun --name #{name} -d #{dir},#{gem_dir} -p **/*.{rb,js,css,scss,ru,yml}"
|
|
134
|
+
else
|
|
135
|
+
"rerun --name #{name} -d #{dir} -p #{name}"
|
|
136
|
+
end
|
|
137
|
+
rerun << ' -q' if options[:quiet]
|
|
138
|
+
rerun << ' -c' if options[:clear]
|
|
139
|
+
rerun << " -- #{rackup}"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
exec(rerun || rackup)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Kept as a separate method because mocking 'gemfile' (dsl) is complex otherwise
|
|
146
|
+
def setup_gems(run_from_repo)
|
|
147
|
+
require 'bundler/inline'
|
|
148
|
+
gemfile(!run_from_repo) do
|
|
149
|
+
source 'https://rubygems.org'
|
|
150
|
+
gem 'logger'
|
|
151
|
+
gem 'rackup'
|
|
152
|
+
gem 'rerun' if LINUX
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|