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 +4 -4
- data/apps/calendar.ru +1 -1
- data/apps/demo.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/bin/pagy +1 -1
- data/config/pagy.rb +1 -1
- data/javascripts/ai_widget.js +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/exceptions.rb +0 -8
- data/lib/pagy/classes/request.rb +5 -8
- data/lib/pagy/modules/abilities/linkable.rb +4 -4
- data/lib/pagy/modules/console.rb +2 -16
- data/lib/pagy/modules/searcher.rb +5 -7
- data/lib/pagy/toolbox/paginators/calendar.rb +0 -1
- data/lib/pagy/toolbox/paginators/countless.rb +11 -14
- data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +16 -19
- data/lib/pagy/toolbox/paginators/keynav_js.rb +13 -16
- data/lib/pagy/toolbox/paginators/keyset.rb +6 -9
- data/lib/pagy/toolbox/paginators/meilisearch.rb +16 -19
- data/lib/pagy/toolbox/paginators/method.rb +9 -3
- data/lib/pagy/toolbox/paginators/offset.rb +7 -10
- data/lib/pagy/toolbox/paginators/searchkick.rb +16 -19
- data/lib/pagy.rb +1 -1
- data/locales/id.yml +1 -3
- data/locales/ja.yml +1 -3
- data/locales/km.yml +1 -3
- data/locales/sw.yml +2 -2
- 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: 81897225205a29f9ae8e98c57cddcc7c92a6bdbc3ce1cdd466f68ade1cf80785
|
|
4
|
+
data.tar.gz: e801dafc6c5acd601da4e88d68f21df621f6a0f01af19edbfa1df4d43d55fd54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74cab9eeb2eb7dd535daff6523c9ee9190856dee5be12bd96e274748897d39ccca6a9eea6cc9fd5394fbd164c4bfb48a499dadf28ff1fe8fb625113b239806b3
|
|
7
|
+
data.tar.gz: 06eb01e238504df2242a2c9a4b6855eab9bcf349ecd5a30487f89ce4491abfde2a06dcf4b453b745be8d5560b11eed5744426d22e91578d9ebe03c6517ed2531
|
data/apps/calendar.ru
CHANGED
data/apps/demo.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/bin/pagy
CHANGED
data/config/pagy.rb
CHANGED
data/javascripts/ai_widget.js
CHANGED
|
@@ -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 = '
|
|
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
|
|
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=
|
|
150
|
+
//# debugId=7F9EF7EF7A33670664756E2164756E21
|
|
151
151
|
//# 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 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": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "7F9EF7EF7A33670664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
data/javascripts/pagy.min.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
data/lib/pagy/classes/request.rb
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Pagy
|
|
4
|
-
# Decouple the
|
|
5
|
-
# Resolve :page and :limit
|
|
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
|
|
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 = @
|
|
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 = @
|
|
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
|
-
|
|
43
|
+
root_key, page_key, limit_key, limit, client_max_limit, querify, absolute, path, fragment =
|
|
44
44
|
@options.merge(options)
|
|
45
|
-
.values_at(:
|
|
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(
|
|
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!(
|
|
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?
|
data/lib/pagy/modules/console.rb
CHANGED
|
@@ -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
|
|
35
|
-
def
|
|
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(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
8
|
-
def paginate(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if
|
|
12
|
-
|
|
13
|
-
if
|
|
14
|
-
|
|
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(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
8
|
-
def paginate(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
8
|
-
def paginate(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
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: "<"
|
data/locales/ja.yml
CHANGED
|
@@ -4,9 +4,7 @@ ja:
|
|
|
4
4
|
pagy:
|
|
5
5
|
p11n: 'Other'
|
|
6
6
|
aria_label:
|
|
7
|
-
|
|
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: "<"
|
data/locales/km.yml
CHANGED
|
@@ -4,9 +4,7 @@ km:
|
|
|
4
4
|
pagy:
|
|
5
5
|
p11n: 'Other'
|
|
6
6
|
aria_label:
|
|
7
|
-
|
|
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: "<"
|
data/locales/sw.yml
CHANGED