pagy 43.0.7 → 43.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e7fefb6090426f4176e075f28d0338c8e7d0e02f0261f77cfb292b8592d43ca
4
- data.tar.gz: 5855eb37981912ca1d2bd9955674d7d20702547b1788aeeda1bf198525778939
3
+ metadata.gz: 81897225205a29f9ae8e98c57cddcc7c92a6bdbc3ce1cdd466f68ade1cf80785
4
+ data.tar.gz: e801dafc6c5acd601da4e88d68f21df621f6a0f01af19edbfa1df4d43d55fd54
5
5
  SHA512:
6
- metadata.gz: 55463b5ae81ab94a293de8e4392f8507b520063d733c54861ec605ec85ac120ce412d5384cb795b3e2558440bde4d1273c224c0715e8fa907213f8ce9952c310
7
- data.tar.gz: 7ae49f5008738375f6a0828ee6cdcee0581663e420a63d497b4da1b26f437139d055892b1d0d5597dd243561e880b5f224e933250a5fd286c044a96ac69188f4
6
+ metadata.gz: 74cab9eeb2eb7dd535daff6523c9ee9190856dee5be12bd96e274748897d39ccca6a9eea6cc9fd5394fbd164c4bfb48a499dadf28ff1fe8fb625113b239806b3
7
+ data.tar.gz: 06eb01e238504df2242a2c9a4b6855eab9bcf349ecd5a30487f89ce4491abfde2a06dcf4b453b745be8d5560b11eed5744426d22e91578d9ebe03c6517ed2531
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.0.7'
19
+ VERSION = '43.1.0'
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.0.7'
22
+ VERSION = '43.1.0'
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")
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.0.7'
19
+ VERSION = '43.1.0'
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.0.7'
19
+ VERSION = '43.1.0'
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.0.7'
19
+ VERSION = '43.1.0'
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.0.7'
19
+ VERSION = '43.1.0'
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.0.7'
19
+ VERSION = '43.1.0'
20
20
 
21
21
  if VERSION != Pagy::VERSION
22
22
  Warning.warn("\n>>> WARNING! '#{File.basename(__FILE__)}-#{VERSION}' running with 'pagy-#{Pagy::VERSION}'! <<< \n\n")
data/bin/pagy CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- VERSION = '43.0.7'
4
+ VERSION = '43.1.0'
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.0.7)
3
+ # Pagy initializer file (43.1.0)
4
4
  # See https://ddnexus.github.io/pagy/resources/initializer/
5
5
 
6
6
  ############ Global Options ################################################################
@@ -68,7 +68,7 @@ const PagyAIWidget = {
68
68
  script.dataset.iconUrl = this.ICON_URL;
69
69
  script.dataset.text = 'Pagy AI';
70
70
  script.dataset.bgColor = '#1f7a1f';
71
- script.dataset.lightMode = 'false';
71
+ script.dataset.lightMode = 'auto';
72
72
  script.dataset.tooltipSide = 'bottom';
73
73
  document.head.appendChild(script);
74
74
  }
data/javascripts/pagy.js CHANGED
@@ -124,7 +124,7 @@ window.Pagy = (() => {
124
124
  });
125
125
  };
126
126
  return {
127
- version: "43.0.7",
127
+ version: "43.1.0",
128
128
  init(arg) {
129
129
  const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
130
130
  for (const element of elements) {
@@ -147,5 +147,5 @@ window.Pagy = (() => {
147
147
  };
148
148
  })();
149
149
 
150
- //# debugId=340F68B25BC9072264756E2164756E21
150
+ //# debugId=7F9EF7EF7A33670664756E2164756E21
151
151
  //# sourceMappingURL=pagy.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/pagy.ts"],
4
4
  "sourcesContent": [
5
- "interface SyncData {\n from?: number\n to?: number\n key: string\n str?: string\n}\ntype InitArgs = [\"k\", KeynavArgs] | // series_nav[_js] with keynav instance\n [\"snj\", SeriesNavJsArgs] | // series_nav_js\n [\"inj\", InputNavJsArgs] | // input_nav_js\n [\"ltj\", LimitTagJsArgs] // limit_tag_js\ntype AugmentKeynav = (nav:HTMLElement, keynavArgs:KeynavArgs) => Promise<((page: string) => string)>\ntype KeynavArgs = readonly [storageKey: string | null,\n pageKey: string,\n last: number,\n spliceArgs?: SpliceArgs]\ntype SpliceArgs = readonly [start: number,\n deleteCount: number, // it would be optional, but ts complains\n ...items: Cutoff[]]\ntype Cutoff = readonly (string | number | boolean)[]\ntype AugmentedPage = [browserId: string,\n storageKey: string,\n pageNumber: number,\n pages: number,\n priorCutoff: Cutoff | null,\n pageCutoff: Cutoff | null]\ntype SeriesNavJsArgs = readonly [NavJsTokens, NavJsSeries, KeynavArgs?]\ntype NavJsSeries = readonly [widths: number[],\n series: (string | number)[][],\n labels: string[][] | null]\ntype InputNavJsArgs = readonly [urlToken: string, KeynavArgs?]\ntype LimitTagJsArgs = readonly [from: number,\n urlToken: string]\ntype NavJsTokens = readonly [before: string,\n anchor: string,\n current: string,\n gap: string,\n after: string]\ninterface NavJsElement extends HTMLElement {\n render(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst Pagy = (() => {\n const storageSupport = 'sessionStorage' in window && 'BroadcastChannel' in window,\n pageRe = \"P \"; // shorten the compiled size\n // eslint-disable-next-line prefer-const\n let pagy = \"pagy\", storage: Storage, sync: BroadcastChannel, tabId: number;\n if (storageSupport) {\n storage = sessionStorage; // shorten the compiled size\n sync = new BroadcastChannel(pagy);\n tabId = Date.now();\n // Sync the sessionStorage keys for the cutoffs opened in a new tab/window\n sync.addEventListener(\"message\", (e:MessageEvent<SyncData>) => {\n if (e.data.from) { // request cutoffs\n const cutoffs = storage.getItem(e.data.key);\n if (cutoffs) {\n sync.postMessage(<SyncData>{to: e.data.from, key: e.data.key, str: cutoffs});\n } // send response\n } else if (e.data.to) { // receive cutoffs\n if (e.data.to == tabId) {\n storage.setItem(e.data.key, <string>e.data.str);\n }\n }\n });\n }\n // The observer instance for responsive navs\n const rjsObserver = new ResizeObserver(\n entries => entries.forEach(e => {\n e.target.querySelectorAll<NavJsElement>(\".pagy-rjs\").forEach(el => el.render());\n }));\n\n /* Full set of B64 functions\n const B64Encode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode))),\n B64Safe = (unsafe:string) => unsafe.replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64SafeEncode = (unicode:string) => B64Safe(B64Encode(unicode)),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0))),\n B64Unsafe = (safe:string) => safe.replace(/[-_]/g, (match) => match == \"-\" ? \"+\" : \"/\"),\n B64SafeDecode = (base64:string) => B64Decode(B64Unsafe(base64))\n */\n const B64SafeEncode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode)))\n .replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));\n\n // Return a random key: 3 chars max, base-36 number < 36**3\n const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);\n\n // Manage the page augmentation for Keynav, called only if storageSupport\n const augmentKeynav: AugmentKeynav = async (nav, [storageKey, pageKey, last, spliceArgs]) => {\n let augment;\n const browserKey = document.cookie.split(/;\\s+/) // it works even if malformed\n .find((row) => row.startsWith(pagy + \"=\"))\n ?.split(\"=\")[1] ?? randKey();\n document.cookie = pagy + \"=\" + browserKey; // Smaller .min size: set the cookie without checking\n if (storageKey && !(storageKey in storage)) {\n // Sync the sessiongStorage from other tabs/windows (e.g., open page in the new tab/window)\n sync.postMessage(<SyncData>{ from: tabId, key: storageKey });\n // Wait for the listener to copy the cutoffs in the current sessionStorage\n await new Promise<string|null>((resolve) => setTimeout(() => resolve(\"\"), 100));\n if (!(storageKey in storage)) { // the storageKey didn't get copied: fallback to countless pagination\n augment = (page: string) => page + '+' + last;\n }\n }\n if (!augment) { // regular keynav pagination\n if (!storageKey) { do { storageKey = randKey() } while (storageKey in storage) } // no dup keys\n const data = storage.getItem(storageKey),\n cutoffs = <Cutoff[]>(data ? JSON.parse(data) : [undefined]);\n if (spliceArgs) {\n cutoffs.splice(...spliceArgs);\n storage.setItem(storageKey, JSON.stringify(cutoffs));\n }\n // Augment function\n augment = (page:string) => {\n const pageNum = parseInt(page);\n return B64SafeEncode(JSON.stringify(\n <AugmentedPage>[browserKey,\n storageKey,\n pageNum,\n cutoffs.length, // pages/last\n cutoffs[pageNum - 1], // priorCutoff\n cutoffs[pageNum]])); // pageCutoff\n };\n }\n // Augment the page param of each href\n for (const a of <NodeListOf<HTMLAnchorElement>><unknown>nav.querySelectorAll('a[href]')) {\n const url = a.href,\n re = new RegExp(`(?<=\\\\?.*)\\\\b${pageKey}=(\\\\d+)`); // find the numeric page from pageKey\n a.href = url.replace(re, pageKey + \"=\" + augment(url.match(re)![1])); // eslint-disable-line @typescript-eslint/no-non-null-assertion\n }\n // Return the augment function for further augmentation (i.e., url token in input_nav_js)\n return augment;\n };\n\n // Build the series_nav_js helper\n const buildNavJs = (nav:NavJsElement, [[before, anchor, current, gap, after],\n [widths, series, labels], keynavArgs]:SeriesNavJsArgs) => {\n const parent = <HTMLElement>nav.parentElement;\n let lastWidth = -1;\n (nav.render = () => {\n const index = widths.findIndex(w => w < parent.clientWidth);\n if (widths[index] === lastWidth) { return } // no change: abort\n\n let html = before;\n series[index].forEach((item, i) => {\n // Avoid the if blocks and chain the results (shorter pagy.min.js and easier reading)\n html += item == \"gap\" ? gap :\n // @ts-expect-error the item may be a number, but the 'replace' converts it to string (shorter pagy.min.js)\n (typeof item == \"number\" ? anchor.replace(pageRe, item) : current)\n .replace(\"L<\", labels?.[index][i] ?? item + \"<\");\n });\n html += after;\n nav.innerHTML = \"\";\n nav.insertAdjacentHTML(\"afterbegin\", html);\n lastWidth = widths[index];\n if (keynavArgs && storageSupport) { void augmentKeynav(nav, keynavArgs) }\n })();\n if (nav.classList.contains(pagy + \"-rjs\")) { rjsObserver.observe(parent) }\n };\n\n // Init the input_nav_js helpers\n const initInputNavJs = async (nav:HTMLElement, [url_token, keynavArgs]:InputNavJsArgs) => {\n const augment = keynavArgs && storageSupport\n ? await augmentKeynav(nav, keynavArgs)\n : (page: string) => page;\n initInput(nav, inputValue => url_token.replace(pageRe, augment(inputValue)));\n };\n\n // Init the limit_tag_js helper\n const initLimitTagJs = (span:HTMLSpanElement, [from, url_token]:LimitTagJsArgs) => {\n initInput(span, inputValue => {\n // @ts-expect-error the page is a number, but the 'replace' converts it to string (shorter pagy.min.js)\n return url_token.replace(pageRe, Math.max(Math.ceil(from / parseInt(inputValue)), 1))\n .replace('L ', inputValue);\n });\n };\n\n // Init the input element\n const initInput = (element:HTMLElement, getUrl:(v:string) => string) => {\n const input = <HTMLInputElement>element.querySelector(\"input\"),\n link = <HTMLAnchorElement>element.querySelector(\"a\"),\n initial = input.value,\n action = () => {\n if (input.value === initial) { return } // not changed\n const [min, val, max] = [input.min, input.value, input.max].map(n => parseInt(n) || 0);\n if (val < min || val > max) { // reset invalid/out-of-range\n input.value = initial;\n input.select();\n return;\n }\n link.href = getUrl(input.value);\n link.click();\n };\n input.addEventListener(\"focus\", () => input.select());\n input.addEventListener(\"focusout\", action);\n input.addEventListener(\"keypress\", e => { if (e.key == \"Enter\") { action() } });\n };\n\n // Public interface\n return {\n version: \"43.0.7\",\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 pageKey: string,\n last: number,\n spliceArgs?: SpliceArgs]\ntype SpliceArgs = readonly [start: number,\n deleteCount: number, // it would be optional, but ts complains\n ...items: Cutoff[]]\ntype Cutoff = readonly (string | number | boolean)[]\ntype AugmentedPage = [browserId: string,\n storageKey: string,\n pageNumber: number,\n pages: number,\n priorCutoff: Cutoff | null,\n pageCutoff: Cutoff | null]\ntype SeriesNavJsArgs = readonly [NavJsTokens, NavJsSeries, KeynavArgs?]\ntype NavJsSeries = readonly [widths: number[],\n series: (string | number)[][],\n labels: string[][] | null]\ntype InputNavJsArgs = readonly [urlToken: string, KeynavArgs?]\ntype LimitTagJsArgs = readonly [from: number,\n urlToken: string]\ntype NavJsTokens = readonly [before: string,\n anchor: string,\n current: string,\n gap: string,\n after: string]\ninterface NavJsElement extends HTMLElement {\n render(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst Pagy = (() => {\n const storageSupport = 'sessionStorage' in window && 'BroadcastChannel' in window,\n pageRe = \"P \"; // shorten the compiled size\n // eslint-disable-next-line prefer-const\n let pagy = \"pagy\", storage: Storage, sync: BroadcastChannel, tabId: number;\n if (storageSupport) {\n storage = sessionStorage; // shorten the compiled size\n sync = new BroadcastChannel(pagy);\n tabId = Date.now();\n // Sync the sessionStorage keys for the cutoffs opened in a new tab/window\n sync.addEventListener(\"message\", (e:MessageEvent<SyncData>) => {\n if (e.data.from) { // request cutoffs\n const cutoffs = storage.getItem(e.data.key);\n if (cutoffs) {\n sync.postMessage(<SyncData>{to: e.data.from, key: e.data.key, str: cutoffs});\n } // send response\n } else if (e.data.to) { // receive cutoffs\n if (e.data.to == tabId) {\n storage.setItem(e.data.key, <string>e.data.str);\n }\n }\n });\n }\n // The observer instance for responsive navs\n const rjsObserver = new ResizeObserver(\n entries => entries.forEach(e => {\n e.target.querySelectorAll<NavJsElement>(\".pagy-rjs\").forEach(el => el.render());\n }));\n\n /* Full set of B64 functions\n const B64Encode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode))),\n B64Safe = (unsafe:string) => unsafe.replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64SafeEncode = (unicode:string) => B64Safe(B64Encode(unicode)),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0))),\n B64Unsafe = (safe:string) => safe.replace(/[-_]/g, (match) => match == \"-\" ? \"+\" : \"/\"),\n B64SafeDecode = (base64:string) => B64Decode(B64Unsafe(base64))\n */\n const B64SafeEncode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode)))\n .replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));\n\n // Return a random key: 3 chars max, base-36 number < 36**3\n const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);\n\n // Manage the page augmentation for Keynav, called only if storageSupport\n const augmentKeynav: AugmentKeynav = async (nav, [storageKey, pageKey, last, spliceArgs]) => {\n let augment;\n const browserKey = document.cookie.split(/;\\s+/) // it works even if malformed\n .find((row) => row.startsWith(pagy + \"=\"))\n ?.split(\"=\")[1] ?? randKey();\n document.cookie = pagy + \"=\" + browserKey; // Smaller .min size: set the cookie without checking\n if (storageKey && !(storageKey in storage)) {\n // Sync the sessiongStorage from other tabs/windows (e.g., open page in the new tab/window)\n sync.postMessage(<SyncData>{ from: tabId, key: storageKey });\n // Wait for the listener to copy the cutoffs in the current sessionStorage\n await new Promise<string|null>((resolve) => setTimeout(() => resolve(\"\"), 100));\n if (!(storageKey in storage)) { // the storageKey didn't get copied: fallback to countless pagination\n augment = (page: string) => page + '+' + last;\n }\n }\n if (!augment) { // regular keynav pagination\n if (!storageKey) { do { storageKey = randKey() } while (storageKey in storage) } // no dup keys\n const data = storage.getItem(storageKey),\n cutoffs = <Cutoff[]>(data ? JSON.parse(data) : [undefined]);\n if (spliceArgs) {\n cutoffs.splice(...spliceArgs);\n storage.setItem(storageKey, JSON.stringify(cutoffs));\n }\n // Augment function\n augment = (page:string) => {\n const pageNum = parseInt(page);\n return B64SafeEncode(JSON.stringify(\n <AugmentedPage>[browserKey,\n storageKey,\n pageNum,\n cutoffs.length, // pages/last\n cutoffs[pageNum - 1], // priorCutoff\n cutoffs[pageNum]])); // pageCutoff\n };\n }\n // Augment the page param of each href\n for (const a of <NodeListOf<HTMLAnchorElement>><unknown>nav.querySelectorAll('a[href]')) {\n const url = a.href,\n re = new RegExp(`(?<=\\\\?.*)\\\\b${pageKey}=(\\\\d+)`); // find the numeric page from pageKey\n a.href = url.replace(re, pageKey + \"=\" + augment(url.match(re)![1])); // eslint-disable-line @typescript-eslint/no-non-null-assertion\n }\n // Return the augment function for further augmentation (i.e., url token in input_nav_js)\n return augment;\n };\n\n // Build the series_nav_js helper\n const buildNavJs = (nav:NavJsElement, [[before, anchor, current, gap, after],\n [widths, series, labels], keynavArgs]:SeriesNavJsArgs) => {\n const parent = <HTMLElement>nav.parentElement;\n let lastWidth = -1;\n (nav.render = () => {\n const index = widths.findIndex(w => w < parent.clientWidth);\n if (widths[index] === lastWidth) { return } // no change: abort\n\n let html = before;\n series[index].forEach((item, i) => {\n // Avoid the if blocks and chain the results (shorter pagy.min.js and easier reading)\n html += item == \"gap\" ? gap :\n // @ts-expect-error the item may be a number, but the 'replace' converts it to string (shorter pagy.min.js)\n (typeof item == \"number\" ? anchor.replace(pageRe, item) : current)\n .replace(\"L<\", labels?.[index][i] ?? item + \"<\");\n });\n html += after;\n nav.innerHTML = \"\";\n nav.insertAdjacentHTML(\"afterbegin\", html);\n lastWidth = widths[index];\n if (keynavArgs && storageSupport) { void augmentKeynav(nav, keynavArgs) }\n })();\n if (nav.classList.contains(pagy + \"-rjs\")) { rjsObserver.observe(parent) }\n };\n\n // Init the input_nav_js helpers\n const initInputNavJs = async (nav:HTMLElement, [url_token, keynavArgs]:InputNavJsArgs) => {\n const augment = keynavArgs && storageSupport\n ? await augmentKeynav(nav, keynavArgs)\n : (page: string) => page;\n initInput(nav, inputValue => url_token.replace(pageRe, augment(inputValue)));\n };\n\n // Init the limit_tag_js helper\n const initLimitTagJs = (span:HTMLSpanElement, [from, url_token]:LimitTagJsArgs) => {\n initInput(span, inputValue => {\n // @ts-expect-error the page is a number, but the 'replace' converts it to string (shorter pagy.min.js)\n return url_token.replace(pageRe, Math.max(Math.ceil(from / parseInt(inputValue)), 1))\n .replace('L ', inputValue);\n });\n };\n\n // Init the input element\n const initInput = (element:HTMLElement, getUrl:(v:string) => string) => {\n const input = <HTMLInputElement>element.querySelector(\"input\"),\n link = <HTMLAnchorElement>element.querySelector(\"a\"),\n initial = input.value,\n action = () => {\n if (input.value === initial) { return } // not changed\n const [min, val, max] = [input.min, input.value, input.max].map(n => parseInt(n) || 0);\n if (val < min || val > max) { // reset invalid/out-of-range\n input.value = initial;\n input.select();\n return;\n }\n link.href = getUrl(input.value);\n link.click();\n };\n input.addEventListener(\"focus\", () => input.select());\n input.addEventListener(\"focusout\", action);\n input.addEventListener(\"keypress\", e => { if (e.key == \"Enter\") { action() } });\n };\n\n // Public interface\n return {\n version: \"43.1.0\",\n\n // Scan for elements with a \"data-pagy\" attribute and call their init functions with the decoded args\n init(arg?:HTMLElement) {\n const target = arg instanceof HTMLElement ? arg : document,\n elements = target.querySelectorAll(\"[data-pagy]\");\n for (const element of <NodeListOf<HTMLElement>>elements) {\n try {\n const [helperId, ...args] = <InitArgs>JSON.parse(B64Decode(<string>element.getAttribute(\"data-pagy\")));\n if (helperId == \"k\") {\n // @ts-expect-error spread 2 arguments, not 3 as it complains about\n void augmentKeynav(element, ...<KeynavArgs><unknown>args);\n } else if (helperId == \"snj\") {\n buildNavJs(<NavJsElement>element, <SeriesNavJsArgs><unknown>args);\n } else if (helperId == \"inj\") {\n void initInputNavJs(element, <InputNavJsArgs><unknown>args);\n } else if (helperId == \"ltj\") {\n initLimitTagJs(element, <LimitTagJsArgs><unknown>args);\n }\n // else { console.warn(\"Pagy.init: %o\\nUnknown helperId '%s'\", element, helperId) }\n } catch (err) { console.warn(\"Pagy.init: %o\\n%s\", element, err) }\n }\n }\n };\n})();\n"
6
6
  ],
7
7
  "mappings": ";AA0CA,IAAM,QAAQ,MAAM;AAClB,QAAM,iBAAiB,oBAAoB,UAAU,sBAAsB,QACrE,SAAiB;AAEvB,MAAI,OAAO,QAAQ,SAAkB,MAAwB;AAC7D,MAAI,gBAAgB;AAClB,cAAU;AACV,WAAU,IAAI,iBAAiB,IAAI;AACnC,YAAU,KAAK,IAAI;AAEnB,SAAK,iBAAiB,WAAW,CAAC,MAA6B;AAC7D,UAAI,EAAE,KAAK,MAAM;AACf,cAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,GAAG;AAC1C,YAAI,SAAS;AACX,eAAK,YAAsB,EAAC,IAAI,EAAE,KAAK,MAAM,KAAK,EAAE,KAAK,KAAK,KAAK,QAAO,CAAC;AAAA,QAC7E;AAAA,MACF,WAAW,EAAE,KAAK,IAAI;AACpB,YAAI,EAAE,KAAK,MAAM,OAAO;AACtB,kBAAQ,QAAQ,EAAE,KAAK,KAAa,EAAE,KAAK,GAAG;AAAA,QAChD;AAAA,MACF;AAAA,KACD;AAAA,EACH;AAEA,QAAM,cAAc,IAAI,eACpB,aAAW,QAAQ,QAAQ,OAAK;AAC9B,MAAE,OAAO,iBAA+B,WAAW,EAAE,QAAQ,QAAM,GAAG,OAAO,CAAC;AAAA,GAC/E,CAAC;AAUN,QAAM,gBAAgB,CAAC,YAAmB,KAAK,OAAO,aAAa,GAAI,IAAI,cAAa,OAAO,OAAO,CAAC,CAAC,EAC7D,QAAQ,UAAU,CAAC,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,MAAM,EAAE,GAClG,YAAgB,CAAC,WAAoB,IAAI,YAAY,EAAG,OAAO,WAAW,KAAK,KAAK,MAAM,GAAG,OAAK,EAAE,WAAW,CAAC,CAAC,CAAC;AAGxH,QAAM,UAAU,MAAM,KAAK,MAAM,KAAK,OAAO,IAAI,MAAM,CAAC,EAAE,SAAS,EAAE;AAGrE,QAAM,gBAA+B,OAAO,MAAM,YAAY,SAAS,MAAM,gBAAgB;AAC3F,QAAI;AACJ,UAAM,aAAa,SAAS,OAAO,MAAM,MAAM,EACnB,KAAK,CAAC,QAAQ,IAAI,WAAW,OAAO,GAAG,CAAC,GACvC,MAAM,GAAG,EAAE,MAAM,QAAQ;AACtD,aAAS,SAAS,OAAO,MAAM;AAC/B,QAAI,gBAAgB,cAAc,UAAU;AAE1C,WAAK,YAAsB,EAAE,MAAM,OAAO,KAAK,WAAW,CAAC;AAE3D,YAAM,IAAI,QAAqB,CAAC,YAAY,WAAW,MAAM,QAAQ,EAAE,GAAG,GAAG,CAAC;AAC9E,YAAM,cAAc,UAAU;AAC5B,kBAAU,CAAC,SAAiB,OAAO,MAAM;AAAA,MAC3C;AAAA,IACF;AACA,SAAK,SAAS;AACZ,WAAK,YAAY;AAAE,WAAG;AAAE,uBAAa,QAAQ;AAAA,QAAE,SAAS,cAAc;AAAA,MAAS;AAC/E,YAAM,OAAO,QAAQ,QAAQ,UAAU,GACnC,UAAqB,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS;AAC7D,UAAI,YAAY;AACd,gBAAQ,OAAO,GAAG,UAAU;AAC5B,gBAAQ,QAAQ,YAAY,KAAK,UAAU,OAAO,CAAC;AAAA,MACrD;AAEA,gBAAU,CAAC,SAAgB;AACzB,cAAM,UAAU,SAAS,IAAI;AAC7B,eAAO,cAAc,KAAK,UACP;AAAA,UAAC;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,QAAQ,UAAU;AAAA,UAClB,QAAQ;AAAA,QAAQ,CAAC,CAAC;AAAA;AAAA,IAE1C;AAEA,eAAW,KAA6C,IAAI,iBAAiB,SAAS,GAAG;AACvF,YAAM,MAAM,EAAE,MACR,KAAM,IAAI,OAAO,gBAAgB,gBAAgB;AACvD,QAAE,OAAU,IAAI,QAAQ,IAAI,UAAU,MAAM,QAAQ,IAAI,MAAM,EAAE,EAAG,EAAE,CAAC;AAAA,IACxE;AAEA,WAAO;AAAA;AAIT,QAAM,aAAa,CAAC;AAAA,KAAoB,QAAQ,QAAQ,SAAS,KAAK;AAAA,KAC/B,QAAQ,QAAQ;AAAA,IAAS;AAAA,QAAgC;AAC9F,UAAO,SAAsB,IAAI;AACjC,QAAI,YAAY;AAChB,KAAC,IAAI,SAAS,MAAM;AAClB,YAAM,QAAQ,OAAO,UAAU,OAAK,IAAI,OAAO,WAAW;AAC1D,UAAI,OAAO,WAAW,WAAW;AAAE;AAAA,MAAO;AAE1C,UAAI,OAAO;AACX,aAAO,OAAO,QAAQ,CAAC,MAAM,MAAM;AAEjC,gBAAQ,QAAQ,QAAQ,cAER,QAAQ,WAAW,OAAO,QAAQ,QAAQ,IAAI,IAAI,SACrD,QAAQ,MAAM,SAAS,OAAO,MAAM,OAAO,GAAG;AAAA,OAC5D;AACD,cAAgB;AAChB,UAAI,YAAY;AAChB,UAAI,mBAAmB,cAAc,IAAI;AACzC,kBAAY,OAAO;AACnB,UAAI,cAAc,gBAAgB;AAAE,QAAK,cAAc,KAAK,UAAU;AAAA,MAAE;AAAA,OACvE;AACH,QAAI,IAAI,UAAU,SAAS,OAAO,MAAM,GAAG;AAAE,kBAAY,QAAQ,MAAM;AAAA,IAAE;AAAA;AAI3E,QAAM,iBAAiB,OAAO,MAAkB,WAAW,gBAA+B;AACxF,UAAM,UAAU,cAAc,iBACZ,MAAM,cAAc,KAAK,UAAU,IACnC,CAAC,SAAiB;AACpC,cAAU,KAAK,gBAAc,UAAU,QAAQ,QAAQ,QAAQ,UAAU,CAAC,CAAC;AAAA;AAI7E,QAAM,iBAAiB,CAAC,OAAuB,MAAM,eAA8B;AACjF,cAAU,MAAM,gBAAc;AAE5B,aAAO,UAAU,QAAQ,QAAQ,KAAK,IAAI,KAAK,KAAK,OAAO,SAAS,UAAU,CAAC,GAAG,CAAC,CAAC,EACnE,QAAQ,MAAM,UAAU;AAAA,KAC1C;AAAA;AAIH,QAAM,YAAY,CAAC,SAAqB,WAAgC;AACtE,UAAM,QAA4B,QAAQ,cAAc,OAAO,GACzD,OAA6B,QAAQ,cAAc,GAAG,GACtD,UAAU,MAAM,OAChB,SAAU,MAAM;AACJ,UAAI,MAAM,UAAU,SAAS;AAAE;AAAA,MAAO;AACtC,aAAO,KAAK,KAAK,OAAO,CAAC,MAAM,KAAK,MAAM,OAAO,MAAM,GAAG,EAAE,IAAI,OAAK,SAAS,CAAC,KAAK,CAAC;AACrF,UAAI,MAAM,OAAO,MAAM,KAAK;AAC1B,cAAM,QAAQ;AACd,cAAM,OAAO;AACb;AAAA,MACF;AACA,WAAK,OAAO,OAAO,MAAM,KAAK;AAC9B,WAAK,MAAM;AAAA;AAE7B,UAAM,iBAAiB,SAAS,MAAM,MAAM,OAAO,CAAC;AACpD,UAAM,iBAAiB,YAAY,MAAM;AACzC,UAAM,iBAAiB,YAAY,OAAK;AAAE,UAAI,EAAE,OAAO,SAAS;AAAE,eAAO;AAAA,MAAE;AAAA,KAAG;AAAA;AAIhF,SAAO;AAAA,IACL,SAAS;AAAA,IAGT,IAAI,CAAC,KAAkB;AACrB,YAAM,SAAW,eAAe,cAAc,MAAM,UAC9C,WAAW,OAAO,iBAAiB,aAAa;AACtD,iBAAW,WAAoC,UAAU;AACvD,YAAI;AACF,iBAAO,aAAa,QAAkB,KAAK,MAAM,UAAkB,QAAQ,aAAa,WAAW,CAAC,CAAC;AACrG,cAAI,YAAY,KAAK;AAEnB,YAAK,cAAc,SAAS,GAAwB,IAAI;AAAA,UAC1D,WAAW,YAAY,OAAO;AAC5B,uBAAyB,SAAmC,IAAI;AAAA,UAClE,WAAW,YAAY,OAAO;AAC5B,YAAK,eAAe,SAAkC,IAAI;AAAA,UAC5D,WAAW,YAAY,OAAO;AAC5B,2BAAe,SAAkC,IAAI;AAAA,UACvD;AAAA,iBAEO,KAAP;AAAc,kBAAQ,KAAK,qBAAqB,SAAS,GAAG;AAAA;AAAA,MAChE;AAAA;AAAA,EAEJ;AAAA,GACC;",
8
- "debugId": "340F68B25BC9072264756E2164756E21",
8
+ "debugId": "7F9EF7EF7A33670664756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1 +1 @@
1
- window.Pagy=(()=>{const L="sessionStorage"in window&&"BroadcastChannel"in window;let j="pagy",Y,D,O;if(L)Y=sessionStorage,D=new BroadcastChannel(j),O=Date.now(),D.addEventListener("message",(q)=>{if(q.data.from){const z=Y.getItem(q.data.key);if(z)D.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==O)Y.setItem(q.data.key,q.data.str)}});const U=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),S=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),B=()=>Math.floor(Math.random()*46656).toString(36),P=async(q,[z,C,F,G])=>{let M;const R=document.cookie.split(/;\s+/).find((H)=>H.startsWith(j+"="))?.split("=")[1]??B();if(document.cookie=j+"="+R,z&&!(z in Y)){if(D.postMessage({from:O,key:z}),await new Promise((H)=>setTimeout(()=>H(""),100)),!(z in Y))M=(H)=>H+"+"+F}if(!M){if(!z)do z=B();while(z in Y);const H=Y.getItem(z),Q=H?JSON.parse(H):[void 0];if(G)Q.splice(...G),Y.setItem(z,JSON.stringify(Q));M=(X)=>{const Z=parseInt(X);return S(JSON.stringify([R,z,Z,Q.length,Q[Z-1],Q[Z]]))}}for(let H of q.querySelectorAll("a[href]")){const Q=H.href,X=new RegExp(`(?<=\\?.*)\\b${C}=(\\d+)`);H.href=Q.replace(X,C+"="+M(Q.match(X)[1]))}return M},x=(q,[[z,C,F,G,M],[R,H,Q],X])=>{const Z=q.parentElement;let J=-1;if((q.render=()=>{const E=R.findIndex(($)=>$<Z.clientWidth);if(R[E]===J)return;let T=z;if(H[E].forEach(($,A)=>{T+=$=="gap"?G:(typeof $=="number"?C.replace("P ",$):F).replace("L<",Q?.[E][A]??$+"<")}),T+=M,q.innerHTML="",q.insertAdjacentHTML("afterbegin",T),J=R[E],X&&L)P(q,X)})(),q.classList.contains(j+"-rjs"))U.observe(Z)},N=async(q,[z,C])=>{const F=C&&L?await P(q,C):(G)=>G;W(q,(G)=>z.replace("P ",F(G)))},V=(q,[z,C])=>{W(q,(F)=>{return C.replace("P ",Math.max(Math.ceil(z/parseInt(F)),1)).replace("L ",F)})},W=(q,z)=>{const C=q.querySelector("input"),F=q.querySelector("a"),G=C.value,M=()=>{if(C.value===G)return;const[R,H,Q]=[C.min,C.value,C.max].map((X)=>parseInt(X)||0);if(H<R||H>Q){C.value=G,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",M),C.addEventListener("keypress",(R)=>{if(R.key=="Enter")M()})};return{version:"43.0.7",init(q){const z=q instanceof HTMLElement?q:document,C=z.querySelectorAll("[data-pagy]");for(let F of C)try{const[G,...M]=JSON.parse(_(F.getAttribute("data-pagy")));if(G=="k")P(F,...M);else if(G=="snj")x(F,M);else if(G=="inj")N(F,M);else if(G=="ltj")V(F,M)}catch(G){console.warn("Pagy.init: %o\n%s",F,G)}}}})();
1
+ window.Pagy=(()=>{const L="sessionStorage"in window&&"BroadcastChannel"in window;let j="pagy",Y,D,O;if(L)Y=sessionStorage,D=new BroadcastChannel(j),O=Date.now(),D.addEventListener("message",(q)=>{if(q.data.from){const z=Y.getItem(q.data.key);if(z)D.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==O)Y.setItem(q.data.key,q.data.str)}});const U=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),S=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),B=()=>Math.floor(Math.random()*46656).toString(36),P=async(q,[z,C,F,G])=>{let M;const R=document.cookie.split(/;\s+/).find((H)=>H.startsWith(j+"="))?.split("=")[1]??B();if(document.cookie=j+"="+R,z&&!(z in Y)){if(D.postMessage({from:O,key:z}),await new Promise((H)=>setTimeout(()=>H(""),100)),!(z in Y))M=(H)=>H+"+"+F}if(!M){if(!z)do z=B();while(z in Y);const H=Y.getItem(z),Q=H?JSON.parse(H):[void 0];if(G)Q.splice(...G),Y.setItem(z,JSON.stringify(Q));M=(X)=>{const Z=parseInt(X);return S(JSON.stringify([R,z,Z,Q.length,Q[Z-1],Q[Z]]))}}for(let H of q.querySelectorAll("a[href]")){const Q=H.href,X=new RegExp(`(?<=\\?.*)\\b${C}=(\\d+)`);H.href=Q.replace(X,C+"="+M(Q.match(X)[1]))}return M},x=(q,[[z,C,F,G,M],[R,H,Q],X])=>{const Z=q.parentElement;let J=-1;if((q.render=()=>{const E=R.findIndex(($)=>$<Z.clientWidth);if(R[E]===J)return;let T=z;if(H[E].forEach(($,A)=>{T+=$=="gap"?G:(typeof $=="number"?C.replace("P ",$):F).replace("L<",Q?.[E][A]??$+"<")}),T+=M,q.innerHTML="",q.insertAdjacentHTML("afterbegin",T),J=R[E],X&&L)P(q,X)})(),q.classList.contains(j+"-rjs"))U.observe(Z)},N=async(q,[z,C])=>{const F=C&&L?await P(q,C):(G)=>G;W(q,(G)=>z.replace("P ",F(G)))},V=(q,[z,C])=>{W(q,(F)=>{return C.replace("P ",Math.max(Math.ceil(z/parseInt(F)),1)).replace("L ",F)})},W=(q,z)=>{const C=q.querySelector("input"),F=q.querySelector("a"),G=C.value,M=()=>{if(C.value===G)return;const[R,H,Q]=[C.min,C.value,C.max].map((X)=>parseInt(X)||0);if(H<R||H>Q){C.value=G,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",M),C.addEventListener("keypress",(R)=>{if(R.key=="Enter")M()})};return{version:"43.1.0",init(q){const z=q instanceof HTMLElement?q:document,C=z.querySelectorAll("[data-pagy]");for(let F of C)try{const[G,...M]=JSON.parse(_(F.getAttribute("data-pagy")));if(G=="k")P(F,...M);else if(G=="snj")x(F,M);else if(G=="inj")N(F,M);else if(G=="ltj")V(F,M)}catch(G){console.warn("Pagy.init: %o\n%s",F,G)}}}})();
data/javascripts/pagy.mjs CHANGED
@@ -123,7 +123,7 @@ const Pagy = (() => {
123
123
  });
124
124
  };
125
125
  return {
126
- version: "43.0.7",
126
+ version: "43.1.0",
127
127
  init(arg) {
128
128
  const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
129
129
  for (const element of elements) {
@@ -22,12 +22,4 @@ class Pagy
22
22
 
23
23
  # Generic internal error
24
24
  class InternalError < StandardError; end
25
-
26
- # JsonApi :page param error
27
- class JsonapiReservedParamError < StandardError
28
- # Inform about the actual value
29
- def initialize(value)
30
- super("expected reserved :page param to be nil or Hash-like; got #{value.inspect}")
31
- end
32
- end
33
25
  end
@@ -1,26 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Pagy
4
- # Decouple the reuest from the env, allowing non-rack apps to use pagy by passing a hash.
5
- # Resolve :page and :limit, supporting the :jsonapi option. Support for URL composition.
6
- #
4
+ # Decouple the request from the env, allowing non-rack apps to use pagy by passing a hash.
5
+ # Resolve the :page and :limit options from params.
7
6
  class Request
8
- def initialize(request, options = {})
7
+ def initialize(request)
9
8
  @base_url, @path, @params, @cookie =
10
9
  if request.is_a?(Hash)
11
10
  request.values_at(:base_url, :path, :params, :cookie)
12
11
  else
13
12
  [request.base_url, request.path, request.GET.merge(request.POST), request.cookies['pagy']]
14
13
  end
15
- @jsonapi = @params['page'] && options[:jsonapi]
16
- raise JsonapiReservedParamError, @params['page'] if @jsonapi && !@params['page'].respond_to?(:fetch)
17
14
  end
18
15
 
19
16
  attr_reader :base_url, :path, :params, :cookie
20
17
 
21
18
  def resolve_page(options, force_integer: true)
22
19
  page_key = options[:page_key] || DEFAULT[:page_key]
23
- page = @jsonapi ? @params['page'][page_key] : @params[page_key]
20
+ page = @params.dig(options[:root_key], page_key) || @params[page_key]
24
21
  page = nil if page == '' # fix for app-generated queries like ?page=
25
22
  force_integer ? (page || 1).to_i : page
26
23
  end
@@ -29,7 +26,7 @@ class Pagy
29
26
  limit_key = options[:limit_key] || DEFAULT[:limit_key]
30
27
  return options[:limit] || DEFAULT[:limit] \
31
28
  unless options[:client_max_limit] &&
32
- (requested_limit = @jsonapi ? @params['page'][limit_key] : @params[limit_key])
29
+ (requested_limit = @params.dig(options[:root_key], limit_key) || @params[limit_key])
33
30
 
34
31
  [requested_limit.to_i, options[:client_max_limit]].min
35
32
  end
@@ -40,16 +40,16 @@ class Pagy
40
40
 
41
41
  # Return the URL for the page, relying on the Pagy::Request
42
42
  def compose_page_url(page, limit_token: nil, **options)
43
- jsonapi, page_key, limit_key, limit, client_max_limit, querify, absolute, path, fragment =
43
+ root_key, page_key, limit_key, limit, client_max_limit, querify, absolute, path, fragment =
44
44
  @options.merge(options)
45
- .values_at(:jsonapi, :page_key, :limit_key, :limit, :client_max_limit, :querify, :absolute, :path, :fragment)
45
+ .values_at(:root_key, :page_key, :limit_key, :limit, :client_max_limit, :querify, :absolute, :path, :fragment)
46
46
  params = @request.params.clone(freeze: false)
47
- params.delete(jsonapi ? 'page' : page_key)
47
+ params.delete(root_key || page_key)
48
48
  factors = {}.tap do |h|
49
49
  h[page_key] = countless? ? "#{page || 1}+#{@last}" : page
50
50
  h[limit_key] = limit_token || limit if client_max_limit
51
51
  end.compact # No empty params
52
- params.merge!(jsonapi ? { 'page' => factors } : factors) if factors.size.positive?
52
+ params.merge!(root_key ? { root_key => factors } : factors) if factors.size.positive?
53
53
  querify&.(params) # Must modify the params: the returned value is ignored
54
54
  query_string = QueryUtils.build_nested_query(params, nil, [page_key, limit_key])
55
55
  query_string = "?#{query_string}" unless query_string.empty?
@@ -3,20 +3,6 @@
3
3
  class Pagy
4
4
  # Provide a ready to use pagy environment when included in irb/rails console
5
5
  module Console
6
- class Request
7
- attr_accessor :base_url, :path, :params
8
-
9
- def initialize
10
- @base_url = 'http://www.example.com'
11
- @path = '/path'
12
- @params = { example: '123' }
13
- end
14
-
15
- def GET = @params # rubocop:disable Naming/MethodName
16
-
17
- def cookies = {}
18
- end
19
-
20
6
  class Collection < Array
21
7
  def initialize(arr = Array(1..1000))
22
8
  super
@@ -31,8 +17,8 @@ class Pagy
31
17
  include Method
32
18
 
33
19
  # Direct reference to request.params via a method
34
- def params = request.params
35
- def request = @request ||= Request.new
20
+ def request = @request ||= { base_url: 'http://www.example.com', path: '/path', params: { example: '123' } }
21
+ def params = request[:params]
36
22
  def collection = Collection
37
23
  end
38
24
  end
@@ -6,13 +6,11 @@ class Pagy
6
6
  module_function
7
7
 
8
8
  # Common search logic
9
- def wrap(context, pagy_search_args, options)
10
- context.instance_exec do
11
- options[:page] ||= options[:request].resolve_page(options)
12
- options[:limit] ||= options[:request].resolve_limit(options)
13
- end
14
- pagy, results = yield
15
- calling = pagy_search_args[4..]
9
+ def wrap(pagy_search_args, options)
10
+ options[:page] ||= options[:request].resolve_page(options)
11
+ options[:limit] = options[:request].resolve_limit(options)
12
+ pagy, results = yield
13
+ calling = pagy_search_args[4..]
16
14
  [pagy, calling.empty? ? results : results.send(*calling)]
17
15
  end
18
16
  end
@@ -11,7 +11,6 @@ class Pagy
11
11
  raise ArgumentError, "keys must be in #{allowed_options.inspect}" \
12
12
  unless config.is_a?(Hash) && (config.keys - allowed_options).empty?
13
13
 
14
- config[:request] = Request.new(config[:request] || request)
15
14
  config[:offset] ||= {}
16
15
  unless config[:disabled]
17
16
  calendar, from, to =
@@ -4,22 +4,19 @@ class Pagy
4
4
  module CountlessPaginator
5
5
  module_function
6
6
 
7
- # Return Pagy object and records
8
- def paginate(context, collection, **options)
9
- context.instance_eval do
10
- request = Request.new(options[:request] || self.request, options)
11
- if options[:page].nil?
12
- page = request.resolve_page(options, force_integer: false) # accept nil and strings
13
- if page.is_a?(String)
14
- p, l = page.split(/ /, 2).map(&:to_i)
15
- options[:page] = p if p.positive?
16
- options[:last] = l if l&.positive?
17
- end
7
+ # Return the Offset::Countless instance and records
8
+ def paginate(collection, options)
9
+ if options[:page].nil?
10
+ page = options[:request].resolve_page(options, force_integer: false) # accept nil and strings
11
+ if page.is_a?(String)
12
+ p, l = page.split(/ /, 2).map(&:to_i) # decoded '+' added by the compose_page_url
13
+ options[:page] = p if p.positive?
14
+ options[:last] = l if l&.positive?
18
15
  end
19
- options[:limit] = request.resolve_limit(options)
20
- pagy = Offset::Countless.new(**options, request:)
21
- [pagy, pagy.records(collection)]
22
16
  end
17
+ options[:limit] = options[:request].resolve_limit(options)
18
+ pagy = Offset::Countless.new(**options)
19
+ [pagy, pagy.records(collection)]
23
20
  end
24
21
  end
25
22
  end
@@ -7,26 +7,23 @@ class Pagy
7
7
  module_function
8
8
 
9
9
  # Paginate from the search object
10
- def paginate(context, search, **options)
11
- context.instance_eval do
12
- options[:request] = Request.new(options[:request] || request, options)
13
- if search.is_a?(Search::Arguments)
14
- # The search is the array of pagy_search arguments
15
- Searcher.wrap(self, search, options) do
16
- model, query_or_payload, search_options = search
17
- search_options[:size] = options[:limit]
18
- search_options[:from] = options[:limit] * ((options[:page] || 1) - 1)
19
- results = model.send(ElasticsearchRails::DEFAULT[:search_method], query_or_payload, **search_options)
20
- options[:count] = ElasticsearchRails.total_count(results)
21
- [ElasticsearchRails.new(**options), results]
22
- end
23
- else
24
- # The search is an elasticsearch_rails response
25
- options[:limit] = search.search.options[:size] || 10
26
- options[:page] = ((search.search.options[:from] || 0) / options[:limit]) + 1
27
- options[:count] = ElasticsearchRails.total_count(search)
28
- ElasticsearchRails.new(**options)
10
+ def paginate(search, options)
11
+ if search.is_a?(Search::Arguments)
12
+ # The search is the array of pagy_search arguments
13
+ Searcher.wrap(search, options) do
14
+ model, query_or_payload, search_options = search
15
+ search_options[:size] = options[:limit]
16
+ search_options[:from] = options[:limit] * ((options[:page] || 1) - 1)
17
+ results = model.send(ElasticsearchRails::DEFAULT[:search_method], query_or_payload, **search_options)
18
+ options[:count] = ElasticsearchRails.total_count(results)
19
+ [ElasticsearchRails.new(**options), results]
29
20
  end
21
+ else
22
+ # The search is an elasticsearch_rails response
23
+ options[:limit] = search.search.options[:size] || 10
24
+ options[:page] = ((search.search.options[:from] || 0) / options[:limit]) + 1
25
+ options[:count] = ElasticsearchRails.total_count(search)
26
+ ElasticsearchRails.new(**options)
30
27
  end
31
28
  end
32
29
  end
@@ -6,24 +6,21 @@ class Pagy
6
6
  module KeynavJsPaginator
7
7
  module_function
8
8
 
9
- # Return Pagy::Keyset::Keynav object and paginated records.
9
+ # Return the Pagy::Keyset::Keynav instance and paginated records.
10
10
  # Fall back to :countless if the :page has no client data.
11
- def paginate(context, set, **options)
12
- context.instance_eval do
13
- request = Request.new(options[:request] || self.request, options)
14
- page = request.resolve_page(options, force_integer: false) # allow nil
15
- if page&.match(' ') # countless page -> no augmentation -> fallback
16
- return pagy(:countless, set, page:, **options)
17
- elsif page.is_a?(String) # keynav page param
18
- page_arguments = JSON.parse(B64.urlsafe_decode(page))
19
- # Restart the pagination from page 1 if the url has been requested from another browser
20
- options[:page] = page_arguments if request.cookie == page_arguments.shift
21
- end
22
-
23
- options[:limit] = request.resolve_limit(options)
24
- pagy = Keyset::Keynav.new(set, **options, request:)
25
- [pagy, pagy.records]
11
+ def paginate(set, options)
12
+ page = options[:request].resolve_page(options, 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
16
+ page_arguments = JSON.parse(B64.urlsafe_decode(page))
17
+ # Restart the pagination from page 1 if the url has been requested from another browser
18
+ options[:page] = page_arguments if options[:request].cookie == page_arguments.shift
26
19
  end
20
+
21
+ options[:limit] = options[:request].resolve_limit(options)
22
+ pagy = Keyset::Keynav.new(set, **options)
23
+ [pagy, pagy.records]
27
24
  end
28
25
  end
29
26
  end
@@ -4,15 +4,12 @@ class Pagy
4
4
  module KeysetPaginator
5
5
  module_function
6
6
 
7
- # Return Pagy::Keyset object and paginated records
8
- def paginate(context, set, **options)
9
- context.instance_eval do
10
- request = Request.new(options[:request] || self.request, options)
11
- options[:page] ||= request.resolve_page(options, force_integer: false) # allow nil
12
- options[:limit] = request.resolve_limit(options)
13
- pagy = Keyset.new(set, **options, request:)
14
- [pagy, pagy.records]
15
- end
7
+ # Return Pagy::Keyset instance and paginated records
8
+ def paginate(set, options)
9
+ options[:page] ||= options[:request].resolve_page(options, force_integer: false) # allow nil
10
+ options[:limit] = options[:request].resolve_limit(options)
11
+ pagy = Keyset.new(set, **options)
12
+ [pagy, pagy.records]
16
13
  end
17
14
  end
18
15
  end
@@ -7,26 +7,23 @@ class Pagy
7
7
  module_function
8
8
 
9
9
  # Paginate from the search object
10
- def paginate(context, search, **options)
11
- context.instance_eval do
12
- options[:request] = Request.new(options[:request] || request, options)
13
- if search.is_a?(Search::Arguments)
14
- # The search is the array of pagy_search arguments
15
- Searcher.wrap(self, search, options) do
16
- model, term, search_options = search
17
- search_options[:hits_per_page] = options[:limit]
18
- search_options[:page] = options[:page]
19
- results = model.send(Meilisearch::DEFAULT[:search_method], term, search_options)
20
- options[:count] = results.raw_answer['totalHits']
21
- [Meilisearch.new(**options), results]
22
- end
23
- else
24
- # The search is a meilisearch results object
25
- options[:limit] = search.raw_answer['hitsPerPage']
26
- options[:page] = search.raw_answer['page']
27
- options[:count] = search.raw_answer['totalHits']
28
- Meilisearch.new(**options)
10
+ def paginate(search, options)
11
+ if search.is_a?(Search::Arguments)
12
+ # The search is the array of pagy_search arguments
13
+ Searcher.wrap(search, options) do
14
+ model, term, search_options = search
15
+ search_options[:hits_per_page] = options[:limit]
16
+ search_options[:page] = options[:page]
17
+ results = model.send(Meilisearch::DEFAULT[:search_method], term, search_options)
18
+ options[:count] = results.raw_answer['totalHits']
19
+ [Meilisearch.new(**options), results]
29
20
  end
21
+ else
22
+ # The search is a meilisearch results object
23
+ options[:limit] = search.raw_answer['hitsPerPage']
24
+ options[:page] = search.raw_answer['page']
25
+ options[:count] = search.raw_answer['totalHits']
26
+ Meilisearch.new(**options)
30
27
  end
31
28
  end
32
29
  end
@@ -15,13 +15,19 @@ class Pagy
15
15
  path = Pathname.new(__dir__)
16
16
  paginators.each { |symbol, name| autoload name, path.join(symbol.to_s) }
17
17
 
18
- # Defines the pagy method. Include in the app controller/view.
18
+ # Pagy::Method defines the pagy method to be included in the app controller/view.
19
19
  Method = Module.new do
20
20
  protected
21
21
 
22
22
  define_method :pagy do |paginator = :offset, collection, **options|
23
- merged_options = paginator == :calendar ? options : Pagy.options.merge(options)
24
- Pagy.const_get(paginators[paginator]).paginate(self, collection, **merged_options)
23
+ options[:root_key] = 'page' if options[:jsonapi] # enforce 'page' root_key for JSON:API
24
+ options[:request] = Request.new(options[:request] || request)
25
+ arguments = if paginator == :calendar
26
+ [self, collection, options]
27
+ else
28
+ [collection, Pagy.options.merge(options)]
29
+ end
30
+ Pagy.const_get(paginators[paginator]).paginate(*arguments)
25
31
  end
26
32
  end
27
33
  end
@@ -4,16 +4,13 @@ class Pagy
4
4
  module OffsetPaginator
5
5
  module_function
6
6
 
7
- # Return instance and page of results
8
- def paginate(context, collection, **options)
9
- context.instance_eval do
10
- request = Request.new(options[:request] || self.request, options)
11
- options[:page] ||= request.resolve_page(options)
12
- options[:limit] = request.resolve_limit(options)
13
- options[:count] ||= collection.instance_of?(Array) ? collection.size : OffsetPaginator.get_count(collection, options)
14
- pagy = Offset.new(**options, request:)
15
- [pagy, collection.instance_of?(Array) ? collection[pagy.offset, pagy.limit] : pagy.records(collection)]
16
- end
7
+ # Return the Pagy::Offset instance and results
8
+ def paginate(collection, options)
9
+ options[:page] ||= options[:request].resolve_page(options)
10
+ options[:limit] = options[:request].resolve_limit(options)
11
+ options[:count] ||= collection.instance_of?(Array) ? collection.size : OffsetPaginator.get_count(collection, options)
12
+ pagy = Offset.new(**options)
13
+ [pagy, collection.instance_of?(Array) ? collection[pagy.offset, pagy.limit] : pagy.records(collection)]
17
14
  end
18
15
 
19
16
  # Get the collection count
@@ -7,26 +7,23 @@ class Pagy
7
7
  module_function
8
8
 
9
9
  # Paginate from the search object
10
- def paginate(context, search, **options)
11
- context.instance_eval do
12
- options[:request] = Request.new(options[:request] || request, options)
13
- if search.is_a?(Search::Arguments)
14
- # The search is the array of pagy_search arguments
15
- Searcher.wrap(self, search, options) do
16
- model, term, search_options, block = search
17
- search_options[:per_page] = options[:limit]
18
- search_options[:page] = options[:page]
19
- results = model.send(Searchkick::DEFAULT[:search_method], term || '*', **search_options, &block)
20
- options[:count] = results.total_count
21
- [Searchkick.new(**options), results]
22
- end
23
- else
24
- # The search is a searchkick results object
25
- options[:limit] = search.respond_to?(:options) ? search.options[:per_page] : search.per_page
26
- options[:page] = search.respond_to?(:options) ? search.options[:page] : search.current_page
27
- options[:count] = search.total_count
28
- Searchkick.new(**options)
10
+ def paginate(search, options)
11
+ if search.is_a?(Search::Arguments)
12
+ # The search is the array of pagy_search arguments
13
+ Searcher.wrap(search, options) do
14
+ model, term, search_options, block = search
15
+ search_options[:per_page] = options[:limit]
16
+ search_options[:page] = options[:page]
17
+ results = model.send(Searchkick::DEFAULT[:search_method], term || '*', **search_options, &block)
18
+ options[:count] = results.total_count
19
+ [Searchkick.new(**options), results]
29
20
  end
21
+ else
22
+ # The search is a searchkick results object
23
+ options[:limit] = search.respond_to?(:options) ? search.options[:per_page] : search.per_page
24
+ options[:page] = search.respond_to?(:options) ? search.options[:page] : search.current_page
25
+ options[:count] = search.total_count
26
+ Searchkick.new(**options)
30
27
  end
31
28
  end
32
29
  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.0.7'
11
+ VERSION = '43.1.0'
12
12
  ROOT = Pathname.new(__dir__).parent.freeze
13
13
  DEFAULT = { limit: 20, limit_key: 'limit', page_key: 'page' }.freeze
14
14
  PAGE_TOKEN = 'P '
data/locales/id.yml CHANGED
@@ -4,9 +4,7 @@ id:
4
4
  pagy:
5
5
  p11n: 'Other'
6
6
  aria_label:
7
- # please add a comment in the https://github.com/ddnexus/pagy/issues/588
8
- # posting the translation of the following "Page"/"Pages" with the plurals for this locale
9
- nav: "Pages"
7
+ nav: "Halaman"
10
8
  previous: "Sebelumnya"
11
9
  next: "Selanjutnya"
12
10
  previous: "&lt;"
data/locales/ja.yml CHANGED
@@ -4,9 +4,7 @@ ja:
4
4
  pagy:
5
5
  p11n: 'Other'
6
6
  aria_label:
7
- # please add a comment in the https://github.com/ddnexus/pagy/issues/590
8
- # posting the translation of the following "Page"/"Pages" with the plurals for this locale
9
- nav: "Pages"
7
+ nav: "ページ"
10
8
  previous: "前へ"
11
9
  next: "次へ"
12
10
  previous: "&lt;"
data/locales/km.yml CHANGED
@@ -4,9 +4,7 @@ km:
4
4
  pagy:
5
5
  p11n: 'Other'
6
6
  aria_label:
7
- # please add a comment in the https://github.com/ddnexus/pagy/issues/591
8
- # posting the translation of the following "Page"/"Pages" with the plurals for this locale
9
- nav: "Pages"
7
+ nav: "ទំព័រ"
10
8
  previous: "មុន"
11
9
  next: "បន្ទាប់"
12
10
  previous: "&lt;"
data/locales/sw.yml CHANGED
@@ -9,8 +9,8 @@ sw:
9
9
  # after you make these changes.
10
10
  aria_label:
11
11
  nav:
12
- one: "Page"
13
- other: "Pages"
12
+ one: "Ukurasa"
13
+ other: "Kurasa"
14
14
  previous: "Awali"
15
15
  next: "Ifuatayo"
16
16
  previous: "&lt;"
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.0.7
4
+ version: 43.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis