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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +11 -12
  4. data/apps/demo.ru +5 -5
  5. data/apps/enable_rails_page_segment.rb +54 -0
  6. data/apps/index.rb +1 -1
  7. data/apps/keynav+root_key.ru +316 -0
  8. data/apps/keynav.ru +10 -13
  9. data/apps/keyset.ru +5 -11
  10. data/apps/keyset_sequel.ru +10 -12
  11. data/apps/rails.ru +8 -12
  12. data/apps/repro.ru +11 -11
  13. data/bin/pagy +2 -94
  14. data/config/pagy.rb +8 -7
  15. data/javascripts/ai_widget.js +65 -51
  16. data/javascripts/pagy.js +20 -17
  17. data/javascripts/pagy.js.map +3 -3
  18. data/javascripts/pagy.min.js +2 -1
  19. data/javascripts/pagy.mjs +19 -16
  20. data/javascripts/wand.js +15 -9
  21. data/lib/pagy/classes/calendar/calendar.rb +36 -31
  22. data/lib/pagy/classes/calendar/day.rb +1 -1
  23. data/lib/pagy/classes/calendar/month.rb +1 -1
  24. data/lib/pagy/classes/calendar/quarter.rb +1 -1
  25. data/lib/pagy/classes/calendar/unit.rb +12 -13
  26. data/lib/pagy/classes/calendar/year.rb +1 -1
  27. data/lib/pagy/classes/exceptions.rb +1 -8
  28. data/lib/pagy/classes/keyset/adapters/active_record.rb +3 -1
  29. data/lib/pagy/classes/keyset/adapters/sequel.rb +3 -1
  30. data/lib/pagy/classes/keyset/keynav.rb +9 -4
  31. data/lib/pagy/classes/keyset/keyset.rb +57 -32
  32. data/lib/pagy/classes/offset/countish.rb +17 -0
  33. data/lib/pagy/classes/offset/countless.rb +26 -14
  34. data/lib/pagy/classes/offset/offset.rb +8 -2
  35. data/lib/pagy/classes/offset/search.rb +6 -10
  36. data/lib/pagy/classes/request.rb +29 -20
  37. data/lib/pagy/cli.rb +135 -0
  38. data/lib/pagy/console.rb +6 -0
  39. data/lib/pagy/modules/abilities/configurable.rb +2 -2
  40. data/lib/pagy/modules/abilities/countable.rb +24 -0
  41. data/lib/pagy/modules/abilities/linkable.rb +35 -24
  42. data/lib/pagy/modules/abilities/rangeable.rb +3 -3
  43. data/lib/pagy/modules/b64.rb +9 -3
  44. data/lib/pagy/modules/console.rb +15 -20
  45. data/lib/pagy/modules/i18n/i18n.rb +38 -14
  46. data/lib/pagy/modules/i18n/p11n/arabic.rb +1 -0
  47. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +1 -0
  48. data/lib/pagy/modules/i18n/p11n/polish.rb +1 -0
  49. data/lib/pagy/modules/searcher.rb +9 -8
  50. data/lib/pagy/toolbox/helpers/anchor_tags.rb +11 -15
  51. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +3 -0
  52. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +3 -1
  53. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +2 -0
  54. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +3 -0
  55. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +1 -1
  56. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +3 -1
  57. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +2 -0
  58. data/lib/pagy/toolbox/helpers/data_hash.rb +19 -17
  59. data/lib/pagy/toolbox/helpers/headers_hash.rb +15 -9
  60. data/lib/pagy/toolbox/helpers/info_tag.rb +2 -0
  61. data/lib/pagy/toolbox/helpers/input_nav_js.rb +9 -6
  62. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +4 -3
  63. data/lib/pagy/toolbox/helpers/loader.rb +3 -0
  64. data/lib/pagy/toolbox/helpers/page_url.rb +10 -16
  65. data/lib/pagy/toolbox/helpers/series_nav.rb +5 -4
  66. data/lib/pagy/toolbox/helpers/series_nav_js.rb +2 -1
  67. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +8 -6
  68. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +6 -1
  69. data/lib/pagy/toolbox/helpers/support/series.rb +1 -2
  70. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +1 -1
  71. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +2 -1
  72. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +10 -4
  73. data/lib/pagy/toolbox/helpers/urls_hash.rb +7 -7
  74. data/lib/pagy/toolbox/paginators/calendar.rb +13 -9
  75. data/lib/pagy/toolbox/paginators/countish.rb +39 -0
  76. data/lib/pagy/toolbox/paginators/countless.rb +13 -15
  77. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +43 -18
  78. data/lib/pagy/toolbox/paginators/keynav_js.rb +14 -15
  79. data/lib/pagy/toolbox/paginators/keyset.rb +7 -9
  80. data/lib/pagy/toolbox/paginators/meilisearch.rb +21 -18
  81. data/lib/pagy/toolbox/paginators/method.rb +15 -3
  82. data/lib/pagy/toolbox/paginators/offset.rb +14 -22
  83. data/lib/pagy/toolbox/paginators/searchkick.rb +21 -18
  84. data/lib/pagy/toolbox/paginators/typesense_rails.rb +35 -0
  85. data/lib/pagy.rb +23 -10
  86. data/locales/id.yml +1 -3
  87. data/locales/ja.yml +1 -3
  88. data/locales/km.yml +1 -3
  89. data/locales/sw.yml +2 -2
  90. data/locales/tr.yml +10 -8
  91. data/stylesheets/pagy-tailwind.css +1 -1
  92. data/stylesheets/pagy.css +1 -6
  93. metadata +25 -8
  94. data/lib/optimist.rb +0 -1022
  95. data/lib/pagy/classes/keyset/active_record.rb +0 -11
  96. data/lib/pagy/classes/keyset/keynav/active_record.rb +0 -13
  97. data/lib/pagy/classes/keyset/keynav/sequel.rb +0 -13
  98. data/lib/pagy/classes/keyset/sequel.rb +0 -11
@@ -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": "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",
8
- "debugId": "46963D274F782A9664756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "975696F2DDC5F9D164756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1 +1,2 @@
1
- window.Pagy=(()=>{const L="sessionStorage"in window&&"BroadcastChannel"in window;let j="pagy",Y,D,O;if(L)Y=sessionStorage,D=new BroadcastChannel(j),O=Date.now(),D.addEventListener("message",(q)=>{if(q.data.from){const z=Y.getItem(q.data.key);if(z)D.postMessage({to:q.data.from,key:q.data.key,str:z})}else if(q.data.to){if(q.data.to==O)Y.setItem(q.data.key,q.data.str)}});const U=new ResizeObserver((q)=>q.forEach((z)=>{z.target.querySelectorAll(".pagy-rjs").forEach((C)=>C.render())})),S=(q)=>btoa(String.fromCharCode(...new TextEncoder().encode(q))).replace(/[+/=]/g,(z)=>z=="+"?"-":z=="/"?"_":""),_=(q)=>new TextDecoder().decode(Uint8Array.from(atob(q),(z)=>z.charCodeAt(0))),B=()=>Math.floor(Math.random()*46656).toString(36),P=async(q,[z,C,F,G])=>{let M;const R=document.cookie.split(/;\s+/).find((H)=>H.startsWith(j+"="))?.split("=")[1]??B();if(document.cookie=j+"="+R,z&&!(z in Y)){if(D.postMessage({from:O,key:z}),await new Promise((H)=>setTimeout(()=>H(""),100)),!(z in Y))M=(H)=>H+"+"+F}if(!M){if(!z)do z=B();while(z in Y);const H=Y.getItem(z),Q=H?JSON.parse(H):[void 0];if(G)Q.splice(...G),Y.setItem(z,JSON.stringify(Q));M=(X)=>{const Z=parseInt(X);return S(JSON.stringify([R,z,Z,Q.length,Q[Z-1],Q[Z]]))}}for(let H of q.querySelectorAll("a[href]")){const Q=H.href,X=new RegExp(`(?<=\\?.*)\\b${C}=(\\d+)`);H.href=Q.replace(X,C+"="+M(Q.match(X)[1]))}return M},x=(q,[[z,C,F,G,M],[R,H,Q],X])=>{const Z=q.parentElement;let J=-1;if((q.render=()=>{const E=R.findIndex(($)=>$<Z.clientWidth);if(R[E]===J)return;let T=z;if(H[E].forEach(($,A)=>{T+=$=="gap"?G:(typeof $=="number"?C.replace("P ",$):F).replace("L<",Q?.[E][A]??$+"<")}),T+=M,q.innerHTML="",q.insertAdjacentHTML("afterbegin",T),J=R[E],X&&L)P(q,X)})(),q.classList.contains(j+"-rjs"))U.observe(Z)},N=async(q,[z,C])=>{const F=C&&L?await P(q,C):(G)=>G;W(q,(G)=>z.replace("P ",F(G)))},V=(q,[z,C])=>{W(q,(F)=>{return C.replace("P ",Math.max(Math.ceil(z/parseInt(F)),1)).replace("L ",F)})},W=(q,z)=>{const C=q.querySelector("input"),F=q.querySelector("a"),G=C.value,M=()=>{if(C.value===G)return;const[R,H,Q]=[C.min,C.value,C.max].map((X)=>parseInt(X)||0);if(H<R||H>Q){C.value=G,C.select();return}F.href=z(C.value),F.click()};C.addEventListener("focus",()=>C.select()),C.addEventListener("focusout",M),C.addEventListener("keypress",(R)=>{if(R.key=="Enter")M()})};return{version:"43.0.0",init(q){const z=q instanceof HTMLElement?q:document,C=z.querySelectorAll("[data-pagy]");for(let F of C)try{const[G,...M]=JSON.parse(_(F.getAttribute("data-pagy")));if(G=="k")P(F,...M);else if(G=="snj")x(F,M);else if(G=="inj")N(F,M);else if(G=="ltj")V(F,M)}catch(G){console.warn("Pagy.init: %o\n%s",F,G)}}}})();
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, pageRe = "P ";
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 augment;
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
- augment = (page) => page + "+" + last;
34
+ augmentPage = (page) => page + "+" + last;
35
35
  }
36
36
  }
37
- if (!augment) {
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
- augment = (page) => {
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
- const url = a.href, re = new RegExp(`(?<=\\?.*)\\b${pageKey}=(\\d+)`);
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 augment;
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(pageRe, item) : current).replace("L<", labels?.[index][i] ?? item + "<");
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(pageRe, augment(inputValue)));
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(pageRe, Math.max(Math.ceil(from / parseInt(inputValue)), 1)).replace("L ", inputValue);
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.0.0",
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("Pagy.init: %o\n%s", element, err);
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, `\$1${mapping.custom}\$2`);
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("\n\n") : "";
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("\n");
83
- const allComponentCss = processedResults.map((r) => r.componentCss).join("\n");
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>\u2022 You can change Pagy's styling quite radically, by just setting a few CSS Custom Properties:
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>\u2022 Pick a Presets as a starting point, customize it with the controls,
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>\u2022 Add further customization to the <code>.pagy</code> CSS Override, or override the calculated properties
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 {\n`;
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};\n`;
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
- @units.inject(nil) do |object, unit|
54
- conf[unit][:period] = object&.send(:active_period) || @period
55
- conf[unit][:page] = page_keys["#{unit}_#{@page_key}"] \
56
- = create(unit, **conf[unit]).send(:page_at, time, **)
57
- conf[unit][:querify] = ->(query) { query.merge!(page_keys) }
58
- create(unit, **conf[unit])
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, query)
66
- @conf = Marshal.load(Marshal.dump(conf)) # store a copy
67
- @units = Calendar::UNITS & @conf.keys # get the units in time length desc order
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
- @query = query
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
- calendar = {}
74
- object = nil
74
+
75
+ calendar = {}
76
+ unit_object = nil
77
+
75
78
  @units.each_with_index do |unit, index|
76
- params_to_delete = @units[(index + 1), @units.length].map { |sub| conf[sub][:page_key] } + [@page_key]
77
- conf[unit][:querify] = ->(up) { up.except!(*params_to_delete.map(&:to_s)) }
78
- conf[unit][:period] = object&.send(:active_period) || @period
79
- conf[unit][:page] = @query["#{unit}_#{@page_key}"] # requested page
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
- conf[unit][:counts] = yield(unit, conf[unit][:period]) if block_given?
86
+ unit_conf[:counts] = yield(unit, unit_conf[:period]) if block_given?
83
87
  # :nocov:
84
- calendar[unit] = object = create(unit, **conf[unit])
88
+ calendar[unit] = unit_object = create(unit, **unit_conf)
85
89
  end
86
- [replace(calendar), object.from, object.to]
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
- name = +unit.to_s
94
- name[0] = name[0].capitalize
95
- Pagy::Calendar.const_get(name).new(**, request: @conf[:request])
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
@@ -3,7 +3,7 @@
3
3
  class Pagy
4
4
  class Calendar
5
5
  class Day < Unit
6
- DEFAULT = { slots: 31,
6
+ DEFAULT = { slots: 31,
7
7
  compact: true,
8
8
  order: :asc,
9
9
  format: '%d' }.freeze
@@ -3,7 +3,7 @@
3
3
  class Pagy # :nodoc:
4
4
  class Calendar # :nodoc:
5
5
  class Month < Unit
6
- DEFAULT = { slots: 12,
6
+ DEFAULT = { slots: 12,
7
7
  compact: true,
8
8
  order: :asc,
9
9
  format: '%b' }.freeze
@@ -3,7 +3,7 @@
3
3
  class Pagy # :nodoc:
4
4
  class Calendar # :nodoc:
5
5
  class Quarter < Unit
6
- DEFAULT = { slots: 4,
6
+ DEFAULT = { slots: 4,
7
7
  compact: true,
8
8
  order: :asc,
9
9
  format: 'Q%q' }.freeze # '%q' token
@@ -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 durtion order of the UNITS (i.e. insert it between `:quarter` and `:month`)
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(**) # rubocop:disable Lint/MissingSuper
20
+ def initialize(**)
21
21
  assign_options(**)
22
22
  assign_and_check(page: 1)
23
23
  assign_unit_variables
24
- return unless in_range? { @page <= @last }
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
- if time < @final
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 a an Array of min and max TimeWithZone instances', @options[:period]) \
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, require the rails-I18n gem.
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.
@@ -4,7 +4,7 @@ class Pagy # :nodoc:
4
4
  class Calendar # :nodoc:
5
5
  # Year unit subclass
6
6
  class Year < Unit
7
- DEFAULT = { slots: 10,
7
+ DEFAULT = { slots: 10,
8
8
  compact: true,
9
9
  order: :asc,
10
10
  format: '%Y' }.freeze
@@ -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) = record.slice(*@keyset.keys)
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) = record.to_hash.slice(*@keyset.keys)
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
- path = Pathname.new(__FILE__).sub_ext('')
12
- autoload :ActiveRecord, path.join('active_record')
13
- autoload :Sequel, path.join('sequel')
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
- @update = [storage_key, @options[:page_key]]
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))