feat(richtext-lexical): relationships

This commit is contained in:
Alessio Gravili
2024-03-01 14:07:12 -05:00
parent a216af40dc
commit a3ffb80344
11 changed files with 151 additions and 101 deletions

View File

@@ -68,11 +68,11 @@ const RelationshipDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }
}, [editor, openDrawer])
const onSelect = useCallback(
({ collectionConfig, docID }) => {
({ collectionSlug, docID }) => {
insertRelationship({
id: docID,
editor,
relationTo: collectionConfig.slug,
relationTo: collectionSlug,
replaceNodeKey,
})
closeDrawer()

View File

@@ -0,0 +1,57 @@
'use client'
import { withMergedProps } from '@payloadcms/ui'
import type { FeatureProviderProviderClient } from '../types'
import type { RelationshipFeatureProps } from './feature.server'
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types'
import { RelationshipIcon } from '../../lexical/ui/icons/Relationship'
import { createClientComponent } from '../createClientComponent'
import { INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND } from './drawer/commands'
import { RelationshipNode } from './nodes/RelationshipNode'
import { RelationshipPlugin } from './plugins'
const RelationshipFeatureClient: FeatureProviderProviderClient<RelationshipFeatureProps> = (
props,
) => {
return {
clientFeatureProps: props,
feature: () => ({
clientFeatureProps: props,
nodes: [RelationshipNode],
plugins: [
{
Component: withMergedProps({
Component: RelationshipPlugin,
toMergeIntoProps: props,
}),
position: 'normal',
},
],
slashMenu: {
options: [
{
displayName: 'Basic',
key: 'basic',
options: [
new SlashMenuOption('relationship', {
Icon: RelationshipIcon,
displayName: 'Relationship',
keywords: ['relationship', 'relation', 'rel'],
onSelect: ({ editor }) => {
// dispatch INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND
editor.dispatchCommand(INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND, {
replace: false,
})
},
}),
],
},
],
},
}),
}
}
export const RelationshipFeatureClientComponent = createClientComponent(RelationshipFeatureClient)

View File

@@ -0,0 +1,51 @@
import type { FeatureProviderProviderServer } from '../types'
import { RelationshipFeatureClientComponent } from './feature.client'
import { RelationshipNode } from './nodes/RelationshipNode'
import { relationshipPopulationPromise } from './populationPromise'
export type RelationshipFeatureProps =
| {
/**
* The collections that should be disabled. Overrides the `enableRichTextRelationship` property in the collection config.
* When this property is set, `enabledCollections` will not be available.
**/
disabledCollections?: string[]
// Ensures that enabledCollections is not available when disabledCollections is set
enabledCollections?: never
}
| {
// Ensures that disabledCollections is not available when enabledCollections is set
disabledCollections?: never
/**
* The collections that should be enabled. Overrides the `enableRichTextRelationship` property in the collection config
* When this property is set, `disabledCollections` will not be available.
**/
enabledCollections?: string[]
}
export const RelationshipFeature: FeatureProviderProviderServer<
RelationshipFeatureProps,
RelationshipFeatureProps
> = (props) => {
return {
feature: () => {
return {
ClientComponent: RelationshipFeatureClientComponent,
clientFeatureProps: props,
nodes: [
{
node: RelationshipNode,
populationPromises: [relationshipPopulationPromise],
// TODO: Add validation similar to upload
},
],
serverFeatureProps: props,
}
},
key: 'relationship',
serverFeatureProps: props,
}
}

View File

@@ -1,88 +0,0 @@
import type { FeatureProvider } from '../types'
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types'
import { INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND } from './drawer/commands'
import { RelationshipNode } from './nodes/RelationshipNode'
import { relationshipPopulationPromise } from './populationPromise'
export type RelationshipFeatureProps =
| {
/**
* The collections that should be disabled. Overrides the `enableRichTextRelationship` property in the collection config.
* When this property is set, `enabledCollections` will not be available.
**/
disabledCollections?: string[]
// Ensures that enabledCollections is not available when disabledCollections is set
enabledCollections?: never
}
| {
// Ensures that disabledCollections is not available when enabledCollections is set
disabledCollections?: never
/**
* The collections that should be enabled. Overrides the `enableRichTextRelationship` property in the collection config
* When this property is set, `disabledCollections` will not be available.
**/
enabledCollections?: string[]
}
export const RelationshipFeature = (props?: RelationshipFeatureProps): FeatureProvider => {
return {
feature: () => {
return {
nodes: [
{
type: RelationshipNode.getType(),
node: RelationshipNode,
populationPromises: [relationshipPopulationPromise],
// TODO: Add validation similar to upload
},
],
plugins: [
{
Component: () =>
// @ts-expect-error-next-line
import('./plugins').then((module) => {
const RelationshipPlugin = module.RelationshipPlugin
return import('@payloadcms/ui').then((module2) =>
module2.withMergedProps({
Component: RelationshipPlugin,
toMergeIntoProps: props,
}),
)
}),
position: 'normal',
},
],
props: props,
slashMenu: {
options: [
{
displayName: 'Basic',
key: 'basic',
options: [
new SlashMenuOption('relationship', {
Icon: () =>
// @ts-expect-error-next-line
import('../../lexical/ui/icons/Relationship').then(
(module) => module.RelationshipIcon,
),
displayName: 'Relationship',
keywords: ['relationship', 'relation', 'rel'],
onSelect: ({ editor }) => {
// dispatch INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND
editor.dispatchCommand(INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND, {
replace: false,
})
},
}),
],
},
],
},
}
},
key: 'relationship',
}
}

View File

@@ -57,7 +57,7 @@ const Component: React.FC<Props> = (props) => {
)
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
id: id,
id,
collectionSlug: relatedCollection.slug,
})
@@ -93,7 +93,7 @@ const Component: React.FC<Props> = (props) => {
</p>
<DocumentDrawerToggler className={`${baseClass}__doc-drawer-toggler`}>
<p className={`${baseClass}__title`}>
{data[relatedCollection?.admin?.useAsTitle || 'id']}
{data ? data[relatedCollection?.admin?.useAsTitle || 'id'] : id}
</p>
</DocumentDrawerToggler>
</div>
@@ -102,7 +102,7 @@ const Component: React.FC<Props> = (props) => {
<Button
buttonStyle="icon-label"
className={`${baseClass}__swapButton`}
disabled={field?.admin?.readOnly}
disabled={field?.readOnly}
el="div"
icon="swap"
onClick={() => {
@@ -116,7 +116,7 @@ const Component: React.FC<Props> = (props) => {
<Button
buttonStyle="icon-label"
className={`${baseClass}__removeButton`}
disabled={field?.admin?.readOnly}
disabled={field?.readOnly}
icon="x"
onClick={(e) => {
e.preventDefault()

View File

@@ -14,7 +14,7 @@ import {
import { useEffect } from 'react'
import React from 'react'
import type { RelationshipFeatureProps } from '../index'
import type { RelationshipFeatureProps } from '../feature.server'
import type { RelationshipData } from '../nodes/RelationshipNode'
import { RelationshipDrawer } from '../drawer'
@@ -24,7 +24,7 @@ export const INSERT_RELATIONSHIP_COMMAND: LexicalCommand<RelationshipData> = cre
'INSERT_RELATIONSHIP_COMMAND',
)
export function RelationshipPlugin(props?: RelationshipFeatureProps): JSX.Element | null {
export function RelationshipPlugin(props?: RelationshipFeatureProps): React.ReactNode {
const [editor] = useLexicalComposerContext()
const { collections } = useConfig()

View File

@@ -19,7 +19,7 @@ import { CheckListFeature } from '../../../features/lists/checklist/feature.serv
import { OrderedListFeature } from '../../../features/lists/orderedlist/feature.server'
import { UnorderedListFeature } from '../../../features/lists/unorderedlist/feature.server'
import { ParagraphFeature } from '../../../features/paragraph/feature.server'
import { RelationshipFeature } from '../../../features/relationship'
import { RelationshipFeature } from '../../../features/relationship/feature.server'
import { UploadFeature } from '../../../features/upload'
import { LexicalEditorTheme } from '../../theme/EditorTheme'
import { sanitizeServerEditorConfig } from './sanitize'

View File

@@ -319,7 +319,7 @@ export type {
} from './field/features/migrations/slateToLexical/converter/types'
export { SlateToLexicalFeature } from './field/features/migrations/slateToLexical/feature.server'
export { ParagraphFeature } from './field/features/paragraph/feature.server'
export { RelationshipFeature } from './field/features/relationship'
export { RelationshipFeature } from './field/features/relationship/feature.server'
export {
$createRelationshipNode,
$isRelationshipNode,

31
pnpm-lock.yaml generated
View File

@@ -668,9 +668,6 @@ importers:
scmp:
specifier: 2.1.0
version: 2.1.0
sharp:
specifier: 0.33.2
version: 0.33.2
devDependencies:
'@monaco-editor/react':
specifier: 4.5.1
@@ -804,6 +801,9 @@ importers:
serve-static:
specifier: 1.15.0
version: 1.15.0
sharp:
specifier: 0.33.2
version: 0.33.2
ts-essentials:
specifier: 7.0.3
version: 7.0.3(typescript@5.2.2)
@@ -2581,6 +2581,7 @@ packages:
requiresBuild: true
dependencies:
tslib: 2.6.2
dev: true
optional: true
/@emotion/babel-plugin@11.11.0:
@@ -3280,6 +3281,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.1
dev: true
optional: true
/@img/sharp-darwin-x64@0.33.2:
@@ -3290,6 +3292,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.0.1
dev: true
optional: true
/@img/sharp-libvips-darwin-arm64@1.0.1:
@@ -3298,6 +3301,7 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@img/sharp-libvips-darwin-x64@1.0.1:
@@ -3306,6 +3310,7 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@img/sharp-libvips-linux-arm64@1.0.1:
@@ -3314,6 +3319,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@img/sharp-libvips-linux-arm@1.0.1:
@@ -3322,6 +3328,7 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@img/sharp-libvips-linux-s390x@1.0.1:
@@ -3330,6 +3337,7 @@ packages:
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@img/sharp-libvips-linux-x64@1.0.1:
@@ -3338,6 +3346,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@img/sharp-libvips-linuxmusl-arm64@1.0.1:
@@ -3346,6 +3355,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@img/sharp-libvips-linuxmusl-x64@1.0.1:
@@ -3354,6 +3364,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@img/sharp-linux-arm64@0.33.2:
@@ -3364,6 +3375,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.0.1
dev: true
optional: true
/@img/sharp-linux-arm@0.33.2:
@@ -3374,6 +3386,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.0.1
dev: true
optional: true
/@img/sharp-linux-s390x@0.33.2:
@@ -3384,6 +3397,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.0.1
dev: true
optional: true
/@img/sharp-linux-x64@0.33.2:
@@ -3394,6 +3408,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.0.1
dev: true
optional: true
/@img/sharp-linuxmusl-arm64@0.33.2:
@@ -3404,6 +3419,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.1
dev: true
optional: true
/@img/sharp-linuxmusl-x64@0.33.2:
@@ -3414,6 +3430,7 @@ packages:
requiresBuild: true
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.0.1
dev: true
optional: true
/@img/sharp-wasm32@0.33.2:
@@ -3423,6 +3440,7 @@ packages:
requiresBuild: true
dependencies:
'@emnapi/runtime': 0.45.0
dev: true
optional: true
/@img/sharp-win32-ia32@0.33.2:
@@ -3431,6 +3449,7 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@img/sharp-win32-x64@0.33.2:
@@ -3439,6 +3458,7 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@isaacs/cliui@8.0.2:
@@ -7309,6 +7329,7 @@ packages:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
dev: true
/color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
@@ -7316,6 +7337,7 @@ packages:
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
dev: true
/colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
@@ -10359,6 +10381,7 @@ packages:
/is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
dev: true
/is-async-function@2.0.0:
resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==}
@@ -14328,6 +14351,7 @@ packages:
'@img/sharp-wasm32': 0.33.2
'@img/sharp-win32-ia32': 0.33.2
'@img/sharp-win32-x64': 0.33.2
dev: true
/shebang-command@1.2.0:
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
@@ -14394,6 +14418,7 @@ packages:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
dependencies:
is-arrayish: 0.3.2
dev: true
/simple-update-notifier@2.0.0:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}

View File

@@ -5,6 +5,9 @@ import { mediaSlug } from '../Media'
export const postsSlug = 'posts'
export const PostsCollection: CollectionConfig = {
admin: {
useAsTitle: 'text',
},
fields: [
{
name: 'text',

View File

@@ -10,6 +10,7 @@ import {
ItalicFeature,
LinkFeature,
OrderedListFeature,
RelationshipFeature,
StrikethroughFeature,
SubscriptFeature,
SuperscriptFeature,
@@ -95,6 +96,7 @@ export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<S
editor: lexicalEditor({
features: [
ParagraphFeature(),
RelationshipFeature(),
LinkFeature(),
CheckListFeature(),
UnorderedListFeature(),