pagy 43.1.8 → 43.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +1 -1
  3. data/apps/demo.ru +1 -1
  4. data/apps/keynav+root_key.ru +1 -1
  5. data/apps/keynav.ru +1 -1
  6. data/apps/keyset.ru +1 -1
  7. data/apps/keyset_sequel.ru +1 -1
  8. data/apps/rails.ru +3 -1
  9. data/apps/rails_page_segment.rb +71 -0
  10. data/apps/repro.ru +3 -2
  11. data/bin/pagy +1 -1
  12. data/config/pagy.rb +1 -1
  13. data/javascripts/pagy.js +11 -9
  14. data/javascripts/pagy.js.map +3 -3
  15. data/javascripts/pagy.min.js +2 -1
  16. data/javascripts/pagy.mjs +10 -8
  17. data/javascripts/wand.js +15 -9
  18. data/lib/pagy/classes/calendar/calendar.rb +2 -2
  19. data/lib/pagy/classes/calendar/unit.rb +5 -9
  20. data/lib/pagy/classes/keyset/keynav.rb +4 -3
  21. data/lib/pagy/classes/keyset/keyset.rb +34 -12
  22. data/lib/pagy/classes/offset/countish.rb +17 -0
  23. data/lib/pagy/classes/offset/countless.rb +2 -2
  24. data/lib/pagy/classes/offset/offset.rb +2 -1
  25. data/lib/pagy/classes/offset/search.rb +1 -1
  26. data/lib/pagy/classes/request.rb +5 -1
  27. data/lib/pagy/modules/abilities/countable.rb +23 -0
  28. data/lib/pagy/modules/abilities/linkable.rb +19 -9
  29. data/lib/pagy/toolbox/helpers/data_hash.rb +14 -14
  30. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +3 -3
  31. data/lib/pagy/toolbox/helpers/loader.rb +3 -0
  32. data/lib/pagy/toolbox/helpers/page_url.rb +6 -8
  33. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +1 -1
  34. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +1 -1
  35. data/lib/pagy/toolbox/paginators/countish.rb +37 -0
  36. data/lib/pagy/toolbox/paginators/countless.rb +1 -1
  37. data/lib/pagy/toolbox/paginators/method.rb +6 -5
  38. data/lib/pagy/toolbox/paginators/offset.rb +3 -15
  39. data/lib/pagy.rb +3 -5
  40. metadata +6 -6
  41. data/lib/pagy/classes/keyset/active_record.rb +0 -11
  42. data/lib/pagy/classes/keyset/keynav/active_record.rb +0 -13
  43. data/lib/pagy/classes/keyset/keynav/sequel.rb +0 -13
  44. data/lib/pagy/classes/keyset/sequel.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16e959906210363656c8985bb239027b2ed443f6fc9f1a91eb5f6415e7133825
4
- data.tar.gz: a793f4c037cde37a6a46cd2a86a2cce3f0f90ae0b65530dc09526754f81ce5bd
3
+ metadata.gz: 48e5f5812c5ef4b7f2a655990b494066bfbc0730635463d140d1e2bdd636523c
4
+ data.tar.gz: b68fde5b5b43bb8014ea9664a91703c7453dca9c7fd23fa3bebce40b9d92678d
5
5
  SHA512:
6
- metadata.gz: a32c5d0809c5973bedaf9e4b545860d4e58ede50ad9e72de178d6facdd71c5598f3dbf5289894ab919b3df9ab9704c96063d8d4e16763af6db42b22c0f90e4ab
7
- data.tar.gz: d53f557db4887a52a03eeb84e3f11ed8fa4f524264a5c22bcb93c068beb6b9da451fc2f42ab20b081069ef90f83b7c935ee7527b5eb3e19d84cfc69bb7fdd6f6
6
+ metadata.gz: ea47f8b51fd9a8d67df167f37fa6817ae7ec5dc1a509ae973f6b66af6e6e4aba0326d727adf6fcdc7e2a0ab053ce3872cc28e7c8ad4b62f4e08347f15049402e
7
+ data.tar.gz: 33a577b3e6fe5b9b9124bdccbcf5cb4ec8082e9cdf3122ccac941fe2f87b126084032ea0b849b9587ac62a8d547e690c8d571e46c376dc7e396265092b2db148
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.8'
19
+ VERSION = '43.2.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.8'
22
+ VERSION = '43.2.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")
@@ -16,7 +16,7 @@
16
16
  # URL
17
17
  # http://127.0.0.1:8000
18
18
 
19
- VERSION = '43.1.8'
19
+ VERSION = '43.2.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/keynav.ru CHANGED
@@ -16,7 +16,7 @@
16
16
  # URL
17
17
  # http://127.0.0.1:8000
18
18
 
19
- VERSION = '43.1.8'
19
+ VERSION = '43.2.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/keyset.ru CHANGED
@@ -16,7 +16,7 @@
16
16
  # URL
17
17
  # http://127.0.0.1:8000
18
18
 
19
- VERSION = '43.1.8'
19
+ VERSION = '43.2.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.8'
19
+ VERSION = '43.2.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.8'
19
+ VERSION = '43.2.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")
@@ -35,6 +35,7 @@ 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
38
39
 
39
40
  OUTPUT = Rails.env.showcase? ? IO::NULL : $stdout
40
41
 
@@ -49,6 +50,7 @@ class PagyRails < Rails::Application # :nodoc:
49
50
 
50
51
  routes.draw do
51
52
  root to: 'comments#index'
53
+ # get '/comments(/:page)', to: 'comments#index' # Uncomment to test the rails_page_segment.rb override
52
54
  get '/javascripts/:file', to: 'pagy#javascripts', file: /.*/
53
55
  end
54
56
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ################# IMPORTANT WARNING #################
4
+ # Use this override ONLY if you strictly need to support the page param as a dynamic segment.
5
+ # (e.g. get '/comments(/:page)', to: 'comments#index').
6
+ #
7
+ # This setup forces Pagy to use the Rails `url_for` method, which is significantly
8
+ # slower (~20x) than Pagy's native URL generation.
9
+ # #####################################################
10
+
11
+ # 1. CONSTANT REDEFINITION
12
+ # We must replace Pagy's internal tokens.
13
+ # Why: Pagy marks its default tokens as "Escaped" to allow spaces into the URL template for safe interpolation.
14
+ # Since we are handing control back to Rails' standard QueryUtils, we need simple, URL-safe placeholders
15
+ # that Rails won't need to escape, to avoid mismatches between non-escaped and escaped tokens.
16
+ Pagy.send(:remove_const, :PAGE_TOKEN)
17
+ Pagy.send(:remove_const, :LIMIT_TOKEN)
18
+
19
+ Pagy::PAGE_TOKEN = '___PAGY_PAGE___'
20
+ Pagy::LIMIT_TOKEN = '___PAGY_LIMIT___'
21
+
22
+ require 'pagy/toolbox/paginators/method'
23
+ require 'pagy/modules/abilities/linkable'
24
+
25
+ class Pagy
26
+ # 2. REQUEST PARAMETERS
27
+ # Why: Pagy defaults to Rack::Request to be framework agnostic.
28
+ # To support dynamic segments (which are routing concepts, not query params),
29
+ # we must switch to the Rails `request.params` method which includes path parameters.
30
+ module RequestOverride
31
+ def get_params(request)
32
+ request.params
33
+ end
34
+ end
35
+ Request.prepend RequestOverride
36
+
37
+ # 3. CONTEXT INJECTION
38
+ # Why: The Pagy object needs access to the Controller instance to call `url_for`.
39
+ # We intercept the `pagy` method in the controller to inject `self` (the controller)
40
+ # into the Pagy instance as `@context`.
41
+ module MethodOverride
42
+ def pagy(...)
43
+ super.tap do |result|
44
+ # result is [pagy_object, records]
45
+ # We inject the controller (self) directly into the pagy object
46
+ result[0].instance_variable_set(:@context, self)
47
+ end
48
+ end
49
+ end
50
+ Method.prepend MethodOverride
51
+
52
+ # 4. URL GENERATION
53
+ # Why: We override Pagy's optimized string interpolation with Rails' `url_for`.
54
+ # We combine the current Rails parameters with Pagy's options and generate the URL.
55
+ module LinkableOverride
56
+ def compose_url(absolute, _path, params, fragment)
57
+ params[:anchor] = fragment if fragment
58
+ params[:only_path] = !absolute
59
+
60
+ # Call the Rails url_for method from the controller context
61
+ @context.url_for(params)
62
+ end
63
+ end
64
+ Linkable.prepend LinkableOverride
65
+ end
66
+
67
+ # USAGE with rails.ru
68
+
69
+ # Search and uncomment the following lines in the rails.ru app:
70
+ # require_relative 'rails_page_segment' # Uncomment to test the rails_page_segment.rb override
71
+ # get '/comments(/:page)', to: 'comments#index' # Uncomment to test the rails_page_segment.rb override
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.8'
19
+ VERSION = '43.2.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")
@@ -55,8 +55,9 @@ class PagyRepro < Sinatra::Base
55
55
  # Edit this action as needed
56
56
  get '/' do
57
57
  collection = MockCollection.new
58
- @pagy, @records = pagy(collection)
58
+ @pagy, @records = pagy(collection) # simplest form
59
59
  # @pagy, @records = pagy(:offset, collection, limit: 7, client_max_limit: 30)
60
+ # @pagy, @records = pagy(:countish, collection, ttl: 20)
60
61
  # @pagy, @records = pagy(:countless, collection)
61
62
  # @pagy, @records = pagy(Array(1..1000))
62
63
  # response.headers.merge!(@pagy.headers_hash)
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.8'
4
+ VERSION = '43.2.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.8)
3
+ # Pagy initializer file (43.2.1)
4
4
  # See https://ddnexus.github.io/pagy/resources/initializer/
5
5
 
6
6
  ############ Global Options ################################################################
data/javascripts/pagy.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // pagy.ts
2
2
  window.Pagy = (() => {
3
- const storageSupport = "sessionStorage" in window && "BroadcastChannel" in window, pageRe = "P ";
3
+ const storageSupport = "sessionStorage" in window && "BroadcastChannel" in window;
4
4
  let pagy = "pagy", storage, sync, tabId;
5
5
  if (storageSupport) {
6
6
  storage = sessionStorage;
@@ -67,6 +67,7 @@ window.Pagy = (() => {
67
67
  };
68
68
  const buildNavJs = (nav, [
69
69
  [before, anchor, current, gap, after],
70
+ pageToken,
70
71
  [widths, series, labels],
71
72
  keynavArgs
72
73
  ]) => {
@@ -79,7 +80,7 @@ window.Pagy = (() => {
79
80
  }
80
81
  let html = before;
81
82
  series[index].forEach((item, i) => {
82
- html += item == "gap" ? gap : (typeof item == "number" ? anchor.replace(pageRe, item) : current).replace("L<", labels?.[index][i] ?? item + "<");
83
+ html += item == "gap" ? gap : (typeof item == "number" ? anchor.replace(pageToken, item) : current).replace("L<", labels?.[index][i] ?? item + "<");
83
84
  });
84
85
  html += after;
85
86
  nav.innerHTML = "";
@@ -93,13 +94,13 @@ window.Pagy = (() => {
93
94
  rjsObserver.observe(parent);
94
95
  }
95
96
  };
96
- const initInputNavJs = async (nav, [url_token, keynavArgs]) => {
97
+ const initInputNavJs = async (nav, [url_token, pageToken, keynavArgs]) => {
97
98
  const augment = keynavArgs && storageSupport ? await augmentKeynav(nav, keynavArgs) : (page) => page;
98
- initInput(nav, (inputValue) => url_token.replace(pageRe, augment(inputValue)));
99
+ initInput(nav, (inputValue) => url_token.replace(pageToken, augment(inputValue)));
99
100
  };
100
- const initLimitTagJs = (span, [from, url_token]) => {
101
+ const initLimitTagJs = (span, [from, url_token, page_token, limitToken]) => {
101
102
  initInput(span, (inputValue) => {
102
- return url_token.replace(pageRe, Math.max(Math.ceil(from / parseInt(inputValue)), 1)).replace("L ", inputValue);
103
+ return url_token.replace(page_token, Math.max(Math.ceil(from / parseInt(inputValue)), 1)).replace(limitToken, inputValue);
103
104
  });
104
105
  };
105
106
  const initInput = (element, getUrl) => {
@@ -125,7 +126,7 @@ window.Pagy = (() => {
125
126
  });
126
127
  };
127
128
  return {
128
- version: "43.1.8",
129
+ version: "43.2.1",
129
130
  init(arg) {
130
131
  const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
131
132
  for (const element of elements) {
@@ -141,12 +142,13 @@ window.Pagy = (() => {
141
142
  initLimitTagJs(element, args);
142
143
  }
143
144
  } catch (err) {
144
- console.warn("Pagy.init: %o\n%s", element, err);
145
+ console.warn(`Pagy.init: %o
146
+ %s`, element, err);
145
147
  }
146
148
  }
147
149
  }
148
150
  };
149
151
  })();
150
152
 
151
- //# debugId=391D3AC59BE9808764756E2164756E21
153
+ //# debugId=F3282E394DA790A164756E2164756E21
152
154
  //# 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 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.8\",\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.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": "391D3AC59BE9808764756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "F3282E394DA790A164756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1 +1,2 @@
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.8",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)}}}})();
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",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
+ %s`,F,H)}}}})();
data/javascripts/pagy.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  const Pagy = (() => {
2
- const storageSupport = "sessionStorage" in window && "BroadcastChannel" in window, pageRe = "P ";
2
+ const storageSupport = "sessionStorage" in window && "BroadcastChannel" in window;
3
3
  let pagy = "pagy", storage, sync, tabId;
4
4
  if (storageSupport) {
5
5
  storage = sessionStorage;
@@ -66,6 +66,7 @@ const Pagy = (() => {
66
66
  };
67
67
  const buildNavJs = (nav, [
68
68
  [before, anchor, current, gap, after],
69
+ pageToken,
69
70
  [widths, series, labels],
70
71
  keynavArgs
71
72
  ]) => {
@@ -78,7 +79,7 @@ const Pagy = (() => {
78
79
  }
79
80
  let html = before;
80
81
  series[index].forEach((item, i) => {
81
- html += item == "gap" ? gap : (typeof item == "number" ? anchor.replace(pageRe, item) : current).replace("L<", labels?.[index][i] ?? item + "<");
82
+ html += item == "gap" ? gap : (typeof item == "number" ? anchor.replace(pageToken, item) : current).replace("L<", labels?.[index][i] ?? item + "<");
82
83
  });
83
84
  html += after;
84
85
  nav.innerHTML = "";
@@ -92,13 +93,13 @@ const Pagy = (() => {
92
93
  rjsObserver.observe(parent);
93
94
  }
94
95
  };
95
- const initInputNavJs = async (nav, [url_token, keynavArgs]) => {
96
+ const initInputNavJs = async (nav, [url_token, pageToken, keynavArgs]) => {
96
97
  const augment = keynavArgs && storageSupport ? await augmentKeynav(nav, keynavArgs) : (page) => page;
97
- initInput(nav, (inputValue) => url_token.replace(pageRe, augment(inputValue)));
98
+ initInput(nav, (inputValue) => url_token.replace(pageToken, augment(inputValue)));
98
99
  };
99
- const initLimitTagJs = (span, [from, url_token]) => {
100
+ const initLimitTagJs = (span, [from, url_token, page_token, limitToken]) => {
100
101
  initInput(span, (inputValue) => {
101
- return url_token.replace(pageRe, Math.max(Math.ceil(from / parseInt(inputValue)), 1)).replace("L ", inputValue);
102
+ return url_token.replace(page_token, Math.max(Math.ceil(from / parseInt(inputValue)), 1)).replace(limitToken, inputValue);
102
103
  });
103
104
  };
104
105
  const initInput = (element, getUrl) => {
@@ -124,7 +125,7 @@ const Pagy = (() => {
124
125
  });
125
126
  };
126
127
  return {
127
- version: "43.1.8",
128
+ version: "43.2.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) {
@@ -140,7 +141,8 @@ const Pagy = (() => {
140
141
  initLimitTagJs(element, args);
141
142
  }
142
143
  } catch (err) {
143
- console.warn("Pagy.init: %o\n%s", element, err);
144
+ console.warn(`Pagy.init: %o
145
+ %s`, element, err);
144
146
  }
145
147
  }
146
148
  }
data/javascripts/wand.js CHANGED
@@ -62,7 +62,7 @@ const fontIsolation = (async () => {
62
62
  let cssText = await response.text();
63
63
  fontMappings.forEach((mapping) => {
64
64
  const regex = new RegExp(`(font-family:\\s*['"]?)${mapping.original}(['"]?;?)`, "g");
65
- cssText = cssText.replace(regex, `\$1${mapping.custom}\$2`);
65
+ cssText = cssText.replace(regex, `$1${mapping.custom}$2`);
66
66
  });
67
67
  classMappings.forEach((mapping) => {
68
68
  const regex = new RegExp(`\\.(${mapping.original})(?=[\\s\\.{,:])`, "g");
@@ -70,7 +70,9 @@ const fontIsolation = (async () => {
70
70
  });
71
71
  const fontFaceRegex = /(?:\/\*[\s\S]*?\*\/[\s\n]*)?@font-face\s*\{[^}]*}/g;
72
72
  const fontFaceMatches = cssText.match(fontFaceRegex);
73
- result.fontFaceRules = fontFaceMatches ? fontFaceMatches.join("\n\n") : "";
73
+ result.fontFaceRules = fontFaceMatches ? fontFaceMatches.join(`
74
+
75
+ `) : "";
74
76
  result.componentCss = cssText.replace(fontFaceRegex, "").trim();
75
77
  } catch (error) {
76
78
  console.error("Failed to process Google Font CSS:", fontCssUrl, error);
@@ -79,8 +81,10 @@ const fontIsolation = (async () => {
79
81
  return result;
80
82
  }
81
83
  const processedResults = await Promise.all(fontDefinitions.map((def) => processGoogleFontCSS(def.url, def.fontMappings, def.classMappings)));
82
- const allFontFaceRules = processedResults.map((r) => r.fontFaceRules).join("\n");
83
- const allComponentCss = processedResults.map((r) => r.componentCss).join("\n");
84
+ const allFontFaceRules = processedResults.map((r) => r.fontFaceRules).join(`
85
+ `);
86
+ const allComponentCss = processedResults.map((r) => r.componentCss).join(`
87
+ `);
84
88
  if (allFontFaceRules) {
85
89
  const styleEl = document.createElement("style");
86
90
  styleEl.id = "pagy-wand-font-css";
@@ -696,11 +700,11 @@ document.addEventListener("DOMContentLoaded", async () => {
696
700
  </dd>
697
701
  </dl>
698
702
  <h4>Customizing</h4>
699
- <p>\u2022 You can change Pagy's styling quite radically, by just setting a few CSS Custom Properties:
703
+ <p>• You can change Pagy's styling quite radically, by just setting a few CSS Custom Properties:
700
704
  the <code><i>pagy.css</i></code> or <code><i>pagy-tailwind.css</i></code> calculates all the other metrics.</p>
701
- <p>\u2022 Pick a Presets as a starting point, customize it with the controls,
705
+ <p>• Pick a Presets as a starting point, customize it with the controls,
702
706
  and copy/paste the CSS Override in your Stylesheet.</p>
703
- <p>\u2022 Add further customization to the <code>.pagy</code> CSS Override, or override the calculated properties
707
+ <p>• Add further customization to the <code>.pagy</code> CSS Override, or override the calculated properties
704
708
  for full control over the final style.</p>
705
709
  <p><b>Important</b>: Do not link the Pagy CSS file. Copy its customized content in your CSS,
706
710
  to avoid unwanted cosmetic changes that could happen on update.</p>
@@ -973,10 +977,12 @@ document.addEventListener("DOMContentLoaded", async () => {
973
977
  applyCSS(presetCSS);
974
978
  }
975
979
  function updateOverride() {
976
- let override = `.pagy {\n`;
980
+ let override = `.pagy {
981
+ `;
977
982
  Object.values(controls).forEach((c) => {
978
983
  if (c.name !== "") {
979
- override += ` ${c.name}: ${c.input.value}${c.unit};\n`;
984
+ override += ` ${c.name}: ${c.input.value}${c.unit};
985
+ `;
980
986
  }
981
987
  });
982
988
  override += "}";
@@ -21,17 +21,17 @@ class Pagy
21
21
  UNITS = %i[year quarter month week day] # rubocop:disable Style/MutableConstant
22
22
 
23
23
  class << self
24
- # :nocov:
25
24
  # Localize with rails-i18n in any env
26
25
  def localize_with_rails_i18n_gem(*locales)
27
26
  Unit.prepend(Module.new { def localize(...) = ::I18n.localize(...) })
27
+ # :nocov:
28
28
  raise RailsI18nLoadError, "Pagy: The gem 'rails-i18n' must be installed if you don't use Rails" \
29
29
  unless (path = Gem.loaded_specs['rails-i18n']&.full_gem_path)
30
30
 
31
+ # :nocov:
31
32
  path = Pathname.new(path)
32
33
  ::I18n.load_path += locales.map { |locale| path.join("rails/locale/#{locale}.yml") }
33
34
  end
34
- # :nocov:
35
35
 
36
36
  private
37
37
 
@@ -17,7 +17,7 @@ class Pagy
17
17
  include Rangeable
18
18
  include Shiftable
19
19
 
20
- def initialize(**) # rubocop:disable Lint/MissingSuper
20
+ def initialize(**)
21
21
  assign_options(**)
22
22
  assign_and_check(page: 1)
23
23
  assign_unit_variables
@@ -50,14 +50,7 @@ class Pagy
50
50
  unless time.between?(@initial, fit_final)
51
51
  raise RangeError.new(self, :time, "between #{@initial} and #{fit_final}", time) unless options[:fit_time]
52
52
 
53
- if time < @final
54
- fit_time = @initial
55
- ordinal = 'first'
56
- else
57
- fit_time = fit_final
58
- ordinal = 'last'
59
- end
60
- warn "Pagy::Calendar#page_at: Rescued #{time} out of range by returning the #{ordinal} page."
53
+ fit_time = time < @final ? @initial : fit_final
61
54
  end
62
55
  offset = page_offset_at(fit_time) # offset starts from 0
63
56
  @order == :asc ? offset + 1 : @last - offset
@@ -75,7 +68,10 @@ class Pagy
75
68
  # Apply the strftime format to the time.
76
69
  # Localization other than :en, require the rails-I18n gem.
77
70
  def localize(time, **options)
71
+ # Impossible to "unprepend" the rails-i18n after it runs localize_with_rails_i18n_gem in test
72
+ # :nocov:
78
73
  time.strftime(options[:format])
74
+ # :nocov:
79
75
  end
80
76
 
81
77
  # The number of time units to offset from the @initial time, in order to get the ordered starting time for the page.
@@ -8,9 +8,10 @@ class Pagy
8
8
  PRIOR_PREFIX = 'prior_'
9
9
  PAGE_PREFIX = 'page_'
10
10
 
11
- path = Pathname.new(__FILE__).sub_ext('')
12
- autoload :ActiveRecord, path.join('active_record')
13
- autoload :Sequel, path.join('sequel')
11
+ # Define empty subclasses to allow specific typing without triggering autoload.
12
+ # The .new factory in Keyset will handle mixing in the adapter logic from Pagy::Keyset::Adapters.
13
+ class ActiveRecord < self; end
14
+ class Sequel < self; end
14
15
 
15
16
  # Finalize the instance variables needed for the UI
16
17
  def initialize(set, **)
@@ -6,31 +6,53 @@ require_relative '../../modules/b64'
6
6
  class Pagy
7
7
  # Implement wicked-fast keyset pagination for big data
8
8
  class Keyset < Pagy
9
- path = Pathname.new(__dir__)
10
- autoload :ActiveRecord, path.join('active_record')
11
- autoload :Sequel, path.join('sequel')
12
- autoload :Keynav, path.join('keynav')
9
+ # Autoload adapters: files are loaded only when const_get accesses them
10
+ module Adapters
11
+ path = Pathname.new(__dir__)
12
+ autoload :ActiveRecord, path.join('adapters/active_record')
13
+ autoload :Sequel, path.join('adapters/sequel')
14
+ end
15
+
16
+ autoload :Keynav, Pathname.new(__dir__).join('keynav')
17
+
18
+ # Define empty subclasses to allow specific typing without triggering autoload
19
+ class ActiveRecord < self; end
20
+ class Sequel < self; end
13
21
 
14
22
  class TypeError < ::TypeError; end
15
23
 
16
- # Initialize Keyset* and Keyset::Keynav* classes and subclasses
24
+ # Factory method: detects the set type, configures the subclass, and instantiates
17
25
  def self.new(set, **)
18
- # Subclass instances run only the initializer
19
- if /::(?:ActiveRecord|Sequel)$/.match?(name) # check without triggering autoload
26
+ # 1. Handle direct subclass usage (e.g. Pagy::Keyset::ActiveRecord.new)
27
+ if /::(?:ActiveRecord|Sequel)$/.match?(name)
28
+ # Ensure the adapter is mixed in (lazy load)
29
+ mix_in_adapter(name.split('::').last)
20
30
  return allocate.tap { |instance| instance.send(:initialize, set, **) }
21
31
  end
22
32
 
23
- subclass = if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
24
- self::ActiveRecord
33
+ # 2. Handle Factory usage (Pagy::Keyset.new)
34
+ orm_name = if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
35
+ :ActiveRecord
25
36
  elsif defined?(::Sequel) && set.is_a?(::Sequel::Dataset)
26
- self::Sequel
37
+ :Sequel
27
38
  else
28
39
  raise TypeError, "expected an ActiveRecord::Relation or Sequel::Dataset; got #{set.class}"
29
40
  end
41
+
42
+ # Get the specific subclass (self::ActiveRecord)
43
+ subclass = const_get(orm_name)
44
+ # Ensure the adapter is mixed in (lazy load)
45
+ subclass.mix_in_adapter(orm_name)
30
46
  subclass.new(set, **)
31
47
  end
32
48
 
33
- def initialize(set, **) # rubocop:disable Lint/MissingSuper
49
+ # Helper to lazy-include the adapter module
50
+ def self.mix_in_adapter(orm_name)
51
+ adapter_module = Pagy::Keyset::Adapters.const_get(orm_name)
52
+ include(adapter_module) unless self < adapter_module
53
+ end
54
+
55
+ def initialize(set, **)
34
56
  assign_options(**)
35
57
  assign_and_check(limit: 1)
36
58
  @set = set
@@ -102,7 +124,7 @@ class Pagy
102
124
  last_column, last_direction = keyset.pop
103
125
  intersection = +'('
104
126
  intersection << (keyset.map { |column, _d| "#{identifier[column]} = #{placeholder[column]}" } \
105
- << "#{identifier[last_column]} #{operator[last_direction]} #{placeholder[last_column]}").join(' AND ')
127
+ << "#{identifier[last_column]} #{operator[last_direction]} #{placeholder[last_column]}").join(' AND ')
106
128
  intersection << ')'
107
129
  union << intersection
108
130
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ class Offset
5
+ # Offset pagination with memoized count
6
+ class Countish < Offset
7
+ protected
8
+
9
+ # Return page+count or page+count+epoch
10
+ def compose_page_param(page)
11
+ value = "#{page || 1}+#{@count}"
12
+ value << "+#{@options[:epoch]}" if @options[:epoch]
13
+ EscapedValue.new(value)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -4,7 +4,7 @@ class Pagy
4
4
  class Offset
5
5
  # Offset pagination without a count
6
6
  class Countless < Offset
7
- def initialize(**) # rubocop:disable Lint/MissingSuper
7
+ def initialize(**)
8
8
  assign_options(**)
9
9
  assign_and_check(limit: 1, page: 1)
10
10
  @page = upto_max_pages(@page)
@@ -55,7 +55,7 @@ class Pagy
55
55
  end
56
56
 
57
57
  # Support easy countless page param overriding (for legacy param and behavior)
58
- def compose_page_param(page) = "#{page || 1}+#{@last}"
58
+ def compose_page_param(page) = EscapedValue.new("#{page || 1}+#{@last}")
59
59
  end
60
60
  end
61
61
  end
@@ -9,11 +9,12 @@ class Pagy
9
9
  DEFAULT = { page: 1 }.freeze
10
10
 
11
11
  autoload :Countless, Pathname.new(__dir__).join('countless')
12
+ autoload :Countish, Pathname.new(__dir__).join('countish')
12
13
 
13
14
  include Rangeable
14
15
  include Shiftable
15
16
 
16
- def initialize(**) # rubocop:disable Lint/MissingSuper
17
+ def initialize(**)
17
18
  assign_options(**)
18
19
  assign_and_check(limit: 1, count: 0, page: 1)
19
20
  assign_last
@@ -3,7 +3,7 @@
3
3
  class Pagy
4
4
  module Search
5
5
  class Arguments < Array
6
- def respond_to_missing? = true
6
+ def respond_to_missing?(*) = true
7
7
 
8
8
  def method_missing(*) = push(*)
9
9
  end
@@ -11,7 +11,7 @@ class Pagy
11
11
  if request.is_a?(Hash)
12
12
  request.values_at(:base_url, :path, :params, :cookie)
13
13
  else
14
- [request.base_url, request.path, request.GET.merge(request.POST).to_h, request.cookies['pagy']]
14
+ [request.base_url, request.path, get_params(request), request.cookies['pagy']]
15
15
  end
16
16
  end
17
17
 
@@ -32,5 +32,9 @@ class Pagy
32
32
 
33
33
  [requested_limit.to_i, @options[:client_max_limit]].min
34
34
  end
35
+
36
+ private
37
+
38
+ def get_params(request) = request.GET.merge(request.POST).to_h
35
39
  end
36
40
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Provide the helpers to count a collection
5
+ module Countable
6
+ module_function
7
+
8
+ # Get the collection count
9
+ def get_count(collection, options)
10
+ return collection.size if collection.instance_of?(Array)
11
+ return collection.count unless defined?(::ActiveRecord) && collection.is_a?(::ActiveRecord::Relation)
12
+
13
+ count = if options[:count_over] && !collection.group_values.empty?
14
+ # COUNT(*) OVER ()
15
+ sql = Arel.star.count.over(Arel::Nodes::Grouping.new([]))
16
+ collection.unscope(:order).pick(sql).to_i
17
+ else
18
+ collection.count(:all)
19
+ end
20
+ count.is_a?(Hash) ? count.size : count
21
+ end
22
+ end
23
+ end
@@ -3,6 +3,9 @@
3
3
  require 'uri'
4
4
 
5
5
  class Pagy
6
+ # Support spaces in placeholder params
7
+ class EscapedValue < String; end
8
+
6
9
  # Provide the helpers to handle the url and anchor
7
10
  module Linkable
8
11
  module QueryUtils
@@ -23,8 +26,8 @@ class Pagy
23
26
  else
24
27
  raise ArgumentError, 'value must be a Hash' if prefix.nil?
25
28
 
26
- final_value = value.is_a?(RawQueryValue) ? value.to_s : escape(value)
27
- "#{escape(prefix)}=#{final_value}"
29
+ escaped_value = value.is_a?(EscapedValue) ? value : escape(value)
30
+ "#{escape(prefix)}=#{escaped_value}"
28
31
  end
29
32
  end
30
33
 
@@ -35,19 +38,26 @@ class Pagy
35
38
 
36
39
  protected
37
40
 
41
+ # Overriddable by classes with composite page param
42
+ def compose_page_param(page) = page
43
+
38
44
  # Return the URL for the page, relying on the Pagy::Request
39
- def compose_page_url(page, limit_token: nil, **options)
40
- root_key, page_key, limit_key, limit, client_max_limit, querify, absolute, path, fragment =
45
+ def compose_page_url(page, **options)
46
+ root_key, page_key, limit_key, client_max_limit, limit, querify, absolute, path, fragment =
41
47
  @options.merge(options)
42
- .values_at(:root_key, :page_key, :limit_key, :limit, :client_max_limit, :querify, :absolute, :path, :fragment)
48
+ .values_at(:root_key, :page_key, :limit_key, :client_max_limit, :limit, :querify, :absolute, :path, :fragment)
43
49
  params = @request.params.clone(freeze: false)
44
- (root_key ? params[root_key] ||= {} : params).tap do |h|
45
- { page_key => countless? ? RawQueryValue.new(compose_page_param(page)) : page,
46
- limit_key => (limit_token || limit if client_max_limit) }.each { |k, v| v ? h[k] = v : h.delete(k) }
50
+ (root_key ? params[root_key] = params[root_key]&.clone(freeze: false) || {} : params).tap do |h|
51
+ { page_key => compose_page_param(page),
52
+ limit_key => client_max_limit && limit }.each { |k, v| v ? h[k] = v : h.delete(k) }
47
53
  end
48
54
  querify&.(params) # Must modify the params: the returned value is ignored
55
+ fragment &&= "##{fragment.delete_prefix('#')}"
56
+ compose_url(absolute, path, params, fragment)
57
+ end
58
+
59
+ def compose_url(absolute, path, params, fragment)
49
60
  query_string = QueryUtils.build_nested_query(params).sub(/\A(?=.)/, '?')
50
- fragment &&= "##{fragment.delete_prefix('#')}"
51
61
  "#{@request.base_url if absolute}#{path || @request.path}#{query_string}#{fragment}"
52
62
  end
53
63
  end
@@ -1,27 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Pagy
4
- DEFAULT_DATA_KEYS = %i[url_template first_url previous_url page_url next_url last_url
4
+ DEFAULT_DATA_KEYS = %i[url_template first_url previous_url current_url page_url next_url last_url
5
5
  count page limit last in from to previous next options].freeze
6
6
 
7
7
  # Generate a hash of the wanted internal data
8
8
  def data_hash(data_keys: @options[:data_keys] || DEFAULT_DATA_KEYS, **)
9
- data_keys -= %i[count limit] if calendar?
10
9
  template = compose_page_url(PAGE_TOKEN, **)
11
- to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) }
12
-
10
+ to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) if page }
11
+ data_keys -= %i[count limit] if calendar?
13
12
  data_keys.each_with_object({}) do |key, data|
14
- data[key] = case key
15
- when :url_template then template
16
- when :first_url then compose_page_url(nil, **)
17
- when :previous_url then to_url.(@previous)
18
- when :page_url then to_url.(@page)
19
- when :next_url then to_url.(@next)
20
- when :last_url then to_url.(@last)
21
- else send(key)
22
- end
13
+ value = case key
14
+ when :url_template then template
15
+ when :first_url then compose_page_url(nil, **)
16
+ when :previous_url then to_url.(@previous)
17
+ when :current_url, :page_url then to_url.(@page)
18
+ when :next_url then to_url.(@next)
19
+ when :last_url then to_url.(@last)
20
+ else send(key)
21
+ end
22
+ data[key] = value if value
23
23
  rescue NoMethodError
24
- raise OptionError.new(self, :data, 'to contain known keys', key)
24
+ raise OptionError.new(self, :data_keys, 'to contain known keys/methods', key)
25
25
  end
26
26
  end
27
27
  end
@@ -7,12 +7,12 @@ class Pagy
7
7
  def limit_tag_js(id: nil, item_name: nil, client_max_limit: @options[:client_max_limit], **)
8
8
  raise OptionError.new(self, :client_max_limit, 'to be truthy', client_max_limit) unless client_max_limit
9
9
 
10
- url_token = compose_page_url(PAGE_TOKEN, limit_token: LIMIT_TOKEN)
11
- limit_input = %(<input name="limit" type="number" min="1" max="#{@options[:client_max_limit]}" value="#{
10
+ limit_input = %(<input name="limit" type="number" min="1" max="#{client_max_limit}" value="#{
12
11
  @limit}" style="padding: 0; text-align: center; width: #{@limit.to_s.length + 1}rem;">#{A_TAG})
12
+ url_token = compose_page_url(PAGE_TOKEN, limit: LIMIT_TOKEN)
13
13
 
14
14
  %(<span#{%( id="#{id}") if id} class="pagy limit-tag-js" #{
15
- data_pagy_attribute(:ltj, @from, url_token)
15
+ data_pagy_attribute(:ltj, @from, url_token, PAGE_TOKEN, LIMIT_TOKEN)
16
16
  }><label>#{
17
17
  I18n.translate('pagy.limit_tag_js',
18
18
  item_name: item_name || I18n.translate('pagy.item_name', count: @limit),
@@ -24,8 +24,11 @@ class Pagy
24
24
  send(visibility)
25
25
  # Load the method, overriding its own alias. Next requests will call the method directly.
26
26
  define_method(:"load_#{visibility}") do |*args, **kwargs|
27
+ # Tests shadow the usage of these lines
28
+ # :nocov:
27
29
  require_relative methods[__callee__]
28
30
  send(__callee__, *args, **kwargs)
31
+ # :nocov:
29
32
  end
30
33
  methods.each_key { |method| alias_method method, :"load_#{visibility}" }
31
34
  end
@@ -2,17 +2,15 @@
2
2
 
3
3
  class Pagy
4
4
  # Return the page url for any page
5
- # :nocov:
6
5
  def page_url(page, **)
7
6
  target = case page
8
- when :first then nil
9
- when :current then @page
10
- when :previous then @previous
11
- when :next then @next
12
- when :last then @last
13
- else page
7
+ when :first then nil
8
+ when :current, :page then @page
9
+ when :previous then @previous
10
+ when :next then @next
11
+ when :last then @last
12
+ else page
14
13
  end
15
14
  compose_page_url(target, **) if target || page == :first
16
15
  end
17
- # :nocov:
18
16
  end
@@ -12,7 +12,7 @@ class Pagy
12
12
  def wrap_input_nav_js(html, nav_classes, id: nil, aria_label: nil, **)
13
13
  %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
14
14
  nav_aria_label_attribute(aria_label:)} #{
15
- data = [:inj, compose_page_url(PAGE_TOKEN, **)]
15
+ data = [:inj, compose_page_url(PAGE_TOKEN, **), PAGE_TOKEN]
16
16
  data.push(@update) if keynav?
17
17
  data_pagy_attribute(*data)
18
18
  }>#{html}</nav>)
@@ -29,7 +29,7 @@ class Pagy
29
29
  nav_classes = "pagy-rjs #{nav_classes}" if sequels[0].size > 1
30
30
  %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
31
31
  nav_aria_label_attribute(aria_label:)} #{
32
- data = [:snj, tokens.values, sequels]
32
+ data = [:snj, tokens.values, PAGE_TOKEN, sequels]
33
33
  data.push(@update) if keynav?
34
34
  data_pagy_attribute(*data)
35
35
  }></nav>)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/abilities/countable'
4
+
5
+ class Pagy
6
+ module CountishPaginator
7
+ module_function
8
+
9
+ # Return the Offset::Countish instance and records
10
+ def paginate(collection, options)
11
+ options[:page] ||= options[:request].resolve_page(force_integer: false)
12
+ if options[:page].is_a?(String)
13
+ page, count, epoch = options[:page].split.map(&:to_i)
14
+ options[:page] = page
15
+ end
16
+ setup_options(count, epoch, collection, options)
17
+ options[:limit] = options[:request].resolve_limit
18
+ pagy = Offset::Countish.new(**options)
19
+ [pagy, pagy.records(collection)]
20
+ end
21
+
22
+ # Get the count from the page and set epoch when ttl (Time To Live) requires it
23
+ def setup_options(count, epoch, collection, options)
24
+ now = Time.now.to_i
25
+ if !options[:count] && count && (!options[:ttl] ||
26
+ (epoch && epoch <= now && now < (epoch + options[:ttl]))) # ongoing
27
+ # puts 'ongoing'
28
+ options[:count] = count
29
+ options[:epoch] = epoch if options[:ttl]
30
+ else # recount
31
+ # puts 'recount'
32
+ options[:count] ||= Countable.get_count(collection, options)
33
+ options[:epoch] = now if options[:ttl]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -8,7 +8,7 @@ class Pagy
8
8
  def paginate(collection, options)
9
9
  options[:page] ||= options[:request].resolve_page(force_integer: false) # accept nil and strings
10
10
  if options[:page].is_a?(String)
11
- page, last = options[:page].split(/ /, 2).map(&:to_i) # decoded '+' added by the compose_page_url
11
+ page, last = options[:page].split.map(&:to_i) # decoded '+' added by the compose_page_url
12
12
  options[:page] = page
13
13
  options[:last] = last if last&.positive?
14
14
  end
@@ -5,6 +5,7 @@ require_relative '../../classes/request'
5
5
  class Pagy
6
6
  paginators = { offset: :OffsetPaginator,
7
7
  countless: :CountlessPaginator,
8
+ countish: :CountishPaginator,
8
9
  keyset: :KeysetPaginator,
9
10
  keynav_js: :KeynavJsPaginator,
10
11
  calendar: :CalendarPaginator,
@@ -20,11 +21,11 @@ class Pagy
20
21
  protected
21
22
 
22
23
  define_method :pagy do |paginator = :offset, collection, **options|
23
- arguments = if paginator == :calendar
24
- [self, collection, options]
25
- else
26
- [collection, options = Pagy.options.merge(options)]
27
- end
24
+ arguments = if paginator == :calendar
25
+ [self, collection, options]
26
+ else
27
+ [collection, options = Pagy.options.merge(options)]
28
+ end
28
29
  options[:root_key] = 'page' if options[:jsonapi] # enforce 'page' root_key for JSON:API
29
30
  options[:request] ||= request # user set request or self.request
30
31
  options[:request] = Request.new(options) # Pagy::Request
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../modules/abilities/countable'
4
+
3
5
  class Pagy
4
6
  module OffsetPaginator
5
7
  module_function
@@ -8,23 +10,9 @@ class Pagy
8
10
  def paginate(collection, options)
9
11
  options[:page] ||= options[:request].resolve_page
10
12
  options[:limit] = options[:request].resolve_limit
11
- options[:count] ||= collection.instance_of?(Array) ? collection.size : OffsetPaginator.get_count(collection, options)
13
+ options[:count] ||= Countable.get_count(collection, options)
12
14
  pagy = Offset.new(**options)
13
15
  [pagy, collection.instance_of?(Array) ? collection[pagy.offset, pagy.limit] : pagy.records(collection)]
14
16
  end
15
-
16
- # Get the collection count
17
- def get_count(collection, options)
18
- return collection.count unless defined?(::ActiveRecord) && collection.is_a?(::ActiveRecord::Relation)
19
-
20
- count = if options[:count_over] && !collection.group_values.empty?
21
- # COUNT(*) OVER ()
22
- sql = Arel.star.count.over(Arel::Nodes::Grouping.new([]))
23
- collection.unscope(:order).pick(sql).to_i
24
- else
25
- collection.count(:all)
26
- end
27
- count.is_a?(Hash) ? count.size : count
28
- end
29
17
  end
30
18
  end
data/lib/pagy.rb CHANGED
@@ -8,13 +8,11 @@ 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
- class RawQueryValue < String; end
12
-
13
- VERSION = '43.1.8'
11
+ VERSION = '43.2.1'
14
12
  ROOT = Pathname.new(__dir__).parent.freeze
15
13
  DEFAULT = { limit: 20, limit_key: 'limit', page_key: 'page' }.freeze
16
- PAGE_TOKEN = RawQueryValue.new('P ')
17
- LIMIT_TOKEN = RawQueryValue.new('L ')
14
+ PAGE_TOKEN = EscapedValue.new('P ')
15
+ LIMIT_TOKEN = EscapedValue.new('L ')
18
16
  LABEL_TOKEN = 'L'
19
17
  A_TAG = '<a style="display: none;">#</a>'
20
18
 
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.8
4
+ version: 43.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis
@@ -54,6 +54,7 @@ files:
54
54
  - apps/keyset.ru
55
55
  - apps/keyset_sequel.ru
56
56
  - apps/rails.ru
57
+ - apps/rails_page_segment.rb
57
58
  - apps/repro.ru
58
59
  - bin/pagy
59
60
  - config/pagy.rb
@@ -74,20 +75,18 @@ files:
74
75
  - lib/pagy/classes/calendar/week.rb
75
76
  - lib/pagy/classes/calendar/year.rb
76
77
  - lib/pagy/classes/exceptions.rb
77
- - lib/pagy/classes/keyset/active_record.rb
78
78
  - lib/pagy/classes/keyset/adapters/active_record.rb
79
79
  - lib/pagy/classes/keyset/adapters/sequel.rb
80
80
  - lib/pagy/classes/keyset/keynav.rb
81
- - lib/pagy/classes/keyset/keynav/active_record.rb
82
- - lib/pagy/classes/keyset/keynav/sequel.rb
83
81
  - lib/pagy/classes/keyset/keyset.rb
84
- - lib/pagy/classes/keyset/sequel.rb
82
+ - lib/pagy/classes/offset/countish.rb
85
83
  - lib/pagy/classes/offset/countless.rb
86
84
  - lib/pagy/classes/offset/offset.rb
87
85
  - lib/pagy/classes/offset/search.rb
88
86
  - lib/pagy/classes/request.rb
89
87
  - lib/pagy/console.rb
90
88
  - lib/pagy/modules/abilities/configurable.rb
89
+ - lib/pagy/modules/abilities/countable.rb
91
90
  - lib/pagy/modules/abilities/linkable.rb
92
91
  - lib/pagy/modules/abilities/rangeable.rb
93
92
  - lib/pagy/modules/abilities/shiftable.rb
@@ -130,6 +129,7 @@ files:
130
129
  - lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb
131
130
  - lib/pagy/toolbox/helpers/urls_hash.rb
132
131
  - lib/pagy/toolbox/paginators/calendar.rb
132
+ - lib/pagy/toolbox/paginators/countish.rb
133
133
  - lib/pagy/toolbox/paginators/countless.rb
134
134
  - lib/pagy/toolbox/paginators/elasticsearch_rails.rb
135
135
  - lib/pagy/toolbox/paginators/keynav_js.rb
@@ -202,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
202
202
  - !ruby/object:Gem::Version
203
203
  version: '0'
204
204
  requirements: []
205
- rubygems_version: 3.6.7
205
+ rubygems_version: 3.6.9
206
206
  specification_version: 4
207
207
  summary: "Pagy \U0001F438 The Leaping Gem!"
208
208
  test_files: []
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'adapters/active_record'
4
-
5
- class Pagy
6
- class Keyset
7
- class ActiveRecord < Keyset
8
- include Adapters::ActiveRecord
9
- end
10
- end
11
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../adapters/active_record'
4
-
5
- class Pagy
6
- class Keyset
7
- class Keynav
8
- class ActiveRecord < Keynav
9
- include Adapters::ActiveRecord
10
- end
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../adapters/sequel'
4
-
5
- class Pagy
6
- class Keyset
7
- class Keynav
8
- class Sequel < Keynav
9
- include Adapters::Sequel
10
- end
11
- end
12
- end
13
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'adapters/sequel'
4
-
5
- class Pagy
6
- class Keyset
7
- class Sequel < Keyset
8
- include Adapters::Sequel
9
- end
10
- end
11
- end