diff --git a/dist/globals/SiteSettings/fields/general.d.ts b/dist/globals/SiteSettings/fields/general.d.ts index 0a7c294..78ed235 100644 --- a/dist/globals/SiteSettings/fields/general.d.ts +++ b/dist/globals/SiteSettings/fields/general.d.ts @@ -1,6 +1,6 @@ import type { Field } from 'payload'; /** * General site identity fields. - * Consumed by SEO/OG (siteName, defaultShareImage) and frontend (logo). + * Consumed by SEO/OG (siteName, defaultShareImage) and frontend (logo, favicon). */ export declare const generalFields: Field[]; diff --git a/dist/globals/SiteSettings/fields/general.js b/dist/globals/SiteSettings/fields/general.js index 50f4797..34f2ac9 100644 --- a/dist/globals/SiteSettings/fields/general.js +++ b/dist/globals/SiteSettings/fields/general.js @@ -1,6 +1,7 @@ +import { validateFaviconField } from '../../../modules/seo/index.js'; /** * General site identity fields. - * Consumed by SEO/OG (siteName, defaultShareImage) and frontend (logo). + * Consumed by SEO/OG (siteName, defaultShareImage) and frontend (logo, favicon). */ export const generalFields = [ { name: 'siteName', @@ -63,7 +64,7 @@ name: 'logo', type: 'upload', admin: { - description: 'Primary site logo.' + description: 'Primary site logo. Also used for Organization structured data (buildOrganizationJsonLd).' }, relationTo: 'media' }, @@ -79,7 +80,14 @@ name: 'favicon', type: 'upload', admin: { - description: 'Square source icon (PNG or SVG) for the browser tab. Rendered by the frontend.' + description: 'Square icon, PNG or SVG, min. 48×48px (Google requires this to show it in search). Rendered via buildIconsMetadata.' + }, + hooks: { + // Warns the editor at save time if the favicon is too small (<48×48) or + // not square — Google won't display such favicons in search results. + beforeValidate: [ + validateFaviconField + ] }, relationTo: 'media' } diff --git a/dist/globals/SiteSettings/fields/general.js.map b/dist/globals/SiteSettings/fields/general.js.map index 003be51..c8eefd2 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":["../../../../src/globals/SiteSettings/fields/general.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport { validateFaviconField } from '../../../modules/seo/index.js'\n\n/**\n * General site identity fields.\n * Consumed by SEO/OG (siteName, defaultShareImage) and frontend (logo, favicon).\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:\n 'Primary site logo. Also used for Organization structured data (buildOrganizationJsonLd).',\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:\n 'Square icon, PNG or SVG, min. 48×48px (Google requires this to show it in search). Rendered via buildIconsMetadata.',\n },\n hooks: {\n // Warns the editor at save time if the favicon is too small (<48×48) or\n // not square — Google won't display such favicons in search results.\n beforeValidate: [validateFaviconField],\n },\n relationTo: 'media',\n },\n]\n"],"names":["validateFaviconField","generalFields","name","type","admin","description","localized","required","defaultValue","options","label","value","relationTo","hooks","beforeValidate"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,gCAA+B;AAEpE;;;CAGC,GACD,OAAO,MAAMC,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,aACE;QACJ;QACAO,YAAY;IACd;IACA;QACEV,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;QACf;QACAO,YAAY;IACd;IACA;QACEV,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aACE;QACJ;QACAQ,OAAO;YACL,wEAAwE;YACxE,qEAAqE;YACrEC,gBAAgB;gBAACd;aAAqB;QACxC;QACAY,YAAY;IACd;CACD,CAAA"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index 2eb5335..b41d7e1 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -29,6 +29,7 @@ export type { GlobalQueryOptions } from './modules/payload/index.js'; export { getGlobal, getSiteIntegrations, getSiteSettings, SITE_INTEGRATIONS_SLUG, SITE_SETTINGS_SLUG, } from './modules/payload/index.js'; export { buildSecurityHeaders } from './modules/security/index.js'; export type { BuildSecurityHeadersArgs, SecurityHeader } from './modules/security/index.js'; +export { buildIconsMetadata, buildOrganizationJsonLd, validateFaviconField, } from './modules/seo/index.js'; export type { PageMetadata, SeoMeta, SeoOption } from './modules/seo/index.js'; export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js'; export type { AutoFillMapping, RobotsRules, SitemapEntry } from './modules/seo/index.js'; diff --git a/dist/index.js b/dist/index.js index 8f1d1de..29d5f2f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -18,6 +18,7 @@ export { getNotificationTexts, NOTIFICATION_FALLBACK, resolveFormMessage } from export { ALL_SYSTEM_PAGE_ROLES, getSystemPagePath } from './modules/pages/index.js'; export { getGlobal, getSiteIntegrations, getSiteSettings, SITE_INTEGRATIONS_SLUG, SITE_SETTINGS_SLUG } from './modules/payload/index.js'; export { buildSecurityHeaders } from './modules/security/index.js'; +export { buildIconsMetadata, buildOrganizationJsonLd, validateFaviconField } from './modules/seo/index.js'; 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'; diff --git a/dist/index.js.map b/dist/index.js.map index f98c9fa..6c2bc83 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'\nexport { graphAdapter } from './modules/email/graphAdapter.js'\nexport type { GraphAdapterArgs } from './modules/email/graphAdapter.js'\nexport { mailAdapter } from './modules/email/mailAdapter.js'\nexport type { MailAdapterArgs } from './modules/email/mailAdapter.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'\n// Media — filename normalization hook for upload collections (Media).\nexport { normalizeFilename, normalizeFilenameHook } from './modules/media/index.js'\nexport {\n getNotificationTexts,\n NOTIFICATION_FALLBACK,\n resolveFormMessage,\n} from './modules/notifications/index.js'\nexport type {\n FormNotificationTexts,\n NotificationsData,\n NotificationTexts,\n} from './modules/notifications/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 { buildSecurityHeaders } from './modules/security/index.js'\nexport type { BuildSecurityHeadersArgs, SecurityHeader } from './modules/security/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'\n// Storage — Cloudflare R2 media offload, configured from .env.\nexport { buildR2Storage } from './modules/storage/index.js'\n\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","graphAdapter","mailAdapter","panelSmtpAdapter","buildFormsPlugin","createContentHelpers","buildLocalizedPath","getDefaultLocale","getLocaleCodes","getLocaleDefinition","getLocalizedSlugs","isValidLocale","LOCALE_COOKIE_NAME","matchAcceptLanguage","negotiateLocale","switchLocalePath","createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER","normalizeFilename","normalizeFilenameHook","getNotificationTexts","NOTIFICATION_FALLBACK","resolveFormMessage","ALL_SYSTEM_PAGE_ROLES","getSystemPagePath","getGlobal","getSiteIntegrations","getSiteSettings","SITE_INTEGRATIONS_SLUG","SITE_SETTINGS_SLUG","buildSecurityHeaders","buildHreflangAlternates","buildMetadata","composeTitle","buildAutoFillMetaHook","buildRobots","buildSitemapEntries","createMetadataGenerator","createPageMetadata","injectAutoFillMeta","buildSlugField","toSlug","buildR2Storage","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,SAASC,YAAY,QAAQ,kCAAiC;AAE9D,SAASC,WAAW,QAAQ,iCAAgC;AAE5D,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;AAC5F,sEAAsE;AACtE,SAASC,iBAAiB,EAAEC,qBAAqB,QAAQ,2BAA0B;AACnF,SACEC,oBAAoB,EACpBC,qBAAqB,EACrBC,kBAAkB,QACb,mCAAkC;AAOzC,SAASC,qBAAqB,EAAEC,iBAAiB,QAAQ,2BAA0B;AAEnF,SACEC,SAAS,EACTC,mBAAmB,EACnBC,eAAe,EACfC,sBAAsB,EACtBC,kBAAkB,QACb,6BAA4B;AACnC,SAASC,oBAAoB,QAAQ,8BAA6B;AAGlE,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,+DAA+D;AAC/D,SAASC,cAAc,QAAQ,6BAA4B;AAE3D,SAASC,OAAO,QAAQ,cAAa"} \ No newline at end of file +{"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'\nexport { graphAdapter } from './modules/email/graphAdapter.js'\nexport type { GraphAdapterArgs } from './modules/email/graphAdapter.js'\nexport { mailAdapter } from './modules/email/mailAdapter.js'\nexport type { MailAdapterArgs } from './modules/email/mailAdapter.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'\n// Media — filename normalization hook for upload collections (Media).\nexport { normalizeFilename, normalizeFilenameHook } from './modules/media/index.js'\nexport {\n getNotificationTexts,\n NOTIFICATION_FALLBACK,\n resolveFormMessage,\n} from './modules/notifications/index.js'\nexport type {\n FormNotificationTexts,\n NotificationsData,\n NotificationTexts,\n} from './modules/notifications/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 { buildSecurityHeaders } from './modules/security/index.js'\nexport type { BuildSecurityHeadersArgs, SecurityHeader } from './modules/security/index.js'\nexport {\n buildIconsMetadata,\n buildOrganizationJsonLd,\n validateFaviconField,\n} from './modules/seo/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'\n// Storage — Cloudflare R2 media offload, configured from .env.\nexport { buildR2Storage } from './modules/storage/index.js'\n\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","graphAdapter","mailAdapter","panelSmtpAdapter","buildFormsPlugin","createContentHelpers","buildLocalizedPath","getDefaultLocale","getLocaleCodes","getLocaleDefinition","getLocalizedSlugs","isValidLocale","LOCALE_COOKIE_NAME","matchAcceptLanguage","negotiateLocale","switchLocalePath","createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER","normalizeFilename","normalizeFilenameHook","getNotificationTexts","NOTIFICATION_FALLBACK","resolveFormMessage","ALL_SYSTEM_PAGE_ROLES","getSystemPagePath","getGlobal","getSiteIntegrations","getSiteSettings","SITE_INTEGRATIONS_SLUG","SITE_SETTINGS_SLUG","buildSecurityHeaders","buildIconsMetadata","buildOrganizationJsonLd","validateFaviconField","buildHreflangAlternates","buildMetadata","composeTitle","buildAutoFillMetaHook","buildRobots","buildSitemapEntries","createMetadataGenerator","createPageMetadata","injectAutoFillMeta","buildSlugField","toSlug","buildR2Storage","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,SAASC,YAAY,QAAQ,kCAAiC;AAE9D,SAASC,WAAW,QAAQ,iCAAgC;AAE5D,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;AAC5F,sEAAsE;AACtE,SAASC,iBAAiB,EAAEC,qBAAqB,QAAQ,2BAA0B;AACnF,SACEC,oBAAoB,EACpBC,qBAAqB,EACrBC,kBAAkB,QACb,mCAAkC;AAOzC,SAASC,qBAAqB,EAAEC,iBAAiB,QAAQ,2BAA0B;AAEnF,SACEC,SAAS,EACTC,mBAAmB,EACnBC,eAAe,EACfC,sBAAsB,EACtBC,kBAAkB,QACb,6BAA4B;AACnC,SAASC,oBAAoB,QAAQ,8BAA6B;AAElE,SACEC,kBAAkB,EAClBC,uBAAuB,EACvBC,oBAAoB,QACf,yBAAwB;AAE/B,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,+DAA+D;AAC/D,SAASC,cAAc,QAAQ,6BAA4B;AAE3D,SAASC,OAAO,QAAQ,cAAa"} \ No newline at end of file diff --git a/dist/modules/seo/buildIconsMetadata.d.ts b/dist/modules/seo/buildIconsMetadata.d.ts new file mode 100644 index 0000000..86d34ac --- /dev/null +++ b/dist/modules/seo/buildIconsMetadata.d.ts @@ -0,0 +1,33 @@ +import type { Metadata } from 'next'; +type MediaLike = { + height?: null | number; + mimeType?: null | string; + url?: null | string; + width?: null | number; +} | null | undefined; +/** + * Builds Next.js `icons` metadata (favicon / apple-touch-icon) from the panel's + * favicon upload, so the browser tab AND Google get a proper . + * + * Why the plugin must do this (not the project): favicon-in-Google has strict + * rules — a real in , square, ≥48×48, at a stable URL. + * Leaving it to each project meant inconsistent hand-rolled tags and no favicon + * in search results. This generates the tags correctly, every time, from the + * panel field. + * + * Favicon is GLOBAL (same across pages), so call this once in the ROOT layout's + * generateMetadata — not per page: + * + * import { buildIconsMetadata } from '@intecion/ipal-kit' + * export async function generateMetadata(): Promise { + * const settings = await getSettings(locale) + * return buildIconsMetadata(settings.favicon) + * } + * + * Google notes: it caches favicons separately and slowly (days/weeks), and only + * shows them for icons it deems valid. Warn on too-small icons at upload time + * (see the media validation hook) so editors don't ship a <48px favicon Google + * will reject. + */ +export declare function buildIconsMetadata(favicon: MediaLike): Metadata; +export {}; diff --git a/dist/modules/seo/buildIconsMetadata.js b/dist/modules/seo/buildIconsMetadata.js new file mode 100644 index 0000000..70f6d6d --- /dev/null +++ b/dist/modules/seo/buildIconsMetadata.js @@ -0,0 +1,54 @@ +/** + * Builds Next.js `icons` metadata (favicon / apple-touch-icon) from the panel's + * favicon upload, so the browser tab AND Google get a proper . + * + * Why the plugin must do this (not the project): favicon-in-Google has strict + * rules — a real in , square, ≥48×48, at a stable URL. + * Leaving it to each project meant inconsistent hand-rolled tags and no favicon + * in search results. This generates the tags correctly, every time, from the + * panel field. + * + * Favicon is GLOBAL (same across pages), so call this once in the ROOT layout's + * generateMetadata — not per page: + * + * import { buildIconsMetadata } from '@intecion/ipal-kit' + * export async function generateMetadata(): Promise { + * const settings = await getSettings(locale) + * return buildIconsMetadata(settings.favicon) + * } + * + * Google notes: it caches favicons separately and slowly (days/weeks), and only + * shows them for icons it deems valid. Warn on too-small icons at upload time + * (see the media validation hook) so editors don't ship a <48px favicon Google + * will reject. + */ export function buildIconsMetadata(favicon) { + const url = favicon?.url; + if (!url) { + return {}; + } + const isSvg = favicon?.mimeType === 'image/svg+xml' || url.endsWith('.svg'); + return { + icons: { + // Main favicon. SVG scales; PNG should be ≥48×48 (ideally 96 or 192). + icon: isSvg ? [ + { + type: 'image/svg+xml', + url + } + ] : [ + { + sizes: 'any', + url + } + ], + // Apple touch icon (home-screen bookmark on iOS). Reuses the same asset. + apple: [ + { + url + } + ] + } + }; +} + +//# sourceMappingURL=buildIconsMetadata.js.map \ No newline at end of file diff --git a/dist/modules/seo/buildIconsMetadata.js.map b/dist/modules/seo/buildIconsMetadata.js.map new file mode 100644 index 0000000..b873848 --- /dev/null +++ b/dist/modules/seo/buildIconsMetadata.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/modules/seo/buildIconsMetadata.ts"],"sourcesContent":["import type { Metadata } from 'next'\n\ntype MediaLike =\n | { height?: null | number; mimeType?: null | string; url?: null | string; width?: null | number }\n | null\n | undefined\n\n/**\n * Builds Next.js `icons` metadata (favicon / apple-touch-icon) from the panel's\n * favicon upload, so the browser tab AND Google get a proper .\n *\n * Why the plugin must do this (not the project): favicon-in-Google has strict\n * rules — a real in , square, ≥48×48, at a stable URL.\n * Leaving it to each project meant inconsistent hand-rolled tags and no favicon\n * in search results. This generates the tags correctly, every time, from the\n * panel field.\n *\n * Favicon is GLOBAL (same across pages), so call this once in the ROOT layout's\n * generateMetadata — not per page:\n *\n * import { buildIconsMetadata } from '@intecion/ipal-kit'\n * export async function generateMetadata(): Promise {\n * const settings = await getSettings(locale)\n * return buildIconsMetadata(settings.favicon)\n * }\n *\n * Google notes: it caches favicons separately and slowly (days/weeks), and only\n * shows them for icons it deems valid. Warn on too-small icons at upload time\n * (see the media validation hook) so editors don't ship a <48px favicon Google\n * will reject.\n */\nexport function buildIconsMetadata(favicon: MediaLike): Metadata {\n const url = favicon?.url\n if (!url) {return {}}\n\n const isSvg = favicon?.mimeType === 'image/svg+xml' || url.endsWith('.svg')\n\n return {\n icons: {\n // Main favicon. SVG scales; PNG should be ≥48×48 (ideally 96 or 192).\n icon: isSvg ? [{ type: 'image/svg+xml', url }] : [{ sizes: 'any', url }],\n // Apple touch icon (home-screen bookmark on iOS). Reuses the same asset.\n apple: [{ url }],\n },\n }\n}\n"],"names":["buildIconsMetadata","favicon","url","isSvg","mimeType","endsWith","icons","icon","type","sizes","apple"],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GACD,OAAO,SAASA,mBAAmBC,OAAkB;IACnD,MAAMC,MAAMD,SAASC;IACrB,IAAI,CAACA,KAAK;QAAC,OAAO,CAAC;IAAC;IAEpB,MAAMC,QAAQF,SAASG,aAAa,mBAAmBF,IAAIG,QAAQ,CAAC;IAEpE,OAAO;QACLC,OAAO;YACL,sEAAsE;YACtEC,MAAMJ,QAAQ;gBAAC;oBAAEK,MAAM;oBAAiBN;gBAAI;aAAE,GAAG;gBAAC;oBAAEO,OAAO;oBAAOP;gBAAI;aAAE;YACxE,yEAAyE;YACzEQ,OAAO;gBAAC;oBAAER;gBAAI;aAAE;QAClB;IACF;AACF"} \ No newline at end of file diff --git a/dist/modules/seo/buildOrganizationJsonLd.d.ts b/dist/modules/seo/buildOrganizationJsonLd.d.ts new file mode 100644 index 0000000..884204e --- /dev/null +++ b/dist/modules/seo/buildOrganizationJsonLd.d.ts @@ -0,0 +1,41 @@ +type MediaLike = { + url?: null | string; +} | null | undefined; +type OrganizationJsonLdArgs = { + /** Logo media (from panel). Google uses this for brand knowledge panels. */ + logo?: MediaLike; + /** Organization / site name. */ + name: string; + /** Optional social / official profile URLs (sameAs). */ + sameAs?: string[]; + /** Absolute site URL (https://…). */ + url: string; +}; +/** + * Builds Organization JSON-LD (schema.org) — helps Google associate the site + * with a brand: name, logo, official links. Improves how the site appears in + * search (brand recognition, logo in knowledge panels) and is a signal used + * alongside favicon for identity. + * + * Returns a plain object; the project renders it as a