From 398b6096f20cade607db09e86361c935c3d4f0c5 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 6 Jan 2025 14:18:54 -0600 Subject: [PATCH] templates: fix nested docs url generation for categories (#10403) Fixes https://github.com/payloadcms/payload/issues/10374 --- .../website/src/collections/Categories.ts | 2 + templates/website/src/endpoints/seed/index.ts | 36 ++++ templates/website/src/payload-types.ts | 33 +++ templates/website/src/plugins/index.ts | 1 + .../src/collections/Categories.ts | 2 + .../src/endpoints/seed/index.ts | 36 ++++ .../with-vercel-website/src/payload-types.ts | 196 +++++++++++------- .../with-vercel-website/src/plugins/index.ts | 1 + 8 files changed, 233 insertions(+), 74 deletions(-) diff --git a/templates/website/src/collections/Categories.ts b/templates/website/src/collections/Categories.ts index 1a508b58384..19d2b0c2065 100644 --- a/templates/website/src/collections/Categories.ts +++ b/templates/website/src/collections/Categories.ts @@ -2,6 +2,7 @@ import type { CollectionConfig } from 'payload' import { anyone } from '../access/anyone' import { authenticated } from '../access/authenticated' +import { slugField } from '@/fields/slug' export const Categories: CollectionConfig = { slug: 'categories', @@ -20,5 +21,6 @@ export const Categories: CollectionConfig = { type: 'text', required: true, }, + ...slugField(), ], } diff --git a/templates/website/src/endpoints/seed/index.ts b/templates/website/src/endpoints/seed/index.ts index 60752caca5c..8681da25808 100644 --- a/templates/website/src/endpoints/seed/index.ts +++ b/templates/website/src/endpoints/seed/index.ts @@ -141,6 +141,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'Technology', + breadcrumbs: [ + { + label: 'Technology', + url: '/technology', + }, + ], }, }), @@ -148,6 +154,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'News', + breadcrumbs: [ + { + label: 'News', + url: '/news', + }, + ], }, }), @@ -155,12 +167,24 @@ export const seed = async ({ collection: 'categories', data: { title: 'Finance', + breadcrumbs: [ + { + label: 'Finance', + url: '/finance', + }, + ], }, }), payload.create({ collection: 'categories', data: { title: 'Design', + breadcrumbs: [ + { + label: 'Design', + url: '/design', + }, + ], }, }), @@ -168,6 +192,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'Software', + breadcrumbs: [ + { + label: 'Software', + url: '/software', + }, + ], }, }), @@ -175,6 +205,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'Engineering', + breadcrumbs: [ + { + label: 'Engineering', + url: '/engineering', + }, + ], }, }), ]) diff --git a/templates/website/src/payload-types.ts b/templates/website/src/payload-types.ts index 61387fac8bf..3fde2ef399f 100644 --- a/templates/website/src/payload-types.ts +++ b/templates/website/src/payload-types.ts @@ -117,6 +117,9 @@ export interface Page { } | null); url?: string | null; label: string; + /** + * Choose how the link should be rendered. + */ appearance?: ('default' | 'outline') | null; }; id?: string | null; @@ -127,6 +130,9 @@ export interface Page { layout: (CallToActionBlock | ContentBlock | MediaBlock | ArchiveBlock | FormBlock)[]; meta?: { title?: string | null; + /** + * Maximum upload file size: 12MB. Recommended file size for images is <500KB. + */ image?: (string | null) | Media; description?: string | null; }; @@ -164,6 +170,9 @@ export interface Post { categories?: (string | Category)[] | null; meta?: { title?: string | null; + /** + * Maximum upload file size: 12MB. Recommended file size for images is <500KB. + */ image?: (string | null) | Media; description?: string | null; }; @@ -280,6 +289,8 @@ export interface Media { export interface Category { id: string; title: string; + slug?: string | null; + slugLock?: boolean | null; parent?: (string | null) | Category; breadcrumbs?: | { @@ -346,6 +357,9 @@ export interface CallToActionBlock { } | null); url?: string | null; label: string; + /** + * Choose how the link should be rendered. + */ appearance?: ('default' | 'outline') | null; }; id?: string | null; @@ -393,6 +407,9 @@ export interface ContentBlock { } | null); url?: string | null; label: string; + /** + * Choose how the link should be rendered. + */ appearance?: ('default' | 'outline') | null; }; id?: string | null; @@ -588,6 +605,9 @@ export interface Form { )[] | null; submitButtonLabel?: string | null; + /** + * Choose whether to display an on-page message or redirect to a different page after they submit the form. + */ confirmationType?: ('message' | 'redirect') | null; confirmationMessage?: { root: { @@ -607,6 +627,9 @@ export interface Form { redirect?: { url: string; }; + /** + * Send custom emails when the form submits. Use comma separated lists to send the same email to multiple recipients. To reference a value from this form, wrap that field's name with double curly brackets, i.e. {{firstName}}. You can use a wildcard {{*}} to output all data and {{*:table}} to format it as an HTML table in the email. + */ emails?: | { emailTo?: string | null; @@ -615,6 +638,9 @@ export interface Form { replyTo?: string | null; emailFrom?: string | null; subject: string; + /** + * Enter the message that should be sent in this email. + */ message?: { root: { type: string; @@ -642,6 +668,9 @@ export interface Form { */ export interface Redirect { id: string; + /** + * You will need to rebuild the website when changing this field. + */ from: string; to?: { type?: ('reference' | 'custom') | null; @@ -677,6 +706,8 @@ export interface FormSubmission { createdAt: string; } /** + * This is a collection of automatically created search results. These results are used by the global site search and will be updated automatically as documents in the CMS are created or updated. + * * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "search". */ @@ -1054,6 +1085,8 @@ export interface MediaSelect { */ export interface CategoriesSelect { title?: T; + slug?: T; + slugLock?: T; parent?: T; breadcrumbs?: | T diff --git a/templates/website/src/plugins/index.ts b/templates/website/src/plugins/index.ts index f958a52ac77..efc8a137988 100644 --- a/templates/website/src/plugins/index.ts +++ b/templates/website/src/plugins/index.ts @@ -49,6 +49,7 @@ export const plugins: Plugin[] = [ }), nestedDocsPlugin({ collections: ['categories'], + generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''), }), seoPlugin({ generateTitle, diff --git a/templates/with-vercel-website/src/collections/Categories.ts b/templates/with-vercel-website/src/collections/Categories.ts index 1a508b58384..19d2b0c2065 100644 --- a/templates/with-vercel-website/src/collections/Categories.ts +++ b/templates/with-vercel-website/src/collections/Categories.ts @@ -2,6 +2,7 @@ import type { CollectionConfig } from 'payload' import { anyone } from '../access/anyone' import { authenticated } from '../access/authenticated' +import { slugField } from '@/fields/slug' export const Categories: CollectionConfig = { slug: 'categories', @@ -20,5 +21,6 @@ export const Categories: CollectionConfig = { type: 'text', required: true, }, + ...slugField(), ], } diff --git a/templates/with-vercel-website/src/endpoints/seed/index.ts b/templates/with-vercel-website/src/endpoints/seed/index.ts index 60752caca5c..8681da25808 100644 --- a/templates/with-vercel-website/src/endpoints/seed/index.ts +++ b/templates/with-vercel-website/src/endpoints/seed/index.ts @@ -141,6 +141,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'Technology', + breadcrumbs: [ + { + label: 'Technology', + url: '/technology', + }, + ], }, }), @@ -148,6 +154,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'News', + breadcrumbs: [ + { + label: 'News', + url: '/news', + }, + ], }, }), @@ -155,12 +167,24 @@ export const seed = async ({ collection: 'categories', data: { title: 'Finance', + breadcrumbs: [ + { + label: 'Finance', + url: '/finance', + }, + ], }, }), payload.create({ collection: 'categories', data: { title: 'Design', + breadcrumbs: [ + { + label: 'Design', + url: '/design', + }, + ], }, }), @@ -168,6 +192,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'Software', + breadcrumbs: [ + { + label: 'Software', + url: '/software', + }, + ], }, }), @@ -175,6 +205,12 @@ export const seed = async ({ collection: 'categories', data: { title: 'Engineering', + breadcrumbs: [ + { + label: 'Engineering', + url: '/engineering', + }, + ], }, }), ]) diff --git a/templates/with-vercel-website/src/payload-types.ts b/templates/with-vercel-website/src/payload-types.ts index 6845cd1abba..cbc28703337 100644 --- a/templates/with-vercel-website/src/payload-types.ts +++ b/templates/with-vercel-website/src/payload-types.ts @@ -117,6 +117,9 @@ export interface Page { } | null); url?: string | null; label: string; + /** + * Choose how the link should be rendered. + */ appearance?: ('default' | 'outline') | null; }; id?: string | null; @@ -127,6 +130,9 @@ export interface Page { layout: (CallToActionBlock | ContentBlock | MediaBlock | ArchiveBlock | FormBlock)[]; meta?: { title?: string | null; + /** + * Maximum upload file size: 12MB. Recommended file size for images is <500KB. + */ image?: (number | null) | Media; description?: string | null; }; @@ -164,6 +170,9 @@ export interface Post { categories?: (number | Category)[] | null; meta?: { title?: string | null; + /** + * Maximum upload file size: 12MB. Recommended file size for images is <500KB. + */ image?: (number | null) | Media; description?: string | null; }; @@ -280,6 +289,8 @@ export interface Media { export interface Category { id: number; title: string; + slug?: string | null; + slugLock?: boolean | null; parent?: (number | null) | Category; breadcrumbs?: | { @@ -346,6 +357,9 @@ export interface CallToActionBlock { } | null); url?: string | null; label: string; + /** + * Choose how the link should be rendered. + */ appearance?: ('default' | 'outline') | null; }; id?: string | null; @@ -393,6 +407,9 @@ export interface ContentBlock { } | null); url?: string | null; label: string; + /** + * Choose how the link should be rendered. + */ appearance?: ('default' | 'outline') | null; }; id?: string | null; @@ -588,6 +605,9 @@ export interface Form { )[] | null; submitButtonLabel?: string | null; + /** + * Choose whether to display an on-page message or redirect to a different page after they submit the form. + */ confirmationType?: ('message' | 'redirect') | null; confirmationMessage?: { root: { @@ -607,6 +627,9 @@ export interface Form { redirect?: { url: string; }; + /** + * Send custom emails when the form submits. Use comma separated lists to send the same email to multiple recipients. To reference a value from this form, wrap that field's name with double curly brackets, i.e. {{firstName}}. You can use a wildcard {{*}} to output all data and {{*:table}} to format it as an HTML table in the email. + */ emails?: | { emailTo?: string | null; @@ -615,6 +638,9 @@ export interface Form { replyTo?: string | null; emailFrom?: string | null; subject: string; + /** + * Enter the message that should be sent in this email. + */ message?: { root: { type: string; @@ -642,6 +668,9 @@ export interface Form { */ export interface Redirect { id: number; + /** + * You will need to rebuild the website when changing this field. + */ from: string; to?: { type?: ('reference' | 'custom') | null; @@ -677,6 +706,8 @@ export interface FormSubmission { createdAt: string; } /** + * This is a collection of automatically created search results. These results are used by the global site search and will be updated automatically as documents in the CMS are created or updated. + * * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "search". */ @@ -820,80 +851,11 @@ export interface PagesSelect { layout?: | T | { - cta?: - | T - | { - richText?: T; - links?: - | T - | { - link?: - | T - | { - type?: T; - newTab?: T; - reference?: T; - url?: T; - label?: T; - appearance?: T; - }; - id?: T; - }; - id?: T; - blockName?: T; - }; - content?: - | T - | { - columns?: - | T - | { - size?: T; - richText?: T; - enableLink?: T; - link?: - | T - | { - type?: T; - newTab?: T; - reference?: T; - url?: T; - label?: T; - appearance?: T; - }; - id?: T; - }; - id?: T; - blockName?: T; - }; - mediaBlock?: - | T - | { - media?: T; - id?: T; - blockName?: T; - }; - archive?: - | T - | { - introContent?: T; - populateBy?: T; - relationTo?: T; - categories?: T; - limit?: T; - selectedDocs?: T; - id?: T; - blockName?: T; - }; - formBlock?: - | T - | { - form?: T; - enableIntro?: T; - introContent?: T; - id?: T; - blockName?: T; - }; + cta?: T | CallToActionBlockSelect; + content?: T | ContentBlockSelect; + mediaBlock?: T | MediaBlockSelect; + archive?: T | ArchiveBlockSelect; + formBlock?: T | FormBlockSelect; }; meta?: | T @@ -909,6 +871,90 @@ export interface PagesSelect { createdAt?: T; _status?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "CallToActionBlock_select". + */ +export interface CallToActionBlockSelect { + richText?: T; + links?: + | T + | { + link?: + | T + | { + type?: T; + newTab?: T; + reference?: T; + url?: T; + label?: T; + appearance?: T; + }; + id?: T; + }; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ContentBlock_select". + */ +export interface ContentBlockSelect { + columns?: + | T + | { + size?: T; + richText?: T; + enableLink?: T; + link?: + | T + | { + type?: T; + newTab?: T; + reference?: T; + url?: T; + label?: T; + appearance?: T; + }; + id?: T; + }; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "MediaBlock_select". + */ +export interface MediaBlockSelect { + media?: T; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ArchiveBlock_select". + */ +export interface ArchiveBlockSelect { + introContent?: T; + populateBy?: T; + relationTo?: T; + categories?: T; + limit?: T; + selectedDocs?: T; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "FormBlock_select". + */ +export interface FormBlockSelect { + form?: T; + enableIntro?: T; + introContent?: T; + id?: T; + blockName?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "posts_select". @@ -1039,6 +1085,8 @@ export interface MediaSelect { */ export interface CategoriesSelect { title?: T; + slug?: T; + slugLock?: T; parent?: T; breadcrumbs?: | T diff --git a/templates/with-vercel-website/src/plugins/index.ts b/templates/with-vercel-website/src/plugins/index.ts index f958a52ac77..efc8a137988 100644 --- a/templates/with-vercel-website/src/plugins/index.ts +++ b/templates/with-vercel-website/src/plugins/index.ts @@ -49,6 +49,7 @@ export const plugins: Plugin[] = [ }), nestedDocsPlugin({ collections: ['categories'], + generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''), }), seoPlugin({ generateTitle,