pagy 43.0.0 → 43.3.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/LICENSE.txt +1 -1
- data/apps/calendar.ru +11 -12
- data/apps/demo.ru +5 -5
- data/apps/enable_rails_page_segment.rb +54 -0
- data/apps/index.rb +1 -1
- data/apps/keynav+root_key.ru +316 -0
- data/apps/keynav.ru +10 -13
- data/apps/keyset.ru +5 -11
- data/apps/keyset_sequel.ru +10 -12
- data/apps/rails.ru +8 -12
- data/apps/repro.ru +11 -11
- data/bin/pagy +2 -94
- data/config/pagy.rb +8 -7
- data/javascripts/ai_widget.js +65 -51
- data/javascripts/pagy.js +20 -17
- data/javascripts/pagy.js.map +3 -3
- data/javascripts/pagy.min.js +2 -1
- data/javascripts/pagy.mjs +19 -16
- data/javascripts/wand.js +15 -9
- data/lib/pagy/classes/calendar/calendar.rb +36 -31
- data/lib/pagy/classes/calendar/day.rb +1 -1
- data/lib/pagy/classes/calendar/month.rb +1 -1
- data/lib/pagy/classes/calendar/quarter.rb +1 -1
- data/lib/pagy/classes/calendar/unit.rb +12 -13
- data/lib/pagy/classes/calendar/year.rb +1 -1
- data/lib/pagy/classes/exceptions.rb +1 -8
- data/lib/pagy/classes/keyset/adapters/active_record.rb +3 -1
- data/lib/pagy/classes/keyset/adapters/sequel.rb +3 -1
- data/lib/pagy/classes/keyset/keynav.rb +9 -4
- data/lib/pagy/classes/keyset/keyset.rb +57 -32
- data/lib/pagy/classes/offset/countish.rb +17 -0
- data/lib/pagy/classes/offset/countless.rb +26 -14
- data/lib/pagy/classes/offset/offset.rb +8 -2
- data/lib/pagy/classes/offset/search.rb +6 -10
- data/lib/pagy/classes/request.rb +29 -20
- data/lib/pagy/cli.rb +135 -0
- data/lib/pagy/console.rb +6 -0
- data/lib/pagy/modules/abilities/configurable.rb +2 -2
- data/lib/pagy/modules/abilities/countable.rb +24 -0
- data/lib/pagy/modules/abilities/linkable.rb +35 -24
- data/lib/pagy/modules/abilities/rangeable.rb +3 -3
- data/lib/pagy/modules/b64.rb +9 -3
- data/lib/pagy/modules/console.rb +15 -20
- data/lib/pagy/modules/i18n/i18n.rb +38 -14
- 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/modules/searcher.rb +9 -8
- 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/previous_next_html.rb +1 -1
- 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 +19 -17
- data/lib/pagy/toolbox/helpers/headers_hash.rb +15 -9
- data/lib/pagy/toolbox/helpers/info_tag.rb +2 -0
- data/lib/pagy/toolbox/helpers/input_nav_js.rb +9 -6
- data/lib/pagy/toolbox/helpers/limit_tag_js.rb +4 -3
- data/lib/pagy/toolbox/helpers/loader.rb +3 -0
- data/lib/pagy/toolbox/helpers/page_url.rb +10 -16
- data/lib/pagy/toolbox/helpers/series_nav.rb +5 -4
- data/lib/pagy/toolbox/helpers/series_nav_js.rb +2 -1
- data/lib/pagy/toolbox/helpers/support/a_lambda.rb +8 -6
- data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +6 -1
- data/lib/pagy/toolbox/helpers/support/series.rb +1 -2
- data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +1 -1
- data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +2 -1
- data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +10 -4
- data/lib/pagy/toolbox/helpers/urls_hash.rb +7 -7
- data/lib/pagy/toolbox/paginators/calendar.rb +13 -9
- data/lib/pagy/toolbox/paginators/countish.rb +39 -0
- data/lib/pagy/toolbox/paginators/countless.rb +13 -15
- data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +43 -18
- data/lib/pagy/toolbox/paginators/keynav_js.rb +14 -15
- data/lib/pagy/toolbox/paginators/keyset.rb +7 -9
- data/lib/pagy/toolbox/paginators/meilisearch.rb +21 -18
- data/lib/pagy/toolbox/paginators/method.rb +15 -3
- data/lib/pagy/toolbox/paginators/offset.rb +14 -22
- data/lib/pagy/toolbox/paginators/searchkick.rb +21 -18
- data/lib/pagy/toolbox/paginators/typesense_rails.rb +35 -0
- data/lib/pagy.rb +23 -10
- data/locales/id.yml +1 -3
- data/locales/ja.yml +1 -3
- data/locales/km.yml +1 -3
- data/locales/sw.yml +2 -2
- data/locales/tr.yml +10 -8
- data/stylesheets/pagy-tailwind.css +1 -1
- data/stylesheets/pagy.css +1 -6
- metadata +25 -8
- data/lib/optimist.rb +0 -1022
- data/lib/pagy/classes/keyset/active_record.rb +0 -11
- data/lib/pagy/classes/keyset/keynav/active_record.rb +0 -13
- data/lib/pagy/classes/keyset/keynav/sequel.rb +0 -13
- data/lib/pagy/classes/keyset/sequel.rb +0 -11
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.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"
|
|
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.3.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
|
-
"mappings": ";
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "975696F2DDC5F9D164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
data/javascripts/pagy.min.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
window.Pagy=(()=>{
|
|
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.3.0",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
|
+
%s`,F,H)}}}})();
|
data/javascripts/pagy.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const Pagy = (() => {
|
|
2
|
-
const storageSupport = "sessionStorage" in window && "BroadcastChannel" in window
|
|
2
|
+
const storageSupport = "sessionStorage" in window && "BroadcastChannel" in window;
|
|
3
3
|
let pagy = "pagy", storage, sync, tabId;
|
|
4
4
|
if (storageSupport) {
|
|
5
5
|
storage = sessionStorage;
|
|
@@ -23,18 +23,18 @@ const Pagy = (() => {
|
|
|
23
23
|
}));
|
|
24
24
|
const B64SafeEncode = (unicode) => btoa(String.fromCharCode(...new TextEncoder().encode(unicode))).replace(/[+/=]/g, (m) => m == "+" ? "-" : m == "/" ? "_" : ""), B64Decode = (base64) => new TextDecoder().decode(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));
|
|
25
25
|
const randKey = () => Math.floor(Math.random() * 36 ** 3).toString(36);
|
|
26
|
-
const augmentKeynav = async (nav, [storageKey, pageKey, last, spliceArgs]) => {
|
|
27
|
-
let
|
|
26
|
+
const augmentKeynav = async (nav, [storageKey, rootKey, pageKey, last, spliceArgs]) => {
|
|
27
|
+
let augmentPage;
|
|
28
28
|
const browserKey = document.cookie.split(/;\s+/).find((row) => row.startsWith(pagy + "="))?.split("=")[1] ?? randKey();
|
|
29
29
|
document.cookie = pagy + "=" + browserKey;
|
|
30
30
|
if (storageKey && !(storageKey in storage)) {
|
|
31
31
|
sync.postMessage({ from: tabId, key: storageKey });
|
|
32
32
|
await new Promise((resolve) => setTimeout(() => resolve(""), 100));
|
|
33
33
|
if (!(storageKey in storage)) {
|
|
34
|
-
|
|
34
|
+
augmentPage = (page) => page + "+" + last;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
if (!
|
|
37
|
+
if (!augmentPage) {
|
|
38
38
|
if (!storageKey) {
|
|
39
39
|
do {
|
|
40
40
|
storageKey = randKey();
|
|
@@ -45,7 +45,7 @@ const Pagy = (() => {
|
|
|
45
45
|
cutoffs.splice(...spliceArgs);
|
|
46
46
|
storage.setItem(storageKey, JSON.stringify(cutoffs));
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
augmentPage = (page) => {
|
|
49
49
|
const pageNum = parseInt(page);
|
|
50
50
|
return B64SafeEncode(JSON.stringify([
|
|
51
51
|
browserKey,
|
|
@@ -57,14 +57,16 @@ const Pagy = (() => {
|
|
|
57
57
|
]));
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
const search = rootKey ? `${rootKey}%5B${pageKey}%5D` : pageKey;
|
|
61
|
+
const re = new RegExp(`(?<=\\?.*)(\\b${search}=)(\\d+)`);
|
|
60
62
|
for (const a of nav.querySelectorAll("a[href]")) {
|
|
61
|
-
|
|
62
|
-
a.href = url.replace(re, pageKey + "=" + augment(url.match(re)[1]));
|
|
63
|
+
a.href = a.href.replace(re, (_match, prefix, digit) => `${prefix}${augmentPage(digit)}`);
|
|
63
64
|
}
|
|
64
|
-
return
|
|
65
|
+
return augmentPage;
|
|
65
66
|
};
|
|
66
67
|
const buildNavJs = (nav, [
|
|
67
68
|
[before, anchor, current, gap, after],
|
|
69
|
+
pageToken,
|
|
68
70
|
[widths, series, labels],
|
|
69
71
|
keynavArgs
|
|
70
72
|
]) => {
|
|
@@ -77,7 +79,7 @@ const Pagy = (() => {
|
|
|
77
79
|
}
|
|
78
80
|
let html = before;
|
|
79
81
|
series[index].forEach((item, i) => {
|
|
80
|
-
html += item == "gap" ? gap : (typeof item == "number" ? anchor.replace(
|
|
82
|
+
html += item == "gap" ? gap : (typeof item == "number" ? anchor.replace(pageToken, item) : current).replace("L<", labels?.[index][i] ?? item + "<");
|
|
81
83
|
});
|
|
82
84
|
html += after;
|
|
83
85
|
nav.innerHTML = "";
|
|
@@ -91,13 +93,13 @@ const Pagy = (() => {
|
|
|
91
93
|
rjsObserver.observe(parent);
|
|
92
94
|
}
|
|
93
95
|
};
|
|
94
|
-
const initInputNavJs = async (nav, [url_token, keynavArgs]) => {
|
|
96
|
+
const initInputNavJs = async (nav, [url_token, pageToken, keynavArgs]) => {
|
|
95
97
|
const augment = keynavArgs && storageSupport ? await augmentKeynav(nav, keynavArgs) : (page) => page;
|
|
96
|
-
initInput(nav, (inputValue) => url_token.replace(
|
|
98
|
+
initInput(nav, (inputValue) => url_token.replace(pageToken, augment(inputValue)));
|
|
97
99
|
};
|
|
98
|
-
const initLimitTagJs = (span, [from, url_token]) => {
|
|
100
|
+
const initLimitTagJs = (span, [from, url_token, page_token, limitToken]) => {
|
|
99
101
|
initInput(span, (inputValue) => {
|
|
100
|
-
return url_token.replace(
|
|
102
|
+
return url_token.replace(page_token, Math.max(Math.ceil(from / parseInt(inputValue)), 1)).replace(limitToken, inputValue);
|
|
101
103
|
});
|
|
102
104
|
};
|
|
103
105
|
const initInput = (element, getUrl) => {
|
|
@@ -123,7 +125,7 @@ const Pagy = (() => {
|
|
|
123
125
|
});
|
|
124
126
|
};
|
|
125
127
|
return {
|
|
126
|
-
version: "43.
|
|
128
|
+
version: "43.3.0",
|
|
127
129
|
init(arg) {
|
|
128
130
|
const target = arg instanceof HTMLElement ? arg : document, elements = target.querySelectorAll("[data-pagy]");
|
|
129
131
|
for (const element of elements) {
|
|
@@ -139,7 +141,8 @@ const Pagy = (() => {
|
|
|
139
141
|
initLimitTagJs(element, args);
|
|
140
142
|
}
|
|
141
143
|
} catch (err) {
|
|
142
|
-
console.warn(
|
|
144
|
+
console.warn(`Pagy.init: %o
|
|
145
|
+
%s`, element, err);
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
}
|
data/javascripts/wand.js
CHANGED
|
@@ -62,7 +62,7 @@ const fontIsolation = (async () => {
|
|
|
62
62
|
let cssText = await response.text();
|
|
63
63
|
fontMappings.forEach((mapping) => {
|
|
64
64
|
const regex = new RegExp(`(font-family:\\s*['"]?)${mapping.original}(['"]?;?)`, "g");
|
|
65
|
-
cssText = cssText.replace(regex,
|
|
65
|
+
cssText = cssText.replace(regex, `$1${mapping.custom}$2`);
|
|
66
66
|
});
|
|
67
67
|
classMappings.forEach((mapping) => {
|
|
68
68
|
const regex = new RegExp(`\\.(${mapping.original})(?=[\\s\\.{,:])`, "g");
|
|
@@ -70,7 +70,9 @@ const fontIsolation = (async () => {
|
|
|
70
70
|
});
|
|
71
71
|
const fontFaceRegex = /(?:\/\*[\s\S]*?\*\/[\s\n]*)?@font-face\s*\{[^}]*}/g;
|
|
72
72
|
const fontFaceMatches = cssText.match(fontFaceRegex);
|
|
73
|
-
result.fontFaceRules = fontFaceMatches ? fontFaceMatches.join(
|
|
73
|
+
result.fontFaceRules = fontFaceMatches ? fontFaceMatches.join(`
|
|
74
|
+
|
|
75
|
+
`) : "";
|
|
74
76
|
result.componentCss = cssText.replace(fontFaceRegex, "").trim();
|
|
75
77
|
} catch (error) {
|
|
76
78
|
console.error("Failed to process Google Font CSS:", fontCssUrl, error);
|
|
@@ -79,8 +81,10 @@ const fontIsolation = (async () => {
|
|
|
79
81
|
return result;
|
|
80
82
|
}
|
|
81
83
|
const processedResults = await Promise.all(fontDefinitions.map((def) => processGoogleFontCSS(def.url, def.fontMappings, def.classMappings)));
|
|
82
|
-
const allFontFaceRules = processedResults.map((r) => r.fontFaceRules).join(
|
|
83
|
-
|
|
84
|
+
const allFontFaceRules = processedResults.map((r) => r.fontFaceRules).join(`
|
|
85
|
+
`);
|
|
86
|
+
const allComponentCss = processedResults.map((r) => r.componentCss).join(`
|
|
87
|
+
`);
|
|
84
88
|
if (allFontFaceRules) {
|
|
85
89
|
const styleEl = document.createElement("style");
|
|
86
90
|
styleEl.id = "pagy-wand-font-css";
|
|
@@ -696,11 +700,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
696
700
|
</dd>
|
|
697
701
|
</dl>
|
|
698
702
|
<h4>Customizing</h4>
|
|
699
|
-
<p
|
|
703
|
+
<p>• You can change Pagy's styling quite radically, by just setting a few CSS Custom Properties:
|
|
700
704
|
the <code><i>pagy.css</i></code> or <code><i>pagy-tailwind.css</i></code> calculates all the other metrics.</p>
|
|
701
|
-
<p
|
|
705
|
+
<p>• Pick a Presets as a starting point, customize it with the controls,
|
|
702
706
|
and copy/paste the CSS Override in your Stylesheet.</p>
|
|
703
|
-
<p
|
|
707
|
+
<p>• Add further customization to the <code>.pagy</code> CSS Override, or override the calculated properties
|
|
704
708
|
for full control over the final style.</p>
|
|
705
709
|
<p><b>Important</b>: Do not link the Pagy CSS file. Copy its customized content in your CSS,
|
|
706
710
|
to avoid unwanted cosmetic changes that could happen on update.</p>
|
|
@@ -973,10 +977,12 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
973
977
|
applyCSS(presetCSS);
|
|
974
978
|
}
|
|
975
979
|
function updateOverride() {
|
|
976
|
-
let override = `.pagy {
|
|
980
|
+
let override = `.pagy {
|
|
981
|
+
`;
|
|
977
982
|
Object.values(controls).forEach((c) => {
|
|
978
983
|
if (c.name !== "") {
|
|
979
|
-
override += ` ${c.name}: ${c.input.value}${c.unit}
|
|
984
|
+
override += ` ${c.name}: ${c.input.value}${c.unit};
|
|
985
|
+
`;
|
|
980
986
|
}
|
|
981
987
|
});
|
|
982
988
|
override += "}";
|
|
@@ -8,6 +8,7 @@ require 'active_support/core_ext/integer/time'
|
|
|
8
8
|
|
|
9
9
|
class Pagy
|
|
10
10
|
# Calendar class
|
|
11
|
+
# noinspection RubyMismatchedArgumentType
|
|
11
12
|
class Calendar < Hash
|
|
12
13
|
path = Pathname.new(__dir__)
|
|
13
14
|
autoload :Unit, path.join('unit')
|
|
@@ -21,78 +22,82 @@ class Pagy
|
|
|
21
22
|
UNITS = %i[year quarter month week day] # rubocop:disable Style/MutableConstant
|
|
22
23
|
|
|
23
24
|
class << self
|
|
24
|
-
# :nocov:
|
|
25
25
|
# Localize with rails-i18n in any env
|
|
26
26
|
def localize_with_rails_i18n_gem(*locales)
|
|
27
27
|
Unit.prepend(Module.new { def localize(...) = ::I18n.localize(...) })
|
|
28
|
+
# :nocov:
|
|
28
29
|
raise RailsI18nLoadError, "Pagy: The gem 'rails-i18n' must be installed if you don't use Rails" \
|
|
29
30
|
unless (path = Gem.loaded_specs['rails-i18n']&.full_gem_path)
|
|
30
31
|
|
|
32
|
+
# :nocov:
|
|
31
33
|
path = Pathname.new(path)
|
|
32
34
|
::I18n.load_path += locales.map { |locale| path.join("rails/locale/#{locale}.yml") }
|
|
33
35
|
end
|
|
34
|
-
# :nocov:
|
|
35
36
|
|
|
36
37
|
private
|
|
37
38
|
|
|
38
39
|
# Return calendar, from, to
|
|
39
|
-
def init(...)
|
|
40
|
-
new.send(:init, ...)
|
|
41
|
-
end
|
|
40
|
+
def init(...) = new.send(:init, ...)
|
|
42
41
|
end
|
|
43
42
|
|
|
44
43
|
# Return the current time of the smallest time unit shown
|
|
45
|
-
def showtime
|
|
46
|
-
self[@units.last].from
|
|
47
|
-
end
|
|
44
|
+
def showtime = self[@units.last].from
|
|
48
45
|
|
|
49
46
|
# Return the url for the calendar (shortest unit) page at time
|
|
50
47
|
def url_at(time, **)
|
|
51
|
-
conf = Marshal.load(Marshal.dump(@conf))
|
|
52
48
|
page_keys = {}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
|
|
50
|
+
@units.inject(nil) do |parent, unit|
|
|
51
|
+
unit_conf = @conf[unit]
|
|
52
|
+
unit_conf[:period] = parent&.send(:active_period) || @period
|
|
53
|
+
unit_conf[:page] = page = create(unit, **unit_conf).send(:page_at, time, **)
|
|
54
|
+
|
|
55
|
+
page_keys["#{unit}_#{@page_key}"] = page
|
|
56
|
+
unit_conf[:querify] = ->(params) { params.merge!(page_keys) }
|
|
57
|
+
|
|
58
|
+
create(unit, **unit_conf)
|
|
59
59
|
end.send(:compose_page_url, 1, **)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
private
|
|
63
63
|
|
|
64
64
|
# Create the calendar
|
|
65
|
-
def init(conf, period,
|
|
66
|
-
@conf =
|
|
67
|
-
@units = Calendar::UNITS &
|
|
65
|
+
def init(conf, period, params)
|
|
66
|
+
@conf = conf
|
|
67
|
+
@units = Calendar::UNITS & conf.keys # get the units in time length desc order
|
|
68
68
|
@period = period
|
|
69
|
-
@
|
|
69
|
+
@params = params
|
|
70
70
|
@page_key = conf[:offset][:page_key] || DEFAULT[:page_key]
|
|
71
|
+
|
|
71
72
|
# set all the :page_key options for later deletion
|
|
72
73
|
@units.each { |unit| conf[unit][:page_key] = "#{unit}_#{@page_key}" }
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
|
|
75
|
+
calendar = {}
|
|
76
|
+
unit_object = nil
|
|
77
|
+
|
|
75
78
|
@units.each_with_index do |unit, index|
|
|
76
|
-
params_to_delete
|
|
77
|
-
conf[unit]
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
params_to_delete = @units[(index + 1)..].map { conf[_1][:page_key] } + [@page_key]
|
|
80
|
+
unit_conf = conf[unit]
|
|
81
|
+
unit_conf[:querify] = ->(up) { up.except!(*params_to_delete.map(&:to_s)) }
|
|
82
|
+
unit_conf[:period] = unit_object&.send(:active_period) || @period
|
|
83
|
+
unit_conf[:page] = @params[unit_conf[:page_key]] # requested page
|
|
80
84
|
# :nocov:
|
|
81
85
|
# simplecov doesn't need to fail block_given?
|
|
82
|
-
|
|
86
|
+
unit_conf[:counts] = yield(unit, unit_conf[:period]) if block_given?
|
|
83
87
|
# :nocov:
|
|
84
|
-
calendar[unit] =
|
|
88
|
+
calendar[unit] = unit_object = create(unit, **unit_conf)
|
|
85
89
|
end
|
|
86
|
-
|
|
90
|
+
|
|
91
|
+
[replace(calendar), unit_object.from, unit_object.to]
|
|
87
92
|
end
|
|
88
93
|
|
|
89
94
|
# Create a unit subclass instance by using the unit name (internal use)
|
|
90
95
|
def create(unit, **)
|
|
91
96
|
raise InternalError, "unit must be in #{UNITS.inspect}; got #{unit}" unless UNITS.include?(unit)
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
unit_class = Pagy::Calendar.const_get(unit.to_s.capitalize)
|
|
99
|
+
|
|
100
|
+
unit_class.new(**, request: @conf[:request])
|
|
96
101
|
end
|
|
97
102
|
end
|
|
98
103
|
end
|
|
@@ -10,18 +10,21 @@ class Pagy
|
|
|
10
10
|
# To define a "bimester" unit you should:
|
|
11
11
|
# - Define a `Pagy::Calendar::Bimester` class
|
|
12
12
|
# - Add the `:bimester` unit symbol in the `Pagy::Calendar::UNITS`
|
|
13
|
-
# - Ensure the desc
|
|
13
|
+
# - Ensure the desc duration order of the UNITS (i.e. insert it between `:quarter` and `:month`)
|
|
14
14
|
class Unit < Pagy
|
|
15
15
|
DEFAULT = { page: 1 }.freeze
|
|
16
16
|
|
|
17
17
|
include Rangeable
|
|
18
18
|
include Shiftable
|
|
19
19
|
|
|
20
|
-
def initialize(**)
|
|
20
|
+
def initialize(**)
|
|
21
21
|
assign_options(**)
|
|
22
22
|
assign_and_check(page: 1)
|
|
23
23
|
assign_unit_variables
|
|
24
|
-
|
|
24
|
+
unless in_range? { @page <= @last }
|
|
25
|
+
assign_empty_page_variables
|
|
26
|
+
return
|
|
27
|
+
end
|
|
25
28
|
|
|
26
29
|
assign_previous_and_next
|
|
27
30
|
end
|
|
@@ -50,14 +53,7 @@ class Pagy
|
|
|
50
53
|
unless time.between?(@initial, fit_final)
|
|
51
54
|
raise RangeError.new(self, :time, "between #{@initial} and #{fit_final}", time) unless options[:fit_time]
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
fit_time = @initial
|
|
55
|
-
ordinal = 'first'
|
|
56
|
-
else
|
|
57
|
-
fit_time = fit_final
|
|
58
|
-
ordinal = 'last'
|
|
59
|
-
end
|
|
60
|
-
warn "Pagy::Calendar#page_at: Rescued #{time} out of range by returning the #{ordinal} page."
|
|
56
|
+
fit_time = time < @final ? @initial : fit_final
|
|
61
57
|
end
|
|
62
58
|
offset = page_offset_at(fit_time) # offset starts from 0
|
|
63
59
|
@order == :asc ? offset + 1 : @last - offset
|
|
@@ -67,15 +63,18 @@ class Pagy
|
|
|
67
63
|
def assign_unit_variables
|
|
68
64
|
@order = @options[:order]
|
|
69
65
|
@starting, @ending = @options[:period]
|
|
70
|
-
raise OptionError.new(self, :period, 'to be
|
|
66
|
+
raise OptionError.new(self, :period, 'to be an Array of min and max TimeWithZone instances', @options[:period]) \
|
|
71
67
|
unless @starting.is_a?(ActiveSupport::TimeWithZone) \
|
|
72
68
|
&& @ending.is_a?(ActiveSupport::TimeWithZone) && @starting <= @ending
|
|
73
69
|
end
|
|
74
70
|
|
|
75
71
|
# Apply the strftime format to the time.
|
|
76
|
-
# Localization other than :en,
|
|
72
|
+
# Localization other than :en, requires the rails-I18n gem.
|
|
77
73
|
def localize(time, **options)
|
|
74
|
+
# Impossible to "unprepend" the rails-i18n after it runs localize_with_rails_i18n_gem in test
|
|
75
|
+
# :nocov:
|
|
78
76
|
time.strftime(options[:format])
|
|
77
|
+
# :nocov:
|
|
79
78
|
end
|
|
80
79
|
|
|
81
80
|
# The number of time units to offset from the @initial time, in order to get the ordered starting time for the page.
|
|
@@ -10,6 +10,7 @@ class Pagy
|
|
|
10
10
|
@pagy = pagy
|
|
11
11
|
@option = option
|
|
12
12
|
@value = value
|
|
13
|
+
|
|
13
14
|
super("expected :#{@option} #{description}; got #{@value.inspect}")
|
|
14
15
|
end
|
|
15
16
|
end
|
|
@@ -22,12 +23,4 @@ class Pagy
|
|
|
22
23
|
|
|
23
24
|
# Generic internal error
|
|
24
25
|
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
26
|
end
|
|
@@ -13,7 +13,9 @@ class Pagy
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# Get the keyset attributes from a record
|
|
16
|
-
def keyset_attributes_from(record)
|
|
16
|
+
def keyset_attributes_from(record)
|
|
17
|
+
record.slice(*@keyset.keys)
|
|
18
|
+
end
|
|
17
19
|
|
|
18
20
|
# Get the hash of quoted keyset identifiers
|
|
19
21
|
def quoted_identifiers(table)
|
|
@@ -22,7 +22,9 @@ class Pagy
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Get the keyset attributes from a record
|
|
25
|
-
def keyset_attributes_from(record)
|
|
25
|
+
def keyset_attributes_from(record)
|
|
26
|
+
record.to_hash.slice(*@keyset.keys)
|
|
27
|
+
end
|
|
26
28
|
|
|
27
29
|
# Get the hash of quoted keyset identifiers
|
|
28
30
|
def quoted_identifiers(table)
|
|
@@ -8,9 +8,10 @@ class Pagy
|
|
|
8
8
|
PRIOR_PREFIX = 'prior_'
|
|
9
9
|
PAGE_PREFIX = 'page_'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
# Define empty subclasses to allow specific typing without triggering autoload.
|
|
12
|
+
# The .new factory in Keyset will handle mixing in the adapter logic from Pagy::Keyset::Adapters.
|
|
13
|
+
class ActiveRecord < self; end
|
|
14
|
+
class Sequel < self; end
|
|
14
15
|
|
|
15
16
|
# Finalize the instance variables needed for the UI
|
|
16
17
|
def initialize(set, **)
|
|
@@ -27,6 +28,7 @@ class Pagy
|
|
|
27
28
|
# Prepare the @update for the client when it's a new page, and return the next page number
|
|
28
29
|
def next
|
|
29
30
|
records
|
|
31
|
+
@count = 0 if !@more && @page == 1 # empty records (trigger the right info message for known 0 count)
|
|
30
32
|
return if !@more || (@options[:max_pages] && @page >= @options[:max_pages])
|
|
31
33
|
|
|
32
34
|
@next ||= begin
|
|
@@ -50,7 +52,8 @@ class Pagy
|
|
|
50
52
|
else
|
|
51
53
|
@page = @last = 1
|
|
52
54
|
end
|
|
53
|
-
|
|
55
|
+
|
|
56
|
+
@update = [storage_key, @options[:root_key], @options[:page_key]]
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
# Use a compound predicate to fetch the records
|
|
@@ -60,11 +63,13 @@ class Pagy
|
|
|
60
63
|
# Compound predicate for visited pages
|
|
61
64
|
predicate = +''
|
|
62
65
|
arguments = {}
|
|
66
|
+
|
|
63
67
|
if @prior_cutoff # not the first page
|
|
64
68
|
# Include the records after @prior_cutoff
|
|
65
69
|
predicate << "(#{compose_predicate(PRIOR_PREFIX)}) AND "
|
|
66
70
|
arguments.merge!(arguments_from(@prior_cutoff, PRIOR_PREFIX))
|
|
67
71
|
end
|
|
72
|
+
|
|
68
73
|
# Exclude the records after @page_cutoff
|
|
69
74
|
predicate << "NOT (#{compose_predicate(PAGE_PREFIX)})"
|
|
70
75
|
arguments.merge!(arguments_from(@page_cutoff, PAGE_PREFIX))
|