Compare commits

..

28 Commits

Author SHA1 Message Date
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
60 changed files with 5513 additions and 3182 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

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.1.5",
"version": "0.1.7",
"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

@@ -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)) {

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.6",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",

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'

8
pnpm-lock.yaml generated
View File

@@ -418,9 +418,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 +426,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
@@ -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,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