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.
- checksums.yaml +4 -4
- data/apps/calendar.ru +1 -1
- data/apps/demo.ru +1 -1
- data/apps/enable_rails_page_segment.rb +6 -2
- data/apps/keynav+root_key.ru +1 -1
- data/apps/keynav.ru +1 -1
- data/apps/keyset.ru +1 -1
- data/apps/keyset_sequel.ru +1 -1
- data/apps/rails.ru +1 -1
- data/apps/repro.ru +1 -1
- data/config/pagy.rb +1 -1
- data/javascripts/pagy.js +2 -2
- data/javascripts/pagy.js.map +2 -2
- data/javascripts/pagy.min.js +1 -1
- data/javascripts/pagy.mjs +1 -1
- data/lib/pagy/classes/calendar/calendar.rb +31 -25
- data/lib/pagy/classes/calendar/unit.rb +4 -1
- data/lib/pagy/classes/exceptions.rb +1 -0
- data/lib/pagy/classes/keyset/adapters/active_record.rb +3 -1
- data/lib/pagy/classes/keyset/adapters/sequel.rb +3 -1
- data/lib/pagy/classes/keyset/keynav.rb +3 -0
- data/lib/pagy/classes/keyset/keyset.rb +13 -17
- data/lib/pagy/classes/offset/countless.rb +9 -2
- data/lib/pagy/classes/offset/offset.rb +6 -1
- data/lib/pagy/classes/request.rb +9 -5
- data/lib/pagy/modules/abilities/countable.rb +1 -0
- data/lib/pagy/modules/abilities/linkable.rb +18 -11
- data/lib/pagy/modules/abilities/rangeable.rb +0 -1
- data/lib/pagy/modules/b64.rb +8 -2
- data/lib/pagy/modules/console.rb +15 -6
- data/lib/pagy/modules/i18n/i18n.rb +11 -4
- data/lib/pagy/modules/searcher.rb +8 -4
- data/lib/pagy/toolbox/helpers/data_hash.rb +1 -0
- data/lib/pagy/toolbox/helpers/headers_hash.rb +2 -1
- data/lib/pagy/toolbox/helpers/info_tag.rb +2 -0
- data/lib/pagy/toolbox/helpers/input_nav_js.rb +1 -0
- data/lib/pagy/toolbox/helpers/page_url.rb +1 -0
- data/lib/pagy/toolbox/helpers/series_nav.rb +1 -0
- data/lib/pagy/toolbox/helpers/series_nav_js.rb +1 -0
- data/lib/pagy/toolbox/paginators/calendar.rb +9 -6
- data/lib/pagy/toolbox/paginators/countish.rb +6 -3
- data/lib/pagy/toolbox/paginators/countless.rb +5 -2
- data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +12 -6
- data/lib/pagy/toolbox/paginators/keynav_js.rb +5 -3
- data/lib/pagy/toolbox/paginators/keyset.rb +2 -1
- data/lib/pagy/toolbox/paginators/meilisearch.rb +10 -7
- data/lib/pagy/toolbox/paginators/method.rb +7 -5
- data/lib/pagy/toolbox/paginators/offset.rb +9 -2
- data/lib/pagy/toolbox/paginators/searchkick.rb +12 -9
- data/lib/pagy.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0e875b9703ce63e0ea0fb713757bebbb752f17a80b7414e8b88ef1abbc9d3446
|
|
4
|
+
data.tar.gz: e6ea0a3dfcae811e97dd851c00ecb5089c4700d70763c6ef37112486bf129d97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5da60ae142b168c6f0d465a88fa7938fd23c9bb832fb4da5afcb2bd87d113238796bb1a01f37a304db4a0d77e90b59ad10847ac33403dd596f8c02a8d6a38f60
|
|
7
|
+
data.tar.gz: 0a914f93b8d74895ce872812f50e7d5f9a883f0fc848bb077f452a389817078ace734cdbb1592d0aaab43f4fb4dff948732a16b4b11dd1a9bbdebf7a163f417b
|
data/apps/calendar.ru
CHANGED
data/apps/demo.ru
CHANGED
|
@@ -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)
|
|
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(...)
|
|
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
|
|
data/apps/keynav+root_key.ru
CHANGED
data/apps/keynav.ru
CHANGED
data/apps/keyset.ru
CHANGED
data/apps/keyset_sequel.ru
CHANGED
data/apps/rails.ru
CHANGED
data/apps/repro.ru
CHANGED
data/config/pagy.rb
CHANGED
data/javascripts/pagy.js
CHANGED
|
@@ -126,7 +126,7 @@ window.Pagy = (() => {
|
|
|
126
126
|
});
|
|
127
127
|
};
|
|
128
128
|
return {
|
|
129
|
-
version: "43.2.
|
|
129
|
+
version: "43.2.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=
|
|
153
|
+
//# debugId=9616AB509F644C8D64756E2164756E21
|
|
154
154
|
//# sourceMappingURL=pagy.js.map
|
data/javascripts/pagy.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/pagy.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"interface SyncData {\n from?: number\n to?: number\n key: string\n str?: string\n}\ntype InitArgs = [\"k\", KeynavArgs] | // series_nav[_js] with keynav instance\n [\"snj\", SeriesNavJsArgs] | // series_nav_js\n [\"inj\", InputNavJsArgs] | // input_nav_js\n [\"ltj\", LimitTagJsArgs] // limit_tag_js\ntype AugmentKeynav = (nav:HTMLElement, keynavArgs:KeynavArgs) => Promise<((page: string) => string)>\ntype KeynavArgs = readonly [storageKey: string | null,\n rootKey: string | null,\n pageKey: string,\n last: number,\n spliceArgs?: SpliceArgs]\ntype SpliceArgs = readonly [start: number,\n deleteCount: number, // it would be optional, but ts complains\n ...items: Cutoff[]]\ntype Cutoff = readonly (string | number | boolean)[]\ntype AugmentedPage = [browserId: string,\n storageKey: string,\n pageNumber: number,\n pages: number,\n priorCutoff: Cutoff | null,\n pageCutoff: Cutoff | null]\ntype SeriesNavJsArgs = readonly [NavJsTokens, pageToken: string, NavJsSeries, KeynavArgs?]\ntype NavJsSeries = readonly [widths: number[],\n series: (string | number)[][],\n labels: string[][] | null]\ntype InputNavJsArgs = readonly [urlToken: string,\n pageToken: string,\n KeynavArgs?]\ntype LimitTagJsArgs = readonly [from: number,\n urlToken: string,\n pageToken: string,\n limitToken: string]\ntype NavJsTokens = readonly [before: string,\n anchor: string,\n current: string,\n gap: string,\n after: string]\ninterface NavJsElement extends HTMLElement {\n render(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst Pagy = (() => {\n const storageSupport = 'sessionStorage' in window && 'BroadcastChannel' in window;\n // eslint-disable-next-line prefer-const\n let pagy = \"pagy\", storage: Storage, sync: BroadcastChannel, tabId: number;\n if (storageSupport) {\n storage = sessionStorage; // shorten the compiled size\n sync = new BroadcastChannel(pagy);\n tabId = Date.now();\n // Sync the sessionStorage keys for the cutoffs opened in a new tab/window\n sync.addEventListener(\"message\", (e:MessageEvent<SyncData>) => {\n if (e.data.from) { // request cutoffs\n const cutoffs = storage.getItem(e.data.key);\n if (cutoffs) {\n sync.postMessage(<SyncData>{to: e.data.from, key: e.data.key, str: cutoffs});\n } // send response\n } else if (e.data.to) { // receive cutoffs\n if (e.data.to == tabId) {\n storage.setItem(e.data.key, <string>e.data.str);\n }\n }\n });\n }\n // The observer instance for responsive navs\n const rjsObserver = new ResizeObserver(\n entries => entries.forEach(e => {\n e.target.querySelectorAll<NavJsElement>(\".pagy-rjs\").forEach(el => el.render());\n }));\n\n /* Full set of B64 functions\n const B64Encode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode))),\n B64Safe = (unsafe:string) => unsafe.replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64SafeEncode = (unicode:string) => B64Safe(B64Encode(unicode)),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0))),\n B64Unsafe = (safe:string) => safe.replace(/[-_]/g, (match) => match == \"-\" ? \"+\" : \"/\"),\n B64SafeDecode = (base64:string) => B64Decode(B64Unsafe(base64))\n */\n const B64SafeEncode = (unicode:string) => btoa(String.fromCharCode(...(new TextEncoder).encode(unicode)))\n .replace(/[+/=]/g, (m) => m == \"+\" ? \"-\" : m == \"/\" ? \"_\" : \"\"),\n B64Decode = (base64:string) => (new TextDecoder()).decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));\n\n // Return a random key: 3 chars max, base-36 number < 36**3\n const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);\n\n // Manage the page augmentation for Keynav, called only if storageSupport\n const augmentKeynav: AugmentKeynav = async (nav, [storageKey, rootKey, pageKey, last, spliceArgs]) => {\n let augmentPage:(page: string) => string;\n const browserKey = document.cookie.split(/;\\s+/) // it works even if malformed\n .find((row) => row.startsWith(pagy + \"=\"))\n ?.split(\"=\")[1] ?? randKey();\n document.cookie = pagy + \"=\" + browserKey; // Smaller .min size: set the cookie without checking\n if (storageKey && !(storageKey in storage)) {\n // Sync the sessiongStorage from other tabs/windows (e.g., open page in the new tab/window)\n sync.postMessage(<SyncData>{ from: tabId, key: storageKey });\n // Wait for the listener to copy the cutoffs in the current sessionStorage\n await new Promise<string|null>((resolve) => setTimeout(() => resolve(\"\"), 100));\n if (!(storageKey in storage)) { // the storageKey didn't get copied: fallback to countless pagination\n augmentPage = (page: string) => page + '+' + last;\n }\n }\n // @ts-expect-error If it is not assigned it means it supports keynav\n if (!augmentPage) { // regular keynav pagination\n if (!storageKey) { do { storageKey = randKey() } while (storageKey in storage) } // no dup keys\n const data = storage.getItem(storageKey),\n cutoffs = <Cutoff[]>(data ? JSON.parse(data) : [undefined]);\n if (spliceArgs) {\n cutoffs.splice(...spliceArgs);\n storage.setItem(storageKey, JSON.stringify(cutoffs));\n }\n // Augment function\n augmentPage = (page:string) => {\n const pageNum = parseInt(page);\n return B64SafeEncode(JSON.stringify(\n <AugmentedPage>[browserKey,\n storageKey,\n pageNum,\n cutoffs.length, // pages/last\n cutoffs[pageNum - 1], // priorCutoff\n cutoffs[pageNum]])); // pageCutoff\n };\n }\n const search = (rootKey) ? `${rootKey}%5B${pageKey}%5D` : pageKey;\n const re = new RegExp(`(?<=\\\\?.*)(\\\\b${search}=)(\\\\d+)`);\n // Augment the page param of each href\n for (const a of <NodeListOf<HTMLAnchorElement>><unknown>nav.querySelectorAll('a[href]')) {\n a.href = a.href.replace(re, (_match, prefix, digit): string => `${prefix}${augmentPage(<string>digit)}`);\n }\n // Return the augment function for further augmentation (i.e., url token in input_nav_js)\n return augmentPage;\n };\n\n // Build the series_nav_js helper\n const buildNavJs = (nav:NavJsElement, [[before, anchor, current, gap, after], pageToken,\n [widths, series, labels], keynavArgs]:SeriesNavJsArgs) => {\n const parent = <HTMLElement>nav.parentElement;\n let lastWidth = -1;\n (nav.render = () => {\n const index = widths.findIndex(w => w < parent.clientWidth);\n if (widths[index] === lastWidth) { return } // no change: abort\n\n let html = before;\n series[index].forEach((item, i) => {\n // Avoid the if blocks and chain the results (shorter pagy.min.js and easier reading)\n html += item == \"gap\" ? gap :\n // @ts-expect-error the item may be a number, but the 'replace' converts it to string (shorter pagy.min.js)\n (typeof item == \"number\" ? anchor.replace(pageToken, item) : current)\n .replace(\"L<\", labels?.[index][i] ?? item + \"<\");\n });\n html += after;\n nav.innerHTML = \"\";\n nav.insertAdjacentHTML(\"afterbegin\", html);\n lastWidth = widths[index];\n if (keynavArgs && storageSupport) { void augmentKeynav(nav, keynavArgs) }\n })();\n if (nav.classList.contains(pagy + \"-rjs\")) { rjsObserver.observe(parent) }\n };\n\n // Init the input_nav_js helpers\n const initInputNavJs = async (nav:HTMLElement, [url_token, pageToken, keynavArgs]:InputNavJsArgs) => {\n const augment = keynavArgs && storageSupport\n ? await augmentKeynav(nav, keynavArgs)\n : (page: string) => page;\n initInput(nav, inputValue => url_token.replace(pageToken, augment(inputValue)));\n };\n\n // Init the limit_tag_js helper\n const initLimitTagJs = (span:HTMLSpanElement, [from, url_token, page_token, limitToken]:LimitTagJsArgs) => {\n initInput(span, inputValue => {\n // @ts-expect-error the page is a number, but the 'replace' converts it to string (shorter pagy.min.js)\n return url_token.replace(page_token, Math.max(Math.ceil(from / parseInt(inputValue)), 1))\n .replace(limitToken, inputValue);\n });\n };\n\n // Init the input element\n const initInput = (element:HTMLElement, getUrl:(v:string) => string) => {\n const input = <HTMLInputElement>element.querySelector(\"input\"),\n link = <HTMLAnchorElement>element.querySelector(\"a\"),\n initial = input.value,\n action = () => {\n if (input.value === initial) { return } // not changed\n const [min, val, max] = [input.min, input.value, input.max].map(n => parseInt(n) || 0);\n if (val < min || val > max) { // reset invalid/out-of-range\n input.value = initial;\n input.select();\n return;\n }\n link.href = getUrl(input.value);\n link.click();\n };\n input.addEventListener(\"focus\", () => input.select());\n input.addEventListener(\"focusout\", action);\n input.addEventListener(\"keypress\", e => { if (e.key == \"Enter\") { action() } });\n };\n\n // Public interface\n return {\n version: \"43.2.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": "
|
|
8
|
+
"debugId": "9616AB509F644C8D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
data/javascripts/pagy.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
window.Pagy=(()=>{let B="sessionStorage"in window&&"BroadcastChannel"in window,L="pagy",Z,O,W;if(B)Z=sessionStorage,O=new BroadcastChannel(L),W=Date.now(),O.addEventListener("message",(q)=>{if(q.data.from){let z=Z.getItem(q.data.key);if(z)O.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==W)Z.setItem(q.data.key,q.data.str)}});let P=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),V=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),S=()=>Math.floor(Math.random()*46656).toString(36),J=async(q,[z,C,F,H,G])=>{let Q,X=document.cookie.split(/;\s+/).find((M)=>M.startsWith(L+"="))?.split("=")[1]??S();if(document.cookie=L+"="+X,z&&!(z in Z)){if(O.postMessage({from:W,key:z}),await new Promise((M)=>setTimeout(()=>M(""),100)),!(z in Z))Q=(M)=>M+"+"+H}if(!Q){if(!z)do z=S();while(z in Z);let M=Z.getItem(z),Y=M?JSON.parse(M):[void 0];if(G)Y.splice(...G),Z.setItem(z,JSON.stringify(Y));Q=($)=>{let R=parseInt($);return V(JSON.stringify([X,z,R,Y.length,Y[R-1],Y[R]]))}}let D=C?`${C}%5B${F}%5D`:F,E=new RegExp(`(?<=\\?.*)(\\b${D}=)(\\d+)`);for(let M of q.querySelectorAll("a[href]"))M.href=M.href.replace(E,(Y,$,R)=>`${$}${Q(R)}`);return Q},x=(q,[[z,C,F,H,G],Q,[X,D,E],M])=>{let Y=q.parentElement,$=-1;if((q.render=()=>{let R=X.findIndex((j)=>j<Y.clientWidth);if(X[R]===$)return;let U=z;if(D[R].forEach((j,I)=>{U+=j=="gap"?H:(typeof j=="number"?C.replace(Q,j):F).replace("L<",E?.[R][I]??j+"<")}),U+=G,q.innerHTML="",q.insertAdjacentHTML("afterbegin",U),$=X[R],M&&B)J(q,M)})(),q.classList.contains(L+"-rjs"))P.observe(Y)},T=async(q,[z,C,F])=>{let H=F&&B?await J(q,F):(G)=>G;N(q,(G)=>z.replace(C,H(G)))},A=(q,[z,C,F,H])=>{N(q,(G)=>{return C.replace(F,Math.max(Math.ceil(z/parseInt(G)),1)).replace(H,G)})},N=(q,z)=>{let C=q.querySelector("input"),F=q.querySelector("a"),H=C.value,G=()=>{if(C.value===H)return;let[Q,X,D]=[C.min,C.value,C.max].map((E)=>parseInt(E)||0);if(X<Q||X>D){C.value=H,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",G),C.addEventListener("keypress",(Q)=>{if(Q.key=="Enter")G()})};return{version:"43.2.
|
|
1
|
+
window.Pagy=(()=>{let B="sessionStorage"in window&&"BroadcastChannel"in window,L="pagy",Z,O,W;if(B)Z=sessionStorage,O=new BroadcastChannel(L),W=Date.now(),O.addEventListener("message",(q)=>{if(q.data.from){let z=Z.getItem(q.data.key);if(z)O.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==W)Z.setItem(q.data.key,q.data.str)}});let P=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),V=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),S=()=>Math.floor(Math.random()*46656).toString(36),J=async(q,[z,C,F,H,G])=>{let Q,X=document.cookie.split(/;\s+/).find((M)=>M.startsWith(L+"="))?.split("=")[1]??S();if(document.cookie=L+"="+X,z&&!(z in Z)){if(O.postMessage({from:W,key:z}),await new Promise((M)=>setTimeout(()=>M(""),100)),!(z in Z))Q=(M)=>M+"+"+H}if(!Q){if(!z)do z=S();while(z in Z);let M=Z.getItem(z),Y=M?JSON.parse(M):[void 0];if(G)Y.splice(...G),Z.setItem(z,JSON.stringify(Y));Q=($)=>{let R=parseInt($);return V(JSON.stringify([X,z,R,Y.length,Y[R-1],Y[R]]))}}let D=C?`${C}%5B${F}%5D`:F,E=new RegExp(`(?<=\\?.*)(\\b${D}=)(\\d+)`);for(let M of q.querySelectorAll("a[href]"))M.href=M.href.replace(E,(Y,$,R)=>`${$}${Q(R)}`);return Q},x=(q,[[z,C,F,H,G],Q,[X,D,E],M])=>{let Y=q.parentElement,$=-1;if((q.render=()=>{let R=X.findIndex((j)=>j<Y.clientWidth);if(X[R]===$)return;let U=z;if(D[R].forEach((j,I)=>{U+=j=="gap"?H:(typeof j=="number"?C.replace(Q,j):F).replace("L<",E?.[R][I]??j+"<")}),U+=G,q.innerHTML="",q.insertAdjacentHTML("afterbegin",U),$=X[R],M&&B)J(q,M)})(),q.classList.contains(L+"-rjs"))P.observe(Y)},T=async(q,[z,C,F])=>{let H=F&&B?await J(q,F):(G)=>G;N(q,(G)=>z.replace(C,H(G)))},A=(q,[z,C,F,H])=>{N(q,(G)=>{return C.replace(F,Math.max(Math.ceil(z/parseInt(G)),1)).replace(H,G)})},N=(q,z)=>{let C=q.querySelector("input"),F=q.querySelector("a"),H=C.value,G=()=>{if(C.value===H)return;let[Q,X,D]=[C.min,C.value,C.max].map((E)=>parseInt(E)||0);if(X<Q||X>D){C.value=H,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",G),C.addEventListener("keypress",(Q)=>{if(Q.key=="Enter")G()})};return{version:"43.2.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.
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 =
|
|
67
|
-
@units = Calendar::UNITS &
|
|
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
|
-
|
|
74
|
-
|
|
73
|
+
|
|
74
|
+
calendar = {}
|
|
75
|
+
unit_object = nil
|
|
76
|
+
|
|
75
77
|
@units.each_with_index do |unit, index|
|
|
76
|
-
params_to_delete
|
|
77
|
-
conf[unit]
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
85
|
+
unit_conf[:counts] = yield(unit, unit_conf[:period]) if block_given?
|
|
83
86
|
# :nocov:
|
|
84
|
-
calendar[unit] =
|
|
87
|
+
calendar[unit] = unit_object = create(unit, **unit_conf)
|
|
85
88
|
end
|
|
86
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
|
@@ -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)
|
|
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)
|
|
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,
|
|
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 {
|
|
30
|
+
return allocate.tap { _1.send(:initialize, set, **) }
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# 2. Handle Factory usage (Pagy::Keyset.new)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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(
|
|
51
|
-
adapter_module =
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
|
data/lib/pagy/classes/request.rb
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
default = @options[:limit] || DEFAULT[:limit]
|
|
30
|
+
max_limit = @options[:client_max_limit]
|
|
31
|
+
return default unless max_limit
|
|
31
32
|
|
|
32
|
-
|
|
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)
|
|
39
|
+
def get_params(request)
|
|
40
|
+
request.GET.merge(request.POST).to_h.freeze
|
|
41
|
+
end
|
|
38
42
|
end
|
|
39
43
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
data/lib/pagy/modules/b64.rb
CHANGED
|
@@ -5,14 +5,19 @@ class Pagy
|
|
|
5
5
|
module B64
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
-
def encode(bin)
|
|
8
|
+
def encode(bin)
|
|
9
|
+
[bin].pack('m0')
|
|
10
|
+
end
|
|
9
11
|
|
|
10
|
-
def decode(str)
|
|
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
|
data/lib/pagy/modules/console.rb
CHANGED
|
@@ -9,16 +9,25 @@ class Pagy
|
|
|
9
9
|
@collection = clone
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def offset(value)
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
12
|
-
|
|
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
|
|
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(
|
|
9
|
+
def wrap(search_arguments, options)
|
|
10
10
|
options[:page] ||= options[:request].resolve_page
|
|
11
11
|
options[:limit] = options[:request].resolve_limit
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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:
|
|
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
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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) #
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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]
|
|
16
|
-
search_options[:page]
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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.
|
|
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 ')
|