pagy 43.1.0 → 43.1.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: 81897225205a29f9ae8e98c57cddcc7c92a6bdbc3ce1cdd466f68ade1cf80785
4
- data.tar.gz: e801dafc6c5acd601da4e88d68f21df621f6a0f01af19edbfa1df4d43d55fd54
3
+ metadata.gz: c2d26c7bd62793daa6d3e9244fa9e01e21e13f9f2ae301b0e1a7611549f8fd69
4
+ data.tar.gz: 13d1f5785fc1eb8fdf771545c2b3f83778e8ce61fa942bff1ba9140a815052f5
5
5
  SHA512:
6
- metadata.gz: 74cab9eeb2eb7dd535daff6523c9ee9190856dee5be12bd96e274748897d39ccca6a9eea6cc9fd5394fbd164c4bfb48a499dadf28ff1fe8fb625113b239806b3
7
- data.tar.gz: 06eb01e238504df2242a2c9a4b6855eab9bcf349ecd5a30487f89ce4491abfde2a06dcf4b453b745be8d5560b11eed5744426d22e91578d9ebe03c6517ed2531
6
+ metadata.gz: b465de78aeb499d2b7949d2ba1213fc77df827b3a48e7fb55e0b019c96b143ce20d5bb013e52555f7747911bd1ee314812a4b9f57ca1ab95e42b1f9bfa298caf
7
+ data.tar.gz: 689d5d5581ba78d43067f592888a19fc06d22df945158b124cea5b9eafd1a391a29969c28a798535f5f81059e92829d9a88cf52ba00f458f076e292545841370
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.1.0'
19
+ VERSION = '43.1.1'
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")
data/apps/demo.ru CHANGED
@@ -19,7 +19,7 @@
19
19
  # URL
20
20
  # http://127.0.0.1:8000
21
21
 
22
- VERSION = '43.1.0'
22
+ VERSION = '43.1.1'
23
23
 
24
24
  if VERSION != Pagy::VERSION
25
25
  Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
@@ -0,0 +1,321 @@
1
+ # frozen_string_literal: true
2
+
3
+ # DESCRIPTION
4
+ # Showcase the Keynav pagination with independent instances
5
+ #
6
+ # DOC
7
+ # https://ddnexus.github.io/pagy/playground/#keyset-apps
8
+ #
9
+ # BIN HELP
10
+ # pagy -h
11
+ #
12
+ # DEV USAGE
13
+ # pagy clone keynav
14
+ # pagy ./keynav.ru
15
+ #
16
+ # URL
17
+ # http://127.0.0.1:8000
18
+
19
+ VERSION = '43.1.1'
20
+
21
+ if VERSION != Pagy::VERSION
22
+ Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
23
+ end
24
+
25
+ # Bundle
26
+ require 'bundler/inline'
27
+ gemfile(!Pagy::ROOT.join('pagy.gemspec').exist?) do
28
+ source 'https://rubygems.org'
29
+ gem 'activerecord'
30
+ gem 'puma'
31
+ gem 'sinatra'
32
+ gem 'sqlite3'
33
+ end
34
+
35
+ # Sinatra setup
36
+ require 'sinatra/base'
37
+ # Sinatra application
38
+ class PagyKeynav < Sinatra::Base
39
+ include Pagy::Method
40
+
41
+ get('/javascripts/:file') do
42
+ format = params[:file].split('.').last
43
+ if format == 'js'
44
+ content_type 'application/javascript'
45
+ elsif format == 'map'
46
+ content_type 'application/json'
47
+ end
48
+ send_file Pagy::ROOT.join('javascripts', params[:file])
49
+ end
50
+
51
+ # Root route/action
52
+ get '/' do
53
+ Time.zone = 'UTC'
54
+
55
+ @order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
56
+ @pagy1, @pets1 = pagy(:keynav_js, Pet.order(@order), limit: 4, root_key: 'animal1')
57
+ @ids1 = @pets1.pluck(:id)
58
+ @pagy2, @pets2 = pagy(:keynav_js, Pet.order(@order), limit: 4, root_key: 'animal2')
59
+ @ids2 = @pets2.pluck(:id)
60
+ erb :main
61
+ end
62
+
63
+ helpers do
64
+ def order_symbol(dir)
65
+ { asc: '&#x2197;', desc: '&#x2198;' }[dir]
66
+ end
67
+ end
68
+
69
+ # Views
70
+ template :layout do
71
+ <<~ERB
72
+ <!DOCTYPE html>
73
+ <html lang="en">
74
+ <html>
75
+ <head>
76
+ <title>Pagy Keynav (root_key) App</title>
77
+ <script src="javascripts/pagy.js"></script>
78
+ <script>
79
+ window.addEventListener("load", Pagy.init);
80
+ </script>
81
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
82
+ <style type="text/css">
83
+ @media screen { html, body {
84
+ font-size: 1rem;
85
+ line-height: 1.2s;
86
+ padding: 0;
87
+ margin: 0;
88
+ } }
89
+ body {
90
+ background: white !important;
91
+ margin: 0 !important;
92
+ font-family: sans-serif !important;
93
+ }
94
+ .main-content {
95
+ padding: 1rem 1.5rem 2rem !important;
96
+ }
97
+ #content {
98
+ display: flex; /* Enables Flexbox */
99
+ flex-wrap: wrap; /* Allows stacking */
100
+ gap: 20px; /* Gap between items */
101
+ /* Just for visual clarity of the container boundaries */
102
+ /*padding: 10px;
103
+ background-color: #e0e7ff;
104
+ border-radius: 8px;
105
+ border: 2px dashed #6366f1; */
106
+ }
107
+ .box {
108
+ flex: 1; /* Grow to fill space */
109
+ min-width: 300px; /* Stack if space < 300px */
110
+ /* Visual Styling */
111
+ background-color: white;
112
+ padding: 25px;
113
+ border-radius: 6px;
114
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
115
+ border: 1px solid #ddd;
116
+ }
117
+ .pagy {
118
+ padding: .5em;
119
+ margin: .3em 0;
120
+ width: fit-content;
121
+ box-shadow: 5px 5px 10px 0px rgba(0,0,0,0.2);
122
+ }
123
+ <%= Pagy::ROOT.join('stylesheets/pagy.css').read %>
124
+ </style>
125
+ </head>
126
+ <body>
127
+ <%= yield %>
128
+ </body>
129
+ </html>
130
+ ERB
131
+ end
132
+
133
+ template :main do
134
+ <<~ERB
135
+ <div class="main-content">
136
+ <h1>Pagy Keynav App</h1>
137
+ <p>Self-contained, standalone app usable to easily reproduce any Keynav related pagy issue
138
+ with ActiveRecord sets.</p>
139
+ <p>The panels below show how to use the <code>:root_key</code> option for independent instances in the same request.</p>
140
+
141
+ <p>Notice that Keynav works also with Sequel sets.</p>
142
+
143
+ <h2>Versions</h2>
144
+ <ul>
145
+ <li>Ruby: <%= RUBY_VERSION %></li>
146
+ <li>Rack: <%= Rack::RELEASE %></li>
147
+ <li>Sinatra: <%= Sinatra::VERSION %></li>
148
+ <li>Pagy: <%= Pagy::VERSION %></li>
149
+ </ul>
150
+
151
+ <div id="content">
152
+ <div class="box">
153
+ <h3>Collection 1</h3>
154
+ <p id="records">@ids: <%= @ids1.join(',') %></p>
155
+ <div class="collection">
156
+ <table border="1" style="border-collapse: collapse; border-spacing: 0; padding: 0.2rem;">
157
+ <tr>
158
+ <th scope="col">animal <%= order_symbol(@order[:animal]) %></th>
159
+ <th scope="col">name <%= order_symbol(@order[:name]) %></th>
160
+ <th scope="col">birthdate <%= order_symbol(@order[:birthdate]) %></th>
161
+ <th scope="col">id <%= order_symbol(@order[:id]) %></th>
162
+ </tr>
163
+ <% @pets1.each do |pet| %>
164
+ <tr>
165
+ <td><%= pet.animal %></td>
166
+ <td><%= pet.name %></td>
167
+ <td><%= pet.birthdate %></td>
168
+ <td><%= pet.id %></td>
169
+ </tr>
170
+ <% end %>
171
+ </table>
172
+ </div>
173
+
174
+ <h4>@pagy.series_nav</h4>
175
+ <%= @pagy1.series_nav(id: 'series-nav',
176
+ aria_label: 'Pages (nav)') %>
177
+
178
+ <h4>@pagy.series_nav_js (responsive)</h4>
179
+ <%= @pagy1.series_nav_js(id: 'series-nav-js-responsive',
180
+ aria_label: 'Pages (nav_js_responsive)',
181
+ steps: { 0 => 5, 500 => 7, 750 => 9, 1000 => 11 }) %>
182
+ <h4>@pagy.input_nav_js</h4>
183
+ <%= @pagy1.input_nav_js(id: 'input-nav-js',
184
+ aria_label: 'Pages (input_nav_js)') %>
185
+
186
+ <h4>@pagy.info_tag</h4>
187
+ <%= @pagy1.info_tag(id: 'pagy-info') %>
188
+ </div>
189
+
190
+ <div class="box">
191
+ <h3>Collection 2</h3>
192
+ <p id="records">@ids: <%= @ids2.join(',') %></p>
193
+ <div class="collection">
194
+ <table border="1" style="border-collapse: collapse; border-spacing: 0; padding: 0.2rem;">
195
+ <tr>
196
+ <th scope="col">animal <%= order_symbol(@order[:animal]) %></th>
197
+ <th scope="col">name <%= order_symbol(@order[:name]) %></th>
198
+ <th scope="col">birthdate <%= order_symbol(@order[:birthdate]) %></th>
199
+ <th scope="col">id <%= order_symbol(@order[:id]) %></th>
200
+ </tr>
201
+ <% @pets2.each do |pet| %>
202
+ <tr>
203
+ <td><%= pet.animal %></td>
204
+ <td><%= pet.name %></td>
205
+ <td><%= pet.birthdate %></td>
206
+ <td><%= pet.id %></td>
207
+ </tr>
208
+ <% end %>
209
+ </table>
210
+ </div>
211
+
212
+ <h4>@pagy.series_nav</h4>
213
+ <%= @pagy2.series_nav(id: 'series-nav',
214
+ aria_label: 'Pages (nav)') %>
215
+
216
+ <h4>@pagy.series_nav_js (responsive)</h4>
217
+ <%= @pagy2.series_nav_js(id: 'series-nav-js-responsive',
218
+ aria_label: 'Pages (nav_js_responsive)',
219
+ steps: { 0 => 5, 500 => 7, 750 => 9, 1000 => 11 }) %>
220
+ <h4>@pagy.input_nav_js</h4>
221
+ <%= @pagy2.input_nav_js(id: 'input-nav-js',
222
+ aria_label: 'Pages (input_nav_js)') %>
223
+
224
+ <h4>@pagy.info_tag</h4>
225
+ <%= @pagy2.info_tag(id: 'pagy-info') %>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ ERB
230
+ end
231
+ end
232
+
233
+ # ActiveRecord setup
234
+ require 'active_record'
235
+
236
+ # Match the microsecods with the strings stored into the time columns of SQLite
237
+ # ActiveSupport::JSON::Encoding.time_precision = 6
238
+
239
+ # Log
240
+ output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
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
+ # Connection
247
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: "#{dir}/tmp/pagy-keyset-ar.sqlite3")
248
+ # Schema
249
+ ActiveRecord::Schema.define do
250
+ create_table :pets, force: true do |t|
251
+ t.string :animal
252
+ t.string :name
253
+ t.date :birthdate
254
+ end
255
+ end
256
+
257
+ # Models
258
+ class Pet < ActiveRecord::Base; end
259
+
260
+ data = <<~DATA
261
+ Luna | dog | 2018-03-10
262
+ Coco | cat | 2019-05-15
263
+ Dodo | dog | 2020-06-25
264
+ Wiki | bird | 2018-03-12
265
+ Baby | rabbit | 2020-01-13
266
+ Neki | horse | 2021-07-20
267
+ Tino | donkey | 2019-06-18
268
+ Plot | cat | 2022-09-21
269
+ Riki | cat | 2018-09-14
270
+ Susi | horse | 2018-10-26
271
+ Coco | pig | 2020-08-29
272
+ Momo | bird | 2023-08-25
273
+ Lili | cat | 2021-07-22
274
+ Beli | pig | 2020-07-26
275
+ Rocky | bird | 2022-08-19
276
+ Vyvy | dog | 2018-05-16
277
+ Susi | horse | 2024-01-25
278
+ Ella | cat | 2020-02-20
279
+ Rocky | dog | 2019-09-19
280
+ Juni | rabbit | 2020-08-24
281
+ Coco | bird | 2021-03-17
282
+ Susi | dog | 2021-07-28
283
+ Luna | horse | 2023-05-14
284
+ Gigi | pig | 2022-05-19
285
+ Coco | cat | 2020-02-20
286
+ Nino | donkey | 2019-06-17
287
+ Luna | cat | 2022-02-09
288
+ Popi | dog | 2020-09-26
289
+ Lili | pig | 2022-06-18
290
+ Mina | horse | 2021-04-21
291
+ Susi | rabbit | 2023-05-18
292
+ Toni | donkey | 2018-06-22
293
+ Rocky | horse | 2019-09-28
294
+ Lili | cat | 2019-03-18
295
+ Roby | cat | 2022-06-19
296
+ Anto | horse | 2022-08-18
297
+ Susi | pig | 2021-04-21
298
+ Boly | bird | 2020-03-29
299
+ Sky | cat | 2023-07-19
300
+ Lili | dog | 2020-01-28
301
+ Fami | snake | 2023-04-27
302
+ Lopi | pig | 2019-06-19
303
+ Rocky | snake | 2022-03-13
304
+ Denis | dog | 2022-06-19
305
+ Maca | cat | 2022-06-19
306
+ Luna | dog | 2022-08-15
307
+ Jeme | horse | 2019-08-08
308
+ Sary | bird | 2023-04-29
309
+ Rocky | bird | 2023-05-14
310
+ Coco | dog | 2023-05-27
311
+ DATA
312
+
313
+ # DB seed
314
+ pets = []
315
+ data.each_line(chomp: true) do |pet|
316
+ name, animal, birthdate = pet.split('|').map(&:strip)
317
+ pets << { name:, animal:, birthdate: }
318
+ end
319
+ Pet.insert_all(pets)
320
+
321
+ run PagyKeynav
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.1.0'
19
+ VERSION = '43.1.1'
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")
@@ -54,6 +54,8 @@ class PagyKeynav < Sinatra::Base
54
54
 
55
55
  @order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
56
56
  @pagy, @pets = pagy(:keynav_js, Pet.order(@order), limit: 4, client_max_limit: 100)
57
+ # Support also root_key for replacing url in javascript
58
+ # @pagy, @pets = pagy(:keynav_js, Pet.order(@order), limit: 4, client_max_limit: 100, root_key: 'animal')
57
59
  @ids = @pets.pluck(:id)
58
60
  erb :main
59
61
  end
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.1.0'
19
+ VERSION = '43.1.1'
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")
@@ -16,7 +16,7 @@
16
16
  # URL
17
17
  # http://127.0.0.1:8000
18
18
 
19
- VERSION = '43.1.0'
19
+ VERSION = '43.1.1'
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")
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.1.0'
19
+ VERSION = '43.1.1'
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")
data/apps/repro.ru CHANGED
@@ -16,7 +16,7 @@
16
16
  # URL
17
17
  # http://127.0.0.1:8000
18
18
 
19
- VERSION = '43.1.0'
19
+ VERSION = '43.1.1'
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")
data/bin/pagy CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- VERSION = '43.1.0'
4
+ VERSION = '43.1.1'
5
5
  LINUX = RbConfig::CONFIG['host_os'].include?('linux')
6
6
  HOST = 'localhost'
7
7
  PORT = '8000'
data/config/pagy.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Pagy initializer file (43.1.0)
3
+ # Pagy initializer file (43.1.1)
4
4
  # See https://ddnexus.github.io/pagy/resources/initializer/
5
5
 
6
6
  ############ Global Options ################################################################
data/javascripts/pagy.js CHANGED
@@ -24,18 +24,18 @@ window.Pagy = (() => {
24
24
  }));
25
25
  const B64SafeEncode = (unicode) => btoa(String.fromCharCode(...new TextEncoder().encode(unicode))).replace(/[+/=]/g, (m) => m == "+" ? "-" : m == "/" ? "_" : ""), B64Decode = (base64) => new TextDecoder().decode(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));
26
26
  const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);
27
- const augmentKeynav = async (nav, [storageKey, pageKey, last, spliceArgs]) => {
28
- let augment;
27
+ const augmentKeynav = async (nav, [storageKey, rootKey, pageKey, last, spliceArgs]) => {
28
+ let augmentPage;
29
29
  const browserKey = document.cookie.split(/;\s+/).find((row) => row.startsWith(pagy + "="))?.split("=")[1] ?? randKey();
30
30
  document.cookie = pagy + "=" + browserKey;
31
31
  if (storageKey && !(storageKey in storage)) {
32
32
  sync.postMessage({ from: tabId, key: storageKey });
33
33
  await new Promise((resolve) => setTimeout(() => resolve(""), 100));
34
34
  if (!(storageKey in storage)) {
35
- augment = (page) => page + "+" + last;
35
+ augmentPage = (page) => page + "+" + last;
36
36
  }
37
37
  }
38
- if (!augment) {
38
+ if (!augmentPage) {
39
39
  if (!storageKey) {
40
40
  do {
41
41
  storageKey = randKey();
@@ -46,7 +46,7 @@ window.Pagy = (() => {
46
46
  cutoffs.splice(...spliceArgs);
47
47
  storage.setItem(storageKey, JSON.stringify(cutoffs));
48
48
  }
49
- augment = (page) => {
49
+ augmentPage = (page) => {
50
50
  const pageNum = parseInt(page);
51
51
  return B64SafeEncode(JSON.stringify([
52
52
  browserKey,
@@ -58,11 +58,12 @@ window.Pagy = (() => {
58
58
  ]));
59
59
  };
60
60
  }
61
+ const search = rootKey ? `${rootKey}%5B${pageKey}%5D` : pageKey;
62
+ const re = new RegExp(`(?<=\\?.*)(\\b${search}=)(\\d+)`);
61
63
  for (const a of nav.querySelectorAll("a[href]")) {
62
- const url = a.href, re = new RegExp(`(?<=\\?.*)\\b${pageKey}=(\\d+)`);
63
- a.href = url.replace(re, pageKey + "=" + augment(url.match(re)[1]));
64
+ a.href = a.href.replace(re, (_match, prefix, digit) => `${prefix}${augmentPage(digit)}`);
64
65
  }
65
- return augment;
66
+ return augmentPage;
66
67
  };
67
68
  const buildNavJs = (nav, [
68
69
  [before, anchor, current, gap, after],
@@ -124,7 +125,7 @@ window.Pagy = (() => {
124
125
  });
125
126
  };
126
127
  return {
127
- version: "43.1.0",
128
+ version: "43.1.1",
128
129
  init(arg) {
129
130
  const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
130
131
  for (const element of elements) {
@@ -147,5 +148,5 @@ window.Pagy = (() => {
147
148
  };
148
149
  })();
149
150
 
150
- //# debugId=7F9EF7EF7A33670664756E2164756E21
151
+ //# debugId=7800AFC87F2D38E864756E2164756E21
151
152
  //# sourceMappingURL=pagy.js.map
@@ -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 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, NavJsSeries, KeynavArgs?]\ntype NavJsSeries = readonly [widths: number[],\n series: (string | number)[][],\n labels: string[][] | null]\ntype InputNavJsArgs = readonly [urlToken: string, KeynavArgs?]\ntype LimitTagJsArgs = readonly [from: number,\n urlToken: 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 pageRe = \"P \"; // shorten the compiled size\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, pageKey, last, spliceArgs]) => {\n let augment;\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 augment = (page: string) => page + '+' + last;\n }\n }\n if (!augment) { // 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 augment = (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 // Augment the page param of each href\n for (const a of <NodeListOf<HTMLAnchorElement>><unknown>nav.querySelectorAll('a[href]')) {\n const url = a.href,\n re = new RegExp(`(?<=\\\\?.*)\\\\b${pageKey}=(\\\\d+)`); // find the numeric page from pageKey\n a.href = url.replace(re, pageKey + \"=\" + augment(url.match(re)![1])); // eslint-disable-line @typescript-eslint/no-non-null-assertion\n }\n // Return the augment function for further augmentation (i.e., url token in input_nav_js)\n return augment;\n };\n\n // Build the series_nav_js helper\n const buildNavJs = (nav:NavJsElement, [[before, anchor, current, gap, after],\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(pageRe, 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, keynavArgs]:InputNavJsArgs) => {\n const augment = keynavArgs && storageSupport\n ? await augmentKeynav(nav, keynavArgs)\n : (page: string) => page;\n initInput(nav, inputValue => url_token.replace(pageRe, augment(inputValue)));\n };\n\n // Init the limit_tag_js helper\n const initLimitTagJs = (span:HTMLSpanElement, [from, url_token]: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(pageRe, Math.max(Math.ceil(from / parseInt(inputValue)), 1))\n .replace('L ', 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.1.0\",\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, NavJsSeries, KeynavArgs?]\ntype NavJsSeries = readonly [widths: number[],\n series: (string | number)[][],\n labels: string[][] | null]\ntype InputNavJsArgs = readonly [urlToken: string, KeynavArgs?]\ntype LimitTagJsArgs = readonly [from: number,\n urlToken: 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 pageRe = \"P \"; // shorten the compiled size\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],\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(pageRe, 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, keynavArgs]:InputNavJsArgs) => {\n const augment = keynavArgs && storageSupport\n ? await augmentKeynav(nav, keynavArgs)\n : (page: string) => page;\n initInput(nav, inputValue => url_token.replace(pageRe, augment(inputValue)));\n };\n\n // Init the limit_tag_js helper\n const initLimitTagJs = (span:HTMLSpanElement, [from, url_token]: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(pageRe, Math.max(Math.ceil(from / parseInt(inputValue)), 1))\n .replace('L ', 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.1.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"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "7F9EF7EF7A33670664756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "7800AFC87F2D38E864756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1 +1 @@
1
- window.Pagy=(()=>{const L="sessionStorage"in window&&"BroadcastChannel"in window;let j="pagy",Y,D,O;if(L)Y=sessionStorage,D=new BroadcastChannel(j),O=Date.now(),D.addEventListener("message",(q)=>{if(q.data.from){const z=Y.getItem(q.data.key);if(z)D.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==O)Y.setItem(q.data.key,q.data.str)}});const U=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),S=(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))),B=()=>Math.floor(Math.random()*46656).toString(36),P=async(q,[z,C,F,G])=>{let M;const R=document.cookie.split(/;\s+/).find((H)=>H.startsWith(j+"="))?.split("=")[1]??B();if(document.cookie=j+"="+R,z&&!(z in Y)){if(D.postMessage({from:O,key:z}),await new Promise((H)=>setTimeout(()=>H(""),100)),!(z in Y))M=(H)=>H+"+"+F}if(!M){if(!z)do z=B();while(z in Y);const H=Y.getItem(z),Q=H?JSON.parse(H):[void 0];if(G)Q.splice(...G),Y.setItem(z,JSON.stringify(Q));M=(X)=>{const Z=parseInt(X);return S(JSON.stringify([R,z,Z,Q.length,Q[Z-1],Q[Z]]))}}for(let H of q.querySelectorAll("a[href]")){const Q=H.href,X=new RegExp(`(?<=\\?.*)\\b${C}=(\\d+)`);H.href=Q.replace(X,C+"="+M(Q.match(X)[1]))}return M},x=(q,[[z,C,F,G,M],[R,H,Q],X])=>{const Z=q.parentElement;let J=-1;if((q.render=()=>{const E=R.findIndex(($)=>$<Z.clientWidth);if(R[E]===J)return;let T=z;if(H[E].forEach(($,A)=>{T+=$=="gap"?G:(typeof $=="number"?C.replace("P ",$):F).replace("L<",Q?.[E][A]??$+"<")}),T+=M,q.innerHTML="",q.insertAdjacentHTML("afterbegin",T),J=R[E],X&&L)P(q,X)})(),q.classList.contains(j+"-rjs"))U.observe(Z)},N=async(q,[z,C])=>{const F=C&&L?await P(q,C):(G)=>G;W(q,(G)=>z.replace("P ",F(G)))},V=(q,[z,C])=>{W(q,(F)=>{return C.replace("P ",Math.max(Math.ceil(z/parseInt(F)),1)).replace("L ",F)})},W=(q,z)=>{const C=q.querySelector("input"),F=q.querySelector("a"),G=C.value,M=()=>{if(C.value===G)return;const[R,H,Q]=[C.min,C.value,C.max].map((X)=>parseInt(X)||0);if(H<R||H>Q){C.value=G,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",M),C.addEventListener("keypress",(R)=>{if(R.key=="Enter")M()})};return{version:"43.1.0",init(q){const z=q instanceof HTMLElement?q:document,C=z.querySelectorAll("[data-pagy]");for(let F of C)try{const[G,...M]=JSON.parse(_(F.getAttribute("data-pagy")));if(G=="k")P(F,...M);else if(G=="snj")x(F,M);else if(G=="inj")N(F,M);else if(G=="ltj")V(F,M)}catch(G){console.warn("Pagy.init: %o\n%s",F,G)}}}})();
1
+ window.Pagy=(()=>{const T="sessionStorage"in window&&"BroadcastChannel"in window;let L="pagy",Y,O,B;if(T)Y=sessionStorage,O=new BroadcastChannel(L),B=Date.now(),O.addEventListener("message",(q)=>{if(q.data.from){const z=Y.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==B)Y.setItem(q.data.key,q.data.str)}});const S=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),_=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),N=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),J=()=>Math.floor(Math.random()*46656).toString(36),W=async(q,[z,C,F,G,Q])=>{let M;const $=document.cookie.split(/;\s+/).find((H)=>H.startsWith(L+"="))?.split("=")[1]??J();if(document.cookie=L+"="+$,z&&!(z in Y)){if(O.postMessage({from:B,key:z}),await new Promise((H)=>setTimeout(()=>H(""),100)),!(z in Y))M=(H)=>H+"+"+G}if(!M){if(!z)do z=J();while(z in Y);const H=Y.getItem(z),R=H?JSON.parse(H):[void 0];if(Q)R.splice(...Q),Y.setItem(z,JSON.stringify(R));M=(Z)=>{const X=parseInt(Z);return _(JSON.stringify([$,z,X,R.length,R[X-1],R[X]]))}}const E=C?`${C}%5B${F}%5D`:F,j=new RegExp(`(?<=\\?.*)(\\b${E}=)(\\d+)`);for(let H of q.querySelectorAll("a[href]"))H.href=H.href.replace(j,(R,Z,X)=>`${Z}${M(X)}`);return M},P=(q,[[z,C,F,G,Q],[M,$,E],j])=>{const H=q.parentElement;let R=-1;if((q.render=()=>{const Z=M.findIndex((D)=>D<H.clientWidth);if(M[Z]===R)return;let X=z;if($[Z].forEach((D,A)=>{X+=D=="gap"?G:(typeof D=="number"?C.replace("P ",D):F).replace("L<",E?.[Z][A]??D+"<")}),X+=Q,q.innerHTML="",q.insertAdjacentHTML("afterbegin",X),R=M[Z],j&&T)W(q,j)})(),q.classList.contains(L+"-rjs"))S.observe(H)},V=async(q,[z,C])=>{const F=C&&T?await W(q,C):(G)=>G;U(q,(G)=>z.replace("P ",F(G)))},x=(q,[z,C])=>{U(q,(F)=>{return C.replace("P ",Math.max(Math.ceil(z/parseInt(F)),1)).replace("L ",F)})},U=(q,z)=>{const C=q.querySelector("input"),F=q.querySelector("a"),G=C.value,Q=()=>{if(C.value===G)return;const[M,$,E]=[C.min,C.value,C.max].map((j)=>parseInt(j)||0);if($<M||$>E){C.value=G,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",Q),C.addEventListener("keypress",(M)=>{if(M.key=="Enter")Q()})};return{version:"43.1.1",init(q){const z=q instanceof HTMLElement?q:document,C=z.querySelectorAll("[data-pagy]");for(let F of C)try{const[G,...Q]=JSON.parse(N(F.getAttribute("data-pagy")));if(G=="k")W(F,...Q);else if(G=="snj")P(F,Q);else if(G=="inj")V(F,Q);else if(G=="ltj")x(F,Q)}catch(G){console.warn("Pagy.init: %o\n%s",F,G)}}}})();
data/javascripts/pagy.mjs CHANGED
@@ -23,18 +23,18 @@ const Pagy = (() => {
23
23
  }));
24
24
  const B64SafeEncode = (unicode) => btoa(String.fromCharCode(...new TextEncoder().encode(unicode))).replace(/[+/=]/g, (m) => m == "+" ? "-" : m == "/" ? "_" : ""), B64Decode = (base64) => new TextDecoder().decode(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));
25
25
  const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);
26
- const augmentKeynav = async (nav, [storageKey, pageKey, last, spliceArgs]) => {
27
- let augment;
26
+ const augmentKeynav = async (nav, [storageKey, rootKey, pageKey, last, spliceArgs]) => {
27
+ let augmentPage;
28
28
  const browserKey = document.cookie.split(/;\s+/).find((row) => row.startsWith(pagy + "="))?.split("=")[1] ?? randKey();
29
29
  document.cookie = pagy + "=" + browserKey;
30
30
  if (storageKey && !(storageKey in storage)) {
31
31
  sync.postMessage({ from: tabId, key: storageKey });
32
32
  await new Promise((resolve) => setTimeout(() => resolve(""), 100));
33
33
  if (!(storageKey in storage)) {
34
- augment = (page) => page + "+" + last;
34
+ augmentPage = (page) => page + "+" + last;
35
35
  }
36
36
  }
37
- if (!augment) {
37
+ if (!augmentPage) {
38
38
  if (!storageKey) {
39
39
  do {
40
40
  storageKey = randKey();
@@ -45,7 +45,7 @@ const Pagy = (() => {
45
45
  cutoffs.splice(...spliceArgs);
46
46
  storage.setItem(storageKey, JSON.stringify(cutoffs));
47
47
  }
48
- augment = (page) => {
48
+ augmentPage = (page) => {
49
49
  const pageNum = parseInt(page);
50
50
  return B64SafeEncode(JSON.stringify([
51
51
  browserKey,
@@ -57,11 +57,12 @@ const Pagy = (() => {
57
57
  ]));
58
58
  };
59
59
  }
60
+ const search = rootKey ? `${rootKey}%5B${pageKey}%5D` : pageKey;
61
+ const re = new RegExp(`(?<=\\?.*)(\\b${search}=)(\\d+)`);
60
62
  for (const a of nav.querySelectorAll("a[href]")) {
61
- const url = a.href, re = new RegExp(`(?<=\\?.*)\\b${pageKey}=(\\d+)`);
62
- a.href = url.replace(re, pageKey + "=" + augment(url.match(re)[1]));
63
+ a.href = a.href.replace(re, (_match, prefix, digit) => `${prefix}${augmentPage(digit)}`);
63
64
  }
64
- return augment;
65
+ return augmentPage;
65
66
  };
66
67
  const buildNavJs = (nav, [
67
68
  [before, anchor, current, gap, after],
@@ -123,7 +124,7 @@ const Pagy = (() => {
123
124
  });
124
125
  };
125
126
  return {
126
- version: "43.1.0",
127
+ version: "43.1.1",
127
128
  init(arg) {
128
129
  const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
129
130
  for (const element of elements) {
@@ -50,7 +50,7 @@ class Pagy
50
50
  else
51
51
  @page = @last = 1
52
52
  end
53
- @update = [storage_key, @options[:page_key]]
53
+ @update = [storage_key, @options[:root_key], @options[:page_key]]
54
54
  end
55
55
 
56
56
  # Use a compound predicate to fetch the records
@@ -4,7 +4,9 @@ class Pagy
4
4
  # Decouple the request from the env, allowing non-rack apps to use pagy by passing a hash.
5
5
  # Resolve the :page and :limit options from params.
6
6
  class Request
7
- def initialize(request)
7
+ def initialize(options) # default empty options for test only
8
+ @options = options
9
+ request = @options[:request]
8
10
  @base_url, @path, @params, @cookie =
9
11
  if request.is_a?(Hash)
10
12
  request.values_at(:base_url, :path, :params, :cookie)
@@ -15,20 +17,20 @@ class Pagy
15
17
 
16
18
  attr_reader :base_url, :path, :params, :cookie
17
19
 
18
- def resolve_page(options, force_integer: true)
19
- page_key = options[:page_key] || DEFAULT[:page_key]
20
- page = @params.dig(options[:root_key], page_key) || @params[page_key]
20
+ def resolve_page(force_integer: true)
21
+ page_key = @options[:page_key] || DEFAULT[:page_key]
22
+ page = @params.dig(@options[:root_key], page_key) || @params[page_key]
21
23
  page = nil if page == '' # fix for app-generated queries like ?page=
22
24
  force_integer ? (page || 1).to_i : page
23
25
  end
24
26
 
25
- def resolve_limit(options)
26
- limit_key = options[:limit_key] || DEFAULT[:limit_key]
27
- return options[:limit] || DEFAULT[:limit] \
28
- unless options[:client_max_limit] &&
29
- (requested_limit = @params.dig(options[:root_key], limit_key) || @params[limit_key])
27
+ def resolve_limit
28
+ limit_key = @options[:limit_key] || DEFAULT[:limit_key]
29
+ return @options[:limit] || DEFAULT[:limit] \
30
+ unless @options[:client_max_limit] &&
31
+ (requested_limit = @params.dig(@options[:root_key], limit_key) || @params[limit_key])
30
32
 
31
- [requested_limit.to_i, options[:client_max_limit]].min
33
+ [requested_limit.to_i, @options[:client_max_limit]].min
32
34
  end
33
35
  end
34
36
  end
@@ -9,25 +9,22 @@ class Pagy
9
9
  module_function
10
10
 
11
11
  # Extracted from Rack::Utils and reformatted for rubocop
12
- # Add the 'unescaped' param, and use it for simple and safe url-templating.
13
- # All string keyed hashes
14
- def build_nested_query(value, prefix = nil, unescaped = [])
12
+ # Skip escaping for Pagy::RawQueryValue
13
+ def build_nested_query(value, prefix = nil)
15
14
  case value
16
15
  when Array
17
- value.map { |v| build_nested_query(v, "#{prefix}[]", unescaped) }.join('&')
16
+ value.map { |v| build_nested_query(v, "#{prefix}[]") }.join('&')
18
17
  when Hash
19
18
  value.map do |k, v|
20
- new_k = prefix ? "#{prefix}[#{escape(k)}]" : escape(k)
21
- unescaped[unescaped.find_index(k)] = new_k if unescaped.size.positive? && new_k != k && unescaped.include?(k)
22
- build_nested_query(v, new_k, unescaped)
19
+ build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
23
20
  end.delete_if(&:empty?).join('&')
24
21
  when nil
25
22
  escape(prefix)
26
23
  else
27
24
  raise ArgumentError, 'value must be a Hash' if prefix.nil?
28
- return "#{escape(prefix)}=#{value}" if unescaped.include?(prefix)
29
25
 
30
- "#{escape(prefix)}=#{escape(value)}"
26
+ final_value = value.is_a?(RawQueryValue) ? value.to_s : escape(value)
27
+ "#{escape(prefix)}=#{final_value}"
31
28
  end
32
29
  end
33
30
 
@@ -46,12 +43,12 @@ class Pagy
46
43
  params = @request.params.clone(freeze: false)
47
44
  params.delete(root_key || page_key)
48
45
  factors = {}.tap do |h|
49
- h[page_key] = countless? ? "#{page || 1}+#{@last}" : page
46
+ h[page_key] = countless? ? RawQueryValue.new("#{page || 1}+#{@last}") : page
50
47
  h[limit_key] = limit_token || limit if client_max_limit
51
48
  end.compact # No empty params
52
49
  params.merge!(root_key ? { root_key => factors } : factors) if factors.size.positive?
53
50
  querify&.(params) # Must modify the params: the returned value is ignored
54
- query_string = QueryUtils.build_nested_query(params, nil, [page_key, limit_key])
51
+ query_string = QueryUtils.build_nested_query(params)
55
52
  query_string = "?#{query_string}" unless query_string.empty?
56
53
  fragment &&= %(##{fragment}) unless fragment&.start_with?('#')
57
54
  "#{@request.base_url if absolute}#{path || @request.path}#{query_string}#{fragment}"
@@ -7,8 +7,8 @@ class Pagy
7
7
 
8
8
  # Common search logic
9
9
  def wrap(pagy_search_args, options)
10
- options[:page] ||= options[:request].resolve_page(options)
11
- options[:limit] = options[:request].resolve_limit(options)
10
+ options[:page] ||= options[:request].resolve_page
11
+ options[:limit] = options[:request].resolve_limit
12
12
  pagy, results = yield
13
13
  calling = pagy_search_args[4..]
14
14
  [pagy, calling.empty? ? results : results.send(*calling)]
@@ -18,7 +18,7 @@ class Pagy
18
18
  %( #{anchor_string}) if anchor_string}).split(PAGE_TOKEN, 2)
19
19
 
20
20
  lambda do |page, text = page_label(page), classes: nil, aria_label: nil|
21
- title = if (counts = @options[:counts]) # only for calendar + counts
21
+ title = if (counts = @options[:counts]) # only for calendar with counts
22
22
  count = counts[page - 1]
23
23
  classes = classes ? "#{classes} empty-page" : 'empty-page' if count.zero?
24
24
  info_key = count.zero? ? 'pagy.info_tag.no_items' : 'pagy.info_tag.single_page'
@@ -7,14 +7,14 @@ class Pagy
7
7
  # Return the Offset::Countless instance and records
8
8
  def paginate(collection, options)
9
9
  if options[:page].nil?
10
- page = options[:request].resolve_page(options, force_integer: false) # accept nil and strings
10
+ page = options[:request].resolve_page(force_integer: false) # accept nil and strings
11
11
  if page.is_a?(String)
12
12
  p, l = page.split(/ /, 2).map(&:to_i) # decoded '+' added by the compose_page_url
13
13
  options[:page] = p if p.positive?
14
14
  options[:last] = l if l&.positive?
15
15
  end
16
16
  end
17
- options[:limit] = options[:request].resolve_limit(options)
17
+ options[:limit] = options[:request].resolve_limit
18
18
  pagy = Offset::Countless.new(**options)
19
19
  [pagy, pagy.records(collection)]
20
20
  end
@@ -9,16 +9,16 @@ class Pagy
9
9
  # Return the Pagy::Keyset::Keynav instance and paginated records.
10
10
  # Fall back to :countless if the :page has no client data.
11
11
  def paginate(set, options)
12
- page = options[:request].resolve_page(options, force_integer: false) # allow nil
12
+ page = options[:request].resolve_page(force_integer: false) # allow nil
13
13
  if page&.match(' ') # countless page -> no augmentation -> fallback
14
14
  return CountlessPaginator.paginate(set, page:, **options)
15
15
  elsif page.is_a?(String) # keynav page param
16
16
  page_arguments = JSON.parse(B64.urlsafe_decode(page))
17
- # Restart the pagination from page 1 if the url has been requested from another browser
17
+ # Restart the pagination from page 1/nil if the url has been requested from another browser
18
18
  options[:page] = page_arguments if options[:request].cookie == page_arguments.shift
19
19
  end
20
20
 
21
- options[:limit] = options[:request].resolve_limit(options)
21
+ options[:limit] = options[:request].resolve_limit
22
22
  pagy = Keyset::Keynav.new(set, **options)
23
23
  [pagy, pagy.records]
24
24
  end
@@ -6,8 +6,8 @@ class Pagy
6
6
 
7
7
  # Return Pagy::Keyset instance and paginated records
8
8
  def paginate(set, options)
9
- options[:page] ||= options[:request].resolve_page(options, force_integer: false) # allow nil
10
- options[:limit] = options[:request].resolve_limit(options)
9
+ options[:page] ||= options[:request].resolve_page(force_integer: false) # allow nil
10
+ options[:limit] = options[:request].resolve_limit
11
11
  pagy = Keyset.new(set, **options)
12
12
  [pagy, pagy.records]
13
13
  end
@@ -20,13 +20,14 @@ class Pagy
20
20
  protected
21
21
 
22
22
  define_method :pagy do |paginator = :offset, collection, **options|
23
- options[:root_key] = 'page' if options[:jsonapi] # enforce 'page' root_key for JSON:API
24
- options[:request] = Request.new(options[:request] || request)
25
- arguments = if paginator == :calendar
26
- [self, collection, options]
27
- else
28
- [collection, Pagy.options.merge(options)]
29
- end
23
+ arguments = if paginator == :calendar
24
+ [self, collection, options]
25
+ else
26
+ [collection, options = Pagy.options.merge(options)]
27
+ end
28
+ options[:root_key] = 'page' if options[:jsonapi] # enforce 'page' root_key for JSON:API
29
+ options[:request] ||= request # user set request or self.request
30
+ options[:request] = Request.new(options) # Pagy::Request
30
31
  Pagy.const_get(paginators[paginator]).paginate(*arguments)
31
32
  end
32
33
  end
@@ -6,8 +6,8 @@ class Pagy
6
6
 
7
7
  # Return the Pagy::Offset instance and results
8
8
  def paginate(collection, options)
9
- options[:page] ||= options[:request].resolve_page(options)
10
- options[:limit] = options[:request].resolve_limit(options)
9
+ options[:page] ||= options[:request].resolve_page
10
+ options[:limit] = options[:request].resolve_limit
11
11
  options[:count] ||= collection.instance_of?(Array) ? collection.size : OffsetPaginator.get_count(collection, options)
12
12
  pagy = Offset.new(**options)
13
13
  [pagy, collection.instance_of?(Array) ? collection[pagy.offset, pagy.limit] : pagy.records(collection)]
data/lib/pagy.rb CHANGED
@@ -8,11 +8,13 @@ require_relative 'pagy/toolbox/helpers/loader'
8
8
 
9
9
  # Top superclass: it defines only what's common to all the subclasses
10
10
  class Pagy
11
- VERSION = '43.1.0'
11
+ class RawQueryValue < String; end
12
+
13
+ VERSION = '43.1.1'
12
14
  ROOT = Pathname.new(__dir__).parent.freeze
13
15
  DEFAULT = { limit: 20, limit_key: 'limit', page_key: 'page' }.freeze
14
- PAGE_TOKEN = 'P '
15
- LIMIT_TOKEN = 'L '
16
+ PAGE_TOKEN = RawQueryValue.new('P ')
17
+ LIMIT_TOKEN = RawQueryValue.new('L ')
16
18
  LABEL_TOKEN = 'L'
17
19
  A_TAG = '<a style="display: none;">#</a>'
18
20
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pagy
3
3
  version: !ruby/object:Gem::Version
4
- version: 43.1.0
4
+ version: 43.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis
@@ -49,6 +49,7 @@ files:
49
49
  - apps/calendar.ru
50
50
  - apps/demo.ru
51
51
  - apps/index.rb
52
+ - apps/keynav+root_key.ru
52
53
  - apps/keynav.ru
53
54
  - apps/keyset.ru
54
55
  - apps/keyset_sequel.ru