pagy 43.2.6 → 43.2.7
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+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/keyset/keyset.rb +17 -9
- data/lib/pagy/modules/b64.rb +1 -1
- data/lib/pagy/modules/i18n/p11n/arabic.rb +1 -0
- data/lib/pagy/modules/i18n/p11n/east_slavic.rb +1 -0
- data/lib/pagy/modules/i18n/p11n/polish.rb +1 -0
- data/lib/pagy/toolbox/helpers/anchor_tags.rb +11 -15
- data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +3 -0
- data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +3 -1
- data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +2 -0
- data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +3 -0
- data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +3 -1
- data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +2 -0
- data/lib/pagy/toolbox/helpers/data_hash.rb +3 -2
- data/lib/pagy/toolbox/helpers/input_nav_js.rb +7 -5
- data/lib/pagy/toolbox/helpers/limit_tag_js.rb +2 -1
- data/lib/pagy/toolbox/helpers/series_nav.rb +3 -3
- data/lib/pagy/toolbox/helpers/support/a_lambda.rb +6 -4
- data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +6 -1
- data/lib/pagy/toolbox/helpers/support/nav_aria_label_attribute.rb +1 -2
- data/lib/pagy/toolbox/helpers/support/series.rb +1 -2
- data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +1 -0
- data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +7 -2
- data/lib/pagy/toolbox/paginators/calendar.rb +4 -2
- data/lib/pagy/toolbox/paginators/countish.rb +4 -5
- data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +1 -0
- data/lib/pagy/toolbox/paginators/keyset.rb +1 -1
- data/lib/pagy/toolbox/paginators/meilisearch.rb +1 -0
- data/lib/pagy/toolbox/paginators/searchkick.rb +3 -2
- data/lib/pagy.rb +12 -4
- 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: 17c8ff1175a4401c1b0491a11369da541e0d55a6cb5590af9840a815c5453358
|
|
4
|
+
data.tar.gz: c04ad1216d0b0672100c7b7b04e839b74aca129c3cc7044a6d10a781e3d31d00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8f79a780b5f763eef0829c60b584f91a22757cc714a83f71f69215d654b2de9f6beaa76486ce58b1fa75f8a542fdebea51fcaac3ddac970e9f791a7fca5a6511
|
|
7
|
+
data.tar.gz: 136b87f07fd0ad161e03ae8cd689eee102a02c2296426bf7fc28c34421c27ca99927fdacab69f419f45a39e99fcc71201fb448a35336c14b652240a0fa0051c8
|
data/apps/calendar.ru
CHANGED
data/apps/demo.ru
CHANGED
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.7",
|
|
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=2F5F1987603BEC1464756E2164756E21
|
|
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.6\",\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.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"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "2F5F1987603BEC1464756E2164756E21",
|
|
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.7",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.7",
|
|
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) {
|
|
@@ -55,6 +55,8 @@ class Pagy
|
|
|
55
55
|
@keyset = @options[:keyset] || extract_keyset
|
|
56
56
|
raise InternalError, 'the set must be ordered' if @keyset.empty?
|
|
57
57
|
|
|
58
|
+
@identifiers = quoted_identifiers(@set.model.table_name)
|
|
59
|
+
|
|
58
60
|
assign_page
|
|
59
61
|
self.next
|
|
60
62
|
end
|
|
@@ -109,22 +111,28 @@ class Pagy
|
|
|
109
111
|
def compose_predicate(prefix = nil)
|
|
110
112
|
operator = { asc: '>', desc: '<' }
|
|
111
113
|
directions = @keyset.values
|
|
112
|
-
identifier =
|
|
114
|
+
identifier = @identifiers
|
|
113
115
|
placeholder = @keyset.to_h { |column| [column, ":#{prefix}#{column}"] }
|
|
116
|
+
|
|
114
117
|
if @options[:tuple_comparison] && (directions.all?(:asc) || directions.all?(:desc))
|
|
115
118
|
"(#{identifier.values.join(', ')}) #{operator[directions.first]} (#{placeholder.values.join(', ')})"
|
|
116
119
|
else
|
|
117
120
|
keyset = @keyset.to_a
|
|
118
|
-
|
|
121
|
+
ors = []
|
|
122
|
+
|
|
119
123
|
until keyset.empty?
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<< "#{
|
|
124
|
-
intersection << ')'
|
|
125
|
-
union << intersection
|
|
124
|
+
column, direction = keyset.pop
|
|
125
|
+
ands = keyset.map { |k, _| "#{identifier[k]} = #{placeholder[k]}" }
|
|
126
|
+
ands << "#{identifier[column]} #{operator[direction]} #{placeholder[column]}"
|
|
127
|
+
ors << "(#{ands.join(' AND ')})"
|
|
126
128
|
end
|
|
127
|
-
|
|
129
|
+
query = ors.join(' OR ')
|
|
130
|
+
return query unless @keyset.size > 1
|
|
131
|
+
|
|
132
|
+
# Add hint predicate for DB optimizers that struggle with ORs
|
|
133
|
+
column, direction = @keyset.first
|
|
134
|
+
hint = "#{identifier[column]} #{operator[direction]}= #{placeholder[column]}"
|
|
135
|
+
"#{hint} AND (#{query})"
|
|
128
136
|
end
|
|
129
137
|
end
|
|
130
138
|
|
data/lib/pagy/modules/b64.rb
CHANGED
|
@@ -4,22 +4,18 @@ require_relative 'support/a_lambda' # inheritable
|
|
|
4
4
|
|
|
5
5
|
class Pagy
|
|
6
6
|
# Return the enabled/disabled previous page anchor tag
|
|
7
|
-
def previous_tag(
|
|
8
|
-
aria_label: I18n.translate('pagy.aria_label.previous'), **)
|
|
9
|
-
if @previous
|
|
10
|
-
(a || a_lambda(**)).(@previous, text, aria_label:)
|
|
11
|
-
else
|
|
12
|
-
%(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
7
|
+
def previous_tag(...) = anchor_tag_for(:previous, ...)
|
|
15
8
|
|
|
16
9
|
# Return the enabled/disabled next page anchor tag
|
|
17
|
-
def next_tag(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
def next_tag(...) = anchor_tag_for(:next, ...)
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def anchor_tag_for(which, a = nil, text: I18n.translate("pagy.#{which}"),
|
|
15
|
+
aria_label: I18n.translate("pagy.aria_label.#{which}"), **)
|
|
16
|
+
page = send(which)
|
|
17
|
+
return (a || a_lambda(**)).(page.to_i, text, aria_label:) if page
|
|
18
|
+
|
|
19
|
+
%(<a role="link" aria-disabled="true" aria-label="#{aria_label}">#{text}</a>)
|
|
24
20
|
end
|
|
25
21
|
end
|
|
@@ -9,9 +9,11 @@ class Pagy
|
|
|
9
9
|
# Javascript combo pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
|
|
10
10
|
def bootstrap_input_nav_js(classes: 'pagination', **)
|
|
11
11
|
a_lambda = a_lambda(**)
|
|
12
|
+
|
|
12
13
|
input = %(<input name="page" type="number" min="1" max="#{last}" value="#{@page}" aria-current="page" ) +
|
|
13
14
|
%(style="text-align: center; width: #{@page.to_s.length + 1}rem; padding: 0; border-radius: .25rem; ) +
|
|
14
15
|
%(border: none; display: inline-block;" class="page-link active">#{A_TAG})
|
|
16
|
+
|
|
15
17
|
html = %(<ul class="#{classes}">#{
|
|
16
18
|
bootstrap_html_for(:previous, a_lambda)
|
|
17
19
|
}<li class="page-item"><label class="page-link">#{
|
|
@@ -19,6 +21,7 @@ class Pagy
|
|
|
19
21
|
}</label></li>#{
|
|
20
22
|
bootstrap_html_for(:next, a_lambda)
|
|
21
23
|
}</ul>)
|
|
24
|
+
|
|
22
25
|
wrap_input_nav_js(html, 'pagy-bootstrap input-nav-js', **)
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -9,7 +9,8 @@ class Pagy
|
|
|
9
9
|
# Pagination for bootstrap: it returns the html with the series of links to the pages
|
|
10
10
|
def bootstrap_series_nav(classes: 'pagination', **)
|
|
11
11
|
a_lambda = a_lambda(**)
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
html = %(<ul class="#{classes}">#{bootstrap_html_for(:previous, a_lambda)})
|
|
13
14
|
series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
|
|
14
15
|
html << case item
|
|
15
16
|
when Integer
|
|
@@ -24,6 +25,7 @@ class Pagy
|
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
html << %(#{bootstrap_html_for(:next, a_lambda)}</ul>)
|
|
28
|
+
|
|
27
29
|
wrap_series_nav(html, 'pagy-bootstrap series-nav', **)
|
|
28
30
|
end
|
|
29
31
|
end
|
|
@@ -9,6 +9,7 @@ class Pagy
|
|
|
9
9
|
# Javascript pagination for bootstrap: it returns a nav with a data-pagy attribute used by the pagy.js file
|
|
10
10
|
def bootstrap_series_nav_js(classes: 'pagination', **)
|
|
11
11
|
a_lambda = a_lambda(**)
|
|
12
|
+
|
|
12
13
|
tokens = { before: %(<ul class="#{classes}">#{bootstrap_html_for(:previous, a_lambda)}),
|
|
13
14
|
anchor: %(<li class="page-item">#{a_lambda.(PAGE_TOKEN, LABEL_TOKEN, classes: 'page-link')}</li>),
|
|
14
15
|
current: %(<li class="page-item active"><a role="link" class="page-link" ) +
|
|
@@ -16,6 +17,7 @@ class Pagy
|
|
|
16
17
|
gap: %(<li class="page-item gap disabled"><a role="link" class="page-link" aria-disabled="true">#{
|
|
17
18
|
I18n.translate('pagy.gap')}</a></li>),
|
|
18
19
|
after: %(#{bootstrap_html_for(:next, a_lambda)}</ul>) }
|
|
20
|
+
|
|
19
21
|
wrap_series_nav_js(tokens, 'pagy-bootstrap series-nav-js', **)
|
|
20
22
|
end
|
|
21
23
|
end
|
|
@@ -9,13 +9,16 @@ class Pagy
|
|
|
9
9
|
# Javascript combo pagination for bulma: it returns a nav with a data-pagy attribute used by the pagy.js file
|
|
10
10
|
def bulma_input_nav_js(classes: 'pagination', **)
|
|
11
11
|
a_lambda = a_lambda(**)
|
|
12
|
+
|
|
12
13
|
input = %(<input name="page" type="number" min="1" max="#{@last}" value="#{@page}" aria-current="page") +
|
|
13
14
|
%(style="text-align: center; width: #{@page.to_s.length + 1}rem; line-height: 1.2rem; ) +
|
|
14
15
|
%(border: none; border-radius: .25rem; padding: .0625rem; color: white; ) +
|
|
15
16
|
%(background-color: #485fc7;">#{A_TAG})
|
|
17
|
+
|
|
16
18
|
html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)}<li class="pagination-link"><label>#{
|
|
17
19
|
I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)
|
|
18
20
|
}</label></li>#{bulma_html_for(:next, a_lambda)}</ul>)
|
|
21
|
+
|
|
19
22
|
wrap_input_nav_js(html, "pagy-bulma input-nav-js #{classes}", **)
|
|
20
23
|
end
|
|
21
24
|
end
|
|
@@ -9,7 +9,8 @@ class Pagy
|
|
|
9
9
|
# Pagination for bulma: it returns the html with the series of links to the pages
|
|
10
10
|
def bulma_series_nav(classes: 'pagination', **)
|
|
11
11
|
a_lambda = a_lambda(**)
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
html = %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)})
|
|
13
14
|
series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
|
|
14
15
|
html << case item
|
|
15
16
|
when Integer
|
|
@@ -23,6 +24,7 @@ class Pagy
|
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
html << %(#{bulma_html_for(:next, a_lambda)}</ul>)
|
|
27
|
+
|
|
26
28
|
wrap_series_nav(html, "pagy-bulma series-nav #{classes}", **)
|
|
27
29
|
end
|
|
28
30
|
end
|
|
@@ -9,12 +9,14 @@ class Pagy
|
|
|
9
9
|
# Javascript pagination for bulma: it returns a nav with a data-pagy attribute used by the Pagy.nav javascript
|
|
10
10
|
def bulma_series_nav_js(classes: 'pagination', **)
|
|
11
11
|
a_lambda = a_lambda(**)
|
|
12
|
+
|
|
12
13
|
tokens = { before: %(<ul class="pagination-list">#{bulma_html_for(:previous, a_lambda)}),
|
|
13
14
|
anchor: %(<li>#{a_lambda.(PAGE_TOKEN, LABEL_TOKEN, classes: 'pagination-link')}</li>),
|
|
14
15
|
current: %(<li><a role="link" class="pagination-link is-current" ) +
|
|
15
16
|
%(aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</a></li>),
|
|
16
17
|
gap: %(<li><span class="pagination-ellipsis">#{I18n.translate('pagy.gap')}</span></li>),
|
|
17
18
|
after: %(#{bulma_html_for(:next, a_lambda)}</ul>) }
|
|
19
|
+
|
|
18
20
|
wrap_series_nav_js(tokens, "pagy-bulma series-nav-js #{classes}", **)
|
|
19
21
|
end
|
|
20
22
|
end
|
|
@@ -6,8 +6,9 @@ class Pagy
|
|
|
6
6
|
|
|
7
7
|
# Generate a hash of the wanted internal data
|
|
8
8
|
def data_hash(data_keys: @options[:data_keys] || DEFAULT_DATA_KEYS, **)
|
|
9
|
-
template
|
|
10
|
-
to_url
|
|
9
|
+
template = compose_page_url(PAGE_TOKEN, **)
|
|
10
|
+
to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) if page }
|
|
11
|
+
|
|
11
12
|
data_keys -= %i[count limit] if calendar?
|
|
12
13
|
|
|
13
14
|
data_keys.each_with_object({}) do |key, data|
|
|
@@ -8,11 +8,13 @@ class Pagy
|
|
|
8
8
|
return send(:"#{style}_input_nav_js", **) if style
|
|
9
9
|
|
|
10
10
|
a_lambda = a_lambda(**)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
|
|
12
|
+
input = %(<input name="page" type="number" min="1" max="#{@last}" value="#{@page}" aria-current="page" ) +
|
|
13
|
+
%(style="text-align: center; width: #{@page.to_s.length + 1}rem; padding: 0;">#{A_TAG})
|
|
14
|
+
|
|
15
|
+
html = %(#{previous_tag(a_lambda)}<label>#{
|
|
16
|
+
I18n.translate('pagy.input_nav_js', page_input: input, pages: @last)}</label>#{
|
|
17
|
+
next_tag(a_lambda)})
|
|
16
18
|
|
|
17
19
|
wrap_input_nav_js(html, 'pagy input-nav-js', **)
|
|
18
20
|
end
|
|
@@ -9,7 +9,8 @@ class Pagy
|
|
|
9
9
|
|
|
10
10
|
limit_input = %(<input name="limit" type="number" min="1" max="#{client_max_limit}" value="#{
|
|
11
11
|
@limit}" style="padding: 0; text-align: center; width: #{@limit.to_s.length + 1}rem;">#{A_TAG})
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
url_token = compose_page_url(PAGE_TOKEN, limit: LIMIT_TOKEN)
|
|
13
14
|
|
|
14
15
|
%(<span#{%( id="#{id}") if id} class="pagy limit-tag-js" #{
|
|
15
16
|
data_pagy_attribute(:ltj, @from, url_token, PAGE_TOKEN, LIMIT_TOKEN)
|
|
@@ -8,9 +8,9 @@ class Pagy
|
|
|
8
8
|
return send(:"#{style}_series_nav", **) if style
|
|
9
9
|
|
|
10
10
|
a_lambda = a_lambda(**)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
|
|
12
|
+
html = previous_tag(a_lambda)
|
|
13
|
+
series(**).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
|
|
14
14
|
html << case item
|
|
15
15
|
when Integer
|
|
16
16
|
a_lambda.(item)
|
|
@@ -24,10 +24,12 @@ class Pagy
|
|
|
24
24
|
info_key = count.zero? ? 'pagy.info_tag.no_items' : 'pagy.info_tag.single_page'
|
|
25
25
|
%( title="#{I18n.translate(info_key, item_name: I18n.translate('pagy.item_name', count:), count:)}")
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
|
|
28
|
+
rel = case page
|
|
29
|
+
when @previous then %( rel="prev")
|
|
30
|
+
when @next then %( rel="next")
|
|
31
|
+
end
|
|
32
|
+
|
|
31
33
|
%(#{left}#{page}#{right}#{title}#{
|
|
32
34
|
%( class="#{classes}") if classes}#{rel}#{%( aria-label="#{aria_label}") if aria_label}>#{text}</a>)
|
|
33
35
|
end
|
|
@@ -9,7 +9,12 @@ class Pagy
|
|
|
9
9
|
|
|
10
10
|
# Compose the data-pagy attribute, with the base64 encoded JSON-serialized args. Use the faster oj gem if defined.
|
|
11
11
|
def data_pagy_attribute(*args)
|
|
12
|
-
data = defined?(Oj)
|
|
12
|
+
data = if defined?(Oj)
|
|
13
|
+
Oj.dump(args, mode: :compat)
|
|
14
|
+
else
|
|
15
|
+
JSON.dump(args)
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
%(data-pagy="#{B64.encode(data)}")
|
|
14
19
|
end
|
|
15
20
|
end
|
|
@@ -5,8 +5,7 @@ class Pagy
|
|
|
5
5
|
private
|
|
6
6
|
|
|
7
7
|
# Compose the aria label attribute for the nav
|
|
8
|
-
def nav_aria_label_attribute(aria_label:
|
|
9
|
-
aria_label ||= I18n.translate('pagy.aria_label.nav', count: @last)
|
|
8
|
+
def nav_aria_label_attribute(aria_label: I18n.translate('pagy.aria_label.nav', count: @last))
|
|
10
9
|
%(aria-label="#{aria_label}")
|
|
11
10
|
end
|
|
12
11
|
end
|
|
@@ -7,8 +7,7 @@ class Pagy
|
|
|
7
7
|
|
|
8
8
|
# Return the array of page numbers and :gap e.g. [1, :gap, 8, "9", 10, :gap, 36]
|
|
9
9
|
def series(slots: @options[:slots] || SERIES_SLOTS, compact: @options[:compact], **)
|
|
10
|
-
raise OptionError.new(self, :slots, 'to be an Integer >= 0', slots)
|
|
11
|
-
unless slots.is_a?(Integer) && slots >= 0
|
|
10
|
+
raise OptionError.new(self, :slots, 'to be an Integer >= 0', slots) unless slots.is_a?(Integer) && slots >= 0
|
|
12
11
|
return [] if slots.zero?
|
|
13
12
|
|
|
14
13
|
[].tap do |series|
|
|
@@ -12,6 +12,7 @@ class Pagy
|
|
|
12
12
|
# Build the nav tag, with the specific inner html for the style
|
|
13
13
|
def wrap_series_nav(html, nav_classes, id: nil, aria_label: nil, **)
|
|
14
14
|
data = %( #{data_pagy_attribute(:k, @update)}) if keynav?
|
|
15
|
+
|
|
15
16
|
%(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{nav_aria_label_attribute(aria_label:)}#{data}>#{html}</nav>)
|
|
16
17
|
end
|
|
17
18
|
end
|
|
@@ -20,13 +20,18 @@ class Pagy
|
|
|
20
20
|
|
|
21
21
|
# Support for the Calendar API
|
|
22
22
|
def page_labels(series)
|
|
23
|
-
|
|
23
|
+
return unless calendar?
|
|
24
|
+
|
|
25
|
+
series.map do |s|
|
|
26
|
+
s.map { _1 == :gap ? :gap : page_label(_1) }
|
|
27
|
+
end
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
# Build the nav_js tag, with the specific tokens for the style
|
|
27
31
|
def wrap_series_nav_js(tokens, nav_classes, id: nil, aria_label: nil, **)
|
|
28
|
-
sequels
|
|
32
|
+
sequels = sequels(**)
|
|
29
33
|
nav_classes = "pagy-rjs #{nav_classes}" if sequels[0].size > 1
|
|
34
|
+
|
|
30
35
|
%(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
|
|
31
36
|
nav_aria_label_attribute(aria_label:)} #{
|
|
32
37
|
data = [:snj, tokens.values, PAGE_TOKEN, sequels]
|
|
@@ -8,8 +8,10 @@ class Pagy
|
|
|
8
8
|
def paginate(context, collection, config)
|
|
9
9
|
context.instance_eval do
|
|
10
10
|
allowed_options = Calendar::UNITS + %i[offset disabled request]
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
unless config.is_a?(Hash) && (config.keys - allowed_options).empty?
|
|
13
|
+
raise ArgumentError, "keys must be in #{allowed_options.inspect}"
|
|
14
|
+
end
|
|
13
15
|
|
|
14
16
|
config[:offset] ||= {}
|
|
15
17
|
|
|
@@ -15,8 +15,8 @@ class Pagy
|
|
|
15
15
|
options[:page] = page
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
setup_options(count, epoch, collection, options)
|
|
19
18
|
options[:limit] = options[:request].resolve_limit
|
|
19
|
+
setup_options(count, epoch, collection, options)
|
|
20
20
|
|
|
21
21
|
pagy = Offset::Countish.new(**options)
|
|
22
22
|
[pagy, pagy.records(collection)]
|
|
@@ -24,13 +24,12 @@ class Pagy
|
|
|
24
24
|
|
|
25
25
|
# Get the count from the page and set epoch when ttl (Time To Live) requires it
|
|
26
26
|
def setup_options(count, epoch, collection, options)
|
|
27
|
-
now
|
|
27
|
+
now = Time.now.to_i
|
|
28
|
+
ongoing = !options[:ttl] || (epoch && epoch <= now && now < (epoch + options[:ttl]))
|
|
28
29
|
|
|
29
|
-
if !options[:count] && count &&
|
|
30
|
-
(epoch && epoch <= now && now < (epoch + options[:ttl]))) # ongoing
|
|
30
|
+
if !options[:count] && count && ongoing
|
|
31
31
|
options[:count] = count
|
|
32
32
|
options[:epoch] = epoch if options[:ttl]
|
|
33
|
-
|
|
34
33
|
else # recount
|
|
35
34
|
options[:count] ||= Countable.get_count(collection, options)
|
|
36
35
|
options[:epoch] = now if options[:ttl]
|
|
@@ -8,8 +8,8 @@ class Pagy
|
|
|
8
8
|
def paginate(set, options)
|
|
9
9
|
options[:page] ||= options[:request].resolve_page(force_integer: false) # allow nil
|
|
10
10
|
options[:limit] = options[:request].resolve_limit
|
|
11
|
-
pagy = Keyset.new(set, **options)
|
|
12
11
|
|
|
12
|
+
pagy = Keyset.new(set, **options)
|
|
13
13
|
[pagy, pagy.records]
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -12,8 +12,9 @@ class Pagy
|
|
|
12
12
|
|
|
13
13
|
Searcher.wrap(search, options) do
|
|
14
14
|
model, term, search_options, block = search
|
|
15
|
-
|
|
16
|
-
search_options[:
|
|
15
|
+
|
|
16
|
+
search_options[:per_page] = options[:limit]
|
|
17
|
+
search_options[:page] = options[:page]
|
|
17
18
|
|
|
18
19
|
method = options[:search_method] || Searchkick::DEFAULT[:search_method]
|
|
19
20
|
results = model.send(method, term || '*', **search_options, &block)
|
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.7'
|
|
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 ')
|
|
@@ -49,8 +49,13 @@ class Pagy
|
|
|
49
49
|
# Validates and assign the passed options: they must be present and value.to_i must be >= min
|
|
50
50
|
def assign_and_check(name_min)
|
|
51
51
|
name_min.each do |name, min|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
value = @options[name]
|
|
53
|
+
|
|
54
|
+
if value.respond_to?(:to_i) && (integer = value.to_i) >= min
|
|
55
|
+
instance_variable_set(:"@#{name}", integer)
|
|
56
|
+
else
|
|
57
|
+
raise OptionError.new(self, name, ">= #{min}", value)
|
|
58
|
+
end
|
|
54
59
|
end
|
|
55
60
|
end
|
|
56
61
|
|
|
@@ -59,10 +64,13 @@ class Pagy
|
|
|
59
64
|
@request = options.delete(:request) # internal object
|
|
60
65
|
default = {}
|
|
61
66
|
current = self.class
|
|
67
|
+
|
|
62
68
|
begin
|
|
63
69
|
default = current::DEFAULT.merge(default)
|
|
64
70
|
current = current.superclass
|
|
65
71
|
end until current == Object # rubocop:disable Lint/Loop -- see https://github.com/rubocop/rubocop-performance/issues/362
|
|
66
|
-
|
|
72
|
+
|
|
73
|
+
clean_options = options.delete_if { |k, v| default.key?(k) && (v.nil? || v == '') }
|
|
74
|
+
@options = default.merge!(clean_options).freeze
|
|
67
75
|
end
|
|
68
76
|
end
|