pagy 43.2.4 → 43.2.5

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +1 -1
  3. data/apps/demo.ru +1 -1
  4. data/apps/enable_rails_page_segment.rb +6 -2
  5. data/apps/keynav+root_key.ru +1 -1
  6. data/apps/keynav.ru +1 -1
  7. data/apps/keyset.ru +1 -1
  8. data/apps/keyset_sequel.ru +1 -1
  9. data/apps/rails.ru +1 -1
  10. data/apps/repro.ru +1 -1
  11. data/config/pagy.rb +1 -1
  12. data/javascripts/pagy.js +2 -2
  13. data/javascripts/pagy.js.map +2 -2
  14. data/javascripts/pagy.min.js +1 -1
  15. data/javascripts/pagy.mjs +1 -1
  16. data/lib/pagy/classes/calendar/calendar.rb +31 -25
  17. data/lib/pagy/classes/calendar/unit.rb +4 -1
  18. data/lib/pagy/classes/exceptions.rb +1 -0
  19. data/lib/pagy/classes/keyset/adapters/active_record.rb +3 -1
  20. data/lib/pagy/classes/keyset/adapters/sequel.rb +3 -1
  21. data/lib/pagy/classes/keyset/keynav.rb +3 -0
  22. data/lib/pagy/classes/keyset/keyset.rb +13 -17
  23. data/lib/pagy/classes/offset/countless.rb +9 -2
  24. data/lib/pagy/classes/offset/offset.rb +6 -1
  25. data/lib/pagy/classes/request.rb +9 -5
  26. data/lib/pagy/modules/abilities/countable.rb +1 -0
  27. data/lib/pagy/modules/abilities/linkable.rb +18 -11
  28. data/lib/pagy/modules/abilities/rangeable.rb +0 -1
  29. data/lib/pagy/modules/b64.rb +8 -2
  30. data/lib/pagy/modules/console.rb +15 -6
  31. data/lib/pagy/modules/i18n/i18n.rb +11 -4
  32. data/lib/pagy/modules/searcher.rb +8 -4
  33. data/lib/pagy/toolbox/helpers/data_hash.rb +1 -0
  34. data/lib/pagy/toolbox/helpers/headers_hash.rb +2 -1
  35. data/lib/pagy/toolbox/helpers/info_tag.rb +2 -0
  36. data/lib/pagy/toolbox/helpers/input_nav_js.rb +1 -0
  37. data/lib/pagy/toolbox/helpers/page_url.rb +1 -0
  38. data/lib/pagy/toolbox/helpers/series_nav.rb +1 -0
  39. data/lib/pagy/toolbox/helpers/series_nav_js.rb +1 -0
  40. data/lib/pagy/toolbox/paginators/calendar.rb +9 -6
  41. data/lib/pagy/toolbox/paginators/countish.rb +6 -3
  42. data/lib/pagy/toolbox/paginators/countless.rb +5 -2
  43. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +12 -6
  44. data/lib/pagy/toolbox/paginators/keynav_js.rb +5 -3
  45. data/lib/pagy/toolbox/paginators/keyset.rb +2 -1
  46. data/lib/pagy/toolbox/paginators/meilisearch.rb +10 -7
  47. data/lib/pagy/toolbox/paginators/method.rb +7 -5
  48. data/lib/pagy/toolbox/paginators/offset.rb +9 -2
  49. data/lib/pagy/toolbox/paginators/searchkick.rb +12 -9
  50. data/lib/pagy.rb +1 -1
  51. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccb42c6368e3fa431f97d6be3b7f89122ae7b3e4cf2bff5748fb7ea978c2382b
4
- data.tar.gz: 465601dc8f911ef033ca3aa18dc714515c840a2cf8fdcd005fc2e82be99b9d96
3
+ metadata.gz: 0e875b9703ce63e0ea0fb713757bebbb752f17a80b7414e8b88ef1abbc9d3446
4
+ data.tar.gz: e6ea0a3dfcae811e97dd851c00ecb5089c4700d70763c6ef37112486bf129d97
5
5
  SHA512:
6
- metadata.gz: 59459274129f2b25073ccdaf5bc1bd0323f5cfa95fefb69121e51e08e6ad84254524e4c708cea293920bc693187fff0806244d82d497d44513d97463aa2e2647
7
- data.tar.gz: 1c2b1356ec82ce9a582f2ea75bcb9e7340ec9d3ff6f190858b2af039d3f361c350d779db5e170003318624af3c53c974c00cf5865eb0915318dca8da576da59a
6
+ metadata.gz: 5da60ae142b168c6f0d465a88fa7938fd23c9bb832fb4da5afcb2bd87d113238796bb1a01f37a304db4a0d77e90b59ad10847ac33403dd596f8c02a8d6a38f60
7
+ data.tar.gz: 0a914f93b8d74895ce872812f50e7d5f9a883f0fc848bb077f452a389817078ace734cdbb1592d0aaab43f4fb4dff948732a16b4b11dd1a9bbdebf7a163f417b
data/apps/calendar.ru CHANGED
@@ -16,7 +16,7 @@
16
16
  # URL
17
17
  # http://127.0.0.1:8000
18
18
 
19
- VERSION = '43.2.4'
19
+ VERSION = '43.2.5'
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.2.4'
22
+ VERSION = '43.2.5'
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")
@@ -28,13 +28,17 @@ require_relative '../lib/pagy/modules/abilities/linkable'
28
28
  class Pagy
29
29
  # Switch to the `request.params` to get access to rails-added path parameters
30
30
  module RequestOverride
31
- def get_params(request) = request.params
31
+ def get_params(request)
32
+ request.params
33
+ end
32
34
  end
33
35
  Request.prepend RequestOverride
34
36
 
35
37
  # Inject the caller context into the Pagy instance
36
38
  module MethodOverride
37
- def pagy(...) = super.tap { |result| result[0].instance_variable_set(:@context, self) }
39
+ def pagy(...)
40
+ super.tap { _1[0].instance_variable_set(:@context, self) }
41
+ end
38
42
  end
39
43
  Method.prepend MethodOverride
40
44
 
@@ -16,7 +16,7 @@
16
16
  # URL
17
17
  # http://127.0.0.1:8000
18
18
 
19
- VERSION = '43.2.4'
19
+ VERSION = '43.2.5'
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.2.4'
19
+ VERSION = '43.2.5'
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.2.4'
19
+ VERSION = '43.2.5'
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.2.4'
19
+ VERSION = '43.2.5'
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.2.4'
19
+ VERSION = '43.2.5'
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.2.4'
19
+ VERSION = '43.2.5'
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/config/pagy.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Pagy initializer file (43.2.4)
3
+ # Pagy initializer file (43.2.5)
4
4
  # See https://ddnexus.github.io/pagy/resources/initializer/
5
5
 
6
6
  ############ Global Options ################################################################
data/javascripts/pagy.js CHANGED
@@ -126,7 +126,7 @@ window.Pagy = (() => {
126
126
  });
127
127
  };
128
128
  return {
129
- version: "43.2.4",
129
+ version: "43.2.5",
130
130
  init(arg) {
131
131
  const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
132
132
  for (const element of elements) {
@@ -150,5 +150,5 @@ window.Pagy = (() => {
150
150
  };
151
151
  })();
152
152
 
153
- //# debugId=5554FC8F70F87D9964756E2164756E21
153
+ //# debugId=9616AB509F644C8D64756E2164756E21
154
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, 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.4\",\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.5\",\n\n // Scan for elements with a \"data-pagy\" attribute and call their init functions with the decoded args\n init(arg?:HTMLElement) {\n const target = arg instanceof HTMLElement ? arg : document,\n elements = target.querySelectorAll(\"[data-pagy]\");\n for (const element of <NodeListOf<HTMLElement>>elements) {\n try {\n const [helperId, ...args] = <InitArgs>JSON.parse(B64Decode(<string>element.getAttribute(\"data-pagy\")));\n if (helperId == \"k\") {\n // @ts-expect-error spread 2 arguments, not 3 as it complains about\n void augmentKeynav(element, ...<KeynavArgs><unknown>args);\n } else if (helperId == \"snj\") {\n buildNavJs(<NavJsElement>element, <SeriesNavJsArgs><unknown>args);\n } else if (helperId == \"inj\") {\n void initInputNavJs(element, <InputNavJsArgs><unknown>args);\n } else if (helperId == \"ltj\") {\n initLimitTagJs(element, <LimitTagJsArgs><unknown>args);\n }\n // else { console.warn(\"Pagy.init: %o\\nUnknown helperId '%s'\", element, helperId) }\n } catch (err) { console.warn(\"Pagy.init: %o\\n%s\", element, err) }\n }\n }\n };\n})();\n"
6
6
  ],
7
7
  "mappings": "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",
8
- "debugId": "5554FC8F70F87D9964756E2164756E21",
8
+ "debugId": "9616AB509F644C8D64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,2 +1,2 @@
1
- window.Pagy=(()=>{let B="sessionStorage"in window&&"BroadcastChannel"in window,L="pagy",Z,O,W;if(B)Z=sessionStorage,O=new BroadcastChannel(L),W=Date.now(),O.addEventListener("message",(q)=>{if(q.data.from){let z=Z.getItem(q.data.key);if(z)O.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==W)Z.setItem(q.data.key,q.data.str)}});let P=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),V=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),S=()=>Math.floor(Math.random()*46656).toString(36),J=async(q,[z,C,F,H,G])=>{let Q,X=document.cookie.split(/;\s+/).find((M)=>M.startsWith(L+"="))?.split("=")[1]??S();if(document.cookie=L+"="+X,z&&!(z in Z)){if(O.postMessage({from:W,key:z}),await new Promise((M)=>setTimeout(()=>M(""),100)),!(z in Z))Q=(M)=>M+"+"+H}if(!Q){if(!z)do z=S();while(z in Z);let M=Z.getItem(z),Y=M?JSON.parse(M):[void 0];if(G)Y.splice(...G),Z.setItem(z,JSON.stringify(Y));Q=($)=>{let R=parseInt($);return V(JSON.stringify([X,z,R,Y.length,Y[R-1],Y[R]]))}}let D=C?`${C}%5B${F}%5D`:F,E=new RegExp(`(?<=\\?.*)(\\b${D}=)(\\d+)`);for(let M of q.querySelectorAll("a[href]"))M.href=M.href.replace(E,(Y,$,R)=>`${$}${Q(R)}`);return Q},x=(q,[[z,C,F,H,G],Q,[X,D,E],M])=>{let Y=q.parentElement,$=-1;if((q.render=()=>{let R=X.findIndex((j)=>j<Y.clientWidth);if(X[R]===$)return;let U=z;if(D[R].forEach((j,I)=>{U+=j=="gap"?H:(typeof j=="number"?C.replace(Q,j):F).replace("L<",E?.[R][I]??j+"<")}),U+=G,q.innerHTML="",q.insertAdjacentHTML("afterbegin",U),$=X[R],M&&B)J(q,M)})(),q.classList.contains(L+"-rjs"))P.observe(Y)},T=async(q,[z,C,F])=>{let H=F&&B?await J(q,F):(G)=>G;N(q,(G)=>z.replace(C,H(G)))},A=(q,[z,C,F,H])=>{N(q,(G)=>{return C.replace(F,Math.max(Math.ceil(z/parseInt(G)),1)).replace(H,G)})},N=(q,z)=>{let C=q.querySelector("input"),F=q.querySelector("a"),H=C.value,G=()=>{if(C.value===H)return;let[Q,X,D]=[C.min,C.value,C.max].map((E)=>parseInt(E)||0);if(X<Q||X>D){C.value=H,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",G),C.addEventListener("keypress",(Q)=>{if(Q.key=="Enter")G()})};return{version:"43.2.4",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
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.5",init(q){let z=q instanceof HTMLElement?q:document,C=z.querySelectorAll("[data-pagy]");for(let F of C)try{let[H,...G]=JSON.parse(_(F.getAttribute("data-pagy")));if(H=="k")J(F,...G);else if(H=="snj")x(F,G);else if(H=="inj")T(F,G);else if(H=="ltj")A(F,G)}catch(H){console.warn(`Pagy.init: %o
2
2
  %s`,F,H)}}}})();
data/javascripts/pagy.mjs CHANGED
@@ -125,7 +125,7 @@ const Pagy = (() => {
125
125
  });
126
126
  };
127
127
  return {
128
- version: "43.2.4",
128
+ version: "43.2.5",
129
129
  init(arg) {
130
130
  const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
131
131
  for (const element of elements) {
@@ -36,26 +36,25 @@ class Pagy
36
36
  private
37
37
 
38
38
  # Return calendar, from, to
39
- def init(...)
40
- new.send(:init, ...)
41
- end
39
+ def init(...) = new.send(:init, ...)
42
40
  end
43
41
 
44
42
  # Return the current time of the smallest time unit shown
45
- def showtime
46
- self[@units.last].from
47
- end
43
+ def showtime = self[@units.last].from
48
44
 
49
45
  # Return the url for the calendar (shortest unit) page at time
50
46
  def url_at(time, **)
51
- conf = Marshal.load(Marshal.dump(@conf))
52
47
  page_keys = {}
53
- @units.inject(nil) do |object, unit|
54
- conf[unit][:period] = object&.send(:active_period) || @period
55
- conf[unit][:page] = page_keys["#{unit}_#{@page_key}"] \
56
- = create(unit, **conf[unit]).send(:page_at, time, **)
57
- conf[unit][:querify] = ->(params) { params.merge!(page_keys) }
58
- create(unit, **conf[unit])
48
+
49
+ @units.inject(nil) do |parent, unit|
50
+ unit_conf = @conf[unit]
51
+ unit_conf[:period] = parent&.send(:active_period) || @period
52
+ unit_conf[:page] = page = create(unit, **unit_conf).send(:page_at, time, **)
53
+
54
+ page_keys["#{unit}_#{@page_key}"] = page
55
+ unit_conf[:querify] = ->(params) { params.merge!(page_keys) }
56
+
57
+ create(unit, **unit_conf)
59
58
  end.send(:compose_page_url, 1, **)
60
59
  end
61
60
 
@@ -63,34 +62,41 @@ class Pagy
63
62
 
64
63
  # Create the calendar
65
64
  def init(conf, period, params)
66
- @conf = Marshal.load(Marshal.dump(conf)) # store a copy
67
- @units = Calendar::UNITS & @conf.keys # get the units in time length desc order
65
+ @conf = conf
66
+ @units = Calendar::UNITS & conf.keys # get the units in time length desc order
68
67
  @period = period
69
68
  @params = params
70
69
  @page_key = conf[:offset][:page_key] || DEFAULT[:page_key]
70
+
71
71
  # set all the :page_key options for later deletion
72
72
  @units.each { |unit| conf[unit][:page_key] = "#{unit}_#{@page_key}" }
73
- calendar = {}
74
- object = nil
73
+
74
+ calendar = {}
75
+ unit_object = nil
76
+
75
77
  @units.each_with_index do |unit, index|
76
- params_to_delete = @units[(index + 1), @units.length].map { |sub| conf[sub][:page_key] } + [@page_key]
77
- conf[unit][:querify] = ->(up) { up.except!(*params_to_delete.map(&:to_s)) }
78
- conf[unit][:period] = object&.send(:active_period) || @period
79
- conf[unit][:page] = @params["#{unit}_#{@page_key}"] # requested page
78
+ params_to_delete = @units[(index + 1)..].map { conf[_1][:page_key] } + [@page_key]
79
+ unit_conf = conf[unit]
80
+ unit_conf[:querify] = ->(up) { up.except!(*params_to_delete.map(&:to_s)) }
81
+ unit_conf[:period] = unit_object&.send(:active_period) || @period
82
+ unit_conf[:page] = @params[unit_conf[:page_key]] # requested page
80
83
  # :nocov:
81
84
  # simplecov doesn't need to fail block_given?
82
- conf[unit][:counts] = yield(unit, conf[unit][:period]) if block_given?
85
+ unit_conf[:counts] = yield(unit, unit_conf[:period]) if block_given?
83
86
  # :nocov:
84
- calendar[unit] = object = create(unit, **conf[unit])
87
+ calendar[unit] = unit_object = create(unit, **unit_conf)
85
88
  end
86
- [replace(calendar), object.from, object.to]
89
+
90
+ [replace(calendar), unit_object.from, unit_object.to]
87
91
  end
88
92
 
89
93
  # Create a unit subclass instance by using the unit name (internal use)
90
94
  def create(unit, **)
91
95
  raise InternalError, "unit must be in #{UNITS.inspect}; got #{unit}" unless UNITS.include?(unit)
92
96
 
93
- Pagy::Calendar.const_get(unit.to_s.capitalize).new(**, request: @conf[:request])
97
+ unit_class = Pagy::Calendar.const_get(unit.to_s.capitalize)
98
+
99
+ unit_class.new(**, request: @conf[:request])
94
100
  end
95
101
  end
96
102
  end
@@ -21,7 +21,10 @@ class Pagy
21
21
  assign_options(**)
22
22
  assign_and_check(page: 1)
23
23
  assign_unit_variables
24
- return unless in_range? { @page <= @last }
24
+ unless in_range? { @page <= @last }
25
+ assign_empty_page_variables
26
+ return
27
+ end
25
28
 
26
29
  assign_previous_and_next
27
30
  end
@@ -10,6 +10,7 @@ class Pagy
10
10
  @pagy = pagy
11
11
  @option = option
12
12
  @value = value
13
+
13
14
  super("expected :#{@option} #{description}; got #{@value.inspect}")
14
15
  end
15
16
  end
@@ -13,7 +13,9 @@ class Pagy
13
13
  end
14
14
 
15
15
  # Get the keyset attributes from a record
16
- def keyset_attributes_from(record) = record.slice(*@keyset.keys)
16
+ def keyset_attributes_from(record)
17
+ record.slice(*@keyset.keys)
18
+ end
17
19
 
18
20
  # Get the hash of quoted keyset identifiers
19
21
  def quoted_identifiers(table)
@@ -22,7 +22,9 @@ class Pagy
22
22
  end
23
23
 
24
24
  # Get the keyset attributes from a record
25
- def keyset_attributes_from(record) = record.to_hash.slice(*@keyset.keys)
25
+ def keyset_attributes_from(record)
26
+ record.to_hash.slice(*@keyset.keys)
27
+ end
26
28
 
27
29
  # Get the hash of quoted keyset identifiers
28
30
  def quoted_identifiers(table)
@@ -52,6 +52,7 @@ class Pagy
52
52
  else
53
53
  @page = @last = 1
54
54
  end
55
+
55
56
  @update = [storage_key, @options[:root_key], @options[:page_key]]
56
57
  end
57
58
 
@@ -62,11 +63,13 @@ class Pagy
62
63
  # Compound predicate for visited pages
63
64
  predicate = +''
64
65
  arguments = {}
66
+
65
67
  if @prior_cutoff # not the first page
66
68
  # Include the records after @prior_cutoff
67
69
  predicate << "(#{compose_predicate(PRIOR_PREFIX)}) AND "
68
70
  arguments.merge!(arguments_from(@prior_cutoff, PRIOR_PREFIX))
69
71
  end
72
+
70
73
  # Exclude the records after @page_cutoff
71
74
  predicate << "NOT (#{compose_predicate(PAGE_PREFIX)})"
72
75
  arguments.merge!(arguments_from(@page_cutoff, PAGE_PREFIX))
@@ -13,7 +13,7 @@ class Pagy
13
13
  autoload :Sequel, path.join('adapters/sequel')
14
14
  end
15
15
 
16
- autoload :Keynav, Pathname.new(__dir__).join('keynav')
16
+ autoload :Keynav, Pathname.new(__dir__).join('keynav')
17
17
 
18
18
  # Define empty subclasses to allow specific typing without triggering autoload
19
19
  class ActiveRecord < self; end
@@ -27,28 +27,24 @@ class Pagy
27
27
  if /::(?:ActiveRecord|Sequel)$/.match?(name)
28
28
  # Ensure the adapter is mixed in (lazy load)
29
29
  mix_in_adapter(name.split('::').last)
30
- return allocate.tap { |instance| instance.send(:initialize, set, **) }
30
+ return allocate.tap { _1.send(:initialize, set, **) }
31
31
  end
32
32
 
33
33
  # 2. Handle Factory usage (Pagy::Keyset.new)
34
- orm_name = if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
35
- :ActiveRecord
36
- elsif defined?(::Sequel) && set.is_a?(::Sequel::Dataset)
37
- :Sequel
38
- else
39
- raise TypeError, "expected an ActiveRecord::Relation or Sequel::Dataset; got #{set.class}"
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)
46
- subclass.new(set, **)
34
+ adapter = if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
35
+ :ActiveRecord
36
+ elsif defined?(::Sequel) && set.is_a?(::Sequel::Dataset)
37
+ :Sequel
38
+ else
39
+ raise TypeError, "expected an ActiveRecord::Relation or Sequel::Dataset; got #{set.class}"
40
+ end
41
+
42
+ const_get(adapter).tap { _1.mix_in_adapter(adapter) }.new(set, **)
47
43
  end
48
44
 
49
45
  # 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)
46
+ def self.mix_in_adapter(adapter)
47
+ adapter_module = Adapters.const_get(adapter)
52
48
  include(adapter_module) unless self < adapter_module
53
49
  end
54
50
 
@@ -34,7 +34,11 @@ class Pagy
34
34
  def finalize(fetched_size)
35
35
  # empty records (trigger the right info message for known 0 count)
36
36
  @count = 0 if fetched_size.zero? && @page == 1
37
- return self unless in_range? { fetched_size.positive? || @page == 1 }
37
+
38
+ unless in_range? { fetched_size.positive? || @page == 1 }
39
+ assign_empty_page_variables
40
+ return self
41
+ end
38
42
 
39
43
  past = @last && @page < @last # current page is before the known last page
40
44
  more = fetched_size > @limit # more pages after this one
@@ -43,6 +47,7 @@ class Pagy
43
47
  @from = @in.zero? ? 0 : @offset + 1
44
48
  @to = @offset + @in
45
49
  assign_previous_and_next
50
+
46
51
  self
47
52
  end
48
53
 
@@ -55,7 +60,9 @@ class Pagy
55
60
  end
56
61
 
57
62
  # Support easy countless page param overriding (for legacy param and behavior)
58
- def compose_page_param(page) = EscapedValue.new("#{page || 1}+#{@last}")
63
+ def compose_page_param(page)
64
+ EscapedValue.new("#{page || 1}+#{@last}")
65
+ end
59
66
  end
60
67
  end
61
68
  end
@@ -19,11 +19,16 @@ class Pagy
19
19
  assign_and_check(limit: 1, count: 0, page: 1)
20
20
  assign_last
21
21
  assign_offset
22
- return unless in_range? { @page <= @last }
22
+
23
+ unless in_range? { @page <= @last }
24
+ assign_empty_page_variables
25
+ return
26
+ end
23
27
 
24
28
  @from = [@offset + 1, @count].min
25
29
  @to = [@offset + @limit, @count].min
26
30
  @in = [@to - @from + 1, @count].min
31
+
27
32
  assign_previous_and_next
28
33
  end
29
34
 
@@ -13,6 +13,7 @@ class Pagy
13
13
  else
14
14
  [request.base_url, request.path, get_params(request), request.cookies['pagy']]
15
15
  end
16
+ freeze
16
17
  end
17
18
 
18
19
  attr_reader :base_url, :path, :params, :cookie
@@ -25,15 +26,18 @@ class Pagy
25
26
 
26
27
  def resolve_limit
27
28
  limit_key = @options[:limit_key] || DEFAULT[:limit_key]
28
- return @options[:limit] || DEFAULT[:limit] \
29
- unless @options[:client_max_limit] &&
30
- (requested_limit = @params.dig(@options[:root_key], limit_key) || @params[limit_key])
29
+ default = @options[:limit] || DEFAULT[:limit]
30
+ max_limit = @options[:client_max_limit]
31
+ return default unless max_limit
31
32
 
32
- [requested_limit.to_i, @options[:client_max_limit]].min
33
+ limit = @params.dig(@options[:root_key], limit_key) || @params[limit_key]
34
+ limit ? [limit.to_i, max_limit].min : default
33
35
  end
34
36
 
35
37
  private
36
38
 
37
- def get_params(request) = request.GET.merge(request.POST).to_h
39
+ def get_params(request)
40
+ request.GET.merge(request.POST).to_h.freeze
41
+ end
38
42
  end
39
43
  end
@@ -17,6 +17,7 @@ class Pagy
17
17
  else
18
18
  collection.count(:all)
19
19
  end
20
+
20
21
  count.is_a?(Hash) ? count.size : count
21
22
  end
22
23
  end
@@ -43,21 +43,28 @@ class Pagy
43
43
 
44
44
  # Return the URL for the page, relying on the Pagy::Request
45
45
  def compose_page_url(page, **options)
46
- root_key, page_key, limit_key, client_max_limit, limit, querify, absolute, path, fragment =
47
- @options.merge(options)
48
- .values_at(:root_key, :page_key, :limit_key, :client_max_limit, :limit, :querify, :absolute, :path, :fragment)
49
- params = @request.params.clone(freeze: false)
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) }
46
+ opts = @options.merge(options)
47
+ params = @request.params.clone(freeze: false)
48
+ root_key = opts[:root_key]
49
+ container = if root_key
50
+ params[root_key] = params[root_key]&.clone(freeze: false) || {}
51
+ else
52
+ params
53
+ end
54
+
55
+ { opts[:page_key] => compose_page_param(page),
56
+ opts[:limit_key] => opts[:client_max_limit] && opts[:limit] }.each do |k, v|
57
+ v ? container[k] = v : container.delete(k)
53
58
  end
54
- querify&.(params) # Must modify the params: the returned value is ignored
55
- fragment &&= "##{fragment.delete_prefix('#')}"
56
- compose_url(absolute, path, params, fragment)
59
+
60
+ opts[:querify]&.(params) # Must modify the params: the returned value is ignored
61
+ fragment = opts[:fragment].to_s.sub(/\A(?=[^#])/, '#') # conditionally prepend '#'
62
+
63
+ compose_url(opts[:absolute], opts[:path], params, fragment)
57
64
  end
58
65
 
59
66
  def compose_url(absolute, path, params, fragment)
60
- query_string = QueryUtils.build_nested_query(params).sub(/\A(?=.)/, '?')
67
+ query_string = QueryUtils.build_nested_query(params).sub(/\A(?=.)/, '?') # conditionally prepend '?'
61
68
  "#{@request.base_url if absolute}#{path || @request.path}#{query_string}#{fragment}"
62
69
  end
63
70
  end
@@ -9,7 +9,6 @@ class Pagy
9
9
  return true if (@in_range = yield)
10
10
  raise RangeError.new(self, :page, "in 1..#{@last}", @page) if @options[:raise_range_error]
11
11
 
12
- assign_empty_page_variables
13
12
  @in_range = false
14
13
  end
15
14
  end
@@ -5,14 +5,19 @@ class Pagy
5
5
  module B64
6
6
  module_function
7
7
 
8
- def encode(bin) = [bin].pack('m0')
8
+ def encode(bin)
9
+ [bin].pack('m0')
10
+ end
9
11
 
10
- def decode(str) = str.unpack1('m0')
12
+ def decode(str)
13
+ str.unpack1('m0')
14
+ end
11
15
 
12
16
  def urlsafe_encode(bin)
13
17
  str = encode(bin)
14
18
  str.chomp!('==') or str.chomp!('=')
15
19
  str.tr!('+/', '-_')
20
+
16
21
  str
17
22
  end
18
23
 
@@ -23,6 +28,7 @@ class Pagy
23
28
  else
24
29
  str = str.tr('-_', '+/')
25
30
  end
31
+
26
32
  decode(str)
27
33
  end
28
34
  end
@@ -9,16 +9,25 @@ class Pagy
9
9
  @collection = clone
10
10
  end
11
11
 
12
- def offset(value) = tap { @collection = self[value..] }
13
- def limit(value) = @collection[0, value]
14
- def count(*) = size
12
+ def offset(value)
13
+ tap { @collection = self[value..] }
14
+ end
15
+
16
+ def limit(value)
17
+ @collection[0, value]
18
+ end
19
+
20
+ def count(*) = size
15
21
  end
16
22
 
17
23
  include Method
18
24
 
19
- # Direct reference to request.params via a method
20
- def request = @request ||= { base_url: 'http://www.example.com', path: '/path', params: { example: '123' } }
21
- def params = request[:params]
25
+ def request
26
+ @request ||= { base_url: 'http://www.example.com', path: '/path', params: { example: '123' } }
27
+ end
28
+
29
+ def params = request[:params]
30
+
22
31
  def collection = Collection
23
32
  end
24
33
  end
@@ -8,15 +8,22 @@ class Pagy
8
8
  module I18n
9
9
  extend self
10
10
 
11
- def pathnames = @pathnames ||= [ROOT.join('locales')]
12
- def locales = @locales ||= {}
11
+ def pathnames
12
+ @pathnames ||= [ROOT.join('locales')]
13
+ end
14
+
15
+ def locales
16
+ @locales ||= {}
17
+ end
13
18
 
14
19
  # Store the variable for the duration of a single request
15
20
  def locale=(value)
16
- Thread.current[:pagy_locale] = value
21
+ Thread.current[:pagy_locale] = value.to_s
17
22
  end
18
23
 
19
- def locale = Thread.current[:pagy_locale] || 'en'
24
+ def locale
25
+ Thread.current[:pagy_locale] || 'en'
26
+ end
20
27
 
21
28
  # Translate and pluralize the key with the locale entries
22
29
  def translate(key, **options)
@@ -6,12 +6,16 @@ class Pagy
6
6
  module_function
7
7
 
8
8
  # Common search logic
9
- def wrap(pagy_search_args, options)
9
+ def wrap(search_arguments, options)
10
10
  options[:page] ||= options[:request].resolve_page
11
11
  options[:limit] = options[:request].resolve_limit
12
- pagy, results = yield
13
- calling = pagy_search_args[4..]
14
- [pagy, calling.empty? ? results : results.send(*calling)]
12
+
13
+ pagy, results = yield
14
+
15
+ arguments = search_arguments[4..]
16
+ results = results.send(*arguments) unless arguments.empty?
17
+
18
+ [pagy, results]
15
19
  end
16
20
  end
17
21
  end
@@ -9,6 +9,7 @@ class Pagy
9
9
  template = compose_page_url(PAGE_TOKEN, **)
10
10
  to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) if page }
11
11
  data_keys -= %i[count limit] if calendar?
12
+
12
13
  data_keys.each_with_object({}) do |key, data|
13
14
  value = case key
14
15
  when :url_template then template
@@ -4,7 +4,7 @@ require_relative 'urls_hash'
4
4
 
5
5
  # Add pagination response headers
6
6
  class Pagy
7
- DEFAULT_HEADERS_MAP = { page: 'current-page',
7
+ DEFAULT_HEADERS_MAP = { page: 'current-page',
8
8
  limit: 'page-limit',
9
9
  count: 'total-count',
10
10
  pages: 'total-pages' }.freeze
@@ -12,6 +12,7 @@ class Pagy
12
12
  # Generate a hash of RFC-8288-compliant http headers
13
13
  def headers_hash(headers_map: @options[:headers_map] || DEFAULT_HEADERS_MAP, **)
14
14
  links = urls_hash(**, absolute: true).map { %(<#{_2}>; rel="#{_1}") }.join(', ')
15
+
15
16
  headers_map.each_with_object('link' => links) do |(key, name), hash|
16
17
  next unless name
17
18
 
@@ -13,6 +13,7 @@ class Pagy
13
13
  else
14
14
  'pagy.info_tag.multiple_pages'
15
15
  end
16
+
16
17
  info_data = if @count.nil?
17
18
  { page: @page, pages: @last }
18
19
  else
@@ -21,6 +22,7 @@ class Pagy
21
22
  from: @from,
22
23
  to: @to }
23
24
  end
25
+
24
26
  %(<span#{%( id="#{id}") if id} class="pagy info">#{I18n.translate(i18n_key, **info_data)}</span>)
25
27
  end
26
28
  end
@@ -13,6 +13,7 @@ class Pagy
13
13
  html = %(#{previous_tag(a_lambda)}<label>#{
14
14
  I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)}</label>#{
15
15
  next_tag(a_lambda)})
16
+
16
17
  wrap_input_nav_js(html, 'pagy input-nav-js', **)
17
18
  end
18
19
  end
@@ -11,6 +11,7 @@ class Pagy
11
11
  when :last then @last
12
12
  else page
13
13
  end
14
+
14
15
  compose_page_url(target, **) if target || page == :first
15
16
  end
16
17
  end
@@ -23,6 +23,7 @@ class Pagy
23
23
  end
24
24
  end
25
25
  html << next_tag(a_lambda)
26
+
26
27
  wrap_series_nav(html, 'pagy series-nav', **)
27
28
  end
28
29
  end
@@ -13,6 +13,7 @@ class Pagy
13
13
  current: %(<a role="link" aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a>),
14
14
  gap: %(<a role="separator" aria-disabled="true">#{I18n.translate('pagy.gap')}</a>),
15
15
  after: next_tag(a_lambda) }
16
+
16
17
  wrap_series_nav_js(tokens, 'pagy series-nav-js', **)
17
18
  end
18
19
  end
@@ -12,15 +12,18 @@ class Pagy
12
12
  unless config.is_a?(Hash) && (config.keys - allowed_options).empty?
13
13
 
14
14
  config[:offset] ||= {}
15
+
15
16
  unless config[:disabled]
16
- calendar, from, to =
17
- Calendar.send(:init, config,
18
- pagy_calendar_period(collection),
19
- config[:request].params) do |unit, period|
20
- pagy_calendar_counts(collection, unit, *period) if respond_to?(:pagy_calendar_counts)
21
- end
17
+ period = pagy_calendar_period(collection)
18
+ params = config[:request].params
19
+
20
+ calendar, from, to = Calendar.send(:init, config, period, params) do |unit, unit_period|
21
+ pagy_calendar_counts(collection, unit, *unit_period) if respond_to?(:pagy_calendar_counts)
22
+ end
23
+
22
24
  collection = pagy_calendar_filter(collection, from, to)
23
25
  end
26
+
24
27
  pagy, records = pagy(:offset, collection, **config[:offset])
25
28
  [calendar, pagy, records]
26
29
  end
@@ -9,26 +9,29 @@ class Pagy
9
9
  # Return the Offset::Countish instance and records
10
10
  def paginate(collection, options)
11
11
  options[:page] ||= options[:request].resolve_page(force_integer: false)
12
+
12
13
  if options[:page].is_a?(String)
13
14
  page, count, epoch = options[:page].split.map(&:to_i)
14
15
  options[:page] = page
15
16
  end
17
+
16
18
  setup_options(count, epoch, collection, options)
17
19
  options[:limit] = options[:request].resolve_limit
18
- pagy = Offset::Countish.new(**options)
20
+
21
+ pagy = Offset::Countish.new(**options)
19
22
  [pagy, pagy.records(collection)]
20
23
  end
21
24
 
22
25
  # Get the count from the page and set epoch when ttl (Time To Live) requires it
23
26
  def setup_options(count, epoch, collection, options)
24
27
  now = Time.now.to_i
28
+
25
29
  if !options[:count] && count && (!options[:ttl] ||
26
30
  (epoch && epoch <= now && now < (epoch + options[:ttl]))) # ongoing
27
- # puts 'ongoing'
28
31
  options[:count] = count
29
32
  options[:epoch] = epoch if options[:ttl]
33
+
30
34
  else # recount
31
- # puts 'recount'
32
35
  options[:count] ||= Countable.get_count(collection, options)
33
36
  options[:epoch] = now if options[:ttl]
34
37
  end
@@ -7,13 +7,16 @@ class Pagy
7
7
  # Return the Offset::Countless instance and records
8
8
  def paginate(collection, options)
9
9
  options[:page] ||= options[:request].resolve_page(force_integer: false) # accept nil and strings
10
+
10
11
  if options[:page].is_a?(String)
11
- page, last = options[:page].split.map(&:to_i) # decoded '+' added by the compose_page_url
12
+ page, last = options[:page].split.map(&:to_i) # ' ' separator, (encoded as '+' by Countless#compose_page_param)
12
13
  options[:page] = page
13
14
  options[:last] = last if last&.positive?
14
15
  end
16
+
15
17
  options[:limit] = options[:request].resolve_limit
16
- pagy = Offset::Countless.new(**options)
18
+
19
+ pagy = Offset::Countless.new(**options)
17
20
  [pagy, pagy.records(collection)]
18
21
  end
19
22
  end
@@ -8,22 +8,26 @@ class Pagy
8
8
 
9
9
  # Paginate from the search object
10
10
  def paginate(search, options)
11
- if search.is_a?(Search::Arguments)
12
- # The search is the array of pagy_search arguments
11
+ if search.is_a?(Search::Arguments) # Active mode
12
+
13
13
  Searcher.wrap(search, options) do
14
14
  model, query_or_payload, search_options = search
15
15
  search_options[:size] = options[:limit]
16
16
  search_options[:from] = options[:limit] * ((options[:page] || 1) - 1)
17
- response_object = model.send(options[:search_method] || ElasticsearchRails::DEFAULT[:search_method],
18
- query_or_payload, **search_options)
19
- options[:count] = total_count_from(response_object)
17
+
18
+ method = options[:search_method] || ElasticsearchRails::DEFAULT[:search_method]
19
+ response_object = model.send(method, query_or_payload, **search_options)
20
+ options[:count] = total_count_from(response_object)
21
+
20
22
  [ElasticsearchRails.new(**options), response_object]
21
23
  end
22
- else
24
+
25
+ else # Passive mode
23
26
  from, size = pagination_params_from(search)
24
27
  options[:limit] = size
25
28
  options[:page] = ((from || 0) / options[:limit]) + 1
26
29
  options[:count] = total_count_from(search)
30
+
27
31
  ElasticsearchRails.new(**options)
28
32
  end
29
33
  end
@@ -36,6 +40,7 @@ class Pagy
36
40
  from = (container[:from] || container['from']).to_i
37
41
  size = (container[:size] || container['size']).to_i
38
42
  size = 10 if size.zero?
43
+
39
44
  [from, size]
40
45
  end
41
46
 
@@ -44,6 +49,7 @@ class Pagy
44
49
  total = response_object.instance_eval do
45
50
  respond_to?(:response) ? response['hits']['total'] : raw_response['hits']['total']
46
51
  end
52
+
47
53
  total.is_a?(Hash) ? total['value'] : total
48
54
  end
49
55
  end
@@ -10,15 +10,17 @@ class Pagy
10
10
  # Fall back to :countless if the :page has no client data.
11
11
  def paginate(set, options)
12
12
  page = options[:request].resolve_page(force_integer: false) # allow nil
13
- if page&.match(' ') # countless page -> no augmentation -> fallback
14
- return CountlessPaginator.paginate(set, page:, **options)
15
- elsif page.is_a?(String) # keynav page param
13
+
14
+ return CountlessPaginator.paginate(set, page:, **options) if page&.match(' ') # countless fallback
15
+
16
+ if page.is_a?(String) # keynav page param
16
17
  page_arguments = JSON.parse(B64.urlsafe_decode(page))
17
18
  # Restart the pagination from page 1/nil if the url has been requested from another browser
18
19
  options[:page] = page_arguments if options[:request].cookie == page_arguments.shift
19
20
  end
20
21
 
21
22
  options[:limit] = options[:request].resolve_limit
23
+
22
24
  pagy = Keyset::Keynav.new(set, **options)
23
25
  [pagy, pagy.records]
24
26
  end
@@ -8,7 +8,8 @@ class Pagy
8
8
  def paginate(set, options)
9
9
  options[:page] ||= options[:request].resolve_page(force_integer: false) # allow nil
10
10
  options[:limit] = options[:request].resolve_limit
11
- pagy = Keyset.new(set, **options)
11
+ pagy = Keyset.new(set, **options)
12
+
12
13
  [pagy, pagy.records]
13
14
  end
14
15
  end
@@ -8,22 +8,25 @@ class Pagy
8
8
 
9
9
  # Paginate from the search object
10
10
  def paginate(search, options)
11
- if search.is_a?(Search::Arguments)
12
- # The search is the array of pagy_search arguments
11
+ if search.is_a?(Search::Arguments) # Active mode
12
+
13
13
  Searcher.wrap(search, options) do
14
14
  model, term, search_options = search
15
15
  search_options[:hits_per_page] = options[:limit]
16
16
  search_options[:page] = options[:page]
17
- results = model.send(options[:search_method] || Meilisearch::DEFAULT[:search_method],
18
- term, search_options)
19
- options[:count] = results.raw_answer['totalHits']
17
+
18
+ method = options[:search_method] || Meilisearch::DEFAULT[:search_method]
19
+ results = model.send(method, term, search_options)
20
+ options[:count] = results.raw_answer['totalHits']
21
+
20
22
  [Meilisearch.new(**options), results]
21
23
  end
22
- else
23
- # The search is a meilisearch results object
24
+
25
+ else # Passive mode
24
26
  options[:limit] = search.raw_answer['hitsPerPage']
25
27
  options[:page] = search.raw_answer['page']
26
28
  options[:count] = search.raw_answer['totalHits']
29
+
27
30
  Meilisearch.new(**options)
28
31
  end
29
32
  end
@@ -21,14 +21,16 @@ class Pagy
21
21
  protected
22
22
 
23
23
  define_method :pagy do |paginator = :offset, collection, **options|
24
- arguments = if paginator == :calendar
25
- [self, collection, options]
26
- else
27
- [collection, options = Pagy.options.merge(options)]
28
- end
24
+ arguments = if paginator == :calendar
25
+ [self, collection, options]
26
+ else
27
+ [collection, options = Pagy.options.merge(options)]
28
+ end
29
+
29
30
  options[:root_key] = 'page' if options[:jsonapi] # enforce 'page' root_key for JSON:API
30
31
  options[:request] ||= request # user set request or self.request
31
32
  options[:request] = Request.new(options) # Pagy::Request
33
+
32
34
  Pagy.const_get(paginators[paginator]).paginate(*arguments)
33
35
  end
34
36
  end
@@ -11,8 +11,15 @@ class Pagy
11
11
  options[:page] ||= options[:request].resolve_page
12
12
  options[:limit] = options[:request].resolve_limit
13
13
  options[:count] ||= Countable.get_count(collection, options)
14
- pagy = Offset.new(**options)
15
- [pagy, collection.instance_of?(Array) ? collection[pagy.offset, pagy.limit] : pagy.records(collection)]
14
+
15
+ pagy = Offset.new(**options)
16
+ records = if collection.instance_of?(Array)
17
+ collection[pagy.offset, pagy.limit]
18
+ else
19
+ pagy.records(collection)
20
+ end
21
+
22
+ [pagy, records]
16
23
  end
17
24
  end
18
25
  end
@@ -8,22 +8,25 @@ class Pagy
8
8
 
9
9
  # Paginate from the search object
10
10
  def paginate(search, options)
11
- if search.is_a?(Search::Arguments)
12
- # The search is the array of pagy_search arguments
11
+ if search.is_a?(Search::Arguments) # Active mode
12
+
13
13
  Searcher.wrap(search, options) do
14
14
  model, term, search_options, block = search
15
- search_options[:per_page] = options[:limit]
16
- search_options[:page] = options[:page]
17
- results = model.send(options[:search_method] || Searchkick::DEFAULT[:search_method],
18
- term || '*', **search_options, &block)
19
- options[:count] = results.total_count
15
+ search_options[:per_page] = options[:limit]
16
+ search_options[:page] = options[:page]
17
+
18
+ method = options[:search_method] || Searchkick::DEFAULT[:search_method]
19
+ results = model.send(method, term || '*', **search_options, &block)
20
+ options[:count] = results.total_count
21
+
20
22
  [Searchkick.new(**options), results]
21
23
  end
22
- else
23
- # The search is a searchkick results object
24
+
25
+ else # Passive mode
24
26
  options[:limit] = search.respond_to?(:options) ? search.options[:per_page] : search.per_page
25
27
  options[:page] = search.respond_to?(:options) ? search.options[:page] : search.current_page
26
28
  options[:count] = search.total_count
29
+
27
30
  Searchkick.new(**options)
28
31
  end
29
32
  end
data/lib/pagy.rb CHANGED
@@ -8,7 +8,7 @@ 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.2.4'
11
+ VERSION = '43.2.5'
12
12
  ROOT = Pathname.new(__dir__).parent.freeze
13
13
  DEFAULT = { limit: 20, limit_key: 'limit', page_key: 'page' }.freeze
14
14
  PAGE_TOKEN = EscapedValue.new('P ')
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.2.4
4
+ version: 43.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis