added /dist
This commit is contained in:
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Builds a beforeChange hook that fills empty SEO meta fields from document
|
||||
* content. Only fills when the meta field is blank — never overwrites what an
|
||||
* editor typed. This runs server-side on every create/update, so editors get
|
||||
* sensible meta without clicking "auto-generate".
|
||||
*
|
||||
* The plugin doesn't know the client's collection shape, so the field mapping
|
||||
* is configurable; defaults follow the website template.
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=autoFillMeta.d.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["autoFillMeta.d.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload';\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 * 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 declare function buildAutoFillMetaHook(mapping?: AutoFillMapping): CollectionBeforeChangeHook;\n"],"names":[],"mappings":"AAWA;;;;;;;;CAQC,GACD,WAAqG"}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
import type { CollectionBeforeChangeHook } from 'payload';
|
||||
/**
|
||||
* Maps document fields to SEO meta fields for auto-fill.
|
||||
* Defaults match the Payload website template (title → meta.title).
|
||||
*/
|
||||
export type AutoFillMapping = {
|
||||
/** Document field used to fill meta.description when empty. */
|
||||
description?: string;
|
||||
/** Document field used to fill meta.title when empty. Default: 'title'. */
|
||||
title?: string;
|
||||
};
|
||||
/**
|
||||
* Builds a beforeChange hook that fills empty SEO meta fields from document
|
||||
* content. Only fills when the meta field is blank — never overwrites what an
|
||||
* editor typed. This runs server-side on every create/update, so editors get
|
||||
* sensible meta without clicking "auto-generate".
|
||||
*
|
||||
* The plugin doesn't know the client's collection shape, so the field mapping
|
||||
* is configurable; defaults follow the website template.
|
||||
*/
|
||||
export declare function buildAutoFillMetaHook(mapping?: AutoFillMapping): CollectionBeforeChangeHook;
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Reads a possibly-localized field value into a plain string.
|
||||
* With localized fields at this hook stage the value is the active-locale
|
||||
* string, so we just coerce defensively.
|
||||
*/ function readString(value) {
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return value.trim();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Builds a beforeChange hook that fills empty SEO meta fields from document
|
||||
* content. Only fills when the meta field is blank — never overwrites what an
|
||||
* editor typed. This runs server-side on every create/update, so editors get
|
||||
* sensible meta without clicking "auto-generate".
|
||||
*
|
||||
* The plugin doesn't know the client's collection shape, so the field mapping
|
||||
* is configurable; defaults follow the website template.
|
||||
*/ export function buildAutoFillMetaHook(mapping = {}) {
|
||||
const titleField = mapping.title ?? 'title';
|
||||
const descriptionField = mapping.description;
|
||||
return ({ data })=>{
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
// plugin-seo stores meta as a group under `meta`
|
||||
const meta = data.meta ?? {};
|
||||
// Fill meta.title from the document title when empty
|
||||
if (!readString(meta.title)) {
|
||||
const sourceTitle = readString(data[titleField]);
|
||||
if (sourceTitle) {
|
||||
meta.title = sourceTitle;
|
||||
}
|
||||
}
|
||||
// Fill meta.description from a configured field when empty
|
||||
if (descriptionField && !readString(meta.description)) {
|
||||
const sourceDescription = readString(data[descriptionField]);
|
||||
if (sourceDescription) {
|
||||
meta.description = sourceDescription;
|
||||
}
|
||||
}
|
||||
data.meta = meta;
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=autoFillMeta.js.map
|
||||
Vendored
+1
@@ -0,0 +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<string, unknown> | 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"}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=buildMetadata.d.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["buildMetadata.d.ts"],"sourcesContent":["import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js';\nimport type { TitleOrder } from './composeTitle.js';\nimport type { SeoMeta } from './types.js';\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<string, string>;\n };\n description?: string;\n openGraph?: {\n description?: string;\n images?: {\n url: string;\n }[];\n locale?: string;\n title: string;\n };\n title: string;\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 * 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 declare function buildMetadata({ baseUrl, config, homeSlug, imageUrl, locale, meta, order, prefix, query, separator, siteName, slugs, }: BuildMetadataArgs): PageMetadata;\nexport {};\n"],"names":[],"mappings":"AAoEA,WAAU"}
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js';
|
||||
import type { TitleOrder } from './composeTitle.js';
|
||||
import type { SeoMeta } from './types.js';
|
||||
/**
|
||||
* Subset of Next.js `Metadata` this helper produces. Kept local so the plugin
|
||||
* doesn't depend on `next` types; the shape is assignable to Next's Metadata.
|
||||
*/
|
||||
export type PageMetadata = {
|
||||
alternates?: {
|
||||
canonical?: string;
|
||||
languages?: Record<string, string>;
|
||||
};
|
||||
description?: string;
|
||||
openGraph?: {
|
||||
description?: string;
|
||||
images?: {
|
||||
url: string;
|
||||
}[];
|
||||
locale?: string;
|
||||
title: string;
|
||||
};
|
||||
title: string;
|
||||
};
|
||||
type BuildMetadataArgs = {
|
||||
/** Absolute site origin, e.g. 'https://example.com'. */
|
||||
baseUrl?: string;
|
||||
config: I18nConfig;
|
||||
/** Home slug that collapses to the locale root. Defaults to 'home'. */
|
||||
homeSlug?: string;
|
||||
/** Resolved OG image URL (page image or site defaultShareImage). */
|
||||
imageUrl?: null | string;
|
||||
/** Current locale being rendered. */
|
||||
locale: string;
|
||||
/** SEO meta from the document (plugin-seo group). */
|
||||
meta?: null | SeoMeta;
|
||||
/** Page title or site name first. Defaults to 'page-first'. */
|
||||
order?: TitleOrder;
|
||||
/**
|
||||
* Localized segment the document lives under (an archive page's slugs).
|
||||
* Feeds both canonical and hreflang, so /pl/artykuly/moj-post and
|
||||
* /en/articles/my-post point at each other correctly.
|
||||
*/
|
||||
prefix?: LocalizedSlugs;
|
||||
/**
|
||||
* Query string appended to canonical and every hreflang, e.g. '?page=2'.
|
||||
*
|
||||
* A paginated listing must be canonical to itself — pointing page 2 at page 1
|
||||
* tells Google the entries on it don't exist. Alternates carry the same page,
|
||||
* since /pl/artykuly?page=2 corresponds to /en/articles?page=2.
|
||||
*/
|
||||
query?: string;
|
||||
/** Separator between page title and site name. Defaults to ' | '. */
|
||||
separator?: string;
|
||||
/** Site name for title composition and OG. */
|
||||
siteName?: null | string;
|
||||
/** slug per locale for this document — drives canonical + hreflang. */
|
||||
slugs: LocalizedSlugs;
|
||||
};
|
||||
/**
|
||||
* Assembles a Next.js-compatible Metadata object from document SEO fields and
|
||||
* site-level data. Locale-aware: canonical points at the current locale's
|
||||
* path, and hreflang alternates cover every locale the document exists in.
|
||||
*
|
||||
* Designed for use inside Next.js `generateMetadata`. The caller resolves the
|
||||
* pieces (meta group, site name, image URL, localized slugs) and passes them
|
||||
* in — the plugin composes, it doesn't fetch.
|
||||
*/
|
||||
export declare function buildMetadata({ baseUrl, config, homeSlug, imageUrl, locale, meta, order, prefix, query, separator, siteName, slugs, }: BuildMetadataArgs): PageMetadata;
|
||||
export {};
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
import { buildLocalizedPath } from '../i18n/index.js';
|
||||
import { composeTitle } from './composeTitle.js';
|
||||
import { buildHreflangAlternates } from './hreflang.js';
|
||||
/**
|
||||
* Assembles a Next.js-compatible Metadata object from document SEO fields and
|
||||
* site-level data. Locale-aware: canonical points at the current locale's
|
||||
* path, and hreflang alternates cover every locale the document exists in.
|
||||
*
|
||||
* Designed for use inside Next.js `generateMetadata`. The caller resolves the
|
||||
* pieces (meta group, site name, image URL, localized slugs) and passes them
|
||||
* in — the plugin composes, it doesn't fetch.
|
||||
*/ export function buildMetadata({ baseUrl, config, homeSlug = 'home', imageUrl, locale, meta, order, prefix, query, separator, siteName, slugs }) {
|
||||
// titleOverride wins outright: an editor who filled it in wants that exact
|
||||
// string in the tab, not a composition.
|
||||
const override = meta?.titleOverride?.trim();
|
||||
const title = override || composeTitle({
|
||||
order,
|
||||
pageTitle: meta?.title,
|
||||
separator,
|
||||
siteName
|
||||
});
|
||||
const description = meta?.description?.trim() || undefined;
|
||||
const origin = baseUrl?.replace(/\/$/, '') ?? '';
|
||||
const suffix = query ?? '';
|
||||
const currentPath = buildLocalizedPath({
|
||||
config,
|
||||
homeSlug,
|
||||
locale,
|
||||
prefix,
|
||||
slugs
|
||||
});
|
||||
const canonical = currentPath ? `${origin}${currentPath}${suffix}` : undefined;
|
||||
const languages = buildHreflangAlternates({
|
||||
baseUrl,
|
||||
config,
|
||||
homeSlug,
|
||||
prefix,
|
||||
slugs
|
||||
});
|
||||
if (suffix) {
|
||||
for (const code of Object.keys(languages)){
|
||||
languages[code] = `${languages[code]}${suffix}`;
|
||||
}
|
||||
}
|
||||
const images = imageUrl ? [
|
||||
{
|
||||
url: imageUrl
|
||||
}
|
||||
] : undefined;
|
||||
return {
|
||||
title,
|
||||
...description && {
|
||||
description
|
||||
},
|
||||
alternates: {
|
||||
...canonical && {
|
||||
canonical
|
||||
},
|
||||
...Object.keys(languages).length > 0 && {
|
||||
languages
|
||||
}
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
...description && {
|
||||
description
|
||||
},
|
||||
...images && {
|
||||
images
|
||||
},
|
||||
locale
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=buildMetadata.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=buildRobots.d.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["buildRobots.d.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 * Shape for Next's `app/robots.ts`.\n */\nexport type RobotsRules = {\n host?: string;\n rules: {\n allow?: string;\n disallow?: string[];\n userAgent: string;\n };\n sitemap?: string;\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 declare function buildRobots({ baseUrl, disallow, sitemap, }: BuildRobotsArgs): RobotsRules;\nexport {};\n"],"names":[],"mappings":"AAmCA,WAAU"}
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
type BuildRobotsArgs = {
|
||||
/** Absolute origin, e.g. 'https://example.com'. */
|
||||
baseUrl: string;
|
||||
/** Paths to disallow. Defaults to Payload's admin and API. */
|
||||
disallow?: string[];
|
||||
/** Whether to advertise the sitemap. Defaults to true. */
|
||||
sitemap?: boolean;
|
||||
};
|
||||
/**
|
||||
* Shape for Next's `app/robots.ts`.
|
||||
*/
|
||||
export type RobotsRules = {
|
||||
host?: string;
|
||||
rules: {
|
||||
allow?: string;
|
||||
disallow?: string[];
|
||||
userAgent: string;
|
||||
};
|
||||
sitemap?: string;
|
||||
};
|
||||
/**
|
||||
* Builds robots rules that allow crawling everything except the admin panel and
|
||||
* API, and point crawlers at the sitemap.
|
||||
*
|
||||
* ```ts
|
||||
* // app/robots.ts
|
||||
* export default function robots() {
|
||||
* return buildRobots({ baseUrl: process.env.NEXT_PUBLIC_SERVER_URL! })
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The admin and API are disallowed by default — no reason for them in the index,
|
||||
* and it keeps crawl budget on real content.
|
||||
*/
|
||||
export declare function buildRobots({ baseUrl, disallow, sitemap, }: BuildRobotsArgs): RobotsRules;
|
||||
export {};
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Builds robots rules that allow crawling everything except the admin panel and
|
||||
* API, and point crawlers at the sitemap.
|
||||
*
|
||||
* ```ts
|
||||
* // app/robots.ts
|
||||
* export default function robots() {
|
||||
* return buildRobots({ baseUrl: process.env.NEXT_PUBLIC_SERVER_URL! })
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The admin and API are disallowed by default — no reason for them in the index,
|
||||
* and it keeps crawl budget on real content.
|
||||
*/ export function buildRobots({ baseUrl, disallow = [
|
||||
'/admin',
|
||||
'/api'
|
||||
], sitemap = true }) {
|
||||
const origin = baseUrl.replace(/\/$/, '');
|
||||
return {
|
||||
rules: {
|
||||
allow: '/',
|
||||
disallow,
|
||||
userAgent: '*'
|
||||
},
|
||||
...sitemap ? {
|
||||
sitemap: `${origin}/sitemap.xml`
|
||||
} : {},
|
||||
host: origin
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=buildRobots.js.map
|
||||
Vendored
+1
@@ -0,0 +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"}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=buildSitemapEntries.d.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["buildSitemapEntries.d.ts"],"sourcesContent":["import type { BasePayload } from 'payload';\nimport type { ContentOption } from '../content/index.js';\nimport type { I18nConfig } from '../i18n/index.js';\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 `<xhtml:link rel=\"alternate\" hreflang=…>` 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?: {\n languages: Record<string, string>;\n };\n changeFrequency?: 'always' | 'daily' | 'hourly' | 'monthly' | 'never' | 'weekly' | 'yearly';\n lastModified?: Date | string;\n priority?: number;\n url: string;\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/**\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 declare function buildSitemapEntries({ baseUrl, changeFrequency, config, content, homeSlug, pagesSlug, payload, settingsSlug, }: BuildSitemapArgs): Promise<SitemapEntry[]>;\nexport {};\n"],"names":[],"mappings":"AA4DA,WAAU"}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { ContentOption } from '../content/index.js';
|
||||
import type { I18nConfig } from '../i18n/index.js';
|
||||
/**
|
||||
* One sitemap entry, shaped for Next's `app/sitemap.ts`.
|
||||
*
|
||||
* `alternates.languages` is the important part for a multilingual site: Next
|
||||
* renders it as `<xhtml:link rel="alternate" hreflang=…>` per URL, which is
|
||||
* exactly what Google uses to connect language versions. A sitemap without it
|
||||
* is the common, weaker kind.
|
||||
*/
|
||||
export type SitemapEntry = {
|
||||
alternates?: {
|
||||
languages: Record<string, string>;
|
||||
};
|
||||
changeFrequency?: 'always' | 'daily' | 'hourly' | 'monthly' | 'never' | 'weekly' | 'yearly';
|
||||
lastModified?: Date | string;
|
||||
priority?: number;
|
||||
url: string;
|
||||
};
|
||||
type BuildSitemapArgs = {
|
||||
/** Absolute origin, e.g. 'https://example.com'. Required for valid sitemap URLs. */
|
||||
baseUrl: string;
|
||||
changeFrequency?: SitemapEntry['changeFrequency'];
|
||||
config: I18nConfig;
|
||||
/** Archive-backed collections, same value as the plugin option. */
|
||||
content?: ContentOption;
|
||||
/**
|
||||
* Slug of the page that is the site root (collapses to /{locale}).
|
||||
* Read from System Pages when omitted.
|
||||
*/
|
||||
homeSlug?: string;
|
||||
/** Pages collection slug. Defaults to 'pages'. */
|
||||
pagesSlug?: string;
|
||||
payload: BasePayload;
|
||||
/** SiteSettings global slug. Defaults to 'site-settings'. */
|
||||
settingsSlug?: string;
|
||||
};
|
||||
/**
|
||||
* Collects every public URL — pages and archive entries — as sitemap entries
|
||||
* with per-URL hreflang and lastmod.
|
||||
*
|
||||
* Reuses the same path/hreflang builders as page metadata, so the sitemap can't
|
||||
* drift from what the pages actually render (a classic source of sitemap bugs:
|
||||
* a URL listed one way and served another). Drafts and noindex documents are
|
||||
* omitted.
|
||||
*
|
||||
* ```ts
|
||||
* // app/sitemap.ts
|
||||
* export default async function sitemap() {
|
||||
* return buildSitemapEntries({
|
||||
* payload: await getPayload({ config }),
|
||||
* config: i18nConfig,
|
||||
* baseUrl: process.env.NEXT_PUBLIC_SERVER_URL!,
|
||||
* content: contentConfig,
|
||||
* })
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function buildSitemapEntries({ baseUrl, changeFrequency, config, content, homeSlug, pagesSlug, payload, settingsSlug, }: BuildSitemapArgs): Promise<SitemapEntry[]>;
|
||||
export {};
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
import { archiveFieldName } from '../content/index.js';
|
||||
import { buildLocalizedPath, getLocalizedSlugs } from '../i18n/index.js';
|
||||
import { buildHreflangAlternates } from './hreflang.js';
|
||||
/** Skip drafts and anything flagged noindex in the SEO tab. */ function isIndexable(doc) {
|
||||
if (doc._status && doc._status !== 'published') {
|
||||
return false;
|
||||
}
|
||||
if (doc.meta?.noindex) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Turns one document into a sitemap entry per default locale isn't needed —
|
||||
* one entry with all languages as alternates is the correct, de-duplicated
|
||||
* shape. The `url` is the current locale's path; `alternates.languages` carries
|
||||
* every locale (including itself, per Google's guidance).
|
||||
*/ function entryFor(doc, locale, config, baseUrl, homeSlug, prefix, changeFrequency) {
|
||||
const slugs = doc.slug && typeof doc.slug === 'object' ? getLocalizedSlugs({
|
||||
config,
|
||||
slugField: doc.slug
|
||||
}) : {};
|
||||
const path = buildLocalizedPath({
|
||||
config,
|
||||
homeSlug,
|
||||
locale,
|
||||
prefix,
|
||||
slugs
|
||||
});
|
||||
if (!path) {
|
||||
return null;
|
||||
}
|
||||
const origin = baseUrl.replace(/\/$/, '');
|
||||
const languages = buildHreflangAlternates({
|
||||
baseUrl,
|
||||
config,
|
||||
homeSlug,
|
||||
prefix,
|
||||
slugs
|
||||
});
|
||||
return {
|
||||
url: `${origin}${path}`,
|
||||
...doc.updatedAt ? {
|
||||
lastModified: doc.updatedAt
|
||||
} : {},
|
||||
...changeFrequency ? {
|
||||
changeFrequency
|
||||
} : {},
|
||||
...Object.keys(languages).length ? {
|
||||
alternates: {
|
||||
languages
|
||||
}
|
||||
} : {}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Collects every public URL — pages and archive entries — as sitemap entries
|
||||
* with per-URL hreflang and lastmod.
|
||||
*
|
||||
* Reuses the same path/hreflang builders as page metadata, so the sitemap can't
|
||||
* drift from what the pages actually render (a classic source of sitemap bugs:
|
||||
* a URL listed one way and served another). Drafts and noindex documents are
|
||||
* omitted.
|
||||
*
|
||||
* ```ts
|
||||
* // app/sitemap.ts
|
||||
* export default async function sitemap() {
|
||||
* return buildSitemapEntries({
|
||||
* payload: await getPayload({ config }),
|
||||
* config: i18nConfig,
|
||||
* baseUrl: process.env.NEXT_PUBLIC_SERVER_URL!,
|
||||
* content: contentConfig,
|
||||
* })
|
||||
* }
|
||||
* ```
|
||||
*/ export async function buildSitemapEntries({ baseUrl, changeFrequency = 'weekly', config, content, homeSlug, pagesSlug = 'pages', payload, settingsSlug = 'site-settings' }) {
|
||||
const locales = config.locales.map((l)=>l.code);
|
||||
const defaultLocale = config.defaultLocale;
|
||||
// Resolve homeSlug and archive prefixes from System Pages (read once, in all
|
||||
// locales so archive prefixes are available per language).
|
||||
const settings = await payload.findGlobal({
|
||||
slug: settingsSlug,
|
||||
depth: 1,
|
||||
locale: 'all'
|
||||
});
|
||||
const resolvedHomeSlug = homeSlug ?? extractSlugInLocale(settings.homepage, defaultLocale) ?? 'home';
|
||||
// Which collections to walk: pages (no prefix) + each content collection with
|
||||
// its archive-page slugs as the localized prefix.
|
||||
const collections = [
|
||||
{
|
||||
slug: pagesSlug
|
||||
}
|
||||
];
|
||||
for (const c of content?.collections ?? []){
|
||||
const archive = settings[archiveFieldName(c.slug)];
|
||||
const prefixSlugs = archive ? slugMapAllLocales(archive, locales) : undefined;
|
||||
collections.push({
|
||||
slug: c.slug,
|
||||
prefixSlugs
|
||||
});
|
||||
}
|
||||
const entries = [];
|
||||
for (const collection of collections){
|
||||
// Read every doc once, across locales, so one row yields all its language
|
||||
// alternates without re-querying per locale.
|
||||
const result = await payload.find({
|
||||
collection: collection.slug,
|
||||
depth: 0,
|
||||
limit: 0,
|
||||
locale: 'all',
|
||||
pagination: false
|
||||
});
|
||||
for (const raw of result.docs){
|
||||
if (!isIndexable(raw)) {
|
||||
continue;
|
||||
}
|
||||
// Emit the entry under the default locale's URL; alternates cover the rest.
|
||||
const entry = entryFor(raw, defaultLocale, config, baseUrl, resolvedHomeSlug, collection.prefixSlugs, changeFrequency);
|
||||
if (entry) {
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
/** Pulls a slug string from a populated relationship in a specific locale. */ function extractSlugInLocale(rel, locale) {
|
||||
if (!rel || typeof rel !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
const slug = rel.slug;
|
||||
if (typeof slug === 'string') {
|
||||
return slug;
|
||||
}
|
||||
if (slug && typeof slug === 'object') {
|
||||
const v = slug[locale];
|
||||
return typeof v === 'string' ? v : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/** Builds a locale→slug map from a populated archive relationship. */ function slugMapAllLocales(rel, locales) {
|
||||
if (!rel || typeof rel !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
const slug = rel.slug;
|
||||
if (!slug || typeof slug !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
const map = {};
|
||||
for (const locale of locales){
|
||||
const v = slug[locale];
|
||||
if (typeof v === 'string') {
|
||||
map[locale] = v;
|
||||
}
|
||||
}
|
||||
return Object.keys(map).length ? map : undefined;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=buildSitemapEntries.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
/** Which part comes first in a composed title. */ export { };
|
||||
|
||||
//# sourceMappingURL=composeTitle.d.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["composeTitle.d.ts"],"sourcesContent":["/** Which part comes first in a composed title. */\nexport type TitleOrder = 'page-first' | 'site-first';\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 * 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 declare function composeTitle({ order, pageTitle, separator, siteName, }: ComposeTitleArgs): string;\nexport {};\n"],"names":[],"mappings":"AAAA,gDAAgD,GAwBhD,WAAU"}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/** Which part comes first in a composed title. */
|
||||
export type TitleOrder = 'page-first' | 'site-first';
|
||||
type ComposeTitleArgs = {
|
||||
/** Defaults to 'page-first' — the page title is what a visitor scans for. */
|
||||
order?: TitleOrder;
|
||||
/** Page-specific title, e.g. 'About Us'. */
|
||||
pageTitle?: null | string;
|
||||
/** Separator between page title and site name. Defaults to ' | '. */
|
||||
separator?: string;
|
||||
/** Site name, e.g. 'Acme Inc'. */
|
||||
siteName?: null | string;
|
||||
};
|
||||
/**
|
||||
* Composes a full document title from a page title and the site name.
|
||||
*
|
||||
* - both, page-first: "About Us | Acme Inc"
|
||||
* - both, site-first: "Acme Inc | About Us"
|
||||
* - page only: "About Us"
|
||||
* - site only: "Acme Inc"
|
||||
* - neither: ""
|
||||
*
|
||||
* Pure function — no dependency on Payload or request state.
|
||||
*/
|
||||
export declare function composeTitle({ order, pageTitle, separator, siteName, }: ComposeTitleArgs): string;
|
||||
export {};
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/** Which part comes first in a composed title. */ /**
|
||||
* Composes a full document title from a page title and the site name.
|
||||
*
|
||||
* - both, page-first: "About Us | Acme Inc"
|
||||
* - both, site-first: "Acme Inc | About Us"
|
||||
* - page only: "About Us"
|
||||
* - site only: "Acme Inc"
|
||||
* - neither: ""
|
||||
*
|
||||
* Pure function — no dependency on Payload or request state.
|
||||
*/ export function composeTitle({ order = 'page-first', pageTitle, separator = ' | ', siteName }) {
|
||||
const page = pageTitle?.trim();
|
||||
const site = siteName?.trim();
|
||||
if (page && site) {
|
||||
return order === 'site-first' ? `${site}${separator}${page}` : `${page}${separator}${site}`;
|
||||
}
|
||||
return page || site || '';
|
||||
}
|
||||
|
||||
//# sourceMappingURL=composeTitle.js.map
|
||||
Vendored
+1
@@ -0,0 +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"}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=createMetadataGenerator.d.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["createMetadataGenerator.d.ts"],"sourcesContent":["import type { BasePayload } from 'payload';\nimport type { I18nConfig } from '../i18n/index.js';\nimport type { PageMetadata } from './buildMetadata.js';\nimport type { SeoMeta } from './types.js';\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<string, unknown>;\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<string, string | string[]>;\n payload: BasePayload;\n }) => Promise<MetadataDocument | null>;\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<null | string>;\n /** Resolves the site name (e.g. from SiteSettings). */\n resolveSiteName?: (args: {\n locale: string;\n payload: BasePayload;\n }) => Promise<null | string>;\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 declare function createMetadataGenerator(args: CreateMetadataGeneratorArgs): (context: {\n locale: string;\n params: Record<string, string | string[]>;\n payload: BasePayload;\n}) => Promise<PageMetadata>;\nexport {};\n"],"names":[],"mappings":"AAyEA,WAAU"}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { I18nConfig } from '../i18n/index.js';
|
||||
import type { PageMetadata } from './buildMetadata.js';
|
||||
import type { SeoMeta } from './types.js';
|
||||
/**
|
||||
* A document as needed for metadata: its SEO meta group and localized slug.
|
||||
*/
|
||||
type MetadataDocument = {
|
||||
meta?: null | SeoMeta;
|
||||
slug?: null | Record<string, unknown>;
|
||||
};
|
||||
type CreateMetadataGeneratorArgs = {
|
||||
/** Absolute site origin, e.g. 'https://example.com'. */
|
||||
baseUrl?: string;
|
||||
config: I18nConfig;
|
||||
/** Home slug that collapses to the locale root. Defaults to 'home'. */
|
||||
homeSlug?: string;
|
||||
/**
|
||||
* Resolves the document to build metadata for, given route params.
|
||||
* The client supplies this (they own the collections and routing); it should
|
||||
* fetch with `locale: 'all'` so the slug field is a locale→value map.
|
||||
*/
|
||||
resolveDocument: (args: {
|
||||
locale: string;
|
||||
params: Record<string, string | string[]>;
|
||||
payload: BasePayload;
|
||||
}) => Promise<MetadataDocument | null>;
|
||||
/** Resolves the OG image URL for the document, if any. */
|
||||
resolveImageUrl?: (args: {
|
||||
doc: MetadataDocument;
|
||||
locale: string;
|
||||
payload: BasePayload;
|
||||
}) => Promise<null | string>;
|
||||
/** Resolves the site name (e.g. from SiteSettings). */
|
||||
resolveSiteName?: (args: {
|
||||
locale: string;
|
||||
payload: BasePayload;
|
||||
}) => Promise<null | string>;
|
||||
};
|
||||
/**
|
||||
* Builds a metadata generator, collapsing the usual generateMetadata
|
||||
* boilerplate into a single wired-up function.
|
||||
*
|
||||
* The client provides resolvers (they own collections/routing); the plugin
|
||||
* owns the assembly (title composition, canonical, hreflang, OG).
|
||||
*
|
||||
* The returned function takes `{ payload, params, locale }` — Next calls
|
||||
* `generateMetadata({ params })` without those, and the plugin never calls
|
||||
* getPayload itself, so the client wraps it in their route file:
|
||||
*
|
||||
* ```ts
|
||||
* // app/(frontend)/[locale]/[[...slug]]/page.tsx
|
||||
* const generate = createMetadataGenerator({
|
||||
* config: i18nConfig,
|
||||
* baseUrl: process.env.NEXT_PUBLIC_SERVER_URL,
|
||||
* resolveDocument: async ({ payload, params, locale }) => { ... },
|
||||
* })
|
||||
*
|
||||
* export async function generateMetadata({ params }) {
|
||||
* const { locale, slug } = await params
|
||||
* const payload = await getPayload({ config: await config })
|
||||
* return generate({ payload, params: { slug: slug ?? [] }, locale })
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* `resolveDocument` should fetch with `locale: 'all'` so the slug field comes
|
||||
* back as a locale→value map — that's what hreflang alternates are built from.
|
||||
*/
|
||||
export declare function createMetadataGenerator(args: CreateMetadataGeneratorArgs): (context: {
|
||||
locale: string;
|
||||
params: Record<string, string | string[]>;
|
||||
payload: BasePayload;
|
||||
}) => Promise<PageMetadata>;
|
||||
export {};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { getLocalizedSlugs } from '../i18n/index.js';
|
||||
import { buildMetadata } from './buildMetadata.js';
|
||||
/**
|
||||
* Builds a metadata generator, collapsing the usual generateMetadata
|
||||
* boilerplate into a single wired-up function.
|
||||
*
|
||||
* The client provides resolvers (they own collections/routing); the plugin
|
||||
* owns the assembly (title composition, canonical, hreflang, OG).
|
||||
*
|
||||
* The returned function takes `{ payload, params, locale }` — Next calls
|
||||
* `generateMetadata({ params })` without those, and the plugin never calls
|
||||
* getPayload itself, so the client wraps it in their route file:
|
||||
*
|
||||
* ```ts
|
||||
* // app/(frontend)/[locale]/[[...slug]]/page.tsx
|
||||
* const generate = createMetadataGenerator({
|
||||
* config: i18nConfig,
|
||||
* baseUrl: process.env.NEXT_PUBLIC_SERVER_URL,
|
||||
* resolveDocument: async ({ payload, params, locale }) => { ... },
|
||||
* })
|
||||
*
|
||||
* export async function generateMetadata({ params }) {
|
||||
* const { locale, slug } = await params
|
||||
* const payload = await getPayload({ config: await config })
|
||||
* return generate({ payload, params: { slug: slug ?? [] }, locale })
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* `resolveDocument` should fetch with `locale: 'all'` so the slug field comes
|
||||
* back as a locale→value map — that's what hreflang alternates are built from.
|
||||
*/ export function createMetadataGenerator(args) {
|
||||
const { baseUrl, config, homeSlug, resolveDocument, resolveImageUrl, resolveSiteName } = args;
|
||||
return async function generateMetadata(context) {
|
||||
const { locale, params, payload } = context;
|
||||
const doc = await resolveDocument({
|
||||
locale,
|
||||
params,
|
||||
payload
|
||||
});
|
||||
const slugs = doc?.slug ? getLocalizedSlugs({
|
||||
config,
|
||||
slugField: doc.slug
|
||||
}) : {};
|
||||
const [siteName, imageUrl] = await Promise.all([
|
||||
resolveSiteName?.({
|
||||
locale,
|
||||
payload
|
||||
}) ?? Promise.resolve(null),
|
||||
doc && resolveImageUrl ? resolveImageUrl({
|
||||
doc,
|
||||
locale,
|
||||
payload
|
||||
}) : Promise.resolve(null)
|
||||
]);
|
||||
return buildMetadata({
|
||||
baseUrl,
|
||||
config,
|
||||
homeSlug,
|
||||
imageUrl,
|
||||
locale,
|
||||
meta: doc?.meta,
|
||||
siteName,
|
||||
slugs
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=createMetadataGenerator.js.map
|
||||
@@ -0,0 +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<string, unknown>\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<string, string | string[]>\n payload: BasePayload\n }) => Promise<MetadataDocument | null>\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<null | string>\n /** Resolves the site name (e.g. from SiteSettings). */\n resolveSiteName?: (args: { locale: string; payload: BasePayload }) => Promise<null | string>\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<string, string | string[]>\n payload: BasePayload\n }): Promise<PageMetadata> {\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"}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=createPageMetadata.d.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["createPageMetadata.d.ts"],"sourcesContent":["import type { BasePayload } from 'payload';\nimport type { ContentOption } from '../content/index.js';\nimport type { I18nConfig } from '../i18n/index.js';\nimport type { PageMetadata } from './buildMetadata.js';\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};\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/**\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 declare function createPageMetadata(args: CreatePageMetadataArgs): ({ slug, locale, page, payload, }: PageMetadataContext) => Promise<PageMetadata>;\nexport {};\n"],"names":[],"mappings":"AA0DA,WAAU"}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { ContentOption } from '../content/index.js';
|
||||
import type { I18nConfig } from '../i18n/index.js';
|
||||
import type { PageMetadata } from './buildMetadata.js';
|
||||
type CreatePageMetadataArgs = {
|
||||
/** Absolute site origin, e.g. 'https://example.com'. */
|
||||
baseUrl?: string;
|
||||
/** Collection holding pages. Defaults to 'pages'. */
|
||||
collection?: string;
|
||||
config: I18nConfig;
|
||||
/**
|
||||
* Archive-backed collections, same value as the plugin option. Pass it and
|
||||
* entry URLs (/pl/artykuly/moj-post) get correct canonical and hreflang;
|
||||
* omit it and only pages are handled.
|
||||
*/
|
||||
content?: ContentOption;
|
||||
/** SiteSettings global slug. Defaults to 'site-settings'. */
|
||||
settingsSlug?: string;
|
||||
/** Field on SiteSettings holding the site name. Defaults to 'siteName'. */
|
||||
siteNameField?: string;
|
||||
};
|
||||
type PageMetadataContext = {
|
||||
locale: string;
|
||||
/**
|
||||
* Page number from ?page= on an archive listing. Pass it and page 2 gets a
|
||||
* canonical to itself; leave it out and every page claims to be page 1.
|
||||
*/
|
||||
page?: number;
|
||||
payload: BasePayload;
|
||||
/** Route slug segments; empty/undefined means the locale root. */
|
||||
slug?: string[];
|
||||
};
|
||||
/**
|
||||
* Metadata for the page route, with every resolver already wired.
|
||||
*
|
||||
* `createMetadataGenerator` asks the client for resolvers because it can't know
|
||||
* their collections. But for the plugin's own conventions it does know: pages
|
||||
* live in one collection, the site name sits on SiteSettings, the OG image sits
|
||||
* on the plugin-seo `meta` group, the home page is whatever System Pages points
|
||||
* at, and — with `content` — entries live under their collection's archive page.
|
||||
* Re-declaring all that in every project is copy-paste, so this resolves it.
|
||||
*
|
||||
* Reach for `createMetadataGenerator` instead when a route doesn't follow those
|
||||
* conventions — custom image logic, a different global, hand-rolled paths.
|
||||
*
|
||||
* The plugin never calls getPayload, and Next calls generateMetadata without a
|
||||
* payload, so the client keeps a small wrapper:
|
||||
*
|
||||
* ```ts
|
||||
* const pageMetadata = createPageMetadata({ config: i18nConfig, baseUrl, content })
|
||||
*
|
||||
* export async function generateMetadata({ params }) {
|
||||
* const { locale, slug } = await params
|
||||
* return pageMetadata({ payload: await getPayload({ config }), locale, slug })
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function createPageMetadata(args: CreatePageMetadataArgs): ({ slug, locale, page, payload, }: PageMetadataContext) => Promise<PageMetadata>;
|
||||
export {};
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { resolveRoute } from '../content/index.js';
|
||||
import { buildMetadata } from './buildMetadata.js';
|
||||
import { readSiteMetaConfig } from './readSiteMetaConfig.js';
|
||||
import { slugsAcrossLocales } from './slugsAcrossLocales.js';
|
||||
/** plugin-seo stores the OG image as an upload relationship. */ function resolveOgImage(doc) {
|
||||
const image = doc.meta?.image;
|
||||
if (image && typeof image === 'object' && 'url' in image) {
|
||||
return image.url ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Metadata for the page route, with every resolver already wired.
|
||||
*
|
||||
* `createMetadataGenerator` asks the client for resolvers because it can't know
|
||||
* their collections. But for the plugin's own conventions it does know: pages
|
||||
* live in one collection, the site name sits on SiteSettings, the OG image sits
|
||||
* on the plugin-seo `meta` group, the home page is whatever System Pages points
|
||||
* at, and — with `content` — entries live under their collection's archive page.
|
||||
* Re-declaring all that in every project is copy-paste, so this resolves it.
|
||||
*
|
||||
* Reach for `createMetadataGenerator` instead when a route doesn't follow those
|
||||
* conventions — custom image logic, a different global, hand-rolled paths.
|
||||
*
|
||||
* The plugin never calls getPayload, and Next calls generateMetadata without a
|
||||
* payload, so the client keeps a small wrapper:
|
||||
*
|
||||
* ```ts
|
||||
* const pageMetadata = createPageMetadata({ config: i18nConfig, baseUrl, content })
|
||||
*
|
||||
* export async function generateMetadata({ params }) {
|
||||
* const { locale, slug } = await params
|
||||
* return pageMetadata({ payload: await getPayload({ config }), locale, slug })
|
||||
* }
|
||||
* ```
|
||||
*/ export function createPageMetadata(args) {
|
||||
const { baseUrl, collection = 'pages', config, content, settingsSlug = 'site-settings', siteNameField = 'siteName' } = args;
|
||||
return async function pageMetadata({ slug, locale, page, payload }) {
|
||||
const site = await readSiteMetaConfig({
|
||||
locale,
|
||||
payload,
|
||||
settingsSlug,
|
||||
siteNameField
|
||||
});
|
||||
const base = {
|
||||
baseUrl,
|
||||
config,
|
||||
homeSlug: site.homeSlug,
|
||||
locale,
|
||||
order: site.order,
|
||||
separator: site.separator,
|
||||
siteName: site.siteName
|
||||
};
|
||||
const route = await resolveRoute({
|
||||
content,
|
||||
locale,
|
||||
page,
|
||||
pagesSlug: collection,
|
||||
payload,
|
||||
segments: slug,
|
||||
settingsSlug
|
||||
});
|
||||
// Unknown route (the page component will 404) — still return something
|
||||
// coherent rather than throwing during metadata generation.
|
||||
if (!route) {
|
||||
return buildMetadata({
|
||||
...base,
|
||||
meta: null,
|
||||
slugs: {}
|
||||
});
|
||||
}
|
||||
const doc = route.doc;
|
||||
// An entry sits under its archive, so its URLs need that segment — and the
|
||||
// segment differs per locale, since it's the archive page's own slug.
|
||||
const prefix = route.type === 'entry' ? await slugsAcrossLocales({
|
||||
id: route.archive.id,
|
||||
collection,
|
||||
config,
|
||||
payload
|
||||
}) : undefined;
|
||||
const slugs = await slugsAcrossLocales({
|
||||
id: doc.id,
|
||||
collection: route.type === 'entry' ? route.collection : collection,
|
||||
config,
|
||||
payload
|
||||
});
|
||||
// Page 2 of a listing is its own URL, not a variant of page 1.
|
||||
const query = route.type === 'archive' && route.page > 1 ? `?page=${route.page}` : undefined;
|
||||
return buildMetadata({
|
||||
...base,
|
||||
imageUrl: resolveOgImage(doc),
|
||||
meta: doc.meta,
|
||||
prefix,
|
||||
query,
|
||||
slugs
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=createPageMetadata.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=hreflang.d.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["hreflang.d.ts"],"sourcesContent":["import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js';\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 * 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 declare function buildHreflangAlternates({ baseUrl, config, homeSlug, prefix, slugs, }: BuildHreflangArgs): Record<string, string>;\nexport {};\n"],"names":[],"mappings":"AAiCA,WAAU"}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js';
|
||||
type BuildHreflangArgs = {
|
||||
/** Absolute site origin, e.g. 'https://example.com'. Omit for relative paths. */
|
||||
baseUrl?: string;
|
||||
config: I18nConfig;
|
||||
/** Home slug that collapses to the locale root. Defaults to 'home'. */
|
||||
homeSlug?: string;
|
||||
/**
|
||||
* Localized segment the document lives under (an archive page's slugs),
|
||||
* e.g. { pl: 'artykuly', en: 'articles' }. Locales missing from the prefix
|
||||
* are omitted — an entry with no archive in that language has no URL there.
|
||||
*/
|
||||
prefix?: LocalizedSlugs;
|
||||
/** slug per locale for the current document, e.g. { pl: 'o-nas', en: 'about' } */
|
||||
slugs: LocalizedSlugs;
|
||||
};
|
||||
/**
|
||||
* Builds a map of locale → URL for hreflang alternate links, suitable for
|
||||
* Next.js Metadata `alternates.languages`.
|
||||
*
|
||||
* Bridges SEO and i18n: for each configured locale that the document has a
|
||||
* slug in, it produces the locale-aware path (via buildLocalizedPath),
|
||||
* optionally prefixed with an absolute origin.
|
||||
*
|
||||
* @example
|
||||
* buildHreflangAlternates({
|
||||
* slugs: { pl: 'o-nas', en: 'about' },
|
||||
* config,
|
||||
* baseUrl: 'https://example.com',
|
||||
* })
|
||||
* // → { pl: 'https://example.com/pl/o-nas', en: 'https://example.com/en/about' }
|
||||
*/
|
||||
export declare function buildHreflangAlternates({ baseUrl, config, homeSlug, prefix, slugs, }: BuildHreflangArgs): Record<string, string>;
|
||||
export {};
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
import { buildLocalizedPath, getLocaleCodes } from '../i18n/index.js';
|
||||
/**
|
||||
* Builds a map of locale → URL for hreflang alternate links, suitable for
|
||||
* Next.js Metadata `alternates.languages`.
|
||||
*
|
||||
* Bridges SEO and i18n: for each configured locale that the document has a
|
||||
* slug in, it produces the locale-aware path (via buildLocalizedPath),
|
||||
* optionally prefixed with an absolute origin.
|
||||
*
|
||||
* @example
|
||||
* buildHreflangAlternates({
|
||||
* slugs: { pl: 'o-nas', en: 'about' },
|
||||
* config,
|
||||
* baseUrl: 'https://example.com',
|
||||
* })
|
||||
* // → { pl: 'https://example.com/pl/o-nas', en: 'https://example.com/en/about' }
|
||||
*/ export function buildHreflangAlternates({ baseUrl, config, homeSlug = 'home', prefix, slugs }) {
|
||||
const origin = baseUrl?.replace(/\/$/, '') ?? '';
|
||||
const alternates = {};
|
||||
for (const locale of getLocaleCodes(config)){
|
||||
const path = buildLocalizedPath({
|
||||
config,
|
||||
homeSlug,
|
||||
locale,
|
||||
prefix,
|
||||
slugs
|
||||
});
|
||||
if (path) {
|
||||
alternates[locale] = `${origin}${path}`;
|
||||
}
|
||||
}
|
||||
return alternates;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=hreflang.js.map
|
||||
Vendored
+1
@@ -0,0 +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<string, string> {\n const origin = baseUrl?.replace(/\\/$/, '') ?? ''\n const alternates: Record<string, string> = {}\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"}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
export { buildAutoFillMetaHook } from './autoFillMeta.js';
|
||||
export { buildMetadata } from './buildMetadata.js';
|
||||
export { buildRobots } from './buildRobots.js';
|
||||
export { buildSitemapEntries } from './buildSitemapEntries.js';
|
||||
export { composeTitle } from './composeTitle.js';
|
||||
export { createMetadataGenerator } from './createMetadataGenerator.js';
|
||||
export { createPageMetadata } from './createPageMetadata.js';
|
||||
export { buildHreflangAlternates } from './hreflang.js';
|
||||
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';
|
||||
|
||||
//# sourceMappingURL=index.d.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["index.d.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,oBAAoB;AAE1D,SAASC,aAAa,QAAQ,qBAAqB;AAEnD,SAASC,WAAW,QAAQ,mBAAmB;AAE/C,SAASC,mBAAmB,QAAQ,2BAA2B;AAE/D,SAASC,YAAY,QAAQ,oBAAoB;AAEjD,SAASC,uBAAuB,QAAQ,+BAA+B;AACvE,SAASC,kBAAkB,QAAQ,0BAA0B;AAC7D,SAASC,uBAAuB,QAAQ,gBAAgB;AACxD,SAASC,kBAAkB,QAAQ,0BAA0B;AAC7D,SAASC,aAAa,QAAQ,qBAAqB;AACnD,SAASC,kBAAkB,QAAQ,0BAA0B;AAE7D,SAASC,cAAc,QAAQ,uBAAuB;AACtD,SAASC,kBAAkB,QAAQ,0BAA0B"}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
export { buildAutoFillMetaHook } from './autoFillMeta.js';
|
||||
export type { AutoFillMapping } from './autoFillMeta.js';
|
||||
export { buildMetadata } from './buildMetadata.js';
|
||||
export type { PageMetadata } from './buildMetadata.js';
|
||||
export { buildRobots } from './buildRobots.js';
|
||||
export type { RobotsRules } from './buildRobots.js';
|
||||
export { buildSitemapEntries } from './buildSitemapEntries.js';
|
||||
export type { SitemapEntry } from './buildSitemapEntries.js';
|
||||
export { composeTitle } from './composeTitle.js';
|
||||
export type { TitleOrder } from './composeTitle.js';
|
||||
export { createMetadataGenerator } from './createMetadataGenerator.js';
|
||||
export { createPageMetadata } from './createPageMetadata.js';
|
||||
export { buildHreflangAlternates } from './hreflang.js';
|
||||
export { injectAutoFillMeta } from './injectAutoFillMeta.js';
|
||||
export { injectSeoTabs } from './injectSeoTabs.js';
|
||||
export { readSiteMetaConfig } from './readSiteMetaConfig.js';
|
||||
export type { SiteMetaConfig } from './readSiteMetaConfig.js';
|
||||
export { buildSeoPlugin } from './seoPluginConfig.js';
|
||||
export { slugsAcrossLocales } from './slugsAcrossLocales.js';
|
||||
export type { SeoMeta, SeoOption } from './types.js';
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
export { buildAutoFillMetaHook } from './autoFillMeta.js';
|
||||
export { buildMetadata } from './buildMetadata.js';
|
||||
export { buildRobots } from './buildRobots.js';
|
||||
export { buildSitemapEntries } from './buildSitemapEntries.js';
|
||||
export { composeTitle } from './composeTitle.js';
|
||||
export { createMetadataGenerator } from './createMetadataGenerator.js';
|
||||
export { createPageMetadata } from './createPageMetadata.js';
|
||||
export { buildHreflangAlternates } from './hreflang.js';
|
||||
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';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
+1
@@ -0,0 +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"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Injects the auto-fill meta hook into every SEO-enabled collection.
|
||||
*
|
||||
* Runs after @payloadcms/plugin-seo has added the meta group, appending a
|
||||
* beforeChange hook that fills empty meta from document content. Existing
|
||||
* hooks are preserved (plugin hook runs last, so editor input and other hooks
|
||||
* win first).
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=injectAutoFillMeta.d.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["injectAutoFillMeta.d.ts"],"sourcesContent":["import type { Config } from 'payload';\nimport type { SeoOption } from './types.js';\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 declare function injectAutoFillMeta(config: Config, seo: SeoOption): Config;\n"],"names":[],"mappings":"AAEA;;;;;;;CAOC,GACD,WAAmF"}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import type { Config } from 'payload';
|
||||
import type { SeoOption } from './types.js';
|
||||
/**
|
||||
* Injects the auto-fill meta hook into every SEO-enabled collection.
|
||||
*
|
||||
* Runs after @payloadcms/plugin-seo has added the meta group, appending a
|
||||
* beforeChange hook that fills empty meta from document content. Existing
|
||||
* hooks are preserved (plugin hook runs last, so editor input and other hooks
|
||||
* win first).
|
||||
*/
|
||||
export declare function injectAutoFillMeta(config: Config, seo: SeoOption): Config;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { buildAutoFillMetaHook } from './autoFillMeta.js';
|
||||
/**
|
||||
* Injects the auto-fill meta hook into every SEO-enabled collection.
|
||||
*
|
||||
* Runs after @payloadcms/plugin-seo has added the meta group, appending a
|
||||
* beforeChange hook that fills empty meta from document content. Existing
|
||||
* hooks are preserved (plugin hook runs last, so editor input and other hooks
|
||||
* win first).
|
||||
*/ export function injectAutoFillMeta(config, seo) {
|
||||
// autoFill disabled explicitly — leave collections untouched
|
||||
if (seo.autoFill === false) {
|
||||
return config;
|
||||
}
|
||||
const hook = buildAutoFillMetaHook(seo.autoFill);
|
||||
return {
|
||||
...config,
|
||||
collections: (config.collections ?? []).map((collection)=>{
|
||||
if (!seo.collections.includes(collection.slug)) {
|
||||
return collection;
|
||||
}
|
||||
return {
|
||||
...collection,
|
||||
hooks: {
|
||||
...collection.hooks,
|
||||
beforeChange: [
|
||||
...collection.hooks?.beforeChange ?? [],
|
||||
hook
|
||||
]
|
||||
}
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=injectAutoFillMeta.js.map
|
||||
+1
@@ -0,0 +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"}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Wraps each SEO-enabled collection's fields into a tabbed UI: a "Content" tab
|
||||
* holding the collection's own fields, and an "SEO" tab holding the `meta`
|
||||
* group that @payloadcms/plugin-seo added.
|
||||
*
|
||||
* This replaces plugin-seo's own `tabbedUI`, which merges tabs by inspecting
|
||||
* the first field and breaks when other plugins/fields (roles, slug) already
|
||||
* sit in the collection — producing an empty SEO tab. Running this AFTER
|
||||
* plugin-seo (so `meta` already exists) and after other field injections lets
|
||||
* us build the tabs deterministically.
|
||||
*
|
||||
* If a collection's first field is already a tabs field, the SEO tab is
|
||||
* appended to it instead of creating a new wrapper.
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=injectSeoTabs.d.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["injectSeoTabs.d.ts"],"sourcesContent":["import type { Config } from 'payload';\nimport type { SeoOption } from './types.js';\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 declare function injectSeoTabs(config: Config, seo: SeoOption): Config;\n"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;CAaC,GACD,WAA8E"}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
import type { Config } from 'payload';
|
||||
import type { SeoOption } from './types.js';
|
||||
/**
|
||||
* Wraps each SEO-enabled collection's fields into a tabbed UI: a "Content" tab
|
||||
* holding the collection's own fields, and an "SEO" tab holding the `meta`
|
||||
* group that @payloadcms/plugin-seo added.
|
||||
*
|
||||
* This replaces plugin-seo's own `tabbedUI`, which merges tabs by inspecting
|
||||
* the first field and breaks when other plugins/fields (roles, slug) already
|
||||
* sit in the collection — producing an empty SEO tab. Running this AFTER
|
||||
* plugin-seo (so `meta` already exists) and after other field injections lets
|
||||
* us build the tabs deterministically.
|
||||
*
|
||||
* If a collection's first field is already a tabs field, the SEO tab is
|
||||
* appended to it instead of creating a new wrapper.
|
||||
*/
|
||||
export declare function injectSeoTabs(config: Config, seo: SeoOption): Config;
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Wraps each SEO-enabled collection's fields into a tabbed UI: a "Content" tab
|
||||
* holding the collection's own fields, and an "SEO" tab holding the `meta`
|
||||
* group that @payloadcms/plugin-seo added.
|
||||
*
|
||||
* This replaces plugin-seo's own `tabbedUI`, which merges tabs by inspecting
|
||||
* the first field and breaks when other plugins/fields (roles, slug) already
|
||||
* sit in the collection — producing an empty SEO tab. Running this AFTER
|
||||
* plugin-seo (so `meta` already exists) and after other field injections lets
|
||||
* us build the tabs deterministically.
|
||||
*
|
||||
* If a collection's first field is already a tabs field, the SEO tab is
|
||||
* appended to it instead of creating a new wrapper.
|
||||
*/ export function injectSeoTabs(config, seo) {
|
||||
return {
|
||||
...config,
|
||||
collections: (config.collections ?? []).map((collection)=>{
|
||||
if (!seo.collections.includes(collection.slug)) {
|
||||
return collection;
|
||||
}
|
||||
const fields = collection.fields ?? [];
|
||||
// Separate the meta group (added by plugin-seo) from the rest.
|
||||
const metaField = fields.find((f)=>'name' in f && f.name === 'meta');
|
||||
const otherFields = fields.filter((f)=>!('name' in f && f.name === 'meta'));
|
||||
// Nothing to do if plugin-seo hasn't added meta (shouldn't happen).
|
||||
if (!metaField) {
|
||||
return collection;
|
||||
}
|
||||
// If the collection already leads with a tabs field, append an SEO tab.
|
||||
const firstField = otherFields[0];
|
||||
if (firstField && firstField.type === 'tabs') {
|
||||
const existingTabs = firstField;
|
||||
const withSeoTab = {
|
||||
...existingTabs,
|
||||
tabs: [
|
||||
...existingTabs.tabs,
|
||||
{
|
||||
fields: [
|
||||
metaField
|
||||
],
|
||||
label: 'SEO'
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
...collection,
|
||||
fields: [
|
||||
withSeoTab,
|
||||
...otherFields.slice(1)
|
||||
]
|
||||
};
|
||||
}
|
||||
// Otherwise wrap everything: Content tab + SEO tab.
|
||||
const tabs = {
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
fields: otherFields,
|
||||
label: collection.labels?.singular?.toString() || 'Content'
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
metaField
|
||||
],
|
||||
label: 'SEO'
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
...collection,
|
||||
fields: [
|
||||
tabs
|
||||
]
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=injectSeoTabs.js.map
|
||||
+1
@@ -0,0 +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"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Reads the site-wide inputs to title and URL composition from SiteSettings.
|
||||
*
|
||||
* Split out of metadata assembly because these are one concern with one source:
|
||||
* whatever an editor set in the panel. Metadata generation shouldn't also know
|
||||
* that the separator arrives unpadded, or that the home slug hides inside a
|
||||
* relationship — it should receive a resolved config.
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=readSiteMetaConfig.d.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["readSiteMetaConfig.d.ts"],"sourcesContent":["import type { BasePayload } from 'payload';\nimport type { TitleOrder } from './composeTitle.js';\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 * 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 declare function readSiteMetaConfig({ locale, payload, settingsSlug, siteNameField, }: {\n locale: string;\n payload: BasePayload;\n settingsSlug?: string;\n siteNameField?: string;\n}): Promise<SiteMetaConfig>;\n"],"names":[],"mappings":"AAUA;;;;;;;CAOC,GACD,WAK4B"}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { TitleOrder } from './composeTitle.js';
|
||||
export type SiteMetaConfig = {
|
||||
/** Slug of the page assigned as Homepage; collapses to the locale root. */
|
||||
homeSlug?: string;
|
||||
order?: TitleOrder;
|
||||
/** Padded separator, e.g. ' | ' — the panel stores the bare character. */
|
||||
separator?: string;
|
||||
siteName: null | string;
|
||||
};
|
||||
/**
|
||||
* Reads the site-wide inputs to title and URL composition from SiteSettings.
|
||||
*
|
||||
* Split out of metadata assembly because these are one concern with one source:
|
||||
* whatever an editor set in the panel. Metadata generation shouldn't also know
|
||||
* that the separator arrives unpadded, or that the home slug hides inside a
|
||||
* relationship — it should receive a resolved config.
|
||||
*/
|
||||
export declare function readSiteMetaConfig({ locale, payload, settingsSlug, siteNameField, }: {
|
||||
locale: string;
|
||||
payload: BasePayload;
|
||||
settingsSlug?: string;
|
||||
siteNameField?: string;
|
||||
}): Promise<SiteMetaConfig>;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Reads the site-wide inputs to title and URL composition from SiteSettings.
|
||||
*
|
||||
* Split out of metadata assembly because these are one concern with one source:
|
||||
* whatever an editor set in the panel. Metadata generation shouldn't also know
|
||||
* that the separator arrives unpadded, or that the home slug hides inside a
|
||||
* relationship — it should receive a resolved config.
|
||||
*/ export async function readSiteMetaConfig({ locale, payload, settingsSlug = 'site-settings', siteNameField = 'siteName' }) {
|
||||
// depth 1 populates the homepage relationship, so its slug is available
|
||||
// without a second query.
|
||||
const settings = await payload.findGlobal({
|
||||
slug: settingsSlug,
|
||||
depth: 1,
|
||||
locale: locale
|
||||
});
|
||||
const homepage = settings.homepage && typeof settings.homepage === 'object' ? settings.homepage : null;
|
||||
return {
|
||||
siteName: settings[siteNameField] ?? null,
|
||||
// The panel stores '|'; titles need it padded.
|
||||
homeSlug: typeof homepage?.slug === 'string' ? homepage.slug : undefined,
|
||||
order: settings.titleOrder ?? undefined,
|
||||
separator: settings.titleSeparator ? ` ${settings.titleSeparator} ` : undefined
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=readSiteMetaConfig.js.map
|
||||
+1
@@ -0,0 +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<SiteMetaConfig> {\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"}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=seoPluginConfig.d.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["seoPluginConfig.d.ts"],"sourcesContent":["import type { Plugin } from 'payload';\nimport type { SeoOption } from './types.js';\ntype BuildSeoPluginArgs = {\n seo: SeoOption;\n /** Upload collection slug for the meta image (typically 'media'). */\n uploadsCollection?: string;\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 declare function buildSeoPlugin({ seo, uploadsCollection }: BuildSeoPluginArgs): Plugin;\nexport {};\n"],"names":[],"mappings":"AAiBA,WAAU"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import type { Plugin } from 'payload';
|
||||
import type { SeoOption } from './types.js';
|
||||
type BuildSeoPluginArgs = {
|
||||
seo: SeoOption;
|
||||
/** Upload collection slug for the meta image (typically 'media'). */
|
||||
uploadsCollection?: string;
|
||||
};
|
||||
/**
|
||||
* Configures @payloadcms/plugin-seo from IPAL's SeoOption.
|
||||
*
|
||||
* Adds the `meta` field group (title, description, image) to the client's
|
||||
* chosen collections. tabbedUI is intentionally NOT used — it merges tabs by
|
||||
* inspecting the first field, which breaks when other plugins/fields (roles,
|
||||
* slug) already sit in the collection, leaving an empty SEO tab. Instead the
|
||||
* SEO UI fields are injected into a controlled tab by injectSeoTabs.
|
||||
*/
|
||||
export declare function buildSeoPlugin({ seo, uploadsCollection }: BuildSeoPluginArgs): Plugin;
|
||||
export {};
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
import { seoPlugin } from '@payloadcms/plugin-seo';
|
||||
/**
|
||||
* Escape hatch from automatic title composition: whatever is typed here becomes
|
||||
* the entire title — no site name, no separator. Useful on a home page, where
|
||||
* composing would produce "Intecion Software | Intecion Software".
|
||||
*/ const titleOverrideField = {
|
||||
name: 'titleOverride',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: 'Use this exact text as the browser-tab title — no site name, no separator. Leave empty to compose the title automatically.'
|
||||
},
|
||||
localized: true
|
||||
};
|
||||
/**
|
||||
* Configures @payloadcms/plugin-seo from IPAL's SeoOption.
|
||||
*
|
||||
* Adds the `meta` field group (title, description, image) to the client's
|
||||
* chosen collections. tabbedUI is intentionally NOT used — it merges tabs by
|
||||
* inspecting the first field, which breaks when other plugins/fields (roles,
|
||||
* slug) already sit in the collection, leaving an empty SEO tab. Instead the
|
||||
* SEO UI fields are injected into a controlled tab by injectSeoTabs.
|
||||
*/ export function buildSeoPlugin({ seo, uploadsCollection = 'media' }) {
|
||||
return seoPlugin({
|
||||
collections: seo.collections,
|
||||
fields: ({ defaultFields })=>[
|
||||
...defaultFields,
|
||||
titleOverrideField,
|
||||
...seo.fields ?? []
|
||||
],
|
||||
uploadsCollection,
|
||||
...seo.generateTitle && {
|
||||
generateTitle: seo.generateTitle
|
||||
},
|
||||
...seo.generateDescription && {
|
||||
generateDescription: seo.generateDescription
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=seoPluginConfig.js.map
|
||||
+1
@@ -0,0 +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"}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Reads a document's slug in every locale — the map hreflang alternates are
|
||||
* built from.
|
||||
*
|
||||
* Needs its own query because a document fetched in one locale returns `slug`
|
||||
* as a plain string. Fetching with `locale: 'all'` turns *every* localized
|
||||
* field into a locale→value map, which is right for the slug and wrong for
|
||||
* everything else (a title map would break title composition), so this asks
|
||||
* only for what it needs and at depth 0.
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=slugsAcrossLocales.d.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["slugsAcrossLocales.d.ts"],"sourcesContent":["import type { BasePayload } from 'payload';\nimport type { I18nConfig, LocalizedSlugs } from '../i18n/index.js';\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 declare function slugsAcrossLocales({ id, collection, config, payload, }: {\n collection: string;\n config: I18nConfig;\n id: number | string;\n payload: BasePayload;\n}): Promise<LocalizedSlugs>;\n"],"names":[],"mappings":"AAEA;;;;;;;;;CASC,GACD,WAK4B"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js';
|
||||
/**
|
||||
* Reads a document's slug in every locale — the map hreflang alternates are
|
||||
* built from.
|
||||
*
|
||||
* Needs its own query because a document fetched in one locale returns `slug`
|
||||
* as a plain string. Fetching with `locale: 'all'` turns *every* localized
|
||||
* field into a locale→value map, which is right for the slug and wrong for
|
||||
* everything else (a title map would break title composition), so this asks
|
||||
* only for what it needs and at depth 0.
|
||||
*/
|
||||
export declare function slugsAcrossLocales({ id, collection, config, payload, }: {
|
||||
collection: string;
|
||||
config: I18nConfig;
|
||||
id: number | string;
|
||||
payload: BasePayload;
|
||||
}): Promise<LocalizedSlugs>;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { getLocalizedSlugs } from '../i18n/index.js';
|
||||
/**
|
||||
* Reads a document's slug in every locale — the map hreflang alternates are
|
||||
* built from.
|
||||
*
|
||||
* Needs its own query because a document fetched in one locale returns `slug`
|
||||
* as a plain string. Fetching with `locale: 'all'` turns *every* localized
|
||||
* field into a locale→value map, which is right for the slug and wrong for
|
||||
* everything else (a title map would break title composition), so this asks
|
||||
* only for what it needs and at depth 0.
|
||||
*/ export async function slugsAcrossLocales({ id, collection, config, payload }) {
|
||||
const doc = await payload.findByID({
|
||||
id,
|
||||
collection: collection,
|
||||
depth: 0,
|
||||
locale: 'all'
|
||||
});
|
||||
return doc.slug && typeof doc.slug === 'object' ? getLocalizedSlugs({
|
||||
config,
|
||||
slugField: doc.slug
|
||||
}) : {};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=slugsAcrossLocales.js.map
|
||||
+1
@@ -0,0 +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<LocalizedSlugs> {\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<string, unknown> })\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"}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 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 { };
|
||||
|
||||
//# sourceMappingURL=types.d.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["types.d.ts"],"sourcesContent":["import type { Field } from 'payload';\nimport type { AutoFillMapping } from './autoFillMeta.js';\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: {\n doc: Record<string, unknown>;\n }) => string;\n /** Optional: customize how meta titles are generated in the admin preview. */\n generateTitle?: (args: {\n doc: Record<string, unknown>;\n }) => string;\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":"AA4BA;;;;CAIC,GACD,WAME"}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
import type { Field } from 'payload';
|
||||
import type { AutoFillMapping } from './autoFillMeta.js';
|
||||
/**
|
||||
* SEO configuration.
|
||||
*
|
||||
* The plugin wires @payloadcms/plugin-seo into the client's config and adds
|
||||
* locale-aware metadata helpers on top. Collections come from options because
|
||||
* the plugin doesn't own the client's content collections (e.g. Pages).
|
||||
*/
|
||||
export type SeoOption = {
|
||||
/**
|
||||
* Auto-fill empty meta from document fields on save. Defaults to the website
|
||||
* template mapping (title → meta.title). Set to false to disable.
|
||||
*/
|
||||
autoFill?: AutoFillMapping | false;
|
||||
/** Collection slugs that receive SEO meta fields, e.g. ['pages', 'posts']. */
|
||||
collections: string[];
|
||||
/** Extra fields appended to the SEO group in the admin. */
|
||||
fields?: Field[];
|
||||
/** Optional: customize how meta descriptions are generated. */
|
||||
generateDescription?: (args: {
|
||||
doc: Record<string, unknown>;
|
||||
}) => string;
|
||||
/** Optional: customize how meta titles are generated in the admin preview. */
|
||||
generateTitle?: (args: {
|
||||
doc: Record<string, unknown>;
|
||||
}) => string;
|
||||
};
|
||||
/**
|
||||
* 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 type SeoMeta = {
|
||||
description?: null | string;
|
||||
image?: unknown;
|
||||
title?: null | string;
|
||||
/** When set, used as the whole title — no site name, no separator. */
|
||||
titleOverride?: null | string;
|
||||
};
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 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 { };
|
||||
|
||||
//# sourceMappingURL=types.js.map
|
||||
Vendored
+1
@@ -0,0 +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, unknown> }) => string\n /** Optional: customize how meta titles are generated in the admin preview. */\n generateTitle?: (args: { doc: Record<string, unknown> }) => 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"}
|
||||
Reference in New Issue
Block a user