@justanarthur/payload-www 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/collections.d.ts +31 -2
- package/dist/collections.js +30 -11
- package/dist/config.d.ts +8 -0
- package/dist/config.js +37 -15
- package/dist/core-fields.d.ts +8 -0
- package/dist/core-fields.js +5 -1
- package/dist/data-collections.d.ts +31 -2
- package/dist/data-collections.js +30 -11
- package/dist/data-test.js +37 -15
- package/dist/fields.d.ts +8 -0
- package/dist/fields.js +5 -1
- package/dist/globals.d.ts +23 -2
- package/dist/globals.js +21 -7
- package/dist/pages.d.ts +14 -1
- package/dist/pages.js +8 -8
- package/dist/render-pages.d.ts +14 -1
- package/dist/render-pages.js +8 -8
- package/dist/render-utils.d.ts +8 -0
- package/dist/render-utils.js +4 -2
- package/dist/server.d.ts +65 -7
- package/dist/server.js +45 -23
- package/dist/test.js +37 -15
- package/dist/with-www-config.d.ts +8 -0
- package/dist/with-www-config.js +37 -15
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -156,6 +156,19 @@ type CreateCollectionPageExportsArgs<S extends string = "pages"> = {
|
|
|
156
156
|
* defined your own Server Component for the collection.
|
|
157
157
|
*/
|
|
158
158
|
renderPath?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Enable nested (hierarchical) slugs. When `true`, the catch-all
|
|
161
|
+
* `[...slug]` segments are joined with the `_` divider to form the
|
|
162
|
+
* stored slug (URL `/about/us` ⇄ stored `about_us`), URL paths and
|
|
163
|
+
* hreflang alternates are expanded back to `/about/us`, and
|
|
164
|
+
* `generateStaticParams` emits multi-segment params. When `false`
|
|
165
|
+
* (the default) only the first URL segment is used — flat slugs.
|
|
166
|
+
*
|
|
167
|
+
* Pair with `createPagesCollection({ nested: true })` (or
|
|
168
|
+
* `createWWWConfig({ nested: true })`) so the collection's slug
|
|
169
|
+
* validation accepts the `_` divider.
|
|
170
|
+
*/
|
|
171
|
+
nested?: boolean;
|
|
159
172
|
};
|
|
160
173
|
type CreateCollectionPageExportsDeps<S extends string> = {
|
|
161
174
|
/**
|
|
@@ -234,7 +247,7 @@ type CreateCollectionPageExportsDeps<S extends string> = {
|
|
|
234
247
|
* { generateMetadata, generateStaticParams }
|
|
235
248
|
* Page
|
|
236
249
|
*/
|
|
237
|
-
declare function createCollectionPageExports<S extends string = "pages">({ slug, config: configPromise, routing, importMap: importMapArg, renderPath }: CreateCollectionPageExportsArgs<S>, deps: CreateCollectionPageExportsDeps<S>, options?: MetadataOptions): {
|
|
250
|
+
declare function createCollectionPageExports<S extends string = "pages">({ slug, config: configPromise, routing, importMap: importMapArg, renderPath, nested }: CreateCollectionPageExportsArgs<S>, deps: CreateCollectionPageExportsDeps<S>, options?: MetadataOptions): {
|
|
238
251
|
default: (props: {
|
|
239
252
|
params: Promise<{
|
|
240
253
|
locale?: string;
|
|
@@ -382,6 +395,14 @@ type CreatePagesCollectionOptions = {
|
|
|
382
395
|
* when omitted.
|
|
383
396
|
*/
|
|
384
397
|
defaultLocale?: CreateRevalidateCollectionHookOptions["defaultLocale"];
|
|
398
|
+
/**
|
|
399
|
+
* Enable nested (hierarchical) slugs. When `true`, the slug field
|
|
400
|
+
* additionally accepts the `_` nesting divider (so `about_us`
|
|
401
|
+
* renders at `/about/us`) and the admin description documents the
|
|
402
|
+
* convention. Pair with `createCollectionPageExports({ nested: true })`
|
|
403
|
+
* on the matching route. Default: `false` (flat, hyphen-only slugs).
|
|
404
|
+
*/
|
|
405
|
+
nested?: boolean;
|
|
385
406
|
};
|
|
386
407
|
declare const createPagesCollection: (blocks: Block[], options?: Omit<CreatePagesCollectionOptions, "blocks">) => CollectionConfig;
|
|
387
408
|
import { CollectionConfig as CollectionConfig2 } from "payload";
|
|
@@ -439,7 +460,7 @@ type CreateStaticPagesCollectionOptions = {
|
|
|
439
460
|
blocks: Block2[];
|
|
440
461
|
};
|
|
441
462
|
declare const createStaticPagesCollection: (blocks: Block2[]) => CollectionConfig3;
|
|
442
|
-
import { GlobalConfig } from "payload";
|
|
463
|
+
import { Field, GlobalConfig } from "payload";
|
|
443
464
|
type CreateHeaderGlobalOptions = {
|
|
444
465
|
/**
|
|
445
466
|
* Optional override for the `custom.path` Payload uses to resolve
|
|
@@ -453,6 +474,17 @@ type CreateHeaderGlobalOptions = {
|
|
|
453
474
|
* different list when your host has additional linkable collections.
|
|
454
475
|
*/
|
|
455
476
|
linkRelationTo?: string[];
|
|
477
|
+
/**
|
|
478
|
+
* Extra fields appended to each `navColumn` link — e.g. a
|
|
479
|
+
* `description` text field or a `navHover` group for a mega-menu.
|
|
480
|
+
* Lets hosts enrich the column-link schema without redefining the
|
|
481
|
+
* whole nav.
|
|
482
|
+
*/
|
|
483
|
+
navColumnLinkFields?: Field[];
|
|
484
|
+
/**
|
|
485
|
+
* Extra fields appended to each top-level `navItem` link.
|
|
486
|
+
*/
|
|
487
|
+
navItemLinkFields?: Field[];
|
|
456
488
|
};
|
|
457
489
|
/**
|
|
458
490
|
* Build the Header global. The lib's default shape: a `nav` blocks
|
|
@@ -460,7 +492,7 @@ type CreateHeaderGlobalOptions = {
|
|
|
460
492
|
* and `navItem` (a single link).
|
|
461
493
|
*/
|
|
462
494
|
declare const createHeaderGlobal: (options?: CreateHeaderGlobalOptions) => GlobalConfig;
|
|
463
|
-
import { GlobalConfig as GlobalConfig2 } from "payload";
|
|
495
|
+
import { Field as Field2, GlobalConfig as GlobalConfig2 } from "payload";
|
|
464
496
|
type CreateFooterGlobalOptions = {
|
|
465
497
|
/**
|
|
466
498
|
* Optional override for the `custom.path` Payload uses to resolve
|
|
@@ -474,6 +506,16 @@ type CreateFooterGlobalOptions = {
|
|
|
474
506
|
* different list when your host has additional linkable collections.
|
|
475
507
|
*/
|
|
476
508
|
linkRelationTo?: string[];
|
|
509
|
+
/**
|
|
510
|
+
* Extra fields appended to each `navColumn` link — e.g. a
|
|
511
|
+
* `description` text field. Lets hosts enrich the column-link
|
|
512
|
+
* schema without redefining the whole nav.
|
|
513
|
+
*/
|
|
514
|
+
navColumnLinkFields?: Field2[];
|
|
515
|
+
/**
|
|
516
|
+
* Extra fields appended to each top-level `navItem` link.
|
|
517
|
+
*/
|
|
518
|
+
navItemLinkFields?: Field2[];
|
|
477
519
|
};
|
|
478
520
|
/**
|
|
479
521
|
* Build the Footer global. The lib's default shape mirrors the
|
|
@@ -517,6 +559,14 @@ type WWWConfigOptions = {
|
|
|
517
559
|
*/
|
|
518
560
|
registerPosts?: boolean;
|
|
519
561
|
/**
|
|
562
|
+
* Enable nested (hierarchical) Pages slugs. When `true`, the lib's
|
|
563
|
+
* Pages collection accepts the `_` nesting divider in its slug
|
|
564
|
+
* (`about_us` → `/about/us`). Pass the same `nested: true` to
|
|
565
|
+
* `createCollectionPageExports` on the Pages route so URL building,
|
|
566
|
+
* hreflang, and static params expand the divider. Default: `false`.
|
|
567
|
+
*/
|
|
568
|
+
nested?: boolean;
|
|
569
|
+
/**
|
|
520
570
|
* Final say on the lib's default plugin list. Receives the
|
|
521
571
|
* `[seoPlugin, imageHashPlugin, translator]` array; return the
|
|
522
572
|
* list to apply. Common uses: drop the translator (no AI
|
|
@@ -575,7 +625,7 @@ import { GlobalAfterChangeHook } from "payload";
|
|
|
575
625
|
* uses.
|
|
576
626
|
*/
|
|
577
627
|
declare function createRevalidateGlobalHook(slug: string): GlobalAfterChangeHook;
|
|
578
|
-
import { Field, GroupField } from "payload";
|
|
628
|
+
import { Field as Field3, GroupField } from "payload";
|
|
579
629
|
type LinkAppearances = "default" | "outline";
|
|
580
630
|
declare const appearanceOptions: Record<LinkAppearances, {
|
|
581
631
|
label: string;
|
|
@@ -597,10 +647,18 @@ type LinkOptions = {
|
|
|
597
647
|
overrides?: Partial<GroupField>;
|
|
598
648
|
/** Legacy alias for the host's custom field overrides. */
|
|
599
649
|
linkOverrides?: Partial<GroupField>;
|
|
650
|
+
/**
|
|
651
|
+
* Extra fields appended to the link group — e.g. a `description`
|
|
652
|
+
* text field or a `navHover` group for a mega-menu. Lets hosts
|
|
653
|
+
* extend the nav link schema without re-implementing the whole
|
|
654
|
+
* `link` field. Appended after the standard type/label/appearance
|
|
655
|
+
* fields.
|
|
656
|
+
*/
|
|
657
|
+
extraFields?: Field3[];
|
|
600
658
|
};
|
|
601
|
-
declare const link: (options?: LinkOptions) =>
|
|
602
|
-
import { Field as
|
|
603
|
-
declare const linkGroup: (options?: LinkOptions) =>
|
|
659
|
+
declare const link: (options?: LinkOptions) => Field3;
|
|
660
|
+
import { Field as Field4 } from "payload";
|
|
661
|
+
declare const linkGroup: (options?: LinkOptions) => Field4;
|
|
604
662
|
import { ImportMap as ImportMap2 } from "payload";
|
|
605
663
|
declare function getFromImportMap(key: string, importMap: ImportMap2): any;
|
|
606
664
|
declare function generateImportName(type: "block" | "page", slug: string): string;
|
package/dist/server.js
CHANGED
|
@@ -364,20 +364,25 @@ var createPagesCollection = (blocks, options = {}) => {
|
|
|
364
364
|
renderPath = PAGES_RENDER_PATH,
|
|
365
365
|
slug: collectionSlug = PAGES_SLUG2,
|
|
366
366
|
localePrefix,
|
|
367
|
-
defaultLocale
|
|
367
|
+
defaultLocale,
|
|
368
|
+
nested = false
|
|
368
369
|
} = options;
|
|
370
|
+
const slugPattern = nested ? /^[a-z0-9_-]+$/ : /^[a-z0-9-]+$/;
|
|
369
371
|
const slugField = {
|
|
370
372
|
name: "slug",
|
|
371
373
|
type: "text",
|
|
372
374
|
required: true,
|
|
373
375
|
unique: true,
|
|
374
376
|
index: true,
|
|
375
|
-
admin: {
|
|
377
|
+
admin: {
|
|
378
|
+
position: "sidebar",
|
|
379
|
+
...nested ? { description: "Lowercase, hyphens for words. Use the `_` divider for nesting, e.g. `about_us` → `/about/us`." } : {}
|
|
380
|
+
},
|
|
376
381
|
validate: (value) => {
|
|
377
382
|
if (typeof value !== "string")
|
|
378
383
|
return "Slug must be a string";
|
|
379
|
-
if (value !== "" &&
|
|
380
|
-
return "Slug must be lowercase, with hyphens (no spaces or special characters)";
|
|
384
|
+
if (value !== "" && !slugPattern.test(value)) {
|
|
385
|
+
return nested ? "Slug must be lowercase, with hyphens or the `_` nesting divider (no spaces or other special characters)" : "Slug must be lowercase, with hyphens (no spaces or special characters)";
|
|
381
386
|
}
|
|
382
387
|
return true;
|
|
383
388
|
}
|
|
@@ -589,7 +594,8 @@ var link = (options = {}) => {
|
|
|
589
594
|
disableLabel = false,
|
|
590
595
|
relationTo = [PAGES_SLUG],
|
|
591
596
|
localized = false,
|
|
592
|
-
overrides = {}
|
|
597
|
+
overrides = {},
|
|
598
|
+
extraFields = []
|
|
593
599
|
} = options;
|
|
594
600
|
const result = {
|
|
595
601
|
name: "link",
|
|
@@ -666,6 +672,9 @@ var link = (options = {}) => {
|
|
|
666
672
|
options: opts
|
|
667
673
|
});
|
|
668
674
|
}
|
|
675
|
+
if (extraFields.length) {
|
|
676
|
+
result.fields.push(...extraFields);
|
|
677
|
+
}
|
|
669
678
|
return { ...result, ...overrides };
|
|
670
679
|
};
|
|
671
680
|
|
|
@@ -685,17 +694,22 @@ function createRevalidateGlobalHook(slug) {
|
|
|
685
694
|
|
|
686
695
|
// src/data/collections/globals/Header/config.ts
|
|
687
696
|
var createHeaderGlobal = (options = {}) => {
|
|
688
|
-
const {
|
|
697
|
+
const {
|
|
698
|
+
renderPath = HEADER_RENDER_PATH,
|
|
699
|
+
linkRelationTo = [PAGES_SLUG],
|
|
700
|
+
navColumnLinkFields = [],
|
|
701
|
+
navItemLinkFields = []
|
|
702
|
+
} = options;
|
|
689
703
|
const navColumnBlock = {
|
|
690
704
|
slug: "navColumn",
|
|
691
705
|
fields: [
|
|
692
706
|
{ name: "title", type: "text", required: true, localized: true },
|
|
693
|
-
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })] }
|
|
707
|
+
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navColumnLinkFields })] }
|
|
694
708
|
]
|
|
695
709
|
};
|
|
696
710
|
const navItemBlock = {
|
|
697
711
|
slug: "navItem",
|
|
698
|
-
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })]
|
|
712
|
+
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navItemLinkFields })]
|
|
699
713
|
};
|
|
700
714
|
return {
|
|
701
715
|
slug: "header",
|
|
@@ -717,17 +731,22 @@ var createHeaderGlobal = (options = {}) => {
|
|
|
717
731
|
|
|
718
732
|
// src/data/collections/globals/Footer/config.ts
|
|
719
733
|
var createFooterGlobal = (options = {}) => {
|
|
720
|
-
const {
|
|
734
|
+
const {
|
|
735
|
+
renderPath = FOOTER_RENDER_PATH,
|
|
736
|
+
linkRelationTo = [PAGES_SLUG],
|
|
737
|
+
navColumnLinkFields = [],
|
|
738
|
+
navItemLinkFields = []
|
|
739
|
+
} = options;
|
|
721
740
|
const navColumnBlock = {
|
|
722
741
|
slug: "navColumn",
|
|
723
742
|
fields: [
|
|
724
743
|
{ name: "title", type: "text", required: true, localized: true },
|
|
725
|
-
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })] }
|
|
744
|
+
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navColumnLinkFields })] }
|
|
726
745
|
]
|
|
727
746
|
};
|
|
728
747
|
const navItemBlock = {
|
|
729
748
|
slug: "navItem",
|
|
730
|
-
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })]
|
|
749
|
+
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navItemLinkFields })]
|
|
731
750
|
};
|
|
732
751
|
return {
|
|
733
752
|
slug: "footer",
|
|
@@ -925,6 +944,7 @@ function createSitemapFile(options) {
|
|
|
925
944
|
const collectionDefaults = options.perCollection?.[collectionSlug] ?? {};
|
|
926
945
|
const urlPrefix = (options.urlPrefixes?.[collectionSlug] ?? "").replace(/\/$/, "");
|
|
927
946
|
const siteUrl = options.getServerSideURL().replace(/\/$/, "");
|
|
947
|
+
const isNested = options.nested?.[collectionSlug] ?? false;
|
|
928
948
|
for (const locale of activeLocales) {
|
|
929
949
|
const docs = await queryAllDocs({
|
|
930
950
|
collectionSlug,
|
|
@@ -940,13 +960,14 @@ function createSitemapFile(options) {
|
|
|
940
960
|
if (seen.has(dedupeKey))
|
|
941
961
|
continue;
|
|
942
962
|
seen.add(dedupeKey);
|
|
943
|
-
const
|
|
963
|
+
const slugPath = isNested ? storedSlug.replaceAll("_", "/") : storedSlug;
|
|
964
|
+
const url = `${siteUrl}${prefixFor(locale, defaultLocale, options.localePrefix ?? "always")}${urlPrefix}/${slugPath}`;
|
|
944
965
|
const languages = await buildHreflangAlternates({
|
|
945
966
|
siteUrl,
|
|
946
967
|
locale,
|
|
947
968
|
urlPrefix,
|
|
948
969
|
storedSlug,
|
|
949
|
-
nested:
|
|
970
|
+
nested: isNested,
|
|
950
971
|
homeSlug: "",
|
|
951
972
|
defaultLocale,
|
|
952
973
|
locales: activeLocales,
|
|
@@ -977,7 +998,7 @@ function createSitemapFile(options) {
|
|
|
977
998
|
}
|
|
978
999
|
// src/config/createWWWConfig.ts
|
|
979
1000
|
function createWWWConfig(options) {
|
|
980
|
-
const { locales, routing, blocks, linkRelationTo, registerPosts = true, defaultPlugins } = options;
|
|
1001
|
+
const { locales, routing, blocks, linkRelationTo, registerPosts = true, nested = false, defaultPlugins } = options;
|
|
981
1002
|
const defaultLocale = routing?.defaultLocale ?? locales[0] ?? "";
|
|
982
1003
|
const localePrefixMode = (() => {
|
|
983
1004
|
const raw = routing?.localePrefix;
|
|
@@ -990,7 +1011,8 @@ function createWWWConfig(options) {
|
|
|
990
1011
|
}
|
|
991
1012
|
const buildPagesCollection = () => createPagesCollection(blocks, {
|
|
992
1013
|
localePrefix: localePrefixMode,
|
|
993
|
-
defaultLocale
|
|
1014
|
+
defaultLocale,
|
|
1015
|
+
nested
|
|
994
1016
|
});
|
|
995
1017
|
const buildPostsCollection = () => createPostsCollection({
|
|
996
1018
|
localePrefix: localePrefixMode,
|
|
@@ -1635,7 +1657,8 @@ function createCollectionPageExports({
|
|
|
1635
1657
|
config: configPromise,
|
|
1636
1658
|
routing,
|
|
1637
1659
|
importMap: importMapArg,
|
|
1638
|
-
renderPath
|
|
1660
|
+
renderPath,
|
|
1661
|
+
nested = false
|
|
1639
1662
|
}, deps, options = {}) {
|
|
1640
1663
|
const importMap = importMapArg ?? {};
|
|
1641
1664
|
const { jsonLd: jsonLdOption = true, changefreq = "weekly", priority = 0.5, websiteName } = options;
|
|
@@ -1654,7 +1677,7 @@ function createCollectionPageExports({
|
|
|
1654
1677
|
const defaultRenderPath = slug === POSTS_SLUG ? POSTS_RENDER_PATH : PAGES_RENDER_PATH;
|
|
1655
1678
|
const localePrefixMode = typeof routing.localePrefix === "string" ? routing.localePrefix : routing.localePrefix?.mode ?? "always";
|
|
1656
1679
|
const buildLocalePath = (locale, storedSlug) => {
|
|
1657
|
-
const urlPath = getUrlPath(storedSlug,
|
|
1680
|
+
const urlPath = getUrlPath(storedSlugToSegments(storedSlug, nested), nested, HOME_SLUG);
|
|
1658
1681
|
if (localePrefixMode === "never")
|
|
1659
1682
|
return urlPath;
|
|
1660
1683
|
if (localePrefixMode === "as-needed" && locale === defaultLocale)
|
|
@@ -1679,7 +1702,7 @@ function createCollectionPageExports({
|
|
|
1679
1702
|
locale,
|
|
1680
1703
|
urlPrefix: "",
|
|
1681
1704
|
storedSlug,
|
|
1682
|
-
nested
|
|
1705
|
+
nested,
|
|
1683
1706
|
homeSlug: HOME_SLUG,
|
|
1684
1707
|
defaultLocale,
|
|
1685
1708
|
locales,
|
|
@@ -1704,7 +1727,7 @@ function createCollectionPageExports({
|
|
|
1704
1727
|
setRequestLocale(locale);
|
|
1705
1728
|
const { draftMode } = await import("next/headers");
|
|
1706
1729
|
const { isEnabled: draft } = await draftMode();
|
|
1707
|
-
const storedSlug = segmentsToStoredSlug(slugSegments,
|
|
1730
|
+
const storedSlug = segmentsToStoredSlug(slugSegments, nested);
|
|
1708
1731
|
const doc = await fetchDoc(locale, storedSlug, draft);
|
|
1709
1732
|
if (!doc) {
|
|
1710
1733
|
const { notFound } = await import("next/navigation");
|
|
@@ -1727,7 +1750,6 @@ function createCollectionPageExports({
|
|
|
1727
1750
|
});
|
|
1728
1751
|
const jsonLdNodes = doc ? await generateJsonLd(slugSegments, doc, locale) : [];
|
|
1729
1752
|
const { alternates: hreflangAlternates, canonical } = await resolveHreflangAlternates(locale, storedSlug);
|
|
1730
|
-
console.log("render", { render, doc });
|
|
1731
1753
|
const inner = /* @__PURE__ */ jsxs3(Fragment4, {
|
|
1732
1754
|
children: [
|
|
1733
1755
|
jsonLdNodes.map(({ id, schema }) => /* @__PURE__ */ jsx7("script", {
|
|
@@ -1777,7 +1799,7 @@ function createCollectionPageExports({
|
|
|
1777
1799
|
const locale = typeof incomingLocale === "string" ? incomingLocale : defaultLocale;
|
|
1778
1800
|
if (!locales.includes(locale))
|
|
1779
1801
|
return { title: "Not found", robots: { index: false, follow: false } };
|
|
1780
|
-
const storedSlug = segmentsToStoredSlug(slugSegments,
|
|
1802
|
+
const storedSlug = segmentsToStoredSlug(slugSegments, nested);
|
|
1781
1803
|
const doc = await fetchDoc(locale, storedSlug);
|
|
1782
1804
|
if (!doc)
|
|
1783
1805
|
return { title: "Not found", robots: { index: false, follow: false } };
|
|
@@ -1801,7 +1823,7 @@ function createCollectionPageExports({
|
|
|
1801
1823
|
if (jsonLdOption === false || !doc)
|
|
1802
1824
|
return [];
|
|
1803
1825
|
const siteUrl = getServerSideURL();
|
|
1804
|
-
const storedSlug = segmentsToStoredSlug(slugSegments,
|
|
1826
|
+
const storedSlug = segmentsToStoredSlug(slugSegments, nested);
|
|
1805
1827
|
const urlPath = buildLocalePath(locale, storedSlug);
|
|
1806
1828
|
const canonical = `${siteUrl}${urlPath}`;
|
|
1807
1829
|
const entries = Array.isArray(jsonLdOption) ? jsonLdOption : metadataType === "article" ? [{ type: "article" }] : [{ type: "website" }];
|
|
@@ -1872,7 +1894,7 @@ function createCollectionPageExports({
|
|
|
1872
1894
|
const slugVal = doc.slug;
|
|
1873
1895
|
if (typeof slugVal !== "string" || slugVal === "")
|
|
1874
1896
|
continue;
|
|
1875
|
-
const segments = storedSlugToSegments(slugVal,
|
|
1897
|
+
const segments = storedSlugToSegments(slugVal, nested);
|
|
1876
1898
|
params.push({ slug: Array.isArray(segments) ? segments : [segments], locale });
|
|
1877
1899
|
}
|
|
1878
1900
|
}
|
package/dist/test.js
CHANGED
|
@@ -201,20 +201,25 @@ var createPagesCollection = (blocks, options = {}) => {
|
|
|
201
201
|
renderPath = PAGES_RENDER_PATH,
|
|
202
202
|
slug: collectionSlug = PAGES_SLUG2,
|
|
203
203
|
localePrefix,
|
|
204
|
-
defaultLocale
|
|
204
|
+
defaultLocale,
|
|
205
|
+
nested = false
|
|
205
206
|
} = options;
|
|
207
|
+
const slugPattern = nested ? /^[a-z0-9_-]+$/ : /^[a-z0-9-]+$/;
|
|
206
208
|
const slugField = {
|
|
207
209
|
name: "slug",
|
|
208
210
|
type: "text",
|
|
209
211
|
required: true,
|
|
210
212
|
unique: true,
|
|
211
213
|
index: true,
|
|
212
|
-
admin: {
|
|
214
|
+
admin: {
|
|
215
|
+
position: "sidebar",
|
|
216
|
+
...nested ? { description: "Lowercase, hyphens for words. Use the `_` divider for nesting, e.g. `about_us` → `/about/us`." } : {}
|
|
217
|
+
},
|
|
213
218
|
validate: (value) => {
|
|
214
219
|
if (typeof value !== "string")
|
|
215
220
|
return "Slug must be a string";
|
|
216
|
-
if (value !== "" &&
|
|
217
|
-
return "Slug must be lowercase, with hyphens (no spaces or special characters)";
|
|
221
|
+
if (value !== "" && !slugPattern.test(value)) {
|
|
222
|
+
return nested ? "Slug must be lowercase, with hyphens or the `_` nesting divider (no spaces or other special characters)" : "Slug must be lowercase, with hyphens (no spaces or special characters)";
|
|
218
223
|
}
|
|
219
224
|
return true;
|
|
220
225
|
}
|
|
@@ -426,7 +431,8 @@ var link = (options = {}) => {
|
|
|
426
431
|
disableLabel = false,
|
|
427
432
|
relationTo = [PAGES_SLUG],
|
|
428
433
|
localized = false,
|
|
429
|
-
overrides = {}
|
|
434
|
+
overrides = {},
|
|
435
|
+
extraFields = []
|
|
430
436
|
} = options;
|
|
431
437
|
const result = {
|
|
432
438
|
name: "link",
|
|
@@ -503,6 +509,9 @@ var link = (options = {}) => {
|
|
|
503
509
|
options: opts
|
|
504
510
|
});
|
|
505
511
|
}
|
|
512
|
+
if (extraFields.length) {
|
|
513
|
+
result.fields.push(...extraFields);
|
|
514
|
+
}
|
|
506
515
|
return { ...result, ...overrides };
|
|
507
516
|
};
|
|
508
517
|
|
|
@@ -522,17 +531,22 @@ function createRevalidateGlobalHook(slug) {
|
|
|
522
531
|
|
|
523
532
|
// src/data/collections/globals/Header/config.ts
|
|
524
533
|
var createHeaderGlobal = (options = {}) => {
|
|
525
|
-
const {
|
|
534
|
+
const {
|
|
535
|
+
renderPath = HEADER_RENDER_PATH,
|
|
536
|
+
linkRelationTo = [PAGES_SLUG],
|
|
537
|
+
navColumnLinkFields = [],
|
|
538
|
+
navItemLinkFields = []
|
|
539
|
+
} = options;
|
|
526
540
|
const navColumnBlock = {
|
|
527
541
|
slug: "navColumn",
|
|
528
542
|
fields: [
|
|
529
543
|
{ name: "title", type: "text", required: true, localized: true },
|
|
530
|
-
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })] }
|
|
544
|
+
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navColumnLinkFields })] }
|
|
531
545
|
]
|
|
532
546
|
};
|
|
533
547
|
const navItemBlock = {
|
|
534
548
|
slug: "navItem",
|
|
535
|
-
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })]
|
|
549
|
+
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navItemLinkFields })]
|
|
536
550
|
};
|
|
537
551
|
return {
|
|
538
552
|
slug: "header",
|
|
@@ -554,17 +568,22 @@ var createHeaderGlobal = (options = {}) => {
|
|
|
554
568
|
|
|
555
569
|
// src/data/collections/globals/Footer/config.ts
|
|
556
570
|
var createFooterGlobal = (options = {}) => {
|
|
557
|
-
const {
|
|
571
|
+
const {
|
|
572
|
+
renderPath = FOOTER_RENDER_PATH,
|
|
573
|
+
linkRelationTo = [PAGES_SLUG],
|
|
574
|
+
navColumnLinkFields = [],
|
|
575
|
+
navItemLinkFields = []
|
|
576
|
+
} = options;
|
|
558
577
|
const navColumnBlock = {
|
|
559
578
|
slug: "navColumn",
|
|
560
579
|
fields: [
|
|
561
580
|
{ name: "title", type: "text", required: true, localized: true },
|
|
562
|
-
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })] }
|
|
581
|
+
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navColumnLinkFields })] }
|
|
563
582
|
]
|
|
564
583
|
};
|
|
565
584
|
const navItemBlock = {
|
|
566
585
|
slug: "navItem",
|
|
567
|
-
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })]
|
|
586
|
+
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navItemLinkFields })]
|
|
568
587
|
};
|
|
569
588
|
return {
|
|
570
589
|
slug: "footer",
|
|
@@ -762,6 +781,7 @@ function createSitemapFile(options) {
|
|
|
762
781
|
const collectionDefaults = options.perCollection?.[collectionSlug] ?? {};
|
|
763
782
|
const urlPrefix = (options.urlPrefixes?.[collectionSlug] ?? "").replace(/\/$/, "");
|
|
764
783
|
const siteUrl = options.getServerSideURL().replace(/\/$/, "");
|
|
784
|
+
const isNested = options.nested?.[collectionSlug] ?? false;
|
|
765
785
|
for (const locale of activeLocales) {
|
|
766
786
|
const docs = await queryAllDocs({
|
|
767
787
|
collectionSlug,
|
|
@@ -777,13 +797,14 @@ function createSitemapFile(options) {
|
|
|
777
797
|
if (seen.has(dedupeKey))
|
|
778
798
|
continue;
|
|
779
799
|
seen.add(dedupeKey);
|
|
780
|
-
const
|
|
800
|
+
const slugPath = isNested ? storedSlug.replaceAll("_", "/") : storedSlug;
|
|
801
|
+
const url = `${siteUrl}${prefixFor(locale, defaultLocale, options.localePrefix ?? "always")}${urlPrefix}/${slugPath}`;
|
|
781
802
|
const languages = await buildHreflangAlternates({
|
|
782
803
|
siteUrl,
|
|
783
804
|
locale,
|
|
784
805
|
urlPrefix,
|
|
785
806
|
storedSlug,
|
|
786
|
-
nested:
|
|
807
|
+
nested: isNested,
|
|
787
808
|
homeSlug: "",
|
|
788
809
|
defaultLocale,
|
|
789
810
|
locales: activeLocales,
|
|
@@ -814,7 +835,7 @@ function createSitemapFile(options) {
|
|
|
814
835
|
}
|
|
815
836
|
// src/config/createWWWConfig.ts
|
|
816
837
|
function createWWWConfig(options) {
|
|
817
|
-
const { locales, routing, blocks, linkRelationTo, registerPosts = true, defaultPlugins } = options;
|
|
838
|
+
const { locales, routing, blocks, linkRelationTo, registerPosts = true, nested = false, defaultPlugins } = options;
|
|
818
839
|
const defaultLocale = routing?.defaultLocale ?? locales[0] ?? "";
|
|
819
840
|
const localePrefixMode = (() => {
|
|
820
841
|
const raw = routing?.localePrefix;
|
|
@@ -827,7 +848,8 @@ function createWWWConfig(options) {
|
|
|
827
848
|
}
|
|
828
849
|
const buildPagesCollection = () => createPagesCollection(blocks, {
|
|
829
850
|
localePrefix: localePrefixMode,
|
|
830
|
-
defaultLocale
|
|
851
|
+
defaultLocale,
|
|
852
|
+
nested
|
|
831
853
|
});
|
|
832
854
|
const buildPostsCollection = () => createPostsCollection({
|
|
833
855
|
localePrefix: localePrefixMode,
|
|
@@ -58,6 +58,14 @@ type WWWConfigOptions = {
|
|
|
58
58
|
*/
|
|
59
59
|
registerPosts?: boolean;
|
|
60
60
|
/**
|
|
61
|
+
* Enable nested (hierarchical) Pages slugs. When `true`, the lib's
|
|
62
|
+
* Pages collection accepts the `_` nesting divider in its slug
|
|
63
|
+
* (`about_us` → `/about/us`). Pass the same `nested: true` to
|
|
64
|
+
* `createCollectionPageExports` on the Pages route so URL building,
|
|
65
|
+
* hreflang, and static params expand the divider. Default: `false`.
|
|
66
|
+
*/
|
|
67
|
+
nested?: boolean;
|
|
68
|
+
/**
|
|
61
69
|
* Final say on the lib's default plugin list. Receives the
|
|
62
70
|
* `[seoPlugin, imageHashPlugin, translator]` array; return the
|
|
63
71
|
* list to apply. Common uses: drop the translator (no AI
|
package/dist/with-www-config.js
CHANGED
|
@@ -195,20 +195,25 @@ var createPagesCollection = (blocks, options = {}) => {
|
|
|
195
195
|
renderPath = PAGES_RENDER_PATH,
|
|
196
196
|
slug: collectionSlug = PAGES_SLUG2,
|
|
197
197
|
localePrefix,
|
|
198
|
-
defaultLocale
|
|
198
|
+
defaultLocale,
|
|
199
|
+
nested = false
|
|
199
200
|
} = options;
|
|
201
|
+
const slugPattern = nested ? /^[a-z0-9_-]+$/ : /^[a-z0-9-]+$/;
|
|
200
202
|
const slugField = {
|
|
201
203
|
name: "slug",
|
|
202
204
|
type: "text",
|
|
203
205
|
required: true,
|
|
204
206
|
unique: true,
|
|
205
207
|
index: true,
|
|
206
|
-
admin: {
|
|
208
|
+
admin: {
|
|
209
|
+
position: "sidebar",
|
|
210
|
+
...nested ? { description: "Lowercase, hyphens for words. Use the `_` divider for nesting, e.g. `about_us` → `/about/us`." } : {}
|
|
211
|
+
},
|
|
207
212
|
validate: (value) => {
|
|
208
213
|
if (typeof value !== "string")
|
|
209
214
|
return "Slug must be a string";
|
|
210
|
-
if (value !== "" &&
|
|
211
|
-
return "Slug must be lowercase, with hyphens (no spaces or special characters)";
|
|
215
|
+
if (value !== "" && !slugPattern.test(value)) {
|
|
216
|
+
return nested ? "Slug must be lowercase, with hyphens or the `_` nesting divider (no spaces or other special characters)" : "Slug must be lowercase, with hyphens (no spaces or special characters)";
|
|
212
217
|
}
|
|
213
218
|
return true;
|
|
214
219
|
}
|
|
@@ -420,7 +425,8 @@ var link = (options = {}) => {
|
|
|
420
425
|
disableLabel = false,
|
|
421
426
|
relationTo = [PAGES_SLUG],
|
|
422
427
|
localized = false,
|
|
423
|
-
overrides = {}
|
|
428
|
+
overrides = {},
|
|
429
|
+
extraFields = []
|
|
424
430
|
} = options;
|
|
425
431
|
const result = {
|
|
426
432
|
name: "link",
|
|
@@ -497,6 +503,9 @@ var link = (options = {}) => {
|
|
|
497
503
|
options: opts
|
|
498
504
|
});
|
|
499
505
|
}
|
|
506
|
+
if (extraFields.length) {
|
|
507
|
+
result.fields.push(...extraFields);
|
|
508
|
+
}
|
|
500
509
|
return { ...result, ...overrides };
|
|
501
510
|
};
|
|
502
511
|
|
|
@@ -516,17 +525,22 @@ function createRevalidateGlobalHook(slug) {
|
|
|
516
525
|
|
|
517
526
|
// src/data/collections/globals/Header/config.ts
|
|
518
527
|
var createHeaderGlobal = (options = {}) => {
|
|
519
|
-
const {
|
|
528
|
+
const {
|
|
529
|
+
renderPath = HEADER_RENDER_PATH,
|
|
530
|
+
linkRelationTo = [PAGES_SLUG],
|
|
531
|
+
navColumnLinkFields = [],
|
|
532
|
+
navItemLinkFields = []
|
|
533
|
+
} = options;
|
|
520
534
|
const navColumnBlock = {
|
|
521
535
|
slug: "navColumn",
|
|
522
536
|
fields: [
|
|
523
537
|
{ name: "title", type: "text", required: true, localized: true },
|
|
524
|
-
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })] }
|
|
538
|
+
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navColumnLinkFields })] }
|
|
525
539
|
]
|
|
526
540
|
};
|
|
527
541
|
const navItemBlock = {
|
|
528
542
|
slug: "navItem",
|
|
529
|
-
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })]
|
|
543
|
+
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navItemLinkFields })]
|
|
530
544
|
};
|
|
531
545
|
return {
|
|
532
546
|
slug: "header",
|
|
@@ -548,17 +562,22 @@ var createHeaderGlobal = (options = {}) => {
|
|
|
548
562
|
|
|
549
563
|
// src/data/collections/globals/Footer/config.ts
|
|
550
564
|
var createFooterGlobal = (options = {}) => {
|
|
551
|
-
const {
|
|
565
|
+
const {
|
|
566
|
+
renderPath = FOOTER_RENDER_PATH,
|
|
567
|
+
linkRelationTo = [PAGES_SLUG],
|
|
568
|
+
navColumnLinkFields = [],
|
|
569
|
+
navItemLinkFields = []
|
|
570
|
+
} = options;
|
|
552
571
|
const navColumnBlock = {
|
|
553
572
|
slug: "navColumn",
|
|
554
573
|
fields: [
|
|
555
574
|
{ name: "title", type: "text", required: true, localized: true },
|
|
556
|
-
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })] }
|
|
575
|
+
{ name: "links", type: "array", fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navColumnLinkFields })] }
|
|
557
576
|
]
|
|
558
577
|
};
|
|
559
578
|
const navItemBlock = {
|
|
560
579
|
slug: "navItem",
|
|
561
|
-
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo })]
|
|
580
|
+
fields: [link({ appearances: false, localized: true, relationTo: linkRelationTo, extraFields: navItemLinkFields })]
|
|
562
581
|
};
|
|
563
582
|
return {
|
|
564
583
|
slug: "footer",
|
|
@@ -756,6 +775,7 @@ function createSitemapFile(options) {
|
|
|
756
775
|
const collectionDefaults = options.perCollection?.[collectionSlug] ?? {};
|
|
757
776
|
const urlPrefix = (options.urlPrefixes?.[collectionSlug] ?? "").replace(/\/$/, "");
|
|
758
777
|
const siteUrl = options.getServerSideURL().replace(/\/$/, "");
|
|
778
|
+
const isNested = options.nested?.[collectionSlug] ?? false;
|
|
759
779
|
for (const locale of activeLocales) {
|
|
760
780
|
const docs = await queryAllDocs({
|
|
761
781
|
collectionSlug,
|
|
@@ -771,13 +791,14 @@ function createSitemapFile(options) {
|
|
|
771
791
|
if (seen.has(dedupeKey))
|
|
772
792
|
continue;
|
|
773
793
|
seen.add(dedupeKey);
|
|
774
|
-
const
|
|
794
|
+
const slugPath = isNested ? storedSlug.replaceAll("_", "/") : storedSlug;
|
|
795
|
+
const url = `${siteUrl}${prefixFor(locale, defaultLocale, options.localePrefix ?? "always")}${urlPrefix}/${slugPath}`;
|
|
775
796
|
const languages = await buildHreflangAlternates({
|
|
776
797
|
siteUrl,
|
|
777
798
|
locale,
|
|
778
799
|
urlPrefix,
|
|
779
800
|
storedSlug,
|
|
780
|
-
nested:
|
|
801
|
+
nested: isNested,
|
|
781
802
|
homeSlug: "",
|
|
782
803
|
defaultLocale,
|
|
783
804
|
locales: activeLocales,
|
|
@@ -808,7 +829,7 @@ function createSitemapFile(options) {
|
|
|
808
829
|
}
|
|
809
830
|
// src/config/createWWWConfig.ts
|
|
810
831
|
function createWWWConfig(options) {
|
|
811
|
-
const { locales, routing, blocks, linkRelationTo, registerPosts = true, defaultPlugins } = options;
|
|
832
|
+
const { locales, routing, blocks, linkRelationTo, registerPosts = true, nested = false, defaultPlugins } = options;
|
|
812
833
|
const defaultLocale = routing?.defaultLocale ?? locales[0] ?? "";
|
|
813
834
|
const localePrefixMode = (() => {
|
|
814
835
|
const raw = routing?.localePrefix;
|
|
@@ -821,7 +842,8 @@ function createWWWConfig(options) {
|
|
|
821
842
|
}
|
|
822
843
|
const buildPagesCollection = () => createPagesCollection(blocks, {
|
|
823
844
|
localePrefix: localePrefixMode,
|
|
824
|
-
defaultLocale
|
|
845
|
+
defaultLocale,
|
|
846
|
+
nested
|
|
825
847
|
});
|
|
826
848
|
const buildPostsCollection = () => createPostsCollection({
|
|
827
849
|
localePrefix: localePrefixMode,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justanarthur/payload-www",
|
|
3
3
|
"description": "Reusable Payload CMS website template — config builder, collections, globals, blocks, fields, access, hooks, metadata (JSON-LD, hreflang), page renderers, and test helpers.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"module": "./dist/index.js",
|