Compare commits

..

11 Commits

Author SHA1 Message Date
Elliot DeNolf
4cda7d2363 chore(release): v3.0.0-beta.92 [skip ci] 2024-08-27 09:44:02 -04:00
Elliot DeNolf
ea48cfbfe9 feat: implement info command (#7882)
Implements `info` command similar to Next.js.

`pnpm payload info` will output info in this format:

```
Binaries:
  Node: 18.20.2
  npm: 10.5.0
  Yarn: 1.22.19
  pnpm: 9.7.0
Relevant Packages:
  payload: 3.0.0-beta.91
  next: 15.0.0-canary.104
  @payloadcms/db-mongodb: 3.0.0-beta.91
  @payloadcms/db-postgres: 3.0.0-beta.91
  @payloadcms/email-nodemailer: 3.0.0-beta.91
  @payloadcms/graphql: 3.0.0-beta.91
  @payloadcms/next/utilities: 3.0.0-beta.91
  @payloadcms/plugin-cloud: 3.0.0-beta.91
  @payloadcms/richtext-lexical: 3.0.0-beta.91
  @payloadcms/richtext-slate: 3.0.0-beta.91
  @payloadcms/translations: 3.0.0-beta.91
  @payloadcms/ui/shared: 3.0.0-beta.91
  react: 19.0.0-rc-06d0b89e-20240801
  react-dom: 19.0.0-rc-06d0b89e-20240801
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Mon Jul 29 21:13:04 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T6020
  Available memory (MB): 32768
  Available CPU cores: 12
 ```
2024-08-27 01:41:39 +00:00
Paul
1aeb912762 fix(templates): website live preview and code block (#7881) 2024-08-26 23:42:45 +00:00
Paul
ce2cb35d71 fix(plugin-seo): remove dependency on import from payload/next package (#7879) 2024-08-26 22:40:38 +00:00
Paul
d3ec68ac2f fix: error when closing the live preview popup window (#7878) 2024-08-26 22:33:56 +00:00
Paul
05bf52aac3 fix(templates): website bug fixes for slug generation and form builder and adds support for strictNullChecks: true (#7877) 2024-08-26 22:10:09 +00:00
Jacob Fletcher
fed7f2fa5b fix: sanitizes modifyResponseHeaders from client config (#7876) 2024-08-26 21:50:29 +00:00
James Mikrut
686b0865b2 fix: exports richtext-slate useSlatePlugin (#7875)
## Description

exports `useSlatePlugin` for `richtext-slate`
2024-08-26 17:21:25 -04:00
Alessio Gravili
dfb4c8eb4c feat: add nextjs and react version checks to dependency checker (#7868) 2024-08-26 17:19:14 -04:00
Alessio Gravili
ad7a387e19 feat(richtext-lexical): more lenient url validation, URL-encode invalid urls on save (#7870)
Fixes https://github.com/payloadcms/payload/issues/7477

This simplifies validation to the point where it only errors on spaces.
Actual validation is then used in beforeChange, which then automatically
url encodes the input if it doesn't pass
2024-08-26 15:33:29 -04:00
Elliot DeNolf
d05be016ce ci(scripts): emoji release notes 2024-08-23 16:25:50 -04:00
88 changed files with 10902 additions and 294 deletions

View File

@@ -23,7 +23,7 @@ export default withBundleAnalyzer(
env: {
PAYLOAD_CORE_DEV: 'true',
ROOT_DIR: path.resolve(dirname),
PAYLOAD_DISABLE_DEPENDENCY_CHECKER: 'true',
PAYLOAD_CI_DEPENDENCY_CHECKER: 'true',
},
async redirects() {
return [

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -37,6 +37,8 @@ export const DeviceContainer: React.FC<{
x = '-50%'
if (
desiredSize &&
measuredDeviceSize &&
typeof zoom === 'number' &&
typeof desiredSize.width === 'number' &&
typeof desiredSize.height === 'number' &&

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -7,6 +7,7 @@ import type { BinScript } from '../config/types.js'
import { findConfig } from '../config/find.js'
import { generateImportMap } from './generateImportMap/index.js'
import { generateTypes } from './generateTypes.js'
import { info } from './info.js'
import { loadEnv } from './loadEnv.js'
import { migrate } from './migrate.js'
@@ -16,6 +17,11 @@ export const bin = async () => {
const args = minimist(process.argv.slice(2))
const script = (typeof args._[0] === 'string' ? args._[0] : '').toLowerCase()
if (script === 'info') {
await info()
return
}
if (script === 'run') {
const scriptPath = args._[1]
if (!scriptPath) {

View File

@@ -0,0 +1,63 @@
import { execFileSync } from 'child_process'
import os from 'os'
import { getDependencies } from '../index.js'
import { PAYLOAD_PACKAGE_LIST } from '../versions/payloadPackageList.js'
export const info = async () => {
const deps = await getDependencies(process.cwd(), [
...PAYLOAD_PACKAGE_LIST,
'next',
'react',
'react-dom',
])
const formattedDeps = Array.from(deps.resolved.entries()).map(([name, { version }]) => ({
name,
version,
}))
console.log(generateOutput(formattedDeps))
}
function generateOutput(packages: Array<{ name: string; version: string }>) {
const cpuCores = os.cpus().length
const primaryDeps = packages.filter(({ name }) => name === 'payload' || name === 'next')
const otherDeps = packages
.filter(({ name }) => name !== 'payload' && name !== 'next')
.sort((a, b) => a.name.localeCompare(b.name))
const formattedDeps = [...primaryDeps, ...otherDeps]
.map(({ name, version }) => ` ${name}: ${version}`)
.join('\n')
return `
Binaries:
Node: ${process.versions.node}
npm: ${getBinaryVersion('npm')}
Yarn: ${getBinaryVersion('yarn')}
pnpm: ${getBinaryVersion('pnpm')}
Relevant Packages:
${formattedDeps}
Operating System:
Platform: ${os.platform()}
Arch: ${os.arch()}
Version: ${os.version()}
Available memory (MB): ${Math.ceil(os.totalmem() / 1024 / 1024)}
Available CPU cores: ${cpuCores > 0 ? cpuCores : 'N/A'}
`
}
function getBinaryVersion(binaryName: string) {
try {
return execFileSync(binaryName, ['--version']).toString().trim()
} catch {
return 'N/A'
}
}
// Direct execution
if (import.meta.url === `file://${process.argv[1]}`) {
void info()
}

View File

@@ -0,0 +1,59 @@
import type { CustomVersionParser } from './utilities/dependencies/dependencyChecker.js'
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
import { PAYLOAD_PACKAGE_LIST } from './versions/payloadPackageList.js'
const customReactVersionParser: CustomVersionParser = (version) => {
const [mainVersion, ...preReleases] = version.split('-')
if (preReleases?.length === 3) {
// Needs different handling, as it's in a format like 19.0.0-rc-06d0b89e-20240801 format
const date = preReleases[2]
const parts = mainVersion.split('.').map(Number)
return { parts, preReleases: [date] }
}
const parts = mainVersion.split('.').map(Number)
return { parts, preReleases }
}
export async function checkPayloadDependencies() {
const dependencies = [...PAYLOAD_PACKAGE_LIST]
if (process.env.PAYLOAD_CI_DEPENDENCY_CHECKER !== 'true') {
dependencies.push('@payloadcms/plugin-sentry')
}
// First load. First check if there are mismatching dependency versions of payload packages
await checkDependencies({
dependencyGroups: [
{
name: 'payload',
dependencies,
targetVersionDependency: 'payload',
},
{
name: 'react',
dependencies: ['react', 'react-dom'],
targetVersionDependency: 'react',
},
],
dependencyVersions: {
next: {
required: false,
version: '>=15.0.0-canary.104',
},
react: {
customVersionParser: customReactVersionParser,
required: false,
version: '>=19.0.0-rc-06d0b89e-20240801',
},
'react-dom': {
customVersionParser: customReactVersionParser,
required: false,
version: '>=19.0.0-rc-06d0b89e-20240801',
},
},
})
}

View File

@@ -14,6 +14,15 @@ export type ServerOnlyCollectionAdminProperties = keyof Pick<
'hidden' | 'preview'
>
export type ServerOnlyUploadProperties = keyof Pick<
SanitizedCollectionConfig['upload'],
| 'adminThumbnail'
| 'externalFileHeaderFilter'
| 'handlers'
| 'modifyResponseHeaders'
| 'withMetadata'
>
export type ClientCollectionConfig = {
_isPreviewEnabled?: true
admin: {

View File

@@ -57,11 +57,12 @@ import type { TypeWithVersion } from './versions/types.js'
import { decrypt, encrypt } from './auth/crypto.js'
import { APIKeyAuthentication } from './auth/strategies/apiKey.js'
import { JWTAuthentication } from './auth/strategies/jwt.js'
import { checkPayloadDependencies } from './checkPayloadDependencies.js'
import localOperations from './collections/operations/local/index.js'
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
import { fieldAffectsData } from './fields/config/types.js'
import localGlobalOperations from './globals/operations/local/index.js'
import { getDependencies } from './utilities/dependencies/getDependencies.js'
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
import flattenFields from './utilities/flattenTopLevelFields.js'
import { getLogger } from './utilities/logger.js'
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
@@ -430,58 +431,7 @@ export class BasePayload {
process.env.NODE_ENV !== 'production' &&
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
) {
// First load. First check if there are mismatching dependency versions of payload packages
const resolvedDependencies = await getDependencies(dirname, [
'@payloadcms/ui/shared',
'payload',
'@payloadcms/next/utilities',
'@payloadcms/richtext-lexical',
'@payloadcms/richtext-slate',
'@payloadcms/graphql',
'@payloadcms/plugin-cloud',
'@payloadcms/db-mongodb',
'@payloadcms/db-postgres',
'@payloadcms/plugin-form-builder',
'@payloadcms/plugin-nested-docs',
'@payloadcms/plugin-seo',
'@payloadcms/plugin-search',
'@payloadcms/plugin-cloud-storage',
'@payloadcms/plugin-stripe',
'@payloadcms/plugin-zapier',
'@payloadcms/plugin-redirects',
'@payloadcms/plugin-sentry',
'@payloadcms/bundler-webpack',
'@payloadcms/bundler-vite',
'@payloadcms/live-preview',
'@payloadcms/live-preview-react',
'@payloadcms/translations',
'@payloadcms/email-nodemailer',
'@payloadcms/email-resend',
'@payloadcms/storage-azure',
'@payloadcms/storage-s3',
'@payloadcms/storage-gcs',
'@payloadcms/storage-vercel-blob',
'@payloadcms/storage-uploadthing',
])
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
const foundVersions: {
[version: string]: string
} = {}
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
if (!Object.keys(foundVersions).includes(version)) {
foundVersions[version] = _pkg
}
}
if (Object.keys(foundVersions).length > 1) {
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
.map(([version, pkg]) => `${pkg}@${version}`)
.join(', ')
throw new Error(
`Mismatching payload dependency versions found: ${formattedVersionsWithPackageNameString}. All payload and @payloadcms/* packages must have the same version. This is an error with your set-up, caused by you, not a bug in payload. Please go to your package.json and ensure all payload and @payloadcms/* packages have the same version.`,
)
}
await checkPayloadDependencies()
}
this.importMap = options.importMap
@@ -713,6 +663,7 @@ export type { ClientCollectionConfig } from './collections/config/client.js'
export type {
ServerOnlyCollectionAdminProperties,
ServerOnlyCollectionProperties,
ServerOnlyUploadProperties,
} from './collections/config/client.js'
export type {
AfterChangeHook as CollectionAfterChangeHook,
@@ -1047,6 +998,7 @@ export {
deepMergeWithReactComponents,
deepMergeWithSourceArrays,
} from './utilities/deepMerge.js'
export { getDependencies } from './utilities/dependencies/getDependencies.js'
export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'
export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'
@@ -1061,7 +1013,7 @@ export { mapAsync } from './utilities/mapAsync.js'
export { mergeListSearchAndWhere } from './utilities/mergeListSearchAndWhere.js'
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'
export { buildVersionGlobalFields } from './versions/buildGlobalFields.js'
export { getDependencies }
export { checkDependencies }
export { versionDefaults } from './versions/defaults.js'
export { deleteCollectionVersions } from './versions/deleteCollectionVersions.js'
export { enforceMaxVersions } from './versions/enforceMaxVersions.js'

View File

@@ -0,0 +1,113 @@
import path from 'path'
import { fileURLToPath } from 'url'
import { getDependencies } from '../../index.js'
import { compareVersions } from './versionUtils.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export type CustomVersionParser = (version: string) => { parts: number[]; preReleases: string[] }
export type DependencyCheckerArgs = {
/**
* Define dependency groups to ensure that all dependencies within that group are on the same version, and that no dependencies in that group with different versions are found
*/
dependencyGroups?: {
dependencies: string[]
/**
* Name of the dependency group to be displayed in the error message
*/
name: string
targetVersionDependency: string
}[]
/**
* Dependency package names keyed to their required versions. Supports >= (greater or equal than version) as a prefix, or no prefix for the exact version
*/
dependencyVersions?: {
[dependency: string]: {
customVersionParser?: CustomVersionParser
required?: boolean
version?: string
}
}
}
export async function checkDependencies({
dependencyGroups,
dependencyVersions,
}: DependencyCheckerArgs): Promise<void> {
if (dependencyGroups?.length) {
for (const dependencyGroup of dependencyGroups) {
const resolvedDependencies = await getDependencies(dirname, dependencyGroup.dependencies)
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
const foundVersions: {
[version: string]: string
} = {}
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
if (!Object.keys(foundVersions).includes(version)) {
foundVersions[version] = _pkg
}
}
if (Object.keys(foundVersions).length > 1) {
const targetVersion = resolvedDependencies.resolved.get(
dependencyGroup.targetVersionDependency,
)?.version
if (targetVersion) {
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
.filter(([version]) => version !== targetVersion)
.map(([version, pkg]) => `${pkg}@${version} (Please change this to ${targetVersion})`)
.join(', ')
throw new Error(
`Mismatching "${dependencyGroup.name}" dependency versions found: ${formattedVersionsWithPackageNameString}. All "${dependencyGroup.name}" packages must have the same version. This is an error with your set-up, not a bug in Payload. Please go to your package.json and ensure all "${dependencyGroup.name}" packages have the same version.`,
)
} else {
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
.map(([version, pkg]) => `${pkg}@${version}`)
.join(', ')
throw new Error(
`Mismatching "${dependencyGroup.name}" dependency versions found: ${formattedVersionsWithPackageNameString}. All "${dependencyGroup.name}" packages must have the same version. This is an error with your set-up, not a bug in Payload. Please go to your package.json and ensure all "${dependencyGroup.name}" packages have the same version.`,
)
}
}
}
}
if (dependencyVersions && Object.keys(dependencyVersions).length) {
const resolvedDependencies = await getDependencies(dirname, Object.keys(dependencyVersions))
for (const [dependency, settings] of Object.entries(dependencyVersions)) {
const resolvedDependency = resolvedDependencies.resolved.get(dependency)
if (!resolvedDependency) {
if (!settings.required) {
continue
}
throw new Error(`Dependency ${dependency} not found. Please ensure it is installed.`)
}
if (settings.version) {
const settingsVersionToCheck = settings.version.startsWith('>=')
? settings.version.slice(2)
: settings.version
const versionCompareResult = compareVersions(
resolvedDependency.version,
settingsVersionToCheck,
settings.customVersionParser,
)
if (settings.version.startsWith('>=')) {
if (versionCompareResult === 'lower') {
throw new Error(
`Dependency ${dependency} is on version ${resolvedDependency.version}, but ${settings.version} or greater is required. Please update this dependency.`,
)
}
} else if (versionCompareResult === 'lower' || versionCompareResult === 'greater') {
throw new Error(
`Dependency ${dependency} is on version ${resolvedDependency.version}, but ${settings.version} is required. Please update this dependency.`,
)
}
}
}
}
}

View File

@@ -0,0 +1,75 @@
import type { CustomVersionParser } from './dependencyChecker.js'
export function parseVersion(version: string): { parts: number[]; preReleases: string[] } {
const [mainVersion, ...preReleases] = version.split('-')
const parts = mainVersion.split('.').map(Number)
return { parts, preReleases }
}
function extractNumbers(str: string): number[] {
const matches = str.match(/\d+/g) || []
return matches.map(Number)
}
function comparePreRelease(v1: string, v2: string): number {
const num1 = extractNumbers(v1)
const num2 = extractNumbers(v2)
for (let i = 0; i < Math.max(num1.length, num2.length); i++) {
if ((num1[i] || 0) < (num2[i] || 0)) return -1
if ((num1[i] || 0) > (num2[i] || 0)) return 1
}
// If numeric parts are equal, compare the whole string
if (v1 < v2) return -1
if (v1 > v2) return 1
return 0
}
/**
* Compares two semantic version strings, including handling pre-release identifiers.
*
* This function first compares the major, minor, and patch components as integers.
* If these components are equal, it then moves on to compare pre-release versions.
* Pre-release versions are compared first by extracting and comparing any numerical values.
* If numerical values are equal, it compares the whole pre-release string lexicographically.
*
* @param {string} compare - The first version string to compare.
* @param {string} to - The second version string to compare.
* @param {function} [customVersionParser] - An optional function to parse version strings into parts and pre-releases.
* @returns {string} - Returns greater if compare is greater than to, lower if compare is less than to, and equal if they are equal.
*/
export function compareVersions(
compare: string,
to: string,
customVersionParser?: CustomVersionParser,
): 'equal' | 'greater' | 'lower' {
const { parts: parts1, preReleases: preReleases1 } = customVersionParser
? customVersionParser(compare)
: parseVersion(compare)
const { parts: parts2, preReleases: preReleases2 } = customVersionParser
? customVersionParser(to)
: parseVersion(to)
// Compare main version parts
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
if ((parts1[i] || 0) > (parts2[i] || 0)) return 'greater'
if ((parts1[i] || 0) < (parts2[i] || 0)) return 'lower'
}
// Compare pre-release parts if main versions are equal
if (preReleases1?.length || preReleases2?.length) {
for (let i = 0; i < Math.max(preReleases1.length, preReleases2.length); i++) {
if (!preReleases1[i]) return 'greater'
if (!preReleases2[i]) return 'lower'
const result = comparePreRelease(preReleases1[i], preReleases2[i])
if (result !== 0) {
return result === 1 ? 'greater' : 'lower'
}
// Equal => continue for loop to check for next pre-release part
}
}
return 'equal'
}

View File

@@ -0,0 +1,31 @@
export const PAYLOAD_PACKAGE_LIST = [
'payload',
'@payloadcms/bundler-vite',
'@payloadcms/bundler-webpack',
'@payloadcms/db-mongodb',
'@payloadcms/db-postgres',
'@payloadcms/email-nodemailer',
'@payloadcms/email-resend',
'@payloadcms/graphql',
'@payloadcms/live-preview-react',
'@payloadcms/live-preview',
'@payloadcms/next/utilities',
'@payloadcms/plugin-cloud-storage',
'@payloadcms/plugin-cloud',
'@payloadcms/plugin-form-builder',
'@payloadcms/plugin-nested-docs',
'@payloadcms/plugin-redirects',
'@payloadcms/plugin-search',
'@payloadcms/plugin-seo',
'@payloadcms/plugin-stripe',
'@payloadcms/plugin-zapier',
'@payloadcms/richtext-lexical',
'@payloadcms/richtext-slate',
'@payloadcms/storage-azure',
'@payloadcms/storage-gcs',
'@payloadcms/storage-s3',
'@payloadcms/storage-uploadthing',
'@payloadcms/storage-vercel-blob',
'@payloadcms/translations',
'@payloadcms/ui/shared',
]

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-relationship-object-ids",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "A Payload plugin to store all relationship IDs as ObjectIDs",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Search plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "SEO plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,5 @@
import type { Config, GroupField, TabsField, TextField } from 'payload'
import { addDataAndFileToRequest } from '@payloadcms/next/utilities'
import { deepMergeSimple } from 'payload/shared'
import type {
@@ -133,7 +132,11 @@ export const seoPlugin =
...(config.endpoints ?? []),
{
handler: async (req) => {
await addDataAndFileToRequest(req)
const data = await req.json()
if (data) {
req.data = data
}
const result = pluginConfig.generateTitle
? await pluginConfig.generateTitle({
@@ -148,7 +151,11 @@ export const seoPlugin =
},
{
handler: async (req) => {
await addDataAndFileToRequest(req)
const data = await req.json()
if (data) {
req.data = data
}
const result = pluginConfig.generateDescription
? await pluginConfig.generateDescription({
@@ -163,7 +170,11 @@ export const seoPlugin =
},
{
handler: async (req) => {
await addDataAndFileToRequest(req)
const data = await req.json()
if (data) {
req.data = data
}
const result = pluginConfig.generateURL
? await pluginConfig.generateURL({
@@ -178,7 +189,11 @@ export const seoPlugin =
},
{
handler: async (req) => {
await addDataAndFileToRequest(req)
const data = await req.json()
if (data) {
req.data = data
}
const result = pluginConfig.generateImage
? await pluginConfig.generateImage({

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-stripe",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Stripe plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The officially supported Lexical richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -7,7 +7,7 @@ import type {
User,
} from 'payload'
import { validateUrl } from '../../../lexical/utils/url.js'
import { validateUrl, validateUrlMinimal } from '../../../lexical/utils/url.js'
export const getBaseFields = (
config: SanitizedConfig,
@@ -64,10 +64,20 @@ export const getBaseFields = (
{
name: 'url',
type: 'text',
hooks: {
beforeChange: [
({ value }) => {
if (!validateUrl(value)) {
return encodeURIComponent(value)
}
return value
},
],
},
label: ({ t }) => t('fields:enterURL'),
required: true,
validate: (value: string) => {
if (!validateUrl(value)) {
if (!validateUrlMinimal(value)) {
return 'Invalid URL'
}
},

View File

@@ -5,16 +5,14 @@ import type {
SerializedLexicalNode,
} from 'lexical'
import { fileURLToPath } from 'node:url'
import path from 'path'
import {
afterChangeTraverseFields,
afterReadTraverseFields,
beforeChangeTraverseFields,
beforeValidateTraverseFields,
checkDependencies,
deepCopyObject,
deepCopyObjectSimple,
getDependencies,
withNullableJSONSchemaType,
} from 'payload'
@@ -42,46 +40,32 @@ import { richTextValidateHOC } from './validate/index.js'
let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
return async ({ config, isRoot }) => {
if (
process.env.NODE_ENV !== 'production' &&
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
) {
const resolvedDependencies = await getDependencies(dirname, [
'lexical',
'@lexical/headless',
'@lexical/link',
'@lexical/list',
'@lexical/mark',
'@lexical/markdown',
'@lexical/react',
'@lexical/rich-text',
'@lexical/selection',
'@lexical/utils',
])
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
const foundVersions: {
[version: string]: string
} = {}
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
if (!Object.keys(foundVersions).includes(version)) {
foundVersions[version] = _pkg
}
}
if (Object.keys(foundVersions).length > 1) {
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
.map(([version, pkg]) => `${pkg}@${version}`)
.join(', ')
throw new Error(
`Mismatching lexical dependency versions found: ${formattedVersionsWithPackageNameString}. All lexical and @lexical/* packages must have the same version. This is an error with your set-up, caused by you, not a bug in payload. Please go to your package.json and ensure all lexical and @lexical/* packages have the same version.`,
)
}
await checkDependencies({
dependencyGroups: [
{
name: 'lexical',
dependencies: [
'lexical',
'@lexical/headless',
'@lexical/link',
'@lexical/list',
'@lexical/mark',
'@lexical/markdown',
'@lexical/react',
'@lexical/rich-text',
'@lexical/selection',
'@lexical/utils',
],
targetVersionDependency: 'lexical',
},
],
})
}
let features: FeatureProviderServer<any, any, any>[] = []

View File

@@ -25,6 +25,14 @@ const absoluteRegExp =
* */
const relativeOrAnchorRegExp = /^[\w\-./]*(?:#\w[\w-]*)?$/
/**
* Prevents unreasonable URLs from being inserted into the editor.
* @param url
*/
export function validateUrlMinimal(url: string): boolean {
return !url.includes(' ')
}
// Do not keep validateUrl function too loose. This is run when pasting in text, to determine if links are in that text and if it should create AutoLinkNodes.
// This is why we do not allow stuff like anchors here, as we don't want copied anchors to be turned into AutoLinkNodes.
export function validateUrl(url: string): boolean {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "The officially supported Slate richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -63,3 +63,4 @@ export { UnderlineLeafButton } from '../../field/leaves/underline/LeafButton.js'
export { UnderlineLeaf } from '../../field/leaves/underline/Underline/index.js'
export { useLeaf } from '../../field/providers/LeafProvider.js'
export { useSlatePlugin } from '../../utilities/useSlatePlugin.js'

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-azure",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Payload storage adapter for Azure Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-gcs",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Payload storage adapter for Google Cloud Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-s3",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Payload storage adapter for Amazon S3",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-uploadthing",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Payload storage adapter for uploadthing",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-vercel-blob",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"description": "Payload storage adapter for Vercel Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.0.0-beta.91",
"version": "3.0.0-beta.92",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -11,6 +11,7 @@ import type {
SanitizedCollectionConfig,
ServerOnlyCollectionAdminProperties,
ServerOnlyCollectionProperties,
ServerOnlyUploadProperties,
} from 'payload'
import type React from 'react'
@@ -18,6 +19,30 @@ import { deepCopyObjectSimple } from 'payload'
import { createClientFields } from './fields.js'
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
'hooks',
'access',
'endpoints',
'custom',
// `upload`
// `admin`
// are all handled separately
]
const serverOnlyUploadProperties: Partial<ServerOnlyUploadProperties>[] = [
'adminThumbnail',
'externalFileHeaderFilter',
'handlers',
'modifyResponseHeaders',
'withMetadata',
]
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
'hidden',
'preview',
// `livePreview` is handled separately
]
export const createClientCollectionConfig = ({
DefaultEditView,
DefaultListView,
@@ -46,16 +71,6 @@ export const createClientCollectionConfig = ({
payload,
})
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
'hooks',
'access',
'endpoints',
'custom',
// `upload`
// `admin`
// are all handled separately
]
serverOnlyCollectionProperties.forEach((key) => {
if (key in clientCollection) {
delete clientCollection[key]
@@ -63,10 +78,11 @@ export const createClientCollectionConfig = ({
})
if ('upload' in clientCollection && typeof clientCollection.upload === 'object') {
delete clientCollection.upload.handlers
delete clientCollection.upload.adminThumbnail
delete clientCollection.upload.externalFileHeaderFilter
delete clientCollection.upload.withMetadata
serverOnlyUploadProperties.forEach((key) => {
if (key in clientCollection.upload) {
delete clientCollection.upload[key]
}
})
if ('imageSizes' in clientCollection.upload && clientCollection.upload.imageSizes.length) {
clientCollection.upload.imageSizes = clientCollection.upload.imageSizes.map((size) => {
@@ -91,12 +107,6 @@ export const createClientCollectionConfig = ({
})
}
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
'hidden',
'preview',
// `livePreview` is handled separately
]
serverOnlyCollectionAdminProperties.forEach((key) => {
if (key in clientCollection.admin) {
delete clientCollection.admin[key]

View File

@@ -87,13 +87,13 @@ export const updateChangelog = async (args: Args = {}): Promise<ChangelogResult>
// Might need to swap out HEAD for the new proposed version
let changelog = `## [${proposedReleaseVersion}](https://github.com/payloadcms/payload/compare/${fromVersion}...${proposedReleaseVersion}) (${yyyyMMdd})\n\n\n`
if (sections.feat.length) {
changelog += `### Features\n\n${sections.feat.join('\n')}\n\n`
changelog += `### 🚀 Features\n\n${sections.feat.join('\n')}\n\n`
}
if (sections.fix.length) {
changelog += `### Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
changelog += `### 🐛 Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
}
if (sections.breaking.length) {
changelog += `### BREAKING CHANGES\n\n${sections.breaking.join('\n')}\n\n`
changelog += `### ⚠️ BREAKING CHANGES\n\n${sections.breaking.join('\n')}\n\n`
}
if (writeChangelog) {

View File

@@ -18,16 +18,16 @@
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
},
"dependencies": {
"@payloadcms/db-mongodb": "3.0.0-beta.84",
"@payloadcms/live-preview-react": "3.0.0-beta.84",
"@payloadcms/next": "3.0.0-beta.84",
"@payloadcms/plugin-cloud": "3.0.0-beta.84",
"@payloadcms/plugin-form-builder": "3.0.0-beta.84",
"@payloadcms/plugin-nested-docs": "3.0.0-beta.84",
"@payloadcms/plugin-redirects": "3.0.0-beta.84",
"@payloadcms/plugin-seo": "3.0.0-beta.84",
"@payloadcms/richtext-lexical": "3.0.0-beta.84",
"@payloadcms/ui": "3.0.0-beta.84",
"@payloadcms/db-mongodb": "3.0.0-beta.91",
"@payloadcms/live-preview-react": "3.0.0-beta.91",
"@payloadcms/next": "3.0.0-beta.91",
"@payloadcms/plugin-cloud": "3.0.0-beta.91",
"@payloadcms/plugin-form-builder": "3.0.0-beta.91",
"@payloadcms/plugin-nested-docs": "3.0.0-beta.91",
"@payloadcms/plugin-redirects": "3.0.0-beta.91",
"@payloadcms/plugin-seo": "3.0.0-beta.91",
"@payloadcms/richtext-lexical": "3.0.0-beta.91",
"@payloadcms/ui": "3.0.0-beta.91",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0",
@@ -41,7 +41,7 @@
"lexical": "0.17.0",
"lucide-react": "^0.378.0",
"next": "15.0.0-canary.104",
"payload": "3.0.0-beta.84",
"payload": "3.0.0-beta.91",
"payload-admin-bar": "^1.0.6",
"prism-react-renderer": "^2.3.1",
"react": "19.0.0-rc-06d0b89e-20240801",
@@ -56,6 +56,7 @@
"@payloadcms/eslint-config": "^1.1.1",
"@tailwindcss/typography": "^0.5.13",
"@types/escape-html": "^1.0.2",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "18.11.3",
"@types/react": "npm:types-react@19.0.0-rc.0",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",

10230
templates/website/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -41,8 +41,8 @@ export default async function Post({ params: { slug = '' } }) {
<PostHero post={post} />
<div className="flex flex-col gap-4 pt-8">
<div className="container lg:grid lg:grid-cols-[1fr_48rem_1fr] grid-rows-[1fr]">
<div className="flex flex-col items-center gap-4 pt-8">
<div className="container lg:mx-0 lg:grid lg:grid-cols-[1fr_48rem_1fr] grid-rows-[1fr]">
<RichText
className="lg:grid lg:grid-cols-subgrid col-start-1 col-span-3 grid-rows-[1fr]"
content={post.content}
@@ -50,16 +50,22 @@ export default async function Post({ params: { slug = '' } }) {
/>
</div>
<RelatedPosts
className="mt-12"
docs={post.relatedPosts.filter((post) => typeof post === 'object')}
/>
{post.relatedPosts && post.relatedPosts.length > 0 && (
<RelatedPosts
className="mt-12"
docs={post.relatedPosts.filter((post) => typeof post === 'object')}
/>
)}
</div>
</article>
)
}
export async function generateMetadata({ params: { slug } }): Promise<Metadata> {
export async function generateMetadata({
params: { slug },
}: {
params: { slug: string }
}): Promise<Metadata> {
const post = await queryPostBySlug({ slug })
return generateMeta({ doc: post })

View File

@@ -39,7 +39,9 @@ export default async function Page() {
<CollectionArchive posts={posts.docs} />
<div className="container">
{posts.totalPages > 1 && <Pagination page={posts.page} totalPages={posts.totalPages} />}
{posts.totalPages > 1 && posts.page && (
<Pagination page={posts.page} totalPages={posts.totalPages} />
)}
</div>
</div>
)

View File

@@ -40,7 +40,9 @@ export default async function Page({ params: { pageNumber = 2 } }) {
<CollectionArchive posts={posts.docs} />
<div className="container">
{posts.totalPages > 1 && <Pagination page={posts.page} totalPages={posts.totalPages} />}
{posts.totalPages > 1 && posts.page && (
<Pagination page={posts.page} totalPages={posts.totalPages} />
)}
</div>
</div>
)
@@ -60,7 +62,7 @@ export async function generateStaticParams() {
limit: 10,
})
const pages = []
const pages: number[] = []
for (let i = 1; i <= posts.totalPages; i++) {
pages.push(i)

View File

@@ -19,24 +19,23 @@ import { default as default_17 } from 'src/payload/components/BeforeDashboard'
import { default as default_18 } from 'src/payload/components/BeforeLogin'
export const importMap = {
'@payloadcms/richtext-lexical/client#RichTextCell': RichTextCell_0,
'@payloadcms/richtext-lexical/client#RichTextField': RichTextField_1,
'@payloadcms/richtext-lexical/generateComponentMap#getGenerateComponentMap':
getGenerateComponentMap_2,
'@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient': InlineToolbarFeatureClient_3,
'@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient': FixedToolbarFeatureClient_4,
'@payloadcms/richtext-lexical/client#HeadingFeatureClient': HeadingFeatureClient_5,
'@payloadcms/richtext-lexical/client#UnderlineFeatureClient': UnderlineFeatureClient_6,
'@payloadcms/richtext-lexical/client#BoldFeatureClient': BoldFeatureClient_7,
'@payloadcms/richtext-lexical/client#ItalicFeatureClient': ItalicFeatureClient_8,
'@payloadcms/richtext-lexical/client#LinkFeatureClient': LinkFeatureClient_9,
'@payloadcms/plugin-seo/client#OverviewComponent': OverviewComponent_10,
'@payloadcms/plugin-seo/client#MetaTitleComponent': MetaTitleComponent_11,
'@payloadcms/plugin-seo/client#MetaImageComponent': MetaImageComponent_12,
'@payloadcms/plugin-seo/client#MetaDescriptionComponent': MetaDescriptionComponent_13,
'@payloadcms/plugin-seo/client#PreviewComponent': PreviewComponent_14,
'@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient': HorizontalRuleFeatureClient_15,
'@payloadcms/richtext-lexical/client#BlocksFeatureClient': BlocksFeatureClient_16,
'/payload/components/BeforeDashboard#default': default_17,
'/payload/components/BeforeLogin#default': default_18,
"@payloadcms/richtext-lexical/client#RichTextCell": RichTextCell_0,
"@payloadcms/richtext-lexical/client#RichTextField": RichTextField_1,
"@payloadcms/richtext-lexical/generateComponentMap#getGenerateComponentMap": getGenerateComponentMap_2,
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_3,
"@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_4,
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_5,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_6,
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_7,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_8,
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_9,
"@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_10,
"@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_11,
"@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_12,
"@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_13,
"@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_14,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_15,
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_16,
"/payload/components/BeforeDashboard#default": default_17,
"/payload/components/BeforeLogin#default": default_18
}

View File

@@ -14,14 +14,16 @@ export const ArchiveBlock: React.FC<
id?: string
}
> = async (props) => {
const { id, categories, introContent, limit = 3, populateBy, selectedDocs } = props
const { id, categories, introContent, limit: limitFromProps, populateBy, selectedDocs } = props
const limit = limitFromProps || 3
let posts: Post[] = []
if (populateBy === 'collection') {
const payload = await getPayloadHMR({ config: configPromise })
const flattenedCategories = categories.map((category) => {
const flattenedCategories = categories?.map((category) => {
if (typeof category === 'object') return category.id
else return category
})
@@ -43,9 +45,13 @@ export const ArchiveBlock: React.FC<
posts = fetchedPosts.docs
} else {
posts = selectedDocs.map((post) => {
if (typeof post.value === 'object') return post.value
})
if (selectedDocs?.length) {
const filteredSelectedPosts = selectedDocs.map((post) => {
if (typeof post.value === 'object') return post.value
}) as Post[]
posts = filteredSelectedPosts
}
}
return (

View File

@@ -16,7 +16,7 @@ export const CallToActionBlock: React.FC<
<div className="container">
<div className="bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center">
<div className="max-w-[48rem] flex items-center">
<RichText className="" content={richText} enableGutter={false} />
{richText && <RichText className="mb-0" content={richText} enableGutter={false} />}
</div>
<div className="flex flex-col gap-8">
{(links || []).map(({ link }, i) => {

View File

@@ -7,7 +7,9 @@ type Props = {
language?: string
}
export const Code: React.FC<Props> = ({ code, language }) => {
export const Code: React.FC<Props> = ({ code, language = '' }) => {
if (!code) return null
return (
<Highlight code={code} language={language} theme={themes.vsDark}>
{({ getLineProps, getTokenProps, tokens }) => (

View File

@@ -32,12 +32,13 @@ export const ContentBlock: React.FC<
return (
<div
className={cn(`col-span-4 lg:col-span-${colsSpanClasses[size]}`, {
className={cn(`col-span-4 lg:col-span-${colsSpanClasses[size!]}`, {
'md:col-span-2': size !== 'full',
})}
key={index}
>
<RichText content={richText} enableGutter={false} />
{richText && <RichText content={richText} enableGutter={false} />}
{enableLink && <CMSLink {...link} />}
</div>
)

View File

@@ -1,6 +1,8 @@
import type { CheckboxField } from '@payloadcms/plugin-form-builder/types'
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
import { useFormContext } from 'react-hook-form'
import { Checkbox as CheckboxUi } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import React from 'react'
@@ -20,13 +22,19 @@ export const Checkbox: React.FC<
setValue: any
}
> = ({ name, defaultValue, errors, label, register, required: requiredFromProps, width }) => {
const props = register(name, { required: requiredFromProps })
const { setValue } = useFormContext()
return (
<Width width={width}>
<div className="flex items-center gap-2">
<CheckboxUi
defaultChecked={defaultValue}
id={name}
{...register(name, { required: requiredFromProps })}
{...props}
onCheckedChange={(checked) => {
setValue(props.name, checked)
}}
/>
<Label htmlFor={name}>{label}</Label>
</div>

View File

@@ -1,14 +1,12 @@
import type { MessageField } from '@payloadcms/plugin-form-builder/types'
import RichText from '@/components/RichText'
import React from 'react'
import { Width } from '../Width'
export const Message: React.FC<MessageField> = ({ message }) => {
export const Message: React.FC = ({ message }: { message: Record<string, any> }) => {
return (
<Width className="my-12" width="100">
<RichText content={message} />
{message && <RichText content={message} />}
</Width>
)
}

View File

@@ -5,7 +5,7 @@ export const buildInitialFormState = (fields: FormFieldBlock[]) => {
if (field.blockType === 'checkbox') {
return {
...initialSchema,
[field.name]: false,
[field.name]: field.defaultValue,
}
}
if (field.blockType === 'country') {

View File

@@ -3,7 +3,7 @@ import type { Form as FormType } from '@payloadcms/plugin-form-builder/types'
import { useRouter } from 'next/navigation'
import React, { useCallback, useState } from 'react'
import { useForm } from 'react-hook-form'
import { useForm, FormProvider } from 'react-hook-form'
import RichText from 'src/app/components/RichText'
import { Button } from 'src/app/components/ui/button'
@@ -48,10 +48,8 @@ export const FormBlock: React.FC<
const {
control,
formState: { errors },
getValues,
handleSubmit,
register,
setValue,
} = formMethods
const [isLoading, setIsLoading] = useState(false)
@@ -128,44 +126,46 @@ export const FormBlock: React.FC<
return (
<div className="container max-w-[48rem] pb-20">
{enableIntro && introContent && !hasSubmitted && (
<RichText className="mb-8" content={introContent} enableGutter={false} />
)}
{!isLoading && hasSubmitted && confirmationType === 'message' && (
<RichText content={confirmationMessage} />
)}
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
{!hasSubmitted && (
<form id={formID} onSubmit={handleSubmit(onSubmit)}>
<div className="mb-4 last:mb-0">
{formFromProps &&
formFromProps.fields &&
formFromProps.fields?.map((field, index) => {
const Field: React.FC<any> = fields?.[field.blockType]
if (Field) {
return (
<div className="mb-6 last:mb-0" key={index}>
<Field
form={formFromProps}
{...field}
{...formMethods}
control={control}
errors={errors}
register={register}
/>
</div>
)
}
return null
})}
</div>
<FormProvider {...formMethods}>
{enableIntro && introContent && !hasSubmitted && (
<RichText className="mb-8" content={introContent} enableGutter={false} />
)}
{!isLoading && hasSubmitted && confirmationType === 'message' && (
<RichText content={confirmationMessage} />
)}
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
{!hasSubmitted && (
<form id={formID} onSubmit={handleSubmit(onSubmit)}>
<div className="mb-4 last:mb-0">
{formFromProps &&
formFromProps.fields &&
formFromProps.fields?.map((field, index) => {
const Field: React.FC<any> = fields?.[field.blockType]
if (Field) {
return (
<div className="mb-6 last:mb-0" key={index}>
<Field
form={formFromProps}
{...field}
{...formMethods}
control={control}
errors={errors}
register={register}
/>
</div>
)
}
return null
})}
</div>
<Button form={formID} type="submit" variant="default">
{submitButtonLabel}
</Button>
</form>
)}
<Button form={formID} type="submit" variant="default">
{submitButtonLabel}
</Button>
</form>
)}
</FormProvider>
</div>
)
}

View File

@@ -16,6 +16,7 @@ type Props = Extract<Page['layout'][0], { blockType: 'mediaBlock' }> & {
id?: string
imgClassName?: string
staticImage?: StaticImageData
disableInnerContainer?: boolean
}
export const MediaBlock: React.FC<Props> = (props) => {
@@ -27,6 +28,7 @@ export const MediaBlock: React.FC<Props> = (props) => {
media,
position = 'default',
staticImage,
disableInnerContainer,
} = props
let caption
@@ -55,7 +57,7 @@ export const MediaBlock: React.FC<Props> = (props) => {
className={cn(
'mt-6',
{
container: position === 'fullscreen',
container: position === 'fullscreen' && !disableInnerContainer,
},
captionClassName,
)}

View File

@@ -29,7 +29,7 @@ export const Blocks: React.FC<{
return (
<Fragment>
{blocks.map((block, index) => {
const { blockName, blockType } = block
const { blockType } = block
if (blockType && blockType in blockComponents) {
const Block = blockComponents[blockType]
@@ -38,7 +38,7 @@ export const Blocks: React.FC<{
return (
<div className="my-16" key={index}>
{/* @ts-expect-error */}
<Block id={toKebabCase(blockName)} {...block} />
<Block {...block} />
</div>
)
}

View File

@@ -15,7 +15,7 @@ interface HeaderClientProps {
export const HeaderClient: React.FC<HeaderClientProps> = ({ header }) => {
/* Storing the value in a useState to avoid hydration errors */
const [theme, setTheme] = useState(null)
const [theme, setTheme] = useState<string | null>(null)
const { headerTheme, setHeaderTheme } = useHeaderTheme()
const pathname = usePathname()
@@ -25,7 +25,7 @@ export const HeaderClient: React.FC<HeaderClientProps> = ({ header }) => {
}, [pathname])
useEffect(() => {
if (headerTheme !== theme) setTheme(headerTheme)
if (headerTheme && headerTheme !== theme) setTheme(headerTheme)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [headerTheme])

View File

@@ -9,15 +9,15 @@ type CMSLinkType = {
appearance?: 'inline' | ButtonProps['variant']
children?: React.ReactNode
className?: string
label?: string
newTab?: boolean
label?: string | null
newTab?: boolean | null
reference?: {
relationTo: 'pages' | 'posts'
value: Page | Post | string | number
}
size?: ButtonProps['size']
type?: 'custom' | 'reference'
url?: string
} | null
size?: ButtonProps['size'] | null
type?: 'custom' | 'reference' | null
url?: string | null
}
export const CMSLink: React.FC<CMSLinkType> = (props) => {
@@ -48,7 +48,7 @@ export const CMSLink: React.FC<CMSLinkType> = (props) => {
/* Ensure we don't break any styles set by richText */
if (appearance === 'inline') {
return (
<Link className={cn(className)} href={href || url} {...newTabProps}>
<Link className={cn(className)} href={href || url || ''} {...newTabProps}>
{label && label}
{children && children}
</Link>
@@ -57,7 +57,7 @@ export const CMSLink: React.FC<CMSLinkType> = (props) => {
return (
<Button asChild className={className} size={size} variant={appearance}>
<Link className={cn(className)} href={href || url} {...newTabProps}>
<Link className={cn(className)} href={href || url || ''} {...newTabProps}>
{label && label}
{children && children}
</Link>

View File

@@ -6,6 +6,6 @@ import React from 'react'
export const LivePreviewListener: React.FC = () => {
const router = useRouter()
return (
<PayloadLivePreview refresh={router.refresh} serverURL={process.env.NEXT_PUBLIC_SERVER_URL} />
<PayloadLivePreview refresh={router.refresh} serverURL={process.env.NEXT_PUBLIC_SERVER_URL!} />
)
}

View File

@@ -41,8 +41,8 @@ export const ImageMedia: React.FC<MediaProps> = (props) => {
width: fullWidth,
} = resource
width = fullWidth
height = fullHeight
width = fullWidth!
height = fullHeight!
alt = altFromResource
src = `${process.env.NEXT_PUBLIC_SERVER_URL}${url}`

View File

@@ -118,6 +118,7 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
{...block}
captionClassName="mx-auto max-w-[48rem]"
enableGutter={false}
disableInnerContainer={true}
/>
)
case 'banner':

View File

@@ -19,7 +19,7 @@ export const HighImpactHero: React.FC<Page['hero']> = ({ links, media, richText
<div className="relative -mt-[10.4rem] flex items-end text-white" data-theme="dark">
<div className="container mb-8 z-10 relative">
<div className="max-w-[34rem]">
<RichText className="mb-6" content={richText} enableGutter={false} />
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
{Array.isArray(links) && links.length > 0 && (
<ul className="flex gap-4">
{links.map(({ link }, i) => {
@@ -34,7 +34,7 @@ export const HighImpactHero: React.FC<Page['hero']> = ({ links, media, richText
</div>
</div>
<div className="min-h-[80vh] select-none">
{typeof media === 'object' && (
{media && typeof media === 'object' && (
<React.Fragment>
<Media fill imgClassName="-z-10 object-cover" priority resource={media} />
<div className="absolute pointer-events-none left-0 bottom-0 w-full h-1/2 bg-gradient-to-t from-black to-transparent" />

View File

@@ -18,7 +18,7 @@ export const LowImpactHero: React.FC<LowImpactHeroType> = ({ children, richText
return (
<div className="container mt-16">
<div className="max-w-[48rem]">
{children || <RichText content={richText} enableGutter={false} />}
{children || (richText && <RichText content={richText} enableGutter={false} />)}
</div>
</div>
)

View File

@@ -10,7 +10,8 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
return (
<div className="">
<div className="container mb-8">
<RichText className="mb-6" content={richText} enableGutter={false} />
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
{Array.isArray(links) && links.length > 0 && (
<ul className="flex gap-4">
{links.map(({ link }, i) => {
@@ -24,7 +25,7 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
)}
</div>
<div className="container ">
{typeof media === 'object' && (
{media && typeof media === 'object' && (
<div>
<Media
className="-mx-4 md:-mx-8 2xl:-mx-16"

View File

@@ -1,6 +1,8 @@
import jwt from 'jsonwebtoken'
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import configPromise from '@payload-config'
const payloadToken = 'payload-token'
@@ -13,6 +15,7 @@ export async function GET(
}
},
): Promise<Response> {
const payload = await getPayloadHMR({ config: configPromise })
const token = req.cookies.get(payloadToken)?.value
const { searchParams } = new URL(req.url)
const path = searchParams.get('path')
@@ -25,8 +28,15 @@ export async function GET(
new Response('You are not allowed to preview this page', { status: 403 })
}
const user = jwt.decode(token, process.env.PAYLOAD_SECRET)
let user
try {
user = jwt.verify(token, payload.secret)
} catch (error) {
payload.logger.error('Error verifying token for live preview:', error)
}
// You can add additional checks here to see if the user is allowed to preview this page
if (!user) {
draftMode().disable()
return new Response('You are not allowed to preview this page', { status: 403 })

View File

@@ -19,7 +19,7 @@ const initialContext: ContextType = {
const HeaderThemeContext = createContext(initialContext)
export const HeaderThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [headerTheme, setThemeState] = useState<Theme | undefined>(
const [headerTheme, setThemeState] = useState<Theme | undefined | null>(
canUseDOM ? (document.documentElement.getAttribute('data-theme') as Theme) : undefined,
)

View File

@@ -20,7 +20,7 @@ export const generateMeta = async (args: { doc: Page | Post }): Promise<Metadata
return {
description: doc?.meta?.description,
openGraph: mergeOpenGraph({
description: doc?.meta?.description,
description: doc?.meta?.description || '',
images: ogImage
? [
{

View File

@@ -34,8 +34,9 @@ export const getMeUser = async (args?: {
redirect(nullUserRedirect)
}
// Token will exist here because if it doesn't the user will be redirected
return {
token,
token: token!,
user,
}
}

View File

@@ -6,10 +6,10 @@ import { useCallback, useEffect, useRef } from 'react'
type UseClickableCardType<T extends HTMLElement> = {
card: {
ref: RefObject<T>
ref: RefObject<T | null>
}
link: {
ref: RefObject<HTMLAnchorElement>
ref: RefObject<HTMLAnchorElement | null>
}
}

View File

@@ -92,7 +92,7 @@ export interface Page {
id?: string | null;
}[]
| null;
media?: string | Media | null;
media?: (string | null) | Media;
};
layout: (
| {
@@ -231,7 +231,7 @@ export interface Page {
)[];
meta?: {
title?: string | null;
image?: string | Media | null;
image?: (string | null) | Media;
description?: string | null;
};
publishedAt?: string | null;
@@ -319,7 +319,7 @@ export interface Post {
categories?: (string | Category)[] | null;
meta?: {
title?: string | null;
image?: string | Media | null;
image?: (string | null) | Media;
description?: string | null;
};
publishedAt?: string | null;

View File

@@ -41,8 +41,8 @@ const generateTitle: GenerateTitle<Post | Page> = ({ doc }) => {
const generateURL: GenerateURL<Post | Page> = ({ doc }) => {
return doc?.slug
? `${process.env.NEXT_PUBLIC_SERVER_URL}/${doc.slug}`
: process.env.NEXT_PUBLIC_SERVER_URL
? `${process.env.NEXT_PUBLIC_SERVER_URL!}/${doc.slug}`
: process.env.NEXT_PUBLIC_SERVER_URL!
}
export default buildConfig({
@@ -116,7 +116,7 @@ export default buildConfig({
}),
// database-adapter-config-start
db: mongooseAdapter({
url: process.env.DATABASE_URI,
url: process.env.DATABASE_URI!,
}),
// database-adapter-config-end
collections: [Pages, Posts, Media, Categories, Users],
@@ -190,7 +190,7 @@ export default buildConfig({
}),
payloadCloudPlugin(), // storage-adapter-placeholder
],
secret: process.env.PAYLOAD_SECRET,
secret: process.env.PAYLOAD_SECRET!,
sharp,
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),

View File

@@ -5,7 +5,5 @@ import type { User } from '../../payload-types'
type isAuthenticated = (args: AccessArgs<User>) => boolean
export const authenticated: isAuthenticated = ({ req: { user } }) => {
if (user) {
return true
}
return Boolean(user)
}

View File

@@ -1,4 +1,5 @@
import type { CollectionAfterReadHook } from 'payload'
import { User } from 'src/payload-types'
// The `user` collection has access control locked so that users are not publicly accessible
// This means that we need to populate the authors manually here to protect user privacy
@@ -6,7 +7,7 @@ import type { CollectionAfterReadHook } from 'payload'
// So we use an alternative `populatedAuthors` field to populate the user data, hidden from the admin UI
export const populateAuthors: CollectionAfterReadHook = async ({ doc, req, req: { payload } }) => {
if (doc?.authors) {
const authorDocs = []
const authorDocs: User[] = []
for (const author of doc.authors) {
const authorDoc = await payload.findByID({

View File

@@ -13,8 +13,8 @@ const formatSlug =
return format(value)
}
if (operation === 'create') {
const fallbackData = data?.[fallback] || originalDoc?.[fallback]
if (operation === 'create' || !data?.slug) {
const fallbackData = data?.[fallback] || data?.[fallback]
if (fallbackData && typeof fallbackData === 'string') {
return format(fallbackData)

View File

@@ -11,6 +11,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,

View File

@@ -32,7 +32,7 @@ export async function initPayloadE2E({ dirname }: Args): Promise<Result> {
const port = 3000
process.env.PORT = String(port)
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER = 'true'
process.env.PAYLOAD_CI_DEPENDENCY_CHECKER = 'true'
const serverURL = `http://localhost:${port}`

View File

@@ -39,7 +39,7 @@ export async function initPayloadE2ENoConfig<T extends GeneratedTypes<T>>({
const port = 3000
process.env.PORT = String(port)
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER = 'true'
process.env.PAYLOAD_CI_DEPENDENCY_CHECKER = 'true'
const serverURL = `http://localhost:${port}`

View File

@@ -9,7 +9,7 @@ process.env.PAYLOAD_DROP_DATABASE = 'true'
process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER = 's3'
process.env.NODE_OPTIONS = '--no-deprecation'
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER = 'true'
process.env.PAYLOAD_CI_DEPENDENCY_CHECKER = 'true'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)