Compare commits

..

36 Commits

Author SHA1 Message Date
Elliot DeNolf
4b9e87bb4d chore(release): db-postgres/0.1.8 2023-10-15 21:04:46 -04:00
Elliot DeNolf
ff5e174497 chore(script): multiselect publishing prompt 2023-10-15 20:53:16 -04:00
Elliot DeNolf
34017e1758 chore(release): payload/2.0.7 2023-10-15 20:49:18 -04:00
James Mikrut
23d95526ab Merge pull request #3666 from payloadcms/fix/empty-in-array
fix: empty inArray no longer crashes postgres
2023-10-15 18:05:15 -04:00
James
59b87fdb21 chore: typo 2023-10-15 17:55:30 -04:00
James
f2ac1f7d48 chore: merge main 2023-10-15 17:31:24 -04:00
James
8d4f39af5e Merge branch 'main' of github.com:payloadcms/payload into fix/empty-in-array 2023-10-15 17:29:51 -04:00
James
c4ac341d75 fix: empty inArray no longer crashes postgres 2023-10-15 17:28:13 -04:00
Elliot DeNolf
27589482dd chore(release): @payloadcms/db-postgres/0.1.7 2023-10-15 14:12:02 -04:00
James Mikrut
d7ab4b7062 Merge pull request #3642 from payloadcms/fix/#3568-postgres-relationships-in-array
fix(db-postgres): query relationship in array alias
2023-10-15 11:31:57 -04:00
James
2c8fbf1be3 chore: adds specificity to tests 2023-10-15 11:08:16 -04:00
James
eec88f8f1b Merge branch 'fix/#3568-postgres-relationships-in-array' of github.com:payloadcms/payload into fix/#3568-postgres-relationships-in-array 2023-10-15 10:43:32 -04:00
James
1481ef97b5 Merge branch 'main' of github.com:payloadcms/payload 2023-10-15 09:44:19 -04:00
James
a89e89fb80 chore: documents pagination: false 2023-10-15 09:44:00 -04:00
Alessio Gravili
7e7eeb059d chore(richtext-lexical): add 'use client' to field and cell 2023-10-15 14:25:36 +02:00
Alessio Gravili
dc2a502dcc perf(richtext-lexical): remove unnecessary prop drilling and load hooks being run for initialState 2023-10-15 14:07:24 +02:00
Elliot DeNolf
a9a5ba82d8 chore: update pnpm-lock.yaml 2023-10-14 21:09:15 -04:00
James
e6e8fae1c5 Merge branch 'main' of github.com:payloadcms/payload 2023-10-14 19:24:50 -04:00
James
a05868a7f3 chore: remove unnecessary peer dep 2023-10-14 19:24:32 -04:00
Elliot DeNolf
f27cd26575 chore(release): richtext-lexical/0.1.11 2023-10-14 17:35:44 -04:00
Elliot DeNolf
db835ea5c8 chore(release): db-postgres/0.1.6 2023-10-14 17:35:26 -04:00
Jacob Fletcher
d8f265fb94 chore(examples): bumps deps to latest (#3655) 2023-10-14 16:49:47 -04:00
Alessio Gravili
d81d4eb075 feat(richtext-lexical): LexicalPluginToLexical migration feature 2023-10-14 22:36:16 +02:00
James
52f89c0136 fix(db-postgres): ensures columns are nullable if within field with condition 2023-10-14 15:34:27 -04:00
Dan Ribbens
b0083b7c07 test: fix missing variable 2023-10-14 15:32:06 -04:00
Dan Ribbens
21649537a6 fix(db-postgres): query relationship path inside arrays 2023-10-14 15:17:42 -04:00
Elliot DeNolf
9be34c9599 chore(release): richtext-lexical/0.1.10 2023-10-14 14:38:58 -04:00
Elliot DeNolf
8ca632e541 chore(release): richtext-slate/1.0.5 2023-10-14 14:38:48 -04:00
Elliot DeNolf
2ef79145a4 chore(release): payload/2.0.6 2023-10-14 14:37:18 -04:00
James
a0641a445d Merge branch 'main' of github.com:payloadcms/payload 2023-10-14 14:34:46 -04:00
James
3a2e78f7f3 chore: add peer deps for richtext packages 2023-10-14 14:34:40 -04:00
James Mikrut
976d69d154 Merge pull request #3657 from payloadcms/chore/export-pattern
chore: properly separates server / client exports
2023-10-14 14:32:11 -04:00
James
66018362fe chore: properly separates server / client exports 2023-10-14 14:08:08 -04:00
Elliot DeNolf
647fe23d1c chore(release): richtext-lexical/0.1.9 2023-10-14 12:26:28 -04:00
Elliot DeNolf
d7c61861f6 chore(release): richtext-slate/1.0.4 2023-10-14 12:26:19 -04:00
Dan Ribbens
2c67eff059 fix(db-postgres): query relationship in array alias 2023-10-13 13:32:44 -04:00
66 changed files with 5696 additions and 3292 deletions

View File

@@ -131,6 +131,7 @@ const result = await payload.find({
depth: 2,
page: 1,
limit: 10,
pagination: false, // If you want to disable pagination count, etc.
where: {}, // pass a `where` query here
sort: '-title',
locale: 'en',

View File

@@ -59,3 +59,7 @@ All Payload APIs support the pagination controls below. With them, you can creat
| ------- | --------------------------------------- |
| `limit` | Limits the number of documents returned |
| `page` | Get a specific page number |
### Disabling pagination within Local API
For `find` operations within the Local API, you can disable pagination to retrieve all documents from a collection by passing `pagination: false` to the `find` local operation. This is not supported in REST or GraphQL, however, because it could potentially lead to malicious activity.

View File

@@ -17,9 +17,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

File diff suppressed because it is too large Load Diff

View File

@@ -20,9 +20,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"escape-html": "^1.0.3",
"express": "^4.17.1",

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

File diff suppressed because it is too large Load Diff

View File

@@ -15,9 +15,9 @@
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"@faceless-ui/modal": "^2.0.1",
"@payloadcms/plugin-form-builder": "^1.0.12",
"@payloadcms/plugin-seo": "^1.0.8",

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"@payloadcms/plugin-redirects": "^1.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,7 @@
"fs-extra": "10.1.0",
"get-port": "5.1.1",
"glob": "8.1.0",
"graphql-request": "6.1.0",
"husky": "^8.0.3",
"isomorphic-fetch": "3.0.0",
"jest": "29.6.4",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.1.5",
"version": "0.1.8",
"description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",
@@ -34,9 +34,6 @@
"@types/to-snake-case": "1.0.0",
"payload": "workspace:*"
},
"peerDependencies": {
"better-sqlite3": "^8.5.0"
},
"publishConfig": {
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",

View File

@@ -33,7 +33,7 @@ export const findMany = async function find({
const db = adapter.sessions[req.transactionID]?.db || adapter.drizzle
const table = adapter.tables[tableName]
let limit = limitArg
let limit = limitArg ?? 10
let totalDocs: number
let totalPages: number
let hasPrevPage: boolean
@@ -119,7 +119,11 @@ export const findMany = async function find({
findManyArgs.where = inArray(adapter.tables[tableName].id, Object.keys(orderedIDMap))
} else {
findManyArgs.limit = limitArg === 0 ? undefined : limitArg
findManyArgs.offset = skip || (page - 1) * limitArg
const offset = skip || (page - 1) * limitArg
if (!Number.isNaN(offset)) findManyArgs.offset = offset
if (where) {
findManyArgs.where = where
}

View File

@@ -34,12 +34,14 @@ type Args = {
aliasTable?: GenericTable
collectionPath: string
columnPrefix?: string
constraintPath?: string
constraints?: Constraint[]
fields: (Field | TabAsField)[]
joinAliases: BuildQueryJoinAliases
joins: BuildQueryJoins
locale?: string
pathSegments: string[]
rootTableName?: string
selectFields: Record<string, GenericColumn>
tableName: string
}
@@ -53,17 +55,22 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix = '',
constraintPath: incomingConstraintPath,
constraints = [],
fields,
joinAliases,
joins,
locale: incomingLocale,
pathSegments: incomingSegments,
rootTableName: incomingRootTableName,
selectFields,
tableName,
}: Args): TableColumn => {
const fieldPath = incomingSegments[0]
let locale = incomingLocale
const rootTableName = incomingRootTableName || tableName
let constraintPath = incomingConstraintPath || ''
const field = flattenTopLevelFields(fields as Field[]).find(
(fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath,
) as Field | TabAsField
@@ -105,6 +112,7 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix,
constraintPath,
constraints,
fields: field.tabs.map((tab) => ({
...tab,
@@ -114,6 +122,7 @@ export const getTableColumnFromPath = ({
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -125,12 +134,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -140,12 +151,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -172,12 +185,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -185,6 +200,7 @@ export const getTableColumnFromPath = ({
case 'array': {
newTableName = `${tableName}_${toSnakeCase(field.name)}`
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
@@ -206,12 +222,14 @@ export const getTableColumnFromPath = ({
return getTableColumnFromPath({
adapter,
collectionPath,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -229,12 +247,14 @@ export const getTableColumnFromPath = ({
result = getTableColumnFromPath({
adapter,
collectionPath,
constraintPath: '',
constraints: blockConstraints,
fields: block.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields: blockSelectFields,
tableName: newTableName,
})
@@ -283,9 +303,8 @@ export const getTableColumnFromPath = ({
case 'relationship':
case 'upload': {
let relationshipFields
const relationTableName = `${tableName}_rels`
const relationTableName = `${rootTableName}_rels`
const newCollectionPath = pathSegments.slice(1).join('.')
const aliasRelationshipTableName = uuid()
const aliasRelationshipTable = alias(
adapter.tables[relationTableName],
@@ -295,7 +314,7 @@ export const getTableColumnFromPath = ({
// Join in the relationships table
joinAliases.push({
condition: eq(
(aliasTable || adapter.tables[tableName]).id,
(aliasTable || adapter.tables[rootTableName]).id,
aliasRelationshipTable.parent,
),
table: aliasRelationshipTable,
@@ -306,7 +325,7 @@ export const getTableColumnFromPath = ({
constraints.push({
columnName: 'path',
table: aliasRelationshipTable,
value: field.name,
value: `${constraintPath}${field.name}`,
})
let newAliasTable
@@ -368,6 +387,7 @@ export const getTableColumnFromPath = ({
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName: newTableName,
selectFields,
tableName: newTableName,
})

View File

@@ -100,7 +100,11 @@ export async function parseParams({
const val = where[relationOrPath][operator]
queryConstraints.forEach(({ columnName: col, table: constraintTable, value }) => {
constraints.push(operatorMap.equals(constraintTable[col], value))
if (typeof value === 'string' && value.indexOf('%') > -1) {
constraints.push(operatorMap.like(constraintTable[col], value))
} else {
constraints.push(operatorMap.equals(constraintTable[col], value))
}
})
if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) {
@@ -144,13 +148,19 @@ export async function parseParams({
break
}
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
const sanitizedQueryValue = sanitizeQueryValue({
field,
operator,
relationOrPath,
val,
})
if (sanitizedQueryValue === null) {
break
}
const { operator: queryOperator, value: queryValue } = sanitizedQueryValue
if (queryOperator === 'not_equals' && queryValue !== null) {
constraints.push(
or(
@@ -159,7 +169,10 @@ export async function parseParams({
ne<any>(rawColumn || table[columnName], queryValue),
),
)
} else if (
break
}
if (
(field.type === 'relationship' || field.type === 'upload') &&
Array.isArray(queryValue) &&
operator === 'not_in'
@@ -170,11 +183,13 @@ export async function parseParams({
IS
NULL`,
)
} else {
constraints.push(
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
)
break
}
constraints.push(
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
)
}
}
}

View File

@@ -42,11 +42,17 @@ export const sanitizeQueryValue = ({
if (val.toLowerCase() === 'false') formattedValue = false
}
if (['all', 'in', 'not_in'].includes(operator) && typeof formattedValue === 'string') {
formattedValue = createArrayFromCommaDelineated(formattedValue)
if (['all', 'in', 'not_in'].includes(operator)) {
if (typeof formattedValue === 'string') {
formattedValue = createArrayFromCommaDelineated(formattedValue)
if (field.type === 'number') {
formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal))
if (field.type === 'number') {
formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal))
}
}
if (!Array.isArray(formattedValue) || formattedValue.length === 0) {
return null
}
}

View File

@@ -252,6 +252,8 @@ export const traverseFields = ({
}
case 'array': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`
const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(),
@@ -277,7 +279,7 @@ export const traverseFields = ({
adapter,
baseColumns,
baseExtraConfig,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fields: field.fields,
rootRelationsToBuild,
@@ -314,6 +316,8 @@ export const traverseFields = ({
}
case 'blocks': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
field.blocks.forEach((block) => {
const blockTableName = `${rootTableName}_blocks_${toSnakeCase(block.slug)}`
if (!adapter.tables[blockTableName]) {
@@ -343,7 +347,7 @@ export const traverseFields = ({
adapter,
baseColumns,
baseExtraConfig,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fields: block.fields,
rootRelationsToBuild,
@@ -428,6 +432,8 @@ export const traverseFields = ({
break
}
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const {
hasLocalizedField: groupHasLocalizedField,
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
@@ -438,7 +444,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix: `${columnName}_`,
columns,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fieldPrefix: `${fieldName}_`,
fields: field.fields,
@@ -463,6 +469,8 @@ export const traverseFields = ({
}
case 'tabs': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const {
hasLocalizedField: tabHasLocalizedField,
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
@@ -473,7 +481,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fieldPrefix,
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
@@ -500,6 +508,7 @@ export const traverseFields = ({
case 'row':
case 'collapsible': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const {
hasLocalizedField: rowHasLocalizedField,
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
@@ -510,7 +519,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fieldPrefix,
fields: field.fields,

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.0.5",
"version": "2.0.7",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",
@@ -187,7 +187,6 @@
"file-loader": "6.2.0",
"form-data": "3.0.1",
"get-port": "5.1.1",
"graphql-request": "6.1.0",
"mini-css-extract-plugin": "1.6.2",
"node-fetch": "2.6.12",
"nodemon": "3.0.1",

View File

@@ -18,16 +18,18 @@ export {
*/
useWatchForm,
} from '../../admin/components/forms/Form/context'
export { createNestedFieldPath } from '../../admin/components/forms/Form/createNestedFieldPath'
export { default as getSiblingData } from '../../admin/components/forms/Form/getSiblingData'
export { default as reduceFieldsToValues } from '../../admin/components/forms/Form/reduceFieldsToValues'
export { default as Label } from '../../admin/components/forms/Label'
export { default as RenderFields } from '../../admin/components/forms/RenderFields'
export { default as RenderFields } from '../../admin/components/forms/RenderFields'
export { default as Submit } from '../../admin/components/forms/Submit'
export { default as FormSubmit } from '../../admin/components/forms/Submit'
export { fieldTypes } from '../../admin/components/forms/field-types'
export { default as Checkbox } from '../../admin/components/forms/field-types/Checkbox'
export { default as Collapsible } from '../../admin/components/forms/field-types/Collapsible'

View File

@@ -1,7 +1,7 @@
export { buildConfig } from '../config/build'
export * from '../config/types'
export { type FieldTypes, fieldTypes } from '../admin/components/forms/field-types'
export { type FieldTypes } from '../admin/components/forms/field-types'
export { defaults } from '../config/defaults'
export { sanitizeConfig } from '../config/sanitize'
export { baseBlockFields } from '../fields/baseFields/baseBlockFields'

View File

@@ -1,10 +1,11 @@
export { withMergedProps } from '../admin/components/utilities/WithMergedProps'
export { extractTranslations } from '../translations/extractTranslations'
export { i18nInit } from '../translations/init'
export { i18nInit } from '../translations/init'
export { combineMerge } from '../utilities/combineMerge'
export { configToJSONSchema, entityToJSONSchema } from '../utilities/configToJSONSchema'
export { createArrayFromCommaDelineated } from '../utilities/createArrayFromCommaDelineated'
export { createArrayFromCommaDelineated } from '../utilities/createArrayFromCommaDelineated'
export { deepCopyObject } from '../utilities/deepCopyObject'
export { deepMerge } from '../utilities/deepMerge'
export { default as flattenTopLevelFields } from '../utilities/flattenTopLevelFields'

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "0.1.8",
"version": "0.1.11",
"description": "The officially supported Lexical richtext adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",
@@ -55,6 +55,9 @@
"@types/react": "18.2.15",
"payload": "workspace:*"
},
"peerDependencies": {
"payload": "^2.0.6"
},
"exports": {
".": {
"default": "./src/index.ts",

View File

@@ -1,3 +1,4 @@
'use client'
import type { SerializedEditorState } from 'lexical'
import type { CellComponentProps, RichTextField } from 'payload/types'

View File

@@ -47,7 +47,7 @@ const RichText: React.FC<FieldProps> = (props) => {
validate: memoizedValidate,
})
const { errorMessage, initialValue, setValue, showError, value } = fieldType
const { errorMessage, setValue, showError, value } = fieldType
let valueToUse = value
@@ -87,7 +87,6 @@ const RichText: React.FC<FieldProps> = (props) => {
<LexicalProvider
editorConfig={editorConfig}
fieldProps={props}
initialState={initialValue}
onChange={(editorState, editor, tags) => {
let serializedEditorState = editorState.toJSON()
@@ -101,7 +100,6 @@ const RichText: React.FC<FieldProps> = (props) => {
setValue(serializedEditorState)
}}
readOnly={readOnly}
setValue={setValue}
value={value}
/>
<FieldDescription description={description} value={value} />

View File

@@ -8,7 +8,7 @@ import { SectionTitle } from 'payload/components/fields/Blocks'
import { RenderFields, createNestedFieldPath, useFormSubmitted } from 'payload/components/forms'
import { useDocumentInfo } from 'payload/components/utilities'
import { getTranslation } from 'payload/utilities'
import React, { useCallback, useEffect } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import type { FieldProps } from '../../../../types'

View File

@@ -2,7 +2,7 @@ import { Drawer } from 'payload/components/elements'
import { Form } from 'payload/components/forms'
import { RenderFields } from 'payload/components/forms'
import { FormSubmit } from 'payload/components/forms'
import { fieldTypes } from 'payload/config'
import { fieldTypes } from 'payload/components/forms'
import React from 'react'
import { useTranslation } from 'react-i18next'

View File

@@ -5,7 +5,7 @@ import type { Field } from 'payload/types'
import LexicalClickableLinkPlugin from '@lexical/react/LexicalClickableLinkPlugin'
import { $findMatchingParent } from '@lexical/utils'
import { $getSelection, $isRangeSelection } from 'lexical'
import { withMergedProps } from 'payload/components/utilities'
import { withMergedProps } from 'payload/utilities'
import type { FeatureProvider } from '../types'
import type { LinkFields } from './nodes/LinkNode'

View File

@@ -4,7 +4,7 @@ import { useModal } from '@faceless-ui/modal'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $getNodeByKey } from 'lexical'
import { Drawer } from 'payload/components/elements'
import { Form, FormSubmit, RenderFields } from 'payload/components/forms'
import { Form, FormSubmit, RenderFields, fieldTypes } from 'payload/components/forms'
import {
buildStateFromSchema,
useAuth,
@@ -12,7 +12,7 @@ import {
useDocumentInfo,
useLocale,
} from 'payload/components/utilities'
import { fieldTypes, sanitizeFields } from 'payload/config'
import { sanitizeFields } from 'payload/config'
import { deepCopyObject, getTranslation } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'

View File

@@ -0,0 +1,21 @@
import type { SerializedHeadingNode } from '@lexical/rich-text'
import type { LexicalPluginNodeConverter } from '../types'
import { convertLexicalPluginNodesToLexical } from '..'
export const HeadingConverter: LexicalPluginNodeConverter = {
converter({ converters, lexicalPluginNode }) {
return {
...lexicalPluginNode,
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
parentNodeType: 'heading',
}),
type: 'heading',
version: 1,
} as const as SerializedHeadingNode
},
nodeTypes: ['heading'],
}

View File

@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { SerializedLinkNode } from '../../../../Link/nodes/LinkNode'
import type { LexicalPluginNodeConverter } from '../types'
import { convertLexicalPluginNodesToLexical } from '..'
export const LinkConverter: LexicalPluginNodeConverter = {
converter({ converters, lexicalPluginNode }) {
return {
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
parentNodeType: 'link',
}),
direction: (lexicalPluginNode as any).direction || 'ltr',
fields: {
doc: (lexicalPluginNode as any).attributes?.doc
? {
relationTo: (lexicalPluginNode as any).attributes?.doc?.relationTo,
value: {
id: (lexicalPluginNode as any).attributes?.doc?.value,
},
}
: undefined,
linkType: (lexicalPluginNode as any).attributes?.linkType || 'custom',
newTab: (lexicalPluginNode as any).attributes?.newTab || false,
url: (lexicalPluginNode as any).attributes?.url || undefined,
},
format: (lexicalPluginNode as any).format || '',
indent: (lexicalPluginNode as any).indent || 0,
type: 'link',
version: 1,
} as const as SerializedLinkNode
},
nodeTypes: ['link'],
}

View File

@@ -0,0 +1,23 @@
import type { SerializedListNode } from '@lexical/list'
import type { LexicalPluginNodeConverter } from '../types'
import { convertLexicalPluginNodesToLexical } from '..'
export const ListConverter: LexicalPluginNodeConverter = {
converter({ converters, lexicalPluginNode }) {
return {
...lexicalPluginNode,
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
parentNodeType: 'list',
}),
listType: (lexicalPluginNode as any)?.listType || 'number',
tag: (lexicalPluginNode as any)?.tag || 'ol',
type: 'list',
version: 1,
} as const as SerializedListNode
},
nodeTypes: ['list'],
}

View File

@@ -0,0 +1,23 @@
import type { SerializedListItemNode } from '@lexical/list'
import type { LexicalPluginNodeConverter } from '../types'
import { convertLexicalPluginNodesToLexical } from '..'
export const ListItemConverter: LexicalPluginNodeConverter = {
converter({ childIndex, converters, lexicalPluginNode }) {
return {
...lexicalPluginNode,
checked: undefined,
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: (lexicalPluginNode as any)?.children || [],
parentNodeType: 'listitem',
}),
type: 'listitem',
value: childIndex + 1,
version: 1,
} as const as SerializedListItemNode
},
nodeTypes: ['listitem'],
}

View File

@@ -0,0 +1,21 @@
import type { SerializedHeadingNode } from '@lexical/rich-text'
import type { LexicalPluginNodeConverter } from '../types'
import { convertLexicalPluginNodesToLexical } from '..'
export const QuoteConverter: LexicalPluginNodeConverter = {
converter({ converters, lexicalPluginNode }) {
return {
...lexicalPluginNode,
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: (lexicalPluginNode as any).children || [],
parentNodeType: 'quote',
}),
type: 'quote',
version: 1,
} as const as SerializedHeadingNode
},
nodeTypes: ['quote'],
}

View File

@@ -0,0 +1,26 @@
import type { SerializedUnknownConvertedNode } from '../../nodes/unknownConvertedNode'
import type { LexicalPluginNodeConverter } from '../types'
import { convertLexicalPluginNodesToLexical } from '..'
export const UnknownConverter: LexicalPluginNodeConverter = {
converter({ converters, lexicalPluginNode }) {
return {
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: (lexicalPluginNode as any)?.children || [],
parentNodeType: 'unknownConverted',
}),
data: {
nodeData: lexicalPluginNode,
nodeType: lexicalPluginNode.type,
},
direction: 'ltr',
format: '',
indent: 0,
type: 'unknownConverted',
version: 1,
} as const as SerializedUnknownConvertedNode
},
nodeTypes: ['unknown'],
}

View File

@@ -0,0 +1,24 @@
import type { SerializedUploadNode } from '../../../../../..'
import type { LexicalPluginNodeConverter } from '../types'
export const UploadConverter: LexicalPluginNodeConverter = {
converter({ lexicalPluginNode }) {
let fields = {}
if ((lexicalPluginNode as any)?.caption?.editorState) {
fields = {
caption: (lexicalPluginNode as any)?.caption,
}
}
return {
fields,
format: (lexicalPluginNode as any)?.format || '',
relationTo: (lexicalPluginNode as any)?.rawImagePayload?.relationTo,
type: 'upload',
value: {
id: (lexicalPluginNode as any)?.rawImagePayload?.value?.id || '',
},
version: 1,
} as const as SerializedUploadNode
},
nodeTypes: ['upload'],
}

View File

@@ -0,0 +1,19 @@
import type { LexicalPluginNodeConverter } from './types'
import { HeadingConverter } from './converters/heading'
import { LinkConverter } from './converters/link'
import { ListConverter } from './converters/list'
import { ListItemConverter } from './converters/listItem'
import { QuoteConverter } from './converters/quote'
import { UnknownConverter } from './converters/unknown'
import { UploadConverter } from './converters/upload'
export const defaultConverters: LexicalPluginNodeConverter[] = [
UnknownConverter,
UploadConverter,
ListConverter,
ListItemConverter,
LinkConverter,
HeadingConverter,
QuoteConverter,
]

View File

@@ -0,0 +1,98 @@
import type {
SerializedEditorState,
SerializedLexicalNode,
SerializedParagraphNode,
SerializedTextNode,
} from 'lexical'
import type { LexicalPluginNodeConverter, PayloadPluginLexicalData } from './types'
export function convertLexicalPluginToLexical({
converters,
lexicalPluginData,
}: {
converters: LexicalPluginNodeConverter[]
lexicalPluginData: PayloadPluginLexicalData
}): SerializedEditorState {
return {
root: {
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: lexicalPluginData?.jsonContent?.root?.children || [],
parentNodeType: 'root',
}),
direction: lexicalPluginData?.jsonContent?.root?.direction || 'ltr',
format: lexicalPluginData?.jsonContent?.root?.format || '',
indent: lexicalPluginData?.jsonContent?.root?.indent || 0,
type: 'root',
version: 1,
},
}
}
export function convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes,
parentNodeType,
}: {
converters: LexicalPluginNodeConverter[]
lexicalPluginNodes: SerializedLexicalNode[]
/**
* Type of the parent lexical node (not the type of the original, parent payload-plugin-lexical type)
*/
parentNodeType: string
}): SerializedLexicalNode[] {
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
return (
lexicalPluginNodes.map((lexicalPluginNode, i) => {
if (lexicalPluginNode.type === 'paragraph') {
return convertParagraphNode(converters, lexicalPluginNode)
}
if (lexicalPluginNode.type === 'text' || !lexicalPluginNode.type) {
return convertTextNode(lexicalPluginNode)
}
const converter = converters.find((converter) =>
converter.nodeTypes.includes(lexicalPluginNode.type),
)
if (converter) {
return converter.converter({ childIndex: i, converters, lexicalPluginNode, parentNodeType })
}
console.warn(
'lexicalPluginToLexical > No converter found for node type: ' + lexicalPluginNode.type,
)
return unknownConverter?.converter({
childIndex: i,
converters,
lexicalPluginNode,
parentNodeType,
})
}) || []
)
}
export function convertParagraphNode(
converters: LexicalPluginNodeConverter[],
node: SerializedLexicalNode,
): SerializedParagraphNode {
return {
...node,
children: convertLexicalPluginNodesToLexical({
converters,
lexicalPluginNodes: (node as any).children || [],
parentNodeType: 'paragraph',
}),
type: 'paragraph',
version: 1,
} as SerializedParagraphNode
}
export function convertTextNode(node: SerializedLexicalNode): SerializedTextNode {
return node as SerializedTextNode
}
export function convertNodeToFormat(node: SerializedLexicalNode): number {
return (node as any).format
}

View File

@@ -0,0 +1,26 @@
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
export type LexicalPluginNodeConverter<T extends SerializedLexicalNode = SerializedLexicalNode> = {
converter: ({
childIndex,
converters,
lexicalPluginNode,
parentNodeType,
}: {
childIndex: number
converters: LexicalPluginNodeConverter[]
lexicalPluginNode: SerializedLexicalNode
parentNodeType: string
}) => T
nodeTypes: string[]
}
export type PayloadPluginLexicalData = {
characters: number
comments: unknown[]
html?: string
jsonContent: SerializedEditorState
markdown?: string
preview: string
words: number
}

View File

@@ -0,0 +1,56 @@
import type { FeatureProvider } from '../../types'
import type { LexicalPluginNodeConverter, PayloadPluginLexicalData } from './converter/types'
import { convertLexicalPluginToLexical } from './converter'
import { defaultConverters } from './converter/defaultConverters'
import { UnknownConvertedNode } from './nodes/unknownConvertedNode'
type Props = {
converters?:
| (({
defaultConverters,
}: {
defaultConverters: LexicalPluginNodeConverter[]
}) => LexicalPluginNodeConverter[])
| LexicalPluginNodeConverter[]
}
export const LexicalPluginToLexicalFeature = (props?: Props): FeatureProvider => {
if (!props) {
props = {}
}
props.converters =
props?.converters && typeof props?.converters === 'function'
? props.converters({ defaultConverters: defaultConverters })
: (props?.converters as LexicalPluginNodeConverter[]) || defaultConverters
return {
feature: ({ resolvedFeatures, unsanitizedEditorConfig }) => {
return {
hooks: {
load({ incomingEditorState }) {
if (!incomingEditorState || !('jsonContent' in incomingEditorState)) {
// incomingEditorState null or not from Lexical Plugin
return incomingEditorState
}
// Lexical Plugin => convert to lexical
return convertLexicalPluginToLexical({
converters: props.converters as LexicalPluginNodeConverter[],
lexicalPluginData: incomingEditorState as unknown as PayloadPluginLexicalData,
})
},
},
nodes: [
{
node: UnknownConvertedNode,
type: UnknownConvertedNode.getType(),
},
],
props,
}
},
key: 'lexicalPluginToLexical',
}
}

View File

@@ -0,0 +1,16 @@
@import 'payload/scss';
span.unknownConverted {
text-transform: uppercase;
font-family: 'Roboto Mono', monospace;
letter-spacing: 2px;
font-size: base(0.5);
margin: 0 0 base(1);
background: red;
color: white;
display: inline-block;
div {
background: red;
}
}

View File

@@ -0,0 +1,101 @@
import type { SerializedLexicalNode, Spread } from 'lexical'
import { addClassNamesToElement } from '@lexical/utils'
import { DecoratorNode, type EditorConfig, type LexicalNode, type NodeKey } from 'lexical'
import React from 'react'
import './index.scss'
export type UnknownConvertedNodeData = {
nodeData: unknown
nodeType: string
}
export type SerializedUnknownConvertedNode = Spread<
{
data: UnknownConvertedNodeData
},
SerializedLexicalNode
>
/** @noInheritDoc */
export class UnknownConvertedNode extends DecoratorNode<JSX.Element> {
__data: UnknownConvertedNodeData
constructor({ data, key }: { data: UnknownConvertedNodeData; key?: NodeKey }) {
super(key)
this.__data = data
}
static clone(node: UnknownConvertedNode): UnknownConvertedNode {
return new UnknownConvertedNode({
data: node.__data,
key: node.__key,
})
}
static getType(): string {
return 'unknownConverted'
}
static importJSON(serializedNode: SerializedUnknownConvertedNode): UnknownConvertedNode {
const node = $createUnknownConvertedNode({ data: serializedNode.data })
return node
}
canInsertTextAfter(): true {
return true
}
canInsertTextBefore(): true {
return true
}
createDOM(config: EditorConfig): HTMLElement {
const element = document.createElement('span')
addClassNamesToElement(element, 'unknownConverted')
return element
}
decorate(): JSX.Element | null {
return (
<div>
Unknown converted payload-plugin-lexical node: <strong>{this.__data?.nodeType}</strong>
</div>
)
}
exportJSON(): SerializedUnknownConvertedNode {
return {
data: this.__data,
type: this.getType(),
version: 1,
}
}
// Mutation
isInline(): boolean {
return true
}
updateDOM(prevNode: UnknownConvertedNode, dom: HTMLElement): boolean {
return false
}
}
export function $createUnknownConvertedNode({
data,
}: {
data: UnknownConvertedNodeData
}): UnknownConvertedNode {
return new UnknownConvertedNode({
data,
})
}
export function $isUnknownConvertedNode(
node: LexicalNode | null | undefined,
): node is UnknownConvertedNode {
return node instanceof UnknownConvertedNode
}

View File

@@ -58,7 +58,11 @@ export class UnknownConvertedNode extends DecoratorNode<JSX.Element> {
}
decorate(): JSX.Element | null {
return <div>Unknown converted Slate node: {this.__data?.nodeType}</div>
return (
<div>
Unknown converted Slate node: <strong>{this.__data?.nodeType}</strong>
</div>
)
}
exportJSON(): SerializedUnknownConvertedNode {

View File

@@ -1,3 +1,4 @@
'use client'
import { ShimmerEffect } from 'payload/components'
import React, { Suspense, lazy } from 'react'

View File

@@ -17,7 +17,9 @@ import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin'
import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin'
import { LexicalContentEditable } from './ui/ContentEditable'
export const LexicalEditor: React.FC<LexicalProviderProps> = (props) => {
export const LexicalEditor: React.FC<Pick<LexicalProviderProps, 'editorConfig' | 'onChange'>> = (
props,
) => {
const { editorConfig, onChange } = props
const [editor] = useLexicalComposerContext()

View File

@@ -14,28 +14,21 @@ import { getEnabledNodes } from './nodes'
export type LexicalProviderProps = {
editorConfig: SanitizedEditorConfig
fieldProps: FieldProps
initialState: SerializedEditorState
onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void
readOnly: boolean
setValue: (value: SerializedEditorState) => void
value: SerializedEditorState
}
export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
const { editorConfig, fieldProps, onChange, readOnly, setValue } = props
let { initialState, value } = props
const { editorConfig, fieldProps, onChange, readOnly } = props
let { value } = props
// Transform initialState through load hooks
if (editorConfig?.features?.hooks?.load?.length) {
editorConfig.features.hooks.load.forEach((hook) => {
initialState = hook({ incomingEditorState: initialState })
value = hook({ incomingEditorState: value })
})
}
if (
(value && Array.isArray(value) && !('root' in value)) ||
(initialState && Array.isArray(initialState) && !('root' in initialState))
) {
if (value && Array.isArray(value) && !('root' in value)) {
throw new Error(
'You have tried to pass in data from the old, Slate editor, to the new, Lexical editor. This is not supported. There is no automatic conversion from Slate to Lexical data available yet (coming soon). Please remove the data from the field and start again.',
)
@@ -49,7 +42,7 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
const initialConfig: InitialConfigType = {
editable: readOnly === true ? false : true,
editorState: initialState != null ? JSON.stringify(initialState) : undefined,
editorState: value != null ? JSON.stringify(value) : undefined,
namespace: editorConfig.lexical.namespace,
nodes: [...getEnabledNodes({ editorConfig })],
onError: (error: Error) => {
@@ -62,15 +55,7 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
<LexicalComposer initialConfig={initialConfig}>
<EditorConfigProvider editorConfig={editorConfig} fieldProps={fieldProps}>
<div className="editor-shell">
<LexicalEditorComponent
editorConfig={editorConfig}
fieldProps={fieldProps}
initialState={initialState}
onChange={onChange}
readOnly={readOnly}
setValue={setValue}
value={value}
/>
<LexicalEditorComponent editorConfig={editorConfig} onChange={onChange} />
</div>
</EditorConfigProvider>
</LexicalComposer>

View File

@@ -2,7 +2,7 @@ import type { SerializedEditorState } from 'lexical'
import type { EditorConfig as LexicalEditorConfig } from 'lexical/LexicalEditor'
import type { RichTextAdapter } from 'payload/types'
import { withMergedProps } from 'payload/components/utilities'
import { withMergedProps } from 'payload/utilities'
import type { FeatureProvider } from './field/features/types'
import type { EditorConfig, SanitizedEditorConfig } from './field/lexical/config/types'
@@ -153,7 +153,9 @@ export { IndentFeature } from './field/features/indent'
export { CheckListFeature } from './field/features/lists/CheckList'
export { OrderedListFeature } from './field/features/lists/OrderedList'
export { UnoderedListFeature } from './field/features/lists/UnorderedList'
export { LexicalPluginToLexicalFeature } from './field/features/migrations/LexicalPluginToLexical'
export { SlateToLexicalFeature } from './field/features/migrations/SlateToLexical'
export type {
AfterReadPromise,
Feature,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "1.0.3",
"version": "1.0.5",
"description": "The officially supported Slate richtext adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",
@@ -33,6 +33,9 @@
"@types/react": "18.2.15",
"payload": "workspace:*"
},
"peerDependencies": {
"payload": "^2.0.6"
},
"exports": {
".": {
"default": "./src/index.ts",

View File

@@ -1,8 +1,8 @@
import { Drawer } from 'payload/components/elements'
import { Form, FormSubmit, RenderFields } from 'payload/components/forms'
import { fieldTypes } from 'payload/components/forms'
import { useHotkey } from 'payload/components/hooks'
import { useEditDepth } from 'payload/components/utilities'
import { fieldTypes } from 'payload/config'
import React, { useRef } from 'react'
import { useTranslation } from 'react-i18next'

View File

@@ -2,7 +2,7 @@ import type { SanitizedCollectionConfig } from 'payload/types'
import { useModal } from '@faceless-ui/modal'
import { Drawer } from 'payload/components/elements'
import { Form, FormSubmit, RenderFields } from 'payload/components/forms'
import { Form, FormSubmit, RenderFields, fieldTypes } from 'payload/components/forms'
import {
buildStateFromSchema,
useAuth,
@@ -10,7 +10,7 @@ import {
useDocumentInfo,
useLocale,
} from 'payload/components/utilities'
import { fieldTypes, sanitizeFields } from 'payload/config'
import { sanitizeFields } from 'payload/config'
import { deepCopyObject, getTranslation } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'

View File

@@ -1,6 +1,6 @@
import type { RichTextAdapter } from 'payload/types'
import { withMergedProps } from 'payload/components/utilities'
import { withMergedProps } from 'payload/utilities'
import type { AdapterArguments } from './types'

14
pnpm-lock.yaml generated
View File

@@ -96,6 +96,9 @@ importers:
glob:
specifier: 8.1.0
version: 8.1.0
graphql-request:
specifier: 6.1.0
version: 6.1.0(graphql@16.8.1)
husky:
specifier: ^8.0.3
version: 8.0.3
@@ -418,9 +421,6 @@ importers:
'@libsql/client':
specifier: ^0.3.1
version: 0.3.4
better-sqlite3:
specifier: ^8.5.0
version: 8.6.0
console-table-printer:
specifier: 2.11.2
version: 2.11.2
@@ -429,7 +429,7 @@ importers:
version: 0.19.13-e99bac1
drizzle-orm:
specifier: 0.28.5
version: 0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(better-sqlite3@8.6.0)(pg@8.11.3)
version: 0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(pg@8.11.3)
pg:
specifier: 8.11.3
version: 8.11.3
@@ -943,9 +943,6 @@ importers:
get-port:
specifier: 5.1.1
version: 5.1.1
graphql-request:
specifier: 6.1.0
version: 6.1.0(graphql@16.8.1)
mini-css-extract-plugin:
specifier: 1.6.2
version: 1.6.2(webpack@5.88.2)
@@ -8447,7 +8444,7 @@ packages:
- supports-color
dev: false
/drizzle-orm@0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(better-sqlite3@8.6.0)(pg@8.11.3):
/drizzle-orm@0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(pg@8.11.3):
resolution: {integrity: sha512-6r6Iw4c38NAmW6TiKH3TUpGUQ1YdlEoLJOQptn8XPx3Z63+vFNKfAiANqrIiYZiMjKR9+NYAL219nFrmo1duXA==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
@@ -8511,7 +8508,6 @@ packages:
dependencies:
'@libsql/client': 0.3.4
'@types/pg': 8.10.2
better-sqlite3: 8.6.0
pg: 8.11.3
dev: false

View File

@@ -0,0 +1,79 @@
import path from 'path'
import fse from 'fs-extra'
import chalk from 'chalk'
import chalkTemplate from 'chalk-template'
import simpleGit from 'simple-git'
const git = simpleGit()
const packagesDir = path.resolve(__dirname, '../../packages')
export type PackageDetails = {
name: string
newCommits: number
shortName: string
packagePath: string
publishedVersion: string
publishDate: string
version: string
}
export const getPackageDetails = async (): Promise<PackageDetails[]> => {
const packageDirs = fse.readdirSync(packagesDir).filter((d) => d !== 'eslint-config-payload')
const packageDetails = await Promise.all(
packageDirs.map(async (dirName) => {
const packageJson = await fse.readJson(`${packagesDir}/${dirName}/package.json`)
const isPublic = packageJson.private !== true
if (!isPublic) return null
// Get published version from npm
const json = await fetch(`https://registry.npmjs.org/${packageJson.name}`).then((res) =>
res.json(),
)
const publishedVersion = json?.['dist-tags']?.latest
const publishDate = json?.time?.[publishedVersion]
const prevGitTag = `${dirName}/${packageJson.version}`
const prevGitTagHash = await git.revparse(prevGitTag)
const newCommits = await git.log({
from: prevGitTagHash,
file: `packages/${dirName}`,
})
return {
name: packageJson.name as string,
newCommits: newCommits.total,
shortName: dirName,
packagePath: `packages/${dirName}`,
publishedVersion,
publishDate,
version: packageJson.version,
}
}),
)
return packageDetails.filter((p): p is Exclude<typeof p, null> => p !== null)
}
export const showPackageDetails = (details: PackageDetails[]) => {
console.log(chalkTemplate`
{bold Packages:}
${details
.map((p) => {
const name = p?.newCommits ? chalk.bold.green(p?.shortName.padEnd(28)) : p?.shortName.padEnd(28)
const publishData = `${p?.publishedVersion} at ${p?.publishDate
.split(':')
.slice(0, 2)
.join(':')
.replace('T', ' ')}`
const newCommits = `${p?.newCommits ? `${chalk.bold.green(p?.newCommits)} new commits` : ''}`
return ` ${name}${publishData} ${newCommits}`
})
.join('\n')}
`)
}

View File

@@ -1,74 +1,10 @@
import path from 'path'
import fse from 'fs-extra'
import chalk from 'chalk'
import chalkTemplate from 'chalk-template'
import simpleGit from 'simple-git'
const git = simpleGit()
const packagesDir = path.resolve(__dirname, '../packages')
import { getPackageDetails, showPackageDetails } from './lib/getPackageDetails'
async function main() {
// List all public packages excluding eslint-config-payload
const packageDirs = fse.readdirSync(packagesDir).filter((d) => d !== 'eslint-config-payload')
const packageDetails = await Promise.all(
packageDirs.map(async (dirName) => {
const packageJson = await fse.readJson(`${packagesDir}/${dirName}/package.json`)
const isPublic = packageJson.private !== true
if (!isPublic) return null
// Get published version from npm
const json = await fetch(`https://registry.npmjs.org/${packageJson.name}`).then((res) =>
res.json(),
)
const publishedVersion = json?.['dist-tags']?.latest
const publishDate = json?.time?.[publishedVersion]
const prevGitTag = `${dirName}/${packageJson.version}`
const prevGitTagHash = await git.revparse(prevGitTag)
const newCommits = await git.log({
from: prevGitTagHash,
file: `packages/${dirName}`,
})
return {
name: packageJson.name,
newCommits: newCommits.total,
packageDir: dirName,
packagePath: `packages/${dirName}`,
publishedVersion,
publishDate,
version: packageJson.version,
}
}),
)
console.log(chalkTemplate`
{bold Packages:}
${packageDetails
.map((p) => {
const name = p?.newCommits
? chalk.bold.green(p?.packageDir.padEnd(28))
: p?.packageDir.padEnd(28)
const publishData = `${p?.publishedVersion} at ${p?.publishDate
.split(':')
.slice(0, 2)
.join(':')
.replace('T', ' ')}`
const newCommits = `${p?.newCommits ? `${chalk.bold.green(p?.newCommits)} new commits` : ''}`
return ` ${name}${publishData} ${newCommits}`
})
.join('\n')}
`)
const packageDetails = await getPackageDetails()
showPackageDetails(packageDetails)
}
// console.log(packageNames)
main().catch((error) => {
console.error(error)
process.exit(1)

View File

@@ -4,32 +4,49 @@ import chalk from 'chalk'
import prompts from 'prompts'
import minimist from 'minimist'
import chalkTemplate from 'chalk-template'
import { PackageDetails, getPackageDetails, showPackageDetails } from './lib/getPackageDetails'
const execOpts: ExecSyncOptions = { stdio: 'inherit' }
const args = minimist(process.argv.slice(2))
async function main() {
const { _: packageNames, tag = 'latest', bump = 'patch' } = args
const { tag = 'latest', bump = 'patch' } = args
if (packageNames.length === 0) {
const packageDetails = await getPackageDetails()
showPackageDetails(packageDetails)
const { packagesToRelease } = (await prompts({
type: 'multiselect',
name: 'packagesToRelease',
message: 'Select packages to release',
instructions: 'Space to select. Enter to submit.',
choices: packageDetails.map((p) => {
const title = p?.newCommits ? chalk.bold.green(p?.shortName) : p?.shortName
return {
title,
value: p.shortName,
}
}),
})) as { packagesToRelease: string[] }
if (!packagesToRelease) {
abort()
}
if (packagesToRelease.length === 0) {
abort('Please specify a package to publish')
}
if (packageNames.find((p) => p === 'payload' && packageNames.length > 1)) {
abort('Cannot publish payload with other packages')
if (packagesToRelease.find((p) => p === 'payload' && packagesToRelease.length > 1)) {
abort('Cannot publish payload with other packages. Release Payload first.')
}
// Get current version of each package from package.json
const packageDetails = await Promise.all(
packageNames.map(async (packageName) => {
const packageDir = `packages/${packageName}`
if (!(await fse.pathExists(packageDir))) {
abort(`Package path ${packageDir} does not exist`)
}
const packageObj = await fse.readJson(`${packageDir}/package.json`)
return { name: packageName, version: packageObj.version, dir: packageDir }
}),
const packageMap = packageDetails.reduce(
(acc, p) => {
acc[p.shortName] = p
return acc
},
{} as Record<string, PackageDetails>,
)
console.log(chalkTemplate`
@@ -38,10 +55,15 @@ async function main() {
{bold.yellow Bump: ${bump}}
{bold.yellow Tag: ${tag}}
${packageDetails.map((p) => ` ${p.name} - current: ${p.version}`).join('\n')}
${packagesToRelease
.map((p) => {
const { shortName, version } = packageMap[p]
return ` ${shortName.padEnd(24)} ${version}`
})
.join('\n')}
`)
const confirmPublish = await confirm(`Publish ${packageNames.length} package(s)?`)
const confirmPublish = await confirm(`Publish ${packagesToRelease.length} package(s)?`)
if (!confirmPublish) {
abort()
@@ -49,26 +71,26 @@ ${packageDetails.map((p) => ` ${p.name} - current: ${p.version}`).join('\n')}
const results: { name: string; success: boolean }[] = []
for (const pkg of packageDetails) {
const { dir, name } = pkg
for (const pkg of packagesToRelease) {
const { packagePath, shortName } = packageMap[pkg]
try {
console.log(chalk.bold(`\n\nPublishing ${name}...\n\n`))
console.log(chalk.bold(`\n\nPublishing ${shortName}...\n\n`))
execSync(`npm --no-git-tag-version --prefix ${dir} version ${bump}`, execOpts)
execSync(`git add ${dir}/package.json`, execOpts)
execSync(`npm --no-git-tag-version --prefix ${packagePath} version ${bump}`, execOpts)
execSync(`git add ${packagePath}/package.json`, execOpts)
const packageObj = await fse.readJson(`${dir}/package.json`)
const packageObj = await fse.readJson(`${packagePath}/package.json`)
const newVersion = packageObj.version
const tagName = `${name}/${newVersion}`
const tagName = `${shortName}/${newVersion}`
execSync(`git commit -m "chore(release): ${tagName}"`, execOpts)
execSync(`git tag -a ${tagName} -m "${tagName}"`, execOpts)
execSync(`pnpm publish -C ${dir} --no-git-checks`, execOpts)
results.push({ name, success: true })
execSync(`pnpm publish -C ${packagePath} --no-git-checks`, execOpts)
results.push({ name: shortName, success: true })
} catch (error) {
console.error(chalk.bold.red(`ERROR: ${error.message}`))
results.push({ name, success: false })
results.push({ name: shortName, success: false })
}
}

View File

@@ -0,0 +1,958 @@
export const payloadPluginLexicalData = {
words: 49,
preview:
'paragraph text bold italic underline and all subscript superscript code internal link external link…',
comments: [],
characters: 493,
jsonContent: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'paragraph text ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 1,
mode: 'normal',
style: '',
text: 'bold',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 2,
mode: 'normal',
style: '',
text: 'italic',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 8,
mode: 'normal',
style: '',
text: 'underline',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' and ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 11,
mode: 'normal',
style: '',
text: 'all',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 32,
mode: 'normal',
style: '',
text: 'subscript',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 64,
mode: 'normal',
style: '',
text: 'superscript',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 16,
mode: 'normal',
style: '',
text: 'code',
type: 'text',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'internal link',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'link',
version: 2,
attributes: {
newTab: true,
linkType: 'internal',
doc: {
value: '{{TEXT_DOC_ID}}',
relationTo: 'text-fields',
data: {}, // populated data
},
text: 'internal link',
},
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' ',
type: 'text',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'external link',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'link',
version: 2,
attributes: {
newTab: true,
nofollow: false,
url: 'https://fewfwef.de',
linkType: 'custom',
text: 'external link',
},
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: ' s. ',
type: 'text',
version: 1,
},
{
detail: 0,
format: 4,
mode: 'normal',
style: '',
text: 'strikethrough',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'heading 1',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'heading',
version: 1,
tag: 'h1',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'heading 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'heading',
version: 1,
tag: 'h2',
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'bullet list ',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 2,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 3',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 3,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'list',
version: 1,
listType: 'bullet',
start: 1,
tag: 'ul',
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'ordered list',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 2,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 3',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 3,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'list',
version: 1,
listType: 'number',
start: 1,
tag: 'ol',
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'check list',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 2',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 2,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'item 3',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 3,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'list',
version: 1,
listType: 'check',
start: 1,
tag: 'ul',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'quoteeee',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'quote',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'code block line ',
type: 'code-highlight',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '1',
type: 'code-highlight',
version: 1,
highlightType: 'number',
},
{
type: 'linebreak',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'code block line ',
type: 'code-highlight',
version: 1,
},
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2',
type: 'code-highlight',
version: 1,
highlightType: 'number',
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'code',
version: 1,
language: 'javascript',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Upload:',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
type: 'upload',
version: 1,
rawImagePayload: {
value: {
id: '{{UPLOAD_DOC_ID}}',
},
relationTo: 'uploads',
},
caption: {
editorState: {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'upload caption',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'root',
version: 1,
},
},
},
showCaption: true,
data: {}, // populated upload data
},
],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
children: [
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table top left',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 3,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table top right',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablerow',
version: 1,
},
{
children: [
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table bottom left',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 2,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '2x2 table bottom right',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablecell',
version: 1,
colSpan: 1,
rowSpan: 1,
backgroundColor: null,
headerState: 0,
},
],
direction: null,
format: '',
indent: 0,
type: 'tablerow',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'table',
version: 1,
},
{
rows: [
{
cells: [
{
colSpan: 1,
id: 'kafuj',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
{
colSpan: 1,
id: 'iussu',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
],
height: null,
id: 'tnied',
},
{
cells: [
{
colSpan: 1,
id: 'hpnnv',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
{
colSpan: 1,
id: 'ndteg',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'normal',
width: null,
},
],
height: null,
id: 'rxyey',
},
{
cells: [
{
colSpan: 1,
id: 'rtueq',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'header',
width: null,
},
{
colSpan: 1,
id: 'vrzoi',
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
type: 'normal',
width: null,
},
],
height: null,
id: 'qzglv',
},
],
type: 'tablesheet',
version: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'youtube:',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
format: '',
type: 'youtube',
version: 1,
videoID: '3Nwt3qu0_UY',
},
{
children: [
{
equation: '3+3',
inline: true,
type: 'equation',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'collapsible title',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'collapsible-title',
version: 1,
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'collabsible conteent',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'collapsible-content',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'collapsible-container',
version: 1,
open: true,
},
{
children: [],
direction: null,
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
{
type: 'horizontalrule',
version: 1,
},
],
direction: 'ltr',
},
},
}

View File

@@ -2,6 +2,7 @@ import type { CollectionConfig } from '../../../../packages/payload/src/collecti
import {
BlocksFeature,
LexicalPluginToLexicalFeature,
LinkFeature,
TreeviewFeature,
UploadFeature,
@@ -16,6 +17,7 @@ import {
UploadAndRichTextBlock,
} from './blocks'
import { generateLexicalRichText } from './generateLexicalRichText'
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
export const LexicalFields: CollectionConfig = {
slug: 'lexical-fields',
@@ -32,6 +34,45 @@ export const LexicalFields: CollectionConfig = {
type: 'text',
required: true,
},
{
name: 'richTextLexicalWithLexicalPluginData',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
LexicalPluginToLexicalFeature(),
TreeviewFeature(),
LinkFeature({
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: ['noopener', 'noreferrer', 'nofollow'],
admin: {
description:
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
},
},
],
}),
UploadFeature({
collections: {
uploads: {
fields: [
{
name: 'caption',
type: 'richText',
editor: lexicalEditor(),
},
],
},
},
}),
],
}),
},
{
name: 'richTextLexicalCustomFields',
type: 'richText',
@@ -87,4 +128,5 @@ export const LexicalFields: CollectionConfig = {
export const LexicalRichTextDoc = {
title: 'Rich Text',
richTextLexicalCustomFields: generateLexicalRichText(),
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
}

View File

@@ -3,63 +3,81 @@ import type { CollectionConfig } from '../../../../packages/payload/src/collecti
export const relationshipFieldsSlug = 'relationship-fields'
const RelationshipFields: CollectionConfig = {
slug: relationshipFieldsSlug,
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'relationship',
type: 'relationship',
relationTo: ['text-fields', 'array-fields'],
required: true,
type: 'relationship',
},
{
name: 'relationToSelf',
type: 'relationship',
relationTo: relationshipFieldsSlug,
type: 'relationship',
},
{
name: 'relationToSelfSelectOnly',
type: 'relationship',
relationTo: relationshipFieldsSlug,
admin: {
allowCreate: false,
},
relationTo: relationshipFieldsSlug,
type: 'relationship',
},
{
name: 'relationWithDynamicDefault',
type: 'relationship',
defaultValue: ({ user }) => user?.id,
relationTo: 'users',
defaultValue: ({ user }) => user.id,
type: 'relationship',
},
{
name: 'relationHasManyWithDynamicDefault',
type: 'relationship',
defaultValue: ({ user }) =>
user
? {
relationTo: 'users',
value: user.id,
}
: undefined,
relationTo: ['users'],
defaultValue: ({ user }) => ({
relationTo: 'users',
value: user.id,
}),
type: 'relationship',
},
{
name: 'relationshipWithMin',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true,
minRows: 2,
relationTo: 'text-fields',
type: 'relationship',
},
{
name: 'relationshipWithMax',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true,
maxRows: 2,
relationTo: 'text-fields',
type: 'relationship',
},
{
name: 'relationshipHasMany',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true,
relationTo: 'text-fields',
type: 'relationship',
},
{
name: 'array',
fields: [
{
name: 'relationship',
relationTo: 'text-fields',
type: 'relationship',
},
],
type: 'array',
},
],
slug: relationshipFieldsSlug,
}
export default RelationshipFields

View File

@@ -44,18 +44,18 @@ export default buildConfigWithDefaults({
collections: [
LexicalFields,
{
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
{
name: 'canViewConditionalField',
type: 'checkbox',
defaultValue: true,
type: 'checkbox',
},
],
slug: 'users',
},
ArrayFields,
BlockFields,
@@ -81,8 +81,8 @@ export default buildConfigWithDefaults({
],
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
fallback: true,
locales: ['en', 'es'],
},
onInit: async (payload) => {
await payload.create({

View File

@@ -1093,7 +1093,7 @@ describe('fields', () => {
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click()
const textField = page.locator('#field-text')
const textField = page.locator('.drawer__content #field-text')
const textValue = 'hello'
await textField.fill(textValue)
@@ -1217,7 +1217,7 @@ describe('fields', () => {
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click()
await page.locator('#field-text').fill('something')
await page.locator('.drawer__content #field-text').fill('something')
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
await expect(page.locator('.Toastify')).toContainText('successfully')
@@ -1290,7 +1290,7 @@ describe('fields', () => {
await page.getByRole('button', { name: 'Edit Seeded text document' }).click()
// Fill 'text' field of 'Seeded text document'
await page.locator('#field-text').fill('some updated text value')
await page.locator('.drawer__content #field-text').fill('some updated text value')
// Save drawer (not parent page) with hotkey
await saveDocHotkeyAndAssert(page)

View File

@@ -21,6 +21,7 @@ import {
} from './collections/Group'
import { defaultNumber, numberDoc } from './collections/Number'
import { pointDoc } from './collections/Point'
import { relationshipFieldsSlug } from './collections/Relationship'
import { tabsDoc } from './collections/Tabs'
import {
localizedTextValue,
@@ -73,6 +74,122 @@ describe('Fields', () => {
})
})
describe('relationship', () => {
let textDoc
let otherTextDoc
let selfReferencing
let parent
let child
let grandChild
let relationshipInArray
const textDocText = 'text document'
const otherTextDocText = 'alt text'
const relationshipText = 'relationship text'
beforeAll(async () => {
textDoc = await payload.create({
collection: 'text-fields',
data: {
text: textDocText,
},
})
otherTextDoc = await payload.create({
collection: 'text-fields',
data: {
text: otherTextDocText,
},
})
const relationship = { relationTo: 'text-fields', value: textDoc.id }
parent = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationship,
text: relationshipText,
},
})
child = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationToSelf: parent.id,
relationship,
text: relationshipText,
},
})
grandChild = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationToSelf: child.id,
relationship,
text: relationshipText,
},
})
selfReferencing = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationship,
text: relationshipText,
},
})
relationshipInArray = await payload.create({
collection: relationshipFieldsSlug,
data: {
array: [
{
relationship: otherTextDoc.id,
},
],
relationship,
},
})
})
it('should query parent self-reference', async () => {
const childResult = await payload.find({
collection: relationshipFieldsSlug,
where: {
relationToSelf: { equals: parent.id },
},
})
const grandChildResult = await payload.find({
collection: relationshipFieldsSlug,
where: {
relationToSelf: { equals: child.id },
},
})
const anyChildren = await payload.find({
collection: relationshipFieldsSlug,
})
const allChildren = await payload.find({
collection: relationshipFieldsSlug,
where: {
'relationToSelf.text': { equals: relationshipText },
},
})
expect(childResult.docs[0].id).toStrictEqual(child.id)
expect(grandChildResult.docs[0].id).toStrictEqual(grandChild.id)
expect(allChildren.docs).toHaveLength(2)
})
it('should query relationship inside array', async () => {
const result = await payload.find({
collection: relationshipFieldsSlug,
where: {
'array.relationship.text': { equals: otherTextDocText },
},
})
expect(result.docs).toHaveLength(1)
expect(result.docs[0]).toMatchObject(relationshipInArray)
})
})
describe('timestamps', () => {
const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10)
let doc
@@ -796,4 +913,19 @@ describe('Fields', () => {
expect(uploadElement.value.media.filename).toStrictEqual('payload.png')
})
})
describe('relationships', () => {
it('should not crash if querying with empty in operator', async () => {
const query = await payload.find({
collection: 'relationship-fields',
where: {
'relationship.value': {
in: [],
},
},
})
expect(query.docs).toBeDefined()
})
})
})