Compare commits
30 Commits
test/ts-ty
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4ee623907 | ||
|
|
1cb1e5e8b3 | ||
|
|
e0699838e1 | ||
|
|
46f70d9df4 | ||
|
|
b7e2c59622 | ||
|
|
0cc7184023 | ||
|
|
e905675a05 | ||
|
|
4a20a63563 | ||
|
|
8d1fc6e8fb | ||
|
|
62744e79ac | ||
|
|
e8bed7b315 | ||
|
|
f2b8ddb299 | ||
|
|
ffd8ea516d | ||
|
|
3bf09703e9 | ||
|
|
c15d679b65 | ||
|
|
a422a0d568 | ||
|
|
edaeb1e29f | ||
|
|
6f35c356fe | ||
|
|
0b9397399a | ||
|
|
cdcc35ccdb | ||
|
|
442189ec48 | ||
|
|
5d1cc760c9 | ||
|
|
2f90683c7d | ||
|
|
3f5403a52a | ||
|
|
9bccdfd60a | ||
|
|
62666a9897 | ||
|
|
eb27b84854 | ||
|
|
c3480811d3 | ||
|
|
12ba820de4 | ||
|
|
e65b6478c9 |
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -56,6 +56,20 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js fields-relationship",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Fields-Relationship",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js login-with-username",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Login-With-Username",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev plugin-cloud-storage",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -96,7 +96,7 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@libsql/client": "0.6.2",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.53",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.104",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
@@ -131,15 +131,15 @@
|
||||
"lint-staged": "15.2.7",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "15.0.0-canary.53",
|
||||
"next": "15.0.0-canary.104",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"playwright": "1.43.0",
|
||||
"playwright-core": "1.43.0",
|
||||
"prettier": "3.3.2",
|
||||
"prompts": "2.4.2",
|
||||
"react": "^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0-rc-6230622a1a-20240610",
|
||||
"react": "^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0-rc-06d0b89e-20240801",
|
||||
"rimraf": "3.0.2",
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "0.32.6",
|
||||
@@ -153,8 +153,8 @@
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -54,6 +54,10 @@ export const connect: Connect = async function connect(
|
||||
this.payload.logger.info('---- DROPPED DATABASE ----')
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
|
||||
await this.migrate({ migrations: this.prodMigrations })
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
this.payload.logger.error(`Error: cannot connect to MongoDB. Details: ${err.message}`, err)
|
||||
|
||||
@@ -8,7 +8,7 @@ import mongoose from 'mongoose'
|
||||
import path from 'path'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { CollectionModel, GlobalModel } from './types.js'
|
||||
import type { CollectionModel, GlobalModel, MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
@@ -78,6 +78,11 @@ export interface Args {
|
||||
* typed as any to avoid dependency
|
||||
*/
|
||||
mongoMemoryServer?: MongoMemoryReplSet
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
transactionOptions?: TransactionOptions | false
|
||||
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
|
||||
url: false | string
|
||||
@@ -90,6 +95,11 @@ export type MongooseAdapter = {
|
||||
connection: Connection
|
||||
globals: GlobalModel
|
||||
mongoMemoryServer: MongoMemoryReplSet
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
sessions: Record<number | string, ClientSession>
|
||||
versions: {
|
||||
[slug: string]: CollectionModel
|
||||
@@ -107,6 +117,11 @@ declare module 'payload' {
|
||||
connection: Connection
|
||||
globals: GlobalModel
|
||||
mongoMemoryServer: MongoMemoryReplSet
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
sessions: Record<number | string, ClientSession>
|
||||
transactionOptions: TransactionOptions
|
||||
versions: {
|
||||
@@ -121,6 +136,7 @@ export function mongooseAdapter({
|
||||
disableIndexHints = false,
|
||||
migrationDir: migrationDirArg,
|
||||
mongoMemoryServer,
|
||||
prodMigrations,
|
||||
transactionOptions = {},
|
||||
url,
|
||||
}: Args): DatabaseAdapterObj {
|
||||
@@ -167,6 +183,7 @@ export function mongooseAdapter({
|
||||
migrateFresh,
|
||||
migrationDir,
|
||||
payload,
|
||||
prodMigrations,
|
||||
queryDrafts,
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -47,7 +47,7 @@
|
||||
"dependencies": {
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.23.2",
|
||||
"drizzle-kit": "0.23.2-df9e596",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -91,4 +91,8 @@ export const connect: Connect = async function connect(
|
||||
}
|
||||
|
||||
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
|
||||
await this.migrate({ migrations: this.prodMigrations })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { CreateMigration } from 'payload'
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import path from 'path'
|
||||
import { getPredefinedMigration } from 'payload'
|
||||
import { getPredefinedMigration, writeMigrationIndex } from 'payload'
|
||||
import prompts from 'prompts'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -25,7 +25,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/api')
|
||||
const { generateDrizzleJson, generateMigration, upPgSnapshot } = require('drizzle-kit/api')
|
||||
const drizzleJsonAfter = generateDrizzleJson(this.schema)
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
@@ -64,9 +64,11 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
.reverse()?.[0]
|
||||
|
||||
if (latestSnapshot) {
|
||||
drizzleJsonBefore = JSON.parse(
|
||||
fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'),
|
||||
) as DrizzleSnapshotJSON
|
||||
drizzleJsonBefore = JSON.parse(fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'))
|
||||
|
||||
if (drizzleJsonBefore.version < drizzleJsonAfter.version) {
|
||||
drizzleJsonBefore = upPgSnapshot(drizzleJsonBefore)
|
||||
}
|
||||
}
|
||||
|
||||
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||
@@ -113,5 +115,8 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
upSQL: upSQL || ` // Migration code`,
|
||||
}),
|
||||
)
|
||||
|
||||
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
|
||||
|
||||
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
import { type PgSchema, pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
@@ -94,6 +94,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
pgSchema: adapterSchema,
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
prodMigrations: args.prodMigrations,
|
||||
push: args.push,
|
||||
relations: {},
|
||||
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||
|
||||
@@ -33,6 +33,11 @@ export type Args = {
|
||||
logger?: DrizzleConfig['logger']
|
||||
migrationDir?: string
|
||||
pool: PoolConfig
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push?: boolean
|
||||
relationshipsSuffix?: string
|
||||
/**
|
||||
@@ -136,6 +141,11 @@ export type PostgresAdapter = {
|
||||
pgSchema?: Schema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relations: Record<string, GenericRelation>
|
||||
@@ -178,6 +188,11 @@ declare module 'payload' {
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relationshipsSuffix?: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -46,7 +46,7 @@
|
||||
"@libsql/client": "^0.6.2",
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.23.2",
|
||||
"drizzle-kit": "0.23.2-df9e596",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
|
||||
@@ -52,4 +52,8 @@ export const connect: Connect = async function connect(
|
||||
}
|
||||
|
||||
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
|
||||
await this.migrate({ migrations: this.prodMigrations })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { CreateMigration } from 'payload'
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import path from 'path'
|
||||
import { getPredefinedMigration } from 'payload'
|
||||
import { getPredefinedMigration, writeMigrationIndex } from 'payload'
|
||||
import prompts from 'prompts'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -112,5 +112,8 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
upSQL: upSQL || ` // Migration code`,
|
||||
}),
|
||||
)
|
||||
|
||||
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
|
||||
|
||||
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
operators,
|
||||
prodMigrations: args.prodMigrations,
|
||||
push: args.push,
|
||||
relations: {},
|
||||
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AnySQLiteColumn} from 'drizzle-orm/sqlite-core';
|
||||
import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ export type Args = {
|
||||
localesSuffix?: string
|
||||
logger?: DrizzleConfig['logger']
|
||||
migrationDir?: string
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push?: boolean
|
||||
relationshipsSuffix?: string
|
||||
schemaName?: string
|
||||
@@ -100,6 +105,11 @@ export type SQLiteAdapter = {
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
operators: Operators
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relations: Record<string, GenericRelation>
|
||||
@@ -139,6 +149,11 @@ declare module 'payload' {
|
||||
initializing: Promise<void>
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relationshipsSuffix?: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
|
||||
@@ -9,9 +8,12 @@ import type { DrizzleAdapter, Migration } from './types.js'
|
||||
import { migrationTableExists } from './utilities/migrationTableExists.js'
|
||||
import { parseError } from './utilities/parseError.js'
|
||||
|
||||
export async function migrate(this: DrizzleAdapter): Promise<void> {
|
||||
export const migrate: DrizzleAdapter['migrate'] = async function migrate(
|
||||
this: DrizzleAdapter,
|
||||
args,
|
||||
): Promise<void> {
|
||||
const { payload } = this
|
||||
const migrationFiles = await readMigrationFiles({ payload })
|
||||
const migrationFiles = args?.migrations || (await readMigrationFiles({ payload }))
|
||||
|
||||
if (!migrationFiles.length) {
|
||||
payload.logger.info({ msg: 'No migrations to run.' })
|
||||
@@ -64,7 +66,7 @@ export async function migrate(this: DrizzleAdapter): Promise<void> {
|
||||
|
||||
// If already ran, skip
|
||||
if (alreadyRan) {
|
||||
continue
|
||||
continue
|
||||
}
|
||||
|
||||
await runMigrationFile(payload, migration, newBatch)
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function migrateFresh(
|
||||
|
||||
await this.dropDatabase({ adapter: this })
|
||||
|
||||
const migrationFiles = (await readMigrationFiles({ payload })) as Migration[]
|
||||
const migrationFiles = await readMigrationFiles({ payload })
|
||||
payload.logger.debug({
|
||||
msg: `Found ${migrationFiles.length} migration files.`,
|
||||
})
|
||||
|
||||
@@ -133,7 +133,7 @@ export type Migration = {
|
||||
db?: DrizzleTransaction | LibSQLDatabase<Record<string, never>> | PostgresDB
|
||||
payload: Payload
|
||||
req: PayloadRequest
|
||||
}) => Promise<boolean>
|
||||
}) => Promise<void>
|
||||
up: ({
|
||||
db,
|
||||
payload,
|
||||
@@ -142,7 +142,7 @@ export type Migration = {
|
||||
db?: DrizzleTransaction | LibSQLDatabase | PostgresDB
|
||||
payload: Payload
|
||||
req: PayloadRequest
|
||||
}) => Promise<boolean>
|
||||
}) => Promise<void>
|
||||
} & MigrationData
|
||||
|
||||
export type CreateJSONQueryArgs = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -41,8 +41,8 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
|
||||
@@ -39,6 +39,9 @@ export const RefreshRouteOnSave: React.FC<{
|
||||
ready({
|
||||
serverURL,
|
||||
})
|
||||
|
||||
// refresh after the ready message is sent to get the latest data
|
||||
refresh()
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -46,7 +49,7 @@ export const RefreshRouteOnSave: React.FC<{
|
||||
window.removeEventListener('message', onMessage)
|
||||
}
|
||||
}
|
||||
}, [serverURL, onMessage, depth, apiRoute])
|
||||
}, [serverURL, onMessage, depth, apiRoute, refresh])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -94,7 +94,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^16.8.1",
|
||||
"next": "^15.0.0-canary.53",
|
||||
"next": "^15.0.0-canary.104",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,45 +1,103 @@
|
||||
'use client'
|
||||
|
||||
import type { LoginWithUsernameOptions } from 'payload'
|
||||
import type { FieldPermissions, LoginWithUsernameOptions } from 'payload'
|
||||
|
||||
import { EmailField, TextField, useTranslation } from '@payloadcms/ui'
|
||||
import { EmailField, RenderFields, TextField, useTranslation } from '@payloadcms/ui'
|
||||
import { email, username } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
loginWithUsername?: LoginWithUsernameOptions | false
|
||||
}
|
||||
export const EmailAndUsernameFields: React.FC<Props> = ({ loginWithUsername }) => {
|
||||
function EmailFieldComponent(props: Props) {
|
||||
const { loginWithUsername } = props
|
||||
const { t } = useTranslation()
|
||||
|
||||
const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail)
|
||||
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
|
||||
const showEmailField =
|
||||
!loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin
|
||||
|
||||
if (showEmailField) {
|
||||
return (
|
||||
<EmailField
|
||||
autoComplete="off"
|
||||
label={t('general:email')}
|
||||
name="email"
|
||||
path="email"
|
||||
required={requireEmail}
|
||||
validate={email}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function UsernameFieldComponent(props: Props) {
|
||||
const { loginWithUsername } = props
|
||||
const { t } = useTranslation()
|
||||
|
||||
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
|
||||
const showUsernameField = Boolean(loginWithUsername)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{showEmailField && (
|
||||
<EmailField
|
||||
autoComplete="email"
|
||||
label={t('general:email')}
|
||||
name="email"
|
||||
path="email"
|
||||
required={requireEmail}
|
||||
validate={email}
|
||||
/>
|
||||
)}
|
||||
if (showUsernameField) {
|
||||
return (
|
||||
<TextField
|
||||
label={t('authentication:username')}
|
||||
name="username"
|
||||
path="username"
|
||||
required={requireUsername}
|
||||
validate={username}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{showUsernameField && (
|
||||
<TextField
|
||||
label={t('authentication:username')}
|
||||
name="username"
|
||||
path="username"
|
||||
required={requireUsername}
|
||||
validate={username}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
return null
|
||||
}
|
||||
|
||||
type RenderEmailAndUsernameFieldsProps = {
|
||||
className?: string
|
||||
loginWithUsername?: LoginWithUsernameOptions | false
|
||||
operation?: 'create' | 'update'
|
||||
permissions?: {
|
||||
[fieldName: string]: FieldPermissions
|
||||
}
|
||||
readOnly: boolean
|
||||
}
|
||||
export function RenderEmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps) {
|
||||
const { className, loginWithUsername, operation, permissions, readOnly } = props
|
||||
|
||||
return (
|
||||
<RenderFields
|
||||
className={className}
|
||||
fieldMap={[
|
||||
{
|
||||
name: 'email',
|
||||
type: 'text',
|
||||
CustomField: <EmailFieldComponent loginWithUsername={loginWithUsername} />,
|
||||
cellComponentProps: null,
|
||||
fieldComponentProps: { type: 'email', autoComplete: 'off', readOnly },
|
||||
fieldIsPresentational: false,
|
||||
isFieldAffectingData: true,
|
||||
localized: false,
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
type: 'text',
|
||||
CustomField: <UsernameFieldComponent loginWithUsername={loginWithUsername} />,
|
||||
cellComponentProps: null,
|
||||
fieldComponentProps: { type: 'text', readOnly },
|
||||
fieldIsPresentational: false,
|
||||
isFieldAffectingData: true,
|
||||
localized: false,
|
||||
},
|
||||
]}
|
||||
forceRender
|
||||
operation={operation}
|
||||
path=""
|
||||
permissions={permissions}
|
||||
readOnly={readOnly}
|
||||
schemaPath=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { EntityType, formatAdminURL, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import LinkWithDefault from 'next/link.js'
|
||||
import { usePathname } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
const baseClass = 'nav'
|
||||
@@ -21,6 +22,7 @@ const baseClass = 'nav'
|
||||
export const DefaultNavClient: React.FC = () => {
|
||||
const { permissions } = useAuth()
|
||||
const { isEntityVisible } = useEntityVisibility()
|
||||
const pathname = usePathname()
|
||||
|
||||
const {
|
||||
collections,
|
||||
@@ -84,17 +86,11 @@ export const DefaultNavClient: React.FC = () => {
|
||||
LinkWithDefault) as typeof LinkWithDefault.default
|
||||
|
||||
const LinkElement = Link || 'a'
|
||||
|
||||
const activeCollection = window?.location?.pathname
|
||||
?.split('/')
|
||||
.find(
|
||||
(_, index, arr) =>
|
||||
arr[index - 1] === 'collections' || arr[index - 1] === 'globals',
|
||||
)
|
||||
const activeCollection = pathname.endsWith(href)
|
||||
|
||||
return (
|
||||
<LinkElement
|
||||
className={[`${baseClass}__link`, activeCollection === entity?.slug && `active`]
|
||||
className={[`${baseClass}__link`, activeCollection && `active`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
href={href}
|
||||
@@ -102,9 +98,11 @@ export const DefaultNavClient: React.FC = () => {
|
||||
key={i}
|
||||
tabIndex={!navOpen ? -1 : undefined}
|
||||
>
|
||||
<span className={`${baseClass}__link-icon`}>
|
||||
<ChevronIcon direction="right" />
|
||||
</span>
|
||||
{activeCollection && (
|
||||
<span className={`${baseClass}__link-icon`}>
|
||||
<ChevronIcon direction="right" />
|
||||
</span>
|
||||
)}
|
||||
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
|
||||
</LinkElement>
|
||||
)
|
||||
|
||||
@@ -110,16 +110,9 @@
|
||||
&__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.active {
|
||||
.nav__link-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__link-icon {
|
||||
display: none;
|
||||
margin-right: calc(var(--base) * 0.25);
|
||||
top: -1px;
|
||||
position: relative;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { AcceptedLanguages, I18nClient } from '@payloadcms/translations'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
import type { PayloadRequest, SanitizedConfig } from 'payload'
|
||||
|
||||
import { initI18n, rtlLanguages } from '@payloadcms/translations'
|
||||
import { RootProvider } from '@payloadcms/ui'
|
||||
import '@payloadcms/ui/scss/app.scss'
|
||||
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
|
||||
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
|
||||
import { createClientConfig, parseCookies } from 'payload'
|
||||
import { createClientConfig, createLocalReq, parseCookies } from 'payload'
|
||||
import * as qs from 'qs-esm'
|
||||
import React from 'react'
|
||||
|
||||
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
|
||||
@@ -52,6 +53,20 @@ export const RootLayout = async ({
|
||||
language: languageCode,
|
||||
})
|
||||
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n,
|
||||
url: `${payload.config.serverURL}`,
|
||||
} as PayloadRequest,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
const { permissions, user } = await payload.auth({ headers, req })
|
||||
|
||||
const clientConfig = await createClientConfig({ config, t: i18n.t })
|
||||
|
||||
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
|
||||
@@ -100,9 +115,11 @@ export const RootLayout = async ({
|
||||
fallbackLang={clientConfig.i18n.fallbackLanguage}
|
||||
languageCode={languageCode}
|
||||
languageOptions={languageOptions}
|
||||
permissions={permissions}
|
||||
switchLanguageServerAction={switchLanguageServerAction}
|
||||
theme={theme}
|
||||
translations={i18n.translations}
|
||||
user={user}
|
||||
>
|
||||
{wrappedChildren}
|
||||
</RootProvider>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Metadata } from 'next'
|
||||
import type { Icon } from 'next/dist/lib/metadata/types/metadata-types.js'
|
||||
import type { MetaConfig } from 'payload'
|
||||
import type { IconConfig, MetaConfig } from 'payload'
|
||||
|
||||
import { payloadFaviconDark, payloadFaviconLight, staticOGImage } from '@payloadcms/ui/assets'
|
||||
import * as qs from 'qs-esm'
|
||||
@@ -24,7 +23,7 @@ export const meta = async (args: { serverURL: string } & MetaConfig): Promise<an
|
||||
titleSuffix,
|
||||
} = args
|
||||
|
||||
const payloadIcons: Icon[] = [
|
||||
const payloadIcons: IconConfig[] = [
|
||||
{
|
||||
type: 'image/png',
|
||||
rel: 'icon',
|
||||
@@ -40,10 +39,10 @@ export const meta = async (args: { serverURL: string } & MetaConfig): Promise<an
|
||||
},
|
||||
]
|
||||
|
||||
let icons = customIcons ?? payloadIcons // TODO: fix this type assertion
|
||||
let icons = payloadIcons
|
||||
|
||||
if (customIcons && typeof customIcons === 'object' && Array.isArray(customIcons)) {
|
||||
icons = payloadIcons.concat(customIcons) // TODO: fix this type assertion
|
||||
icons = customIcons
|
||||
}
|
||||
|
||||
const metaTitle = `${title} ${titleSuffix}`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AdminViewProps, ServerSideEditViewProps } from 'payload'
|
||||
|
||||
import { DocumentInfoProvider, HydrateClientUser } from '@payloadcms/ui'
|
||||
import { DocumentInfoProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { RenderCustomComponent } from '@payloadcms/ui/shared'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
@@ -82,7 +82,7 @@ export const Account: React.FC<AdminViewProps> = async ({
|
||||
i18n={i18n}
|
||||
permissions={permissions}
|
||||
/>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomAccountComponent === 'function' ? CustomAccountComponent : undefined
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { getFormState } from '@payloadcms/ui/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { EmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
|
||||
import { RenderEmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
|
||||
|
||||
export const CreateFirstUserClient: React.FC<{
|
||||
initialState: FormState
|
||||
@@ -57,8 +57,14 @@ export const CreateFirstUserClient: React.FC<{
|
||||
redirect={admin}
|
||||
validationOperation="create"
|
||||
>
|
||||
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
|
||||
<RenderEmailAndUsernameFields
|
||||
className="emailAndUsername"
|
||||
loginWithUsername={loginWithUsername}
|
||||
operation="create"
|
||||
readOnly={false}
|
||||
/>
|
||||
<PasswordField
|
||||
autoComplete="off"
|
||||
label={t('authentication:newPassword')}
|
||||
name="password"
|
||||
path="password"
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
}
|
||||
|
||||
.emailAndUsername {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { EntityToGroup } from '@payloadcms/ui/shared'
|
||||
import type { AdminViewProps } from 'payload'
|
||||
|
||||
import { HydrateClientUser } from '@payloadcms/ui'
|
||||
import { HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { EntityType, RenderCustomComponent, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React, { Fragment } from 'react'
|
||||
@@ -79,7 +79,7 @@ export const Dashboard: React.FC<AdminViewProps> = ({ initPageResult, params, se
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { AdminViewComponent, AdminViewProps, EditViewComponent } from 'payload'
|
||||
|
||||
import { DocumentInfoProvider, EditDepthProvider, HydrateClientUser } from '@payloadcms/ui'
|
||||
import { RenderCustomComponent, formatAdminURL , isEditing as getIsEditing } from '@payloadcms/ui/shared'
|
||||
import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import {
|
||||
RenderCustomComponent,
|
||||
formatAdminURL,
|
||||
isEditing as getIsEditing,
|
||||
} from '@payloadcms/ui/shared'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
|
||||
@@ -208,7 +212,15 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
permissions={permissions}
|
||||
/>
|
||||
)}
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
{/**
|
||||
* After bumping the Next.js canary to 104, and React to 19.0.0-rc-06d0b89e-20240801" we have to deepCopy the permissions object (https://github.com/payloadcms/payload/pull/7541).
|
||||
* If both HydrateClientUser and RenderCustomComponent receive the same permissions object (same object reference), we get a
|
||||
* "TypeError: Cannot read properties of undefined (reading '$$typeof')" error when loading up some version views - for example a versions
|
||||
* view in the draft-posts collection of the versions test suite. RenderCustomComponent is what renders the versions view.
|
||||
*
|
||||
* // TODO: Revisit this in the future and figure out why this is happening. Might be a React/Next.js bug. We don't know why it happens, and a future React/Next version might unbreak this (keep an eye on this and remove deepCopyObjectSimple if that's the case)
|
||||
*/}
|
||||
<EditDepthProvider
|
||||
depth={1}
|
||||
key={`${collectionSlug || globalSlug}${locale?.code ? `-${locale?.code}` : ''}`}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { toast } from 'sonner'
|
||||
|
||||
import type { Props } from './types.js'
|
||||
|
||||
import { EmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
|
||||
import { RenderEmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
|
||||
import { APIKey } from './APIKey.js'
|
||||
import './index.scss'
|
||||
|
||||
@@ -47,7 +47,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
const dispatchFields = useFormFields((reducer) => reducer[1])
|
||||
const modified = useFormModified()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { isInitializing } = useDocumentInfo()
|
||||
const { docPermissions, isInitializing } = useDocumentInfo()
|
||||
|
||||
const {
|
||||
routes: { api },
|
||||
@@ -138,7 +138,12 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')}>
|
||||
{!disableLocalStrategy && (
|
||||
<React.Fragment>
|
||||
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
|
||||
<RenderEmailAndUsernameFields
|
||||
loginWithUsername={loginWithUsername}
|
||||
operation={operation}
|
||||
permissions={docPermissions?.fields}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
{(showPasswordFields || requirePassword) && (
|
||||
<div className={`${baseClass}__changing-password`}>
|
||||
<PasswordField
|
||||
|
||||
@@ -103,7 +103,15 @@ export const DefaultEditView: React.FC = () => {
|
||||
const classes = [baseClass, id && `${baseClass}--is-editing`].filter(Boolean).join(' ')
|
||||
|
||||
const [schemaPath, setSchemaPath] = React.useState(entitySlug)
|
||||
const [validateBeforeSubmit, setValidateBeforeSubmit] = useState(false)
|
||||
const [validateBeforeSubmit, setValidateBeforeSubmit] = useState(() => {
|
||||
if (
|
||||
operation === 'create' &&
|
||||
collectionConfig.auth &&
|
||||
!collectionConfig.auth.disableLocalStrategy
|
||||
)
|
||||
return true
|
||||
return false
|
||||
})
|
||||
|
||||
const onSave = useCallback(
|
||||
(json) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdminViewProps, Where } from 'payload'
|
||||
|
||||
import {
|
||||
HydrateClientUser,
|
||||
HydrateAuthProvider,
|
||||
ListInfoProvider,
|
||||
ListQueryProvider,
|
||||
TableColumnsProvider,
|
||||
@@ -57,9 +57,23 @@ export const ListView: React.FC<AdminViewProps> = async ({
|
||||
req,
|
||||
user,
|
||||
where: {
|
||||
key: {
|
||||
equals: preferenceKey,
|
||||
},
|
||||
and: [
|
||||
{
|
||||
key: {
|
||||
equals: preferenceKey,
|
||||
},
|
||||
},
|
||||
{
|
||||
'user.relationTo': {
|
||||
equals: user.collection,
|
||||
},
|
||||
},
|
||||
{
|
||||
'user.value': {
|
||||
equals: user?.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
?.then((res) => res?.docs?.[0]?.value)) as ListPreferences
|
||||
@@ -124,7 +138,7 @@ export const ListView: React.FC<AdminViewProps> = async ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<ListInfoProvider
|
||||
collectionConfig={createClientCollectionConfig({
|
||||
collection: collectionConfig,
|
||||
|
||||
@@ -45,13 +45,10 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
|
||||
path="username"
|
||||
required={required}
|
||||
validate={(value, options) => {
|
||||
const passesUsername = username(
|
||||
value,
|
||||
options as ValidateOptions<any, { email?: string }, any, string>,
|
||||
)
|
||||
const passesUsername = username(value, options)
|
||||
const passesEmail = email(
|
||||
value,
|
||||
options as ValidateOptions<any, { username?: string }, any, string>,
|
||||
options as ValidateOptions<any, { username?: string }, any, any>,
|
||||
)
|
||||
|
||||
if (!passesEmail && !passesUsername) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { I18n } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type { AdminViewComponent, SanitizedConfig } from 'payload'
|
||||
|
||||
import { HydrateClientUser } from '@payloadcms/ui'
|
||||
import { HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
@@ -58,21 +58,18 @@ export const NotFoundPage = async ({
|
||||
})
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={initPageResult.permissions} user={initPageResult.req.user} />
|
||||
<DefaultTemplate
|
||||
i18n={initPageResult.req.i18n}
|
||||
locale={initPageResult.locale}
|
||||
params={params}
|
||||
payload={initPageResult.req.payload}
|
||||
permissions={initPageResult.permissions}
|
||||
searchParams={searchParams}
|
||||
user={initPageResult.req.user}
|
||||
visibleEntities={initPageResult.visibleEntities}
|
||||
>
|
||||
<NotFoundClient />
|
||||
</DefaultTemplate>
|
||||
</Fragment>
|
||||
<DefaultTemplate
|
||||
i18n={initPageResult.req.i18n}
|
||||
locale={initPageResult.locale}
|
||||
params={params}
|
||||
payload={initPageResult.req.payload}
|
||||
permissions={initPageResult.permissions}
|
||||
searchParams={searchParams}
|
||||
user={initPageResult.req.user}
|
||||
visibleEntities={initPageResult.visibleEntities}
|
||||
>
|
||||
<NotFoundClient />
|
||||
</DefaultTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,13 @@ const generateLabelFromValue = (
|
||||
locale: string,
|
||||
value: { relationTo: string; value: RelationshipValue } | RelationshipValue,
|
||||
): string => {
|
||||
let relation: string
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((v) => generateLabelFromValue(collections, field, locale, v))
|
||||
.filter(Boolean) // Filters out any undefined or empty values
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
let relatedDoc: RelationshipValue
|
||||
let valueToReturn = '' as any
|
||||
|
||||
@@ -37,17 +43,20 @@ const generateLabelFromValue = (
|
||||
return String(value)
|
||||
}
|
||||
|
||||
if (Array.isArray(relationTo)) {
|
||||
if (typeof value === 'object') {
|
||||
relation = value.relationTo
|
||||
relatedDoc = value.value
|
||||
}
|
||||
if (typeof value === 'object' && 'relationTo' in value) {
|
||||
relatedDoc = value.value
|
||||
} else {
|
||||
relation = relationTo
|
||||
// Non-polymorphic relationship
|
||||
relatedDoc = value
|
||||
}
|
||||
|
||||
const relatedCollection = collections.find((c) => c.slug === relation)
|
||||
const relatedCollection = relationTo
|
||||
? collections.find(
|
||||
(c) =>
|
||||
c.slug ===
|
||||
(typeof value === 'object' && 'relationTo' in value ? value.relationTo : relationTo),
|
||||
)
|
||||
: null
|
||||
|
||||
if (relatedCollection) {
|
||||
const useAsTitle = relatedCollection?.admin?.useAsTitle
|
||||
@@ -56,45 +65,65 @@ const generateLabelFromValue = (
|
||||
)
|
||||
let titleFieldIsLocalized = false
|
||||
|
||||
if (useAsTitleField && fieldAffectsData(useAsTitleField))
|
||||
if (useAsTitleField && fieldAffectsData(useAsTitleField)) {
|
||||
titleFieldIsLocalized = useAsTitleField.localized
|
||||
}
|
||||
|
||||
if (typeof relatedDoc?.[useAsTitle] !== 'undefined') {
|
||||
valueToReturn = relatedDoc[useAsTitle]
|
||||
} else if (typeof relatedDoc?.id !== 'undefined') {
|
||||
valueToReturn = relatedDoc.id
|
||||
} else {
|
||||
valueToReturn = relatedDoc
|
||||
}
|
||||
|
||||
if (typeof valueToReturn === 'object' && titleFieldIsLocalized) {
|
||||
valueToReturn = valueToReturn[locale]
|
||||
}
|
||||
} else if (relatedDoc) {
|
||||
// Handle non-polymorphic `hasMany` relationships or fallback
|
||||
if (typeof relatedDoc?.id !== 'undefined') {
|
||||
valueToReturn = relatedDoc.id
|
||||
} else {
|
||||
valueToReturn = relatedDoc
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof valueToReturn === 'object' && valueToReturn !== null) {
|
||||
valueToReturn = JSON.stringify(valueToReturn)
|
||||
}
|
||||
|
||||
return valueToReturn
|
||||
}
|
||||
|
||||
const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, version }) => {
|
||||
let placeholder = ''
|
||||
const placeholder = `[${i18n.t('general:noValue')}]`
|
||||
|
||||
const { collections } = useConfig()
|
||||
|
||||
if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]`
|
||||
let versionToRender: string | undefined = placeholder
|
||||
let comparisonToRender: string | undefined = placeholder
|
||||
|
||||
let versionToRender = version
|
||||
let comparisonToRender = comparison
|
||||
if (version) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(version)) {
|
||||
versionToRender =
|
||||
version.map((val) => generateLabelFromValue(collections, field, locale, val)).join(', ') ||
|
||||
placeholder
|
||||
} else {
|
||||
versionToRender = generateLabelFromValue(collections, field, locale, version) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
if ('hasMany' in field && field.hasMany) {
|
||||
if (Array.isArray(version))
|
||||
versionToRender = version
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ')
|
||||
if (Array.isArray(comparison))
|
||||
comparisonToRender = comparison
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ')
|
||||
} else {
|
||||
versionToRender = generateLabelFromValue(collections, field, locale, version)
|
||||
comparisonToRender = generateLabelFromValue(collections, field, locale, comparison)
|
||||
if (comparison) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(comparison)) {
|
||||
comparisonToRender =
|
||||
comparison
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ') || placeholder
|
||||
} else {
|
||||
comparisonToRender =
|
||||
generateLabelFromValue(collections, field, locale, comparison) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
const label =
|
||||
@@ -112,10 +141,8 @@ const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, versio
|
||||
</Label>
|
||||
<ReactDiffViewer
|
||||
hideLineNumbers
|
||||
newValue={typeof versionToRender !== 'undefined' ? String(versionToRender) : placeholder}
|
||||
oldValue={
|
||||
typeof comparisonToRender !== 'undefined' ? String(comparisonToRender) : placeholder
|
||||
}
|
||||
newValue={versionToRender}
|
||||
oldValue={comparisonToRender}
|
||||
showDiffOnly={false}
|
||||
splitView
|
||||
styles={diffStyles}
|
||||
|
||||
@@ -18,12 +18,12 @@ export default {
|
||||
number: Text,
|
||||
point: Text,
|
||||
radio: Select,
|
||||
relationship: null,
|
||||
relationship: Relationship,
|
||||
richText: Text,
|
||||
row: Nested,
|
||||
select: Select,
|
||||
tabs: Tabs,
|
||||
text: Text,
|
||||
textarea: Text,
|
||||
upload: null,
|
||||
upload: Relationship,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type {
|
||||
CollectionPermission,
|
||||
Document,
|
||||
EditViewComponent,
|
||||
GlobalPermission,
|
||||
OptionObject,
|
||||
} from 'payload'
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import {
|
||||
type CollectionPermission,
|
||||
type Document,
|
||||
type EditViewComponent,
|
||||
type GlobalPermission,
|
||||
type OptionObject,
|
||||
deepCopyObjectSimple,
|
||||
} from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { getLatestVersion } from '../Versions/getLatestVersion.js'
|
||||
@@ -55,8 +55,18 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (collectionConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, slug, 'draft', 'collection')
|
||||
latestPublishedVersion = await getLatestVersion(payload, slug, 'published', 'collection')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
@@ -80,8 +90,18 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (globalConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, slug, 'draft', 'global')
|
||||
latestPublishedVersion = await getLatestVersion(payload, slug, 'published', 'global')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
@@ -116,7 +136,14 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
return (
|
||||
<DefaultVersionView
|
||||
doc={doc}
|
||||
docPermissions={docPermissions}
|
||||
/**
|
||||
* After bumping the Next.js canary to 104, and React to 19.0.0-rc-06d0b89e-20240801" we have to deepCopy the permissions object (https://github.com/payloadcms/payload/pull/7541).
|
||||
* If both HydrateClientUser and RenderCustomComponent receive the same permissions object (same object reference), we get a
|
||||
* "TypeError: Cannot read properties of undefined (reading '$$typeof')" error
|
||||
*
|
||||
* // TODO: Revisit this in the future and figure out why this is happening. Might be a React/Next.js bug. We don't know why it happens, and a future React/Next version might unbreak this (keep an eye on this and remove deepCopyObjectSimple if that's the case)
|
||||
*/
|
||||
docPermissions={deepCopyObjectSimple(docPermissions)}
|
||||
initialComparisonDoc={latestVersion}
|
||||
latestDraftVersion={latestDraftVersion?.id}
|
||||
latestPublishedVersion={latestPublishedVersion?.id}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
export async function getLatestVersion(payload, slug, status, type = 'collection') {
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
type ReturnType = {
|
||||
id: string
|
||||
updatedAt: string
|
||||
} | null
|
||||
|
||||
type Args = {
|
||||
payload: Payload
|
||||
slug: string
|
||||
status: 'draft' | 'published'
|
||||
type: 'collection' | 'global'
|
||||
}
|
||||
export async function getLatestVersion(args: Args): Promise<ReturnType> {
|
||||
const { slug, type = 'collection', payload, status } = args
|
||||
|
||||
try {
|
||||
const sharedOptions = {
|
||||
depth: 0,
|
||||
@@ -22,11 +37,16 @@ export async function getLatestVersion(payload, slug, status, type = 'collection
|
||||
...sharedOptions,
|
||||
})
|
||||
|
||||
if (!response.docs.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: response.docs[0].id,
|
||||
updatedAt: response.docs[0].updatedAt,
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,18 @@ export const VersionsView: EditViewComponent = async (props) => {
|
||||
},
|
||||
})
|
||||
if (collectionConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, collectionSlug, 'draft', 'collection')
|
||||
latestPublishedVersion = await getLatestVersion(
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
collectionSlug,
|
||||
'published',
|
||||
'collection',
|
||||
)
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
@@ -90,8 +95,18 @@ export const VersionsView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (globalConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, globalSlug, 'draft', 'global')
|
||||
latestPublishedVersion = await getLatestVersion(payload, globalSlug, 'published', 'global')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug: globalSlug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug: globalSlug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
|
||||
@@ -18,21 +18,20 @@ export const withPayload = (nextConfig = {}) => {
|
||||
env: {
|
||||
...(nextConfig?.env || {}),
|
||||
},
|
||||
outputFileTracingExcludes: {
|
||||
...(nextConfig?.outputFileTracingExcludes || {}),
|
||||
'**/*': [
|
||||
...(nextConfig?.outputFileTracingExcludes?.['**/*'] || []),
|
||||
'drizzle-kit',
|
||||
'drizzle-kit/api',
|
||||
],
|
||||
},
|
||||
outputFileTracingIncludes: {
|
||||
...(nextConfig?.outputFileTracingIncludes || {}),
|
||||
'**/*': [...(nextConfig?.outputFileTracingIncludes?.['**/*'] || []), '@libsql/client'],
|
||||
},
|
||||
experimental: {
|
||||
...(nextConfig?.experimental || {}),
|
||||
outputFileTracingExcludes: {
|
||||
'**/*': [
|
||||
...(nextConfig.experimental?.outputFileTracingExcludes?.['**/*'] || []),
|
||||
'drizzle-kit',
|
||||
'drizzle-kit/api',
|
||||
],
|
||||
},
|
||||
outputFileTracingIncludes: {
|
||||
'**/*': [
|
||||
...(nextConfig.experimental?.outputFileTracingIncludes?.['**/*'] || []),
|
||||
'@libsql/client',
|
||||
],
|
||||
},
|
||||
turbo: {
|
||||
...(nextConfig?.experimental?.turbo || {}),
|
||||
resolveAlias: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
@@ -84,7 +84,7 @@
|
||||
"pretest": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/env": "^15.0.0-canary.53",
|
||||
"@next/env": "^15.0.0-canary.104",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@swc-node/core": "1.13.1",
|
||||
"@swc-node/sourcemap-support": "0.5.0",
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { SanitizedCollectionConfig, TypeWithID } from '../collections/confi
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
import type { Field, FieldAffectingData, RichTextField, Validate } from '../fields/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../types/index.js'
|
||||
import type { JsonObject, Payload, PayloadRequest, RequestContext } from '../types/index.js'
|
||||
import type { WithServerSidePropsComponentProps } from './elements/WithServerSideProps.js'
|
||||
|
||||
export type RichTextFieldProps<Value extends object, AdapterProps, ExtraFieldProperties = {}> = {
|
||||
@@ -189,6 +189,7 @@ type RichTextAdapterBase<
|
||||
WithServerSideProps: React.FC<Omit<WithServerSidePropsComponentProps, 'serverOnlyProps'>>
|
||||
config: SanitizedConfig
|
||||
i18n: I18nClient
|
||||
payload: Payload
|
||||
schemaPath: string
|
||||
}) => Map<string, React.ReactNode>
|
||||
generateSchemaMap?: (args: {
|
||||
|
||||
@@ -15,7 +15,7 @@ export type ArrayFieldProps = {
|
||||
name?: string
|
||||
validate?: ArrayFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type ArrayFieldLabelComponent = LabelComponent<'array'>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export type BlocksFieldProps = {
|
||||
slug?: string
|
||||
validate?: BlockFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type ReducedBlock = {
|
||||
LabelComponent: Block['admin']['components']['Label']
|
||||
|
||||
@@ -12,7 +12,7 @@ export type CheckboxFieldProps = {
|
||||
path?: string
|
||||
validate?: CheckboxFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type CheckboxFieldLabelComponent = LabelComponent<'checkbox'>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export type CodeFieldProps = {
|
||||
path?: string
|
||||
validate?: CodeFieldValidation
|
||||
width: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type CodeFieldLabelComponent = LabelComponent<'code'>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export type DateFieldProps = {
|
||||
placeholder?: DateField['admin']['placeholder'] | string
|
||||
validate?: DateFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type DateFieldLabelComponent = LabelComponent<'date'>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export type EmailFieldProps = {
|
||||
placeholder?: EmailField['admin']['placeholder']
|
||||
validate?: EmailFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type EmailFieldLabelComponent = LabelComponent<'email'>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export type JSONFieldProps = {
|
||||
path?: string
|
||||
validate?: JSONFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type JSONFieldLabelComponent = LabelComponent<'json'>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export type NumberFieldProps = {
|
||||
step?: number
|
||||
validate?: NumberFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type NumberFieldLabelComponent = LabelComponent<'number'>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export type PointFieldProps = {
|
||||
step?: number
|
||||
validate?: PointFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type PointFieldLabelComponent = LabelComponent<'point'>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export type RadioFieldProps = {
|
||||
validate?: RadioFieldValidation
|
||||
value?: string
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type OnChange<T = string> = (value: T) => void
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export type RelationshipFieldProps = {
|
||||
sortOptions?: RelationshipField['admin']['sortOptions']
|
||||
validate?: RelationshipFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type RelationshipFieldLabelComponent = LabelComponent<'relationship'>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export type RichTextComponentProps = {
|
||||
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
|
||||
validate?: RichTextFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type RichTextFieldLabelComponent = LabelComponent<'richText'>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export type SelectFieldProps = {
|
||||
validate?: SelectFieldValidation
|
||||
value?: string
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type SelectFieldLabelComponent = LabelComponent<'select'>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export type TextFieldProps = {
|
||||
placeholder?: TextField['admin']['placeholder']
|
||||
validate?: TextFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type TextFieldLabelComponent = LabelComponent<'text'>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export type TextareaFieldProps = {
|
||||
rows?: number
|
||||
validate?: TextareaFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type TextareaFieldLabelComponent = LabelComponent<'textarea'>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export type UploadFieldProps = {
|
||||
relationTo?: UploadField['relationTo']
|
||||
validate?: UploadFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type UploadFieldLabelComponent = LabelComponent<'upload'>
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import fs from 'fs'
|
||||
|
||||
import type { CreateMigration } from '../types.js'
|
||||
|
||||
import { writeMigrationIndex } from '../../index.js'
|
||||
import { migrationTemplate } from './migrationTemplate.js'
|
||||
|
||||
export const createMigration: CreateMigration = async function createMigration({
|
||||
export const createMigration: CreateMigration = function createMigration({
|
||||
migrationName,
|
||||
payload,
|
||||
}) {
|
||||
@@ -23,5 +24,8 @@ export const createMigration: CreateMigration = async function createMigration({
|
||||
const fileName = `${timestamp}_${formattedName}.ts`
|
||||
const filePath = `${dir}/${fileName}`
|
||||
fs.writeFileSync(filePath, migrationTemplate)
|
||||
|
||||
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
|
||||
|
||||
payload.logger.info({ msg: `Migration created at ${filePath}` })
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
import { getMigrations } from './getMigrations.js'
|
||||
import { readMigrationFiles } from './readMigrationFiles.js'
|
||||
|
||||
export async function migrate(this: BaseDatabaseAdapter): Promise<void> {
|
||||
export const migrate: BaseDatabaseAdapter['migrate'] = async function migrate(
|
||||
this: BaseDatabaseAdapter,
|
||||
args,
|
||||
): Promise<void> {
|
||||
const { payload } = this
|
||||
const migrationFiles = await readMigrationFiles({ payload })
|
||||
const migrationFiles = args?.migrations || (await readMigrationFiles({ payload }))
|
||||
const { existingMigrations, latestBatch } = await getMigrations({ payload })
|
||||
|
||||
const newBatch = latestBatch + 1
|
||||
|
||||
@@ -27,7 +27,7 @@ export const readMigrationFiles = async ({
|
||||
.readdirSync(payload.db.migrationDir)
|
||||
.sort()
|
||||
.filter((f) => {
|
||||
return f.endsWith('.ts') || f.endsWith('.js')
|
||||
return (f.endsWith('.ts') || f.endsWith('.js')) && !f.includes('index.')
|
||||
})
|
||||
.map((file) => {
|
||||
return path.resolve(payload.db.migrationDir, file)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import fs from 'fs'
|
||||
import { getTsconfig } from 'get-tsconfig'
|
||||
import path from 'path'
|
||||
|
||||
// Function to get all migration files (TS or JS) excluding 'index'
|
||||
const getMigrationFiles = (dir: string) => {
|
||||
return fs
|
||||
.readdirSync(dir)
|
||||
.filter(
|
||||
(file) =>
|
||||
(file.endsWith('.ts') || file.endsWith('.js')) &&
|
||||
file !== 'index.ts' &&
|
||||
file !== 'index.js',
|
||||
)
|
||||
.sort()
|
||||
}
|
||||
|
||||
// Function to generate the index.ts content
|
||||
const generateIndexContent = (files: string[]) => {
|
||||
const tsconfig = getTsconfig()
|
||||
const importExt = tsconfig?.config?.compilerOptions?.moduleResolution === 'NodeNext' ? '.js' : ''
|
||||
|
||||
let imports = ''
|
||||
let exportsArray = 'export const migrations = [\n'
|
||||
|
||||
files.forEach((file, index) => {
|
||||
const fileNameWithoutExt = file.replace(/\.[^/.]+$/, '')
|
||||
imports += `import * as migration_${fileNameWithoutExt} from './${fileNameWithoutExt}${importExt}';\n`
|
||||
exportsArray += ` {
|
||||
up: migration_${fileNameWithoutExt}.up,
|
||||
down: migration_${fileNameWithoutExt}.down,
|
||||
name: '${fileNameWithoutExt}'${index !== files.length - 1 ? ',' : ''}\n },\n`
|
||||
})
|
||||
|
||||
exportsArray += '];\n'
|
||||
return imports + '\n' + exportsArray
|
||||
}
|
||||
|
||||
// Main function to create the index.ts file
|
||||
export const writeMigrationIndex = (args: { migrationsDir: string }) => {
|
||||
const migrationFiles = getMigrationFiles(args.migrationsDir)
|
||||
const indexContent = generateIndexContent(migrationFiles)
|
||||
|
||||
fs.writeFileSync(path.join(args.migrationsDir, 'index.ts'), indexContent)
|
||||
}
|
||||
@@ -44,7 +44,6 @@ export interface BaseDatabaseAdapter {
|
||||
deleteOne: DeleteOne
|
||||
|
||||
deleteVersions: DeleteVersions
|
||||
|
||||
/**
|
||||
* Terminate the connection with the database
|
||||
*/
|
||||
@@ -68,7 +67,7 @@ export interface BaseDatabaseAdapter {
|
||||
/**
|
||||
* Run any migration up functions that have not yet been performed and update the status
|
||||
*/
|
||||
migrate: () => Promise<void>
|
||||
migrate: (args?: { migrations?: Migration[] }) => Promise<void>
|
||||
|
||||
/**
|
||||
* Run any migration down functions that have been performed
|
||||
@@ -79,15 +78,16 @@ export interface BaseDatabaseAdapter {
|
||||
* Drop the current database and run all migrate up functions
|
||||
*/
|
||||
migrateFresh: (args: { forceAcceptWarning?: boolean }) => Promise<void>
|
||||
|
||||
/**
|
||||
* Run all migration down functions before running up
|
||||
*/
|
||||
migrateRefresh: () => Promise<void>
|
||||
|
||||
/**
|
||||
* Run all migrate down functions
|
||||
*/
|
||||
migrateReset: () => Promise<void>
|
||||
|
||||
/**
|
||||
* Read the current state of migrations and output the result to show which have been run
|
||||
*/
|
||||
@@ -148,7 +148,7 @@ export type CreateMigration = (args: {
|
||||
forceAcceptWarning?: boolean
|
||||
migrationName?: string
|
||||
payload: Payload
|
||||
}) => Promise<void>
|
||||
}) => Promise<void> | void
|
||||
|
||||
export type Transaction = (
|
||||
callback: () => Promise<void>,
|
||||
@@ -396,8 +396,8 @@ export type DeleteManyArgs = {
|
||||
export type DeleteMany = (args: DeleteManyArgs) => Promise<void>
|
||||
|
||||
export type Migration = {
|
||||
down: ({ payload, req }: { payload: Payload; req: PayloadRequest }) => Promise<boolean>
|
||||
up: ({ payload, req }: { payload: Payload; req: PayloadRequest }) => Promise<boolean>
|
||||
down: (args: unknown) => Promise<void>
|
||||
up: (args: unknown) => Promise<void>
|
||||
} & MigrationData
|
||||
|
||||
export type MigrationData = {
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { CSSProperties } from 'react'
|
||||
import monacoeditor from 'monaco-editor' // IMPORTANT - DO NOT REMOVE: This is required for pnpm's default isolated mode to work - even though the import is not used. This is due to a typescript bug: https://github.com/microsoft/TypeScript/issues/47663#issuecomment-1519138189. (tsbugisolatedmode)
|
||||
import type { JSONSchema4 } from 'json-schema'
|
||||
import type React from 'react'
|
||||
import type { DeepPartial } from 'ts-essentials'
|
||||
|
||||
import type { RichTextAdapter, RichTextAdapterProvider } from '../../admin/RichText.js'
|
||||
import type { ErrorComponent } from '../../admin/forms/Error.js'
|
||||
@@ -33,6 +32,10 @@ export type FieldHookArgs<TData extends TypeWithID = any, TValue = any, TSibling
|
||||
context: RequestContext
|
||||
/** The data passed to update the document within create and update operations, and the full document itself in the afterRead hook. */
|
||||
data?: Partial<TData>
|
||||
/**
|
||||
* Only available in the `afterRead` hook.
|
||||
*/
|
||||
draft?: boolean
|
||||
/** The field which the hook is running against. */
|
||||
field: FieldAffectingData
|
||||
/** Boolean to denote if this hook is running against finding one, or finding many within the afterRead hook. */
|
||||
@@ -60,6 +63,10 @@ export type FieldHookArgs<TData extends TypeWithID = any, TValue = any, TSibling
|
||||
* The schemaPath of the field, e.g. ["group", "myArray", "textField"]. The schemaPath is the path but without indexes and would be used in the context of field schemas, not field data.
|
||||
*/
|
||||
schemaPath: string[]
|
||||
/**
|
||||
* Only available in the `afterRead` hook.
|
||||
*/
|
||||
showHiddenFields?: boolean
|
||||
/** The sibling data passed to a field that the hook is running against. */
|
||||
siblingData: Partial<TSiblingData>
|
||||
/**
|
||||
|
||||
@@ -205,6 +205,7 @@ export const promise = async ({
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
draft,
|
||||
field,
|
||||
findMany,
|
||||
global,
|
||||
@@ -214,6 +215,7 @@ export const promise = async ({
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
value,
|
||||
})
|
||||
@@ -230,6 +232,7 @@ export const promise = async ({
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
draft,
|
||||
field,
|
||||
findMany,
|
||||
global,
|
||||
@@ -239,6 +242,7 @@ export const promise = async ({
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
value: siblingDoc[field.name],
|
||||
})
|
||||
|
||||
@@ -126,24 +126,25 @@ export const relationshipPopulationPromise = async ({
|
||||
|
||||
if (fieldSupportsMany(field) && field.hasMany) {
|
||||
if (
|
||||
field.localized &&
|
||||
locale === 'all' &&
|
||||
typeof siblingDoc[field.name] === 'object' &&
|
||||
siblingDoc[field.name] !== null
|
||||
) {
|
||||
Object.keys(siblingDoc[field.name]).forEach((key) => {
|
||||
if (Array.isArray(siblingDoc[field.name][key])) {
|
||||
siblingDoc[field.name][key].forEach((relatedDoc, index) => {
|
||||
Object.keys(siblingDoc[field.name]).forEach((localeKey) => {
|
||||
if (Array.isArray(siblingDoc[field.name][localeKey])) {
|
||||
siblingDoc[field.name][localeKey].forEach((relatedDoc, index) => {
|
||||
const rowPromise = async () => {
|
||||
await populate({
|
||||
currentDepth,
|
||||
data: siblingDoc[field.name][key][index],
|
||||
data: siblingDoc[field.name][localeKey][index],
|
||||
dataReference: resultingDoc,
|
||||
depth: populateDepth,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
field,
|
||||
index,
|
||||
key,
|
||||
key: localeKey,
|
||||
locale,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -179,21 +180,22 @@ export const relationshipPopulationPromise = async ({
|
||||
})
|
||||
}
|
||||
} else if (
|
||||
field.localized &&
|
||||
locale === 'all' &&
|
||||
typeof siblingDoc[field.name] === 'object' &&
|
||||
siblingDoc[field.name] !== null &&
|
||||
locale === 'all'
|
||||
siblingDoc[field.name] !== null
|
||||
) {
|
||||
Object.keys(siblingDoc[field.name]).forEach((key) => {
|
||||
Object.keys(siblingDoc[field.name]).forEach((localeKey) => {
|
||||
const rowPromise = async () => {
|
||||
await populate({
|
||||
currentDepth,
|
||||
data: siblingDoc[field.name][key],
|
||||
data: siblingDoc[field.name][localeKey],
|
||||
dataReference: resultingDoc,
|
||||
depth: populateDepth,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
field,
|
||||
key,
|
||||
key: localeKey,
|
||||
locale,
|
||||
overrideAccess,
|
||||
req,
|
||||
|
||||
@@ -777,6 +777,7 @@ export { migrateStatus } from './database/migrations/migrateStatus.js'
|
||||
export { migrationTemplate } from './database/migrations/migrationTemplate.js'
|
||||
export { migrationsCollection } from './database/migrations/migrationsCollection.js'
|
||||
export { readMigrationFiles } from './database/migrations/readMigrationFiles.js'
|
||||
export { writeMigrationIndex } from './database/migrations/writeMigrationIndex.js'
|
||||
export type * from './database/queryValidation/types.js'
|
||||
export type { EntityPolicies, PathToQuery } from './database/queryValidation/types.js'
|
||||
export { validateQueryPaths } from './database/queryValidation/validateQueryPaths.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
@@ -62,8 +62,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import type { TextFieldProps } from 'payload'
|
||||
import type { SelectFieldValidation, TextFieldProps } from 'payload'
|
||||
|
||||
import { SelectField, useForm } from '@payloadcms/ui'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import type { SelectFieldOption } from '../../types.js'
|
||||
|
||||
export const DynamicFieldSelector: React.FC<TextFieldProps> = (props) => {
|
||||
export const DynamicFieldSelector: React.FC<
|
||||
{ validate: SelectFieldValidation } & TextFieldProps
|
||||
> = (props) => {
|
||||
const { fields, getDataByPath } = useForm()
|
||||
|
||||
const [options, setOptions] = useState<SelectFieldOption[]>([])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-relationship-object-ids",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "A Payload plugin to store all relationship IDs as ObjectIDs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
@@ -55,8 +55,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
@@ -64,8 +64,8 @@
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
|
||||
@@ -54,7 +54,7 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
const { result: generatedImage } = await genImageResponse.json()
|
||||
const generatedImage = await genImageResponse.text()
|
||||
|
||||
setValue(generatedImage || '')
|
||||
}, [hasGenerateImageFn, docInfo, getData, locale, setValue])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -41,24 +41,24 @@
|
||||
"translateNewKeys": "tsx scripts/translateNewKeys.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lexical/headless": "0.16.1",
|
||||
"@lexical/link": "0.16.1",
|
||||
"@lexical/list": "0.16.1",
|
||||
"@lexical/mark": "0.16.1",
|
||||
"@lexical/markdown": "0.16.1",
|
||||
"@lexical/react": "0.16.1",
|
||||
"@lexical/rich-text": "0.16.1",
|
||||
"@lexical/selection": "0.16.1",
|
||||
"@lexical/utils": "0.16.1",
|
||||
"@lexical/headless": "0.17.0",
|
||||
"@lexical/link": "0.17.0",
|
||||
"@lexical/list": "0.17.0",
|
||||
"@lexical/mark": "0.17.0",
|
||||
"@lexical/markdown": "0.17.0",
|
||||
"@lexical/react": "0.17.0",
|
||||
"@lexical/rich-text": "0.17.0",
|
||||
"@lexical/selection": "0.17.0",
|
||||
"@lexical/utils": "0.17.0",
|
||||
"@types/uuid": "10.0.0",
|
||||
"bson-objectid": "2.0.4",
|
||||
"dequal": "2.0.3",
|
||||
"lexical": "0.16.1",
|
||||
"lexical": "0.17.0",
|
||||
"react-error-boundary": "4.0.13",
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lexical/eslint-plugin": " 0.16.1",
|
||||
"@lexical/eslint-plugin": "0.17.0",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/next": "workspace:*",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
@@ -75,23 +75,23 @@
|
||||
"peerDependencies": {
|
||||
"@faceless-ui/modal": "3.0.0-beta.2",
|
||||
"@faceless-ui/scroll-info": "2.0.0-beta.0",
|
||||
"@lexical/headless": "0.16.1",
|
||||
"@lexical/link": "0.16.1",
|
||||
"@lexical/list": "0.16.1",
|
||||
"@lexical/mark": "0.16.1",
|
||||
"@lexical/markdown": "0.16.1",
|
||||
"@lexical/react": "0.16.1",
|
||||
"@lexical/rich-text": "0.16.1",
|
||||
"@lexical/selection": "0.16.1",
|
||||
"@lexical/table": "0.16.1",
|
||||
"@lexical/utils": "0.16.1",
|
||||
"@lexical/headless": "0.17.0",
|
||||
"@lexical/link": "0.17.0",
|
||||
"@lexical/list": "0.17.0",
|
||||
"@lexical/mark": "0.17.0",
|
||||
"@lexical/markdown": "0.17.0",
|
||||
"@lexical/react": "0.17.0",
|
||||
"@lexical/rich-text": "0.17.0",
|
||||
"@lexical/selection": "0.17.0",
|
||||
"@lexical/table": "0.17.0",
|
||||
"@lexical/utils": "0.17.0",
|
||||
"@payloadcms/next": "workspace:*",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"lexical": "0.16.1",
|
||||
"lexical": "0.17.0",
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0"
|
||||
|
||||
@@ -28,15 +28,26 @@ export const BlockquoteFeature = createServerFeature({
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, req }) => {
|
||||
converter: async ({
|
||||
converters,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
parent: {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
|
||||
return `<blockquote>${childrenText}</blockquote>`
|
||||
|
||||
@@ -5,15 +5,18 @@ import type { HTMLConverter } from '../types.js'
|
||||
import { convertLexicalNodesToHTML } from '../index.js'
|
||||
|
||||
export const ParagraphHTMLConverter: HTMLConverter<SerializedParagraphNode> = {
|
||||
async converter({ converters, node, parent, req }) {
|
||||
async converter({ converters, draft, node, overrideAccess, parent, req, showHiddenFields }) {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
parent: {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
return `<p>${childrenText}</p>`
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@ import type { HTMLConverter, SerializedLexicalNodeWithParent } from './types.js'
|
||||
export type ConvertLexicalToHTMLArgs = {
|
||||
converters: HTMLConverter[]
|
||||
data: SerializedEditorState
|
||||
draft?: boolean // default false
|
||||
overrideAccess?: boolean // default false
|
||||
showHiddenFields?: boolean // default false
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
@@ -40,8 +43,11 @@ export type ConvertLexicalToHTMLArgs = {
|
||||
export async function convertLexicalToHTML({
|
||||
converters,
|
||||
data,
|
||||
draft,
|
||||
overrideAccess,
|
||||
payload,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}: ConvertLexicalToHTMLArgs): Promise<string> {
|
||||
if (data?.root?.children?.length) {
|
||||
if (req === undefined && payload) {
|
||||
@@ -50,9 +56,12 @@ export async function convertLexicalToHTML({
|
||||
|
||||
return await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
draft: draft === undefined ? false : draft,
|
||||
lexicalNodes: data?.root?.children,
|
||||
overrideAccess: overrideAccess === undefined ? false : overrideAccess,
|
||||
parent: data?.root,
|
||||
req,
|
||||
showHiddenFields: showHiddenFields === undefined ? false : showHiddenFields,
|
||||
})
|
||||
}
|
||||
return ''
|
||||
@@ -60,17 +69,23 @@ export async function convertLexicalToHTML({
|
||||
|
||||
export async function convertLexicalNodesToHTML({
|
||||
converters,
|
||||
draft,
|
||||
lexicalNodes,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}: {
|
||||
converters: HTMLConverter[]
|
||||
draft: boolean
|
||||
lexicalNodes: SerializedLexicalNode[]
|
||||
overrideAccess: boolean
|
||||
parent: SerializedLexicalNodeWithParent
|
||||
/**
|
||||
* When the converter is called, req CAN be passed in depending on where it's run.
|
||||
*/
|
||||
req: PayloadRequest | null
|
||||
showHiddenFields: boolean
|
||||
}): Promise<string> {
|
||||
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
|
||||
|
||||
@@ -85,9 +100,12 @@ export async function convertLexicalNodesToHTML({
|
||||
return await unknownConverter.converter({
|
||||
childIndex: i,
|
||||
converters,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
}
|
||||
return '<span>unknown node</span>'
|
||||
@@ -95,9 +113,12 @@ export async function convertLexicalNodesToHTML({
|
||||
return await converterForNode.converter({
|
||||
childIndex: i,
|
||||
converters,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error converting lexical node to HTML:', error, 'node:', node)
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import type { SerializedLexicalNode } from 'lexical'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
import type { PayloadRequest } from 'payload'
|
||||
|
||||
export type HTMLConverter<T extends SerializedLexicalNode = SerializedLexicalNode> = {
|
||||
converter: ({
|
||||
childIndex,
|
||||
converters,
|
||||
node,
|
||||
parent,
|
||||
req,
|
||||
}: {
|
||||
converter: (args: {
|
||||
childIndex: number
|
||||
converters: HTMLConverter[]
|
||||
converters: HTMLConverter<any>[]
|
||||
draft: boolean
|
||||
node: T
|
||||
overrideAccess: boolean
|
||||
parent: SerializedLexicalNodeWithParent
|
||||
/**
|
||||
* When the converter is called, req CAN be passed in depending on where it's run.
|
||||
*/
|
||||
req: PayloadRequest | null
|
||||
showHiddenFields: boolean
|
||||
}) => Promise<string> | string
|
||||
nodeTypes: string[]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { createServerFeature } from '../../../utilities/createServerFeature.js'
|
||||
|
||||
export type HTMLConverterFeatureProps = {
|
||||
converters?:
|
||||
| (({ defaultConverters }: { defaultConverters: HTMLConverter[] }) => HTMLConverter[])
|
||||
| HTMLConverter[]
|
||||
| (({ defaultConverters }: { defaultConverters: HTMLConverter<any>[] }) => HTMLConverter<any>[])
|
||||
| HTMLConverter<any>[]
|
||||
}
|
||||
|
||||
// This is just used to save the props on the richText field
|
||||
|
||||
@@ -160,7 +160,16 @@ export const lexicalHTML: (
|
||||
},
|
||||
hooks: {
|
||||
afterRead: [
|
||||
async ({ collection, field, global, req, siblingData }) => {
|
||||
async ({
|
||||
collection,
|
||||
draft,
|
||||
field,
|
||||
global,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingData,
|
||||
}) => {
|
||||
const fields = collection ? collection.fields : global.fields
|
||||
|
||||
const foundSiblingFields = findFieldPathAndSiblingFields(fields, [], field)
|
||||
@@ -209,7 +218,10 @@ export const lexicalHTML: (
|
||||
return await convertLexicalToHTML({
|
||||
converters: finalConverters,
|
||||
data: lexicalFieldData,
|
||||
draft,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
},
|
||||
],
|
||||
|
||||
@@ -8,6 +8,7 @@ import { slashMenuBasicGroupWithItems } from '../shared/slashMenu/basicGroup.js'
|
||||
import { toolbarAddDropdownGroupWithItems } from '../shared/toolbar/addDropdownGroup.js'
|
||||
import { TableActionMenuPlugin } from './plugins/TableActionMenuPlugin/index.js'
|
||||
import { TableCellResizerPlugin } from './plugins/TableCellResizerPlugin/index.js'
|
||||
import { TableHoverActionsPlugin } from './plugins/TableHoverActionsPlugin/index.js'
|
||||
import {
|
||||
OPEN_TABLE_DRAWER_COMMAND,
|
||||
TableContext,
|
||||
@@ -29,6 +30,10 @@ export const TableFeatureClient = createClientFeature({
|
||||
Component: TableActionMenuPlugin,
|
||||
position: 'floatingAnchorElem',
|
||||
},
|
||||
{
|
||||
Component: TableHoverActionsPlugin,
|
||||
position: 'floatingAnchorElem',
|
||||
},
|
||||
],
|
||||
providers: [TableContext],
|
||||
slashMenu: {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import type {
|
||||
SerializedTableCellNode as _SerializedTableCellNode,
|
||||
SerializedTableNode as _SerializedTableNode,
|
||||
SerializedTableRowNode as _SerializedTableRowNode,
|
||||
} from '@lexical/table'
|
||||
import type { Spread } from 'lexical'
|
||||
import type { Config, Field } from 'payload'
|
||||
|
||||
import { TableCellNode, TableNode, TableRowNode } from '@lexical/table'
|
||||
@@ -6,6 +12,8 @@ import { sanitizeFields } from 'payload'
|
||||
// eslint-disable-next-line payload/no-imports-from-exports-dir
|
||||
import { TableFeatureClient } from '../../exports/client/index.js'
|
||||
import { createServerFeature } from '../../utilities/createServerFeature.js'
|
||||
import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js'
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
|
||||
const fields: Field[] = [
|
||||
{
|
||||
@@ -21,6 +29,27 @@ const fields: Field[] = [
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
export type SerializedTableCellNode = Spread<
|
||||
{
|
||||
type: 'tablecell'
|
||||
},
|
||||
_SerializedTableCellNode
|
||||
>
|
||||
|
||||
export type SerializedTableNode = Spread<
|
||||
{
|
||||
type: 'table'
|
||||
},
|
||||
_SerializedTableNode
|
||||
>
|
||||
|
||||
export type SerializedTableRowNode = Spread<
|
||||
{
|
||||
type: 'tablerow'
|
||||
},
|
||||
_SerializedTableRowNode
|
||||
>
|
||||
export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
feature: async ({ config, isRoot }) => {
|
||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||
@@ -41,15 +70,108 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
return schemaMap
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
parent: {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
return `<table class="lexical-table" style="border-collapse: collapse;">${childrenText}</table>`
|
||||
},
|
||||
nodeTypes: [TableNode.getType()],
|
||||
},
|
||||
},
|
||||
node: TableNode,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
parent: {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
|
||||
const tagName = node.headerState > 0 ? 'th' : 'td'
|
||||
const headerStateClass = `lexical-table-cell-header-${node.headerState}`
|
||||
const backgroundColor = node.backgroundColor
|
||||
? `background-color: ${node.backgroundColor};`
|
||||
: ''
|
||||
const colSpan = node.colSpan > 1 ? `colspan="${node.colSpan}"` : ''
|
||||
const rowSpan = node.rowSpan > 1 ? `rowspan="${node.rowSpan}"` : ''
|
||||
|
||||
return `<${tagName} class="lexical-table-cell ${headerStateClass}" style="border: 1px solid #ccc; padding: 8px; ${backgroundColor}" ${colSpan} ${rowSpan}>${childrenText}</${tagName}>`
|
||||
},
|
||||
nodeTypes: [TableCellNode.getType()],
|
||||
},
|
||||
},
|
||||
node: TableCellNode,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
parent: {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
return `<tr class="lexical-table-row">${childrenText}</tr>`
|
||||
},
|
||||
nodeTypes: [TableRowNode.getType()],
|
||||
},
|
||||
},
|
||||
node: TableRowNode,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
|
||||
.table-cell-action-button {
|
||||
background-color: var(--theme-elevation-200);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 0;
|
||||
padding: 2px;
|
||||
position: relative;
|
||||
|
||||
@@ -160,15 +160,19 @@ function TableActionMenu({
|
||||
const { y } = useScrollInfo()
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerMutationListener(TableCellNode, (nodeMutations) => {
|
||||
const nodeUpdated = nodeMutations.get(tableCellNode.getKey()) === 'updated'
|
||||
return editor.registerMutationListener(
|
||||
TableCellNode,
|
||||
(nodeMutations) => {
|
||||
const nodeUpdated = nodeMutations.get(tableCellNode.getKey()) === 'updated'
|
||||
|
||||
if (nodeUpdated) {
|
||||
editor.getEditorState().read(() => {
|
||||
updateTableCellNode(tableCellNode.getLatest())
|
||||
})
|
||||
}
|
||||
})
|
||||
if (nodeUpdated) {
|
||||
editor.getEditorState().read(() => {
|
||||
updateTableCellNode(tableCellNode.getLatest())
|
||||
})
|
||||
}
|
||||
},
|
||||
{ skipInitialization: true },
|
||||
)
|
||||
}, [editor, tableCellNode])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user