diff --git a/dist/exports/client.js b/dist/exports/client.js index df1c2ee..08493c4 100644 --- a/dist/exports/client.js +++ b/dist/exports/client.js @@ -7,6 +7,6 @@ export { Analytics } from '../modules/analytics/client.js'; * components. Kept separate from the main entry so server bundles don't pull in * client-only code. */ export { ConsentProvider, CookieBanner, CookieButton, useConsent, useConsentContext } from '../modules/consent/client.js'; -export { Turnstile } from '../modules/turnstile/client.js'; +export { Turnstile } from '../modules/turnstile/client.js'; //# sourceMappingURL=client.js.map //# sourceMappingURL=client.js.map \ No newline at end of file diff --git a/dist/exports/client.js.map b/dist/exports/client.js.map index 66254f1..be8c648 100644 --- a/dist/exports/client.js.map +++ b/dist/exports/client.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["'use client'\nexport { Analytics } from '../modules/analytics/client.js'\n/**\n * Entry point: ipal-kit/client\n *\n * Client-side ('use client') exports — React hooks, providers, and UI\n * components. Kept separate from the main entry so server bundles don't pull in\n * client-only code.\n */\nexport {\n ConsentProvider,\n CookieBanner,\n CookieButton,\n useConsent,\n useConsentContext,\n} from '../modules/consent/client.js'\nexport type { CookieBannerClassNames } from '../modules/consent/client.js'\nexport { Turnstile } from '../modules/turnstile/client.js'\nexport type { TurnstileProps } from '../modules/turnstile/client.js'\n"],"names":["Analytics","ConsentProvider","CookieBanner","CookieButton","useConsent","useConsentContext","Turnstile"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,iCAAgC;AAC1D;;;;;;CAMC,GACD,SACEC,eAAe,EACfC,YAAY,EACZC,YAAY,EACZC,UAAU,EACVC,iBAAiB,QACZ,+BAA8B;AAErC,SAASC,SAAS,QAAQ,iCAAgC"} \ No newline at end of file +{"version":3,"sources":["client.js"],"sourcesContent":["'use client'\nexport { Analytics } from '../modules/analytics/client.js'\n/**\n * Entry point: ipal-kit/client\n *\n * Client-side ('use client') exports — React hooks, providers, and UI\n * components. Kept separate from the main entry so server bundles don't pull in\n * client-only code.\n */\nexport {\n ConsentProvider,\n CookieBanner,\n CookieButton,\n useConsent,\n useConsentContext,\n} from '../modules/consent/client.js'\nexport type { CookieBannerClassNames } from '../modules/consent/client.js'\nexport { Turnstile } from '../modules/turnstile/client.js'\nexport type { TurnstileProps } from '../modules/turnstile/client.js'\n"],"names":["Analytics","ConsentProvider","CookieBanner","CookieButton","useConsent","useConsentContext","Turnstile"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,iCAAgC;AAC1D;;;;;;CAMC,GACD,SACEC,eAAe,EACfC,YAAY,EACZC,YAAY,EACZC,UAAU,EACVC,iBAAiB,QACZ,+BAA8B;AAErC,SAASC,SAAS,QAAQ,iCAAgC"} \ No newline at end of file diff --git a/dist/exports/next-middleware.js b/dist/exports/next-middleware.js index 3ebd34f..f7c23a9 100644 --- a/dist/exports/next-middleware.js +++ b/dist/exports/next-middleware.js @@ -4,6 +4,6 @@ * Re-exports the locale middleware factory and default matcher for use in a * client project's middleware.ts. Kept as a thin re-export so the Next-facing * surface is separate from the main package export. - */ export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from '../modules/i18n/index.js'; + */ export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from '../modules/i18n/index.js'; //# sourceMappingURL=next-middleware.js.map //# sourceMappingURL=next-middleware.js.map \ No newline at end of file diff --git a/dist/exports/next-middleware.js.map b/dist/exports/next-middleware.js.map index ad94411..17c21fd 100644 --- a/dist/exports/next-middleware.js.map +++ b/dist/exports/next-middleware.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/exports/next-middleware.ts"],"sourcesContent":["/**\n * Entry point: ipal-kit/next/middleware\n *\n * Re-exports the locale middleware factory and default matcher for use in a\n * client project's middleware.ts. Kept as a thin re-export so the Next-facing\n * surface is separate from the main package export.\n */\nexport { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from '../modules/i18n/index.js'\nexport type { LocaleMiddlewareResult } from '../modules/i18n/index.js'\n"],"names":["createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER"],"mappings":"AAAA;;;;;;CAMC,GACD,SAASA,sBAAsB,EAAEC,0BAA0B,QAAQ,2BAA0B"} \ No newline at end of file +{"version":3,"sources":["next-middleware.js"],"sourcesContent":["/**\n * Entry point: ipal-kit/next/middleware\n *\n * Re-exports the locale middleware factory and default matcher for use in a\n * client project's middleware.ts. Kept as a thin re-export so the Next-facing\n * surface is separate from the main package export.\n */\nexport { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from '../modules/i18n/index.js'\nexport type { LocaleMiddlewareResult } from '../modules/i18n/index.js'\n"],"names":["createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER"],"mappings":"AAAA;;;;;;CAMC,GACD,SAASA,sBAAsB,EAAEC,0BAA0B,QAAQ,2BAA0B"} \ No newline at end of file diff --git a/dist/exports/rsc.js b/dist/exports/rsc.js index fbe1661..0722e72 100644 --- a/dist/exports/rsc.js +++ b/dist/exports/rsc.js @@ -4,6 +4,6 @@ * Server-component exports. RenderBlocks is a React Server Component, so it * lives here rather than in the main package entry to keep React out of the * server-config bundle. - */ export { RenderBlocks } from '../modules/blocks/index.js'; + */ export { RenderBlocks } from '../modules/blocks/index.js'; //# sourceMappingURL=rsc.js.map //# sourceMappingURL=rsc.js.map \ No newline at end of file diff --git a/dist/exports/rsc.js.map b/dist/exports/rsc.js.map index 6f4f24d..c4168c7 100644 --- a/dist/exports/rsc.js.map +++ b/dist/exports/rsc.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["/**\n * Entry point: ipal-kit/rsc\n *\n * Server-component exports. RenderBlocks is a React Server Component, so it\n * lives here rather than in the main package entry to keep React out of the\n * server-config bundle.\n */\nexport { RenderBlocks } from '../modules/blocks/index.js'\nexport type {\n BlockComponentMap,\n BlockData,\n EnhanceProps,\n RenderBlocksProps,\n} from '../modules/blocks/index.js'\n"],"names":["RenderBlocks"],"mappings":"AAAA;;;;;;CAMC,GACD,SAASA,YAAY,QAAQ,6BAA4B"} \ No newline at end of file +{"version":3,"sources":["rsc.js"],"sourcesContent":["/**\n * Entry point: ipal-kit/rsc\n *\n * Server-component exports. RenderBlocks is a React Server Component, so it\n * lives here rather than in the main package entry to keep React out of the\n * server-config bundle.\n */\nexport { RenderBlocks } from '../modules/blocks/index.js'\nexport type {\n BlockComponentMap,\n BlockData,\n EnhanceProps,\n RenderBlocksProps,\n} from '../modules/blocks/index.js'\n"],"names":["RenderBlocks"],"mappings":"AAAA;;;;;;CAMC,GACD,SAASA,YAAY,QAAQ,6BAA4B"} \ No newline at end of file diff --git a/dist/exports/server.js b/dist/exports/server.js index 8b9d79d..76711ad 100644 --- a/dist/exports/server.js +++ b/dist/exports/server.js @@ -11,6 +11,6 @@ * never from the Payload config or client code. */ export { verifyTurnstile } from '../modules/turnstile/index.js'; export { sendEmail } from '../modules/email/index.js'; -export { submitForm } from '../modules/forms/index.js'; +export { submitForm } from '../modules/forms/index.js'; //# sourceMappingURL=server.js.map //# sourceMappingURL=server.js.map \ No newline at end of file diff --git a/dist/exports/server.js.map b/dist/exports/server.js.map index 8e406f9..b387aca 100644 --- a/dist/exports/server.js.map +++ b/dist/exports/server.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/exports/server.ts"],"sourcesContent":["/**\n * Entry point: ipal-kit/server\n *\n * Server-only runtime functions — these import 'server-only' (Turnstile secret,\n * SMTP password, nodemailer). Kept OUT of the main entry so that loading the\n * Payload config or running `payload generate:importmap` (both Node scripts,\n * no bundler) never pulls in 'server-only', which throws outside a bundled\n * server context.\n *\n * Import these only from Server Actions, route handlers, or server components —\n * never from the Payload config or client code.\n */\nexport { verifyTurnstile } from '../modules/turnstile/index.js'\nexport { sendEmail } from '../modules/email/index.js'\nexport type { SendEmailArgs, SendEmailResult } from '../modules/email/index.js'\nexport { submitForm } from '../modules/forms/index.js'\nexport type { SubmitFormArgs, SubmitFormResult } from '../modules/forms/index.js'\n"],"names":["verifyTurnstile","sendEmail","submitForm"],"mappings":"AAAA;;;;;;;;;;;CAWC,GACD,SAASA,eAAe,QAAQ,gCAA+B;AAC/D,SAASC,SAAS,QAAQ,4BAA2B;AAErD,SAASC,UAAU,QAAQ,4BAA2B"} \ No newline at end of file +{"version":3,"sources":["server.js"],"sourcesContent":["/**\n * Entry point: ipal-kit/server\n *\n * Server-only runtime functions — these import 'server-only' (Turnstile secret,\n * SMTP password, nodemailer). Kept OUT of the main entry so that loading the\n * Payload config or running `payload generate:importmap` (both Node scripts,\n * no bundler) never pulls in 'server-only', which throws outside a bundled\n * server context.\n *\n * Import these only from Server Actions, route handlers, or server components —\n * never from the Payload config or client code.\n */\nexport { verifyTurnstile } from '../modules/turnstile/index.js'\nexport { sendEmail } from '../modules/email/index.js'\nexport type { SendEmailArgs, SendEmailResult } from '../modules/email/index.js'\nexport { submitForm } from '../modules/forms/index.js'\nexport type { SubmitFormArgs, SubmitFormResult } from '../modules/forms/index.js'\n"],"names":["verifyTurnstile","sendEmail","submitForm"],"mappings":"AAAA;;;;;;;;;;;CAWC,GACD,SAASA,eAAe,QAAQ,gCAA+B;AAC/D,SAASC,SAAS,QAAQ,4BAA2B;AAErD,SAASC,UAAU,QAAQ,4BAA2B"} \ No newline at end of file diff --git a/dist/globals/CookieSettings/fields.js b/dist/globals/CookieSettings/fields.js index 8f08ab6..1fb2b93 100644 --- a/dist/globals/CookieSettings/fields.js +++ b/dist/globals/CookieSettings/fields.js @@ -82,6 +82,6 @@ import { CONSENT_CATEGORIES } from '../../modules/consent/index.js'; } ] } -]; +]; //# sourceMappingURL=fields.js.map //# sourceMappingURL=fields.js.map \ No newline at end of file diff --git a/dist/globals/CookieSettings/fields.js.map b/dist/globals/CookieSettings/fields.js.map index d8bbdcb..01a684d 100644 --- a/dist/globals/CookieSettings/fields.js.map +++ b/dist/globals/CookieSettings/fields.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/globals/CookieSettings/fields.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport { CONSENT_CATEGORIES } from '../../modules/consent/index.js'\n\n/**\n * Fields for the CookieSettings global — GDPR consent banner copy, editable\n * per locale. The privacy-policy link is NOT stored here: it comes from the\n * pages module (system page role 'privacyPolicy'), keeping one source of truth.\n */\nexport const cookieSettingsFields: Field[] = [\n {\n name: 'message',\n type: 'textarea',\n admin: {\n description: 'Main consent message shown in the banner.',\n },\n localized: true,\n },\n {\n name: 'settingsTitle',\n type: 'text',\n admin: {\n description: 'Heading for the detailed settings panel.',\n },\n localized: true,\n },\n {\n name: 'buttons',\n type: 'group',\n admin: {\n description: 'Button labels.',\n },\n fields: [\n { name: 'acceptAll', type: 'text', localized: true },\n { name: 'reject', type: 'text', localized: true },\n { name: 'settings', type: 'text', localized: true },\n { name: 'save', type: 'text', localized: true },\n { name: 'back', type: 'text', localized: true },\n ],\n },\n {\n name: 'categories',\n type: 'array',\n admin: {\n description: 'Per-category titles and descriptions.',\n },\n fields: [\n {\n name: 'key',\n type: 'select',\n options: CONSENT_CATEGORIES.map((c) => ({ label: c, value: c })),\n required: true,\n },\n { name: 'title', type: 'text', localized: true },\n { name: 'description', type: 'textarea', localized: true },\n ],\n },\n]\n"],"names":["CONSENT_CATEGORIES","cookieSettingsFields","name","type","admin","description","localized","fields","options","map","c","label","value","required"],"mappings":"AAEA,SAASA,kBAAkB,QAAQ,iCAAgC;AAEnE;;;;CAIC,GACD,OAAO,MAAMC,uBAAgC;IAC3C;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,WAAW;IACb;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,WAAW;IACb;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAE,QAAQ;YACN;gBAAEL,MAAM;gBAAaC,MAAM;gBAAQG,WAAW;YAAK;YACnD;gBAAEJ,MAAM;gBAAUC,MAAM;gBAAQG,WAAW;YAAK;YAChD;gBAAEJ,MAAM;gBAAYC,MAAM;gBAAQG,WAAW;YAAK;YAClD;gBAAEJ,MAAM;gBAAQC,MAAM;gBAAQG,WAAW;YAAK;YAC9C;gBAAEJ,MAAM;gBAAQC,MAAM;gBAAQG,WAAW;YAAK;SAC/C;IACH;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAE,QAAQ;YACN;gBACEL,MAAM;gBACNC,MAAM;gBACNK,SAASR,mBAAmBS,GAAG,CAAC,CAACC,IAAO,CAAA;wBAAEC,OAAOD;wBAAGE,OAAOF;oBAAE,CAAA;gBAC7DG,UAAU;YACZ;YACA;gBAAEX,MAAM;gBAASC,MAAM;gBAAQG,WAAW;YAAK;YAC/C;gBAAEJ,MAAM;gBAAeC,MAAM;gBAAYG,WAAW;YAAK;SAC1D;IACH;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["fields.js"],"sourcesContent":["import type { Field } from 'payload'\n\nimport { CONSENT_CATEGORIES } from '../../modules/consent/index.js'\n\n/**\n * Fields for the CookieSettings global — GDPR consent banner copy, editable\n * per locale. The privacy-policy link is NOT stored here: it comes from the\n * pages module (system page role 'privacyPolicy'), keeping one source of truth.\n */\nexport const cookieSettingsFields: Field[] = [\n {\n name: 'message',\n type: 'textarea',\n admin: {\n description: 'Main consent message shown in the banner.',\n },\n localized: true,\n },\n {\n name: 'settingsTitle',\n type: 'text',\n admin: {\n description: 'Heading for the detailed settings panel.',\n },\n localized: true,\n },\n {\n name: 'buttons',\n type: 'group',\n admin: {\n description: 'Button labels.',\n },\n fields: [\n { name: 'acceptAll', type: 'text', localized: true },\n { name: 'reject', type: 'text', localized: true },\n { name: 'settings', type: 'text', localized: true },\n { name: 'save', type: 'text', localized: true },\n { name: 'back', type: 'text', localized: true },\n ],\n },\n {\n name: 'categories',\n type: 'array',\n admin: {\n description: 'Per-category titles and descriptions.',\n },\n fields: [\n {\n name: 'key',\n type: 'select',\n options: CONSENT_CATEGORIES.map((c) => ({ label: c, value: c })),\n required: true,\n },\n { name: 'title', type: 'text', localized: true },\n { name: 'description', type: 'textarea', localized: true },\n ],\n },\n]\n"],"names":["CONSENT_CATEGORIES","cookieSettingsFields","name","type","admin","description","localized","fields","options","map","c","label","value","required"],"mappings":"AAEA,SAASA,kBAAkB,QAAQ,iCAAgC;AAEnE;;;;CAIC,GACD,OAAO,MAAMC,uBAAgC;IAC3C;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,WAAW;IACb;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,WAAW;IACb;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAE,QAAQ;YACN;gBAAEL,MAAM;gBAAaC,MAAM;gBAAQG,WAAW;YAAK;YACnD;gBAAEJ,MAAM;gBAAUC,MAAM;gBAAQG,WAAW;YAAK;YAChD;gBAAEJ,MAAM;gBAAYC,MAAM;gBAAQG,WAAW;YAAK;YAClD;gBAAEJ,MAAM;gBAAQC,MAAM;gBAAQG,WAAW;YAAK;YAC9C;gBAAEJ,MAAM;gBAAQC,MAAM;gBAAQG,WAAW;YAAK;SAC/C;IACH;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAE,QAAQ;YACN;gBACEL,MAAM;gBACNC,MAAM;gBACNK,SAASR,mBAAmBS,GAAG,CAAC,CAACC,IAAO,CAAA;wBAAEC,OAAOD;wBAAGE,OAAOF;qBAAE,CAAA;gBAC7DG,UAAU;YACZ;YACA;gBAAEX,MAAM;gBAASC,MAAM;gBAAQG,WAAW;YAAK;YAC/C;gBAAEJ,MAAM;gBAAeC,MAAM;gBAAYG,WAAW;YAAK;SAC1D;IACH;CACD,CAAA"} \ No newline at end of file diff --git a/dist/globals/CookieSettings/index.js b/dist/globals/CookieSettings/index.js index eef7b2f..9663123 100644 --- a/dist/globals/CookieSettings/index.js +++ b/dist/globals/CookieSettings/index.js @@ -14,6 +14,6 @@ import { cookieSettingsFields } from './fields.js'; fields: cookieSettingsFields, label: 'Cookie Settings' }; -} +} //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/globals/CookieSettings/index.js.map b/dist/globals/CookieSettings/index.js.map index f2c58d5..d0d89c9 100644 --- a/dist/globals/CookieSettings/index.js.map +++ b/dist/globals/CookieSettings/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/globals/CookieSettings/index.ts"],"sourcesContent":["import type { GlobalConfig } from 'payload'\n\nimport { cookieSettingsFields } from './fields.js'\n\n/**\n * Builds the CookieSettings global — consent banner copy managed by editors.\n * Public read (the banner needs it on the frontend for anonymous visitors).\n */\nexport function buildCookieSettings(): GlobalConfig {\n return {\n slug: 'cookie-settings',\n access: {\n read: () => true,\n },\n admin: {\n group: 'Settings',\n },\n fields: cookieSettingsFields,\n label: 'Cookie Settings',\n }\n}\n"],"names":["cookieSettingsFields","buildCookieSettings","slug","access","read","admin","group","fields","label"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,cAAa;AAElD;;;CAGC,GACD,OAAO,SAASC;IACd,OAAO;QACLC,MAAM;QACNC,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,OAAO;YACLC,OAAO;QACT;QACAC,QAAQP;QACRQ,OAAO;IACT;AACF"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["import type { GlobalConfig } from 'payload'\n\nimport { cookieSettingsFields } from './fields.js'\n\n/**\n * Builds the CookieSettings global — consent banner copy managed by editors.\n * Public read (the banner needs it on the frontend for anonymous visitors).\n */\nexport function buildCookieSettings(): GlobalConfig {\n return {\n slug: 'cookie-settings',\n access: {\n read: () => true,\n },\n admin: {\n group: 'Settings',\n },\n fields: cookieSettingsFields,\n label: 'Cookie Settings',\n }\n}\n"],"names":["cookieSettingsFields","buildCookieSettings","slug","access","read","admin","group","fields","label"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,cAAa;AAElD;;;CAGC,GACD,OAAO,SAASC;IACd,OAAO;QACLC,MAAM;QACNC,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,OAAO;YACLC,OAAO;QACT;QACAC,QAAQP;QACRQ,OAAO;IACT;AACF"} \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/analytics.js b/dist/globals/SiteIntegrations/fields/analytics.js index 31e3543..6363fc2 100644 --- a/dist/globals/SiteIntegrations/fields/analytics.js +++ b/dist/globals/SiteIntegrations/fields/analytics.js @@ -18,6 +18,6 @@ placeholder: 'GTM-XXXXXXX' } } -]; +]; //# sourceMappingURL=analytics.js.map //# sourceMappingURL=analytics.js.map \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/analytics.js.map b/dist/globals/SiteIntegrations/fields/analytics.js.map index 34eed37..697a7e6 100644 --- a/dist/globals/SiteIntegrations/fields/analytics.js.map +++ b/dist/globals/SiteIntegrations/fields/analytics.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/globals/SiteIntegrations/fields/analytics.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Analytics configuration — GA4 measurement ID and GTM container ID.\n * IDs are public (exposed client-side), so no read restriction needed.\n */\nexport const analyticsFields: Field[] = [\n {\n name: 'ga4MeasurementId',\n type: 'text',\n admin: {\n description: 'Google Analytics 4 Measurement ID.',\n placeholder: 'G-XXXXXXXXXX',\n },\n },\n {\n name: 'gtmContainerId',\n type: 'text',\n admin: {\n description: 'Google Tag Manager container ID.',\n placeholder: 'GTM-XXXXXXX',\n },\n },\n]\n"],"names":["analyticsFields","name","type","admin","description","placeholder"],"mappings":"AAEA;;;CAGC,GACD,OAAO,MAAMA,kBAA2B;IACtC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;YACbC,aAAa;QACf;IACF;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;YACbC,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["analytics.js"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Analytics configuration — GA4 measurement ID and GTM container ID.\n * IDs are public (exposed client-side), so no read restriction needed.\n */\nexport const analyticsFields: Field[] = [\n {\n name: 'ga4MeasurementId',\n type: 'text',\n admin: {\n description: 'Google Analytics 4 Measurement ID.',\n placeholder: 'G-XXXXXXXXXX',\n },\n },\n {\n name: 'gtmContainerId',\n type: 'text',\n admin: {\n description: 'Google Tag Manager container ID.',\n placeholder: 'GTM-XXXXXXX',\n },\n },\n]\n"],"names":["analyticsFields","name","type","admin","description","placeholder"],"mappings":"AAEA;;;CAGC,GACD,OAAO,MAAMA,kBAA2B;IACtC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;YACbC,aAAa;QACf;IACF;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;YACbC,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/smtp.js b/dist/globals/SiteIntegrations/fields/smtp.js index 748dc0e..45bc723 100644 --- a/dist/globals/SiteIntegrations/fields/smtp.js +++ b/dist/globals/SiteIntegrations/fields/smtp.js @@ -54,6 +54,6 @@ description: 'Default "from" display name.' } } -]; +]; //# sourceMappingURL=smtp.js.map //# sourceMappingURL=smtp.js.map \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/smtp.js.map b/dist/globals/SiteIntegrations/fields/smtp.js.map index 6ca3457..233b84c 100644 --- a/dist/globals/SiteIntegrations/fields/smtp.js.map +++ b/dist/globals/SiteIntegrations/fields/smtp.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/globals/SiteIntegrations/fields/smtp.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * SMTP transport settings for outbound email.\n *\n * Protected at the global level (SiteIntegrations requires an authenticated\n * user), so all fields — including the password — stay editable in the admin\n * panel while remaining inaccessible to anonymous API requests.\n */\nexport const smtpFields: Field[] = [\n {\n type: 'row',\n fields: [\n {\n name: 'smtpHost',\n type: 'text',\n admin: { placeholder: 'smtp.example.com', width: '70%' },\n },\n {\n name: 'smtpPort',\n type: 'number',\n admin: { width: '30%' },\n defaultValue: 587,\n },\n ],\n },\n {\n name: 'smtpUser',\n type: 'text',\n admin: {\n description: 'SMTP account username.',\n },\n },\n {\n name: 'smtpPassword',\n type: 'text',\n admin: {\n description: 'SMTP account password.',\n },\n },\n {\n name: 'smtpFromAddress',\n type: 'email',\n admin: {\n description: 'Default \"from\" address for outgoing mail.',\n },\n },\n {\n name: 'smtpFromName',\n type: 'text',\n admin: {\n description: 'Default \"from\" display name.',\n },\n },\n]\n"],"names":["smtpFields","type","fields","name","admin","placeholder","width","defaultValue","description"],"mappings":"AAEA;;;;;;CAMC,GACD,OAAO,MAAMA,aAAsB;IACjC;QACEC,MAAM;QACNC,QAAQ;YACN;gBACEC,MAAM;gBACNF,MAAM;gBACNG,OAAO;oBAAEC,aAAa;oBAAoBC,OAAO;gBAAM;YACzD;YACA;gBACEH,MAAM;gBACNF,MAAM;gBACNG,OAAO;oBAAEE,OAAO;gBAAM;gBACtBC,cAAc;YAChB;SACD;IACH;IACA;QACEJ,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;IACA;QACEL,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;IACA;QACEL,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;IACA;QACEL,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["smtp.js"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * SMTP transport settings for outbound email.\n *\n * Protected at the global level (SiteIntegrations requires an authenticated\n * user), so all fields — including the password — stay editable in the admin\n * panel while remaining inaccessible to anonymous API requests.\n */\nexport const smtpFields: Field[] = [\n {\n type: 'row',\n fields: [\n {\n name: 'smtpHost',\n type: 'text',\n admin: { placeholder: 'smtp.example.com', width: '70%' },\n },\n {\n name: 'smtpPort',\n type: 'number',\n admin: { width: '30%' },\n defaultValue: 587,\n },\n ],\n },\n {\n name: 'smtpUser',\n type: 'text',\n admin: {\n description: 'SMTP account username.',\n },\n },\n {\n name: 'smtpPassword',\n type: 'text',\n admin: {\n description: 'SMTP account password.',\n },\n },\n {\n name: 'smtpFromAddress',\n type: 'email',\n admin: {\n description: 'Default \"from\" address for outgoing mail.',\n },\n },\n {\n name: 'smtpFromName',\n type: 'text',\n admin: {\n description: 'Default \"from\" display name.',\n },\n },\n]\n"],"names":["smtpFields","type","fields","name","admin","placeholder","width","defaultValue","description"],"mappings":"AAEA;;;;;;CAMC,GACD,OAAO,MAAMA,aAAsB;IACjC;QACEC,MAAM;QACNC,QAAQ;YACN;gBACEC,MAAM;gBACNF,MAAM;gBACNG,OAAO;oBAAEC,aAAa;oBAAoBC,OAAO;gBAAM;YACzD;YACA;gBACEH,MAAM;gBACNF,MAAM;gBACNG,OAAO;oBAAEE,OAAO;gBAAM;gBACtBC,cAAc;YAChB;SACD;IACH;IACA;QACEJ,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;IACA;QACEL,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;IACA;QACEL,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;IACA;QACEL,MAAM;QACNF,MAAM;QACNG,OAAO;YACLI,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/storage.js b/dist/globals/SiteIntegrations/fields/storage.js index f700fe0..6aef31c 100644 --- a/dist/globals/SiteIntegrations/fields/storage.js +++ b/dist/globals/SiteIntegrations/fields/storage.js @@ -34,6 +34,6 @@ description: 'R2 secret access key.' } } -]; +]; //# sourceMappingURL=storage.js.map //# sourceMappingURL=storage.js.map \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/storage.js.map b/dist/globals/SiteIntegrations/fields/storage.js.map index f84dc37..0754845 100644 --- a/dist/globals/SiteIntegrations/fields/storage.js.map +++ b/dist/globals/SiteIntegrations/fields/storage.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/globals/SiteIntegrations/fields/storage.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Cloudflare R2 storage credentials.\n * Reserved for future use — media offloading to R2.\n *\n * Protected at the global level (SiteIntegrations requires an authenticated\n * user), so the access keys stay editable in the admin panel while remaining\n * inaccessible to anonymous API requests.\n */\nexport const storageFields: Field[] = [\n {\n name: 'r2Bucket',\n type: 'text',\n admin: {\n description: 'R2 bucket name.',\n },\n },\n {\n name: 'r2Endpoint',\n type: 'text',\n admin: {\n description: 'R2 S3-compatible endpoint URL.',\n },\n },\n {\n name: 'r2AccessKeyId',\n type: 'text',\n admin: {\n description: 'R2 access key ID.',\n },\n },\n {\n name: 'r2SecretAccessKey',\n type: 'text',\n admin: {\n description: 'R2 secret access key.',\n },\n },\n]\n"],"names":["storageFields","name","type","admin","description"],"mappings":"AAEA;;;;;;;CAOC,GACD,OAAO,MAAMA,gBAAyB;IACpC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["storage.js"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Cloudflare R2 storage credentials.\n * Reserved for future use — media offloading to R2.\n *\n * Protected at the global level (SiteIntegrations requires an authenticated\n * user), so the access keys stay editable in the admin panel while remaining\n * inaccessible to anonymous API requests.\n */\nexport const storageFields: Field[] = [\n {\n name: 'r2Bucket',\n type: 'text',\n admin: {\n description: 'R2 bucket name.',\n },\n },\n {\n name: 'r2Endpoint',\n type: 'text',\n admin: {\n description: 'R2 S3-compatible endpoint URL.',\n },\n },\n {\n name: 'r2AccessKeyId',\n type: 'text',\n admin: {\n description: 'R2 access key ID.',\n },\n },\n {\n name: 'r2SecretAccessKey',\n type: 'text',\n admin: {\n description: 'R2 secret access key.',\n },\n },\n]\n"],"names":["storageFields","name","type","admin","description"],"mappings":"AAEA;;;;;;;CAOC,GACD,OAAO,MAAMA,gBAAyB;IACpC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/turnstile.js b/dist/globals/SiteIntegrations/fields/turnstile.js index be87901..34c8f57 100644 --- a/dist/globals/SiteIntegrations/fields/turnstile.js +++ b/dist/globals/SiteIntegrations/fields/turnstile.js @@ -20,6 +20,6 @@ description: 'Secret key used for server-side verification.' } } -]; +]; //# sourceMappingURL=turnstile.js.map //# sourceMappingURL=turnstile.js.map \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/fields/turnstile.js.map b/dist/globals/SiteIntegrations/fields/turnstile.js.map index 634b86a..c65d057 100644 --- a/dist/globals/SiteIntegrations/fields/turnstile.js.map +++ b/dist/globals/SiteIntegrations/fields/turnstile.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/globals/SiteIntegrations/fields/turnstile.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Cloudflare Turnstile credentials.\n *\n * siteKey is public (rendered in the widget); secretKey is used for\n * server-side verification. Both are protected at the global level\n * (SiteIntegrations requires an authenticated user) rather than per-field,\n * so they remain editable in the admin panel.\n */\nexport const turnstileFields: Field[] = [\n {\n name: 'turnstileSiteKey',\n type: 'text',\n admin: {\n description: 'Public site key rendered in the Turnstile widget.',\n },\n },\n {\n name: 'turnstileSecretKey',\n type: 'text',\n admin: {\n description: 'Secret key used for server-side verification.',\n },\n },\n]\n"],"names":["turnstileFields","name","type","admin","description"],"mappings":"AAEA;;;;;;;CAOC,GACD,OAAO,MAAMA,kBAA2B;IACtC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["turnstile.js"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Cloudflare Turnstile credentials.\n *\n * siteKey is public (rendered in the widget); secretKey is used for\n * server-side verification. Both are protected at the global level\n * (SiteIntegrations requires an authenticated user) rather than per-field,\n * so they remain editable in the admin panel.\n */\nexport const turnstileFields: Field[] = [\n {\n name: 'turnstileSiteKey',\n type: 'text',\n admin: {\n description: 'Public site key rendered in the Turnstile widget.',\n },\n },\n {\n name: 'turnstileSecretKey',\n type: 'text',\n admin: {\n description: 'Secret key used for server-side verification.',\n },\n },\n]\n"],"names":["turnstileFields","name","type","admin","description"],"mappings":"AAEA;;;;;;;CAOC,GACD,OAAO,MAAMA,kBAA2B;IACtC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;IACF;CACD,CAAA"} \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/index.js b/dist/globals/SiteIntegrations/index.js index caabdad..4e343a0 100644 --- a/dist/globals/SiteIntegrations/index.js +++ b/dist/globals/SiteIntegrations/index.js @@ -57,6 +57,6 @@ import { turnstileFields } from './fields/turnstile.js'; ], label: 'Site Integrations' }; -} +} //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/globals/SiteIntegrations/index.js.map b/dist/globals/SiteIntegrations/index.js.map index 7aa28fa..73766ee 100644 --- a/dist/globals/SiteIntegrations/index.js.map +++ b/dist/globals/SiteIntegrations/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/globals/SiteIntegrations/index.ts"],"sourcesContent":["import type { Field, GlobalConfig } from 'payload'\n\nimport { isAdmin } from '../../modules/access/index.js'\nimport { analyticsFields } from './fields/analytics.js'\nimport { smtpFields } from './fields/smtp.js'\nimport { storageFields } from './fields/storage.js'\nimport { turnstileFields } from './fields/turnstile.js'\n\ntype BuildSiteIntegrationsArgs = {\n /** Extra fields injected by the client project */\n additionalFields?: Field[]\n}\n\n/**\n * Builds the SiteIntegrations global.\n *\n * Holds third-party service credentials. Access is enforced at the global\n * level — the whole global requires an authenticated user — so secrets stay\n * out of anonymous API responses while remaining editable in the admin panel\n * and readable via the server-side Local API. (Field-level read:false was\n * avoided because it also hides fields from the admin UI, making them\n * impossible to enter.)\n *\n * Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).\n */\nexport function buildSiteIntegrations({\n additionalFields,\n}: BuildSiteIntegrationsArgs = {}): GlobalConfig {\n return {\n slug: 'site-integrations',\n access: {\n // Admin-only — secrets live here. Anonymous and non-admin users get\n // nothing through the API; admins read/edit in the panel and via Local API.\n read: ({ req: { user } }) => isAdmin(user),\n update: ({ req: { user } }) => isAdmin(user),\n },\n admin: {\n group: 'Settings',\n },\n fields: [\n {\n type: 'tabs',\n tabs: [\n { fields: analyticsFields, label: 'Analytics' },\n { fields: turnstileFields, label: 'Turnstile' },\n { fields: smtpFields, label: 'SMTP' },\n { fields: storageFields, label: 'Storage' },\n ...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),\n ],\n },\n ],\n label: 'Site Integrations',\n }\n}\n"],"names":["isAdmin","analyticsFields","smtpFields","storageFields","turnstileFields","buildSiteIntegrations","additionalFields","slug","access","read","req","user","update","admin","group","fields","type","tabs","label","length"],"mappings":"AAEA,SAASA,OAAO,QAAQ,gCAA+B;AACvD,SAASC,eAAe,QAAQ,wBAAuB;AACvD,SAASC,UAAU,QAAQ,mBAAkB;AAC7C,SAASC,aAAa,QAAQ,sBAAqB;AACnD,SAASC,eAAe,QAAQ,wBAAuB;AAOvD;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,sBAAsB,EACpCC,gBAAgB,EACU,GAAG,CAAC,CAAC;IAC/B,OAAO;QACLC,MAAM;QACNC,QAAQ;YACN,oEAAoE;YACpE,4EAA4E;YAC5EC,MAAM,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKX,QAAQW;YACrCC,QAAQ,CAAC,EAAEF,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKX,QAAQW;QACzC;QACAE,OAAO;YACLC,OAAO;QACT;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;oBACJ;wBAAEF,QAAQd;wBAAiBiB,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQX;wBAAiBc,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQb;wBAAYgB,OAAO;oBAAO;oBACpC;wBAAEH,QAAQZ;wBAAee,OAAO;oBAAU;uBACtCZ,kBAAkBa,SAAS;wBAAC;4BAAEJ,QAAQT;4BAAkBY,OAAO;wBAAS;qBAAE,GAAG,EAAE;iBACpF;YACH;SACD;QACDA,OAAO;IACT;AACF"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["import type { Field, GlobalConfig } from 'payload'\n\nimport { isAdmin } from '../../modules/access/index.js'\nimport { analyticsFields } from './fields/analytics.js'\nimport { smtpFields } from './fields/smtp.js'\nimport { storageFields } from './fields/storage.js'\nimport { turnstileFields } from './fields/turnstile.js'\n\ntype BuildSiteIntegrationsArgs = {\n /** Extra fields injected by the client project */\n additionalFields?: Field[]\n}\n\n/**\n * Builds the SiteIntegrations global.\n *\n * Holds third-party service credentials. Access is enforced at the global\n * level — the whole global requires an authenticated user — so secrets stay\n * out of anonymous API responses while remaining editable in the admin panel\n * and readable via the server-side Local API. (Field-level read:false was\n * avoided because it also hides fields from the admin UI, making them\n * impossible to enter.)\n *\n * Unnamed tabs keep data flat (siteIntegrations.ga4MeasurementId).\n */\nexport function buildSiteIntegrations({\n additionalFields,\n}: BuildSiteIntegrationsArgs = {}): GlobalConfig {\n return {\n slug: 'site-integrations',\n access: {\n // Admin-only — secrets live here. Anonymous and non-admin users get\n // nothing through the API; admins read/edit in the panel and via Local API.\n read: ({ req: { user } }) => isAdmin(user),\n update: ({ req: { user } }) => isAdmin(user),\n },\n admin: {\n group: 'Settings',\n },\n fields: [\n {\n type: 'tabs',\n tabs: [\n { fields: analyticsFields, label: 'Analytics' },\n { fields: turnstileFields, label: 'Turnstile' },\n { fields: smtpFields, label: 'SMTP' },\n { fields: storageFields, label: 'Storage' },\n ...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),\n ],\n },\n ],\n label: 'Site Integrations',\n }\n}\n"],"names":["isAdmin","analyticsFields","smtpFields","storageFields","turnstileFields","buildSiteIntegrations","additionalFields","slug","access","read","req","user","update","admin","group","fields","type","tabs","label","length"],"mappings":"AAEA,SAASA,OAAO,QAAQ,gCAA+B;AACvD,SAASC,eAAe,QAAQ,wBAAuB;AACvD,SAASC,UAAU,QAAQ,mBAAkB;AAC7C,SAASC,aAAa,QAAQ,sBAAqB;AACnD,SAASC,eAAe,QAAQ,wBAAuB;AAOvD;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,sBAAsB,EACpCC,gBAAgB,EACU,GAAG,CAAC,CAAC;IAC/B,OAAO;QACLC,MAAM;QACNC,QAAQ;YACN,oEAAoE;YACpE,4EAA4E;YAC5EC,MAAM,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKX,QAAQW;YACrCC,QAAQ,CAAC,EAAEF,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKX,QAAQW;QACzC;QACAE,OAAO;YACLC,OAAO;QACT;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;oBACJ;wBAAEF,QAAQd;wBAAiBiB,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQX;wBAAiBc,OAAO;oBAAY;oBAC9C;wBAAEH,QAAQb;wBAAYgB,OAAO;oBAAO;oBACpC;wBAAEH,QAAQZ;wBAAee,OAAO;oBAAU;uBACtCZ,kBAAkBa,SAAS;wBAAC;4BAAEJ,QAAQT;4BAAkBY,OAAO;wBAAS;qBAAE,GAAG,EAAE;iBACpF;YACH;SACD;QACDA,OAAO;IACT;AACF"} \ No newline at end of file diff --git a/dist/globals/SiteSettings/fields/general.js b/dist/globals/SiteSettings/fields/general.js index 50f4797..366459f 100644 --- a/dist/globals/SiteSettings/fields/general.js +++ b/dist/globals/SiteSettings/fields/general.js @@ -83,6 +83,6 @@ }, relationTo: 'media' } -]; +]; //# sourceMappingURL=general.js.map //# sourceMappingURL=general.js.map \ No newline at end of file diff --git a/dist/globals/SiteSettings/fields/general.js.map b/dist/globals/SiteSettings/fields/general.js.map index 003be51..b5b5dd5 100644 --- a/dist/globals/SiteSettings/fields/general.js.map +++ b/dist/globals/SiteSettings/fields/general.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/globals/SiteSettings/fields/general.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * General site identity fields.\n * Consumed by SEO/OG (siteName, defaultShareImage) and frontend (logo).\n */\nexport const generalFields: Field[] = [\n {\n name: 'siteName',\n type: 'text',\n admin: {\n description: 'Used in page titles and Open Graph metadata.',\n },\n localized: true,\n required: true,\n },\n {\n name: 'titleOrder',\n type: 'select',\n admin: {\n description: 'Which comes first in browser tabs.',\n },\n defaultValue: 'page-first',\n options: [\n { label: 'Page first — About Us | Acme', value: 'page-first' },\n { label: 'Site first — Acme | About Us', value: 'site-first' },\n ],\n },\n {\n name: 'titleSeparator',\n type: 'select',\n admin: {\n description: 'Separates the page title from the site name in browser tabs.',\n },\n defaultValue: '|',\n options: [\n { label: 'Pipe — Page | Site', value: '|' },\n { label: 'Dash — Page – Site', value: '–' },\n { label: 'Hyphen — Page - Site', value: '-' },\n { label: 'Bullet — Page · Site', value: '·' },\n { label: 'Slash — Page / Site', value: '/' },\n ],\n },\n {\n name: 'logo',\n type: 'upload',\n admin: {\n description: 'Primary site logo.',\n },\n relationTo: 'media',\n },\n {\n name: 'defaultShareImage',\n type: 'upload',\n admin: {\n description: 'Fallback Open Graph image when a page has none.',\n },\n relationTo: 'media',\n },\n {\n name: 'favicon',\n type: 'upload',\n admin: {\n description: 'Square source icon (PNG or SVG) for the browser tab. Rendered by the frontend.',\n },\n relationTo: 'media',\n },\n]\n"],"names":["generalFields","name","type","admin","description","localized","required","defaultValue","options","label","value","relationTo"],"mappings":"AAEA;;;CAGC,GACD,OAAO,MAAMA,gBAAyB;IACpC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,WAAW;QACXC,UAAU;IACZ;IACA;QACEL,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAG,cAAc;QACdC,SAAS;YACP;gBAAEC,OAAO;gBAAkCC,OAAO;YAAa;YAC/D;gBAAED,OAAO;gBAAkCC,OAAO;YAAa;SAChE;IACH;IACA;QACET,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAG,cAAc;QACdC,SAAS;YACP;gBAAEC,OAAO;gBAAwBC,OAAO;YAAI;YAC5C;gBAAED,OAAO;gBAAwBC,OAAO;YAAI;YAC5C;gBAAED,OAAO;gBAA0BC,OAAO;YAAI;YAC9C;gBAAED,OAAO;gBAA0BC,OAAO;YAAI;YAC9C;gBAAED,OAAO;gBAAyBC,OAAO;YAAI;SAC9C;IACH;IACA;QACET,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAO,YAAY;IACd;IACA;QACEV,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAO,YAAY;IACd;IACA;QACEV,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAO,YAAY;IACd;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["general.js"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * General site identity fields.\n * Consumed by SEO/OG (siteName, defaultShareImage) and frontend (logo).\n */\nexport const generalFields: Field[] = [\n {\n name: 'siteName',\n type: 'text',\n admin: {\n description: 'Used in page titles and Open Graph metadata.',\n },\n localized: true,\n required: true,\n },\n {\n name: 'titleOrder',\n type: 'select',\n admin: {\n description: 'Which comes first in browser tabs.',\n },\n defaultValue: 'page-first',\n options: [\n { label: 'Page first — About Us | Acme', value: 'page-first' },\n { label: 'Site first — Acme | About Us', value: 'site-first' },\n ],\n },\n {\n name: 'titleSeparator',\n type: 'select',\n admin: {\n description: 'Separates the page title from the site name in browser tabs.',\n },\n defaultValue: '|',\n options: [\n { label: 'Pipe — Page | Site', value: '|' },\n { label: 'Dash — Page – Site', value: '–' },\n { label: 'Hyphen — Page - Site', value: '-' },\n { label: 'Bullet — Page · Site', value: '·' },\n { label: 'Slash — Page / Site', value: '/' },\n ],\n },\n {\n name: 'logo',\n type: 'upload',\n admin: {\n description: 'Primary site logo.',\n },\n relationTo: 'media',\n },\n {\n name: 'defaultShareImage',\n type: 'upload',\n admin: {\n description: 'Fallback Open Graph image when a page has none.',\n },\n relationTo: 'media',\n },\n {\n name: 'favicon',\n type: 'upload',\n admin: {\n description: 'Square source icon (PNG or SVG) for the browser tab. Rendered by the frontend.',\n },\n relationTo: 'media',\n },\n]\n"],"names":["generalFields","name","type","admin","description","localized","required","defaultValue","options","label","value","relationTo"],"mappings":"AAEA;;;CAGC,GACD,OAAO,MAAMA,gBAAyB;IACpC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,WAAW;QACXC,UAAU;IACZ;IACA;QACEL,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAG,cAAc;QACdC,SAAS;YACP;gBAAEC,OAAO;gBAAkCC,OAAO;YAAa;YAC/D;gBAAED,OAAO;gBAAkCC,OAAO;YAAa;SAChE;IACH;IACA;QACET,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAG,cAAc;QACdC,SAAS;YACP;gBAAEC,OAAO;gBAAwBC,OAAO;YAAI;YAC5C;gBAAED,OAAO;gBAAwBC,OAAO;YAAI;YAC5C;gBAAED,OAAO;gBAA0BC,OAAO;YAAI;YAC9C;gBAAED,OAAO;gBAA0BC,OAAO;YAAI;YAC9C;gBAAED,OAAO;gBAAyBC,OAAO;YAAI;SAC9C;IACH;IACA;QACET,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAO,YAAY;IACd;IACA;QACEV,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAO,YAAY;IACd;IACA;QACEV,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAO,YAAY;IACd;CACD,CAAA"} \ No newline at end of file diff --git a/dist/globals/SiteSettings/fields/navigation.js b/dist/globals/SiteSettings/fields/navigation.js index 0dd88ba..1eda2dc 100644 --- a/dist/globals/SiteSettings/fields/navigation.js +++ b/dist/globals/SiteSettings/fields/navigation.js @@ -1,2 +1,3 @@ +//# sourceMappingURL=navigation.js.map //# sourceMappingURL=navigation.js.map \ No newline at end of file diff --git a/dist/globals/SiteSettings/fields/navigation.js.map b/dist/globals/SiteSettings/fields/navigation.js.map index 93c4e97..dcf6bb9 100644 --- a/dist/globals/SiteSettings/fields/navigation.js.map +++ b/dist/globals/SiteSettings/fields/navigation.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/globals/SiteSettings/fields/navigation.ts"],"names":[],"mappings":""} \ No newline at end of file +{"version":3,"sources":["navigation.js"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/globals/SiteSettings/fields/theme.js b/dist/globals/SiteSettings/fields/theme.js index f434b1e..9c6638d 100644 --- a/dist/globals/SiteSettings/fields/theme.js +++ b/dist/globals/SiteSettings/fields/theme.js @@ -32,6 +32,6 @@ }, defaultValue: true } -]; +]; //# sourceMappingURL=theme.js.map //# sourceMappingURL=theme.js.map \ No newline at end of file diff --git a/dist/globals/SiteSettings/fields/theme.js.map b/dist/globals/SiteSettings/fields/theme.js.map index 506a34b..0cd5b38 100644 --- a/dist/globals/SiteSettings/fields/theme.js.map +++ b/dist/globals/SiteSettings/fields/theme.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/globals/SiteSettings/fields/theme.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Theme behavior configuration.\n *\n * These are decisions, not visuals — the plugin stores them, the client\n * template reads them and decides whether to render a toggle and which\n * mode to start in. Appearance itself stays in the template.\n */\nexport const themeFields: Field[] = [\n {\n name: 'defaultTheme',\n type: 'select',\n admin: {\n description: 'Theme applied on a visitor’s first visit.',\n },\n defaultValue: 'light',\n options: [\n { label: 'Light', value: 'light' },\n { label: 'Dark', value: 'dark' },\n ],\n required: true,\n },\n {\n name: 'allowThemeToggle',\n type: 'checkbox',\n admin: {\n description: 'Show a light/dark switch on the site.',\n },\n defaultValue: true,\n },\n]\n"],"names":["themeFields","name","type","admin","description","defaultValue","options","label","value","required"],"mappings":"AAEA;;;;;;CAMC,GACD,OAAO,MAAMA,cAAuB;IAClC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,cAAc;QACdC,SAAS;YACP;gBAAEC,OAAO;gBAASC,OAAO;YAAQ;YACjC;gBAAED,OAAO;gBAAQC,OAAO;YAAO;SAChC;QACDC,UAAU;IACZ;IACA;QACER,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,cAAc;IAChB;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["theme.js"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Theme behavior configuration.\n *\n * These are decisions, not visuals — the plugin stores them, the client\n * template reads them and decides whether to render a toggle and which\n * mode to start in. Appearance itself stays in the template.\n */\nexport const themeFields: Field[] = [\n {\n name: 'defaultTheme',\n type: 'select',\n admin: {\n description: 'Theme applied on a visitor’s first visit.',\n },\n defaultValue: 'light',\n options: [\n { label: 'Light', value: 'light' },\n { label: 'Dark', value: 'dark' },\n ],\n required: true,\n },\n {\n name: 'allowThemeToggle',\n type: 'checkbox',\n admin: {\n description: 'Show a light/dark switch on the site.',\n },\n defaultValue: true,\n },\n]\n"],"names":["themeFields","name","type","admin","description","defaultValue","options","label","value","required"],"mappings":"AAEA;;;;;;CAMC,GACD,OAAO,MAAMA,cAAuB;IAClC;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,cAAc;QACdC,SAAS;YACP;gBAAEC,OAAO;gBAASC,OAAO;YAAQ;YACjC;gBAAED,OAAO;gBAAQC,OAAO;YAAO;SAChC;QACDC,UAAU;IACZ;IACA;QACER,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAC,cAAc;IAChB;CACD,CAAA"} \ No newline at end of file diff --git a/dist/globals/SiteSettings/index.js b/dist/globals/SiteSettings/index.js index 613c0f2..3455e0b 100644 --- a/dist/globals/SiteSettings/index.js +++ b/dist/globals/SiteSettings/index.js @@ -50,6 +50,6 @@ import { themeFields } from './fields/theme.js'; ], label: 'Site Settings' }; -} +} //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/globals/SiteSettings/index.js.map b/dist/globals/SiteSettings/index.js.map index 9e4d89f..9b929b7 100644 --- a/dist/globals/SiteSettings/index.js.map +++ b/dist/globals/SiteSettings/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/globals/SiteSettings/index.ts"],"sourcesContent":["import type { Field, GlobalConfig } from 'payload'\n\nimport { generalFields } from './fields/general.js'\nimport { themeFields } from './fields/theme.js'\n\ntype BuildSiteSettingsArgs = {\n /** Extra fields injected by the client project */\n additionalFields?: Field[]\n /**\n * Fields for the \"System Pages\" tab — page assignments by role (homepage,\n * privacy policy, collection archives). Assembled by the caller, which is the\n * only place that knows which modules are enabled. The tab is omitted when\n * this is empty.\n */\n systemPageFields?: Field[]\n}\n\n/**\n * Builds the SiteSettings global.\n *\n * Composes tabs out of field groups; it deliberately doesn't know where those\n * groups come from. Earlier it imported the pages and content modules directly,\n * which meant a global — a presentation-layer concern — had an opinion about\n * blogs. Now the plugin's composition root passes fields in, and adding a new\n * kind of page assignment needs no change here.\n *\n * Uses unnamed tabs — data stays flat (siteSettings.siteName, not\n * siteSettings.general.siteName). Client-provided fields land in their own\n * \"Custom\" tab so core data paths never shift.\n */\nexport function buildSiteSettings({\n additionalFields,\n systemPageFields,\n}: BuildSiteSettingsArgs = {}): GlobalConfig {\n return {\n slug: 'site-settings',\n access: {\n read: () => true,\n },\n admin: {\n group: 'Settings',\n },\n fields: [\n {\n type: 'tabs',\n tabs: [\n {\n fields: generalFields,\n label: 'General',\n },\n {\n fields: themeFields,\n label: 'Theme',\n },\n ...(systemPageFields?.length\n ? [{ fields: systemPageFields, label: 'System Pages' }]\n : []),\n ...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),\n ],\n },\n ],\n label: 'Site Settings',\n }\n}\n"],"names":["generalFields","themeFields","buildSiteSettings","additionalFields","systemPageFields","slug","access","read","admin","group","fields","type","tabs","label","length"],"mappings":"AAEA,SAASA,aAAa,QAAQ,sBAAqB;AACnD,SAASC,WAAW,QAAQ,oBAAmB;AAc/C;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,kBAAkB,EAChCC,gBAAgB,EAChBC,gBAAgB,EACM,GAAG,CAAC,CAAC;IAC3B,OAAO;QACLC,MAAM;QACNC,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,OAAO;YACLC,OAAO;QACT;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;oBACJ;wBACEF,QAAQV;wBACRa,OAAO;oBACT;oBACA;wBACEH,QAAQT;wBACRY,OAAO;oBACT;uBACIT,kBAAkBU,SAClB;wBAAC;4BAAEJ,QAAQN;4BAAkBS,OAAO;wBAAe;qBAAE,GACrD,EAAE;uBACFV,kBAAkBW,SAAS;wBAAC;4BAAEJ,QAAQP;4BAAkBU,OAAO;wBAAS;qBAAE,GAAG,EAAE;iBACpF;YACH;SACD;QACDA,OAAO;IACT;AACF"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["import type { Field, GlobalConfig } from 'payload'\n\nimport { generalFields } from './fields/general.js'\nimport { themeFields } from './fields/theme.js'\n\ntype BuildSiteSettingsArgs = {\n /** Extra fields injected by the client project */\n additionalFields?: Field[]\n /**\n * Fields for the \"System Pages\" tab — page assignments by role (homepage,\n * privacy policy, collection archives). Assembled by the caller, which is the\n * only place that knows which modules are enabled. The tab is omitted when\n * this is empty.\n */\n systemPageFields?: Field[]\n}\n\n/**\n * Builds the SiteSettings global.\n *\n * Composes tabs out of field groups; it deliberately doesn't know where those\n * groups come from. Earlier it imported the pages and content modules directly,\n * which meant a global — a presentation-layer concern — had an opinion about\n * blogs. Now the plugin's composition root passes fields in, and adding a new\n * kind of page assignment needs no change here.\n *\n * Uses unnamed tabs — data stays flat (siteSettings.siteName, not\n * siteSettings.general.siteName). Client-provided fields land in their own\n * \"Custom\" tab so core data paths never shift.\n */\nexport function buildSiteSettings({\n additionalFields,\n systemPageFields,\n}: BuildSiteSettingsArgs = {}): GlobalConfig {\n return {\n slug: 'site-settings',\n access: {\n read: () => true,\n },\n admin: {\n group: 'Settings',\n },\n fields: [\n {\n type: 'tabs',\n tabs: [\n {\n fields: generalFields,\n label: 'General',\n },\n {\n fields: themeFields,\n label: 'Theme',\n },\n ...(systemPageFields?.length\n ? [{ fields: systemPageFields, label: 'System Pages' }]\n : []),\n ...(additionalFields?.length ? [{ fields: additionalFields, label: 'Custom' }] : []),\n ],\n },\n ],\n label: 'Site Settings',\n }\n}\n"],"names":["generalFields","themeFields","buildSiteSettings","additionalFields","systemPageFields","slug","access","read","admin","group","fields","type","tabs","label","length"],"mappings":"AAEA,SAASA,aAAa,QAAQ,sBAAqB;AACnD,SAASC,WAAW,QAAQ,oBAAmB;AAc/C;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,kBAAkB,EAChCC,gBAAgB,EAChBC,gBAAgB,EACM,GAAG,CAAC,CAAC;IAC3B,OAAO;QACLC,MAAM;QACNC,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,OAAO;YACLC,OAAO;QACT;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;oBACJ;wBACEF,QAAQV;wBACRa,OAAO;oBACT;oBACA;wBACEH,QAAQT;wBACRY,OAAO;oBACT;uBACIT,kBAAkBU,SAClB;wBAAC;4BAAEJ,QAAQN;4BAAkBS,OAAO;wBAAe;qBAAE,GACrD,EAAE;uBACFV,kBAAkBW,SAAS;wBAAC;4BAAEJ,QAAQP;4BAAkBU,OAAO;wBAAS;qBAAE,GAAG,EAAE;iBACpF;YACH;SACD;QACDA,OAAO;IACT;AACF"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 8ac3b44..3098673 100644 --- a/dist/index.js +++ b/dist/index.js @@ -15,6 +15,6 @@ export { getGlobal, getSiteIntegrations, getSiteSettings, SITE_INTEGRATIONS_SLUG export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js'; export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta } from './modules/seo/index.js'; export { buildSlugField, toSlug } from './modules/slug/index.js'; -export { ipalKit } from './plugin.js'; +export { ipalKit } from './plugin.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map index b8b0389..996c7cc 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type { AccessOption, Role } from './modules/access/index.js'\nexport {\n adminOnly,\n adminOnlyField,\n adminOrEditor,\n adminOrEditorField,\n adminOrSelf,\n authenticated,\n hasMinimumRole,\n isAdmin,\n isEditor,\n requireRole,\n requireRoleField,\n ROLE_HIERARCHY,\n} from './modules/access/index.js'\nexport type { AnalyticsConfig } from './modules/analytics/index.js'\nexport { getAnalyticsConfig } from './modules/analytics/index.js'\nexport {\n ACCEPT_ALL_CONSENT,\n CONSENT_CATEGORIES,\n CONSENT_COOKIE,\n CONSENT_MAX_AGE,\n CONSENT_VERSION,\n DEFAULT_CONSENT,\n getConsentTexts,\n parseConsent,\n REJECT_ALL_CONSENT,\n serializeConsent,\n setDefaultConsent,\n updateConsent,\n} from './modules/consent/index.js'\nexport type { ConsentCategory, ConsentState, ConsentTexts } from './modules/consent/index.js'\nexport type {\n ContentCollectionOption,\n ContentOption,\n ResolvedRoute,\n} from './modules/content/index.js'\nexport {\n archiveFieldName,\n buildArchivePath,\n buildEntryPath,\n getArchiveEntries,\n parsePageParam,\n resolveRoute,\n} from './modules/content/index.js'\nexport type { ArchiveEntries } from './modules/content/index.js'\n// Imported straight from the file, NOT from ./modules/email/index.js — that\n// barrel re-exports sendEmail, which imports 'server-only' and would crash when\n// Payload loads the config (or runs generate:importmap) as a plain Node script.\nexport { panelSmtpAdapter } from './modules/email/panelSmtpAdapter.js'\nexport type { PanelSmtpAdapterArgs } from './modules/email/panelSmtpAdapter.js'\nexport { buildFormsPlugin } from './modules/forms/formsPluginConfig.js'\nexport type {\n FormsCollectionOverrides,\n FormsFieldsOverride,\n FormsOption,\n} from './modules/forms/types.js'\nexport { createContentHelpers } from './modules/frontend/index.js'\nexport type { I18nConfig, LocaleDefinition, LocalizedSlugs } from './modules/i18n/index.js'\nexport {\n buildLocalizedPath,\n getDefaultLocale,\n getLocaleCodes,\n getLocaleDefinition,\n getLocalizedSlugs,\n isValidLocale,\n LOCALE_COOKIE_NAME,\n matchAcceptLanguage,\n negotiateLocale,\n switchLocalePath,\n} from './modules/i18n/index.js'\nexport type { LocaleMiddlewareResult } from './modules/i18n/index.js'\nexport { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js'\nexport type { PagesOption, SystemPageRole } from './modules/pages/index.js'\nexport { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js'\nexport type { GlobalQueryOptions } from './modules/payload/index.js'\nexport {\n getGlobal,\n getSiteIntegrations,\n getSiteSettings,\n SITE_INTEGRATIONS_SLUG,\n SITE_SETTINGS_SLUG,\n} from './modules/payload/index.js'\nexport type { PageMetadata, SeoMeta, SeoOption } from './modules/seo/index.js'\nexport { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js'\nexport type { AutoFillMapping, RobotsRules, SitemapEntry } from './modules/seo/index.js'\nexport {\n buildAutoFillMetaHook,\n buildRobots,\n buildSitemapEntries,\n createMetadataGenerator,\n createPageMetadata,\n injectAutoFillMeta,\n} from './modules/seo/index.js'\nexport { buildSlugField, toSlug } from './modules/slug/index.js'\nexport { ipalKit } from './plugin.js'\nexport type { IpalOptions } from './types.js'\n"],"names":["adminOnly","adminOnlyField","adminOrEditor","adminOrEditorField","adminOrSelf","authenticated","hasMinimumRole","isAdmin","isEditor","requireRole","requireRoleField","ROLE_HIERARCHY","getAnalyticsConfig","ACCEPT_ALL_CONSENT","CONSENT_CATEGORIES","CONSENT_COOKIE","CONSENT_MAX_AGE","CONSENT_VERSION","DEFAULT_CONSENT","getConsentTexts","parseConsent","REJECT_ALL_CONSENT","serializeConsent","setDefaultConsent","updateConsent","archiveFieldName","buildArchivePath","buildEntryPath","getArchiveEntries","parsePageParam","resolveRoute","panelSmtpAdapter","buildFormsPlugin","createContentHelpers","buildLocalizedPath","getDefaultLocale","getLocaleCodes","getLocaleDefinition","getLocalizedSlugs","isValidLocale","LOCALE_COOKIE_NAME","matchAcceptLanguage","negotiateLocale","switchLocalePath","createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER","ALL_SYSTEM_PAGE_ROLES","getSystemPagePath","getGlobal","getSiteIntegrations","getSiteSettings","SITE_INTEGRATIONS_SLUG","SITE_SETTINGS_SLUG","buildHreflangAlternates","buildMetadata","composeTitle","buildAutoFillMetaHook","buildRobots","buildSitemapEntries","createMetadataGenerator","createPageMetadata","injectAutoFillMeta","buildSlugField","toSlug","ipalKit"],"mappings":"AACA,SACEA,SAAS,EACTC,cAAc,EACdC,aAAa,EACbC,kBAAkB,EAClBC,WAAW,EACXC,aAAa,EACbC,cAAc,EACdC,OAAO,EACPC,QAAQ,EACRC,WAAW,EACXC,gBAAgB,EAChBC,cAAc,QACT,4BAA2B;AAElC,SAASC,kBAAkB,QAAQ,+BAA8B;AACjE,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,cAAc,EACdC,eAAe,EACfC,eAAe,EACfC,eAAe,EACfC,eAAe,EACfC,YAAY,EACZC,kBAAkB,EAClBC,gBAAgB,EAChBC,iBAAiB,EACjBC,aAAa,QACR,6BAA4B;AAOnC,SACEC,gBAAgB,EAChBC,gBAAgB,EAChBC,cAAc,EACdC,iBAAiB,EACjBC,cAAc,EACdC,YAAY,QACP,6BAA4B;AAEnC,4EAA4E;AAC5E,gFAAgF;AAChF,gFAAgF;AAChF,SAASC,gBAAgB,QAAQ,sCAAqC;AAEtE,SAASC,gBAAgB,QAAQ,uCAAsC;AAMvE,SAASC,oBAAoB,QAAQ,8BAA6B;AAElE,SACEC,kBAAkB,EAClBC,gBAAgB,EAChBC,cAAc,EACdC,mBAAmB,EACnBC,iBAAiB,EACjBC,aAAa,EACbC,kBAAkB,EAClBC,mBAAmB,EACnBC,eAAe,EACfC,gBAAgB,QACX,0BAAyB;AAEhC,SAASC,sBAAsB,EAAEC,0BAA0B,QAAQ,0BAAyB;AAE5F,SAASC,qBAAqB,EAAEC,iBAAiB,QAAQ,2BAA0B;AAEnF,SACEC,SAAS,EACTC,mBAAmB,EACnBC,eAAe,EACfC,sBAAsB,EACtBC,kBAAkB,QACb,6BAA4B;AAEnC,SAASC,uBAAuB,EAAEC,aAAa,EAAEC,YAAY,QAAQ,yBAAwB;AAE7F,SACEC,qBAAqB,EACrBC,WAAW,EACXC,mBAAmB,EACnBC,uBAAuB,EACvBC,kBAAkB,EAClBC,kBAAkB,QACb,yBAAwB;AAC/B,SAASC,cAAc,EAAEC,MAAM,QAAQ,0BAAyB;AAChE,SAASC,OAAO,QAAQ,cAAa"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export type { AccessOption, Role } from './modules/access/index.js'\nexport {\n adminOnly,\n adminOnlyField,\n adminOrEditor,\n adminOrEditorField,\n adminOrSelf,\n authenticated,\n hasMinimumRole,\n isAdmin,\n isEditor,\n requireRole,\n requireRoleField,\n ROLE_HIERARCHY,\n} from './modules/access/index.js'\nexport type { AnalyticsConfig } from './modules/analytics/index.js'\nexport { getAnalyticsConfig } from './modules/analytics/index.js'\nexport {\n ACCEPT_ALL_CONSENT,\n CONSENT_CATEGORIES,\n CONSENT_COOKIE,\n CONSENT_MAX_AGE,\n CONSENT_VERSION,\n DEFAULT_CONSENT,\n getConsentTexts,\n parseConsent,\n REJECT_ALL_CONSENT,\n serializeConsent,\n setDefaultConsent,\n updateConsent,\n} from './modules/consent/index.js'\nexport type { ConsentCategory, ConsentState, ConsentTexts } from './modules/consent/index.js'\nexport type {\n ContentCollectionOption,\n ContentOption,\n ResolvedRoute,\n} from './modules/content/index.js'\nexport {\n archiveFieldName,\n buildArchivePath,\n buildEntryPath,\n getArchiveEntries,\n parsePageParam,\n resolveRoute,\n} from './modules/content/index.js'\nexport type { ArchiveEntries } from './modules/content/index.js'\n// Imported straight from the file, NOT from ./modules/email/index.js — that\n// barrel re-exports sendEmail, which imports 'server-only' and would crash when\n// Payload loads the config (or runs generate:importmap) as a plain Node script.\nexport { panelSmtpAdapter } from './modules/email/panelSmtpAdapter.js'\nexport type { PanelSmtpAdapterArgs } from './modules/email/panelSmtpAdapter.js'\nexport { buildFormsPlugin } from './modules/forms/formsPluginConfig.js'\nexport type {\n FormsCollectionOverrides,\n FormsFieldsOverride,\n FormsOption,\n} from './modules/forms/types.js'\nexport { createContentHelpers } from './modules/frontend/index.js'\nexport type { I18nConfig, LocaleDefinition, LocalizedSlugs } from './modules/i18n/index.js'\nexport {\n buildLocalizedPath,\n getDefaultLocale,\n getLocaleCodes,\n getLocaleDefinition,\n getLocalizedSlugs,\n isValidLocale,\n LOCALE_COOKIE_NAME,\n matchAcceptLanguage,\n negotiateLocale,\n switchLocalePath,\n} from './modules/i18n/index.js'\nexport type { LocaleMiddlewareResult } from './modules/i18n/index.js'\nexport { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './modules/i18n/index.js'\nexport type { PagesOption, SystemPageRole } from './modules/pages/index.js'\nexport { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js'\nexport type { GlobalQueryOptions } from './modules/payload/index.js'\nexport {\n getGlobal,\n getSiteIntegrations,\n getSiteSettings,\n SITE_INTEGRATIONS_SLUG,\n SITE_SETTINGS_SLUG,\n} from './modules/payload/index.js'\nexport type { PageMetadata, SeoMeta, SeoOption } from './modules/seo/index.js'\nexport { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js'\nexport type { AutoFillMapping, RobotsRules, SitemapEntry } from './modules/seo/index.js'\nexport {\n buildAutoFillMetaHook,\n buildRobots,\n buildSitemapEntries,\n createMetadataGenerator,\n createPageMetadata,\n injectAutoFillMeta,\n} from './modules/seo/index.js'\nexport { buildSlugField, toSlug } from './modules/slug/index.js'\nexport { ipalKit } from './plugin.js'\nexport type { IpalOptions } from './types.js'\n"],"names":["adminOnly","adminOnlyField","adminOrEditor","adminOrEditorField","adminOrSelf","authenticated","hasMinimumRole","isAdmin","isEditor","requireRole","requireRoleField","ROLE_HIERARCHY","getAnalyticsConfig","ACCEPT_ALL_CONSENT","CONSENT_CATEGORIES","CONSENT_COOKIE","CONSENT_MAX_AGE","CONSENT_VERSION","DEFAULT_CONSENT","getConsentTexts","parseConsent","REJECT_ALL_CONSENT","serializeConsent","setDefaultConsent","updateConsent","archiveFieldName","buildArchivePath","buildEntryPath","getArchiveEntries","parsePageParam","resolveRoute","panelSmtpAdapter","buildFormsPlugin","createContentHelpers","buildLocalizedPath","getDefaultLocale","getLocaleCodes","getLocaleDefinition","getLocalizedSlugs","isValidLocale","LOCALE_COOKIE_NAME","matchAcceptLanguage","negotiateLocale","switchLocalePath","createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER","ALL_SYSTEM_PAGE_ROLES","getSystemPagePath","getGlobal","getSiteIntegrations","getSiteSettings","SITE_INTEGRATIONS_SLUG","SITE_SETTINGS_SLUG","buildHreflangAlternates","buildMetadata","composeTitle","buildAutoFillMetaHook","buildRobots","buildSitemapEntries","createMetadataGenerator","createPageMetadata","injectAutoFillMeta","buildSlugField","toSlug","ipalKit"],"mappings":"AACA,SACEA,SAAS,EACTC,cAAc,EACdC,aAAa,EACbC,kBAAkB,EAClBC,WAAW,EACXC,aAAa,EACbC,cAAc,EACdC,OAAO,EACPC,QAAQ,EACRC,WAAW,EACXC,gBAAgB,EAChBC,cAAc,QACT,4BAA2B;AAElC,SAASC,kBAAkB,QAAQ,+BAA8B;AACjE,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,cAAc,EACdC,eAAe,EACfC,eAAe,EACfC,eAAe,EACfC,eAAe,EACfC,YAAY,EACZC,kBAAkB,EAClBC,gBAAgB,EAChBC,iBAAiB,EACjBC,aAAa,QACR,6BAA4B;AAOnC,SACEC,gBAAgB,EAChBC,gBAAgB,EAChBC,cAAc,EACdC,iBAAiB,EACjBC,cAAc,EACdC,YAAY,QACP,6BAA4B;AAEnC,4EAA4E;AAC5E,gFAAgF;AAChF,gFAAgF;AAChF,SAASC,gBAAgB,QAAQ,sCAAqC;AAEtE,SAASC,gBAAgB,QAAQ,uCAAsC;AAMvE,SAASC,oBAAoB,QAAQ,8BAA6B;AAElE,SACEC,kBAAkB,EAClBC,gBAAgB,EAChBC,cAAc,EACdC,mBAAmB,EACnBC,iBAAiB,EACjBC,aAAa,EACbC,kBAAkB,EAClBC,mBAAmB,EACnBC,eAAe,EACfC,gBAAgB,QACX,0BAAyB;AAEhC,SAASC,sBAAsB,EAAEC,0BAA0B,QAAQ,0BAAyB;AAE5F,SAASC,qBAAqB,EAAEC,iBAAiB,QAAQ,2BAA0B;AAEnF,SACEC,SAAS,EACTC,mBAAmB,EACnBC,eAAe,EACfC,sBAAsB,EACtBC,kBAAkB,QACb,6BAA4B;AAEnC,SAASC,uBAAuB,EAAEC,aAAa,EAAEC,YAAY,QAAQ,yBAAwB;AAE7F,SACEC,qBAAqB,EACrBC,WAAW,EACXC,mBAAmB,EACnBC,uBAAuB,EACvBC,kBAAkB,EAClBC,kBAAkB,QACb,yBAAwB;AAC/B,SAASC,cAAc,EAAEC,MAAM,QAAQ,0BAAyB;AAChE,SAASC,OAAO,QAAQ,cAAa"} \ No newline at end of file diff --git a/dist/modules/access/access.js b/dist/modules/access/access.js index 16e56d0..448fda7 100644 --- a/dist/modules/access/access.js +++ b/dist/modules/access/access.js @@ -24,6 +24,6 @@ export const authenticated = ({ req: { user } })=>Boolean(user); * Use in field `access.read/update`. */ export const adminOnlyField = ({ req: { user } })=>isAdmin(user); export const adminOrEditorField = ({ req: { user } })=>hasMinimumRole(user, 'editor'); -/** Requires at least the given role, for field-level access. */ export const requireRoleField = (minimum)=>({ req: { user } })=>hasMinimumRole(user, minimum); +/** Requires at least the given role, for field-level access. */ export const requireRoleField = (minimum)=>({ req: { user } })=>hasMinimumRole(user, minimum); //# sourceMappingURL=access.js.map //# sourceMappingURL=access.js.map \ No newline at end of file diff --git a/dist/modules/access/access.js.map b/dist/modules/access/access.js.map index 4fd68e0..04922ad 100644 --- a/dist/modules/access/access.js.map +++ b/dist/modules/access/access.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/access/access.ts"],"sourcesContent":["import type { Access, FieldAccess } from 'payload'\n\nimport type { Role } from './types.js'\n\nimport { hasMinimumRole, isAdmin } from './predicates.js'\n\n/**\n * Collection-level access (returns boolean | Where).\n * Use in collection `access.read/create/update/delete`.\n */\nexport const adminOnly: Access = ({ req: { user } }) => isAdmin(user)\n\nexport const adminOrEditor: Access = ({ req: { user } }) => hasMinimumRole(user, 'editor')\n\nexport const authenticated: Access = ({ req: { user } }) => Boolean(user)\n\n/** Requires at least the given role. */\nexport const requireRole =\n (minimum: Role): Access =>\n ({ req: { user } }) =>\n hasMinimumRole(user, minimum)\n\n/** Admins see all; others are constrained to their own document. */\nexport const adminOrSelf: Access = ({ req: { user } }) => {\n if (isAdmin(user)) {return true}\n if (!user) {return false}\n return { id: { equals: user.id } }\n}\n\n/**\n * Field-level access (returns boolean only — no Where support).\n * Use in field `access.read/update`.\n */\nexport const adminOnlyField: FieldAccess = ({ req: { user } }) => isAdmin(user)\n\nexport const adminOrEditorField: FieldAccess = ({ req: { user } }) => hasMinimumRole(user, 'editor')\n\n/** Requires at least the given role, for field-level access. */\nexport const requireRoleField =\n (minimum: Role): FieldAccess =>\n ({ req: { user } }) =>\n hasMinimumRole(user, minimum)\n"],"names":["hasMinimumRole","isAdmin","adminOnly","req","user","adminOrEditor","authenticated","Boolean","requireRole","minimum","adminOrSelf","id","equals","adminOnlyField","adminOrEditorField","requireRoleField"],"mappings":"AAIA,SAASA,cAAc,EAAEC,OAAO,QAAQ,kBAAiB;AAEzD;;;CAGC,GACD,OAAO,MAAMC,YAAoB,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKH,QAAQG,MAAK;AAErE,OAAO,MAAMC,gBAAwB,CAAC,EAAEF,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKJ,eAAeI,MAAM,UAAS;AAE1F,OAAO,MAAME,gBAAwB,CAAC,EAAEH,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKG,QAAQH,MAAK;AAEzE,sCAAsC,GACtC,OAAO,MAAMI,cACX,CAACC,UACD,CAAC,EAAEN,KAAK,EAAEC,IAAI,EAAE,EAAE,GAChBJ,eAAeI,MAAMK,SAAQ;AAEjC,kEAAkE,GAClE,OAAO,MAAMC,cAAsB,CAAC,EAAEP,KAAK,EAAEC,IAAI,EAAE,EAAE;IACnD,IAAIH,QAAQG,OAAO;QAAC,OAAO;IAAI;IAC/B,IAAI,CAACA,MAAM;QAAC,OAAO;IAAK;IACxB,OAAO;QAAEO,IAAI;YAAEC,QAAQR,KAAKO,EAAE;QAAC;IAAE;AACnC,EAAC;AAED;;;CAGC,GACD,OAAO,MAAME,iBAA8B,CAAC,EAAEV,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKH,QAAQG,MAAK;AAE/E,OAAO,MAAMU,qBAAkC,CAAC,EAAEX,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKJ,eAAeI,MAAM,UAAS;AAEpG,8DAA8D,GAC9D,OAAO,MAAMW,mBACX,CAACN,UACD,CAAC,EAAEN,KAAK,EAAEC,IAAI,EAAE,EAAE,GAChBJ,eAAeI,MAAMK,SAAQ"} \ No newline at end of file +{"version":3,"sources":["access.js"],"sourcesContent":["import type { Access, FieldAccess } from 'payload'\n\nimport type { Role } from './types.js'\n\nimport { hasMinimumRole, isAdmin } from './predicates.js'\n\n/**\n * Collection-level access (returns boolean | Where).\n * Use in collection `access.read/create/update/delete`.\n */\nexport const adminOnly: Access = ({ req: { user } }) => isAdmin(user)\n\nexport const adminOrEditor: Access = ({ req: { user } }) => hasMinimumRole(user, 'editor')\n\nexport const authenticated: Access = ({ req: { user } }) => Boolean(user)\n\n/** Requires at least the given role. */\nexport const requireRole =\n (minimum: Role): Access =>\n ({ req: { user } }) =>\n hasMinimumRole(user, minimum)\n\n/** Admins see all; others are constrained to their own document. */\nexport const adminOrSelf: Access = ({ req: { user } }) => {\n if (isAdmin(user)) {return true}\n if (!user) {return false}\n return { id: { equals: user.id } }\n}\n\n/**\n * Field-level access (returns boolean only — no Where support).\n * Use in field `access.read/update`.\n */\nexport const adminOnlyField: FieldAccess = ({ req: { user } }) => isAdmin(user)\n\nexport const adminOrEditorField: FieldAccess = ({ req: { user } }) => hasMinimumRole(user, 'editor')\n\n/** Requires at least the given role, for field-level access. */\nexport const requireRoleField =\n (minimum: Role): FieldAccess =>\n ({ req: { user } }) =>\n hasMinimumRole(user, minimum)\n"],"names":["hasMinimumRole","isAdmin","adminOnly","req","user","adminOrEditor","authenticated","Boolean","requireRole","minimum","adminOrSelf","id","equals","adminOnlyField","adminOrEditorField","requireRoleField"],"mappings":"AAIA,SAASA,cAAc,EAAEC,OAAO,QAAQ,kBAAiB;AAEzD;;;CAGC,GACD,OAAO,MAAMC,YAAoB,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKH,QAAQG,MAAK;AAErE,OAAO,MAAMC,gBAAwB,CAAC,EAAEF,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKJ,eAAeI,MAAM,UAAS;AAE1F,OAAO,MAAME,gBAAwB,CAAC,EAAEH,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKG,QAAQH,MAAK;AAEzE,sCAAsC,GACtC,OAAO,MAAMI,cACX,CAACC,UACD,CAAC,EAAEN,KAAK,EAAEC,IAAI,EAAE,EAAE,GAChBJ,eAAeI,MAAMK,SAAQ;AAEjC,kEAAkE,GAClE,OAAO,MAAMC,cAAsB,CAAC,EAAEP,KAAK,EAAEC,IAAI,EAAE,EAAE;IACnD,IAAIH,QAAQG,OAAO;QAAC,OAAO;IAAI;IAC/B,IAAI,CAACA,MAAM;QAAC,OAAO;IAAK;IACxB,OAAO;QAAEO,IAAI;YAAEC,QAAQR,KAAKO,EAAE;QAAC;IAAE;AACnC,EAAC;AAED;;;CAGC,GACD,OAAO,MAAME,iBAA8B,CAAC,EAAEV,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKH,QAAQG,MAAK;AAE/E,OAAO,MAAMU,qBAAkC,CAAC,EAAEX,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKJ,eAAeI,MAAM,UAAS;AAEpG,8DAA8D,GAC9D,OAAO,MAAMW,mBACX,CAACN,UACD,CAAC,EAAEN,KAAK,EAAEC,IAAI,EAAE,EAAE,GAChBJ,eAAeI,MAAMK,SAAQ"} \ No newline at end of file diff --git a/dist/modules/access/index.js b/dist/modules/access/index.js index f466bc5..030a12c 100644 --- a/dist/modules/access/index.js +++ b/dist/modules/access/index.js @@ -2,6 +2,6 @@ export { adminOnly, adminOnlyField, adminOrEditor, adminOrEditorField, adminOrSe export { injectRoles } from './injectRoles.js'; export { hasMinimumRole, isAdmin, isEditor } from './predicates.js'; export { buildRolesField } from './rolesField.js'; -export { ROLE_HIERARCHY } from './types.js'; +export { ROLE_HIERARCHY } from './types.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/access/index.js.map b/dist/modules/access/index.js.map index 6a3b064..adaeade 100644 --- a/dist/modules/access/index.js.map +++ b/dist/modules/access/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/access/index.ts"],"sourcesContent":["export {\n adminOnly,\n adminOnlyField,\n adminOrEditor,\n adminOrEditorField,\n adminOrSelf,\n authenticated,\n requireRole,\n requireRoleField,\n} from './access.js'\nexport { injectRoles } from './injectRoles.js'\nexport { hasMinimumRole, isAdmin, isEditor } from './predicates.js'\nexport { buildRolesField } from './rolesField.js'\nexport type { AccessOption, Role } from './types.js'\nexport { ROLE_HIERARCHY } from './types.js'\n"],"names":["adminOnly","adminOnlyField","adminOrEditor","adminOrEditorField","adminOrSelf","authenticated","requireRole","requireRoleField","injectRoles","hasMinimumRole","isAdmin","isEditor","buildRolesField","ROLE_HIERARCHY"],"mappings":"AAAA,SACEA,SAAS,EACTC,cAAc,EACdC,aAAa,EACbC,kBAAkB,EAClBC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,gBAAgB,QACX,cAAa;AACpB,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,cAAc,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,kBAAiB;AACnE,SAASC,eAAe,QAAQ,kBAAiB;AAEjD,SAASC,cAAc,QAAQ,aAAY"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export {\n adminOnly,\n adminOnlyField,\n adminOrEditor,\n adminOrEditorField,\n adminOrSelf,\n authenticated,\n requireRole,\n requireRoleField,\n} from './access.js'\nexport { injectRoles } from './injectRoles.js'\nexport { hasMinimumRole, isAdmin, isEditor } from './predicates.js'\nexport { buildRolesField } from './rolesField.js'\nexport type { AccessOption, Role } from './types.js'\nexport { ROLE_HIERARCHY } from './types.js'\n"],"names":["adminOnly","adminOnlyField","adminOrEditor","adminOrEditorField","adminOrSelf","authenticated","requireRole","requireRoleField","injectRoles","hasMinimumRole","isAdmin","isEditor","buildRolesField","ROLE_HIERARCHY"],"mappings":"AAAA,SACEA,SAAS,EACTC,cAAc,EACdC,aAAa,EACbC,kBAAkB,EAClBC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,gBAAgB,QACX,cAAa;AACpB,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,cAAc,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,kBAAiB;AACnE,SAASC,eAAe,QAAQ,kBAAiB;AAEjD,SAASC,cAAc,QAAQ,aAAY"} \ No newline at end of file diff --git a/dist/modules/access/injectRoles.js b/dist/modules/access/injectRoles.js index e43897b..4172413 100644 --- a/dist/modules/access/injectRoles.js +++ b/dist/modules/access/injectRoles.js @@ -22,6 +22,6 @@ import { buildRolesField } from './rolesField.js'; }; }) }; -} +} //# sourceMappingURL=injectRoles.js.map //# sourceMappingURL=injectRoles.js.map \ No newline at end of file diff --git a/dist/modules/access/injectRoles.js.map b/dist/modules/access/injectRoles.js.map index 8e74bdf..75f1c50 100644 --- a/dist/modules/access/injectRoles.js.map +++ b/dist/modules/access/injectRoles.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/access/injectRoles.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { AccessOption } from './types.js'\n\nimport { buildRolesField } from './rolesField.js'\n\n/**\n * Injects the fixed `roles` field into the client's auth collection.\n *\n * The plugin owns the role definition; the client owns the collection. This\n * finds the collection by slug and appends the field. We control the whole\n * stack, so no conflict handling is needed — the field is simply added.\n */\nexport function injectRoles(config: Config, access: AccessOption): Config {\n const rolesField = buildRolesField(access.defaultRole)\n\n return {\n ...config,\n collections: (config.collections ?? []).map((collection) => {\n if (collection.slug !== access.authCollection) {\n return collection\n }\n return {\n ...collection,\n fields: [...collection.fields, rolesField],\n }\n }),\n }\n}\n"],"names":["buildRolesField","injectRoles","config","access","rolesField","defaultRole","collections","map","collection","slug","authCollection","fields"],"mappings":"AAIA,SAASA,eAAe,QAAQ,kBAAiB;AAEjD;;;;;;CAMC,GACD,OAAO,SAASC,YAAYC,MAAc,EAAEC,MAAoB;IAC9D,MAAMC,aAAaJ,gBAAgBG,OAAOE,WAAW;IAErD,OAAO;QACL,GAAGH,MAAM;QACTI,aAAa,AAACJ,CAAAA,OAAOI,WAAW,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YAC3C,IAAIA,WAAWC,IAAI,KAAKN,OAAOO,cAAc,EAAE;gBAC7C,OAAOF;YACT;YACA,OAAO;gBACL,GAAGA,UAAU;gBACbG,QAAQ;uBAAIH,WAAWG,MAAM;oBAAEP;iBAAW;YAC5C;QACF;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["injectRoles.js"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { AccessOption } from './types.js'\n\nimport { buildRolesField } from './rolesField.js'\n\n/**\n * Injects the fixed `roles` field into the client's auth collection.\n *\n * The plugin owns the role definition; the client owns the collection. This\n * finds the collection by slug and appends the field. We control the whole\n * stack, so no conflict handling is needed — the field is simply added.\n */\nexport function injectRoles(config: Config, access: AccessOption): Config {\n const rolesField = buildRolesField(access.defaultRole)\n\n return {\n ...config,\n collections: (config.collections ?? []).map((collection) => {\n if (collection.slug !== access.authCollection) {\n return collection\n }\n return {\n ...collection,\n fields: [...collection.fields, rolesField],\n }\n }),\n }\n}\n"],"names":["buildRolesField","injectRoles","config","access","rolesField","defaultRole","collections","map","collection","slug","authCollection","fields"],"mappings":"AAIA,SAASA,eAAe,QAAQ,kBAAiB;AAEjD;;;;;;CAMC,GACD,OAAO,SAASC,YAAYC,MAAc,EAAEC,MAAoB;IAC9D,MAAMC,aAAaJ,gBAAgBG,OAAOE,WAAW;IAErD,OAAO;QACL,GAAGH,MAAM;QACTI,aAAcJ,CAAAA,OAAOI,WAAW,IAAI,EAAA,AAAC,EAAGC,GAAG,CAAC,CAACC;YAC3C,IAAIA,WAAWC,IAAI,KAAKN,OAAOO,cAAc,EAAE;gBAC7C,OAAOF;YACT;YACA,OAAO;gBACL,GAAGA,UAAU;gBACbG,QAAQ;uBAAIH,WAAWG,MAAM;oBAAEP;iBAAW;YAC5C;QACF;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/access/predicates.js b/dist/modules/access/predicates.js index b68a956..4a560c1 100644 --- a/dist/modules/access/predicates.js +++ b/dist/modules/access/predicates.js @@ -27,6 +27,6 @@ import { ROLE_HIERARCHY } from './types.js'; } /** True if the user is an editor or higher (editor, admin). */ export function isEditor(user) { return hasMinimumRole(user, 'editor'); -} +} //# sourceMappingURL=predicates.js.map //# sourceMappingURL=predicates.js.map \ No newline at end of file diff --git a/dist/modules/access/predicates.js.map b/dist/modules/access/predicates.js.map index cd6db80..5bc4095 100644 --- a/dist/modules/access/predicates.js.map +++ b/dist/modules/access/predicates.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/access/predicates.ts"],"sourcesContent":["import type { Role } from './types.js'\n\nimport { ROLE_HIERARCHY } from './types.js'\n\n/**\n * The plugin can't know the client's generated User type, and Payload types\n * `req.user` loosely (UntypedUser | null). Predicates therefore accept an\n * unknown-ish user and read `roles` defensively — no assumptions about shape\n * beyond an optional roles array.\n */\ntype MaybeUser = { roles?: null | Role[] } | null | Record | undefined\n\n/** Safely extracts the roles array from a loosely-typed user. */\nfunction getRoles(user: MaybeUser): Role[] {\n if (!user || typeof user !== 'object') {return []}\n const roles = (user as { roles?: unknown }).roles\n if (!Array.isArray(roles)) {return []}\n return roles.filter((role): role is Role => ROLE_HIERARCHY.includes(role as Role))\n}\n\n/** Highest-privilege role index the user holds, or -1 if none. */\nfunction highestRoleIndex(user: MaybeUser): number {\n const roles = getRoles(user)\n if (!roles.length) {return -1}\n return Math.max(...roles.map((role) => ROLE_HIERARCHY.indexOf(role)))\n}\n\n/**\n * True if the user holds at least the given role in the hierarchy.\n * admin satisfies 'editor' and 'user'; editor satisfies 'user'.\n */\nexport function hasMinimumRole(user: MaybeUser, minimum: Role): boolean {\n return highestRoleIndex(user) >= ROLE_HIERARCHY.indexOf(minimum)\n}\n\n/** True if the user is an admin. */\nexport function isAdmin(user: MaybeUser): boolean {\n return hasMinimumRole(user, 'admin')\n}\n\n/** True if the user is an editor or higher (editor, admin). */\nexport function isEditor(user: MaybeUser): boolean {\n return hasMinimumRole(user, 'editor')\n}\n"],"names":["ROLE_HIERARCHY","getRoles","user","roles","Array","isArray","filter","role","includes","highestRoleIndex","length","Math","max","map","indexOf","hasMinimumRole","minimum","isAdmin","isEditor"],"mappings":"AAEA,SAASA,cAAc,QAAQ,aAAY;AAU3C,+DAA+D,GAC/D,SAASC,SAASC,IAAe;IAC/B,IAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU;QAAC,OAAO,EAAE;IAAA;IACjD,MAAMC,QAAQ,AAACD,KAA6BC,KAAK;IACjD,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;QAAC,OAAO,EAAE;IAAA;IACrC,OAAOA,MAAMG,MAAM,CAAC,CAACC,OAAuBP,eAAeQ,QAAQ,CAACD;AACtE;AAEA,gEAAgE,GAChE,SAASE,iBAAiBP,IAAe;IACvC,MAAMC,QAAQF,SAASC;IACvB,IAAI,CAACC,MAAMO,MAAM,EAAE;QAAC,OAAO,CAAC;IAAC;IAC7B,OAAOC,KAAKC,GAAG,IAAIT,MAAMU,GAAG,CAAC,CAACN,OAASP,eAAec,OAAO,CAACP;AAChE;AAEA;;;CAGC,GACD,OAAO,SAASQ,eAAeb,IAAe,EAAEc,OAAa;IAC3D,OAAOP,iBAAiBP,SAASF,eAAec,OAAO,CAACE;AAC1D;AAEA,kCAAkC,GAClC,OAAO,SAASC,QAAQf,IAAe;IACrC,OAAOa,eAAeb,MAAM;AAC9B;AAEA,6DAA6D,GAC7D,OAAO,SAASgB,SAAShB,IAAe;IACtC,OAAOa,eAAeb,MAAM;AAC9B"} \ No newline at end of file +{"version":3,"sources":["predicates.js"],"sourcesContent":["import type { Role } from './types.js'\n\nimport { ROLE_HIERARCHY } from './types.js'\n\n/**\n * The plugin can't know the client's generated User type, and Payload types\n * `req.user` loosely (UntypedUser | null). Predicates therefore accept an\n * unknown-ish user and read `roles` defensively — no assumptions about shape\n * beyond an optional roles array.\n */\ntype MaybeUser = { roles?: null | Role[] } | null | Record | undefined\n\n/** Safely extracts the roles array from a loosely-typed user. */\nfunction getRoles(user: MaybeUser): Role[] {\n if (!user || typeof user !== 'object') {return []}\n const roles = (user as { roles?: unknown }).roles\n if (!Array.isArray(roles)) {return []}\n return roles.filter((role): role is Role => ROLE_HIERARCHY.includes(role as Role))\n}\n\n/** Highest-privilege role index the user holds, or -1 if none. */\nfunction highestRoleIndex(user: MaybeUser): number {\n const roles = getRoles(user)\n if (!roles.length) {return -1}\n return Math.max(...roles.map((role) => ROLE_HIERARCHY.indexOf(role)))\n}\n\n/**\n * True if the user holds at least the given role in the hierarchy.\n * admin satisfies 'editor' and 'user'; editor satisfies 'user'.\n */\nexport function hasMinimumRole(user: MaybeUser, minimum: Role): boolean {\n return highestRoleIndex(user) >= ROLE_HIERARCHY.indexOf(minimum)\n}\n\n/** True if the user is an admin. */\nexport function isAdmin(user: MaybeUser): boolean {\n return hasMinimumRole(user, 'admin')\n}\n\n/** True if the user is an editor or higher (editor, admin). */\nexport function isEditor(user: MaybeUser): boolean {\n return hasMinimumRole(user, 'editor')\n}\n"],"names":["ROLE_HIERARCHY","getRoles","user","roles","Array","isArray","filter","role","includes","highestRoleIndex","length","Math","max","map","indexOf","hasMinimumRole","minimum","isAdmin","isEditor"],"mappings":"AAEA,SAASA,cAAc,QAAQ,aAAY;AAU3C,+DAA+D,GAC/D,SAASC,SAASC,IAAe;IAC/B,IAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU;QAAC,OAAO,EAAE;IAAA;IACjD,MAAMC,QAASD,KAA6BC,KAAK;IACjD,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;QAAC,OAAO,EAAE;IAAA;IACrC,OAAOA,MAAMG,MAAM,CAAC,CAACC,OAAuBP,eAAeQ,QAAQ,CAACD;AACtE;AAEA,gEAAgE,GAChE,SAASE,iBAAiBP,IAAe;IACvC,MAAMC,QAAQF,SAASC;IACvB,IAAI,CAACC,MAAMO,MAAM,EAAE;QAAC,OAAO,CAAC;IAAC;IAC7B,OAAOC,KAAKC,GAAG,IAAIT,MAAMU,GAAG,CAAC,CAACN,OAASP,eAAec,OAAO,CAACP;AAChE;AAEA;;;CAGC,GACD,OAAO,SAASQ,eAAeb,IAAe,EAAEc,OAAa;IAC3D,OAAOP,iBAAiBP,SAASF,eAAec,OAAO,CAACE;AAC1D;AAEA,kCAAkC,GAClC,OAAO,SAASC,QAAQf,IAAe;IACrC,OAAOa,eAAeb,MAAM;AAC9B;AAEA,6DAA6D,GAC7D,OAAO,SAASgB,SAAShB,IAAe;IACtC,OAAOa,eAAeb,MAAM;AAC9B"} \ No newline at end of file diff --git a/dist/modules/access/rolesField.js b/dist/modules/access/rolesField.js index 73f474b..9bb815d 100644 --- a/dist/modules/access/rolesField.js +++ b/dist/modules/access/rolesField.js @@ -27,6 +27,6 @@ import { ROLE_HIERARCHY } from './types.js'; required: true, saveToJWT: true }; -} +} //# sourceMappingURL=rolesField.js.map //# sourceMappingURL=rolesField.js.map \ No newline at end of file diff --git a/dist/modules/access/rolesField.js.map b/dist/modules/access/rolesField.js.map index ac8872c..932495b 100644 --- a/dist/modules/access/rolesField.js.map +++ b/dist/modules/access/rolesField.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/access/rolesField.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { Role } from './types.js'\n\nimport { isAdmin } from './predicates.js'\nimport { ROLE_HIERARCHY } from './types.js'\n\n/**\n * Builds the fixed `roles` field the plugin injects into the auth collection.\n *\n * Saved to the JWT so role checks avoid a database lookup. Only admins can\n * change roles, preventing privilege escalation by lower-privilege users.\n */\nexport function buildRolesField(defaultRole: Role = 'user'): Field {\n return {\n name: 'roles',\n type: 'select',\n access: {\n // Only admins may assign or change roles\n update: ({ req: { user } }) => isAdmin(user),\n },\n admin: {\n description: 'Role hierarchy: admin > editor > user.',\n },\n defaultValue: [defaultRole],\n hasMany: true,\n options: ROLE_HIERARCHY.map((role) => ({\n label: role.charAt(0).toUpperCase() + role.slice(1),\n value: role,\n })),\n required: true,\n saveToJWT: true,\n }\n}\n"],"names":["isAdmin","ROLE_HIERARCHY","buildRolesField","defaultRole","name","type","access","update","req","user","admin","description","defaultValue","hasMany","options","map","role","label","charAt","toUpperCase","slice","value","required","saveToJWT"],"mappings":"AAIA,SAASA,OAAO,QAAQ,kBAAiB;AACzC,SAASC,cAAc,QAAQ,aAAY;AAE3C;;;;;CAKC,GACD,OAAO,SAASC,gBAAgBC,cAAoB,MAAM;IACxD,OAAO;QACLC,MAAM;QACNC,MAAM;QACNC,QAAQ;YACN,yCAAyC;YACzCC,QAAQ,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKT,QAAQS;QACzC;QACAC,OAAO;YACLC,aAAa;QACf;QACAC,cAAc;YAACT;SAAY;QAC3BU,SAAS;QACTC,SAASb,eAAec,GAAG,CAAC,CAACC,OAAU,CAAA;gBACrCC,OAAOD,KAAKE,MAAM,CAAC,GAAGC,WAAW,KAAKH,KAAKI,KAAK,CAAC;gBACjDC,OAAOL;YACT,CAAA;QACAM,UAAU;QACVC,WAAW;IACb;AACF"} \ No newline at end of file +{"version":3,"sources":["rolesField.js"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { Role } from './types.js'\n\nimport { isAdmin } from './predicates.js'\nimport { ROLE_HIERARCHY } from './types.js'\n\n/**\n * Builds the fixed `roles` field the plugin injects into the auth collection.\n *\n * Saved to the JWT so role checks avoid a database lookup. Only admins can\n * change roles, preventing privilege escalation by lower-privilege users.\n */\nexport function buildRolesField(defaultRole: Role = 'user'): Field {\n return {\n name: 'roles',\n type: 'select',\n access: {\n // Only admins may assign or change roles\n update: ({ req: { user } }) => isAdmin(user),\n },\n admin: {\n description: 'Role hierarchy: admin > editor > user.',\n },\n defaultValue: [defaultRole],\n hasMany: true,\n options: ROLE_HIERARCHY.map((role) => ({\n label: role.charAt(0).toUpperCase() + role.slice(1),\n value: role,\n })),\n required: true,\n saveToJWT: true,\n }\n}\n"],"names":["isAdmin","ROLE_HIERARCHY","buildRolesField","defaultRole","name","type","access","update","req","user","admin","description","defaultValue","hasMany","options","map","role","label","charAt","toUpperCase","slice","value","required","saveToJWT"],"mappings":"AAIA,SAASA,OAAO,QAAQ,kBAAiB;AACzC,SAASC,cAAc,QAAQ,aAAY;AAE3C;;;;;CAKC,GACD,OAAO,SAASC,gBAAgBC,cAAoB,MAAM;IACxD,OAAO;QACLC,MAAM;QACNC,MAAM;QACNC,QAAQ;YACN,yCAAyC;YACzCC,QAAQ,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKT,QAAQS;QACzC;QACAC,OAAO;YACLC,aAAa;QACf;QACAC,cAAc;YAACT;SAAY;QAC3BU,SAAS;QACTC,SAASb,eAAec,GAAG,CAAC,CAACC,OAAU,CAAA;gBACrCC,OAAOD,KAAKE,MAAM,CAAC,GAAGC,WAAW,KAAKH,KAAKI,KAAK,CAAC;gBACjDC,OAAOL;aACT,CAAA;QACAM,UAAU;QACVC,WAAW;IACb;AACF"} \ No newline at end of file diff --git a/dist/modules/access/types.js b/dist/modules/access/types.js index 3afbaa8..c281ec9 100644 --- a/dist/modules/access/types.js +++ b/dist/modules/access/types.js @@ -5,6 +5,6 @@ 'user', 'editor', 'admin' -]; +]; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/access/types.js.map b/dist/modules/access/types.js.map index 795ba05..5e9b13a 100644 --- a/dist/modules/access/types.js.map +++ b/dist/modules/access/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/access/types.ts"],"sourcesContent":["/**\n * Role hierarchy, lowest to highest privilege.\n * A higher role satisfies any requirement met by a lower one.\n */\nexport const ROLE_HIERARCHY = ['user', 'editor', 'admin'] as const\n\nexport type Role = (typeof ROLE_HIERARCHY)[number]\n\n/**\n * Access-control options.\n *\n * The plugin injects a fixed `roles` field into the client's auth collection\n * — the collection itself belongs to the client (create-payload-app), the\n * role definition belongs to the plugin.\n */\nexport type AccessOption = {\n /** Slug of the client's auth collection, e.g. 'users'. */\n authCollection: string\n /** Role assigned to new users. Defaults to 'user'. */\n defaultRole?: Role\n}\n"],"names":["ROLE_HIERARCHY"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,iBAAiB;IAAC;IAAQ;IAAU;CAAQ,CAAS"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["/**\n * Role hierarchy, lowest to highest privilege.\n * A higher role satisfies any requirement met by a lower one.\n */\nexport const ROLE_HIERARCHY = ['user', 'editor', 'admin'] as const\n\nexport type Role = (typeof ROLE_HIERARCHY)[number]\n\n/**\n * Access-control options.\n *\n * The plugin injects a fixed `roles` field into the client's auth collection\n * — the collection itself belongs to the client (create-payload-app), the\n * role definition belongs to the plugin.\n */\nexport type AccessOption = {\n /** Slug of the client's auth collection, e.g. 'users'. */\n authCollection: string\n /** Role assigned to new users. Defaults to 'user'. */\n defaultRole?: Role\n}\n"],"names":["ROLE_HIERARCHY"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,iBAAiB;IAAC;IAAQ;IAAU;CAAQ,CAAS"} \ No newline at end of file diff --git a/dist/modules/analytics/Analytics.js b/dist/modules/analytics/Analytics.js index f3eef21..c8d928a 100644 --- a/dist/modules/analytics/Analytics.js +++ b/dist/modules/analytics/Analytics.js @@ -58,6 +58,6 @@ function injectScript(src) { gtmContainerId ]); return null; -} +} //# sourceMappingURL=Analytics.js.map //# sourceMappingURL=Analytics.js.map \ No newline at end of file diff --git a/dist/modules/analytics/Analytics.js.map b/dist/modules/analytics/Analytics.js.map index 7fa6bc1..b6df076 100644 --- a/dist/modules/analytics/Analytics.js.map +++ b/dist/modules/analytics/Analytics.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/analytics/Analytics.tsx"],"sourcesContent":["'use client'\nimport { useEffect } from 'react'\n\nimport type { AnalyticsConfig } from './types.js'\n\nimport { gtag } from '../consent/googleConsent.js'\nimport { CONSENT_COOKIE, DEFAULT_CONSENT, parseConsent, setDefaultConsent } from '../consent/index.js'\n\ndeclare global {\n interface Window {\n dataLayer?: unknown[]\n }\n}\n\nconst GTM_SCRIPT = (id: string) => `https://www.googletagmanager.com/gtm.js?id=${id}`\nconst GA4_SCRIPT = (id: string) => `https://www.googletagmanager.com/gtag/js?id=${id}`\n\n/** Reads the current consent state from cookie, or defaults if none. */\nfunction readConsent() {\n if (typeof document === 'undefined') {return DEFAULT_CONSENT}\n const raw = document.cookie\n .split('; ')\n .find((c) => c.startsWith(`${CONSENT_COOKIE}=`))\n ?.split('=')[1]\n return parseConsent(raw) ?? DEFAULT_CONSENT\n}\n\nfunction injectScript(src: string) {\n if (document.querySelector(`script[src=\"${src}\"]`)) {return}\n const s = document.createElement('script')\n s.src = src\n s.async = true\n document.head.appendChild(s)\n}\n\n/**\n * Loads Google Analytics — GTM if a container ID is set, otherwise GA4 gtag.\n *\n * Consent Mode is initialised to the visitor's stored consent BEFORE the tag\n * loads (via setDefaultConsent from the consent module), so tags respect the\n * choice from the first hit; useConsent sends an update the moment the visitor\n * decides. IDs are public and come from SiteIntegrations, passed in as props\n * (the client project reads them server-side).\n *\n * Renders nothing — it only injects the scripts. Mount once, high in the tree\n * (e.g. root layout), inside ConsentProvider.\n */\nexport function Analytics({ ga4MeasurementId, gtmContainerId }: AnalyticsConfig) {\n useEffect(() => {\n if (!ga4MeasurementId && !gtmContainerId) {return}\n\n // 1. Consent Mode default = the visitor's stored choice, before tags load.\n setDefaultConsent(readConsent())\n\n // 2. Load the tag.\n if (gtmContainerId) {\n window.dataLayer = window.dataLayer || []\n window.dataLayer.push({ event: 'gtm.js', 'gtm.start': Date.now() })\n injectScript(GTM_SCRIPT(gtmContainerId))\n } else if (ga4MeasurementId) {\n injectScript(GA4_SCRIPT(ga4MeasurementId))\n // Uses the shared gtag (pushes `arguments`, as Google's tags expect);\n // real gtag.js wires itself up once the script loads.\n gtag('js', new Date())\n gtag('config', ga4MeasurementId)\n }\n }, [ga4MeasurementId, gtmContainerId])\n\n return null\n}\n"],"names":["useEffect","gtag","CONSENT_COOKIE","DEFAULT_CONSENT","parseConsent","setDefaultConsent","GTM_SCRIPT","id","GA4_SCRIPT","readConsent","document","raw","cookie","split","find","c","startsWith","injectScript","src","querySelector","s","createElement","async","head","appendChild","Analytics","ga4MeasurementId","gtmContainerId","window","dataLayer","push","event","Date","now"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,QAAO;AAIjC,SAASC,IAAI,QAAQ,8BAA6B;AAClD,SAASC,cAAc,EAAEC,eAAe,EAAEC,YAAY,EAAEC,iBAAiB,QAAS,sBAAqB;AAQvG,MAAMC,aAAa,CAACC,KAAe,CAAC,2CAA2C,EAAEA,IAAI;AACrF,MAAMC,aAAa,CAACD,KAAe,CAAC,4CAA4C,EAAEA,IAAI;AAEtF,sEAAsE,GACtE,SAASE;IACP,IAAI,OAAOC,aAAa,aAAa;QAAC,OAAOP;IAAe;IAC5D,MAAMQ,MAAMD,SAASE,MAAM,CACxBC,KAAK,CAAC,MACNC,IAAI,CAAC,CAACC,IAAMA,EAAEC,UAAU,CAAC,GAAGd,eAAe,CAAC,CAAC,IAC5CW,MAAM,IAAI,CAAC,EAAE;IACjB,OAAOT,aAAaO,QAAQR;AAC9B;AAEA,SAASc,aAAaC,GAAW;IAC/B,IAAIR,SAASS,aAAa,CAAC,CAAC,YAAY,EAAED,IAAI,EAAE,CAAC,GAAG;QAAC;IAAM;IAC3D,MAAME,IAAIV,SAASW,aAAa,CAAC;IACjCD,EAAEF,GAAG,GAAGA;IACRE,EAAEE,KAAK,GAAG;IACVZ,SAASa,IAAI,CAACC,WAAW,CAACJ;AAC5B;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASK,UAAU,EAAEC,gBAAgB,EAAEC,cAAc,EAAmB;IAC7E3B,UAAU;QACR,IAAI,CAAC0B,oBAAoB,CAACC,gBAAgB;YAAC;QAAM;QAEjD,2EAA2E;QAC3EtB,kBAAkBI;QAElB,mBAAmB;QACnB,IAAIkB,gBAAgB;YAClBC,OAAOC,SAAS,GAAGD,OAAOC,SAAS,IAAI,EAAE;YACzCD,OAAOC,SAAS,CAACC,IAAI,CAAC;gBAAEC,OAAO;gBAAU,aAAaC,KAAKC,GAAG;YAAG;YACjEhB,aAAaX,WAAWqB;QAC1B,OAAO,IAAID,kBAAkB;YAC3BT,aAAaT,WAAWkB;YACxB,sEAAsE;YACtE,sDAAsD;YACtDzB,KAAK,MAAM,IAAI+B;YACf/B,KAAK,UAAUyB;QACjB;IACF,GAAG;QAACA;QAAkBC;KAAe;IAErC,OAAO;AACT"} \ No newline at end of file +{"version":3,"sources":["Analytics.js"],"sourcesContent":["'use client'\nimport { useEffect } from 'react'\n\nimport type { AnalyticsConfig } from './types.js'\n\nimport { gtag } from '../consent/googleConsent.js'\nimport { CONSENT_COOKIE, DEFAULT_CONSENT, parseConsent, setDefaultConsent } from '../consent/index.js'\n\ndeclare global {\n interface Window {\n dataLayer?: unknown[]\n }\n}\n\nconst GTM_SCRIPT = (id: string) => `https://www.googletagmanager.com/gtm.js?id=${id}`\nconst GA4_SCRIPT = (id: string) => `https://www.googletagmanager.com/gtag/js?id=${id}`\n\n/** Reads the current consent state from cookie, or defaults if none. */\nfunction readConsent() {\n if (typeof document === 'undefined') {return DEFAULT_CONSENT}\n const raw = document.cookie\n .split('; ')\n .find((c) => c.startsWith(`${CONSENT_COOKIE}=`))\n ?.split('=')[1]\n return parseConsent(raw) ?? DEFAULT_CONSENT\n}\n\nfunction injectScript(src: string) {\n if (document.querySelector(`script[src=\"${src}\"]`)) {return}\n const s = document.createElement('script')\n s.src = src\n s.async = true\n document.head.appendChild(s)\n}\n\n/**\n * Loads Google Analytics — GTM if a container ID is set, otherwise GA4 gtag.\n *\n * Consent Mode is initialised to the visitor's stored consent BEFORE the tag\n * loads (via setDefaultConsent from the consent module), so tags respect the\n * choice from the first hit; useConsent sends an update the moment the visitor\n * decides. IDs are public and come from SiteIntegrations, passed in as props\n * (the client project reads them server-side).\n *\n * Renders nothing — it only injects the scripts. Mount once, high in the tree\n * (e.g. root layout), inside ConsentProvider.\n */\nexport function Analytics({ ga4MeasurementId, gtmContainerId }: AnalyticsConfig) {\n useEffect(() => {\n if (!ga4MeasurementId && !gtmContainerId) {return}\n\n // 1. Consent Mode default = the visitor's stored choice, before tags load.\n setDefaultConsent(readConsent())\n\n // 2. Load the tag.\n if (gtmContainerId) {\n window.dataLayer = window.dataLayer || []\n window.dataLayer.push({ event: 'gtm.js', 'gtm.start': Date.now() })\n injectScript(GTM_SCRIPT(gtmContainerId))\n } else if (ga4MeasurementId) {\n injectScript(GA4_SCRIPT(ga4MeasurementId))\n // Uses the shared gtag (pushes `arguments`, as Google's tags expect);\n // real gtag.js wires itself up once the script loads.\n gtag('js', new Date())\n gtag('config', ga4MeasurementId)\n }\n }, [ga4MeasurementId, gtmContainerId])\n\n return null\n}\n"],"names":["useEffect","gtag","CONSENT_COOKIE","DEFAULT_CONSENT","parseConsent","setDefaultConsent","GTM_SCRIPT","id","GA4_SCRIPT","readConsent","document","raw","cookie","split","find","c","startsWith","injectScript","src","querySelector","s","createElement","async","head","appendChild","Analytics","ga4MeasurementId","gtmContainerId","window","dataLayer","push","event","Date","now"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,QAAO;AAIjC,SAASC,IAAI,QAAQ,8BAA6B;AAClD,SAASC,cAAc,EAAEC,eAAe,EAAEC,YAAY,EAAEC,iBAAiB,QAAS,sBAAqB;AAQvG,MAAMC,aAAa,CAACC,KAAe,CAAC,2CAA2C,EAAEA,IAAI;AACrF,MAAMC,aAAa,CAACD,KAAe,CAAC,4CAA4C,EAAEA,IAAI;AAEtF,sEAAsE,GACtE,SAASE;IACP,IAAI,OAAOC,aAAa,aAAa;QAAC,OAAOP;IAAe;IAC5D,MAAMQ,MAAMD,SAASE,MAAM,CACxBC,KAAK,CAAC,MACNC,IAAI,CAAC,CAACC,IAAMA,EAAEC,UAAU,CAAC,GAAGd,eAAe,CAAC,CAAC,IAC5CW,MAAM,IAAI,CAAC,EAAE;IACjB,OAAOT,aAAaO,QAAQR;AAC9B;AAEA,SAASc,aAAaC,GAAW;IAC/B,IAAIR,SAASS,aAAa,CAAC,CAAC,YAAY,EAAED,IAAI,EAAE,CAAC,GAAG;QAAC;IAAM;IAC3D,MAAME,IAAIV,SAASW,aAAa,CAAC;IACjCD,EAAEF,GAAG,GAAGA;IACRE,EAAEE,KAAK,GAAG;IACVZ,SAASa,IAAI,CAACC,WAAW,CAACJ;AAC5B;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASK,UAAU,EAAEC,gBAAgB,EAAEC,cAAc,EAAmB;IAC7E3B,UAAU;QACR,IAAI,CAAC0B,oBAAoB,CAACC,gBAAgB;YAAC;QAAM;QAEjD,2EAA2E;QAC3EtB,kBAAkBI;QAElB,mBAAmB;QACnB,IAAIkB,gBAAgB;YAClBC,OAAOC,SAAS,GAAGD,OAAOC,SAAS,IAAI,EAAE;YACzCD,OAAOC,SAAS,CAACC,IAAI,CAAC;gBAAEC,OAAO;gBAAU,aAAaC,KAAKC,GAAG;YAAG;YACjEhB,aAAaX,WAAWqB;QAC1B,OAAO,IAAID,kBAAkB;YAC3BT,aAAaT,WAAWkB;YACxB,sEAAsE;YACtE,sDAAsD;YACtDzB,KAAK,MAAM,IAAI+B;YACf/B,KAAK,UAAUyB;QACjB;IACF,GAAG;QAACA;QAAkBC;KAAe;IAErC,OAAO;AACT"} \ No newline at end of file diff --git a/dist/modules/analytics/client.js b/dist/modules/analytics/client.js index 8b711fa..050b198 100644 --- a/dist/modules/analytics/client.js +++ b/dist/modules/analytics/client.js @@ -1,4 +1,4 @@ 'use client'; -export { Analytics } from './Analytics.js'; +export { Analytics } from './Analytics.js'; //# sourceMappingURL=client.js.map //# sourceMappingURL=client.js.map \ No newline at end of file diff --git a/dist/modules/analytics/client.js.map b/dist/modules/analytics/client.js.map index 734ed82..f0805d0 100644 --- a/dist/modules/analytics/client.js.map +++ b/dist/modules/analytics/client.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/analytics/client.ts"],"sourcesContent":["'use client'\nexport { Analytics } from './Analytics.js'\n"],"names":["Analytics"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,iBAAgB"} \ No newline at end of file +{"version":3,"sources":["client.js"],"sourcesContent":["'use client'\nexport { Analytics } from './Analytics.js'\n"],"names":["Analytics"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,iBAAgB"} \ No newline at end of file diff --git a/dist/modules/analytics/getAnalyticsConfig.js b/dist/modules/analytics/getAnalyticsConfig.js index 2b6cfd0..6565ee9 100644 --- a/dist/modules/analytics/getAnalyticsConfig.js +++ b/dist/modules/analytics/getAnalyticsConfig.js @@ -10,6 +10,6 @@ import { getSiteIntegrations } from '../payload/index.js'; ga4MeasurementId: integrations.ga4MeasurementId ?? null, gtmContainerId: integrations.gtmContainerId ?? null }; -} +} //# sourceMappingURL=getAnalyticsConfig.js.map //# sourceMappingURL=getAnalyticsConfig.js.map \ No newline at end of file diff --git a/dist/modules/analytics/getAnalyticsConfig.js.map b/dist/modules/analytics/getAnalyticsConfig.js.map index 493a328..265c5b5 100644 --- a/dist/modules/analytics/getAnalyticsConfig.js.map +++ b/dist/modules/analytics/getAnalyticsConfig.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/analytics/getAnalyticsConfig.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { AnalyticsConfig } from './types.js'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\n/**\n * Reads the public analytics IDs from SiteIntegrations, server-side.\n *\n * Returns only the two public IDs (GA4 / GTM) — safe to pass to the client\n * component. Does NOT expose any secret from SiteIntegrations.\n */\nexport async function getAnalyticsConfig(payload: BasePayload): Promise {\n const integrations = await getSiteIntegrations(payload)\n return {\n ga4MeasurementId: integrations.ga4MeasurementId ?? null,\n gtmContainerId: integrations.gtmContainerId ?? null,\n }\n}\n"],"names":["getSiteIntegrations","getAnalyticsConfig","payload","integrations","ga4MeasurementId","gtmContainerId"],"mappings":"AAIA,SAASA,mBAAmB,QAAQ,sBAAqB;AAEzD;;;;;CAKC,GACD,OAAO,eAAeC,mBAAmBC,OAAoB;IAC3D,MAAMC,eAAe,MAAMH,oBAAqCE;IAChE,OAAO;QACLE,kBAAkBD,aAAaC,gBAAgB,IAAI;QACnDC,gBAAgBF,aAAaE,cAAc,IAAI;IACjD;AACF"} \ No newline at end of file +{"version":3,"sources":["getAnalyticsConfig.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { AnalyticsConfig } from './types.js'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\n/**\n * Reads the public analytics IDs from SiteIntegrations, server-side.\n *\n * Returns only the two public IDs (GA4 / GTM) — safe to pass to the client\n * component. Does NOT expose any secret from SiteIntegrations.\n */\nexport async function getAnalyticsConfig(payload: BasePayload): Promise {\n const integrations = await getSiteIntegrations(payload)\n return {\n ga4MeasurementId: integrations.ga4MeasurementId ?? null,\n gtmContainerId: integrations.gtmContainerId ?? null,\n }\n}\n"],"names":["getSiteIntegrations","getAnalyticsConfig","payload","integrations","ga4MeasurementId","gtmContainerId"],"mappings":"AAIA,SAASA,mBAAmB,QAAQ,sBAAqB;AAEzD;;;;;CAKC,GACD,OAAO,eAAeC,mBAAmBC,OAAoB;IAC3D,MAAMC,eAAe,MAAMH,oBAAqCE;IAChE,OAAO;QACLE,kBAAkBD,aAAaC,gBAAgB,IAAI;QACnDC,gBAAgBF,aAAaE,cAAc,IAAI;IACjD;AACF"} \ No newline at end of file diff --git a/dist/modules/analytics/index.js b/dist/modules/analytics/index.js index 11b6e49..2451234 100644 --- a/dist/modules/analytics/index.js +++ b/dist/modules/analytics/index.js @@ -1,5 +1,5 @@ // Server-safe: config type + the helper that reads IDs from SiteIntegrations. // The component is client-side — exported via ./client. -export { getAnalyticsConfig } from './getAnalyticsConfig.js'; +export { getAnalyticsConfig } from './getAnalyticsConfig.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/analytics/index.js.map b/dist/modules/analytics/index.js.map index 8a3ea22..473198b 100644 --- a/dist/modules/analytics/index.js.map +++ b/dist/modules/analytics/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/analytics/index.ts"],"sourcesContent":["// Server-safe: config type + the helper that reads IDs from SiteIntegrations.\n// The component is client-side — exported via ./client.\nexport type { AnalyticsConfig } from './types.js'\nexport { getAnalyticsConfig } from './getAnalyticsConfig.js'\n"],"names":["getAnalyticsConfig"],"mappings":"AAAA,8EAA8E;AAC9E,oEAAoE;AAEpE,SAASA,kBAAkB,QAAQ,0BAAyB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["// Server-safe: config type + the helper that reads IDs from SiteIntegrations.\n// The component is client-side — exported via ./client.\nexport type { AnalyticsConfig } from './types.js'\nexport { getAnalyticsConfig } from './getAnalyticsConfig.js'\n"],"names":["getAnalyticsConfig"],"mappings":"AAAA,8EAA8E;AAC9E,oEAAoE;AAEpE,SAASA,kBAAkB,QAAQ,0BAAyB"} \ No newline at end of file diff --git a/dist/modules/analytics/types.js b/dist/modules/analytics/types.js index 93dfc2e..04249e0 100644 --- a/dist/modules/analytics/types.js +++ b/dist/modules/analytics/types.js @@ -1,5 +1,5 @@ /** * Analytics IDs, read from SiteIntegrations (public — safe on the client). - */ export { }; + */ export { }; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/analytics/types.js.map b/dist/modules/analytics/types.js.map index 95b37d4..f907978 100644 --- a/dist/modules/analytics/types.js.map +++ b/dist/modules/analytics/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/analytics/types.ts"],"sourcesContent":["/**\n * Analytics IDs, read from SiteIntegrations (public — safe on the client).\n */\nexport type AnalyticsConfig = {\n /** GA4 Measurement ID, e.g. 'G-XXXXXXXXXX'. */\n ga4MeasurementId?: null | string\n /** GTM Container ID, e.g. 'GTM-XXXXXXX'. */\n gtmContainerId?: null | string\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GACD,WAKC"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["/**\n * Analytics IDs, read from SiteIntegrations (public — safe on the client).\n */\nexport type AnalyticsConfig = {\n /** GA4 Measurement ID, e.g. 'G-XXXXXXXXXX'. */\n ga4MeasurementId?: null | string\n /** GTM Container ID, e.g. 'GTM-XXXXXXX'. */\n gtmContainerId?: null | string\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GACD,WAKC"} \ No newline at end of file diff --git a/dist/modules/blocks/RenderBlocks.js b/dist/modules/blocks/RenderBlocks.js index 2cfb1ea..3c08975 100644 --- a/dist/modules/blocks/RenderBlocks.js +++ b/dist/modules/blocks/RenderBlocks.js @@ -39,6 +39,6 @@ import { Fragment } from 'react'; }, index); }) }); -} +} //# sourceMappingURL=RenderBlocks.js.map //# sourceMappingURL=RenderBlocks.js.map \ No newline at end of file diff --git a/dist/modules/blocks/RenderBlocks.js.map b/dist/modules/blocks/RenderBlocks.js.map index a292dfe..03cf0fc 100644 --- a/dist/modules/blocks/RenderBlocks.js.map +++ b/dist/modules/blocks/RenderBlocks.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/blocks/RenderBlocks.tsx"],"sourcesContent":["import { Fragment } from 'react'\n\nimport type { BlockComponentMap, BlockData, EnhanceProps } from './types.js'\n\nexport type RenderBlocksProps = {\n /** Block data array from a Payload document (e.g. page.layout). */\n blocks: BlockData[] | null | undefined\n /** Client-provided map of blockType → component. */\n components: BlockComponentMap\n /**\n * Optional client hook to inject block-specific props (nav anchors, etc.).\n * Keeps the engine generic — the plugin knows no concrete block types.\n */\n enhanceProps?: EnhanceProps\n}\n\n/**\n * Generic, server-side block renderer.\n *\n * Iterates a document's blocks and renders each via the client's component\n * map. Unknown blocks are skipped (null), never thrown. Block-specific logic is\n * delegated to the client's optional `enhanceProps` — the plugin itself is\n * block-agnostic.\n *\n * The client owns components and block schemas (they pass `components` and\n * define blocks in their config); the plugin owns only the iteration and\n * wiring. No wrapper markup is added — spacing/layout belong to the client's\n * components.\n *\n * Nested blocks: a block that needs to render child blocks should render its\n * own and import the registry itself, rather than receiving the\n * component map as a prop. Passing the map as a prop breaks React Server\n * Components — functions (client components) can't cross the server→client\n * boundary via props.\n */\nexport function RenderBlocks({ blocks, components, enhanceProps }: RenderBlocksProps) {\n if (!blocks || !Array.isArray(blocks) || blocks.length === 0) {\n return null\n }\n\n return (\n \n {blocks.map((block, index) => {\n const Component = components[block.blockType]\n if (!Component) {return null}\n\n const extra = enhanceProps ? enhanceProps({ allBlocks: blocks, block, index }) : {}\n\n return \n })}\n \n )\n}\n"],"names":["Fragment","RenderBlocks","blocks","components","enhanceProps","Array","isArray","length","map","block","index","Component","blockType","extra","allBlocks"],"mappings":";AAAA,SAASA,QAAQ,QAAQ,QAAO;AAgBhC;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,aAAa,EAAEC,MAAM,EAAEC,UAAU,EAAEC,YAAY,EAAqB;IAClF,IAAI,CAACF,UAAU,CAACG,MAAMC,OAAO,CAACJ,WAAWA,OAAOK,MAAM,KAAK,GAAG;QAC5D,OAAO;IACT;IAEA,qBACE,KAACP;kBACEE,OAAOM,GAAG,CAAC,CAACC,OAAOC;YAClB,MAAMC,YAAYR,UAAU,CAACM,MAAMG,SAAS,CAAC;YAC7C,IAAI,CAACD,WAAW;gBAAC,OAAO;YAAI;YAE5B,MAAME,QAAQT,eAAeA,aAAa;gBAAEU,WAAWZ;gBAAQO;gBAAOC;YAAM,KAAK,CAAC;YAElF,qBAAO,KAACC;gBAAuB,GAAGF,KAAK;gBAAG,GAAGI,KAAK;eAA3BH;QACzB;;AAGN"} \ No newline at end of file +{"version":3,"sources":["RenderBlocks.js"],"sourcesContent":["import { Fragment } from 'react'\n\nimport type { BlockComponentMap, BlockData, EnhanceProps } from './types.js'\n\nexport type RenderBlocksProps = {\n /** Block data array from a Payload document (e.g. page.layout). */\n blocks: BlockData[] | null | undefined\n /** Client-provided map of blockType → component. */\n components: BlockComponentMap\n /**\n * Optional client hook to inject block-specific props (nav anchors, etc.).\n * Keeps the engine generic — the plugin knows no concrete block types.\n */\n enhanceProps?: EnhanceProps\n}\n\n/**\n * Generic, server-side block renderer.\n *\n * Iterates a document's blocks and renders each via the client's component\n * map. Unknown blocks are skipped (null), never thrown. Block-specific logic is\n * delegated to the client's optional `enhanceProps` — the plugin itself is\n * block-agnostic.\n *\n * The client owns components and block schemas (they pass `components` and\n * define blocks in their config); the plugin owns only the iteration and\n * wiring. No wrapper markup is added — spacing/layout belong to the client's\n * components.\n *\n * Nested blocks: a block that needs to render child blocks should render its\n * own and import the registry itself, rather than receiving the\n * component map as a prop. Passing the map as a prop breaks React Server\n * Components — functions (client components) can't cross the server→client\n * boundary via props.\n */\nexport function RenderBlocks({ blocks, components, enhanceProps }: RenderBlocksProps) {\n if (!blocks || !Array.isArray(blocks) || blocks.length === 0) {\n return null\n }\n\n return (\n \n {blocks.map((block, index) => {\n const Component = components[block.blockType]\n if (!Component) {return null}\n\n const extra = enhanceProps ? enhanceProps({ allBlocks: blocks, block, index }) : {}\n\n return \n })}\n \n )\n}\n"],"names":["Fragment","RenderBlocks","blocks","components","enhanceProps","Array","isArray","length","map","block","index","Component","blockType","extra","allBlocks"],"mappings":";AAAA,SAASA,QAAQ,QAAQ,QAAO;AAgBhC;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,aAAa,EAAEC,MAAM,EAAEC,UAAU,EAAEC,YAAY,EAAqB;IAClF,IAAI,CAACF,UAAU,CAACG,MAAMC,OAAO,CAACJ,WAAWA,OAAOK,MAAM,KAAK,GAAG;QAC5D,OAAO;IACT;IAEA,OAAA,WAAA,GACE,KAACP,UAAAA;kBACEE,OAAOM,GAAG,CAAC,CAACC,OAAOC;YAClB,MAAMC,YAAYR,UAAU,CAACM,MAAMG,SAAS,CAAC;YAC7C,IAAI,CAACD,WAAW;gBAAC,OAAO;YAAI;YAE5B,MAAME,QAAQT,eAAeA,aAAa;gBAAEU,WAAWZ;gBAAQO;gBAAOC;YAAM,KAAK,CAAC;YAElF,OAAA,WAAA,GAAO,KAACC,WAAAA;gBAAuB,GAAGF,KAAK;gBAAG,GAAGI,KAAK;eAA3BH;QACzB;;AAGN"} \ No newline at end of file diff --git a/dist/modules/blocks/index.js b/dist/modules/blocks/index.js index 4563f88..61673a1 100644 --- a/dist/modules/blocks/index.js +++ b/dist/modules/blocks/index.js @@ -1,3 +1,3 @@ -export { RenderBlocks } from './RenderBlocks.js'; +export { RenderBlocks } from './RenderBlocks.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/blocks/index.js.map b/dist/modules/blocks/index.js.map index b502360..744080b 100644 --- a/dist/modules/blocks/index.js.map +++ b/dist/modules/blocks/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/blocks/index.ts"],"sourcesContent":["export { RenderBlocks } from './RenderBlocks.js'\nexport type { RenderBlocksProps } from './RenderBlocks.js'\nexport type { BlockComponentMap, BlockData, EnhanceProps } from './types.js'\n"],"names":["RenderBlocks"],"mappings":"AAAA,SAASA,YAAY,QAAQ,oBAAmB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export { RenderBlocks } from './RenderBlocks.js'\nexport type { RenderBlocksProps } from './RenderBlocks.js'\nexport type { BlockComponentMap, BlockData, EnhanceProps } from './types.js'\n"],"names":["RenderBlocks"],"mappings":"AAAA,SAASA,YAAY,QAAQ,oBAAmB"} \ No newline at end of file diff --git a/dist/modules/blocks/types.js b/dist/modules/blocks/types.js index f7a3e54..bc9d967 100644 --- a/dist/modules/blocks/types.js +++ b/dist/modules/blocks/types.js @@ -4,6 +4,6 @@ * Lets the client inject block-specific logic (e.g. collect anchors for a nav * block) without the plugin knowing any concrete block types. Returns extra * props merged into the rendered block. - */ export { }; + */ export { }; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/blocks/types.js.map b/dist/modules/blocks/types.js.map index 9650f08..6e93b93 100644 --- a/dist/modules/blocks/types.js.map +++ b/dist/modules/blocks/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/blocks/types.ts"],"sourcesContent":["import type { ComponentType } from 'react'\n\n/**\n * A single block's data as stored by Payload — always has a blockType, plus\n * arbitrary block-specific fields. The plugin stays generic over the shape.\n */\nexport type BlockData = {\n [key: string]: unknown\n blockType: string\n}\n\n/**\n * Maps a blockType to the client's component for it.\n * The client owns the components; the plugin only receives this map.\n */\nexport type BlockComponentMap = Record>\n\n/**\n * Optional per-block prop enhancer supplied by the client.\n *\n * Lets the client inject block-specific logic (e.g. collect anchors for a nav\n * block) without the plugin knowing any concrete block types. Returns extra\n * props merged into the rendered block.\n */\nexport type EnhanceProps = (args: {\n allBlocks: BlockData[]\n block: BlockData\n index: number\n}) => Record\n"],"names":[],"mappings":"AAiBA;;;;;;CAMC,GACD,WAI6B"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["import type { ComponentType } from 'react'\n\n/**\n * A single block's data as stored by Payload — always has a blockType, plus\n * arbitrary block-specific fields. The plugin stays generic over the shape.\n */\nexport type BlockData = {\n [key: string]: unknown\n blockType: string\n}\n\n/**\n * Maps a blockType to the client's component for it.\n * The client owns the components; the plugin only receives this map.\n */\nexport type BlockComponentMap = Record>\n\n/**\n * Optional per-block prop enhancer supplied by the client.\n *\n * Lets the client inject block-specific logic (e.g. collect anchors for a nav\n * block) without the plugin knowing any concrete block types. Returns extra\n * props merged into the rendered block.\n */\nexport type EnhanceProps = (args: {\n allBlocks: BlockData[]\n block: BlockData\n index: number\n}) => Record\n"],"names":[],"mappings":"AAiBA;;;;;;CAMC,GACD,WAI6B"} \ No newline at end of file diff --git a/dist/modules/consent/ConsentContext.js b/dist/modules/consent/ConsentContext.js index 77a88bc..e6d5a89 100644 --- a/dist/modules/consent/ConsentContext.js +++ b/dist/modules/consent/ConsentContext.js @@ -19,6 +19,6 @@ export function useConsentContext() { throw new Error('useConsentContext must be used within ConsentProvider'); } return ctx; -} +} //# sourceMappingURL=ConsentContext.js.map //# sourceMappingURL=ConsentContext.js.map \ No newline at end of file diff --git a/dist/modules/consent/ConsentContext.js.map b/dist/modules/consent/ConsentContext.js.map index a5c7f2e..2b91bb0 100644 --- a/dist/modules/consent/ConsentContext.js.map +++ b/dist/modules/consent/ConsentContext.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/ConsentContext.tsx"],"sourcesContent":["'use client'\nimport { createContext, type ReactNode, use } from 'react'\n\nimport type { ConsentTexts } from './texts.js'\n\nimport { useConsent } from './useConsent.js'\n\ntype ConsentContextValue = { texts: ConsentTexts } & ReturnType\n\nconst ConsentContext = createContext(null)\n\nexport function ConsentProvider({ children, texts }: { children: ReactNode; texts: ConsentTexts }) {\n const consent = useConsent()\n return {children}\n}\n\nexport function useConsentContext(): ConsentContextValue {\n const ctx = use(ConsentContext)\n if (!ctx) {throw new Error('useConsentContext must be used within ConsentProvider')}\n return ctx\n}\n"],"names":["createContext","use","useConsent","ConsentContext","ConsentProvider","children","texts","consent","value","useConsentContext","ctx","Error"],"mappings":"AAAA;;AACA,SAASA,aAAa,EAAkBC,GAAG,QAAQ,QAAO;AAI1D,SAASC,UAAU,QAAQ,kBAAiB;AAI5C,MAAMC,+BAAiBH,cAA0C;AAEjE,OAAO,SAASI,gBAAgB,EAAEC,QAAQ,EAAEC,KAAK,EAAgD;IAC/F,MAAMC,UAAUL;IAChB,qBAAO,KAACC;QAAeK,OAAO;YAAE,GAAGD,OAAO;YAAED;QAAM;kBAAID;;AACxD;AAEA,OAAO,SAASI;IACd,MAAMC,MAAMT,IAAIE;IAChB,IAAI,CAACO,KAAK;QAAC,MAAM,IAAIC,MAAM;IAAwD;IACnF,OAAOD;AACT"} \ No newline at end of file +{"version":3,"sources":["ConsentContext.js"],"sourcesContent":["'use client'\nimport { createContext, type ReactNode, use } from 'react'\n\nimport type { ConsentTexts } from './texts.js'\n\nimport { useConsent } from './useConsent.js'\n\ntype ConsentContextValue = { texts: ConsentTexts } & ReturnType\n\nconst ConsentContext = createContext(null)\n\nexport function ConsentProvider({ children, texts }: { children: ReactNode; texts: ConsentTexts }) {\n const consent = useConsent()\n return {children}\n}\n\nexport function useConsentContext(): ConsentContextValue {\n const ctx = use(ConsentContext)\n if (!ctx) {throw new Error('useConsentContext must be used within ConsentProvider')}\n return ctx\n}\n"],"names":["createContext","use","useConsent","ConsentContext","ConsentProvider","children","texts","consent","value","useConsentContext","ctx","Error"],"mappings":"AAAA;;AACA,SAASA,aAAa,EAAkBC,GAAG,QAAQ,QAAO;AAI1D,SAASC,UAAU,QAAQ,kBAAiB;AAI5C,MAAMC,iBAAAA,WAAAA,GAAiBH,cAA0C;AAEjE,OAAO,SAASI,gBAAgB,EAAEC,QAAQ,EAAEC,KAAK,EAAgD;IAC/F,MAAMC,UAAUL;IAChB,OAAA,WAAA,GAAO,KAACC,gBAAAA;QAAeK,OAAO;YAAE,GAAGD,OAAO;YAAED;QAAM;kBAAID;;AACxD;AAEA,OAAO,SAASI;IACd,MAAMC,MAAMT,IAAIE;IAChB,IAAI,CAACO,KAAK;QAAC,MAAM,IAAIC,MAAM;IAAwD;IACnF,OAAOD;AACT"} \ No newline at end of file diff --git a/dist/modules/consent/CookieBanner.js b/dist/modules/consent/CookieBanner.js index aae002e..c4fd485 100644 --- a/dist/modules/consent/CookieBanner.js +++ b/dist/modules/consent/CookieBanner.js @@ -174,6 +174,6 @@ export function CookieBanner({ classNames } = {}) { }) }) }); -} +} //# sourceMappingURL=CookieBanner.js.map //# sourceMappingURL=CookieBanner.js.map \ No newline at end of file diff --git a/dist/modules/consent/CookieBanner.js.map b/dist/modules/consent/CookieBanner.js.map index f268c1a..31948dc 100644 --- a/dist/modules/consent/CookieBanner.js.map +++ b/dist/modules/consent/CookieBanner.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/CookieBanner.tsx"],"sourcesContent":["'use client'\nimport { Cookie } from 'lucide-react'\nimport { useEffect, useState } from 'react'\n\nimport { CONSENT_CATEGORIES, type ConsentCategory } from './categories.js'\nimport { useConsentContext } from './ConsentContext.js'\n\n/**\n * Optional class overrides — for when tokens aren't enough, e.g. turning the\n * bottom bar into a centred modal. Replaces the slot's classes outright.\n */\nexport type CookieBannerClassNames = {\n primaryButton?: string\n root?: string\n secondaryButton?: string\n}\n\n/**\n * Colours and radius come from CSS custom properties with built-in fallbacks,\n * so the banner looks right with no setup, and re-skinning per client means\n * declaring a few variables — no imports, no props, no specificity fights:\n *\n * ```css\n * :root {\n * --ipal-primary: #16a34a;\n * --ipal-radius: 1rem;\n * }\n * ```\n *\n * Available: --ipal-surface, --ipal-border, --ipal-text, --ipal-text-strong,\n * --ipal-text-muted, --ipal-primary, --ipal-primary-hover, --ipal-primary-text,\n * --ipal-hover, --ipal-radius. Each has a `-dark` counterpart used under\n * Tailwind's `dark:` variant (e.g. --ipal-surface-dark).\n *\n * Layout stays in Tailwind: spacing and flow aren't things a brand changes, and\n * exposing them as tokens would mean re-inventing CSS one variable at a time.\n */\nconst surface = 'bg-[var(--ipal-surface,#fff)] dark:bg-[var(--ipal-surface-dark,#171717)]'\nconst border = 'border-[var(--ipal-border,#e5e5e5)] dark:border-[var(--ipal-border-dark,#262626)]'\nconst radius = 'rounded-[var(--ipal-radius,0.375rem)]'\nconst accent = 'text-[var(--ipal-primary,#2563eb)]'\n\nconst defaults = {\n primaryButton: `${radius} px-3 py-1.5 text-sm font-medium bg-[var(--ipal-primary,#2563eb)] text-[var(--ipal-primary-text,#fff)] hover:bg-[var(--ipal-primary-hover,#1d4ed8)]`,\n root: `fixed inset-x-0 bottom-0 z-50 border-t p-4 shadow-lg ${surface} ${border}`,\n secondaryButton: `${radius} px-3 py-1.5 text-sm font-medium text-[var(--ipal-text,#404040)] hover:bg-[var(--ipal-hover,#f5f5f5)] dark:text-[var(--ipal-text-dark,#e5e5e5)] dark:hover:bg-[var(--ipal-hover-dark,#262626)]`,\n}\n\nconst bodyText = 'text-[var(--ipal-text,#404040)] dark:text-[var(--ipal-text-dark,#d4d4d4)]'\nconst strongText =\n 'text-[var(--ipal-text-strong,#171717)] dark:text-[var(--ipal-text-strong-dark,#f5f5f5)]'\nconst mutedText =\n 'text-[var(--ipal-text-muted,#737373)] dark:text-[var(--ipal-text-muted-dark,#a3a3a3)]'\n\nexport function CookieBanner({ classNames }: { classNames?: CookieBannerClassNames } = {}) {\n const { acceptAll, decided, rejectAll, savePreferences, state, texts } = useConsentContext()\n const [showSettings, setShowSettings] = useState(false)\n const [choices, setChoices] = useState>(state)\n\n useEffect(() => {\n setChoices(state)\n }, [state])\n\n if (decided) {return null}\n\n const toggle = (cat: ConsentCategory) => {\n if (cat === 'necessary') {return}\n setChoices((prev) => ({ ...prev, [cat]: !prev[cat] }))\n }\n\n const rootClass = classNames?.root ?? defaults.root\n const primaryClass = classNames?.primaryButton ?? defaults.primaryButton\n const secondaryClass = classNames?.secondaryButton ?? defaults.secondaryButton\n\n return (\n
\n
\n {!showSettings ? (\n
\n
\n \n

\n {texts.message}\n {texts.privacyLink && (\n <>\n {' '}\n \n {texts.privacyLink.label}\n \n \n )}\n

\n
\n
\n \n \n \n
\n
\n ) : (\n
\n

\n \n {texts.settingsTitle}\n

\n
    \n {CONSENT_CATEGORIES.map((cat) => (\n
  • \n
    \n

    \n {texts.categories[cat].title}\n

    \n

    {texts.categories[cat].description}

    \n
    \n toggle(cat)}\n type=\"checkbox\"\n />\n
  • \n ))}\n
\n
\n \n \n
\n
\n )}\n
\n
\n )\n}\n"],"names":["Cookie","useEffect","useState","CONSENT_CATEGORIES","useConsentContext","surface","border","radius","accent","defaults","primaryButton","root","secondaryButton","bodyText","strongText","mutedText","CookieBanner","classNames","acceptAll","decided","rejectAll","savePreferences","state","texts","showSettings","setShowSettings","choices","setChoices","toggle","cat","prev","rootClass","primaryClass","secondaryClass","div","aria-label","className","data-ipal","role","aria-hidden","p","message","privacyLink","a","href","label","button","onClick","buttons","settings","reject","h2","settingsTitle","ul","map","li","categories","title","description","input","checked","disabled","onChange","type","back","save"],"mappings":"AAAA;;AACA,SAASA,MAAM,QAAQ,eAAc;AACrC,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE3C,SAASC,kBAAkB,QAA8B,kBAAiB;AAC1E,SAASC,iBAAiB,QAAQ,sBAAqB;AAYvD;;;;;;;;;;;;;;;;;;;CAmBC,GACD,MAAMC,UAAU;AAChB,MAAMC,SAAS;AACf,MAAMC,SAAS;AACf,MAAMC,SAAS;AAEf,MAAMC,WAAW;IACfC,eAAe,GAAGH,OAAO,mJAAmJ,CAAC;IAC7KI,MAAM,CAAC,qDAAqD,EAAEN,QAAQ,CAAC,EAAEC,QAAQ;IACjFM,iBAAiB,GAAGL,OAAO,8LAA8L,CAAC;AAC5N;AAEA,MAAMM,WAAW;AACjB,MAAMC,aACJ;AACF,MAAMC,YACJ;AAEF,OAAO,SAASC,aAAa,EAAEC,UAAU,EAA2C,GAAG,CAAC,CAAC;IACvF,MAAM,EAAEC,SAAS,EAAEC,OAAO,EAAEC,SAAS,EAAEC,eAAe,EAAEC,KAAK,EAAEC,KAAK,EAAE,GAAGnB;IACzE,MAAM,CAACoB,cAAcC,gBAAgB,GAAGvB,SAAS;IACjD,MAAM,CAACwB,SAASC,WAAW,GAAGzB,SAA2CoB;IAEzErB,UAAU;QACR0B,WAAWL;IACb,GAAG;QAACA;KAAM;IAEV,IAAIH,SAAS;QAAC,OAAO;IAAI;IAEzB,MAAMS,SAAS,CAACC;QACd,IAAIA,QAAQ,aAAa;YAAC;QAAM;QAChCF,WAAW,CAACG,OAAU,CAAA;gBAAE,GAAGA,IAAI;gBAAE,CAACD,IAAI,EAAE,CAACC,IAAI,CAACD,IAAI;YAAC,CAAA;IACrD;IAEA,MAAME,YAAYd,YAAYN,QAAQF,SAASE,IAAI;IACnD,MAAMqB,eAAef,YAAYP,iBAAiBD,SAASC,aAAa;IACxE,MAAMuB,iBAAiBhB,YAAYL,mBAAmBH,SAASG,eAAe;IAE9E,qBACE,KAACsB;QAAIC,cAAW;QAAiBC,WAAWL;QAAWM,aAAU;QAASC,MAAK;kBAC7E,cAAA,KAACJ;YAAIE,WAAU;sBACZ,CAACZ,6BACA,MAACU;gBAAIE,WAAU;;kCACb,MAACF;wBAAIE,WAAU;;0CACb,KAACpC;gCAAOuC,eAAY;gCAAOH,WAAW,CAAC,iBAAiB,EAAE5B,QAAQ;;0CAClE,MAACgC;gCAAEJ,WAAW,CAAC,qBAAqB,EAAEvB,UAAU;;oCAC7CU,MAAMkB,OAAO;oCACblB,MAAMmB,WAAW,kBAChB;;4CACG;0DACD,KAACC;gDACCP,WAAW,GAAG5B,OAAO,6BAA6B,CAAC;gDACnDoC,MAAMrB,MAAMmB,WAAW,CAACE,IAAI;0DAE3BrB,MAAMmB,WAAW,CAACG,KAAK;;;;;;;;kCAMlC,MAACX;wBAAIE,WAAU;;0CACb,KAACU;gCAAOV,WAAWH;gCAAgBc,SAAS,IAAMtB,gBAAgB;0CAC/DF,MAAMyB,OAAO,CAACC,QAAQ;;0CAEzB,KAACH;gCAAOV,WAAWH;gCAAgBc,SAAS3B;0CACzCG,MAAMyB,OAAO,CAACE,MAAM;;0CAEvB,KAACJ;gCAAOV,WAAWJ;gCAAce,SAAS7B;0CACvCK,MAAMyB,OAAO,CAAC9B,SAAS;;;;;+BAK9B,MAACgB;gBAAIE,WAAU;;kCACb,MAACe;wBAAGf,WAAW,CAAC,gDAAgD,EAAEtB,YAAY;;0CAC5E,KAACd;gCAAOuC,eAAY;gCAAOH,WAAW,CAAC,QAAQ,EAAE5B,QAAQ;;4BACxDe,MAAM6B,aAAa;;;kCAEtB,KAACC;wBAAGjB,WAAU;kCACXjC,mBAAmBmD,GAAG,CAAC,CAACzB,oBACvB,MAAC0B;gCAAGnB,WAAU;;kDACZ,MAACF;;0DACC,KAACM;gDAAEJ,WAAW,CAAC,oBAAoB,EAAEtB,YAAY;0DAC9CS,MAAMiC,UAAU,CAAC3B,IAAI,CAAC4B,KAAK;;0DAE9B,KAACjB;gDAAEJ,WAAW,CAAC,QAAQ,EAAErB,WAAW;0DAAGQ,MAAMiC,UAAU,CAAC3B,IAAI,CAAC6B,WAAW;;;;kDAE1E,KAACC;wCACCC,SAAS/B,QAAQ,cAAc,OAAOH,OAAO,CAACG,IAAI;wCAClDO,WAAU;wCACVyB,UAAUhC,QAAQ;wCAClBiC,UAAU,IAAMlC,OAAOC;wCACvBkC,MAAK;;;+BAZmDlC;;kCAiBhE,MAACK;wBAAIE,WAAU;;0CACb,KAACU;gCAAOV,WAAWH;gCAAgBc,SAAS,IAAMtB,gBAAgB;0CAC/DF,MAAMyB,OAAO,CAACgB,IAAI;;0CAErB,KAAClB;gCAAOV,WAAWJ;gCAAce,SAAS,IAAM1B,gBAAgBK;0CAC7DH,MAAMyB,OAAO,CAACiB,IAAI;;;;;;;;AAQnC"} \ No newline at end of file +{"version":3,"sources":["CookieBanner.js"],"sourcesContent":["'use client'\nimport { Cookie } from 'lucide-react'\nimport { useEffect, useState } from 'react'\n\nimport { CONSENT_CATEGORIES, type ConsentCategory } from './categories.js'\nimport { useConsentContext } from './ConsentContext.js'\n\n/**\n * Optional class overrides — for when tokens aren't enough, e.g. turning the\n * bottom bar into a centred modal. Replaces the slot's classes outright.\n */\nexport type CookieBannerClassNames = {\n primaryButton?: string\n root?: string\n secondaryButton?: string\n}\n\n/**\n * Colours and radius come from CSS custom properties with built-in fallbacks,\n * so the banner looks right with no setup, and re-skinning per client means\n * declaring a few variables — no imports, no props, no specificity fights:\n *\n * ```css\n * :root {\n * --ipal-primary: #16a34a;\n * --ipal-radius: 1rem;\n * }\n * ```\n *\n * Available: --ipal-surface, --ipal-border, --ipal-text, --ipal-text-strong,\n * --ipal-text-muted, --ipal-primary, --ipal-primary-hover, --ipal-primary-text,\n * --ipal-hover, --ipal-radius. Each has a `-dark` counterpart used under\n * Tailwind's `dark:` variant (e.g. --ipal-surface-dark).\n *\n * Layout stays in Tailwind: spacing and flow aren't things a brand changes, and\n * exposing them as tokens would mean re-inventing CSS one variable at a time.\n */\nconst surface = 'bg-[var(--ipal-surface,#fff)] dark:bg-[var(--ipal-surface-dark,#171717)]'\nconst border = 'border-[var(--ipal-border,#e5e5e5)] dark:border-[var(--ipal-border-dark,#262626)]'\nconst radius = 'rounded-[var(--ipal-radius,0.375rem)]'\nconst accent = 'text-[var(--ipal-primary,#2563eb)]'\n\nconst defaults = {\n primaryButton: `${radius} px-3 py-1.5 text-sm font-medium bg-[var(--ipal-primary,#2563eb)] text-[var(--ipal-primary-text,#fff)] hover:bg-[var(--ipal-primary-hover,#1d4ed8)]`,\n root: `fixed inset-x-0 bottom-0 z-50 border-t p-4 shadow-lg ${surface} ${border}`,\n secondaryButton: `${radius} px-3 py-1.5 text-sm font-medium text-[var(--ipal-text,#404040)] hover:bg-[var(--ipal-hover,#f5f5f5)] dark:text-[var(--ipal-text-dark,#e5e5e5)] dark:hover:bg-[var(--ipal-hover-dark,#262626)]`,\n}\n\nconst bodyText = 'text-[var(--ipal-text,#404040)] dark:text-[var(--ipal-text-dark,#d4d4d4)]'\nconst strongText =\n 'text-[var(--ipal-text-strong,#171717)] dark:text-[var(--ipal-text-strong-dark,#f5f5f5)]'\nconst mutedText =\n 'text-[var(--ipal-text-muted,#737373)] dark:text-[var(--ipal-text-muted-dark,#a3a3a3)]'\n\nexport function CookieBanner({ classNames }: { classNames?: CookieBannerClassNames } = {}) {\n const { acceptAll, decided, rejectAll, savePreferences, state, texts } = useConsentContext()\n const [showSettings, setShowSettings] = useState(false)\n const [choices, setChoices] = useState>(state)\n\n useEffect(() => {\n setChoices(state)\n }, [state])\n\n if (decided) {return null}\n\n const toggle = (cat: ConsentCategory) => {\n if (cat === 'necessary') {return}\n setChoices((prev) => ({ ...prev, [cat]: !prev[cat] }))\n }\n\n const rootClass = classNames?.root ?? defaults.root\n const primaryClass = classNames?.primaryButton ?? defaults.primaryButton\n const secondaryClass = classNames?.secondaryButton ?? defaults.secondaryButton\n\n return (\n
\n
\n {!showSettings ? (\n
\n
\n \n

\n {texts.message}\n {texts.privacyLink && (\n <>\n {' '}\n \n {texts.privacyLink.label}\n \n \n )}\n

\n
\n
\n \n \n \n
\n
\n ) : (\n
\n

\n \n {texts.settingsTitle}\n

\n
    \n {CONSENT_CATEGORIES.map((cat) => (\n
  • \n
    \n

    \n {texts.categories[cat].title}\n

    \n

    {texts.categories[cat].description}

    \n
    \n toggle(cat)}\n type=\"checkbox\"\n />\n
  • \n ))}\n
\n
\n \n \n
\n
\n )}\n
\n
\n )\n}\n"],"names":["Cookie","useEffect","useState","CONSENT_CATEGORIES","useConsentContext","surface","border","radius","accent","defaults","primaryButton","root","secondaryButton","bodyText","strongText","mutedText","CookieBanner","classNames","acceptAll","decided","rejectAll","savePreferences","state","texts","showSettings","setShowSettings","choices","setChoices","toggle","cat","prev","rootClass","primaryClass","secondaryClass","div","aria-label","className","data-ipal","role","aria-hidden","p","message","privacyLink","a","href","label","button","onClick","buttons","settings","reject","h2","settingsTitle","ul","map","li","categories","title","description","input","checked","disabled","onChange","type","back","save"],"mappings":"AAAA;;AACA,SAASA,MAAM,QAAQ,eAAc;AACrC,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE3C,SAASC,kBAAkB,QAA8B,kBAAiB;AAC1E,SAASC,iBAAiB,QAAQ,sBAAqB;AAYvD;;;;;;;;;;;;;;;;;;;CAmBC,GACD,MAAMC,UAAU;AAChB,MAAMC,SAAS;AACf,MAAMC,SAAS;AACf,MAAMC,SAAS;AAEf,MAAMC,WAAW;IACfC,eAAe,GAAGH,OAAO,mJAAmJ,CAAC;IAC7KI,MAAM,CAAC,qDAAqD,EAAEN,QAAQ,CAAC,EAAEC,QAAQ;IACjFM,iBAAiB,GAAGL,OAAO,8LAA8L,CAAC;AAC5N;AAEA,MAAMM,WAAW;AACjB,MAAMC,aACJ;AACF,MAAMC,YACJ;AAEF,OAAO,SAASC,aAAa,EAAEC,UAAU,EAA2C,GAAG,CAAC,CAAC;IACvF,MAAM,EAAEC,SAAS,EAAEC,OAAO,EAAEC,SAAS,EAAEC,eAAe,EAAEC,KAAK,EAAEC,KAAK,EAAE,GAAGnB;IACzE,MAAM,CAACoB,cAAcC,gBAAgB,GAAGvB,SAAS;IACjD,MAAM,CAACwB,SAASC,WAAW,GAAGzB,SAA2CoB;IAEzErB,UAAU;QACR0B,WAAWL;IACb,GAAG;QAACA;KAAM;IAEV,IAAIH,SAAS;QAAC,OAAO;IAAI;IAEzB,MAAMS,SAAS,CAACC;QACd,IAAIA,QAAQ,aAAa;YAAC;QAAM;QAChCF,WAAW,CAACG,OAAU,CAAA;gBAAE,GAAGA,IAAI;gBAAE,CAACD,IAAI,EAAE,CAACC,IAAI,CAACD,IAAI;aAAC,CAAA;IACrD;IAEA,MAAME,YAAYd,YAAYN,QAAQF,SAASE,IAAI;IACnD,MAAMqB,eAAef,YAAYP,iBAAiBD,SAASC,aAAa;IACxE,MAAMuB,iBAAiBhB,YAAYL,mBAAmBH,SAASG,eAAe;IAE9E,OAAA,WAAA,GACE,KAACsB,OAAAA;QAAIC,cAAW;QAAiBC,WAAWL;QAAWM,aAAU;QAASC,MAAK;kBAC7E,WAAA,GAAA,KAACJ,OAAAA;YAAIE,WAAU;sBACZ,CAACZ,eAAAA,WAAAA,GACA,MAACU,OAAAA;gBAAIE,WAAU;;kCACb,MAACF,OAAAA;wBAAIE,WAAU;;0CACb,KAACpC,QAAAA;gCAAOuC,eAAY;gCAAOH,WAAW,CAAC,iBAAiB,EAAE5B,QAAQ;;0CAClE,MAACgC,KAAAA;gCAAEJ,WAAW,CAAC,qBAAqB,EAAEvB,UAAU;;oCAC7CU,MAAMkB,OAAO;oCACblB,MAAMmB,WAAW,IAAA,WAAA,GAChB,MAAA,WAAA;;4CACG;0DACD,KAACC,KAAAA;gDACCP,WAAW,GAAG5B,OAAO,6BAA6B,CAAC;gDACnDoC,MAAMrB,MAAMmB,WAAW,CAACE,IAAI;0DAE3BrB,MAAMmB,WAAW,CAACG,KAAK;;;;;;;;kCAMlC,MAACX,OAAAA;wBAAIE,WAAU;;0CACb,KAACU,UAAAA;gCAAOV,WAAWH;gCAAgBc,SAAS,IAAMtB,gBAAgB;0CAC/DF,MAAMyB,OAAO,CAACC,QAAQ;;0CAEzB,KAACH,UAAAA;gCAAOV,WAAWH;gCAAgBc,SAAS3B;0CACzCG,MAAMyB,OAAO,CAACE,MAAM;;0CAEvB,KAACJ,UAAAA;gCAAOV,WAAWJ;gCAAce,SAAS7B;0CACvCK,MAAMyB,OAAO,CAAC9B,SAAS;;;;;+BAK9B,MAACgB,OAAAA;gBAAIE,WAAU;;kCACb,MAACe,MAAAA;wBAAGf,WAAW,CAAC,gDAAgD,EAAEtB,YAAY;;0CAC5E,KAACd,QAAAA;gCAAOuC,eAAY;gCAAOH,WAAW,CAAC,QAAQ,EAAE5B,QAAQ;;4BACxDe,MAAM6B,aAAa;;;kCAEtB,KAACC,MAAAA;wBAAGjB,WAAU;kCACXjC,mBAAmBmD,GAAG,CAAC,CAACzB,MAAAA,WAAAA,GACvB,MAAC0B,MAAAA;gCAAGnB,WAAU;;kDACZ,MAACF,OAAAA;;0DACC,KAACM,KAAAA;gDAAEJ,WAAW,CAAC,oBAAoB,EAAEtB,YAAY;0DAC9CS,MAAMiC,UAAU,CAAC3B,IAAI,CAAC4B,KAAK;;0DAE9B,KAACjB,KAAAA;gDAAEJ,WAAW,CAAC,QAAQ,EAAErB,WAAW;0DAAGQ,MAAMiC,UAAU,CAAC3B,IAAI,CAAC6B,WAAW;;;;kDAE1E,KAACC,SAAAA;wCACCC,SAAS/B,QAAQ,cAAc,OAAOH,OAAO,CAACG,IAAI;wCAClDO,WAAU;wCACVyB,UAAUhC,QAAQ;wCAClBiC,UAAU,IAAMlC,OAAOC;wCACvBkC,MAAK;;;+BAZmDlC;;kCAiBhE,MAACK,OAAAA;wBAAIE,WAAU;;0CACb,KAACU,UAAAA;gCAAOV,WAAWH;gCAAgBc,SAAS,IAAMtB,gBAAgB;0CAC/DF,MAAMyB,OAAO,CAACgB,IAAI;;0CAErB,KAAClB,UAAAA;gCAAOV,WAAWJ;gCAAce,SAAS,IAAM1B,gBAAgBK;0CAC7DH,MAAMyB,OAAO,CAACiB,IAAI;;;;;;;;AAQnC"} \ No newline at end of file diff --git a/dist/modules/consent/CookieButton.js b/dist/modules/consent/CookieButton.js index 9b8ec8e..776f670 100644 --- a/dist/modules/consent/CookieButton.js +++ b/dist/modules/consent/CookieButton.js @@ -27,6 +27,6 @@ import { useConsentContext } from './ConsentContext.js'; className: "h-5 w-5 text-[var(--ipal-text,#525252)] dark:text-[var(--ipal-text-dark,#d4d4d4)]" }) }); -} +} //# sourceMappingURL=CookieButton.js.map //# sourceMappingURL=CookieButton.js.map \ No newline at end of file diff --git a/dist/modules/consent/CookieButton.js.map b/dist/modules/consent/CookieButton.js.map index 42d2f2e..fef9555 100644 --- a/dist/modules/consent/CookieButton.js.map +++ b/dist/modules/consent/CookieButton.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/CookieButton.tsx"],"sourcesContent":["'use client'\nimport { Cookie } from 'lucide-react'\n\nimport { useConsentContext } from './ConsentContext.js'\n\n/**\n * Floating \"cookie settings\" button — lets visitors reopen the consent panel\n * after deciding (GDPR: withdrawing consent must be as easy as giving it).\n * Hidden while the banner is showing.\n *\n * Colours follow the same CSS custom properties as CookieBanner\n * (--ipal-surface, --ipal-border, --ipal-hover, --ipal-text), so a client's\n * palette applies to both without touching either component. Pass className to\n * replace the styling outright.\n */\nexport function CookieButton({ className }: { className?: string } = {}) {\n const { decided, reopen } = useConsentContext()\n\n if (!decided) {return null}\n\n const cls =\n className ??\n 'fixed bottom-4 left-4 z-40 flex h-11 w-11 items-center justify-center rounded-full border shadow-md transition-colors ' +\n 'bg-[var(--ipal-surface,#fff)] border-[var(--ipal-border,#e5e5e5)] hover:bg-[var(--ipal-hover,#f5f5f5)] ' +\n 'dark:bg-[var(--ipal-surface-dark,#171717)] dark:border-[var(--ipal-border-dark,#262626)] dark:hover:bg-[var(--ipal-hover-dark,#262626)]'\n\n return (\n \n )\n}\n"],"names":["Cookie","useConsentContext","CookieButton","className","decided","reopen","cls","button","aria-label","data-ipal","onClick","aria-hidden"],"mappings":"AAAA;;AACA,SAASA,MAAM,QAAQ,eAAc;AAErC,SAASC,iBAAiB,QAAQ,sBAAqB;AAEvD;;;;;;;;;CASC,GACD,OAAO,SAASC,aAAa,EAAEC,SAAS,EAA0B,GAAG,CAAC,CAAC;IACrE,MAAM,EAAEC,OAAO,EAAEC,MAAM,EAAE,GAAGJ;IAE5B,IAAI,CAACG,SAAS;QAAC,OAAO;IAAI;IAE1B,MAAME,MACJH,aACA,2HACE,4GACA;IAEJ,qBACE,KAACI;QAAOC,cAAW;QAAkBL,WAAWG;QAAKG,aAAU;QAAgBC,SAASL;kBACtF,cAAA,KAACL;YACCW,eAAY;YACZR,WAAU;;;AAIlB"} \ No newline at end of file +{"version":3,"sources":["CookieButton.js"],"sourcesContent":["'use client'\nimport { Cookie } from 'lucide-react'\n\nimport { useConsentContext } from './ConsentContext.js'\n\n/**\n * Floating \"cookie settings\" button — lets visitors reopen the consent panel\n * after deciding (GDPR: withdrawing consent must be as easy as giving it).\n * Hidden while the banner is showing.\n *\n * Colours follow the same CSS custom properties as CookieBanner\n * (--ipal-surface, --ipal-border, --ipal-hover, --ipal-text), so a client's\n * palette applies to both without touching either component. Pass className to\n * replace the styling outright.\n */\nexport function CookieButton({ className }: { className?: string } = {}) {\n const { decided, reopen } = useConsentContext()\n\n if (!decided) {return null}\n\n const cls =\n className ??\n 'fixed bottom-4 left-4 z-40 flex h-11 w-11 items-center justify-center rounded-full border shadow-md transition-colors ' +\n 'bg-[var(--ipal-surface,#fff)] border-[var(--ipal-border,#e5e5e5)] hover:bg-[var(--ipal-hover,#f5f5f5)] ' +\n 'dark:bg-[var(--ipal-surface-dark,#171717)] dark:border-[var(--ipal-border-dark,#262626)] dark:hover:bg-[var(--ipal-hover-dark,#262626)]'\n\n return (\n \n )\n}\n"],"names":["Cookie","useConsentContext","CookieButton","className","decided","reopen","cls","button","aria-label","data-ipal","onClick","aria-hidden"],"mappings":"AAAA;;AACA,SAASA,MAAM,QAAQ,eAAc;AAErC,SAASC,iBAAiB,QAAQ,sBAAqB;AAEvD;;;;;;;;;CASC,GACD,OAAO,SAASC,aAAa,EAAEC,SAAS,EAA0B,GAAG,CAAC,CAAC;IACrE,MAAM,EAAEC,OAAO,EAAEC,MAAM,EAAE,GAAGJ;IAE5B,IAAI,CAACG,SAAS;QAAC,OAAO;IAAI;IAE1B,MAAME,MACJH,aACA,2HACE,4GACA;IAEJ,OAAA,WAAA,GACE,KAACI,UAAAA;QAAOC,cAAW;QAAkBL,WAAWG;QAAKG,aAAU;QAAgBC,SAASL;kBACtF,WAAA,GAAA,KAACL,QAAAA;YACCW,eAAY;YACZR,WAAU;;;AAIlB"} \ No newline at end of file diff --git a/dist/modules/consent/categories.js b/dist/modules/consent/categories.js index 5a7fc4d..b499e0d 100644 --- a/dist/modules/consent/categories.js +++ b/dist/modules/consent/categories.js @@ -21,6 +21,6 @@ export const ACCEPT_ALL_CONSENT = { }; export const REJECT_ALL_CONSENT = { ...DEFAULT_CONSENT -}; +}; //# sourceMappingURL=categories.js.map //# sourceMappingURL=categories.js.map \ No newline at end of file diff --git a/dist/modules/consent/categories.js.map b/dist/modules/consent/categories.js.map index 20e7768..c7657ce 100644 --- a/dist/modules/consent/categories.js.map +++ b/dist/modules/consent/categories.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/categories.ts"],"sourcesContent":["/**\n * Cookie consent categories (GDPR). 'necessary' is always granted and cannot be\n * disabled. The rest default to false until the user opts in.\n */\nexport const CONSENT_CATEGORIES = ['necessary', 'functional', 'analytics', 'marketing'] as const\n\nexport type ConsentCategory = (typeof CONSENT_CATEGORIES)[number]\n\nexport type ConsentState = Record\n\nexport const DEFAULT_CONSENT: ConsentState = {\n necessary: true,\n functional: false,\n analytics: false,\n marketing: false,\n}\n\nexport const ACCEPT_ALL_CONSENT: ConsentState = {\n necessary: true,\n functional: true,\n analytics: true,\n marketing: true,\n}\n\nexport const REJECT_ALL_CONSENT: ConsentState = { ...DEFAULT_CONSENT }\n"],"names":["CONSENT_CATEGORIES","DEFAULT_CONSENT","necessary","functional","analytics","marketing","ACCEPT_ALL_CONSENT","REJECT_ALL_CONSENT"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,qBAAqB;IAAC;IAAa;IAAc;IAAa;CAAY,CAAS;AAMhG,OAAO,MAAMC,kBAAgC;IAC3CC,WAAW;IACXC,YAAY;IACZC,WAAW;IACXC,WAAW;AACb,EAAC;AAED,OAAO,MAAMC,qBAAmC;IAC9CJ,WAAW;IACXC,YAAY;IACZC,WAAW;IACXC,WAAW;AACb,EAAC;AAED,OAAO,MAAME,qBAAmC;IAAE,GAAGN,eAAe;AAAC,EAAC"} \ No newline at end of file +{"version":3,"sources":["categories.js"],"sourcesContent":["/**\n * Cookie consent categories (GDPR). 'necessary' is always granted and cannot be\n * disabled. The rest default to false until the user opts in.\n */\nexport const CONSENT_CATEGORIES = ['necessary', 'functional', 'analytics', 'marketing'] as const\n\nexport type ConsentCategory = (typeof CONSENT_CATEGORIES)[number]\n\nexport type ConsentState = Record\n\nexport const DEFAULT_CONSENT: ConsentState = {\n necessary: true,\n functional: false,\n analytics: false,\n marketing: false,\n}\n\nexport const ACCEPT_ALL_CONSENT: ConsentState = {\n necessary: true,\n functional: true,\n analytics: true,\n marketing: true,\n}\n\nexport const REJECT_ALL_CONSENT: ConsentState = { ...DEFAULT_CONSENT }\n"],"names":["CONSENT_CATEGORIES","DEFAULT_CONSENT","necessary","functional","analytics","marketing","ACCEPT_ALL_CONSENT","REJECT_ALL_CONSENT"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,qBAAqB;IAAC;IAAa;IAAc;IAAa;CAAY,CAAS;AAMhG,OAAO,MAAMC,kBAAgC;IAC3CC,WAAW;IACXC,YAAY;IACZC,WAAW;IACXC,WAAW;AACb,EAAC;AAED,OAAO,MAAMC,qBAAmC;IAC9CJ,WAAW;IACXC,YAAY;IACZC,WAAW;IACXC,WAAW;AACb,EAAC;AAED,OAAO,MAAME,qBAAmC;IAAE,GAAGN,eAAe;AAAC,EAAC"} \ No newline at end of file diff --git a/dist/modules/consent/client.js b/dist/modules/consent/client.js index 12946c3..dc590db 100644 --- a/dist/modules/consent/client.js +++ b/dist/modules/consent/client.js @@ -3,6 +3,6 @@ export { ConsentProvider, useConsentContext } from './ConsentContext.js'; export { CookieBanner } from './CookieBanner.js'; export { CookieButton } from './CookieButton.js'; // Client-only consent exports — hook, provider, and UI components. -export { useConsent } from './useConsent.js'; +export { useConsent } from './useConsent.js'; //# sourceMappingURL=client.js.map //# sourceMappingURL=client.js.map \ No newline at end of file diff --git a/dist/modules/consent/client.js.map b/dist/modules/consent/client.js.map index e28050d..6313822 100644 --- a/dist/modules/consent/client.js.map +++ b/dist/modules/consent/client.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/client.ts"],"sourcesContent":["'use client'\nexport { ConsentProvider, useConsentContext } from './ConsentContext.js'\nexport { CookieBanner } from './CookieBanner.js'\nexport type { CookieBannerClassNames } from './CookieBanner.js'\nexport { CookieButton } from './CookieButton.js'\n// Client-only consent exports — hook, provider, and UI components.\nexport { useConsent } from './useConsent.js'\n"],"names":["ConsentProvider","useConsentContext","CookieBanner","CookieButton","useConsent"],"mappings":"AAAA;AACA,SAASA,eAAe,EAAEC,iBAAiB,QAAQ,sBAAqB;AACxE,SAASC,YAAY,QAAQ,oBAAmB;AAEhD,SAASC,YAAY,QAAQ,oBAAmB;AAChD,mEAAmE;AACnE,SAASC,UAAU,QAAQ,kBAAiB"} \ No newline at end of file +{"version":3,"sources":["client.js"],"sourcesContent":["'use client'\nexport { ConsentProvider, useConsentContext } from './ConsentContext.js'\nexport { CookieBanner } from './CookieBanner.js'\nexport type { CookieBannerClassNames } from './CookieBanner.js'\nexport { CookieButton } from './CookieButton.js'\n// Client-only consent exports — hook, provider, and UI components.\nexport { useConsent } from './useConsent.js'\n"],"names":["ConsentProvider","useConsentContext","CookieBanner","CookieButton","useConsent"],"mappings":"AAAA;AACA,SAASA,eAAe,EAAEC,iBAAiB,QAAQ,sBAAqB;AACxE,SAASC,YAAY,QAAQ,oBAAmB;AAEhD,SAASC,YAAY,QAAQ,oBAAmB;AAChD,mEAAmE;AACnE,SAASC,UAAU,QAAQ,kBAAiB"} \ No newline at end of file diff --git a/dist/modules/consent/getConsentTexts.js b/dist/modules/consent/getConsentTexts.js index 6b2a0be..5d1cada 100644 --- a/dist/modules/consent/getConsentTexts.js +++ b/dist/modules/consent/getConsentTexts.js @@ -74,6 +74,6 @@ import { CONSENT_CATEGORIES } from './categories.js'; privacyLink, settingsTitle: g.settingsTitle || FALLBACK.settingsTitle }; -} +} //# sourceMappingURL=getConsentTexts.js.map //# sourceMappingURL=getConsentTexts.js.map \ No newline at end of file diff --git a/dist/modules/consent/getConsentTexts.js.map b/dist/modules/consent/getConsentTexts.js.map index af0898f..afef696 100644 --- a/dist/modules/consent/getConsentTexts.js.map +++ b/dist/modules/consent/getConsentTexts.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/getConsentTexts.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig } from '../i18n/index.js'\nimport type { ConsentTexts } from './texts.js'\n\nimport { getSystemPagePath } from '../pages/index.js'\nimport { getGlobal } from '../payload/index.js'\nimport { CONSENT_CATEGORIES } from './categories.js'\n\n/** English fallback used when the global has no value for a field. */\nconst FALLBACK: ConsentTexts = {\n buttons: {\n acceptAll: 'Accept all',\n back: 'Back',\n reject: 'Reject',\n save: 'Save preferences',\n settings: 'Settings',\n },\n categories: {\n analytics: { description: 'Help us understand usage.', title: 'Analytics' },\n functional: { description: 'Remember preferences.', title: 'Functional' },\n marketing: { description: 'Used to show relevant ads.', title: 'Marketing' },\n necessary: {\n description:\n 'Required for the site to work, including your language and theme preferences. Always on.',\n title: 'Necessary',\n },\n },\n message:\n 'We use cookies to run the site, analyze traffic, and improve your experience. You can accept all, reject non-essential, or choose which to allow.',\n privacyLink: null,\n settingsTitle: 'Cookie settings',\n}\n\ntype CookieSettingsData = {\n buttons?: null | Partial\n categories?: Array<{ description?: null | string; key: string; title?: null | string }> | null\n message?: null | string\n settingsTitle?: null | string\n}\n\ntype GetConsentTextsArgs = {\n config: I18nConfig\n locale?: string\n payload: BasePayload\n /**\n * Privacy-policy label + the system page (from SiteSettings.privacyPolicy,\n * read with locale:'all') to link to. Optional — omit for no link.\n */\n privacyPolicy?: {\n label: string\n page: { slug?: null | Record } | null\n }\n}\n\n/**\n * Resolves consent banner texts from the CookieSettings global, falling back to\n * English defaults per field. The privacy-policy link is built from the pages\n * module (system page role), not stored in CookieSettings — one source of truth.\n */\nexport async function getConsentTexts({\n config,\n locale,\n payload,\n privacyPolicy,\n}: GetConsentTextsArgs): Promise {\n const g = await getGlobal(payload, 'cookie-settings', { locale })\n\n const categories = {} as ConsentTexts['categories']\n for (const cat of CONSENT_CATEGORIES) {\n const entry = g.categories?.find((c) => c.key === cat)\n categories[cat] = {\n description: entry?.description || FALLBACK.categories[cat].description,\n title: entry?.title || FALLBACK.categories[cat].title,\n }\n }\n\n let privacyLink: ConsentTexts['privacyLink'] = null\n if (privacyPolicy?.page && locale) {\n const href = getSystemPagePath({ config, locale, page: privacyPolicy.page })\n if (href) {\n privacyLink = { href, label: privacyPolicy.label }\n }\n }\n\n return {\n buttons: {\n acceptAll: g.buttons?.acceptAll || FALLBACK.buttons.acceptAll,\n back: g.buttons?.back || FALLBACK.buttons.back,\n reject: g.buttons?.reject || FALLBACK.buttons.reject,\n save: g.buttons?.save || FALLBACK.buttons.save,\n settings: g.buttons?.settings || FALLBACK.buttons.settings,\n },\n categories,\n message: g.message || FALLBACK.message,\n privacyLink,\n settingsTitle: g.settingsTitle || FALLBACK.settingsTitle,\n }\n}\n"],"names":["getSystemPagePath","getGlobal","CONSENT_CATEGORIES","FALLBACK","buttons","acceptAll","back","reject","save","settings","categories","analytics","description","title","functional","marketing","necessary","message","privacyLink","settingsTitle","getConsentTexts","config","locale","payload","privacyPolicy","g","cat","entry","find","c","key","page","href","label"],"mappings":"AAKA,SAASA,iBAAiB,QAAQ,oBAAmB;AACrD,SAASC,SAAS,QAAQ,sBAAqB;AAC/C,SAASC,kBAAkB,QAAQ,kBAAiB;AAEpD,oEAAoE,GACpE,MAAMC,WAAyB;IAC7BC,SAAS;QACPC,WAAW;QACXC,MAAM;QACNC,QAAQ;QACRC,MAAM;QACNC,UAAU;IACZ;IACAC,YAAY;QACVC,WAAW;YAAEC,aAAa;YAA6BC,OAAO;QAAY;QAC1EC,YAAY;YAAEF,aAAa;YAAyBC,OAAO;QAAa;QACxEE,WAAW;YAAEH,aAAa;YAA8BC,OAAO;QAAY;QAC3EG,WAAW;YACTJ,aACE;YACFC,OAAO;QACT;IACF;IACAI,SACE;IACFC,aAAa;IACbC,eAAe;AACjB;AAuBA;;;;CAIC,GACD,OAAO,eAAeC,gBAAgB,EACpCC,MAAM,EACNC,MAAM,EACNC,OAAO,EACPC,aAAa,EACO;IACpB,MAAMC,IAAI,MAAMxB,UAA8BsB,SAAS,mBAAmB;QAAED;IAAO;IAEnF,MAAMZ,aAAa,CAAC;IACpB,KAAK,MAAMgB,OAAOxB,mBAAoB;QACpC,MAAMyB,QAAQF,EAAEf,UAAU,EAAEkB,KAAK,CAACC,IAAMA,EAAEC,GAAG,KAAKJ;QAClDhB,UAAU,CAACgB,IAAI,GAAG;YAChBd,aAAae,OAAOf,eAAeT,SAASO,UAAU,CAACgB,IAAI,CAACd,WAAW;YACvEC,OAAOc,OAAOd,SAASV,SAASO,UAAU,CAACgB,IAAI,CAACb,KAAK;QACvD;IACF;IAEA,IAAIK,cAA2C;IAC/C,IAAIM,eAAeO,QAAQT,QAAQ;QACjC,MAAMU,OAAOhC,kBAAkB;YAAEqB;YAAQC;YAAQS,MAAMP,cAAcO,IAAI;QAAC;QAC1E,IAAIC,MAAM;YACRd,cAAc;gBAAEc;gBAAMC,OAAOT,cAAcS,KAAK;YAAC;QACnD;IACF;IAEA,OAAO;QACL7B,SAAS;YACPC,WAAWoB,EAAErB,OAAO,EAAEC,aAAaF,SAASC,OAAO,CAACC,SAAS;YAC7DC,MAAMmB,EAAErB,OAAO,EAAEE,QAAQH,SAASC,OAAO,CAACE,IAAI;YAC9CC,QAAQkB,EAAErB,OAAO,EAAEG,UAAUJ,SAASC,OAAO,CAACG,MAAM;YACpDC,MAAMiB,EAAErB,OAAO,EAAEI,QAAQL,SAASC,OAAO,CAACI,IAAI;YAC9CC,UAAUgB,EAAErB,OAAO,EAAEK,YAAYN,SAASC,OAAO,CAACK,QAAQ;QAC5D;QACAC;QACAO,SAASQ,EAAER,OAAO,IAAId,SAASc,OAAO;QACtCC;QACAC,eAAeM,EAAEN,aAAa,IAAIhB,SAASgB,aAAa;IAC1D;AACF"} \ No newline at end of file +{"version":3,"sources":["getConsentTexts.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig } from '../i18n/index.js'\nimport type { ConsentTexts } from './texts.js'\n\nimport { getSystemPagePath } from '../pages/index.js'\nimport { getGlobal } from '../payload/index.js'\nimport { CONSENT_CATEGORIES } from './categories.js'\n\n/** English fallback used when the global has no value for a field. */\nconst FALLBACK: ConsentTexts = {\n buttons: {\n acceptAll: 'Accept all',\n back: 'Back',\n reject: 'Reject',\n save: 'Save preferences',\n settings: 'Settings',\n },\n categories: {\n analytics: { description: 'Help us understand usage.', title: 'Analytics' },\n functional: { description: 'Remember preferences.', title: 'Functional' },\n marketing: { description: 'Used to show relevant ads.', title: 'Marketing' },\n necessary: {\n description:\n 'Required for the site to work, including your language and theme preferences. Always on.',\n title: 'Necessary',\n },\n },\n message:\n 'We use cookies to run the site, analyze traffic, and improve your experience. You can accept all, reject non-essential, or choose which to allow.',\n privacyLink: null,\n settingsTitle: 'Cookie settings',\n}\n\ntype CookieSettingsData = {\n buttons?: null | Partial\n categories?: Array<{ description?: null | string; key: string; title?: null | string }> | null\n message?: null | string\n settingsTitle?: null | string\n}\n\ntype GetConsentTextsArgs = {\n config: I18nConfig\n locale?: string\n payload: BasePayload\n /**\n * Privacy-policy label + the system page (from SiteSettings.privacyPolicy,\n * read with locale:'all') to link to. Optional — omit for no link.\n */\n privacyPolicy?: {\n label: string\n page: { slug?: null | Record } | null\n }\n}\n\n/**\n * Resolves consent banner texts from the CookieSettings global, falling back to\n * English defaults per field. The privacy-policy link is built from the pages\n * module (system page role), not stored in CookieSettings — one source of truth.\n */\nexport async function getConsentTexts({\n config,\n locale,\n payload,\n privacyPolicy,\n}: GetConsentTextsArgs): Promise {\n const g = await getGlobal(payload, 'cookie-settings', { locale })\n\n const categories = {} as ConsentTexts['categories']\n for (const cat of CONSENT_CATEGORIES) {\n const entry = g.categories?.find((c) => c.key === cat)\n categories[cat] = {\n description: entry?.description || FALLBACK.categories[cat].description,\n title: entry?.title || FALLBACK.categories[cat].title,\n }\n }\n\n let privacyLink: ConsentTexts['privacyLink'] = null\n if (privacyPolicy?.page && locale) {\n const href = getSystemPagePath({ config, locale, page: privacyPolicy.page })\n if (href) {\n privacyLink = { href, label: privacyPolicy.label }\n }\n }\n\n return {\n buttons: {\n acceptAll: g.buttons?.acceptAll || FALLBACK.buttons.acceptAll,\n back: g.buttons?.back || FALLBACK.buttons.back,\n reject: g.buttons?.reject || FALLBACK.buttons.reject,\n save: g.buttons?.save || FALLBACK.buttons.save,\n settings: g.buttons?.settings || FALLBACK.buttons.settings,\n },\n categories,\n message: g.message || FALLBACK.message,\n privacyLink,\n settingsTitle: g.settingsTitle || FALLBACK.settingsTitle,\n }\n}\n"],"names":["getSystemPagePath","getGlobal","CONSENT_CATEGORIES","FALLBACK","buttons","acceptAll","back","reject","save","settings","categories","analytics","description","title","functional","marketing","necessary","message","privacyLink","settingsTitle","getConsentTexts","config","locale","payload","privacyPolicy","g","cat","entry","find","c","key","page","href","label"],"mappings":"AAKA,SAASA,iBAAiB,QAAQ,oBAAmB;AACrD,SAASC,SAAS,QAAQ,sBAAqB;AAC/C,SAASC,kBAAkB,QAAQ,kBAAiB;AAEpD,oEAAoE,GACpE,MAAMC,WAAyB;IAC7BC,SAAS;QACPC,WAAW;QACXC,MAAM;QACNC,QAAQ;QACRC,MAAM;QACNC,UAAU;IACZ;IACAC,YAAY;QACVC,WAAW;YAAEC,aAAa;YAA6BC,OAAO;QAAY;QAC1EC,YAAY;YAAEF,aAAa;YAAyBC,OAAO;QAAa;QACxEE,WAAW;YAAEH,aAAa;YAA8BC,OAAO;QAAY;QAC3EG,WAAW;YACTJ,aACE;YACFC,OAAO;QACT;IACF;IACAI,SACE;IACFC,aAAa;IACbC,eAAe;AACjB;AAuBA;;;;CAIC,GACD,OAAO,eAAeC,gBAAgB,EACpCC,MAAM,EACNC,MAAM,EACNC,OAAO,EACPC,aAAa,EACO;IACpB,MAAMC,IAAI,MAAMxB,UAA8BsB,SAAS,mBAAmB;QAAED;IAAO;IAEnF,MAAMZ,aAAa,CAAC;IACpB,KAAK,MAAMgB,OAAOxB,mBAAoB;QACpC,MAAMyB,QAAQF,EAAEf,UAAU,EAAEkB,KAAK,CAACC,IAAMA,EAAEC,GAAG,KAAKJ;QAClDhB,UAAU,CAACgB,IAAI,GAAG;YAChBd,aAAae,OAAOf,eAAeT,SAASO,UAAU,CAACgB,IAAI,CAACd,WAAW;YACvEC,OAAOc,OAAOd,SAASV,SAASO,UAAU,CAACgB,IAAI,CAACb,KAAK;QACvD;IACF;IAEA,IAAIK,cAA2C;IAC/C,IAAIM,eAAeO,QAAQT,QAAQ;QACjC,MAAMU,OAAOhC,kBAAkB;YAAEqB;YAAQC;YAAQS,MAAMP,cAAcO,IAAI;QAAC;QAC1E,IAAIC,MAAM;YACRd,cAAc;gBAAEc;gBAAMC,OAAOT,cAAcS,KAAK;YAAC;QACnD;IACF;IAEA,OAAO;QACL7B,SAAS;YACPC,WAAWoB,EAAErB,OAAO,EAAEC,aAAaF,SAASC,OAAO,CAACC,SAAS;YAC7DC,MAAMmB,EAAErB,OAAO,EAAEE,QAAQH,SAASC,OAAO,CAACE,IAAI;YAC9CC,QAAQkB,EAAErB,OAAO,EAAEG,UAAUJ,SAASC,OAAO,CAACG,MAAM;YACpDC,MAAMiB,EAAErB,OAAO,EAAEI,QAAQL,SAASC,OAAO,CAACI,IAAI;YAC9CC,UAAUgB,EAAErB,OAAO,EAAEK,YAAYN,SAASC,OAAO,CAACK,QAAQ;QAC5D;QACAC;QACAO,SAASQ,EAAER,OAAO,IAAId,SAASc,OAAO;QACtCC;QACAC,eAAeM,EAAEN,aAAa,IAAIhB,SAASgB,aAAa;IAC1D;AACF"} \ No newline at end of file diff --git a/dist/modules/consent/googleConsent.js b/dist/modules/consent/googleConsent.js index f196814..b8d2eb0 100644 --- a/dist/modules/consent/googleConsent.js +++ b/dist/modules/consent/googleConsent.js @@ -37,6 +37,6 @@ function toSignals(state) { } /** Updates consent after the user decides. */ export function updateConsent(state) { gtag('consent', 'update', toSignals(state)); -} +} //# sourceMappingURL=googleConsent.js.map //# sourceMappingURL=googleConsent.js.map \ No newline at end of file diff --git a/dist/modules/consent/googleConsent.js.map b/dist/modules/consent/googleConsent.js.map index cccd52f..a2ecf3b 100644 --- a/dist/modules/consent/googleConsent.js.map +++ b/dist/modules/consent/googleConsent.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/googleConsent.ts"],"sourcesContent":["import type { ConsentState } from './categories.js'\n\ntype ConsentValue = 'denied' | 'granted'\ntype GoogleConsentSignals = Record\n\nfunction toSignals(state: ConsentState): GoogleConsentSignals {\n const g = (b: boolean): ConsentValue => (b ? 'granted' : 'denied')\n return {\n ad_personalization: g(state.marketing),\n ad_storage: g(state.marketing),\n ad_user_data: g(state.marketing),\n analytics_storage: g(state.analytics),\n functionality_storage: g(state.functional),\n personalization_storage: g(state.functional),\n security_storage: 'granted', // necessary — always on\n }\n}\n\ndeclare global {\n interface Window {\n dataLayer?: unknown[]\n }\n}\n\n/**\n * Mirrors Google's canonical snippet: `function gtag(){dataLayer.push(arguments)}`.\n *\n * Pushing `arguments` rather than a plain array is not a stylistic detail.\n * Google's tags recognise a consent command by the Arguments object it arrives\n * in; a real array lands in the dataLayer as ordinary data and the command is\n * silently ignored — the tag loads, but consent never updates and analytics\n * stays denied. The rest parameter exists only to type the call sites.\n *\n * Exported for the analytics module (which needs `js`/`config` commands) —\n * intentionally not re-exported from the package's public API.\n */\nexport function gtag(..._args: unknown[]) {\n if (typeof window === 'undefined') {return}\n window.dataLayer = window.dataLayer || []\n // eslint-disable-next-line prefer-rest-params\n window.dataLayer.push(arguments)\n}\n\n/** Sets the default consent state (call before Google tags load). */\nexport function setDefaultConsent(state: ConsentState) {\n gtag('consent', 'default', {\n ...toSignals(state),\n wait_for_update: 500, // ms to wait for an update before firing\n })\n}\n\n/** Updates consent after the user decides. */\nexport function updateConsent(state: ConsentState) {\n gtag('consent', 'update', toSignals(state))\n}\n"],"names":["toSignals","state","g","b","ad_personalization","marketing","ad_storage","ad_user_data","analytics_storage","analytics","functionality_storage","functional","personalization_storage","security_storage","gtag","_args","window","dataLayer","push","arguments","setDefaultConsent","wait_for_update","updateConsent"],"mappings":"AAKA,SAASA,UAAUC,KAAmB;IACpC,MAAMC,IAAI,CAACC,IAA8BA,IAAI,YAAY;IACzD,OAAO;QACLC,oBAAoBF,EAAED,MAAMI,SAAS;QACrCC,YAAYJ,EAAED,MAAMI,SAAS;QAC7BE,cAAcL,EAAED,MAAMI,SAAS;QAC/BG,mBAAmBN,EAAED,MAAMQ,SAAS;QACpCC,uBAAuBR,EAAED,MAAMU,UAAU;QACzCC,yBAAyBV,EAAED,MAAMU,UAAU;QAC3CE,kBAAkB;IACpB;AACF;AAQA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,KAAK,GAAGC,KAAgB;IACtC,IAAI,OAAOC,WAAW,aAAa;QAAC;IAAM;IAC1CA,OAAOC,SAAS,GAAGD,OAAOC,SAAS,IAAI,EAAE;IACzC,8CAA8C;IAC9CD,OAAOC,SAAS,CAACC,IAAI,CAACC;AACxB;AAEA,mEAAmE,GACnE,OAAO,SAASC,kBAAkBnB,KAAmB;IACnDa,KAAK,WAAW,WAAW;QACzB,GAAGd,UAAUC,MAAM;QACnBoB,iBAAiB;IACnB;AACF;AAEA,4CAA4C,GAC5C,OAAO,SAASC,cAAcrB,KAAmB;IAC/Ca,KAAK,WAAW,UAAUd,UAAUC;AACtC"} \ No newline at end of file +{"version":3,"sources":["googleConsent.js"],"sourcesContent":["import type { ConsentState } from './categories.js'\n\ntype ConsentValue = 'denied' | 'granted'\ntype GoogleConsentSignals = Record\n\nfunction toSignals(state: ConsentState): GoogleConsentSignals {\n const g = (b: boolean): ConsentValue => (b ? 'granted' : 'denied')\n return {\n ad_personalization: g(state.marketing),\n ad_storage: g(state.marketing),\n ad_user_data: g(state.marketing),\n analytics_storage: g(state.analytics),\n functionality_storage: g(state.functional),\n personalization_storage: g(state.functional),\n security_storage: 'granted', // necessary — always on\n }\n}\n\ndeclare global {\n interface Window {\n dataLayer?: unknown[]\n }\n}\n\n/**\n * Mirrors Google's canonical snippet: `function gtag(){dataLayer.push(arguments)}`.\n *\n * Pushing `arguments` rather than a plain array is not a stylistic detail.\n * Google's tags recognise a consent command by the Arguments object it arrives\n * in; a real array lands in the dataLayer as ordinary data and the command is\n * silently ignored — the tag loads, but consent never updates and analytics\n * stays denied. The rest parameter exists only to type the call sites.\n *\n * Exported for the analytics module (which needs `js`/`config` commands) —\n * intentionally not re-exported from the package's public API.\n */\nexport function gtag(..._args: unknown[]) {\n if (typeof window === 'undefined') {return}\n window.dataLayer = window.dataLayer || []\n // eslint-disable-next-line prefer-rest-params\n window.dataLayer.push(arguments)\n}\n\n/** Sets the default consent state (call before Google tags load). */\nexport function setDefaultConsent(state: ConsentState) {\n gtag('consent', 'default', {\n ...toSignals(state),\n wait_for_update: 500, // ms to wait for an update before firing\n })\n}\n\n/** Updates consent after the user decides. */\nexport function updateConsent(state: ConsentState) {\n gtag('consent', 'update', toSignals(state))\n}\n"],"names":["toSignals","state","g","b","ad_personalization","marketing","ad_storage","ad_user_data","analytics_storage","analytics","functionality_storage","functional","personalization_storage","security_storage","gtag","_args","window","dataLayer","push","arguments","setDefaultConsent","wait_for_update","updateConsent"],"mappings":"AAKA,SAASA,UAAUC,KAAmB;IACpC,MAAMC,IAAI,CAACC,IAA8BA,IAAI,YAAY;IACzD,OAAO;QACLC,oBAAoBF,EAAED,MAAMI,SAAS;QACrCC,YAAYJ,EAAED,MAAMI,SAAS;QAC7BE,cAAcL,EAAED,MAAMI,SAAS;QAC/BG,mBAAmBN,EAAED,MAAMQ,SAAS;QACpCC,uBAAuBR,EAAED,MAAMU,UAAU;QACzCC,yBAAyBV,EAAED,MAAMU,UAAU;QAC3CE,kBAAkB;IACpB;AACF;AAQA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,KAAK,GAAGC,KAAgB;IACtC,IAAI,OAAOC,WAAW,aAAa;QAAC;IAAM;IAC1CA,OAAOC,SAAS,GAAGD,OAAOC,SAAS,IAAI,EAAE;IACzC,8CAA8C;IAC9CD,OAAOC,SAAS,CAACC,IAAI,CAACC;AACxB;AAEA,mEAAmE,GACnE,OAAO,SAASC,kBAAkBnB,KAAmB;IACnDa,KAAK,WAAW,WAAW;QACzB,GAAGd,UAAUC,MAAM;QACnBoB,iBAAiB;IACnB;AACF;AAEA,4CAA4C,GAC5C,OAAO,SAASC,cAAcrB,KAAmB;IAC/Ca,KAAK,WAAW,UAAUd,UAAUC;AACtC"} \ No newline at end of file diff --git a/dist/modules/consent/index.js b/dist/modules/consent/index.js index 4ec72b8..840184f 100644 --- a/dist/modules/consent/index.js +++ b/dist/modules/consent/index.js @@ -4,6 +4,6 @@ export { ACCEPT_ALL_CONSENT, CONSENT_CATEGORIES, DEFAULT_CONSENT, REJECT_ALL_CONSENT } from './categories.js'; export { getConsentTexts } from './getConsentTexts.js'; export { setDefaultConsent, updateConsent } from './googleConsent.js'; -export { CONSENT_COOKIE, CONSENT_MAX_AGE, CONSENT_VERSION, parseConsent, serializeConsent } from './storage.js'; +export { CONSENT_COOKIE, CONSENT_MAX_AGE, CONSENT_VERSION, parseConsent, serializeConsent } from './storage.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/consent/index.js.map b/dist/modules/consent/index.js.map index c041a2c..96938d4 100644 --- a/dist/modules/consent/index.js.map +++ b/dist/modules/consent/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/index.ts"],"sourcesContent":["// Server-safe exports (logic, types, helper). Client components (banner,\n// provider, hook) are exported separately via ./client to keep the RSC/client\n// boundary clean.\nexport {\n ACCEPT_ALL_CONSENT,\n CONSENT_CATEGORIES,\n DEFAULT_CONSENT,\n REJECT_ALL_CONSENT,\n} from './categories.js'\nexport type { ConsentCategory, ConsentState } from './categories.js'\nexport { getConsentTexts } from './getConsentTexts.js'\nexport { setDefaultConsent, updateConsent } from './googleConsent.js'\nexport {\n CONSENT_COOKIE,\n CONSENT_MAX_AGE,\n CONSENT_VERSION,\n parseConsent,\n serializeConsent,\n} from './storage.js'\nexport type { ConsentTexts } from './texts.js'\n"],"names":["ACCEPT_ALL_CONSENT","CONSENT_CATEGORIES","DEFAULT_CONSENT","REJECT_ALL_CONSENT","getConsentTexts","setDefaultConsent","updateConsent","CONSENT_COOKIE","CONSENT_MAX_AGE","CONSENT_VERSION","parseConsent","serializeConsent"],"mappings":"AAAA,yEAAyE;AACzE,8EAA8E;AAC9E,kBAAkB;AAClB,SACEA,kBAAkB,EAClBC,kBAAkB,EAClBC,eAAe,EACfC,kBAAkB,QACb,kBAAiB;AAExB,SAASC,eAAe,QAAQ,uBAAsB;AACtD,SAASC,iBAAiB,EAAEC,aAAa,QAAQ,qBAAoB;AACrE,SACEC,cAAc,EACdC,eAAe,EACfC,eAAe,EACfC,YAAY,EACZC,gBAAgB,QACX,eAAc"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["// Server-safe exports (logic, types, helper). Client components (banner,\n// provider, hook) are exported separately via ./client to keep the RSC/client\n// boundary clean.\nexport {\n ACCEPT_ALL_CONSENT,\n CONSENT_CATEGORIES,\n DEFAULT_CONSENT,\n REJECT_ALL_CONSENT,\n} from './categories.js'\nexport type { ConsentCategory, ConsentState } from './categories.js'\nexport { getConsentTexts } from './getConsentTexts.js'\nexport { setDefaultConsent, updateConsent } from './googleConsent.js'\nexport {\n CONSENT_COOKIE,\n CONSENT_MAX_AGE,\n CONSENT_VERSION,\n parseConsent,\n serializeConsent,\n} from './storage.js'\nexport type { ConsentTexts } from './texts.js'\n"],"names":["ACCEPT_ALL_CONSENT","CONSENT_CATEGORIES","DEFAULT_CONSENT","REJECT_ALL_CONSENT","getConsentTexts","setDefaultConsent","updateConsent","CONSENT_COOKIE","CONSENT_MAX_AGE","CONSENT_VERSION","parseConsent","serializeConsent"],"mappings":"AAAA,yEAAyE;AACzE,8EAA8E;AAC9E,kBAAkB;AAClB,SACEA,kBAAkB,EAClBC,kBAAkB,EAClBC,eAAe,EACfC,kBAAkB,QACb,kBAAiB;AAExB,SAASC,eAAe,QAAQ,uBAAsB;AACtD,SAASC,iBAAiB,EAAEC,aAAa,QAAQ,qBAAoB;AACrE,SACEC,cAAc,EACdC,eAAe,EACfC,eAAe,EACfC,YAAY,EACZC,gBAAgB,QACX,eAAc"} \ No newline at end of file diff --git a/dist/modules/consent/storage.js b/dist/modules/consent/storage.js index 882c8ec..7020db6 100644 --- a/dist/modules/consent/storage.js +++ b/dist/modules/consent/storage.js @@ -37,6 +37,6 @@ export function parseConsent(raw) { } catch { return null; } -} +} //# sourceMappingURL=storage.js.map //# sourceMappingURL=storage.js.map \ No newline at end of file diff --git a/dist/modules/consent/storage.js.map b/dist/modules/consent/storage.js.map index 039588f..20f4b90 100644 --- a/dist/modules/consent/storage.js.map +++ b/dist/modules/consent/storage.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/storage.ts"],"sourcesContent":["import type { ConsentState } from './categories.js'\n\nimport { CONSENT_CATEGORIES, DEFAULT_CONSENT } from './categories.js'\n\n/**\n * Consent persistence in a cookie. Stored with a version so we can invalidate\n * old consents when the policy changes, and a timestamp (GDPR: consent must be\n * dated). Readable both client-side (banner) and server-side (script gating).\n */\nexport const CONSENT_COOKIE = 'cookie-consent'\nexport const CONSENT_VERSION = 1\nexport const CONSENT_MAX_AGE = 60 * 60 * 24 * 180 // 180 days\n\ntype StoredConsent = {\n state: ConsentState\n timestamp: string\n version: number\n}\n\nexport function serializeConsent(state: ConsentState): string {\n const payload: StoredConsent = {\n state,\n timestamp: new Date().toISOString(),\n version: CONSENT_VERSION,\n }\n return encodeURIComponent(JSON.stringify(payload))\n}\n\nexport function parseConsent(raw: string | undefined): ConsentState | null {\n if (!raw) {return null}\n try {\n const parsed = JSON.parse(decodeURIComponent(raw)) as StoredConsent\n if (parsed.version !== CONSENT_VERSION) {return null}\n const state = { ...DEFAULT_CONSENT }\n for (const cat of CONSENT_CATEGORIES) {\n if (typeof parsed.state?.[cat] === 'boolean') {state[cat] = parsed.state[cat]}\n }\n state.necessary = true\n return state\n } catch {\n return null\n }\n}\n"],"names":["CONSENT_CATEGORIES","DEFAULT_CONSENT","CONSENT_COOKIE","CONSENT_VERSION","CONSENT_MAX_AGE","serializeConsent","state","payload","timestamp","Date","toISOString","version","encodeURIComponent","JSON","stringify","parseConsent","raw","parsed","parse","decodeURIComponent","cat","necessary"],"mappings":"AAEA,SAASA,kBAAkB,EAAEC,eAAe,QAAQ,kBAAiB;AAErE;;;;CAIC,GACD,OAAO,MAAMC,iBAAiB,iBAAgB;AAC9C,OAAO,MAAMC,kBAAkB,EAAC;AAChC,OAAO,MAAMC,kBAAkB,KAAK,KAAK,KAAK,IAAI,WAAW;CAAZ;AAQjD,OAAO,SAASC,iBAAiBC,KAAmB;IAClD,MAAMC,UAAyB;QAC7BD;QACAE,WAAW,IAAIC,OAAOC,WAAW;QACjCC,SAASR;IACX;IACA,OAAOS,mBAAmBC,KAAKC,SAAS,CAACP;AAC3C;AAEA,OAAO,SAASQ,aAAaC,GAAuB;IAClD,IAAI,CAACA,KAAK;QAAC,OAAO;IAAI;IACtB,IAAI;QACF,MAAMC,SAASJ,KAAKK,KAAK,CAACC,mBAAmBH;QAC7C,IAAIC,OAAON,OAAO,KAAKR,iBAAiB;YAAC,OAAO;QAAI;QACpD,MAAMG,QAAQ;YAAE,GAAGL,eAAe;QAAC;QACnC,KAAK,MAAMmB,OAAOpB,mBAAoB;YACpC,IAAI,OAAOiB,OAAOX,KAAK,EAAE,CAACc,IAAI,KAAK,WAAW;gBAACd,KAAK,CAACc,IAAI,GAAGH,OAAOX,KAAK,CAACc,IAAI;YAAA;QAC/E;QACAd,MAAMe,SAAS,GAAG;QAClB,OAAOf;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"} \ No newline at end of file +{"version":3,"sources":["storage.js"],"sourcesContent":["import type { ConsentState } from './categories.js'\n\nimport { CONSENT_CATEGORIES, DEFAULT_CONSENT } from './categories.js'\n\n/**\n * Consent persistence in a cookie. Stored with a version so we can invalidate\n * old consents when the policy changes, and a timestamp (GDPR: consent must be\n * dated). Readable both client-side (banner) and server-side (script gating).\n */\nexport const CONSENT_COOKIE = 'cookie-consent'\nexport const CONSENT_VERSION = 1\nexport const CONSENT_MAX_AGE = 60 * 60 * 24 * 180 // 180 days\n\ntype StoredConsent = {\n state: ConsentState\n timestamp: string\n version: number\n}\n\nexport function serializeConsent(state: ConsentState): string {\n const payload: StoredConsent = {\n state,\n timestamp: new Date().toISOString(),\n version: CONSENT_VERSION,\n }\n return encodeURIComponent(JSON.stringify(payload))\n}\n\nexport function parseConsent(raw: string | undefined): ConsentState | null {\n if (!raw) {return null}\n try {\n const parsed = JSON.parse(decodeURIComponent(raw)) as StoredConsent\n if (parsed.version !== CONSENT_VERSION) {return null}\n const state = { ...DEFAULT_CONSENT }\n for (const cat of CONSENT_CATEGORIES) {\n if (typeof parsed.state?.[cat] === 'boolean') {state[cat] = parsed.state[cat]}\n }\n state.necessary = true\n return state\n } catch {\n return null\n }\n}\n"],"names":["CONSENT_CATEGORIES","DEFAULT_CONSENT","CONSENT_COOKIE","CONSENT_VERSION","CONSENT_MAX_AGE","serializeConsent","state","payload","timestamp","Date","toISOString","version","encodeURIComponent","JSON","stringify","parseConsent","raw","parsed","parse","decodeURIComponent","cat","necessary"],"mappings":"AAEA,SAASA,kBAAkB,EAAEC,eAAe,QAAQ,kBAAiB;AAErE;;;;CAIC,GACD,OAAO,MAAMC,iBAAiB,iBAAgB;AAC9C,OAAO,MAAMC,kBAAkB,EAAC;AAChC,OAAO,MAAMC,kBAAkB,KAAK,KAAK,KAAK,IAAI,WAAW;CAAZ;AAQjD,OAAO,SAASC,iBAAiBC,KAAmB;IAClD,MAAMC,UAAyB;QAC7BD;QACAE,WAAW,IAAIC,OAAOC,WAAW;QACjCC,SAASR;IACX;IACA,OAAOS,mBAAmBC,KAAKC,SAAS,CAACP;AAC3C;AAEA,OAAO,SAASQ,aAAaC,GAAuB;IAClD,IAAI,CAACA,KAAK;QAAC,OAAO;IAAI;IACtB,IAAI;QACF,MAAMC,SAASJ,KAAKK,KAAK,CAACC,mBAAmBH;QAC7C,IAAIC,OAAON,OAAO,KAAKR,iBAAiB;YAAC,OAAO;QAAI;QACpD,MAAMG,QAAQ;YAAE,GAAGL,eAAe;QAAC;QACnC,KAAK,MAAMmB,OAAOpB,mBAAoB;YACpC,IAAI,OAAOiB,OAAOX,KAAK,EAAE,CAACc,IAAI,KAAK,WAAW;gBAACd,KAAK,CAACc,IAAI,GAAGH,OAAOX,KAAK,CAACc,IAAI;YAAA;QAC/E;QACAd,MAAMe,SAAS,GAAG;QAClB,OAAOf;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"} \ No newline at end of file diff --git a/dist/modules/consent/texts.js b/dist/modules/consent/texts.js index 11b227e..60206a1 100644 --- a/dist/modules/consent/texts.js +++ b/dist/modules/consent/texts.js @@ -1,3 +1,3 @@ -export { }; +export { }; //# sourceMappingURL=texts.js.map //# sourceMappingURL=texts.js.map \ No newline at end of file diff --git a/dist/modules/consent/texts.js.map b/dist/modules/consent/texts.js.map index 275e5a3..1f3eed3 100644 --- a/dist/modules/consent/texts.js.map +++ b/dist/modules/consent/texts.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/texts.ts"],"sourcesContent":["import type { ConsentCategory } from './categories.js'\n\nexport type ConsentTexts = {\n buttons: {\n acceptAll: string\n back: string\n reject: string\n save: string\n settings: string\n }\n categories: Record\n message: string\n /** null = no privacy-policy link shown */\n privacyLink: { href: string; label: string } | null\n settingsTitle: string\n}\n"],"names":[],"mappings":"AAEA,WAaC"} \ No newline at end of file +{"version":3,"sources":["texts.js"],"sourcesContent":["import type { ConsentCategory } from './categories.js'\n\nexport type ConsentTexts = {\n buttons: {\n acceptAll: string\n back: string\n reject: string\n save: string\n settings: string\n }\n categories: Record\n message: string\n /** null = no privacy-policy link shown */\n privacyLink: { href: string; label: string } | null\n settingsTitle: string\n}\n"],"names":[],"mappings":"AAEA,WAaC"} \ No newline at end of file diff --git a/dist/modules/consent/useConsent.js b/dist/modules/consent/useConsent.js index f66c2ed..8aacd5a 100644 --- a/dist/modules/consent/useConsent.js +++ b/dist/modules/consent/useConsent.js @@ -46,6 +46,6 @@ export function useConsent() { savePreferences, state }; -} +} //# sourceMappingURL=useConsent.js.map //# sourceMappingURL=useConsent.js.map \ No newline at end of file diff --git a/dist/modules/consent/useConsent.js.map b/dist/modules/consent/useConsent.js.map index 3a761c1..87393de 100644 --- a/dist/modules/consent/useConsent.js.map +++ b/dist/modules/consent/useConsent.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/consent/useConsent.ts"],"sourcesContent":["'use client'\nimport { useCallback, useEffect, useState } from 'react'\n\nimport type { ConsentCategory, ConsentState } from './categories.js'\n\nimport { ACCEPT_ALL_CONSENT, DEFAULT_CONSENT, REJECT_ALL_CONSENT } from './categories.js'\nimport { updateConsent } from './googleConsent.js'\nimport { CONSENT_COOKIE, CONSENT_MAX_AGE, parseConsent, serializeConsent } from './storage.js'\n\nexport function useConsent() {\n const [state, setState] = useState(DEFAULT_CONSENT)\n const [decided, setDecided] = useState(false)\n\n useEffect(() => {\n const raw = document.cookie\n .split('; ')\n .find((c) => c.startsWith(`${CONSENT_COOKIE}=`))\n ?.split('=')[1]\n const parsed = parseConsent(raw)\n if (parsed) {\n setState(parsed)\n setDecided(true)\n }\n }, [])\n\n const persist = useCallback((next: ConsentState) => {\n document.cookie = `${CONSENT_COOKIE}=${serializeConsent(next)};path=/;max-age=${CONSENT_MAX_AGE};samesite=lax`\n setState(next)\n setDecided(true)\n // Tell Google right away. Tags are already on the page holding whatever\n // defaults Analytics set at load; without this update they keep them for\n // the rest of the session and never write their cookies — the banner would\n // look like it worked while nothing changed.\n updateConsent(next)\n }, [])\n\n const acceptAll = useCallback(() => persist(ACCEPT_ALL_CONSENT), [persist])\n const rejectAll = useCallback(() => persist(REJECT_ALL_CONSENT), [persist])\n const savePreferences = useCallback(\n (choices: Partial>) =>\n persist({ ...DEFAULT_CONSENT, ...choices, necessary: true }),\n [persist],\n )\n const reopen = useCallback(() => setDecided(false), [])\n\n return { acceptAll, decided, rejectAll, reopen, savePreferences, state }\n}\n"],"names":["useCallback","useEffect","useState","ACCEPT_ALL_CONSENT","DEFAULT_CONSENT","REJECT_ALL_CONSENT","updateConsent","CONSENT_COOKIE","CONSENT_MAX_AGE","parseConsent","serializeConsent","useConsent","state","setState","decided","setDecided","raw","document","cookie","split","find","c","startsWith","parsed","persist","next","acceptAll","rejectAll","savePreferences","choices","necessary","reopen"],"mappings":"AAAA;AACA,SAASA,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAIxD,SAASC,kBAAkB,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,kBAAiB;AACzF,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,cAAc,EAAEC,eAAe,EAAEC,YAAY,EAAEC,gBAAgB,QAAQ,eAAc;AAE9F,OAAO,SAASC;IACd,MAAM,CAACC,OAAOC,SAAS,GAAGX,SAAuBE;IACjD,MAAM,CAACU,SAASC,WAAW,GAAGb,SAAS;IAEvCD,UAAU;QACR,MAAMe,MAAMC,SAASC,MAAM,CACxBC,KAAK,CAAC,MACNC,IAAI,CAAC,CAACC,IAAMA,EAAEC,UAAU,CAAC,GAAGf,eAAe,CAAC,CAAC,IAC5CY,MAAM,IAAI,CAAC,EAAE;QACjB,MAAMI,SAASd,aAAaO;QAC5B,IAAIO,QAAQ;YACVV,SAASU;YACTR,WAAW;QACb;IACF,GAAG,EAAE;IAEL,MAAMS,UAAUxB,YAAY,CAACyB;QAC3BR,SAASC,MAAM,GAAG,GAAGX,eAAe,CAAC,EAAEG,iBAAiBe,MAAM,gBAAgB,EAAEjB,gBAAgB,aAAa,CAAC;QAC9GK,SAASY;QACTV,WAAW;QACX,wEAAwE;QACxE,yEAAyE;QACzE,2EAA2E;QAC3E,6CAA6C;QAC7CT,cAAcmB;IAChB,GAAG,EAAE;IAEL,MAAMC,YAAY1B,YAAY,IAAMwB,QAAQrB,qBAAqB;QAACqB;KAAQ;IAC1E,MAAMG,YAAY3B,YAAY,IAAMwB,QAAQnB,qBAAqB;QAACmB;KAAQ;IAC1E,MAAMI,kBAAkB5B,YACtB,CAAC6B,UACCL,QAAQ;YAAE,GAAGpB,eAAe;YAAE,GAAGyB,OAAO;YAAEC,WAAW;QAAK,IAC5D;QAACN;KAAQ;IAEX,MAAMO,SAAS/B,YAAY,IAAMe,WAAW,QAAQ,EAAE;IAEtD,OAAO;QAAEW;QAAWZ;QAASa;QAAWI;QAAQH;QAAiBhB;IAAM;AACzE"} \ No newline at end of file +{"version":3,"sources":["useConsent.js"],"sourcesContent":["'use client'\nimport { useCallback, useEffect, useState } from 'react'\n\nimport type { ConsentCategory, ConsentState } from './categories.js'\n\nimport { ACCEPT_ALL_CONSENT, DEFAULT_CONSENT, REJECT_ALL_CONSENT } from './categories.js'\nimport { updateConsent } from './googleConsent.js'\nimport { CONSENT_COOKIE, CONSENT_MAX_AGE, parseConsent, serializeConsent } from './storage.js'\n\nexport function useConsent() {\n const [state, setState] = useState(DEFAULT_CONSENT)\n const [decided, setDecided] = useState(false)\n\n useEffect(() => {\n const raw = document.cookie\n .split('; ')\n .find((c) => c.startsWith(`${CONSENT_COOKIE}=`))\n ?.split('=')[1]\n const parsed = parseConsent(raw)\n if (parsed) {\n setState(parsed)\n setDecided(true)\n }\n }, [])\n\n const persist = useCallback((next: ConsentState) => {\n document.cookie = `${CONSENT_COOKIE}=${serializeConsent(next)};path=/;max-age=${CONSENT_MAX_AGE};samesite=lax`\n setState(next)\n setDecided(true)\n // Tell Google right away. Tags are already on the page holding whatever\n // defaults Analytics set at load; without this update they keep them for\n // the rest of the session and never write their cookies — the banner would\n // look like it worked while nothing changed.\n updateConsent(next)\n }, [])\n\n const acceptAll = useCallback(() => persist(ACCEPT_ALL_CONSENT), [persist])\n const rejectAll = useCallback(() => persist(REJECT_ALL_CONSENT), [persist])\n const savePreferences = useCallback(\n (choices: Partial>) =>\n persist({ ...DEFAULT_CONSENT, ...choices, necessary: true }),\n [persist],\n )\n const reopen = useCallback(() => setDecided(false), [])\n\n return { acceptAll, decided, rejectAll, reopen, savePreferences, state }\n}\n"],"names":["useCallback","useEffect","useState","ACCEPT_ALL_CONSENT","DEFAULT_CONSENT","REJECT_ALL_CONSENT","updateConsent","CONSENT_COOKIE","CONSENT_MAX_AGE","parseConsent","serializeConsent","useConsent","state","setState","decided","setDecided","raw","document","cookie","split","find","c","startsWith","parsed","persist","next","acceptAll","rejectAll","savePreferences","choices","necessary","reopen"],"mappings":"AAAA;AACA,SAASA,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAIxD,SAASC,kBAAkB,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,kBAAiB;AACzF,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,cAAc,EAAEC,eAAe,EAAEC,YAAY,EAAEC,gBAAgB,QAAQ,eAAc;AAE9F,OAAO,SAASC;IACd,MAAM,CAACC,OAAOC,SAAS,GAAGX,SAAuBE;IACjD,MAAM,CAACU,SAASC,WAAW,GAAGb,SAAS;IAEvCD,UAAU;QACR,MAAMe,MAAMC,SAASC,MAAM,CACxBC,KAAK,CAAC,MACNC,IAAI,CAAC,CAACC,IAAMA,EAAEC,UAAU,CAAC,GAAGf,eAAe,CAAC,CAAC,IAC5CY,MAAM,IAAI,CAAC,EAAE;QACjB,MAAMI,SAASd,aAAaO;QAC5B,IAAIO,QAAQ;YACVV,SAASU;YACTR,WAAW;QACb;IACF,GAAG,EAAE;IAEL,MAAMS,UAAUxB,YAAY,CAACyB;QAC3BR,SAASC,MAAM,GAAG,GAAGX,eAAe,CAAC,EAAEG,iBAAiBe,MAAM,gBAAgB,EAAEjB,gBAAgB,aAAa,CAAC;QAC9GK,SAASY;QACTV,WAAW;QACX,wEAAwE;QACxE,yEAAyE;QACzE,2EAA2E;QAC3E,6CAA6C;QAC7CT,cAAcmB;IAChB,GAAG,EAAE;IAEL,MAAMC,YAAY1B,YAAY,IAAMwB,QAAQrB,qBAAqB;QAACqB;KAAQ;IAC1E,MAAMG,YAAY3B,YAAY,IAAMwB,QAAQnB,qBAAqB;QAACmB;KAAQ;IAC1E,MAAMI,kBAAkB5B,YACtB,CAAC6B,UACCL,QAAQ;YAAE,GAAGpB,eAAe;YAAE,GAAGyB,OAAO;YAAEC,WAAW;QAAK,IAC5D;QAACN;KAAQ;IAEX,MAAMO,SAAS/B,YAAY,IAAMe,WAAW,QAAQ,EAAE;IAEtD,OAAO;QAAEW;QAAWZ;QAASa;QAAWI;QAAQH;QAAiBhB;IAAM;AACzE"} \ No newline at end of file diff --git a/dist/modules/content/archiveFields.js b/dist/modules/content/archiveFields.js index f563b16..d7a6588 100644 --- a/dist/modules/content/archiveFields.js +++ b/dist/modules/content/archiveFields.js @@ -22,6 +22,6 @@ import { archiveFieldName } from './types.js'; relationTo: pagesSlug }; }); -} +} //# sourceMappingURL=archiveFields.js.map //# sourceMappingURL=archiveFields.js.map \ No newline at end of file diff --git a/dist/modules/content/archiveFields.js.map b/dist/modules/content/archiveFields.js.map index 2f4b94e..5c5e91d 100644 --- a/dist/modules/content/archiveFields.js.map +++ b/dist/modules/content/archiveFields.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/content/archiveFields.ts"],"sourcesContent":["import type { Field, RelationshipField } from 'payload'\n\nimport type { ContentOption } from './types.js'\n\nimport { archiveFieldName } from './types.js'\n\n/**\n * Builds one relationship field per content collection, pointing at the\n * client's Pages collection.\n *\n * Sits alongside the system-page roles (homepage, privacy policy) because it's\n * the same idea: a page referenced by function rather than by slug. The\n * assignment is locale-agnostic — one page document — while the resulting path\n * is locale-aware, since the page's slug is localized. Assign the \"Artykuły\"\n * page once and you get /pl/artykuly and /en/articles from its own slugs.\n */\nexport function buildArchiveFields(content: ContentOption, pagesSlug: string): Field[] {\n return content.collections.map((collection): RelationshipField => {\n const label = collection.label ?? collection.slug\n\n return {\n name: archiveFieldName(collection.slug),\n type: 'relationship',\n admin: {\n description: `Page listing ${label}. Its slug becomes the URL segment for entries (e.g. /pl/artykuly/moj-wpis).`,\n },\n label: `${label} — archive page`,\n maxDepth: 1,\n relationTo: pagesSlug,\n }\n })\n}\n"],"names":["archiveFieldName","buildArchiveFields","content","pagesSlug","collections","map","collection","label","slug","name","type","admin","description","maxDepth","relationTo"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,aAAY;AAE7C;;;;;;;;;CASC,GACD,OAAO,SAASC,mBAAmBC,OAAsB,EAAEC,SAAiB;IAC1E,OAAOD,QAAQE,WAAW,CAACC,GAAG,CAAC,CAACC;QAC9B,MAAMC,QAAQD,WAAWC,KAAK,IAAID,WAAWE,IAAI;QAEjD,OAAO;YACLC,MAAMT,iBAAiBM,WAAWE,IAAI;YACtCE,MAAM;YACNC,OAAO;gBACLC,aAAa,CAAC,aAAa,EAAEL,MAAM,4EAA4E,CAAC;YAClH;YACAA,OAAO,GAAGA,MAAM,eAAe,CAAC;YAChCM,UAAU;YACVC,YAAYX;QACd;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["archiveFields.js"],"sourcesContent":["import type { Field, RelationshipField } from 'payload'\n\nimport type { ContentOption } from './types.js'\n\nimport { archiveFieldName } from './types.js'\n\n/**\n * Builds one relationship field per content collection, pointing at the\n * client's Pages collection.\n *\n * Sits alongside the system-page roles (homepage, privacy policy) because it's\n * the same idea: a page referenced by function rather than by slug. The\n * assignment is locale-agnostic — one page document — while the resulting path\n * is locale-aware, since the page's slug is localized. Assign the \"Artykuły\"\n * page once and you get /pl/artykuly and /en/articles from its own slugs.\n */\nexport function buildArchiveFields(content: ContentOption, pagesSlug: string): Field[] {\n return content.collections.map((collection): RelationshipField => {\n const label = collection.label ?? collection.slug\n\n return {\n name: archiveFieldName(collection.slug),\n type: 'relationship',\n admin: {\n description: `Page listing ${label}. Its slug becomes the URL segment for entries (e.g. /pl/artykuly/moj-wpis).`,\n },\n label: `${label} — archive page`,\n maxDepth: 1,\n relationTo: pagesSlug,\n }\n })\n}\n"],"names":["archiveFieldName","buildArchiveFields","content","pagesSlug","collections","map","collection","label","slug","name","type","admin","description","maxDepth","relationTo"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,aAAY;AAE7C;;;;;;;;;CASC,GACD,OAAO,SAASC,mBAAmBC,OAAsB,EAAEC,SAAiB;IAC1E,OAAOD,QAAQE,WAAW,CAACC,GAAG,CAAC,CAACC;QAC9B,MAAMC,QAAQD,WAAWC,KAAK,IAAID,WAAWE,IAAI;QAEjD,OAAO;YACLC,MAAMT,iBAAiBM,WAAWE,IAAI;YACtCE,MAAM;YACNC,OAAO;gBACLC,aAAa,CAAC,aAAa,EAAEL,MAAM,4EAA4E,CAAC;YAClH;YACAA,OAAO,GAAGA,MAAM,eAAe,CAAC;YAChCM,UAAU;YACVC,YAAYX;QACd;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/content/getArchiveEntries.js b/dist/modules/content/getArchiveEntries.js index d9493d9..e790a57 100644 --- a/dist/modules/content/getArchiveEntries.js +++ b/dist/modules/content/getArchiveEntries.js @@ -28,6 +28,6 @@ totalDocs: result.totalDocs, totalPages: result.totalPages }; -} +} //# sourceMappingURL=getArchiveEntries.js.map //# sourceMappingURL=getArchiveEntries.js.map \ No newline at end of file diff --git a/dist/modules/content/getArchiveEntries.js.map b/dist/modules/content/getArchiveEntries.js.map index 879cde1..3930c8a 100644 --- a/dist/modules/content/getArchiveEntries.js.map +++ b/dist/modules/content/getArchiveEntries.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/content/getArchiveEntries.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nexport type ArchiveEntries> = {\n docs: T[]\n hasNextPage: boolean\n hasPrevPage: boolean\n /** 1-based. */\n page: number\n totalDocs: number\n totalPages: number\n}\n\ntype GetArchiveEntriesArgs = {\n /** Collection to list, e.g. 'posts'. */\n collection: string\n /** Relationship depth. Defaults to 1 — enough for a cover image. */\n depth?: number\n locale: string\n /** 1-based. Values below 1 are clamped. */\n page?: number\n payload: BasePayload\n /** Defaults to 10. */\n perPage?: number\n /** Payload sort string. Defaults to newest first. */\n sort?: string\n /** Extra constraints merged into the query, e.g. { category: { equals: id } }. */\n where?: Record\n}\n\n/**\n * Fetches one page of a collection's entries for an archive listing.\n *\n * Only published documents are returned when the collection has drafts enabled;\n * Payload's `where` on `_status` is left to the caller, since a collection\n * without drafts has no such field.\n *\n * Returns pagination facts rather than markup — the listing itself belongs to\n * the client (as a block on the archive page, most likely), because how a list\n * of posts should look isn't something a plugin can decide.\n */\nexport async function getArchiveEntries>({\n collection,\n depth = 1,\n locale,\n page = 1,\n payload,\n perPage = 10,\n sort = '-createdAt',\n where,\n}: GetArchiveEntriesArgs): Promise> {\n const result = await payload.find({\n collection: collection as never,\n depth,\n limit: perPage,\n locale: locale as never,\n page: Math.max(1, page),\n sort,\n ...(where ? { where: where as never } : {}),\n })\n\n return {\n docs: result.docs as T[],\n hasNextPage: result.hasNextPage,\n hasPrevPage: result.hasPrevPage,\n page: result.page ?? 1,\n totalDocs: result.totalDocs,\n totalPages: result.totalPages,\n }\n}\n"],"names":["getArchiveEntries","collection","depth","locale","page","payload","perPage","sort","where","result","find","limit","Math","max","docs","hasNextPage","hasPrevPage","totalDocs","totalPages"],"mappings":"AA6BA;;;;;;;;;;CAUC,GACD,OAAO,eAAeA,kBAA+C,EACnEC,UAAU,EACVC,QAAQ,CAAC,EACTC,MAAM,EACNC,OAAO,CAAC,EACRC,OAAO,EACPC,UAAU,EAAE,EACZC,OAAO,YAAY,EACnBC,KAAK,EACiB;IACtB,MAAMC,SAAS,MAAMJ,QAAQK,IAAI,CAAC;QAChCT,YAAYA;QACZC;QACAS,OAAOL;QACPH,QAAQA;QACRC,MAAMQ,KAAKC,GAAG,CAAC,GAAGT;QAClBG;QACA,GAAIC,QAAQ;YAAEA,OAAOA;QAAe,IAAI,CAAC,CAAC;IAC5C;IAEA,OAAO;QACLM,MAAML,OAAOK,IAAI;QACjBC,aAAaN,OAAOM,WAAW;QAC/BC,aAAaP,OAAOO,WAAW;QAC/BZ,MAAMK,OAAOL,IAAI,IAAI;QACrBa,WAAWR,OAAOQ,SAAS;QAC3BC,YAAYT,OAAOS,UAAU;IAC/B;AACF"} \ No newline at end of file +{"version":3,"sources":["getArchiveEntries.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nexport type ArchiveEntries> = {\n docs: T[]\n hasNextPage: boolean\n hasPrevPage: boolean\n /** 1-based. */\n page: number\n totalDocs: number\n totalPages: number\n}\n\ntype GetArchiveEntriesArgs = {\n /** Collection to list, e.g. 'posts'. */\n collection: string\n /** Relationship depth. Defaults to 1 — enough for a cover image. */\n depth?: number\n locale: string\n /** 1-based. Values below 1 are clamped. */\n page?: number\n payload: BasePayload\n /** Defaults to 10. */\n perPage?: number\n /** Payload sort string. Defaults to newest first. */\n sort?: string\n /** Extra constraints merged into the query, e.g. { category: { equals: id } }. */\n where?: Record\n}\n\n/**\n * Fetches one page of a collection's entries for an archive listing.\n *\n * Only published documents are returned when the collection has drafts enabled;\n * Payload's `where` on `_status` is left to the caller, since a collection\n * without drafts has no such field.\n *\n * Returns pagination facts rather than markup — the listing itself belongs to\n * the client (as a block on the archive page, most likely), because how a list\n * of posts should look isn't something a plugin can decide.\n */\nexport async function getArchiveEntries>({\n collection,\n depth = 1,\n locale,\n page = 1,\n payload,\n perPage = 10,\n sort = '-createdAt',\n where,\n}: GetArchiveEntriesArgs): Promise> {\n const result = await payload.find({\n collection: collection as never,\n depth,\n limit: perPage,\n locale: locale as never,\n page: Math.max(1, page),\n sort,\n ...(where ? { where: where as never } : {}),\n })\n\n return {\n docs: result.docs as T[],\n hasNextPage: result.hasNextPage,\n hasPrevPage: result.hasPrevPage,\n page: result.page ?? 1,\n totalDocs: result.totalDocs,\n totalPages: result.totalPages,\n }\n}\n"],"names":["getArchiveEntries","collection","depth","locale","page","payload","perPage","sort","where","result","find","limit","Math","max","docs","hasNextPage","hasPrevPage","totalDocs","totalPages"],"mappings":"AA6BA;;;;;;;;;;CAUC,GACD,OAAO,eAAeA,kBAA+C,EACnEC,UAAU,EACVC,QAAQ,CAAC,EACTC,MAAM,EACNC,OAAO,CAAC,EACRC,OAAO,EACPC,UAAU,EAAE,EACZC,OAAO,YAAY,EACnBC,KAAK,EACiB;IACtB,MAAMC,SAAS,MAAMJ,QAAQK,IAAI,CAAC;QAChCT,YAAYA;QACZC;QACAS,OAAOL;QACPH,QAAQA;QACRC,MAAMQ,KAAKC,GAAG,CAAC,GAAGT;QAClBG;QACA,GAAIC,QAAQ;YAAEA,OAAOA;QAAe,IAAI,CAAC,CAAC;IAC5C;IAEA,OAAO;QACLM,MAAML,OAAOK,IAAI;QACjBC,aAAaN,OAAOM,WAAW;QAC/BC,aAAaP,OAAOO,WAAW;QAC/BZ,MAAMK,OAAOL,IAAI,IAAI;QACrBa,WAAWR,OAAOQ,SAAS;QAC3BC,YAAYT,OAAOS,UAAU;IAC/B;AACF"} \ No newline at end of file diff --git a/dist/modules/content/index.js b/dist/modules/content/index.js index 385dea1..1d689d8 100644 --- a/dist/modules/content/index.js +++ b/dist/modules/content/index.js @@ -2,6 +2,6 @@ export { buildArchiveFields } from './archiveFields.js'; export { getArchiveEntries } from './getArchiveEntries.js'; export { buildArchivePath, buildEntryPath, parsePageParam } from './paths.js'; export { resolveRoute } from './resolveRoute.js'; -export { archiveFieldName } from './types.js'; +export { archiveFieldName } from './types.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/content/index.js.map b/dist/modules/content/index.js.map index 165ea1f..2c4329f 100644 --- a/dist/modules/content/index.js.map +++ b/dist/modules/content/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/content/index.ts"],"sourcesContent":["export { buildArchiveFields } from './archiveFields.js'\nexport { getArchiveEntries } from './getArchiveEntries.js'\nexport type { ArchiveEntries } from './getArchiveEntries.js'\nexport { buildArchivePath, buildEntryPath, parsePageParam } from './paths.js'\nexport { resolveRoute } from './resolveRoute.js'\nexport type { ResolvedRoute } from './resolveRoute.js'\nexport type { ContentCollectionOption, ContentOption } from './types.js'\nexport { archiveFieldName } from './types.js'\n"],"names":["buildArchiveFields","getArchiveEntries","buildArchivePath","buildEntryPath","parsePageParam","resolveRoute","archiveFieldName"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,qBAAoB;AACvD,SAASC,iBAAiB,QAAQ,yBAAwB;AAE1D,SAASC,gBAAgB,EAAEC,cAAc,EAAEC,cAAc,QAAQ,aAAY;AAC7E,SAASC,YAAY,QAAQ,oBAAmB;AAGhD,SAASC,gBAAgB,QAAQ,aAAY"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export { buildArchiveFields } from './archiveFields.js'\nexport { getArchiveEntries } from './getArchiveEntries.js'\nexport type { ArchiveEntries } from './getArchiveEntries.js'\nexport { buildArchivePath, buildEntryPath, parsePageParam } from './paths.js'\nexport { resolveRoute } from './resolveRoute.js'\nexport type { ResolvedRoute } from './resolveRoute.js'\nexport type { ContentCollectionOption, ContentOption } from './types.js'\nexport { archiveFieldName } from './types.js'\n"],"names":["buildArchiveFields","getArchiveEntries","buildArchivePath","buildEntryPath","parsePageParam","resolveRoute","archiveFieldName"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,qBAAoB;AACvD,SAASC,iBAAiB,QAAQ,yBAAwB;AAE1D,SAASC,gBAAgB,EAAEC,cAAc,EAAEC,cAAc,QAAQ,aAAY;AAC7E,SAASC,YAAY,QAAQ,oBAAmB;AAGhD,SAASC,gBAAgB,QAAQ,aAAY"} \ No newline at end of file diff --git a/dist/modules/content/paths.js b/dist/modules/content/paths.js index 44caf07..0b67d05 100644 --- a/dist/modules/content/paths.js +++ b/dist/modules/content/paths.js @@ -23,6 +23,6 @@ const raw = Array.isArray(value) ? value[0] : value; const n = Number(raw); return Number.isInteger(n) && n > 0 ? n : 1; -} +} //# sourceMappingURL=paths.js.map //# sourceMappingURL=paths.js.map \ No newline at end of file diff --git a/dist/modules/content/paths.js.map b/dist/modules/content/paths.js.map index 92c556b..8222200 100644 --- a/dist/modules/content/paths.js.map +++ b/dist/modules/content/paths.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/content/paths.ts"],"sourcesContent":["type ArchivePathArgs = {\n /** Archive page's slug in this locale, e.g. 'artykuly'. */\n archiveSlug: string\n locale: string\n /** 1-based. Page 1 is omitted from the URL. */\n page?: number\n}\n\n/**\n * Path to an archive listing: /pl/artykuly, /pl/artykuly?page=2.\n *\n * Pagination goes in the query string rather than the path. A path segment\n * (/pl/artykuly/2) would be ambiguous with an entry slugged \"2\", and the\n * alternative (/pl/artykuly/strona/2) drags in yet another localized segment to\n * configure and translate. Google has understood ?page= for years, and\n * rel=next/prev — the reason people used to prefer path segments — was retired.\n */\nexport function buildArchivePath({ archiveSlug, locale, page = 1 }: ArchivePathArgs): string {\n const base = `/${locale}/${archiveSlug}`\n return page > 1 ? `${base}?page=${page}` : base\n}\n\ntype EntryPathArgs = {\n /** Archive page's slug in this locale, e.g. 'artykuly'. */\n archiveSlug: string\n /** Entry's slug in this locale, e.g. 'moj-post'. */\n entrySlug: string\n locale: string\n}\n\n/** Path to a single entry: /pl/artykuly/moj-post. */\nexport function buildEntryPath({ archiveSlug, entrySlug, locale }: EntryPathArgs): string {\n return `/${locale}/${archiveSlug}/${entrySlug}`\n}\n\n/**\n * Reads a page number out of a query parameter.\n *\n * Anything that isn't a positive integer is page 1 — `?page=abc`, `?page=-5`,\n * `?page=1.5` and a repeated `?page=1&page=2` all have to resolve to something,\n * and silently showing the first page beats a 500 on a URL a crawler invented.\n */\nexport function parsePageParam(value: string | string[] | undefined): number {\n const raw = Array.isArray(value) ? value[0] : value\n const n = Number(raw)\n return Number.isInteger(n) && n > 0 ? n : 1\n}\n"],"names":["buildArchivePath","archiveSlug","locale","page","base","buildEntryPath","entrySlug","parsePageParam","value","raw","Array","isArray","n","Number","isInteger"],"mappings":"AAQA;;;;;;;;CAQC,GACD,OAAO,SAASA,iBAAiB,EAAEC,WAAW,EAAEC,MAAM,EAAEC,OAAO,CAAC,EAAmB;IACjF,MAAMC,OAAO,CAAC,CAAC,EAAEF,OAAO,CAAC,EAAED,aAAa;IACxC,OAAOE,OAAO,IAAI,GAAGC,KAAK,MAAM,EAAED,MAAM,GAAGC;AAC7C;AAUA,mDAAmD,GACnD,OAAO,SAASC,eAAe,EAAEJ,WAAW,EAAEK,SAAS,EAAEJ,MAAM,EAAiB;IAC9E,OAAO,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAED,YAAY,CAAC,EAAEK,WAAW;AACjD;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,KAAoC;IACjE,MAAMC,MAAMC,MAAMC,OAAO,CAACH,SAASA,KAAK,CAAC,EAAE,GAAGA;IAC9C,MAAMI,IAAIC,OAAOJ;IACjB,OAAOI,OAAOC,SAAS,CAACF,MAAMA,IAAI,IAAIA,IAAI;AAC5C"} \ No newline at end of file +{"version":3,"sources":["paths.js"],"sourcesContent":["type ArchivePathArgs = {\n /** Archive page's slug in this locale, e.g. 'artykuly'. */\n archiveSlug: string\n locale: string\n /** 1-based. Page 1 is omitted from the URL. */\n page?: number\n}\n\n/**\n * Path to an archive listing: /pl/artykuly, /pl/artykuly?page=2.\n *\n * Pagination goes in the query string rather than the path. A path segment\n * (/pl/artykuly/2) would be ambiguous with an entry slugged \"2\", and the\n * alternative (/pl/artykuly/strona/2) drags in yet another localized segment to\n * configure and translate. Google has understood ?page= for years, and\n * rel=next/prev — the reason people used to prefer path segments — was retired.\n */\nexport function buildArchivePath({ archiveSlug, locale, page = 1 }: ArchivePathArgs): string {\n const base = `/${locale}/${archiveSlug}`\n return page > 1 ? `${base}?page=${page}` : base\n}\n\ntype EntryPathArgs = {\n /** Archive page's slug in this locale, e.g. 'artykuly'. */\n archiveSlug: string\n /** Entry's slug in this locale, e.g. 'moj-post'. */\n entrySlug: string\n locale: string\n}\n\n/** Path to a single entry: /pl/artykuly/moj-post. */\nexport function buildEntryPath({ archiveSlug, entrySlug, locale }: EntryPathArgs): string {\n return `/${locale}/${archiveSlug}/${entrySlug}`\n}\n\n/**\n * Reads a page number out of a query parameter.\n *\n * Anything that isn't a positive integer is page 1 — `?page=abc`, `?page=-5`,\n * `?page=1.5` and a repeated `?page=1&page=2` all have to resolve to something,\n * and silently showing the first page beats a 500 on a URL a crawler invented.\n */\nexport function parsePageParam(value: string | string[] | undefined): number {\n const raw = Array.isArray(value) ? value[0] : value\n const n = Number(raw)\n return Number.isInteger(n) && n > 0 ? n : 1\n}\n"],"names":["buildArchivePath","archiveSlug","locale","page","base","buildEntryPath","entrySlug","parsePageParam","value","raw","Array","isArray","n","Number","isInteger"],"mappings":"AAQA;;;;;;;;CAQC,GACD,OAAO,SAASA,iBAAiB,EAAEC,WAAW,EAAEC,MAAM,EAAEC,OAAO,CAAC,EAAmB;IACjF,MAAMC,OAAO,CAAC,CAAC,EAAEF,OAAO,CAAC,EAAED,aAAa;IACxC,OAAOE,OAAO,IAAI,GAAGC,KAAK,MAAM,EAAED,MAAM,GAAGC;AAC7C;AAUA,mDAAmD,GACnD,OAAO,SAASC,eAAe,EAAEJ,WAAW,EAAEK,SAAS,EAAEJ,MAAM,EAAiB;IAC9E,OAAO,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAED,YAAY,CAAC,EAAEK,WAAW;AACjD;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,KAAoC;IACjE,MAAMC,MAAMC,MAAMC,OAAO,CAACH,SAASA,KAAK,CAAC,EAAE,GAAGA;IAC9C,MAAMI,IAAIC,OAAOJ;IACjB,OAAOI,OAAOC,SAAS,CAACF,MAAMA,IAAI,IAAIA,IAAI;AAC5C"} \ No newline at end of file diff --git a/dist/modules/content/resolveRoute.js b/dist/modules/content/resolveRoute.js index d2341fb..9923607 100644 --- a/dist/modules/content/resolveRoute.js +++ b/dist/modules/content/resolveRoute.js @@ -106,6 +106,6 @@ import { archiveFieldName } from './types.js'; type: 'page', doc: doc }; -} +} //# sourceMappingURL=resolveRoute.js.map //# sourceMappingURL=resolveRoute.js.map \ No newline at end of file diff --git a/dist/modules/content/resolveRoute.js.map b/dist/modules/content/resolveRoute.js.map index bb6b4dc..cd63f18 100644 --- a/dist/modules/content/resolveRoute.js.map +++ b/dist/modules/content/resolveRoute.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/content/resolveRoute.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from './types.js'\n\nimport { archiveFieldName } from './types.js'\n\ntype ArchivePage = { id: number | string; slug?: unknown }\n\nexport type ResolvedRoute =\n /** Locale root — the page assigned as Homepage in System Pages. */\n | {\n /** The archive page this entry lives under — its slug is the URL prefix. */\n archive: Record\n collection: string\n doc: Record\n type: 'entry'\n }\n /** An ordinary page. */\n | {\n collection: string\n doc: Record\n /** 1-based, from ?page=. */\n page: number\n perPage: number\n type: 'archive'\n }\n /** A collection's archive page, e.g. /pl/artykuly. */\n | { doc: Record; type: 'home' }\n /** A single entry, e.g. /pl/artykuly/moj-post. */\n | { doc: Record; type: 'page' }\n\ntype ResolveRouteArgs = {\n content?: ContentOption\n locale: string\n /** Page number from the query string (?page=2). Defaults to 1. */\n page?: number\n /** Client's Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** Route segments after the locale, e.g. ['artykuly', 'moj-post']. */\n segments?: string[]\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\n/**\n * Works out what a URL points at: the home page, an ordinary page, a\n * collection's archive, or a single entry.\n *\n * Routing only. An archive result says which collection to list and on which\n * page, but doesn't fetch the entries — that's `getArchiveEntries`, called by\n * whoever actually needs them. Keeping the two apart means listing changes\n * (sorting, filtering, pagination) never touch routing rules, and metadata\n * generation doesn't pay for a query it would discard.\n *\n * The trick is that archive prefixes aren't configured anywhere — they're the\n * slug of whichever page an editor assigned as that collection's archive. So\n * /pl/artykuly and /en/articles come from one assignment, and renaming the page\n * moves the whole section.\n *\n * Resolution order matters: a first segment matching an archive's slug wins\n * over an ordinary page of the same name, because an archive *is* a page and\n * would otherwise shadow its own entries.\n *\n * Depth beyond {archive}/{entry} isn't supported — categories in the path would\n * make canonical and hreflang ambiguous (the same entry reachable under several\n * URLs).\n */\nexport async function resolveRoute({\n content,\n locale,\n page = 1,\n pagesSlug = 'pages',\n payload,\n segments,\n settingsSlug = 'site-settings',\n}: ResolveRouteArgs): Promise {\n const settings = (await payload.findGlobal({\n slug: settingsSlug,\n depth: 1,\n locale: locale as never,\n })) as Record\n\n // Locale root → Homepage from System Pages.\n if (!segments?.length) {\n const homepage = settings.homepage\n if (homepage && typeof homepage === 'object') {\n return { type: 'home', doc: homepage as Record }\n }\n return null\n }\n\n const [first, ...rest] = segments\n\n // Does the first segment name an archive page?\n for (const collection of content?.collections ?? []) {\n const archive = settings[archiveFieldName(collection.slug)]\n if (!archive || typeof archive !== 'object') {continue}\n\n const archiveDoc = archive as ArchivePage\n if (archiveDoc.slug !== first) {continue}\n\n // /pl/artykuly → the archive page itself.\n if (rest.length === 0) {\n return {\n type: 'archive',\n collection: collection.slug,\n doc: archiveDoc as Record,\n page,\n perPage: collection.perPage ?? 10,\n }\n }\n\n // /pl/artykuly/moj-post → an entry.\n if (rest.length === 1) {\n const found = await payload.find({\n collection: collection.slug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: rest[0] } },\n })\n const entry = found.docs[0]\n if (!entry) {return null}\n\n return {\n type: 'entry',\n archive: archiveDoc as Record,\n collection: collection.slug,\n doc: entry as Record,\n }\n }\n\n // Deeper than {archive}/{entry}.\n return null\n }\n\n // An ordinary page. Joined, so nested slugs ('a/b') keep working.\n const found = await payload.find({\n collection: pagesSlug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: segments.join('/') } },\n })\n const doc = found.docs[0]\n if (!doc) {return null}\n\n return { type: 'page', doc: doc as Record }\n}\n"],"names":["archiveFieldName","resolveRoute","content","locale","page","pagesSlug","payload","segments","settingsSlug","settings","findGlobal","slug","depth","length","homepage","type","doc","first","rest","collection","collections","archive","archiveDoc","perPage","found","find","limit","where","equals","entry","docs","join"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,aAAY;AAyC7C;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,eAAeC,aAAa,EACjCC,OAAO,EACPC,MAAM,EACNC,OAAO,CAAC,EACRC,YAAY,OAAO,EACnBC,OAAO,EACPC,QAAQ,EACRC,eAAe,eAAe,EACb;IACjB,MAAMC,WAAY,MAAMH,QAAQI,UAAU,CAAC;QACzCC,MAAMH;QACNI,OAAO;QACPT,QAAQA;IACV;IAEA,4CAA4C;IAC5C,IAAI,CAACI,UAAUM,QAAQ;QACrB,MAAMC,WAAWL,SAASK,QAAQ;QAClC,IAAIA,YAAY,OAAOA,aAAa,UAAU;YAC5C,OAAO;gBAAEC,MAAM;gBAAQC,KAAKF;YAAoC;QAClE;QACA,OAAO;IACT;IAEA,MAAM,CAACG,OAAO,GAAGC,KAAK,GAAGX;IAEzB,+CAA+C;IAC/C,KAAK,MAAMY,cAAcjB,SAASkB,eAAe,EAAE,CAAE;QACnD,MAAMC,UAAUZ,QAAQ,CAACT,iBAAiBmB,WAAWR,IAAI,EAAE;QAC3D,IAAI,CAACU,WAAW,OAAOA,YAAY,UAAU;YAAC;QAAQ;QAEtD,MAAMC,aAAaD;QACnB,IAAIC,WAAWX,IAAI,KAAKM,OAAO;YAAC;QAAQ;QAExC,0CAA0C;QAC1C,IAAIC,KAAKL,MAAM,KAAK,GAAG;YACrB,OAAO;gBACLE,MAAM;gBACNI,YAAYA,WAAWR,IAAI;gBAC3BK,KAAKM;gBACLlB;gBACAmB,SAASJ,WAAWI,OAAO,IAAI;YACjC;QACF;QAEA,oCAAoC;QACpC,IAAIL,KAAKL,MAAM,KAAK,GAAG;YACrB,MAAMW,QAAQ,MAAMlB,QAAQmB,IAAI,CAAC;gBAC/BN,YAAYA,WAAWR,IAAI;gBAC3BC,OAAO;gBACPc,OAAO;gBACPvB,QAAQA;gBACRwB,OAAO;oBAAEhB,MAAM;wBAAEiB,QAAQV,IAAI,CAAC,EAAE;oBAAC;gBAAE;YACrC;YACA,MAAMW,QAAQL,MAAMM,IAAI,CAAC,EAAE;YAC3B,IAAI,CAACD,OAAO;gBAAC,OAAO;YAAI;YAExB,OAAO;gBACLd,MAAM;gBACNM,SAASC;gBACTH,YAAYA,WAAWR,IAAI;gBAC3BK,KAAKa;YACP;QACF;QAEA,iCAAiC;QACjC,OAAO;IACT;IAEA,kEAAkE;IAClE,MAAML,QAAQ,MAAMlB,QAAQmB,IAAI,CAAC;QAC/BN,YAAYd;QACZO,OAAO;QACPc,OAAO;QACPvB,QAAQA;QACRwB,OAAO;YAAEhB,MAAM;gBAAEiB,QAAQrB,SAASwB,IAAI,CAAC;YAAK;QAAE;IAChD;IACA,MAAMf,MAAMQ,MAAMM,IAAI,CAAC,EAAE;IACzB,IAAI,CAACd,KAAK;QAAC,OAAO;IAAI;IAEtB,OAAO;QAAED,MAAM;QAAQC,KAAKA;IAA+B;AAC7D"} \ No newline at end of file +{"version":3,"sources":["resolveRoute.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from './types.js'\n\nimport { archiveFieldName } from './types.js'\n\ntype ArchivePage = { id: number | string; slug?: unknown }\n\nexport type ResolvedRoute =\n /** Locale root — the page assigned as Homepage in System Pages. */\n | {\n /** The archive page this entry lives under — its slug is the URL prefix. */\n archive: Record\n collection: string\n doc: Record\n type: 'entry'\n }\n /** An ordinary page. */\n | {\n collection: string\n doc: Record\n /** 1-based, from ?page=. */\n page: number\n perPage: number\n type: 'archive'\n }\n /** A collection's archive page, e.g. /pl/artykuly. */\n | { doc: Record; type: 'home' }\n /** A single entry, e.g. /pl/artykuly/moj-post. */\n | { doc: Record; type: 'page' }\n\ntype ResolveRouteArgs = {\n content?: ContentOption\n locale: string\n /** Page number from the query string (?page=2). Defaults to 1. */\n page?: number\n /** Client's Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** Route segments after the locale, e.g. ['artykuly', 'moj-post']. */\n segments?: string[]\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\n/**\n * Works out what a URL points at: the home page, an ordinary page, a\n * collection's archive, or a single entry.\n *\n * Routing only. An archive result says which collection to list and on which\n * page, but doesn't fetch the entries — that's `getArchiveEntries`, called by\n * whoever actually needs them. Keeping the two apart means listing changes\n * (sorting, filtering, pagination) never touch routing rules, and metadata\n * generation doesn't pay for a query it would discard.\n *\n * The trick is that archive prefixes aren't configured anywhere — they're the\n * slug of whichever page an editor assigned as that collection's archive. So\n * /pl/artykuly and /en/articles come from one assignment, and renaming the page\n * moves the whole section.\n *\n * Resolution order matters: a first segment matching an archive's slug wins\n * over an ordinary page of the same name, because an archive *is* a page and\n * would otherwise shadow its own entries.\n *\n * Depth beyond {archive}/{entry} isn't supported — categories in the path would\n * make canonical and hreflang ambiguous (the same entry reachable under several\n * URLs).\n */\nexport async function resolveRoute({\n content,\n locale,\n page = 1,\n pagesSlug = 'pages',\n payload,\n segments,\n settingsSlug = 'site-settings',\n}: ResolveRouteArgs): Promise {\n const settings = (await payload.findGlobal({\n slug: settingsSlug,\n depth: 1,\n locale: locale as never,\n })) as Record\n\n // Locale root → Homepage from System Pages.\n if (!segments?.length) {\n const homepage = settings.homepage\n if (homepage && typeof homepage === 'object') {\n return { type: 'home', doc: homepage as Record }\n }\n return null\n }\n\n const [first, ...rest] = segments\n\n // Does the first segment name an archive page?\n for (const collection of content?.collections ?? []) {\n const archive = settings[archiveFieldName(collection.slug)]\n if (!archive || typeof archive !== 'object') {continue}\n\n const archiveDoc = archive as ArchivePage\n if (archiveDoc.slug !== first) {continue}\n\n // /pl/artykuly → the archive page itself.\n if (rest.length === 0) {\n return {\n type: 'archive',\n collection: collection.slug,\n doc: archiveDoc as Record,\n page,\n perPage: collection.perPage ?? 10,\n }\n }\n\n // /pl/artykuly/moj-post → an entry.\n if (rest.length === 1) {\n const found = await payload.find({\n collection: collection.slug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: rest[0] } },\n })\n const entry = found.docs[0]\n if (!entry) {return null}\n\n return {\n type: 'entry',\n archive: archiveDoc as Record,\n collection: collection.slug,\n doc: entry as Record,\n }\n }\n\n // Deeper than {archive}/{entry}.\n return null\n }\n\n // An ordinary page. Joined, so nested slugs ('a/b') keep working.\n const found = await payload.find({\n collection: pagesSlug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: segments.join('/') } },\n })\n const doc = found.docs[0]\n if (!doc) {return null}\n\n return { type: 'page', doc: doc as Record }\n}\n"],"names":["archiveFieldName","resolveRoute","content","locale","page","pagesSlug","payload","segments","settingsSlug","settings","findGlobal","slug","depth","length","homepage","type","doc","first","rest","collection","collections","archive","archiveDoc","perPage","found","find","limit","where","equals","entry","docs","join"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,aAAY;AAyC7C;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,eAAeC,aAAa,EACjCC,OAAO,EACPC,MAAM,EACNC,OAAO,CAAC,EACRC,YAAY,OAAO,EACnBC,OAAO,EACPC,QAAQ,EACRC,eAAe,eAAe,EACb;IACjB,MAAMC,WAAY,MAAMH,QAAQI,UAAU,CAAC;QACzCC,MAAMH;QACNI,OAAO;QACPT,QAAQA;IACV;IAEA,4CAA4C;IAC5C,IAAI,CAACI,UAAUM,QAAQ;QACrB,MAAMC,WAAWL,SAASK,QAAQ;QAClC,IAAIA,YAAY,OAAOA,aAAa,UAAU;YAC5C,OAAO;gBAAEC,MAAM;gBAAQC,KAAKF;YAAoC;QAClE;QACA,OAAO;IACT;IAEA,MAAM,CAACG,OAAO,GAAGC,KAAK,GAAGX;IAEzB,+CAA+C;IAC/C,KAAK,MAAMY,cAAcjB,SAASkB,eAAe,EAAE,CAAE;QACnD,MAAMC,UAAUZ,QAAQ,CAACT,iBAAiBmB,WAAWR,IAAI,EAAE;QAC3D,IAAI,CAACU,WAAW,OAAOA,YAAY,UAAU;YAAC;QAAQ;QAEtD,MAAMC,aAAaD;QACnB,IAAIC,WAAWX,IAAI,KAAKM,OAAO;YAAC;QAAQ;QAExC,0CAA0C;QAC1C,IAAIC,KAAKL,MAAM,KAAK,GAAG;YACrB,OAAO;gBACLE,MAAM;gBACNI,YAAYA,WAAWR,IAAI;gBAC3BK,KAAKM;gBACLlB;gBACAmB,SAASJ,WAAWI,OAAO,IAAI;YACjC;QACF;QAEA,oCAAoC;QACpC,IAAIL,KAAKL,MAAM,KAAK,GAAG;YACrB,MAAMW,QAAQ,MAAMlB,QAAQmB,IAAI,CAAC;gBAC/BN,YAAYA,WAAWR,IAAI;gBAC3BC,OAAO;gBACPc,OAAO;gBACPvB,QAAQA;gBACRwB,OAAO;oBAAEhB,MAAM;wBAAEiB,QAAQV,IAAI,CAAC,EAAE;oBAAC;gBAAE;YACrC;YACA,MAAMW,QAAQL,MAAMM,IAAI,CAAC,EAAE;YAC3B,IAAI,CAACD,OAAO;gBAAC,OAAO;YAAI;YAExB,OAAO;gBACLd,MAAM;gBACNM,SAASC;gBACTH,YAAYA,WAAWR,IAAI;gBAC3BK,KAAKa;YACP;QACF;QAEA,iCAAiC;QACjC,OAAO;IACT;IAEA,kEAAkE;IAClE,MAAML,QAAQ,MAAMlB,QAAQmB,IAAI,CAAC;QAC/BN,YAAYd;QACZO,OAAO;QACPc,OAAO;QACPvB,QAAQA;QACRwB,OAAO;YAAEhB,MAAM;gBAAEiB,QAAQrB,SAASwB,IAAI,CAAC;YAAK;QAAE;IAChD;IACA,MAAMf,MAAMQ,MAAMM,IAAI,CAAC,EAAE;IACzB,IAAI,CAACd,KAAK;QAAC,OAAO;IAAI;IAEtB,OAAO;QAAED,MAAM;QAAQC,KAAKA;IAA+B;AAC7D"} \ No newline at end of file diff --git a/dist/modules/content/types.js b/dist/modules/content/types.js index 976c958..d8b4c5e 100644 --- a/dist/modules/content/types.js +++ b/dist/modules/content/types.js @@ -12,6 +12,6 @@ * `posts` → `postsArchive`. */ export function archiveFieldName(collectionSlug) { return `${collectionSlug}Archive`; -} +} //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/content/types.js.map b/dist/modules/content/types.js.map index c7df3bf..e8e43bb 100644 --- a/dist/modules/content/types.js.map +++ b/dist/modules/content/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/content/types.ts"],"sourcesContent":["/**\n * A collection whose entries live under an archive page, e.g. blog posts at\n * /pl/artykuly/moj-post.\n *\n * The collection itself belongs to the client — Payload keeps its schema in\n * code, so adding one is always a code change. What this option adds is the\n * routing: the plugin exposes an \"archive page\" assignment in SiteSettings, and\n * whichever page an editor picks becomes the URL segment. Rename that page from\n * \"Artykuły\" to \"Wpisy\" and the path follows, per locale, with no deploy.\n */\nexport type ContentCollectionOption = {\n /** Slug of the client's collection, e.g. 'posts'. */\n slug: string\n /** Admin label for the archive assignment. Defaults to the slug. */\n label?: string\n /** Entries per page in listings. Defaults to 10. */\n perPage?: number\n}\n\n/**\n * Plugin option wiring archive-backed collections into routing.\n */\nexport type ContentOption = {\n collections: ContentCollectionOption[]\n}\n\n/**\n * Field name holding a collection's archive page in SiteSettings.\n * `posts` → `postsArchive`.\n */\nexport function archiveFieldName(collectionSlug: string): string {\n return `${collectionSlug}Archive`\n}\n"],"names":["archiveFieldName","collectionSlug"],"mappings":"AAAA;;;;;;;;;CASC,GAiBD;;;CAGC,GACD,OAAO,SAASA,iBAAiBC,cAAsB;IACrD,OAAO,GAAGA,eAAe,OAAO,CAAC;AACnC"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["/**\n * A collection whose entries live under an archive page, e.g. blog posts at\n * /pl/artykuly/moj-post.\n *\n * The collection itself belongs to the client — Payload keeps its schema in\n * code, so adding one is always a code change. What this option adds is the\n * routing: the plugin exposes an \"archive page\" assignment in SiteSettings, and\n * whichever page an editor picks becomes the URL segment. Rename that page from\n * \"Artykuły\" to \"Wpisy\" and the path follows, per locale, with no deploy.\n */\nexport type ContentCollectionOption = {\n /** Slug of the client's collection, e.g. 'posts'. */\n slug: string\n /** Admin label for the archive assignment. Defaults to the slug. */\n label?: string\n /** Entries per page in listings. Defaults to 10. */\n perPage?: number\n}\n\n/**\n * Plugin option wiring archive-backed collections into routing.\n */\nexport type ContentOption = {\n collections: ContentCollectionOption[]\n}\n\n/**\n * Field name holding a collection's archive page in SiteSettings.\n * `posts` → `postsArchive`.\n */\nexport function archiveFieldName(collectionSlug: string): string {\n return `${collectionSlug}Archive`\n}\n"],"names":["archiveFieldName","collectionSlug"],"mappings":"AAAA;;;;;;;;;CASC,GAiBD;;;CAGC,GACD,OAAO,SAASA,iBAAiBC,cAAsB;IACrD,OAAO,GAAGA,eAAe,OAAO,CAAC;AACnC"} \ No newline at end of file diff --git a/dist/modules/email/index.js b/dist/modules/email/index.js index 385c3b2..caf0ea4 100644 --- a/dist/modules/email/index.js +++ b/dist/modules/email/index.js @@ -1,5 +1,5 @@ // Server-only exports. sendEmail imports 'server-only' (SMTP password, nodemailer) // so this must never be imported from a client component. -export { sendEmail } from './sendEmail.js'; +export { sendEmail } from './sendEmail.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/email/index.js.map b/dist/modules/email/index.js.map index d62dbdf..02e71de 100644 --- a/dist/modules/email/index.js.map +++ b/dist/modules/email/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/email/index.ts"],"sourcesContent":["// Server-only exports. sendEmail imports 'server-only' (SMTP password, nodemailer)\n// so this must never be imported from a client component.\nexport { sendEmail } from './sendEmail.js'\nexport type { SendEmailArgs, SendEmailResult } from './sendEmail.js'\n"],"names":["sendEmail"],"mappings":"AAAA,mFAAmF;AACnF,0DAA0D;AAC1D,SAASA,SAAS,QAAQ,iBAAgB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["// Server-only exports. sendEmail imports 'server-only' (SMTP password, nodemailer)\n// so this must never be imported from a client component.\nexport { sendEmail } from './sendEmail.js'\nexport type { SendEmailArgs, SendEmailResult } from './sendEmail.js'\n"],"names":["sendEmail"],"mappings":"AAAA,mFAAmF;AACnF,0DAA0D;AAC1D,SAASA,SAAS,QAAQ,iBAAgB"} \ No newline at end of file diff --git a/dist/modules/email/panelSmtpAdapter.js b/dist/modules/email/panelSmtpAdapter.js index 19133e5..3e4ba7f 100644 --- a/dist/modules/email/panelSmtpAdapter.js +++ b/dist/modules/email/panelSmtpAdapter.js @@ -78,6 +78,6 @@ import { getSiteIntegrations } from '../payload/index.js'; }; } } - }); + }); //# sourceMappingURL=panelSmtpAdapter.js.map //# sourceMappingURL=panelSmtpAdapter.js.map \ No newline at end of file diff --git a/dist/modules/email/panelSmtpAdapter.js.map b/dist/modules/email/panelSmtpAdapter.js.map index 06b1854..d566629 100644 --- a/dist/modules/email/panelSmtpAdapter.js.map +++ b/dist/modules/email/panelSmtpAdapter.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/email/panelSmtpAdapter.ts"],"sourcesContent":["import type { PayloadEmailAdapter, SendEmailOptions } from 'payload'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\n/** SMTP fields the adapter reads from SiteIntegrations. */\ntype SmtpIntegrations = {\n smtpFromAddress?: null | string\n smtpFromName?: null | string\n smtpHost?: null | string\n smtpPassword?: null | string\n smtpPort?: null | number\n smtpUser?: null | string\n}\n\nexport type PanelSmtpAdapterArgs = {\n /**\n * Used only until the panel is filled in — Payload requires a from-address\n * synchronously at boot, before any global can be read. Once SiteIntegrations\n * has an address, it wins.\n */\n fallbackFromAddress?: string\n fallbackFromName?: string\n}\n\n/**\n * Payload email adapter backed by the SMTP settings in the SiteIntegrations\n * global.\n *\n * Why this exists: Payload builds its email adapter once, at boot, from the\n * config — which would normally mean SMTP credentials living in env vars and a\n * redeploy to change them. This adapter instead resolves SMTP on every send, so\n * an editor can change the mailbox in the admin panel and the next email uses\n * it.\n *\n * Wiring it into the config means `payload.sendEmail` works everywhere — which\n * includes the form-builder's own submission emails (the ones an editor\n * configures per form under \"Emails\"). Those go out over the panel's SMTP with\n * no extra code.\n *\n * Boot-time constraints shape two details:\n * - `defaultFromAddress` / `defaultFromName` must be returned synchronously, so\n * they're placeholders; the real from-address is applied per message below.\n * - nodemailer is imported dynamically inside sendEmail, so merely loading the\n * Payload config (or running `generate:importmap`) doesn't pull it in.\n */\nexport const panelSmtpAdapter =\n (args: PanelSmtpAdapterArgs = {}): PayloadEmailAdapter =>\n ({ payload }) => ({\n name: 'ipal-panel-smtp',\n defaultFromAddress: args.fallbackFromAddress ?? 'noreply@localhost',\n defaultFromName: args.fallbackFromName ?? 'Website',\n\n sendEmail: async (message: SendEmailOptions) => {\n const smtp = await getSiteIntegrations(payload)\n\n const host = smtp.smtpHost\n const port = smtp.smtpPort ?? 587\n const user = smtp.smtpUser\n const pass = smtp.smtpPassword\n\n if (!host || !user || !pass) {\n payload.logger.error('[ipal] Email not sent: SMTP is not configured in Site Integrations.')\n return { error: 'SMTP is not configured in Site Integrations.', sent: false }\n }\n\n // The panel is the source of truth for the sender; fall back to whatever\n // the caller set (Payload fills in defaultFromAddress when unset).\n const fromAddress = smtp.smtpFromAddress\n const fromName = smtp.smtpFromName\n const from = fromAddress\n ? fromName\n ? `${fromName} <${fromAddress}>`\n : fromAddress\n : message.from\n\n // Defence in depth against header injection. Modern nodemailer already\n // normalises CRLF in standard headers, but the form-builder interpolates\n // user data into the subject via {{field}} placeholders, so strip any\n // newlines from header-bound values before they reach the transport.\n const stripCRLF = (v: unknown): string =>\n String(v ?? '')\n .replace(/[\\r\\n]+/g, ' ')\n .trim()\n\n const { default: nodemailer } = await import('nodemailer')\n\n const transporter = nodemailer.createTransport({\n auth: { pass, user },\n host,\n port,\n secure: port === 465, // implicit TLS on 465, STARTTLS otherwise\n })\n\n try {\n const info = await transporter.sendMail({\n ...message,\n from,\n ...(message.subject ? { subject: stripCRLF(message.subject) } : {}),\n })\n return { messageId: info.messageId, sent: true }\n } catch (err) {\n // Never surface SMTP internals to the caller — a form submission\n // shouldn't fail loudly because the mailbox is misconfigured.\n payload.logger.error(`[ipal] Email send failed: ${(err as Error).message}`)\n return { error: 'Failed to send email.', sent: false }\n }\n },\n })\n"],"names":["getSiteIntegrations","panelSmtpAdapter","args","payload","name","defaultFromAddress","fallbackFromAddress","defaultFromName","fallbackFromName","sendEmail","message","smtp","host","smtpHost","port","smtpPort","user","smtpUser","pass","smtpPassword","logger","error","sent","fromAddress","smtpFromAddress","fromName","smtpFromName","from","stripCRLF","v","String","replace","trim","default","nodemailer","transporter","createTransport","auth","secure","info","sendMail","subject","messageId","err"],"mappings":"AAEA,SAASA,mBAAmB,QAAQ,sBAAqB;AAsBzD;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,mBACX,CAACC,OAA6B,CAAC,CAAC,GAChC,CAAC,EAAEC,OAAO,EAAE,GAAM,CAAA;YAChBC,MAAM;YACNC,oBAAoBH,KAAKI,mBAAmB,IAAI;YAChDC,iBAAiBL,KAAKM,gBAAgB,IAAI;YAE1CC,WAAW,OAAOC;gBAChB,MAAMC,OAAO,MAAMX,oBAAsCG;gBAEzD,MAAMS,OAAOD,KAAKE,QAAQ;gBAC1B,MAAMC,OAAOH,KAAKI,QAAQ,IAAI;gBAC9B,MAAMC,OAAOL,KAAKM,QAAQ;gBAC1B,MAAMC,OAAOP,KAAKQ,YAAY;gBAE9B,IAAI,CAACP,QAAQ,CAACI,QAAQ,CAACE,MAAM;oBAC3Bf,QAAQiB,MAAM,CAACC,KAAK,CAAC;oBACrB,OAAO;wBAAEA,OAAO;wBAAgDC,MAAM;oBAAM;gBAC9E;gBAEA,yEAAyE;gBACzE,mEAAmE;gBACnE,MAAMC,cAAcZ,KAAKa,eAAe;gBACxC,MAAMC,WAAWd,KAAKe,YAAY;gBAClC,MAAMC,OAAOJ,cACTE,WACE,GAAGA,SAAS,EAAE,EAAEF,YAAY,CAAC,CAAC,GAC9BA,cACFb,QAAQiB,IAAI;gBAEhB,uEAAuE;gBACvE,yEAAyE;gBACzE,sEAAsE;gBACtE,qEAAqE;gBACrE,MAAMC,YAAY,CAACC,IACjBC,OAAOD,KAAK,IACTE,OAAO,CAAC,YAAY,KACpBC,IAAI;gBAET,MAAM,EAAEC,SAASC,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC;gBAE7C,MAAMC,cAAcD,WAAWE,eAAe,CAAC;oBAC7CC,MAAM;wBAAEnB;wBAAMF;oBAAK;oBACnBJ;oBACAE;oBACAwB,QAAQxB,SAAS;gBACnB;gBAEA,IAAI;oBACF,MAAMyB,OAAO,MAAMJ,YAAYK,QAAQ,CAAC;wBACtC,GAAG9B,OAAO;wBACViB;wBACA,GAAIjB,QAAQ+B,OAAO,GAAG;4BAAEA,SAASb,UAAUlB,QAAQ+B,OAAO;wBAAE,IAAI,CAAC,CAAC;oBACpE;oBACA,OAAO;wBAAEC,WAAWH,KAAKG,SAAS;wBAAEpB,MAAM;oBAAK;gBACjD,EAAE,OAAOqB,KAAK;oBACZ,iEAAiE;oBACjE,8DAA8D;oBAC9DxC,QAAQiB,MAAM,CAACC,KAAK,CAAC,CAAC,0BAA0B,EAAE,AAACsB,IAAcjC,OAAO,EAAE;oBAC1E,OAAO;wBAAEW,OAAO;wBAAyBC,MAAM;oBAAM;gBACvD;YACF;QACF,CAAA,EAAE"} \ No newline at end of file +{"version":3,"sources":["panelSmtpAdapter.js"],"sourcesContent":["import type { PayloadEmailAdapter, SendEmailOptions } from 'payload'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\n/** SMTP fields the adapter reads from SiteIntegrations. */\ntype SmtpIntegrations = {\n smtpFromAddress?: null | string\n smtpFromName?: null | string\n smtpHost?: null | string\n smtpPassword?: null | string\n smtpPort?: null | number\n smtpUser?: null | string\n}\n\nexport type PanelSmtpAdapterArgs = {\n /**\n * Used only until the panel is filled in — Payload requires a from-address\n * synchronously at boot, before any global can be read. Once SiteIntegrations\n * has an address, it wins.\n */\n fallbackFromAddress?: string\n fallbackFromName?: string\n}\n\n/**\n * Payload email adapter backed by the SMTP settings in the SiteIntegrations\n * global.\n *\n * Why this exists: Payload builds its email adapter once, at boot, from the\n * config — which would normally mean SMTP credentials living in env vars and a\n * redeploy to change them. This adapter instead resolves SMTP on every send, so\n * an editor can change the mailbox in the admin panel and the next email uses\n * it.\n *\n * Wiring it into the config means `payload.sendEmail` works everywhere — which\n * includes the form-builder's own submission emails (the ones an editor\n * configures per form under \"Emails\"). Those go out over the panel's SMTP with\n * no extra code.\n *\n * Boot-time constraints shape two details:\n * - `defaultFromAddress` / `defaultFromName` must be returned synchronously, so\n * they're placeholders; the real from-address is applied per message below.\n * - nodemailer is imported dynamically inside sendEmail, so merely loading the\n * Payload config (or running `generate:importmap`) doesn't pull it in.\n */\nexport const panelSmtpAdapter =\n (args: PanelSmtpAdapterArgs = {}): PayloadEmailAdapter =>\n ({ payload }) => ({\n name: 'ipal-panel-smtp',\n defaultFromAddress: args.fallbackFromAddress ?? 'noreply@localhost',\n defaultFromName: args.fallbackFromName ?? 'Website',\n\n sendEmail: async (message: SendEmailOptions) => {\n const smtp = await getSiteIntegrations(payload)\n\n const host = smtp.smtpHost\n const port = smtp.smtpPort ?? 587\n const user = smtp.smtpUser\n const pass = smtp.smtpPassword\n\n if (!host || !user || !pass) {\n payload.logger.error('[ipal] Email not sent: SMTP is not configured in Site Integrations.')\n return { error: 'SMTP is not configured in Site Integrations.', sent: false }\n }\n\n // The panel is the source of truth for the sender; fall back to whatever\n // the caller set (Payload fills in defaultFromAddress when unset).\n const fromAddress = smtp.smtpFromAddress\n const fromName = smtp.smtpFromName\n const from = fromAddress\n ? fromName\n ? `${fromName} <${fromAddress}>`\n : fromAddress\n : message.from\n\n // Defence in depth against header injection. Modern nodemailer already\n // normalises CRLF in standard headers, but the form-builder interpolates\n // user data into the subject via {{field}} placeholders, so strip any\n // newlines from header-bound values before they reach the transport.\n const stripCRLF = (v: unknown): string =>\n String(v ?? '')\n .replace(/[\\r\\n]+/g, ' ')\n .trim()\n\n const { default: nodemailer } = await import('nodemailer')\n\n const transporter = nodemailer.createTransport({\n auth: { pass, user },\n host,\n port,\n secure: port === 465, // implicit TLS on 465, STARTTLS otherwise\n })\n\n try {\n const info = await transporter.sendMail({\n ...message,\n from,\n ...(message.subject ? { subject: stripCRLF(message.subject) } : {}),\n })\n return { messageId: info.messageId, sent: true }\n } catch (err) {\n // Never surface SMTP internals to the caller — a form submission\n // shouldn't fail loudly because the mailbox is misconfigured.\n payload.logger.error(`[ipal] Email send failed: ${(err as Error).message}`)\n return { error: 'Failed to send email.', sent: false }\n }\n },\n })\n"],"names":["getSiteIntegrations","panelSmtpAdapter","args","payload","name","defaultFromAddress","fallbackFromAddress","defaultFromName","fallbackFromName","sendEmail","message","smtp","host","smtpHost","port","smtpPort","user","smtpUser","pass","smtpPassword","logger","error","sent","fromAddress","smtpFromAddress","fromName","smtpFromName","from","stripCRLF","v","String","replace","trim","default","nodemailer","transporter","createTransport","auth","secure","info","sendMail","subject","messageId","err"],"mappings":"AAEA,SAASA,mBAAmB,QAAQ,sBAAqB;AAsBzD;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,mBACX,CAACC,OAA6B,CAAC,CAAC,GAChC,CAAC,EAAEC,OAAO,EAAE,GAAM,CAAA;YAChBC,MAAM;YACNC,oBAAoBH,KAAKI,mBAAmB,IAAI;YAChDC,iBAAiBL,KAAKM,gBAAgB,IAAI;YAE1CC,WAAW,OAAOC;gBAChB,MAAMC,OAAO,MAAMX,oBAAsCG;gBAEzD,MAAMS,OAAOD,KAAKE,QAAQ;gBAC1B,MAAMC,OAAOH,KAAKI,QAAQ,IAAI;gBAC9B,MAAMC,OAAOL,KAAKM,QAAQ;gBAC1B,MAAMC,OAAOP,KAAKQ,YAAY;gBAE9B,IAAI,CAACP,QAAQ,CAACI,QAAQ,CAACE,MAAM;oBAC3Bf,QAAQiB,MAAM,CAACC,KAAK,CAAC;oBACrB,OAAO;wBAAEA,OAAO;wBAAgDC,MAAM;oBAAM;gBAC9E;gBAEA,yEAAyE;gBACzE,mEAAmE;gBACnE,MAAMC,cAAcZ,KAAKa,eAAe;gBACxC,MAAMC,WAAWd,KAAKe,YAAY;gBAClC,MAAMC,OAAOJ,cACTE,WACE,GAAGA,SAAS,EAAE,EAAEF,YAAY,CAAC,CAAC,GAC9BA,cACFb,QAAQiB,IAAI;gBAEhB,uEAAuE;gBACvE,yEAAyE;gBACzE,sEAAsE;gBACtE,qEAAqE;gBACrE,MAAMC,YAAY,CAACC,IACjBC,OAAOD,KAAK,IACTE,OAAO,CAAC,YAAY,KACpBC,IAAI;gBAET,MAAM,EAAEC,SAASC,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC;gBAE7C,MAAMC,cAAcD,WAAWE,eAAe,CAAC;oBAC7CC,MAAM;wBAAEnB;wBAAMF;oBAAK;oBACnBJ;oBACAE;oBACAwB,QAAQxB,SAAS;gBACnB;gBAEA,IAAI;oBACF,MAAMyB,OAAO,MAAMJ,YAAYK,QAAQ,CAAC;wBACtC,GAAG9B,OAAO;wBACViB;wBACA,GAAIjB,QAAQ+B,OAAO,GAAG;4BAAEA,SAASb,UAAUlB,QAAQ+B,OAAO;wBAAE,IAAI,CAAC,CAAC;oBACpE;oBACA,OAAO;wBAAEC,WAAWH,KAAKG,SAAS;wBAAEpB,MAAM;oBAAK;gBACjD,EAAE,OAAOqB,KAAK;oBACZ,iEAAiE;oBACjE,8DAA8D;oBAC9DxC,QAAQiB,MAAM,CAACC,KAAK,CAAC,CAAC,0BAA0B,EAAGsB,IAAcjC,OAAO,EAAE;oBAC1E,OAAO;wBAAEW,OAAO;wBAAyBC,MAAM;oBAAM;gBACvD;YACF;SACF,CAAA,CAAE"} \ No newline at end of file diff --git a/dist/modules/email/sendEmail.js b/dist/modules/email/sendEmail.js index a208f78..db0306c 100644 --- a/dist/modules/email/sendEmail.js +++ b/dist/modules/email/sendEmail.js @@ -65,6 +65,6 @@ import { getSiteIntegrations } from '../payload/index.js'; sent: false }; } -} +} //# sourceMappingURL=sendEmail.js.map //# sourceMappingURL=sendEmail.js.map \ No newline at end of file diff --git a/dist/modules/email/sendEmail.js.map b/dist/modules/email/sendEmail.js.map index 75456cf..ac5ec9e 100644 --- a/dist/modules/email/sendEmail.js.map +++ b/dist/modules/email/sendEmail.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/email/sendEmail.ts"],"sourcesContent":["import 'server-only'\n\nimport type { BasePayload } from 'payload'\n\nimport nodemailer from 'nodemailer'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\n/** SMTP fields the sender reads from SiteIntegrations. */\ntype SmtpIntegrations = {\n smtpFromAddress?: null | string\n smtpFromName?: null | string\n smtpHost?: null | string\n smtpPassword?: null | string\n smtpPort?: null | number\n smtpUser?: null | string\n}\n\nexport type SendEmailArgs = {\n /** Override the configured from-address for this message. */\n from?: string\n /** HTML body. */\n html: string\n /** Payload instance — used to read SMTP config from SiteIntegrations. */\n payload: BasePayload\n replyTo?: string\n subject: string\n /** Optional plain-text fallback. */\n text?: string\n to: string | string[]\n}\n\nexport type SendEmailResult = { error: string; sent: false } | { messageId: string; sent: true }\n\n/**\n * Sends an email using SMTP settings from the SiteIntegrations global.\n *\n * Reads config at call time (not at boot) so editors can change SMTP in the\n * admin panel without a restart — consistent with the plugin's panel-managed\n * model. `server-only` keeps the SMTP password out of any client bundle.\n *\n * Returns a result object rather than throwing, so callers (e.g. form\n * submission) can handle failure without a try/catch and never leak SMTP\n * details to the client.\n */\nexport async function sendEmail({\n from,\n html,\n payload,\n replyTo,\n subject,\n text,\n to,\n}: SendEmailArgs): Promise {\n const smtp = await getSiteIntegrations(payload)\n\n const host = smtp.smtpHost\n const port = smtp.smtpPort ?? 587\n const user = smtp.smtpUser\n const pass = smtp.smtpPassword\n\n if (!host || !user || !pass) {\n return { error: 'SMTP is not configured in Site Integrations.', sent: false }\n }\n\n const fromAddress = from ?? smtp.smtpFromAddress\n if (!fromAddress) {\n return { error: 'No from-address configured.', sent: false }\n }\n\n const fromName = smtp.smtpFromName\n const fromHeader = fromName ? `${fromName} <${fromAddress}>` : fromAddress\n\n const transporter = nodemailer.createTransport({\n auth: { pass, user },\n host,\n port,\n secure: port === 465, // implicit TLS on 465, STARTTLS otherwise\n })\n\n try {\n const info = await transporter.sendMail({\n from: fromHeader,\n html,\n subject,\n to,\n ...(text ? { text } : {}),\n ...(replyTo ? { replyTo } : {}),\n })\n return { messageId: info.messageId, sent: true }\n } catch (err) {\n payload.logger.error(`[ipal] Email send failed: ${(err as Error).message}`)\n return { error: 'Failed to send email.', sent: false }\n }\n}\n"],"names":["nodemailer","getSiteIntegrations","sendEmail","from","html","payload","replyTo","subject","text","to","smtp","host","smtpHost","port","smtpPort","user","smtpUser","pass","smtpPassword","error","sent","fromAddress","smtpFromAddress","fromName","smtpFromName","fromHeader","transporter","createTransport","auth","secure","info","sendMail","messageId","err","logger","message"],"mappings":"AAAA,OAAO,cAAa;AAIpB,OAAOA,gBAAgB,aAAY;AAEnC,SAASC,mBAAmB,QAAQ,sBAAqB;AA4BzD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,UAAU,EAC9BC,IAAI,EACJC,IAAI,EACJC,OAAO,EACPC,OAAO,EACPC,OAAO,EACPC,IAAI,EACJC,EAAE,EACY;IACd,MAAMC,OAAO,MAAMT,oBAAsCI;IAEzD,MAAMM,OAAOD,KAAKE,QAAQ;IAC1B,MAAMC,OAAOH,KAAKI,QAAQ,IAAI;IAC9B,MAAMC,OAAOL,KAAKM,QAAQ;IAC1B,MAAMC,OAAOP,KAAKQ,YAAY;IAE9B,IAAI,CAACP,QAAQ,CAACI,QAAQ,CAACE,MAAM;QAC3B,OAAO;YAAEE,OAAO;YAAgDC,MAAM;QAAM;IAC9E;IAEA,MAAMC,cAAclB,QAAQO,KAAKY,eAAe;IAChD,IAAI,CAACD,aAAa;QAChB,OAAO;YAAEF,OAAO;YAA+BC,MAAM;QAAM;IAC7D;IAEA,MAAMG,WAAWb,KAAKc,YAAY;IAClC,MAAMC,aAAaF,WAAW,GAAGA,SAAS,EAAE,EAAEF,YAAY,CAAC,CAAC,GAAGA;IAE/D,MAAMK,cAAc1B,WAAW2B,eAAe,CAAC;QAC7CC,MAAM;YAAEX;YAAMF;QAAK;QACnBJ;QACAE;QACAgB,QAAQhB,SAAS;IACnB;IAEA,IAAI;QACF,MAAMiB,OAAO,MAAMJ,YAAYK,QAAQ,CAAC;YACtC5B,MAAMsB;YACNrB;YACAG;YACAE;YACA,GAAID,OAAO;gBAAEA;YAAK,IAAI,CAAC,CAAC;YACxB,GAAIF,UAAU;gBAAEA;YAAQ,IAAI,CAAC,CAAC;QAChC;QACA,OAAO;YAAE0B,WAAWF,KAAKE,SAAS;YAAEZ,MAAM;QAAK;IACjD,EAAE,OAAOa,KAAK;QACZ5B,QAAQ6B,MAAM,CAACf,KAAK,CAAC,CAAC,0BAA0B,EAAE,AAACc,IAAcE,OAAO,EAAE;QAC1E,OAAO;YAAEhB,OAAO;YAAyBC,MAAM;QAAM;IACvD;AACF"} \ No newline at end of file +{"version":3,"sources":["sendEmail.js"],"sourcesContent":["import 'server-only'\n\nimport type { BasePayload } from 'payload'\n\nimport nodemailer from 'nodemailer'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\n/** SMTP fields the sender reads from SiteIntegrations. */\ntype SmtpIntegrations = {\n smtpFromAddress?: null | string\n smtpFromName?: null | string\n smtpHost?: null | string\n smtpPassword?: null | string\n smtpPort?: null | number\n smtpUser?: null | string\n}\n\nexport type SendEmailArgs = {\n /** Override the configured from-address for this message. */\n from?: string\n /** HTML body. */\n html: string\n /** Payload instance — used to read SMTP config from SiteIntegrations. */\n payload: BasePayload\n replyTo?: string\n subject: string\n /** Optional plain-text fallback. */\n text?: string\n to: string | string[]\n}\n\nexport type SendEmailResult = { error: string; sent: false } | { messageId: string; sent: true }\n\n/**\n * Sends an email using SMTP settings from the SiteIntegrations global.\n *\n * Reads config at call time (not at boot) so editors can change SMTP in the\n * admin panel without a restart — consistent with the plugin's panel-managed\n * model. `server-only` keeps the SMTP password out of any client bundle.\n *\n * Returns a result object rather than throwing, so callers (e.g. form\n * submission) can handle failure without a try/catch and never leak SMTP\n * details to the client.\n */\nexport async function sendEmail({\n from,\n html,\n payload,\n replyTo,\n subject,\n text,\n to,\n}: SendEmailArgs): Promise {\n const smtp = await getSiteIntegrations(payload)\n\n const host = smtp.smtpHost\n const port = smtp.smtpPort ?? 587\n const user = smtp.smtpUser\n const pass = smtp.smtpPassword\n\n if (!host || !user || !pass) {\n return { error: 'SMTP is not configured in Site Integrations.', sent: false }\n }\n\n const fromAddress = from ?? smtp.smtpFromAddress\n if (!fromAddress) {\n return { error: 'No from-address configured.', sent: false }\n }\n\n const fromName = smtp.smtpFromName\n const fromHeader = fromName ? `${fromName} <${fromAddress}>` : fromAddress\n\n const transporter = nodemailer.createTransport({\n auth: { pass, user },\n host,\n port,\n secure: port === 465, // implicit TLS on 465, STARTTLS otherwise\n })\n\n try {\n const info = await transporter.sendMail({\n from: fromHeader,\n html,\n subject,\n to,\n ...(text ? { text } : {}),\n ...(replyTo ? { replyTo } : {}),\n })\n return { messageId: info.messageId, sent: true }\n } catch (err) {\n payload.logger.error(`[ipal] Email send failed: ${(err as Error).message}`)\n return { error: 'Failed to send email.', sent: false }\n }\n}\n"],"names":["nodemailer","getSiteIntegrations","sendEmail","from","html","payload","replyTo","subject","text","to","smtp","host","smtpHost","port","smtpPort","user","smtpUser","pass","smtpPassword","error","sent","fromAddress","smtpFromAddress","fromName","smtpFromName","fromHeader","transporter","createTransport","auth","secure","info","sendMail","messageId","err","logger","message"],"mappings":"AAAA,OAAO,cAAa;AAIpB,OAAOA,gBAAgB,aAAY;AAEnC,SAASC,mBAAmB,QAAQ,sBAAqB;AA4BzD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,UAAU,EAC9BC,IAAI,EACJC,IAAI,EACJC,OAAO,EACPC,OAAO,EACPC,OAAO,EACPC,IAAI,EACJC,EAAE,EACY;IACd,MAAMC,OAAO,MAAMT,oBAAsCI;IAEzD,MAAMM,OAAOD,KAAKE,QAAQ;IAC1B,MAAMC,OAAOH,KAAKI,QAAQ,IAAI;IAC9B,MAAMC,OAAOL,KAAKM,QAAQ;IAC1B,MAAMC,OAAOP,KAAKQ,YAAY;IAE9B,IAAI,CAACP,QAAQ,CAACI,QAAQ,CAACE,MAAM;QAC3B,OAAO;YAAEE,OAAO;YAAgDC,MAAM;QAAM;IAC9E;IAEA,MAAMC,cAAclB,QAAQO,KAAKY,eAAe;IAChD,IAAI,CAACD,aAAa;QAChB,OAAO;YAAEF,OAAO;YAA+BC,MAAM;QAAM;IAC7D;IAEA,MAAMG,WAAWb,KAAKc,YAAY;IAClC,MAAMC,aAAaF,WAAW,GAAGA,SAAS,EAAE,EAAEF,YAAY,CAAC,CAAC,GAAGA;IAE/D,MAAMK,cAAc1B,WAAW2B,eAAe,CAAC;QAC7CC,MAAM;YAAEX;YAAMF;QAAK;QACnBJ;QACAE;QACAgB,QAAQhB,SAAS;IACnB;IAEA,IAAI;QACF,MAAMiB,OAAO,MAAMJ,YAAYK,QAAQ,CAAC;YACtC5B,MAAMsB;YACNrB;YACAG;YACAE;YACA,GAAID,OAAO;gBAAEA;YAAK,IAAI,CAAC,CAAC;YACxB,GAAIF,UAAU;gBAAEA;YAAQ,IAAI,CAAC,CAAC;QAChC;QACA,OAAO;YAAE0B,WAAWF,KAAKE,SAAS;YAAEZ,MAAM;QAAK;IACjD,EAAE,OAAOa,KAAK;QACZ5B,QAAQ6B,MAAM,CAACf,KAAK,CAAC,CAAC,0BAA0B,EAAGc,IAAcE,OAAO,EAAE;QAC1E,OAAO;YAAEhB,OAAO;YAAyBC,MAAM;QAAM;IACvD;AACF"} \ No newline at end of file diff --git a/dist/modules/forms/formsPluginConfig.js b/dist/modules/forms/formsPluginConfig.js index aaec36a..739fadd 100644 --- a/dist/modules/forms/formsPluginConfig.js +++ b/dist/modules/forms/formsPluginConfig.js @@ -32,6 +32,6 @@ import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'; formSubmissionOverrides: forms.formSubmissionOverrides } : {} }); -} +} //# sourceMappingURL=formsPluginConfig.js.map //# sourceMappingURL=formsPluginConfig.js.map \ No newline at end of file diff --git a/dist/modules/forms/formsPluginConfig.js.map b/dist/modules/forms/formsPluginConfig.js.map index 9fb3ee5..80beeba 100644 --- a/dist/modules/forms/formsPluginConfig.js.map +++ b/dist/modules/forms/formsPluginConfig.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/forms/formsPluginConfig.ts"],"sourcesContent":["import type { Plugin } from 'payload'\n\nimport { formBuilderPlugin } from '@payloadcms/plugin-form-builder'\n\nimport type { FormsOption } from './types.js'\n\n/**\n * Configures @payloadcms/plugin-form-builder from IPAL's FormsOption.\n *\n * Provides the form/form-submissions collections and field types. Email\n * delivery is intentionally NOT handled here — the plugin's own SMTP-from-panel\n * sender (submitForm) does that, so the form-builder's built-in email (which\n * needs a Payload email adapter) is left unused.\n */\nexport function buildFormsPlugin(forms: FormsOption): Plugin {\n return formBuilderPlugin({\n fields: {\n checkbox: true,\n email: true,\n message: true,\n number: true,\n payment: false,\n select: true,\n text: true,\n textarea: true,\n ...forms.fields,\n },\n ...(forms.redirectRelationships ? { redirectRelationships: forms.redirectRelationships } : {}),\n // Client-supplied collection overrides (e.g. a per-form notification\n // address). The plugin provides the hook, not the opinion about which\n // extra fields a form should carry.\n ...(forms.formOverrides ? { formOverrides: forms.formOverrides } : {}),\n ...(forms.formSubmissionOverrides\n ? { formSubmissionOverrides: forms.formSubmissionOverrides }\n : {}),\n })\n}\n"],"names":["formBuilderPlugin","buildFormsPlugin","forms","fields","checkbox","email","message","number","payment","select","text","textarea","redirectRelationships","formOverrides","formSubmissionOverrides"],"mappings":"AAEA,SAASA,iBAAiB,QAAQ,kCAAiC;AAInE;;;;;;;CAOC,GACD,OAAO,SAASC,iBAAiBC,KAAkB;IACjD,OAAOF,kBAAkB;QACvBG,QAAQ;YACNC,UAAU;YACVC,OAAO;YACPC,SAAS;YACTC,QAAQ;YACRC,SAAS;YACTC,QAAQ;YACRC,MAAM;YACNC,UAAU;YACV,GAAGT,MAAMC,MAAM;QACjB;QACA,GAAID,MAAMU,qBAAqB,GAAG;YAAEA,uBAAuBV,MAAMU,qBAAqB;QAAC,IAAI,CAAC,CAAC;QAC7F,qEAAqE;QACrE,sEAAsE;QACtE,oCAAoC;QACpC,GAAIV,MAAMW,aAAa,GAAG;YAAEA,eAAeX,MAAMW,aAAa;QAAC,IAAI,CAAC,CAAC;QACrE,GAAIX,MAAMY,uBAAuB,GAC7B;YAAEA,yBAAyBZ,MAAMY,uBAAuB;QAAC,IACzD,CAAC,CAAC;IACR;AACF"} \ No newline at end of file +{"version":3,"sources":["formsPluginConfig.js"],"sourcesContent":["import type { Plugin } from 'payload'\n\nimport { formBuilderPlugin } from '@payloadcms/plugin-form-builder'\n\nimport type { FormsOption } from './types.js'\n\n/**\n * Configures @payloadcms/plugin-form-builder from IPAL's FormsOption.\n *\n * Provides the form/form-submissions collections and field types. Email\n * delivery is intentionally NOT handled here — the plugin's own SMTP-from-panel\n * sender (submitForm) does that, so the form-builder's built-in email (which\n * needs a Payload email adapter) is left unused.\n */\nexport function buildFormsPlugin(forms: FormsOption): Plugin {\n return formBuilderPlugin({\n fields: {\n checkbox: true,\n email: true,\n message: true,\n number: true,\n payment: false,\n select: true,\n text: true,\n textarea: true,\n ...forms.fields,\n },\n ...(forms.redirectRelationships ? { redirectRelationships: forms.redirectRelationships } : {}),\n // Client-supplied collection overrides (e.g. a per-form notification\n // address). The plugin provides the hook, not the opinion about which\n // extra fields a form should carry.\n ...(forms.formOverrides ? { formOverrides: forms.formOverrides } : {}),\n ...(forms.formSubmissionOverrides\n ? { formSubmissionOverrides: forms.formSubmissionOverrides }\n : {}),\n })\n}\n"],"names":["formBuilderPlugin","buildFormsPlugin","forms","fields","checkbox","email","message","number","payment","select","text","textarea","redirectRelationships","formOverrides","formSubmissionOverrides"],"mappings":"AAEA,SAASA,iBAAiB,QAAQ,kCAAiC;AAInE;;;;;;;CAOC,GACD,OAAO,SAASC,iBAAiBC,KAAkB;IACjD,OAAOF,kBAAkB;QACvBG,QAAQ;YACNC,UAAU;YACVC,OAAO;YACPC,SAAS;YACTC,QAAQ;YACRC,SAAS;YACTC,QAAQ;YACRC,MAAM;YACNC,UAAU;YACV,GAAGT,MAAMC,MAAM;QACjB;QACA,GAAID,MAAMU,qBAAqB,GAAG;YAAEA,uBAAuBV,MAAMU,qBAAqB;QAAC,IAAI,CAAC,CAAC;QAC7F,qEAAqE;QACrE,sEAAsE;QACtE,oCAAoC;QACpC,GAAIV,MAAMW,aAAa,GAAG;YAAEA,eAAeX,MAAMW,aAAa;QAAC,IAAI,CAAC,CAAC;QACrE,GAAIX,MAAMY,uBAAuB,GAC7B;YAAEA,yBAAyBZ,MAAMY,uBAAuB;QAAC,IACzD,CAAC,CAAC;IACR;AACF"} \ No newline at end of file diff --git a/dist/modules/forms/index.js b/dist/modules/forms/index.js index 384833d..e4df150 100644 --- a/dist/modules/forms/index.js +++ b/dist/modules/forms/index.js @@ -3,6 +3,6 @@ export { buildFormsPlugin } from './formsPluginConfig.js'; export { checkRateLimit } from './rateLimit.js'; export { submitForm } from './submitForm.js'; -export { validateSubmission } from './validateSubmission.js'; +export { validateSubmission } from './validateSubmission.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/forms/index.js.map b/dist/modules/forms/index.js.map index 00c6df4..2ab750e 100644 --- a/dist/modules/forms/index.js.map +++ b/dist/modules/forms/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/forms/index.ts"],"sourcesContent":["// buildFormsPlugin is config-time (safe anywhere). submitForm is server-only\n// (Turnstile secret) — never import it from a client component.\nexport { buildFormsPlugin } from './formsPluginConfig.js'\nexport { checkRateLimit } from './rateLimit.js'\nexport type { RateLimitArgs } from './rateLimit.js'\nexport { submitForm } from './submitForm.js'\nexport type { SubmitFormArgs, SubmitFormResult } from './submitForm.js'\nexport type { FormsCollectionOverrides, FormsFieldsOverride, FormsOption } from './types.js'\nexport { validateSubmission } from './validateSubmission.js'\nexport type { FormValidationResult } from './validateSubmission.js'\n"],"names":["buildFormsPlugin","checkRateLimit","submitForm","validateSubmission"],"mappings":"AAAA,6EAA6E;AAC7E,gEAAgE;AAChE,SAASA,gBAAgB,QAAQ,yBAAwB;AACzD,SAASC,cAAc,QAAQ,iBAAgB;AAE/C,SAASC,UAAU,QAAQ,kBAAiB;AAG5C,SAASC,kBAAkB,QAAQ,0BAAyB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["// buildFormsPlugin is config-time (safe anywhere). submitForm is server-only\n// (Turnstile secret) — never import it from a client component.\nexport { buildFormsPlugin } from './formsPluginConfig.js'\nexport { checkRateLimit } from './rateLimit.js'\nexport type { RateLimitArgs } from './rateLimit.js'\nexport { submitForm } from './submitForm.js'\nexport type { SubmitFormArgs, SubmitFormResult } from './submitForm.js'\nexport type { FormsCollectionOverrides, FormsFieldsOverride, FormsOption } from './types.js'\nexport { validateSubmission } from './validateSubmission.js'\nexport type { FormValidationResult } from './validateSubmission.js'\n"],"names":["buildFormsPlugin","checkRateLimit","submitForm","validateSubmission"],"mappings":"AAAA,6EAA6E;AAC7E,gEAAgE;AAChE,SAASA,gBAAgB,QAAQ,yBAAwB;AACzD,SAASC,cAAc,QAAQ,iBAAgB;AAE/C,SAASC,UAAU,QAAQ,kBAAiB;AAG5C,SAASC,kBAAkB,QAAQ,0BAAyB"} \ No newline at end of file diff --git a/dist/modules/forms/rateLimit.js b/dist/modules/forms/rateLimit.js index 1b70b31..51a8c06 100644 --- a/dist/modules/forms/rateLimit.js +++ b/dist/modules/forms/rateLimit.js @@ -46,6 +46,6 @@ function sweep(now) { } bucket.count += 1; return true; -} +} //# sourceMappingURL=rateLimit.js.map //# sourceMappingURL=rateLimit.js.map \ No newline at end of file diff --git a/dist/modules/forms/rateLimit.js.map b/dist/modules/forms/rateLimit.js.map index 8ba4636..f3b811f 100644 --- a/dist/modules/forms/rateLimit.js.map +++ b/dist/modules/forms/rateLimit.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/forms/rateLimit.ts"],"sourcesContent":["type Bucket = { count: number; resetAt: number }\n\n/**\n * Per-IP sliding window, in memory.\n *\n * Deliberately simple: no Redis, no dependency. The trade-off is that the\n * counter lives in one process — with several instances behind a load balancer\n * each keeps its own, so the effective limit is per-instance, not global. For a\n * contact form that's fine (it raises the cost of flooding without pretending\n * to be airtight); a high-security form should put a real limiter in front.\n *\n * State is module-level, so it survives between requests but resets on redeploy\n * — acceptable for abuse throttling.\n */\nconst buckets = new Map()\n\n/** Sweep expired buckets occasionally so the map doesn't grow unbounded. */\nlet lastSweep = Date.now()\nconst SWEEP_INTERVAL = 60_000\n\nfunction sweep(now: number) {\n if (now - lastSweep < SWEEP_INTERVAL) {return}\n lastSweep = now\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) {buckets.delete(key)}\n }\n}\n\nexport type RateLimitArgs = {\n /** Identifier to limit on — typically the client IP. */\n key: string\n /** Max submissions allowed per window. Defaults to 5. */\n max?: number\n /** Window length in ms. Defaults to 60_000 (one minute). */\n windowMs?: number\n}\n\n/**\n * Returns true when the request is within the limit, false when it should be\n * rejected. A missing key (no IP) is allowed through — better to accept a\n * submission than to block everyone behind a proxy that strips the header.\n */\nexport function checkRateLimit({ key, max = 5, windowMs = 60_000 }: RateLimitArgs): boolean {\n if (!key) {return true}\n\n const now = Date.now()\n sweep(now)\n\n const bucket = buckets.get(key)\n\n if (!bucket || bucket.resetAt <= now) {\n buckets.set(key, { count: 1, resetAt: now + windowMs })\n return true\n }\n\n if (bucket.count >= max) {\n return false\n }\n\n bucket.count += 1\n return true\n}\n"],"names":["buckets","Map","lastSweep","Date","now","SWEEP_INTERVAL","sweep","key","bucket","resetAt","delete","checkRateLimit","max","windowMs","get","set","count"],"mappings":"AAEA;;;;;;;;;;;CAWC,GACD,MAAMA,UAAU,IAAIC;AAEpB,0EAA0E,GAC1E,IAAIC,YAAYC,KAAKC,GAAG;AACxB,MAAMC,iBAAiB;AAEvB,SAASC,MAAMF,GAAW;IACxB,IAAIA,MAAMF,YAAYG,gBAAgB;QAAC;IAAM;IAC7CH,YAAYE;IACZ,KAAK,MAAM,CAACG,KAAKC,OAAO,IAAIR,QAAS;QACnC,IAAIQ,OAAOC,OAAO,IAAIL,KAAK;YAACJ,QAAQU,MAAM,CAACH;QAAI;IACjD;AACF;AAWA;;;;CAIC,GACD,OAAO,SAASI,eAAe,EAAEJ,GAAG,EAAEK,MAAM,CAAC,EAAEC,WAAW,MAAM,EAAiB;IAC/E,IAAI,CAACN,KAAK;QAAC,OAAO;IAAI;IAEtB,MAAMH,MAAMD,KAAKC,GAAG;IACpBE,MAAMF;IAEN,MAAMI,SAASR,QAAQc,GAAG,CAACP;IAE3B,IAAI,CAACC,UAAUA,OAAOC,OAAO,IAAIL,KAAK;QACpCJ,QAAQe,GAAG,CAACR,KAAK;YAAES,OAAO;YAAGP,SAASL,MAAMS;QAAS;QACrD,OAAO;IACT;IAEA,IAAIL,OAAOQ,KAAK,IAAIJ,KAAK;QACvB,OAAO;IACT;IAEAJ,OAAOQ,KAAK,IAAI;IAChB,OAAO;AACT"} \ No newline at end of file +{"version":3,"sources":["rateLimit.js"],"sourcesContent":["type Bucket = { count: number; resetAt: number }\n\n/**\n * Per-IP sliding window, in memory.\n *\n * Deliberately simple: no Redis, no dependency. The trade-off is that the\n * counter lives in one process — with several instances behind a load balancer\n * each keeps its own, so the effective limit is per-instance, not global. For a\n * contact form that's fine (it raises the cost of flooding without pretending\n * to be airtight); a high-security form should put a real limiter in front.\n *\n * State is module-level, so it survives between requests but resets on redeploy\n * — acceptable for abuse throttling.\n */\nconst buckets = new Map()\n\n/** Sweep expired buckets occasionally so the map doesn't grow unbounded. */\nlet lastSweep = Date.now()\nconst SWEEP_INTERVAL = 60_000\n\nfunction sweep(now: number) {\n if (now - lastSweep < SWEEP_INTERVAL) {return}\n lastSweep = now\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) {buckets.delete(key)}\n }\n}\n\nexport type RateLimitArgs = {\n /** Identifier to limit on — typically the client IP. */\n key: string\n /** Max submissions allowed per window. Defaults to 5. */\n max?: number\n /** Window length in ms. Defaults to 60_000 (one minute). */\n windowMs?: number\n}\n\n/**\n * Returns true when the request is within the limit, false when it should be\n * rejected. A missing key (no IP) is allowed through — better to accept a\n * submission than to block everyone behind a proxy that strips the header.\n */\nexport function checkRateLimit({ key, max = 5, windowMs = 60_000 }: RateLimitArgs): boolean {\n if (!key) {return true}\n\n const now = Date.now()\n sweep(now)\n\n const bucket = buckets.get(key)\n\n if (!bucket || bucket.resetAt <= now) {\n buckets.set(key, { count: 1, resetAt: now + windowMs })\n return true\n }\n\n if (bucket.count >= max) {\n return false\n }\n\n bucket.count += 1\n return true\n}\n"],"names":["buckets","Map","lastSweep","Date","now","SWEEP_INTERVAL","sweep","key","bucket","resetAt","delete","checkRateLimit","max","windowMs","get","set","count"],"mappings":"AAEA;;;;;;;;;;;CAWC,GACD,MAAMA,UAAU,IAAIC;AAEpB,0EAA0E,GAC1E,IAAIC,YAAYC,KAAKC,GAAG;AACxB,MAAMC,iBAAiB;AAEvB,SAASC,MAAMF,GAAW;IACxB,IAAIA,MAAMF,YAAYG,gBAAgB;QAAC;IAAM;IAC7CH,YAAYE;IACZ,KAAK,MAAM,CAACG,KAAKC,OAAO,IAAIR,QAAS;QACnC,IAAIQ,OAAOC,OAAO,IAAIL,KAAK;YAACJ,QAAQU,MAAM,CAACH;QAAI;IACjD;AACF;AAWA;;;;CAIC,GACD,OAAO,SAASI,eAAe,EAAEJ,GAAG,EAAEK,MAAM,CAAC,EAAEC,WAAW,MAAM,EAAiB;IAC/E,IAAI,CAACN,KAAK;QAAC,OAAO;IAAI;IAEtB,MAAMH,MAAMD,KAAKC,GAAG;IACpBE,MAAMF;IAEN,MAAMI,SAASR,QAAQc,GAAG,CAACP;IAE3B,IAAI,CAACC,UAAUA,OAAOC,OAAO,IAAIL,KAAK;QACpCJ,QAAQe,GAAG,CAACR,KAAK;YAAES,OAAO;YAAGP,SAASL,MAAMS;QAAS;QACrD,OAAO;IACT;IAEA,IAAIL,OAAOQ,KAAK,IAAIJ,KAAK;QACvB,OAAO;IACT;IAEAJ,OAAOQ,KAAK,IAAI;IAChB,OAAO;AACT"} \ No newline at end of file diff --git a/dist/modules/forms/submitForm.js b/dist/modules/forms/submitForm.js index cf5217d..011ba96 100644 --- a/dist/modules/forms/submitForm.js +++ b/dist/modules/forms/submitForm.js @@ -86,6 +86,6 @@ import { validateSubmission } from './validateSubmission.js'; success: false }; } -} +} //# sourceMappingURL=submitForm.js.map //# sourceMappingURL=submitForm.js.map \ No newline at end of file diff --git a/dist/modules/forms/submitForm.js.map b/dist/modules/forms/submitForm.js.map index b289675..e7dd53f 100644 --- a/dist/modules/forms/submitForm.js.map +++ b/dist/modules/forms/submitForm.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/forms/submitForm.ts"],"sourcesContent":["import 'server-only'\n\nimport type { BasePayload } from 'payload'\n\nimport { verifyTurnstile } from '../turnstile/index.js'\nimport { checkRateLimit } from './rateLimit.js'\nimport { validateSubmission } from './validateSubmission.js'\n\nexport type SubmitFormArgs = {\n /** Submitted field data — shape matches the form's fields. */\n data: Record\n /** Form-builder form ID this submission belongs to. */\n formId: string\n /** Client IP — used for Turnstile and rate limiting. */\n ip?: string\n /**\n * Rate limit: max submissions per IP per minute. Defaults to 5.\n * Set to 0 to disable (e.g. when a real limiter sits in front).\n */\n maxPerMinute?: number\n payload: BasePayload\n /** Turnstile token; when present it is verified, when absent it is skipped. */\n turnstileToken?: string\n}\n\n/**\n * Why a submission failed — a code, never a user-facing string.\n *\n * The plugin knows what went wrong; it deliberately doesn't decide how to say\n * it. The frontend maps these to its own copy, in its own language, and renders\n * whatever component fits — a field error, a toast, a full message. The plugin\n * has no business choosing the wording or the locale.\n *\n * - `rate_limited` — too many submissions from this IP\n * - `turnstile` — bot check failed\n * - `validation` — a field is missing/too long, or unknown keys were sent;\n * `field` and `kind` narrow it down when a specific field is\n * at fault (absent for whole-payload problems like unknown keys)\n * - `not_found` — no form with this id\n * - `error` — persistence failed unexpectedly\n */\nexport type SubmitFailure =\n | {\n /** The offending field's name, when one field is at fault. */\n field?: string\n /** What was wrong with it. */\n kind?: 'required' | 'too_long' | 'unknown_fields'\n reason: 'validation'\n success: false\n }\n | { reason: 'error'; success: false }\n | { reason: 'not_found'; success: false }\n | { reason: 'rate_limited'; success: false }\n | { reason: 'turnstile'; success: false }\n\nexport type SubmitFormResult = { submissionId: number | string; success: true } | SubmitFailure\n\n/**\n * Handles a form submission end to end: rate limit, verify Turnstile, validate\n * against the form's own schema, then store.\n *\n * The order is cost-ascending on purpose — the cheapest checks reject first, so\n * a flood never reaches Turnstile's network call or the database.\n *\n * Emails aren't sent here. The form-builder sends whatever an editor configured\n * under the form's \"Emails\" tab (form-submissions hook → payload.sendEmail),\n * which goes out over panelSmtpAdapter. Storing the submission is enough.\n *\n * server-only: touches the Turnstile secret.\n */\nexport async function submitForm({\n data,\n formId,\n ip,\n maxPerMinute = 5,\n payload,\n turnstileToken,\n}: SubmitFormArgs): Promise {\n // 1. Rate limit — cheapest gate, drops a flood before any real work.\n if (maxPerMinute > 0 && ip) {\n if (!checkRateLimit({ key: ip, max: maxPerMinute })) {\n return { reason: 'rate_limited', success: false }\n }\n }\n\n // 2. Turnstile — verify when a token is supplied; reject on failure.\n if (turnstileToken !== undefined) {\n const ok = await verifyTurnstile({ ip, payload, token: turnstileToken })\n if (!ok) {\n return { reason: 'turnstile', success: false }\n }\n }\n\n // 3. Validate against the form's schema. A public endpoint can't trust the\n // shape of `data` — drop unknown keys, enforce required, cap length.\n const validation = await validateSubmission(payload, formId, data)\n if (!validation.ok) {\n if (validation.reason === 'not_found') {\n return { reason: 'not_found', success: false }\n }\n return {\n reason: 'validation',\n success: false,\n ...(validation.field ? { field: validation.field } : {}),\n ...(validation.kind ? { kind: validation.kind } : {}),\n }\n }\n\n // 4. Persist (form-builder shape: submissionData array). Triggers the email\n // hook. Only validated, known fields are stored.\n try {\n const submission = await payload.create({\n collection: 'form-submissions',\n data: {\n form: formId,\n submissionData: Object.entries(validation.cleaned).map(([field, value]) => ({\n field,\n value: value == null ? '' : String(value),\n })),\n } as never,\n })\n return { submissionId: submission.id, success: true }\n } catch (err) {\n payload.logger.error(`[ipal] Form submission failed: ${(err as Error).message}`)\n return { reason: 'error', success: false }\n }\n}\n"],"names":["verifyTurnstile","checkRateLimit","validateSubmission","submitForm","data","formId","ip","maxPerMinute","payload","turnstileToken","key","max","reason","success","undefined","ok","token","validation","field","kind","submission","create","collection","form","submissionData","Object","entries","cleaned","map","value","String","submissionId","id","err","logger","error","message"],"mappings":"AAAA,OAAO,cAAa;AAIpB,SAASA,eAAe,QAAQ,wBAAuB;AACvD,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,SAASC,kBAAkB,QAAQ,0BAAyB;AAmD5D;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeC,WAAW,EAC/BC,IAAI,EACJC,MAAM,EACNC,EAAE,EACFC,eAAe,CAAC,EAChBC,OAAO,EACPC,cAAc,EACC;IACf,qEAAqE;IACrE,IAAIF,eAAe,KAAKD,IAAI;QAC1B,IAAI,CAACL,eAAe;YAAES,KAAKJ;YAAIK,KAAKJ;QAAa,IAAI;YACnD,OAAO;gBAAEK,QAAQ;gBAAgBC,SAAS;YAAM;QAClD;IACF;IAEA,qEAAqE;IACrE,IAAIJ,mBAAmBK,WAAW;QAChC,MAAMC,KAAK,MAAMf,gBAAgB;YAAEM;YAAIE;YAASQ,OAAOP;QAAe;QACtE,IAAI,CAACM,IAAI;YACP,OAAO;gBAAEH,QAAQ;gBAAaC,SAAS;YAAM;QAC/C;IACF;IAEA,2EAA2E;IAC3E,wEAAwE;IACxE,MAAMI,aAAa,MAAMf,mBAAmBM,SAASH,QAAQD;IAC7D,IAAI,CAACa,WAAWF,EAAE,EAAE;QAClB,IAAIE,WAAWL,MAAM,KAAK,aAAa;YACrC,OAAO;gBAAEA,QAAQ;gBAAaC,SAAS;YAAM;QAC/C;QACA,OAAO;YACLD,QAAQ;YACRC,SAAS;YACT,GAAII,WAAWC,KAAK,GAAG;gBAAEA,OAAOD,WAAWC,KAAK;YAAC,IAAI,CAAC,CAAC;YACvD,GAAID,WAAWE,IAAI,GAAG;gBAAEA,MAAMF,WAAWE,IAAI;YAAC,IAAI,CAAC,CAAC;QACtD;IACF;IAEA,4EAA4E;IAC5E,oDAAoD;IACpD,IAAI;QACF,MAAMC,aAAa,MAAMZ,QAAQa,MAAM,CAAC;YACtCC,YAAY;YACZlB,MAAM;gBACJmB,MAAMlB;gBACNmB,gBAAgBC,OAAOC,OAAO,CAACT,WAAWU,OAAO,EAAEC,GAAG,CAAC,CAAC,CAACV,OAAOW,MAAM,GAAM,CAAA;wBAC1EX;wBACAW,OAAOA,SAAS,OAAO,KAAKC,OAAOD;oBACrC,CAAA;YACF;QACF;QACA,OAAO;YAAEE,cAAcX,WAAWY,EAAE;YAAEnB,SAAS;QAAK;IACtD,EAAE,OAAOoB,KAAK;QACZzB,QAAQ0B,MAAM,CAACC,KAAK,CAAC,CAAC,+BAA+B,EAAE,AAACF,IAAcG,OAAO,EAAE;QAC/E,OAAO;YAAExB,QAAQ;YAASC,SAAS;QAAM;IAC3C;AACF"} \ No newline at end of file +{"version":3,"sources":["submitForm.js"],"sourcesContent":["import 'server-only'\n\nimport type { BasePayload } from 'payload'\n\nimport { verifyTurnstile } from '../turnstile/index.js'\nimport { checkRateLimit } from './rateLimit.js'\nimport { validateSubmission } from './validateSubmission.js'\n\nexport type SubmitFormArgs = {\n /** Submitted field data — shape matches the form's fields. */\n data: Record\n /** Form-builder form ID this submission belongs to. */\n formId: string\n /** Client IP — used for Turnstile and rate limiting. */\n ip?: string\n /**\n * Rate limit: max submissions per IP per minute. Defaults to 5.\n * Set to 0 to disable (e.g. when a real limiter sits in front).\n */\n maxPerMinute?: number\n payload: BasePayload\n /** Turnstile token; when present it is verified, when absent it is skipped. */\n turnstileToken?: string\n}\n\n/**\n * Why a submission failed — a code, never a user-facing string.\n *\n * The plugin knows what went wrong; it deliberately doesn't decide how to say\n * it. The frontend maps these to its own copy, in its own language, and renders\n * whatever component fits — a field error, a toast, a full message. The plugin\n * has no business choosing the wording or the locale.\n *\n * - `rate_limited` — too many submissions from this IP\n * - `turnstile` — bot check failed\n * - `validation` — a field is missing/too long, or unknown keys were sent;\n * `field` and `kind` narrow it down when a specific field is\n * at fault (absent for whole-payload problems like unknown keys)\n * - `not_found` — no form with this id\n * - `error` — persistence failed unexpectedly\n */\nexport type SubmitFailure =\n | {\n /** The offending field's name, when one field is at fault. */\n field?: string\n /** What was wrong with it. */\n kind?: 'required' | 'too_long' | 'unknown_fields'\n reason: 'validation'\n success: false\n }\n | { reason: 'error'; success: false }\n | { reason: 'not_found'; success: false }\n | { reason: 'rate_limited'; success: false }\n | { reason: 'turnstile'; success: false }\n\nexport type SubmitFormResult = { submissionId: number | string; success: true } | SubmitFailure\n\n/**\n * Handles a form submission end to end: rate limit, verify Turnstile, validate\n * against the form's own schema, then store.\n *\n * The order is cost-ascending on purpose — the cheapest checks reject first, so\n * a flood never reaches Turnstile's network call or the database.\n *\n * Emails aren't sent here. The form-builder sends whatever an editor configured\n * under the form's \"Emails\" tab (form-submissions hook → payload.sendEmail),\n * which goes out over panelSmtpAdapter. Storing the submission is enough.\n *\n * server-only: touches the Turnstile secret.\n */\nexport async function submitForm({\n data,\n formId,\n ip,\n maxPerMinute = 5,\n payload,\n turnstileToken,\n}: SubmitFormArgs): Promise {\n // 1. Rate limit — cheapest gate, drops a flood before any real work.\n if (maxPerMinute > 0 && ip) {\n if (!checkRateLimit({ key: ip, max: maxPerMinute })) {\n return { reason: 'rate_limited', success: false }\n }\n }\n\n // 2. Turnstile — verify when a token is supplied; reject on failure.\n if (turnstileToken !== undefined) {\n const ok = await verifyTurnstile({ ip, payload, token: turnstileToken })\n if (!ok) {\n return { reason: 'turnstile', success: false }\n }\n }\n\n // 3. Validate against the form's schema. A public endpoint can't trust the\n // shape of `data` — drop unknown keys, enforce required, cap length.\n const validation = await validateSubmission(payload, formId, data)\n if (!validation.ok) {\n if (validation.reason === 'not_found') {\n return { reason: 'not_found', success: false }\n }\n return {\n reason: 'validation',\n success: false,\n ...(validation.field ? { field: validation.field } : {}),\n ...(validation.kind ? { kind: validation.kind } : {}),\n }\n }\n\n // 4. Persist (form-builder shape: submissionData array). Triggers the email\n // hook. Only validated, known fields are stored.\n try {\n const submission = await payload.create({\n collection: 'form-submissions',\n data: {\n form: formId,\n submissionData: Object.entries(validation.cleaned).map(([field, value]) => ({\n field,\n value: value == null ? '' : String(value),\n })),\n } as never,\n })\n return { submissionId: submission.id, success: true }\n } catch (err) {\n payload.logger.error(`[ipal] Form submission failed: ${(err as Error).message}`)\n return { reason: 'error', success: false }\n }\n}\n"],"names":["verifyTurnstile","checkRateLimit","validateSubmission","submitForm","data","formId","ip","maxPerMinute","payload","turnstileToken","key","max","reason","success","undefined","ok","token","validation","field","kind","submission","create","collection","form","submissionData","Object","entries","cleaned","map","value","String","submissionId","id","err","logger","error","message"],"mappings":"AAAA,OAAO,cAAa;AAIpB,SAASA,eAAe,QAAQ,wBAAuB;AACvD,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,SAASC,kBAAkB,QAAQ,0BAAyB;AAmD5D;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeC,WAAW,EAC/BC,IAAI,EACJC,MAAM,EACNC,EAAE,EACFC,eAAe,CAAC,EAChBC,OAAO,EACPC,cAAc,EACC;IACf,qEAAqE;IACrE,IAAIF,eAAe,KAAKD,IAAI;QAC1B,IAAI,CAACL,eAAe;YAAES,KAAKJ;YAAIK,KAAKJ;QAAa,IAAI;YACnD,OAAO;gBAAEK,QAAQ;gBAAgBC,SAAS;YAAM;QAClD;IACF;IAEA,qEAAqE;IACrE,IAAIJ,mBAAmBK,WAAW;QAChC,MAAMC,KAAK,MAAMf,gBAAgB;YAAEM;YAAIE;YAASQ,OAAOP;QAAe;QACtE,IAAI,CAACM,IAAI;YACP,OAAO;gBAAEH,QAAQ;gBAAaC,SAAS;YAAM;QAC/C;IACF;IAEA,2EAA2E;IAC3E,wEAAwE;IACxE,MAAMI,aAAa,MAAMf,mBAAmBM,SAASH,QAAQD;IAC7D,IAAI,CAACa,WAAWF,EAAE,EAAE;QAClB,IAAIE,WAAWL,MAAM,KAAK,aAAa;YACrC,OAAO;gBAAEA,QAAQ;gBAAaC,SAAS;YAAM;QAC/C;QACA,OAAO;YACLD,QAAQ;YACRC,SAAS;YACT,GAAII,WAAWC,KAAK,GAAG;gBAAEA,OAAOD,WAAWC,KAAK;YAAC,IAAI,CAAC,CAAC;YACvD,GAAID,WAAWE,IAAI,GAAG;gBAAEA,MAAMF,WAAWE,IAAI;YAAC,IAAI,CAAC,CAAC;QACtD;IACF;IAEA,4EAA4E;IAC5E,oDAAoD;IACpD,IAAI;QACF,MAAMC,aAAa,MAAMZ,QAAQa,MAAM,CAAC;YACtCC,YAAY;YACZlB,MAAM;gBACJmB,MAAMlB;gBACNmB,gBAAgBC,OAAOC,OAAO,CAACT,WAAWU,OAAO,EAAEC,GAAG,CAAC,CAAC,CAACV,OAAOW,MAAM,GAAM,CAAA;wBAC1EX;wBACAW,OAAOA,SAAS,OAAO,KAAKC,OAAOD;qBACrC,CAAA;YACF;QACF;QACA,OAAO;YAAEE,cAAcX,WAAWY,EAAE;YAAEnB,SAAS;QAAK;IACtD,EAAE,OAAOoB,KAAK;QACZzB,QAAQ0B,MAAM,CAACC,KAAK,CAAC,CAAC,+BAA+B,EAAGF,IAAcG,OAAO,EAAE;QAC/E,OAAO;YAAExB,QAAQ;YAASC,SAAS;QAAM;IAC3C;AACF"} \ No newline at end of file diff --git a/dist/modules/forms/types.js b/dist/modules/forms/types.js index 6f3a4f2..f99c2e3 100644 --- a/dist/modules/forms/types.js +++ b/dist/modules/forms/types.js @@ -1,6 +1,6 @@ /** * Forms configuration — mirrors the fields a client enables in the * form-builder plugin. Kept minimal; the plugin passes these through. - */ export { }; + */ export { }; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/forms/types.js.map b/dist/modules/forms/types.js.map index c58be3d..4dff5a2 100644 --- a/dist/modules/forms/types.js.map +++ b/dist/modules/forms/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/forms/types.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\n/**\n * Receives the collection's default fields and returns the final list — add,\n * remove, or reorder. Same shape the form-builder uses.\n */\nexport type FormsFieldsOverride = (args: { defaultFields: Field[] }) => Field[]\n\n/**\n * Overrides for a forms-related collection: replace the fields and/or any\n * other collection setting (admin, access, hooks…).\n */\nexport type FormsCollectionOverrides = {\n fields?: FormsFieldsOverride\n} & Partial>\n\n/**\n * Forms configuration — mirrors the fields a client enables in the\n * form-builder plugin. Kept minimal; the plugin passes these through.\n */\nexport type FormsOption = {\n /** Field types available in the form builder. Sensible defaults applied. */\n fields?: {\n checkbox?: boolean\n email?: boolean\n message?: boolean\n number?: boolean\n payment?: boolean\n select?: boolean\n text?: boolean\n textarea?: boolean\n }\n /**\n * Override the forms collection. The plugin stays opinion-free about what a\n * form needs beyond its fields — a client that wants, say, a per-form\n * notification address adds it here:\n *\n * formOverrides: {\n * fields: ({ defaultFields }) => [\n * ...defaultFields,\n * { name: 'notificationEmail', type: 'email' },\n * ],\n * }\n */\n formOverrides?: FormsCollectionOverrides\n /** Override the form-submissions collection (same shape). */\n formSubmissionOverrides?: FormsCollectionOverrides\n /** Collections a form can redirect to (e.g. ['pages']). */\n redirectRelationships?: string[]\n}\n"],"names":[],"mappings":"AAgBA;;;CAGC,GACD,WA6BC"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\n/**\n * Receives the collection's default fields and returns the final list — add,\n * remove, or reorder. Same shape the form-builder uses.\n */\nexport type FormsFieldsOverride = (args: { defaultFields: Field[] }) => Field[]\n\n/**\n * Overrides for a forms-related collection: replace the fields and/or any\n * other collection setting (admin, access, hooks…).\n */\nexport type FormsCollectionOverrides = {\n fields?: FormsFieldsOverride\n} & Partial>\n\n/**\n * Forms configuration — mirrors the fields a client enables in the\n * form-builder plugin. Kept minimal; the plugin passes these through.\n */\nexport type FormsOption = {\n /** Field types available in the form builder. Sensible defaults applied. */\n fields?: {\n checkbox?: boolean\n email?: boolean\n message?: boolean\n number?: boolean\n payment?: boolean\n select?: boolean\n text?: boolean\n textarea?: boolean\n }\n /**\n * Override the forms collection. The plugin stays opinion-free about what a\n * form needs beyond its fields — a client that wants, say, a per-form\n * notification address adds it here:\n *\n * formOverrides: {\n * fields: ({ defaultFields }) => [\n * ...defaultFields,\n * { name: 'notificationEmail', type: 'email' },\n * ],\n * }\n */\n formOverrides?: FormsCollectionOverrides\n /** Override the form-submissions collection (same shape). */\n formSubmissionOverrides?: FormsCollectionOverrides\n /** Collections a form can redirect to (e.g. ['pages']). */\n redirectRelationships?: string[]\n}\n"],"names":[],"mappings":"AAgBA;;;CAGC,GACD,WA6BC"} \ No newline at end of file diff --git a/dist/modules/forms/validateSubmission.js b/dist/modules/forms/validateSubmission.js index 97aae90..fcadc91 100644 --- a/dist/modules/forms/validateSubmission.js +++ b/dist/modules/forms/validateSubmission.js @@ -74,6 +74,6 @@ form, ok: true }; -} +} //# sourceMappingURL=validateSubmission.js.map //# sourceMappingURL=validateSubmission.js.map \ No newline at end of file diff --git a/dist/modules/forms/validateSubmission.js.map b/dist/modules/forms/validateSubmission.js.map index 7717af9..2df9ab1 100644 --- a/dist/modules/forms/validateSubmission.js.map +++ b/dist/modules/forms/validateSubmission.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/forms/validateSubmission.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\n/** A form-builder field, trimmed to what validation needs. */\ntype FormField = {\n blockType?: string\n label?: string\n name?: string\n required?: boolean | null\n}\n\ntype FormDoc = {\n fields?: FormField[]\n id: number | string\n /** Per-form notification address, when the client added the field. */\n notificationEmail?: string\n title?: string\n}\n\n/**\n * Validation outcome — codes, not user-facing strings. The frontend turns these\n * into its own copy (see SubmitFailure in submitForm).\n */\nexport type FormValidationResult =\n | {\n /** Offending field, when a single field is at fault. */\n field?: string\n kind: 'required' | 'too_long' | 'unknown_fields'\n ok: false\n reason: 'invalid'\n }\n | { cleaned: Record; form: FormDoc; ok: true }\n | { ok: false; reason: 'not_found' }\n\n/** Field block types that don't carry a submittable value. */\nconst NON_DATA_BLOCKS = new Set(['message'])\n\n/** Hard ceiling on a single field's length, independent of the form config. */\nconst MAX_FIELD_LENGTH = 5000\n\n/**\n * Checks submitted data against the form's own definition, rather than trusting\n * whatever arrived.\n *\n * The server action is a public endpoint: a caller can skip the rendered form\n * and post arbitrary keys. Without this, unknown fields would be stored,\n * required fields could be missing, and an oversized value could sail through.\n * So we load the form, keep only keys that are real fields, reject when a\n * required one is blank, and cap length.\n *\n * Returns the loaded form on success so the caller doesn't fetch it twice, and\n * a code + offending field on failure so the frontend can point at it.\n */\nexport async function validateSubmission(\n payload: BasePayload,\n formId: string,\n data: Record,\n): Promise {\n let form: FormDoc\n try {\n form = (await payload.findByID({\n id: formId,\n collection: 'forms',\n depth: 0,\n })) as FormDoc\n } catch {\n return { ok: false, reason: 'not_found' }\n }\n\n const fields = (form.fields ?? []).filter(\n (f): f is { name: string } & FormField =>\n typeof f.name === 'string' && !NON_DATA_BLOCKS.has(f.blockType ?? ''),\n )\n const known = new Map(fields.map((f) => [f.name, f]))\n\n const cleaned: Record = {}\n\n for (const field of fields) {\n const value = data[field.name]\n const isBlank =\n value == null || (typeof value === 'string' && value.trim() === '') || value === false\n\n if (field.required && isBlank) {\n return { field: field.name, kind: 'required', ok: false, reason: 'invalid' }\n }\n\n if (typeof value === 'string' && value.length > MAX_FIELD_LENGTH) {\n return { field: field.name, kind: 'too_long', ok: false, reason: 'invalid' }\n }\n\n // Only carry through keys that belong to the form — unknown keys from a\n // hand-crafted request are dropped, not stored.\n if (value !== undefined) {\n cleaned[field.name] = value\n }\n }\n\n // Reject outright if the payload carried keys the form doesn't define — a\n // sign the request wasn't produced by the rendered form.\n const unknownKeys = Object.keys(data).filter((k) => !known.has(k))\n if (unknownKeys.length > 0) {\n return { kind: 'unknown_fields', ok: false, reason: 'invalid' }\n }\n\n return { cleaned, form, ok: true }\n}\n"],"names":["NON_DATA_BLOCKS","Set","MAX_FIELD_LENGTH","validateSubmission","payload","formId","data","form","findByID","id","collection","depth","ok","reason","fields","filter","f","name","has","blockType","known","Map","map","cleaned","field","value","isBlank","trim","required","kind","length","undefined","unknownKeys","Object","keys","k"],"mappings":"AAiCA,4DAA4D,GAC5D,MAAMA,kBAAkB,IAAIC,IAAI;IAAC;CAAU;AAE3C,6EAA6E,GAC7E,MAAMC,mBAAmB;AAEzB;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeC,mBACpBC,OAAoB,EACpBC,MAAc,EACdC,IAA6B;IAE7B,IAAIC;IACJ,IAAI;QACFA,OAAQ,MAAMH,QAAQI,QAAQ,CAAC;YAC7BC,IAAIJ;YACJK,YAAY;YACZC,OAAO;QACT;IACF,EAAE,OAAM;QACN,OAAO;YAAEC,IAAI;YAAOC,QAAQ;QAAY;IAC1C;IAEA,MAAMC,SAAS,AAACP,CAAAA,KAAKO,MAAM,IAAI,EAAE,AAAD,EAAGC,MAAM,CACvC,CAACC,IACC,OAAOA,EAAEC,IAAI,KAAK,YAAY,CAACjB,gBAAgBkB,GAAG,CAACF,EAAEG,SAAS,IAAI;IAEtE,MAAMC,QAAQ,IAAIC,IAAIP,OAAOQ,GAAG,CAAC,CAACN,IAAM;YAACA,EAAEC,IAAI;YAAED;SAAE;IAEnD,MAAMO,UAAmC,CAAC;IAE1C,KAAK,MAAMC,SAASV,OAAQ;QAC1B,MAAMW,QAAQnB,IAAI,CAACkB,MAAMP,IAAI,CAAC;QAC9B,MAAMS,UACJD,SAAS,QAAS,OAAOA,UAAU,YAAYA,MAAME,IAAI,OAAO,MAAOF,UAAU;QAEnF,IAAID,MAAMI,QAAQ,IAAIF,SAAS;YAC7B,OAAO;gBAAEF,OAAOA,MAAMP,IAAI;gBAAEY,MAAM;gBAAYjB,IAAI;gBAAOC,QAAQ;YAAU;QAC7E;QAEA,IAAI,OAAOY,UAAU,YAAYA,MAAMK,MAAM,GAAG5B,kBAAkB;YAChE,OAAO;gBAAEsB,OAAOA,MAAMP,IAAI;gBAAEY,MAAM;gBAAYjB,IAAI;gBAAOC,QAAQ;YAAU;QAC7E;QAEA,wEAAwE;QACxE,gDAAgD;QAChD,IAAIY,UAAUM,WAAW;YACvBR,OAAO,CAACC,MAAMP,IAAI,CAAC,GAAGQ;QACxB;IACF;IAEA,0EAA0E;IAC1E,yDAAyD;IACzD,MAAMO,cAAcC,OAAOC,IAAI,CAAC5B,MAAMS,MAAM,CAAC,CAACoB,IAAM,CAACf,MAAMF,GAAG,CAACiB;IAC/D,IAAIH,YAAYF,MAAM,GAAG,GAAG;QAC1B,OAAO;YAAED,MAAM;YAAkBjB,IAAI;YAAOC,QAAQ;QAAU;IAChE;IAEA,OAAO;QAAEU;QAAShB;QAAMK,IAAI;IAAK;AACnC"} \ No newline at end of file +{"version":3,"sources":["validateSubmission.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\n/** A form-builder field, trimmed to what validation needs. */\ntype FormField = {\n blockType?: string\n label?: string\n name?: string\n required?: boolean | null\n}\n\ntype FormDoc = {\n fields?: FormField[]\n id: number | string\n /** Per-form notification address, when the client added the field. */\n notificationEmail?: string\n title?: string\n}\n\n/**\n * Validation outcome — codes, not user-facing strings. The frontend turns these\n * into its own copy (see SubmitFailure in submitForm).\n */\nexport type FormValidationResult =\n | {\n /** Offending field, when a single field is at fault. */\n field?: string\n kind: 'required' | 'too_long' | 'unknown_fields'\n ok: false\n reason: 'invalid'\n }\n | { cleaned: Record; form: FormDoc; ok: true }\n | { ok: false; reason: 'not_found' }\n\n/** Field block types that don't carry a submittable value. */\nconst NON_DATA_BLOCKS = new Set(['message'])\n\n/** Hard ceiling on a single field's length, independent of the form config. */\nconst MAX_FIELD_LENGTH = 5000\n\n/**\n * Checks submitted data against the form's own definition, rather than trusting\n * whatever arrived.\n *\n * The server action is a public endpoint: a caller can skip the rendered form\n * and post arbitrary keys. Without this, unknown fields would be stored,\n * required fields could be missing, and an oversized value could sail through.\n * So we load the form, keep only keys that are real fields, reject when a\n * required one is blank, and cap length.\n *\n * Returns the loaded form on success so the caller doesn't fetch it twice, and\n * a code + offending field on failure so the frontend can point at it.\n */\nexport async function validateSubmission(\n payload: BasePayload,\n formId: string,\n data: Record,\n): Promise {\n let form: FormDoc\n try {\n form = (await payload.findByID({\n id: formId,\n collection: 'forms',\n depth: 0,\n })) as FormDoc\n } catch {\n return { ok: false, reason: 'not_found' }\n }\n\n const fields = (form.fields ?? []).filter(\n (f): f is { name: string } & FormField =>\n typeof f.name === 'string' && !NON_DATA_BLOCKS.has(f.blockType ?? ''),\n )\n const known = new Map(fields.map((f) => [f.name, f]))\n\n const cleaned: Record = {}\n\n for (const field of fields) {\n const value = data[field.name]\n const isBlank =\n value == null || (typeof value === 'string' && value.trim() === '') || value === false\n\n if (field.required && isBlank) {\n return { field: field.name, kind: 'required', ok: false, reason: 'invalid' }\n }\n\n if (typeof value === 'string' && value.length > MAX_FIELD_LENGTH) {\n return { field: field.name, kind: 'too_long', ok: false, reason: 'invalid' }\n }\n\n // Only carry through keys that belong to the form — unknown keys from a\n // hand-crafted request are dropped, not stored.\n if (value !== undefined) {\n cleaned[field.name] = value\n }\n }\n\n // Reject outright if the payload carried keys the form doesn't define — a\n // sign the request wasn't produced by the rendered form.\n const unknownKeys = Object.keys(data).filter((k) => !known.has(k))\n if (unknownKeys.length > 0) {\n return { kind: 'unknown_fields', ok: false, reason: 'invalid' }\n }\n\n return { cleaned, form, ok: true }\n}\n"],"names":["NON_DATA_BLOCKS","Set","MAX_FIELD_LENGTH","validateSubmission","payload","formId","data","form","findByID","id","collection","depth","ok","reason","fields","filter","f","name","has","blockType","known","Map","map","cleaned","field","value","isBlank","trim","required","kind","length","undefined","unknownKeys","Object","keys","k"],"mappings":"AAiCA,4DAA4D,GAC5D,MAAMA,kBAAkB,IAAIC,IAAI;IAAC;CAAU;AAE3C,6EAA6E,GAC7E,MAAMC,mBAAmB;AAEzB;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeC,mBACpBC,OAAoB,EACpBC,MAAc,EACdC,IAA6B;IAE7B,IAAIC;IACJ,IAAI;QACFA,OAAQ,MAAMH,QAAQI,QAAQ,CAAC;YAC7BC,IAAIJ;YACJK,YAAY;YACZC,OAAO;QACT;IACF,EAAE,OAAM;QACN,OAAO;YAAEC,IAAI;YAAOC,QAAQ;QAAY;IAC1C;IAEA,MAAMC,SAAUP,CAAAA,KAAKO,MAAM,IAAI,EAAA,AAAC,EAAGC,MAAM,CACvC,CAACC,IACC,OAAOA,EAAEC,IAAI,KAAK,YAAY,CAACjB,gBAAgBkB,GAAG,CAACF,EAAEG,SAAS,IAAI;IAEtE,MAAMC,QAAQ,IAAIC,IAAIP,OAAOQ,GAAG,CAAC,CAACN,IAAM;YAACA,EAAEC,IAAI;YAAED;SAAE;IAEnD,MAAMO,UAAmC,CAAC;IAE1C,KAAK,MAAMC,SAASV,OAAQ;QAC1B,MAAMW,QAAQnB,IAAI,CAACkB,MAAMP,IAAI,CAAC;QAC9B,MAAMS,UACJD,SAAS,QAAS,OAAOA,UAAU,YAAYA,MAAME,IAAI,OAAO,MAAOF,UAAU;QAEnF,IAAID,MAAMI,QAAQ,IAAIF,SAAS;YAC7B,OAAO;gBAAEF,OAAOA,MAAMP,IAAI;gBAAEY,MAAM;gBAAYjB,IAAI;gBAAOC,QAAQ;YAAU;QAC7E;QAEA,IAAI,OAAOY,UAAU,YAAYA,MAAMK,MAAM,GAAG5B,kBAAkB;YAChE,OAAO;gBAAEsB,OAAOA,MAAMP,IAAI;gBAAEY,MAAM;gBAAYjB,IAAI;gBAAOC,QAAQ;YAAU;QAC7E;QAEA,wEAAwE;QACxE,gDAAgD;QAChD,IAAIY,UAAUM,WAAW;YACvBR,OAAO,CAACC,MAAMP,IAAI,CAAC,GAAGQ;QACxB;IACF;IAEA,0EAA0E;IAC1E,yDAAyD;IACzD,MAAMO,cAAcC,OAAOC,IAAI,CAAC5B,MAAMS,MAAM,CAAC,CAACoB,IAAM,CAACf,MAAMF,GAAG,CAACiB;IAC/D,IAAIH,YAAYF,MAAM,GAAG,GAAG;QAC1B,OAAO;YAAED,MAAM;YAAkBjB,IAAI;YAAOC,QAAQ;QAAU;IAChE;IAEA,OAAO;QAAEU;QAAShB;QAAMK,IAAI;IAAK;AACnC"} \ No newline at end of file diff --git a/dist/modules/frontend/createContentHelpers.js b/dist/modules/frontend/createContentHelpers.js index 1342ee6..9c6630f 100644 --- a/dist/modules/frontend/createContentHelpers.js +++ b/dist/modules/frontend/createContentHelpers.js @@ -106,6 +106,6 @@ import { buildRobots, buildSitemapEntries } from '../seo/index.js'; robots, sitemap }; -} +} //# sourceMappingURL=createContentHelpers.js.map //# sourceMappingURL=createContentHelpers.js.map \ No newline at end of file diff --git a/dist/modules/frontend/createContentHelpers.js.map b/dist/modules/frontend/createContentHelpers.js.map index c0b4c3d..e086e70 100644 --- a/dist/modules/frontend/createContentHelpers.js.map +++ b/dist/modules/frontend/createContentHelpers.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/frontend/createContentHelpers.ts"],"sourcesContent":["import type { BasePayload, SanitizedConfig } from 'payload'\n\nimport { getPayload } from 'payload'\nimport { cache } from 'react'\n\nimport type { ArchiveEntries, ContentOption, ResolvedRoute } from '../content/index.js'\nimport type { I18nConfig } from '../i18n/index.js'\nimport type { RobotsRules, SitemapEntry } from '../seo/index.js'\n\nimport { getArchiveEntries, resolveRoute as resolveRouteRaw } from '../content/index.js'\nimport { buildRobots, buildSitemapEntries } from '../seo/index.js'\n\ntype CreateContentHelpersArgs = {\n /**\n * Absolute site origin for sitemap/robots URLs. Falls back to\n * NEXT_PUBLIC_SERVER_URL, then to a relative origin (which most crawlers\n * reject, so set one in production).\n */\n baseUrl?: string\n /**\n * The client's payload config promise (the default export of payload.config).\n * Passed in because the plugin never imports the client's config directly.\n */\n config: Promise | SanitizedConfig\n /** Archive-backed collections, same value as the plugin option. */\n content?: ContentOption\n /**\n * i18n config. Required only if you want the ready-made `sitemap` / `robots`\n * handlers — they need the locale list to emit hreflang.\n */\n i18n?: I18nConfig\n /** Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\n/**\n * Bundles the per-request data helpers a frontend needs — the same cached\n * wrappers every project was writing by hand (getPayload, settings, locale\n * list, route resolution, archive entries).\n *\n * Everything is wrapped in React `cache()`, so within one request a value is\n * fetched once no matter how many times it's asked for — which matters because\n * Next runs generateMetadata and the page component separately, and both hit\n * these. Crucially the Payload instance is cached *here*, once, so every helper\n * shares it; that's why this is a factory and not loose functions importing a\n * shared module.\n *\n * ```ts\n * // src/lib/content.ts\n * import { createContentHelpers } from 'ipal-kit'\n * import config from '@/payload.config'\n * import { contentConfig } from '@/content.config'\n *\n * export const { getCachedPayload, getSettings, getConfiguredLocales, resolveRoute, getEntries } =\n * createContentHelpers({ config, content: contentConfig })\n * ```\n *\n * `resolveRoute` and `getEntries` are separate on purpose: routing decides what\n * a URL is, fetching gets the listing. Metadata generation needs the first and\n * not the second, and a page component composes them in two obvious lines.\n */\nexport function createContentHelpers({\n baseUrl,\n config,\n content,\n i18n,\n pagesSlug = 'pages',\n settingsSlug = 'site-settings',\n}: CreateContentHelpersArgs) {\n const origin = baseUrl ?? process.env.NEXT_PUBLIC_SERVER_URL ?? ''\n const getCachedPayload = cache(async (): Promise =>\n getPayload({ config: await config }),\n )\n\n const getConfiguredLocales = cache(async (): Promise => {\n const c = await config\n return c.localization ? c.localization.locales.map((l) => l.code) : []\n })\n\n const getSettings = cache(async (locale: string) => {\n const payload = await getCachedPayload()\n return payload.findGlobal({ slug: settingsSlug as never, depth: 2, locale: locale as never })\n })\n\n /** What does this URL point at? Routing only — no listing data. */\n const resolveRoute = cache(\n async (\n locale: string,\n segments: string[] | undefined,\n page: number,\n ): Promise => {\n const payload = await getCachedPayload()\n return resolveRouteRaw({ content, locale, page, pagesSlug, payload, segments, settingsSlug })\n },\n )\n\n /** One page of a collection's entries, for an archive listing. */\n const getEntries = cache(\n async (\n collection: string,\n locale: string,\n page: number,\n perPage: number,\n ): Promise => {\n const payload = await getCachedPayload()\n return getArchiveEntries({ collection, locale, page, payload, perPage })\n },\n )\n\n /**\n * Ready-made handler for Next's `app/sitemap.ts` — every page and entry with\n * per-URL hreflang and lastmod. Re-export it directly:\n *\n * ```ts\n * // app/sitemap.ts\n * export { sitemap as default } from '@/lib/content'\n * ```\n */\n const sitemap = cache(async (): Promise => {\n if (!i18n) {\n throw new Error('[ipal] createContentHelpers: pass `i18n` to use the sitemap handler.')\n }\n return buildSitemapEntries({\n baseUrl: origin,\n config: i18n,\n content,\n pagesSlug,\n payload: await getCachedPayload(),\n settingsSlug,\n })\n })\n\n /**\n * Ready-made handler for Next's `app/robots.ts`. Re-export directly:\n *\n * ```ts\n * // app/robots.ts\n * export { robots as default } from '@/lib/content'\n * ```\n */\n const robots = (): RobotsRules => buildRobots({ baseUrl: origin })\n\n return {\n getCachedPayload,\n getConfiguredLocales,\n getEntries,\n getSettings,\n resolveRoute,\n robots,\n sitemap,\n }\n}\n"],"names":["getPayload","cache","getArchiveEntries","resolveRoute","resolveRouteRaw","buildRobots","buildSitemapEntries","createContentHelpers","baseUrl","config","content","i18n","pagesSlug","settingsSlug","origin","process","env","NEXT_PUBLIC_SERVER_URL","getCachedPayload","getConfiguredLocales","c","localization","locales","map","l","code","getSettings","locale","payload","findGlobal","slug","depth","segments","page","getEntries","collection","perPage","sitemap","Error","robots"],"mappings":"AAEA,SAASA,UAAU,QAAQ,UAAS;AACpC,SAASC,KAAK,QAAQ,QAAO;AAM7B,SAASC,iBAAiB,EAAEC,gBAAgBC,eAAe,QAAQ,sBAAqB;AACxF,SAASC,WAAW,EAAEC,mBAAmB,QAAQ,kBAAiB;AA2BlE;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASC,qBAAqB,EACnCC,OAAO,EACPC,MAAM,EACNC,OAAO,EACPC,IAAI,EACJC,YAAY,OAAO,EACnBC,eAAe,eAAe,EACL;IACzB,MAAMC,SAASN,WAAWO,QAAQC,GAAG,CAACC,sBAAsB,IAAI;IAChE,MAAMC,mBAAmBjB,MAAM,UAC7BD,WAAW;YAAES,QAAQ,MAAMA;QAAO;IAGpC,MAAMU,uBAAuBlB,MAAM;QACjC,MAAMmB,IAAI,MAAMX;QAChB,OAAOW,EAAEC,YAAY,GAAGD,EAAEC,YAAY,CAACC,OAAO,CAACC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI,IAAI,EAAE;IACxE;IAEA,MAAMC,cAAczB,MAAM,OAAO0B;QAC/B,MAAMC,UAAU,MAAMV;QACtB,OAAOU,QAAQC,UAAU,CAAC;YAAEC,MAAMjB;YAAuBkB,OAAO;YAAGJ,QAAQA;QAAgB;IAC7F;IAEA,iEAAiE,GACjE,MAAMxB,eAAeF,MACnB,OACE0B,QACAK,UACAC;QAEA,MAAML,UAAU,MAAMV;QACtB,OAAOd,gBAAgB;YAAEM;YAASiB;YAAQM;YAAMrB;YAAWgB;YAASI;YAAUnB;QAAa;IAC7F;IAGF,gEAAgE,GAChE,MAAMqB,aAAajC,MACjB,OACEkC,YACAR,QACAM,MACAG;QAEA,MAAMR,UAAU,MAAMV;QACtB,OAAOhB,kBAAkB;YAAEiC;YAAYR;YAAQM;YAAML;YAASQ;QAAQ;IACxE;IAGF;;;;;;;;GAQC,GACD,MAAMC,UAAUpC,MAAM;QACpB,IAAI,CAACU,MAAM;YACT,MAAM,IAAI2B,MAAM;QAClB;QACA,OAAOhC,oBAAoB;YACzBE,SAASM;YACTL,QAAQE;YACRD;YACAE;YACAgB,SAAS,MAAMV;YACfL;QACF;IACF;IAEA;;;;;;;GAOC,GACD,MAAM0B,SAAS,IAAmBlC,YAAY;YAAEG,SAASM;QAAO;IAEhE,OAAO;QACLI;QACAC;QACAe;QACAR;QACAvB;QACAoC;QACAF;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["createContentHelpers.js"],"sourcesContent":["import type { BasePayload, SanitizedConfig } from 'payload'\n\nimport { getPayload } from 'payload'\nimport { cache } from 'react'\n\nimport type { ArchiveEntries, ContentOption, ResolvedRoute } from '../content/index.js'\nimport type { I18nConfig } from '../i18n/index.js'\nimport type { RobotsRules, SitemapEntry } from '../seo/index.js'\n\nimport { getArchiveEntries, resolveRoute as resolveRouteRaw } from '../content/index.js'\nimport { buildRobots, buildSitemapEntries } from '../seo/index.js'\n\ntype CreateContentHelpersArgs = {\n /**\n * Absolute site origin for sitemap/robots URLs. Falls back to\n * NEXT_PUBLIC_SERVER_URL, then to a relative origin (which most crawlers\n * reject, so set one in production).\n */\n baseUrl?: string\n /**\n * The client's payload config promise (the default export of payload.config).\n * Passed in because the plugin never imports the client's config directly.\n */\n config: Promise | SanitizedConfig\n /** Archive-backed collections, same value as the plugin option. */\n content?: ContentOption\n /**\n * i18n config. Required only if you want the ready-made `sitemap` / `robots`\n * handlers — they need the locale list to emit hreflang.\n */\n i18n?: I18nConfig\n /** Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\n/**\n * Bundles the per-request data helpers a frontend needs — the same cached\n * wrappers every project was writing by hand (getPayload, settings, locale\n * list, route resolution, archive entries).\n *\n * Everything is wrapped in React `cache()`, so within one request a value is\n * fetched once no matter how many times it's asked for — which matters because\n * Next runs generateMetadata and the page component separately, and both hit\n * these. Crucially the Payload instance is cached *here*, once, so every helper\n * shares it; that's why this is a factory and not loose functions importing a\n * shared module.\n *\n * ```ts\n * // src/lib/content.ts\n * import { createContentHelpers } from 'ipal-kit'\n * import config from '@/payload.config'\n * import { contentConfig } from '@/content.config'\n *\n * export const { getCachedPayload, getSettings, getConfiguredLocales, resolveRoute, getEntries } =\n * createContentHelpers({ config, content: contentConfig })\n * ```\n *\n * `resolveRoute` and `getEntries` are separate on purpose: routing decides what\n * a URL is, fetching gets the listing. Metadata generation needs the first and\n * not the second, and a page component composes them in two obvious lines.\n */\nexport function createContentHelpers({\n baseUrl,\n config,\n content,\n i18n,\n pagesSlug = 'pages',\n settingsSlug = 'site-settings',\n}: CreateContentHelpersArgs) {\n const origin = baseUrl ?? process.env.NEXT_PUBLIC_SERVER_URL ?? ''\n const getCachedPayload = cache(async (): Promise =>\n getPayload({ config: await config }),\n )\n\n const getConfiguredLocales = cache(async (): Promise => {\n const c = await config\n return c.localization ? c.localization.locales.map((l) => l.code) : []\n })\n\n const getSettings = cache(async (locale: string) => {\n const payload = await getCachedPayload()\n return payload.findGlobal({ slug: settingsSlug as never, depth: 2, locale: locale as never })\n })\n\n /** What does this URL point at? Routing only — no listing data. */\n const resolveRoute = cache(\n async (\n locale: string,\n segments: string[] | undefined,\n page: number,\n ): Promise => {\n const payload = await getCachedPayload()\n return resolveRouteRaw({ content, locale, page, pagesSlug, payload, segments, settingsSlug })\n },\n )\n\n /** One page of a collection's entries, for an archive listing. */\n const getEntries = cache(\n async (\n collection: string,\n locale: string,\n page: number,\n perPage: number,\n ): Promise => {\n const payload = await getCachedPayload()\n return getArchiveEntries({ collection, locale, page, payload, perPage })\n },\n )\n\n /**\n * Ready-made handler for Next's `app/sitemap.ts` — every page and entry with\n * per-URL hreflang and lastmod. Re-export it directly:\n *\n * ```ts\n * // app/sitemap.ts\n * export { sitemap as default } from '@/lib/content'\n * ```\n */\n const sitemap = cache(async (): Promise => {\n if (!i18n) {\n throw new Error('[ipal] createContentHelpers: pass `i18n` to use the sitemap handler.')\n }\n return buildSitemapEntries({\n baseUrl: origin,\n config: i18n,\n content,\n pagesSlug,\n payload: await getCachedPayload(),\n settingsSlug,\n })\n })\n\n /**\n * Ready-made handler for Next's `app/robots.ts`. Re-export directly:\n *\n * ```ts\n * // app/robots.ts\n * export { robots as default } from '@/lib/content'\n * ```\n */\n const robots = (): RobotsRules => buildRobots({ baseUrl: origin })\n\n return {\n getCachedPayload,\n getConfiguredLocales,\n getEntries,\n getSettings,\n resolveRoute,\n robots,\n sitemap,\n }\n}\n"],"names":["getPayload","cache","getArchiveEntries","resolveRoute","resolveRouteRaw","buildRobots","buildSitemapEntries","createContentHelpers","baseUrl","config","content","i18n","pagesSlug","settingsSlug","origin","process","env","NEXT_PUBLIC_SERVER_URL","getCachedPayload","getConfiguredLocales","c","localization","locales","map","l","code","getSettings","locale","payload","findGlobal","slug","depth","segments","page","getEntries","collection","perPage","sitemap","Error","robots"],"mappings":"AAEA,SAASA,UAAU,QAAQ,UAAS;AACpC,SAASC,KAAK,QAAQ,QAAO;AAM7B,SAASC,iBAAiB,EAAEC,gBAAgBC,eAAe,QAAQ,sBAAqB;AACxF,SAASC,WAAW,EAAEC,mBAAmB,QAAQ,kBAAiB;AA2BlE;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASC,qBAAqB,EACnCC,OAAO,EACPC,MAAM,EACNC,OAAO,EACPC,IAAI,EACJC,YAAY,OAAO,EACnBC,eAAe,eAAe,EACL;IACzB,MAAMC,SAASN,WAAWO,QAAQC,GAAG,CAACC,sBAAsB,IAAI;IAChE,MAAMC,mBAAmBjB,MAAM,UAC7BD,WAAW;YAAES,QAAQ,MAAMA;QAAO;IAGpC,MAAMU,uBAAuBlB,MAAM;QACjC,MAAMmB,IAAI,MAAMX;QAChB,OAAOW,EAAEC,YAAY,GAAGD,EAAEC,YAAY,CAACC,OAAO,CAACC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI,IAAI,EAAE;IACxE;IAEA,MAAMC,cAAczB,MAAM,OAAO0B;QAC/B,MAAMC,UAAU,MAAMV;QACtB,OAAOU,QAAQC,UAAU,CAAC;YAAEC,MAAMjB;YAAuBkB,OAAO;YAAGJ,QAAQA;QAAgB;IAC7F;IAEA,iEAAiE,GACjE,MAAMxB,eAAeF,MACnB,OACE0B,QACAK,UACAC;QAEA,MAAML,UAAU,MAAMV;QACtB,OAAOd,gBAAgB;YAAEM;YAASiB;YAAQM;YAAMrB;YAAWgB;YAASI;YAAUnB;QAAa;IAC7F;IAGF,gEAAgE,GAChE,MAAMqB,aAAajC,MACjB,OACEkC,YACAR,QACAM,MACAG;QAEA,MAAMR,UAAU,MAAMV;QACtB,OAAOhB,kBAAkB;YAAEiC;YAAYR;YAAQM;YAAML;YAASQ;QAAQ;IACxE;IAGF;;;;;;;;GAQC,GACD,MAAMC,UAAUpC,MAAM;QACpB,IAAI,CAACU,MAAM;YACT,MAAM,IAAI2B,MAAM;QAClB;QACA,OAAOhC,oBAAoB;YACzBE,SAASM;YACTL,QAAQE;YACRD;YACAE;YACAgB,SAAS,MAAMV;YACfL;QACF;IACF;IAEA;;;;;;;GAOC,GACD,MAAM0B,SAAS,IAAmBlC,YAAY;YAAEG,SAASM;QAAO;IAEhE,OAAO;QACLI;QACAC;QACAe;QACAR;QACAvB;QACAoC;QACAF;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/frontend/index.js b/dist/modules/frontend/index.js index 889f6c0..caf9af7 100644 --- a/dist/modules/frontend/index.js +++ b/dist/modules/frontend/index.js @@ -1,3 +1,3 @@ -export { createContentHelpers } from './createContentHelpers.js'; +export { createContentHelpers } from './createContentHelpers.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/frontend/index.js.map b/dist/modules/frontend/index.js.map index 1f67e27..aceea48 100644 --- a/dist/modules/frontend/index.js.map +++ b/dist/modules/frontend/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/frontend/index.ts"],"sourcesContent":["export { createContentHelpers } from './createContentHelpers.js'\n"],"names":["createContentHelpers"],"mappings":"AAAA,SAASA,oBAAoB,QAAQ,4BAA2B"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export { createContentHelpers } from './createContentHelpers.js'\n"],"names":["createContentHelpers"],"mappings":"AAAA,SAASA,oBAAoB,QAAQ,4BAA2B"} \ No newline at end of file diff --git a/dist/modules/i18n/getLocalizedSlugs.js b/dist/modules/i18n/getLocalizedSlugs.js index c7a27f7..9119bd3 100644 --- a/dist/modules/i18n/getLocalizedSlugs.js +++ b/dist/modules/i18n/getLocalizedSlugs.js @@ -34,6 +34,6 @@ import { isValidLocale } from './helpers.js'; result[locale] = slug; } return result; -} +} //# sourceMappingURL=getLocalizedSlugs.js.map //# sourceMappingURL=getLocalizedSlugs.js.map \ No newline at end of file diff --git a/dist/modules/i18n/getLocalizedSlugs.js.map b/dist/modules/i18n/getLocalizedSlugs.js.map index 8885d1b..a0bc95e 100644 --- a/dist/modules/i18n/getLocalizedSlugs.js.map +++ b/dist/modules/i18n/getLocalizedSlugs.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/getLocalizedSlugs.ts"],"sourcesContent":["import type { LocalizedSlugs } from './localizedPath.js'\nimport type { I18nConfig } from './types.js'\n\nimport { isValidLocale } from './helpers.js'\n\n/**\n * A localized field as Payload returns it when queried with `locale: 'all'`:\n * a map of locale code → value.\n */\ntype LocalizedField = Record\n\ntype GetLocalizedSlugsArgs = {\n config: I18nConfig\n /**\n * The document's localized slug field, as returned by Payload with\n * `locale: 'all'` — e.g. { pl: 'strona-glowna', en: 'home' }.\n */\n slugField: LocalizedField | null | undefined\n}\n\n/**\n * Normalizes a document's localized slug field into the LocalizedSlugs\n * contract consumed by buildLocalizedPath / switchLocalePath.\n *\n * Pure function — the template fetches the document (payload.findByID with\n * `locale: 'all'`, where the slug field must be `localized: true`) and passes\n * the raw field in. The plugin never touches the database.\n *\n * Keeps only entries whose locale is configured and whose slug is a\n * non-empty string, so callers get a clean, trustworthy map.\n *\n * @example\n * const doc = await payload.findByID({ collection: 'pages', id, locale: 'all' })\n * const slugs = getLocalizedSlugs({ slugField: doc.slug, config })\n * // → { pl: 'strona-glowna', en: 'home' }\n * switchLocalePath({ slugs, targetLocale: 'en', config }) // → '/en'\n */\nexport function getLocalizedSlugs({ config, slugField }: GetLocalizedSlugsArgs): LocalizedSlugs {\n const result: LocalizedSlugs = {}\n\n if (!slugField || typeof slugField !== 'object') {\n return result\n }\n\n for (const [locale, value] of Object.entries(slugField)) {\n if (!isValidLocale(locale, config)) {continue}\n if (typeof value !== 'string') {continue}\n\n const slug = value.trim()\n if (!slug) {continue}\n\n result[locale] = slug\n }\n\n return result\n}\n"],"names":["isValidLocale","getLocalizedSlugs","config","slugField","result","locale","value","Object","entries","slug","trim"],"mappings":"AAGA,SAASA,aAAa,QAAQ,eAAc;AAiB5C;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,kBAAkB,EAAEC,MAAM,EAAEC,SAAS,EAAyB;IAC5E,MAAMC,SAAyB,CAAC;IAEhC,IAAI,CAACD,aAAa,OAAOA,cAAc,UAAU;QAC/C,OAAOC;IACT;IAEA,KAAK,MAAM,CAACC,QAAQC,MAAM,IAAIC,OAAOC,OAAO,CAACL,WAAY;QACvD,IAAI,CAACH,cAAcK,QAAQH,SAAS;YAAC;QAAQ;QAC7C,IAAI,OAAOI,UAAU,UAAU;YAAC;QAAQ;QAExC,MAAMG,OAAOH,MAAMI,IAAI;QACvB,IAAI,CAACD,MAAM;YAAC;QAAQ;QAEpBL,MAAM,CAACC,OAAO,GAAGI;IACnB;IAEA,OAAOL;AACT"} \ No newline at end of file +{"version":3,"sources":["getLocalizedSlugs.js"],"sourcesContent":["import type { LocalizedSlugs } from './localizedPath.js'\nimport type { I18nConfig } from './types.js'\n\nimport { isValidLocale } from './helpers.js'\n\n/**\n * A localized field as Payload returns it when queried with `locale: 'all'`:\n * a map of locale code → value.\n */\ntype LocalizedField = Record\n\ntype GetLocalizedSlugsArgs = {\n config: I18nConfig\n /**\n * The document's localized slug field, as returned by Payload with\n * `locale: 'all'` — e.g. { pl: 'strona-glowna', en: 'home' }.\n */\n slugField: LocalizedField | null | undefined\n}\n\n/**\n * Normalizes a document's localized slug field into the LocalizedSlugs\n * contract consumed by buildLocalizedPath / switchLocalePath.\n *\n * Pure function — the template fetches the document (payload.findByID with\n * `locale: 'all'`, where the slug field must be `localized: true`) and passes\n * the raw field in. The plugin never touches the database.\n *\n * Keeps only entries whose locale is configured and whose slug is a\n * non-empty string, so callers get a clean, trustworthy map.\n *\n * @example\n * const doc = await payload.findByID({ collection: 'pages', id, locale: 'all' })\n * const slugs = getLocalizedSlugs({ slugField: doc.slug, config })\n * // → { pl: 'strona-glowna', en: 'home' }\n * switchLocalePath({ slugs, targetLocale: 'en', config }) // → '/en'\n */\nexport function getLocalizedSlugs({ config, slugField }: GetLocalizedSlugsArgs): LocalizedSlugs {\n const result: LocalizedSlugs = {}\n\n if (!slugField || typeof slugField !== 'object') {\n return result\n }\n\n for (const [locale, value] of Object.entries(slugField)) {\n if (!isValidLocale(locale, config)) {continue}\n if (typeof value !== 'string') {continue}\n\n const slug = value.trim()\n if (!slug) {continue}\n\n result[locale] = slug\n }\n\n return result\n}\n"],"names":["isValidLocale","getLocalizedSlugs","config","slugField","result","locale","value","Object","entries","slug","trim"],"mappings":"AAGA,SAASA,aAAa,QAAQ,eAAc;AAiB5C;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,kBAAkB,EAAEC,MAAM,EAAEC,SAAS,EAAyB;IAC5E,MAAMC,SAAyB,CAAC;IAEhC,IAAI,CAACD,aAAa,OAAOA,cAAc,UAAU;QAC/C,OAAOC;IACT;IAEA,KAAK,MAAM,CAACC,QAAQC,MAAM,IAAIC,OAAOC,OAAO,CAACL,WAAY;QACvD,IAAI,CAACH,cAAcK,QAAQH,SAAS;YAAC;QAAQ;QAC7C,IAAI,OAAOI,UAAU,UAAU;YAAC;QAAQ;QAExC,MAAMG,OAAOH,MAAMI,IAAI;QACvB,IAAI,CAACD,MAAM;YAAC;QAAQ;QAEpBL,MAAM,CAACC,OAAO,GAAGI;IACnB;IAEA,OAAOL;AACT"} \ No newline at end of file diff --git a/dist/modules/i18n/helpers.js b/dist/modules/i18n/helpers.js index deec843..7a1d62f 100644 --- a/dist/modules/i18n/helpers.js +++ b/dist/modules/i18n/helpers.js @@ -17,6 +17,6 @@ * Returns the full locale definition for a given code, or undefined if not found. */ export function getLocaleDefinition(code, config) { return config.locales.find((locale)=>locale.code === code); -} +} //# sourceMappingURL=helpers.js.map //# sourceMappingURL=helpers.js.map \ No newline at end of file diff --git a/dist/modules/i18n/helpers.js.map b/dist/modules/i18n/helpers.js.map index 8db416a..f95a1d7 100644 --- a/dist/modules/i18n/helpers.js.map +++ b/dist/modules/i18n/helpers.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/helpers.ts"],"sourcesContent":["import type { I18nConfig, LocaleDefinition } from './types.js'\n\n/**\n * Returns all configured locale codes.\n */\nexport function getLocaleCodes(config: I18nConfig): string[] {\n return config.locales.map((locale) => locale.code)\n}\n\n/**\n * Returns the default locale code.\n */\nexport function getDefaultLocale(config: I18nConfig): string {\n return config.defaultLocale\n}\n\n/**\n * Checks if a string is a valid configured locale code.\n */\nexport function isValidLocale(code: string, config: I18nConfig): boolean {\n return config.locales.some((locale) => locale.code === code)\n}\n\n/**\n * Returns the full locale definition for a given code, or undefined if not found.\n */\nexport function getLocaleDefinition(\n code: string,\n config: I18nConfig,\n): LocaleDefinition | undefined {\n return config.locales.find((locale) => locale.code === code)\n}\n"],"names":["getLocaleCodes","config","locales","map","locale","code","getDefaultLocale","defaultLocale","isValidLocale","some","getLocaleDefinition","find"],"mappings":"AAEA;;CAEC,GACD,OAAO,SAASA,eAAeC,MAAkB;IAC/C,OAAOA,OAAOC,OAAO,CAACC,GAAG,CAAC,CAACC,SAAWA,OAAOC,IAAI;AACnD;AAEA;;CAEC,GACD,OAAO,SAASC,iBAAiBL,MAAkB;IACjD,OAAOA,OAAOM,aAAa;AAC7B;AAEA;;CAEC,GACD,OAAO,SAASC,cAAcH,IAAY,EAAEJ,MAAkB;IAC5D,OAAOA,OAAOC,OAAO,CAACO,IAAI,CAAC,CAACL,SAAWA,OAAOC,IAAI,KAAKA;AACzD;AAEA;;CAEC,GACD,OAAO,SAASK,oBACdL,IAAY,EACZJ,MAAkB;IAElB,OAAOA,OAAOC,OAAO,CAACS,IAAI,CAAC,CAACP,SAAWA,OAAOC,IAAI,KAAKA;AACzD"} \ No newline at end of file +{"version":3,"sources":["helpers.js"],"sourcesContent":["import type { I18nConfig, LocaleDefinition } from './types.js'\n\n/**\n * Returns all configured locale codes.\n */\nexport function getLocaleCodes(config: I18nConfig): string[] {\n return config.locales.map((locale) => locale.code)\n}\n\n/**\n * Returns the default locale code.\n */\nexport function getDefaultLocale(config: I18nConfig): string {\n return config.defaultLocale\n}\n\n/**\n * Checks if a string is a valid configured locale code.\n */\nexport function isValidLocale(code: string, config: I18nConfig): boolean {\n return config.locales.some((locale) => locale.code === code)\n}\n\n/**\n * Returns the full locale definition for a given code, or undefined if not found.\n */\nexport function getLocaleDefinition(\n code: string,\n config: I18nConfig,\n): LocaleDefinition | undefined {\n return config.locales.find((locale) => locale.code === code)\n}\n"],"names":["getLocaleCodes","config","locales","map","locale","code","getDefaultLocale","defaultLocale","isValidLocale","some","getLocaleDefinition","find"],"mappings":"AAEA;;CAEC,GACD,OAAO,SAASA,eAAeC,MAAkB;IAC/C,OAAOA,OAAOC,OAAO,CAACC,GAAG,CAAC,CAACC,SAAWA,OAAOC,IAAI;AACnD;AAEA;;CAEC,GACD,OAAO,SAASC,iBAAiBL,MAAkB;IACjD,OAAOA,OAAOM,aAAa;AAC7B;AAEA;;CAEC,GACD,OAAO,SAASC,cAAcH,IAAY,EAAEJ,MAAkB;IAC5D,OAAOA,OAAOC,OAAO,CAACO,IAAI,CAAC,CAACL,SAAWA,OAAOC,IAAI,KAAKA;AACzD;AAEA;;CAEC,GACD,OAAO,SAASK,oBACdL,IAAY,EACZJ,MAAkB;IAElB,OAAOA,OAAOC,OAAO,CAACS,IAAI,CAAC,CAACP,SAAWA,OAAOC,IAAI,KAAKA;AACzD"} \ No newline at end of file diff --git a/dist/modules/i18n/index.js b/dist/modules/i18n/index.js index 6dbd28a..122f37e 100644 --- a/dist/modules/i18n/index.js +++ b/dist/modules/i18n/index.js @@ -4,6 +4,6 @@ export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './localeMidd export { buildLocalizationConfig } from './localizationConfig.js'; export { buildLocalizedPath, switchLocalePath } from './localizedPath.js'; export { LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale } from './negotiateLocale.js'; -export { validateI18nConfig } from './validation.js'; +export { validateI18nConfig } from './validation.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/i18n/index.js.map b/dist/modules/i18n/index.js.map index 9708da6..4df16fb 100644 --- a/dist/modules/i18n/index.js.map +++ b/dist/modules/i18n/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/index.ts"],"sourcesContent":["export { getLocalizedSlugs } from './getLocalizedSlugs.js'\nexport { getDefaultLocale, getLocaleCodes, getLocaleDefinition, isValidLocale } from './helpers.js'\nexport { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './localeMiddleware.js'\nexport type { LocaleMiddlewareResult } from './localeMiddleware.js'\nexport { buildLocalizationConfig } from './localizationConfig.js'\nexport { buildLocalizedPath, switchLocalePath } from './localizedPath.js'\nexport type { LocalizedSlugs } from './localizedPath.js'\nexport { LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale } from './negotiateLocale.js'\nexport type { I18nConfig, LocaleDefinition } from './types.js'\nexport { validateI18nConfig } from './validation.js'\n"],"names":["getLocalizedSlugs","getDefaultLocale","getLocaleCodes","getLocaleDefinition","isValidLocale","createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER","buildLocalizationConfig","buildLocalizedPath","switchLocalePath","LOCALE_COOKIE_NAME","matchAcceptLanguage","negotiateLocale","validateI18nConfig"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,yBAAwB;AAC1D,SAASC,gBAAgB,EAAEC,cAAc,EAAEC,mBAAmB,EAAEC,aAAa,QAAQ,eAAc;AACnG,SAASC,sBAAsB,EAAEC,0BAA0B,QAAQ,wBAAuB;AAE1F,SAASC,uBAAuB,QAAQ,0BAAyB;AACjE,SAASC,kBAAkB,EAAEC,gBAAgB,QAAQ,qBAAoB;AAEzE,SAASC,kBAAkB,EAAEC,mBAAmB,EAAEC,eAAe,QAAQ,uBAAsB;AAE/F,SAASC,kBAAkB,QAAQ,kBAAiB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export { getLocalizedSlugs } from './getLocalizedSlugs.js'\nexport { getDefaultLocale, getLocaleCodes, getLocaleDefinition, isValidLocale } from './helpers.js'\nexport { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './localeMiddleware.js'\nexport type { LocaleMiddlewareResult } from './localeMiddleware.js'\nexport { buildLocalizationConfig } from './localizationConfig.js'\nexport { buildLocalizedPath, switchLocalePath } from './localizedPath.js'\nexport type { LocalizedSlugs } from './localizedPath.js'\nexport { LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale } from './negotiateLocale.js'\nexport type { I18nConfig, LocaleDefinition } from './types.js'\nexport { validateI18nConfig } from './validation.js'\n"],"names":["getLocalizedSlugs","getDefaultLocale","getLocaleCodes","getLocaleDefinition","isValidLocale","createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER","buildLocalizationConfig","buildLocalizedPath","switchLocalePath","LOCALE_COOKIE_NAME","matchAcceptLanguage","negotiateLocale","validateI18nConfig"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,yBAAwB;AAC1D,SAASC,gBAAgB,EAAEC,cAAc,EAAEC,mBAAmB,EAAEC,aAAa,QAAQ,eAAc;AACnG,SAASC,sBAAsB,EAAEC,0BAA0B,QAAQ,wBAAuB;AAE1F,SAASC,uBAAuB,QAAQ,0BAAyB;AACjE,SAASC,kBAAkB,EAAEC,gBAAgB,QAAQ,qBAAoB;AAEzE,SAASC,kBAAkB,EAAEC,mBAAmB,EAAEC,eAAe,QAAQ,uBAAsB;AAE/F,SAASC,kBAAkB,QAAQ,kBAAiB"} \ No newline at end of file diff --git a/dist/modules/i18n/localeMiddleware.js b/dist/modules/i18n/localeMiddleware.js index e251ad4..cfddb5c 100644 --- a/dist/modules/i18n/localeMiddleware.js +++ b/dist/modules/i18n/localeMiddleware.js @@ -62,6 +62,6 @@ import { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/inde * admin panel, Next internals, and files with an extension (static assets). */ export const DEFAULT_MIDDLEWARE_MATCHER = [ '/((?!api|admin|_next|.*\\..*).*)' -]; +]; //# sourceMappingURL=localeMiddleware.js.map //# sourceMappingURL=localeMiddleware.js.map \ No newline at end of file diff --git a/dist/modules/i18n/localeMiddleware.js.map b/dist/modules/i18n/localeMiddleware.js.map index de77963..8faa9a1 100644 --- a/dist/modules/i18n/localeMiddleware.js.map +++ b/dist/modules/i18n/localeMiddleware.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/localeMiddleware.ts"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/index.js'\n\n/**\n * Minimal request shape the middleware reads. Kept structural so the plugin\n * doesn't hard-depend on next/server types; a Next.js `NextRequest` satisfies it.\n */\ntype MiddlewareRequest = {\n cookies: { get: (name: string) => { value: string } | undefined }\n headers: { get: (name: string) => null | string }\n nextUrl: { clone: () => URL; pathname: string; search: string }\n url: string\n}\n\n/**\n * What the factory returns — the caller (in Next next-middleware.ts) decides how to\n * act: `redirect` means send a 307 to `location` and set the locale cookie;\n * `next` means let the request pass through untouched.\n */\nexport type LocaleMiddlewareResult =\n { cookie: { name: string; value: string }; location: string; type: 'redirect' } | { type: 'next' }\n\ntype CreateLocaleMiddlewareArgs = {\n config: I18nConfig\n /** Cookie name for the locale choice. Defaults to LOCALE_COOKIE_NAME. */\n cookieName?: string\n}\n\n/**\n * First path segment of a URL pathname, or '' for root.\n * '/pl/o-nas' → 'pl', '/o-nas' → 'o-nas', '/' → ''.\n */\nfunction firstSegment(pathname: string): string {\n return pathname.split('/').filter(Boolean)[0] ?? ''\n}\n\n/**\n * Builds locale-routing logic for Next.js middleware.\n *\n * Behavior:\n * - path already starts with a valid locale (/pl/...) → pass through\n * - any other path (/, /o-nas) → redirect to /{locale}{path}, where locale\n * comes from negotiateLocale (cookie → Accept-Language → default)\n * - the chosen locale is written to a cookie so the next visit is stable\n *\n * The plugin returns a decision; the thin next-middleware.ts in the client project\n * turns it into a NextResponse. This keeps all logic in the plugin while\n * respecting that next-middleware.ts must physically live in the client app.\n *\n * @example\n * // next-middleware.ts (client project) — one wiring file, no logic:\n * import { NextResponse } from 'next/server'\n * import { localeMiddleware } from './ipal.middleware' // created from this factory\n * export function middleware(req) {\n * const r = localeMiddleware(req)\n * if (r.type === 'next') return NextResponse.next()\n * const res = NextResponse.redirect(r.location)\n * res.cookies.set(r.cookie.name, r.cookie.value)\n * return res\n * }\n */\nexport function createLocaleMiddleware({\n config,\n cookieName = LOCALE_COOKIE_NAME,\n}: CreateLocaleMiddlewareArgs) {\n return function localeMiddleware(request: MiddlewareRequest): LocaleMiddlewareResult {\n const { pathname } = request.nextUrl\n\n // Already locale-prefixed → nothing to do\n if (isValidLocale(firstSegment(pathname), config)) {\n return { type: 'next' }\n }\n\n // Resolve the locale to use\n const locale = negotiateLocale({\n acceptLanguage: request.headers.get('accept-language'),\n config,\n cookieLocale: request.cookies.get(cookieName)?.value ?? null,\n })\n\n // Redirect to the locale-prefixed path, preserving the rest\n const url = request.nextUrl.clone()\n url.pathname = `/${locale}${pathname === '/' ? '' : pathname}`\n\n return {\n type: 'redirect',\n cookie: { name: cookieName, value: locale },\n location: url.toString(),\n }\n }\n}\n\n/**\n * Default Next.js middleware matcher: run on everything except API routes, the\n * admin panel, Next internals, and files with an extension (static assets).\n */\nexport const DEFAULT_MIDDLEWARE_MATCHER = ['/((?!api|admin|_next|.*\\\\..*).*)']\n"],"names":["isValidLocale","LOCALE_COOKIE_NAME","negotiateLocale","firstSegment","pathname","split","filter","Boolean","createLocaleMiddleware","config","cookieName","localeMiddleware","request","nextUrl","type","locale","acceptLanguage","headers","get","cookieLocale","cookies","value","url","clone","cookie","name","location","toString","DEFAULT_MIDDLEWARE_MATCHER"],"mappings":"AAEA,SAASA,aAAa,EAAEC,kBAAkB,EAAEC,eAAe,QAAQ,mBAAkB;AA2BrF;;;CAGC,GACD,SAASC,aAAaC,QAAgB;IACpC,OAAOA,SAASC,KAAK,CAAC,KAAKC,MAAM,CAACC,QAAQ,CAAC,EAAE,IAAI;AACnD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASC,uBAAuB,EACrCC,MAAM,EACNC,aAAaT,kBAAkB,EACJ;IAC3B,OAAO,SAASU,iBAAiBC,OAA0B;QACzD,MAAM,EAAER,QAAQ,EAAE,GAAGQ,QAAQC,OAAO;QAEpC,0CAA0C;QAC1C,IAAIb,cAAcG,aAAaC,WAAWK,SAAS;YACjD,OAAO;gBAAEK,MAAM;YAAO;QACxB;QAEA,4BAA4B;QAC5B,MAAMC,SAASb,gBAAgB;YAC7Bc,gBAAgBJ,QAAQK,OAAO,CAACC,GAAG,CAAC;YACpCT;YACAU,cAAcP,QAAQQ,OAAO,CAACF,GAAG,CAACR,aAAaW,SAAS;QAC1D;QAEA,4DAA4D;QAC5D,MAAMC,MAAMV,QAAQC,OAAO,CAACU,KAAK;QACjCD,IAAIlB,QAAQ,GAAG,CAAC,CAAC,EAAEW,SAASX,aAAa,MAAM,KAAKA,UAAU;QAE9D,OAAO;YACLU,MAAM;YACNU,QAAQ;gBAAEC,MAAMf;gBAAYW,OAAON;YAAO;YAC1CW,UAAUJ,IAAIK,QAAQ;QACxB;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,MAAMC,6BAA6B;IAAC;CAAmC,CAAA"} \ No newline at end of file +{"version":3,"sources":["localeMiddleware.js"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/index.js'\n\n/**\n * Minimal request shape the middleware reads. Kept structural so the plugin\n * doesn't hard-depend on next/server types; a Next.js `NextRequest` satisfies it.\n */\ntype MiddlewareRequest = {\n cookies: { get: (name: string) => { value: string } | undefined }\n headers: { get: (name: string) => null | string }\n nextUrl: { clone: () => URL; pathname: string; search: string }\n url: string\n}\n\n/**\n * What the factory returns — the caller (in Next next-middleware.ts) decides how to\n * act: `redirect` means send a 307 to `location` and set the locale cookie;\n * `next` means let the request pass through untouched.\n */\nexport type LocaleMiddlewareResult =\n { cookie: { name: string; value: string }; location: string; type: 'redirect' } | { type: 'next' }\n\ntype CreateLocaleMiddlewareArgs = {\n config: I18nConfig\n /** Cookie name for the locale choice. Defaults to LOCALE_COOKIE_NAME. */\n cookieName?: string\n}\n\n/**\n * First path segment of a URL pathname, or '' for root.\n * '/pl/o-nas' → 'pl', '/o-nas' → 'o-nas', '/' → ''.\n */\nfunction firstSegment(pathname: string): string {\n return pathname.split('/').filter(Boolean)[0] ?? ''\n}\n\n/**\n * Builds locale-routing logic for Next.js middleware.\n *\n * Behavior:\n * - path already starts with a valid locale (/pl/...) → pass through\n * - any other path (/, /o-nas) → redirect to /{locale}{path}, where locale\n * comes from negotiateLocale (cookie → Accept-Language → default)\n * - the chosen locale is written to a cookie so the next visit is stable\n *\n * The plugin returns a decision; the thin next-middleware.ts in the client project\n * turns it into a NextResponse. This keeps all logic in the plugin while\n * respecting that next-middleware.ts must physically live in the client app.\n *\n * @example\n * // next-middleware.ts (client project) — one wiring file, no logic:\n * import { NextResponse } from 'next/server'\n * import { localeMiddleware } from './ipal.middleware' // created from this factory\n * export function middleware(req) {\n * const r = localeMiddleware(req)\n * if (r.type === 'next') return NextResponse.next()\n * const res = NextResponse.redirect(r.location)\n * res.cookies.set(r.cookie.name, r.cookie.value)\n * return res\n * }\n */\nexport function createLocaleMiddleware({\n config,\n cookieName = LOCALE_COOKIE_NAME,\n}: CreateLocaleMiddlewareArgs) {\n return function localeMiddleware(request: MiddlewareRequest): LocaleMiddlewareResult {\n const { pathname } = request.nextUrl\n\n // Already locale-prefixed → nothing to do\n if (isValidLocale(firstSegment(pathname), config)) {\n return { type: 'next' }\n }\n\n // Resolve the locale to use\n const locale = negotiateLocale({\n acceptLanguage: request.headers.get('accept-language'),\n config,\n cookieLocale: request.cookies.get(cookieName)?.value ?? null,\n })\n\n // Redirect to the locale-prefixed path, preserving the rest\n const url = request.nextUrl.clone()\n url.pathname = `/${locale}${pathname === '/' ? '' : pathname}`\n\n return {\n type: 'redirect',\n cookie: { name: cookieName, value: locale },\n location: url.toString(),\n }\n }\n}\n\n/**\n * Default Next.js middleware matcher: run on everything except API routes, the\n * admin panel, Next internals, and files with an extension (static assets).\n */\nexport const DEFAULT_MIDDLEWARE_MATCHER = ['/((?!api|admin|_next|.*\\\\..*).*)']\n"],"names":["isValidLocale","LOCALE_COOKIE_NAME","negotiateLocale","firstSegment","pathname","split","filter","Boolean","createLocaleMiddleware","config","cookieName","localeMiddleware","request","nextUrl","type","locale","acceptLanguage","headers","get","cookieLocale","cookies","value","url","clone","cookie","name","location","toString","DEFAULT_MIDDLEWARE_MATCHER"],"mappings":"AAEA,SAASA,aAAa,EAAEC,kBAAkB,EAAEC,eAAe,QAAQ,mBAAkB;AA2BrF;;;CAGC,GACD,SAASC,aAAaC,QAAgB;IACpC,OAAOA,SAASC,KAAK,CAAC,KAAKC,MAAM,CAACC,QAAQ,CAAC,EAAE,IAAI;AACnD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASC,uBAAuB,EACrCC,MAAM,EACNC,aAAaT,kBAAkB,EACJ;IAC3B,OAAO,SAASU,iBAAiBC,OAA0B;QACzD,MAAM,EAAER,QAAQ,EAAE,GAAGQ,QAAQC,OAAO;QAEpC,0CAA0C;QAC1C,IAAIb,cAAcG,aAAaC,WAAWK,SAAS;YACjD,OAAO;gBAAEK,MAAM;YAAO;QACxB;QAEA,4BAA4B;QAC5B,MAAMC,SAASb,gBAAgB;YAC7Bc,gBAAgBJ,QAAQK,OAAO,CAACC,GAAG,CAAC;YACpCT;YACAU,cAAcP,QAAQQ,OAAO,CAACF,GAAG,CAACR,aAAaW,SAAS;QAC1D;QAEA,4DAA4D;QAC5D,MAAMC,MAAMV,QAAQC,OAAO,CAACU,KAAK;QACjCD,IAAIlB,QAAQ,GAAG,CAAC,CAAC,EAAEW,SAASX,aAAa,MAAM,KAAKA,UAAU;QAE9D,OAAO;YACLU,MAAM;YACNU,QAAQ;gBAAEC,MAAMf;gBAAYW,OAAON;YAAO;YAC1CW,UAAUJ,IAAIK,QAAQ;QACxB;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,MAAMC,6BAA6B;IAAC;CAAmC,CAAA"} \ No newline at end of file diff --git a/dist/modules/i18n/localizationConfig.js b/dist/modules/i18n/localizationConfig.js index 8471755..42aaec1 100644 --- a/dist/modules/i18n/localizationConfig.js +++ b/dist/modules/i18n/localizationConfig.js @@ -14,6 +14,6 @@ } })) }; -} +} //# sourceMappingURL=localizationConfig.js.map //# sourceMappingURL=localizationConfig.js.map \ No newline at end of file diff --git a/dist/modules/i18n/localizationConfig.js.map b/dist/modules/i18n/localizationConfig.js.map index 0beb8b9..8eff423 100644 --- a/dist/modules/i18n/localizationConfig.js.map +++ b/dist/modules/i18n/localizationConfig.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/localizationConfig.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { I18nConfig } from './types.js'\n\ntype PayloadLocalizationConfig = NonNullable\n\n/**\n * Transforms validated i18n config into Payload's localization object.\n * Pure function — no side effects, no validation (caller validates first).\n */\nexport function buildLocalizationConfig(config: I18nConfig): PayloadLocalizationConfig {\n const { defaultLocale, fallback = true, locales } = config\n\n return {\n defaultLocale,\n fallback,\n locales: locales.map((locale) => ({\n code: locale.code,\n label: locale.label,\n ...(locale.rtl && { rtl: true }),\n })),\n }\n}\n"],"names":["buildLocalizationConfig","config","defaultLocale","fallback","locales","map","locale","code","label","rtl"],"mappings":"AAMA;;;CAGC,GACD,OAAO,SAASA,wBAAwBC,MAAkB;IACxD,MAAM,EAAEC,aAAa,EAAEC,WAAW,IAAI,EAAEC,OAAO,EAAE,GAAGH;IAEpD,OAAO;QACLC;QACAC;QACAC,SAASA,QAAQC,GAAG,CAAC,CAACC,SAAY,CAAA;gBAChCC,MAAMD,OAAOC,IAAI;gBACjBC,OAAOF,OAAOE,KAAK;gBACnB,GAAIF,OAAOG,GAAG,IAAI;oBAAEA,KAAK;gBAAK,CAAC;YACjC,CAAA;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["localizationConfig.js"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { I18nConfig } from './types.js'\n\ntype PayloadLocalizationConfig = NonNullable\n\n/**\n * Transforms validated i18n config into Payload's localization object.\n * Pure function — no side effects, no validation (caller validates first).\n */\nexport function buildLocalizationConfig(config: I18nConfig): PayloadLocalizationConfig {\n const { defaultLocale, fallback = true, locales } = config\n\n return {\n defaultLocale,\n fallback,\n locales: locales.map((locale) => ({\n code: locale.code,\n label: locale.label,\n ...(locale.rtl && { rtl: true }),\n })),\n }\n}\n"],"names":["buildLocalizationConfig","config","defaultLocale","fallback","locales","map","locale","code","label","rtl"],"mappings":"AAMA;;;CAGC,GACD,OAAO,SAASA,wBAAwBC,MAAkB;IACxD,MAAM,EAAEC,aAAa,EAAEC,WAAW,IAAI,EAAEC,OAAO,EAAE,GAAGH;IAEpD,OAAO;QACLC;QACAC;QACAC,SAASA,QAAQC,GAAG,CAAC,CAACC,SAAY,CAAA;gBAChCC,MAAMD,OAAOC,IAAI;gBACjBC,OAAOF,OAAOE,KAAK;gBACnB,GAAIF,OAAOG,GAAG,IAAI;oBAAEA,KAAK;gBAAK,CAAC;aACjC,CAAA;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/i18n/localizedPath.js b/dist/modules/i18n/localizedPath.js index 40a37be..aca6de6 100644 --- a/dist/modules/i18n/localizedPath.js +++ b/dist/modules/i18n/localizedPath.js @@ -66,6 +66,6 @@ import { isValidLocale } from './helpers.js'; return `/${targetLocale}/${archiveSegment}`; } return `/${targetLocale}`; -} +} //# sourceMappingURL=localizedPath.js.map //# sourceMappingURL=localizedPath.js.map \ No newline at end of file diff --git a/dist/modules/i18n/localizedPath.js.map b/dist/modules/i18n/localizedPath.js.map index 1f73694..8ec6f9f 100644 --- a/dist/modules/i18n/localizedPath.js.map +++ b/dist/modules/i18n/localizedPath.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/localizedPath.ts"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { isValidLocale } from './helpers.js'\n\n/**\n * Minimal shape the path builder needs from a document.\n *\n * The plugin doesn't know the client's Pages type, so it depends only on\n * this contract: a map of locale code → slug for that locale. The template\n * supplies it (e.g. by reading the localized slug field across locales).\n */\nexport type LocalizedSlugs = Record\n\ntype BuildPathArgs = {\n config: I18nConfig\n /**\n * Slug that represents the site root (served at /{locale} with no trailing\n * segment). Defaults to 'home'. Matched against the slug in the target locale.\n */\n homeSlug?: string\n /** Target locale to build the path for */\n locale: string\n /**\n * Localized segment the document lives under, e.g.\n * `{ pl: 'artykuly', en: 'articles' }` → /pl/artykuly/moj-post.\n *\n * These are the slugs of the collection's archive page, so the prefix is\n * whatever an editor named that page — and it differs per locale for free.\n * A document under a prefix is never the home page, so homeSlug is ignored.\n */\n prefix?: LocalizedSlugs\n /** slug per locale, e.g. { pl: 'strona-glowna', en: 'home' } */\n slugs: LocalizedSlugs\n}\n\n/**\n * Builds a locale-prefixed path for a document in a target locale.\n *\n * Always prefixes the locale: /{locale} or /{locale}/{slug}. The home slug\n * collapses to the locale root. Returns undefined if the document has no slug\n * in the target locale (caller decides fallback behavior).\n *\n * @example\n * buildLocalizedPath({ slugs: { pl: 'strona-glowna', en: 'home' }, locale: 'en', config })\n * // → '/en' (home slug collapses to root)\n *\n * buildLocalizedPath({ slugs: { pl: 'o-nas', en: 'about' }, locale: 'en', config })\n * // → '/en/about'\n *\n * buildLocalizedPath({\n * slugs: { pl: 'moj-post', en: 'my-post' },\n * prefix: { pl: 'artykuly', en: 'articles' },\n * locale: 'en',\n * config,\n * })\n * // → '/en/articles/my-post'\n */\nexport function buildLocalizedPath({\n config,\n homeSlug = 'home',\n locale,\n prefix,\n slugs,\n}: BuildPathArgs): string | undefined {\n if (!isValidLocale(locale, config)) {\n return undefined\n }\n\n const slug = slugs[locale]\n if (!slug) {\n return undefined\n }\n\n if (prefix) {\n const segment = prefix[locale]\n // No archive slug in this locale means the entry is unreachable there —\n // there's no path to point at, so hreflang should omit it rather than\n // invent /en/moj-post.\n if (!segment) {\n return undefined\n }\n return `/${locale}/${segment}/${slug}`\n }\n\n if (slug === homeSlug) {\n return `/${locale}`\n }\n\n return `/${locale}/${slug}`\n}\n\ntype SwitchLocaleArgs = {\n config: I18nConfig\n homeSlug?: string\n prefix?: LocalizedSlugs\n slugs: LocalizedSlugs\n targetLocale: string\n}\n\n/**\n * Resolves the equivalent path for the same document in a different locale —\n * the language-switcher use case (/pl/strona-glowna → /en/home).\n *\n * Never dead-ends on a 404. When the document has no slug in the target locale,\n * falls back to the archive it belongs to (/en/articles) if there is one, and\n * to the locale root otherwise — the closest place the visitor would want.\n */\nexport function switchLocalePath({\n config,\n homeSlug = 'home',\n prefix,\n slugs,\n targetLocale,\n}: SwitchLocaleArgs): string {\n const path = buildLocalizedPath({ config, homeSlug, locale: targetLocale, prefix, slugs })\n if (path) {return path}\n\n const archiveSegment = prefix?.[targetLocale]\n if (archiveSegment) {return `/${targetLocale}/${archiveSegment}`}\n\n return `/${targetLocale}`\n}\n"],"names":["isValidLocale","buildLocalizedPath","config","homeSlug","locale","prefix","slugs","undefined","slug","segment","switchLocalePath","targetLocale","path","archiveSegment"],"mappings":"AAEA,SAASA,aAAa,QAAQ,eAAc;AAiC5C;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,mBAAmB,EACjCC,MAAM,EACNC,WAAW,MAAM,EACjBC,MAAM,EACNC,MAAM,EACNC,KAAK,EACS;IACd,IAAI,CAACN,cAAcI,QAAQF,SAAS;QAClC,OAAOK;IACT;IAEA,MAAMC,OAAOF,KAAK,CAACF,OAAO;IAC1B,IAAI,CAACI,MAAM;QACT,OAAOD;IACT;IAEA,IAAIF,QAAQ;QACV,MAAMI,UAAUJ,MAAM,CAACD,OAAO;QAC9B,wEAAwE;QACxE,sEAAsE;QACtE,uBAAuB;QACvB,IAAI,CAACK,SAAS;YACZ,OAAOF;QACT;QACA,OAAO,CAAC,CAAC,EAAEH,OAAO,CAAC,EAAEK,QAAQ,CAAC,EAAED,MAAM;IACxC;IAEA,IAAIA,SAASL,UAAU;QACrB,OAAO,CAAC,CAAC,EAAEC,QAAQ;IACrB;IAEA,OAAO,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAEI,MAAM;AAC7B;AAUA;;;;;;;CAOC,GACD,OAAO,SAASE,iBAAiB,EAC/BR,MAAM,EACNC,WAAW,MAAM,EACjBE,MAAM,EACNC,KAAK,EACLK,YAAY,EACK;IACjB,MAAMC,OAAOX,mBAAmB;QAAEC;QAAQC;QAAUC,QAAQO;QAAcN;QAAQC;IAAM;IACxF,IAAIM,MAAM;QAAC,OAAOA;IAAI;IAEtB,MAAMC,iBAAiBR,QAAQ,CAACM,aAAa;IAC7C,IAAIE,gBAAgB;QAAC,OAAO,CAAC,CAAC,EAAEF,aAAa,CAAC,EAAEE,gBAAgB;IAAA;IAEhE,OAAO,CAAC,CAAC,EAAEF,cAAc;AAC3B"} \ No newline at end of file +{"version":3,"sources":["localizedPath.js"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { isValidLocale } from './helpers.js'\n\n/**\n * Minimal shape the path builder needs from a document.\n *\n * The plugin doesn't know the client's Pages type, so it depends only on\n * this contract: a map of locale code → slug for that locale. The template\n * supplies it (e.g. by reading the localized slug field across locales).\n */\nexport type LocalizedSlugs = Record\n\ntype BuildPathArgs = {\n config: I18nConfig\n /**\n * Slug that represents the site root (served at /{locale} with no trailing\n * segment). Defaults to 'home'. Matched against the slug in the target locale.\n */\n homeSlug?: string\n /** Target locale to build the path for */\n locale: string\n /**\n * Localized segment the document lives under, e.g.\n * `{ pl: 'artykuly', en: 'articles' }` → /pl/artykuly/moj-post.\n *\n * These are the slugs of the collection's archive page, so the prefix is\n * whatever an editor named that page — and it differs per locale for free.\n * A document under a prefix is never the home page, so homeSlug is ignored.\n */\n prefix?: LocalizedSlugs\n /** slug per locale, e.g. { pl: 'strona-glowna', en: 'home' } */\n slugs: LocalizedSlugs\n}\n\n/**\n * Builds a locale-prefixed path for a document in a target locale.\n *\n * Always prefixes the locale: /{locale} or /{locale}/{slug}. The home slug\n * collapses to the locale root. Returns undefined if the document has no slug\n * in the target locale (caller decides fallback behavior).\n *\n * @example\n * buildLocalizedPath({ slugs: { pl: 'strona-glowna', en: 'home' }, locale: 'en', config })\n * // → '/en' (home slug collapses to root)\n *\n * buildLocalizedPath({ slugs: { pl: 'o-nas', en: 'about' }, locale: 'en', config })\n * // → '/en/about'\n *\n * buildLocalizedPath({\n * slugs: { pl: 'moj-post', en: 'my-post' },\n * prefix: { pl: 'artykuly', en: 'articles' },\n * locale: 'en',\n * config,\n * })\n * // → '/en/articles/my-post'\n */\nexport function buildLocalizedPath({\n config,\n homeSlug = 'home',\n locale,\n prefix,\n slugs,\n}: BuildPathArgs): string | undefined {\n if (!isValidLocale(locale, config)) {\n return undefined\n }\n\n const slug = slugs[locale]\n if (!slug) {\n return undefined\n }\n\n if (prefix) {\n const segment = prefix[locale]\n // No archive slug in this locale means the entry is unreachable there —\n // there's no path to point at, so hreflang should omit it rather than\n // invent /en/moj-post.\n if (!segment) {\n return undefined\n }\n return `/${locale}/${segment}/${slug}`\n }\n\n if (slug === homeSlug) {\n return `/${locale}`\n }\n\n return `/${locale}/${slug}`\n}\n\ntype SwitchLocaleArgs = {\n config: I18nConfig\n homeSlug?: string\n prefix?: LocalizedSlugs\n slugs: LocalizedSlugs\n targetLocale: string\n}\n\n/**\n * Resolves the equivalent path for the same document in a different locale —\n * the language-switcher use case (/pl/strona-glowna → /en/home).\n *\n * Never dead-ends on a 404. When the document has no slug in the target locale,\n * falls back to the archive it belongs to (/en/articles) if there is one, and\n * to the locale root otherwise — the closest place the visitor would want.\n */\nexport function switchLocalePath({\n config,\n homeSlug = 'home',\n prefix,\n slugs,\n targetLocale,\n}: SwitchLocaleArgs): string {\n const path = buildLocalizedPath({ config, homeSlug, locale: targetLocale, prefix, slugs })\n if (path) {return path}\n\n const archiveSegment = prefix?.[targetLocale]\n if (archiveSegment) {return `/${targetLocale}/${archiveSegment}`}\n\n return `/${targetLocale}`\n}\n"],"names":["isValidLocale","buildLocalizedPath","config","homeSlug","locale","prefix","slugs","undefined","slug","segment","switchLocalePath","targetLocale","path","archiveSegment"],"mappings":"AAEA,SAASA,aAAa,QAAQ,eAAc;AAiC5C;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,mBAAmB,EACjCC,MAAM,EACNC,WAAW,MAAM,EACjBC,MAAM,EACNC,MAAM,EACNC,KAAK,EACS;IACd,IAAI,CAACN,cAAcI,QAAQF,SAAS;QAClC,OAAOK;IACT;IAEA,MAAMC,OAAOF,KAAK,CAACF,OAAO;IAC1B,IAAI,CAACI,MAAM;QACT,OAAOD;IACT;IAEA,IAAIF,QAAQ;QACV,MAAMI,UAAUJ,MAAM,CAACD,OAAO;QAC9B,wEAAwE;QACxE,sEAAsE;QACtE,uBAAuB;QACvB,IAAI,CAACK,SAAS;YACZ,OAAOF;QACT;QACA,OAAO,CAAC,CAAC,EAAEH,OAAO,CAAC,EAAEK,QAAQ,CAAC,EAAED,MAAM;IACxC;IAEA,IAAIA,SAASL,UAAU;QACrB,OAAO,CAAC,CAAC,EAAEC,QAAQ;IACrB;IAEA,OAAO,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAEI,MAAM;AAC7B;AAUA;;;;;;;CAOC,GACD,OAAO,SAASE,iBAAiB,EAC/BR,MAAM,EACNC,WAAW,MAAM,EACjBE,MAAM,EACNC,KAAK,EACLK,YAAY,EACK;IACjB,MAAMC,OAAOX,mBAAmB;QAAEC;QAAQC;QAAUC,QAAQO;QAAcN;QAAQC;IAAM;IACxF,IAAIM,MAAM;QAAC,OAAOA;IAAI;IAEtB,MAAMC,iBAAiBR,QAAQ,CAACM,aAAa;IAC7C,IAAIE,gBAAgB;QAAC,OAAO,CAAC,CAAC,EAAEF,aAAa,CAAC,EAAEE,gBAAgB;IAAA;IAEhE,OAAO,CAAC,CAAC,EAAEF,cAAc;AAC3B"} \ No newline at end of file diff --git a/dist/modules/i18n/negotiateLocale.js b/dist/modules/i18n/negotiateLocale.js index 7996d0e..31f9a43 100644 --- a/dist/modules/i18n/negotiateLocale.js +++ b/dist/modules/i18n/negotiateLocale.js @@ -63,6 +63,6 @@ import { getLocaleCodes, isValidLocale } from './helpers.js'; tag: tag.trim().toLowerCase() }; }).filter((entry)=>entry.tag && entry.tag !== '*').sort((a, b)=>b.quality - a.quality).map((entry)=>entry.tag); -} +} //# sourceMappingURL=negotiateLocale.js.map //# sourceMappingURL=negotiateLocale.js.map \ No newline at end of file diff --git a/dist/modules/i18n/negotiateLocale.js.map b/dist/modules/i18n/negotiateLocale.js.map index a5dd6ac..5609f5b 100644 --- a/dist/modules/i18n/negotiateLocale.js.map +++ b/dist/modules/i18n/negotiateLocale.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/negotiateLocale.ts"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { getLocaleCodes, isValidLocale } from './helpers.js'\n\n/** Cookie name the template uses to persist a visitor's locale choice. */\nexport const LOCALE_COOKIE_NAME = 'ipal-locale'\n\ntype NegotiateLocaleArgs = {\n /** Raw Accept-Language header value */\n acceptLanguage?: null | string\n config: I18nConfig\n /** Value of the locale cookie, if present (from LOCALE_COOKIE_NAME) */\n cookieLocale?: null | string\n}\n\n/**\n * Resolves which locale to serve, in priority order:\n * 1. Cookie (explicit prior choice)\n * 2. Accept-Language header (best match against configured locales)\n * 3. Configured default locale\n *\n * Pure function — the template feeds it request data and acts on the result\n * (redirect, cookie set). No Next.js or request objects here.\n */\nexport function negotiateLocale({\n acceptLanguage,\n config,\n cookieLocale,\n}: NegotiateLocaleArgs): string {\n // 1. Explicit prior choice wins\n if (cookieLocale && isValidLocale(cookieLocale, config)) {\n return cookieLocale\n }\n\n // 2. Best match from Accept-Language\n const fromHeader = matchAcceptLanguage(acceptLanguage, config)\n if (fromHeader) {\n return fromHeader\n }\n\n // 3. Fall back to configured default\n return config.defaultLocale\n}\n\n/**\n * Parses an Accept-Language header and returns the best-matching configured\n * locale, or undefined if none match.\n *\n * Matches on the primary subtag (e.g. \"en-US\" matches configured \"en\"),\n * respecting the header's quality-value ordering.\n */\nexport function matchAcceptLanguage(\n acceptLanguage: null | string | undefined,\n config: I18nConfig,\n): string | undefined {\n if (!acceptLanguage) {return undefined}\n\n const available = getLocaleCodes(config)\n const ranked = parseAcceptLanguage(acceptLanguage)\n\n for (const tag of ranked) {\n // Exact match (e.g. \"pt-BR\" === \"pt-BR\")\n const exact = available.find((code) => code.toLowerCase() === tag)\n if (exact) {return exact}\n\n // Primary-subtag match (e.g. \"en-us\" → \"en\")\n const primary = tag.split('-')[0]\n const partial = available.find((code) => code.toLowerCase().split('-')[0] === primary)\n if (partial) {return partial}\n }\n\n return undefined\n}\n\n/**\n * Parses an Accept-Language header into locale tags ordered by descending\n * quality value. Tags are lowercased for comparison.\n *\n * \"en-US,en;q=0.9,pl;q=0.8\" → [\"en-us\", \"en\", \"pl\"]\n */\nfunction parseAcceptLanguage(header: string): string[] {\n return header\n .split(',')\n .map((part) => {\n const [tag, ...params] = part.trim().split(';')\n const qParam = params.find((p) => p.trim().startsWith('q='))\n const quality = qParam ? parseFloat(qParam.split('=')[1]) : 1\n return { quality: Number.isNaN(quality) ? 0 : quality, tag: tag.trim().toLowerCase() }\n })\n .filter((entry) => entry.tag && entry.tag !== '*')\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.tag)\n}\n"],"names":["getLocaleCodes","isValidLocale","LOCALE_COOKIE_NAME","negotiateLocale","acceptLanguage","config","cookieLocale","fromHeader","matchAcceptLanguage","defaultLocale","undefined","available","ranked","parseAcceptLanguage","tag","exact","find","code","toLowerCase","primary","split","partial","header","map","part","params","trim","qParam","p","startsWith","quality","parseFloat","Number","isNaN","filter","entry","sort","a","b"],"mappings":"AAEA,SAASA,cAAc,EAAEC,aAAa,QAAQ,eAAc;AAE5D,wEAAwE,GACxE,OAAO,MAAMC,qBAAqB,cAAa;AAU/C;;;;;;;;CAQC,GACD,OAAO,SAASC,gBAAgB,EAC9BC,cAAc,EACdC,MAAM,EACNC,YAAY,EACQ;IACpB,gCAAgC;IAChC,IAAIA,gBAAgBL,cAAcK,cAAcD,SAAS;QACvD,OAAOC;IACT;IAEA,qCAAqC;IACrC,MAAMC,aAAaC,oBAAoBJ,gBAAgBC;IACvD,IAAIE,YAAY;QACd,OAAOA;IACT;IAEA,qCAAqC;IACrC,OAAOF,OAAOI,aAAa;AAC7B;AAEA;;;;;;CAMC,GACD,OAAO,SAASD,oBACdJ,cAAyC,EACzCC,MAAkB;IAElB,IAAI,CAACD,gBAAgB;QAAC,OAAOM;IAAS;IAEtC,MAAMC,YAAYX,eAAeK;IACjC,MAAMO,SAASC,oBAAoBT;IAEnC,KAAK,MAAMU,OAAOF,OAAQ;QACxB,yCAAyC;QACzC,MAAMG,QAAQJ,UAAUK,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,OAAOJ;QAC9D,IAAIC,OAAO;YAAC,OAAOA;QAAK;QAExB,6CAA6C;QAC7C,MAAMI,UAAUL,IAAIM,KAAK,CAAC,IAAI,CAAC,EAAE;QACjC,MAAMC,UAAUV,UAAUK,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,GAAGE,KAAK,CAAC,IAAI,CAAC,EAAE,KAAKD;QAC9E,IAAIE,SAAS;YAAC,OAAOA;QAAO;IAC9B;IAEA,OAAOX;AACT;AAEA;;;;;CAKC,GACD,SAASG,oBAAoBS,MAAc;IACzC,OAAOA,OACJF,KAAK,CAAC,KACNG,GAAG,CAAC,CAACC;QACJ,MAAM,CAACV,KAAK,GAAGW,OAAO,GAAGD,KAAKE,IAAI,GAAGN,KAAK,CAAC;QAC3C,MAAMO,SAASF,OAAOT,IAAI,CAAC,CAACY,IAAMA,EAAEF,IAAI,GAAGG,UAAU,CAAC;QACtD,MAAMC,UAAUH,SAASI,WAAWJ,OAAOP,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;QAC5D,OAAO;YAAEU,SAASE,OAAOC,KAAK,CAACH,WAAW,IAAIA;YAAShB,KAAKA,IAAIY,IAAI,GAAGR,WAAW;QAAG;IACvF,GACCgB,MAAM,CAAC,CAACC,QAAUA,MAAMrB,GAAG,IAAIqB,MAAMrB,GAAG,KAAK,KAC7CsB,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAER,OAAO,GAAGO,EAAEP,OAAO,EACpCP,GAAG,CAAC,CAACY,QAAUA,MAAMrB,GAAG;AAC7B"} \ No newline at end of file +{"version":3,"sources":["negotiateLocale.js"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { getLocaleCodes, isValidLocale } from './helpers.js'\n\n/** Cookie name the template uses to persist a visitor's locale choice. */\nexport const LOCALE_COOKIE_NAME = 'ipal-locale'\n\ntype NegotiateLocaleArgs = {\n /** Raw Accept-Language header value */\n acceptLanguage?: null | string\n config: I18nConfig\n /** Value of the locale cookie, if present (from LOCALE_COOKIE_NAME) */\n cookieLocale?: null | string\n}\n\n/**\n * Resolves which locale to serve, in priority order:\n * 1. Cookie (explicit prior choice)\n * 2. Accept-Language header (best match against configured locales)\n * 3. Configured default locale\n *\n * Pure function — the template feeds it request data and acts on the result\n * (redirect, cookie set). No Next.js or request objects here.\n */\nexport function negotiateLocale({\n acceptLanguage,\n config,\n cookieLocale,\n}: NegotiateLocaleArgs): string {\n // 1. Explicit prior choice wins\n if (cookieLocale && isValidLocale(cookieLocale, config)) {\n return cookieLocale\n }\n\n // 2. Best match from Accept-Language\n const fromHeader = matchAcceptLanguage(acceptLanguage, config)\n if (fromHeader) {\n return fromHeader\n }\n\n // 3. Fall back to configured default\n return config.defaultLocale\n}\n\n/**\n * Parses an Accept-Language header and returns the best-matching configured\n * locale, or undefined if none match.\n *\n * Matches on the primary subtag (e.g. \"en-US\" matches configured \"en\"),\n * respecting the header's quality-value ordering.\n */\nexport function matchAcceptLanguage(\n acceptLanguage: null | string | undefined,\n config: I18nConfig,\n): string | undefined {\n if (!acceptLanguage) {return undefined}\n\n const available = getLocaleCodes(config)\n const ranked = parseAcceptLanguage(acceptLanguage)\n\n for (const tag of ranked) {\n // Exact match (e.g. \"pt-BR\" === \"pt-BR\")\n const exact = available.find((code) => code.toLowerCase() === tag)\n if (exact) {return exact}\n\n // Primary-subtag match (e.g. \"en-us\" → \"en\")\n const primary = tag.split('-')[0]\n const partial = available.find((code) => code.toLowerCase().split('-')[0] === primary)\n if (partial) {return partial}\n }\n\n return undefined\n}\n\n/**\n * Parses an Accept-Language header into locale tags ordered by descending\n * quality value. Tags are lowercased for comparison.\n *\n * \"en-US,en;q=0.9,pl;q=0.8\" → [\"en-us\", \"en\", \"pl\"]\n */\nfunction parseAcceptLanguage(header: string): string[] {\n return header\n .split(',')\n .map((part) => {\n const [tag, ...params] = part.trim().split(';')\n const qParam = params.find((p) => p.trim().startsWith('q='))\n const quality = qParam ? parseFloat(qParam.split('=')[1]) : 1\n return { quality: Number.isNaN(quality) ? 0 : quality, tag: tag.trim().toLowerCase() }\n })\n .filter((entry) => entry.tag && entry.tag !== '*')\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.tag)\n}\n"],"names":["getLocaleCodes","isValidLocale","LOCALE_COOKIE_NAME","negotiateLocale","acceptLanguage","config","cookieLocale","fromHeader","matchAcceptLanguage","defaultLocale","undefined","available","ranked","parseAcceptLanguage","tag","exact","find","code","toLowerCase","primary","split","partial","header","map","part","params","trim","qParam","p","startsWith","quality","parseFloat","Number","isNaN","filter","entry","sort","a","b"],"mappings":"AAEA,SAASA,cAAc,EAAEC,aAAa,QAAQ,eAAc;AAE5D,wEAAwE,GACxE,OAAO,MAAMC,qBAAqB,cAAa;AAU/C;;;;;;;;CAQC,GACD,OAAO,SAASC,gBAAgB,EAC9BC,cAAc,EACdC,MAAM,EACNC,YAAY,EACQ;IACpB,gCAAgC;IAChC,IAAIA,gBAAgBL,cAAcK,cAAcD,SAAS;QACvD,OAAOC;IACT;IAEA,qCAAqC;IACrC,MAAMC,aAAaC,oBAAoBJ,gBAAgBC;IACvD,IAAIE,YAAY;QACd,OAAOA;IACT;IAEA,qCAAqC;IACrC,OAAOF,OAAOI,aAAa;AAC7B;AAEA;;;;;;CAMC,GACD,OAAO,SAASD,oBACdJ,cAAyC,EACzCC,MAAkB;IAElB,IAAI,CAACD,gBAAgB;QAAC,OAAOM;IAAS;IAEtC,MAAMC,YAAYX,eAAeK;IACjC,MAAMO,SAASC,oBAAoBT;IAEnC,KAAK,MAAMU,OAAOF,OAAQ;QACxB,yCAAyC;QACzC,MAAMG,QAAQJ,UAAUK,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,OAAOJ;QAC9D,IAAIC,OAAO;YAAC,OAAOA;QAAK;QAExB,6CAA6C;QAC7C,MAAMI,UAAUL,IAAIM,KAAK,CAAC,IAAI,CAAC,EAAE;QACjC,MAAMC,UAAUV,UAAUK,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,GAAGE,KAAK,CAAC,IAAI,CAAC,EAAE,KAAKD;QAC9E,IAAIE,SAAS;YAAC,OAAOA;QAAO;IAC9B;IAEA,OAAOX;AACT;AAEA;;;;;CAKC,GACD,SAASG,oBAAoBS,MAAc;IACzC,OAAOA,OACJF,KAAK,CAAC,KACNG,GAAG,CAAC,CAACC;QACJ,MAAM,CAACV,KAAK,GAAGW,OAAO,GAAGD,KAAKE,IAAI,GAAGN,KAAK,CAAC;QAC3C,MAAMO,SAASF,OAAOT,IAAI,CAAC,CAACY,IAAMA,EAAEF,IAAI,GAAGG,UAAU,CAAC;QACtD,MAAMC,UAAUH,SAASI,WAAWJ,OAAOP,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;QAC5D,OAAO;YAAEU,SAASE,OAAOC,KAAK,CAACH,WAAW,IAAIA;YAAShB,KAAKA,IAAIY,IAAI,GAAGR,WAAW;QAAG;IACvF,GACCgB,MAAM,CAAC,CAACC,QAAUA,MAAMrB,GAAG,IAAIqB,MAAMrB,GAAG,KAAK,KAC7CsB,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAER,OAAO,GAAGO,EAAEP,OAAO,EACpCP,GAAG,CAAC,CAACY,QAAUA,MAAMrB,GAAG;AAC7B"} \ No newline at end of file diff --git a/dist/modules/i18n/types.js b/dist/modules/i18n/types.js index 550d3a4..5f4b246 100644 --- a/dist/modules/i18n/types.js +++ b/dist/modules/i18n/types.js @@ -2,6 +2,6 @@ * Single locale definition provided by the client project. */ /** * Full i18n configuration passed through plugin options. - */ export { }; + */ export { }; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/i18n/types.js.map b/dist/modules/i18n/types.js.map index a962d21..eee6181 100644 --- a/dist/modules/i18n/types.js.map +++ b/dist/modules/i18n/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/types.ts"],"sourcesContent":["/**\n * Single locale definition provided by the client project.\n */\nexport type LocaleDefinition = {\n /** BCP-47 language code, e.g. 'pl', 'en', 'de' */\n code: string\n /** Human-readable label, e.g. 'Polski', 'English' */\n label: string\n /** Right-to-left script (defaults to false) */\n rtl?: boolean\n}\n\n/**\n * Full i18n configuration passed through plugin options.\n */\nexport type I18nConfig = {\n defaultLocale: string\n /** Enable locale fallback when content is missing (defaults to true) */\n fallback?: boolean\n locales: [LocaleDefinition, ...LocaleDefinition[]]\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAUD;;CAEC,GACD,WAKC"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["/**\n * Single locale definition provided by the client project.\n */\nexport type LocaleDefinition = {\n /** BCP-47 language code, e.g. 'pl', 'en', 'de' */\n code: string\n /** Human-readable label, e.g. 'Polski', 'English' */\n label: string\n /** Right-to-left script (defaults to false) */\n rtl?: boolean\n}\n\n/**\n * Full i18n configuration passed through plugin options.\n */\nexport type I18nConfig = {\n defaultLocale: string\n /** Enable locale fallback when content is missing (defaults to true) */\n fallback?: boolean\n locales: [LocaleDefinition, ...LocaleDefinition[]]\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAUD;;CAEC,GACD,WAKC"} \ No newline at end of file diff --git a/dist/modules/i18n/validation.js b/dist/modules/i18n/validation.js index 0f82961..75e0bfa 100644 --- a/dist/modules/i18n/validation.js +++ b/dist/modules/i18n/validation.js @@ -25,6 +25,6 @@ const LOCALE_CODE_PATTERN = /^[a-z]{2,3}(-[A-Z]{2})?$/; ...codes ].join(', ')}].`); } -} +} //# sourceMappingURL=validation.js.map //# sourceMappingURL=validation.js.map \ No newline at end of file diff --git a/dist/modules/i18n/validation.js.map b/dist/modules/i18n/validation.js.map index e231400..46476f3 100644 --- a/dist/modules/i18n/validation.js.map +++ b/dist/modules/i18n/validation.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/i18n/validation.ts"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nconst LOCALE_CODE_PATTERN = /^[a-z]{2,3}(-[A-Z]{2})?$/\n\n/**\n * Validates i18n config at plugin initialization.\n * Throws descriptive errors — fail fast, no silent defaults.\n */\nexport function validateI18nConfig(config: I18nConfig): void {\n const { defaultLocale, locales } = config\n\n if (!locales?.length) {\n throw new Error('[ipal] i18n: \"locales\" must contain at least one locale.')\n }\n\n const codes = new Set()\n\n for (const locale of locales) {\n if (!locale.code || !locale.label) {\n throw new Error(\n `[ipal] i18n: Every locale must have \"code\" and \"label\". Received: ${JSON.stringify(locale)}`,\n )\n }\n\n if (!LOCALE_CODE_PATTERN.test(locale.code)) {\n throw new Error(\n `[ipal] i18n: Invalid locale code \"${locale.code}\". Expected format: \"pl\", \"en\", \"pt-BR\".`,\n )\n }\n\n if (codes.has(locale.code)) {\n throw new Error(`[ipal] i18n: Duplicate locale code \"${locale.code}\".`)\n }\n\n codes.add(locale.code)\n }\n\n if (!codes.has(defaultLocale)) {\n throw new Error(\n `[ipal] i18n: defaultLocale \"${defaultLocale}\" not found in locales [${[...codes].join(', ')}].`,\n )\n }\n}\n"],"names":["LOCALE_CODE_PATTERN","validateI18nConfig","config","defaultLocale","locales","length","Error","codes","Set","locale","code","label","JSON","stringify","test","has","add","join"],"mappings":"AAEA,MAAMA,sBAAsB;AAE5B;;;CAGC,GACD,OAAO,SAASC,mBAAmBC,MAAkB;IACnD,MAAM,EAAEC,aAAa,EAAEC,OAAO,EAAE,GAAGF;IAEnC,IAAI,CAACE,SAASC,QAAQ;QACpB,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,QAAQ,IAAIC;IAElB,KAAK,MAAMC,UAAUL,QAAS;QAC5B,IAAI,CAACK,OAAOC,IAAI,IAAI,CAACD,OAAOE,KAAK,EAAE;YACjC,MAAM,IAAIL,MACR,CAAC,kEAAkE,EAAEM,KAAKC,SAAS,CAACJ,SAAS;QAEjG;QAEA,IAAI,CAACT,oBAAoBc,IAAI,CAACL,OAAOC,IAAI,GAAG;YAC1C,MAAM,IAAIJ,MACR,CAAC,kCAAkC,EAAEG,OAAOC,IAAI,CAAC,wCAAwC,CAAC;QAE9F;QAEA,IAAIH,MAAMQ,GAAG,CAACN,OAAOC,IAAI,GAAG;YAC1B,MAAM,IAAIJ,MAAM,CAAC,oCAAoC,EAAEG,OAAOC,IAAI,CAAC,EAAE,CAAC;QACxE;QAEAH,MAAMS,GAAG,CAACP,OAAOC,IAAI;IACvB;IAEA,IAAI,CAACH,MAAMQ,GAAG,CAACZ,gBAAgB;QAC7B,MAAM,IAAIG,MACR,CAAC,4BAA4B,EAAEH,cAAc,wBAAwB,EAAE;eAAII;SAAM,CAACU,IAAI,CAAC,MAAM,EAAE,CAAC;IAEpG;AACF"} \ No newline at end of file +{"version":3,"sources":["validation.js"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nconst LOCALE_CODE_PATTERN = /^[a-z]{2,3}(-[A-Z]{2})?$/\n\n/**\n * Validates i18n config at plugin initialization.\n * Throws descriptive errors — fail fast, no silent defaults.\n */\nexport function validateI18nConfig(config: I18nConfig): void {\n const { defaultLocale, locales } = config\n\n if (!locales?.length) {\n throw new Error('[ipal] i18n: \"locales\" must contain at least one locale.')\n }\n\n const codes = new Set()\n\n for (const locale of locales) {\n if (!locale.code || !locale.label) {\n throw new Error(\n `[ipal] i18n: Every locale must have \"code\" and \"label\". Received: ${JSON.stringify(locale)}`,\n )\n }\n\n if (!LOCALE_CODE_PATTERN.test(locale.code)) {\n throw new Error(\n `[ipal] i18n: Invalid locale code \"${locale.code}\". Expected format: \"pl\", \"en\", \"pt-BR\".`,\n )\n }\n\n if (codes.has(locale.code)) {\n throw new Error(`[ipal] i18n: Duplicate locale code \"${locale.code}\".`)\n }\n\n codes.add(locale.code)\n }\n\n if (!codes.has(defaultLocale)) {\n throw new Error(\n `[ipal] i18n: defaultLocale \"${defaultLocale}\" not found in locales [${[...codes].join(', ')}].`,\n )\n }\n}\n"],"names":["LOCALE_CODE_PATTERN","validateI18nConfig","config","defaultLocale","locales","length","Error","codes","Set","locale","code","label","JSON","stringify","test","has","add","join"],"mappings":"AAEA,MAAMA,sBAAsB;AAE5B;;;CAGC,GACD,OAAO,SAASC,mBAAmBC,MAAkB;IACnD,MAAM,EAAEC,aAAa,EAAEC,OAAO,EAAE,GAAGF;IAEnC,IAAI,CAACE,SAASC,QAAQ;QACpB,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,QAAQ,IAAIC;IAElB,KAAK,MAAMC,UAAUL,QAAS;QAC5B,IAAI,CAACK,OAAOC,IAAI,IAAI,CAACD,OAAOE,KAAK,EAAE;YACjC,MAAM,IAAIL,MACR,CAAC,kEAAkE,EAAEM,KAAKC,SAAS,CAACJ,SAAS;QAEjG;QAEA,IAAI,CAACT,oBAAoBc,IAAI,CAACL,OAAOC,IAAI,GAAG;YAC1C,MAAM,IAAIJ,MACR,CAAC,kCAAkC,EAAEG,OAAOC,IAAI,CAAC,wCAAwC,CAAC;QAE9F;QAEA,IAAIH,MAAMQ,GAAG,CAACN,OAAOC,IAAI,GAAG;YAC1B,MAAM,IAAIJ,MAAM,CAAC,oCAAoC,EAAEG,OAAOC,IAAI,CAAC,EAAE,CAAC;QACxE;QAEAH,MAAMS,GAAG,CAACP,OAAOC,IAAI;IACvB;IAEA,IAAI,CAACH,MAAMQ,GAAG,CAACZ,gBAAgB;QAC7B,MAAM,IAAIG,MACR,CAAC,4BAA4B,EAAEH,cAAc,wBAAwB,EAAE;eAAII;SAAM,CAACU,IAAI,CAAC,MAAM,EAAE,CAAC;IAEpG;AACF"} \ No newline at end of file diff --git a/dist/modules/pages/getSystemPagePath.js b/dist/modules/pages/getSystemPagePath.js index 2e69747..280c22c 100644 --- a/dist/modules/pages/getSystemPagePath.js +++ b/dist/modules/pages/getSystemPagePath.js @@ -31,6 +31,6 @@ import { buildLocalizedPath, getLocalizedSlugs } from '../i18n/index.js'; locale, slugs }); -} +} //# sourceMappingURL=getSystemPagePath.js.map //# sourceMappingURL=getSystemPagePath.js.map \ No newline at end of file diff --git a/dist/modules/pages/getSystemPagePath.js.map b/dist/modules/pages/getSystemPagePath.js.map index 5ca5cca..2119353 100644 --- a/dist/modules/pages/getSystemPagePath.js.map +++ b/dist/modules/pages/getSystemPagePath.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/pages/getSystemPagePath.ts"],"sourcesContent":["import type { I18nConfig } from '../i18n/index.js'\nimport type { SystemPageRole } from './types.js'\n\nimport { buildLocalizedPath, getLocalizedSlugs } from '../i18n/index.js'\n\n/**\n * The shape we need from an assigned system-page document: its localized slug\n * field, as returned by Payload when the parent is read with `locale: 'all'`.\n *\n * The plugin doesn't know the client's Pages type, so it depends only on this\n * minimal contract.\n */\ntype AssignedPage = {\n slug?: null | Record\n}\n\ntype GetSystemPagePathArgs = {\n config: I18nConfig\n /**\n * Slug that represents the site root. Defaults to 'home'. When the assigned\n * page's slug in the target locale equals this, the path collapses to the\n * locale root (/pl, /en).\n */\n homeSlug?: string\n /** Target locale to build the path for. */\n locale: string\n /**\n * The resolved system-page assignment from SiteSettings — the related\n * document object (not just an ID), read with `locale: 'all'` so its slug\n * is a locale→value map. Pass null/undefined if the role is unassigned.\n */\n page: AssignedPage | null | undefined\n}\n\n/**\n * Resolves a system-page assignment to a locale-aware path.\n *\n * Bridges the Pages module (which document plays a role) and the i18n module\n * (how that document's localized slug becomes a URL). This is what powers\n * \"visit /pl → serve the homepage\": read SiteSettings.homepage, pass the\n * related document here, get /pl/strona-glowna (or /pl if it's the home slug).\n *\n * Returns undefined when the role is unassigned or the assigned page has no\n * slug in the target locale — the caller decides the fallback (e.g. 404,\n * redirect to default locale).\n *\n * @example\n * const settings = await payload.findGlobal({ slug: 'site-settings', locale: 'all', depth: 1 })\n * getSystemPagePath({ page: settings.homepage, locale: 'pl', config })\n * // → '/pl' (home slug collapses to root)\n * getSystemPagePath({ page: settings.privacyPolicy, locale: 'en', config })\n * // → '/en/privacy-policy'\n */\nexport function getSystemPagePath({\n config,\n homeSlug = 'home',\n locale,\n page,\n}: GetSystemPagePathArgs): string | undefined {\n if (!page?.slug) {\n return undefined\n }\n\n const slugs = getLocalizedSlugs({ config, slugField: page.slug })\n return buildLocalizedPath({ config, homeSlug, locale, slugs })\n}\n"],"names":["buildLocalizedPath","getLocalizedSlugs","getSystemPagePath","config","homeSlug","locale","page","slug","undefined","slugs","slugField"],"mappings":"AAGA,SAASA,kBAAkB,EAAEC,iBAAiB,QAAQ,mBAAkB;AA+BxE;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,kBAAkB,EAChCC,MAAM,EACNC,WAAW,MAAM,EACjBC,MAAM,EACNC,IAAI,EACkB;IACtB,IAAI,CAACA,MAAMC,MAAM;QACf,OAAOC;IACT;IAEA,MAAMC,QAAQR,kBAAkB;QAAEE;QAAQO,WAAWJ,KAAKC,IAAI;IAAC;IAC/D,OAAOP,mBAAmB;QAAEG;QAAQC;QAAUC;QAAQI;IAAM;AAC9D"} \ No newline at end of file +{"version":3,"sources":["getSystemPagePath.js"],"sourcesContent":["import type { I18nConfig } from '../i18n/index.js'\nimport type { SystemPageRole } from './types.js'\n\nimport { buildLocalizedPath, getLocalizedSlugs } from '../i18n/index.js'\n\n/**\n * The shape we need from an assigned system-page document: its localized slug\n * field, as returned by Payload when the parent is read with `locale: 'all'`.\n *\n * The plugin doesn't know the client's Pages type, so it depends only on this\n * minimal contract.\n */\ntype AssignedPage = {\n slug?: null | Record\n}\n\ntype GetSystemPagePathArgs = {\n config: I18nConfig\n /**\n * Slug that represents the site root. Defaults to 'home'. When the assigned\n * page's slug in the target locale equals this, the path collapses to the\n * locale root (/pl, /en).\n */\n homeSlug?: string\n /** Target locale to build the path for. */\n locale: string\n /**\n * The resolved system-page assignment from SiteSettings — the related\n * document object (not just an ID), read with `locale: 'all'` so its slug\n * is a locale→value map. Pass null/undefined if the role is unassigned.\n */\n page: AssignedPage | null | undefined\n}\n\n/**\n * Resolves a system-page assignment to a locale-aware path.\n *\n * Bridges the Pages module (which document plays a role) and the i18n module\n * (how that document's localized slug becomes a URL). This is what powers\n * \"visit /pl → serve the homepage\": read SiteSettings.homepage, pass the\n * related document here, get /pl/strona-glowna (or /pl if it's the home slug).\n *\n * Returns undefined when the role is unassigned or the assigned page has no\n * slug in the target locale — the caller decides the fallback (e.g. 404,\n * redirect to default locale).\n *\n * @example\n * const settings = await payload.findGlobal({ slug: 'site-settings', locale: 'all', depth: 1 })\n * getSystemPagePath({ page: settings.homepage, locale: 'pl', config })\n * // → '/pl' (home slug collapses to root)\n * getSystemPagePath({ page: settings.privacyPolicy, locale: 'en', config })\n * // → '/en/privacy-policy'\n */\nexport function getSystemPagePath({\n config,\n homeSlug = 'home',\n locale,\n page,\n}: GetSystemPagePathArgs): string | undefined {\n if (!page?.slug) {\n return undefined\n }\n\n const slugs = getLocalizedSlugs({ config, slugField: page.slug })\n return buildLocalizedPath({ config, homeSlug, locale, slugs })\n}\n"],"names":["buildLocalizedPath","getLocalizedSlugs","getSystemPagePath","config","homeSlug","locale","page","slug","undefined","slugs","slugField"],"mappings":"AAGA,SAASA,kBAAkB,EAAEC,iBAAiB,QAAQ,mBAAkB;AA+BxE;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,kBAAkB,EAChCC,MAAM,EACNC,WAAW,MAAM,EACjBC,MAAM,EACNC,IAAI,EACkB;IACtB,IAAI,CAACA,MAAMC,MAAM;QACf,OAAOC;IACT;IAEA,MAAMC,QAAQR,kBAAkB;QAAEE;QAAQO,WAAWJ,KAAKC,IAAI;IAAC;IAC/D,OAAOP,mBAAmB;QAAEG;QAAQC;QAAUC;QAAQI;IAAM;AAC9D"} \ No newline at end of file diff --git a/dist/modules/pages/index.js b/dist/modules/pages/index.js index 1a4d929..61dc3e3 100644 --- a/dist/modules/pages/index.js +++ b/dist/modules/pages/index.js @@ -1,5 +1,5 @@ export { getSystemPagePath } from './getSystemPagePath.js'; export { buildSystemPagesFields } from './systemPagesFields.js'; -export { ALL_SYSTEM_PAGE_ROLES } from './types.js'; +export { ALL_SYSTEM_PAGE_ROLES } from './types.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/pages/index.js.map b/dist/modules/pages/index.js.map index 14e6426..2722d81 100644 --- a/dist/modules/pages/index.js.map +++ b/dist/modules/pages/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/pages/index.ts"],"sourcesContent":["export { getSystemPagePath } from './getSystemPagePath.js'\nexport { buildSystemPagesFields } from './systemPagesFields.js'\nexport type { PagesOption, SystemPageRole } from './types.js'\nexport { ALL_SYSTEM_PAGE_ROLES } from './types.js'\n"],"names":["getSystemPagePath","buildSystemPagesFields","ALL_SYSTEM_PAGE_ROLES"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,yBAAwB;AAC1D,SAASC,sBAAsB,QAAQ,yBAAwB;AAE/D,SAASC,qBAAqB,QAAQ,aAAY"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export { getSystemPagePath } from './getSystemPagePath.js'\nexport { buildSystemPagesFields } from './systemPagesFields.js'\nexport type { PagesOption, SystemPageRole } from './types.js'\nexport { ALL_SYSTEM_PAGE_ROLES } from './types.js'\n"],"names":["getSystemPagePath","buildSystemPagesFields","ALL_SYSTEM_PAGE_ROLES"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,yBAAwB;AAC1D,SAASC,sBAAsB,QAAQ,yBAAwB;AAE/D,SAASC,qBAAqB,QAAQ,aAAY"} \ No newline at end of file diff --git a/dist/modules/pages/systemPagesFields.js b/dist/modules/pages/systemPagesFields.js index 8ce13d4..bc3c230 100644 --- a/dist/modules/pages/systemPagesFields.js +++ b/dist/modules/pages/systemPagesFields.js @@ -31,6 +31,6 @@ import { ALL_SYSTEM_PAGE_ROLES } from './types.js'; maxDepth: 1, relationTo: pages.slug })); -} +} //# sourceMappingURL=systemPagesFields.js.map //# sourceMappingURL=systemPagesFields.js.map \ No newline at end of file diff --git a/dist/modules/pages/systemPagesFields.js.map b/dist/modules/pages/systemPagesFields.js.map index c8cfe91..d4e80f0 100644 --- a/dist/modules/pages/systemPagesFields.js.map +++ b/dist/modules/pages/systemPagesFields.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/pages/systemPagesFields.ts"],"sourcesContent":["import type { Field, RelationshipField } from 'payload'\n\nimport type { PagesOption, SystemPageRole } from './types.js'\n\nimport { ALL_SYSTEM_PAGE_ROLES } from './types.js'\n\n/** Admin-facing labels per role. */\nconst ROLE_LABELS: Record = {\n cookiePolicy: 'Cookie Policy',\n homepage: 'Homepage',\n privacyPolicy: 'Privacy Policy',\n}\n\n/** Admin descriptions per role. */\nconst ROLE_DESCRIPTIONS: Record = {\n cookiePolicy: 'Page linked from the cookie consent banner.',\n homepage: 'Page served at the locale root (e.g. /pl, /en).',\n privacyPolicy: 'Page linked as the privacy policy.',\n}\n\n/**\n * Builds one relationship field per configured system-page role.\n *\n * Each field points at the client's Pages collection (via the provided slug)\n * and holds a single document reference. The referenced document's slug is\n * localized, so the same assignment resolves to /pl/strona-glowna and\n * /en/home at runtime — role assignment is locale-agnostic, path resolution\n * is locale-aware (see getSystemPagePath).\n */\nexport function buildSystemPagesFields(pages: PagesOption): Field[] {\n const roles = pages.roles?.length ? pages.roles : [...ALL_SYSTEM_PAGE_ROLES]\n\n return roles.map((role): RelationshipField => ({\n name: role,\n type: 'relationship',\n admin: {\n description: ROLE_DESCRIPTIONS[role],\n },\n label: ROLE_LABELS[role],\n maxDepth: 1,\n relationTo: pages.slug,\n }))\n}\n"],"names":["ALL_SYSTEM_PAGE_ROLES","ROLE_LABELS","cookiePolicy","homepage","privacyPolicy","ROLE_DESCRIPTIONS","buildSystemPagesFields","pages","roles","length","map","role","name","type","admin","description","label","maxDepth","relationTo","slug"],"mappings":"AAIA,SAASA,qBAAqB,QAAQ,aAAY;AAElD,kCAAkC,GAClC,MAAMC,cAA8C;IAClDC,cAAc;IACdC,UAAU;IACVC,eAAe;AACjB;AAEA,iCAAiC,GACjC,MAAMC,oBAAoD;IACxDH,cAAc;IACdC,UAAU;IACVC,eAAe;AACjB;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASE,uBAAuBC,KAAkB;IACvD,MAAMC,QAAQD,MAAMC,KAAK,EAAEC,SAASF,MAAMC,KAAK,GAAG;WAAIR;KAAsB;IAE5E,OAAOQ,MAAME,GAAG,CAAC,CAACC,OAA6B,CAAA;YAC7CC,MAAMD;YACNE,MAAM;YACNC,OAAO;gBACLC,aAAaV,iBAAiB,CAACM,KAAK;YACtC;YACAK,OAAOf,WAAW,CAACU,KAAK;YACxBM,UAAU;YACVC,YAAYX,MAAMY,IAAI;QACxB,CAAA;AACF"} \ No newline at end of file +{"version":3,"sources":["systemPagesFields.js"],"sourcesContent":["import type { Field, RelationshipField } from 'payload'\n\nimport type { PagesOption, SystemPageRole } from './types.js'\n\nimport { ALL_SYSTEM_PAGE_ROLES } from './types.js'\n\n/** Admin-facing labels per role. */\nconst ROLE_LABELS: Record = {\n cookiePolicy: 'Cookie Policy',\n homepage: 'Homepage',\n privacyPolicy: 'Privacy Policy',\n}\n\n/** Admin descriptions per role. */\nconst ROLE_DESCRIPTIONS: Record = {\n cookiePolicy: 'Page linked from the cookie consent banner.',\n homepage: 'Page served at the locale root (e.g. /pl, /en).',\n privacyPolicy: 'Page linked as the privacy policy.',\n}\n\n/**\n * Builds one relationship field per configured system-page role.\n *\n * Each field points at the client's Pages collection (via the provided slug)\n * and holds a single document reference. The referenced document's slug is\n * localized, so the same assignment resolves to /pl/strona-glowna and\n * /en/home at runtime — role assignment is locale-agnostic, path resolution\n * is locale-aware (see getSystemPagePath).\n */\nexport function buildSystemPagesFields(pages: PagesOption): Field[] {\n const roles = pages.roles?.length ? pages.roles : [...ALL_SYSTEM_PAGE_ROLES]\n\n return roles.map((role): RelationshipField => ({\n name: role,\n type: 'relationship',\n admin: {\n description: ROLE_DESCRIPTIONS[role],\n },\n label: ROLE_LABELS[role],\n maxDepth: 1,\n relationTo: pages.slug,\n }))\n}\n"],"names":["ALL_SYSTEM_PAGE_ROLES","ROLE_LABELS","cookiePolicy","homepage","privacyPolicy","ROLE_DESCRIPTIONS","buildSystemPagesFields","pages","roles","length","map","role","name","type","admin","description","label","maxDepth","relationTo","slug"],"mappings":"AAIA,SAASA,qBAAqB,QAAQ,aAAY;AAElD,kCAAkC,GAClC,MAAMC,cAA8C;IAClDC,cAAc;IACdC,UAAU;IACVC,eAAe;AACjB;AAEA,iCAAiC,GACjC,MAAMC,oBAAoD;IACxDH,cAAc;IACdC,UAAU;IACVC,eAAe;AACjB;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASE,uBAAuBC,KAAkB;IACvD,MAAMC,QAAQD,MAAMC,KAAK,EAAEC,SAASF,MAAMC,KAAK,GAAG;WAAIR;KAAsB;IAE5E,OAAOQ,MAAME,GAAG,CAAC,CAACC,OAA6B,CAAA;YAC7CC,MAAMD;YACNE,MAAM;YACNC,OAAO;gBACLC,aAAaV,iBAAiB,CAACM,KAAK;YACtC;YACAK,OAAOf,WAAW,CAACU,KAAK;YACxBM,UAAU;YACVC,YAAYX,MAAMY,IAAI;SACxB,CAAA;AACF"} \ No newline at end of file diff --git a/dist/modules/pages/types.js b/dist/modules/pages/types.js index c29e5d6..d62eb0b 100644 --- a/dist/modules/pages/types.js +++ b/dist/modules/pages/types.js @@ -7,6 +7,6 @@ 'homepage', 'privacyPolicy', 'cookiePolicy' -]; +]; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/pages/types.js.map b/dist/modules/pages/types.js.map index ae8ef81..2e62142 100644 --- a/dist/modules/pages/types.js.map +++ b/dist/modules/pages/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/pages/types.ts"],"sourcesContent":["/**\n * Built-in \"system page\" roles — documents from the client's Pages\n * collection that the site references by function rather than by slug.\n */\nexport type SystemPageRole = 'cookiePolicy' | 'homepage' | 'privacyPolicy'\n\n/**\n * All system roles in a stable order (used when no subset is configured).\n */\nexport const ALL_SYSTEM_PAGE_ROLES: readonly SystemPageRole[] = [\n 'homepage',\n 'privacyPolicy',\n 'cookiePolicy',\n]\n\n/**\n * Plugin option enabling system-page assignments in SiteSettings.\n *\n * The plugin doesn't own the Pages collection — the client creates it in\n * their own project. This option hands the plugin the collection's slug so\n * it can build relationship fields pointing at it. No hardcoding: the slug\n * always comes from the client.\n */\nexport type PagesOption = {\n /**\n * Which system roles to expose as assignable fields.\n * Defaults to all roles when omitted.\n */\n roles?: SystemPageRole[]\n /** Slug of the client's Pages collection, e.g. 'pages'. */\n slug: string\n}\n"],"names":["ALL_SYSTEM_PAGE_ROLES"],"mappings":"AAAA;;;CAGC,GAGD;;CAEC,GACD,OAAO,MAAMA,wBAAmD;IAC9D;IACA;IACA;CACD,CAAA"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["/**\n * Built-in \"system page\" roles — documents from the client's Pages\n * collection that the site references by function rather than by slug.\n */\nexport type SystemPageRole = 'cookiePolicy' | 'homepage' | 'privacyPolicy'\n\n/**\n * All system roles in a stable order (used when no subset is configured).\n */\nexport const ALL_SYSTEM_PAGE_ROLES: readonly SystemPageRole[] = [\n 'homepage',\n 'privacyPolicy',\n 'cookiePolicy',\n]\n\n/**\n * Plugin option enabling system-page assignments in SiteSettings.\n *\n * The plugin doesn't own the Pages collection — the client creates it in\n * their own project. This option hands the plugin the collection's slug so\n * it can build relationship fields pointing at it. No hardcoding: the slug\n * always comes from the client.\n */\nexport type PagesOption = {\n /**\n * Which system roles to expose as assignable fields.\n * Defaults to all roles when omitted.\n */\n roles?: SystemPageRole[]\n /** Slug of the client's Pages collection, e.g. 'pages'. */\n slug: string\n}\n"],"names":["ALL_SYSTEM_PAGE_ROLES"],"mappings":"AAAA;;;CAGC,GAGD;;CAEC,GACD,OAAO,MAAMA,wBAAmD;IAC9D;IACA;IACA;CACD,CAAA"} \ No newline at end of file diff --git a/dist/modules/payload/getGlobal.js b/dist/modules/payload/getGlobal.js index 4f4dcbf..2c1cba7 100644 --- a/dist/modules/payload/getGlobal.js +++ b/dist/modules/payload/getGlobal.js @@ -14,6 +14,6 @@ } : {} }); return result; -} +} //# sourceMappingURL=getGlobal.js.map //# sourceMappingURL=getGlobal.js.map \ No newline at end of file diff --git a/dist/modules/payload/getGlobal.js.map b/dist/modules/payload/getGlobal.js.map index f2be8f4..dff0355 100644 --- a/dist/modules/payload/getGlobal.js.map +++ b/dist/modules/payload/getGlobal.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/payload/getGlobal.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\n/** Locale argument accepted by the global helpers. */\nexport type GlobalQueryOptions = {\n /** Relationship population depth. Defaults to Payload's config default. */\n depth?: number\n /** Locale to fetch, or 'all' for every locale's values. Defaults to Payload's default. */\n locale?: string\n}\n\n/**\n * Thin wrapper over payload.findGlobal that keeps locale/depth handling in one\n * place. The plugin never calls getPayload itself — the caller passes the\n * instance (from getPayload in their app, or req.payload in a hook), matching\n * the plugin's data-access rule: logic here, instance from outside.\n */\nexport async function getGlobal>(\n payload: BasePayload,\n slug: string,\n options: GlobalQueryOptions = {},\n): Promise {\n const result = await payload.findGlobal({\n slug,\n ...(options.locale ? { locale: options.locale as never } : {}),\n ...(typeof options.depth === 'number' ? { depth: options.depth } : {}),\n })\n\n return result as T\n}\n"],"names":["getGlobal","payload","slug","options","result","findGlobal","locale","depth"],"mappings":"AAUA;;;;;CAKC,GACD,OAAO,eAAeA,UACpBC,OAAoB,EACpBC,IAAY,EACZC,UAA8B,CAAC,CAAC;IAEhC,MAAMC,SAAS,MAAMH,QAAQI,UAAU,CAAC;QACtCH;QACA,GAAIC,QAAQG,MAAM,GAAG;YAAEA,QAAQH,QAAQG,MAAM;QAAU,IAAI,CAAC,CAAC;QAC7D,GAAI,OAAOH,QAAQI,KAAK,KAAK,WAAW;YAAEA,OAAOJ,QAAQI,KAAK;QAAC,IAAI,CAAC,CAAC;IACvE;IAEA,OAAOH;AACT"} \ No newline at end of file +{"version":3,"sources":["getGlobal.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\n/** Locale argument accepted by the global helpers. */\nexport type GlobalQueryOptions = {\n /** Relationship population depth. Defaults to Payload's config default. */\n depth?: number\n /** Locale to fetch, or 'all' for every locale's values. Defaults to Payload's default. */\n locale?: string\n}\n\n/**\n * Thin wrapper over payload.findGlobal that keeps locale/depth handling in one\n * place. The plugin never calls getPayload itself — the caller passes the\n * instance (from getPayload in their app, or req.payload in a hook), matching\n * the plugin's data-access rule: logic here, instance from outside.\n */\nexport async function getGlobal>(\n payload: BasePayload,\n slug: string,\n options: GlobalQueryOptions = {},\n): Promise {\n const result = await payload.findGlobal({\n slug,\n ...(options.locale ? { locale: options.locale as never } : {}),\n ...(typeof options.depth === 'number' ? { depth: options.depth } : {}),\n })\n\n return result as T\n}\n"],"names":["getGlobal","payload","slug","options","result","findGlobal","locale","depth"],"mappings":"AAUA;;;;;CAKC,GACD,OAAO,eAAeA,UACpBC,OAAoB,EACpBC,IAAY,EACZC,UAA8B,CAAC,CAAC;IAEhC,MAAMC,SAAS,MAAMH,QAAQI,UAAU,CAAC;QACtCH;QACA,GAAIC,QAAQG,MAAM,GAAG;YAAEA,QAAQH,QAAQG,MAAM;QAAU,IAAI,CAAC,CAAC;QAC7D,GAAI,OAAOH,QAAQI,KAAK,KAAK,WAAW;YAAEA,OAAOJ,QAAQI,KAAK;QAAC,IAAI,CAAC,CAAC;IACvE;IAEA,OAAOH;AACT"} \ No newline at end of file diff --git a/dist/modules/payload/getSiteIntegrations.js b/dist/modules/payload/getSiteIntegrations.js index e8b85ae..d351e73 100644 --- a/dist/modules/payload/getSiteIntegrations.js +++ b/dist/modules/payload/getSiteIntegrations.js @@ -17,6 +17,6 @@ import { getGlobal } from './getGlobal.js'; * const integrations = await getSiteIntegrations(payload) */ export function getSiteIntegrations(payload, options) { return getGlobal(payload, SITE_INTEGRATIONS_SLUG, options); -} +} //# sourceMappingURL=getSiteIntegrations.js.map //# sourceMappingURL=getSiteIntegrations.js.map \ No newline at end of file diff --git a/dist/modules/payload/getSiteIntegrations.js.map b/dist/modules/payload/getSiteIntegrations.js.map index 63c6b31..3ef6a95 100644 --- a/dist/modules/payload/getSiteIntegrations.js.map +++ b/dist/modules/payload/getSiteIntegrations.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/payload/getSiteIntegrations.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { GlobalQueryOptions } from './getGlobal.js'\n\nimport { getGlobal } from './getGlobal.js'\n\n/** Slug of the SiteIntegrations global defined by the plugin. */\nexport const SITE_INTEGRATIONS_SLUG = 'site-integrations'\n\n/**\n * Fetches the SiteIntegrations global.\n *\n * This global is admin-only through access control, but the Local API bypasses\n * access control by default (overrideAccess: true), so server-side callers get\n * the secrets they need (SMTP password, Turnstile secret, R2 keys). Never\n * expose the raw result to the client — read the specific values you need\n * server-side and pass only what's safe to the browser.\n *\n * Generic over the return type so the client can pass their generated\n * `SiteIntegration` type.\n *\n * @example\n * import type { SiteIntegration } from '@/payload-types'\n * const integrations = await getSiteIntegrations(payload)\n */\nexport function getSiteIntegrations>(\n payload: BasePayload,\n options?: GlobalQueryOptions,\n): Promise {\n return getGlobal(payload, SITE_INTEGRATIONS_SLUG, options)\n}\n"],"names":["getGlobal","SITE_INTEGRATIONS_SLUG","getSiteIntegrations","payload","options"],"mappings":"AAIA,SAASA,SAAS,QAAQ,iBAAgB;AAE1C,+DAA+D,GAC/D,OAAO,MAAMC,yBAAyB,oBAAmB;AAEzD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,oBACdC,OAAoB,EACpBC,OAA4B;IAE5B,OAAOJ,UAAaG,SAASF,wBAAwBG;AACvD"} \ No newline at end of file +{"version":3,"sources":["getSiteIntegrations.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { GlobalQueryOptions } from './getGlobal.js'\n\nimport { getGlobal } from './getGlobal.js'\n\n/** Slug of the SiteIntegrations global defined by the plugin. */\nexport const SITE_INTEGRATIONS_SLUG = 'site-integrations'\n\n/**\n * Fetches the SiteIntegrations global.\n *\n * This global is admin-only through access control, but the Local API bypasses\n * access control by default (overrideAccess: true), so server-side callers get\n * the secrets they need (SMTP password, Turnstile secret, R2 keys). Never\n * expose the raw result to the client — read the specific values you need\n * server-side and pass only what's safe to the browser.\n *\n * Generic over the return type so the client can pass their generated\n * `SiteIntegration` type.\n *\n * @example\n * import type { SiteIntegration } from '@/payload-types'\n * const integrations = await getSiteIntegrations(payload)\n */\nexport function getSiteIntegrations>(\n payload: BasePayload,\n options?: GlobalQueryOptions,\n): Promise {\n return getGlobal(payload, SITE_INTEGRATIONS_SLUG, options)\n}\n"],"names":["getGlobal","SITE_INTEGRATIONS_SLUG","getSiteIntegrations","payload","options"],"mappings":"AAIA,SAASA,SAAS,QAAQ,iBAAgB;AAE1C,+DAA+D,GAC/D,OAAO,MAAMC,yBAAyB,oBAAmB;AAEzD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,oBACdC,OAAoB,EACpBC,OAA4B;IAE5B,OAAOJ,UAAaG,SAASF,wBAAwBG;AACvD"} \ No newline at end of file diff --git a/dist/modules/payload/getSiteSettings.js b/dist/modules/payload/getSiteSettings.js index bbea669..33ce34c 100644 --- a/dist/modules/payload/getSiteSettings.js +++ b/dist/modules/payload/getSiteSettings.js @@ -11,6 +11,6 @@ import { getGlobal } from './getGlobal.js'; * const settings = await getSiteSettings(payload, { locale: 'pl' }) */ export function getSiteSettings(payload, options) { return getGlobal(payload, SITE_SETTINGS_SLUG, options); -} +} //# sourceMappingURL=getSiteSettings.js.map //# sourceMappingURL=getSiteSettings.js.map \ No newline at end of file diff --git a/dist/modules/payload/getSiteSettings.js.map b/dist/modules/payload/getSiteSettings.js.map index daeec8f..4ee157a 100644 --- a/dist/modules/payload/getSiteSettings.js.map +++ b/dist/modules/payload/getSiteSettings.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/payload/getSiteSettings.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { GlobalQueryOptions } from './getGlobal.js'\n\nimport { getGlobal } from './getGlobal.js'\n\n/** Slug of the SiteSettings global defined by the plugin. */\nexport const SITE_SETTINGS_SLUG = 'site-settings'\n\n/**\n * Fetches the SiteSettings global.\n *\n * Generic over the return type so the client can pass their generated\n * `SiteSetting` type for full type safety, while still working without it.\n *\n * @example\n * import type { SiteSetting } from '@/payload-types'\n * const settings = await getSiteSettings(payload, { locale: 'pl' })\n */\nexport function getSiteSettings>(\n payload: BasePayload,\n options?: GlobalQueryOptions,\n): Promise {\n return getGlobal(payload, SITE_SETTINGS_SLUG, options)\n}\n"],"names":["getGlobal","SITE_SETTINGS_SLUG","getSiteSettings","payload","options"],"mappings":"AAIA,SAASA,SAAS,QAAQ,iBAAgB;AAE1C,2DAA2D,GAC3D,OAAO,MAAMC,qBAAqB,gBAAe;AAEjD;;;;;;;;;CASC,GACD,OAAO,SAASC,gBACdC,OAAoB,EACpBC,OAA4B;IAE5B,OAAOJ,UAAaG,SAASF,oBAAoBG;AACnD"} \ No newline at end of file +{"version":3,"sources":["getSiteSettings.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { GlobalQueryOptions } from './getGlobal.js'\n\nimport { getGlobal } from './getGlobal.js'\n\n/** Slug of the SiteSettings global defined by the plugin. */\nexport const SITE_SETTINGS_SLUG = 'site-settings'\n\n/**\n * Fetches the SiteSettings global.\n *\n * Generic over the return type so the client can pass their generated\n * `SiteSetting` type for full type safety, while still working without it.\n *\n * @example\n * import type { SiteSetting } from '@/payload-types'\n * const settings = await getSiteSettings(payload, { locale: 'pl' })\n */\nexport function getSiteSettings>(\n payload: BasePayload,\n options?: GlobalQueryOptions,\n): Promise {\n return getGlobal(payload, SITE_SETTINGS_SLUG, options)\n}\n"],"names":["getGlobal","SITE_SETTINGS_SLUG","getSiteSettings","payload","options"],"mappings":"AAIA,SAASA,SAAS,QAAQ,iBAAgB;AAE1C,2DAA2D,GAC3D,OAAO,MAAMC,qBAAqB,gBAAe;AAEjD;;;;;;;;;CASC,GACD,OAAO,SAASC,gBACdC,OAAoB,EACpBC,OAA4B;IAE5B,OAAOJ,UAAaG,SAASF,oBAAoBG;AACnD"} \ No newline at end of file diff --git a/dist/modules/payload/index.js b/dist/modules/payload/index.js index f85eef0..6f1771d 100644 --- a/dist/modules/payload/index.js +++ b/dist/modules/payload/index.js @@ -1,5 +1,5 @@ export { getGlobal } from './getGlobal.js'; export { getSiteIntegrations, SITE_INTEGRATIONS_SLUG } from './getSiteIntegrations.js'; -export { getSiteSettings, SITE_SETTINGS_SLUG } from './getSiteSettings.js'; +export { getSiteSettings, SITE_SETTINGS_SLUG } from './getSiteSettings.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/payload/index.js.map b/dist/modules/payload/index.js.map index 46813a1..0cc8224 100644 --- a/dist/modules/payload/index.js.map +++ b/dist/modules/payload/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/payload/index.ts"],"sourcesContent":["export type { GlobalQueryOptions } from './getGlobal.js'\nexport { getGlobal } from './getGlobal.js'\nexport { getSiteIntegrations, SITE_INTEGRATIONS_SLUG } from './getSiteIntegrations.js'\nexport { getSiteSettings, SITE_SETTINGS_SLUG } from './getSiteSettings.js'\n"],"names":["getGlobal","getSiteIntegrations","SITE_INTEGRATIONS_SLUG","getSiteSettings","SITE_SETTINGS_SLUG"],"mappings":"AACA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,mBAAmB,EAAEC,sBAAsB,QAAQ,2BAA0B;AACtF,SAASC,eAAe,EAAEC,kBAAkB,QAAQ,uBAAsB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export type { GlobalQueryOptions } from './getGlobal.js'\nexport { getGlobal } from './getGlobal.js'\nexport { getSiteIntegrations, SITE_INTEGRATIONS_SLUG } from './getSiteIntegrations.js'\nexport { getSiteSettings, SITE_SETTINGS_SLUG } from './getSiteSettings.js'\n"],"names":["getGlobal","getSiteIntegrations","SITE_INTEGRATIONS_SLUG","getSiteSettings","SITE_SETTINGS_SLUG"],"mappings":"AACA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,mBAAmB,EAAEC,sBAAsB,QAAQ,2BAA0B;AACtF,SAASC,eAAe,EAAEC,kBAAkB,QAAQ,uBAAsB"} \ No newline at end of file diff --git a/dist/modules/seo/autoFillMeta.js b/dist/modules/seo/autoFillMeta.js index 5f548da..d9761d9 100644 --- a/dist/modules/seo/autoFillMeta.js +++ b/dist/modules/seo/autoFillMeta.js @@ -42,6 +42,6 @@ data.meta = meta; return data; }; -} +} //# sourceMappingURL=autoFillMeta.js.map //# sourceMappingURL=autoFillMeta.js.map \ No newline at end of file diff --git a/dist/modules/seo/autoFillMeta.js.map b/dist/modules/seo/autoFillMeta.js.map index 3239280..b859a5a 100644 --- a/dist/modules/seo/autoFillMeta.js.map +++ b/dist/modules/seo/autoFillMeta.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/autoFillMeta.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\n/**\n * Maps document fields to SEO meta fields for auto-fill.\n * Defaults match the Payload website template (title → meta.title).\n */\nexport type AutoFillMapping = {\n /** Document field used to fill meta.description when empty. */\n description?: string\n /** Document field used to fill meta.title when empty. Default: 'title'. */\n title?: string\n}\n\n/**\n * Reads a possibly-localized field value into a plain string.\n * With localized fields at this hook stage the value is the active-locale\n * string, so we just coerce defensively.\n */\nfunction readString(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim()) {return value.trim()}\n return undefined\n}\n\n/**\n * Builds a beforeChange hook that fills empty SEO meta fields from document\n * content. Only fills when the meta field is blank — never overwrites what an\n * editor typed. This runs server-side on every create/update, so editors get\n * sensible meta without clicking \"auto-generate\".\n *\n * The plugin doesn't know the client's collection shape, so the field mapping\n * is configurable; defaults follow the website template.\n */\nexport function buildAutoFillMetaHook(mapping: AutoFillMapping = {}): CollectionBeforeChangeHook {\n const titleField = mapping.title ?? 'title'\n const descriptionField = mapping.description\n\n return ({ data }) => {\n if (!data) {return data}\n\n // plugin-seo stores meta as a group under `meta`\n const meta = (data.meta as Record | undefined) ?? {}\n\n // Fill meta.title from the document title when empty\n if (!readString(meta.title)) {\n const sourceTitle = readString(data[titleField])\n if (sourceTitle) {meta.title = sourceTitle}\n }\n\n // Fill meta.description from a configured field when empty\n if (descriptionField && !readString(meta.description)) {\n const sourceDescription = readString(data[descriptionField])\n if (sourceDescription) {meta.description = sourceDescription}\n }\n\n data.meta = meta\n return data\n }\n}\n"],"names":["readString","value","trim","undefined","buildAutoFillMetaHook","mapping","titleField","title","descriptionField","description","data","meta","sourceTitle","sourceDescription"],"mappings":"AAaA;;;;CAIC,GACD,SAASA,WAAWC,KAAc;IAChC,IAAI,OAAOA,UAAU,YAAYA,MAAMC,IAAI,IAAI;QAAC,OAAOD,MAAMC,IAAI;IAAE;IACnE,OAAOC;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC,sBAAsBC,UAA2B,CAAC,CAAC;IACjE,MAAMC,aAAaD,QAAQE,KAAK,IAAI;IACpC,MAAMC,mBAAmBH,QAAQI,WAAW;IAE5C,OAAO,CAAC,EAAEC,IAAI,EAAE;QACd,IAAI,CAACA,MAAM;YAAC,OAAOA;QAAI;QAEvB,iDAAiD;QACjD,MAAMC,OAAO,AAACD,KAAKC,IAAI,IAA4C,CAAC;QAEpE,qDAAqD;QACrD,IAAI,CAACX,WAAWW,KAAKJ,KAAK,GAAG;YAC3B,MAAMK,cAAcZ,WAAWU,IAAI,CAACJ,WAAW;YAC/C,IAAIM,aAAa;gBAACD,KAAKJ,KAAK,GAAGK;YAAW;QAC5C;QAEA,2DAA2D;QAC3D,IAAIJ,oBAAoB,CAACR,WAAWW,KAAKF,WAAW,GAAG;YACrD,MAAMI,oBAAoBb,WAAWU,IAAI,CAACF,iBAAiB;YAC3D,IAAIK,mBAAmB;gBAACF,KAAKF,WAAW,GAAGI;YAAiB;QAC9D;QAEAH,KAAKC,IAAI,GAAGA;QACZ,OAAOD;IACT;AACF"} \ No newline at end of file +{"version":3,"sources":["autoFillMeta.js"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\n/**\n * Maps document fields to SEO meta fields for auto-fill.\n * Defaults match the Payload website template (title → meta.title).\n */\nexport type AutoFillMapping = {\n /** Document field used to fill meta.description when empty. */\n description?: string\n /** Document field used to fill meta.title when empty. Default: 'title'. */\n title?: string\n}\n\n/**\n * Reads a possibly-localized field value into a plain string.\n * With localized fields at this hook stage the value is the active-locale\n * string, so we just coerce defensively.\n */\nfunction readString(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim()) {return value.trim()}\n return undefined\n}\n\n/**\n * Builds a beforeChange hook that fills empty SEO meta fields from document\n * content. Only fills when the meta field is blank — never overwrites what an\n * editor typed. This runs server-side on every create/update, so editors get\n * sensible meta without clicking \"auto-generate\".\n *\n * The plugin doesn't know the client's collection shape, so the field mapping\n * is configurable; defaults follow the website template.\n */\nexport function buildAutoFillMetaHook(mapping: AutoFillMapping = {}): CollectionBeforeChangeHook {\n const titleField = mapping.title ?? 'title'\n const descriptionField = mapping.description\n\n return ({ data }) => {\n if (!data) {return data}\n\n // plugin-seo stores meta as a group under `meta`\n const meta = (data.meta as Record | undefined) ?? {}\n\n // Fill meta.title from the document title when empty\n if (!readString(meta.title)) {\n const sourceTitle = readString(data[titleField])\n if (sourceTitle) {meta.title = sourceTitle}\n }\n\n // Fill meta.description from a configured field when empty\n if (descriptionField && !readString(meta.description)) {\n const sourceDescription = readString(data[descriptionField])\n if (sourceDescription) {meta.description = sourceDescription}\n }\n\n data.meta = meta\n return data\n }\n}\n"],"names":["readString","value","trim","undefined","buildAutoFillMetaHook","mapping","titleField","title","descriptionField","description","data","meta","sourceTitle","sourceDescription"],"mappings":"AAaA;;;;CAIC,GACD,SAASA,WAAWC,KAAc;IAChC,IAAI,OAAOA,UAAU,YAAYA,MAAMC,IAAI,IAAI;QAAC,OAAOD,MAAMC,IAAI;IAAE;IACnE,OAAOC;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC,sBAAsBC,UAA2B,CAAC,CAAC;IACjE,MAAMC,aAAaD,QAAQE,KAAK,IAAI;IACpC,MAAMC,mBAAmBH,QAAQI,WAAW;IAE5C,OAAO,CAAC,EAAEC,IAAI,EAAE;QACd,IAAI,CAACA,MAAM;YAAC,OAAOA;QAAI;QAEvB,iDAAiD;QACjD,MAAMC,OAAQD,KAAKC,IAAI,IAA4C,CAAC;QAEpE,qDAAqD;QACrD,IAAI,CAACX,WAAWW,KAAKJ,KAAK,GAAG;YAC3B,MAAMK,cAAcZ,WAAWU,IAAI,CAACJ,WAAW;YAC/C,IAAIM,aAAa;gBAACD,KAAKJ,KAAK,GAAGK;YAAW;QAC5C;QAEA,2DAA2D;QAC3D,IAAIJ,oBAAoB,CAACR,WAAWW,KAAKF,WAAW,GAAG;YACrD,MAAMI,oBAAoBb,WAAWU,IAAI,CAACF,iBAAiB;YAC3D,IAAIK,mBAAmB;gBAACF,KAAKF,WAAW,GAAGI;YAAiB;QAC9D;QAEAH,KAAKC,IAAI,GAAGA;QACZ,OAAOD;IACT;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/buildMetadata.js b/dist/modules/seo/buildMetadata.js index 1a08dc3..11ba260 100644 --- a/dist/modules/seo/buildMetadata.js +++ b/dist/modules/seo/buildMetadata.js @@ -71,6 +71,6 @@ import { buildHreflangAlternates } from './hreflang.js'; locale } }; -} +} //# sourceMappingURL=buildMetadata.js.map //# sourceMappingURL=buildMetadata.js.map \ No newline at end of file diff --git a/dist/modules/seo/buildMetadata.js.map b/dist/modules/seo/buildMetadata.js.map index 5b52d9a..7ce5634 100644 --- a/dist/modules/seo/buildMetadata.js.map +++ b/dist/modules/seo/buildMetadata.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/buildMetadata.ts"],"sourcesContent":["import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\nimport type { TitleOrder } from './composeTitle.js'\nimport type { SeoMeta } from './types.js'\n\nimport { buildLocalizedPath } from '../i18n/index.js'\nimport { composeTitle } from './composeTitle.js'\nimport { buildHreflangAlternates } from './hreflang.js'\n\n/**\n * Subset of Next.js `Metadata` this helper produces. Kept local so the plugin\n * doesn't depend on `next` types; the shape is assignable to Next's Metadata.\n */\nexport type PageMetadata = {\n alternates?: {\n canonical?: string\n languages?: Record\n }\n description?: string\n openGraph?: {\n description?: string\n images?: { url: string }[]\n locale?: string\n title: string\n }\n title: string\n}\n\ntype BuildMetadataArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. */\n baseUrl?: string\n config: I18nConfig\n /** Home slug that collapses to the locale root. Defaults to 'home'. */\n homeSlug?: string\n /** Resolved OG image URL (page image or site defaultShareImage). */\n imageUrl?: null | string\n /** Current locale being rendered. */\n locale: string\n /** SEO meta from the document (plugin-seo group). */\n meta?: null | SeoMeta\n /** Page title or site name first. Defaults to 'page-first'. */\n order?: TitleOrder\n /**\n * Localized segment the document lives under (an archive page's slugs).\n * Feeds both canonical and hreflang, so /pl/artykuly/moj-post and\n * /en/articles/my-post point at each other correctly.\n */\n prefix?: LocalizedSlugs\n /**\n * Query string appended to canonical and every hreflang, e.g. '?page=2'.\n *\n * A paginated listing must be canonical to itself — pointing page 2 at page 1\n * tells Google the entries on it don't exist. Alternates carry the same page,\n * since /pl/artykuly?page=2 corresponds to /en/articles?page=2.\n */\n query?: string\n /** Separator between page title and site name. Defaults to ' | '. */\n separator?: string\n /** Site name for title composition and OG. */\n siteName?: null | string\n /** slug per locale for this document — drives canonical + hreflang. */\n slugs: LocalizedSlugs\n}\n\n/**\n * Assembles a Next.js-compatible Metadata object from document SEO fields and\n * site-level data. Locale-aware: canonical points at the current locale's\n * path, and hreflang alternates cover every locale the document exists in.\n *\n * Designed for use inside Next.js `generateMetadata`. The caller resolves the\n * pieces (meta group, site name, image URL, localized slugs) and passes them\n * in — the plugin composes, it doesn't fetch.\n */\nexport function buildMetadata({\n baseUrl,\n config,\n homeSlug = 'home',\n imageUrl,\n locale,\n meta,\n order,\n prefix,\n query,\n separator,\n siteName,\n slugs,\n}: BuildMetadataArgs): PageMetadata {\n // titleOverride wins outright: an editor who filled it in wants that exact\n // string in the tab, not a composition.\n const override = meta?.titleOverride?.trim()\n const title = override || composeTitle({ order, pageTitle: meta?.title, separator, siteName })\n const description = meta?.description?.trim() || undefined\n const origin = baseUrl?.replace(/\\/$/, '') ?? ''\n\n const suffix = query ?? ''\n\n const currentPath = buildLocalizedPath({ config, homeSlug, locale, prefix, slugs })\n const canonical = currentPath ? `${origin}${currentPath}${suffix}` : undefined\n\n const languages = buildHreflangAlternates({ baseUrl, config, homeSlug, prefix, slugs })\n if (suffix) {\n for (const code of Object.keys(languages)) {\n languages[code] = `${languages[code]}${suffix}`\n }\n }\n\n const images = imageUrl ? [{ url: imageUrl }] : undefined\n\n return {\n title,\n ...(description && { description }),\n alternates: {\n ...(canonical && { canonical }),\n ...(Object.keys(languages).length > 0 && { languages }),\n },\n openGraph: {\n title,\n ...(description && { description }),\n ...(images && { images }),\n locale,\n },\n }\n}\n"],"names":["buildLocalizedPath","composeTitle","buildHreflangAlternates","buildMetadata","baseUrl","config","homeSlug","imageUrl","locale","meta","order","prefix","query","separator","siteName","slugs","override","titleOverride","trim","title","pageTitle","description","undefined","origin","replace","suffix","currentPath","canonical","languages","code","Object","keys","images","url","alternates","length","openGraph"],"mappings":"AAIA,SAASA,kBAAkB,QAAQ,mBAAkB;AACrD,SAASC,YAAY,QAAQ,oBAAmB;AAChD,SAASC,uBAAuB,QAAQ,gBAAe;AAyDvD;;;;;;;;CAQC,GACD,OAAO,SAASC,cAAc,EAC5BC,OAAO,EACPC,MAAM,EACNC,WAAW,MAAM,EACjBC,QAAQ,EACRC,MAAM,EACNC,IAAI,EACJC,KAAK,EACLC,MAAM,EACNC,KAAK,EACLC,SAAS,EACTC,QAAQ,EACRC,KAAK,EACa;IAClB,2EAA2E;IAC3E,wCAAwC;IACxC,MAAMC,WAAWP,MAAMQ,eAAeC;IACtC,MAAMC,QAAQH,YAAYf,aAAa;QAAES;QAAOU,WAAWX,MAAMU;QAAON;QAAWC;IAAS;IAC5F,MAAMO,cAAcZ,MAAMY,aAAaH,UAAUI;IACjD,MAAMC,SAASnB,SAASoB,QAAQ,OAAO,OAAO;IAE9C,MAAMC,SAASb,SAAS;IAExB,MAAMc,cAAc1B,mBAAmB;QAAEK;QAAQC;QAAUE;QAAQG;QAAQI;IAAM;IACjF,MAAMY,YAAYD,cAAc,GAAGH,SAASG,cAAcD,QAAQ,GAAGH;IAErE,MAAMM,YAAY1B,wBAAwB;QAAEE;QAASC;QAAQC;QAAUK;QAAQI;IAAM;IACrF,IAAIU,QAAQ;QACV,KAAK,MAAMI,QAAQC,OAAOC,IAAI,CAACH,WAAY;YACzCA,SAAS,CAACC,KAAK,GAAG,GAAGD,SAAS,CAACC,KAAK,GAAGJ,QAAQ;QACjD;IACF;IAEA,MAAMO,SAASzB,WAAW;QAAC;YAAE0B,KAAK1B;QAAS;KAAE,GAAGe;IAEhD,OAAO;QACLH;QACA,GAAIE,eAAe;YAAEA;QAAY,CAAC;QAClCa,YAAY;YACV,GAAIP,aAAa;gBAAEA;YAAU,CAAC;YAC9B,GAAIG,OAAOC,IAAI,CAACH,WAAWO,MAAM,GAAG,KAAK;gBAAEP;YAAU,CAAC;QACxD;QACAQ,WAAW;YACTjB;YACA,GAAIE,eAAe;gBAAEA;YAAY,CAAC;YAClC,GAAIW,UAAU;gBAAEA;YAAO,CAAC;YACxBxB;QACF;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["buildMetadata.js"],"sourcesContent":["import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\nimport type { TitleOrder } from './composeTitle.js'\nimport type { SeoMeta } from './types.js'\n\nimport { buildLocalizedPath } from '../i18n/index.js'\nimport { composeTitle } from './composeTitle.js'\nimport { buildHreflangAlternates } from './hreflang.js'\n\n/**\n * Subset of Next.js `Metadata` this helper produces. Kept local so the plugin\n * doesn't depend on `next` types; the shape is assignable to Next's Metadata.\n */\nexport type PageMetadata = {\n alternates?: {\n canonical?: string\n languages?: Record\n }\n description?: string\n openGraph?: {\n description?: string\n images?: { url: string }[]\n locale?: string\n title: string\n }\n title: string\n}\n\ntype BuildMetadataArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. */\n baseUrl?: string\n config: I18nConfig\n /** Home slug that collapses to the locale root. Defaults to 'home'. */\n homeSlug?: string\n /** Resolved OG image URL (page image or site defaultShareImage). */\n imageUrl?: null | string\n /** Current locale being rendered. */\n locale: string\n /** SEO meta from the document (plugin-seo group). */\n meta?: null | SeoMeta\n /** Page title or site name first. Defaults to 'page-first'. */\n order?: TitleOrder\n /**\n * Localized segment the document lives under (an archive page's slugs).\n * Feeds both canonical and hreflang, so /pl/artykuly/moj-post and\n * /en/articles/my-post point at each other correctly.\n */\n prefix?: LocalizedSlugs\n /**\n * Query string appended to canonical and every hreflang, e.g. '?page=2'.\n *\n * A paginated listing must be canonical to itself — pointing page 2 at page 1\n * tells Google the entries on it don't exist. Alternates carry the same page,\n * since /pl/artykuly?page=2 corresponds to /en/articles?page=2.\n */\n query?: string\n /** Separator between page title and site name. Defaults to ' | '. */\n separator?: string\n /** Site name for title composition and OG. */\n siteName?: null | string\n /** slug per locale for this document — drives canonical + hreflang. */\n slugs: LocalizedSlugs\n}\n\n/**\n * Assembles a Next.js-compatible Metadata object from document SEO fields and\n * site-level data. Locale-aware: canonical points at the current locale's\n * path, and hreflang alternates cover every locale the document exists in.\n *\n * Designed for use inside Next.js `generateMetadata`. The caller resolves the\n * pieces (meta group, site name, image URL, localized slugs) and passes them\n * in — the plugin composes, it doesn't fetch.\n */\nexport function buildMetadata({\n baseUrl,\n config,\n homeSlug = 'home',\n imageUrl,\n locale,\n meta,\n order,\n prefix,\n query,\n separator,\n siteName,\n slugs,\n}: BuildMetadataArgs): PageMetadata {\n // titleOverride wins outright: an editor who filled it in wants that exact\n // string in the tab, not a composition.\n const override = meta?.titleOverride?.trim()\n const title = override || composeTitle({ order, pageTitle: meta?.title, separator, siteName })\n const description = meta?.description?.trim() || undefined\n const origin = baseUrl?.replace(/\\/$/, '') ?? ''\n\n const suffix = query ?? ''\n\n const currentPath = buildLocalizedPath({ config, homeSlug, locale, prefix, slugs })\n const canonical = currentPath ? `${origin}${currentPath}${suffix}` : undefined\n\n const languages = buildHreflangAlternates({ baseUrl, config, homeSlug, prefix, slugs })\n if (suffix) {\n for (const code of Object.keys(languages)) {\n languages[code] = `${languages[code]}${suffix}`\n }\n }\n\n const images = imageUrl ? [{ url: imageUrl }] : undefined\n\n return {\n title,\n ...(description && { description }),\n alternates: {\n ...(canonical && { canonical }),\n ...(Object.keys(languages).length > 0 && { languages }),\n },\n openGraph: {\n title,\n ...(description && { description }),\n ...(images && { images }),\n locale,\n },\n }\n}\n"],"names":["buildLocalizedPath","composeTitle","buildHreflangAlternates","buildMetadata","baseUrl","config","homeSlug","imageUrl","locale","meta","order","prefix","query","separator","siteName","slugs","override","titleOverride","trim","title","pageTitle","description","undefined","origin","replace","suffix","currentPath","canonical","languages","code","Object","keys","images","url","alternates","length","openGraph"],"mappings":"AAIA,SAASA,kBAAkB,QAAQ,mBAAkB;AACrD,SAASC,YAAY,QAAQ,oBAAmB;AAChD,SAASC,uBAAuB,QAAQ,gBAAe;AAyDvD;;;;;;;;CAQC,GACD,OAAO,SAASC,cAAc,EAC5BC,OAAO,EACPC,MAAM,EACNC,WAAW,MAAM,EACjBC,QAAQ,EACRC,MAAM,EACNC,IAAI,EACJC,KAAK,EACLC,MAAM,EACNC,KAAK,EACLC,SAAS,EACTC,QAAQ,EACRC,KAAK,EACa;IAClB,2EAA2E;IAC3E,wCAAwC;IACxC,MAAMC,WAAWP,MAAMQ,eAAeC;IACtC,MAAMC,QAAQH,YAAYf,aAAa;QAAES;QAAOU,WAAWX,MAAMU;QAAON;QAAWC;IAAS;IAC5F,MAAMO,cAAcZ,MAAMY,aAAaH,UAAUI;IACjD,MAAMC,SAASnB,SAASoB,QAAQ,OAAO,OAAO;IAE9C,MAAMC,SAASb,SAAS;IAExB,MAAMc,cAAc1B,mBAAmB;QAAEK;QAAQC;QAAUE;QAAQG;QAAQI;IAAM;IACjF,MAAMY,YAAYD,cAAc,GAAGH,SAASG,cAAcD,QAAQ,GAAGH;IAErE,MAAMM,YAAY1B,wBAAwB;QAAEE;QAASC;QAAQC;QAAUK;QAAQI;IAAM;IACrF,IAAIU,QAAQ;QACV,KAAK,MAAMI,QAAQC,OAAOC,IAAI,CAACH,WAAY;YACzCA,SAAS,CAACC,KAAK,GAAG,GAAGD,SAAS,CAACC,KAAK,GAAGJ,QAAQ;QACjD;IACF;IAEA,MAAMO,SAASzB,WAAW;QAAC;YAAE0B,KAAK1B;QAAS;KAAE,GAAGe;IAEhD,OAAO;QACLH;QACA,GAAIE,eAAe;YAAEA;QAAY,CAAC;QAClCa,YAAY;YACV,GAAIP,aAAa;gBAAEA;YAAU,CAAC;YAC9B,GAAIG,OAAOC,IAAI,CAACH,WAAWO,MAAM,GAAG,KAAK;gBAAEP;YAAU,CAAC;QACxD;QACAQ,WAAW;YACTjB;YACA,GAAIE,eAAe;gBAAEA;YAAY,CAAC;YAClC,GAAIW,UAAU;gBAAEA;YAAO,CAAC;YACxBxB;QACF;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/buildRobots.js b/dist/modules/seo/buildRobots.js index 226441f..bc9d418 100644 --- a/dist/modules/seo/buildRobots.js +++ b/dist/modules/seo/buildRobots.js @@ -27,6 +27,6 @@ } : {}, host: origin }; -} +} //# sourceMappingURL=buildRobots.js.map //# sourceMappingURL=buildRobots.js.map \ No newline at end of file diff --git a/dist/modules/seo/buildRobots.js.map b/dist/modules/seo/buildRobots.js.map index e27a67b..9d324dd 100644 --- a/dist/modules/seo/buildRobots.js.map +++ b/dist/modules/seo/buildRobots.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/buildRobots.ts"],"sourcesContent":["type BuildRobotsArgs = {\n /** Absolute origin, e.g. 'https://example.com'. */\n baseUrl: string\n /** Paths to disallow. Defaults to Payload's admin and API. */\n disallow?: string[]\n /** Whether to advertise the sitemap. Defaults to true. */\n sitemap?: boolean\n}\n\n/**\n * Shape for Next's `app/robots.ts`.\n */\nexport type RobotsRules = {\n host?: string\n rules: { allow?: string; disallow?: string[]; userAgent: string }\n sitemap?: string\n}\n\n/**\n * Builds robots rules that allow crawling everything except the admin panel and\n * API, and point crawlers at the sitemap.\n *\n * ```ts\n * // app/robots.ts\n * export default function robots() {\n * return buildRobots({ baseUrl: process.env.NEXT_PUBLIC_SERVER_URL! })\n * }\n * ```\n *\n * The admin and API are disallowed by default — no reason for them in the index,\n * and it keeps crawl budget on real content.\n */\nexport function buildRobots({\n baseUrl,\n disallow = ['/admin', '/api'],\n sitemap = true,\n}: BuildRobotsArgs): RobotsRules {\n const origin = baseUrl.replace(/\\/$/, '')\n\n return {\n rules: {\n allow: '/',\n disallow,\n userAgent: '*',\n },\n ...(sitemap ? { sitemap: `${origin}/sitemap.xml` } : {}),\n host: origin,\n }\n}\n"],"names":["buildRobots","baseUrl","disallow","sitemap","origin","replace","rules","allow","userAgent","host"],"mappings":"AAkBA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,YAAY,EAC1BC,OAAO,EACPC,WAAW;IAAC;IAAU;CAAO,EAC7BC,UAAU,IAAI,EACE;IAChB,MAAMC,SAASH,QAAQI,OAAO,CAAC,OAAO;IAEtC,OAAO;QACLC,OAAO;YACLC,OAAO;YACPL;YACAM,WAAW;QACb;QACA,GAAIL,UAAU;YAAEA,SAAS,GAAGC,OAAO,YAAY,CAAC;QAAC,IAAI,CAAC,CAAC;QACvDK,MAAML;IACR;AACF"} \ No newline at end of file +{"version":3,"sources":["buildRobots.js"],"sourcesContent":["type BuildRobotsArgs = {\n /** Absolute origin, e.g. 'https://example.com'. */\n baseUrl: string\n /** Paths to disallow. Defaults to Payload's admin and API. */\n disallow?: string[]\n /** Whether to advertise the sitemap. Defaults to true. */\n sitemap?: boolean\n}\n\n/**\n * Shape for Next's `app/robots.ts`.\n */\nexport type RobotsRules = {\n host?: string\n rules: { allow?: string; disallow?: string[]; userAgent: string }\n sitemap?: string\n}\n\n/**\n * Builds robots rules that allow crawling everything except the admin panel and\n * API, and point crawlers at the sitemap.\n *\n * ```ts\n * // app/robots.ts\n * export default function robots() {\n * return buildRobots({ baseUrl: process.env.NEXT_PUBLIC_SERVER_URL! })\n * }\n * ```\n *\n * The admin and API are disallowed by default — no reason for them in the index,\n * and it keeps crawl budget on real content.\n */\nexport function buildRobots({\n baseUrl,\n disallow = ['/admin', '/api'],\n sitemap = true,\n}: BuildRobotsArgs): RobotsRules {\n const origin = baseUrl.replace(/\\/$/, '')\n\n return {\n rules: {\n allow: '/',\n disallow,\n userAgent: '*',\n },\n ...(sitemap ? { sitemap: `${origin}/sitemap.xml` } : {}),\n host: origin,\n }\n}\n"],"names":["buildRobots","baseUrl","disallow","sitemap","origin","replace","rules","allow","userAgent","host"],"mappings":"AAkBA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,YAAY,EAC1BC,OAAO,EACPC,WAAW;IAAC;IAAU;CAAO,EAC7BC,UAAU,IAAI,EACE;IAChB,MAAMC,SAASH,QAAQI,OAAO,CAAC,OAAO;IAEtC,OAAO;QACLC,OAAO;YACLC,OAAO;YACPL;YACAM,WAAW;QACb;QACA,GAAIL,UAAU;YAAEA,SAAS,GAAGC,OAAO,YAAY,CAAC;QAAC,IAAI,CAAC,CAAC;QACvDK,MAAML;IACR;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/buildSitemapEntries.js b/dist/modules/seo/buildSitemapEntries.js index 53f83ce..e8e5e5c 100644 --- a/dist/modules/seo/buildSitemapEntries.js +++ b/dist/modules/seo/buildSitemapEntries.js @@ -153,6 +153,6 @@ import { buildHreflangAlternates } from './hreflang.js'; } } return Object.keys(map).length ? map : undefined; -} +} //# sourceMappingURL=buildSitemapEntries.js.map //# sourceMappingURL=buildSitemapEntries.js.map \ No newline at end of file diff --git a/dist/modules/seo/buildSitemapEntries.js.map b/dist/modules/seo/buildSitemapEntries.js.map index ccf0101..30baa98 100644 --- a/dist/modules/seo/buildSitemapEntries.js.map +++ b/dist/modules/seo/buildSitemapEntries.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/buildSitemapEntries.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from '../content/index.js'\nimport type { I18nConfig } from '../i18n/index.js'\n\nimport { archiveFieldName } from '../content/index.js'\nimport { buildLocalizedPath, getLocalizedSlugs } from '../i18n/index.js'\nimport { buildHreflangAlternates } from './hreflang.js'\n\n/**\n * One sitemap entry, shaped for Next's `app/sitemap.ts`.\n *\n * `alternates.languages` is the important part for a multilingual site: Next\n * renders it as `` per URL, which is\n * exactly what Google uses to connect language versions. A sitemap without it\n * is the common, weaker kind.\n */\nexport type SitemapEntry = {\n alternates?: { languages: Record }\n changeFrequency?: 'always' | 'daily' | 'hourly' | 'monthly' | 'never' | 'weekly' | 'yearly'\n lastModified?: Date | string\n priority?: number\n url: string\n}\n\ntype CollectionEntry = {\n /** Localized segment for entries (archive page slugs), when applicable. */\n prefixSlugs?: Record\n slug: string\n}\n\ntype BuildSitemapArgs = {\n /** Absolute origin, e.g. 'https://example.com'. Required for valid sitemap URLs. */\n baseUrl: string\n changeFrequency?: SitemapEntry['changeFrequency']\n config: I18nConfig\n /** Archive-backed collections, same value as the plugin option. */\n content?: ContentOption\n /**\n * Slug of the page that is the site root (collapses to /{locale}).\n * Read from System Pages when omitted.\n */\n homeSlug?: string\n /** Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\ntype DocRow = {\n _status?: string\n id: number | string\n meta?: { noindex?: boolean } | null\n slug?: unknown\n updatedAt?: string\n}\n\n/** Skip drafts and anything flagged noindex in the SEO tab. */\nfunction isIndexable(doc: DocRow): boolean {\n if (doc._status && doc._status !== 'published') {return false}\n if (doc.meta?.noindex) {return false}\n return true\n}\n\n/**\n * Turns one document into a sitemap entry per default locale isn't needed —\n * one entry with all languages as alternates is the correct, de-duplicated\n * shape. The `url` is the current locale's path; `alternates.languages` carries\n * every locale (including itself, per Google's guidance).\n */\nfunction entryFor(\n doc: DocRow,\n locale: string,\n config: I18nConfig,\n baseUrl: string,\n homeSlug: string | undefined,\n prefix: Record | undefined,\n changeFrequency: SitemapEntry['changeFrequency'],\n): null | SitemapEntry {\n const slugs =\n doc.slug && typeof doc.slug === 'object'\n ? getLocalizedSlugs({ config, slugField: doc.slug as Record })\n : {}\n\n const path = buildLocalizedPath({ config, homeSlug, locale, prefix, slugs })\n if (!path) {return null}\n\n const origin = baseUrl.replace(/\\/$/, '')\n const languages = buildHreflangAlternates({ baseUrl, config, homeSlug, prefix, slugs })\n\n return {\n url: `${origin}${path}`,\n ...(doc.updatedAt ? { lastModified: doc.updatedAt } : {}),\n ...(changeFrequency ? { changeFrequency } : {}),\n ...(Object.keys(languages).length ? { alternates: { languages } } : {}),\n }\n}\n\n/**\n * Collects every public URL — pages and archive entries — as sitemap entries\n * with per-URL hreflang and lastmod.\n *\n * Reuses the same path/hreflang builders as page metadata, so the sitemap can't\n * drift from what the pages actually render (a classic source of sitemap bugs:\n * a URL listed one way and served another). Drafts and noindex documents are\n * omitted.\n *\n * ```ts\n * // app/sitemap.ts\n * export default async function sitemap() {\n * return buildSitemapEntries({\n * payload: await getPayload({ config }),\n * config: i18nConfig,\n * baseUrl: process.env.NEXT_PUBLIC_SERVER_URL!,\n * content: contentConfig,\n * })\n * }\n * ```\n */\nexport async function buildSitemapEntries({\n baseUrl,\n changeFrequency = 'weekly',\n config,\n content,\n homeSlug,\n pagesSlug = 'pages',\n payload,\n settingsSlug = 'site-settings',\n}: BuildSitemapArgs): Promise {\n const locales = config.locales.map((l) => l.code)\n const defaultLocale = config.defaultLocale\n\n // Resolve homeSlug and archive prefixes from System Pages (read once, in all\n // locales so archive prefixes are available per language).\n const settings = (await payload.findGlobal({\n slug: settingsSlug as never,\n depth: 1,\n locale: 'all' as never,\n })) as Record\n\n const resolvedHomeSlug =\n homeSlug ?? extractSlugInLocale(settings.homepage, defaultLocale) ?? 'home'\n\n // Which collections to walk: pages (no prefix) + each content collection with\n // its archive-page slugs as the localized prefix.\n const collections: CollectionEntry[] = [{ slug: pagesSlug }]\n\n for (const c of content?.collections ?? []) {\n const archive = settings[archiveFieldName(c.slug)]\n const prefixSlugs = archive ? slugMapAllLocales(archive, locales) : undefined\n collections.push({ slug: c.slug, prefixSlugs })\n }\n\n const entries: SitemapEntry[] = []\n\n for (const collection of collections) {\n // Read every doc once, across locales, so one row yields all its language\n // alternates without re-querying per locale.\n const result = await payload.find({\n collection: collection.slug as never,\n depth: 0,\n limit: 0, // no pagination — sitemap wants everything\n locale: 'all' as never,\n pagination: false as never,\n })\n\n for (const raw of result.docs as DocRow[]) {\n if (!isIndexable(raw)) {continue}\n\n // Emit the entry under the default locale's URL; alternates cover the rest.\n const entry = entryFor(\n raw,\n defaultLocale,\n config,\n baseUrl,\n resolvedHomeSlug,\n collection.prefixSlugs,\n changeFrequency,\n )\n if (entry) {entries.push(entry)}\n }\n }\n\n return entries\n}\n\n/** Pulls a slug string from a populated relationship in a specific locale. */\nfunction extractSlugInLocale(rel: unknown, locale: string): string | undefined {\n if (!rel || typeof rel !== 'object') {return undefined}\n const slug = (rel as { slug?: unknown }).slug\n if (typeof slug === 'string') {return slug}\n if (slug && typeof slug === 'object') {\n const v = (slug as Record)[locale]\n return typeof v === 'string' ? v : undefined\n }\n return undefined\n}\n\n/** Builds a locale→slug map from a populated archive relationship. */\nfunction slugMapAllLocales(rel: unknown, locales: string[]): Record | undefined {\n if (!rel || typeof rel !== 'object') {return undefined}\n const slug = (rel as { slug?: unknown }).slug\n if (!slug || typeof slug !== 'object') {return undefined}\n\n const map: Record = {}\n for (const locale of locales) {\n const v = (slug as Record)[locale]\n if (typeof v === 'string') {map[locale] = v}\n }\n return Object.keys(map).length ? map : undefined\n}\n"],"names":["archiveFieldName","buildLocalizedPath","getLocalizedSlugs","buildHreflangAlternates","isIndexable","doc","_status","meta","noindex","entryFor","locale","config","baseUrl","homeSlug","prefix","changeFrequency","slugs","slug","slugField","path","origin","replace","languages","url","updatedAt","lastModified","Object","keys","length","alternates","buildSitemapEntries","content","pagesSlug","payload","settingsSlug","locales","map","l","code","defaultLocale","settings","findGlobal","depth","resolvedHomeSlug","extractSlugInLocale","homepage","collections","c","archive","prefixSlugs","slugMapAllLocales","undefined","push","entries","collection","result","find","limit","pagination","raw","docs","entry","rel","v"],"mappings":"AAKA,SAASA,gBAAgB,QAAQ,sBAAqB;AACtD,SAASC,kBAAkB,EAAEC,iBAAiB,QAAS,mBAAkB;AACzE,SAASC,uBAAuB,QAAQ,gBAAe;AAmDvD,6DAA6D,GAC7D,SAASC,YAAYC,GAAW;IAC9B,IAAIA,IAAIC,OAAO,IAAID,IAAIC,OAAO,KAAK,aAAa;QAAC,OAAO;IAAK;IAC7D,IAAID,IAAIE,IAAI,EAAEC,SAAS;QAAC,OAAO;IAAK;IACpC,OAAO;AACT;AAEA;;;;;CAKC,GACD,SAASC,SACPJ,GAAW,EACXK,MAAc,EACdC,MAAkB,EAClBC,OAAe,EACfC,QAA4B,EAC5BC,MAA0C,EAC1CC,eAAgD;IAEhD,MAAMC,QACJX,IAAIY,IAAI,IAAI,OAAOZ,IAAIY,IAAI,KAAK,WAC5Bf,kBAAkB;QAAES;QAAQO,WAAWb,IAAIY,IAAI;IAA4B,KAC3E,CAAC;IAEP,MAAME,OAAOlB,mBAAmB;QAAEU;QAAQE;QAAUH;QAAQI;QAAQE;IAAM;IAC1E,IAAI,CAACG,MAAM;QAAC,OAAO;IAAI;IAEvB,MAAMC,SAASR,QAAQS,OAAO,CAAC,OAAO;IACtC,MAAMC,YAAYnB,wBAAwB;QAAES;QAASD;QAAQE;QAAUC;QAAQE;IAAM;IAErF,OAAO;QACLO,KAAK,GAAGH,SAASD,MAAM;QACvB,GAAId,IAAImB,SAAS,GAAG;YAAEC,cAAcpB,IAAImB,SAAS;QAAC,IAAI,CAAC,CAAC;QACxD,GAAIT,kBAAkB;YAAEA;QAAgB,IAAI,CAAC,CAAC;QAC9C,GAAIW,OAAOC,IAAI,CAACL,WAAWM,MAAM,GAAG;YAAEC,YAAY;gBAAEP;YAAU;QAAE,IAAI,CAAC,CAAC;IACxE;AACF;AAEA;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,eAAeQ,oBAAoB,EACxClB,OAAO,EACPG,kBAAkB,QAAQ,EAC1BJ,MAAM,EACNoB,OAAO,EACPlB,QAAQ,EACRmB,YAAY,OAAO,EACnBC,OAAO,EACPC,eAAe,eAAe,EACb;IACjB,MAAMC,UAAUxB,OAAOwB,OAAO,CAACC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAChD,MAAMC,gBAAgB5B,OAAO4B,aAAa;IAE1C,6EAA6E;IAC7E,2DAA2D;IAC3D,MAAMC,WAAY,MAAMP,QAAQQ,UAAU,CAAC;QACzCxB,MAAMiB;QACNQ,OAAO;QACPhC,QAAQ;IACV;IAEA,MAAMiC,mBACJ9B,YAAY+B,oBAAoBJ,SAASK,QAAQ,EAAEN,kBAAkB;IAEvE,8EAA8E;IAC9E,kDAAkD;IAClD,MAAMO,cAAiC;QAAC;YAAE7B,MAAMe;QAAU;KAAE;IAE5D,KAAK,MAAMe,KAAKhB,SAASe,eAAe,EAAE,CAAE;QAC1C,MAAME,UAAUR,QAAQ,CAACxC,iBAAiB+C,EAAE9B,IAAI,EAAE;QAClD,MAAMgC,cAAcD,UAAUE,kBAAkBF,SAASb,WAAWgB;QACpEL,YAAYM,IAAI,CAAC;YAAEnC,MAAM8B,EAAE9B,IAAI;YAAEgC;QAAY;IAC/C;IAEA,MAAMI,UAA0B,EAAE;IAElC,KAAK,MAAMC,cAAcR,YAAa;QACpC,0EAA0E;QAC1E,6CAA6C;QAC7C,MAAMS,SAAS,MAAMtB,QAAQuB,IAAI,CAAC;YAChCF,YAAYA,WAAWrC,IAAI;YAC3ByB,OAAO;YACPe,OAAO;YACP/C,QAAQ;YACRgD,YAAY;QACd;QAEA,KAAK,MAAMC,OAAOJ,OAAOK,IAAI,CAAc;YACzC,IAAI,CAACxD,YAAYuD,MAAM;gBAAC;YAAQ;YAEhC,4EAA4E;YAC5E,MAAME,QAAQpD,SACZkD,KACApB,eACA5B,QACAC,SACA+B,kBACAW,WAAWL,WAAW,EACtBlC;YAEF,IAAI8C,OAAO;gBAACR,QAAQD,IAAI,CAACS;YAAM;QACjC;IACF;IAEA,OAAOR;AACT;AAEA,4EAA4E,GAC5E,SAAST,oBAAoBkB,GAAY,EAAEpD,MAAc;IACvD,IAAI,CAACoD,OAAO,OAAOA,QAAQ,UAAU;QAAC,OAAOX;IAAS;IACtD,MAAMlC,OAAO,AAAC6C,IAA2B7C,IAAI;IAC7C,IAAI,OAAOA,SAAS,UAAU;QAAC,OAAOA;IAAI;IAC1C,IAAIA,QAAQ,OAAOA,SAAS,UAAU;QACpC,MAAM8C,IAAI,AAAC9C,IAAgC,CAACP,OAAO;QACnD,OAAO,OAAOqD,MAAM,WAAWA,IAAIZ;IACrC;IACA,OAAOA;AACT;AAEA,oEAAoE,GACpE,SAASD,kBAAkBY,GAAY,EAAE3B,OAAiB;IACxD,IAAI,CAAC2B,OAAO,OAAOA,QAAQ,UAAU;QAAC,OAAOX;IAAS;IACtD,MAAMlC,OAAO,AAAC6C,IAA2B7C,IAAI;IAC7C,IAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU;QAAC,OAAOkC;IAAS;IAExD,MAAMf,MAA8B,CAAC;IACrC,KAAK,MAAM1B,UAAUyB,QAAS;QAC5B,MAAM4B,IAAI,AAAC9C,IAAgC,CAACP,OAAO;QACnD,IAAI,OAAOqD,MAAM,UAAU;YAAC3B,GAAG,CAAC1B,OAAO,GAAGqD;QAAC;IAC7C;IACA,OAAOrC,OAAOC,IAAI,CAACS,KAAKR,MAAM,GAAGQ,MAAMe;AACzC"} \ No newline at end of file +{"version":3,"sources":["buildSitemapEntries.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from '../content/index.js'\nimport type { I18nConfig } from '../i18n/index.js'\n\nimport { archiveFieldName } from '../content/index.js'\nimport { buildLocalizedPath, getLocalizedSlugs } from '../i18n/index.js'\nimport { buildHreflangAlternates } from './hreflang.js'\n\n/**\n * One sitemap entry, shaped for Next's `app/sitemap.ts`.\n *\n * `alternates.languages` is the important part for a multilingual site: Next\n * renders it as `` per URL, which is\n * exactly what Google uses to connect language versions. A sitemap without it\n * is the common, weaker kind.\n */\nexport type SitemapEntry = {\n alternates?: { languages: Record }\n changeFrequency?: 'always' | 'daily' | 'hourly' | 'monthly' | 'never' | 'weekly' | 'yearly'\n lastModified?: Date | string\n priority?: number\n url: string\n}\n\ntype CollectionEntry = {\n /** Localized segment for entries (archive page slugs), when applicable. */\n prefixSlugs?: Record\n slug: string\n}\n\ntype BuildSitemapArgs = {\n /** Absolute origin, e.g. 'https://example.com'. Required for valid sitemap URLs. */\n baseUrl: string\n changeFrequency?: SitemapEntry['changeFrequency']\n config: I18nConfig\n /** Archive-backed collections, same value as the plugin option. */\n content?: ContentOption\n /**\n * Slug of the page that is the site root (collapses to /{locale}).\n * Read from System Pages when omitted.\n */\n homeSlug?: string\n /** Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\ntype DocRow = {\n _status?: string\n id: number | string\n meta?: { noindex?: boolean } | null\n slug?: unknown\n updatedAt?: string\n}\n\n/** Skip drafts and anything flagged noindex in the SEO tab. */\nfunction isIndexable(doc: DocRow): boolean {\n if (doc._status && doc._status !== 'published') {return false}\n if (doc.meta?.noindex) {return false}\n return true\n}\n\n/**\n * Turns one document into a sitemap entry per default locale isn't needed —\n * one entry with all languages as alternates is the correct, de-duplicated\n * shape. The `url` is the current locale's path; `alternates.languages` carries\n * every locale (including itself, per Google's guidance).\n */\nfunction entryFor(\n doc: DocRow,\n locale: string,\n config: I18nConfig,\n baseUrl: string,\n homeSlug: string | undefined,\n prefix: Record | undefined,\n changeFrequency: SitemapEntry['changeFrequency'],\n): null | SitemapEntry {\n const slugs =\n doc.slug && typeof doc.slug === 'object'\n ? getLocalizedSlugs({ config, slugField: doc.slug as Record })\n : {}\n\n const path = buildLocalizedPath({ config, homeSlug, locale, prefix, slugs })\n if (!path) {return null}\n\n const origin = baseUrl.replace(/\\/$/, '')\n const languages = buildHreflangAlternates({ baseUrl, config, homeSlug, prefix, slugs })\n\n return {\n url: `${origin}${path}`,\n ...(doc.updatedAt ? { lastModified: doc.updatedAt } : {}),\n ...(changeFrequency ? { changeFrequency } : {}),\n ...(Object.keys(languages).length ? { alternates: { languages } } : {}),\n }\n}\n\n/**\n * Collects every public URL — pages and archive entries — as sitemap entries\n * with per-URL hreflang and lastmod.\n *\n * Reuses the same path/hreflang builders as page metadata, so the sitemap can't\n * drift from what the pages actually render (a classic source of sitemap bugs:\n * a URL listed one way and served another). Drafts and noindex documents are\n * omitted.\n *\n * ```ts\n * // app/sitemap.ts\n * export default async function sitemap() {\n * return buildSitemapEntries({\n * payload: await getPayload({ config }),\n * config: i18nConfig,\n * baseUrl: process.env.NEXT_PUBLIC_SERVER_URL!,\n * content: contentConfig,\n * })\n * }\n * ```\n */\nexport async function buildSitemapEntries({\n baseUrl,\n changeFrequency = 'weekly',\n config,\n content,\n homeSlug,\n pagesSlug = 'pages',\n payload,\n settingsSlug = 'site-settings',\n}: BuildSitemapArgs): Promise {\n const locales = config.locales.map((l) => l.code)\n const defaultLocale = config.defaultLocale\n\n // Resolve homeSlug and archive prefixes from System Pages (read once, in all\n // locales so archive prefixes are available per language).\n const settings = (await payload.findGlobal({\n slug: settingsSlug as never,\n depth: 1,\n locale: 'all' as never,\n })) as Record\n\n const resolvedHomeSlug =\n homeSlug ?? extractSlugInLocale(settings.homepage, defaultLocale) ?? 'home'\n\n // Which collections to walk: pages (no prefix) + each content collection with\n // its archive-page slugs as the localized prefix.\n const collections: CollectionEntry[] = [{ slug: pagesSlug }]\n\n for (const c of content?.collections ?? []) {\n const archive = settings[archiveFieldName(c.slug)]\n const prefixSlugs = archive ? slugMapAllLocales(archive, locales) : undefined\n collections.push({ slug: c.slug, prefixSlugs })\n }\n\n const entries: SitemapEntry[] = []\n\n for (const collection of collections) {\n // Read every doc once, across locales, so one row yields all its language\n // alternates without re-querying per locale.\n const result = await payload.find({\n collection: collection.slug as never,\n depth: 0,\n limit: 0, // no pagination — sitemap wants everything\n locale: 'all' as never,\n pagination: false as never,\n })\n\n for (const raw of result.docs as DocRow[]) {\n if (!isIndexable(raw)) {continue}\n\n // Emit the entry under the default locale's URL; alternates cover the rest.\n const entry = entryFor(\n raw,\n defaultLocale,\n config,\n baseUrl,\n resolvedHomeSlug,\n collection.prefixSlugs,\n changeFrequency,\n )\n if (entry) {entries.push(entry)}\n }\n }\n\n return entries\n}\n\n/** Pulls a slug string from a populated relationship in a specific locale. */\nfunction extractSlugInLocale(rel: unknown, locale: string): string | undefined {\n if (!rel || typeof rel !== 'object') {return undefined}\n const slug = (rel as { slug?: unknown }).slug\n if (typeof slug === 'string') {return slug}\n if (slug && typeof slug === 'object') {\n const v = (slug as Record)[locale]\n return typeof v === 'string' ? v : undefined\n }\n return undefined\n}\n\n/** Builds a locale→slug map from a populated archive relationship. */\nfunction slugMapAllLocales(rel: unknown, locales: string[]): Record | undefined {\n if (!rel || typeof rel !== 'object') {return undefined}\n const slug = (rel as { slug?: unknown }).slug\n if (!slug || typeof slug !== 'object') {return undefined}\n\n const map: Record = {}\n for (const locale of locales) {\n const v = (slug as Record)[locale]\n if (typeof v === 'string') {map[locale] = v}\n }\n return Object.keys(map).length ? map : undefined\n}\n"],"names":["archiveFieldName","buildLocalizedPath","getLocalizedSlugs","buildHreflangAlternates","isIndexable","doc","_status","meta","noindex","entryFor","locale","config","baseUrl","homeSlug","prefix","changeFrequency","slugs","slug","slugField","path","origin","replace","languages","url","updatedAt","lastModified","Object","keys","length","alternates","buildSitemapEntries","content","pagesSlug","payload","settingsSlug","locales","map","l","code","defaultLocale","settings","findGlobal","depth","resolvedHomeSlug","extractSlugInLocale","homepage","collections","c","archive","prefixSlugs","slugMapAllLocales","undefined","push","entries","collection","result","find","limit","pagination","raw","docs","entry","rel","v"],"mappings":"AAKA,SAASA,gBAAgB,QAAQ,sBAAqB;AACtD,SAASC,kBAAkB,EAAEC,iBAAiB,QAAS,mBAAkB;AACzE,SAASC,uBAAuB,QAAQ,gBAAe;AAmDvD,6DAA6D,GAC7D,SAASC,YAAYC,GAAW;IAC9B,IAAIA,IAAIC,OAAO,IAAID,IAAIC,OAAO,KAAK,aAAa;QAAC,OAAO;IAAK;IAC7D,IAAID,IAAIE,IAAI,EAAEC,SAAS;QAAC,OAAO;IAAK;IACpC,OAAO;AACT;AAEA;;;;;CAKC,GACD,SAASC,SACPJ,GAAW,EACXK,MAAc,EACdC,MAAkB,EAClBC,OAAe,EACfC,QAA4B,EAC5BC,MAA0C,EAC1CC,eAAgD;IAEhD,MAAMC,QACJX,IAAIY,IAAI,IAAI,OAAOZ,IAAIY,IAAI,KAAK,WAC5Bf,kBAAkB;QAAES;QAAQO,WAAWb,IAAIY,IAAI;IAA4B,KAC3E,CAAC;IAEP,MAAME,OAAOlB,mBAAmB;QAAEU;QAAQE;QAAUH;QAAQI;QAAQE;IAAM;IAC1E,IAAI,CAACG,MAAM;QAAC,OAAO;IAAI;IAEvB,MAAMC,SAASR,QAAQS,OAAO,CAAC,OAAO;IACtC,MAAMC,YAAYnB,wBAAwB;QAAES;QAASD;QAAQE;QAAUC;QAAQE;IAAM;IAErF,OAAO;QACLO,KAAK,GAAGH,SAASD,MAAM;QACvB,GAAId,IAAImB,SAAS,GAAG;YAAEC,cAAcpB,IAAImB,SAAS;QAAC,IAAI,CAAC,CAAC;QACxD,GAAIT,kBAAkB;YAAEA;QAAgB,IAAI,CAAC,CAAC;QAC9C,GAAIW,OAAOC,IAAI,CAACL,WAAWM,MAAM,GAAG;YAAEC,YAAY;gBAAEP;YAAU;QAAE,IAAI,CAAC,CAAC;IACxE;AACF;AAEA;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,eAAeQ,oBAAoB,EACxClB,OAAO,EACPG,kBAAkB,QAAQ,EAC1BJ,MAAM,EACNoB,OAAO,EACPlB,QAAQ,EACRmB,YAAY,OAAO,EACnBC,OAAO,EACPC,eAAe,eAAe,EACb;IACjB,MAAMC,UAAUxB,OAAOwB,OAAO,CAACC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAChD,MAAMC,gBAAgB5B,OAAO4B,aAAa;IAE1C,6EAA6E;IAC7E,2DAA2D;IAC3D,MAAMC,WAAY,MAAMP,QAAQQ,UAAU,CAAC;QACzCxB,MAAMiB;QACNQ,OAAO;QACPhC,QAAQ;IACV;IAEA,MAAMiC,mBACJ9B,YAAY+B,oBAAoBJ,SAASK,QAAQ,EAAEN,kBAAkB;IAEvE,8EAA8E;IAC9E,kDAAkD;IAClD,MAAMO,cAAiC;QAAC;YAAE7B,MAAMe;QAAU;KAAE;IAE5D,KAAK,MAAMe,KAAKhB,SAASe,eAAe,EAAE,CAAE;QAC1C,MAAME,UAAUR,QAAQ,CAACxC,iBAAiB+C,EAAE9B,IAAI,EAAE;QAClD,MAAMgC,cAAcD,UAAUE,kBAAkBF,SAASb,WAAWgB;QACpEL,YAAYM,IAAI,CAAC;YAAEnC,MAAM8B,EAAE9B,IAAI;YAAEgC;QAAY;IAC/C;IAEA,MAAMI,UAA0B,EAAE;IAElC,KAAK,MAAMC,cAAcR,YAAa;QACpC,0EAA0E;QAC1E,6CAA6C;QAC7C,MAAMS,SAAS,MAAMtB,QAAQuB,IAAI,CAAC;YAChCF,YAAYA,WAAWrC,IAAI;YAC3ByB,OAAO;YACPe,OAAO;YACP/C,QAAQ;YACRgD,YAAY;QACd;QAEA,KAAK,MAAMC,OAAOJ,OAAOK,IAAI,CAAc;YACzC,IAAI,CAACxD,YAAYuD,MAAM;gBAAC;YAAQ;YAEhC,4EAA4E;YAC5E,MAAME,QAAQpD,SACZkD,KACApB,eACA5B,QACAC,SACA+B,kBACAW,WAAWL,WAAW,EACtBlC;YAEF,IAAI8C,OAAO;gBAACR,QAAQD,IAAI,CAACS;YAAM;QACjC;IACF;IAEA,OAAOR;AACT;AAEA,4EAA4E,GAC5E,SAAST,oBAAoBkB,GAAY,EAAEpD,MAAc;IACvD,IAAI,CAACoD,OAAO,OAAOA,QAAQ,UAAU;QAAC,OAAOX;IAAS;IACtD,MAAMlC,OAAQ6C,IAA2B7C,IAAI;IAC7C,IAAI,OAAOA,SAAS,UAAU;QAAC,OAAOA;IAAI;IAC1C,IAAIA,QAAQ,OAAOA,SAAS,UAAU;QACpC,MAAM8C,IAAK9C,IAAgC,CAACP,OAAO;QACnD,OAAO,OAAOqD,MAAM,WAAWA,IAAIZ;IACrC;IACA,OAAOA;AACT;AAEA,oEAAoE,GACpE,SAASD,kBAAkBY,GAAY,EAAE3B,OAAiB;IACxD,IAAI,CAAC2B,OAAO,OAAOA,QAAQ,UAAU;QAAC,OAAOX;IAAS;IACtD,MAAMlC,OAAQ6C,IAA2B7C,IAAI;IAC7C,IAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU;QAAC,OAAOkC;IAAS;IAExD,MAAMf,MAA8B,CAAC;IACrC,KAAK,MAAM1B,UAAUyB,QAAS;QAC5B,MAAM4B,IAAK9C,IAAgC,CAACP,OAAO;QACnD,IAAI,OAAOqD,MAAM,UAAU;YAAC3B,GAAG,CAAC1B,OAAO,GAAGqD;QAAC;IAC7C;IACA,OAAOrC,OAAOC,IAAI,CAACS,KAAKR,MAAM,GAAGQ,MAAMe;AACzC"} \ No newline at end of file diff --git a/dist/modules/seo/composeTitle.js b/dist/modules/seo/composeTitle.js index 5d5b958..3bb97e6 100644 --- a/dist/modules/seo/composeTitle.js +++ b/dist/modules/seo/composeTitle.js @@ -15,6 +15,6 @@ return order === 'site-first' ? `${site}${separator}${page}` : `${page}${separator}${site}`; } return page || site || ''; -} +} //# sourceMappingURL=composeTitle.js.map //# sourceMappingURL=composeTitle.js.map \ No newline at end of file diff --git a/dist/modules/seo/composeTitle.js.map b/dist/modules/seo/composeTitle.js.map index bfefe1e..c28090d 100644 --- a/dist/modules/seo/composeTitle.js.map +++ b/dist/modules/seo/composeTitle.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/composeTitle.ts"],"sourcesContent":["/** Which part comes first in a composed title. */\nexport type TitleOrder = 'page-first' | 'site-first'\n\ntype ComposeTitleArgs = {\n /** Defaults to 'page-first' — the page title is what a visitor scans for. */\n order?: TitleOrder\n /** Page-specific title, e.g. 'About Us'. */\n pageTitle?: null | string\n /** Separator between page title and site name. Defaults to ' | '. */\n separator?: string\n /** Site name, e.g. 'Acme Inc'. */\n siteName?: null | string\n}\n\n/**\n * Composes a full document title from a page title and the site name.\n *\n * - both, page-first: \"About Us | Acme Inc\"\n * - both, site-first: \"Acme Inc | About Us\"\n * - page only: \"About Us\"\n * - site only: \"Acme Inc\"\n * - neither: \"\"\n *\n * Pure function — no dependency on Payload or request state.\n */\nexport function composeTitle({\n order = 'page-first',\n pageTitle,\n separator = ' | ',\n siteName,\n}: ComposeTitleArgs): string {\n const page = pageTitle?.trim()\n const site = siteName?.trim()\n\n if (page && site) {\n return order === 'site-first' ? `${site}${separator}${page}` : `${page}${separator}${site}`\n }\n return page || site || ''\n}\n"],"names":["composeTitle","order","pageTitle","separator","siteName","page","trim","site"],"mappings":"AAAA,gDAAgD,GAchD;;;;;;;;;;CAUC,GACD,OAAO,SAASA,aAAa,EAC3BC,QAAQ,YAAY,EACpBC,SAAS,EACTC,YAAY,KAAK,EACjBC,QAAQ,EACS;IACjB,MAAMC,OAAOH,WAAWI;IACxB,MAAMC,OAAOH,UAAUE;IAEvB,IAAID,QAAQE,MAAM;QAChB,OAAON,UAAU,eAAe,GAAGM,OAAOJ,YAAYE,MAAM,GAAG,GAAGA,OAAOF,YAAYI,MAAM;IAC7F;IACA,OAAOF,QAAQE,QAAQ;AACzB"} \ No newline at end of file +{"version":3,"sources":["composeTitle.js"],"sourcesContent":["/** Which part comes first in a composed title. */\nexport type TitleOrder = 'page-first' | 'site-first'\n\ntype ComposeTitleArgs = {\n /** Defaults to 'page-first' — the page title is what a visitor scans for. */\n order?: TitleOrder\n /** Page-specific title, e.g. 'About Us'. */\n pageTitle?: null | string\n /** Separator between page title and site name. Defaults to ' | '. */\n separator?: string\n /** Site name, e.g. 'Acme Inc'. */\n siteName?: null | string\n}\n\n/**\n * Composes a full document title from a page title and the site name.\n *\n * - both, page-first: \"About Us | Acme Inc\"\n * - both, site-first: \"Acme Inc | About Us\"\n * - page only: \"About Us\"\n * - site only: \"Acme Inc\"\n * - neither: \"\"\n *\n * Pure function — no dependency on Payload or request state.\n */\nexport function composeTitle({\n order = 'page-first',\n pageTitle,\n separator = ' | ',\n siteName,\n}: ComposeTitleArgs): string {\n const page = pageTitle?.trim()\n const site = siteName?.trim()\n\n if (page && site) {\n return order === 'site-first' ? `${site}${separator}${page}` : `${page}${separator}${site}`\n }\n return page || site || ''\n}\n"],"names":["composeTitle","order","pageTitle","separator","siteName","page","trim","site"],"mappings":"AAAA,gDAAgD,GAchD;;;;;;;;;;CAUC,GACD,OAAO,SAASA,aAAa,EAC3BC,QAAQ,YAAY,EACpBC,SAAS,EACTC,YAAY,KAAK,EACjBC,QAAQ,EACS;IACjB,MAAMC,OAAOH,WAAWI;IACxB,MAAMC,OAAOH,UAAUE;IAEvB,IAAID,QAAQE,MAAM;QAChB,OAAON,UAAU,eAAe,GAAGM,OAAOJ,YAAYE,MAAM,GAAG,GAAGA,OAAOF,YAAYI,MAAM;IAC7F;IACA,OAAOF,QAAQE,QAAQ;AACzB"} \ No newline at end of file diff --git a/dist/modules/seo/createMetadataGenerator.js b/dist/modules/seo/createMetadataGenerator.js index 4ce37ab..4214430 100644 --- a/dist/modules/seo/createMetadataGenerator.js +++ b/dist/modules/seo/createMetadataGenerator.js @@ -63,6 +63,6 @@ import { buildMetadata } from './buildMetadata.js'; slugs }); }; -} +} //# sourceMappingURL=createMetadataGenerator.js.map //# sourceMappingURL=createMetadataGenerator.js.map \ No newline at end of file diff --git a/dist/modules/seo/createMetadataGenerator.js.map b/dist/modules/seo/createMetadataGenerator.js.map index bd001e5..f63cdf4 100644 --- a/dist/modules/seo/createMetadataGenerator.js.map +++ b/dist/modules/seo/createMetadataGenerator.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/createMetadataGenerator.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\nimport type { PageMetadata } from './buildMetadata.js'\nimport type { SeoMeta } from './types.js'\n\nimport { getLocalizedSlugs } from '../i18n/index.js'\nimport { buildMetadata } from './buildMetadata.js'\n\n/**\n * A document as needed for metadata: its SEO meta group and localized slug.\n */\ntype MetadataDocument = {\n meta?: null | SeoMeta\n slug?: null | Record\n}\n\ntype CreateMetadataGeneratorArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. */\n baseUrl?: string\n config: I18nConfig\n /** Home slug that collapses to the locale root. Defaults to 'home'. */\n homeSlug?: string\n /**\n * Resolves the document to build metadata for, given route params.\n * The client supplies this (they own the collections and routing); it should\n * fetch with `locale: 'all'` so the slug field is a locale→value map.\n */\n resolveDocument: (args: {\n locale: string\n params: Record\n payload: BasePayload\n }) => Promise\n /** Resolves the OG image URL for the document, if any. */\n resolveImageUrl?: (args: {\n doc: MetadataDocument\n locale: string\n payload: BasePayload\n }) => Promise\n /** Resolves the site name (e.g. from SiteSettings). */\n resolveSiteName?: (args: { locale: string; payload: BasePayload }) => Promise\n}\n\n/**\n * Builds a metadata generator, collapsing the usual generateMetadata\n * boilerplate into a single wired-up function.\n *\n * The client provides resolvers (they own collections/routing); the plugin\n * owns the assembly (title composition, canonical, hreflang, OG).\n *\n * The returned function takes `{ payload, params, locale }` — Next calls\n * `generateMetadata({ params })` without those, and the plugin never calls\n * getPayload itself, so the client wraps it in their route file:\n *\n * ```ts\n * // app/(frontend)/[locale]/[[...slug]]/page.tsx\n * const generate = createMetadataGenerator({\n * config: i18nConfig,\n * baseUrl: process.env.NEXT_PUBLIC_SERVER_URL,\n * resolveDocument: async ({ payload, params, locale }) => { ... },\n * })\n *\n * export async function generateMetadata({ params }) {\n * const { locale, slug } = await params\n * const payload = await getPayload({ config: await config })\n * return generate({ payload, params: { slug: slug ?? [] }, locale })\n * }\n * ```\n *\n * `resolveDocument` should fetch with `locale: 'all'` so the slug field comes\n * back as a locale→value map — that's what hreflang alternates are built from.\n */\nexport function createMetadataGenerator(args: CreateMetadataGeneratorArgs) {\n const { baseUrl, config, homeSlug, resolveDocument, resolveImageUrl, resolveSiteName } = args\n\n return async function generateMetadata(context: {\n locale: string\n params: Record\n payload: BasePayload\n }): Promise {\n const { locale, params, payload } = context\n\n const doc = await resolveDocument({ locale, params, payload })\n\n const slugs: LocalizedSlugs = doc?.slug\n ? getLocalizedSlugs({ config, slugField: doc.slug })\n : {}\n\n const [siteName, imageUrl] = await Promise.all([\n resolveSiteName?.({ locale, payload }) ?? Promise.resolve(null),\n doc && resolveImageUrl ? resolveImageUrl({ doc, locale, payload }) : Promise.resolve(null),\n ])\n\n return buildMetadata({\n baseUrl,\n config,\n homeSlug,\n imageUrl,\n locale,\n meta: doc?.meta,\n siteName,\n slugs,\n })\n }\n}\n"],"names":["getLocalizedSlugs","buildMetadata","createMetadataGenerator","args","baseUrl","config","homeSlug","resolveDocument","resolveImageUrl","resolveSiteName","generateMetadata","context","locale","params","payload","doc","slugs","slug","slugField","siteName","imageUrl","Promise","all","resolve","meta"],"mappings":"AAMA,SAASA,iBAAiB,QAAQ,mBAAkB;AACpD,SAASC,aAAa,QAAQ,qBAAoB;AAoClD;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,SAASC,wBAAwBC,IAAiC;IACvE,MAAM,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,eAAe,EAAEC,eAAe,EAAEC,eAAe,EAAE,GAAGN;IAEzF,OAAO,eAAeO,iBAAiBC,OAItC;QACC,MAAM,EAAEC,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAGH;QAEpC,MAAMI,MAAM,MAAMR,gBAAgB;YAAEK;YAAQC;YAAQC;QAAQ;QAE5D,MAAME,QAAwBD,KAAKE,OAC/BjB,kBAAkB;YAAEK;YAAQa,WAAWH,IAAIE,IAAI;QAAC,KAChD,CAAC;QAEL,MAAM,CAACE,UAAUC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAC7Cb,kBAAkB;gBAAEG;gBAAQE;YAAQ,MAAMO,QAAQE,OAAO,CAAC;YAC1DR,OAAOP,kBAAkBA,gBAAgB;gBAAEO;gBAAKH;gBAAQE;YAAQ,KAAKO,QAAQE,OAAO,CAAC;SACtF;QAED,OAAOtB,cAAc;YACnBG;YACAC;YACAC;YACAc;YACAR;YACAY,MAAMT,KAAKS;YACXL;YACAH;QACF;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["createMetadataGenerator.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\nimport type { PageMetadata } from './buildMetadata.js'\nimport type { SeoMeta } from './types.js'\n\nimport { getLocalizedSlugs } from '../i18n/index.js'\nimport { buildMetadata } from './buildMetadata.js'\n\n/**\n * A document as needed for metadata: its SEO meta group and localized slug.\n */\ntype MetadataDocument = {\n meta?: null | SeoMeta\n slug?: null | Record\n}\n\ntype CreateMetadataGeneratorArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. */\n baseUrl?: string\n config: I18nConfig\n /** Home slug that collapses to the locale root. Defaults to 'home'. */\n homeSlug?: string\n /**\n * Resolves the document to build metadata for, given route params.\n * The client supplies this (they own the collections and routing); it should\n * fetch with `locale: 'all'` so the slug field is a locale→value map.\n */\n resolveDocument: (args: {\n locale: string\n params: Record\n payload: BasePayload\n }) => Promise\n /** Resolves the OG image URL for the document, if any. */\n resolveImageUrl?: (args: {\n doc: MetadataDocument\n locale: string\n payload: BasePayload\n }) => Promise\n /** Resolves the site name (e.g. from SiteSettings). */\n resolveSiteName?: (args: { locale: string; payload: BasePayload }) => Promise\n}\n\n/**\n * Builds a metadata generator, collapsing the usual generateMetadata\n * boilerplate into a single wired-up function.\n *\n * The client provides resolvers (they own collections/routing); the plugin\n * owns the assembly (title composition, canonical, hreflang, OG).\n *\n * The returned function takes `{ payload, params, locale }` — Next calls\n * `generateMetadata({ params })` without those, and the plugin never calls\n * getPayload itself, so the client wraps it in their route file:\n *\n * ```ts\n * // app/(frontend)/[locale]/[[...slug]]/page.tsx\n * const generate = createMetadataGenerator({\n * config: i18nConfig,\n * baseUrl: process.env.NEXT_PUBLIC_SERVER_URL,\n * resolveDocument: async ({ payload, params, locale }) => { ... },\n * })\n *\n * export async function generateMetadata({ params }) {\n * const { locale, slug } = await params\n * const payload = await getPayload({ config: await config })\n * return generate({ payload, params: { slug: slug ?? [] }, locale })\n * }\n * ```\n *\n * `resolveDocument` should fetch with `locale: 'all'` so the slug field comes\n * back as a locale→value map — that's what hreflang alternates are built from.\n */\nexport function createMetadataGenerator(args: CreateMetadataGeneratorArgs) {\n const { baseUrl, config, homeSlug, resolveDocument, resolveImageUrl, resolveSiteName } = args\n\n return async function generateMetadata(context: {\n locale: string\n params: Record\n payload: BasePayload\n }): Promise {\n const { locale, params, payload } = context\n\n const doc = await resolveDocument({ locale, params, payload })\n\n const slugs: LocalizedSlugs = doc?.slug\n ? getLocalizedSlugs({ config, slugField: doc.slug })\n : {}\n\n const [siteName, imageUrl] = await Promise.all([\n resolveSiteName?.({ locale, payload }) ?? Promise.resolve(null),\n doc && resolveImageUrl ? resolveImageUrl({ doc, locale, payload }) : Promise.resolve(null),\n ])\n\n return buildMetadata({\n baseUrl,\n config,\n homeSlug,\n imageUrl,\n locale,\n meta: doc?.meta,\n siteName,\n slugs,\n })\n }\n}\n"],"names":["getLocalizedSlugs","buildMetadata","createMetadataGenerator","args","baseUrl","config","homeSlug","resolveDocument","resolveImageUrl","resolveSiteName","generateMetadata","context","locale","params","payload","doc","slugs","slug","slugField","siteName","imageUrl","Promise","all","resolve","meta"],"mappings":"AAMA,SAASA,iBAAiB,QAAQ,mBAAkB;AACpD,SAASC,aAAa,QAAQ,qBAAoB;AAoClD;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,SAASC,wBAAwBC,IAAiC;IACvE,MAAM,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,eAAe,EAAEC,eAAe,EAAEC,eAAe,EAAE,GAAGN;IAEzF,OAAO,eAAeO,iBAAiBC,OAItC;QACC,MAAM,EAAEC,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAGH;QAEpC,MAAMI,MAAM,MAAMR,gBAAgB;YAAEK;YAAQC;YAAQC;QAAQ;QAE5D,MAAME,QAAwBD,KAAKE,OAC/BjB,kBAAkB;YAAEK;YAAQa,WAAWH,IAAIE,IAAI;QAAC,KAChD,CAAC;QAEL,MAAM,CAACE,UAAUC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAC7Cb,kBAAkB;gBAAEG;gBAAQE;YAAQ,MAAMO,QAAQE,OAAO,CAAC;YAC1DR,OAAOP,kBAAkBA,gBAAgB;gBAAEO;gBAAKH;gBAAQE;YAAQ,KAAKO,QAAQE,OAAO,CAAC;SACtF;QAED,OAAOtB,cAAc;YACnBG;YACAC;YACAC;YACAc;YACAR;YACAY,MAAMT,KAAKS;YACXL;YACAH;QACF;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/createPageMetadata.js b/dist/modules/seo/createPageMetadata.js index 0880024..af0aefc 100644 --- a/dist/modules/seo/createPageMetadata.js +++ b/dist/modules/seo/createPageMetadata.js @@ -95,6 +95,6 @@ import { slugsAcrossLocales } from './slugsAcrossLocales.js'; slugs }); }; -} +} //# sourceMappingURL=createPageMetadata.js.map //# sourceMappingURL=createPageMetadata.js.map \ No newline at end of file diff --git a/dist/modules/seo/createPageMetadata.js.map b/dist/modules/seo/createPageMetadata.js.map index e1b3faf..729a97a 100644 --- a/dist/modules/seo/createPageMetadata.js.map +++ b/dist/modules/seo/createPageMetadata.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/createPageMetadata.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from '../content/index.js'\nimport type { I18nConfig } from '../i18n/index.js'\nimport type { PageMetadata } from './buildMetadata.js'\nimport type { SeoMeta } from './types.js'\n\nimport { resolveRoute } from '../content/index.js'\nimport { buildMetadata } from './buildMetadata.js'\nimport { readSiteMetaConfig } from './readSiteMetaConfig.js'\nimport { slugsAcrossLocales } from './slugsAcrossLocales.js'\n\ntype CreatePageMetadataArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. */\n baseUrl?: string\n /** Collection holding pages. Defaults to 'pages'. */\n collection?: string\n config: I18nConfig\n /**\n * Archive-backed collections, same value as the plugin option. Pass it and\n * entry URLs (/pl/artykuly/moj-post) get correct canonical and hreflang;\n * omit it and only pages are handled.\n */\n content?: ContentOption\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n /** Field on SiteSettings holding the site name. Defaults to 'siteName'. */\n siteNameField?: string\n}\n\ntype PageMetadataContext = {\n locale: string\n /**\n * Page number from ?page= on an archive listing. Pass it and page 2 gets a\n * canonical to itself; leave it out and every page claims to be page 1.\n */\n page?: number\n payload: BasePayload\n /** Route slug segments; empty/undefined means the locale root. */\n slug?: string[]\n}\n\ntype DocShape = {\n id: number | string\n meta?: null | SeoMeta\n}\n\n/** plugin-seo stores the OG image as an upload relationship. */\nfunction resolveOgImage(doc: DocShape): null | string {\n const image = (doc.meta as { image?: unknown } | null | undefined)?.image\n if (image && typeof image === 'object' && 'url' in image) {\n return (image as { url: string }).url ?? null\n }\n return null\n}\n\n/**\n * Metadata for the page route, with every resolver already wired.\n *\n * `createMetadataGenerator` asks the client for resolvers because it can't know\n * their collections. But for the plugin's own conventions it does know: pages\n * live in one collection, the site name sits on SiteSettings, the OG image sits\n * on the plugin-seo `meta` group, the home page is whatever System Pages points\n * at, and — with `content` — entries live under their collection's archive page.\n * Re-declaring all that in every project is copy-paste, so this resolves it.\n *\n * Reach for `createMetadataGenerator` instead when a route doesn't follow those\n * conventions — custom image logic, a different global, hand-rolled paths.\n *\n * The plugin never calls getPayload, and Next calls generateMetadata without a\n * payload, so the client keeps a small wrapper:\n *\n * ```ts\n * const pageMetadata = createPageMetadata({ config: i18nConfig, baseUrl, content })\n *\n * export async function generateMetadata({ params }) {\n * const { locale, slug } = await params\n * return pageMetadata({ payload: await getPayload({ config }), locale, slug })\n * }\n * ```\n */\nexport function createPageMetadata(args: CreatePageMetadataArgs) {\n const {\n baseUrl,\n collection = 'pages',\n config,\n content,\n settingsSlug = 'site-settings',\n siteNameField = 'siteName',\n } = args\n\n return async function pageMetadata({\n slug,\n locale,\n page,\n payload,\n }: PageMetadataContext): Promise {\n const site = await readSiteMetaConfig({ locale, payload, settingsSlug, siteNameField })\n\n const base = {\n baseUrl,\n config,\n homeSlug: site.homeSlug,\n locale,\n order: site.order,\n separator: site.separator,\n siteName: site.siteName,\n }\n\n const route = await resolveRoute({\n content,\n locale,\n page,\n pagesSlug: collection,\n payload,\n segments: slug,\n settingsSlug,\n })\n\n // Unknown route (the page component will 404) — still return something\n // coherent rather than throwing during metadata generation.\n if (!route) {\n return buildMetadata({ ...base, meta: null, slugs: {} })\n }\n\n const doc = route.doc as DocShape\n\n // An entry sits under its archive, so its URLs need that segment — and the\n // segment differs per locale, since it's the archive page's own slug.\n const prefix =\n route.type === 'entry'\n ? await slugsAcrossLocales({\n id: (route.archive as DocShape).id,\n collection,\n config,\n payload,\n })\n : undefined\n\n const slugs = await slugsAcrossLocales({\n id: doc.id,\n collection: route.type === 'entry' ? route.collection : collection,\n config,\n payload,\n })\n\n // Page 2 of a listing is its own URL, not a variant of page 1.\n const query = route.type === 'archive' && route.page > 1 ? `?page=${route.page}` : undefined\n\n return buildMetadata({\n ...base,\n imageUrl: resolveOgImage(doc),\n meta: doc.meta,\n prefix,\n query,\n slugs,\n })\n }\n}\n"],"names":["resolveRoute","buildMetadata","readSiteMetaConfig","slugsAcrossLocales","resolveOgImage","doc","image","meta","url","createPageMetadata","args","baseUrl","collection","config","content","settingsSlug","siteNameField","pageMetadata","slug","locale","page","payload","site","base","homeSlug","order","separator","siteName","route","pagesSlug","segments","slugs","prefix","type","id","archive","undefined","query","imageUrl"],"mappings":"AAOA,SAASA,YAAY,QAAQ,sBAAqB;AAClD,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,kBAAkB,QAAQ,0BAAyB;AAqC5D,8DAA8D,GAC9D,SAASC,eAAeC,GAAa;IACnC,MAAMC,QAASD,IAAIE,IAAI,EAA6CD;IACpE,IAAIA,SAAS,OAAOA,UAAU,YAAY,SAASA,OAAO;QACxD,OAAO,AAACA,MAA0BE,GAAG,IAAI;IAC3C;IACA,OAAO;AACT;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASC,mBAAmBC,IAA4B;IAC7D,MAAM,EACJC,OAAO,EACPC,aAAa,OAAO,EACpBC,MAAM,EACNC,OAAO,EACPC,eAAe,eAAe,EAC9BC,gBAAgB,UAAU,EAC3B,GAAGN;IAEJ,OAAO,eAAeO,aAAa,EACjCC,IAAI,EACJC,MAAM,EACNC,IAAI,EACJC,OAAO,EACa;QACpB,MAAMC,OAAO,MAAMpB,mBAAmB;YAAEiB;YAAQE;YAASN;YAAcC;QAAc;QAErF,MAAMO,OAAO;YACXZ;YACAE;YACAW,UAAUF,KAAKE,QAAQ;YACvBL;YACAM,OAAOH,KAAKG,KAAK;YACjBC,WAAWJ,KAAKI,SAAS;YACzBC,UAAUL,KAAKK,QAAQ;QACzB;QAEA,MAAMC,QAAQ,MAAM5B,aAAa;YAC/Bc;YACAK;YACAC;YACAS,WAAWjB;YACXS;YACAS,UAAUZ;YACVH;QACF;QAEA,uEAAuE;QACvE,4DAA4D;QAC5D,IAAI,CAACa,OAAO;YACV,OAAO3B,cAAc;gBAAE,GAAGsB,IAAI;gBAAEhB,MAAM;gBAAMwB,OAAO,CAAC;YAAE;QACxD;QAEA,MAAM1B,MAAMuB,MAAMvB,GAAG;QAErB,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM2B,SACJJ,MAAMK,IAAI,KAAK,UACX,MAAM9B,mBAAmB;YACvB+B,IAAI,AAACN,MAAMO,OAAO,CAAcD,EAAE;YAClCtB;YACAC;YACAQ;QACF,KACAe;QAEN,MAAML,QAAQ,MAAM5B,mBAAmB;YACrC+B,IAAI7B,IAAI6B,EAAE;YACVtB,YAAYgB,MAAMK,IAAI,KAAK,UAAUL,MAAMhB,UAAU,GAAGA;YACxDC;YACAQ;QACF;QAEA,+DAA+D;QAC/D,MAAMgB,QAAQT,MAAMK,IAAI,KAAK,aAAaL,MAAMR,IAAI,GAAG,IAAI,CAAC,MAAM,EAAEQ,MAAMR,IAAI,EAAE,GAAGgB;QAEnF,OAAOnC,cAAc;YACnB,GAAGsB,IAAI;YACPe,UAAUlC,eAAeC;YACzBE,MAAMF,IAAIE,IAAI;YACdyB;YACAK;YACAN;QACF;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["createPageMetadata.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from '../content/index.js'\nimport type { I18nConfig } from '../i18n/index.js'\nimport type { PageMetadata } from './buildMetadata.js'\nimport type { SeoMeta } from './types.js'\n\nimport { resolveRoute } from '../content/index.js'\nimport { buildMetadata } from './buildMetadata.js'\nimport { readSiteMetaConfig } from './readSiteMetaConfig.js'\nimport { slugsAcrossLocales } from './slugsAcrossLocales.js'\n\ntype CreatePageMetadataArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. */\n baseUrl?: string\n /** Collection holding pages. Defaults to 'pages'. */\n collection?: string\n config: I18nConfig\n /**\n * Archive-backed collections, same value as the plugin option. Pass it and\n * entry URLs (/pl/artykuly/moj-post) get correct canonical and hreflang;\n * omit it and only pages are handled.\n */\n content?: ContentOption\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n /** Field on SiteSettings holding the site name. Defaults to 'siteName'. */\n siteNameField?: string\n}\n\ntype PageMetadataContext = {\n locale: string\n /**\n * Page number from ?page= on an archive listing. Pass it and page 2 gets a\n * canonical to itself; leave it out and every page claims to be page 1.\n */\n page?: number\n payload: BasePayload\n /** Route slug segments; empty/undefined means the locale root. */\n slug?: string[]\n}\n\ntype DocShape = {\n id: number | string\n meta?: null | SeoMeta\n}\n\n/** plugin-seo stores the OG image as an upload relationship. */\nfunction resolveOgImage(doc: DocShape): null | string {\n const image = (doc.meta as { image?: unknown } | null | undefined)?.image\n if (image && typeof image === 'object' && 'url' in image) {\n return (image as { url: string }).url ?? null\n }\n return null\n}\n\n/**\n * Metadata for the page route, with every resolver already wired.\n *\n * `createMetadataGenerator` asks the client for resolvers because it can't know\n * their collections. But for the plugin's own conventions it does know: pages\n * live in one collection, the site name sits on SiteSettings, the OG image sits\n * on the plugin-seo `meta` group, the home page is whatever System Pages points\n * at, and — with `content` — entries live under their collection's archive page.\n * Re-declaring all that in every project is copy-paste, so this resolves it.\n *\n * Reach for `createMetadataGenerator` instead when a route doesn't follow those\n * conventions — custom image logic, a different global, hand-rolled paths.\n *\n * The plugin never calls getPayload, and Next calls generateMetadata without a\n * payload, so the client keeps a small wrapper:\n *\n * ```ts\n * const pageMetadata = createPageMetadata({ config: i18nConfig, baseUrl, content })\n *\n * export async function generateMetadata({ params }) {\n * const { locale, slug } = await params\n * return pageMetadata({ payload: await getPayload({ config }), locale, slug })\n * }\n * ```\n */\nexport function createPageMetadata(args: CreatePageMetadataArgs) {\n const {\n baseUrl,\n collection = 'pages',\n config,\n content,\n settingsSlug = 'site-settings',\n siteNameField = 'siteName',\n } = args\n\n return async function pageMetadata({\n slug,\n locale,\n page,\n payload,\n }: PageMetadataContext): Promise {\n const site = await readSiteMetaConfig({ locale, payload, settingsSlug, siteNameField })\n\n const base = {\n baseUrl,\n config,\n homeSlug: site.homeSlug,\n locale,\n order: site.order,\n separator: site.separator,\n siteName: site.siteName,\n }\n\n const route = await resolveRoute({\n content,\n locale,\n page,\n pagesSlug: collection,\n payload,\n segments: slug,\n settingsSlug,\n })\n\n // Unknown route (the page component will 404) — still return something\n // coherent rather than throwing during metadata generation.\n if (!route) {\n return buildMetadata({ ...base, meta: null, slugs: {} })\n }\n\n const doc = route.doc as DocShape\n\n // An entry sits under its archive, so its URLs need that segment — and the\n // segment differs per locale, since it's the archive page's own slug.\n const prefix =\n route.type === 'entry'\n ? await slugsAcrossLocales({\n id: (route.archive as DocShape).id,\n collection,\n config,\n payload,\n })\n : undefined\n\n const slugs = await slugsAcrossLocales({\n id: doc.id,\n collection: route.type === 'entry' ? route.collection : collection,\n config,\n payload,\n })\n\n // Page 2 of a listing is its own URL, not a variant of page 1.\n const query = route.type === 'archive' && route.page > 1 ? `?page=${route.page}` : undefined\n\n return buildMetadata({\n ...base,\n imageUrl: resolveOgImage(doc),\n meta: doc.meta,\n prefix,\n query,\n slugs,\n })\n }\n}\n"],"names":["resolveRoute","buildMetadata","readSiteMetaConfig","slugsAcrossLocales","resolveOgImage","doc","image","meta","url","createPageMetadata","args","baseUrl","collection","config","content","settingsSlug","siteNameField","pageMetadata","slug","locale","page","payload","site","base","homeSlug","order","separator","siteName","route","pagesSlug","segments","slugs","prefix","type","id","archive","undefined","query","imageUrl"],"mappings":"AAOA,SAASA,YAAY,QAAQ,sBAAqB;AAClD,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,kBAAkB,QAAQ,0BAAyB;AAqC5D,8DAA8D,GAC9D,SAASC,eAAeC,GAAa;IACnC,MAAMC,QAASD,IAAIE,IAAI,EAA6CD;IACpE,IAAIA,SAAS,OAAOA,UAAU,YAAY,SAASA,OAAO;QACxD,OAAQA,MAA0BE,GAAG,IAAI;IAC3C;IACA,OAAO;AACT;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASC,mBAAmBC,IAA4B;IAC7D,MAAM,EACJC,OAAO,EACPC,aAAa,OAAO,EACpBC,MAAM,EACNC,OAAO,EACPC,eAAe,eAAe,EAC9BC,gBAAgB,UAAU,EAC3B,GAAGN;IAEJ,OAAO,eAAeO,aAAa,EACjCC,IAAI,EACJC,MAAM,EACNC,IAAI,EACJC,OAAO,EACa;QACpB,MAAMC,OAAO,MAAMpB,mBAAmB;YAAEiB;YAAQE;YAASN;YAAcC;QAAc;QAErF,MAAMO,OAAO;YACXZ;YACAE;YACAW,UAAUF,KAAKE,QAAQ;YACvBL;YACAM,OAAOH,KAAKG,KAAK;YACjBC,WAAWJ,KAAKI,SAAS;YACzBC,UAAUL,KAAKK,QAAQ;QACzB;QAEA,MAAMC,QAAQ,MAAM5B,aAAa;YAC/Bc;YACAK;YACAC;YACAS,WAAWjB;YACXS;YACAS,UAAUZ;YACVH;QACF;QAEA,uEAAuE;QACvE,4DAA4D;QAC5D,IAAI,CAACa,OAAO;YACV,OAAO3B,cAAc;gBAAE,GAAGsB,IAAI;gBAAEhB,MAAM;gBAAMwB,OAAO,CAAC;YAAE;QACxD;QAEA,MAAM1B,MAAMuB,MAAMvB,GAAG;QAErB,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM2B,SACJJ,MAAMK,IAAI,KAAK,UACX,MAAM9B,mBAAmB;YACvB+B,IAAKN,MAAMO,OAAO,CAAcD,EAAE;YAClCtB;YACAC;YACAQ;QACF,KACAe;QAEN,MAAML,QAAQ,MAAM5B,mBAAmB;YACrC+B,IAAI7B,IAAI6B,EAAE;YACVtB,YAAYgB,MAAMK,IAAI,KAAK,UAAUL,MAAMhB,UAAU,GAAGA;YACxDC;YACAQ;QACF;QAEA,+DAA+D;QAC/D,MAAMgB,QAAQT,MAAMK,IAAI,KAAK,aAAaL,MAAMR,IAAI,GAAG,IAAI,CAAC,MAAM,EAAEQ,MAAMR,IAAI,EAAE,GAAGgB;QAEnF,OAAOnC,cAAc;YACnB,GAAGsB,IAAI;YACPe,UAAUlC,eAAeC;YACzBE,MAAMF,IAAIE,IAAI;YACdyB;YACAK;YACAN;QACF;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/hreflang.js b/dist/modules/seo/hreflang.js index 0a74d4e..93df8cf 100644 --- a/dist/modules/seo/hreflang.js +++ b/dist/modules/seo/hreflang.js @@ -30,6 +30,6 @@ import { buildLocalizedPath, getLocaleCodes } from '../i18n/index.js'; } } return alternates; -} +} //# sourceMappingURL=hreflang.js.map //# sourceMappingURL=hreflang.js.map \ No newline at end of file diff --git a/dist/modules/seo/hreflang.js.map b/dist/modules/seo/hreflang.js.map index 4ca97fe..672c330 100644 --- a/dist/modules/seo/hreflang.js.map +++ b/dist/modules/seo/hreflang.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/hreflang.ts"],"sourcesContent":["import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\n\nimport { buildLocalizedPath, getLocaleCodes } from '../i18n/index.js'\n\ntype BuildHreflangArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. Omit for relative paths. */\n baseUrl?: string\n config: I18nConfig\n /** Home slug that collapses to the locale root. Defaults to 'home'. */\n homeSlug?: string\n /**\n * Localized segment the document lives under (an archive page's slugs),\n * e.g. { pl: 'artykuly', en: 'articles' }. Locales missing from the prefix\n * are omitted — an entry with no archive in that language has no URL there.\n */\n prefix?: LocalizedSlugs\n /** slug per locale for the current document, e.g. { pl: 'o-nas', en: 'about' } */\n slugs: LocalizedSlugs\n}\n\n/**\n * Builds a map of locale → URL for hreflang alternate links, suitable for\n * Next.js Metadata `alternates.languages`.\n *\n * Bridges SEO and i18n: for each configured locale that the document has a\n * slug in, it produces the locale-aware path (via buildLocalizedPath),\n * optionally prefixed with an absolute origin.\n *\n * @example\n * buildHreflangAlternates({\n * slugs: { pl: 'o-nas', en: 'about' },\n * config,\n * baseUrl: 'https://example.com',\n * })\n * // → { pl: 'https://example.com/pl/o-nas', en: 'https://example.com/en/about' }\n */\nexport function buildHreflangAlternates({\n baseUrl,\n config,\n homeSlug = 'home',\n prefix,\n slugs,\n}: BuildHreflangArgs): Record {\n const origin = baseUrl?.replace(/\\/$/, '') ?? ''\n const alternates: Record = {}\n\n for (const locale of getLocaleCodes(config)) {\n const path = buildLocalizedPath({ config, homeSlug, locale, prefix, slugs })\n if (path) {\n alternates[locale] = `${origin}${path}`\n }\n }\n\n return alternates\n}\n"],"names":["buildLocalizedPath","getLocaleCodes","buildHreflangAlternates","baseUrl","config","homeSlug","prefix","slugs","origin","replace","alternates","locale","path"],"mappings":"AAEA,SAASA,kBAAkB,EAAEC,cAAc,QAAQ,mBAAkB;AAkBrE;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,wBAAwB,EACtCC,OAAO,EACPC,MAAM,EACNC,WAAW,MAAM,EACjBC,MAAM,EACNC,KAAK,EACa;IAClB,MAAMC,SAASL,SAASM,QAAQ,OAAO,OAAO;IAC9C,MAAMC,aAAqC,CAAC;IAE5C,KAAK,MAAMC,UAAUV,eAAeG,QAAS;QAC3C,MAAMQ,OAAOZ,mBAAmB;YAAEI;YAAQC;YAAUM;YAAQL;YAAQC;QAAM;QAC1E,IAAIK,MAAM;YACRF,UAAU,CAACC,OAAO,GAAG,GAAGH,SAASI,MAAM;QACzC;IACF;IAEA,OAAOF;AACT"} \ No newline at end of file +{"version":3,"sources":["hreflang.js"],"sourcesContent":["import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\n\nimport { buildLocalizedPath, getLocaleCodes } from '../i18n/index.js'\n\ntype BuildHreflangArgs = {\n /** Absolute site origin, e.g. 'https://example.com'. Omit for relative paths. */\n baseUrl?: string\n config: I18nConfig\n /** Home slug that collapses to the locale root. Defaults to 'home'. */\n homeSlug?: string\n /**\n * Localized segment the document lives under (an archive page's slugs),\n * e.g. { pl: 'artykuly', en: 'articles' }. Locales missing from the prefix\n * are omitted — an entry with no archive in that language has no URL there.\n */\n prefix?: LocalizedSlugs\n /** slug per locale for the current document, e.g. { pl: 'o-nas', en: 'about' } */\n slugs: LocalizedSlugs\n}\n\n/**\n * Builds a map of locale → URL for hreflang alternate links, suitable for\n * Next.js Metadata `alternates.languages`.\n *\n * Bridges SEO and i18n: for each configured locale that the document has a\n * slug in, it produces the locale-aware path (via buildLocalizedPath),\n * optionally prefixed with an absolute origin.\n *\n * @example\n * buildHreflangAlternates({\n * slugs: { pl: 'o-nas', en: 'about' },\n * config,\n * baseUrl: 'https://example.com',\n * })\n * // → { pl: 'https://example.com/pl/o-nas', en: 'https://example.com/en/about' }\n */\nexport function buildHreflangAlternates({\n baseUrl,\n config,\n homeSlug = 'home',\n prefix,\n slugs,\n}: BuildHreflangArgs): Record {\n const origin = baseUrl?.replace(/\\/$/, '') ?? ''\n const alternates: Record = {}\n\n for (const locale of getLocaleCodes(config)) {\n const path = buildLocalizedPath({ config, homeSlug, locale, prefix, slugs })\n if (path) {\n alternates[locale] = `${origin}${path}`\n }\n }\n\n return alternates\n}\n"],"names":["buildLocalizedPath","getLocaleCodes","buildHreflangAlternates","baseUrl","config","homeSlug","prefix","slugs","origin","replace","alternates","locale","path"],"mappings":"AAEA,SAASA,kBAAkB,EAAEC,cAAc,QAAQ,mBAAkB;AAkBrE;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,wBAAwB,EACtCC,OAAO,EACPC,MAAM,EACNC,WAAW,MAAM,EACjBC,MAAM,EACNC,KAAK,EACa;IAClB,MAAMC,SAASL,SAASM,QAAQ,OAAO,OAAO;IAC9C,MAAMC,aAAqC,CAAC;IAE5C,KAAK,MAAMC,UAAUV,eAAeG,QAAS;QAC3C,MAAMQ,OAAOZ,mBAAmB;YAAEI;YAAQC;YAAUM;YAAQL;YAAQC;QAAM;QAC1E,IAAIK,MAAM;YACRF,UAAU,CAACC,OAAO,GAAG,GAAGH,SAASI,MAAM;QACzC;IACF;IAEA,OAAOF;AACT"} \ No newline at end of file diff --git a/dist/modules/seo/index.js b/dist/modules/seo/index.js index ef3cc66..f2b93ac 100644 --- a/dist/modules/seo/index.js +++ b/dist/modules/seo/index.js @@ -10,6 +10,6 @@ export { injectAutoFillMeta } from './injectAutoFillMeta.js'; export { injectSeoTabs } from './injectSeoTabs.js'; export { readSiteMetaConfig } from './readSiteMetaConfig.js'; export { buildSeoPlugin } from './seoPluginConfig.js'; -export { slugsAcrossLocales } from './slugsAcrossLocales.js'; +export { slugsAcrossLocales } from './slugsAcrossLocales.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/seo/index.js.map b/dist/modules/seo/index.js.map index 7917a96..f498f9c 100644 --- a/dist/modules/seo/index.js.map +++ b/dist/modules/seo/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/index.ts"],"sourcesContent":["export { buildAutoFillMetaHook } from './autoFillMeta.js'\nexport type { AutoFillMapping } from './autoFillMeta.js'\nexport { buildMetadata } from './buildMetadata.js'\nexport type { PageMetadata } from './buildMetadata.js'\nexport { buildRobots } from './buildRobots.js'\nexport type { RobotsRules } from './buildRobots.js'\nexport { buildSitemapEntries } from './buildSitemapEntries.js'\nexport type { SitemapEntry } from './buildSitemapEntries.js'\nexport { composeTitle } from './composeTitle.js'\nexport type { TitleOrder } from './composeTitle.js'\nexport { createMetadataGenerator } from './createMetadataGenerator.js'\nexport { createPageMetadata } from './createPageMetadata.js'\nexport { buildHreflangAlternates } from './hreflang.js'\nexport { injectAutoFillMeta } from './injectAutoFillMeta.js'\nexport { injectSeoTabs } from './injectSeoTabs.js'\nexport { readSiteMetaConfig } from './readSiteMetaConfig.js'\nexport type { SiteMetaConfig } from './readSiteMetaConfig.js'\nexport { buildSeoPlugin } from './seoPluginConfig.js'\nexport { slugsAcrossLocales } from './slugsAcrossLocales.js'\nexport type { SeoMeta, SeoOption } from './types.js'\n"],"names":["buildAutoFillMetaHook","buildMetadata","buildRobots","buildSitemapEntries","composeTitle","createMetadataGenerator","createPageMetadata","buildHreflangAlternates","injectAutoFillMeta","injectSeoTabs","readSiteMetaConfig","buildSeoPlugin","slugsAcrossLocales"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,oBAAmB;AAEzD,SAASC,aAAa,QAAQ,qBAAoB;AAElD,SAASC,WAAW,QAAQ,mBAAkB;AAE9C,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9D,SAASC,YAAY,QAAQ,oBAAmB;AAEhD,SAASC,uBAAuB,QAAQ,+BAA8B;AACtE,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,uBAAuB,QAAQ,gBAAe;AACvD,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,kBAAkB,QAAQ,0BAAyB;AAE5D,SAASC,cAAc,QAAQ,uBAAsB;AACrD,SAASC,kBAAkB,QAAQ,0BAAyB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export { buildAutoFillMetaHook } from './autoFillMeta.js'\nexport type { AutoFillMapping } from './autoFillMeta.js'\nexport { buildMetadata } from './buildMetadata.js'\nexport type { PageMetadata } from './buildMetadata.js'\nexport { buildRobots } from './buildRobots.js'\nexport type { RobotsRules } from './buildRobots.js'\nexport { buildSitemapEntries } from './buildSitemapEntries.js'\nexport type { SitemapEntry } from './buildSitemapEntries.js'\nexport { composeTitle } from './composeTitle.js'\nexport type { TitleOrder } from './composeTitle.js'\nexport { createMetadataGenerator } from './createMetadataGenerator.js'\nexport { createPageMetadata } from './createPageMetadata.js'\nexport { buildHreflangAlternates } from './hreflang.js'\nexport { injectAutoFillMeta } from './injectAutoFillMeta.js'\nexport { injectSeoTabs } from './injectSeoTabs.js'\nexport { readSiteMetaConfig } from './readSiteMetaConfig.js'\nexport type { SiteMetaConfig } from './readSiteMetaConfig.js'\nexport { buildSeoPlugin } from './seoPluginConfig.js'\nexport { slugsAcrossLocales } from './slugsAcrossLocales.js'\nexport type { SeoMeta, SeoOption } from './types.js'\n"],"names":["buildAutoFillMetaHook","buildMetadata","buildRobots","buildSitemapEntries","composeTitle","createMetadataGenerator","createPageMetadata","buildHreflangAlternates","injectAutoFillMeta","injectSeoTabs","readSiteMetaConfig","buildSeoPlugin","slugsAcrossLocales"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,oBAAmB;AAEzD,SAASC,aAAa,QAAQ,qBAAoB;AAElD,SAASC,WAAW,QAAQ,mBAAkB;AAE9C,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9D,SAASC,YAAY,QAAQ,oBAAmB;AAEhD,SAASC,uBAAuB,QAAQ,+BAA8B;AACtE,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,uBAAuB,QAAQ,gBAAe;AACvD,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,kBAAkB,QAAQ,0BAAyB;AAE5D,SAASC,cAAc,QAAQ,uBAAsB;AACrD,SAASC,kBAAkB,QAAQ,0BAAyB"} \ No newline at end of file diff --git a/dist/modules/seo/injectAutoFillMeta.js b/dist/modules/seo/injectAutoFillMeta.js index cdb8518..eeecc49 100644 --- a/dist/modules/seo/injectAutoFillMeta.js +++ b/dist/modules/seo/injectAutoFillMeta.js @@ -30,6 +30,6 @@ import { buildAutoFillMetaHook } from './autoFillMeta.js'; }; }) }; -} +} //# sourceMappingURL=injectAutoFillMeta.js.map //# sourceMappingURL=injectAutoFillMeta.js.map \ No newline at end of file diff --git a/dist/modules/seo/injectAutoFillMeta.js.map b/dist/modules/seo/injectAutoFillMeta.js.map index 8d2a54b..76e43cb 100644 --- a/dist/modules/seo/injectAutoFillMeta.js.map +++ b/dist/modules/seo/injectAutoFillMeta.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/injectAutoFillMeta.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { SeoOption } from './types.js'\n\nimport { buildAutoFillMetaHook } from './autoFillMeta.js'\n\n/**\n * Injects the auto-fill meta hook into every SEO-enabled collection.\n *\n * Runs after @payloadcms/plugin-seo has added the meta group, appending a\n * beforeChange hook that fills empty meta from document content. Existing\n * hooks are preserved (plugin hook runs last, so editor input and other hooks\n * win first).\n */\nexport function injectAutoFillMeta(config: Config, seo: SeoOption): Config {\n // autoFill disabled explicitly — leave collections untouched\n if (seo.autoFill === false) {\n return config\n }\n\n const hook = buildAutoFillMetaHook(seo.autoFill)\n\n return {\n ...config,\n collections: (config.collections ?? []).map((collection) => {\n if (!seo.collections.includes(collection.slug)) {\n return collection\n }\n return {\n ...collection,\n hooks: {\n ...collection.hooks,\n beforeChange: [...(collection.hooks?.beforeChange ?? []), hook],\n },\n }\n }),\n }\n}\n"],"names":["buildAutoFillMetaHook","injectAutoFillMeta","config","seo","autoFill","hook","collections","map","collection","includes","slug","hooks","beforeChange"],"mappings":"AAIA,SAASA,qBAAqB,QAAQ,oBAAmB;AAEzD;;;;;;;CAOC,GACD,OAAO,SAASC,mBAAmBC,MAAc,EAAEC,GAAc;IAC/D,6DAA6D;IAC7D,IAAIA,IAAIC,QAAQ,KAAK,OAAO;QAC1B,OAAOF;IACT;IAEA,MAAMG,OAAOL,sBAAsBG,IAAIC,QAAQ;IAE/C,OAAO;QACL,GAAGF,MAAM;QACTI,aAAa,AAACJ,CAAAA,OAAOI,WAAW,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YAC3C,IAAI,CAACL,IAAIG,WAAW,CAACG,QAAQ,CAACD,WAAWE,IAAI,GAAG;gBAC9C,OAAOF;YACT;YACA,OAAO;gBACL,GAAGA,UAAU;gBACbG,OAAO;oBACL,GAAGH,WAAWG,KAAK;oBACnBC,cAAc;2BAAKJ,WAAWG,KAAK,EAAEC,gBAAgB,EAAE;wBAAGP;qBAAK;gBACjE;YACF;QACF;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["injectAutoFillMeta.js"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { SeoOption } from './types.js'\n\nimport { buildAutoFillMetaHook } from './autoFillMeta.js'\n\n/**\n * Injects the auto-fill meta hook into every SEO-enabled collection.\n *\n * Runs after @payloadcms/plugin-seo has added the meta group, appending a\n * beforeChange hook that fills empty meta from document content. Existing\n * hooks are preserved (plugin hook runs last, so editor input and other hooks\n * win first).\n */\nexport function injectAutoFillMeta(config: Config, seo: SeoOption): Config {\n // autoFill disabled explicitly — leave collections untouched\n if (seo.autoFill === false) {\n return config\n }\n\n const hook = buildAutoFillMetaHook(seo.autoFill)\n\n return {\n ...config,\n collections: (config.collections ?? []).map((collection) => {\n if (!seo.collections.includes(collection.slug)) {\n return collection\n }\n return {\n ...collection,\n hooks: {\n ...collection.hooks,\n beforeChange: [...(collection.hooks?.beforeChange ?? []), hook],\n },\n }\n }),\n }\n}\n"],"names":["buildAutoFillMetaHook","injectAutoFillMeta","config","seo","autoFill","hook","collections","map","collection","includes","slug","hooks","beforeChange"],"mappings":"AAIA,SAASA,qBAAqB,QAAQ,oBAAmB;AAEzD;;;;;;;CAOC,GACD,OAAO,SAASC,mBAAmBC,MAAc,EAAEC,GAAc;IAC/D,6DAA6D;IAC7D,IAAIA,IAAIC,QAAQ,KAAK,OAAO;QAC1B,OAAOF;IACT;IAEA,MAAMG,OAAOL,sBAAsBG,IAAIC,QAAQ;IAE/C,OAAO;QACL,GAAGF,MAAM;QACTI,aAAcJ,CAAAA,OAAOI,WAAW,IAAI,EAAA,AAAC,EAAGC,GAAG,CAAC,CAACC;YAC3C,IAAI,CAACL,IAAIG,WAAW,CAACG,QAAQ,CAACD,WAAWE,IAAI,GAAG;gBAC9C,OAAOF;YACT;YACA,OAAO;gBACL,GAAGA,UAAU;gBACbG,OAAO;oBACL,GAAGH,WAAWG,KAAK;oBACnBC,cAAc;2BAAKJ,WAAWG,KAAK,EAAEC,gBAAgB,EAAE;wBAAGP;qBAAK;gBACjE;YACF;QACF;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/injectSeoTabs.js b/dist/modules/seo/injectSeoTabs.js index c7ba4c4..72027fe 100644 --- a/dist/modules/seo/injectSeoTabs.js +++ b/dist/modules/seo/injectSeoTabs.js @@ -74,6 +74,6 @@ }; }) }; -} +} //# sourceMappingURL=injectSeoTabs.js.map //# sourceMappingURL=injectSeoTabs.js.map \ No newline at end of file diff --git a/dist/modules/seo/injectSeoTabs.js.map b/dist/modules/seo/injectSeoTabs.js.map index 0b5dcfc..03f4d31 100644 --- a/dist/modules/seo/injectSeoTabs.js.map +++ b/dist/modules/seo/injectSeoTabs.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/injectSeoTabs.ts"],"sourcesContent":["import type { Config, Field, TabsField } from 'payload'\n\nimport type { SeoOption } from './types.js'\n\n/**\n * Wraps each SEO-enabled collection's fields into a tabbed UI: a \"Content\" tab\n * holding the collection's own fields, and an \"SEO\" tab holding the `meta`\n * group that @payloadcms/plugin-seo added.\n *\n * This replaces plugin-seo's own `tabbedUI`, which merges tabs by inspecting\n * the first field and breaks when other plugins/fields (roles, slug) already\n * sit in the collection — producing an empty SEO tab. Running this AFTER\n * plugin-seo (so `meta` already exists) and after other field injections lets\n * us build the tabs deterministically.\n *\n * If a collection's first field is already a tabs field, the SEO tab is\n * appended to it instead of creating a new wrapper.\n */\nexport function injectSeoTabs(config: Config, seo: SeoOption): Config {\n return {\n ...config,\n collections: (config.collections ?? []).map((collection) => {\n if (!seo.collections.includes(collection.slug)) {\n return collection\n }\n\n const fields = collection.fields ?? []\n\n // Separate the meta group (added by plugin-seo) from the rest.\n const metaField = fields.find(\n (f): f is { name: string } & Field => 'name' in f && f.name === 'meta',\n )\n const otherFields = fields.filter(\n (f) => !('name' in f && f.name === 'meta'),\n )\n\n // Nothing to do if plugin-seo hasn't added meta (shouldn't happen).\n if (!metaField) {return collection}\n\n // If the collection already leads with a tabs field, append an SEO tab.\n const firstField = otherFields[0]\n if (firstField && firstField.type === 'tabs') {\n const existingTabs = firstField\n const withSeoTab: TabsField = {\n ...existingTabs,\n tabs: [...existingTabs.tabs, { fields: [metaField], label: 'SEO' }],\n }\n return {\n ...collection,\n fields: [withSeoTab, ...otherFields.slice(1)],\n }\n }\n\n // Otherwise wrap everything: Content tab + SEO tab.\n const tabs: TabsField = {\n type: 'tabs',\n tabs: [\n { fields: otherFields, label: collection.labels?.singular?.toString() || 'Content' },\n { fields: [metaField], label: 'SEO' },\n ],\n }\n\n return {\n ...collection,\n fields: [tabs],\n }\n }),\n }\n}\n"],"names":["injectSeoTabs","config","seo","collections","map","collection","includes","slug","fields","metaField","find","f","name","otherFields","filter","firstField","type","existingTabs","withSeoTab","tabs","label","slice","labels","singular","toString"],"mappings":"AAIA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,cAAcC,MAAc,EAAEC,GAAc;IAC1D,OAAO;QACL,GAAGD,MAAM;QACTE,aAAa,AAACF,CAAAA,OAAOE,WAAW,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YAC3C,IAAI,CAACH,IAAIC,WAAW,CAACG,QAAQ,CAACD,WAAWE,IAAI,GAAG;gBAC9C,OAAOF;YACT;YAEA,MAAMG,SAASH,WAAWG,MAAM,IAAI,EAAE;YAEtC,+DAA+D;YAC/D,MAAMC,YAAYD,OAAOE,IAAI,CAC3B,CAACC,IAAqC,UAAUA,KAAKA,EAAEC,IAAI,KAAK;YAElE,MAAMC,cAAcL,OAAOM,MAAM,CAC/B,CAACH,IAAM,CAAE,CAAA,UAAUA,KAAKA,EAAEC,IAAI,KAAK,MAAK;YAG1C,oEAAoE;YACpE,IAAI,CAACH,WAAW;gBAAC,OAAOJ;YAAU;YAElC,wEAAwE;YACxE,MAAMU,aAAaF,WAAW,CAAC,EAAE;YACjC,IAAIE,cAAcA,WAAWC,IAAI,KAAK,QAAQ;gBAC5C,MAAMC,eAAeF;gBACrB,MAAMG,aAAwB;oBAC5B,GAAGD,YAAY;oBACfE,MAAM;2BAAIF,aAAaE,IAAI;wBAAE;4BAAEX,QAAQ;gCAACC;6BAAU;4BAAEW,OAAO;wBAAM;qBAAE;gBACrE;gBACA,OAAO;oBACL,GAAGf,UAAU;oBACbG,QAAQ;wBAACU;2BAAeL,YAAYQ,KAAK,CAAC;qBAAG;gBAC/C;YACF;YAEA,oDAAoD;YACpD,MAAMF,OAAkB;gBACtBH,MAAM;gBACNG,MAAM;oBACJ;wBAAEX,QAAQK;wBAAaO,OAAOf,WAAWiB,MAAM,EAAEC,UAAUC,cAAc;oBAAU;oBACnF;wBAAEhB,QAAQ;4BAACC;yBAAU;wBAAEW,OAAO;oBAAM;iBACrC;YACH;YAEA,OAAO;gBACL,GAAGf,UAAU;gBACbG,QAAQ;oBAACW;iBAAK;YAChB;QACF;IACF;AACF"} \ No newline at end of file +{"version":3,"sources":["injectSeoTabs.js"],"sourcesContent":["import type { Config, Field, TabsField } from 'payload'\n\nimport type { SeoOption } from './types.js'\n\n/**\n * Wraps each SEO-enabled collection's fields into a tabbed UI: a \"Content\" tab\n * holding the collection's own fields, and an \"SEO\" tab holding the `meta`\n * group that @payloadcms/plugin-seo added.\n *\n * This replaces plugin-seo's own `tabbedUI`, which merges tabs by inspecting\n * the first field and breaks when other plugins/fields (roles, slug) already\n * sit in the collection — producing an empty SEO tab. Running this AFTER\n * plugin-seo (so `meta` already exists) and after other field injections lets\n * us build the tabs deterministically.\n *\n * If a collection's first field is already a tabs field, the SEO tab is\n * appended to it instead of creating a new wrapper.\n */\nexport function injectSeoTabs(config: Config, seo: SeoOption): Config {\n return {\n ...config,\n collections: (config.collections ?? []).map((collection) => {\n if (!seo.collections.includes(collection.slug)) {\n return collection\n }\n\n const fields = collection.fields ?? []\n\n // Separate the meta group (added by plugin-seo) from the rest.\n const metaField = fields.find(\n (f): f is { name: string } & Field => 'name' in f && f.name === 'meta',\n )\n const otherFields = fields.filter(\n (f) => !('name' in f && f.name === 'meta'),\n )\n\n // Nothing to do if plugin-seo hasn't added meta (shouldn't happen).\n if (!metaField) {return collection}\n\n // If the collection already leads with a tabs field, append an SEO tab.\n const firstField = otherFields[0]\n if (firstField && firstField.type === 'tabs') {\n const existingTabs = firstField\n const withSeoTab: TabsField = {\n ...existingTabs,\n tabs: [...existingTabs.tabs, { fields: [metaField], label: 'SEO' }],\n }\n return {\n ...collection,\n fields: [withSeoTab, ...otherFields.slice(1)],\n }\n }\n\n // Otherwise wrap everything: Content tab + SEO tab.\n const tabs: TabsField = {\n type: 'tabs',\n tabs: [\n { fields: otherFields, label: collection.labels?.singular?.toString() || 'Content' },\n { fields: [metaField], label: 'SEO' },\n ],\n }\n\n return {\n ...collection,\n fields: [tabs],\n }\n }),\n }\n}\n"],"names":["injectSeoTabs","config","seo","collections","map","collection","includes","slug","fields","metaField","find","f","name","otherFields","filter","firstField","type","existingTabs","withSeoTab","tabs","label","slice","labels","singular","toString"],"mappings":"AAIA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,cAAcC,MAAc,EAAEC,GAAc;IAC1D,OAAO;QACL,GAAGD,MAAM;QACTE,aAAcF,CAAAA,OAAOE,WAAW,IAAI,EAAA,AAAC,EAAGC,GAAG,CAAC,CAACC;YAC3C,IAAI,CAACH,IAAIC,WAAW,CAACG,QAAQ,CAACD,WAAWE,IAAI,GAAG;gBAC9C,OAAOF;YACT;YAEA,MAAMG,SAASH,WAAWG,MAAM,IAAI,EAAE;YAEtC,+DAA+D;YAC/D,MAAMC,YAAYD,OAAOE,IAAI,CAC3B,CAACC,IAAqC,UAAUA,KAAKA,EAAEC,IAAI,KAAK;YAElE,MAAMC,cAAcL,OAAOM,MAAM,CAC/B,CAACH,IAAM,CAAE,CAAA,UAAUA,KAAKA,EAAEC,IAAI,KAAK,MAAA,CAAK;YAG1C,oEAAoE;YACpE,IAAI,CAACH,WAAW;gBAAC,OAAOJ;YAAU;YAElC,wEAAwE;YACxE,MAAMU,aAAaF,WAAW,CAAC,EAAE;YACjC,IAAIE,cAAcA,WAAWC,IAAI,KAAK,QAAQ;gBAC5C,MAAMC,eAAeF;gBACrB,MAAMG,aAAwB;oBAC5B,GAAGD,YAAY;oBACfE,MAAM;2BAAIF,aAAaE,IAAI;wBAAE;4BAAEX,QAAQ;gCAACC;6BAAU;4BAAEW,OAAO;wBAAM;qBAAE;gBACrE;gBACA,OAAO;oBACL,GAAGf,UAAU;oBACbG,QAAQ;wBAACU;2BAAeL,YAAYQ,KAAK,CAAC;qBAAG;gBAC/C;YACF;YAEA,oDAAoD;YACpD,MAAMF,OAAkB;gBACtBH,MAAM;gBACNG,MAAM;oBACJ;wBAAEX,QAAQK;wBAAaO,OAAOf,WAAWiB,MAAM,EAAEC,UAAUC,cAAc;oBAAU;oBACnF;wBAAEhB,QAAQ;4BAACC;yBAAU;wBAAEW,OAAO;oBAAM;iBACrC;YACH;YAEA,OAAO;gBACL,GAAGf,UAAU;gBACbG,QAAQ;oBAACW;iBAAK;YAChB;QACF;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/readSiteMetaConfig.js b/dist/modules/seo/readSiteMetaConfig.js index f6f8b61..60ba1fd 100644 --- a/dist/modules/seo/readSiteMetaConfig.js +++ b/dist/modules/seo/readSiteMetaConfig.js @@ -21,6 +21,6 @@ order: settings.titleOrder ?? undefined, separator: settings.titleSeparator ? ` ${settings.titleSeparator} ` : undefined }; -} +} //# sourceMappingURL=readSiteMetaConfig.js.map //# sourceMappingURL=readSiteMetaConfig.js.map \ No newline at end of file diff --git a/dist/modules/seo/readSiteMetaConfig.js.map b/dist/modules/seo/readSiteMetaConfig.js.map index 940557a..a5aa11c 100644 --- a/dist/modules/seo/readSiteMetaConfig.js.map +++ b/dist/modules/seo/readSiteMetaConfig.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/readSiteMetaConfig.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { TitleOrder } from './composeTitle.js'\n\ntype SettingsShape = {\n [key: string]: unknown\n homepage?: { id: number | string; slug?: unknown } | null | number | string\n titleOrder?: null | TitleOrder\n titleSeparator?: null | string\n}\n\nexport type SiteMetaConfig = {\n /** Slug of the page assigned as Homepage; collapses to the locale root. */\n homeSlug?: string\n order?: TitleOrder\n /** Padded separator, e.g. ' | ' — the panel stores the bare character. */\n separator?: string\n siteName: null | string\n}\n\n/**\n * Reads the site-wide inputs to title and URL composition from SiteSettings.\n *\n * Split out of metadata assembly because these are one concern with one source:\n * whatever an editor set in the panel. Metadata generation shouldn't also know\n * that the separator arrives unpadded, or that the home slug hides inside a\n * relationship — it should receive a resolved config.\n */\nexport async function readSiteMetaConfig({\n locale,\n payload,\n settingsSlug = 'site-settings',\n siteNameField = 'siteName',\n}: {\n locale: string\n payload: BasePayload\n settingsSlug?: string\n siteNameField?: string\n}): Promise {\n // depth 1 populates the homepage relationship, so its slug is available\n // without a second query.\n const settings = (await payload.findGlobal({\n slug: settingsSlug as never,\n depth: 1,\n locale: locale as never,\n })) as SettingsShape\n\n const homepage =\n settings.homepage && typeof settings.homepage === 'object' ? settings.homepage : null\n\n return {\n siteName: (settings[siteNameField] as string | undefined) ?? null,\n // The panel stores '|'; titles need it padded.\n homeSlug: typeof homepage?.slug === 'string' ? homepage.slug : undefined,\n order: settings.titleOrder ?? undefined,\n separator: settings.titleSeparator ? ` ${settings.titleSeparator} ` : undefined,\n }\n}\n"],"names":["readSiteMetaConfig","locale","payload","settingsSlug","siteNameField","settings","findGlobal","slug","depth","homepage","siteName","homeSlug","undefined","order","titleOrder","separator","titleSeparator"],"mappings":"AAoBA;;;;;;;CAOC,GACD,OAAO,eAAeA,mBAAmB,EACvCC,MAAM,EACNC,OAAO,EACPC,eAAe,eAAe,EAC9BC,gBAAgB,UAAU,EAM3B;IACC,wEAAwE;IACxE,0BAA0B;IAC1B,MAAMC,WAAY,MAAMH,QAAQI,UAAU,CAAC;QACzCC,MAAMJ;QACNK,OAAO;QACPP,QAAQA;IACV;IAEA,MAAMQ,WACJJ,SAASI,QAAQ,IAAI,OAAOJ,SAASI,QAAQ,KAAK,WAAWJ,SAASI,QAAQ,GAAG;IAEnF,OAAO;QACLC,UAAU,AAACL,QAAQ,CAACD,cAAc,IAA2B;QAC7D,+CAA+C;QAC/CO,UAAU,OAAOF,UAAUF,SAAS,WAAWE,SAASF,IAAI,GAAGK;QAC/DC,OAAOR,SAASS,UAAU,IAAIF;QAC9BG,WAAWV,SAASW,cAAc,GAAG,CAAC,CAAC,EAAEX,SAASW,cAAc,CAAC,CAAC,CAAC,GAAGJ;IACxE;AACF"} \ No newline at end of file +{"version":3,"sources":["readSiteMetaConfig.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { TitleOrder } from './composeTitle.js'\n\ntype SettingsShape = {\n [key: string]: unknown\n homepage?: { id: number | string; slug?: unknown } | null | number | string\n titleOrder?: null | TitleOrder\n titleSeparator?: null | string\n}\n\nexport type SiteMetaConfig = {\n /** Slug of the page assigned as Homepage; collapses to the locale root. */\n homeSlug?: string\n order?: TitleOrder\n /** Padded separator, e.g. ' | ' — the panel stores the bare character. */\n separator?: string\n siteName: null | string\n}\n\n/**\n * Reads the site-wide inputs to title and URL composition from SiteSettings.\n *\n * Split out of metadata assembly because these are one concern with one source:\n * whatever an editor set in the panel. Metadata generation shouldn't also know\n * that the separator arrives unpadded, or that the home slug hides inside a\n * relationship — it should receive a resolved config.\n */\nexport async function readSiteMetaConfig({\n locale,\n payload,\n settingsSlug = 'site-settings',\n siteNameField = 'siteName',\n}: {\n locale: string\n payload: BasePayload\n settingsSlug?: string\n siteNameField?: string\n}): Promise {\n // depth 1 populates the homepage relationship, so its slug is available\n // without a second query.\n const settings = (await payload.findGlobal({\n slug: settingsSlug as never,\n depth: 1,\n locale: locale as never,\n })) as SettingsShape\n\n const homepage =\n settings.homepage && typeof settings.homepage === 'object' ? settings.homepage : null\n\n return {\n siteName: (settings[siteNameField] as string | undefined) ?? null,\n // The panel stores '|'; titles need it padded.\n homeSlug: typeof homepage?.slug === 'string' ? homepage.slug : undefined,\n order: settings.titleOrder ?? undefined,\n separator: settings.titleSeparator ? ` ${settings.titleSeparator} ` : undefined,\n }\n}\n"],"names":["readSiteMetaConfig","locale","payload","settingsSlug","siteNameField","settings","findGlobal","slug","depth","homepage","siteName","homeSlug","undefined","order","titleOrder","separator","titleSeparator"],"mappings":"AAoBA;;;;;;;CAOC,GACD,OAAO,eAAeA,mBAAmB,EACvCC,MAAM,EACNC,OAAO,EACPC,eAAe,eAAe,EAC9BC,gBAAgB,UAAU,EAM3B;IACC,wEAAwE;IACxE,0BAA0B;IAC1B,MAAMC,WAAY,MAAMH,QAAQI,UAAU,CAAC;QACzCC,MAAMJ;QACNK,OAAO;QACPP,QAAQA;IACV;IAEA,MAAMQ,WACJJ,SAASI,QAAQ,IAAI,OAAOJ,SAASI,QAAQ,KAAK,WAAWJ,SAASI,QAAQ,GAAG;IAEnF,OAAO;QACLC,UAAWL,QAAQ,CAACD,cAAc,IAA2B;QAC7D,+CAA+C;QAC/CO,UAAU,OAAOF,UAAUF,SAAS,WAAWE,SAASF,IAAI,GAAGK;QAC/DC,OAAOR,SAASS,UAAU,IAAIF;QAC9BG,WAAWV,SAASW,cAAc,GAAG,CAAC,CAAC,EAAEX,SAASW,cAAc,CAAC,CAAC,CAAC,GAAGJ;IACxE;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/seoPluginConfig.js b/dist/modules/seo/seoPluginConfig.js index 7976dc2..c36394f 100644 --- a/dist/modules/seo/seoPluginConfig.js +++ b/dist/modules/seo/seoPluginConfig.js @@ -35,6 +35,6 @@ import { seoPlugin } from '@payloadcms/plugin-seo'; generateDescription: seo.generateDescription } }); -} +} //# sourceMappingURL=seoPluginConfig.js.map //# sourceMappingURL=seoPluginConfig.js.map \ No newline at end of file diff --git a/dist/modules/seo/seoPluginConfig.js.map b/dist/modules/seo/seoPluginConfig.js.map index f6d6337..3e3c1ee 100644 --- a/dist/modules/seo/seoPluginConfig.js.map +++ b/dist/modules/seo/seoPluginConfig.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/seoPluginConfig.ts"],"sourcesContent":["import type { Field, Plugin } from 'payload'\n\nimport { seoPlugin } from '@payloadcms/plugin-seo'\n\nimport type { SeoOption } from './types.js'\n\ntype BuildSeoPluginArgs = {\n seo: SeoOption\n /** Upload collection slug for the meta image (typically 'media'). */\n uploadsCollection?: string\n}\n\n/**\n * Escape hatch from automatic title composition: whatever is typed here becomes\n * the entire title — no site name, no separator. Useful on a home page, where\n * composing would produce \"Intecion Software | Intecion Software\".\n */\nconst titleOverrideField: Field = {\n name: 'titleOverride',\n type: 'text',\n admin: {\n description:\n 'Use this exact text as the browser-tab title — no site name, no separator. Leave empty to compose the title automatically.',\n },\n localized: true,\n}\n\n/**\n * Configures @payloadcms/plugin-seo from IPAL's SeoOption.\n *\n * Adds the `meta` field group (title, description, image) to the client's\n * chosen collections. tabbedUI is intentionally NOT used — it merges tabs by\n * inspecting the first field, which breaks when other plugins/fields (roles,\n * slug) already sit in the collection, leaving an empty SEO tab. Instead the\n * SEO UI fields are injected into a controlled tab by injectSeoTabs.\n */\nexport function buildSeoPlugin({ seo, uploadsCollection = 'media' }: BuildSeoPluginArgs): Plugin {\n return seoPlugin({\n collections: seo.collections,\n fields: ({ defaultFields }) => [...defaultFields, titleOverrideField, ...(seo.fields ?? [])],\n uploadsCollection,\n ...(seo.generateTitle && { generateTitle: seo.generateTitle }),\n ...(seo.generateDescription && { generateDescription: seo.generateDescription }),\n })\n}\n"],"names":["seoPlugin","titleOverrideField","name","type","admin","description","localized","buildSeoPlugin","seo","uploadsCollection","collections","fields","defaultFields","generateTitle","generateDescription"],"mappings":"AAEA,SAASA,SAAS,QAAQ,yBAAwB;AAUlD;;;;CAIC,GACD,MAAMC,qBAA4B;IAChCC,MAAM;IACNC,MAAM;IACNC,OAAO;QACLC,aACE;IACJ;IACAC,WAAW;AACb;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC,eAAe,EAAEC,GAAG,EAAEC,oBAAoB,OAAO,EAAsB;IACrF,OAAOT,UAAU;QACfU,aAAaF,IAAIE,WAAW;QAC5BC,QAAQ,CAAC,EAAEC,aAAa,EAAE,GAAK;mBAAIA;gBAAeX;mBAAwBO,IAAIG,MAAM,IAAI,EAAE;aAAE;QAC5FF;QACA,GAAID,IAAIK,aAAa,IAAI;YAAEA,eAAeL,IAAIK,aAAa;QAAC,CAAC;QAC7D,GAAIL,IAAIM,mBAAmB,IAAI;YAAEA,qBAAqBN,IAAIM,mBAAmB;QAAC,CAAC;IACjF;AACF"} \ No newline at end of file +{"version":3,"sources":["seoPluginConfig.js"],"sourcesContent":["import type { Field, Plugin } from 'payload'\n\nimport { seoPlugin } from '@payloadcms/plugin-seo'\n\nimport type { SeoOption } from './types.js'\n\ntype BuildSeoPluginArgs = {\n seo: SeoOption\n /** Upload collection slug for the meta image (typically 'media'). */\n uploadsCollection?: string\n}\n\n/**\n * Escape hatch from automatic title composition: whatever is typed here becomes\n * the entire title — no site name, no separator. Useful on a home page, where\n * composing would produce \"Intecion Software | Intecion Software\".\n */\nconst titleOverrideField: Field = {\n name: 'titleOverride',\n type: 'text',\n admin: {\n description:\n 'Use this exact text as the browser-tab title — no site name, no separator. Leave empty to compose the title automatically.',\n },\n localized: true,\n}\n\n/**\n * Configures @payloadcms/plugin-seo from IPAL's SeoOption.\n *\n * Adds the `meta` field group (title, description, image) to the client's\n * chosen collections. tabbedUI is intentionally NOT used — it merges tabs by\n * inspecting the first field, which breaks when other plugins/fields (roles,\n * slug) already sit in the collection, leaving an empty SEO tab. Instead the\n * SEO UI fields are injected into a controlled tab by injectSeoTabs.\n */\nexport function buildSeoPlugin({ seo, uploadsCollection = 'media' }: BuildSeoPluginArgs): Plugin {\n return seoPlugin({\n collections: seo.collections,\n fields: ({ defaultFields }) => [...defaultFields, titleOverrideField, ...(seo.fields ?? [])],\n uploadsCollection,\n ...(seo.generateTitle && { generateTitle: seo.generateTitle }),\n ...(seo.generateDescription && { generateDescription: seo.generateDescription }),\n })\n}\n"],"names":["seoPlugin","titleOverrideField","name","type","admin","description","localized","buildSeoPlugin","seo","uploadsCollection","collections","fields","defaultFields","generateTitle","generateDescription"],"mappings":"AAEA,SAASA,SAAS,QAAQ,yBAAwB;AAUlD;;;;CAIC,GACD,MAAMC,qBAA4B;IAChCC,MAAM;IACNC,MAAM;IACNC,OAAO;QACLC,aACE;IACJ;IACAC,WAAW;AACb;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC,eAAe,EAAEC,GAAG,EAAEC,oBAAoB,OAAO,EAAsB;IACrF,OAAOT,UAAU;QACfU,aAAaF,IAAIE,WAAW;QAC5BC,QAAQ,CAAC,EAAEC,aAAa,EAAE,GAAK;mBAAIA;gBAAeX;mBAAwBO,IAAIG,MAAM,IAAI,EAAE;aAAE;QAC5FF;QACA,GAAID,IAAIK,aAAa,IAAI;YAAEA,eAAeL,IAAIK,aAAa;QAAC,CAAC;QAC7D,GAAIL,IAAIM,mBAAmB,IAAI;YAAEA,qBAAqBN,IAAIM,mBAAmB;QAAC,CAAC;IACjF;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/slugsAcrossLocales.js b/dist/modules/seo/slugsAcrossLocales.js index b01ba98..d043bc8 100644 --- a/dist/modules/seo/slugsAcrossLocales.js +++ b/dist/modules/seo/slugsAcrossLocales.js @@ -19,6 +19,6 @@ import { getLocalizedSlugs } from '../i18n/index.js'; config, slugField: doc.slug }) : {}; -} +} //# sourceMappingURL=slugsAcrossLocales.js.map //# sourceMappingURL=slugsAcrossLocales.js.map \ No newline at end of file diff --git a/dist/modules/seo/slugsAcrossLocales.js.map b/dist/modules/seo/slugsAcrossLocales.js.map index 28ab795..f132994 100644 --- a/dist/modules/seo/slugsAcrossLocales.js.map +++ b/dist/modules/seo/slugsAcrossLocales.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/slugsAcrossLocales.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\n\nimport { getLocalizedSlugs } from '../i18n/index.js'\n\n/**\n * Reads a document's slug in every locale — the map hreflang alternates are\n * built from.\n *\n * Needs its own query because a document fetched in one locale returns `slug`\n * as a plain string. Fetching with `locale: 'all'` turns *every* localized\n * field into a locale→value map, which is right for the slug and wrong for\n * everything else (a title map would break title composition), so this asks\n * only for what it needs and at depth 0.\n */\nexport async function slugsAcrossLocales({\n id,\n collection,\n config,\n payload,\n}: {\n collection: string\n config: I18nConfig\n id: number | string\n payload: BasePayload\n}): Promise {\n const doc = (await payload.findByID({\n id,\n collection: collection as never,\n depth: 0,\n locale: 'all',\n })) as { slug?: unknown }\n\n return doc.slug && typeof doc.slug === 'object'\n ? getLocalizedSlugs({ config, slugField: doc.slug as Record })\n : {}\n}\n"],"names":["getLocalizedSlugs","slugsAcrossLocales","id","collection","config","payload","doc","findByID","depth","locale","slug","slugField"],"mappings":"AAIA,SAASA,iBAAiB,QAAQ,mBAAkB;AAEpD;;;;;;;;;CASC,GACD,OAAO,eAAeC,mBAAmB,EACvCC,EAAE,EACFC,UAAU,EACVC,MAAM,EACNC,OAAO,EAMR;IACC,MAAMC,MAAO,MAAMD,QAAQE,QAAQ,CAAC;QAClCL;QACAC,YAAYA;QACZK,OAAO;QACPC,QAAQ;IACV;IAEA,OAAOH,IAAII,IAAI,IAAI,OAAOJ,IAAII,IAAI,KAAK,WACnCV,kBAAkB;QAAEI;QAAQO,WAAWL,IAAII,IAAI;IAA4B,KAC3E,CAAC;AACP"} \ No newline at end of file +{"version":3,"sources":["slugsAcrossLocales.js"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig, LocalizedSlugs } from '../i18n/index.js'\n\nimport { getLocalizedSlugs } from '../i18n/index.js'\n\n/**\n * Reads a document's slug in every locale — the map hreflang alternates are\n * built from.\n *\n * Needs its own query because a document fetched in one locale returns `slug`\n * as a plain string. Fetching with `locale: 'all'` turns *every* localized\n * field into a locale→value map, which is right for the slug and wrong for\n * everything else (a title map would break title composition), so this asks\n * only for what it needs and at depth 0.\n */\nexport async function slugsAcrossLocales({\n id,\n collection,\n config,\n payload,\n}: {\n collection: string\n config: I18nConfig\n id: number | string\n payload: BasePayload\n}): Promise {\n const doc = (await payload.findByID({\n id,\n collection: collection as never,\n depth: 0,\n locale: 'all',\n })) as { slug?: unknown }\n\n return doc.slug && typeof doc.slug === 'object'\n ? getLocalizedSlugs({ config, slugField: doc.slug as Record })\n : {}\n}\n"],"names":["getLocalizedSlugs","slugsAcrossLocales","id","collection","config","payload","doc","findByID","depth","locale","slug","slugField"],"mappings":"AAIA,SAASA,iBAAiB,QAAQ,mBAAkB;AAEpD;;;;;;;;;CASC,GACD,OAAO,eAAeC,mBAAmB,EACvCC,EAAE,EACFC,UAAU,EACVC,MAAM,EACNC,OAAO,EAMR;IACC,MAAMC,MAAO,MAAMD,QAAQE,QAAQ,CAAC;QAClCL;QACAC,YAAYA;QACZK,OAAO;QACPC,QAAQ;IACV;IAEA,OAAOH,IAAII,IAAI,IAAI,OAAOJ,IAAII,IAAI,KAAK,WACnCV,kBAAkB;QAAEI;QAAQO,WAAWL,IAAII,IAAI;IAA4B,KAC3E,CAAC;AACP"} \ No newline at end of file diff --git a/dist/modules/seo/types.js b/dist/modules/seo/types.js index 7544304..3899db2 100644 --- a/dist/modules/seo/types.js +++ b/dist/modules/seo/types.js @@ -2,6 +2,6 @@ * Minimal shape of the SEO meta group as stored on a document by * @payloadcms/plugin-seo. The client's generated types are richer; helpers * depend only on this. - */ export { }; + */ export { }; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/modules/seo/types.js.map b/dist/modules/seo/types.js.map index 6d93404..4cceb73 100644 --- a/dist/modules/seo/types.js.map +++ b/dist/modules/seo/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/types.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { AutoFillMapping } from './autoFillMeta.js'\n\n/**\n * SEO configuration.\n *\n * The plugin wires @payloadcms/plugin-seo into the client's config and adds\n * locale-aware metadata helpers on top. Collections come from options because\n * the plugin doesn't own the client's content collections (e.g. Pages).\n */\nexport type SeoOption = {\n /**\n * Auto-fill empty meta from document fields on save. Defaults to the website\n * template mapping (title → meta.title). Set to false to disable.\n */\n autoFill?: AutoFillMapping | false\n /** Collection slugs that receive SEO meta fields, e.g. ['pages', 'posts']. */\n collections: string[]\n /** Extra fields appended to the SEO group in the admin. */\n fields?: Field[]\n /** Optional: customize how meta descriptions are generated. */\n generateDescription?: (args: { doc: Record }) => string\n /** Optional: customize how meta titles are generated in the admin preview. */\n generateTitle?: (args: { doc: Record }) => string\n}\n\n/**\n * Minimal shape of the SEO meta group as stored on a document by\n * @payloadcms/plugin-seo. The client's generated types are richer; helpers\n * depend only on this.\n */\nexport type SeoMeta = {\n description?: null | string\n image?: unknown\n title?: null | string\n /** When set, used as the whole title — no site name, no separator. */\n titleOverride?: null | string\n}\n"],"names":[],"mappings":"AA2BA;;;;CAIC,GACD,WAMC"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { AutoFillMapping } from './autoFillMeta.js'\n\n/**\n * SEO configuration.\n *\n * The plugin wires @payloadcms/plugin-seo into the client's config and adds\n * locale-aware metadata helpers on top. Collections come from options because\n * the plugin doesn't own the client's content collections (e.g. Pages).\n */\nexport type SeoOption = {\n /**\n * Auto-fill empty meta from document fields on save. Defaults to the website\n * template mapping (title → meta.title). Set to false to disable.\n */\n autoFill?: AutoFillMapping | false\n /** Collection slugs that receive SEO meta fields, e.g. ['pages', 'posts']. */\n collections: string[]\n /** Extra fields appended to the SEO group in the admin. */\n fields?: Field[]\n /** Optional: customize how meta descriptions are generated. */\n generateDescription?: (args: { doc: Record }) => string\n /** Optional: customize how meta titles are generated in the admin preview. */\n generateTitle?: (args: { doc: Record }) => string\n}\n\n/**\n * Minimal shape of the SEO meta group as stored on a document by\n * @payloadcms/plugin-seo. The client's generated types are richer; helpers\n * depend only on this.\n */\nexport type SeoMeta = {\n description?: null | string\n image?: unknown\n title?: null | string\n /** When set, used as the whole title — no site name, no separator. */\n titleOverride?: null | string\n}\n"],"names":[],"mappings":"AA2BA;;;;CAIC,GACD,WAMC"} \ No newline at end of file diff --git a/dist/modules/slug/buildSlugField.js b/dist/modules/slug/buildSlugField.js index 4352966..8def544 100644 --- a/dist/modules/slug/buildSlugField.js +++ b/dist/modules/slug/buildSlugField.js @@ -50,6 +50,6 @@ import slugify from 'slugify'; required, ...overrides }; -} +} //# sourceMappingURL=buildSlugField.js.map //# sourceMappingURL=buildSlugField.js.map \ No newline at end of file diff --git a/dist/modules/slug/buildSlugField.js.map b/dist/modules/slug/buildSlugField.js.map index a6e53f5..4157838 100644 --- a/dist/modules/slug/buildSlugField.js.map +++ b/dist/modules/slug/buildSlugField.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/slug/buildSlugField.ts"],"sourcesContent":["import type { Field, FieldHook, TextField } from 'payload'\n\nimport slugify from 'slugify'\n\n/**\n * Normalizes a string into a URL slug. slugify handles diacritics out of the\n * box (Polish included: \"Strona główna\" → \"strona-glowna\").\n */\nexport function toSlug(input: string): string {\n return slugify(input, {\n lower: true,\n strict: true, // drop characters that aren't url-safe\n trim: true,\n })\n}\n\ntype BuildSlugFieldOptions = {\n /** Source field to derive the slug from. Default: 'title'. */\n from?: string\n /** Whether the slug is localized (per-locale). Default: true. */\n localized?: boolean\n /** Field name for the slug. Default: 'slug'. */\n name?: string\n /** Override or extend the generated field config. */\n overrides?: Partial\n /** Whether the slug is required. Default: true. */\n required?: boolean\n}\n\n/**\n * Builds a slug field that auto-generates from a source field (default 'title')\n * only when left empty — an editor's manual slug is never overwritten (mode 4a).\n *\n * Localized-safe: on a localized field Payload runs the hook per locale, so\n * `value` is the slug for the active locale and `siblingData[from]` is the\n * source in that same locale. Editing the doc in 'pl' fills slug.pl from\n * title.pl; editing in 'en' fills slug.en from title.en — giving genuinely\n * per-locale slugs (strona-glowna / homepage) that the language switcher needs.\n */\nexport function buildSlugField(options: BuildSlugFieldOptions = {}): Field {\n const {\n name = 'slug',\n from = 'title',\n localized = true,\n overrides = {},\n required = true,\n } = options\n\n const autoGenerate: FieldHook = ({ originalDoc, siblingData, value }) => {\n // Respect a manually entered slug — only generate when empty (mode 4a).\n if (typeof value === 'string' && value.trim()) {\n return toSlug(value) // still normalize what the editor typed\n }\n\n // Derive from the source field in the current locale.\n const source =\n (siblingData as Record)?.[from] ??\n (originalDoc as Record)?.[from]\n\n if (typeof source === 'string' && source.trim()) {\n return toSlug(source)\n }\n\n return value\n }\n\n return {\n name,\n type: 'text',\n admin: {\n description: 'Auto-generated from the title when left empty. You can override it.',\n ...overrides.admin,\n },\n hooks: {\n beforeValidate: [autoGenerate],\n },\n index: true,\n localized,\n required,\n ...overrides,\n } as TextField\n}\n"],"names":["slugify","toSlug","input","lower","strict","trim","buildSlugField","options","name","from","localized","overrides","required","autoGenerate","originalDoc","siblingData","value","source","type","admin","description","hooks","beforeValidate","index"],"mappings":"AAEA,OAAOA,aAAa,UAAS;AAE7B;;;CAGC,GACD,OAAO,SAASC,OAAOC,KAAa;IAClC,OAAOF,QAAQE,OAAO;QACpBC,OAAO;QACPC,QAAQ;QACRC,MAAM;IACR;AACF;AAeA;;;;;;;;;CASC,GACD,OAAO,SAASC,eAAeC,UAAiC,CAAC,CAAC;IAChE,MAAM,EACJC,OAAO,MAAM,EACbC,OAAO,OAAO,EACdC,YAAY,IAAI,EAChBC,YAAY,CAAC,CAAC,EACdC,WAAW,IAAI,EAChB,GAAGL;IAEJ,MAAMM,eAA0B,CAAC,EAAEC,WAAW,EAAEC,WAAW,EAAEC,KAAK,EAAE;QAClE,wEAAwE;QACxE,IAAI,OAAOA,UAAU,YAAYA,MAAMX,IAAI,IAAI;YAC7C,OAAOJ,OAAOe,OAAO,wCAAwC;;QAC/D;QAEA,sDAAsD;QACtD,MAAMC,SACJ,AAACF,aAAyC,CAACN,KAAK,IAC/CK,aAAyC,CAACL,KAAK;QAElD,IAAI,OAAOQ,WAAW,YAAYA,OAAOZ,IAAI,IAAI;YAC/C,OAAOJ,OAAOgB;QAChB;QAEA,OAAOD;IACT;IAEA,OAAO;QACLR;QACAU,MAAM;QACNC,OAAO;YACLC,aAAa;YACb,GAAGT,UAAUQ,KAAK;QACpB;QACAE,OAAO;YACLC,gBAAgB;gBAACT;aAAa;QAChC;QACAU,OAAO;QACPb;QACAE;QACA,GAAGD,SAAS;IACd;AACF"} \ No newline at end of file +{"version":3,"sources":["buildSlugField.js"],"sourcesContent":["import type { Field, FieldHook, TextField } from 'payload'\n\nimport slugify from 'slugify'\n\n/**\n * Normalizes a string into a URL slug. slugify handles diacritics out of the\n * box (Polish included: \"Strona główna\" → \"strona-glowna\").\n */\nexport function toSlug(input: string): string {\n return slugify(input, {\n lower: true,\n strict: true, // drop characters that aren't url-safe\n trim: true,\n })\n}\n\ntype BuildSlugFieldOptions = {\n /** Source field to derive the slug from. Default: 'title'. */\n from?: string\n /** Whether the slug is localized (per-locale). Default: true. */\n localized?: boolean\n /** Field name for the slug. Default: 'slug'. */\n name?: string\n /** Override or extend the generated field config. */\n overrides?: Partial\n /** Whether the slug is required. Default: true. */\n required?: boolean\n}\n\n/**\n * Builds a slug field that auto-generates from a source field (default 'title')\n * only when left empty — an editor's manual slug is never overwritten (mode 4a).\n *\n * Localized-safe: on a localized field Payload runs the hook per locale, so\n * `value` is the slug for the active locale and `siblingData[from]` is the\n * source in that same locale. Editing the doc in 'pl' fills slug.pl from\n * title.pl; editing in 'en' fills slug.en from title.en — giving genuinely\n * per-locale slugs (strona-glowna / homepage) that the language switcher needs.\n */\nexport function buildSlugField(options: BuildSlugFieldOptions = {}): Field {\n const {\n name = 'slug',\n from = 'title',\n localized = true,\n overrides = {},\n required = true,\n } = options\n\n const autoGenerate: FieldHook = ({ originalDoc, siblingData, value }) => {\n // Respect a manually entered slug — only generate when empty (mode 4a).\n if (typeof value === 'string' && value.trim()) {\n return toSlug(value) // still normalize what the editor typed\n }\n\n // Derive from the source field in the current locale.\n const source =\n (siblingData as Record)?.[from] ??\n (originalDoc as Record)?.[from]\n\n if (typeof source === 'string' && source.trim()) {\n return toSlug(source)\n }\n\n return value\n }\n\n return {\n name,\n type: 'text',\n admin: {\n description: 'Auto-generated from the title when left empty. You can override it.',\n ...overrides.admin,\n },\n hooks: {\n beforeValidate: [autoGenerate],\n },\n index: true,\n localized,\n required,\n ...overrides,\n } as TextField\n}\n"],"names":["slugify","toSlug","input","lower","strict","trim","buildSlugField","options","name","from","localized","overrides","required","autoGenerate","originalDoc","siblingData","value","source","type","admin","description","hooks","beforeValidate","index"],"mappings":"AAEA,OAAOA,aAAa,UAAS;AAE7B;;;CAGC,GACD,OAAO,SAASC,OAAOC,KAAa;IAClC,OAAOF,QAAQE,OAAO;QACpBC,OAAO;QACPC,QAAQ;QACRC,MAAM;IACR;AACF;AAeA;;;;;;;;;CASC,GACD,OAAO,SAASC,eAAeC,UAAiC,CAAC,CAAC;IAChE,MAAM,EACJC,OAAO,MAAM,EACbC,OAAO,OAAO,EACdC,YAAY,IAAI,EAChBC,YAAY,CAAC,CAAC,EACdC,WAAW,IAAI,EAChB,GAAGL;IAEJ,MAAMM,eAA0B,CAAC,EAAEC,WAAW,EAAEC,WAAW,EAAEC,KAAK,EAAE;QAClE,wEAAwE;QACxE,IAAI,OAAOA,UAAU,YAAYA,MAAMX,IAAI,IAAI;YAC7C,OAAOJ,OAAOe,OAAO,wCAAwC;;QAC/D;QAEA,sDAAsD;QACtD,MAAMC,SACHF,aAAyC,CAACN,KAAK,IAC/CK,aAAyC,CAACL,KAAK;QAElD,IAAI,OAAOQ,WAAW,YAAYA,OAAOZ,IAAI,IAAI;YAC/C,OAAOJ,OAAOgB;QAChB;QAEA,OAAOD;IACT;IAEA,OAAO;QACLR;QACAU,MAAM;QACNC,OAAO;YACLC,aAAa;YACb,GAAGT,UAAUQ,KAAK;QACpB;QACAE,OAAO;YACLC,gBAAgB;gBAACT;aAAa;QAChC;QACAU,OAAO;QACPb;QACAE;QACA,GAAGD,SAAS;IACd;AACF"} \ No newline at end of file diff --git a/dist/modules/slug/index.js b/dist/modules/slug/index.js index acfd35f..c7d911e 100644 --- a/dist/modules/slug/index.js +++ b/dist/modules/slug/index.js @@ -1,3 +1,3 @@ -export { buildSlugField, toSlug } from './buildSlugField.js'; +export { buildSlugField, toSlug } from './buildSlugField.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/slug/index.js.map b/dist/modules/slug/index.js.map index a337955..7dd8559 100644 --- a/dist/modules/slug/index.js.map +++ b/dist/modules/slug/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/slug/index.ts"],"sourcesContent":["export { buildSlugField, toSlug } from './buildSlugField.js'\n"],"names":["buildSlugField","toSlug"],"mappings":"AAAA,SAASA,cAAc,EAAEC,MAAM,QAAQ,sBAAqB"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["export { buildSlugField, toSlug } from './buildSlugField.js'\n"],"names":["buildSlugField","toSlug"],"mappings":"AAAA,SAASA,cAAc,EAAEC,MAAM,QAAQ,sBAAqB"} \ No newline at end of file diff --git a/dist/modules/turnstile/Turnstile.js b/dist/modules/turnstile/Turnstile.js index fc4ed53..555d0fb 100644 --- a/dist/modules/turnstile/Turnstile.js +++ b/dist/modules/turnstile/Turnstile.js @@ -59,6 +59,6 @@ const SCRIPT_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render return /*#__PURE__*/ _jsx("div", { ref: ref }); -} +} //# sourceMappingURL=Turnstile.js.map //# sourceMappingURL=Turnstile.js.map \ No newline at end of file diff --git a/dist/modules/turnstile/Turnstile.js.map b/dist/modules/turnstile/Turnstile.js.map index 093dfa4..d10134a 100644 --- a/dist/modules/turnstile/Turnstile.js.map +++ b/dist/modules/turnstile/Turnstile.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/turnstile/Turnstile.tsx"],"sourcesContent":["'use client'\nimport { useEffect, useRef } from 'react'\n\ndeclare global {\n interface Window {\n turnstile?: {\n render: (el: HTMLElement, opts: Record) => string\n reset: (id?: string) => void\n }\n }\n}\n\nconst SCRIPT_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'\n\n/** Loads the Turnstile script once, shared across all widget instances. */\nfunction ensureScript(): void {\n if (typeof document === 'undefined') {return}\n if (document.querySelector(`script[src=\"${SCRIPT_SRC}\"]`)) {return}\n const script = document.createElement('script')\n script.src = SCRIPT_SRC\n script.async = true\n script.defer = true\n document.head.appendChild(script)\n}\n\nexport type TurnstileProps = {\n /** Called with the token once solved, or null on expiry/error. */\n onToken: (token: null | string) => void\n /**\n * Public Turnstile site key. The client project reads it server-side from\n * SiteIntegrations (turnstileSiteKey) and passes it in — the widget is a pure\n * client component and can't read Payload itself.\n */\n siteKey: string\n theme?: 'auto' | 'dark' | 'light'\n}\n\n/**\n * Cloudflare Turnstile widget — reusable across any form. Renders the challenge\n * and reports the token via onToken. The token must then be verified\n * server-side (see verifyTurnstile) before a submission is trusted.\n *\n * siteKey is a prop rather than an env var so all Turnstile config lives in one\n * place (SiteIntegrations), consistent with the plugin's panel-managed model.\n * The script is injected directly (no next/script dependency) so the widget\n * stays framework-agnostic.\n */\nexport function Turnstile({ onToken, siteKey, theme = 'auto' }: TurnstileProps) {\n const ref = useRef(null)\n const widgetId = useRef(null)\n\n useEffect(() => {\n if (!siteKey) {return}\n\n ensureScript()\n\n function render() {\n if (!ref.current || !window.turnstile || widgetId.current) {return}\n widgetId.current = window.turnstile.render(ref.current, {\n callback: (token: string) => onToken(token),\n 'error-callback': () => onToken(null),\n 'expired-callback': () => onToken(null),\n sitekey: siteKey,\n theme,\n })\n }\n\n render()\n // Script may load after mount — retry briefly until ready.\n const interval = setInterval(render, 300)\n return () => clearInterval(interval)\n }, [siteKey, theme, onToken])\n\n if (!siteKey) {return null}\n\n return
\n}\n"],"names":["useEffect","useRef","SCRIPT_SRC","ensureScript","document","querySelector","script","createElement","src","async","defer","head","appendChild","Turnstile","onToken","siteKey","theme","ref","widgetId","render","current","window","turnstile","callback","token","sitekey","interval","setInterval","clearInterval","div"],"mappings":"AAAA;;AACA,SAASA,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAWzC,MAAMC,aAAa;AAEnB,yEAAyE,GACzE,SAASC;IACP,IAAI,OAAOC,aAAa,aAAa;QAAC;IAAM;IAC5C,IAAIA,SAASC,aAAa,CAAC,CAAC,YAAY,EAAEH,WAAW,EAAE,CAAC,GAAG;QAAC;IAAM;IAClE,MAAMI,SAASF,SAASG,aAAa,CAAC;IACtCD,OAAOE,GAAG,GAAGN;IACbI,OAAOG,KAAK,GAAG;IACfH,OAAOI,KAAK,GAAG;IACfN,SAASO,IAAI,CAACC,WAAW,CAACN;AAC5B;AAcA;;;;;;;;;CASC,GACD,OAAO,SAASO,UAAU,EAAEC,OAAO,EAAEC,OAAO,EAAEC,QAAQ,MAAM,EAAkB;IAC5E,MAAMC,MAAMhB,OAAuB;IACnC,MAAMiB,WAAWjB,OAAsB;IAEvCD,UAAU;QACR,IAAI,CAACe,SAAS;YAAC;QAAM;QAErBZ;QAEA,SAASgB;YACP,IAAI,CAACF,IAAIG,OAAO,IAAI,CAACC,OAAOC,SAAS,IAAIJ,SAASE,OAAO,EAAE;gBAAC;YAAM;YAClEF,SAASE,OAAO,GAAGC,OAAOC,SAAS,CAACH,MAAM,CAACF,IAAIG,OAAO,EAAE;gBACtDG,UAAU,CAACC,QAAkBV,QAAQU;gBACrC,kBAAkB,IAAMV,QAAQ;gBAChC,oBAAoB,IAAMA,QAAQ;gBAClCW,SAASV;gBACTC;YACF;QACF;QAEAG;QACA,2DAA2D;QAC3D,MAAMO,WAAWC,YAAYR,QAAQ;QACrC,OAAO,IAAMS,cAAcF;IAC7B,GAAG;QAACX;QAASC;QAAOF;KAAQ;IAE5B,IAAI,CAACC,SAAS;QAAC,OAAO;IAAI;IAE1B,qBAAO,KAACc;QAAIZ,KAAKA;;AACnB"} \ No newline at end of file +{"version":3,"sources":["Turnstile.js"],"sourcesContent":["'use client'\nimport { useEffect, useRef } from 'react'\n\ndeclare global {\n interface Window {\n turnstile?: {\n render: (el: HTMLElement, opts: Record) => string\n reset: (id?: string) => void\n }\n }\n}\n\nconst SCRIPT_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'\n\n/** Loads the Turnstile script once, shared across all widget instances. */\nfunction ensureScript(): void {\n if (typeof document === 'undefined') {return}\n if (document.querySelector(`script[src=\"${SCRIPT_SRC}\"]`)) {return}\n const script = document.createElement('script')\n script.src = SCRIPT_SRC\n script.async = true\n script.defer = true\n document.head.appendChild(script)\n}\n\nexport type TurnstileProps = {\n /** Called with the token once solved, or null on expiry/error. */\n onToken: (token: null | string) => void\n /**\n * Public Turnstile site key. The client project reads it server-side from\n * SiteIntegrations (turnstileSiteKey) and passes it in — the widget is a pure\n * client component and can't read Payload itself.\n */\n siteKey: string\n theme?: 'auto' | 'dark' | 'light'\n}\n\n/**\n * Cloudflare Turnstile widget — reusable across any form. Renders the challenge\n * and reports the token via onToken. The token must then be verified\n * server-side (see verifyTurnstile) before a submission is trusted.\n *\n * siteKey is a prop rather than an env var so all Turnstile config lives in one\n * place (SiteIntegrations), consistent with the plugin's panel-managed model.\n * The script is injected directly (no next/script dependency) so the widget\n * stays framework-agnostic.\n */\nexport function Turnstile({ onToken, siteKey, theme = 'auto' }: TurnstileProps) {\n const ref = useRef(null)\n const widgetId = useRef(null)\n\n useEffect(() => {\n if (!siteKey) {return}\n\n ensureScript()\n\n function render() {\n if (!ref.current || !window.turnstile || widgetId.current) {return}\n widgetId.current = window.turnstile.render(ref.current, {\n callback: (token: string) => onToken(token),\n 'error-callback': () => onToken(null),\n 'expired-callback': () => onToken(null),\n sitekey: siteKey,\n theme,\n })\n }\n\n render()\n // Script may load after mount — retry briefly until ready.\n const interval = setInterval(render, 300)\n return () => clearInterval(interval)\n }, [siteKey, theme, onToken])\n\n if (!siteKey) {return null}\n\n return
\n}\n"],"names":["useEffect","useRef","SCRIPT_SRC","ensureScript","document","querySelector","script","createElement","src","async","defer","head","appendChild","Turnstile","onToken","siteKey","theme","ref","widgetId","render","current","window","turnstile","callback","token","sitekey","interval","setInterval","clearInterval","div"],"mappings":"AAAA;;AACA,SAASA,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAWzC,MAAMC,aAAa;AAEnB,yEAAyE,GACzE,SAASC;IACP,IAAI,OAAOC,aAAa,aAAa;QAAC;IAAM;IAC5C,IAAIA,SAASC,aAAa,CAAC,CAAC,YAAY,EAAEH,WAAW,EAAE,CAAC,GAAG;QAAC;IAAM;IAClE,MAAMI,SAASF,SAASG,aAAa,CAAC;IACtCD,OAAOE,GAAG,GAAGN;IACbI,OAAOG,KAAK,GAAG;IACfH,OAAOI,KAAK,GAAG;IACfN,SAASO,IAAI,CAACC,WAAW,CAACN;AAC5B;AAcA;;;;;;;;;CASC,GACD,OAAO,SAASO,UAAU,EAAEC,OAAO,EAAEC,OAAO,EAAEC,QAAQ,MAAM,EAAkB;IAC5E,MAAMC,MAAMhB,OAAuB;IACnC,MAAMiB,WAAWjB,OAAsB;IAEvCD,UAAU;QACR,IAAI,CAACe,SAAS;YAAC;QAAM;QAErBZ;QAEA,SAASgB;YACP,IAAI,CAACF,IAAIG,OAAO,IAAI,CAACC,OAAOC,SAAS,IAAIJ,SAASE,OAAO,EAAE;gBAAC;YAAM;YAClEF,SAASE,OAAO,GAAGC,OAAOC,SAAS,CAACH,MAAM,CAACF,IAAIG,OAAO,EAAE;gBACtDG,UAAU,CAACC,QAAkBV,QAAQU;gBACrC,kBAAkB,IAAMV,QAAQ;gBAChC,oBAAoB,IAAMA,QAAQ;gBAClCW,SAASV;gBACTC;YACF;QACF;QAEAG;QACA,2DAA2D;QAC3D,MAAMO,WAAWC,YAAYR,QAAQ;QACrC,OAAO,IAAMS,cAAcF;IAC7B,GAAG;QAACX;QAASC;QAAOF;KAAQ;IAE5B,IAAI,CAACC,SAAS;QAAC,OAAO;IAAI;IAE1B,OAAA,WAAA,GAAO,KAACc,OAAAA;QAAIZ,KAAKA;;AACnB"} \ No newline at end of file diff --git a/dist/modules/turnstile/client.js b/dist/modules/turnstile/client.js index 157876e..027d7f3 100644 --- a/dist/modules/turnstile/client.js +++ b/dist/modules/turnstile/client.js @@ -1,6 +1,6 @@ 'use client'; // Client-only exports — the Turnstile widget. Kept separate from index.ts so // the server-only verify never leaks into a browser bundle. -export { Turnstile } from './Turnstile.js'; +export { Turnstile } from './Turnstile.js'; //# sourceMappingURL=client.js.map //# sourceMappingURL=client.js.map \ No newline at end of file diff --git a/dist/modules/turnstile/client.js.map b/dist/modules/turnstile/client.js.map index 6beab7c..7a07a79 100644 --- a/dist/modules/turnstile/client.js.map +++ b/dist/modules/turnstile/client.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/turnstile/client.ts"],"sourcesContent":["'use client'\n// Client-only exports — the Turnstile widget. Kept separate from index.ts so\n// the server-only verify never leaks into a browser bundle.\nexport { Turnstile } from './Turnstile.js'\nexport type { TurnstileProps } from './Turnstile.js'\n"],"names":["Turnstile"],"mappings":"AAAA;AACA,6EAA6E;AAC7E,4DAA4D;AAC5D,SAASA,SAAS,QAAQ,iBAAgB"} \ No newline at end of file +{"version":3,"sources":["client.js"],"sourcesContent":["'use client'\n// Client-only exports — the Turnstile widget. Kept separate from index.ts so\n// the server-only verify never leaks into a browser bundle.\nexport { Turnstile } from './Turnstile.js'\nexport type { TurnstileProps } from './Turnstile.js'\n"],"names":["Turnstile"],"mappings":"AAAA;AACA,6EAA6E;AAC7E,4DAA4D;AAC5D,SAASA,SAAS,QAAQ,iBAAgB"} \ No newline at end of file diff --git a/dist/modules/turnstile/index.js b/dist/modules/turnstile/index.js index 69979b6..0e2a9da 100644 --- a/dist/modules/turnstile/index.js +++ b/dist/modules/turnstile/index.js @@ -1,5 +1,5 @@ // Server-only exports. verify.ts imports 'server-only', so this must never be // imported from a client component — use ./client for the widget instead. -export { verifyTurnstile } from './verify.js'; +export { verifyTurnstile } from './verify.js'; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/modules/turnstile/index.js.map b/dist/modules/turnstile/index.js.map index acf77d2..6a54c4f 100644 --- a/dist/modules/turnstile/index.js.map +++ b/dist/modules/turnstile/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/turnstile/index.ts"],"sourcesContent":["// Server-only exports. verify.ts imports 'server-only', so this must never be\n// imported from a client component — use ./client for the widget instead.\nexport { verifyTurnstile } from './verify.js'\n"],"names":["verifyTurnstile"],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,SAASA,eAAe,QAAQ,cAAa"} \ No newline at end of file +{"version":3,"sources":["index.js"],"sourcesContent":["// Server-only exports. verify.ts imports 'server-only', so this must never be\n// imported from a client component — use ./client for the widget instead.\nexport { verifyTurnstile } from './verify.js'\n"],"names":["verifyTurnstile"],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,SAASA,eAAe,QAAQ,cAAa"} \ No newline at end of file diff --git a/dist/modules/turnstile/verify.js b/dist/modules/turnstile/verify.js index 250d69c..0851f3a 100644 --- a/dist/modules/turnstile/verify.js +++ b/dist/modules/turnstile/verify.js @@ -36,6 +36,6 @@ import { getSiteIntegrations } from '../payload/index.js'; } catch { return false; } -} +} //# sourceMappingURL=verify.js.map //# sourceMappingURL=verify.js.map \ No newline at end of file diff --git a/dist/modules/turnstile/verify.js.map b/dist/modules/turnstile/verify.js.map index 91d58b7..6fb23e7 100644 --- a/dist/modules/turnstile/verify.js.map +++ b/dist/modules/turnstile/verify.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/turnstile/verify.ts"],"sourcesContent":["import 'server-only'\n\nimport type { BasePayload } from 'payload'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\ntype SiteverifyResponse = {\n challenge_ts?: string\n 'error-codes'?: string[]\n hostname?: string\n success: boolean\n}\n\ntype IntegrationsWithTurnstile = {\n turnstileSecretKey?: null | string\n}\n\ntype VerifyTurnstileArgs = {\n /** Optional client IP for stricter verification. */\n ip?: string\n /** Payload instance — used to read the secret from SiteIntegrations. */\n payload: BasePayload\n /** Token produced by the client-side widget. */\n token: string\n}\n\n/**\n * Verifies a Turnstile token with Cloudflare, server-side only.\n *\n * The secret comes from the SiteIntegrations global (editor-managed, per the\n * plugin's \"secrets in the panel\" model), read via the Local API which bypasses\n * access control. `server-only` guarantees this never reaches the browser\n * bundle, keeping the secret off the client.\n *\n * Returns false on any failure (missing secret/token, network error, rejected\n * challenge) — callers treat false as \"do not trust this submission\".\n */\nexport async function verifyTurnstile({\n ip,\n payload,\n token,\n}: VerifyTurnstileArgs): Promise {\n if (!token) {return false}\n\n const integrations = await getSiteIntegrations(payload)\n const secret = integrations.turnstileSecretKey\n if (!secret) {return false}\n\n const body = new URLSearchParams({ response: token, secret })\n if (ip) {body.append('remoteip', ip)}\n\n try {\n const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {\n body,\n method: 'POST',\n })\n const data = (await res.json()) as SiteverifyResponse\n return data.success\n } catch {\n return false\n }\n}\n"],"names":["getSiteIntegrations","verifyTurnstile","ip","payload","token","integrations","secret","turnstileSecretKey","body","URLSearchParams","response","append","res","fetch","method","data","json","success"],"mappings":"AAAA,OAAO,cAAa;AAIpB,SAASA,mBAAmB,QAAQ,sBAAqB;AAsBzD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,gBAAgB,EACpCC,EAAE,EACFC,OAAO,EACPC,KAAK,EACe;IACpB,IAAI,CAACA,OAAO;QAAC,OAAO;IAAK;IAEzB,MAAMC,eAAe,MAAML,oBAA+CG;IAC1E,MAAMG,SAASD,aAAaE,kBAAkB;IAC9C,IAAI,CAACD,QAAQ;QAAC,OAAO;IAAK;IAE1B,MAAME,OAAO,IAAIC,gBAAgB;QAAEC,UAAUN;QAAOE;IAAO;IAC3D,IAAIJ,IAAI;QAACM,KAAKG,MAAM,CAAC,YAAYT;IAAG;IAEpC,IAAI;QACF,MAAMU,MAAM,MAAMC,MAAM,6DAA6D;YACnFL;YACAM,QAAQ;QACV;QACA,MAAMC,OAAQ,MAAMH,IAAII,IAAI;QAC5B,OAAOD,KAAKE,OAAO;IACrB,EAAE,OAAM;QACN,OAAO;IACT;AACF"} \ No newline at end of file +{"version":3,"sources":["verify.js"],"sourcesContent":["import 'server-only'\n\nimport type { BasePayload } from 'payload'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\ntype SiteverifyResponse = {\n challenge_ts?: string\n 'error-codes'?: string[]\n hostname?: string\n success: boolean\n}\n\ntype IntegrationsWithTurnstile = {\n turnstileSecretKey?: null | string\n}\n\ntype VerifyTurnstileArgs = {\n /** Optional client IP for stricter verification. */\n ip?: string\n /** Payload instance — used to read the secret from SiteIntegrations. */\n payload: BasePayload\n /** Token produced by the client-side widget. */\n token: string\n}\n\n/**\n * Verifies a Turnstile token with Cloudflare, server-side only.\n *\n * The secret comes from the SiteIntegrations global (editor-managed, per the\n * plugin's \"secrets in the panel\" model), read via the Local API which bypasses\n * access control. `server-only` guarantees this never reaches the browser\n * bundle, keeping the secret off the client.\n *\n * Returns false on any failure (missing secret/token, network error, rejected\n * challenge) — callers treat false as \"do not trust this submission\".\n */\nexport async function verifyTurnstile({\n ip,\n payload,\n token,\n}: VerifyTurnstileArgs): Promise {\n if (!token) {return false}\n\n const integrations = await getSiteIntegrations(payload)\n const secret = integrations.turnstileSecretKey\n if (!secret) {return false}\n\n const body = new URLSearchParams({ response: token, secret })\n if (ip) {body.append('remoteip', ip)}\n\n try {\n const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {\n body,\n method: 'POST',\n })\n const data = (await res.json()) as SiteverifyResponse\n return data.success\n } catch {\n return false\n }\n}\n"],"names":["getSiteIntegrations","verifyTurnstile","ip","payload","token","integrations","secret","turnstileSecretKey","body","URLSearchParams","response","append","res","fetch","method","data","json","success"],"mappings":"AAAA,OAAO,cAAa;AAIpB,SAASA,mBAAmB,QAAQ,sBAAqB;AAsBzD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,gBAAgB,EACpCC,EAAE,EACFC,OAAO,EACPC,KAAK,EACe;IACpB,IAAI,CAACA,OAAO;QAAC,OAAO;IAAK;IAEzB,MAAMC,eAAe,MAAML,oBAA+CG;IAC1E,MAAMG,SAASD,aAAaE,kBAAkB;IAC9C,IAAI,CAACD,QAAQ;QAAC,OAAO;IAAK;IAE1B,MAAME,OAAO,IAAIC,gBAAgB;QAAEC,UAAUN;QAAOE;IAAO;IAC3D,IAAIJ,IAAI;QAACM,KAAKG,MAAM,CAAC,YAAYT;IAAG;IAEpC,IAAI;QACF,MAAMU,MAAM,MAAMC,MAAM,6DAA6D;YACnFL;YACAM,QAAQ;QACV;QACA,MAAMC,OAAQ,MAAMH,IAAII,IAAI;QAC5B,OAAOD,KAAKE,OAAO;IACrB,EAAE,OAAM;QACN,OAAO;IACT;AACF"} \ No newline at end of file diff --git a/dist/plugin.js b/dist/plugin.js index fa81ed0..4a6366c 100644 --- a/dist/plugin.js +++ b/dist/plugin.js @@ -95,6 +95,6 @@ import { buildSeoPlugin, injectAutoFillMeta, injectSeoTabs } from './modules/seo }; return config; }; -}; +}; //# sourceMappingURL=plugin.js.map //# sourceMappingURL=plugin.js.map \ No newline at end of file diff --git a/dist/plugin.js.map b/dist/plugin.js.map index 915cbe6..32d8478 100644 --- a/dist/plugin.js.map +++ b/dist/plugin.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, Plugin } from 'payload'\n\nimport type { IpalOptions } from './types.js'\n\nimport { buildCookieSettings } from './globals/CookieSettings/index.js'\nimport { buildSiteIntegrations } from './globals/SiteIntegrations/index.js'\nimport { buildSiteSettings } from './globals/SiteSettings/index.js'\nimport { injectRoles } from './modules/access/index.js'\nimport { buildArchiveFields } from './modules/content/index.js'\nimport { buildFormsPlugin } from './modules/forms/formsPluginConfig.js'\nimport { buildLocalizationConfig, validateI18nConfig } from './modules/i18n/index.js'\nimport { buildSystemPagesFields } from './modules/pages/index.js'\nimport { buildSeoPlugin, injectAutoFillMeta, injectSeoTabs } from './modules/seo/index.js'\n\n/**\n * IPAL (Intecion Payload Advanced Library) plugin for Payload CMS 3.\n *\n * @example\n * ```ts\n * import { ipalKit } from 'ipal-kit'\n *\n * export default buildConfig({\n * plugins: [\n * ipalKit({\n * i18n: {\n * locales: [\n * { code: 'pl', label: 'Polski' },\n * { code: 'en', label: 'English' },\n * ],\n * defaultLocale: 'pl',\n * },\n * access: { authCollection: 'users' },\n * }),\n * ],\n * })\n * ```\n */\nexport const ipalKit = (options: IpalOptions): Plugin => {\n // Validate eagerly — fail fast before Payload boots\n validateI18nConfig(options.i18n)\n\n return async (incomingConfig: Config): Promise => {\n // Early return when disabled — schema stays, behavior off\n if (options.enabled === false) {\n return incomingConfig\n }\n\n let config = { ...incomingConfig }\n\n // --- i18n ---\n config.localization = buildLocalizationConfig(options.i18n)\n\n // --- access: inject roles into the client's auth collection ---\n if (options.access) {\n config = injectRoles(config, options.access)\n }\n\n // --- seo: apply @payloadcms/plugin-seo directly ---\n // NOTE: apply the plugin function to the config immediately rather than\n // pushing it onto config.plugins. Payload has already iterated the plugins\n // array by the time IPAL runs, so nested plugins added to that list are\n // never executed. Calling the plugin as (config) => config applies its\n // transform now.\n if (options.seo) {\n config = await buildSeoPlugin({ seo: options.seo })(config)\n // Auto-fill empty meta from document content on save\n config = injectAutoFillMeta(config, options.seo)\n // Wrap fields into Content + SEO tabs (replaces plugin-seo's tabbedUI,\n // which breaks when other fields already exist in the collection)\n config = injectSeoTabs(config, options.seo)\n }\n\n // --- forms: apply @payloadcms/plugin-form-builder directly ---\n if (options.forms) {\n config = await buildFormsPlugin(options.forms)(config)\n }\n\n // --- globals ---\n // The System Pages tab collects every \"which page plays this role\"\n // assignment. Composing it here keeps SiteSettings unaware of which modules\n // are enabled — it just renders the fields it's given.\n const systemPageFields = [\n ...(options.pages ? buildSystemPagesFields(options.pages) : []),\n ...(options.content && options.pages\n ? buildArchiveFields(options.content, options.pages.slug)\n : []),\n ]\n\n config.globals = [\n ...(config.globals ?? []),\n buildSiteSettings({\n additionalFields: options.siteSettingsFields,\n systemPageFields,\n }),\n buildSiteIntegrations({ additionalFields: options.integrationsFields }),\n buildCookieSettings(),\n ]\n\n // --- hooks: onInit ---\n const incomingOnInit = config.onInit\n config.onInit = async (payload) => {\n if (incomingOnInit) {await incomingOnInit(payload)}\n payload.logger.info('[ipal] Plugin initialized.')\n }\n\n return config\n }\n}\n"],"names":["buildCookieSettings","buildSiteIntegrations","buildSiteSettings","injectRoles","buildArchiveFields","buildFormsPlugin","buildLocalizationConfig","validateI18nConfig","buildSystemPagesFields","buildSeoPlugin","injectAutoFillMeta","injectSeoTabs","ipalKit","options","i18n","incomingConfig","enabled","config","localization","access","seo","forms","systemPageFields","pages","content","slug","globals","additionalFields","siteSettingsFields","integrationsFields","incomingOnInit","onInit","payload","logger","info"],"mappings":"AAIA,SAASA,mBAAmB,QAAQ,oCAAmC;AACvE,SAASC,qBAAqB,QAAQ,sCAAqC;AAC3E,SAASC,iBAAiB,QAAQ,kCAAiC;AACnE,SAASC,WAAW,QAAQ,4BAA2B;AACvD,SAASC,kBAAkB,QAAQ,6BAA4B;AAC/D,SAASC,gBAAgB,QAAQ,uCAAsC;AACvE,SAASC,uBAAuB,EAAEC,kBAAkB,QAAQ,0BAAyB;AACrF,SAASC,sBAAsB,QAAQ,2BAA0B;AACjE,SAASC,cAAc,EAAEC,kBAAkB,EAAEC,aAAa,QAAQ,yBAAwB;AAE1F;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,MAAMC,UAAU,CAACC;IACtB,oDAAoD;IACpDN,mBAAmBM,QAAQC,IAAI;IAE/B,OAAO,OAAOC;QACZ,0DAA0D;QAC1D,IAAIF,QAAQG,OAAO,KAAK,OAAO;YAC7B,OAAOD;QACT;QAEA,IAAIE,SAAS;YAAE,GAAGF,cAAc;QAAC;QAEjC,eAAe;QACfE,OAAOC,YAAY,GAAGZ,wBAAwBO,QAAQC,IAAI;QAE1D,iEAAiE;QACjE,IAAID,QAAQM,MAAM,EAAE;YAClBF,SAASd,YAAYc,QAAQJ,QAAQM,MAAM;QAC7C;QAEA,qDAAqD;QACrD,wEAAwE;QACxE,2EAA2E;QAC3E,wEAAwE;QACxE,uEAAuE;QACvE,iBAAiB;QACjB,IAAIN,QAAQO,GAAG,EAAE;YACfH,SAAS,MAAMR,eAAe;gBAAEW,KAAKP,QAAQO,GAAG;YAAC,GAAGH;YACpD,qDAAqD;YACrDA,SAASP,mBAAmBO,QAAQJ,QAAQO,GAAG;YAC/C,uEAAuE;YACvE,kEAAkE;YAClEH,SAASN,cAAcM,QAAQJ,QAAQO,GAAG;QAC5C;QAEA,gEAAgE;QAChE,IAAIP,QAAQQ,KAAK,EAAE;YACjBJ,SAAS,MAAMZ,iBAAiBQ,QAAQQ,KAAK,EAAEJ;QACjD;QAEA,kBAAkB;QAClB,mEAAmE;QACnE,4EAA4E;QAC5E,uDAAuD;QACvD,MAAMK,mBAAmB;eACnBT,QAAQU,KAAK,GAAGf,uBAAuBK,QAAQU,KAAK,IAAI,EAAE;eAC1DV,QAAQW,OAAO,IAAIX,QAAQU,KAAK,GAChCnB,mBAAmBS,QAAQW,OAAO,EAAEX,QAAQU,KAAK,CAACE,IAAI,IACtD,EAAE;SACP;QAEDR,OAAOS,OAAO,GAAG;eACXT,OAAOS,OAAO,IAAI,EAAE;YACxBxB,kBAAkB;gBAChByB,kBAAkBd,QAAQe,kBAAkB;gBAC5CN;YACF;YACArB,sBAAsB;gBAAE0B,kBAAkBd,QAAQgB,kBAAkB;YAAC;YACrE7B;SACD;QAED,wBAAwB;QACxB,MAAM8B,iBAAiBb,OAAOc,MAAM;QACpCd,OAAOc,MAAM,GAAG,OAAOC;YACrB,IAAIF,gBAAgB;gBAAC,MAAMA,eAAeE;YAAQ;YAClDA,QAAQC,MAAM,CAACC,IAAI,CAAC;QACtB;QAEA,OAAOjB;IACT;AACF,EAAC"} \ No newline at end of file +{"version":3,"sources":["plugin.js"],"sourcesContent":["import type { Config, Plugin } from 'payload'\n\nimport type { IpalOptions } from './types.js'\n\nimport { buildCookieSettings } from './globals/CookieSettings/index.js'\nimport { buildSiteIntegrations } from './globals/SiteIntegrations/index.js'\nimport { buildSiteSettings } from './globals/SiteSettings/index.js'\nimport { injectRoles } from './modules/access/index.js'\nimport { buildArchiveFields } from './modules/content/index.js'\nimport { buildFormsPlugin } from './modules/forms/formsPluginConfig.js'\nimport { buildLocalizationConfig, validateI18nConfig } from './modules/i18n/index.js'\nimport { buildSystemPagesFields } from './modules/pages/index.js'\nimport { buildSeoPlugin, injectAutoFillMeta, injectSeoTabs } from './modules/seo/index.js'\n\n/**\n * IPAL (Intecion Payload Advanced Library) plugin for Payload CMS 3.\n *\n * @example\n * ```ts\n * import { ipalKit } from 'ipal-kit'\n *\n * export default buildConfig({\n * plugins: [\n * ipalKit({\n * i18n: {\n * locales: [\n * { code: 'pl', label: 'Polski' },\n * { code: 'en', label: 'English' },\n * ],\n * defaultLocale: 'pl',\n * },\n * access: { authCollection: 'users' },\n * }),\n * ],\n * })\n * ```\n */\nexport const ipalKit = (options: IpalOptions): Plugin => {\n // Validate eagerly — fail fast before Payload boots\n validateI18nConfig(options.i18n)\n\n return async (incomingConfig: Config): Promise => {\n // Early return when disabled — schema stays, behavior off\n if (options.enabled === false) {\n return incomingConfig\n }\n\n let config = { ...incomingConfig }\n\n // --- i18n ---\n config.localization = buildLocalizationConfig(options.i18n)\n\n // --- access: inject roles into the client's auth collection ---\n if (options.access) {\n config = injectRoles(config, options.access)\n }\n\n // --- seo: apply @payloadcms/plugin-seo directly ---\n // NOTE: apply the plugin function to the config immediately rather than\n // pushing it onto config.plugins. Payload has already iterated the plugins\n // array by the time IPAL runs, so nested plugins added to that list are\n // never executed. Calling the plugin as (config) => config applies its\n // transform now.\n if (options.seo) {\n config = await buildSeoPlugin({ seo: options.seo })(config)\n // Auto-fill empty meta from document content on save\n config = injectAutoFillMeta(config, options.seo)\n // Wrap fields into Content + SEO tabs (replaces plugin-seo's tabbedUI,\n // which breaks when other fields already exist in the collection)\n config = injectSeoTabs(config, options.seo)\n }\n\n // --- forms: apply @payloadcms/plugin-form-builder directly ---\n if (options.forms) {\n config = await buildFormsPlugin(options.forms)(config)\n }\n\n // --- globals ---\n // The System Pages tab collects every \"which page plays this role\"\n // assignment. Composing it here keeps SiteSettings unaware of which modules\n // are enabled — it just renders the fields it's given.\n const systemPageFields = [\n ...(options.pages ? buildSystemPagesFields(options.pages) : []),\n ...(options.content && options.pages\n ? buildArchiveFields(options.content, options.pages.slug)\n : []),\n ]\n\n config.globals = [\n ...(config.globals ?? []),\n buildSiteSettings({\n additionalFields: options.siteSettingsFields,\n systemPageFields,\n }),\n buildSiteIntegrations({ additionalFields: options.integrationsFields }),\n buildCookieSettings(),\n ]\n\n // --- hooks: onInit ---\n const incomingOnInit = config.onInit\n config.onInit = async (payload) => {\n if (incomingOnInit) {await incomingOnInit(payload)}\n payload.logger.info('[ipal] Plugin initialized.')\n }\n\n return config\n }\n}\n"],"names":["buildCookieSettings","buildSiteIntegrations","buildSiteSettings","injectRoles","buildArchiveFields","buildFormsPlugin","buildLocalizationConfig","validateI18nConfig","buildSystemPagesFields","buildSeoPlugin","injectAutoFillMeta","injectSeoTabs","ipalKit","options","i18n","incomingConfig","enabled","config","localization","access","seo","forms","systemPageFields","pages","content","slug","globals","additionalFields","siteSettingsFields","integrationsFields","incomingOnInit","onInit","payload","logger","info"],"mappings":"AAIA,SAASA,mBAAmB,QAAQ,oCAAmC;AACvE,SAASC,qBAAqB,QAAQ,sCAAqC;AAC3E,SAASC,iBAAiB,QAAQ,kCAAiC;AACnE,SAASC,WAAW,QAAQ,4BAA2B;AACvD,SAASC,kBAAkB,QAAQ,6BAA4B;AAC/D,SAASC,gBAAgB,QAAQ,uCAAsC;AACvE,SAASC,uBAAuB,EAAEC,kBAAkB,QAAQ,0BAAyB;AACrF,SAASC,sBAAsB,QAAQ,2BAA0B;AACjE,SAASC,cAAc,EAAEC,kBAAkB,EAAEC,aAAa,QAAQ,yBAAwB;AAE1F;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,MAAMC,UAAU,CAACC;IACtB,oDAAoD;IACpDN,mBAAmBM,QAAQC,IAAI;IAE/B,OAAO,OAAOC;QACZ,0DAA0D;QAC1D,IAAIF,QAAQG,OAAO,KAAK,OAAO;YAC7B,OAAOD;QACT;QAEA,IAAIE,SAAS;YAAE,GAAGF,cAAc;QAAC;QAEjC,eAAe;QACfE,OAAOC,YAAY,GAAGZ,wBAAwBO,QAAQC,IAAI;QAE1D,iEAAiE;QACjE,IAAID,QAAQM,MAAM,EAAE;YAClBF,SAASd,YAAYc,QAAQJ,QAAQM,MAAM;QAC7C;QAEA,qDAAqD;QACrD,wEAAwE;QACxE,2EAA2E;QAC3E,wEAAwE;QACxE,uEAAuE;QACvE,iBAAiB;QACjB,IAAIN,QAAQO,GAAG,EAAE;YACfH,SAAS,MAAMR,eAAe;gBAAEW,KAAKP,QAAQO,GAAG;YAAC,GAAGH;YACpD,qDAAqD;YACrDA,SAASP,mBAAmBO,QAAQJ,QAAQO,GAAG;YAC/C,uEAAuE;YACvE,kEAAkE;YAClEH,SAASN,cAAcM,QAAQJ,QAAQO,GAAG;QAC5C;QAEA,gEAAgE;QAChE,IAAIP,QAAQQ,KAAK,EAAE;YACjBJ,SAAS,MAAMZ,iBAAiBQ,QAAQQ,KAAK,EAAEJ;QACjD;QAEA,kBAAkB;QAClB,mEAAmE;QACnE,4EAA4E;QAC5E,uDAAuD;QACvD,MAAMK,mBAAmB;eACnBT,QAAQU,KAAK,GAAGf,uBAAuBK,QAAQU,KAAK,IAAI,EAAE;eAC1DV,QAAQW,OAAO,IAAIX,QAAQU,KAAK,GAChCnB,mBAAmBS,QAAQW,OAAO,EAAEX,QAAQU,KAAK,CAACE,IAAI,IACtD,EAAE;SACP;QAEDR,OAAOS,OAAO,GAAG;eACXT,OAAOS,OAAO,IAAI,EAAE;YACxBxB,kBAAkB;gBAChByB,kBAAkBd,QAAQe,kBAAkB;gBAC5CN;YACF;YACArB,sBAAsB;gBAAE0B,kBAAkBd,QAAQgB,kBAAkB;YAAC;YACrE7B;SACD;QAED,wBAAwB;QACxB,MAAM8B,iBAAiBb,OAAOc,MAAM;QACpCd,OAAOc,MAAM,GAAG,OAAOC;YACrB,IAAIF,gBAAgB;gBAAC,MAAMA,eAAeE;YAAQ;YAClDA,QAAQC,MAAM,CAACC,IAAI,CAAC;QACtB;QAEA,OAAOjB;IACT;AACF,EAAC"} \ No newline at end of file diff --git a/dist/types.js b/dist/types.js index 3601f21..748dd6b 100644 --- a/dist/types.js +++ b/dist/types.js @@ -1,6 +1,6 @@ /** * Configuration options for the IPAL plugin. * Passed by the client project in payload.config.ts. - */ export { }; + */ export { }; //# sourceMappingURL=types.js.map //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/types.js.map b/dist/types.js.map index 7c3b73f..9f61466 100644 --- a/dist/types.js.map +++ b/dist/types.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { AccessOption } from './modules/access/types.js'\nimport type { ContentOption } from './modules/content/types.js'\nimport type { FormsOption } from './modules/forms/types.js'\nimport type { I18nConfig } from './modules/i18n/types.js'\nimport type { PagesOption } from './modules/pages/types.js'\nimport type { SeoOption } from './modules/seo/types.js'\n\n/**\n * Configuration options for the IPAL plugin.\n * Passed by the client project in payload.config.ts.\n */\nexport type IpalOptions = {\n /**\n * Role-based access control. Injects a fixed `roles` field\n * (admin > editor > user) into the client's auth collection.\n */\n access?: AccessOption\n\n /**\n * Collections whose entries live under an archive page — blog posts, case\n * studies, anything with a listing. Adds an \"archive page\" assignment per\n * collection in SiteSettings; the assigned page's localized slug becomes the\n * URL segment (/pl/artykuly/moj-post, /en/articles/my-post). Requires `pages`.\n */\n content?: ContentOption\n\n /** Disable the plugin without uninstalling (keeps DB schema intact) */\n enabled?: boolean\n\n /**\n * Forms — form-builder collections (forms, form-submissions) plus the\n * callable submitForm (Turnstile + persistence + SMTP-from-panel email).\n */\n forms?: FormsOption\n\n /** Internationalization — locales, default locale, fallback behavior */\n i18n: I18nConfig\n\n /** Additional fields injected into SiteIntegrations global */\n integrationsFields?: Field[]\n\n /**\n * System-page assignments (homepage, privacy, cookies) in SiteSettings.\n * Provide the slug of the client's Pages collection to enable.\n */\n pages?: PagesOption\n\n /**\n * SEO — adds meta fields to chosen collections (via @payloadcms/plugin-seo)\n * and enables locale-aware metadata helpers.\n */\n seo?: SeoOption\n\n /** Additional fields injected into SiteSettings global */\n siteSettingsFields?: Field[]\n}\n"],"names":[],"mappings":"AASA;;;CAGC,GACD,WA4CC"} \ No newline at end of file +{"version":3,"sources":["types.js"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { AccessOption } from './modules/access/types.js'\nimport type { ContentOption } from './modules/content/types.js'\nimport type { FormsOption } from './modules/forms/types.js'\nimport type { I18nConfig } from './modules/i18n/types.js'\nimport type { PagesOption } from './modules/pages/types.js'\nimport type { SeoOption } from './modules/seo/types.js'\n\n/**\n * Configuration options for the IPAL plugin.\n * Passed by the client project in payload.config.ts.\n */\nexport type IpalOptions = {\n /**\n * Role-based access control. Injects a fixed `roles` field\n * (admin > editor > user) into the client's auth collection.\n */\n access?: AccessOption\n\n /**\n * Collections whose entries live under an archive page — blog posts, case\n * studies, anything with a listing. Adds an \"archive page\" assignment per\n * collection in SiteSettings; the assigned page's localized slug becomes the\n * URL segment (/pl/artykuly/moj-post, /en/articles/my-post). Requires `pages`.\n */\n content?: ContentOption\n\n /** Disable the plugin without uninstalling (keeps DB schema intact) */\n enabled?: boolean\n\n /**\n * Forms — form-builder collections (forms, form-submissions) plus the\n * callable submitForm (Turnstile + persistence + SMTP-from-panel email).\n */\n forms?: FormsOption\n\n /** Internationalization — locales, default locale, fallback behavior */\n i18n: I18nConfig\n\n /** Additional fields injected into SiteIntegrations global */\n integrationsFields?: Field[]\n\n /**\n * System-page assignments (homepage, privacy, cookies) in SiteSettings.\n * Provide the slug of the client's Pages collection to enable.\n */\n pages?: PagesOption\n\n /**\n * SEO — adds meta fields to chosen collections (via @payloadcms/plugin-seo)\n * and enables locale-aware metadata helpers.\n */\n seo?: SeoOption\n\n /** Additional fields injected into SiteSettings global */\n siteSettingsFields?: Field[]\n}\n"],"names":[],"mappings":"AASA;;;CAGC,GACD,WA4CC"} \ No newline at end of file diff --git a/package.json b/package.json index eee70fc..5559dbd 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dist" ], "scripts": { + "prepare": "pnpm build", "prepublishOnly": "pnpm clean && pnpm build", "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc", "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths", @@ -148,4 +149,4 @@ "unrs-resolver" ] } -} \ No newline at end of file +}