From 7438812db312a4ab820d74858cace8f19998cc7b Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 13 May 2024 23:51:27 -0400 Subject: [PATCH] feat!: replaces admin.favicon with admin.icons --- packages/next/src/layouts/Root/index.tsx | 5 ++ packages/next/src/utilities/meta.ts | 54 +++++++++--------- packages/payload/package.json | 1 + packages/payload/src/config/schema.ts | 8 ++- packages/payload/src/config/types.ts | 10 ++-- packages/ui/src/assets/index.ts | 6 +- ...icon-dark.png => payload-favicon-dark.png} | Bin ...on-light.png => payload-favicon-light.png} | Bin .../{favicon.svg => payload-favicon.svg} | 0 pnpm-lock.yaml | 3 + test/_community/config.ts | 1 + test/admin/config.ts | 18 +++++- test/admin/custom-favicon-dark.png | Bin 0 -> 477 bytes test/admin/custom-favicon-light.png | Bin 0 -> 496 bytes test/admin/e2e.spec.ts | 24 ++++++++ test/admin/types.d.ts | 4 ++ 16 files changed, 97 insertions(+), 37 deletions(-) rename packages/ui/src/assets/{favicon-dark.png => payload-favicon-dark.png} (100%) rename packages/ui/src/assets/{favicon-light.png => payload-favicon-light.png} (100%) rename packages/ui/src/assets/{favicon.svg => payload-favicon.svg} (100%) create mode 100644 test/admin/custom-favicon-dark.png create mode 100644 test/admin/custom-favicon-light.png create mode 100644 test/admin/types.d.ts diff --git a/packages/next/src/layouts/Root/index.tsx b/packages/next/src/layouts/Root/index.tsx index 41217a880..b3e3640f7 100644 --- a/packages/next/src/layouts/Root/index.tsx +++ b/packages/next/src/layouts/Root/index.tsx @@ -26,6 +26,11 @@ const merriweather = Merriweather({ weight: ['400', '900'], }) +export const metadata = { + description: 'Generated by Next.js', + title: 'Next.js', +} + export const RootLayout = async ({ children, config: configPromise, diff --git a/packages/next/src/utilities/meta.ts b/packages/next/src/utilities/meta.ts index e6f0d5a40..6034120e0 100644 --- a/packages/next/src/utilities/meta.ts +++ b/packages/next/src/utilities/meta.ts @@ -1,4 +1,5 @@ import type { Metadata } from 'next' +import type { Icon } from 'next/dist/lib/metadata/types/metadata-types.js' import type { SanitizedConfig } from 'payload/types' import { payloadFaviconDark, payloadFaviconLight, payloadOgImage } from '@payloadcms/ui/assets' @@ -13,38 +14,35 @@ export const meta = async (args: { const titleSuffix = config.admin.meta?.titleSuffix ?? '- Payload' - const customFavicon = config.admin.meta?.favicon - const customFaviconFiletype = customFavicon?.split('.').pop() - const customFaviconMediaType = `image/${customFaviconFiletype}` - - const favicon = customFavicon ?? payloadFaviconLight?.src const ogImage = config.admin?.meta?.ogImage ?? payloadOgImage?.src + const customIcons = config.admin.meta.icons as Metadata['icons'] + + let icons = customIcons ?? [] + + const payloadIcons: Icon[] = [ + { + type: 'image/png', + rel: 'icon', + sizes: '32x32', + url: payloadFaviconDark?.src, + }, + { + type: 'image/png', + media: '(prefers-color-scheme: dark)', + rel: 'icon', + sizes: '32x32', + url: payloadFaviconLight?.src, + }, + ] + + if (customIcons && typeof customIcons === 'object' && Array.isArray(customIcons)) { + icons = payloadIcons.concat(customIcons) + } + return Promise.resolve({ description, - icons: [ - ...(customFavicon - ? [ - { - type: customFaviconMediaType, - rel: 'icon', - url: favicon, - }, - ] - : [ - { - type: 'image/png', - rel: 'icon', - url: payloadFaviconDark?.src, - }, - { - type: 'image/png', - media: '(prefers-color-scheme: dark)', - rel: 'icon', - url: payloadFaviconLight?.src, - }, - ]), - ], + icons, keywords, metadataBase: new URL( config?.serverURL || diff --git a/packages/payload/package.json b/packages/payload/package.json index 6a0a7bded..6ca6fcbf7 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -149,6 +149,7 @@ "get-port": "5.1.1", "graphql-http": "^1.22.0", "mini-css-extract-plugin": "1.6.2", + "next": "^14.3.0-canary.7", "nodemon": "3.0.3", "object.assign": "4.1.4", "object.entries": "1.1.6", diff --git a/packages/payload/src/config/schema.ts b/packages/payload/src/config/schema.ts index 23eb9ac71..b3c730dd6 100644 --- a/packages/payload/src/config/schema.ts +++ b/packages/payload/src/config/schema.ts @@ -68,7 +68,13 @@ export default joi.object({ }), logoutRoute: joi.string(), meta: joi.object().keys({ - favicon: joi.string(), + icons: joi + .alternatives() + .try( + joi.array().items(joi.alternatives().try(joi.string(), joi.object())), + joi.object(), + joi.string().allow(null), + ), ogImage: joi.string(), titleSuffix: joi.string(), }), diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index 1177a7c32..a861e7cd0 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -1,6 +1,7 @@ import type { I18nOptions, TFunction } from '@payloadcms/translations' import type { Options as ExpressFileUploadOptions } from 'express-fileupload' import type GraphQL from 'graphql' +import type { Metadata as NextMetadata } from 'next' import type { DestinationStream, LoggerOptions, P } from 'pino' import type React from 'react' import type { default as sharp } from 'sharp' @@ -460,14 +461,15 @@ export type Config = { } /** The route for the logout page. */ logoutRoute?: string - /** Base meta data to use for the Admin panel. Included properties are titleSuffix, ogImage, and favicon. */ + /** Base meta data to use for the Admin Panel. Included properties are titleSuffix, ogImage, and favicon. */ meta?: { /** - * Public path to an icon + * An array of Next.js metadata objects that represent icons to be used by devices and browsers. * - * This image may be displayed in the browser next to the title of the page + * For example browser tabs, phone home screens, and search engine results. + * @reference https://nextjs.org/docs/app/api-reference/functions/generate-metadata#icons */ - favicon?: string + icons?: NextMetadata['icons'] /** * Public path to an image * diff --git a/packages/ui/src/assets/index.ts b/packages/ui/src/assets/index.ts index 68bd5931d..accc985b4 100644 --- a/packages/ui/src/assets/index.ts +++ b/packages/ui/src/assets/index.ts @@ -1,4 +1,4 @@ -export { default as payloadFavicon } from '../assets/favicon.svg' -export { default as payloadFaviconDark } from '../assets/favicon-dark.png' -export { default as payloadFaviconLight } from '../assets/favicon-light.png' export { default as payloadOgImage } from '../assets/og-image.png' +export { default as payloadFavicon } from '../assets/payload-favicon.svg' +export { default as payloadFaviconDark } from '../assets/payload-favicon-dark.png' +export { default as payloadFaviconLight } from '../assets/payload-favicon-light.png' diff --git a/packages/ui/src/assets/favicon-dark.png b/packages/ui/src/assets/payload-favicon-dark.png similarity index 100% rename from packages/ui/src/assets/favicon-dark.png rename to packages/ui/src/assets/payload-favicon-dark.png diff --git a/packages/ui/src/assets/favicon-light.png b/packages/ui/src/assets/payload-favicon-light.png similarity index 100% rename from packages/ui/src/assets/favicon-light.png rename to packages/ui/src/assets/payload-favicon-light.png diff --git a/packages/ui/src/assets/favicon.svg b/packages/ui/src/assets/payload-favicon.svg similarity index 100% rename from packages/ui/src/assets/favicon.svg rename to packages/ui/src/assets/payload-favicon.svg diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 114922ee5..63adaaced 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -912,6 +912,9 @@ importers: mini-css-extract-plugin: specifier: 1.6.2 version: 1.6.2(webpack@5.91.0) + next: + specifier: ^14.3.0-canary.7 + version: 14.3.0-canary.7(@babel/core@7.24.4)(@playwright/test@1.43.0)(react-dom@18.2.0)(react@18.2.0)(sass@1.74.1) nodemon: specifier: 3.0.3 version: 3.0.3 diff --git a/test/_community/config.ts b/test/_community/config.ts index cb68318a2..2824dd76d 100644 --- a/test/_community/config.ts +++ b/test/_community/config.ts @@ -7,6 +7,7 @@ import { devUser } from '../credentials.js' import { MediaCollection } from './collections/Media/index.js' import { PostsCollection, postsSlug } from './collections/Posts/index.js' import { MenuGlobal } from './globals/Menu/index.js' + const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) diff --git a/test/admin/config.ts b/test/admin/config.ts index f121b63bb..36d578837 100644 --- a/test/admin/config.ts +++ b/test/admin/config.ts @@ -25,6 +25,8 @@ import { CustomMinimalView } from './components/views/CustomMinimal/index.js' import { CustomView } from './components/views/CustomView/index.js' import { CustomNestedView } from './components/views/CustomViewNested/index.js' import { CustomViewWithParam } from './components/views/CustomViewWithParam/index.js' +import { default as customFaviconDark } from './custom-favicon-dark.png' +import { default as customFaviconLight } from './custom-favicon-light.png' import { CustomGlobalViews1 } from './globals/CustomViews1.js' import { CustomGlobalViews2 } from './globals/CustomViews2.js' import { Global } from './globals/Global.js' @@ -34,7 +36,6 @@ import { GlobalHidden } from './globals/Hidden.js' import { GlobalNoApiView } from './globals/NoApiView.js' import { seed } from './seed.js' import { customNestedViewPath, customParamViewPath, customViewPath } from './shared.js' - export default buildConfigWithDefaults({ admin: { components: { @@ -74,6 +75,21 @@ export default buildConfigWithDefaults({ }, }, }, + meta: { + icons: [ + { + type: 'image/png', + rel: 'icon', + url: customFaviconDark.src, + }, + { + type: 'image/png', + media: '(prefers-color-scheme: dark)', + rel: 'icon', + url: customFaviconLight.src, + }, + ], + }, }, collections: [ UploadCollection, diff --git a/test/admin/custom-favicon-dark.png b/test/admin/custom-favicon-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..be501c6b78cac68ce4e7f45dff73efb989b3806b GIT binary patch literal 477 zcmV<30V4j1P)sG%!8YK0=iCSLBV8NY z$UC@`uwGb4)`3NOO}MfeR>k^aedx7SAl8M|vN{hH`ff0ZCLRW^HF(BK9gLh20BIr< zq}7Cnk!M{-qlU+5&>(q<+qXub0cR65NdAU-eCEZh2|IZTH#+#67#-0w{CXa0Ky(=f zA@rAz4_%@@2JuPucraqX6b^*=T*W}6jVHvX5d&uSpAer)418*XFkWzMiPVq3tH9KI z8T?tY6aN2}a}4(92nGr}2w68u__50GSjZ8tAj!r8$M>Cn+zDv6YvEJdz~bS2ce)(5k$yWy zwlatW5p()^BRi87WQr`e-94EhYsnsDUs;y*um==ak=@8Tlu`4$uoj-N4Ln9$=M`DG ztOKtg#m-uLPC6T~2#k$EN3TX8c!R_LKQ%rUy(l6O`~#K+OVG0-@PF_UB0xDBLn}&+ zR}{>Ag8=3>cA~t_8U)~al<%Ytb{s3;4+==j!=++(->X0fZX5kqg7yzzK~QQN4yub zH|YH!0ea5ab1$V6BI>!9FdmH?YJ*837>C9+Wf~6EP{>(>@ueAWZqcV5T8&SX(b>Ri m$$tis;9q%uA$x_L%JW|m%<%v)&-Ks%0000 { await ensureAutoLoginAndCompilationIsDone({ page, serverURL }) }) + describe('metadata', () => { + test('should set Payload favicons', async () => { + await page.goto(postsUrl.admin) + const favicons = page.locator('link[rel="icon"]') + await expect(favicons).toHaveCount(4) + await expect(favicons.nth(0)).toHaveAttribute('sizes', '32x32') + await expect(favicons.nth(1)).toHaveAttribute('sizes', '32x32') + await expect(favicons.nth(1)).toHaveAttribute('media', '(prefers-color-scheme: dark)') + await expect(favicons.nth(1)).toHaveAttribute( + 'href', + /\/payload-favicon-light\.[a-z\d]+\.png/, + ) + }) + + test('should inject custom favicons', async () => { + await page.goto(postsUrl.admin) + const favicons = page.locator('link[rel="icon"]') + await expect(favicons).toHaveCount(4) + await expect(favicons.nth(2)).toHaveAttribute('href', /\/custom-favicon-dark\.[a-z\d]+\.png/) + await expect(favicons.nth(3)).toHaveAttribute('media', '(prefers-color-scheme: dark)') + await expect(favicons.nth(3)).toHaveAttribute('href', /\/custom-favicon-light\.[a-z\d]+\.png/) + }) + }) + describe('navigation', () => { test('nav — should navigate to collection', async () => { await page.goto(postsUrl.admin) diff --git a/test/admin/types.d.ts b/test/admin/types.d.ts new file mode 100644 index 000000000..a0d67c32f --- /dev/null +++ b/test/admin/types.d.ts @@ -0,0 +1,4 @@ +declare module '*.png' { + const value: any + export = value +}